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

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

parent 2774ddc3
No related branches found
No related tags found
No related merge requests found
Showing
with 653 additions and 220 deletions
import flash from '~/flash';
import $ from 'jquery';
import { sprintf, __ } from '../../locale';
import { __, sprintf } from '~/locale';
import { once } from 'lodash';
 
// Renders diagrams and flowcharts from text using Mermaid in any element with the
// `js-render-mermaid` class.
Loading
Loading
@@ -18,14 +19,10 @@ import { sprintf, __ } from '../../locale';
 
// This is an arbitrary number; Can be iterated upon when suitable.
const MAX_CHAR_LIMIT = 5000;
let mermaidModule = {};
 
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')
function importMermaidModule() {
return import(/* webpackChunkName: 'mermaid' */ 'mermaid')
.then(mermaid => {
mermaid.initialize({
// mermaid core options
Loading
Loading
@@ -35,68 +32,133 @@ function renderMermaids($els) {
// mermaidAPI options
theme: 'neutral',
flowchart: {
useMaxWidth: true,
htmlLabels: false,
},
securityLevel: 'strict',
});
 
mermaidModule = mermaid;
return mermaid;
})
.catch(err => {
flash(sprintf(__("Can't load mermaid module: %{err}"), { err }));
// eslint-disable-next-line no-console
console.error(err);
});
}
function fixElementSource(el) {
// Mermaid doesn't like `<br />` tags, so collapse all like tags into `<br>`, which is parsed correctly.
const source = el.textContent.replace(/<br\s*\/>/g, '<br>');
// Remove any extra spans added by the backend syntax highlighting.
Object.assign(el, { textContent: source });
return { source };
}
function renderMermaidEl(el) {
mermaidModule.init(undefined, el, id => {
const source = el.textContent;
const svg = document.getElementById(id);
// As of https://github.com/knsv/mermaid/commit/57b780a0d,
// Mermaid will make two init callbacks:one to initialize the
// flow charts, and another to initialize the Gannt charts.
// Guard against an error caused by double initialization.
if (svg.classList.contains('mermaid')) {
return;
}
svg.classList.add('mermaid');
// pre > code > svg
svg.closest('pre').replaceWith(svg);
// We need to add the original source into the DOM to allow Copy-as-GFM
// to access it.
const sourceEl = document.createElement('text');
sourceEl.classList.add('source');
sourceEl.setAttribute('display', 'none');
sourceEl.textContent = source;
svg.appendChild(sourceEl);
});
}
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;
importMermaidModule()
.then(() => {
let renderedChars = 0;
 
$els.each((i, el) => {
// Mermaid doesn't like `<br />` tags, so collapse all like tags into `<br>`, which is parsed correctly.
const source = el.textContent.replace(/<br\s*\/>/g, '<br>');
const { source } = fixElementSource(el);
/**
* Restrict the rendering to a certain amount of character to
* prevent mermaidjs from hanging up the entire thread and
* causing a DoS.
*/
if ((source && source.length > MAX_CHAR_LIMIT) || renderedChars > MAX_CHAR_LIMIT) {
el.textContent = sprintf(
__(
'Cannot render the image. Maximum character count (%{charLimit}) has been exceeded.',
),
{ charLimit: MAX_CHAR_LIMIT },
);
const html = `
<div class="alert gl-alert gl-alert-warning alert-dismissible lazy-render-mermaid-container js-lazy-render-mermaid-container fade show" role="alert">
<div>
<div class="display-flex">
<div>${__(
'Warning: Displaying this diagram might cause performance issues on this page.',
)}</div>
<div class="gl-alert-actions">
<button class="js-lazy-render-mermaid btn gl-alert-action btn-warning btn-md new-gl-button">Display</button>
</div>
</div>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
</div>
`;
const $parent = $(el).parent();
if (!$parent.hasClass('lazy-alert-shown')) {
$parent.after(html);
$parent.addClass('lazy-alert-shown');
}
return;
}
 
renderedChars += source.length;
// Remove any extra spans added by the backend syntax highlighting.
Object.assign(el, { textContent: source });
mermaid.init(undefined, el, id => {
const svg = document.getElementById(id);
// As of https://github.com/knsv/mermaid/commit/57b780a0d,
// Mermaid will make two init callbacks:one to initialize the
// flow charts, and another to initialize the Gannt charts.
// Guard against an error caused by double initialization.
if (svg.classList.contains('mermaid')) {
return;
}
svg.classList.add('mermaid');
// pre > code > svg
svg.closest('pre').replaceWith(svg);
 
// We need to add the original source into the DOM to allow Copy-as-GFM
// to access it.
const sourceEl = document.createElement('text');
sourceEl.classList.add('source');
sourceEl.setAttribute('display', 'none');
sourceEl.textContent = source;
svg.appendChild(sourceEl);
});
renderMermaidEl(el);
});
})
.catch(err => {
flash(`Can't load mermaid module: ${err}`);
flash(sprintf(__('Encountered an error while rendering: %{err}'), { err }));
// eslint-disable-next-line no-console
console.error(err);
});
}
 
