diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-20 12:16:11 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-20 12:16:11 +0300 |
commit | edaa33dee2ff2f7ea3fac488d41558eb5f86d68c (patch) | |
tree | 11f143effbfeba52329fb7afbd05e6e2a3790241 /app/assets/javascripts/vue_shared/components | |
parent | d8a5691316400a0f7ec4f83832698f1988eb27c1 (diff) |
Add latest changes from gitlab-org/gitlab@14-7-stable-eev14.7.0-rc42
Diffstat (limited to 'app/assets/javascripts/vue_shared/components')
21 files changed, 388 insertions, 90 deletions
diff --git a/app/assets/javascripts/vue_shared/components/actions_button.vue b/app/assets/javascripts/vue_shared/components/actions_button.vue index bab13fe7c75..6db18afe51c 100644 --- a/app/assets/javascripts/vue_shared/components/actions_button.vue +++ b/app/assets/javascripts/vue_shared/components/actions_button.vue @@ -66,6 +66,7 @@ export default { :variant="variant" :category="category" split + data-qa-selector="action_dropdown" @click="handleClick(selectedAction, $event)" > <template #button-content> @@ -79,6 +80,7 @@ export default { :is-check-item="true" :is-checked="action.key === selectedAction.key" :secondary-text="action.secondaryText" + :data-qa-selector="`${action.key}_menu_item`" :data-testid="`action_${action.key}`" @click="handleItemClick(action)" > diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue index 400be3ef688..f907b64608c 100644 --- a/app/assets/javascripts/vue_shared/components/clipboard_button.vue +++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue @@ -13,9 +13,23 @@ * /> */ import { GlButton, GlTooltipDirective } from '@gitlab/ui'; +import { uniqueId } from 'lodash'; + +import { __ } from '~/locale'; +import { + CLIPBOARD_SUCCESS_EVENT, + CLIPBOARD_ERROR_EVENT, + I18N_ERROR_MESSAGE, +} from '~/behaviors/copy_to_clipboard'; export default { name: 'ClipboardButton', + i18n: { + copied: __('Copied'), + error: I18N_ERROR_MESSAGE, + }, + CLIPBOARD_SUCCESS_EVENT, + CLIPBOARD_ERROR_EVENT, directives: { GlTooltip: GlTooltipDirective, }, @@ -72,6 +86,13 @@ export default { default: 'default', }, }, + data() { + return { + localTitle: this.title, + titleTimeout: null, + id: null, + }; + }, computed: { clipboardText() { if (this.gfm !== null) { @@ -79,25 +100,50 @@ export default { } return this.text; }, + tooltipDirectiveOptions() { + return { + placement: this.tooltipPlacement, + container: this.tooltipContainer, + boundary: this.tooltipBoundary, + }; + }, + }, + created() { + this.id = uniqueId('clipboard-button-'); + }, + methods: { + updateTooltip(title) { + this.localTitle = title; + this.$root.$emit('bv::show::tooltip', this.id); + + clearTimeout(this.titleTimeout); + + this.titleTimeout = setTimeout(() => { + this.localTitle = this.title; + this.$root.$emit('bv::hide::tooltip', this.id); + }, 1000); + }, }, }; </script> <template> <gl-button - v-gl-tooltip.hover.blur.viewport="{ - placement: tooltipPlacement, - container: tooltipContainer, - boundary: tooltipBoundary, - }" + :id="id" + ref="copyButton" + v-gl-tooltip.hover.focus.click.viewport="tooltipDirectiveOptions" :class="cssClass" - :title="title" + :title="localTitle" :data-clipboard-text="clipboardText" + data-clipboard-handle-tooltip="false" :category="category" :size="size" icon="copy-to-clipboard" - :aria-label="__('Copy this value')" :variant="variant" + :aria-label="localTitle" + aria-live="polite" + @[$options.CLIPBOARD_SUCCESS_EVENT]="updateTooltip($options.i18n.copied)" + @[$options.CLIPBOARD_ERROR_EVENT]="updateTooltip($options.i18n.error)" v-on="$listeners" > <slot></slot> diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger.vue b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger.vue index f93415ced45..e12e06a2454 100644 --- a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger.vue +++ b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger.vue @@ -36,6 +36,11 @@ export default { required: false, default: 'confirm-danger-button', }, + buttonVariant: { + type: String, + required: false, + default: 'danger', + }, }, modalId: CONFIRM_DANGER_MODAL_ID, }; @@ -45,7 +50,7 @@ export default { <gl-button v-gl-modal="$options.modalId" :class="buttonClass" - variant="danger" + :variant="buttonVariant" :disabled="disabled" :data-testid="buttonTestid" >{{ buttonText }}</gl-button diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js index 18fa297da87..6629b293eb9 100644 --- a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js +++ b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js @@ -11,7 +11,9 @@ const Template = (args, { argTypes }) => ({ props: Object.keys(argTypes), template: '<confirm-danger v-bind="$props" />', provide: { - confirmDangerMessage: 'You require more Vespene Gas', + additionalInformation: args.additionalInformation || null, + confirmDangerMessage: args.confirmDangerMessage || 'You require more Vespene Gas', + htmlConfirmationMessage: args.confirmDangerMessage || false, }, }); @@ -26,3 +28,16 @@ Disabled.args = { ...Default.args, disabled: true, }; + +export const AdditionalInformation = Template.bind({}); +AdditionalInformation.args = { + ...Default.args, + additionalInformation: 'This replaces the default warning information', +}; + +export const HtmlMessage = Template.bind({}); +HtmlMessage.args = { + ...Default.args, + confirmDangerMessage: 'You strongly require more <strong>Vespene Gas</strong>', + htmlConfirmationMessage: true, +}; diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue index 5bbe44b20b3..88890b3332d 100644 --- a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue +++ b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue @@ -1,5 +1,12 @@ <script> -import { GlAlert, GlModal, GlFormGroup, GlFormInput, GlSprintf } from '@gitlab/ui'; +import { + GlAlert, + GlModal, + GlFormGroup, + GlFormInput, + GlSafeHtmlDirective as SafeHtml, + GlSprintf, +} from '@gitlab/ui'; import { CONFIRM_DANGER_MODAL_BUTTON, CONFIRM_DANGER_MODAL_TITLE, @@ -17,13 +24,22 @@ export default { GlFormInput, GlSprintf, }, + directives: { + SafeHtml, + }, inject: { + htmlConfirmationMessage: { + default: false, + }, confirmDangerMessage: { default: '', }, confirmButtonText: { default: CONFIRM_DANGER_MODAL_BUTTON, }, + additionalInformation: { + default: CONFIRM_DANGER_WARNING, + }, }, props: { modalId: { @@ -81,9 +97,12 @@ export default { :dismissible="false" class="gl-mb-4" > - {{ confirmDangerMessage }} + <span v-if="htmlConfirmationMessage" v-safe-html="confirmDangerMessage"></span> + <span v-else> + {{ confirmDangerMessage }} + </span> </gl-alert> - <p data-testid="confirm-danger-warning">{{ $options.i18n.CONFIRM_DANGER_WARNING }}</p> + <p data-testid="confirm-danger-warning">{{ additionalInformation }}</p> <p data-testid="confirm-danger-phrase"> <gl-sprintf :message="$options.i18n.CONFIRM_DANGER_PHRASE_TEXT"> <template #phrase_code> diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue index 7c1828f2294..5cdf7b6a3b2 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue @@ -332,7 +332,7 @@ export default { v-if="showCheckbox" class="gl-align-self-center" :checked="checkboxChecked" - @input="$emit('checked-input', $event)" + @change="$emit('checked-input', $event)" > <span class="gl-sr-only">{{ __('Select all') }}</span> </gl-form-checkbox> diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue index 06478a89721..b70317b2ec4 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue @@ -4,7 +4,7 @@ import { compact } from 'lodash'; import createFlash from '~/flash'; import { __ } from '~/locale'; -import { DEFAULT_LABEL_ANY } from '../constants'; +import { DEFAULT_NONE_ANY } from '../constants'; import BaseToken from './base_token.vue'; @@ -36,7 +36,7 @@ export default { }, computed: { defaultAuthors() { - return this.config.defaultAuthors || [DEFAULT_LABEL_ANY]; + return this.config.defaultAuthors || DEFAULT_NONE_ANY; }, preloadedAuthors() { return this.config.preloadedAuthors || []; diff --git a/app/assets/javascripts/vue_shared/components/gitlab_version_check.vue b/app/assets/javascripts/vue_shared/components/gitlab_version_check.vue new file mode 100644 index 00000000000..acddf16bd27 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/gitlab_version_check.vue @@ -0,0 +1,67 @@ +<script> +import { GlBadge } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import axios from '~/lib/utils/axios_utils'; + +const STATUS_TYPES = { + SUCCESS: 'success', + WARNING: 'warning', + DANGER: 'danger', +}; + +export default { + name: 'GitlabVersionCheck', + components: { + GlBadge, + }, + props: { + size: { + type: String, + required: false, + default: 'md', + }, + }, + data() { + return { + status: null, + }; + }, + computed: { + title() { + if (this.status === STATUS_TYPES.SUCCESS) { + return s__('VersionCheck|Up to date'); + } else if (this.status === STATUS_TYPES.WARNING) { + return s__('VersionCheck|Update available'); + } else if (this.status === STATUS_TYPES.DANGER) { + return s__('VersionCheck|Update ASAP'); + } + + return null; + }, + }, + created() { + this.checkGitlabVersion(); + }, + methods: { + checkGitlabVersion() { + axios + .get('/admin/version_check.json') + .then((res) => { + if (res.data) { + this.status = res.data.severity; + } + }) + .catch(() => { + // Silently fail + this.status = null; + }); + }, + }, +}; +</script> + +<template> + <gl-badge v-if="status" class="version-check-badge" :variant="status" :size="size">{{ + title + }}</gl-badge> +</template> diff --git a/app/assets/javascripts/vue_shared/components/line_numbers.vue b/app/assets/javascripts/vue_shared/components/line_numbers.vue index 7e17cca3dcc..11caf3be00a 100644 --- a/app/assets/javascripts/vue_shared/components/line_numbers.vue +++ b/app/assets/javascripts/vue_shared/components/line_numbers.vue @@ -12,31 +12,6 @@ export default { required: true, }, }, - data() { - return { - currentlyHighlightedLine: null, - }; - }, - mounted() { - this.scrollToLine(); - }, - methods: { - scrollToLine(hash = window.location.hash) { - const lineToHighlight = hash && this.$el.querySelector(hash); - - if (!lineToHighlight) { - return; - } - - if (this.currentlyHighlightedLine) { - this.currentlyHighlightedLine.classList.remove('hll'); - } - - lineToHighlight.classList.add('hll'); - this.currentlyHighlightedLine = lineToHighlight; - lineToHighlight.scrollIntoView({ behavior: 'smooth', block: 'center' }); - }, - }, }; </script> <template> @@ -45,10 +20,9 @@ export default { v-for="line in lines" :id="`L${line}`" :key="line" - class="diff-line-num" - :href="`#L${line}`" + class="diff-line-num gl-shadow-none!" + :to="`#LC${line}`" :data-line-number="line" - @click="scrollToLine(`#L${line}`)" > <gl-icon :size="12" name="link" /> {{ line }} diff --git a/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue b/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue index ce7cbafb97d..709d3592828 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue @@ -67,7 +67,7 @@ export default { <gl-button class="gl-w-auto! gl-mt-3 gl-text-center! gl-hover-text-white! gl-transition-medium! float-right" category="primary" - variant="success" + variant="confirm" data-qa-selector="commit_with_custom_message_button" @click="onApply" > diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index 86f04c78ebe..5c86c928ce3 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -2,7 +2,7 @@ import { GlIcon } from '@gitlab/ui'; import $ from 'jquery'; import '~/behaviors/markdown/render_gfm'; -import { unescape } from 'lodash'; +import { debounce, unescape } from 'lodash'; import createFlash from '~/flash'; import GLForm from '~/gl_form'; import axios from '~/lib/utils/axios_utils'; @@ -110,7 +110,7 @@ export default { return { markdownPreview: '', referencedCommands: '', - referencedUsers: '', + referencedUsers: [], hasSuggestion: false, markdownPreviewLoading: false, previewMarkdown: false, @@ -188,6 +188,24 @@ export default { }); } }, + + textareaValue: { + immediate: true, + handler(textareaValue, oldVal) { + const all = /@all([^\w._-]|$)/; + const hasAll = all.test(textareaValue); + const hadAll = all.test(oldVal); + + const justAddedAll = !hadAll && hasAll; + const justRemovedAll = hadAll && !hasAll; + + if (justAddedAll) { + this.debouncedFetchMarkdown(); + } else if (justRemovedAll) { + this.referencedUsers = []; + } + }, + }, }, mounted() { // GLForm class handles all the toolbar buttons @@ -222,9 +240,9 @@ export default { if (this.textareaValue) { this.markdownPreviewLoading = true; this.markdownPreview = __('Loading…'); - axios - .post(this.markdownPreviewPath, { text: this.textareaValue }) - .then((response) => this.renderMarkdown(response.data)) + + this.fetchMarkdown() + .then((data) => this.renderMarkdown(data)) .catch(() => createFlash({ message: __('Error loading markdown preview'), @@ -239,17 +257,28 @@ export default { this.previewMarkdown = false; }, + fetchMarkdown() { + return axios.post(this.markdownPreviewPath, { text: this.textareaValue }).then(({ data }) => { + const { references } = data; + if (references) { + this.referencedCommands = references.commands; + this.referencedUsers = references.users; + this.hasSuggestion = references.suggestions?.length > 0; + this.suggestions = references.suggestions; + } + + return data; + }); + }, + + debouncedFetchMarkdown: debounce(function debouncedFetchMarkdown() { + return this.fetchMarkdown(); + }, 400), + renderMarkdown(data = {}) { this.markdownPreviewLoading = false; this.markdownPreview = data.body || __('Nothing to preview.'); - if (data.references) { - this.referencedCommands = data.references.commands; - this.referencedUsers = data.references.users; - this.hasSuggestion = data.references.suggestions && data.references.suggestions.length; - this.suggestions = data.references.suggestions; - } - this.$nextTick() .then(() => $(this.$refs['markdown-preview']).renderGFM()) .catch(() => @@ -326,18 +355,14 @@ export default { v-html="markdownPreview /* eslint-disable-line vue/no-v-html */" ></div> </template> - <template v-if="previewMarkdown && !markdownPreviewLoading"> - <div - v-if="referencedCommands" - class="referenced-commands" - v-html="referencedCommands /* eslint-disable-line vue/no-v-html */" - ></div> - <div v-if="shouldShowReferencedUsers" class="referenced-users"> - <gl-icon name="warning-solid" /> - <span - v-html="addMultipleToDiscussionWarning /* eslint-disable-line vue/no-v-html */" - ></span> - </div> - </template> + <div + v-if="referencedCommands && previewMarkdown && !markdownPreviewLoading" + class="referenced-commands" + v-html="referencedCommands /* eslint-disable-line vue/no-v-html */" + ></div> + <div v-if="shouldShowReferencedUsers" class="referenced-users"> + <gl-icon name="warning-solid" /> + <span v-html="addMultipleToDiscussionWarning /* eslint-disable-line vue/no-v-html */"></span> + </div> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/modal_copy_button.vue b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue index 38afd56bae6..d4f50e347cb 100644 --- a/app/assets/javascripts/vue_shared/components/modal_copy_button.vue +++ b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue @@ -1,6 +1,6 @@ <script> import { GlButton, GlTooltipDirective } from '@gitlab/ui'; -import Clipboard from 'clipboard'; +import ClipboardJS from 'clipboard'; import { uniqueId } from 'lodash'; import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; @@ -69,7 +69,7 @@ export default { }, mounted() { this.$nextTick(() => { - this.clipboard = new Clipboard(this.$el, { + this.clipboard = new ClipboardJS(this.$el, { container: document.querySelector(`${this.modalDomId} div.modal-content`) || document.getElementById(this.container) || diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue index 13a6dd43207..0fa64a29b3a 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue @@ -179,6 +179,9 @@ export default { this.searchKey = ''; this.setFocus(); }, + selectFirstItem() { + this.$refs.dropdownContentsView.selectFirstItem(); + }, }, }; </script> @@ -204,11 +207,13 @@ export default { @toggleDropdownContentsCreateView="toggleDropdownContent" @closeDropdown="$emit('closeDropdown')" @input="debouncedSearchKeyUpdate" + @searchEnter="selectFirstItem" /> </template> <template #default> <component :is="dropdownContentsView" + ref="dropdownContentsView" v-model="localSelectedLabels" :search-key="searchKey" :allow-multiselect="allowMultiselect" diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue index da626a21b14..b99083713a8 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue @@ -1,5 +1,12 @@ <script> -import { GlTooltipDirective, GlButton, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui'; +import { + GlAlert, + GlTooltipDirective, + GlButton, + GlFormInput, + GlLink, + GlLoadingIcon, +} from '@gitlab/ui'; import produce from 'immer'; import createFlash from '~/flash'; import { __ } from '~/locale'; @@ -11,6 +18,7 @@ const errorMessage = __('Error creating label.'); export default { components: { + GlAlert, GlButton, GlFormInput, GlLink, @@ -42,6 +50,7 @@ export default { labelTitle: '', selectedColor: '', labelCreateInProgress: false, + error: undefined, }; }, computed: { @@ -111,13 +120,14 @@ export default { ) => this.updateLabelsInCache(store, label), }); if (labelCreate.errors.length) { - createFlash({ message: errorMessage }); + [this.error] = labelCreate.errors; + } else { + this.$emit('hideCreateView'); } } catch { createFlash({ message: errorMessage }); } this.labelCreateInProgress = false; - this.$emit('hideCreateView'); }, }, }; @@ -126,6 +136,9 @@ export default { <template> <div class="labels-select-contents-create js-labels-create"> <div class="dropdown-input"> + <gl-alert v-if="error" variant="danger" :dismissible="false" class="gl-mb-3"> + {{ error }} + </gl-alert> <gl-form-input v-model.trim="labelTitle" :placeholder="__('Name new label')" diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue index e9a2d7747e2..ae179ef93c7 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue @@ -84,6 +84,9 @@ export default { showNoMatchingResultsMessage() { return Boolean(this.searchKey) && this.visibleLabels.length === 0; }, + shouldHighlightFirstItem() { + return this.searchKey !== '' && this.visibleLabels.length > 0; + }, }, methods: { isLabelSelected(label) { @@ -128,6 +131,11 @@ export default { onDropdownAppear() { this.isVisible = true; }, + selectFirstItem() { + if (this.shouldHighlightFirstItem) { + this.handleLabelClick(this.visibleLabels[0]); + } + }, }, }; </script> @@ -143,11 +151,13 @@ export default { /> <template v-else> <gl-dropdown-item - v-for="label in visibleLabels" + v-for="(label, index) in visibleLabels" :key="label.id" :is-checked="isLabelSelected(label)" :is-check-centered="true" :is-check-item="true" + :active="shouldHighlightFirstItem && index === 0" + active-class="is-focused" data-testid="labels-list" @click.native.capture.stop="handleLabelClick(label)" > diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_header.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_header.vue index 7a0f20b0c83..faad69732dd 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_header.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_header.vue @@ -83,6 +83,7 @@ export default { data-qa-selector="dropdown_input_field" data-testid="dropdown-input-field" @input="$emit('input', $event)" + @keydown.enter="$emit('searchEnter', $event)" /> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/label_item.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/label_item.vue index f27f0b4e34c..caeee2df7e5 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/label_item.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/label_item.vue @@ -10,7 +10,7 @@ export default { </script> <template> - <div> + <div class="gl-display-flex gl-align-items-center"> <span class="dropdown-label-box gl-flex-shrink-0 gl-top-0 gl-mr-3" :style="{ 'background-color': label.color }" diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue index 3adda69b892..f53b75df4eb 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue @@ -289,6 +289,7 @@ export default { 'is-standalone': isDropdownVariantStandalone(variant), 'is-embedded': isDropdownVariantEmbedded(variant), }" + data-testid="sidebar-labels" data-qa-selector="labels_block" > <template v-if="isDropdownVariantSidebar(variant)"> diff --git a/app/assets/javascripts/vue_shared/components/source_editor.vue b/app/assets/javascripts/vue_shared/components/source_editor.vue index 8a0fef36079..011cad4267c 100644 --- a/app/assets/javascripts/vue_shared/components/source_editor.vue +++ b/app/assets/javascripts/vue_shared/components/source_editor.vue @@ -97,7 +97,7 @@ export default { ref="editor" data-editor-loading data-qa-selector="source_editor_container" - @[$options.readyEvent]="$emit($options.readyEvent)" + @[$options.readyEvent]="$emit($options.readyEvent, $event)" > <pre class="editor-loading-content">{{ value }}</pre> </div> diff --git a/app/assets/javascripts/vue_shared/components/source_viewer.vue b/app/assets/javascripts/vue_shared/components/source_viewer.vue index 8f0d051543f..99895926653 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/source_viewer.vue @@ -1,6 +1,9 @@ <script> import { GlSafeHtmlDirective } from '@gitlab/ui'; import LineNumbers from '~/vue_shared/components/line_numbers.vue'; +import { sanitize } from '~/lib/dompurify'; + +const LINE_SELECT_CLASS_NAME = 'hll'; export default { components: { @@ -46,7 +49,15 @@ export default { } } - return highlightedContent; + return this.wrapLines(highlightedContent); + }, + }, + watch: { + highlightedContent() { + this.$nextTick(() => this.selectLine()); + }, + $route() { + this.selectLine(); }, }, async mounted() { @@ -73,16 +84,40 @@ export default { return languageDefinition; }, + wrapLines(content) { + return ( + content && + content + .split('\n') + .map((line, i) => `<span id="LC${i + 1}" class="line">${line}</span>`) + .join('\r\n') + ); + }, + selectLine() { + const hash = sanitize(this.$route.hash); + const lineToSelect = hash && this.$el.querySelector(hash); + + if (!lineToSelect) { + return; + } + + if (this.$options.currentlySelectedLine) { + this.$options.currentlySelectedLine.classList.remove(LINE_SELECT_CLASS_NAME); + } + + lineToSelect.classList.add(LINE_SELECT_CLASS_NAME); + this.$options.currentlySelectedLine = lineToSelect; + lineToSelect.scrollIntoView({ behavior: 'smooth', block: 'center' }); + }, }, userColorScheme: window.gon.user_color_scheme, + currentlySelectedLine: null, }; </script> <template> - <div class="file-content code" :class="$options.userColorScheme"> + <div class="file-content code js-syntax-highlight" :class="$options.userColorScheme"> <line-numbers :lines="lineNumbers" /> - <pre - class="code gl-pl-3!" - ><code v-safe-html="highlightedContent" class="gl-white-space-pre-wrap!"></code> + <pre class="code"><code v-safe-html="highlightedContent"></code> </pre> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/web_ide_link.vue b/app/assets/javascripts/vue_shared/components/web_ide_link.vue index 6da2d39a95a..f02cd5c4e2e 100644 --- a/app/assets/javascripts/vue_shared/components/web_ide_link.vue +++ b/app/assets/javascripts/vue_shared/components/web_ide_link.vue @@ -1,6 +1,7 @@ <script> import $ from 'jquery'; -import { __ } from '~/locale'; +import { GlModal, GlSprintf, GlLink } from '@gitlab/ui'; +import { s__, __ } from '~/locale'; import ActionsButton from '~/vue_shared/components/actions_button.vue'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; @@ -12,6 +13,19 @@ export default { components: { ActionsButton, LocalStorageSync, + GlModal, + GlSprintf, + GlLink, + }, + i18n: { + modal: { + title: __('Enable Gitpod?'), + content: s__( + 'Gitpod|To use Gitpod you must first enable the feature in the integrations section of your %{linkStart}user preferences%{linkEnd}.', + ), + actionCancelText: __('Cancel'), + actionPrimaryText: __('Enable Gitpod'), + }, }, props: { isFork: { @@ -49,6 +63,16 @@ export default { required: false, default: false, }, + userPreferencesGitpodPath: { + type: String, + required: false, + default: '', + }, + userProfileEnableGitpodPath: { + type: String, + required: false, + default: '', + }, editUrl: { type: String, required: false, @@ -74,10 +98,16 @@ export default { required: false, default: '', }, + disableForkModal: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { selection: KEY_WEB_IDE, + showEnableGitpodModal: false, }; }, computed: { @@ -93,8 +123,12 @@ export default { ? { href: '#modal-confirm-fork-edit', handle: () => { - this.$emit('edit', 'simple'); - this.showModal('#modal-confirm-fork-edit'); + if (this.disableForkModal) { + this.$emit('edit', 'simple'); + return; + } + + this.showJQueryModal('#modal-confirm-fork-edit'); }, } : { href: this.editUrl }; @@ -132,8 +166,12 @@ export default { ? { href: '#modal-confirm-fork-webide', handle: () => { - this.$emit('edit', 'ide'); - this.showModal('#modal-confirm-fork-webide'); + if (this.disableForkModal) { + this.$emit('edit', 'ide'); + return; + } + + this.showJQueryModal('#modal-confirm-fork-webide'); }, } : { href: this.webIdeUrl }; @@ -154,14 +192,23 @@ export default { gitpodActionText() { return this.gitpodText || __('Gitpod'); }, + computedShowGitpodButton() { + return ( + this.showGitpodButton && this.userPreferencesGitpodPath && this.userProfileEnableGitpodPath + ); + }, gitpodAction() { - if (!this.showGitpodButton) { + if (!this.computedShowGitpodButton) { return null; } const handleOptions = this.gitpodEnabled ? { href: this.gitpodUrl } - : { href: '#modal-enable-gitpod', handle: () => this.showModal('#modal-enable-gitpod') }; + : { + handle: () => { + this.showModal('showEnableGitpodModal'); + }, + }; const secondaryText = __('Launch a ready-to-code development environment for your project.'); @@ -176,14 +223,36 @@ export default { ...handleOptions, }; }, + enableGitpodModalProps() { + return { + 'modal-id': 'enable-gitpod-modal', + size: 'sm', + title: this.$options.i18n.modal.title, + 'action-cancel': { + text: this.$options.i18n.modal.actionCancelText, + }, + 'action-primary': { + text: this.$options.i18n.modal.actionPrimaryText, + attributes: { + variant: 'confirm', + category: 'primary', + href: this.userProfileEnableGitpodPath, + 'data-method': 'put', + }, + }, + }; + }, }, methods: { select(key) { this.selection = key; }, - showModal(id) { + showJQueryModal(id) { $(id).modal('show'); }, + showModal(dataKey) { + this[dataKey] = true; + }, }, }; </script> @@ -202,5 +271,16 @@ export default { :value="selection" @input="select" /> + <gl-modal + v-if="computedShowGitpodButton && !gitpodEnabled" + v-model="showEnableGitpodModal" + v-bind="enableGitpodModalProps" + > + <gl-sprintf :message="$options.i18n.modal.content"> + <template #link="{ content }"> + <gl-link :href="userPreferencesGitpodPath">{{ content }}</gl-link> + </template> + </gl-sprintf> + </gl-modal> </div> </template> |