diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-20 17:22:11 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-20 17:22:11 +0300 |
commit | 0c872e02b2c822e3397515ec324051ff540f0cd5 (patch) | |
tree | ce2fb6ce7030e4dad0f4118d21ab6453e5938cdd /app/assets/javascripts/vue_shared | |
parent | f7e05a6853b12f02911494c4b3fe53d9540d74fc (diff) |
Add latest changes from gitlab-org/gitlab@15-7-stable-eev15.7.0-rc42
Diffstat (limited to 'app/assets/javascripts/vue_shared')
115 files changed, 670 insertions, 3791 deletions
diff --git a/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue b/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue index 96c2ffa929c..6803d609dbc 100644 --- a/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue @@ -9,9 +9,9 @@ import { GlTabs, GlTab, GlButton, - GlSafeHtmlDirective, } from '@gitlab/ui'; import * as Sentry from '@sentry/browser'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user'; import { fetchPolicies } from '~/lib/graphql'; import { toggleContainerClasses } from '~/lib/utils/dom_utils'; @@ -41,7 +41,7 @@ export default { reportedAtWithTool: s__('AlertManagement|Reported %{when} by %{tool}'), }, directives: { - SafeHtml: GlSafeHtmlDirective, + SafeHtml, }, severityLabels: SEVERITY_LEVELS, tabsConfig: [ @@ -369,10 +369,10 @@ export default { <alert-details-table :alert="alert" :loading="loading" :statuses="statuses" /> </gl-tab> - <metric-images-tab - :data-testid="$options.tabsConfig[1].id" - :title="$options.tabsConfig[1].title" - /> + <gl-tab :title="$options.tabsConfig[1].title"> + <metric-images-tab :data-testid="$options.tabsConfig[1].id" /> + </gl-tab> + <gl-tab :data-testid="$options.tabsConfig[2].id" :title="$options.tabsConfig[2].title"> <div v-if="alert.notes.nodes.length > 0" class="issuable-discussion"> <ul class="notes main-notes-list timeline"> diff --git a/app/assets/javascripts/vue_shared/alert_details/components/alert_status.vue b/app/assets/javascripts/vue_shared/alert_details/components/alert_status.vue index 672761af1cf..8d2ef20b381 100644 --- a/app/assets/javascripts/vue_shared/alert_details/components/alert_status.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/alert_status.vue @@ -106,7 +106,7 @@ export default { @keydown.esc.native="$emit('hide-dropdown')" @hide="$emit('hide-dropdown')" > - <p v-if="isSidebar" class="gl-new-dropdown-header-top" data-testid="dropdown-header"> + <p v-if="isSidebar" class="gl-dropdown-header-top" data-testid="dropdown-header"> {{ s__('AlertManagement|Assign status') }} </p> <div class="dropdown-content dropdown-body"> diff --git a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue index 72dcc16b57a..4ec301b946b 100644 --- a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue @@ -242,7 +242,7 @@ export default { @keydown.esc.native="hideDropdown" @hide="hideDropdown" > - <p class="gl-new-dropdown-header-top"> + <p class="gl-dropdown-header-top"> {{ __('Assign To') }} </p> <gl-search-box-by-type v-model.trim="search" :placeholder="__('Search users')" /> diff --git a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_header.vue b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_header.vue index 832b154b312..b3ee01f3a24 100644 --- a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_header.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_header.vue @@ -1,5 +1,5 @@ <script> -import ToggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue'; +import ToggleSidebar from '~/sidebar/components/toggle/toggle_sidebar.vue'; import SidebarTodo from './sidebar_todo.vue'; export default { diff --git a/app/assets/javascripts/vue_shared/alert_details/components/system_notes/system_note.vue b/app/assets/javascripts/vue_shared/alert_details/components/system_notes/system_note.vue index 6b774b2a734..3c73f42b6b1 100644 --- a/app/assets/javascripts/vue_shared/alert_details/components/system_notes/system_note.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/system_notes/system_note.vue @@ -1,5 +1,6 @@ <script> -import { GlIcon, GlSafeHtmlDirective } from '@gitlab/ui'; +import { GlIcon } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import NoteHeader from '~/notes/components/note_header.vue'; export default { @@ -8,7 +9,7 @@ export default { GlIcon, }, directives: { - SafeHtml: GlSafeHtmlDirective, + SafeHtml, }, props: { note: { diff --git a/app/assets/javascripts/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql b/app/assets/javascripts/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql index 33091f1ba5e..b04d5773a37 100644 --- a/app/assets/javascripts/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql +++ b/app/assets/javascripts/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql @@ -8,6 +8,7 @@ mutation alertSetAssignees($fullPath: ID!, $assigneeUsernames: [String!]!, $iid: ) { errors issuable: alert { + id iid assignees { nodes { diff --git a/app/assets/javascripts/vue_shared/components/actions_button.vue b/app/assets/javascripts/vue_shared/components/actions_button.vue index c6c22f9c61f..175aef59ae5 100644 --- a/app/assets/javascripts/vue_shared/components/actions_button.vue +++ b/app/assets/javascripts/vue_shared/components/actions_button.vue @@ -1,11 +1,5 @@ <script> -import { - GlDropdown, - GlDropdownItem, - GlDropdownDivider, - GlButton, - GlTooltipDirective, -} from '@gitlab/ui'; +import { GlDropdown, GlDropdownItem, GlDropdownDivider, GlButton, GlTooltip } from '@gitlab/ui'; export default { components: { @@ -13,11 +7,14 @@ export default { GlDropdownItem, GlDropdownDivider, GlButton, - }, - directives: { - GlTooltip: GlTooltipDirective, + GlTooltip, }, props: { + id: { + type: String, + required: false, + default: '', + }, actions: { type: Array, required: true, @@ -37,6 +34,11 @@ export default { required: false, default: 'default', }, + showActionTooltip: { + type: Boolean, + required: false, + default: true, + }, }, computed: { hasMultipleActions() { @@ -51,6 +53,7 @@ export default { this.$emit('select', action.key); }, handleClick(action, evt) { + this.$emit('actionClicked', { action }); return action.handle?.(evt); }, }, @@ -58,46 +61,51 @@ export default { </script> <template> - <gl-dropdown - v-if="hasMultipleActions" - v-gl-tooltip="selectedAction.tooltip" - :text="selectedAction.text" - :split-href="selectedAction.href" - :variant="variant" - :category="category" - split - data-qa-selector="action_dropdown" - @click="handleClick(selectedAction, $event)" - > - <template #button-content> - <span class="gl-new-dropdown-button-text" v-bind="selectedAction.attrs"> - {{ selectedAction.text }} - </span> - </template> - <template v-for="(action, index) in actions"> - <gl-dropdown-item - :key="action.key" - is-check-item - :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)" - > - <span class="gl-font-weight-bold">{{ action.text }}</span> - </gl-dropdown-item> - <gl-dropdown-divider v-if="index != actions.length - 1" :key="action.key + '_divider'" /> - </template> - </gl-dropdown> - <gl-button - v-else-if="selectedAction" - v-gl-tooltip="selectedAction.tooltip" - v-bind="selectedAction.attrs" - :variant="variant" - :category="category" - :href="selectedAction.href" - @click="handleClick(selectedAction, $event)" - > - {{ selectedAction.text }} - </gl-button> + <span> + <gl-dropdown + v-if="hasMultipleActions" + :id="id" + :text="selectedAction.text" + :split-href="selectedAction.href" + :variant="variant" + :category="category" + split + data-qa-selector="action_dropdown" + @click="handleClick(selectedAction, $event)" + > + <template #button-content> + <span class="gl-dropdown-button-text" v-bind="selectedAction.attrs"> + {{ selectedAction.text }} + </span> + </template> + <template v-for="(action, index) in actions"> + <gl-dropdown-item + :key="action.key" + is-check-item + :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)" + > + <span class="gl-font-weight-bold">{{ action.text }}</span> + </gl-dropdown-item> + <gl-dropdown-divider v-if="index != actions.length - 1" :key="action.key + '_divider'" /> + </template> + </gl-dropdown> + <gl-button + v-else-if="selectedAction" + :id="id" + v-bind="selectedAction.attrs" + :variant="variant" + :category="category" + :href="selectedAction.href" + @click="handleClick(selectedAction, $event)" + > + {{ selectedAction.text }} + </gl-button> + <gl-tooltip v-if="selectedAction.tooltip && showActionTooltip" :target="id"> + {{ selectedAction.tooltip }} + </gl-tooltip> + </span> </template> diff --git a/app/assets/javascripts/vue_shared/components/awards_list.vue b/app/assets/javascripts/vue_shared/components/awards_list.vue index f5d8811e83c..cb38b3e13bb 100644 --- a/app/assets/javascripts/vue_shared/components/awards_list.vue +++ b/app/assets/javascripts/vue_shared/components/awards_list.vue @@ -1,6 +1,7 @@ <script> -import { GlIcon, GlButton, GlTooltipDirective, GlSafeHtmlDirective } from '@gitlab/ui'; +import { GlIcon, GlButton, GlTooltipDirective } from '@gitlab/ui'; import { groupBy } from 'lodash'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import EmojiPicker from '~/emoji/components/picker.vue'; import { __, sprintf } from '~/locale'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; @@ -17,7 +18,7 @@ export default { }, directives: { GlTooltip: GlTooltipDirective, - SafeHtml: GlSafeHtmlDirective, + SafeHtml, }, mixins: [glFeatureFlagsMixin()], props: { @@ -158,10 +159,7 @@ export default { return; } - // 100 and 1234 emoji are a number. Callback for v-for click sends it as a string - const parsedName = /^[0-9]+$/.test(awardName) ? Number(awardName) : awardName; - - this.$emit('award', parsedName); + this.$emit('award', awardName); if (document.activeElement) document.activeElement.blur(); }, 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 ed0eb9cc0b8..49181bb847d 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 @@ -1,5 +1,5 @@ <script> -import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import { handleBlobRichViewer } from '~/blob/viewer'; import MarkdownFieldView from '~/vue_shared/components/markdown/field_view.vue'; import ViewerMixin from './mixins'; 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 0117c06c3d5..c7a76af7f74 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 @@ -1,5 +1,6 @@ <script> -import { GlIcon, GlSafeHtmlDirective } from '@gitlab/ui'; +import { GlIcon } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import { HIGHLIGHT_CLASS_NAME } from './constants'; import ViewerMixin from './mixins'; @@ -9,7 +10,7 @@ export default { GlIcon, }, directives: { - SafeHtml: GlSafeHtmlDirective, + SafeHtml, }, mixins: [ViewerMixin], inject: ['blobHash'], diff --git a/app/assets/javascripts/vue_shared/components/code_block_highlighted.vue b/app/assets/javascripts/vue_shared/components/code_block_highlighted.vue index 65b08b608e8..352d03befc3 100644 --- a/app/assets/javascripts/vue_shared/components/code_block_highlighted.vue +++ b/app/assets/javascripts/vue_shared/components/code_block_highlighted.vue @@ -1,5 +1,5 @@ <script> -import { GlSafeHtmlDirective } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import languageLoader from '~/content_editor/services/highlight_js_language_loader'; import CodeBlock from './code_block.vue'; @@ -7,7 +7,7 @@ import CodeBlock from './code_block.vue'; export default { name: 'CodeBlockHighlighted', directives: { - SafeHtml: GlSafeHtmlDirective, + SafeHtml, }, components: { CodeBlock, 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 7a982bc035a..d0a634d8e54 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,12 +1,6 @@ <script> -import { - GlAlert, - GlModal, - GlFormGroup, - GlFormInput, - GlSafeHtmlDirective as SafeHtml, - GlSprintf, -} from '@gitlab/ui'; +import { GlAlert, GlModal, GlFormGroup, GlFormInput, GlSprintf } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import { CONFIRM_DANGER_MODAL_BUTTON, CONFIRM_DANGER_MODAL_TITLE, diff --git a/app/assets/javascripts/vue_shared/components/confirm_modal.vue b/app/assets/javascripts/vue_shared/components/confirm_modal.vue index 72504e5bc50..664c3578785 100644 --- a/app/assets/javascripts/vue_shared/components/confirm_modal.vue +++ b/app/assets/javascripts/vue_shared/components/confirm_modal.vue @@ -1,6 +1,7 @@ <script> -import { GlModal, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; +import { GlModal } from '@gitlab/ui'; import { uniqueId } from 'lodash'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import csrf from '~/lib/utils/csrf'; import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from './confirm_modal_eventhub'; import DomElementListener from './dom_element_listener.vue'; diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue index 3ecfac10f9c..00d12654ee3 100644 --- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue @@ -1,10 +1,10 @@ <script> -import { GlSkeletonLoader, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; -import $ from 'jquery'; -import '~/behaviors/markdown/render_gfm'; +import { GlSkeletonLoader } from '@gitlab/ui'; import { forEach, escape } from 'lodash'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; +import { renderGFM } from '~/behaviors/markdown/render_gfm'; const { CancelToken } = axios; let axiosSource; @@ -96,7 +96,7 @@ export default { this.isLoading = false; this.$nextTick(() => { - $(this.$refs.markdownPreview).renderGFM(); + renderGFM(this.$refs.markdownPreview); }); }) .catch(() => { diff --git a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue index 181c1b89e31..d8a2789a419 100644 --- a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue +++ b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue @@ -265,7 +265,6 @@ export default { <gl-dropdown-item v-for="(option, index) in options" :key="index" - data-qa-selector="quick_range_item" :active="isOptionActive(option)" active-class="active" @click="setQuickRange(option)" diff --git a/app/assets/javascripts/vue_shared/components/dismissible_alert.vue b/app/assets/javascripts/vue_shared/components/dismissible_alert.vue index 0621ec14c6c..8395bc89790 100644 --- a/app/assets/javascripts/vue_shared/components/dismissible_alert.vue +++ b/app/assets/javascripts/vue_shared/components/dismissible_alert.vue @@ -1,5 +1,6 @@ <script> -import { GlAlert, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; +import { GlAlert } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; export default { name: 'DismissibleAlert', 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 755ce004aa9..993b4c11c0e 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 @@ -8,52 +8,44 @@ export const FILTER_ANY = 'Any'; export const FILTER_CURRENT = 'Current'; export const FILTER_UPCOMING = 'Upcoming'; export const FILTER_STARTED = 'Started'; -export const FILTER_NONE_ANY = [FILTER_NONE, FILTER_ANY]; + +export const FILTERS_NONE_ANY = [FILTER_NONE, FILTER_ANY]; export const OPERATOR_IS = '='; export const OPERATOR_IS_TEXT = __('is'); -export const OPERATOR_IS_NOT = '!='; -export const OPERATOR_IS_NOT_TEXT = __('is not one of'); +export const OPERATOR_NOT = '!='; +export const OPERATOR_NOT_TEXT = __('is not one of'); export const OPERATOR_OR = '||'; export const OPERATOR_OR_TEXT = __('is one of'); -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_OR_ONLY = [{ value: OPERATOR_OR, description: OPERATOR_OR_TEXT }]; -export const OPERATOR_IS_AND_IS_NOT = [...OPERATOR_IS_ONLY, ...OPERATOR_IS_NOT_ONLY]; -export const OPERATOR_IS_NOT_OR = [ - ...OPERATOR_IS_ONLY, - ...OPERATOR_IS_NOT_ONLY, - ...OPERATOR_OR_ONLY, -]; - -export const DEFAULT_LABEL_NONE = { value: FILTER_NONE, text: __('None'), title: __('None') }; -export const DEFAULT_LABEL_ANY = { value: FILTER_ANY, text: __('Any'), title: __('Any') }; -export const DEFAULT_NONE_ANY = [DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY]; +export const OPERATORS_IS = [{ value: OPERATOR_IS, description: OPERATOR_IS_TEXT }]; +export const OPERATORS_NOT = [{ value: OPERATOR_NOT, description: OPERATOR_NOT_TEXT }]; +export const OPERATORS_OR = [{ value: OPERATOR_OR, description: OPERATOR_OR_TEXT }]; +export const OPERATORS_IS_NOT = [...OPERATORS_IS, ...OPERATORS_NOT]; +export const OPERATORS_IS_NOT_OR = [...OPERATORS_IS, ...OPERATORS_NOT, ...OPERATORS_OR]; -export const DEFAULT_MILESTONE_UPCOMING = { +export const OPTION_NONE = { value: FILTER_NONE, text: __('None'), title: __('None') }; +export const OPTION_ANY = { value: FILTER_ANY, text: __('Any'), title: __('Any') }; +export const OPTION_CURRENT = { value: FILTER_CURRENT, text: __('Current') }; +export const OPTION_STARTED = { value: FILTER_STARTED, text: __('Started'), title: __('Started') }; +export const OPTION_UPCOMING = { value: FILTER_UPCOMING, text: __('Upcoming'), title: __('Upcoming'), }; -export const DEFAULT_MILESTONE_STARTED = { - value: FILTER_STARTED, - text: __('Started'), - title: __('Started'), -}; -export const DEFAULT_MILESTONES = DEFAULT_NONE_ANY.concat([ - DEFAULT_MILESTONE_UPCOMING, - DEFAULT_MILESTONE_STARTED, -]); -export const SortDirection = { +export const OPTIONS_NONE_ANY = [OPTION_NONE, OPTION_ANY]; + +export const DEFAULT_MILESTONES = OPTIONS_NONE_ANY.concat([OPTION_UPCOMING, OPTION_STARTED]); + +export const SORT_DIRECTION = { descending: 'descending', ascending: 'ascending', }; -export const FILTERED_SEARCH_LABELS = 'labels'; export const FILTERED_SEARCH_TERM = 'filtered-search-term'; +export const TOKEN_TITLE_APPROVED_BY = __('Approved-By'); export const TOKEN_TITLE_ASSIGNEE = s__('SearchToken|Assignee'); export const TOKEN_TITLE_AUTHOR = __('Author'); export const TOKEN_TITLE_CONFIDENTIAL = __('Confidential'); @@ -63,11 +55,14 @@ export const TOKEN_TITLE_MILESTONE = __('Milestone'); export const TOKEN_TITLE_MY_REACTION = __('My-Reaction'); export const TOKEN_TITLE_ORGANIZATION = s__('Crm|Organization'); export const TOKEN_TITLE_RELEASE = __('Release'); +export const TOKEN_TITLE_REVIEWER = s__('SearchToken|Reviewer'); export const TOKEN_TITLE_SOURCE_BRANCH = __('Source Branch'); export const TOKEN_TITLE_STATUS = __('Status'); export const TOKEN_TITLE_TARGET_BRANCH = __('Target Branch'); export const TOKEN_TITLE_TYPE = __('Type'); +export const TOKEN_TITLE_SEARCH_WITHIN = __('Search Within'); +export const TOKEN_TYPE_APPROVED_BY = 'approved-by'; export const TOKEN_TYPE_ASSIGNEE = 'assignee'; export const TOKEN_TYPE_AUTHOR = 'author'; export const TOKEN_TYPE_CONFIDENTIAL = 'confidential'; @@ -84,5 +79,11 @@ export const TOKEN_TYPE_MILESTONE = 'milestone'; export const TOKEN_TYPE_MY_REACTION = 'my-reaction'; export const TOKEN_TYPE_ORGANIZATION = 'organization'; export const TOKEN_TYPE_RELEASE = 'release'; +export const TOKEN_TYPE_REVIEWER = 'reviewer'; +export const TOKEN_TYPE_SOURCE_BRANCH = 'source-branch'; +export const TOKEN_TYPE_STATUS = 'status'; +export const TOKEN_TYPE_TARGET_BRANCH = 'target-branch'; export const TOKEN_TYPE_TYPE = 'type'; export const TOKEN_TYPE_WEIGHT = 'weight'; + +export const TOKEN_TYPE_SEARCH_WITHIN = 'in'; 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 0d0787e7033..34f64dddc41 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 @@ -15,7 +15,7 @@ import RecentSearchesStore from '~/filtered_search/stores/recent_searches_store' import { createAlert } from '~/flash'; import { __ } from '~/locale'; -import { SortDirection } from './constants'; +import { SORT_DIRECTION } from './constants'; import { filterEmptySearchTerm, stripQuotes, uniqueTokens } from './filtered_search_utils'; export default { @@ -107,7 +107,7 @@ export default { recentSearches: [], filterValue: this.initialFilterValue, selectedSortOption: this.sortOptions[0], - selectedSortDirection: SortDirection.descending, + selectedSortDirection: SORT_DIRECTION.descending, }; }, computed: { @@ -130,12 +130,12 @@ export default { ); }, sortDirectionIcon() { - return this.selectedSortDirection === SortDirection.ascending + return this.selectedSortDirection === SORT_DIRECTION.ascending ? 'sort-lowest' : 'sort-highest'; }, sortDirectionTooltip() { - return this.selectedSortDirection === SortDirection.ascending + return this.selectedSortDirection === SORT_DIRECTION.ascending ? __('Sort direction: Ascending') : __('Sort direction: Descending'); }, @@ -267,9 +267,9 @@ export default { }, handleSortDirectionClick() { this.selectedSortDirection = - this.selectedSortDirection === SortDirection.ascending - ? SortDirection.descending - : SortDirection.ascending; + this.selectedSortDirection === SORT_DIRECTION.ascending + ? SORT_DIRECTION.descending + : SORT_DIRECTION.ascending; this.$emit('onSort', this.selectedSortOption.sortDirection[this.selectedSortDirection]); }, handleHistoryItemSelected(filters) { 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 6a4ff07c999..b0fa3e4c27e 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 @@ -9,7 +9,7 @@ import { } from '@gitlab/ui'; import { debounce } from 'lodash'; -import { DEBOUNCE_DELAY, FILTER_NONE_ANY, OPERATOR_IS_NOT } from '../constants'; +import { DEBOUNCE_DELAY, FILTERS_NONE_ANY, OPERATOR_NOT } from '../constants'; import { getRecentlyUsedSuggestions, setTokenValueToRecentlyUsed, @@ -100,9 +100,9 @@ export default { return this.getActiveTokenValue(this.suggestions, this.value.data); }, availableDefaultSuggestions() { - if (this.value.operator === OPERATOR_IS_NOT) { + if (this.value.operator === OPERATOR_NOT) { return this.defaultSuggestions.filter( - (suggestion) => !FILTER_NONE_ANY.includes(suggestion.value), + (suggestion) => !FILTERS_NONE_ANY.includes(suggestion.value), ); } return this.defaultSuggestions; diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/crm_contact_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/crm_contact_token.vue index d34cfb922a9..e0fa06c159e 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/crm_contact_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/crm_contact_token.vue @@ -8,7 +8,7 @@ import { isPositiveInteger } from '~/lib/utils/number_utils'; import { __ } from '~/locale'; import searchCrmContactsQuery from '../queries/search_crm_contacts.query.graphql'; -import { DEFAULT_NONE_ANY } from '../constants'; +import { OPTIONS_NONE_ANY } from '../constants'; import BaseToken from './base_token.vue'; @@ -39,7 +39,7 @@ export default { }, computed: { defaultContacts() { - return this.config.defaultContacts || DEFAULT_NONE_ANY; + return this.config.defaultContacts || OPTIONS_NONE_ANY; }, namespace() { return this.config.isProject ? ITEM_TYPE.PROJECT : ITEM_TYPE.GROUP; diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/crm_organization_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/crm_organization_token.vue index c7c9350ee93..3f030c8698c 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/crm_organization_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/crm_organization_token.vue @@ -8,7 +8,7 @@ import { isPositiveInteger } from '~/lib/utils/number_utils'; import { __ } from '~/locale'; import searchCrmOrganizationsQuery from '../queries/search_crm_organizations.query.graphql'; -import { DEFAULT_NONE_ANY } from '../constants'; +import { OPTIONS_NONE_ANY } from '../constants'; import BaseToken from './base_token.vue'; @@ -39,7 +39,7 @@ export default { }, computed: { defaultOrganizations() { - return this.config.defaultOrganizations || DEFAULT_NONE_ANY; + return this.config.defaultOrganizations || OPTIONS_NONE_ANY; }, namespace() { return this.config.isProject ? ITEM_TYPE.PROJECT : ITEM_TYPE.GROUP; 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 929823f7308..74905dc2ae0 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 @@ -3,7 +3,7 @@ import { GlFilteredSearchSuggestion } from '@gitlab/ui'; import { createAlert } from '~/flash'; import { __ } from '~/locale'; import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue'; -import { DEFAULT_NONE_ANY } from '../constants'; +import { OPTIONS_NONE_ANY } from '../constants'; import { stripQuotes } from '../filtered_search_utils'; export default { @@ -33,7 +33,7 @@ export default { }, computed: { defaultEmojis() { - return this.config.defaultEmojis || DEFAULT_NONE_ANY; + return this.config.defaultEmojis || OPTIONS_NONE_ANY; }, }, methods: { 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 bce0c11aafd..71c50ef292a 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 @@ -5,7 +5,7 @@ import { createAlert } from '~/flash'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { __ } from '~/locale'; -import { DEFAULT_NONE_ANY } from '../constants'; +import { OPTIONS_NONE_ANY } from '../constants'; import { stripQuotes } from '../filtered_search_utils'; import BaseToken from './base_token.vue'; @@ -38,7 +38,7 @@ export default { }, computed: { defaultLabels() { - return this.config.defaultLabels || DEFAULT_NONE_ANY; + return this.config.defaultLabels || OPTIONS_NONE_ANY; }, }, methods: { diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue index 59701b4959e..6d681aab3ca 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue @@ -3,7 +3,7 @@ import { GlFilteredSearchSuggestion } from '@gitlab/ui'; import { createAlert } from '~/flash'; import { __ } from '~/locale'; import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue'; -import { DEFAULT_NONE_ANY } from '../constants'; +import { OPTIONS_NONE_ANY } from '../constants'; export default { components: { @@ -32,7 +32,7 @@ export default { }, computed: { defaultReleases() { - return this.config.defaultReleases || DEFAULT_NONE_ANY; + return this.config.defaultReleases || OPTIONS_NONE_ANY; }, }, methods: { 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/user_token.vue index 7c184a3c391..28e65c1185f 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/user_token.vue @@ -4,7 +4,7 @@ import { compact } from 'lodash'; import { createAlert } from '~/flash'; import { __ } from '~/locale'; -import { DEFAULT_NONE_ANY } from '../constants'; +import { OPTIONS_NONE_ANY } from '../constants'; import BaseToken from './base_token.vue'; @@ -30,30 +30,30 @@ export default { }, data() { return { - authors: this.config.initialAuthors || [], + users: this.config.initialUsers || [], loading: false, }; }, computed: { - defaultAuthors() { - return this.config.defaultAuthors || DEFAULT_NONE_ANY; + defaultUsers() { + return this.config.defaultUsers || OPTIONS_NONE_ANY; }, - preloadedAuthors() { - return this.config.preloadedAuthors || []; + preloadedUsers() { + return this.config.preloadedUsers || []; }, }, methods: { - getActiveAuthor(authors, data) { - return authors.find((author) => author.username.toLowerCase() === data.toLowerCase()); + getActiveUser(users, data) { + return users.find((user) => user.username.toLowerCase() === data.toLowerCase()); }, - getAvatarUrl(author) { - return author.avatarUrl || author.avatar_url; + getAvatarUrl(user) { + return user.avatarUrl || user.avatar_url; }, - fetchAuthors(searchTerm) { + fetchUsers(searchTerm) { this.loading = true; const fetchPromise = this.config.fetchPath - ? this.config.fetchAuthors(this.config.fetchPath, searchTerm) - : this.config.fetchAuthors(searchTerm); + ? this.config.fetchUsers(this.config.fetchPath, searchTerm) + : this.config.fetchUsers(searchTerm); fetchPromise .then((res) => { @@ -62,7 +62,7 @@ export default { // return response differently // TODO: rm when completed https://gitlab.com/gitlab-org/gitlab/-/issues/345756 - this.authors = Array.isArray(res) ? compact(res) : compact(res.data); + this.users = Array.isArray(res) ? compact(res) : compact(res.data); }) .catch(() => createAlert({ @@ -83,12 +83,12 @@ export default { :value="value" :active="active" :suggestions-loading="loading" - :suggestions="authors" - :get-active-token-value="getActiveAuthor" - :default-suggestions="defaultAuthors" - :preloaded-suggestions="preloadedAuthors" + :suggestions="users" + :get-active-token-value="getActiveUser" + :default-suggestions="defaultUsers" + :preloaded-suggestions="preloadedUsers" v-bind="$attrs" - @fetch-suggestions="fetchAuthors" + @fetch-suggestions="fetchUsers" v-on="$listeners" > <template #view="{ viewTokenProps: { inputValue, activeTokenValue } }"> @@ -102,15 +102,15 @@ export default { </template> <template #suggestions-list="{ suggestions }"> <gl-filtered-search-suggestion - v-for="author in suggestions" - :key="author.username" - :value="author.username" + v-for="user in suggestions" + :key="user.username" + :value="user.username" > <div class="gl-display-flex"> - <gl-avatar :size="32" :src="getAvatarUrl(author)" /> + <gl-avatar :size="32" :src="getAvatarUrl(user)" /> <div> - <div>{{ author.name }}</div> - <div>@{{ author.username }}</div> + <div>{{ user.name }}</div> + <div>@{{ user.username }}</div> </div> </div> </gl-filtered-search-suggestion> diff --git a/app/assets/javascripts/vue_shared/components/group_select/group_select.vue b/app/assets/javascripts/vue_shared/components/group_select/group_select.vue index 1de6c0121bc..5db723e1e5a 100644 --- a/app/assets/javascripts/vue_shared/components/group_select/group_select.vue +++ b/app/assets/javascripts/vue_shared/components/group_select/group_select.vue @@ -1,6 +1,6 @@ <script> import { debounce } from 'lodash'; -import { GlListbox } from '@gitlab/ui'; +import { GlCollapsibleListbox } from '@gitlab/ui'; import axios from '~/lib/utils/axios_utils'; import Api from '~/api'; import { __ } from '~/locale'; @@ -18,7 +18,7 @@ const MINIMUM_QUERY_LENGTH = 3; export default { components: { - GlListbox, + GlCollapsibleListbox, }, props: { inputName: { @@ -167,7 +167,7 @@ export default { <template> <div> - <gl-listbox + <gl-collapsible-listbox ref="listbox" v-model="selected" :header-text="$options.i18n.selectGroup" @@ -188,7 +188,7 @@ export default { </div> <div class="gl-text-gray-300">{{ item.full_path }}</div> </template> - </gl-listbox> + </gl-collapsible-listbox> <div class="flash-container"></div> <input :id="inputId" data-testid="input" type="hidden" :name="inputName" :value="inputValue" /> </div> diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue index 96f7427dda1..3c4ae08d2f7 100644 --- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue +++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue @@ -1,12 +1,6 @@ <script> -import { - GlTooltipDirective, - GlButton, - GlSafeHtmlDirective, - GlAvatarLink, - GlAvatarLabeled, - GlTooltip, -} from '@gitlab/ui'; +import { GlTooltipDirective, GlButton, GlAvatarLink, GlAvatarLabeled, GlTooltip } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import { isGid, getIdFromGraphQLId } from '~/graphql_shared/utils'; import { glEmojiTag } from '~/emoji'; import { __, sprintf } from '~/locale'; @@ -31,7 +25,7 @@ export default { }, directives: { GlTooltip: GlTooltipDirective, - SafeHtml: GlSafeHtmlDirective, + SafeHtml, }, EMOJI_REF: 'EMOJI_REF', props: { diff --git a/app/assets/javascripts/vue_shared/components/help_popover.vue b/app/assets/javascripts/vue_shared/components/help_popover.vue index f349aa78bac..92d468cf970 100644 --- a/app/assets/javascripts/vue_shared/components/help_popover.vue +++ b/app/assets/javascripts/vue_shared/components/help_popover.vue @@ -1,5 +1,6 @@ <script> -import { GlButton, GlPopover, GlSafeHtmlDirective } from '@gitlab/ui'; +import { GlButton, GlPopover } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; /** * Render a button with a question mark icon @@ -12,7 +13,7 @@ export default { GlPopover, }, directives: { - SafeHtml: GlSafeHtmlDirective, + SafeHtml, }, props: { options: { diff --git a/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.stories.js b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.stories.js new file mode 100644 index 00000000000..4106de371cb --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.stories.js @@ -0,0 +1,26 @@ +import ListboxInput from './listbox_input.vue'; + +export default { + component: ListboxInput, + title: 'vue_shared/listbox_input', +}; + +const Template = (args, { argTypes }) => ({ + components: { ListboxInput }, + data() { + return { selected: null }; + }, + props: Object.keys(argTypes), + template: '<listbox-input v-model="selected" v-bind="$props" />', +}); + +export const Default = Template.bind({}); +Default.args = { + name: 'input_name', + defaultToggleText: 'Select an option', + items: [ + { text: 'Option 1', value: '1' }, + { text: 'Option 2', value: '2' }, + { text: 'Option 3', value: '3' }, + ], +}; diff --git a/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue new file mode 100644 index 00000000000..b1809e6a9f3 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue @@ -0,0 +1,110 @@ +<script> +import { GlListbox } from '@gitlab/ui'; +import { __ } from '~/locale'; + +const MIN_ITEMS_COUNT_FOR_SEARCHING = 20; + +export default { + i18n: { + noResultsText: __('No results found'), + }, + components: { + GlListbox, + }, + model: GlListbox.model, + props: { + name: { + type: String, + required: true, + }, + defaultToggleText: { + type: String, + required: true, + }, + selected: { + type: String, + required: false, + default: null, + }, + items: { + type: GlListbox.props.items.type, + required: true, + }, + }, + data() { + return { + searchString: '', + }; + }, + computed: { + allOptions() { + const allOptions = []; + + const getOptions = (options) => { + for (let i = 0; i < options.length; i += 1) { + const option = options[i]; + if (option.options) { + getOptions(option.options); + } else { + allOptions.push(option); + } + } + }; + getOptions(this.items); + + return allOptions; + }, + isGrouped() { + return this.items.some((item) => item.options !== undefined); + }, + isSearchable() { + return this.allOptions.length > MIN_ITEMS_COUNT_FOR_SEARCHING; + }, + filteredItems() { + const searchString = this.searchString.toLowerCase(); + + if (!searchString) { + return this.items; + } + + if (this.isGrouped) { + return this.items + .map(({ text, options }) => { + return { + text, + options: options.filter((option) => option.text.toLowerCase().includes(searchString)), + }; + }) + .filter(({ options }) => options.length); + } + + return this.items.filter((item) => item.text.toLowerCase().includes(searchString)); + }, + toggleText() { + return this.selected + ? this.allOptions.find((option) => option.value === this.selected).text + : this.defaultToggleText; + }, + }, + methods: { + search(searchString) { + this.searchString = searchString; + }, + }, +}; +</script> + +<template> + <div> + <gl-listbox + :selected="selected" + :toggle-text="toggleText" + :items="filteredItems" + :searchable="isSearchable" + :no-results-text="$options.i18n.noResultsText" + @search="search" + @select="$emit($options.model.event, $event)" + /> + <input ref="input" type="hidden" :name="name" :value="selected" /> + </div> +</template> 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 caec49c557a..f51ec715678 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue @@ -74,7 +74,7 @@ export default { @submit="onApply" /> <gl-button - class="gl-w-auto! gl-mt-3 gl-text-center! gl-hover-text-white! gl-transition-medium! float-right" + class="gl-w-auto! gl-mt-3 gl-text-center! gl-transition-medium! float-right" category="primary" variant="confirm" data-qa-selector="commit_with_custom_message_button" diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index 657e4498b53..b5f2602af5e 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -1,15 +1,16 @@ <script> -import { GlIcon, GlSafeHtmlDirective } from '@gitlab/ui'; +import { GlIcon } from '@gitlab/ui'; import $ from 'jquery'; -import '~/behaviors/markdown/render_gfm'; import { debounce, unescape } from 'lodash'; import { createAlert } from '~/flash'; import GLForm from '~/gl_form'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import axios from '~/lib/utils/axios_utils'; import { stripHtml } from '~/lib/utils/text_utility'; import { __, sprintf } from '~/locale'; import Suggestions from '~/vue_shared/components/markdown/suggestions.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { renderGFM } from '~/behaviors/markdown/render_gfm'; import MarkdownHeader from './header.vue'; import MarkdownToolbar from './toolbar.vue'; @@ -25,7 +26,7 @@ export default { Suggestions, }, directives: { - SafeHtml: GlSafeHtmlDirective, + SafeHtml, }, mixins: [glFeatureFlagsMixin()], props: { @@ -313,7 +314,9 @@ export default { this.markdownPreview = data.body || __('Nothing to preview.'); this.$nextTick() - .then(() => $(this.$refs['markdown-preview']).renderGFM()) + .then(() => { + renderGFM(this.$refs['markdown-preview']); + }) .catch(() => createAlert({ message: __('Error rendering Markdown preview'), diff --git a/app/assets/javascripts/vue_shared/components/markdown/field_view.vue b/app/assets/javascripts/vue_shared/components/markdown/field_view.vue index d77123371f2..84d40db07bb 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field_view.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field_view.vue @@ -1,15 +1,9 @@ <script> -import $ from 'jquery'; -import '~/behaviors/markdown/render_gfm'; +import { renderGFM } from '~/behaviors/markdown/render_gfm'; export default { mounted() { - this.renderGFM(); - }, - methods: { - renderGFM() { - $(this.$el).renderGFM(); - }, + renderGFM(this.$el); }, }; </script> diff --git a/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue b/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue index c0712e46613..d01eae0308f 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue @@ -82,6 +82,11 @@ export default { required: false, default: false, }, + useBottomToolbar: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -197,6 +202,7 @@ export default { :uploads-path="uploadsPath" :markdown="value" :autofocus="contentEditorAutofocused" + :use-bottom-toolbar="useBottomToolbar" @initialized="setEditorAsAutofocused" @change="updateMarkdownFromContentEditor" @loading="disableSwitchEditingControl" 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 a04f8616acb..0b598d3acaf 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,5 +1,5 @@ <script> -import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; export default { name: 'SuggestionDiffRow', diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue index 30d72332c90..c307601e670 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue @@ -1,6 +1,6 @@ <script> -import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; import Vue from 'vue'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import { createAlert } from '~/flash'; import { __ } from '~/locale'; import SuggestionDiff from './suggestion_diff.vue'; diff --git a/app/assets/javascripts/vue_shared/components/markdown_drawer/makrdown_drawer.stories.js b/app/assets/javascripts/vue_shared/components/markdown_drawer/markdown_drawer.stories.js index 03bd64e2a57..03bd64e2a57 100644 --- a/app/assets/javascripts/vue_shared/components/markdown_drawer/makrdown_drawer.stories.js +++ b/app/assets/javascripts/vue_shared/components/markdown_drawer/markdown_drawer.stories.js diff --git a/app/assets/javascripts/vue_shared/components/markdown_drawer/markdown_drawer.vue b/app/assets/javascripts/vue_shared/components/markdown_drawer/markdown_drawer.vue index a4b509f8656..379f22fdc6f 100644 --- a/app/assets/javascripts/vue_shared/components/markdown_drawer/markdown_drawer.vue +++ b/app/assets/javascripts/vue_shared/components/markdown_drawer/markdown_drawer.vue @@ -1,9 +1,9 @@ <script> -import { GlSafeHtmlDirective as SafeHtml, GlDrawer, GlAlert, GlSkeletonLoader } from '@gitlab/ui'; -import $ from 'jquery'; -import '~/behaviors/markdown/render_gfm'; +import { GlDrawer, GlAlert, GlSkeletonLoader } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import { s__ } from '~/locale'; import { contentTop } from '~/lib/utils/common_utils'; +import { renderGFM } from '~/behaviors/markdown/render_gfm'; import { getRenderedMarkdown } from './utils/fetch'; export const cache = {}; @@ -34,13 +34,9 @@ export default { title: '', body: null, open: false, + drawerTop: '0px', }; }, - computed: { - drawerOffsetTop() { - return `${contentTop()}px`; - }, - }, watch: { documentPath: { immediate: true, @@ -76,18 +72,23 @@ export default { cache[this.documentPath] = { title, body }; } }, + getDrawerTop() { + this.drawerTop = `${contentTop()}px`; + }, renderGLFM() { this.$nextTick(() => { - $(this.$refs['content-element']).renderGFM(); + renderGFM(this.$refs['content-element']); }); }, closeDrawer() { this.open = false; }, toggleDrawer() { + this.getDrawerTop(); this.open = !this.open; }, openDrawer() { + this.getDrawerTop(); this.open = true; }, }, @@ -97,7 +98,7 @@ export default { }; </script> <template> - <gl-drawer :header-height="drawerOffsetTop" :open="open" header-sticky @close="closeDrawer"> + <gl-drawer :header-height="drawerTop" :open="open" header-sticky @close="closeDrawer"> <template #title> <h4 data-testid="title-element" class="gl-m-0">{{ title }}</h4> </template> diff --git a/app/assets/javascripts/vue_shared/components/markdown_drawer/utils/fetch.js b/app/assets/javascripts/vue_shared/components/markdown_drawer/utils/fetch.js index 7c8e1bc160a..27237f2f16b 100644 --- a/app/assets/javascripts/vue_shared/components/markdown_drawer/utils/fetch.js +++ b/app/assets/javascripts/vue_shared/components/markdown_drawer/utils/fetch.js @@ -16,7 +16,7 @@ export const getRenderedMarkdown = (documentPath) => { return axios .get(helpPagePath(documentPath)) .then(({ data }) => { - const { body, title } = splitDocument(data.html); + const { body, title } = splitDocument(data); return { body, title, diff --git a/app/assets/javascripts/vue_shared/components/metric_images/metric_images_tab.vue b/app/assets/javascripts/vue_shared/components/metric_images/metric_images_tab.vue index e23721da223..2cadc87eca3 100644 --- a/app/assets/javascripts/vue_shared/components/metric_images/metric_images_tab.vue +++ b/app/assets/javascripts/vue_shared/components/metric_images/metric_images_tab.vue @@ -1,5 +1,5 @@ <script> -import { GlFormGroup, GlFormInput, GlLoadingIcon, GlModal, GlTab } from '@gitlab/ui'; +import { GlFormGroup, GlFormInput, GlLoadingIcon, GlModal } from '@gitlab/ui'; import { mapState, mapActions } from 'vuex'; import { __, s__ } from '~/locale'; import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue'; @@ -11,7 +11,6 @@ export default { GlFormInput, GlLoadingIcon, GlModal, - GlTab, MetricImagesTable, UploadDropzone, }, @@ -82,7 +81,7 @@ export default { </script> <template> - <gl-tab :title="s__('Incident|Metrics')" data-testid="metrics-tab"> + <div> <div v-if="isLoadingMetricImages"> <gl-loading-icon class="gl-p-5" size="sm" /> </div> @@ -117,5 +116,5 @@ export default { :drop-description-message="$options.i18n.dropDescription" @change="openMetricDialog" /> - </gl-tab> + </div> </template> 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 cf34a60c363..748d6082abd 100644 --- a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue +++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue @@ -16,8 +16,9 @@ * :note="{body: 'This is a note'}" * /> */ -import { GlSafeHtmlDirective as SafeHtml, GlAvatarLink, GlAvatar } from '@gitlab/ui'; +import { GlAvatarLink, GlAvatar } from '@gitlab/ui'; import { mapGetters } from 'vuex'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import { renderMarkdown } from '~/notes/utils'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; 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 1ae5045b34f..1cbbdf0deb0 100644 --- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue +++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue @@ -16,22 +16,17 @@ * }" * /> */ -import { - GlButton, - GlSkeletonLoader, - GlTooltipDirective, - GlIcon, - GlSafeHtmlDirective as SafeHtml, -} from '@gitlab/ui'; +import { GlButton, GlSkeletonLoader, GlTooltipDirective, GlIcon } from '@gitlab/ui'; import $ from 'jquery'; import { mapGetters, mapActions, mapState } from 'vuex'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import descriptionVersionHistoryMixin from 'ee_else_ce/notes/mixins/description_version_history'; -import '~/behaviors/markdown/render_gfm'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; import NoteHeader from '~/notes/components/note_header.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { spriteIcon } from '~/lib/utils/common_utils'; +import { renderGFM } from '~/behaviors/markdown/render_gfm'; import TimelineEntryItem from './timeline_entry_item.vue'; const MAX_VISIBLE_COMMIT_LIST_COUNT = 3; @@ -94,7 +89,7 @@ export default { }, }, mounted() { - $(this.$refs['gfm-content']).renderGFM(); + renderGFM(this.$refs['gfm-content']); }, methods: { ...mapActions(['fetchDescriptionVersion', 'softDeleteDescriptionVersion']), @@ -205,7 +200,7 @@ export default { <tr v-for="line in lines" v-once :key="line.line_code" class="line_holder"> <td :class="line.type" - class="diff-line-num old_line gl-border-bottom-0! gl-border-top-0!" + class="diff-line-num old_line gl-border-bottom-0! gl-border-top-0! gl-border-0! gl-rounded-0!" > {{ line.old_line }} </td> @@ -217,7 +212,7 @@ export default { </td> <td :class="line.type" - class="line_content gl-display-table-cell!" + class="line_content gl-display-table-cell! gl-border-0! gl-rounded-0!" v-html="line.rich_text /* eslint-disable-line vue/no-v-html */" ></td> </tr> 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 867222279b2..57e3a97244e 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 @@ -1,22 +1,19 @@ <script> -import { - GlAlert, - GlBadge, - GlPagination, - GlTab, - GlTabs, - GlSafeHtmlDirective as SafeHtml, -} from '@gitlab/ui'; +import { GlAlert, GlBadge, GlPagination, GlTab, GlTabs } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import Api from '~/api'; import { updateHistory, setUrlParams } from '~/lib/utils/url_utility'; import Tracking from '~/tracking'; import { - OPERATOR_IS_ONLY, + FILTERED_SEARCH_TERM, + OPERATORS_IS, TOKEN_TITLE_ASSIGNEE, TOKEN_TITLE_AUTHOR, + TOKEN_TYPE_ASSIGNEE, + TOKEN_TYPE_AUTHOR, } from '~/vue_shared/components/filtered_search_bar/constants'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; -import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue'; +import UserToken from '~/vue_shared/components/filtered_search_bar/tokens/user_token.vue'; import { initialPaginationState, defaultI18n, defaultPageSize } from './constants'; import { isAny } from './utils'; @@ -95,7 +92,7 @@ export default { filterSearchTokens: { type: Array, required: false, - default: () => ['author_username', 'assignee_username'], + default: () => [TOKEN_TYPE_AUTHOR, TOKEN_TYPE_ASSIGNEE], }, }, data() { @@ -113,26 +110,26 @@ export default { defaultTokens() { return [ { - type: 'author_username', + type: TOKEN_TYPE_AUTHOR, icon: 'user', title: TOKEN_TITLE_AUTHOR, unique: true, symbol: '@', - token: AuthorToken, - operators: OPERATOR_IS_ONLY, + token: UserToken, + operators: OPERATORS_IS, fetchPath: this.projectPath, - fetchAuthors: Api.projectUsers.bind(Api), + fetchUsers: Api.projectUsers.bind(Api), }, { - type: 'assignee_username', + type: TOKEN_TYPE_ASSIGNEE, icon: 'user', title: TOKEN_TITLE_ASSIGNEE, unique: true, symbol: '@', - token: AuthorToken, - operators: OPERATOR_IS_ONLY, + token: UserToken, + operators: OPERATORS_IS, fetchPath: this.projectPath, - fetchAuthors: Api.projectUsers.bind(Api), + fetchUsers: Api.projectUsers.bind(Api), }, ]; }, @@ -144,14 +141,14 @@ export default { if (this.authorUsername) { value.push({ - type: 'author_username', + type: TOKEN_TYPE_AUTHOR, value: { data: this.authorUsername }, }); } if (this.assigneeUsername) { value.push({ - type: 'assignee_username', + type: TOKEN_TYPE_ASSIGNEE, value: { data: this.assigneeUsername }, }); } @@ -226,13 +223,13 @@ export default { filters.forEach((filter) => { if (typeof filter === 'object') { switch (filter.type) { - case 'author_username': + case TOKEN_TYPE_AUTHOR: filterParams.authorUsername = isAny(filter.value.data); break; - case 'assignee_username': + case TOKEN_TYPE_ASSIGNEE: filterParams.assigneeUsername = isAny(filter.value.data); break; - case 'filtered-search-term': + case FILTERED_SEARCH_TERM: if (filter.value.data !== '') filterParams.search = filter.value.data; break; default: 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 66643ff4026..16bc8070dc1 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 @@ -1,9 +1,10 @@ <script> -import { GlButton, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; +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.vue'; +import SafeHtml from '~/vue_shared/directives/safe_html'; export default { name: 'ProjectListItem', diff --git a/app/assets/javascripts/vue_shared/components/registry/registry_search.vue b/app/assets/javascripts/vue_shared/components/registry/registry_search.vue index 8c9c7c63db1..c990baaa2f3 100644 --- a/app/assets/javascripts/vue_shared/components/registry/registry_search.vue +++ b/app/assets/javascripts/vue_shared/components/registry/registry_search.vue @@ -1,7 +1,7 @@ <script> import { GlSorting, GlSortingItem, GlFilteredSearch } from '@gitlab/ui'; -import { FILTERED_SEARCH_TERM } from '~/packages_and_registries/shared/constants'; import { SORT_DIRECTION_UI } from '~/search/sort/constants'; +import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants'; const ASCENDING_ORDER = 'asc'; const DESCENDING_ORDER = 'desc'; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue b/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue deleted file mode 100644 index 6538de085b0..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue +++ /dev/null @@ -1,86 +0,0 @@ -<script> -import { GlLoadingIcon, GlSprintf } from '@gitlab/ui'; -import { s__, __, sprintf } from '~/locale'; -import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; - -/** - * Renders an inline field, whose value can be copied to the clipboard, - * for use in the GitLab sidebar (issues, MRs, etc.). - */ -export default { - name: 'CopyableField', - components: { - ClipboardButton, - GlLoadingIcon, - GlSprintf, - }, - props: { - value: { - type: String, - required: true, - }, - name: { - type: String, - required: true, - }, - isLoading: { - type: Boolean, - required: false, - default: false, - }, - clipboardTooltipText: { - type: String, - required: false, - default: undefined, - }, - }, - computed: { - clipboardProps() { - return { - category: 'tertiary', - tooltipBoundary: 'viewport', - tooltipPlacement: 'left', - text: this.value, - title: - this.clipboardTooltipText || - sprintf(this.$options.i18n.clipboardTooltip, { name: this.name }), - }; - }, - loadingIconLabel() { - return sprintf(this.$options.i18n.loadingIconLabel, { name: this.name }); - }, - }, - i18n: { - loadingIconLabel: __('Loading %{name}'), - clipboardTooltip: __('Copy %{name}'), - templateText: s__('Sidebar|%{name}: %{value}'), - }, -}; -</script> - -<template> - <div> - <clipboard-button - v-if="!isLoading" - css-class="sidebar-collapsed-icon js-dont-change-state gl-rounded-0! gl-hover-bg-transparent" - v-bind="clipboardProps" - /> - - <div - class="gl-display-flex gl-align-items-center gl-justify-content-space-between hide-collapsed" - > - <span - class="gl-overflow-hidden gl-text-overflow-ellipsis gl-white-space-nowrap" - :title="value" - > - <gl-sprintf :message="$options.i18n.templateText"> - <template #name>{{ name }}</template> - <template #value>{{ value }}</template> - </gl-sprintf> - </span> - - <gl-loading-icon v-if="isLoading" size="sm" inline :label="loadingIconLabel" /> - <clipboard-button v-else size="small" v-bind="clipboardProps" /> - </div> - </div> -</template> 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 deleted file mode 100644 index 02323e5a0c6..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue +++ /dev/null @@ -1,217 +0,0 @@ -<script> -import { - GlIcon, - GlLoadingIcon, - GlDropdown, - GlDropdownForm, - GlDropdownItem, - GlSearchBoxByType, - GlButton, - GlTooltipDirective as GlTooltip, -} from '@gitlab/ui'; - -import axios from '~/lib/utils/axios_utils'; - -export default { - components: { - GlIcon, - GlLoadingIcon, - GlDropdown, - GlDropdownForm, - GlDropdownItem, - GlSearchBoxByType, - GlButton, - }, - directives: { - GlTooltip, - }, - props: { - projectsFetchPath: { - type: String, - required: true, - }, - dropdownButtonTitle: { - type: String, - required: true, - }, - dropdownHeaderTitle: { - type: String, - required: true, - }, - moveInProgress: { - type: Boolean, - required: false, - default: false, - }, - disabled: { - type: Boolean, - required: false, - default: false, - }, - }, - data() { - return { - projectsListLoading: false, - projectsListLoadFailed: false, - searchKey: '', - projects: [], - selectedProject: null, - projectItemClick: false, - }; - }, - computed: { - hasNoSearchResults() { - return Boolean( - !this.projectsListLoading && - !this.projectsListLoadFailed && - this.searchKey && - !this.projects.length, - ); - }, - failedToLoadResults() { - return !this.projectsListLoading && this.projectsListLoadFailed; - }, - }, - watch: { - searchKey(value = '') { - this.fetchProjects(value); - }, - }, - methods: { - fetchProjects(search = '') { - this.projectsListLoading = true; - this.projectsListLoadFailed = false; - return axios - .get(this.projectsFetchPath, { - params: { - search, - }, - }) - .then(({ data }) => { - this.projects = data; - this.$refs.searchInput.focusInput(); - }) - .catch(() => { - this.projectsListLoadFailed = true; - }) - .finally(() => { - this.projectsListLoading = false; - }); - }, - isSelectedProject(project) { - if (this.selectedProject) { - return this.selectedProject.id === project.id; - } - return false; - }, - /** - * This handler is to prevent dropdown - * from closing when an item is selected - * and emit an event only when dropdown closes. - */ - handleDropdownHide(e) { - if (this.projectItemClick) { - e.preventDefault(); - this.projectItemClick = false; - } else { - this.$emit('dropdown-close'); - } - }, - handleDropdownCloseClick() { - this.$refs.dropdown.hide(); - }, - handleProjectSelect(project) { - this.selectedProject = project.id === this.selectedProject?.id ? null : project; - this.projectItemClick = true; - }, - handleMoveClick() { - this.$refs.dropdown.hide(); - this.$emit('move-issuable', this.selectedProject); - }, - }, -}; -</script> - -<template> - <div class="js-issuable-move-block issuable-move-dropdown sidebar-move-issue-dropdown"> - <div - v-gl-tooltip.left.viewport - data-testid="move-collapsed" - :title="dropdownButtonTitle" - class="sidebar-collapsed-icon" - @click="$emit('toggle-collapse')" - > - <gl-icon name="arrow-right" /> - </div> - <gl-dropdown - ref="dropdown" - :block="true" - :disabled="moveInProgress || disabled" - class="hide-collapsed" - toggle-class="js-sidebar-dropdown-toggle" - @shown="fetchProjects" - @hide="handleDropdownHide" - > - <template #button-content - ><gl-loading-icon v-if="moveInProgress" size="sm" class="gl-mr-3" />{{ - dropdownButtonTitle - }}</template - > - <gl-dropdown-form class="gl-pt-0"> - <div - data-testid="header" - class="gl-display-flex gl-pb-3 gl-border-1 gl-border-b-solid gl-border-gray-100" - > - <span class="gl-flex-grow-1 gl-text-center gl-font-weight-bold gl-py-1">{{ - dropdownHeaderTitle - }}</span> - <gl-button - variant="link" - icon="close" - class="gl-mr-2 gl-w-auto! gl-p-2!" - :aria-label="__('Close')" - @click.prevent="handleDropdownCloseClick" - /> - </div> - <gl-search-box-by-type - ref="searchInput" - v-model.trim="searchKey" - :placeholder="__('Search project')" - :debounce="300" - /> - <div data-testid="content" class="dropdown-content"> - <gl-loading-icon v-if="projectsListLoading" size="lg" class="gl-p-5" /> - <ul v-else> - <gl-dropdown-item - v-for="project in projects" - :key="project.id" - is-check-item - :is-checked="isSelectedProject(project)" - @click.stop.prevent="handleProjectSelect(project)" - >{{ project.name_with_namespace }}</gl-dropdown-item - > - </ul> - <div v-if="hasNoSearchResults" class="gl-text-center gl-p-3"> - {{ __('No matching results') }} - </div> - <div v-if="failedToLoadResults" class="gl-text-center gl-p-3"> - {{ __('Failed to load projects') }} - </div> - </div> - <div - data-testid="footer" - class="gl-pt-3 gl-px-3 gl-border-1 gl-border-t-solid gl-border-gray-100" - > - <gl-button - category="primary" - variant="confirm" - :disabled="!Boolean(selectedProject)" - class="gl-text-center! issuable-move-button" - @click="handleMoveClick" - >{{ __('Move') }}</gl-button - > - </div> - </gl-dropdown-form> - </gl-dropdown> - </div> -</template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/constants.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/constants.js deleted file mode 100644 index 00c54313292..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/constants.js +++ /dev/null @@ -1,5 +0,0 @@ -export const DropdownVariant = { - Sidebar: 'sidebar', - Standalone: 'standalone', - Embedded: 'embedded', -}; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue deleted file mode 100644 index 9388ef4ba45..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue +++ /dev/null @@ -1,45 +0,0 @@ -<script> -import { GlButton, GlIcon } from '@gitlab/ui'; -import { mapActions, mapGetters } from 'vuex'; - -// @deprecated This component should only be used when there is no GraphQL API. -// In most cases you should use -// `app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget` instead. -export default { - components: { - GlButton, - GlIcon, - }, - computed: { - ...mapGetters([ - 'dropdownButtonText', - 'isDropdownVariantStandalone', - 'isDropdownVariantEmbedded', - ]), - }, - methods: { - ...mapActions(['toggleDropdownContents']), - handleButtonClick(e) { - if (this.isDropdownVariantStandalone || this.isDropdownVariantEmbedded) { - this.toggleDropdownContents(); - } - - if (this.isDropdownVariantStandalone) { - e.stopPropagation(); - } - }, - }, -}; -</script> - -<template> - <gl-button - class="labels-select-dropdown-button js-dropdown-button w-100 text-left" - @click="handleButtonClick" - > - <span class="dropdown-toggle-text gl-pointer-events-none flex-fill"> - {{ dropdownButtonText }} - </span> - <gl-icon name="chevron-down" class="gl-pointer-events-none float-right" /> - </gl-button> -</template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue deleted file mode 100644 index 1064cbc26e3..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue +++ /dev/null @@ -1,48 +0,0 @@ -<script> -import { mapGetters, mapState } from 'vuex'; - -import DropdownContentsCreateView from './dropdown_contents_create_view.vue'; -import DropdownContentsLabelsView from './dropdown_contents_labels_view.vue'; - -// @deprecated This component should only be used when there is no GraphQL API. -// In most cases you should use -// `app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue` instead. -export default { - components: { - DropdownContentsLabelsView, - DropdownContentsCreateView, - }, - props: { - renderOnTop: { - type: Boolean, - required: false, - default: false, - }, - }, - computed: { - ...mapState(['showDropdownContentsCreateView']), - ...mapGetters(['isDropdownVariantSidebar']), - dropdownContentsView() { - if (this.showDropdownContentsCreateView) { - return 'dropdown-contents-create-view'; - } - return 'dropdown-contents-labels-view'; - }, - directionStyle() { - const bottom = this.isDropdownVariantSidebar ? '3rem' : '2rem'; - return this.renderOnTop ? { bottom } : {}; - }, - }, -}; -</script> - -<template> - <div - class="labels-select-dropdown-contents gl-w-full gl-my-2 gl-py-3 gl-rounded-base gl-absolute" - data-testid="labels-select-dropdown-contents" - data-qa-selector="labels_dropdown_content" - :style="directionStyle" - > - <component :is="dropdownContentsView" /> - </div> -</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 deleted file mode 100644 index 3ff3755de46..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue +++ /dev/null @@ -1,122 +0,0 @@ -<script> -import { GlTooltipDirective, GlButton, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui'; -import { mapState, mapActions } from 'vuex'; - -// @deprecated This component should only be used when there is no GraphQL API. -// In most cases you should use -// `app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue` instead. -export default { - components: { - GlButton, - GlFormInput, - GlLink, - GlLoadingIcon, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - data() { - return { - labelTitle: '', - selectedColor: '', - }; - }, - computed: { - ...mapState(['labelsCreateTitle', 'labelCreateInProgress']), - disableCreate() { - return !this.labelTitle.length || !this.selectedColor.length || this.labelCreateInProgress; - }, - suggestedColors() { - const colorsMap = gon.suggested_label_colors; - return Object.keys(colorsMap).map((color) => ({ [color]: colorsMap[color] })); - }, - }, - methods: { - ...mapActions(['toggleDropdownContents', 'toggleDropdownContentsCreateView', 'createLabel']), - getColorCode(color) { - return Object.keys(color).pop(); - }, - getColorName(color) { - return Object.values(color).pop(); - }, - handleColorClick(color) { - this.selectedColor = this.getColorCode(color); - }, - handleCreateClick() { - this.createLabel({ - title: this.labelTitle, - color: this.selectedColor, - }); - }, - }, -}; -</script> - -<template> - <div class="labels-select-contents-create js-labels-create"> - <div class="dropdown-title d-flex align-items-center pt-0 pb-2 gl-mb-0"> - <gl-button - :aria-label="__('Go back')" - category="tertiary" - size="small" - class="js-btn-back dropdown-header-button p-0" - icon="arrow-left" - @click="toggleDropdownContentsCreateView" - /> - <span class="flex-grow-1">{{ labelsCreateTitle }}</span> - <gl-button - :aria-label="__('Close')" - category="tertiary" - size="small" - class="dropdown-header-button p-0" - icon="close" - @click="toggleDropdownContents" - /> - </div> - <div class="dropdown-input"> - <gl-form-input - v-model.trim="labelTitle" - :placeholder="__('Name new label')" - :autofocus="true" - /> - </div> - <div class="dropdown-content px-2"> - <div class="suggest-colors suggest-colors-dropdown mt-0 mb-2"> - <gl-link - v-for="(color, index) in suggestedColors" - :key="index" - v-gl-tooltip:tooltipcontainer - :style="{ backgroundColor: getColorCode(color) }" - :title="getColorName(color)" - @click.prevent="handleColorClick(color)" - /> - </div> - <div class="color-input-container gl-display-flex"> - <span - class="dropdown-label-color-preview position-relative position-relative d-inline-block" - :style="{ backgroundColor: selectedColor }" - ></span> - <gl-form-input - v-model.trim="selectedColor" - class="gl-rounded-top-left-none gl-rounded-bottom-left-none gl-mb-2" - :placeholder="__('Use custom color #FF0000')" - /> - </div> - </div> - <div class="dropdown-actions clearfix pt-2 px-2"> - <gl-button - :disabled="disableCreate" - category="primary" - variant="confirm" - class="float-left d-flex align-items-center" - @click="handleCreateClick" - > - <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"> - {{ __('Cancel') }} - </gl-button> - </div> - </div> -</template> 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 deleted file mode 100644 index e235bfde394..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue +++ /dev/null @@ -1,230 +0,0 @@ -<script> -import { - GlIntersectionObserver, - GlLoadingIcon, - GlButton, - GlSearchBoxByType, - GlLink, -} from '@gitlab/ui'; -import fuzzaldrinPlus from 'fuzzaldrin-plus'; -import { mapState, mapGetters, mapActions } from 'vuex'; - -import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes'; - -import LabelItem from './label_item.vue'; - -// @deprecated This component should only be used when there is no GraphQL API. -// In most cases you should use -// `app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue` instead. -export default { - components: { - GlIntersectionObserver, - GlLoadingIcon, - GlButton, - GlSearchBoxByType, - GlLink, - LabelItem, - }, - data() { - return { - searchKey: '', - currentHighlightItem: -1, - }; - }, - computed: { - ...mapState([ - 'allowLabelCreate', - 'allowMultiselect', - 'labelsManagePath', - 'labels', - 'labelsFetchInProgress', - 'labelsListTitle', - 'footerCreateLabelTitle', - 'footerManageLabelTitle', - ]), - ...mapGetters(['selectedLabelsList', 'isDropdownVariantSidebar', 'isDropdownVariantEmbedded']), - visibleLabels() { - if (this.searchKey) { - return fuzzaldrinPlus.filter(this.labels, this.searchKey, { - key: ['title'], - }); - } - return this.labels; - }, - showDropdownFooter() { - return ( - (this.isDropdownVariantSidebar || this.isDropdownVariantEmbedded) && - (this.allowLabelCreate || this.labelsManagePath) - ); - }, - showNoMatchingResultsMessage() { - return Boolean(this.searchKey) && this.visibleLabels.length === 0; - }, - }, - watch: { - searchKey(value) { - // When there is search string present - // and there are matching results, - // highlight first item by default. - if (value && this.visibleLabels.length) { - this.currentHighlightItem = 0; - } - }, - }, - methods: { - ...mapActions([ - 'toggleDropdownContents', - 'toggleDropdownContentsCreateView', - 'fetchLabels', - 'receiveLabelsSuccess', - 'updateSelectedLabels', - 'toggleDropdownContents', - ]), - isLabelSelected(label) { - return this.selectedLabelsList.includes(label.id); - }, - /** - * This method scrolls item from dropdown into - * the view if it is off the viewable area of the - * container. - */ - scrollIntoViewIfNeeded() { - const highlightedLabel = this.$refs.labelsListContainer.querySelector('.is-focused'); - - if (highlightedLabel) { - const container = this.$refs.labelsListContainer.getBoundingClientRect(); - const label = highlightedLabel.getBoundingClientRect(); - - if (label.bottom > container.bottom) { - this.$refs.labelsListContainer.scrollTop += label.bottom - container.bottom; - } else if (label.top < container.top) { - this.$refs.labelsListContainer.scrollTop -= container.top - label.top; - } - } - }, - handleComponentAppear() { - // We can avoid putting `catch` block here - // as failure is handled within actions.js already. - return this.fetchLabels().then(() => { - this.$refs.searchInput.focusInput(); - }); - }, - /** - * We want to remove loaded labels to ensure component - * fetches fresh set of labels every time when shown. - */ - handleComponentDisappear() { - this.receiveLabelsSuccess([]); - }, - handleCreateLabelClick() { - this.receiveLabelsSuccess([]); - this.toggleDropdownContentsCreateView(); - }, - /** - * This method enables keyboard navigation support for - * the dropdown. - */ - handleKeyDown(e) { - if (e.keyCode === UP_KEY_CODE && this.currentHighlightItem > 0) { - this.currentHighlightItem -= 1; - } else if ( - e.keyCode === DOWN_KEY_CODE && - this.currentHighlightItem < this.visibleLabels.length - 1 - ) { - this.currentHighlightItem += 1; - } else if (e.keyCode === ENTER_KEY_CODE && this.currentHighlightItem > -1) { - this.updateSelectedLabels([this.visibleLabels[this.currentHighlightItem]]); - this.searchKey = ''; - - // Prevent parent form submission upon hitting enter. - e.preventDefault(); - } else if (e.keyCode === ESC_KEY_CODE) { - this.toggleDropdownContents(); - } - - if (e.keyCode !== ESC_KEY_CODE) { - // Scroll the list only after highlighting - // styles are rendered completely. - this.$nextTick(() => { - this.scrollIntoViewIfNeeded(); - }); - } - }, - handleLabelClick(label) { - this.updateSelectedLabels([label]); - if (!this.allowMultiselect) this.toggleDropdownContents(); - }, - }, -}; -</script> - -<template> - <gl-intersection-observer @appear="handleComponentAppear" @disappear="handleComponentDisappear"> - <div class="labels-select-contents-list js-labels-list" @keydown="handleKeyDown"> - <div - v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded" - class="dropdown-title gl-display-flex gl-align-items-center gl-pt-0 gl-pb-3!" - data-testid="dropdown-title" - > - <span class="flex-grow-1">{{ labelsListTitle }}</span> - <gl-button - :aria-label="__('Close')" - category="tertiary" - size="small" - class="dropdown-header-button gl-p-0!" - icon="close" - @click="toggleDropdownContents" - /> - </div> - <div class="dropdown-input" @click.stop="() => {}"> - <gl-search-box-by-type - ref="searchInput" - v-model="searchKey" - :disabled="labelsFetchInProgress" - data-qa-selector="dropdown_input_field" - /> - </div> - <div ref="labelsListContainer" class="dropdown-content" data-testid="dropdown-content"> - <gl-loading-icon - v-if="labelsFetchInProgress" - class="labels-fetch-loading gl-align-items-center w-100 h-100" - size="lg" - /> - <ul v-else class="list-unstyled gl-mb-0 gl-word-break-word"> - <label-item - v-for="(label, index) in visibleLabels" - :key="label.id" - :label="label" - :is-label-set="label.set" - :is-label-indeterminate="label.indeterminate" - :highlight="index === currentHighlightItem" - @clickLabel="handleLabelClick(label)" - /> - <li v-show="showNoMatchingResultsMessage" class="gl-p-3 gl-text-center"> - {{ __('No matching results') }} - </li> - </ul> - </div> - <div v-if="showDropdownFooter" class="dropdown-footer" data-testid="dropdown-footer"> - <ul class="list-unstyled"> - <li v-if="allowLabelCreate"> - <gl-link - class="gl-display-flex w-100 flex-row text-break-word label-item" - @click="handleCreateLabelClick" - > - {{ footerCreateLabelTitle }} - </gl-link> - </li> - <li v-if="labelsManagePath"> - <gl-link - :href="labelsManagePath" - class="gl-display-flex flex-row text-break-word label-item" - > - {{ footerManageLabelTitle }} - </gl-link> - </li> - </ul> - </div> - </div> - </gl-intersection-observer> -</template> 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 deleted file mode 100644 index e4325492334..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue +++ /dev/null @@ -1,46 +0,0 @@ -<script> -import { GlButton, GlLoadingIcon } from '@gitlab/ui'; -import { mapState, mapActions } from 'vuex'; - -// @deprecated This component should only be used when there is no GraphQL API. -// In most cases you should use -// `app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_header.vue` instead. -export default { - components: { - GlButton, - GlLoadingIcon, - }, - props: { - labelsSelectInProgress: { - type: Boolean, - required: true, - }, - }, - computed: { - ...mapState(['allowLabelEdit', 'labelsFetchInProgress']), - }, - methods: { - ...mapActions(['toggleDropdownContents']), - }, -}; -</script> - -<template> - <div - class="hide-collapsed gl-line-height-20 gl-mb-2 gl-text-gray-900 gl-font-weight-bold gl-mb-0" - > - {{ __('Labels') }} - <template v-if="allowLabelEdit"> - <gl-loading-icon v-show="labelsSelectInProgress" size="sm" inline /> - <gl-button - category="tertiary" - size="small" - class="float-right js-sidebar-dropdown-toggle gl-mr-n2" - data-qa-selector="labels_edit_button" - @click="toggleDropdownContents" - > - {{ __('Edit') }} - </gl-button> - </template> - </div> -</template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_value.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_value.vue deleted file mode 100644 index e59d150dd43..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_value.vue +++ /dev/null @@ -1,74 +0,0 @@ -<script> -import { GlLabel } from '@gitlab/ui'; -import { sortBy } from 'lodash'; -import { mapState } from 'vuex'; - -import { isScopedLabel } from '~/lib/utils/common_utils'; - -// @deprecated This component should only be used when there is no GraphQL API. -// In most cases you should use -// `app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue` instead. -export default { - components: { - GlLabel, - }, - props: { - disableLabels: { - type: Boolean, - required: false, - default: false, - }, - }, - computed: { - ...mapState([ - 'selectedLabels', - 'allowLabelRemove', - 'allowScopedLabels', - 'labelsFilterBasePath', - 'labelsFilterParam', - ]), - sortedSelectedLabels() { - return sortBy(this.selectedLabels, (label) => (isScopedLabel(label) ? 0 : 1)); - }, - }, - methods: { - labelFilterUrl(label) { - return `${this.labelsFilterBasePath}?${this.labelsFilterParam}[]=${encodeURIComponent( - label.title, - )}`; - }, - scopedLabel(label) { - return this.allowScopedLabels && isScopedLabel(label); - }, - }, -}; -</script> - -<template> - <div - :class="{ - 'has-labels': selectedLabels.length, - }" - class="hide-collapsed value issuable-show-labels js-value" - > - <span v-if="!selectedLabels.length" class="text-secondary"> - <slot></slot> - </span> - <template v-for="label in sortedSelectedLabels" v-else> - <gl-label - :key="label.id" - data-qa-selector="selected_label_content" - :data-qa-label-name="label.title" - :title="label.title" - :description="label.description" - :background-color="label.color" - :target="labelFilterUrl(label)" - :scoped="scopedLabel(label)" - :show-close-button="allowLabelRemove" - :disabled="disableLabels" - tooltip-placement="top" - @close="$emit('onLabelRemove', label.id)" - /> - </template> - </div> -</template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed.vue deleted file mode 100644 index 5966c78aa51..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed.vue +++ /dev/null @@ -1,53 +0,0 @@ -<script> -import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; -import { s__, sprintf } from '~/locale'; - -// @deprecated This component should only be used when there is no GraphQL API. -// In most cases you should use -// `app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget` instead. -export default { - directives: { - GlTooltip: GlTooltipDirective, - }, - components: { - GlIcon, - }, - props: { - labels: { - type: Array, - required: true, - }, - }, - computed: { - labelsList() { - const labelsString = this.labels.length - ? this.labels - .slice(0, 5) - .map((label) => label.title) - .join(', ') - : s__('LabelSelect|Labels'); - - if (this.labels.length > 5) { - return sprintf(s__('LabelSelect|%{labelsString}, and %{remainingLabelCount} more'), { - labelsString, - remainingLabelCount: this.labels.length - 5, - }); - } - - return labelsString; - }, - }, - methods: { - handleClick() { - this.$emit('onValueClick'); - }, - }, -}; -</script> - -<template> - <div v-gl-tooltip.left.viewport="labelsList" class="sidebar-collapsed-icon" @click="handleClick"> - <gl-icon name="labels" /> - <span>{{ labels.length }}</span> - </div> -</template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/label_item.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/label_item.vue deleted file mode 100644 index 154e3013acd..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/label_item.vue +++ /dev/null @@ -1,109 +0,0 @@ -<script> -import { GlLink, GlIcon } from '@gitlab/ui'; -import { __ } from '~/locale'; - -// @deprecated This component should only be used when there is no GraphQL API. -// In most cases you should use -// `app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/label_item.vue` instead. -export default { - functional: true, - props: { - label: { - type: Object, - required: true, - }, - isLabelSet: { - type: Boolean, - required: true, - }, - isLabelIndeterminate: { - type: Boolean, - required: false, - default: false, - }, - highlight: { - type: Boolean, - required: false, - default: false, - }, - }, - render(h, { props, listeners }) { - const { label, highlight, isLabelSet, isLabelIndeterminate } = props; - - const labelColorBox = h('span', { - class: 'dropdown-label-box gl-flex-shrink-0 gl-top-0 gl-mr-3', - style: { - backgroundColor: label.color, - }, - attrs: { - 'data-testid': 'label-color-box', - }, - }); - - const checkedIcon = h(GlIcon, { - class: { - 'gl-mr-3 gl-flex-shrink-0 has-tooltip': true, - hidden: !isLabelSet, - }, - attrs: { - title: __('Selected for all items.'), - 'data-testid': 'checked-icon', - }, - props: { - name: 'mobile-issue-close', - }, - }); - - const indeterminateIcon = h(GlIcon, { - class: { - 'gl-mr-3 gl-flex-shrink-0 has-tooltip': true, - hidden: !isLabelIndeterminate, - }, - attrs: { - title: __('Selected for some items.'), - 'data-testid': 'indeterminate-icon', - }, - props: { - name: 'dash', - }, - }); - - const noIcon = h('span', { - class: { - 'gl-mr-5 gl-pr-3': true, - hidden: isLabelSet || isLabelIndeterminate, - }, - attrs: { - 'data-testid': 'no-icon', - }, - }); - - const labelTitle = h('span', label.title); - - const labelLink = h( - GlLink, - { - class: 'gl-display-flex gl-align-items-center label-item gl-text-body', - on: { - click: () => { - listeners.clickLabel(label); - }, - }, - }, - [noIcon, checkedIcon, indeterminateIcon, labelColorBox, labelTitle], - ); - - return h( - 'li', - { - class: { - 'gl-display-block': true, - 'gl-text-left': true, - 'is-focused': highlight, - }, - }, - [labelLink], - ); - }, -}; -</script> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue deleted file mode 100644 index e6c29e24f0c..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue +++ /dev/null @@ -1,345 +0,0 @@ -<script> -import $ from 'jquery'; -import Vue from 'vue'; -import Vuex, { mapState, mapActions, mapGetters } from 'vuex'; -import { isInViewport } from '~/lib/utils/common_utils'; -import { __ } from '~/locale'; - -import { DropdownVariant } from './constants'; -import DropdownButton from './dropdown_button.vue'; -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 labelsSelectModule from './store'; - -Vue.use(Vuex); - -// @deprecated This component should only be used when there is no GraphQL API. -// In most cases you should use -// `app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue` instead. -export default { - store: new Vuex.Store(labelsSelectModule()), - components: { - DropdownTitle, - DropdownValue, - DropdownButton, - DropdownContents, - DropdownValueCollapsed, - }, - props: { - allowLabelRemove: { - type: Boolean, - required: false, - default: false, - }, - allowLabelEdit: { - type: Boolean, - required: false, - default: false, - }, - allowLabelCreate: { - type: Boolean, - required: false, - default: false, - }, - allowMultiselect: { - type: Boolean, - required: false, - default: false, - }, - allowScopedLabels: { - type: Boolean, - required: false, - default: false, - }, - allowMultipleScopedLabels: { - type: Boolean, - required: false, - default: false, - }, - variant: { - type: String, - required: false, - default: DropdownVariant.Sidebar, - }, - selectedLabels: { - type: Array, - required: false, - default: () => [], - }, - hideCollapsedView: { - type: Boolean, - required: false, - default: false, - }, - labelsSelectInProgress: { - type: Boolean, - required: false, - default: false, - }, - labelsFetchPath: { - type: String, - required: false, - default: '', - }, - labelsManagePath: { - type: String, - required: false, - default: '', - }, - labelsFilterBasePath: { - type: String, - required: false, - default: '', - }, - labelsFilterParam: { - type: String, - required: false, - default: 'label_name', - }, - dropdownButtonText: { - type: String, - required: false, - default: __('Label'), - }, - labelsListTitle: { - type: String, - required: false, - default: __('Assign labels'), - }, - labelsCreateTitle: { - type: String, - required: false, - default: __('Create group label'), - }, - footerCreateLabelTitle: { - type: String, - required: false, - default: __('Create group label'), - }, - footerManageLabelTitle: { - type: String, - required: false, - default: __('Manage group labels'), - }, - isEditing: { - type: Boolean, - required: false, - default: false, - }, - }, - data() { - return { - contentIsOnViewport: true, - }; - }, - computed: { - ...mapState(['showDropdownButton', 'showDropdownContents']), - ...mapGetters([ - 'isDropdownVariantSidebar', - 'isDropdownVariantStandalone', - 'isDropdownVariantEmbedded', - ]), - dropdownButtonVisible() { - return this.isDropdownVariantSidebar ? this.showDropdownButton : true; - }, - }, - watch: { - selectedLabels(selectedLabels) { - this.setInitialState({ - selectedLabels, - }); - setTimeout(() => this.updateLabelsSetState(), 100); - }, - showDropdownContents(showDropdownContents) { - this.setContentIsOnViewport(showDropdownContents); - }, - isEditing(newVal) { - if (newVal) { - this.toggleDropdownContents(); - } - }, - }, - mounted() { - this.setInitialState({ - variant: this.variant, - allowLabelRemove: this.allowLabelRemove, - allowLabelEdit: this.allowLabelEdit, - allowLabelCreate: this.allowLabelCreate, - allowMultiselect: this.allowMultiselect, - allowScopedLabels: this.allowScopedLabels, - allowMultipleScopedLabels: this.allowMultipleScopedLabels, - dropdownButtonText: this.dropdownButtonText, - selectedLabels: this.selectedLabels, - labelsFetchPath: this.labelsFetchPath, - labelsManagePath: this.labelsManagePath, - labelsFilterBasePath: this.labelsFilterBasePath, - labelsFilterParam: this.labelsFilterParam, - labelsListTitle: this.labelsListTitle, - labelsCreateTitle: this.labelsCreateTitle, - footerCreateLabelTitle: this.footerCreateLabelTitle, - footerManageLabelTitle: this.footerManageLabelTitle, - }); - - this.$store.subscribeAction({ - after: this.handleVuexActionDispatch, - }); - - document.addEventListener('mousedown', this.handleDocumentMousedown); - document.addEventListener('click', this.handleDocumentClick); - - this.updateLabelsSetState(); - }, - beforeDestroy() { - document.removeEventListener('mousedown', this.handleDocumentMousedown); - document.removeEventListener('click', this.handleDocumentClick); - }, - methods: { - ...mapActions(['setInitialState', 'toggleDropdownContents', 'updateLabelsSetState']), - /** - * This method differentiates between - * dispatched actions and calls necessary method. - */ - handleVuexActionDispatch(action, state) { - if ( - action.type === 'toggleDropdownContents' && - !state.showDropdownButton && - !state.showDropdownContents - ) { - const filterTouchedLabelsFn = (label) => label.touched; - const filterSetLabelsFn = (label) => label.set; - const labels = this.isDropdownVariantEmbedded - ? state.labels.filter(filterSetLabelsFn) - : state.labels.filter(filterTouchedLabelsFn); - this.handleDropdownClose(labels, state.labels.filter(filterTouchedLabelsFn)); - } - }, - /** - * This method stores a mousedown event's target. - * Required by the click listener because the click - * event itself has no reference to this element. - */ - handleDocumentMousedown({ target }) { - this.mousedownTarget = target; - }, - /** - * This method listens for document-wide click event - * and toggle dropdown if user clicks anywhere outside - * the dropdown while dropdown is visible. - */ - handleDocumentClick({ target }) { - // We also perform the toggle exception check for the - // last mousedown event's target to avoid hiding the - // box when the mousedown happened inside the box and - // only the mouseup did not. - if ( - this.showDropdownContents && - !this.preventDropdownToggleOnClick(target) && - !this.preventDropdownToggleOnClick(this.mousedownTarget) - ) { - this.toggleDropdownContents(); - } - }, - /** - * This method checks whether a given click target - * should prevent the dropdown from being toggled. - */ - preventDropdownToggleOnClick(target) { - // This approach of element detection is needed - // as the dropdown wrapper is not using `GlDropdown` as - // it will also require us to use `BDropdownForm` - // which is yet to be implemented in GitLab UI. - const hasExceptionClass = [ - 'js-dropdown-button', - 'js-btn-cancel-create', - 'js-sidebar-dropdown-toggle', - ].some( - (className) => - target?.classList.contains(className) || - target?.parentElement?.classList.contains(className), - ); - - const hasExceptionParent = ['.js-btn-back', '.js-labels-list'].some( - (className) => $(target).parents(className).length, - ); - - const isInDropdownButtonCollapsed = this.$refs.dropdownButtonCollapsed?.$el.contains(target); - - const isInDropdownContents = this.$refs.dropdownContents?.$el.contains(target); - - return ( - hasExceptionClass || - hasExceptionParent || - isInDropdownButtonCollapsed || - isInDropdownContents - ); - }, - handleDropdownClose(labels, touchedLabels) { - // Only emit label updates if there are any - // labels to update on UI. - if (labels.length) this.$emit('updateSelectedLabels', labels); - this.$emit('onDropdownClose', touchedLabels); - }, - handleCollapsedValueClick() { - this.$emit('toggleCollapse'); - }, - setContentIsOnViewport(showDropdownContents) { - if (!showDropdownContents) { - this.contentIsOnViewport = true; - - return; - } - - this.$nextTick(() => { - if (this.$refs.dropdownContents) { - this.contentIsOnViewport = isInViewport(this.$refs.dropdownContents.$el); - } - }); - }, - }, -}; -</script> - -<template> - <div - class="labels-select-wrapper position-relative" - :class="{ - 'is-standalone': isDropdownVariantStandalone, - 'is-embedded': isDropdownVariantEmbedded, - }" - > - <template v-if="isDropdownVariantSidebar"> - <dropdown-value-collapsed - v-if="!hideCollapsedView" - ref="dropdownButtonCollapsed" - :labels="selectedLabels" - @onValueClick="handleCollapsedValueClick" - /> - <dropdown-title - :allow-label-edit="allowLabelEdit" - :labels-select-in-progress="labelsSelectInProgress" - /> - <dropdown-value - :disable-labels="labelsSelectInProgress" - @onLabelRemove="$emit('onLabelRemove', $event)" - > - <slot></slot> - </dropdown-value> - <dropdown-button v-show="dropdownButtonVisible" class="gl-mt-2" /> - <dropdown-contents - v-if="dropdownButtonVisible && showDropdownContents" - ref="dropdownContents" - :render-on-top="!contentIsOnViewport" - /> - </template> - <template v-if="isDropdownVariantStandalone || isDropdownVariantEmbedded"> - <dropdown-button v-show="dropdownButtonVisible" /> - <dropdown-contents - v-if="dropdownButtonVisible && showDropdownContents" - ref="dropdownContents" - :render-on-top="!contentIsOnViewport" - /> - </template> - </div> -</template> 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 deleted file mode 100644 index 2dab97826b9..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/actions.js +++ /dev/null @@ -1,69 +0,0 @@ -import { createAlert } from '~/flash'; -import axios from '~/lib/utils/axios_utils'; -import { __ } from '~/locale'; -import * as types from './mutation_types'; - -export const setInitialState = ({ commit }, props) => commit(types.SET_INITIAL_STATE, props); - -export const toggleDropdownButton = ({ commit }) => commit(types.TOGGLE_DROPDOWN_BUTTON); -export const toggleDropdownContents = ({ commit }) => commit(types.TOGGLE_DROPDOWN_CONTENTS); - -export const toggleDropdownContentsCreateView = ({ commit }) => - commit(types.TOGGLE_DROPDOWN_CONTENTS_CREATE_VIEW); - -export const requestLabels = ({ commit }) => commit(types.REQUEST_LABELS); -export const receiveLabelsSuccess = ({ commit }, labels) => - commit(types.RECEIVE_SET_LABELS_SUCCESS, labels); -export const receiveLabelsFailure = ({ commit }) => { - commit(types.RECEIVE_SET_LABELS_FAILURE); - createAlert({ - message: __('Error fetching labels.'), - }); -}; -export const fetchLabels = ({ state, dispatch }, options) => { - if (state.labelsFetched && (!options || !options.refetch)) { - return Promise.resolve(); - } - - dispatch('requestLabels'); - return axios - .get(state.labelsFetchPath) - .then(({ data }) => { - dispatch('receiveLabelsSuccess', data); - }) - .catch(() => dispatch('receiveLabelsFailure')); -}; - -export const requestCreateLabel = ({ commit }) => commit(types.REQUEST_CREATE_LABEL); -export const receiveCreateLabelSuccess = ({ commit }) => commit(types.RECEIVE_CREATE_LABEL_SUCCESS); -export const receiveCreateLabelFailure = ({ commit }) => { - commit(types.RECEIVE_CREATE_LABEL_FAILURE); - createAlert({ - message: __('Error creating label.'), - }); -}; -export const createLabel = ({ state, dispatch }, label) => { - dispatch('requestCreateLabel'); - axios - .post(state.labelsManagePath, { - label, - }) - .then(({ data }) => { - if (data.id) { - dispatch('fetchLabels', { refetch: true }); - dispatch('receiveCreateLabelSuccess'); - dispatch('toggleDropdownContentsCreateView'); - } else { - // eslint-disable-next-line @gitlab/require-i18n-strings - throw new Error('Error Creating Label'); - } - }) - .catch(() => { - dispatch('receiveCreateLabelFailure'); - }); -}; - -export const updateSelectedLabels = ({ commit }, labels) => - commit(types.UPDATE_SELECTED_LABELS, { labels }); - -export const updateLabelsSetState = ({ commit }) => commit(types.UPDATE_LABELS_SET_STATE); diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js deleted file mode 100644 index ef3eedd9bb2..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js +++ /dev/null @@ -1,53 +0,0 @@ -import { __, s__, sprintf } from '~/locale'; -import { DropdownVariant } from '../constants'; - -/** - * Returns string representing current labels - * selection on dropdown button. - * - * @param {object} state - */ -export const dropdownButtonText = (state, getters) => { - const selectedLabels = - getters.isDropdownVariantSidebar || getters.isDropdownVariantEmbedded - ? state.labels.filter((label) => label.set || label.indeterminate) - : state.selectedLabels; - - if (!selectedLabels.length) { - return state.dropdownButtonText || __('Label'); - } else if (selectedLabels.length > 1) { - return sprintf(s__('LabelSelect|%{firstLabelName} +%{remainingLabelCount} more'), { - firstLabelName: selectedLabels[0].title, - remainingLabelCount: selectedLabels.length - 1, - }); - } - return selectedLabels[0].title; -}; - -/** - * Returns array containing only label IDs from - * selectedLabels array. - * @param {object} state - */ -export const selectedLabelsList = (state) => state.selectedLabels.map((label) => label.id); - -/** - * Returns boolean representing whether dropdown variant - * is `sidebar` - * @param {object} state - */ -export const isDropdownVariantSidebar = (state) => state.variant === DropdownVariant.Sidebar; - -/** - * Returns boolean representing whether dropdown variant - * is `standalone` - * @param {object} state - */ -export const isDropdownVariantStandalone = (state) => state.variant === DropdownVariant.Standalone; - -/** - * Returns boolean representing whether dropdown variant - * is `embedded` - * @param {object} state - */ -export const isDropdownVariantEmbedded = (state) => state.variant === DropdownVariant.Embedded; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/index.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/index.js deleted file mode 100644 index 5f61cb732c8..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/index.js +++ /dev/null @@ -1,12 +0,0 @@ -import * as actions from './actions'; -import * as getters from './getters'; -import mutations from './mutations'; -import state from './state'; - -export default () => ({ - namespaced: true, - state: state(), - actions, - getters, - mutations, -}); diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutation_types.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutation_types.js deleted file mode 100644 index f26e36031f4..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutation_types.js +++ /dev/null @@ -1,22 +0,0 @@ -export const SET_INITIAL_STATE = 'SET_INITIAL_STATE'; - -export const REQUEST_LABELS = 'REQUEST_LABELS'; -export const RECEIVE_LABELS_SUCCESS = 'RECEIVE_LABELS_SUCCESS'; -export const RECEIVE_LABELS_FAILURE = 'RECEIVE_LABELS_FAILURE'; - -export const REQUEST_SET_LABELS = 'REQUEST_SET_LABELS'; -export const RECEIVE_SET_LABELS_SUCCESS = 'RECEIVE_SET_LABELS_SUCCESS'; -export const RECEIVE_SET_LABELS_FAILURE = 'RECEIVE_SET_LABELS_FAILURE'; - -export const REQUEST_CREATE_LABEL = 'REQUEST_CREATE_LABEL'; -export const RECEIVE_CREATE_LABEL_SUCCESS = 'RECEIVE_CREATE_LABEL_SUCCESS'; -export const RECEIVE_CREATE_LABEL_FAILURE = 'RECEIVE_CREATE_LABEL_FAILURE'; - -export const TOGGLE_DROPDOWN_BUTTON = 'TOGGLE_DROPDOWN_VISIBILITY'; -export const TOGGLE_DROPDOWN_CONTENTS = 'TOGGLE_DROPDOWN_CONTENTS'; - -export const UPDATE_SELECTED_LABELS = 'UPDATE_SELECTED_LABELS'; - -export const TOGGLE_DROPDOWN_CONTENTS_CREATE_VIEW = 'TOGGLE_DROPDOWN_CONTENTS_CREATE_VIEW'; - -export const UPDATE_LABELS_SET_STATE = 'UPDATE_LABELS_SET_STATE'; 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 deleted file mode 100644 index c85d9befcbb..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js +++ /dev/null @@ -1,113 +0,0 @@ -import { isScopedLabel, scopedLabelKey } from '~/lib/utils/common_utils'; -import { DropdownVariant } from '../constants'; -import * as types from './mutation_types'; - -const transformLabels = (labels, selectedLabels) => - labels.map((label) => { - const selectedLabel = selectedLabels.find(({ id }) => id === label.id); - - return { - ...label, - set: Boolean(selectedLabel?.set), - indeterminate: Boolean(selectedLabel?.indeterminate), - }; - }); - -export default { - [types.SET_INITIAL_STATE](state, props) { - // We need to ensure that selectedLabels have - // `set` & `indeterminate` properties defined. - if (props.selectedLabels?.length) { - props.selectedLabels.forEach((label) => { - /* eslint-disable no-param-reassign */ - if (label.set === undefined && label.indeterminate === undefined) { - label.set = true; - label.indeterminate = false; - } else if (label.set === undefined && label.indeterminate !== undefined) { - label.set = false; - } else if (label.set !== undefined && label.indeterminate === undefined) { - label.indeterminate = false; - } else { - label.set = false; - label.indeterminate = false; - } - /* eslint-enable no-param-reassign */ - }); - } - - Object.assign(state, { ...props }); - }, - - [types.TOGGLE_DROPDOWN_BUTTON](state) { - state.showDropdownButton = !state.showDropdownButton; - }, - - [types.TOGGLE_DROPDOWN_CONTENTS](state) { - if (state.variant === DropdownVariant.Sidebar) { - state.showDropdownButton = !state.showDropdownButton; - } - state.showDropdownContents = !state.showDropdownContents; - // Ensure that Create View is hidden by default - // when dropdown contents are revealed. - if (state.showDropdownContents) { - state.showDropdownContentsCreateView = false; - } - }, - - [types.TOGGLE_DROPDOWN_CONTENTS_CREATE_VIEW](state) { - state.showDropdownContentsCreateView = !state.showDropdownContentsCreateView; - }, - - [types.REQUEST_LABELS](state) { - state.labelsFetchInProgress = true; - }, - [types.RECEIVE_SET_LABELS_SUCCESS](state, labels) { - // Iterate over every label and add a `set` prop - // to determine whether it is already a part of - // selectedLabels array. - state.labelsFetchInProgress = false; - state.labelsFetched = true; - state.labels = transformLabels(labels, state.selectedLabels); - }, - [types.RECEIVE_SET_LABELS_FAILURE](state) { - state.labelsFetchInProgress = false; - }, - - [types.REQUEST_CREATE_LABEL](state) { - state.labelCreateInProgress = true; - }, - [types.RECEIVE_CREATE_LABEL_SUCCESS](state) { - state.labelCreateInProgress = false; - }, - [types.RECEIVE_CREATE_LABEL_FAILURE](state) { - state.labelCreateInProgress = false; - }, - - [types.UPDATE_SELECTED_LABELS](state, { labels }) { - // Find the label to update from all the labels - // and change `set` prop value to represent their current state. - const labelId = labels.pop()?.id; - const candidateLabel = state.labels.find((label) => labelId === label.id); - if (candidateLabel) { - candidateLabel.touched = true; - candidateLabel.set = candidateLabel.indeterminate ? true : !candidateLabel.set; - candidateLabel.indeterminate = false; - } - - if (isScopedLabel(candidateLabel) && !state.allowMultipleScopedLabels) { - const currentActiveScopedLabel = state.labels.find( - ({ set, title }) => - set && - title !== candidateLabel.title && - scopedLabelKey({ title }) === scopedLabelKey(candidateLabel), - ); - if (currentActiveScopedLabel) { - currentActiveScopedLabel.set = false; - } - } - }, - - [types.UPDATE_LABELS_SET_STATE](state) { - state.labels = transformLabels(state.labels, state.selectedLabels); - }, -}; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/state.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/state.js deleted file mode 100644 index 0185d5f88e1..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/state.js +++ /dev/null @@ -1,30 +0,0 @@ -export default () => ({ - // Initial Data - labels: [], - labelsFetched: false, - selectedLabels: [], - labelsListTitle: '', - labelsCreateTitle: '', - footerCreateLabelTitle: '', - footerManageLabelTitle: '', - dropdownButtonText: '', - - // Paths - namespace: '', - labelsFetchPath: '', - labelsFilterBasePath: '', - - // UI Flags - variant: '', - allowLabelRemove: false, - allowLabelCreate: false, - allowLabelEdit: false, - allowScopedLabels: false, - allowMultiselect: false, - showDropdownButton: false, - showDropdownContents: false, - showDropdownContentsCreateView: false, - labelsFetchInProgress: false, - labelCreateInProgress: false, - selectedLabelsUpdated: false, -}); diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/constants.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/constants.js deleted file mode 100644 index cd671b4d8f5..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/constants.js +++ /dev/null @@ -1,13 +0,0 @@ -export const SCOPED_LABEL_DELIMITER = '::'; -export const DEBOUNCE_DROPDOWN_DELAY = 200; - -export const DropdownVariant = { - Sidebar: 'sidebar', - Standalone: 'standalone', - Embedded: 'embedded', -}; - -export const LabelType = { - group: 'group', - project: 'project', -}; 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 deleted file mode 100644 index 27186281c42..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue +++ /dev/null @@ -1,241 +0,0 @@ -<script> -import { GlButton, GlDropdown, GlDropdownItem, GlLink } from '@gitlab/ui'; -import { debounce } from 'lodash'; -import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; -import { __, s__, sprintf } from '~/locale'; -import DropdownContentsCreateView from './dropdown_contents_create_view.vue'; -import DropdownContentsLabelsView from './dropdown_contents_labels_view.vue'; -import DropdownFooter from './dropdown_footer.vue'; -import DropdownHeader from './dropdown_header.vue'; -import { isDropdownVariantStandalone, isDropdownVariantSidebar } from './utils'; - -export default { - components: { - DropdownContentsLabelsView, - DropdownContentsCreateView, - DropdownHeader, - DropdownFooter, - GlButton, - GlDropdown, - GlDropdownItem, - GlLink, - }, - props: { - labelsCreateTitle: { - type: String, - required: true, - }, - selectedLabels: { - type: Array, - required: true, - }, - allowMultiselect: { - type: Boolean, - required: true, - }, - labelsListTitle: { - type: String, - required: true, - }, - dropdownButtonText: { - type: String, - required: true, - }, - footerCreateLabelTitle: { - type: String, - required: true, - }, - footerManageLabelTitle: { - type: String, - required: true, - }, - variant: { - type: String, - required: true, - }, - isVisible: { - type: Boolean, - required: false, - default: false, - }, - fullPath: { - type: String, - required: true, - }, - workspaceType: { - type: String, - required: true, - }, - attrWorkspacePath: { - type: String, - required: true, - }, - labelCreateType: { - type: String, - required: true, - }, - }, - data() { - return { - showDropdownContentsCreateView: false, - localSelectedLabels: [...this.selectedLabels], - isDirty: false, - searchKey: '', - }; - }, - computed: { - dropdownContentsView() { - if (this.showDropdownContentsCreateView) { - return 'dropdown-contents-create-view'; - } - return 'dropdown-contents-labels-view'; - }, - dropdownTitle() { - return this.showDropdownContentsCreateView ? this.labelsCreateTitle : this.labelsListTitle; - }, - buttonText() { - if (!this.localSelectedLabels.length) { - return this.dropdownButtonText || __('Label'); - } else if (this.localSelectedLabels.length > 1) { - return sprintf(s__('LabelSelect|%{firstLabelName} +%{remainingLabelCount} more'), { - firstLabelName: this.localSelectedLabels[0].title, - remainingLabelCount: this.localSelectedLabels.length - 1, - }); - } - return this.localSelectedLabels[0].title; - }, - showDropdownFooter() { - return !this.showDropdownContentsCreateView && !this.isStandalone; - }, - isStandalone() { - return isDropdownVariantStandalone(this.variant); - }, - }, - watch: { - localSelectedLabels: { - handler() { - this.isDirty = true; - }, - deep: true, - }, - isVisible(newVal) { - if (newVal) { - this.$refs.dropdown.show(); - this.isDirty = false; - this.localSelectedLabels = this.selectedLabels; - } else { - this.$refs.dropdown.hide(); - this.setLabels(); - } - }, - selectedLabels(newVal) { - if (!this.isDirty) { - this.localSelectedLabels = newVal; - } - }, - }, - created() { - this.debouncedSearchKeyUpdate = debounce(this.setSearchKey, DEFAULT_DEBOUNCE_AND_THROTTLE_MS); - }, - beforeDestroy() { - this.debouncedSearchKeyUpdate.cancel(); - }, - methods: { - toggleDropdownContentsCreateView() { - this.showDropdownContentsCreateView = !this.showDropdownContentsCreateView; - }, - toggleDropdownContent() { - this.toggleDropdownContentsCreateView(); - // Required to recalculate dropdown position as its size changes - if (this.$refs.dropdown?.$refs.dropdown) { - this.$refs.dropdown.$refs.dropdown.$_popper.scheduleUpdate(); - } - }, - setLabels() { - if (!this.isDirty) { - return; - } - this.$emit('setLabels', this.localSelectedLabels); - }, - handleDropdownHide() { - this.$emit('closeDropdown'); - if (!isDropdownVariantSidebar(this.variant)) { - this.setLabels(); - } - }, - setSearchKey(value) { - this.searchKey = value; - }, - setFocus() { - this.$refs.header.focusInput(); - }, - hideDropdown() { - this.$refs.dropdown.hide(); - }, - showDropdown() { - this.$refs.dropdown.show(); - }, - clearSearch() { - if (!this.allowMultiselect || this.isStandalone) { - return; - } - this.searchKey = ''; - this.setFocus(); - }, - selectFirstItem() { - this.$refs.dropdownContentsView.selectFirstItem(); - }, - }, -}; -</script> - -<template> - <gl-dropdown - ref="dropdown" - :text="buttonText" - class="gl-w-full" - block - data-testid="labels-select-dropdown-contents" - data-qa-selector="labels_dropdown_content" - @hide="handleDropdownHide" - @shown="setFocus" - > - <template #header> - <dropdown-header - ref="header" - :search-key="searchKey" - :labels-create-title="labelsCreateTitle" - :labels-list-title="labelsListTitle" - :show-dropdown-contents-create-view="showDropdownContentsCreateView" - :is-standalone="isStandalone" - @toggleDropdownContentsCreateView="toggleDropdownContent" - @closeDropdown="hideDropdown" - @input="debouncedSearchKeyUpdate" - @searchEnter="selectFirstItem" - /> - </template> - <template #default> - <component - :is="dropdownContentsView" - ref="dropdownContentsView" - v-model="localSelectedLabels" - :search-key="searchKey" - :allow-multiselect="allowMultiselect" - :full-path="fullPath" - :workspace-type="workspaceType" - :attr-workspace-path="attrWorkspacePath" - :label-create-type="labelCreateType" - @hideCreateView="toggleDropdownContent" - @input="clearSearch" - /> - </template> - <template #footer> - <dropdown-footer - v-if="showDropdownFooter" - :footer-create-label-title="footerCreateLabelTitle" - :footer-manage-label-title="footerManageLabelTitle" - @toggleDropdownContentsCreateView="toggleDropdownContent" - /> - </template> - </gl-dropdown> -</template> 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 deleted file mode 100644 index ce93ad216ec..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue +++ /dev/null @@ -1,200 +0,0 @@ -<script> -import { - GlAlert, - GlTooltipDirective, - GlButton, - GlFormInput, - GlLink, - GlLoadingIcon, -} from '@gitlab/ui'; -import produce from 'immer'; -import { createAlert } from '~/flash'; -import { __ } from '~/locale'; -import { workspaceLabelsQueries } from '~/sidebar/constants'; -import createLabelMutation from './graphql/create_label.mutation.graphql'; -import { LabelType } from './constants'; - -const errorMessage = __('Error creating label.'); - -export default { - components: { - GlAlert, - GlButton, - GlFormInput, - GlLink, - GlLoadingIcon, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - props: { - fullPath: { - type: String, - required: true, - }, - attrWorkspacePath: { - type: String, - required: true, - }, - labelCreateType: { - type: String, - required: true, - }, - workspaceType: { - type: String, - required: true, - }, - }, - data() { - return { - labelTitle: '', - selectedColor: '', - labelCreateInProgress: false, - error: undefined, - }; - }, - computed: { - disableCreate() { - return !this.labelTitle.length || !this.selectedColor.length || this.labelCreateInProgress; - }, - suggestedColors() { - const colorsMap = gon.suggested_label_colors; - return Object.keys(colorsMap).map((color) => ({ [color]: colorsMap[color] })); - }, - mutationVariables() { - const attributePath = this.labelCreateType === LabelType.group ? 'groupPath' : 'projectPath'; - - return { - title: this.labelTitle, - color: this.selectedColor, - [attributePath]: this.attrWorkspacePath, - }; - }, - }, - methods: { - getColorCode(color) { - return Object.keys(color).pop(); - }, - getColorName(color) { - return Object.values(color).pop(); - }, - handleColorClick(color) { - this.selectedColor = this.getColorCode(color); - }, - updateLabelsInCache(store, label) { - const { query } = workspaceLabelsQueries[this.workspaceType]; - - const sourceData = store.readQuery({ - query, - variables: { fullPath: this.fullPath, searchTerm: '' }, - }); - - const collator = new Intl.Collator('en'); - const data = produce(sourceData, (draftData) => { - const { nodes } = draftData.workspace.labels; - nodes.push(label); - nodes.sort((a, b) => collator.compare(a.title, b.title)); - }); - - store.writeQuery({ - query, - variables: { fullPath: this.fullPath, searchTerm: '' }, - data, - }); - }, - async createLabel() { - this.labelCreateInProgress = true; - try { - const { - data: { labelCreate }, - } = await this.$apollo.mutate({ - mutation: createLabelMutation, - variables: this.mutationVariables, - update: ( - store, - { - data: { - labelCreate: { label }, - }, - }, - ) => { - if (label) { - this.updateLabelsInCache(store, label); - } - }, - }); - if (labelCreate.errors.length) { - [this.error] = labelCreate.errors; - } else { - this.$emit('hideCreateView'); - } - } catch { - createAlert({ message: errorMessage }); - } - this.labelCreateInProgress = false; - }, - }, -}; -</script> - -<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-mt-3"> - {{ error }} - </gl-alert> - <gl-form-input - v-model.trim="labelTitle" - class="gl-mt-3" - :placeholder="__('Name new label')" - :autofocus="true" - data-testid="label-title-input" - /> - </div> - <div class="dropdown-content gl-px-3"> - <div class="suggest-colors suggest-colors-dropdown gl-mt-0! gl-mb-3! gl-mb-0"> - <gl-link - v-for="(color, index) in suggestedColors" - :key="index" - v-gl-tooltip:tooltipcontainer - :style="{ backgroundColor: getColorCode(color) }" - :title="getColorName(color)" - @click.prevent="handleColorClick(color)" - /> - </div> - <div class="color-input-container gl-display-flex"> - <span - class="dropdown-label-color-preview gl-relative gl-display-inline-block" - data-testid="selected-color" - :style="{ backgroundColor: selectedColor }" - ></span> - <gl-form-input - v-model.trim="selectedColor" - class="gl-rounded-top-left-none gl-rounded-bottom-left-none gl-mb-2" - :placeholder="__('Use custom color #FF0000')" - data-testid="selected-color-text" - /> - </div> - </div> - <div class="dropdown-actions gl-display-flex gl-justify-content-space-between gl-pt-3 gl-px-3"> - <gl-button - :disabled="disableCreate" - category="primary" - variant="confirm" - class="gl-display-flex gl-align-items-center" - data-testid="create-button" - @click="createLabel" - > - <gl-loading-icon v-if="labelCreateInProgress" size="sm" :inline="true" class="mr-1" /> - {{ __('Create') }} - </gl-button> - <gl-button - class="js-btn-cancel-create" - data-testid="cancel-button" - @click.stop="$emit('hideCreateView')" - > - {{ __('Cancel') }} - </gl-button> - </div> - </div> -</template> 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 deleted file mode 100644 index 1d854505d11..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue +++ /dev/null @@ -1,177 +0,0 @@ -<script> -import { GlDropdownForm, GlDropdownItem, GlLoadingIcon, GlIntersectionObserver } from '@gitlab/ui'; -import fuzzaldrinPlus from 'fuzzaldrin-plus'; -import { createAlert } from '~/flash'; -import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -import { __ } from '~/locale'; -import { workspaceLabelsQueries } from '~/sidebar/constants'; -import LabelItem from './label_item.vue'; - -export default { - components: { - GlDropdownForm, - GlDropdownItem, - GlLoadingIcon, - GlIntersectionObserver, - LabelItem, - }, - model: { - prop: 'localSelectedLabels', - }, - props: { - allowMultiselect: { - type: Boolean, - required: true, - }, - localSelectedLabels: { - type: Array, - required: true, - }, - fullPath: { - type: String, - required: true, - }, - searchKey: { - type: String, - required: true, - }, - workspaceType: { - type: String, - required: true, - }, - }, - data() { - return { - labels: [], - isVisible: false, - }; - }, - apollo: { - labels: { - query() { - return workspaceLabelsQueries[this.workspaceType].query; - }, - variables() { - return { - fullPath: this.fullPath, - searchTerm: this.searchKey, - }; - }, - skip() { - return this.searchKey.length === 1 || !this.isVisible; - }, - update: (data) => data.workspace?.labels?.nodes || [], - error() { - createAlert({ message: __('Error fetching labels.') }); - }, - }, - }, - computed: { - labelsFetchInProgress() { - return this.$apollo.queries.labels.loading; - }, - localSelectedLabelsIds() { - return this.localSelectedLabels.map((label) => getIdFromGraphQLId(label.id)); - }, - visibleLabels() { - if (this.searchKey) { - return fuzzaldrinPlus.filter(this.labels, this.searchKey, { - key: ['title'], - }); - } - return this.labels; - }, - showNoMatchingResultsMessage() { - return Boolean(this.searchKey) && this.visibleLabels.length === 0; - }, - shouldHighlightFirstItem() { - return this.searchKey !== '' && this.visibleLabels.length > 0; - }, - }, - methods: { - isLabelSelected(label) { - return this.localSelectedLabelsIds.includes(getIdFromGraphQLId(label.id)); - }, - /** - * This method scrolls item from dropdown into - * the view if it is off the viewable area of the - * container. - */ - scrollIntoViewIfNeeded() { - const highlightedLabel = this.$refs.labelsListContainer.querySelector('.is-focused'); - - if (highlightedLabel) { - const container = this.$refs.labelsListContainer.getBoundingClientRect(); - const label = highlightedLabel.getBoundingClientRect(); - - if (label.bottom > container.bottom) { - this.$refs.labelsListContainer.scrollTop += label.bottom - container.bottom; - } else if (label.top < container.top) { - this.$refs.labelsListContainer.scrollTop -= container.top - label.top; - } - } - }, - updateSelectedLabels(label) { - let labels; - if (this.isLabelSelected(label)) { - labels = this.localSelectedLabels.filter( - ({ id }) => id !== getIdFromGraphQLId(label.id) && id !== label.id, - ); - } else { - labels = [...this.localSelectedLabels, label]; - } - this.$emit('input', labels); - }, - handleLabelClick(label) { - this.updateSelectedLabels(label); - if (!this.allowMultiselect) { - this.$emit('closeDropdown', this.localSelectedLabels); - } - }, - onDropdownAppear() { - this.isVisible = true; - }, - selectFirstItem() { - if (this.shouldHighlightFirstItem) { - this.handleLabelClick(this.visibleLabels[0]); - } - }, - }, -}; -</script> - -<template> - <gl-intersection-observer @appear="onDropdownAppear"> - <gl-dropdown-form class="labels-select-contents-list js-labels-list"> - <div ref="labelsListContainer" data-testid="dropdown-content"> - <gl-loading-icon - v-if="labelsFetchInProgress" - class="labels-fetch-loading gl-align-items-center gl-w-full gl-h-full gl-mb-3" - size="lg" - /> - <template v-else> - <gl-dropdown-item - v-for="(label, index) in visibleLabels" - :key="label.id" - :is-checked="isLabelSelected(label)" - is-check-centered - is-check-item - :active="shouldHighlightFirstItem && index === 0" - active-class="is-focused" - data-testid="labels-list" - @click.native.capture.stop="handleLabelClick(label)" - > - <label-item :label="label" /> - </gl-dropdown-item> - <gl-dropdown-item - v-show="showNoMatchingResultsMessage" - class="gl-p-3 gl-text-center" - data-testid="no-results" - > - {{ __('No matching results') }} - </gl-dropdown-item> - </template> - </div> - </gl-dropdown-form> - </gl-intersection-observer> -</template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_footer.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_footer.vue deleted file mode 100644 index e67e704ffb8..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_footer.vue +++ /dev/null @@ -1,35 +0,0 @@ -<script> -import { GlDropdownItem } from '@gitlab/ui'; - -export default { - components: { - GlDropdownItem, - }, - inject: ['allowLabelCreate', 'labelsManagePath'], - props: { - footerCreateLabelTitle: { - type: String, - required: true, - }, - footerManageLabelTitle: { - type: String, - required: true, - }, - }, -}; -</script> - -<template> - <div data-testid="dropdown-footer"> - <gl-dropdown-item - v-if="allowLabelCreate" - data-testid="create-label-button" - @click.capture.native.stop="$emit('toggleDropdownContentsCreateView')" - > - {{ footerCreateLabelTitle }} - </gl-dropdown-item> - <gl-dropdown-item :href="labelsManagePath" @click.capture.native.stop> - {{ footerManageLabelTitle }} - </gl-dropdown-item> - </div> -</template> 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 deleted file mode 100644 index 154a8e866d0..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_header.vue +++ /dev/null @@ -1,91 +0,0 @@ -<script> -import { GlButton, GlSearchBoxByType } from '@gitlab/ui'; - -export default { - components: { - GlButton, - GlSearchBoxByType, - }, - props: { - labelsCreateTitle: { - type: String, - required: true, - }, - labelsListTitle: { - type: String, - required: true, - }, - showDropdownContentsCreateView: { - type: Boolean, - required: true, - }, - labelsFetchInProgress: { - type: Boolean, - required: false, - default: false, - }, - searchKey: { - type: String, - required: true, - }, - isStandalone: { - type: Boolean, - required: false, - default: false, - }, - }, - computed: { - dropdownTitle() { - return this.showDropdownContentsCreateView ? this.labelsCreateTitle : this.labelsListTitle; - }, - }, - methods: { - focusInput() { - this.$refs.searchInput?.focusInput(); - }, - }, -}; -</script> - -<template> - <div data-testid="dropdown-header"> - <div - v-if="!isStandalone" - class="dropdown-title gl-display-flex gl-align-items-center gl-pt-0 gl-pb-3! gl-mb-0" - data-testid="dropdown-header-title" - > - <gl-button - v-if="showDropdownContentsCreateView" - :aria-label="__('Go back')" - variant="link" - size="small" - class="js-btn-back dropdown-header-button gl-p-0" - icon="arrow-left" - data-testid="go-back-button" - @click.stop="$emit('toggleDropdownContentsCreateView')" - /> - <span class="gl-flex-grow-1">{{ dropdownTitle }}</span> - <gl-button - :aria-label="__('Close')" - variant="link" - size="small" - class="dropdown-header-button gl-p-0!" - icon="close" - data-testid="close-button" - data-qa-selector="close_labels_dropdown_button" - @click="$emit('closeDropdown')" - /> - </div> - <gl-search-box-by-type - v-if="!showDropdownContentsCreateView" - ref="searchInput" - :value="searchKey" - :placeholder="__('Search labels')" - :disabled="labelsFetchInProgress" - 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/dropdown_value.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue deleted file mode 100644 index 57e3ee4aaa5..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue +++ /dev/null @@ -1,125 +0,0 @@ -<script> -import { GlIcon, GlLabel, GlTooltipDirective } from '@gitlab/ui'; -import { sortBy } from 'lodash'; -import { isScopedLabel } from '~/lib/utils/common_utils'; -import { s__, sprintf } from '~/locale'; - -export default { - directives: { - GlTooltip: GlTooltipDirective, - }, - components: { - GlIcon, - GlLabel, - }, - inject: ['allowScopedLabels'], - props: { - disableLabels: { - type: Boolean, - required: false, - default: false, - }, - selectedLabels: { - type: Array, - required: true, - }, - allowLabelRemove: { - type: Boolean, - required: true, - }, - labelsFilterBasePath: { - type: String, - required: true, - }, - labelsFilterParam: { - type: String, - required: true, - }, - }, - computed: { - sortedSelectedLabels() { - return sortBy(this.selectedLabels, (label) => (isScopedLabel(label) ? 0 : 1)); - }, - labelsList() { - const labelsString = this.selectedLabels.length - ? this.selectedLabels - .slice(0, 5) - .map((label) => label.title) - .join(', ') - : s__('LabelSelect|Labels'); - - if (this.selectedLabels.length > 5) { - return sprintf(s__('LabelSelect|%{labelsString}, and %{remainingLabelCount} more'), { - labelsString, - remainingLabelCount: this.selectedLabels.length - 5, - }); - } - - return labelsString; - }, - }, - methods: { - labelFilterUrl(label) { - return `${this.labelsFilterBasePath}?${this.labelsFilterParam}[]=${encodeURIComponent( - label.title, - )}`; - }, - scopedLabel(label) { - return this.allowScopedLabels && isScopedLabel(label); - }, - removeLabel(labelId) { - this.$emit('onLabelRemove', labelId); - }, - handleCollapsedClick() { - this.$emit('onCollapsedValueClick'); - }, - }, -}; -</script> - -<template> - <div - :class="{ - 'has-labels': selectedLabels.length, - }" - class="value issuable-show-labels js-value" - data-testid="value-wrapper" - > - <div - v-gl-tooltip.left.viewport - :title="labelsList" - class="sidebar-collapsed-icon" - @click="handleCollapsedClick" - > - <gl-icon name="labels" /> - <span class="collapse-truncated-title gl-pt-2 gl-px-3 gl-font-sm">{{ - selectedLabels.length - }}</span> - </div> - <span - v-if="!selectedLabels.length" - class="text-secondary hide-collapsed" - data-testid="empty-placeholder" - > - <slot></slot> - </span> - <template v-else> - <gl-label - v-for="label in sortedSelectedLabels" - :key="label.id" - class="hide-collapsed" - data-qa-selector="selected_label_content" - :data-qa-label-name="label.title" - :title="label.title" - :description="label.description" - :background-color="label.color" - :target="labelFilterUrl(label)" - :scoped="scopedLabel(label)" - :show-close-button="allowLabelRemove" - :disabled="disableLabels" - tooltip-placement="top" - @close="removeLabel(label.id)" - /> - </template> - </div> -</template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/create_label.mutation.graphql b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/create_label.mutation.graphql deleted file mode 100644 index a9c791091fc..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/create_label.mutation.graphql +++ /dev/null @@ -1,12 +0,0 @@ -#import "~/graphql_shared/fragments/label.fragment.graphql" - -mutation createLabel($title: String!, $color: String, $projectPath: ID, $groupPath: ID) { - labelCreate( - input: { title: $title, color: $color, projectPath: $projectPath, groupPath: $groupPath } - ) { - label { - ...Label - } - errors - } -} diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/epic_labels.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/epic_labels.query.graphql deleted file mode 100644 index c442c17eb88..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/epic_labels.query.graphql +++ /dev/null @@ -1,15 +0,0 @@ -#import "~/graphql_shared/fragments/label.fragment.graphql" - -query epicLabels($fullPath: ID!, $iid: ID) { - workspace: group(fullPath: $fullPath) { - id - issuable: epic(iid: $iid) { - id - labels { - nodes { - ...Label - } - } - } - } -} diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/epic_update_labels.mutation.graphql b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/epic_update_labels.mutation.graphql deleted file mode 100644 index cb054e2968f..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/epic_update_labels.mutation.graphql +++ /dev/null @@ -1,15 +0,0 @@ -#import "~/graphql_shared/fragments/label.fragment.graphql" - -mutation updateEpicLabels($input: UpdateEpicInput!) { - updateIssuableLabels: updateEpic(input: $input) { - issuable: epic { - id - labels { - nodes { - ...Label - } - } - } - errors - } -} diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/group_labels.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/group_labels.query.graphql deleted file mode 100644 index ce1a69f84c0..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/group_labels.query.graphql +++ /dev/null @@ -1,12 +0,0 @@ -#import "~/graphql_shared/fragments/label.fragment.graphql" - -query groupLabels($fullPath: ID!, $searchTerm: String) { - workspace: group(fullPath: $fullPath) { - id - labels(searchTerm: $searchTerm, onlyGroupLabels: true, includeAncestorGroups: true) { - nodes { - ...Label - } - } - } -} 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 deleted file mode 100644 index 2904857270e..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql +++ /dev/null @@ -1,15 +0,0 @@ -#import "~/graphql_shared/fragments/label.fragment.graphql" - -query issueLabels($fullPath: ID!, $iid: String) { - workspace: project(fullPath: $fullPath) { - id - issuable: issue(iid: $iid) { - id - labels { - nodes { - ...Label - } - } - } - } -} diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/merge_request_labels.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/merge_request_labels.query.graphql deleted file mode 100644 index e0cdfd91658..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/merge_request_labels.query.graphql +++ /dev/null @@ -1,15 +0,0 @@ -#import "~/graphql_shared/fragments/label.fragment.graphql" - -query mergeRequestLabels($fullPath: ID!, $iid: String!) { - workspace: project(fullPath: $fullPath) { - id - issuable: mergeRequest(iid: $iid) { - id - labels { - nodes { - ...Label - } - } - } - } -} diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql deleted file mode 100644 index a7c24620aad..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql +++ /dev/null @@ -1,12 +0,0 @@ -#import "~/graphql_shared/fragments/label.fragment.graphql" - -query projectLabels($fullPath: ID!, $searchTerm: String) { - workspace: project(fullPath: $fullPath) { - id - labels(searchTerm: $searchTerm, includeAncestorGroups: true) { - nodes { - ...Label - } - } - } -} 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 deleted file mode 100644 index 314ffbaf84c..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/label_item.vue +++ /dev/null @@ -1,21 +0,0 @@ -<script> -export default { - props: { - label: { - type: Object, - required: true, - }, - }, -}; -</script> - -<template> - <div class="gl-display-flex gl-align-items-center gl-word-break-word"> - <span - class="dropdown-label-box gl-flex-shrink-0 gl-top-0 gl-mr-3" - :style="{ 'background-color': label.color }" - data-testid="label-color-box" - ></span> - <span>{{ label.title }}</span> - </div> -</template> 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 deleted file mode 100644 index 2c27a69d587..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue +++ /dev/null @@ -1,406 +0,0 @@ -<script> -import { debounce } from 'lodash'; -import issuableLabelsSubscription from 'ee_else_ce/sidebar/queries/issuable_labels.subscription.graphql'; -import { MutationOperationMode, getIdFromGraphQLId } from '~/graphql_shared/utils'; -import { createAlert } from '~/flash'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -import { IssuableType } from '~/issues/constants'; - -import { __ } from '~/locale'; -import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'; -import { issuableLabelsQueries } from '~/sidebar/constants'; -import { DEBOUNCE_DROPDOWN_DELAY, DropdownVariant } from './constants'; -import DropdownContents from './dropdown_contents.vue'; -import DropdownValue from './dropdown_value.vue'; -import { - isDropdownVariantSidebar, - isDropdownVariantStandalone, - isDropdownVariantEmbedded, -} from './utils'; - -export default { - components: { - DropdownValue, - DropdownContents, - SidebarEditableItem, - }, - mixins: [glFeatureFlagsMixin()], - inject: { - allowLabelEdit: { - default: false, - }, - }, - props: { - iid: { - type: String, - required: false, - default: '', - }, - fullPath: { - type: String, - required: true, - }, - allowLabelRemove: { - type: Boolean, - required: false, - default: false, - }, - allowMultiselect: { - type: Boolean, - required: false, - default: false, - }, - variant: { - type: String, - required: false, - default: DropdownVariant.Sidebar, - }, - labelsFilterBasePath: { - type: String, - required: false, - default: '', - }, - labelsFilterParam: { - type: String, - required: false, - default: 'label_name', - }, - dropdownButtonText: { - type: String, - required: false, - default: __('Label'), - }, - labelsListTitle: { - type: String, - required: false, - default: __('Assign labels'), - }, - labelsCreateTitle: { - type: String, - required: false, - default: __('Create group label'), - }, - footerCreateLabelTitle: { - type: String, - required: false, - default: __('Create group label'), - }, - footerManageLabelTitle: { - type: String, - required: false, - default: __('Manage group labels'), - }, - issuableType: { - type: String, - required: true, - }, - workspaceType: { - type: String, - required: true, - }, - attrWorkspacePath: { - type: String, - required: true, - }, - labelCreateType: { - type: String, - required: true, - }, - }, - data() { - return { - contentIsOnViewport: true, - issuable: null, - labelsSelectInProgress: false, - oldIid: null, - sidebarExpandedOnClick: false, - }; - }, - computed: { - isLoading() { - return this.labelsSelectInProgress || this.$apollo.queries.issuable.loading; - }, - issuableLabelIds() { - return this.issuableLabels.map((label) => label.id); - }, - issuableLabels() { - return this.issuable?.labels.nodes || []; - }, - issuableId() { - return this.issuable?.id; - }, - }, - apollo: { - issuable: { - query() { - return issuableLabelsQueries[this.issuableType].issuableQuery; - }, - skip() { - return !isDropdownVariantSidebar(this.variant); - }, - variables() { - return { - iid: this.iid, - fullPath: this.fullPath, - }; - }, - update(data) { - return data.workspace?.issuable; - }, - error() { - createAlert({ message: __('Error fetching labels.') }); - }, - subscribeToMore: { - document() { - return issuableLabelsSubscription; - }, - variables() { - return { - issuableId: this.issuableId, - }; - }, - skip() { - return !this.issuableId || !this.isDropdownVariantSidebar; - }, - updateQuery( - _, - { - subscriptionData: { - data: { issuableLabelsUpdated }, - }, - }, - ) { - if (issuableLabelsUpdated) { - const { - id, - labels: { nodes }, - } = issuableLabelsUpdated; - this.$emit('updateSelectedLabels', { id, labels: nodes }); - } - }, - }, - }, - }, - watch: { - iid(_, oldVal) { - this.oldIid = oldVal; - }, - }, - mounted() { - document.addEventListener('toggleSidebarRevealLabelsDropdown', this.handleCollapsedValueClick); - }, - beforeDestroy() { - document.removeEventListener( - 'toggleSidebarRevealLabelsDropdown', - this.handleCollapsedValueClick, - ); - }, - methods: { - handleDropdownClose(labels) { - if (this.iid !== '') { - this.updateSelectedLabels(this.getUpdateVariables(labels)); - } else { - this.$emit('updateSelectedLabels', { labels }); - } - - this.collapseEditableItem(); - }, - collapseEditableItem() { - this.$refs.editable?.collapse(); - if (this.sidebarExpandedOnClick) { - this.sidebarExpandedOnClick = false; - this.$emit('toggleCollapse'); - } - }, - handleCollapsedValueClick() { - this.sidebarExpandedOnClick = true; - this.$emit('toggleCollapse'); - debounce(() => { - this.$refs.editable.toggle(); - this.$refs.dropdownContents.showDropdown(); - }, DEBOUNCE_DROPDOWN_DELAY)(); - }, - getUpdateVariables(labels) { - let labelIds = []; - - labelIds = labels.map(({ id }) => id); - const currentIid = this.oldIid || this.iid; - - const updateVariables = { - iid: currentIid, - projectPath: this.fullPath, - labelIds, - }; - - switch (this.issuableType) { - case IssuableType.Issue: - return updateVariables; - case IssuableType.MergeRequest: - return { - ...updateVariables, - operationMode: MutationOperationMode.Replace, - }; - case IssuableType.Epic: - return { - iid: currentIid, - groupPath: this.fullPath, - addLabelIds: labelIds.map((id) => getIdFromGraphQLId(id)), - removeLabelIds: this.issuableLabelIds - .filter((id) => !labelIds.includes(id)) - .map((id) => getIdFromGraphQLId(id)), - }; - default: - return {}; - } - }, - updateSelectedLabels(inputVariables) { - this.labelsSelectInProgress = true; - - this.$apollo - .mutate({ - mutation: issuableLabelsQueries[this.issuableType].mutation, - variables: { input: inputVariables }, - }) - .then(({ data }) => { - if (data.updateIssuableLabels?.errors?.length) { - throw new Error(); - } - - this.$emit('updateSelectedLabels', { - id: data.updateIssuableLabels?.issuable?.id, - labels: data.updateIssuableLabels?.issuable?.labels?.nodes, - }); - }) - .catch((error) => - createAlert({ - message: __('An error occurred while updating labels.'), - captureError: true, - error, - }), - ) - .finally(() => { - this.labelsSelectInProgress = false; - }); - }, - getRemoveVariables(labelId) { - const removeVariables = { - iid: this.iid, - projectPath: this.fullPath, - }; - - switch (this.issuableType) { - case IssuableType.Issue: - return { - ...removeVariables, - removeLabelIds: [labelId], - }; - case IssuableType.MergeRequest: - return { - ...removeVariables, - labelIds: [labelId], - operationMode: MutationOperationMode.Remove, - }; - case IssuableType.Epic: - return { - iid: this.iid, - removeLabelIds: [getIdFromGraphQLId(labelId)], - groupPath: this.fullPath, - }; - default: - return {}; - } - }, - handleLabelRemove(labelId) { - this.updateSelectedLabels(this.getRemoveVariables(labelId)); - this.$emit('onLabelRemove', labelId); - }, - isDropdownVariantSidebar, - isDropdownVariantStandalone, - isDropdownVariantEmbedded, - }, -}; -</script> - -<template> - <div - class="labels-select-wrapper gl-relative" - :class="{ - 'is-standalone': isDropdownVariantStandalone(variant), - 'is-embedded': isDropdownVariantEmbedded(variant), - }" - data-testid="sidebar-labels" - data-qa-selector="labels_block" - > - <template v-if="isDropdownVariantSidebar(variant)"> - <sidebar-editable-item - ref="editable" - :title="__('Labels')" - :loading="isLoading" - :can-edit="allowLabelEdit" - @open="oldIid = null" - > - <template #collapsed> - <dropdown-value - :disable-labels="labelsSelectInProgress" - :selected-labels="issuableLabels" - :allow-label-remove="allowLabelRemove" - :labels-filter-base-path="labelsFilterBasePath" - :labels-filter-param="labelsFilterParam" - @onLabelRemove="handleLabelRemove" - @onCollapsedValueClick="handleCollapsedValueClick" - > - <slot></slot> - </dropdown-value> - </template> - <template #default="{ edit }"> - <dropdown-value - :disable-labels="labelsSelectInProgress" - :selected-labels="issuableLabels" - :allow-label-remove="allowLabelRemove" - :labels-filter-base-path="labelsFilterBasePath" - :labels-filter-param="labelsFilterParam" - class="gl-mb-2" - @onLabelRemove="handleLabelRemove" - > - <slot></slot> - </dropdown-value> - <dropdown-contents - ref="dropdownContents" - :dropdown-button-text="dropdownButtonText" - :allow-multiselect="allowMultiselect" - :labels-list-title="labelsListTitle" - :footer-create-label-title="footerCreateLabelTitle" - :footer-manage-label-title="footerManageLabelTitle" - :labels-create-title="labelsCreateTitle" - :selected-labels="issuableLabels" - :variant="variant" - :is-visible="edit" - :full-path="fullPath" - :workspace-type="workspaceType" - :attr-workspace-path="attrWorkspacePath" - :label-create-type="labelCreateType" - @setLabels="handleDropdownClose" - @closeDropdown="collapseEditableItem" - /> - </template> - </sidebar-editable-item> - </template> - <dropdown-contents - v-else - ref="dropdownContents" - :allow-multiselect="allowMultiselect" - :dropdown-button-text="dropdownButtonText" - :labels-list-title="labelsListTitle" - :footer-create-label-title="footerCreateLabelTitle" - :footer-manage-label-title="footerManageLabelTitle" - :labels-create-title="labelsCreateTitle" - :selected-labels="issuableLabels" - :variant="variant" - :full-path="fullPath" - :workspace-type="workspaceType" - :attr-workspace-path="attrWorkspacePath" - :label-create-type="labelCreateType" - @setLabels="handleDropdownClose" - /> - </div> -</template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/utils.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/utils.js deleted file mode 100644 index b5cd946a189..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/utils.js +++ /dev/null @@ -1,22 +0,0 @@ -import { DropdownVariant } from './constants'; - -/** - * Returns boolean representing whether dropdown variant - * is `sidebar` - * @param {string} variant - */ -export const isDropdownVariantSidebar = (variant) => variant === DropdownVariant.Sidebar; - -/** - * Returns boolean representing whether dropdown variant - * is `standalone` - * @param {string} variant - */ -export const isDropdownVariantStandalone = (variant) => variant === DropdownVariant.Standalone; - -/** - * Returns boolean representing whether dropdown variant - * is `embedded` - * @param {string} variant - */ -export const isDropdownVariantEmbedded = (variant) => variant === DropdownVariant.Embedded; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_alert_assignees.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/get_alert_assignees.query.graphql deleted file mode 100644 index bb6c7181e5c..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_alert_assignees.query.graphql +++ /dev/null @@ -1,21 +0,0 @@ -#import "~/graphql_shared/fragments/user.fragment.graphql" -#import "~/graphql_shared/fragments/user_availability.fragment.graphql" - -query alertAssignees( - $domain: AlertManagementDomainFilter = threat_monitoring - $fullPath: ID! - $iid: String! -) { - workspace: project(fullPath: $fullPath) { - id - issuable: alertManagementAlert(domain: $domain, iid: $iid) { - iid - assignees { - nodes { - ...User - ...UserAvailability - } - } - } - } -} diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_issue_assignees.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/get_issue_assignees.query.graphql deleted file mode 100644 index 4af07366a6d..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_issue_assignees.query.graphql +++ /dev/null @@ -1,21 +0,0 @@ -#import "~/graphql_shared/fragments/user.fragment.graphql" -#import "~/graphql_shared/fragments/user_availability.fragment.graphql" - -query issueAssignees($fullPath: ID!, $iid: String!) { - workspace: project(fullPath: $fullPath) { - id - issuable: issue(iid: $iid) { - id - author { - ...User - ...UserAvailability - } - assignees { - nodes { - ...User - ...UserAvailability - } - } - } - } -} diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_issue_participants.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/get_issue_participants.query.graphql deleted file mode 100644 index eae5e96ac46..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_issue_participants.query.graphql +++ /dev/null @@ -1,17 +0,0 @@ -#import "~/graphql_shared/fragments/user.fragment.graphql" -#import "~/graphql_shared/fragments/user_availability.fragment.graphql" - -query issueParticipants($fullPath: ID!, $iid: String!, $getStatus: Boolean = false) { - workspace: project(fullPath: $fullPath) { - id - issuable: issue(iid: $iid) { - id - participants { - nodes { - ...User - ...UserAvailability @include(if: $getStatus) - } - } - } - } -} diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_issue_timelogs.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/get_issue_timelogs.query.graphql deleted file mode 100644 index b127b8ec5a9..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_issue_timelogs.query.graphql +++ /dev/null @@ -1,13 +0,0 @@ -#import "~/graphql_shared/fragments/issuable_timelogs.fragment.graphql" - -query issueTimeTrackingReport($id: IssueID!) { - issuable: issue(id: $id) { - id - title - timelogs { - nodes { - ...TimelogFragment - } - } - } -} diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_merge_request_reviewers.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/get_merge_request_reviewers.query.graphql deleted file mode 100644 index f087ca6c982..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_merge_request_reviewers.query.graphql +++ /dev/null @@ -1,26 +0,0 @@ -#import "~/graphql_shared/fragments/user.fragment.graphql" -#import "~/graphql_shared/fragments/user_availability.fragment.graphql" - -query mergeRequestReviewers($fullPath: ID!, $iid: String!) { - workspace: project(fullPath: $fullPath) { - id - issuable: mergeRequest(iid: $iid) { - id - reviewers { - nodes { - ...User - ...UserAvailability - mergeRequestInteraction { - canMerge - canUpdate - approved - reviewed - } - } - } - userPermissions { - adminMergeRequest - } - } - } -} diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_mr_assignees.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/get_mr_assignees.query.graphql deleted file mode 100644 index f70cd723f2e..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_mr_assignees.query.graphql +++ /dev/null @@ -1,30 +0,0 @@ -#import "~/graphql_shared/fragments/user.fragment.graphql" -#import "~/graphql_shared/fragments/user_availability.fragment.graphql" - -query getMrAssignees($fullPath: ID!, $iid: String!) { - workspace: project(fullPath: $fullPath) { - id - issuable: mergeRequest(iid: $iid) { - id - author { - ...User - ...UserAvailability - mergeRequestInteraction { - canMerge - } - } - assignees { - nodes { - ...User - ...UserAvailability - mergeRequestInteraction { - canMerge - } - } - } - userPermissions { - canMerge - } - } - } -} diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_mr_participants.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/get_mr_participants.query.graphql deleted file mode 100644 index 2781ac71f31..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_mr_participants.query.graphql +++ /dev/null @@ -1,17 +0,0 @@ -#import "~/graphql_shared/fragments/user.fragment.graphql" -#import "~/graphql_shared/fragments/user_availability.fragment.graphql" - -query getMrParticipants($fullPath: ID!, $iid: String!, $getStatus: Boolean = false) { - workspace: project(fullPath: $fullPath) { - id - issuable: mergeRequest(iid: $iid) { - id - participants { - nodes { - ...User - ...UserAvailability @include(if: $getStatus) - } - } - } - } -} diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_mr_timelogs.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/get_mr_timelogs.query.graphql deleted file mode 100644 index 17f548b44b5..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_mr_timelogs.query.graphql +++ /dev/null @@ -1,13 +0,0 @@ -#import "~/graphql_shared/fragments/issuable_timelogs.fragment.graphql" - -query mrTimeTrackingReport($id: MergeRequestID!) { - issuable: mergeRequest(id: $id) { - id - title - timelogs { - nodes { - ...TimelogFragment - } - } - } -} diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/merge_request_reviewers.subscription.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/merge_request_reviewers.subscription.graphql deleted file mode 100644 index a1b16b378b3..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/queries/merge_request_reviewers.subscription.graphql +++ /dev/null @@ -1,22 +0,0 @@ -#import "~/graphql_shared/fragments/user.fragment.graphql" -#import "~/graphql_shared/fragments/user_availability.fragment.graphql" - -subscription mergeRequestReviewersUpdated($issuableId: IssuableID!) { - mergeRequestReviewersUpdated(issuableId: $issuableId) { - ... on MergeRequest { - id - reviewers { - nodes { - ...User - ...UserAvailability - mergeRequestInteraction { - canMerge - canUpdate - approved - reviewed - } - } - } - } - } -} diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/update_issue_assignees.mutation.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/update_issue_assignees.mutation.graphql deleted file mode 100644 index 24de5ea4fe3..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/queries/update_issue_assignees.mutation.graphql +++ /dev/null @@ -1,18 +0,0 @@ -#import "~/graphql_shared/fragments/user.fragment.graphql" -#import "~/graphql_shared/fragments/user_availability.fragment.graphql" - -mutation issueSetAssignees($iid: String!, $assigneeUsernames: [String!]!, $fullPath: ID!) { - issuableSetAssignees: issueSetAssignees( - input: { iid: $iid, assigneeUsernames: $assigneeUsernames, projectPath: $fullPath } - ) { - issuable: issue { - id - assignees { - nodes { - ...User - ...UserAvailability - } - } - } - } -} diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/update_mr_assignees.mutation.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/update_mr_assignees.mutation.graphql deleted file mode 100644 index 5fec2ccbdfb..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/queries/update_mr_assignees.mutation.graphql +++ /dev/null @@ -1,21 +0,0 @@ -#import "~/graphql_shared/fragments/user.fragment.graphql" -#import "~/graphql_shared/fragments/user_availability.fragment.graphql" - -mutation mergeRequestSetAssignees($iid: String!, $assigneeUsernames: [String!]!, $fullPath: ID!) { - issuableSetAssignees: mergeRequestSetAssignees( - input: { iid: $iid, assigneeUsernames: $assigneeUsernames, projectPath: $fullPath } - ) { - issuable: mergeRequest { - id - assignees { - nodes { - ...User - ...UserAvailability - mergeRequestInteraction { - canMerge - } - } - } - } - } -} 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 deleted file mode 100644 index 465ee9aa0d4..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.stories.js +++ /dev/null @@ -1,21 +0,0 @@ -import TodoButton from './todo_button.vue'; - -export default { - component: TodoButton, - title: 'vue_shared/sidebar/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 deleted file mode 100644 index cdc7422c7df..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.vue +++ /dev/null @@ -1,44 +0,0 @@ -<script> -import { GlButton } from '@gitlab/ui'; -import { todoLabel, updateGlobalTodoCount } from './utils'; - -export default { - components: { - GlButton, - }, - props: { - isTodo: { - type: Boolean, - required: false, - default: true, - }, - }, - computed: { - buttonLabel() { - return todoLabel(this.isTodo); - }, - }, - methods: { - incrementGlobalTodoCount() { - updateGlobalTodoCount(1); - }, - decrementGlobalTodoCount() { - 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 deleted file mode 100644 index 098ab72dfb5..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/utils.js +++ /dev/null @@ -1,21 +0,0 @@ -import { __ } from '~/locale'; - -export const todoLabel = (hasTodo) => { - return hasTodo ? __('Mark as done') : __('Add a to do'); -}; - -export const 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); -}; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue b/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue deleted file mode 100644 index 6dacf4e10d3..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue +++ /dev/null @@ -1,55 +0,0 @@ -<script> -import { GlButton, GlTooltipDirective } from '@gitlab/ui'; -import { __ } from '~/locale'; - -export default { - name: 'ToggleSidebar', - components: { - GlButton, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - props: { - collapsed: { - type: Boolean, - required: true, - }, - cssClasses: { - type: String, - required: false, - default: '', - }, - }, - computed: { - tooltipLabel() { - return this.collapsed ? __('Expand sidebar') : __('Collapse sidebar'); - }, - buttonIcon() { - return this.collapsed ? 'chevron-double-lg-left' : 'chevron-double-lg-right'; - }, - allCssClasses() { - return [this.cssClasses, { 'js-sidebar-collapsed': this.collapsed }]; - }, - }, - methods: { - toggle() { - this.$emit('toggle'); - }, - }, -}; -</script> - -<template> - <gl-button - v-gl-tooltip:body.viewport.left - :title="tooltipLabel" - :class="allCssClasses" - class="gutter-toggle btn-sidebar-action js-sidebar-vue-toggle" - :icon="buttonIcon" - category="tertiary" - size="small" - :aria-label="__('toggle collapse')" - @click="toggle" - /> -</template> diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue index a2d8b7cbd15..28a16cd846a 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue +++ b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue @@ -1,6 +1,6 @@ <script> -import { GlIntersectionObserver, GlSafeHtmlDirective } from '@gitlab/ui'; -import { scrollToElement } from '~/lib/utils/common_utils'; +import { GlIntersectionObserver } from '@gitlab/ui'; +import LineHighlighter from '~/blob/line_highlighter'; import ChunkLine from './chunk_line.vue'; /* @@ -20,9 +20,6 @@ export default { ChunkLine, GlIntersectionObserver, }, - directives: { - SafeHtml: GlSafeHtmlDirective, - }, props: { isFirstChunk: { type: Boolean, @@ -84,12 +81,14 @@ export default { return; } - window.requestIdleCallback(() => { + window.requestIdleCallback(async () => { this.isLoading = false; const { hash } = this.$route; if (hash && this.totalChunks > 0 && this.totalChunks === this.chunkIndex + 1) { // when the last chunk is loaded scroll to the hash - scrollToElement(hash, { behavior: 'auto' }); + await this.$nextTick(); + const lineHighlighter = new LineHighlighter({ scrollBehavior: 'auto' }); + lineHighlighter.highlightHash(hash); } }); }, diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue index 0bf19f83d86..ce6741f33b1 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue +++ b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue @@ -1,11 +1,11 @@ <script> -import { GlSafeHtmlDirective } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { getPageParamValue, getPageSearchString } from '~/blob/utils'; export default { directives: { - SafeHtml: GlSafeHtmlDirective, + SafeHtml, }, mixins: [glFeatureFlagMixin()], props: { diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/link_dependencies.js b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/link_dependencies.js index fca2616f069..cd15916851c 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/link_dependencies.js +++ b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/link_dependencies.js @@ -4,6 +4,7 @@ import godepsJsonLinker from './utils/godeps_json_linker'; import gemfileLinker from './utils/gemfile_linker'; import podspecJsonLinker from './utils/podspec_json_linker'; import composerJsonLinker from './utils/composer_json_linker'; +import goSumLinker from './utils/go_sum_linker'; const DEPENDENCY_LINKERS = { package_json: packageJsonLinker, @@ -12,6 +13,7 @@ const DEPENDENCY_LINKERS = { gemfile: gemfileLinker, podspec_json: podspecJsonLinker, composer_json: composerJsonLinker, + go_sum: goSumLinker, }; /** diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/go_sum_linker.js b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/go_sum_linker.js new file mode 100644 index 00000000000..b290dfa78b9 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/go_sum_linker.js @@ -0,0 +1,34 @@ +import { createLink } from './dependency_linker_util'; + +const openTag = '<span class="">'; +const closeTag = '</span>'; +const TAG_URL = 'https://sum.golang.org/lookup/'; +const GO_PACKAGE_URL = 'https://pkg.go.dev/'; + +const DEPENDENCY_REGEX = new RegExp( + /* + * Detects dependencies inside of content that is highlighted by Highlight.js + * Example: '<span class="">cloud.google.com/Go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=</span>' + * Group 1 (packagePath): 'cloud.google.com/Go/bigquery' + * Group 2 (version): 'v1.0.1/go.mod' + * Group 3 (base64url): 'i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=' + */ + `${openTag}(.*) (v.*) h1:(.*)${closeTag}`, + 'gm', +); + +const handleReplace = (packagePath, version, tag) => { + const lowercasePath = packagePath.toLowerCase(); + const packageHref = `${GO_PACKAGE_URL}${lowercasePath}`; + const packageLink = createLink(packageHref, packagePath); + const tagHref = `${TAG_URL}${lowercasePath}@${version.split('/go.mod')[0]}`; + const tagLink = createLink(tagHref, tag); + + return `${openTag}${packageLink} ${version} h1:${tagLink}${closeTag}`; +}; + +export default (result) => { + return result.value.replace(DEPENDENCY_REGEX, (_, packagePath, version, tag) => + handleReplace(packagePath, version, tag), + ); +}; diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue index f621a23734a..0cfee93ce5d 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue @@ -1,5 +1,5 @@ <script> -import { GlSafeHtmlDirective, GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon } from '@gitlab/ui'; import LineHighlighter from '~/blob/line_highlighter'; import eventHub from '~/notes/event_hub'; import languageLoader from '~/content_editor/services/highlight_js_language_loader'; @@ -28,9 +28,6 @@ export default { GlLoadingIcon, Chunk, }, - directives: { - SafeHtml: GlSafeHtmlDirective, - }, mixins: [Tracking.mixin()], props: { blob: { 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 80c1fcbacfa..d06bc7b8f98 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 @@ -4,11 +4,11 @@ import { GlLink, GlSkeletonLoader, GlIcon, - GlSafeHtmlDirective, GlSprintf, GlButton, GlAvatarLabeled, } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import { glEmojiTag } from '~/emoji'; import { createAlert } from '~/flash'; import { followUser, unfollowUser } from '~/rest_api'; @@ -44,7 +44,7 @@ export default { GlAvatarLabeled, }, directives: { - SafeHtml: GlSafeHtmlDirective, + SafeHtml, }, mixins: [Tracking.mixin()], props: { 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 6d179b3dc92..383dc27ea5e 100644 --- a/app/assets/javascripts/vue_shared/components/web_ide_link.vue +++ b/app/assets/javascripts/vue_shared/components/web_ide_link.vue @@ -1,14 +1,16 @@ <script> -import { GlModal, GlSprintf, GlLink } from '@gitlab/ui'; +import { GlModal, GlSprintf, GlLink, GlPopover } from '@gitlab/ui'; import { s__, __ } from '~/locale'; +import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue'; import ActionsButton from '~/vue_shared/components/actions_button.vue'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import ConfirmForkModal from '~/vue_shared/components/confirm_fork_modal.vue'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -const KEY_EDIT = 'edit'; -const KEY_WEB_IDE = 'webide'; -const KEY_GITPOD = 'gitpod'; -const KEY_PIPELINE_EDITOR = 'pipeline_editor'; +export const KEY_EDIT = 'edit'; +export const KEY_WEB_IDE = 'webide'; +export const KEY_GITPOD = 'gitpod'; +export const KEY_PIPELINE_EDITOR = 'pipeline_editor'; export const i18n = { modal: { @@ -25,6 +27,9 @@ export const i18n = { ), }; +export const PREFERRED_EDITOR_KEY = 'gl-web-ide-button-selected'; +export const PREFERRED_EDITOR_RESET_KEY = 'gl-web-ide-button-selected-reset'; + export default { components: { ActionsButton, @@ -32,9 +37,12 @@ export default { GlModal, GlSprintf, GlLink, + GlPopover, ConfirmForkModal, + UserCalloutDismisser, }, i18n, + mixins: [glFeatureFlagsMixin()], props: { isFork: { type: Boolean, @@ -131,6 +139,11 @@ export default { required: false, default: '', }, + webIdePromoPopoverImg: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -296,6 +309,12 @@ export default { }, }; }, + displayVscodeWebIdeCallout() { + return this.glFeatures.vscodeWebIde && !this.showEditButton; + }, + }, + mounted() { + this.resetPreferredEditor(); }, methods: { select(key) { @@ -304,41 +323,109 @@ export default { showModal(dataKey) { this[dataKey] = true; }, + resetPreferredEditor() { + if (!this.glFeatures.vscodeWebIde || this.showEditButton) { + return; + } + + if (localStorage.getItem(PREFERRED_EDITOR_RESET_KEY) === 'true') { + return; + } + + localStorage.setItem(PREFERRED_EDITOR_KEY, KEY_WEB_IDE); + localStorage.setItem(PREFERRED_EDITOR_RESET_KEY, true); + + this.select(KEY_WEB_IDE); + }, + dismissCalloutOnActionClicked(dismiss) { + if (this.displayVscodeWebIdeCallout) { + dismiss(); + } + }, }, + webIdeButtonId: 'web-ide-link', + PREFERRED_EDITOR_KEY, }; </script> <template> - <div class="gl-sm-ml-3"> - <actions-button - :actions="actions" - :selected-key="selection" - :variant="isBlob ? 'confirm' : 'default'" - :category="isBlob ? 'primary' : 'secondary'" - @select="select" - /> - <local-storage-sync - storage-key="gl-web-ide-button-selected" - :value="selection" - as-string - @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> - <confirm-fork-modal - v-if="showWebIdeButton || showEditButton" - v-model="showForkModal" - :modal-id="forkModalId" - :fork-path="forkPath" - /> - </div> + <user-callout-dismisser + :skip-query="!displayVscodeWebIdeCallout" + feature-name="vscode_web_ide_callout" + > + <template #default="{ dismiss, shouldShowCallout }"> + <div class="gl-sm-ml-3"> + <actions-button + :id="$options.webIdeButtonId" + :actions="actions" + :selected-key="selection" + :variant="isBlob ? 'confirm' : 'default'" + :category="isBlob ? 'primary' : 'secondary'" + :show-action-tooltip="!displayVscodeWebIdeCallout || !shouldShowCallout" + @select="select" + @actionClicked="dismissCalloutOnActionClicked(dismiss)" + /> + <local-storage-sync + :storage-key="$options.PREFERRED_EDITOR_KEY" + :value="selection" + as-string + @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> + <confirm-fork-modal + v-if="showWebIdeButton || showEditButton" + v-model="showForkModal" + :modal-id="forkModalId" + :fork-path="forkPath" + /> + <gl-popover + v-if="displayVscodeWebIdeCallout" + :target="$options.webIdeButtonId" + :show="shouldShowCallout" + :css-classes="['web-ide-promo-popover']" + :boundary-padding="80" + show-close-button + triggers="manual" + @close-button-clicked="dismiss" + > + <img + :src="webIdePromoPopoverImg" + class="web-ide-promo-popover-illustration" + width="280" + height="140" + /> + <div class="gl-mx-2"> + <h5 class="gl-mt-3 gl-mb-3">{{ __('The new Web IDE') }}</h5> + <p> + {{ + __( + 'VS Code in your browser. View code and make changes from the same UI as in your local IDE.', + ) + }} + </p> + <gl-link + class="gl-button btn btn-confirm block gl-mb-4 gl-mt-5" + variant="confirm" + category="primary" + target="_blank" + :href="webIdeUrl" + block + > + {{ __('Try it out now') }} + </gl-link> + </div> + </gl-popover> + </div> + </template> + </user-callout-dismisser> </template> diff --git a/app/assets/javascripts/vue_shared/constants.js b/app/assets/javascripts/vue_shared/constants.js index a851f84ed2f..2f85a29fb84 100644 --- a/app/assets/javascripts/vue_shared/constants.js +++ b/app/assets/javascripts/vue_shared/constants.js @@ -13,7 +13,9 @@ export const SHORT_DATE_FORMAT = 'd mmm, yyyy'; export const ISO_SHORT_FORMAT = 'yyyy-mm-dd'; -export const DATE_FORMATS = [SHORT_DATE_FORMAT, ISO_SHORT_FORMAT]; +export const LONG_DATE_FORMAT_WITH_TZ = 'yyyy-mm-dd HH:MM:ss Z'; + +export const DATE_FORMATS = [SHORT_DATE_FORMAT, ISO_SHORT_FORMAT, LONG_DATE_FORMAT_WITH_TZ]; const getTimeLabel = (days) => n__('1 day', '%d days', days); diff --git a/app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue b/app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue index 25799171905..2644befc902 100644 --- a/app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue +++ b/app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue @@ -1,8 +1,8 @@ <script> import { GlForm, GlFormInput, GlFormGroup } from '@gitlab/ui'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; -import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_vue/constants'; -import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue'; +import { DropdownVariant } from '~/sidebar/components/labels/labels_select_vue/constants'; +import LabelsSelect from '~/sidebar/components/labels/labels_select_vue/labels_select_root.vue'; export default { LabelSelectVariant: DropdownVariant, diff --git a/app/assets/javascripts/vue_shared/issuable/create/components/issuable_label_selector.vue b/app/assets/javascripts/vue_shared/issuable/create/components/issuable_label_selector.vue new file mode 100644 index 00000000000..b3f9c8d9fcd --- /dev/null +++ b/app/assets/javascripts/vue_shared/issuable/create/components/issuable_label_selector.vue @@ -0,0 +1,92 @@ +<script> +import { GlFormGroup, GlIcon } from '@gitlab/ui'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import LabelsSelect from '~/sidebar/components/labels/labels_select_widget/labels_select_root.vue'; +import { __ } from '~/locale'; + +export default { + components: { + GlFormGroup, + GlIcon, + LabelsSelect, + }, + inject: [ + 'allowLabelRemove', + 'attrWorkspacePath', + 'fieldName', + 'fullPath', + 'labelsFilterBasePath', + 'initialLabels', + 'issuableType', + 'labelType', + 'variant', + 'workspaceType', + ], + data() { + return { + selectedLabels: this.initialLabels || [], + }; + }, + methods: { + handleUpdateSelectedLabels({ labels }) { + this.selectedLabels = labels.map((label) => ({ ...label, id: getIdFromGraphQLId(label.id) })); + }, + handleLabelRemove(labelId) { + this.selectedLabels = this.selectedLabels.filter((label) => label.id !== labelId); + }, + }, + i18n: { + fieldLabel: __('Labels'), + dropdownButtonText: __('Select label'), + listTitle: __('Select label'), + createTitle: __('Create project label'), + manageTitle: __('Manage project labels'), + emptySelection: __('None'), + }, +}; +</script> + +<template> + <gl-form-group class="row" label-class="gl-display-none"> + <label class="col-12 gl-display-flex gl-align-center gl-mb-1"> + {{ $options.i18n.fieldLabel }} + <div class="gl-ml-3"> + <gl-icon name="labels" /> + <span class="gl-font-base gl-line-height-24">{{ selectedLabels.length }}</span> + </div> + </label> + <div class="col-12"> + <div class="issuable-form-select-holder"> + <input + v-for="selectedLabel in selectedLabels" + :key="selectedLabel.id" + :value="selectedLabel.id" + :name="fieldName" + type="hidden" + /> + <labels-select + class="block labels" + :allow-label-remove="allowLabelRemove" + :allow-multiselect="true" + :show-embedded-labels-list="true" + :full-path="fullPath" + :attr-workspace-path="attrWorkspacePath" + :labels-filter-base-path="labelsFilterBasePath" + :dropdown-button-text="$options.i18n.dropdownButtonText" + :labels-list-title="$options.i18n.listTitle" + :footer-create-label-title="$options.i18n.createTitle" + :footer-manage-label-title="$options.i18n.manageTitle" + :variant="variant" + :workspace-type="workspaceType" + :issuable-type="issuableType" + :label-create-type="labelType" + :selected-labels="selectedLabels" + @updateSelectedLabels="handleUpdateSelectedLabels" + @onLabelRemove="handleLabelRemove" + > + {{ $options.i18n.emptySelection }} + </labels-select> + </div> + </div> + </gl-form-group> +</template> diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue index 30b7b073ac3..5b303b9a314 100644 --- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue +++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue @@ -318,8 +318,8 @@ export default { <slot name="statistics"></slot> <li v-if="showDiscussions" - data-testid="issuable-discussions" - class="issuable-comments gl-display-none gl-sm-display-block" + class="gl-display-none gl-sm-display-block" + data-testid="issuable-comments" > <gl-link v-gl-tooltip.top diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue index dd3d7c8f4d6..5b6c5bf6e03 100644 --- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue +++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue @@ -331,6 +331,7 @@ export default { <slot name="sidebar-items" :checked-issuables="bulkEditIssuables"></slot> </template> </issuable-bulk-edit-sidebar> + <slot name="list-body"></slot> <ul v-if="issuablesLoading" class="content-list"> <li v-for="n in skeletonItemCount" :key="n" class="issue gl-px-5! gl-py-5!"> <gl-skeleton-loader /> diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_description.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_description.vue index d4e9120ff17..ce1851ab873 100644 --- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_description.vue +++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_description.vue @@ -1,7 +1,6 @@ <script> -import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; -import $ from 'jquery'; -import '~/behaviors/markdown/render_gfm'; +import SafeHtml from '~/vue_shared/directives/safe_html'; +import { renderGFM } from '~/behaviors/markdown/render_gfm'; export default { directives: { @@ -26,12 +25,7 @@ export default { }, }, mounted() { - this.renderGFM(); - }, - methods: { - renderGFM() { - $(this.$refs.gfmContainer).renderGFM(); - }, + renderGFM(this.$refs.gfmContainer); }, }; </script> diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue index 35124bd15d2..fd94245b7c9 100644 --- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue +++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue @@ -1,12 +1,6 @@ <script> -import { - GlIcon, - GlBadge, - GlButton, - GlIntersectionObserver, - GlTooltipDirective, - GlSafeHtmlDirective as SafeHtml, -} from '@gitlab/ui'; +import { GlIcon, GlBadge, GlButton, GlIntersectionObserver, GlTooltipDirective } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import { __ } from '~/locale'; import { IssuableStates } from '~/vue_shared/issuable/list/constants'; diff --git a/app/assets/javascripts/vue_shared/new_namespace/components/legacy_container.vue b/app/assets/javascripts/vue_shared/new_namespace/components/legacy_container.vue index e42720bf1db..ae40076ca96 100644 --- a/app/assets/javascripts/vue_shared/new_namespace/components/legacy_container.vue +++ b/app/assets/javascripts/vue_shared/new_namespace/components/legacy_container.vue @@ -1,4 +1,6 @@ <script> +import projectNew from '~/projects/project_new'; + export default { inheritAttrs: false, props: { @@ -16,6 +18,7 @@ export default { this.source = legacyEntry.parentNode; this.$el.appendChild(legacyEntry); legacyEntry.classList.add('active'); + projectNew.bindEvents(); } }, diff --git a/app/assets/javascripts/vue_shared/new_namespace/components/welcome.vue b/app/assets/javascripts/vue_shared/new_namespace/components/welcome.vue index 5cd2018bb8c..b6a459f21e0 100644 --- a/app/assets/javascripts/vue_shared/new_namespace/components/welcome.vue +++ b/app/assets/javascripts/vue_shared/new_namespace/components/welcome.vue @@ -1,5 +1,5 @@ <script> -import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import Tracking from '~/tracking'; export default { diff --git a/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue b/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue index 624ae7027d5..318adec2319 100644 --- a/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue +++ b/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue @@ -1,5 +1,6 @@ <script> -import { GlBreadcrumb, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; +import { GlBreadcrumb, GlIcon } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import NewTopLevelGroupAlert from '~/groups/components/new_top_level_group_alert.vue'; import LegacyContainer from './components/legacy_container.vue'; diff --git a/app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue b/app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue index 0e1975e1c09..b739baad5d7 100644 --- a/app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue +++ b/app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue @@ -2,8 +2,8 @@ import { mapActions, mapGetters } from 'vuex'; import { createAlert } from '~/flash'; import { s__ } from '~/locale'; -import ReportSection from '~/reports/components/report_section.vue'; -import { ERROR, SLOT_SUCCESS, SLOT_LOADING, SLOT_ERROR } from '~/reports/constants'; +import ReportSection from '~/ci/reports/components/report_section.vue'; +import { ERROR, SLOT_SUCCESS, SLOT_LOADING, SLOT_ERROR } from '~/ci/reports/constants'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import HelpIcon from './components/help_icon.vue'; import SecurityReportDownloadDropdown from './components/security_report_download_dropdown.vue'; diff --git a/app/assets/javascripts/vue_shared/security_reports/store/getters.js b/app/assets/javascripts/vue_shared/security_reports/store/getters.js index 08f6bcca15b..c274f531139 100644 --- a/app/assets/javascripts/vue_shared/security_reports/store/getters.js +++ b/app/assets/javascripts/vue_shared/security_reports/store/getters.js @@ -1,5 +1,5 @@ import { s__, sprintf } from '~/locale'; -import { LOADING, ERROR, SUCCESS } from '~/reports/constants'; +import { LOADING, ERROR, SUCCESS } from '~/ci/reports/constants'; import { TRANSLATION_IS_LOADING } from './messages'; import { countVulnerabilities, groupedTextBuilder } from './utils'; diff --git a/app/assets/javascripts/vue_shared/security_reports/store/utils.js b/app/assets/javascripts/vue_shared/security_reports/store/utils.js index a6628fa0f9f..f3cb5fc16f0 100644 --- a/app/assets/javascripts/vue_shared/security_reports/store/utils.js +++ b/app/assets/javascripts/vue_shared/security_reports/store/utils.js @@ -29,7 +29,13 @@ export const fetchDiffData = (state, endpoint, category) => { */ export const enrichVulnerabilityWithFeedback = (vulnerability, feedback = []) => feedback - .filter((fb) => fb.project_fingerprint === vulnerability.project_fingerprint) + .filter((fb) => + // Some records still have a `finding_uuid` with null, we need to fallback to using `project_fingerprint` in those cases. Once all entries have been fixed, we can remove the fallback. + // related epic: https://gitlab.com/groups/gitlab-org/-/epics/2791 + fb.finding_uuid !== null + ? fb.finding_uuid === vulnerability.finding_uuid + : fb.project_fingerprint === vulnerability.project_fingerprint, + ) .reduce((vuln, fb) => { if (fb.feedback_type === FEEDBACK_TYPE_DISMISSAL) { return { |