const hookLazyRenderMermaidEvent = once(() => {
$(document.body).on('click', '.js-lazy-render-mermaid', function eventHandler() {
const parent = $(this).closest('.js-lazy-render-mermaid-container');
const pre = parent.prev();
const el = pre.find('.js-render-mermaid');
parent.remove();
renderMermaidEl(el);
});
});
export default function renderMermaid($els) {
if (!$els.length) return;
 
Loading
Loading
@@ -111,4 +173,6 @@ export default function renderMermaid($els) {
renderMermaids($(this).find('.js-render-mermaid'));
}
});
hookLazyRenderMermaidEvent();
}
import sqljs from 'sql.js';
import { template as _template } from 'underscore';
import { template as _template } from 'lodash';
import axios from '~/lib/utils/axios_utils';
import { successCodes } from '~/lib/utils/http_status';
 
Loading
Loading
Loading
Loading
@@ -45,7 +45,13 @@ export default {
 
<template v-else>
<blob-content-error v-if="viewerError" :viewer-error="viewerError" />
<component :is="viewer" v-else ref="contentViewer" :content="content" />
<component
:is="viewer"
v-else
ref="contentViewer"
:content="content"
:type="activeViewer.fileType"
/>
</template>
</div>
</template>
<script>
import { initEditorLite } from '~/blob/utils';
export default {
props: {
value: {
type: String,
required: true,
},
fileName: {
type: String,
required: false,
default: '',
},
},
data() {
return {
content: this.value,
editor: null,
};
},
watch: {
fileName(newVal) {
this.editor.updateModelLanguage(newVal);
},
},
mounted() {
this.editor = initEditorLite({
el: this.$refs.editor,
blobPath: this.fileName,
blobContent: this.content,
});
},
methods: {
triggerFileChange() {
const val = this.editor.getValue();
this.content = val;
this.$emit('input', val);
},
},
};
</script>
<template>
<div class="file-content code">
<pre id="editor" ref="editor" data-editor-loading @focusout="triggerFileChange">{{
content
}}</pre>
</div>
</template>
<script>
import { GlFormInput } from '@gitlab/ui';
export default {
components: {
GlFormInput,
},
props: {
value: {
type: String,
required: true,
},
},
data() {
return {
name: this.value,
};
},
};
</script>
<template>
<div class="js-file-title file-title-flex-parent">
<gl-form-input
id="snippet_file_name"
v-model="name"
:placeholder="
s__('Snippets|Give your file a name to add code highlighting, e.g. example.rb for Ruby')
"
name="snippet_file_name"
class="form-control js-snippet-file-name qa-snippet-file-name"
type="text"
@change="$emit('input', name)"
/>
</div>
</template>
Loading
Loading
@@ -9,6 +9,7 @@ import GitignoreSelector from './template_selectors/gitignore_selector';
import LicenseSelector from './template_selectors/license_selector';
import toast from '~/vue_shared/plugins/global_toast';
import { __ } from '~/locale';
import initPopover from '~/blob/suggest_gitlab_ci_yml';
 
export default class FileTemplateMediator {
constructor({ editor, currentAction, projectId }) {
Loading
Loading
@@ -128,6 +129,7 @@ export default class FileTemplateMediator {
selectTemplateFile(selector, query, data) {
const self = this;
const { name } = selector.config;
const suggestCommitChanges = document.querySelector('.js-suggest-gitlab-ci-yml-commit-changes');
 
selector.renderLoading();
 
Loading
Loading
@@ -146,6 +148,10 @@ export default class FileTemplateMediator {
},
},
});
if (suggestCommitChanges) {
initPopover(suggestCommitChanges);
}
})
.catch(err => new Flash(`An error occurred while fetching the template: ${err}`));
}
Loading
Loading
/* eslint-disable no-new */
import Vue from 'vue';
import axios from '../../lib/utils/axios_utils';
import notebookLab from '../../notebook/index.vue';
import NotebookViewer from './notebook_viewer.vue';
 
