Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-06-19 15:09:07 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-06-19 15:09:07 +0300
commit235dc61f473cb7f02a9453a59fb26d293e05b092 (patch)
tree06ee9d4f1356e486c5d9c6541a89a31bc1ded670 /app/assets/javascripts
parent12866a3931a2834d911728364d6604c4dd97c004 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r--app/assets/javascripts/blob_edit/constants.js4
-rw-r--r--app/assets/javascripts/blob_edit/edit_blob.js71
-rw-r--r--app/assets/javascripts/editor/editor_lite.js20
-rw-r--r--app/assets/javascripts/gl_form.js6
-rw-r--r--app/assets/javascripts/issue_show/components/pinned_links.vue65
-rw-r--r--app/assets/javascripts/issue_show/constants.js3
-rw-r--r--app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue20
-rw-r--r--app/assets/javascripts/static_site_editor/graphql/typedefs.graphql2
-rw-r--r--app/assets/javascripts/vue_shared/components/gl_mentions.vue30
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue31
10 files changed, 186 insertions, 66 deletions
diff --git a/app/assets/javascripts/blob_edit/constants.js b/app/assets/javascripts/blob_edit/constants.js
new file mode 100644
index 00000000000..a19da2098cf
--- /dev/null
+++ b/app/assets/javascripts/blob_edit/constants.js
@@ -0,0 +1,4 @@
+import { __ } from '~/locale';
+
+export const BLOB_EDITOR_ERROR = __('An error occurred while rendering the editor');
+export const BLOB_PREVIEW_ERROR = __('An error occurred previewing the blob');
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index 011898a5e7a..a725b3fe5d6 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -3,39 +3,75 @@
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
-import { __ } from '~/locale';
+import { BLOB_EDITOR_ERROR, BLOB_PREVIEW_ERROR } from './constants';
import TemplateSelectorMediator from '../blob/file_template_mediator';
import getModeByFileExtension from '~/lib/utils/ace_utils';
import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown';
+const monacoEnabled = window?.gon?.features?.monacoBlobs;
+
export default class EditBlob {
// The options object has:
// assetsPath, filePath, currentAction, projectId, isMarkdown
constructor(options) {
this.options = options;
- this.configureAceEditor();
- this.initModePanesAndLinks();
- this.initSoftWrap();
- this.initFileSelectors();
+ const { isMarkdown } = this.options;
+ Promise.resolve()
+ .then(() => {
+ return monacoEnabled ? this.configureMonacoEditor() : this.configureAceEditor();
+ })
+ .then(() => {
+ this.initModePanesAndLinks();
+ this.initFileSelectors();
+ this.initSoftWrap();
+ if (isMarkdown) {
+ addEditorMarkdownListeners(this.editor);
+ }
+ this.editor.focus();
+ })
+ .catch(() => createFlash(BLOB_EDITOR_ERROR));
+ }
+
+ configureMonacoEditor() {
+ return import(/* webpackChunkName: 'monaco_editor_lite' */ '~/editor/editor_lite').then(
+ EditorModule => {
+ const EditorLite = EditorModule.default;
+ const editorEl = document.getElementById('editor');
+ const fileNameEl =
+ document.getElementById('file_path') || document.getElementById('file_name');
+ const fileContentEl = document.getElementById('file-content');
+ const form = document.querySelector('.js-edit-blob-form');
+
+ this.editor = new EditorLite();
+
+ this.editor.createInstance({
+ el: editorEl,
+ blobPath: fileNameEl.value,
+ blobContent: editorEl.innerText,
+ });
+
+ fileNameEl.addEventListener('change', () => {
+ this.editor.updateModelLanguage(fileNameEl.value);
+ });
+
+ form.addEventListener('submit', () => {
+ fileContentEl.value = this.editor.getValue();
+ });
+ },
+ );
}
configureAceEditor() {
- const { filePath, assetsPath, isMarkdown } = this.options;
+ const { filePath, assetsPath } = this.options;
ace.config.set('modePath', `${assetsPath}/ace`);
ace.config.loadModule('ace/ext/searchbox');
ace.config.loadModule('ace/ext/modelist');
this.editor = ace.edit('editor');
- if (isMarkdown) {
- addEditorMarkdownListeners(this.editor);
- }
-
// This prevents warnings re: automatic scrolling being logged
this.editor.$blockScrolling = Infinity;
- this.editor.focus();
-
if (filePath) {
this.editor.getSession().setMode(getModeByFileExtension(filePath));
}
@@ -81,7 +117,7 @@ export default class EditBlob {
currentPane.empty().append(data);
currentPane.renderGFM();
})
- .catch(() => createFlash(__('An error occurred previewing the blob')));
+ .catch(() => createFlash(BLOB_PREVIEW_ERROR));
}
this.$toggleButton.show();
@@ -90,14 +126,19 @@ export default class EditBlob {
}
initSoftWrap() {
- this.isSoftWrapped = false;
+ this.isSoftWrapped = Boolean(monacoEnabled);
this.$toggleButton = $('.soft-wrap-toggle');
+ this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
this.$toggleButton.on('click', () => this.toggleSoftWrap());
}
toggleSoftWrap() {
this.isSoftWrapped = !this.isSoftWrapped;
this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
- this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
+ if (monacoEnabled) {
+ this.editor.updateOptions({ wordWrap: this.isSoftWrapped ? 'on' : 'off' });
+ } else {
+ this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
+ }
}
}
diff --git a/app/assets/javascripts/editor/editor_lite.js b/app/assets/javascripts/editor/editor_lite.js
index 020ed6dc867..6db24a1b7ef 100644
--- a/app/assets/javascripts/editor/editor_lite.js
+++ b/app/assets/javascripts/editor/editor_lite.js
@@ -1,4 +1,4 @@
-import { editor as monacoEditor, languages as monacoLanguages, Uri } from 'monaco-editor';
+import { editor as monacoEditor, languages as monacoLanguages, Position, Uri } from 'monaco-editor';
import { DEFAULT_THEME, themes } from '~/ide/lib/themes';
import languages from '~/ide/lib/languages';
import { defaultEditorOptions } from '~/ide/lib/editor_options';
@@ -70,6 +70,22 @@ export default class Editor {
}
getValue() {
- return this.model.getValue();
+ return this.instance.getValue();
+ }
+
+ setValue(val) {
+ this.instance.setValue(val);
+ }
+
+ focus() {
+ this.instance.focus();
+ }
+
+ navigateFileStart() {
+ this.instance.setPosition(new Position(1, 1));
+ }
+
+ updateOptions(options = {}) {
+ this.instance.updateOptions(options);
}
}
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index 0b7735a7db9..28f1e3afd3d 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -9,13 +9,15 @@ export default class GLForm {
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
this.enableGFM = { ...defaultAutocompleteConfig, ...enableGFM };
+
// Disable autocomplete for keywords which do not have dataSources available
const dataSources = (gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources) || {};
Object.keys(this.enableGFM).forEach(item => {
- if (item !== 'emojis') {
- this.enableGFM[item] = Boolean(dataSources[item]);
+ if (item !== 'emojis' && !dataSources[item]) {
+ this.enableGFM[item] = false;
}
});
+
// Before we start, we should clean up any previous data for this form
this.destroy();
// Set up the form
diff --git a/app/assets/javascripts/issue_show/components/pinned_links.vue b/app/assets/javascripts/issue_show/components/pinned_links.vue
index 4b50acceb62..a877aa2ac96 100644
--- a/app/assets/javascripts/issue_show/components/pinned_links.vue
+++ b/app/assets/javascripts/issue_show/components/pinned_links.vue
@@ -1,11 +1,10 @@
<script>
-import { GlLink } from '@gitlab/ui';
-import Icon from '~/vue_shared/components/icon.vue';
+import { GlButton } from '@gitlab/ui';
+import { STATUS_PAGE_PUBLISHED, JOIN_ZOOM_MEETING } from '../constants';
export default {
components: {
- Icon,
- GlLink,
+ GlButton,
},
props: {
zoomMeetingUrl: {
@@ -19,32 +18,46 @@ export default {
default: '',
},
},
+ computed: {
+ pinnedLinks() {
+ return [
+ {
+ id: 'publishedIncidentUrl',
+ url: this.publishedIncidentUrl,
+ text: STATUS_PAGE_PUBLISHED,
+ icon: 'tanuki',
+ },
+ {
+ id: 'zoomMeetingUrl',
+ url: this.zoomMeetingUrl,
+ text: JOIN_ZOOM_MEETING,
+ icon: 'brand-zoom',
+ },
+ ];
+ },
+ },
+ methods: {
+ needsPaddingClass(i) {
+ return i < this.pinnedLinks.length - 1;
+ },
+ },
};
</script>
<template>
<div class="border-bottom gl-mb-6 gl-display-flex gl-justify-content-start">
- <div v-if="publishedIncidentUrl" class="gl-pr-3">
- <gl-link
- :href="publishedIncidentUrl"
- target="_blank"
- class="btn btn-inverted btn-secondary btn-sm text-dark mb-3"
- data-testid="publishedIncidentUrl"
- >
- <icon name="tanuki" :size="14" />
- <strong class="vertical-align-top">{{ __('Published on status page') }}</strong>
- </gl-link>
- </div>
- <div v-if="zoomMeetingUrl">
- <gl-link
- :href="zoomMeetingUrl"
- target="_blank"
- class="btn btn-inverted btn-secondary btn-sm text-dark mb-3"
- data-testid="zoomMeetingUrl"
- >
- <icon name="brand-zoom" :size="14" />
- <strong class="vertical-align-top">{{ __('Join Zoom meeting') }}</strong>
- </gl-link>
- </div>
+ <template v-for="(link, i) in pinnedLinks">
+ <div v-if="link.url" :key="link.id" :class="{ 'gl-pr-3': needsPaddingClass(i) }">
+ <gl-button
+ :href="link.url"
+ target="_blank"
+ :icon="link.icon"
+ size="small"
+ class="gl-font-weight-bold gl-mb-5"
+ :data-testid="link.id"
+ >{{ link.text }}</gl-button
+ >
+ </div>
+ </template>
</div>
</template>
diff --git a/app/assets/javascripts/issue_show/constants.js b/app/assets/javascripts/issue_show/constants.js
index d73cc8cf007..6bc6ed2b372 100644
--- a/app/assets/javascripts/issue_show/constants.js
+++ b/app/assets/javascripts/issue_show/constants.js
@@ -15,3 +15,6 @@ export const IssuableType = {
Epic: 'epic',
MergeRequest: 'merge_request',
};
+
+export const STATUS_PAGE_PUBLISHED = __('Published on status page');
+export const JOIN_ZOOM_MEETING = __('Join Zoom meeting');
diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
index be7f27f210d..fe5c289152d 100644
--- a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
+++ b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
@@ -3,6 +3,7 @@ import { mapGetters } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import store from '~/pipelines/stores/test_reports';
import { __ } from '~/locale';
+import { GlTooltipDirective } from '@gitlab/ui';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
export default {
@@ -11,6 +12,9 @@ export default {
Icon,
SmartVirtualList,
},
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
store,
props: {
heading: {
@@ -69,12 +73,24 @@ export default {
>
<div class="table-section section-20 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Class') }}</div>
- <div class="table-mobile-content pr-md-1 text-truncate">{{ testCase.classname }}</div>
+ <div
+ v-gl-tooltip
+ :title="testCase.classname"
+ class="table-mobile-content pr-md-1 text-truncate"
+ >
+ {{ testCase.classname }}
+ </div>
</div>
<div class="table-section section-20 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Name') }}</div>
- <div class="table-mobile-content pr-md-1 text-truncate">{{ testCase.name }}</div>
+ <div
+ v-gl-tooltip
+ :title="testCase.name"
+ class="table-mobile-content pr-md-1 text-truncate"
+ >
+ {{ testCase.name }}
+ </div>
</div>
<div class="table-section section-10 section-wrap">
diff --git a/app/assets/javascripts/static_site_editor/graphql/typedefs.graphql b/app/assets/javascripts/static_site_editor/graphql/typedefs.graphql
index 59da2e27144..78cc1746cdb 100644
--- a/app/assets/javascripts/static_site_editor/graphql/typedefs.graphql
+++ b/app/assets/javascripts/static_site_editor/graphql/typedefs.graphql
@@ -22,7 +22,7 @@ type AppData {
username: String!
}
-type SubmitContentChangesInput {
+input SubmitContentChangesInput {
project: String!
sourcePath: String!
content: String!
diff --git a/app/assets/javascripts/vue_shared/components/gl_mentions.vue b/app/assets/javascripts/vue_shared/components/gl_mentions.vue
index a7fba5e760b..0ef4f1eda27 100644
--- a/app/assets/javascripts/vue_shared/components/gl_mentions.vue
+++ b/app/assets/javascripts/vue_shared/components/gl_mentions.vue
@@ -3,18 +3,19 @@ import { escape } from 'lodash';
import Tribute from 'tributejs';
import axios from '~/lib/utils/axios_utils';
import { spriteIcon } from '~/lib/utils/common_utils';
+import SidebarMediator from '~/sidebar/sidebar_mediator';
/**
* Creates the HTML template for each row of the mentions dropdown.
*
- * @param original An object from the array returned from the `autocomplete_sources/members` API
- * @returns {string} An HTML template
+ * @param original - An object from the array returned from the `autocomplete_sources/members` API
+ * @returns {string} - An HTML template
*/
function menuItemTemplate({ original }) {
const rectAvatarClass = original.type === 'Group' ? 'rect-avatar' : '';
const avatarClasses = `avatar avatar-inline center s26 ${rectAvatarClass}
- gl-display-inline-flex gl-align-items-center gl-justify-content-center`;
+ gl-display-inline-flex! gl-align-items-center gl-justify-content-center`;
const avatarTag = original.avatar_url
? `<img
@@ -48,6 +49,7 @@ export default {
},
data() {
return {
+ assignees: undefined,
members: undefined,
};
},
@@ -76,19 +78,37 @@ export default {
*/
getMembers(inputText, processValues) {
if (this.members) {
- processValues(this.members);
+ processValues(this.getFilteredMembers());
} else if (this.dataSources.members) {
axios
.get(this.dataSources.members)
.then(response => {
this.members = response.data;
- processValues(response.data);
+ processValues(this.getFilteredMembers());
})
.catch(() => {});
} else {
processValues([]);
}
},
+ getFilteredMembers() {
+ const fullText = this.$slots.default[0].elm.value;
+
+ if (!this.assignees) {
+ this.assignees =
+ SidebarMediator.singleton?.store?.assignees?.map(assignee => assignee.username) || [];
+ }
+
+ if (fullText.startsWith('/assign @')) {
+ return this.members.filter(member => !this.assignees.includes(member.username));
+ }
+
+ if (fullText.startsWith('/unassign @')) {
+ return this.members.filter(member => this.assignees.includes(member.username));
+ }
+
+ return this.members;
+ },
},
render(createElement) {
return createElement('div', this.$slots.default);
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 0e05f4a4622..89844f07e7e 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -4,21 +4,25 @@ import '~/behaviors/markdown/render_gfm';
import { unescape } from 'lodash';
import { __, sprintf } from '~/locale';
import { stripHtml } from '~/lib/utils/text_utility';
-import Flash from '../../../flash';
-import GLForm from '../../../gl_form';
-import markdownHeader from './header.vue';
-import markdownToolbar from './toolbar.vue';
-import icon from '../icon.vue';
+import Flash from '~/flash';
+import GLForm from '~/gl_form';
+import MarkdownHeader from './header.vue';
+import MarkdownToolbar from './toolbar.vue';
+import Icon from '../icon.vue';
+import GlMentions from '~/vue_shared/components/gl_mentions.vue';
import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import axios from '~/lib/utils/axios_utils';
export default {
components: {
- markdownHeader,
- markdownToolbar,
- icon,
+ GlMentions,
+ MarkdownHeader,
+ MarkdownToolbar,
+ Icon,
Suggestions,
},
+ mixins: [glFeatureFlagsMixin()],
props: {
isSubmitting: {
type: Boolean,
@@ -159,12 +163,10 @@ export default {
},
},
mounted() {
- /*
- GLForm class handles all the toolbar buttons
- */
+ // GLForm class handles all the toolbar buttons
return new GLForm($(this.$refs['gl-form']), {
emojis: this.enableAutocomplete,
- members: this.enableAutocomplete,
+ members: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete,
issues: this.enableAutocomplete,
mergeRequests: this.enableAutocomplete,
epics: this.enableAutocomplete,
@@ -243,7 +245,10 @@ export default {
/>
<div v-show="!previewMarkdown" class="md-write-holder">
<div class="zen-backdrop">
- <slot name="textarea"></slot>
+ <gl-mentions v-if="glFeatures.tributeAutocomplete">
+ <slot name="textarea"></slot>
+ </gl-mentions>
+ <slot v-else name="textarea"></slot>
<a
class="zen-control zen-control-leave js-zen-leave gl-text-gray-700"
href="#"