diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-19 15:09:07 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-19 15:09:07 +0300 |
commit | 235dc61f473cb7f02a9453a59fb26d293e05b092 (patch) | |
tree | 06ee9d4f1356e486c5d9c6541a89a31bc1ded670 /app | |
parent | 12866a3931a2834d911728364d6604c4dd97c004 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/blob_edit/constants.js | 4 | ||||
-rw-r--r-- | app/assets/javascripts/blob_edit/edit_blob.js | 71 | ||||
-rw-r--r-- | app/assets/javascripts/editor/editor_lite.js | 20 | ||||
-rw-r--r-- | app/assets/javascripts/gl_form.js | 6 | ||||
-rw-r--r-- | app/assets/javascripts/issue_show/components/pinned_links.vue | 65 | ||||
-rw-r--r-- | app/assets/javascripts/issue_show/constants.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue | 20 | ||||
-rw-r--r-- | app/assets/javascripts/static_site_editor/graphql/typedefs.graphql | 2 | ||||
-rw-r--r-- | app/assets/javascripts/vue_shared/components/gl_mentions.vue | 30 | ||||
-rw-r--r-- | app/assets/javascripts/vue_shared/components/markdown/field.vue | 31 | ||||
-rw-r--r-- | app/assets/stylesheets/framework/gitlab_theme.scss | 21 | ||||
-rw-r--r-- | app/controllers/projects/issues_controller.rb | 1 | ||||
-rw-r--r-- | app/models/plan.rb | 2 | ||||
-rw-r--r-- | app/views/projects/blob/_editor.html.haml | 2 | ||||
-rw-r--r-- | app/views/projects/blob/edit.html.haml | 5 | ||||
-rw-r--r-- | app/views/projects/blob/new.html.haml | 6 |
16 files changed, 204 insertions, 85 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="#" diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss index 8d5afe1d312..c1ca4a6f3d1 100644 --- a/app/assets/stylesheets/framework/gitlab_theme.scss +++ b/app/assets/stylesheets/framework/gitlab_theme.scss @@ -74,19 +74,6 @@ } } - &:focus:hover, - &:focus { - &.header-user-dropdown-toggle .header-user-notification-dot { - border-color: $white; - } - } - - &:hover { - &.header-user-dropdown-toggle .header-user-notification-dot { - border-color: $nav-svg-color + 33; - } - } - &:hover, &:focus { @include media-breakpoint-up(sm) { @@ -96,6 +83,10 @@ svg { fill: currentColor; } + + &.header-user-dropdown-toggle .header-user-notification-dot { + border-color: $nav-svg-color + 33; + } } } @@ -109,6 +100,10 @@ fill: $nav-svg-color; } } + + &.header-user-dropdown-toggle .header-user-notification-dot { + border-color: $white; + } } .impersonated-user, diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 693329848de..72fea21b52b 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -46,6 +46,7 @@ class Projects::IssuesController < Projects::ApplicationController before_action do push_frontend_feature_flag(:vue_issuable_sidebar, project.group) + push_frontend_feature_flag(:tribute_autocomplete, @project) end before_action only: :show do diff --git a/app/models/plan.rb b/app/models/plan.rb index acac5f9aeae..b4091e0a755 100644 --- a/app/models/plan.rb +++ b/app/models/plan.rb @@ -27,7 +27,7 @@ class Plan < ApplicationRecord end def actual_limits - self.limits || PlanLimits.new + self.limits || self.build_limits end def default? diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index 032df24a603..67c78620f15 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -40,7 +40,7 @@ = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2', tabindex: '-1' .file-editor.code - %pre.js-edit-mode-pane.qa-editor#editor= params[:content] || local_assigns[:blob_data] + %pre.js-edit-mode-pane.qa-editor#editor{ data: { 'editor-loading': true } }= params[:content] || local_assigns[:blob_data] - if local_assigns[:path] .js-edit-mode-pane#preview.hide .center diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 870e37488cf..3d84adbc49a 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -1,7 +1,8 @@ - breadcrumb_title "Repository" - page_title "Edit", @blob.path, @ref -- content_for :page_specific_javascripts do - = page_specific_javascript_tag('lib/ace.js') +- unless Feature.enabled?(:monaco_blobs) + - content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/ace.js') - if @conflict .alert.alert-danger diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 8f166e9aa16..f9abcffeeb4 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -1,7 +1,9 @@ - breadcrumb_title "Repository" - page_title "New File", @path.presence, @ref -- content_for :page_specific_javascripts do - = page_specific_javascript_tag('lib/ace.js') +- unless Feature.enabled?(:monaco_blobs) + - content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/ace.js') + .editor-title-row %h3.page-title.blob-new-page-title New file |