export default () => {
const el = document.getElementById('js-notebook-viewer');
 
new Vue({
return new Vue({
el,
components: {
notebookLab,
render(createElement) {
return createElement(NotebookViewer, {
props: {
endpoint: el.dataset.endpoint,
},
});
},
data() {
return {
error: false,
loadError: false,
loading: true,
json: {},
};
},
mounted() {
if (gon.katex_css_url) {
const katexStyles = document.createElement('link');
katexStyles.setAttribute('rel', 'stylesheet');
katexStyles.setAttribute('href', gon.katex_css_url);
document.head.appendChild(katexStyles);
}
if (gon.katex_js_url) {
const katexScript = document.createElement('script');
katexScript.addEventListener('load', () => {
this.loadFile();
});
katexScript.setAttribute('src', gon.katex_js_url);
document.head.appendChild(katexScript);
} else {
this.loadFile();
}
},
methods: {
loadFile() {
axios
.get(el.dataset.endpoint)
.then(res => res.data)
.then(data => {
this.json = data;
this.loading = false;
})
.catch(e => {
if (e.status !== 200) {
this.loadError = true;
}
this.error = true;
});
},
},
template: `
<div class="container-fluid md prepend-top-default append-bottom-default">
<div
class="text-center loading"
v-if="loading && !error">
<i
class="fa fa-spinner fa-spin"
aria-hidden="true"
aria-label="iPython notebook loading">
</i>
</div>
<notebook-lab
v-if="!loading && !error"
:notebook="json"
code-css-class="code white" />
<p
class="text-center"
v-if="error">
<span v-if="loadError">
An error occurred while loading the file. Please try again later.
</span>
<span v-else>
An error occurred while parsing the file.
</span>
</p>
</div>
`,
});
};
<script>
import axios from '~/lib/utils/axios_utils';
import notebookLab from '~/notebook/index.vue';
import { GlLoadingIcon } from '@gitlab/ui';
export default {
components: {
notebookLab,
GlLoadingIcon,
},
props: {
endpoint: {
type: String,
required: true,
},
},
data() {
return {
error: false,
loadError: false,
loading: true,
json: {},
};
},
mounted() {
if (gon.katex_css_url) {
const katexStyles = document.createElement('link');
katexStyles.setAttribute('rel', 'stylesheet');
katexStyles.setAttribute('href', gon.katex_css_url);
document.head.appendChild(katexStyles);
}
if (gon.katex_js_url) {
const katexScript = document.createElement('script');
katexScript.addEventListener('load', () => {
this.loadFile();
});
katexScript.setAttribute('src', gon.katex_js_url);
document.head.appendChild(katexScript);
} else {
this.loadFile();
}
},
methods: {
loadFile() {
axios
.get(this.endpoint)
.then(res => res.data)
.then(data => {
this.json = data;
this.loading = false;
})
.catch(e => {
if (e.status !== 200) {
this.loadError = true;
}
this.error = true;
});
},
},
};
</script>
<template>
<div
class="js-notebook-viewer-mounted container-fluid md prepend-top-default append-bottom-default"
>
<div v-if="loading && !error" class="text-center loading">
<gl-loading-icon class="mt-5" size="lg" />
</div>
<notebook-lab v-if="!loading && !error" :notebook="json" code-css-class="code white" />
<p v-if="error" class="text-center">
<span v-if="loadError" ref="loadErrorMessage">{{
__('An error occurred while loading the file. Please try again later.')
}}</span>
<span v-else ref="parsingErrorMessage">{{
__('An error occurred while parsing the file.')
}}</span>
</p>
</div>
</template>
import Vue from 'vue';
import pdfLab from '../../pdf/index.vue';
import { GlLoadingIcon } from '@gitlab/ui';
import PdfViewer from './pdf_viewer.vue';
 
export default () => {
const el = document.getElementById('js-pdf-viewer');
 
return new Vue({
el,
components: {
pdfLab,
GlLoadingIcon,
render(createElement) {
return createElement(PdfViewer, {
props: {
pdf: el.dataset.endpoint,
},
});
},
data() {
return {
error: false,
loadError: false,
loading: true,
pdf: el.dataset.endpoint,
};
},
methods: {
onLoad() {
this.loading = false;
},
onError(error) {
this.loading = false;
this.loadError = true;
this.error = error;
},
},
template: `
<div class="js-pdf-viewer container-fluid md prepend-top-default append-bottom-default">
<div
class="text-center loading"
v-if="loading && !error">
<gl-loading-icon class="mt-5" size="lg"/>
</div>
<pdf-lab
v-if="!loadError"
:pdf="pdf"
@pdflabload="onLoad"
@pdflaberror="onError" />
<p
class="text-center"
v-if="error">
<span v-if="loadError">
An error occurred while loading the file. Please try again later.
</span>
<span v-else>
An error occurred while decoding the file.
</span>
</p>
</div>
`,
});
};
<script>
import PdfLab from '../../pdf/index.vue';
import { GlLoadingIcon } from '@gitlab/ui';
export default {
components: {
PdfLab,
GlLoadingIcon,
},
props: {
pdf: {
type: String,
required: true,
},
},
data() {
return {
error: false,
loadError: false,
loading: true,
};
},
methods: {
onLoad() {
this.loading = false;
},
onError(error) {
this.loading = false;
this.loadError = true;
this.error = error;
},
},
};
</script>
<template>
<div class="js-pdf-viewer container-fluid md prepend-top-default append-bottom-default">
<div v-if="loading && !error" class="text-center loading">
<gl-loading-icon class="mt-5" size="lg" />
</div>
<pdf-lab v-if="!loadError" :pdf="pdf" @pdflabload="onLoad" @pdflaberror="onError" />
<p v-if="error" class="text-center">
<span v-if="loadError" ref="loadError">
{{ __('An error occurred while loading the file. Please try again later.') }}
</span>
<span v-else>{{ __('An error occurred while decoding the file.') }}</span>
</p>
</div>
</template>
<script>
import { GlModal, GlSprintf, GlLink } from '@gitlab/ui';
import { sprintf, s__, __ } from '~/locale';
import Cookies from 'js-cookie';
import { glEmojiTag } from '~/emoji';
export default {
beginnerLink:
'https://about.gitlab.com/blog/2018/01/22/a-beginners-guide-to-continuous-integration/',
exampleLink: 'https://docs.gitlab.com/ee/ci/examples/',
bodyMessage: s__(
'MR widget|The pipeline will now run automatically every time you commit code. Pipelines are useful for deploying static web pages, detecting vulnerabilities in dependencies, static or dynamic application security testing (SAST and DAST), and so much more!',
),
modalTitle: sprintf(
__("That's it, well done!%{celebrate}"),
{
celebrate: glEmojiTag('tada'),
},
false,
),
components: {
GlModal,
GlSprintf,
GlLink,
},
props: {
goToPipelinesPath: {
type: String,
required: true,
},
commitCookie: {
type: String,
required: true,
},
},
mounted() {
this.disableModalFromRenderingAgain();
},
methods: {
disableModalFromRenderingAgain() {
Cookies.remove(this.commitCookie);
},
},
};
</script>
<template>
<gl-modal
visible
size="sm"
:title="$options.modalTitle"
modal-id="success-pipeline-modal-id-not-used"
>
<p>
{{ $options.bodyMessage }}
</p>
<gl-sprintf
:message="
s__(`MR widget|Take a look at our %{beginnerLinkStart}Beginner's Guide to Continuous Integration%{beginnerLinkEnd}
and our %{exampleLinkStart}examples of GitLab CI/CD%{exampleLinkEnd}
to see all the cool stuff you can do with it.`)
"
>
<template #beginnerLink="{content}">
<gl-link :href="$options.beginnerLink" target="_blank">
{{ content }}
</gl-link>
</template>
<template #exampleLink="{content}">
<gl-link :href="$options.exampleLink" target="_blank">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
<template #modal-footer>
<a :href="goToPipelinesPath" class="btn btn-success">{{ __('Go to Pipelines') }}</a>
</template>
</gl-modal>
</template>
<script>
import { GlPopover, GlSprintf, GlButton, GlIcon } from '@gitlab/ui';
import Cookies from 'js-cookie';
import { parseBoolean, scrollToElement } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
import { glEmojiTag } from '~/emoji';
import Tracking from '~/tracking';
const trackingMixin = Tracking.mixin();
const popoverStates = {
suggest_gitlab_ci_yml: {
title: s__(`suggestPipeline|1/2: Choose a template`),
content: s__(
`suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}`,
),
emoji: glEmojiTag('wave'),
},
suggest_commit_first_project_gitlab_ci_yml: {
title: s__(`suggestPipeline|2/2: Commit your changes`),
content: s__(
`suggestPipeline|Commit the changes and your pipeline will automatically run for the first time.`,
),
},
};
export default {
components: {
GlPopover,
GlSprintf,
GlIcon,
GlButton,
},
mixins: [trackingMixin],
props: {
target: {
type: String,
required: true,
},
trackLabel: {
type: String,
required: true,
},
dismissKey: {
type: String,
required: true,
},
humanAccess: {
type: String,
required: true,
},
},
data() {
return {
popoverDismissed: parseBoolean(Cookies.get(this.dismissKey)),
tracking: {
label: this.trackLabel,
property: this.humanAccess,
},
};
},
computed: {
suggestTitle() {
return popoverStates[this.trackLabel].title || '';
},
suggestContent() {
return popoverStates[this.trackLabel].content || '';
},
emoji() {
return popoverStates[this.trackLabel].emoji || '';
},
},
mounted() {
if (this.trackLabel === 'suggest_commit_first_project_gitlab_ci_yml' && !this.popoverDismissed)
scrollToElement(document.querySelector(this.target));
this.trackOnShow();
},
methods: {
onDismiss() {
this.popoverDismissed = true;
Cookies.set(this.dismissKey, this.popoverDismissed, { expires: 365 });
},
trackOnShow() {
if (!this.popoverDismissed) this.track();
},
},
};
</script>
<template>
<gl-popover
v-if="!popoverDismissed"
show
:target="target"
placement="rightbottom"
trigger="manual"
container="viewport"
:css-classes="['suggest-gitlab-ci-yml', 'ml-4']"
>
<template #title>
<span v-html="suggestTitle"></span>
<span class="ml-auto">
<gl-button :aria-label="__('Close')" class="btn-blank" @click="onDismiss">
<gl-icon name="close" aria-hidden="true" />
</gl-button>
</span>
</template>
<gl-sprintf :message="suggestContent">
<template #bold="{content}">
<strong> {{ content }} </strong>
</template>
<template #footer="{content}">
<div class="mt-3">
{{ content }}
<span v-html="emoji"></span>
</div>
</template>
</gl-sprintf>
</gl-popover>
</template>
import Vue from 'vue';
import Popover from './components/popover.vue';
export default el =>
new Vue({
el,
render(createElement) {
return createElement(Popover, {
props: {
target: el.dataset.target,
trackLabel: el.dataset.trackLabel,
dismissKey: el.dataset.dismissKey,
humanAccess: el.dataset.humanAccess,
},
});
},
});
Loading
Loading
@@ -92,10 +92,10 @@ export default class TemplateSelector {
}
 
startLoadingSpinner() {
this.$dropdownIcon.addClass('fa-spinner fa-spin').removeClass('fa-chevron-down');
this.$dropdownIcon.addClass('spinner').removeClass('fa-chevron-down');
}
 
stopLoadingSpinner() {
this.$dropdownIcon.addClass('fa-chevron-down').removeClass('fa-spinner fa-spin');
this.$dropdownIcon.addClass('fa-chevron-down').removeClass('spinner');
}
}
/* global ace */
import Editor from '~/editor/editor_lite';
export function initEditorLite({ el, blobPath, blobContent }) {
if (!el) {
throw new Error(`"el" parameter is required to initialize Editor`);
}
let editor;
if (window?.gon?.features?.monacoSnippets) {
editor = new Editor();
editor.createInstance({
el,
blobPath,
blobContent,
});
} else {
editor = ace.edit(el);
}
return editor;
}
export default () => ({});
Loading
Loading
@@ -5,10 +5,43 @@ import { handleLocationHash } from '../../lib/utils/common_utils';
import axios from '../../lib/utils/axios_utils';
import { __ } from '~/locale';
 
const loadRichBlobViewer = type => {
switch (type) {
case 'balsamiq':
return import(/* webpackChunkName: 'balsamiq_viewer' */ '../balsamiq_viewer');
case 'notebook':
return import(/* webpackChunkName: 'notebook_viewer' */ '../notebook_viewer');
case 'openapi':
return import(/* webpackChunkName: 'openapi_viewer' */ '../openapi_viewer');
case 'pdf':
return import(/* webpackChunkName: 'pdf_viewer' */ '../pdf_viewer');
case 'sketch':
return import(/* webpackChunkName: 'sketch_viewer' */ '../sketch_viewer');
case 'stl':
return import(/* webpackChunkName: 'stl_viewer' */ '../stl_viewer');
default:
return Promise.resolve();
}
};
export const handleBlobRichViewer = (viewer, type) => {
if (!viewer || !type) return;
loadRichBlobViewer(type)
.then(module => module?.default(viewer))
.catch(error => {
Flash(__('Error loading file viewer.'));
throw error;
});
};
export default class BlobViewer {
constructor() {
const viewer = document.querySelector('.blob-viewer[data-type="rich"]');
const type = viewer?.dataset?.richType;
BlobViewer.initAuxiliaryViewer();
BlobViewer.initRichViewer();
handleBlobRichViewer(viewer, type);
 
this.initMainViewers();
}
Loading
Loading
@@ -20,42 +53,6 @@ export default class BlobViewer {
BlobViewer.loadViewer(auxiliaryViewer);
}
 
static initRichViewer() {
const viewer = document.querySelector('.blob-viewer[data-type="rich"]');
if (!viewer || !viewer.dataset.richType) return;
const initViewer = promise =>
promise
.then(module => module.default(viewer))
.catch(error => {
Flash(__('Error loading file viewer.'));
throw error;
});
switch (viewer.dataset.richType) {
case 'balsamiq':
initViewer(import(/* webpackChunkName: 'balsamiq_viewer' */ '../balsamiq_viewer'));
break;
case 'notebook':
initViewer(import(/* webpackChunkName: 'notebook_viewer' */ '../notebook_viewer'));
break;
case 'openapi':
initViewer(import(/* webpackChunkName: 'openapi_viewer' */ '../openapi_viewer'));
break;
case 'pdf':
initViewer(import(/* webpackChunkName: 'pdf_viewer' */ '../pdf_viewer'));
break;
case 'sketch':
initViewer(import(/* webpackChunkName: 'sketch_viewer' */ '../sketch_viewer'));
break;
case 'stl':
initViewer(import(/* webpackChunkName: 'stl_viewer' */ '../stl_viewer'));
break;
default:
break;
}
}
initMainViewers() {
this.$fileHolder = $('.file-holder');
if (!this.$fileHolder.length) return;
Loading
Loading
Loading
Loading
@@ -4,11 +4,13 @@ import $ from 'jquery';
import NewCommitForm from '../new_commit_form';
import EditBlob from './edit_blob';
import BlobFileDropzone from '../blob/blob_file_dropzone';
import initPopover from '~/blob/suggest_gitlab_ci_yml';
 
export default () => {
const editBlobForm = $('.js-edit-blob-form');
const uploadBlobForm = $('.js-upload-blob-form');
const deleteBlobForm = $('.js-delete-blob-form');
const suggestEl = document.querySelector('.js-suggest-gitlab-ci-yml');
 
if (editBlobForm.length) {
const urlRoot = editBlobForm.data('relativeUrlRoot');
Loading
Loading
@@ -56,4 +58,8 @@ export default () => {
if (deleteBlobForm.length) {
new NewCommitForm(deleteBlobForm);
}
if (suggestEl) {
initPopover(suggestEl);
}
};
import $ from 'jquery';
import Sortable from 'sortablejs';
import Vue from 'vue';
import { GlButtonGroup, GlButton, GlTooltip } from '@gitlab/ui';
import { GlButtonGroup, GlButton, GlLabel, GlTooltip } from '@gitlab/ui';
import isWipLimitsOn from 'ee_else_ce/boards/mixins/is_wip_limits';
import { s__, __, sprintf } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
Loading
Loading
@@ -14,6 +14,7 @@ import IssueCount from './issue_count.vue';
import boardsStore from '../stores/boards_store';
import { getBoardSortableDefaultOptions, sortableEnd } from '../mixins/sortable_default_options';
import { ListType } from '../constants';
import { isScopedLabel } from '~/lib/utils/common_utils';
 
export default Vue.extend({
components: {
Loading
Loading
@@ -24,6 +25,7 @@ export default Vue.extend({
GlButtonGroup,
IssueCount,
GlButton,
GlLabel,
GlTooltip,
},
directives: {
Loading
Loading
@@ -95,6 +97,9 @@ export default Vue.extend({
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
return `boards.${this.boardId}.${this.list.type}.${this.list.id}`;
},
helpLink() {
return boardsStore.scopedLabels.helpLink;
},
},
watch: {
filter: {
Loading
Loading
@@ -145,6 +150,10 @@ export default Vue.extend({
}
},
methods: {
showScopedLabels(label) {
return boardsStore.scopedLabels.enabled && isScopedLabel(label);
},
showNewIssueForm() {
this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm;
},
Loading
Loading
Loading
Loading
@@ -65,11 +65,12 @@ export default {
},
showIssue(e) {
if (e.target.classList.contains('js-no-trigger')) return;
if (this.showDetail) {
this.showDetail = false;
 
// If CMD or CTRL is clicked
const isMultiSelect = this.canMultiSelect && (e.ctrlKey || e.metaKey);
// If CMD or CTRL is clicked
const isMultiSelect = this.canMultiSelect && (e.ctrlKey || e.metaKey);
if (this.showDetail || isMultiSelect) {
this.showDetail = false;
 
if (boardsStore.detail.issue && boardsStore.detail.issue.id === this.issue.id) {
eventHub.$emit('clearDetailIssue', isMultiSelect);
Loading
Loading
<script>
import { Sortable, MultiDrag } from 'sortablejs';
import { GlLoadingIcon } from '@gitlab/ui';
import _ from 'underscore';
import boardNewIssue from './board_new_issue.vue';
import boardCard from './board_card.vue';
import eventHub from '../eventhub';
Loading
Loading
@@ -266,11 +265,12 @@ export default {
* same list or the other list. Don't remove items if it's same list.
*/
const isSameList = toList && toList.id === this.list.id;
if (toList && !isSameList && boardsStore.shouldRemoveIssue(this.list, toList)) {
const issues = items.map(item => this.list.findIssue(Number(item.dataset.issueId)));
if (_.compact(issues).length && !boardsStore.issuesAreContiguous(this.list, issues)) {
if (
issues.filter(Boolean).length &&
!boardsStore.issuesAreContiguous(this.list, issues)
) {
const indexes = [];
const ids = this.list.issues.map(i => i.id);
issues.forEach(issue => {
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