Skip to content
Snippets Groups Projects
Commit dba86447 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@12-8-stable-ee

parent b7d29500
No related branches found
No related tags found
No related merge requests found
Showing
with 383 additions and 116 deletions
12.7.6-ee
12.7.0-pre
<script>
import { GlButton, GlFormGroup, GlFormInput, GlModal, GlModalDirective } from '@gitlab/ui';
import _ from 'underscore';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ToggleButton from '~/vue_shared/components/toggle_button.vue';
import axios from '~/lib/utils/axios_utils';
import { s__, __, sprintf } from '~/locale';
import createFlash from '~/flash';
export default {
COPY_TO_CLIPBOARD: __('Copy'),
RESET_KEY: __('Reset key'),
components: {
GlButton,
GlFormGroup,
GlFormInput,
GlModal,
ClipboardButton,
ToggleButton,
},
directives: {
'gl-modal': GlModalDirective,
},
props: {
initialAuthorizationKey: {
type: String,
required: false,
default: '',
},
formPath: {
type: String,
required: true,
},
url: {
type: String,
required: true,
},
learnMoreUrl: {
type: String,
required: false,
default: '',
},
initialActivated: {
type: Boolean,
required: true,
},
},
data() {
return {
activated: this.initialActivated,
loadingActivated: false,
authorizationKey: this.initialAuthorizationKey,
};
},
computed: {
learnMoreDescription() {
return sprintf(
s__(
'AlertService|%{linkStart}Learn more%{linkEnd} about configuring this endpoint to receive alerts.',
),
{
linkStart: `<a href="${_.escape(
this.learnMoreUrl,
)}" target="_blank" rel="noopener noreferrer">`,
linkEnd: '</a>',
},
false,
);
},
sectionDescription() {
const desc = s__(
'AlertService|Each alert source must be authorized using the following URL and authorization key.',
);
const learnMoreDesc = this.learnMoreDescription ? ` ${this.learnMoreDescription}` : '';
return `${desc}${learnMoreDesc}`;
},
},
watch: {
activated() {
this.updateIcon();
},
},
methods: {
updateIcon() {
return document.querySelectorAll('.js-service-active-status').forEach(icon => {
if (icon.dataset.value === this.activated.toString()) {
icon.classList.remove('d-none');
} else {
icon.classList.add('d-none');
}
});
},
resetKey() {
return axios
.put(this.formPath, { service: { token: '' } })
.then(res => {
this.authorizationKey = res.data.token;
})
.catch(() => {
createFlash(__('Failed to reset key. Please try again.'));
});
},
toggleActivated(value) {
this.loadingActivated = true;
return axios
.put(this.formPath, { service: { active: value } })
.then(() => {
this.activated = value;
this.loadingActivated = false;
})
.catch(() => {
createFlash(__('Update failed. Please try again.'));
this.loadingActivated = false;
});
},
},
};
</script>
<template>
<div>
<p v-html="sectionDescription"></p>
<gl-form-group :label="__('Active')" label-for="activated" label-class="label-bold">
<toggle-button
id="activated"
:disabled-input="loadingActivated"
:is-loading="loadingActivated"
:value="activated"
@change="toggleActivated"
/>
</gl-form-group>
<gl-form-group :label="__('URL')" label-for="url" label-class="label-bold">
<div class="input-group">
<gl-form-input id="url" :readonly="true" :value="url" />
<span class="input-group-append">
<clipboard-button :text="url" :title="$options.COPY_TO_CLIPBOARD" />
</span>
</div>
</gl-form-group>
<gl-form-group
:label="__('Authorization key')"
label-for="authorization-key"
label-class="label-bold"
>
<div class="input-group">
<gl-form-input id="authorization-key" :readonly="true" :value="authorizationKey" />
<span class="input-group-append">
<clipboard-button :text="authorizationKey" :title="$options.COPY_TO_CLIPBOARD" />
</span>
</div>
<gl-button v-gl-modal.authKeyModal class="mt-2">{{ $options.RESET_KEY }}</gl-button>
<gl-modal
modal-id="authKeyModal"
:title="$options.RESET_KEY"
:ok-title="$options.RESET_KEY"
ok-variant="danger"
@ok="resetKey"
>
{{
__(
'Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.',
)
}}
</gl-modal>
</gl-form-group>
</div>
</template>
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import AlertsServiceForm from './components/alerts_service_form.vue';
export default el => {
if (!el) {
return null;
}
const { activated: activatedStr, formPath, authorizationKey, url, learnMoreUrl } = el.dataset;
const activated = parseBoolean(activatedStr);
return new Vue({
el,
render(createElement) {
return createElement(AlertsServiceForm, {
props: {
initialActivated: activated,
formPath,
learnMoreUrl,
initialAuthorizationKey: authorizationKey,
url,
},
});
},
});
};
Loading
Loading
@@ -24,6 +24,7 @@ const Api = {
projectMergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
projectMergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
projectRunnersPath: '/api/:version/projects/:id/runners',
projectProtectedBranchesPath: '/api/:version/projects/:id/protected_branches',
mergeRequestsPath: '/api/:version/merge_requests',
groupLabelsPath: '/groups/:namespace_path/-/labels',
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
Loading
Loading
@@ -44,6 +45,8 @@ const Api = {
releasePath: '/api/:version/projects/:id/releases/:tag_name',
mergeRequestsPipeline: '/api/:version/projects/:id/merge_requests/:merge_request_iid/pipelines',
adminStatisticsPath: '/api/:version/application/statistics',
pipelineSinglePath: '/api/:version/projects/:id/pipelines/:pipeline_id',
lsifPath: '/api/:version/projects/:id/commits/:commit_id/lsif/info',
 
group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
Loading
Loading
@@ -218,6 +221,22 @@ const Api = {
return axios.get(url, config);
},
 
projectProtectedBranches(id, query = '') {
const url = Api.buildUrl(Api.projectProtectedBranchesPath).replace(
':id',
encodeURIComponent(id),
);
return axios
.get(url, {
params: {
search: query,
per_page: DEFAULT_PER_PAGE,
},
})
.then(({ data }) => data);
},
mergeRequests(params = {}) {
const url = Api.buildUrl(Api.mergeRequestsPath);
 
Loading
Loading
@@ -448,6 +467,22 @@ const Api = {
return axios.get(url);
},
 
pipelineSingle(id, pipelineId) {
const url = Api.buildUrl(this.pipelineSinglePath)
.replace(':id', encodeURIComponent(id))
.replace(':pipeline_id', encodeURIComponent(pipelineId));
return axios.get(url);
},
lsifData(projectPath, commitId, path) {
const url = Api.buildUrl(this.lsifPath)
.replace(':id', encodeURIComponent(projectPath))
.replace(':commit_id', commitId);
return axios.get(url, { params: { path } });
},
buildUrl(url) {
return joinPaths(gon.relative_url_root || '', url.replace(':version', gon.api_version));
},
Loading
Loading
// https://prosemirror.net/docs/ref/#model.ParseRule.priority
export const DEFAULT_PARSE_RULE_PRIORITY = 50;
export const HIGHER_PARSE_RULE_PRIORITY = 1 + DEFAULT_PARSE_RULE_PRIORITY;
/* eslint-disable class-methods-use-this */
 
import { Mark } from 'tiptap';
import _ from 'underscore';
import { escape as esc } from 'lodash';
 
// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
export default class InlineHTML extends Mark {
Loading
Loading
@@ -35,7 +35,7 @@ export default class InlineHTML extends Mark {
mixable: true,
open(state, mark) {
return `<${mark.attrs.tag}${
mark.attrs.title ? ` title="${state.esc(_.escape(mark.attrs.title))}"` : ''
mark.attrs.title ? ` title="${state.esc(esc(mark.attrs.title))}"` : ''
}>`;
},
close(state, mark) {
Loading
Loading
Loading
Loading
@@ -2,6 +2,7 @@
 
import { Mark } from 'tiptap';
import { defaultMarkdownSerializer } from 'prosemirror-markdown';
import { HIGHER_PARSE_RULE_PRIORITY } from '../constants';
 
// Transforms generated HTML back to GFM for Banzai::Filter::MathFilter
export default class MathMark extends Mark {
Loading
Loading
@@ -15,7 +16,7 @@ export default class MathMark extends Mark {
// Matches HTML generated by Banzai::Filter::MathFilter
{
tag: 'code.code.math[data-math-style=inline]',
priority: 51,
priority: HIGHER_PARSE_RULE_PRIORITY,
},
// Matches HTML after being transformed by app/assets/javascripts/behaviors/markdown/render_math.js
{
Loading
Loading
/* eslint-disable class-methods-use-this */
import { Node } from 'tiptap';
import { defaultMarkdownSerializer } from 'prosemirror-markdown';
import Playable from './playable';
 
// Transforms generated HTML back to GFM for Banzai::Filter::AudioLinkFilter
export default class Audio extends Node {
get name() {
return 'audio';
}
get schema() {
return {
attrs: {
src: {},
alt: {
default: null,
},
},
group: 'block',
draggable: true,
parseDOM: [
{
tag: '.audio-container',
skip: true,
},
{
tag: '.audio-container p',
priority: 51,
ignore: true,
},
{
tag: 'audio[src]',
getAttrs: el => ({ src: el.getAttribute('src'), alt: el.dataset.title }),
},
],
toDOM: node => [
'audio',
{
src: node.attrs.src,
controls: true,
'data-setup': '{}',
'data-title': node.attrs.alt,
},
],
};
}
toMarkdown(state, node) {
defaultMarkdownSerializer.nodes.image(state, node);
state.closeBlock(node);
export default class Audio extends Playable {
constructor() {
super();
this.mediaType = 'audio';
}
}
Loading
Loading
@@ -3,6 +3,7 @@
import { Image as BaseImage } from 'tiptap-extensions';
import { defaultMarkdownSerializer } from 'prosemirror-markdown';
import { placeholderImage } from '~/lazy_loader';
import { HIGHER_PARSE_RULE_PRIORITY } from '../constants';
 
export default class Image extends BaseImage {
get schema() {
Loading
Loading
@@ -23,7 +24,7 @@ export default class Image extends BaseImage {
// Matches HTML generated by Banzai::Filter::ImageLinkFilter
{
tag: 'a.no-attachment-icon',
priority: 51,
priority: HIGHER_PARSE_RULE_PRIORITY,
skip: true,
},
// Matches HTML generated by Banzai::Filter::ImageLazyLoadFilter
Loading
Loading
/* eslint-disable class-methods-use-this */
 
import { Node } from 'tiptap';
import { HIGHER_PARSE_RULE_PRIORITY } from '../constants';
 
// Transforms generated HTML back to GFM for Banzai::Filter::TaskListFilter
export default class OrderedTaskList extends Node {
Loading
Loading
@@ -14,7 +15,7 @@ export default class OrderedTaskList extends Node {
content: '(task_list_item|list_item)+',
parseDOM: [
{
priority: 51,
priority: HIGHER_PARSE_RULE_PRIORITY,
tag: 'ol.task-list',
},
],
Loading
Loading
/* eslint-disable class-methods-use-this */
/* eslint-disable @gitlab/i18n/no-non-i18n-strings */
import { Node } from 'tiptap';
import { defaultMarkdownSerializer } from 'prosemirror-markdown';
import { HIGHER_PARSE_RULE_PRIORITY } from '../constants';
/**
* Abstract base class for playable media, like video and audio.
* Must not be instantiated directly. Subclasses must set
* the `mediaType` property in their constructors.
* @abstract
*/
export default class Playable extends Node {
constructor() {
super();
this.mediaType = '';
this.extraElementAttrs = {};
}
get name() {
return this.mediaType;
}
get schema() {
const attrs = {
src: {},
alt: {
default: null,
},
};
const parseDOM = [
{
tag: `.${this.mediaType}-container`,
skip: true,
},
{
tag: `.${this.mediaType}-container p`,
priority: HIGHER_PARSE_RULE_PRIORITY,
ignore: true,
},
{
tag: `${this.mediaType}[src]`,
getAttrs: el => ({ src: el.src, alt: el.dataset.title }),
},
];
const toDOM = node => [
this.mediaType,
{
src: node.attrs.src,
controls: true,
'data-setup': '{}',
'data-title': node.attrs.alt,
...this.extraElementAttrs,
},
];
return {
attrs,
group: 'block',
draggable: true,
parseDOM,
toDOM,
};
}
toMarkdown(state, node) {
defaultMarkdownSerializer.nodes.image(state, node);
state.closeBlock(node);
}
}
/* eslint-disable class-methods-use-this */
 
import { Node } from 'tiptap';
import { HIGHER_PARSE_RULE_PRIORITY } from '../constants';
 
// Transforms generated HTML back to GFM for Banzai::Filter::ReferenceFilter and subclasses
export default class Reference extends Node {
Loading
Loading
@@ -23,7 +24,7 @@ export default class Reference extends Node {
parseDOM: [
{
tag: 'a.gfm:not([data-link=true])',
priority: 51,
priority: HIGHER_PARSE_RULE_PRIORITY,
getAttrs: el => ({
className: el.className,
referenceType: el.dataset.referenceType,
Loading
Loading
/* eslint-disable class-methods-use-this */
 
import TableRow from './table_row';
import { HIGHER_PARSE_RULE_PRIORITY } from '../constants';
 
const CENTER_ALIGN = 'center';
 
Loading
Loading
@@ -16,7 +17,7 @@ export default class TableHeaderRow extends TableRow {
parseDOM: [
{
tag: 'thead tr',
priority: 51,
priority: HIGHER_PARSE_RULE_PRIORITY,
},
],
toDOM: () => ['tr', 0],
Loading
Loading
Loading
Loading
@@ -2,6 +2,7 @@
 
import { Node } from 'tiptap';
import { __ } from '~/locale';
import { HIGHER_PARSE_RULE_PRIORITY } from '../constants';
 
// Transforms generated HTML back to GFM for Banzai::Filter::TableOfContentsFilter
export default class TableOfContents extends Node {
Loading
Loading
@@ -16,11 +17,11 @@ export default class TableOfContents extends Node {
parseDOM: [
{
tag: 'ul.section-nav',
priority: 51,
priority: HIGHER_PARSE_RULE_PRIORITY,
},
{
tag: 'p.table-of-contents',
priority: 51,
priority: HIGHER_PARSE_RULE_PRIORITY,
},
],
toDOM: () => ['p', { class: 'table-of-contents' }, __('Table of Contents')],
Loading
Loading
/* eslint-disable class-methods-use-this */
 
import { Node } from 'tiptap';
import { HIGHER_PARSE_RULE_PRIORITY } from '../constants';
 
// Transforms generated HTML back to GFM for Banzai::Filter::TaskListFilter
export default class TaskList extends Node {
Loading
Loading
@@ -14,7 +15,7 @@ export default class TaskList extends Node {
content: '(task_list_item|list_item)+',
parseDOM: [
{
priority: 51,
priority: HIGHER_PARSE_RULE_PRIORITY,
tag: 'ul.task-list',
},
],
Loading
Loading
/* eslint-disable class-methods-use-this */
 
import { Node } from 'tiptap';
import { HIGHER_PARSE_RULE_PRIORITY } from '../constants';
 
// Transforms generated HTML back to GFM for Banzai::Filter::TaskListFilter
export default class TaskListItem extends Node {
Loading
Loading
@@ -20,7 +21,7 @@ export default class TaskListItem extends Node {
content: 'paragraph block*',
parseDOM: [
{
priority: 51,
priority: HIGHER_PARSE_RULE_PRIORITY,
tag: 'li.task-list-item',
getAttrs: el => {
const checkbox = el.querySelector('input[type=checkbox].task-list-item-checkbox');
Loading
Loading
/* eslint-disable class-methods-use-this */
import { Node } from 'tiptap';
import { defaultMarkdownSerializer } from 'prosemirror-markdown';
import Playable from './playable';
 
// Transforms generated HTML back to GFM for Banzai::Filter::VideoLinkFilter
export default class Video extends Node {
get name() {
return 'video';
}
get schema() {
return {
attrs: {
src: {},
alt: {
default: null,
},
},
group: 'block',
draggable: true,
parseDOM: [
{
tag: '.video-container',
skip: true,
},
{
tag: '.video-container p',
priority: 51,
ignore: true,
},
{
tag: 'video[src]',
getAttrs: el => ({ src: el.getAttribute('src'), alt: el.dataset.title }),
},
],
toDOM: node => [
'video',
{
src: node.attrs.src,
width: '400',
controls: true,
'data-setup': '{}',
'data-title': node.attrs.alt,
},
],
};
}
toMarkdown(state, node) {
defaultMarkdownSerializer.nodes.image(state, node);
state.closeBlock(node);
export default class Video extends Playable {
constructor() {
super();
this.mediaType = 'video';
this.extraElementAttrs = { width: '400' };
}
}
import flash from '~/flash';
import $ from 'jquery';
import { sprintf, __ } from '../../locale';
 
// Renders diagrams and flowcharts from text using Mermaid in any element with the
Loading
Loading
@@ -18,9 +19,12 @@ import { sprintf, __ } from '../../locale';
// This is an arbitrary number; Can be iterated upon when suitable.
const MAX_CHAR_LIMIT = 5000;
 
export default function renderMermaid($els) {
function renderMermaids($els) {
if (!$els.length) return;
 
// A diagram may have been truncated in search results which will cause errors, so abort the render.
if (document.querySelector('body').dataset.page === 'search:show') return;
import(/* webpackChunkName: 'mermaid' */ 'mermaid')
.then(mermaid => {
mermaid.initialize({
Loading
Loading
@@ -92,3 +96,19 @@ export default function renderMermaid($els) {
flash(`Can't load mermaid module: ${err}`);
});
}
export default function renderMermaid($els) {
if (!$els.length) return;
const visibleMermaids = $els.filter(function filter() {
return $(this).closest('details').length === 0;
});
renderMermaids(visibleMermaids);
$els.closest('details').one('toggle', function toggle() {
if (this.open) {
renderMermaids($(this).find('.js-render-mermaid'));
}
});
}
import $ from 'jquery';
import _ from 'underscore';
import { isEmpty } from 'lodash';
import '../commons/bootstrap';
 
// Requires Input behavior
Loading
Loading
@@ -23,10 +23,10 @@ $.fn.requiresInput = function requiresInput() {
 
function requireInput() {
// Collect the input values of *all* required fields
const values = _.map($(fieldSelector, $form), field => field.value);
const values = Array.from($(fieldSelector, $form)).map(field => field.value);
 
// Disable the button if any required fields are empty
if (values.length && _.some(values, _.isEmpty)) {
if (values.length && values.some(isEmpty)) {
$button.disable();
} else {
$button.enable();
Loading
Loading
import $ from 'jquery';
import Cookies from 'js-cookie';
import Mousetrap from 'mousetrap';
import Vue from 'vue';
import { disableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle';
import ShortcutsToggle from './shortcuts_toggle.vue';
import axios from '../../lib/utils/axios_utils';
import { refreshCurrentPage, visitUrl } from '../../lib/utils/url_utility';
import findAndFollowLink from '../../lib/utils/navigation_utility';
Loading
Loading
@@ -15,6 +18,15 @@ Mousetrap.stopCallback = (e, element, combo) => {
return defaultStopCallback(e, element, combo);
};
 
function initToggleButton() {
return new Vue({
el: document.querySelector('.js-toggle-shortcuts'),
render(createElement) {
return createElement(ShortcutsToggle);
},
});
}
export default class Shortcuts {
constructor() {
this.onToggleHelp = this.onToggleHelp.bind(this);
Loading
Loading
@@ -48,6 +60,14 @@ export default class Shortcuts {
$(this).remove();
e.preventDefault();
});
$('.js-shortcuts-modal-trigger')
.off('click')
.on('click', this.onToggleHelp);
if (shouldDisableShortcuts()) {
disableShortcuts();
}
}
 
onToggleHelp(e) {
Loading
Loading
@@ -104,7 +124,8 @@ export default class Shortcuts {
}
 
return $('.js-more-help-button').remove();
});
})
.then(initToggleButton);
}
 
focusFilter(e) {
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment