summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/vue_shared/components
diff options
context:
space:
mode:
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/awards_list.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js8
-rw-r--r--app/assets/javascripts/vue_shared/components/blob_viewers/rich_viewer.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/changed_file_icon.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_area_chart.vue29
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue9
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_icon.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/deprecated_project_avatar/default.vue (renamed from app/assets/javascripts/vue_shared/components/project_avatar/default.vue)1
-rw-r--r--app/assets/javascripts/vue_shared/components/deprecated_project_avatar/image.vue (renamed from app/assets/javascripts/vue_shared/components/project_avatar/image.vue)0
-rw-r--r--app/assets/javascripts/vue_shared/components/diff_viewer/viewers/renamed.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/dismissible_alert.vue9
-rw-r--r--app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/expand_button.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/file_finder/index.vue5
-rw-r--r--app/assets/javascripts/vue_shared/components/file_icon.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js3
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js26
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue16
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue77
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue21
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue20
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue14
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue14
-rw-r--r--app/assets/javascripts/vue_shared/components/form/form_footer_actions.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue14
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue5
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_row.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestions.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue10
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/oncall_schedules_list.vue10
-rw-r--r--app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/project_avatar.stories.js30
-rw-r--r--app/assets/javascripts/vue_shared/components/project_avatar.vue45
-rw-r--r--app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/select2_select.vue48
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue18
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue14
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/actions.js10
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js12
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_title.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue43
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql15
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue24
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/actions.js6
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutations.js12
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.stories.js23
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.vue56
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/utils.js5
-rw-r--r--app/assets/javascripts/vue_shared/components/source_editor.vue (renamed from app/assets/javascripts/vue_shared/components/editor_lite.vue)8
-rw-r--r--app/assets/javascripts/vue_shared/components/todo_button.vue28
-rw-r--r--app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/user_select/user_select.vue23
-rw-r--r--app/assets/javascripts/vue_shared/components/web_ide_link.vue10
66 files changed, 513 insertions, 307 deletions
diff --git a/app/assets/javascripts/vue_shared/components/actions_button.vue b/app/assets/javascripts/vue_shared/components/actions_button.vue
index 13472b48e84..bab13fe7c75 100644
--- a/app/assets/javascripts/vue_shared/components/actions_button.vue
+++ b/app/assets/javascripts/vue_shared/components/actions_button.vue
@@ -68,7 +68,7 @@ export default {
split
@click="handleClick(selectedAction, $event)"
>
- <template slot="button-content">
+ <template #button-content>
<span class="gl-new-dropdown-button-text" v-bind="selectedAction.attrs">
{{ selectedAction.text }}
</span>
diff --git a/app/assets/javascripts/vue_shared/components/awards_list.vue b/app/assets/javascripts/vue_shared/components/awards_list.vue
index e6d9a38d1fb..f4c73d12923 100644
--- a/app/assets/javascripts/vue_shared/components/awards_list.vue
+++ b/app/assets/javascripts/vue_shared/components/awards_list.vue
@@ -93,12 +93,12 @@ export default {
return {
name,
list,
- title: this.getAwardListTitle(list),
+ title: this.getAwardListTitle(list, name),
classes: this.getAwardClassBindings(list),
html: glEmojiTag(name),
};
},
- getAwardListTitle(awardsList) {
+ getAwardListTitle(awardsList, name) {
if (!awardsList.length) {
return '';
}
@@ -128,7 +128,7 @@ export default {
// We have 10+ awarded user, join them with comma and add `and x more`.
if (remainingAwardList.length) {
title = sprintf(
- __(`%{listToShow}, and %{awardsListLength} more.`),
+ __(`%{listToShow}, and %{awardsListLength} more`),
{
listToShow: namesToShow.join(', '),
awardsListLength: remainingAwardList.length,
@@ -146,7 +146,7 @@ export default {
title = namesToShow.join(__(' and '));
}
- return title;
+ return title + sprintf(__(' reacted with :%{name}:'), { name });
},
handleAward(awardName) {
if (!this.canAwardEmoji) {
diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js b/app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js
index 9c2ed5abf04..0c1d55ae707 100644
--- a/app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js
+++ b/app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js
@@ -5,7 +5,13 @@ export default {
props: {
content: {
type: String,
- required: true,
+ required: false,
+ default: null,
+ },
+ richViewer: {
+ type: String,
+ default: '',
+ required: false,
},
type: {
type: String,
diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/rich_viewer.vue b/app/assets/javascripts/vue_shared/components/blob_viewers/rich_viewer.vue
index a8a053c0d9e..dc4d1bd56e9 100644
--- a/app/assets/javascripts/vue_shared/components/blob_viewers/rich_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/blob_viewers/rich_viewer.vue
@@ -18,5 +18,5 @@ export default {
};
</script>
<template>
- <markdown-field-view ref="content" v-safe-html="content" />
+ <markdown-field-view ref="content" v-safe-html="richViewer || content" />
</template>
diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue b/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue
index f6ab3cac536..0589b47edbd 100644
--- a/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue
@@ -9,8 +9,8 @@ export default {
name: 'SimpleViewer',
components: {
GlIcon,
- EditorLite: () =>
- import(/* webpackChunkName: 'EditorLite' */ '~/vue_shared/components/editor_lite.vue'),
+ SourceEditor: () =>
+ import(/* webpackChunkName: 'SourceEditor' */ '~/vue_shared/components/source_editor.vue'),
},
mixins: [ViewerMixin, glFeatureFlagsMixin()],
inject: ['blobHash'],
@@ -53,7 +53,7 @@ export default {
</script>
<template>
<div>
- <editor-lite
+ <source-editor
v-if="isRawContent && refactorBlobViewerEnabled"
:value="content"
:file-name="fileName"
diff --git a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
index 4b53f55b856..14e99977a85 100644
--- a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
@@ -82,13 +82,7 @@ export default {
data-qa-selector="changed_file_icon_content"
:data-qa-title="tooltipTitle"
>
- <gl-icon
- v-if="showIcon"
- :name="changedIcon"
- :size="size"
- :class="changedIconClass"
- use-deprecated-sizes
- />
+ <gl-icon v-if="showIcon" :name="changedIcon" :size="size" :class="changedIconClass" />
</span>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_area_chart.vue b/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_area_chart.vue
index 2552236a073..fb7105bd416 100644
--- a/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_area_chart.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_area_chart.vue
@@ -28,18 +28,23 @@ export default {
<slot></slot>
</p>
<resizable-chart-container>
- <gl-area-chart
- slot-scope="{ width }"
- v-bind="$attrs"
- :width="width"
- :height="$options.chartContainerHeight"
- :data="chartData"
- :include-legend-avg-max="false"
- :option="areaChartOptions"
- >
- <slot slot="tooltip-title" name="tooltip-title"></slot>
- <slot slot="tooltip-content" name="tooltip-content"></slot>
- </gl-area-chart>
+ <template #default="{ width }">
+ <gl-area-chart
+ v-bind="$attrs"
+ :width="width"
+ :height="$options.chartContainerHeight"
+ :data="chartData"
+ :include-legend-avg-max="false"
+ :option="areaChartOptions"
+ >
+ <template #tooltip-title>
+ <slot name="tooltip-title"></slot>
+ </template>
+ <template #tooltip-content>
+ <slot name="tooltip-content"></slot>
+ </template>
+ </gl-area-chart>
+ </template>
</resizable-chart-container>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue b/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue
index f4fd57e4cdc..0575d7f6404 100644
--- a/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue
@@ -46,9 +46,12 @@ export default {
:area-chart-options="chartOptions"
>
{{ dateRange }}
-
- <slot slot="tooltip-title" name="tooltip-title"></slot>
- <slot slot="tooltip-content" name="tooltip-content"></slot>
+ <template #tooltip-title>
+ <slot name="tooltip-title"></slot>
+ </template>
+ <template #tooltip-content>
+ <slot name="tooltip-content"></slot>
+ </template>
</ci-cd-analytics-area-chart>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/ci_icon.vue b/app/assets/javascripts/vue_shared/components/ci_icon.vue
index dbf459cb289..07bd6019b80 100644
--- a/app/assets/javascripts/vue_shared/components/ci_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_icon.vue
@@ -64,12 +64,6 @@ export default {
</script>
<template>
<span :class="cssClass">
- <gl-icon
- :name="icon"
- :size="size"
- :class="cssClasses"
- :aria-label="status.icon"
- use-deprecated-sizes
- />
+ <gl-icon :name="icon" :size="size" :class="cssClasses" :aria-label="status.icon" />
</span>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/project_avatar/default.vue b/app/assets/javascripts/vue_shared/components/deprecated_project_avatar/default.vue
index 4bc70870767..733accdff44 100644
--- a/app/assets/javascripts/vue_shared/components/project_avatar/default.vue
+++ b/app/assets/javascripts/vue_shared/components/deprecated_project_avatar/default.vue
@@ -3,6 +3,7 @@ import Identicon from '../identicon.vue';
import ProjectAvatarImage from './image.vue';
export default {
+ name: 'DeprecatedProjectAvatar',
components: {
Identicon,
ProjectAvatarImage,
diff --git a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue b/app/assets/javascripts/vue_shared/components/deprecated_project_avatar/image.vue
index 269736c799c..269736c799c 100644
--- a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
+++ b/app/assets/javascripts/vue_shared/components/deprecated_project_avatar/image.vue
diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/renamed.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/renamed.vue
index b3edd05b0ee..b786f7752df 100644
--- a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/renamed.vue
+++ b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/renamed.vue
@@ -89,7 +89,7 @@ export default {
<template>
<div class="nothing-here-block">
- <gl-loading-icon v-if="is($options.STATE_LOADING)" />
+ <gl-loading-icon v-if="is($options.STATE_LOADING)" size="sm" />
<template v-else>
<gl-alert
v-show="is($options.STATE_ERRORED)"
diff --git a/app/assets/javascripts/vue_shared/components/dismissible_alert.vue b/app/assets/javascripts/vue_shared/components/dismissible_alert.vue
index 8494f99fd7d..52371e42ba1 100644
--- a/app/assets/javascripts/vue_shared/components/dismissible_alert.vue
+++ b/app/assets/javascripts/vue_shared/components/dismissible_alert.vue
@@ -1,11 +1,14 @@
<script>
-/* eslint-disable vue/no-v-html */
-import { GlAlert } from '@gitlab/ui';
+import { GlAlert, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
export default {
+ name: 'DismissibleAlert',
components: {
GlAlert,
},
+ directives: {
+ SafeHtml,
+ },
props: {
html: {
type: String,
@@ -28,6 +31,6 @@ export default {
<template>
<gl-alert v-if="!isDismissed" v-bind="$attrs" @dismiss="dismiss" v-on="$listeners">
- <div v-html="html"></div>
+ <div v-safe-html="html"></div>
</gl-alert>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue
index a1c7c4dd142..a512eb687b7 100644
--- a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue
+++ b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue
@@ -36,7 +36,7 @@ export default {
data-toggle="dropdown"
aria-expanded="false"
>
- <gl-loading-icon v-show="isLoading" :inline="true" />
+ <gl-loading-icon v-show="isLoading" size="sm" :inline="true" />
<slot v-if="$slots.default"></slot>
<span v-else class="dropdown-toggle-text"> {{ toggleText }} </span>
<gl-icon
diff --git a/app/assets/javascripts/vue_shared/components/expand_button.vue b/app/assets/javascripts/vue_shared/components/expand_button.vue
index 546ee56355f..0b92c947fc7 100644
--- a/app/assets/javascripts/vue_shared/components/expand_button.vue
+++ b/app/assets/javascripts/vue_shared/components/expand_button.vue
@@ -7,7 +7,7 @@ import { __ } from '~/locale';
*
* @example
* <expand-button>
- * <template slot="expanded">
+ * <template #expanded>
* Text goes here.
* </template>
* </expand-button>
diff --git a/app/assets/javascripts/vue_shared/components/file_finder/index.vue b/app/assets/javascripts/vue_shared/components/file_finder/index.vue
index fbadb202d51..b0c1c1531aa 100644
--- a/app/assets/javascripts/vue_shared/components/file_finder/index.vue
+++ b/app/assets/javascripts/vue_shared/components/file_finder/index.vue
@@ -103,6 +103,9 @@ export default {
focusedIndex() {
if (!this.mouseOver) {
this.$nextTick(() => {
+ if (!this.$refs.virtualScrollList?.$el) {
+ return;
+ }
const el = this.$refs.virtualScrollList.$el;
const scrollTop = this.focusedIndex * FILE_FINDER_ROW_HEIGHT;
const bottom = this.listShowCount * FILE_FINDER_ROW_HEIGHT;
@@ -218,7 +221,7 @@ export default {
</script>
<template>
- <div class="file-finder-overlay" @mousedown.self="toggle(false)">
+ <div v-if="visible" class="file-finder-overlay" @mousedown.self="toggle(false)">
<div class="dropdown-menu diff-file-changes file-finder show">
<div :class="{ 'has-value': showClearInputButton }" class="dropdown-input">
<input
diff --git a/app/assets/javascripts/vue_shared/components/file_icon.vue b/app/assets/javascripts/vue_shared/components/file_icon.vue
index 4244cab902a..276fb35b51f 100644
--- a/app/assets/javascripts/vue_shared/components/file_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/file_icon.vue
@@ -85,7 +85,7 @@ export default {
</script>
<template>
<span>
- <gl-loading-icon v-if="loading" :inline="true" />
+ <gl-loading-icon v-if="loading" size="sm" :inline="true" />
<gl-icon v-else-if="isSymlink" name="symlink" :size="size" use-deprecated-sizes />
<svg v-else-if="!folder" :key="spriteHref" :class="[iconSizeClass, cssClasses]">
<use v-bind="{ 'xlink:href': spriteHref }" />
@@ -95,7 +95,6 @@ export default {
:name="folderIconName"
:size="size"
class="folder-icon"
- use-deprecated-sizes
data-qa-selector="folder_icon_content"
/>
</span>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js
index 9775a9119c6..994ce6a762a 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js
@@ -10,8 +10,11 @@ export const FILTER_CURRENT = 'Current';
export const OPERATOR_IS = '=';
export const OPERATOR_IS_TEXT = __('is');
export const OPERATOR_IS_NOT = '!=';
+export const OPERATOR_IS_NOT_TEXT = __('is not');
export const OPERATOR_IS_ONLY = [{ value: OPERATOR_IS, description: OPERATOR_IS_TEXT }];
+export const OPERATOR_IS_NOT_ONLY = [{ value: OPERATOR_IS_NOT, description: OPERATOR_IS_NOT_TEXT }];
+export const OPERATOR_IS_AND_IS_NOT = [...OPERATOR_IS_ONLY, ...OPERATOR_IS_NOT_ONLY];
export const DEFAULT_LABEL_NONE = { value: FILTER_NONE, text: __(FILTER_NONE) };
export const DEFAULT_LABEL_ANY = { value: FILTER_ANY, text: __(FILTER_ANY) };
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js
index 37436de907f..571d24b50cf 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js
@@ -215,35 +215,35 @@ export function urlQueryToFilter(query = '', options = {}) {
/**
* Returns array of token values from localStorage
- * based on provided recentTokenValuesStorageKey
+ * based on provided recentSuggestionsStorageKey
*
- * @param {String} recentTokenValuesStorageKey
+ * @param {String} recentSuggestionsStorageKey
* @returns
*/
-export function getRecentlyUsedTokenValues(recentTokenValuesStorageKey) {
- let recentlyUsedTokenValues = [];
+export function getRecentlyUsedSuggestions(recentSuggestionsStorageKey) {
+ let recentlyUsedSuggestions = [];
if (AccessorUtilities.isLocalStorageAccessSafe()) {
- recentlyUsedTokenValues = JSON.parse(localStorage.getItem(recentTokenValuesStorageKey)) || [];
+ recentlyUsedSuggestions = JSON.parse(localStorage.getItem(recentSuggestionsStorageKey)) || [];
}
- return recentlyUsedTokenValues;
+ return recentlyUsedSuggestions;
}
/**
* Sets provided token value to recently used array
- * within localStorage for provided recentTokenValuesStorageKey
+ * within localStorage for provided recentSuggestionsStorageKey
*
- * @param {String} recentTokenValuesStorageKey
+ * @param {String} recentSuggestionsStorageKey
* @param {Object} tokenValue
*/
-export function setTokenValueToRecentlyUsed(recentTokenValuesStorageKey, tokenValue) {
- const recentlyUsedTokenValues = getRecentlyUsedTokenValues(recentTokenValuesStorageKey);
+export function setTokenValueToRecentlyUsed(recentSuggestionsStorageKey, tokenValue) {
+ const recentlyUsedSuggestions = getRecentlyUsedSuggestions(recentSuggestionsStorageKey);
- recentlyUsedTokenValues.splice(0, 0, { ...tokenValue });
+ recentlyUsedSuggestions.splice(0, 0, { ...tokenValue });
if (AccessorUtilities.isLocalStorageAccessSafe()) {
localStorage.setItem(
- recentTokenValuesStorageKey,
- JSON.stringify(uniqWith(recentlyUsedTokenValues, isEqual).slice(0, MAX_RECENT_TOKENS_SIZE)),
+ recentSuggestionsStorageKey,
+ JSON.stringify(uniqWith(recentlyUsedSuggestions, isEqual).slice(0, MAX_RECENT_TOKENS_SIZE)),
);
}
}
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 3b261f5ac25..a25a19a006c 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
@@ -74,13 +74,13 @@ export default {
:config="config"
:value="value"
:active="active"
- :tokens-list-loading="loading"
- :token-values="authors"
+ :suggestions-loading="loading"
+ :suggestions="authors"
:fn-active-token-value="getActiveAuthor"
- :default-token-values="defaultAuthors"
- :preloaded-token-values="preloadedAuthors"
- :recent-token-values-storage-key="config.recentTokenValuesStorageKey"
- @fetch-token-values="fetchAuthorBySearchTerm"
+ :default-suggestions="defaultAuthors"
+ :preloaded-suggestions="preloadedAuthors"
+ :recent-suggestions-storage-key="config.recentSuggestionsStorageKey"
+ @fetch-suggestions="fetchAuthorBySearchTerm"
v-on="$listeners"
>
<template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
@@ -93,9 +93,9 @@ export default {
/>
<span>{{ activeTokenValue ? activeTokenValue.name : inputValue }}</span>
</template>
- <template #token-values-list="{ tokenValues }">
+ <template #suggestions-list="{ suggestions }">
<gl-filtered-search-suggestion
- v-for="author in tokenValues"
+ v-for="author in suggestions"
:key="author.username"
:value="author.username"
>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue
index bda6b340871..a4804525a53 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue
@@ -6,9 +6,10 @@ import {
GlDropdownSectionHeader,
GlLoadingIcon,
} from '@gitlab/ui';
+import { debounce } from 'lodash';
import { DEBOUNCE_DELAY } from '../constants';
-import { getRecentlyUsedTokenValues, setTokenValueToRecentlyUsed } from '../filtered_search_utils';
+import { getRecentlyUsedSuggestions, setTokenValueToRecentlyUsed } from '../filtered_search_utils';
export default {
components: {
@@ -31,12 +32,12 @@ export default {
type: Boolean,
required: true,
},
- tokensListLoading: {
+ suggestionsLoading: {
type: Boolean,
required: false,
default: false,
},
- tokenValues: {
+ suggestions: {
type: Array,
required: false,
default: () => [],
@@ -44,21 +45,21 @@ export default {
fnActiveTokenValue: {
type: Function,
required: false,
- default: (tokenValues, currentTokenValue) => {
- return tokenValues.find(({ value }) => value === currentTokenValue);
+ default: (suggestions, currentTokenValue) => {
+ return suggestions.find(({ value }) => value === currentTokenValue);
},
},
- defaultTokenValues: {
+ defaultSuggestions: {
type: Array,
required: false,
default: () => [],
},
- preloadedTokenValues: {
+ preloadedSuggestions: {
type: Array,
required: false,
default: () => [],
},
- recentTokenValuesStorageKey: {
+ recentSuggestionsStorageKey: {
type: String,
required: false,
default: '',
@@ -77,21 +78,21 @@ export default {
data() {
return {
searchKey: '',
- recentTokenValues: this.recentTokenValuesStorageKey
- ? getRecentlyUsedTokenValues(this.recentTokenValuesStorageKey)
+ recentSuggestions: this.recentSuggestionsStorageKey
+ ? getRecentlyUsedSuggestions(this.recentSuggestionsStorageKey)
: [],
loading: false,
};
},
computed: {
- isRecentTokenValuesEnabled() {
- return Boolean(this.recentTokenValuesStorageKey);
+ isRecentSuggestionsEnabled() {
+ return Boolean(this.recentSuggestionsStorageKey);
},
recentTokenIds() {
- return this.recentTokenValues.map((tokenValue) => tokenValue[this.valueIdentifier]);
+ return this.recentSuggestions.map((tokenValue) => tokenValue[this.valueIdentifier]);
},
preloadedTokenIds() {
- return this.preloadedTokenValues.map((tokenValue) => tokenValue[this.valueIdentifier]);
+ return this.preloadedSuggestions.map((tokenValue) => tokenValue[this.valueIdentifier]);
},
currentTokenValue() {
if (this.fnCurrentTokenValue) {
@@ -100,17 +101,17 @@ export default {
return this.value.data.toLowerCase();
},
activeTokenValue() {
- return this.fnActiveTokenValue(this.tokenValues, this.currentTokenValue);
+ return this.fnActiveTokenValue(this.suggestions, this.currentTokenValue);
},
/**
- * Return all the tokenValues when searchKey is present
- * otherwise return only the tokenValues which aren't
+ * Return all the suggestions when searchKey is present
+ * otherwise return only the suggestions which aren't
* present in "Recently used"
*/
- availableTokenValues() {
+ availableSuggestions() {
return this.searchKey
- ? this.tokenValues
- : this.tokenValues.filter(
+ ? this.suggestions
+ : this.suggestions.filter(
(tokenValue) =>
!this.recentTokenIds.includes(tokenValue[this.valueIdentifier]) &&
!this.preloadedTokenIds.includes(tokenValue[this.valueIdentifier]),
@@ -121,30 +122,30 @@ export default {
active: {
immediate: true,
handler(newValue) {
- if (!newValue && !this.tokenValues.length) {
- this.$emit('fetch-token-values', this.value.data);
+ if (!newValue && !this.suggestions.length) {
+ this.$emit('fetch-suggestions', this.value.data);
}
},
},
},
methods: {
- handleInput({ data }) {
+ handleInput: debounce(function debouncedSearch({ data }) {
this.searchKey = data;
- setTimeout(() => {
- if (!this.tokensListLoading) this.$emit('fetch-token-values', data);
- }, DEBOUNCE_DELAY);
- },
+ if (!this.suggestionsLoading) {
+ this.$emit('fetch-suggestions', data);
+ }
+ }, DEBOUNCE_DELAY),
handleTokenValueSelected(activeTokenValue) {
// Make sure that;
// 1. Recently used values feature is enabled
// 2. User has actually selected a value
// 3. Selected value is not part of preloaded list.
if (
- this.isRecentTokenValuesEnabled &&
+ this.isRecentSuggestionsEnabled &&
activeTokenValue &&
!this.preloadedTokenIds.includes(activeTokenValue[this.valueIdentifier])
) {
- setTokenValueToRecentlyUsed(this.recentTokenValuesStorageKey, activeTokenValue);
+ setTokenValueToRecentlyUsed(this.recentSuggestionsStorageKey, activeTokenValue);
}
},
},
@@ -168,9 +169,9 @@ export default {
<slot name="view" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot>
</template>
<template #suggestions>
- <template v-if="defaultTokenValues.length">
+ <template v-if="defaultSuggestions.length">
<gl-filtered-search-suggestion
- v-for="token in defaultTokenValues"
+ v-for="token in defaultSuggestions"
:key="token.value"
:value="token.value"
>
@@ -178,19 +179,19 @@ export default {
</gl-filtered-search-suggestion>
<gl-dropdown-divider />
</template>
- <template v-if="isRecentTokenValuesEnabled && recentTokenValues.length && !searchKey">
+ <template v-if="isRecentSuggestionsEnabled && recentSuggestions.length && !searchKey">
<gl-dropdown-section-header>{{ __('Recently used') }}</gl-dropdown-section-header>
- <slot name="token-values-list" :token-values="recentTokenValues"></slot>
+ <slot name="suggestions-list" :suggestions="recentSuggestions"></slot>
<gl-dropdown-divider />
</template>
<slot
- v-if="preloadedTokenValues.length && !searchKey"
- name="token-values-list"
- :token-values="preloadedTokenValues"
+ v-if="preloadedSuggestions.length && !searchKey"
+ name="suggestions-list"
+ :suggestions="preloadedSuggestions"
></slot>
- <gl-loading-icon v-if="tokensListLoading" />
+ <gl-loading-icon v-if="suggestionsLoading" size="sm" />
<template v-else>
- <slot name="token-values-list" :token-values="availableTokenValues"></slot>
+ <slot name="suggestions-list" :suggestions="availableSuggestions"></slot>
</template>
</template>
</gl-filtered-search-token>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue
index 694dcd95b5e..5859fd10688 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue
@@ -97,7 +97,7 @@ export default {
{{ branch.text }}
</gl-filtered-search-suggestion>
<gl-dropdown-divider v-if="defaultBranches.length" />
- <gl-loading-icon v-if="loading" />
+ <gl-loading-icon v-if="loading" size="sm" />
<template v-else>
<gl-filtered-search-suggestion
v-for="branch in branches"
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
index 9ba7f3d1a1d..d186f46866c 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
@@ -101,7 +101,7 @@ export default {
{{ emoji.value }}
</gl-filtered-search-suggestion>
<gl-dropdown-divider v-if="defaultEmojis.length" />
- <gl-loading-icon v-if="loading" />
+ <gl-loading-icon v-if="loading" size="sm" />
<template v-else>
<gl-filtered-search-suggestion
v-for="emoji in emojis"
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue
index d21fa9a344a..aa234cf86d9 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue
@@ -56,7 +56,7 @@ export default {
}
// Current value is a string.
- const [groupPath, idProperty] = this.currentValue?.split('::&');
+ const [groupPath, idProperty] = this.currentValue?.split(this.$options.separator);
return this.epics.find(
(epic) =>
epic.group_full_path === groupPath &&
@@ -65,6 +65,9 @@ export default {
}
return null;
},
+ displayText() {
+ return `${this.activeEpic?.title}${this.$options.separator}${this.activeEpic?.iid}`;
+ },
},
watch: {
active: {
@@ -103,8 +106,10 @@ export default {
this.fetchEpicsBySearchTerm({ epicPath, search: data });
}, DEBOUNCE_DELAY),
- getEpicDisplayText(epic) {
- return `${epic.title}${this.$options.separator}${epic.iid}`;
+ getValue(epic) {
+ return this.config.useIdValue
+ ? String(epic[this.idProperty])
+ : `${epic.group_full_path}${this.$options.separator}${epic[this.idProperty]}`;
},
},
};
@@ -118,7 +123,7 @@ export default {
@input="searchEpics"
>
<template #view="{ inputValue }">
- {{ activeEpic ? getEpicDisplayText(activeEpic) : inputValue }}
+ {{ activeEpic ? displayText : inputValue }}
</template>
<template #suggestions>
<gl-filtered-search-suggestion
@@ -129,13 +134,9 @@ export default {
{{ epic.text }}
</gl-filtered-search-suggestion>
<gl-dropdown-divider v-if="defaultEpics.length" />
- <gl-loading-icon v-if="loading" />
+ <gl-loading-icon v-if="loading" size="sm" />
<template v-else>
- <gl-filtered-search-suggestion
- v-for="epic in epics"
- :key="epic.id"
- :value="`${epic.group_full_path}::&${epic[idProperty]}`"
- >
+ <gl-filtered-search-suggestion v-for="epic in epics" :key="epic.id" :value="getValue(epic)">
{{ epic.title }}
</gl-filtered-search-suggestion>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue
index 7b6a590279a..ba8b2421726 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue
@@ -7,6 +7,7 @@ import {
} from '@gitlab/ui';
import { debounce } from 'lodash';
import createFlash from '~/flash';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __ } from '~/locale';
import { DEBOUNCE_DELAY, DEFAULT_ITERATIONS } from '../constants';
@@ -30,8 +31,7 @@ export default {
data() {
return {
iterations: this.config.initialIterations || [],
- defaultIterations: this.config.defaultIterations || DEFAULT_ITERATIONS,
- loading: true,
+ loading: false,
};
},
computed: {
@@ -39,7 +39,12 @@ export default {
return this.value.data;
},
activeIteration() {
- return this.iterations.find((iteration) => iteration.title === this.currentValue);
+ return this.iterations.find(
+ (iteration) => getIdFromGraphQLId(iteration.id) === Number(this.currentValue),
+ );
+ },
+ defaultIterations() {
+ return this.config.defaultIterations || DEFAULT_ITERATIONS;
},
},
watch: {
@@ -53,6 +58,9 @@ export default {
},
},
methods: {
+ getValue(iteration) {
+ return String(getIdFromGraphQLId(iteration.id));
+ },
fetchIterationBySearchTerm(searchTerm) {
const fetchPromise = this.config.fetchPath
? this.config.fetchIterations(this.config.fetchPath, searchTerm)
@@ -95,12 +103,12 @@ export default {
{{ iteration.text }}
</gl-filtered-search-suggestion>
<gl-dropdown-divider v-if="defaultIterations.length" />
- <gl-loading-icon v-if="loading" />
+ <gl-loading-icon v-if="loading" size="sm" />
<template v-else>
<gl-filtered-search-suggestion
v-for="iteration in iterations"
- :key="iteration.title"
- :value="iteration.title"
+ :key="iteration.id"
+ :value="getValue(iteration)"
>
{{ iteration.title }}
</gl-filtered-search-suggestion>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
index e496d099a42..4d08f81fee9 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
@@ -96,12 +96,12 @@ export default {
:config="config"
:value="value"
:active="active"
- :tokens-list-loading="loading"
- :token-values="labels"
+ :suggestions-loading="loading"
+ :suggestions="labels"
:fn-active-token-value="getActiveLabel"
- :default-token-values="defaultLabels"
- :recent-token-values-storage-key="config.recentTokenValuesStorageKey"
- @fetch-token-values="fetchLabelBySearchTerm"
+ :default-suggestions="defaultLabels"
+ :recent-suggestions-storage-key="config.recentSuggestionsStorageKey"
+ @fetch-suggestions="fetchLabelBySearchTerm"
v-on="$listeners"
>
<template
@@ -115,9 +115,9 @@ export default {
>~{{ activeTokenValue ? getLabelName(activeTokenValue) : inputValue }}</gl-token
>
</template>
- <template #token-values-list="{ tokenValues }">
+ <template #suggestions-list="{ suggestions }">
<gl-filtered-search-suggestion
- v-for="label in tokenValues"
+ v-for="label in suggestions"
:key="label.id"
:value="getLabelName(label)"
>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue
index cda6e4d6726..66ad5ef5b4e 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue
@@ -9,6 +9,7 @@ import { debounce } from 'lodash';
import createFlash from '~/flash';
import { __ } from '~/locale';
+import { sortMilestonesByDueDate } from '~/milestones/milestone_utils';
import { DEFAULT_MILESTONES, DEBOUNCE_DELAY } from '../constants';
import { stripQuotes } from '../filtered_search_utils';
@@ -34,7 +35,7 @@ export default {
return {
milestones: this.config.initialMilestones || [],
defaultMilestones: this.config.defaultMilestones || DEFAULT_MILESTONES,
- loading: true,
+ loading: false,
};
},
computed: {
@@ -59,11 +60,16 @@ export default {
},
methods: {
fetchMilestoneBySearchTerm(searchTerm = '') {
+ if (this.loading) {
+ return;
+ }
+
this.loading = true;
this.config
.fetchMilestones(searchTerm)
- .then(({ data }) => {
- this.milestones = data;
+ .then((response) => {
+ const data = Array.isArray(response) ? response : response.data;
+ this.milestones = data.slice().sort(sortMilestonesByDueDate);
})
.catch(() => createFlash({ message: __('There was a problem fetching milestones.') }))
.finally(() => {
@@ -96,7 +102,7 @@ export default {
{{ milestone.text }}
</gl-filtered-search-suggestion>
<gl-dropdown-divider v-if="defaultMilestones.length" />
- <gl-loading-icon v-if="loading" />
+ <gl-loading-icon v-if="loading" size="sm" />
<template v-else>
<gl-filtered-search-suggestion
v-for="milestone in milestones"
diff --git a/app/assets/javascripts/vue_shared/components/form/form_footer_actions.vue b/app/assets/javascripts/vue_shared/components/form/form_footer_actions.vue
index 74f988476e3..26c50345c19 100644
--- a/app/assets/javascripts/vue_shared/components/form/form_footer_actions.vue
+++ b/app/assets/javascripts/vue_shared/components/form/form_footer_actions.vue
@@ -1,3 +1,4 @@
+<!-- eslint-disable-next-line vue/no-deprecated-functional-template -->
<template functional>
<footer class="form-actions d-flex justify-content-between">
<div><slot name="prepend"></slot></div>
diff --git a/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue b/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue
index 96d99faa952..dd0c0358ef6 100644
--- a/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue
+++ b/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue
@@ -74,6 +74,8 @@ export default {
@hidden="syncHide"
>
<slot></slot>
- <slot slot="modal-footer" name="modal-footer" :ok="ok" :cancel="cancel"></slot>
+ <template #modal-footer>
+ <slot name="modal-footer" :ok="ok" :cancel="cancel"></slot>
+ </template>
</gl-modal>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 80b7a9b7d05..9ea48050079 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -4,7 +4,7 @@ import { GlIcon } from '@gitlab/ui';
import $ from 'jquery';
import '~/behaviors/markdown/render_gfm';
import { unescape } from 'lodash';
-import { deprecatedCreateFlash as Flash } from '~/flash';
+import createFlash from '~/flash';
import GLForm from '~/gl_form';
import axios from '~/lib/utils/axios_utils';
import { stripHtml } from '~/lib/utils/text_utility';
@@ -222,7 +222,11 @@ export default {
axios
.post(this.markdownPreviewPath, { text: this.textareaValue })
.then((response) => this.renderMarkdown(response.data))
- .catch(() => new Flash(__('Error loading markdown preview')));
+ .catch(() =>
+ createFlash({
+ message: __('Error loading markdown preview'),
+ }),
+ );
} else {
this.renderMarkdown();
}
@@ -245,7 +249,11 @@ export default {
this.$nextTick()
.then(() => $(this.$refs['markdown-preview']).renderGFM())
- .catch(() => new Flash(__('Error rendering markdown preview')));
+ .catch(() =>
+ createFlash({
+ message: __('Error rendering markdown preview'),
+ }),
+ );
},
},
};
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
index 83b8a6ae562..065d9b1b5dd 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
@@ -1,5 +1,6 @@
<script>
import { GlButton, GlLoadingIcon, GlTooltipDirective, GlIcon } from '@gitlab/ui';
+import { isLoggedIn } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import ApplySuggestion from './apply_suggestion.vue';
@@ -73,7 +74,7 @@ export default {
return __('Applying suggestions...');
},
isLoggedIn() {
- return Boolean(gon.current_user_id);
+ return isLoggedIn();
},
},
methods: {
@@ -110,7 +111,7 @@ export default {
</div>
<div v-if="isApplied" class="badge badge-success">{{ __('Applied') }}</div>
<div v-else-if="isApplying" class="d-flex align-items-center text-secondary">
- <gl-loading-icon class="d-flex-center mr-2" />
+ <gl-loading-icon size="sm" class="d-flex-center mr-2" />
<span>{{ applyingSuggestionsMessage }}</span>
</div>
<div v-else-if="canApply && isBatched" class="d-flex align-items-center">
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_row.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_row.vue
index 9059f0d2a8b..a04f8616acb 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_row.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_row.vue
@@ -1,7 +1,11 @@
<script>
-/* eslint-disable vue/no-v-html */
+import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+
export default {
name: 'SuggestionDiffRow',
+ directives: {
+ SafeHtml,
+ },
props: {
line: {
type: Object,
@@ -32,7 +36,7 @@ export default {
:class="[{ 'd-table-cell': displayAsCell }, lineType]"
data-testid="suggestion-diff-content"
>
- <span v-if="line.rich_text" class="line" v-html="line.rich_text"></span>
+ <span v-if="line.rich_text" v-safe-html="line.rich_text" class="line"></span>
<span v-else-if="line.text" class="line">{{ line.text }}</span>
<span v-else class="line"></span>
</td>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
index 53d1cca7af3..63774c6c498 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
@@ -1,7 +1,7 @@
<script>
import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import Vue from 'vue';
-import { deprecatedCreateFlash as Flash } from '~/flash';
+import createFlash from '~/flash';
import { __ } from '~/locale';
import SuggestionDiff from './suggestion_diff.vue';
@@ -79,7 +79,10 @@ export default {
const suggestionElements = container.querySelectorAll('.js-render-suggestion');
if (this.lineType === 'old') {
- Flash(__('Unable to apply suggestions to a deleted line.'), 'alert', this.$el);
+ createFlash({
+ message: __('Unable to apply suggestions to a deleted line.'),
+ parent: this.$el,
+ });
}
suggestionElements.forEach((suggestionEl, i) => {
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
index 7393a8791b7..7112295fa57 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
@@ -82,7 +82,7 @@ export default {
<span class="attaching-file-message"></span>
<!-- eslint-disable-next-line @gitlab/vue-require-i18n-strings -->
<span class="uploading-progress">0%</span>
- <gl-loading-icon inline />
+ <gl-loading-icon size="sm" inline />
</span>
<span class="uploading-error-container hide">
<span class="uploading-error-icon">
diff --git a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
index 69afd711797..d6501a37a35 100644
--- a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
@@ -16,12 +16,15 @@
* :note="{body: 'This is a note'}"
* />
*/
+import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import { mapGetters } from 'vuex';
+import { renderMarkdown } from '~/notes/utils';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import userAvatarLink from '../user_avatar/user_avatar_link.vue';
export default {
name: 'PlaceholderNote',
+ directives: { SafeHtml },
components: {
userAvatarLink,
TimelineEntryItem,
@@ -34,6 +37,9 @@ export default {
},
computed: {
...mapGetters(['getUserData']),
+ renderedNote() {
+ return renderMarkdown(this.note.body);
+ },
},
};
</script>
@@ -57,9 +63,7 @@ export default {
</div>
</div>
<div class="note-body">
- <div class="note-text md">
- <p>{{ note.body }}</p>
- </div>
+ <div v-safe-html="renderedNote" class="note-text md"></div>
</div>
</div>
</timeline-entry-item>
diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
index 149909d263e..c3d861d74bc 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -111,7 +111,7 @@ export default {
<div class="note-header">
<note-header :author="note.author" :created-at="note.created_at" :note-id="note.id">
<span v-safe-html="actionTextHtml"></span>
- <template v-if="canSeeDescriptionVersion" slot="extra-controls">
+ <template v-if="canSeeDescriptionVersion" #extra-controls>
&middot;
<gl-button
variant="link"
diff --git a/app/assets/javascripts/vue_shared/components/oncall_schedules_list.vue b/app/assets/javascripts/vue_shared/components/oncall_schedules_list.vue
index ff2847624c5..e37a663ace3 100644
--- a/app/assets/javascripts/vue_shared/components/oncall_schedules_list.vue
+++ b/app/assets/javascripts/vue_shared/components/oncall_schedules_list.vue
@@ -27,9 +27,13 @@ export default {
title() {
return this.isCurrentUser
? s__('OnCallSchedules|You are currently a part of:')
- : sprintf(s__('OnCallSchedules|User %{name} is currently part of:'), {
- name: this.userName,
- });
+ : sprintf(
+ s__('OnCallSchedules|User %{name} is currently part of:'),
+ {
+ name: this.userName,
+ },
+ false,
+ );
},
footer() {
return this.isCurrentUser
diff --git a/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue b/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue
index d05e45e90b3..79a9e1fca8c 100644
--- a/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue
+++ b/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue
@@ -169,6 +169,12 @@ export default {
methods: {
filterItemsByStatus(tabIndex) {
this.resetPagination();
+ const activeStatusTab = this.statusTabs[tabIndex];
+
+ if (activeStatusTab == null) {
+ return;
+ }
+
const { filters, status } = this.statusTabs[tabIndex];
this.statusFilter = filters;
this.filteredByStatus = status;
diff --git a/app/assets/javascripts/vue_shared/components/project_avatar.stories.js b/app/assets/javascripts/vue_shared/components/project_avatar.stories.js
new file mode 100644
index 00000000000..110c6c73bad
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/project_avatar.stories.js
@@ -0,0 +1,30 @@
+import ProjectAvatar from './project_avatar.vue';
+
+export default {
+ component: ProjectAvatar,
+ title: 'vue_shared/components/project_avatar',
+};
+
+const Template = (args, { argTypes }) => ({
+ components: { ProjectAvatar },
+ props: Object.keys(argTypes),
+ template: '<project-avatar v-bind="$props" />',
+});
+
+export const Default = Template.bind({});
+Default.args = {
+ projectAvatarUrl:
+ 'https://gitlab.com/uploads/-/system/project/avatar/278964/logo-extra-whitespace.png?width=64',
+ projectName: 'GitLab',
+};
+
+export const FallbackAvatar = Template.bind({});
+FallbackAvatar.args = {
+ projectName: 'GitLab',
+};
+
+export const EmptyAltTag = Template.bind({});
+EmptyAltTag.args = {
+ ...Default.args,
+ alt: '',
+};
diff --git a/app/assets/javascripts/vue_shared/components/project_avatar.vue b/app/assets/javascripts/vue_shared/components/project_avatar.vue
new file mode 100644
index 00000000000..f16187022a5
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/project_avatar.vue
@@ -0,0 +1,45 @@
+<script>
+import { GlAvatar } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlAvatar,
+ },
+ props: {
+ projectName: {
+ type: String,
+ required: true,
+ },
+ projectAvatarUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ size: {
+ type: Number,
+ default: 32,
+ required: false,
+ },
+ alt: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
+ },
+ computed: {
+ avatarAlt() {
+ return this.alt ?? this.projectName;
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-avatar
+ shape="rect"
+ :entity-name="projectName"
+ :src="projectAvatarUrl"
+ :alt="avatarAlt"
+ :size="size"
+ />
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue b/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue
index ddc8bbf9b27..69f43c9e464 100644
--- a/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue
+++ b/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue
@@ -4,7 +4,7 @@ import { GlButton, GlIcon } from '@gitlab/ui';
import { isString } from 'lodash';
import highlight from '~/lib/utils/highlight';
import { truncateNamespace } from '~/lib/utils/text_utility';
-import ProjectAvatar from '~/vue_shared/components/project_avatar/default.vue';
+import ProjectAvatar from '~/vue_shared/components/deprecated_project_avatar/default.vue';
export default {
name: 'ProjectListItem',
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
index 580e1668f41..d55c93fd146 100644
--- a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
@@ -194,7 +194,7 @@ export default {
<template v-if="selectedPlatform">
<h5>
{{ $options.i18n.architecture }}
- <gl-loading-icon v-if="$apollo.loading" inline />
+ <gl-loading-icon v-if="$apollo.loading" size="sm" inline />
</h5>
<gl-dropdown class="gl-mb-3" :text="selectedArchitectureName">
diff --git a/app/assets/javascripts/vue_shared/components/select2_select.vue b/app/assets/javascripts/vue_shared/components/select2_select.vue
deleted file mode 100644
index bb1a8fae7b0..00000000000
--- a/app/assets/javascripts/vue_shared/components/select2_select.vue
+++ /dev/null
@@ -1,48 +0,0 @@
-<script>
-import $ from 'jquery';
-import 'select2';
-import { loadCSSFile } from '~/lib/utils/css_utils';
-
-export default {
- // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
- // eslint-disable-next-line @gitlab/require-i18n-strings
- name: 'Select2Select',
- props: {
- options: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- value: {
- type: String,
- required: false,
- default: '',
- },
- },
-
- watch: {
- value() {
- $(this.$refs.dropdownInput).val(this.value).trigger('change');
- },
- },
-
- mounted() {
- loadCSSFile(gon.select2_css_path)
- .then(() => {
- $(this.$refs.dropdownInput)
- .val(this.value)
- .select2(this.options)
- .on('change', (event) => this.$emit('input', event.target.value));
- })
- .catch(() => {});
- },
-
- beforeDestroy() {
- $(this.$refs.dropdownInput).select2('destroy');
- },
-};
-</script>
-
-<template>
- <input ref="dropdownInput" type="hidden" />
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue b/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue
index bbc7e6e7a6e..5c3a6852219 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue
@@ -1,5 +1,5 @@
<script>
-import { GlLoadingIcon } from '@gitlab/ui';
+import { GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import { s__, __, sprintf } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
@@ -10,8 +10,9 @@ import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
export default {
name: 'CopyableField',
components: {
- GlLoadingIcon,
ClipboardButton,
+ GlLoadingIcon,
+ GlSprintf,
},
props: {
value: {
@@ -48,12 +49,6 @@ export default {
loadingIconLabel() {
return sprintf(this.$options.i18n.loadingIconLabel, { name: this.name });
},
- templateText() {
- return sprintf(this.$options.i18n.templateText, {
- name: this.name,
- value: this.value,
- });
- },
},
i18n: {
loadingIconLabel: __('Loading %{name}'),
@@ -78,10 +73,13 @@ export default {
class="gl-overflow-hidden gl-text-overflow-ellipsis gl-white-space-nowrap"
:title="value"
>
- {{ templateText }}
+ <gl-sprintf :message="$options.i18n.templateText">
+ <template #name>{{ name }}</template>
+ <template #value>{{ value }}</template>
+ </gl-sprintf>
</span>
- <gl-loading-icon v-if="isLoading" inline :label="loadingIconLabel" />
+ <gl-loading-icon v-if="isLoading" size="sm" inline :label="loadingIconLabel" />
<clipboard-button v-else size="small" v-bind="clipboardProps" />
</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
index 075681de320..4531fafbf72 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
@@ -104,7 +104,7 @@ export default {
<collapsed-calendar-icon :text="collapsedText" class="sidebar-collapsed-icon" />
<div class="title">
{{ label }}
- <gl-loading-icon v-if="isLoading" :inline="true" />
+ <gl-loading-icon v-if="isLoading" size="sm" :inline="true" />
<div class="float-right">
<button
v-if="editable && !editing"
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue b/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue
index 320e2048f1c..12daaea8758 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue
@@ -148,7 +148,7 @@ export default {
@hide="handleDropdownHide"
>
<template #button-content
- ><gl-loading-icon v-if="moveInProgress" class="gl-mr-3" />{{
+ ><gl-loading-icon v-if="moveInProgress" size="sm" class="gl-mr-3" />{{
dropdownButtonTitle
}}</template
>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue
index f8cc981ba3d..3ec33a653b8 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue
@@ -108,7 +108,7 @@ export default {
class="float-left d-flex align-items-center"
@click="handleCreateClick"
>
- <gl-loading-icon v-show="labelCreateInProgress" :inline="true" class="mr-1" />
+ <gl-loading-icon v-show="labelCreateInProgress" size="sm" :inline="true" class="mr-1" />
{{ __('Create') }}
</gl-button>
<gl-button class="float-right js-btn-cancel-create" @click="toggleDropdownContentsCreateView">
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue
index 86788a84260..9914bfc6026 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue
@@ -48,6 +48,12 @@ export default {
}
return this.labels;
},
+ showDropdownFooter() {
+ return (
+ (this.isDropdownVariantSidebar || this.isDropdownVariantEmbedded) &&
+ (this.allowLabelCreate || this.labelsManagePath)
+ );
+ },
showNoMatchingResultsMessage() {
return Boolean(this.searchKey) && this.visibleLabels.length === 0;
},
@@ -192,11 +198,7 @@ export default {
</li>
</ul>
</div>
- <div
- v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded"
- class="dropdown-footer"
- data-testid="dropdown-footer"
- >
+ <div v-if="showDropdownFooter" class="dropdown-footer" data-testid="dropdown-footer">
<ul class="list-unstyled">
<li v-if="allowLabelCreate">
<gl-link
@@ -206,7 +208,7 @@ export default {
{{ footerCreateLabelTitle }}
</gl-link>
</li>
- <li>
+ <li v-if="labelsManagePath">
<gl-link
:href="labelsManagePath"
class="gl-display-flex flex-row text-break-word label-item"
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue
index 813de528c0b..aad754e15b0 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue
@@ -26,7 +26,7 @@ export default {
<div class="hide-collapsed gl-line-height-20 gl-mb-2 gl-text-gray-900">
{{ __('Labels') }}
<template v-if="allowLabelEdit">
- <gl-loading-icon v-show="labelsSelectInProgress" inline />
+ <gl-loading-icon v-show="labelsSelectInProgress" size="sm" inline />
<gl-button
variant="link"
class="float-right gl-text-gray-900! gl-hover-text-blue-800! js-sidebar-dropdown-toggle"
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/actions.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/actions.js
index 89f96ab916b..178be0f6da0 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/actions.js
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/actions.js
@@ -1,4 +1,4 @@
-import { deprecatedCreateFlash as flash } from '~/flash';
+import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import * as types from './mutation_types';
@@ -16,7 +16,9 @@ export const receiveLabelsSuccess = ({ commit }, labels) =>
commit(types.RECEIVE_SET_LABELS_SUCCESS, labels);
export const receiveLabelsFailure = ({ commit }) => {
commit(types.RECEIVE_SET_LABELS_FAILURE);
- flash(__('Error fetching labels.'));
+ createFlash({
+ message: __('Error fetching labels.'),
+ });
};
export const fetchLabels = ({ state, dispatch }) => {
dispatch('requestLabels');
@@ -32,7 +34,9 @@ export const requestCreateLabel = ({ commit }) => commit(types.REQUEST_CREATE_LA
export const receiveCreateLabelSuccess = ({ commit }) => commit(types.RECEIVE_CREATE_LABEL_SUCCESS);
export const receiveCreateLabelFailure = ({ commit }) => {
commit(types.RECEIVE_CREATE_LABEL_FAILURE);
- flash(__('Error creating label.'));
+ createFlash({
+ message: __('Error creating label.'),
+ });
};
export const createLabel = ({ state, dispatch }, label) => {
dispatch('requestCreateLabel');
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js
index 55716e1105e..2e0a57f15dd 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js
@@ -1,3 +1,4 @@
+import { isScopedLabel, scopedLabelKey } from '~/lib/utils/common_utils';
import { DropdownVariant } from '../constants';
import * as types from './mutation_types';
@@ -66,5 +67,16 @@ export default {
candidateLabel.touched = true;
candidateLabel.set = !candidateLabel.set;
}
+
+ if (isScopedLabel(candidateLabel)) {
+ const scopedBase = scopedLabelKey(candidateLabel);
+ const currentActiveScopedLabel = state.labels.find(({ title }) => {
+ return title.startsWith(scopedBase) && title !== '' && title !== candidateLabel.title;
+ });
+
+ if (currentActiveScopedLabel) {
+ currentActiveScopedLabel.set = false;
+ }
+ }
},
};
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 a7f20fbe851..4651e7a1576 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
@@ -117,7 +117,7 @@ export default {
data-testid="create-button"
@click="createLabel"
>
- <gl-loading-icon v-if="labelCreateInProgress" :inline="true" class="mr-1" />
+ <gl-loading-icon v-if="labelCreateInProgress" size="sm" :inline="true" class="mr-1" />
{{ __('Create') }}
</gl-button>
<gl-button
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_title.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_title.vue
index 5d1663bc1fd..b6d14965cfa 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_title.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_title.vue
@@ -26,7 +26,7 @@ export default {
<div class="title hide-collapsed gl-mb-3">
{{ __('Labels') }}
<template v-if="allowLabelEdit">
- <gl-loading-icon v-show="labelsSelectInProgress" inline />
+ <gl-loading-icon v-show="labelsSelectInProgress" size="sm" inline />
<gl-button
variant="link"
class="float-right js-sidebar-dropdown-toggle"
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue
index 46ccb9470e5..58a940bca3b 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue
@@ -1,7 +1,6 @@
<script>
import { GlLabel } from '@gitlab/ui';
-import { mapState } from 'vuex';
-
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { isScopedLabel } from '~/lib/utils/common_utils';
export default {
@@ -14,15 +13,26 @@ export default {
required: false,
default: false,
},
- },
- computed: {
- ...mapState([
- 'selectedLabels',
- 'allowLabelRemove',
- 'allowScopedLabels',
- 'labelsFilterBasePath',
- 'labelsFilterParam',
- ]),
+ selectedLabels: {
+ type: Array,
+ required: true,
+ },
+ allowLabelRemove: {
+ type: Boolean,
+ required: true,
+ },
+ allowScopedLabels: {
+ type: Boolean,
+ required: true,
+ },
+ labelsFilterBasePath: {
+ type: String,
+ required: true,
+ },
+ labelsFilterParam: {
+ type: String,
+ required: true,
+ },
},
methods: {
labelFilterUrl(label) {
@@ -33,6 +43,9 @@ export default {
scopedLabel(label) {
return this.allowScopedLabels && isScopedLabel(label);
},
+ removeLabel(labelId) {
+ this.$emit('onLabelRemove', getIdFromGraphQLId(labelId));
+ },
},
};
</script>
@@ -43,12 +56,14 @@ export default {
'has-labels': selectedLabels.length,
}"
class="hide-collapsed value issuable-show-labels js-value"
+ data-testid="value-wrapper"
>
- <span v-if="!selectedLabels.length" class="text-secondary">
+ <span v-if="!selectedLabels.length" class="text-secondary" data-testid="empty-placeholder">
<slot></slot>
</span>
- <template v-for="label in selectedLabels" v-else>
+ <template v-else>
<gl-label
+ v-for="label in selectedLabels"
:key="label.id"
data-qa-selector="selected_label_content"
:data-qa-label-name="label.title"
@@ -60,7 +75,7 @@ export default {
:show-close-button="allowLabelRemove"
:disabled="disableLabels"
tooltip-placement="top"
- @close="$emit('onLabelRemove', label.id)"
+ @close="removeLabel(label.id)"
/>
</template>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql
new file mode 100644
index 00000000000..1c2fd3bb7c0
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql
@@ -0,0 +1,15 @@
+query issueLabels($fullPath: ID!, $iid: String) {
+ workspace: project(fullPath: $fullPath) {
+ issuable: issue(iid: $iid) {
+ id
+ labels {
+ nodes {
+ id
+ title
+ color
+ description
+ }
+ }
+ }
+ }
+}
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 7728c758e18..87f36a5bb72 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
@@ -11,6 +11,7 @@ import DropdownContents from './dropdown_contents.vue';
import DropdownTitle from './dropdown_title.vue';
import DropdownValue from './dropdown_value.vue';
import DropdownValueCollapsed from './dropdown_value_collapsed.vue';
+import issueLabelsQuery from './graphql/issue_labels.query.graphql';
import labelsSelectModule from './store';
Vue.use(Vuex);
@@ -24,6 +25,7 @@ export default {
DropdownContents,
DropdownValueCollapsed,
},
+ inject: ['iid', 'projectPath'],
props: {
allowLabelRemove: {
type: Boolean,
@@ -119,8 +121,23 @@ export default {
data() {
return {
contentIsOnViewport: true,
+ issueLabels: [],
};
},
+ apollo: {
+ issueLabels: {
+ query: issueLabelsQuery,
+ variables() {
+ return {
+ iid: this.iid,
+ fullPath: this.projectPath,
+ };
+ },
+ update(data) {
+ return data.workspace?.issuable?.labels.nodes || [];
+ },
+ },
+ },
computed: {
...mapState(['showDropdownButton', 'showDropdownContents']),
...mapGetters([
@@ -293,7 +310,7 @@ export default {
<template v-if="isDropdownVariantSidebar">
<dropdown-value-collapsed
ref="dropdownButtonCollapsed"
- :labels="selectedLabels"
+ :labels="issueLabels"
@onValueClick="handleCollapsedValueClick"
/>
<dropdown-title
@@ -302,6 +319,11 @@ export default {
/>
<dropdown-value
:disable-labels="labelsSelectInProgress"
+ :selected-labels="issueLabels"
+ :allow-label-remove="allowLabelRemove"
+ :allow-scoped-labels="allowScopedLabels"
+ :labels-filter-base-path="labelsFilterBasePath"
+ :labels-filter-param="labelsFilterParam"
@onLabelRemove="$emit('onLabelRemove', $event)"
>
<slot></slot>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/actions.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/actions.js
index 2b96b159ca3..935f020f559 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/actions.js
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/actions.js
@@ -1,4 +1,4 @@
-import { deprecatedCreateFlash as flash } from '~/flash';
+import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import * as types from './mutation_types';
@@ -16,7 +16,9 @@ export const receiveLabelsSuccess = ({ commit }, labels) =>
commit(types.RECEIVE_SET_LABELS_SUCCESS, labels);
export const receiveLabelsFailure = ({ commit }) => {
commit(types.RECEIVE_SET_LABELS_FAILURE);
- flash(__('Error fetching labels.'));
+ createFlash({
+ message: __('Error fetching labels.'),
+ });
};
export const fetchLabels = ({ state, dispatch }) => {
dispatch('requestLabels');
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutations.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutations.js
index 131c6e6fb57..1c03d95f37b 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutations.js
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutations.js
@@ -1,3 +1,4 @@
+import { isScopedLabel, scopedLabelKey } from '~/lib/utils/common_utils';
import { DropdownVariant } from '../constants';
import * as types from './mutation_types';
@@ -55,5 +56,16 @@ export default {
candidateLabel.touched = true;
candidateLabel.set = !candidateLabel.set;
}
+
+ if (isScopedLabel(candidateLabel)) {
+ const scopedBase = scopedLabelKey(candidateLabel);
+ const currentActiveScopedLabel = state.labels.find(
+ ({ title }) => title.indexOf(scopedBase) === 0 && title !== candidateLabel.title,
+ );
+
+ if (currentActiveScopedLabel) {
+ currentActiveScopedLabel.set = false;
+ }
+ }
},
};
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.stories.js b/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.stories.js
new file mode 100644
index 00000000000..d2afc02233e
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.stories.js
@@ -0,0 +1,23 @@
+/* eslint-disable @gitlab/require-i18n-strings */
+
+import TodoButton from './todo_button.vue';
+
+export default {
+ component: TodoButton,
+ title: 'vue_shared/components/todo_toggle/todo_button',
+};
+
+const Template = (args, { argTypes }) => ({
+ components: { TodoButton },
+ props: Object.keys(argTypes),
+ template: '<todo-button v-bind="$props" v-on="$props" />',
+});
+
+export const Default = Template.bind({});
+Default.argTypes = {
+ isTodo: {
+ description: 'True if to-do is unresolved (i.e. not "done")',
+ control: { type: 'boolean' },
+ },
+ click: { action: 'clicked' },
+};
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.vue b/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.vue
new file mode 100644
index 00000000000..e6229cf0a93
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.vue
@@ -0,0 +1,56 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import { todoLabel } from './utils';
+
+export default {
+ components: {
+ GlButton,
+ },
+ props: {
+ isTodo: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ },
+ computed: {
+ buttonLabel() {
+ return todoLabel(this.isTodo);
+ },
+ },
+ methods: {
+ updateGlobalTodoCount(additionalTodoCount) {
+ const countContainer = document.querySelector('.js-todos-count');
+ if (countContainer === null) return;
+ const currentCount = parseInt(countContainer.innerText, 10);
+ const todoToggleEvent = new CustomEvent('todo:toggle', {
+ detail: {
+ count: Math.max(currentCount + additionalTodoCount, 0),
+ },
+ });
+
+ document.dispatchEvent(todoToggleEvent);
+ },
+ incrementGlobalTodoCount() {
+ this.updateGlobalTodoCount(1);
+ },
+ decrementGlobalTodoCount() {
+ this.updateGlobalTodoCount(-1);
+ },
+ onToggle(event) {
+ if (this.isTodo) {
+ this.decrementGlobalTodoCount();
+ } else {
+ this.incrementGlobalTodoCount();
+ }
+ this.$emit('click', event);
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-button v-bind="$attrs" :aria-label="buttonLabel" @click="onToggle($event)">
+ {{ buttonLabel }}
+ </gl-button>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/utils.js b/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/utils.js
new file mode 100644
index 00000000000..59e72a2ffe3
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/utils.js
@@ -0,0 +1,5 @@
+import { __ } from '~/locale';
+
+export const todoLabel = (hasTodo) => {
+ return hasTodo ? __('Mark as done') : __('Add a to do');
+};
diff --git a/app/assets/javascripts/vue_shared/components/editor_lite.vue b/app/assets/javascripts/vue_shared/components/source_editor.vue
index c3bddabea21..fdf0c9baee3 100644
--- a/app/assets/javascripts/vue_shared/components/editor_lite.vue
+++ b/app/assets/javascripts/vue_shared/components/source_editor.vue
@@ -1,9 +1,9 @@
<script>
import { debounce } from 'lodash';
import { CONTENT_UPDATE_DEBOUNCE, EDITOR_READY_EVENT } from '~/editor/constants';
-import Editor from '~/editor/editor_lite';
+import Editor from '~/editor/source_editor';
-function initEditorLite({ el, ...args }) {
+function initSourceEditor({ el, ...args }) {
const editor = new Editor({
scrollbar: {
alwaysConsumeMouseWheel: false,
@@ -64,7 +64,7 @@ export default {
},
},
mounted() {
- this.editor = initEditorLite({
+ this.editor = initSourceEditor({
el: this.$refs.editor,
blobPath: this.fileName,
blobContent: this.value,
@@ -93,7 +93,7 @@ export default {
</script>
<template>
<div
- :id="`editor-lite-${fileGlobalId}`"
+ :id="`source-editor-${fileGlobalId}`"
ref="editor"
data-editor-loading
@[$options.readyEvent]="$emit($options.readyEvent)"
diff --git a/app/assets/javascripts/vue_shared/components/todo_button.vue b/app/assets/javascripts/vue_shared/components/todo_button.vue
deleted file mode 100644
index 935d222a1a9..00000000000
--- a/app/assets/javascripts/vue_shared/components/todo_button.vue
+++ /dev/null
@@ -1,28 +0,0 @@
-<script>
-import { GlButton } from '@gitlab/ui';
-import { __ } from '~/locale';
-
-export default {
- components: {
- GlButton,
- },
- props: {
- isTodo: {
- type: Boolean,
- required: false,
- default: true,
- },
- },
- computed: {
- buttonLabel() {
- return this.isTodo ? __('Mark as done') : __('Add a to do');
- },
- },
-};
-</script>
-
-<template>
- <gl-button v-bind="$attrs" :aria-label="buttonLabel" @click="$emit('click', $event)">
- {{ buttonLabel }}
- </gl-button>
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
index deac24d2270..f387f8ca128 100644
--- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
+++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
@@ -72,7 +72,11 @@ export default {
<template v-else>
<div class="gl-mb-3">
<h5 class="gl-m-0">
- <user-name-with-status :name="user.name" :availability="availabilityStatus" />
+ <user-name-with-status
+ :name="user.name"
+ :availability="availabilityStatus"
+ :pronouns="user.pronouns"
+ />
</h5>
<span class="gl-text-gray-500">@{{ user.username }}</span>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/user_select/user_select.vue b/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
index 04e44aa2ed1..b85cae0c64f 100644
--- a/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
+++ b/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
@@ -96,9 +96,6 @@ export default {
},
},
searchUsers: {
- // TODO Remove error policy
- // https://gitlab.com/gitlab-org/gitlab/-/issues/329750
- errorPolicy: 'all',
query: searchUsers,
variables() {
return {
@@ -111,28 +108,10 @@ export default {
return !this.isEditing;
},
update(data) {
- // TODO Remove null filter (BE fix required)
- // https://gitlab.com/gitlab-org/gitlab/-/issues/329750
return data.workspace?.users?.nodes.filter((x) => x?.user).map(({ user }) => user) || [];
},
debounce: ASSIGNEES_DEBOUNCE_DELAY,
- error({ graphQLErrors }) {
- // TODO This error suppression is temporary (BE fix required)
- // https://gitlab.com/gitlab-org/gitlab/-/issues/329750
- const isNullError = ({ message }) => {
- return message === 'Cannot return null for non-nullable field GroupMember.user';
- };
-
- if (graphQLErrors?.length > 0 && graphQLErrors.every(isNullError)) {
- // only null-related errors exist, suppress them.
- // eslint-disable-next-line no-console
- console.error(
- "Suppressing the error 'Cannot return null for non-nullable field GroupMember.user'. Please see https://gitlab.com/gitlab-org/gitlab/-/issues/329750",
- );
- this.isSearching = false;
- return;
- }
-
+ error() {
this.$emit('error');
this.isSearching = false;
},
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 4bd3e352fd2..5ba7c107c12 100644
--- a/app/assets/javascripts/vue_shared/components/web_ide_link.vue
+++ b/app/assets/javascripts/vue_shared/components/web_ide_link.vue
@@ -93,9 +93,8 @@ export default {
tooltip: '',
attrs: {
'data-qa-selector': 'edit_button',
- 'data-track-event': 'click_edit',
- // eslint-disable-next-line @gitlab/require-i18n-strings
- 'data-track-label': 'Edit',
+ 'data-track-action': 'click_consolidated_edit',
+ 'data-track-label': 'edit',
},
...handleOptions,
};
@@ -127,9 +126,8 @@ export default {
tooltip: '',
attrs: {
'data-qa-selector': 'web_ide_button',
- 'data-track-event': 'click_edit_ide',
- // eslint-disable-next-line @gitlab/require-i18n-strings
- 'data-track-label': 'Web IDE',
+ 'data-track-action': 'click_consolidated_edit_ide',
+ 'data-track-label': 'web_ide',
},
...handleOptions,
};