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>2022-01-20 12:16:11 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-01-20 12:16:11 +0300
commitedaa33dee2ff2f7ea3fac488d41558eb5f86d68c (patch)
tree11f143effbfeba52329fb7afbd05e6e2a3790241 /app/assets/javascripts/vue_shared/components
parentd8a5691316400a0f7ec4f83832698f1988eb27c1 (diff)
Add latest changes from gitlab-org/gitlab@14-7-stable-eev14.7.0-rc42
Diffstat (limited to 'app/assets/javascripts/vue_shared/components')
-rw-r--r--app/assets/javascripts/vue_shared/components/actions_button.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/clipboard_button.vue60
-rw-r--r--app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js17
-rw-r--r--app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue25
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/gitlab_version_check.vue67
-rw-r--r--app/assets/javascripts/vue_shared/components/line_numbers.vue30
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue75
-rw-r--r--app/assets/javascripts/vue_shared/components/modal_copy_button.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue5
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue19
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue12
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_header.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/label_item.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/source_editor.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer.vue45
-rw-r--r--app/assets/javascripts/vue_shared/components/web_ide_link.vue96
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>