Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/vue_shared')
-rw-r--r--app/assets/javascripts/vue_shared/alert_details/components/alert_status.vue53
-rw-r--r--app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_status.vue5
-rw-r--r--app/assets/javascripts/vue_shared/components/badges/beta_badge.vue35
-rw-r--r--app/assets/javascripts/vue_shared/components/badges/experiment_badge.stories.js24
-rw-r--r--app/assets/javascripts/vue_shared/components/badges/experiment_badge.vue43
-rw-r--r--app/assets/javascripts/vue_shared/components/badges/hover_badge.vue52
-rw-r--r--app/assets/javascripts/vue_shared/components/blob_viewers/rich_viewer.vue12
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_badge_link.vue21
-rw-r--r--app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown_item.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/user_token.vue18
-rw-r--r--app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/incubation/pagination.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue99
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue13
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/mount_markdown_editor.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar.vue22
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/utils.js7
-rw-r--r--app/assets/javascripts/vue_shared/components/mr_more_dropdown.vue16
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_cli_instructions.vue35
-rw-r--r--app/assets/javascripts/vue_shared/components/segmented_control_button_group.vue16
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/components/blame_info.vue51
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/utils.js37
-rw-r--r--app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue20
-rw-r--r--app/assets/javascripts/vue_shared/components/toggle_labels.vue62
-rw-r--r--app/assets/javascripts/vue_shared/components/vuex_module_provider.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/web_ide_link.vue2
-rw-r--r--app/assets/javascripts/vue_shared/constants.js3
-rw-r--r--app/assets/javascripts/vue_shared/global_search/constants.js1
-rw-r--r--app/assets/javascripts/vue_shared/issuable/create/components/issuable_create_root.vue5
-rw-r--r--app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue32
-rw-r--r--app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue43
-rw-r--r--app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue32
-rw-r--r--app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue5
43 files changed, 517 insertions, 304 deletions
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 8d2ef20b381..3855e4fc078 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
@@ -1,5 +1,5 @@
<script>
-import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { GlCollapsibleListbox } from '@gitlab/ui';
import updateAlertStatusMutation from '~/graphql_shared/mutations/alert_status_update.mutation.graphql';
import { s__ } from '~/locale';
import Tracking from '~/tracking';
@@ -11,10 +11,10 @@ export default {
'AlertManagement|There was an error while updating the status of the alert.',
),
UPDATE_ALERT_STATUS_INSTRUCTION: s__('AlertManagement|Please try again.'),
+ ASSIGN_STATUS_HEADER: s__('AlertManagement|Assign status'),
},
components: {
- GlDropdown,
- GlDropdownItem,
+ GlCollapsibleListbox,
},
inject: {
trackAlertStatusUpdateOptions: {
@@ -44,10 +44,20 @@ export default {
default: () => PAGE_CONFIG.OPERATIONS.STATUSES,
},
},
+ data() {
+ return {
+ alertStatus: this.alert.status,
+ };
+ },
computed: {
dropdownClass() {
- // eslint-disable-next-line no-nested-ternary
- return this.isSidebar ? (this.isDropdownShowing ? 'show' : 'gl-display-none') : '';
+ return this.isSidebar && !this.isDropdownShowing ? 'gl-display-none' : '';
+ },
+ items() {
+ return Object.entries(this.statuses).map(([value, text]) => ({ value, text }));
+ },
+ headerText() {
+ return this.isSidebar ? this.$options.i18n.ASSIGN_STATUS_HEADER : '';
},
},
methods: {
@@ -97,30 +107,15 @@ export default {
<template>
<div class="dropdown dropdown-menu-selectable" :class="dropdownClass">
- <gl-dropdown
+ <gl-collapsible-listbox
ref="dropdown"
- right
- :text="statuses[alert.status]"
- class="w-100"
- toggle-class="dropdown-menu-toggle"
- @keydown.esc.native="$emit('hide-dropdown')"
- @hide="$emit('hide-dropdown')"
- >
- <p v-if="isSidebar" class="gl-dropdown-header-top" data-testid="dropdown-header">
- {{ s__('AlertManagement|Assign status') }}
- </p>
- <div class="dropdown-content dropdown-body">
- <gl-dropdown-item
- v-for="(label, field) in statuses"
- :key="field"
- data-testid="statusDropdownItem"
- :active="field === alert.status"
- :active-class="'is-active'"
- @click="updateAlertStatus(field)"
- >
- {{ label }}
- </gl-dropdown-item>
- </div>
- </gl-dropdown>
+ v-model="alertStatus"
+ placement="right"
+ :header-text="headerText"
+ :items="items"
+ block
+ @hidden="$emit('hide-dropdown')"
+ @select="updateAlertStatus"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_status.vue b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_status.vue
index c512585b980..7b099516c5b 100644
--- a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_status.vue
+++ b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_status.vue
@@ -58,9 +58,10 @@ export default {
},
toggleFormDropdown() {
this.isDropdownShowing = !this.isDropdownShowing;
- const { dropdown } = this.$refs.status.$refs.dropdown.$refs;
+ const { dropdown } = this.$refs.status.$refs;
+
if (dropdown && this.isDropdownShowing) {
- dropdown.show();
+ dropdown.open();
}
},
handleUpdating(isMutationInProgress) {
diff --git a/app/assets/javascripts/vue_shared/components/badges/beta_badge.vue b/app/assets/javascripts/vue_shared/components/badges/beta_badge.vue
index e8d33b5538e..9cac176a06f 100644
--- a/app/assets/javascripts/vue_shared/components/badges/beta_badge.vue
+++ b/app/assets/javascripts/vue_shared/components/badges/beta_badge.vue
@@ -1,10 +1,10 @@
<script>
-import { GlBadge, GlPopover } from '@gitlab/ui';
import { s__ } from '~/locale';
+import HoverBadge from './hover_badge.vue';
export default {
name: 'BetaBadge',
- components: { GlBadge, GlPopover },
+ components: { HoverBadge },
i18n: {
badgeLabel: s__('BetaBadge|Beta'),
popoverTitle: s__("BetaBadge|What's Beta?"),
@@ -41,27 +41,16 @@ export default {
</script>
<template>
- <div>
- <gl-badge ref="badge" href="#" :size="size" variant="neutral" class="gl-cursor-pointer">{{
- $options.i18n.badgeLabel
- }}</gl-badge>
- <gl-popover
- triggers="hover focus click"
- :show-close-button="true"
- :target="target"
- :title="$options.i18n.popoverTitle"
- data-testid="beta-badge"
- >
- <p>{{ $options.i18n.descriptionParagraph }}</p>
+ <hover-badge :label="$options.i18n.badgeLabel" :size="size" :title="$options.i18n.popoverTitle">
+ <p>{{ $options.i18n.descriptionParagraph }}</p>
- <p class="gl-mb-0">{{ $options.i18n.listIntroduction }}</p>
+ <p class="gl-mb-0">{{ $options.i18n.listIntroduction }}</p>
- <ul class="gl-pl-4">
- <li>{{ $options.i18n.listItemStability }}</li>
- <li>{{ $options.i18n.listItemDataLoss }}</li>
- <li>{{ $options.i18n.listItemReasonableEffort }}</li>
- <li>{{ $options.i18n.listItemNearCompletion }}</li>
- </ul>
- </gl-popover>
- </div>
+ <ul class="gl-pl-4">
+ <li>{{ $options.i18n.listItemStability }}</li>
+ <li>{{ $options.i18n.listItemDataLoss }}</li>
+ <li>{{ $options.i18n.listItemReasonableEffort }}</li>
+ <li>{{ $options.i18n.listItemNearCompletion }}</li>
+ </ul>
+ </hover-badge>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/badges/experiment_badge.stories.js b/app/assets/javascripts/vue_shared/components/badges/experiment_badge.stories.js
new file mode 100644
index 00000000000..8e964c9bdf8
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/badges/experiment_badge.stories.js
@@ -0,0 +1,24 @@
+import ExperimentBadge from './experiment_badge.vue';
+
+export default {
+ component: ExperimentBadge,
+ title: 'vue_shared/experiment-badge',
+};
+
+const template = `
+ <div style="height:600px;" class="gl-display-flex gl-justify-content-center gl-align-items-center">
+ <experiment-badge :size="size" />
+ </div>
+ `;
+
+const Template = (args, { argTypes }) => ({
+ components: { ExperimentBadge },
+ data() {
+ return { value: args.value };
+ },
+ props: Object.keys(argTypes),
+ template,
+});
+
+export const Default = Template.bind({});
+Default.args = {};
diff --git a/app/assets/javascripts/vue_shared/components/badges/experiment_badge.vue b/app/assets/javascripts/vue_shared/components/badges/experiment_badge.vue
new file mode 100644
index 00000000000..26bae71ddb8
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/badges/experiment_badge.vue
@@ -0,0 +1,43 @@
+<script>
+import { s__ } from '~/locale';
+import HoverBadge from './hover_badge.vue';
+
+export default {
+ name: 'ExperimentBadge',
+ components: { HoverBadge },
+ i18n: {
+ badgeLabel: s__('ExperimentBadge|Experiment'),
+ popoverTitle: s__("ExperimentBadge|What's an Experiment?"),
+ descriptionParagraph: s__(
+ "ExperimentBadge|An Experiment is a feature that's in the process of being developed. It's not production-ready. We encourage users to try Experimental features and provide feedback.",
+ ),
+ listIntroduction: s__('ExperimentBadge|An Experiment:'),
+ listItemStability: s__('ExperimentBadge|May be unstable.'),
+ listItemDataLoss: s__('ExperimentBadge|Can cause data loss.'),
+ listItemNoSupport: s__('ExperimentBadge|Has no support and might not be documented.'),
+ listItemCanBeRemoved: s__('ExperimentBadge|Can be removed at any time.'),
+ },
+ props: {
+ size: {
+ type: String,
+ required: false,
+ default: 'md',
+ },
+ },
+};
+</script>
+
+<template>
+ <hover-badge :label="$options.i18n.badgeLabel" :size="size" :title="$options.i18n.popoverTitle">
+ <p>{{ $options.i18n.descriptionParagraph }}</p>
+
+ <p class="gl-mb-0">{{ $options.i18n.listIntroduction }}</p>
+
+ <ul class="gl-pl-4">
+ <li>{{ $options.i18n.listItemStability }}</li>
+ <li>{{ $options.i18n.listItemDataLoss }}</li>
+ <li>{{ $options.i18n.listItemNoSupport }}</li>
+ <li>{{ $options.i18n.listItemCanBeRemoved }}</li>
+ </ul>
+ </hover-badge>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/badges/hover_badge.vue b/app/assets/javascripts/vue_shared/components/badges/hover_badge.vue
new file mode 100644
index 00000000000..351c7bd9da0
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/badges/hover_badge.vue
@@ -0,0 +1,52 @@
+<script>
+import { GlBadge, GlPopover } from '@gitlab/ui';
+
+export default {
+ name: 'HoverBadge',
+ components: { GlBadge, GlPopover },
+ props: {
+ label: {
+ type: String,
+ required: true,
+ },
+ title: {
+ type: String,
+ required: true,
+ },
+ size: {
+ type: String,
+ required: false,
+ default: 'md',
+ },
+ },
+ methods: {
+ target() {
+ /**
+ * BVPopover retrieves the target during the `beforeDestroy` hook to deregister attached
+ * events. Since during `beforeDestroy` refs are `undefined`, it throws a warning in the
+ * console because we're trying to access the `$el` property of `undefined`. Optional
+ * chaining is not working in templates, which is why the method is used.
+ *
+ * See more on https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49628#note_464803276
+ */
+ return this.$refs.badge?.$el;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-badge ref="badge" href="#" :size="size" variant="neutral" class="gl-cursor-pointer">{{
+ label
+ }}</gl-badge>
+ <gl-popover
+ triggers="hover focus click"
+ :show-close-button="true"
+ :target="target"
+ :title="title"
+ >
+ <slot></slot>
+ </gl-popover>
+ </div>
+</template>
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 27bdcc69120..b52752d7e2f 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
@@ -41,7 +41,6 @@ export default {
mounted() {
this.renderRemainingMarkup();
handleBlobRichViewer(this.$refs.content, this.type);
- handleLocationHash();
},
methods: {
optimizeMarkupRendering() {
@@ -76,8 +75,7 @@ export default {
* */
if (!this.isMarkup || !this.remainingContent.length) {
- this.$emit(CONTENT_LOADED_EVENT);
- this.isLoading = false;
+ this.onContentLoaded();
return;
}
@@ -89,11 +87,15 @@ export default {
setTimeout(() => {
fileContent.append(...content);
if (nextChunkEnd < this.remainingContent.length) return;
- this.$emit(CONTENT_LOADED_EVENT);
- this.isLoading = false;
+ this.onContentLoaded();
}, i);
}
},
+ onContentLoaded() {
+ this.$emit(CONTENT_LOADED_EVENT);
+ handleLocationHash();
+ this.isLoading = false;
+ },
},
safeHtmlConfig: {
ADD_TAGS: ['gl-emoji', 'copy-code'],
diff --git a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
index 1f45b4c5c9d..abbeac0e098 100644
--- a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
@@ -57,16 +57,29 @@ export default {
return badgeSizeOptions[value] !== undefined;
},
},
+ showTooltip: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ useLink: {
+ type: Boolean,
+ default: true,
+ required: false,
+ },
},
computed: {
- isSmallBadgeSize() {
- return this.size === badgeSizeOptions.sm;
+ isNotLargeBadgeSize() {
+ return this.size !== badgeSizeOptions.lg;
},
title() {
- return !this.showText ? this.status?.text : '';
+ return this.showTooltip && !this.showText ? this.status?.text : '';
},
detailsPath() {
// For now, this can either come from graphQL with camelCase or REST API in snake_case
+ if (!this.useLink) {
+ return null;
+ }
return this.status.detailsPath || this.status.details_path;
},
badgeStyles() {
@@ -121,7 +134,7 @@ export default {
<template>
<gl-badge
v-gl-tooltip
- :class="{ 'gl-pl-2': isSmallBadgeSize }"
+ :class="{ 'gl-px-2': !showText && isNotLargeBadgeSize }"
:title="title"
:href="detailsPath"
:size="size"
diff --git a/app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown.vue b/app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown.vue
index fa7c5bc1978..066b761ac9b 100644
--- a/app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown.vue
@@ -47,13 +47,13 @@ export default {
v-if="sshLink"
:label="$options.labels.ssh"
:link="sshLink"
- qa-selector="copy_ssh_url_button"
+ test-id="copy-ssh-url-button"
/>
<clone-dropdown-item
v-if="httpLink"
:label="httpLabel"
:link="httpLink"
- qa-selector="copy_http_url_button"
+ test-id="copy-http-url-button"
/>
</gl-disclosure-dropdown>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown_item.vue b/app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown_item.vue
index 0e322ebc686..6980e19733a 100644
--- a/app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown_item.vue
+++ b/app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown_item.vue
@@ -27,7 +27,7 @@ export default {
type: String,
required: true,
},
- qaSelector: {
+ testId: {
type: String,
required: true,
},
@@ -45,7 +45,7 @@ export default {
:title="$options.copyURLTooltip"
:aria-label="$options.copyURLTooltip"
:data-clipboard-text="link"
- :data-qa-selector="qaSelector"
+ :data-testid="testId"
icon="copy-to-clipboard"
class="gl-display-inline-flex"
/>
diff --git a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue
index b34a6b11092..1f5896204ee 100644
--- a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue
+++ b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue
@@ -144,7 +144,7 @@ export default {
</slot>
</template>
<slot name="default">
- <gl-dropdown-form class="gl-relative gl-min-h-7" data-qa-selector="labels_dropdown_content">
+ <gl-dropdown-form class="gl-relative gl-min-h-7" data-testid="labels-dropdown-content">
<gl-loading-icon
v-if="isLoading"
size="lg"
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 5a7382bcd7c..23de8dd5596 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
@@ -262,6 +262,7 @@ export default {
{{ __('No matches found') }}
</gl-dropdown-text>
<gl-dropdown-text v-else-if="hasFetched">{{ __('No suggestions found') }}</gl-dropdown-text>
+ <slot name="footer"></slot>
</template>
</gl-filtered-search-token>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/user_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/user_token.vue
index c294c23abfc..4601287b417 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/user_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/user_token.vue
@@ -4,6 +4,8 @@ import { compact } from 'lodash';
import { createAlert } from '~/alert';
import { __ } from '~/locale';
+import { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
+import usersAutocompleteQuery from '~/graphql_shared/queries/users_autocomplete.query.graphql';
import { OPTIONS_NONE_ANY } from '../constants';
import BaseToken from './base_token.vue';
@@ -41,6 +43,12 @@ export default {
preloadedUsers() {
return this.config.preloadedUsers || [];
},
+ namespace() {
+ return this.config.isProject ? WORKSPACE_PROJECT : WORKSPACE_GROUP;
+ },
+ fetchUsersQuery() {
+ return this.config.fetchUsers ? this.config.fetchUsers : this.fetchUsersBySearchTerm;
+ },
},
methods: {
getActiveUser(users, data) {
@@ -49,11 +57,19 @@ export default {
getAvatarUrl(user) {
return user.avatarUrl || user.avatar_url;
},
+ fetchUsersBySearchTerm(search) {
+ return this.$apollo
+ .query({
+ query: usersAutocompleteQuery,
+ variables: { fullPath: this.config.fullPath, search, isProject: this.config.isProject },
+ })
+ .then(({ data }) => data[this.namespace]?.autocompleteUsers);
+ },
fetchUsers(searchTerm) {
this.loading = true;
const fetchPromise = this.config.fetchPath
? this.config.fetchUsers(this.config.fetchPath, searchTerm)
- : this.config.fetchUsers(searchTerm);
+ : this.fetchUsersQuery(searchTerm);
fetchPromise
.then((res) => {
diff --git a/app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue b/app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue
index ebc6b2cd740..d97f1ae6135 100644
--- a/app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue
+++ b/app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue
@@ -156,7 +156,7 @@ export default {
<gl-form-input
ref="input"
:readonly="readonly"
- :size="size"
+ :width="size"
class="gl-font-monospace! gl-cursor-default!"
v-bind="formInputGroupProps"
:value="value"
@@ -183,7 +183,7 @@ export default {
v-if="showCopyButton"
:text="value"
:title="copyButtonTitle"
- data-qa-selector="clipboard_button"
+ data-testid="clipboard-button"
@click="handleCopyButtonClick"
/>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/incubation/pagination.vue b/app/assets/javascripts/vue_shared/components/incubation/pagination.vue
index b5afe92316a..6b70e9f3ed9 100644
--- a/app/assets/javascripts/vue_shared/components/incubation/pagination.vue
+++ b/app/assets/javascripts/vue_shared/components/incubation/pagination.vue
@@ -57,6 +57,7 @@ export default {
:next-text="$options.i18n.nextPageButtonLabel"
:prev-button-link="previousPageLink"
:next-button-link="nextPageLink"
+ class="gl-mt-4"
/>
</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 05ce007e615..4ebd8861a67 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue
@@ -60,7 +60,7 @@ export default {
<template>
<gl-disclosure-dropdown
- data-qa-selector="apply_suggestion_dropdown"
+ data-testid="apply-suggestion-dropdown"
fluid-width
placement="right"
size="small"
@@ -81,7 +81,7 @@ export default {
class="apply-suggestions-input-min-width"
:placeholder="defaultCommitMessage"
submit-on-enter
- data-qa-selector="commit_message_field"
+ data-testid="commit-message-field"
@submit="onApply"
/>
@@ -93,7 +93,7 @@ export default {
class="gl-w-auto! gl-mt-3 gl-align-self-end"
category="primary"
variant="confirm"
- data-qa-selector="commit_with_custom_message_button"
+ data-testid="commit-with-custom-message-button"
@click="onApply"
>
{{ __('Apply') }}
diff --git a/app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue b/app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue
index f7f5ccdbf31..d99b90fa561 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue
@@ -64,8 +64,8 @@ export default {
const savedReply = this.savedReplies.find((r) => r.id === id);
if (savedReply) {
this.$emit('select', savedReply.content);
- this.track_event(TRACKING_SAVED_REPLIES_USE);
- this.track_event(
+ this.trackEvent(TRACKING_SAVED_REPLIES_USE);
+ this.trackEvent(
isInMr ? TRACKING_SAVED_REPLIES_USE_IN_MR : TRACKING_SAVED_REPLIES_USE_IN_OTHER,
);
}
diff --git a/app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue b/app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue
index 2426a917a53..1327436a9b4 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue
@@ -1,16 +1,10 @@
<script>
-import { GlButton, GlPopover, GlLink } from '@gitlab/ui';
-import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
+import { GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
-import RICH_TEXT_EDITOR_ILLUSTRATION from '../../../../images/callouts/rich_text_editor_illustration.svg?url';
-import { counter } from './utils';
export default {
components: {
GlButton,
- GlLink,
- GlPopover,
- UserCalloutDismisser,
},
props: {
value: {
@@ -18,15 +12,7 @@ export default {
required: true,
},
},
- data() {
- return {
- counter: counter(),
- };
- },
computed: {
- showPromoPopover() {
- return this.markdownEditorSelected && this.counter === 0;
- },
markdownEditorSelected() {
return this.value === 'markdown';
},
@@ -36,84 +22,19 @@ export default {
: __('Switch to plain text editing');
},
},
- methods: {
- switchEditorType(insertTemplate = false) {
- this.$emit('switch', insertTemplate);
- },
- },
richTextEditorButtonId: 'switch-to-rich-text-editor',
- RICH_TEXT_EDITOR_ILLUSTRATION,
};
</script>
<template>
<div class="content-editor-switcher gl-display-inline-flex gl-align-items-center">
- <user-callout-dismisser feature-name="rich_text_editor">
- <template #default="{ dismiss, shouldShowCallout }">
- <div>
- <gl-popover
- :target="$options.richTextEditorButtonId"
- :show="Boolean(showPromoPopover && shouldShowCallout)"
- show-close-button
- :css-classes="['rich-text-promo-popover gl-p-2']"
- triggers="manual"
- data-testid="rich-text-promo-popover"
- @close-button-clicked="dismiss"
- >
- <img
- :src="$options.RICH_TEXT_EDITOR_ILLUSTRATION"
- :alt="''"
- class="rich-text-promo-popover-illustration"
- width="280"
- height="130"
- />
- <h5 class="gl-mt-3 gl-mb-3">{{ __('Writing just got easier') }}</h5>
- <p class="gl-m-0">
- {{
- __(
- 'Use the new rich text editor to see your text and tables fully formatted as you type. No need to remember any formatting syntax, or switch between preview and editing modes!',
- )
- }}
- </p>
- <gl-link
- class="gl-button btn btn-confirm block gl-mb-2 gl-mt-4"
- variant="confirm"
- category="primary"
- target="_blank"
- block
- @click="
- switchEditorType(showPromoPopover);
- dismiss();
- "
- >
- {{ __('Try the rich text editor now') }}
- </gl-link>
- </gl-popover>
- <gl-button
- :id="$options.richTextEditorButtonId"
- class="btn btn-default btn-sm gl-button btn-default-tertiary gl-font-sm! gl-text-secondary! gl-px-4!"
- data-qa-selector="editing_mode_switcher"
- @click="
- switchEditorType();
- dismiss();
- "
- >{{ text }}</gl-button
- >
- </div>
- </template>
- </user-callout-dismisser>
+ <gl-button
+ :id="$options.richTextEditorButtonId"
+ size="small"
+ category="tertiary"
+ class="gl-font-sm! gl-text-secondary! gl-px-4!"
+ data-testid="editing-mode-switcher"
+ @click="$emit('switch')"
+ >{{ text }}</gl-button
+ >
</div>
</template>
-<style>
-.rich-text-promo-popover {
- box-shadow: 0 0 18px -1.9px rgba(119, 89, 194, 0.16), 0 0 12.9px -1.7px rgba(119, 89, 194, 0.16),
- 0 0 9.2px -1.4px rgba(119, 89, 194, 0.16), 0 0 6.4px -1.1px rgba(119, 89, 194, 0.16),
- 0 0 4.5px -0.8px rgba(119, 89, 194, 0.16), 0 0 3px -0.6px rgba(119, 89, 194, 0.16),
- 0 0 1.8px -0.3px rgba(119, 89, 194, 0.16), 0 0 0.6px rgba(119, 89, 194, 0.16);
- z-index: 999;
-}
-
-.rich-text-promo-popover-illustration {
- width: calc(100% + 32px);
- margin: -32px -16px 0;
-}
-</style>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index a26f8f71601..24211833026 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -12,6 +12,8 @@ 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 { MARKDOWN_EDITOR_READY_EVENT } from '~/vue_shared/constants';
+import markdownEditorEventHub from '~/vue_shared/components/markdown/eventhub';
import MarkdownHeader from './header.vue';
import MarkdownToolbar from './toolbar.vue';
@@ -259,7 +261,8 @@ export default {
},
mounted() {
// GLForm class handles all the toolbar buttons
- return new GLForm(
+ // eslint-disable-next-line no-new
+ new GLForm(
$(this.$refs['gl-form']),
{
emojis: this.enableAutocomplete,
@@ -276,6 +279,8 @@ export default {
true,
this.autocompleteDataSources,
);
+
+ markdownEditorEventHub.$emit(MARKDOWN_EDITOR_READY_EVENT);
},
beforeDestroy() {
const glForm = $(this.$refs['gl-form']).data('glForm');
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index 286a1b87ad0..741bdfd211b 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -263,7 +263,7 @@ export default {
<gl-button
v-if="enablePreview"
data-testid="preview-toggle"
- value="preview"
+ :value="previewMarkdown ? 'preview' : 'edit'"
:label="$options.i18n.previewTabTitle"
class="js-md-preview-button gl-flex-direction-row-reverse gl-align-items-center gl-font-weight-normal! gl-mr-2"
size="small"
@@ -281,7 +281,7 @@ export default {
:tag-content="lineContent"
tracking-property="codeSuggestion"
icon="doc-code"
- data-qa-selector="suggestion_button"
+ data-testid="suggestion-button"
class="js-suggestion-btn"
@click="handleSuggestDismissed"
/>
@@ -305,7 +305,7 @@ export default {
variant="confirm"
category="primary"
size="small"
- data-qa-selector="dismiss_suggestion_popover_button"
+ data-testid="dismiss-suggestion-popover-button"
@click="handleSuggestDismissed"
>
{{ __('Got it') }}
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 fc7e0a7c732..4a3c3cf0053 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue
@@ -248,6 +248,13 @@ export default {
});
}
},
+ onKeydown(event) {
+ const isModifierKey = event.ctrlKey || event.metaKey;
+ if (isModifierKey && event.key === 'k') {
+ event.preventDefault();
+ }
+ this.$emit('keydown', event);
+ },
},
EDITING_MODE_KEY,
};
@@ -292,7 +299,7 @@ export default {
class="note-textarea js-gfm-input markdown-area"
dir="auto"
:data-supports-quick-actions="supportsQuickActions"
- :data-qa-selector="formFieldProps['data-qa-selector'] || 'markdown_editor_form_field'"
+ :data-testid="formFieldProps['data-testid'] || 'markdown-editor-form-field'"
:disabled="disabled"
@input="updateMarkdownFromMarkdownField"
@keydown="$emit('keydown', $event)"
@@ -317,13 +324,13 @@ export default {
:code-suggestions-config="codeSuggestionsConfig"
@initialized="setEditorAsAutofocused"
@change="updateMarkdownFromContentEditor"
- @keydown="$emit('keydown', $event)"
+ @keydown="onKeydown"
@enableMarkdownEditor="onEditingModeChange('markdownField')"
/>
<input
v-bind="formFieldProps"
:value="markdown"
- data-qa-selector="markdown_editor_form_field"
+ data-testid="markdown-editor-form-field"
type="hidden"
/>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/mount_markdown_editor.js b/app/assets/javascripts/vue_shared/components/markdown/mount_markdown_editor.js
index 6c2f084591e..f7fb1339bbc 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/mount_markdown_editor.js
+++ b/app/assets/javascripts/vue_shared/components/markdown/mount_markdown_editor.js
@@ -105,7 +105,6 @@ export function mountMarkdownEditor(options = {}) {
return h(MarkdownEditor, {
props: {
setFacade,
- enableContentEditor: Boolean(gon.features?.contentEditorOnIssues),
value: formFieldValue,
renderMarkdownPath,
markdownDocsPath,
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
index 8a0ca8ebac1..a822e2a6151 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
@@ -144,13 +144,13 @@ export default {
<gl-icon name="question-o" css-classes="link-highlight" />
</a>
</div>
- <gl-badge v-if="isApplied" variant="success" data-qa-selector="applied_badge">
+ <gl-badge v-if="isApplied" variant="success" data-testid="applied-badge">
{{ __('Applied') }}
</gl-badge>
<div
v-else-if="isApplying"
class="gl-display-flex gl-align-items-center text-secondary"
- data-qa-selector="applying_badge"
+ data-testid="applying-badge"
>
<gl-loading-icon size="sm" class="gl-align-items-center gl-justify-content-center gl-mr-3" />
<span>{{ applyingSuggestionsMessage }}</span>
@@ -169,7 +169,7 @@ export default {
<div v-else-if="!isDisableButton && suggestionsCount > 1">
<gl-button
class="btn-inverted js-add-to-batch-btn btn-grouped"
- data-qa-selector="add_suggestion_batch_button"
+ data-testid="add-suggestion-batch-button"
:disabled="isDisableButton"
size="small"
@click="addSuggestionToBatch"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
index a4516fae73d..c0c8c4735e7 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
@@ -2,8 +2,6 @@
<script>
import { GlButton, GlLoadingIcon, GlSprintf, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { updateText } from '~/lib/utils/text_markdown';
-import { __, sprintf } from '~/locale';
-import { PROMO_URL } from 'jh_else_ce/lib/utils/url_utility';
import EditorModeSwitcher from './editor_mode_switcher.vue';
export default {
@@ -56,23 +54,6 @@ export default {
});
}
},
- handleEditorModeChanged(isFirstSwitch) {
- if (isFirstSwitch) {
- this.insertIntoTextarea(
- __(`### Rich text editor`),
- '',
- sprintf(
- __(
- 'Try out **styling** _your_ content right here or read the [direction](%{directionUrl}).',
- ),
- {
- directionUrl: `${PROMO_URL}/direction/plan/knowledge/content_editor/`,
- },
- ),
- );
- }
- this.$emit('enableContentEditor');
- },
},
};
</script>
@@ -91,7 +72,7 @@ export default {
v-if="showEditorModeSwitcher"
size="small"
value="markdown"
- @switch="handleEditorModeChanged"
+ @switch="$emit('enableContentEditor')"
/>
<div class="gl-display-flex">
<div v-if="canAttachFile" class="uploading-container gl-font-sm gl-line-height-32 gl-mr-3">
@@ -152,6 +133,7 @@ export default {
category="tertiary"
size="small"
:title="__('Markdown is supported')"
+ :aria-label="__('Markdown is supported')"
class="gl-px-3!"
/>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/utils.js b/app/assets/javascripts/vue_shared/components/markdown/utils.js
deleted file mode 100644
index 0227d5a0fbc..00000000000
--- a/app/assets/javascripts/vue_shared/components/markdown/utils.js
+++ /dev/null
@@ -1,7 +0,0 @@
-let i = 0;
-
-export const counter = () => {
- const n = i;
- i += 1;
- return n;
-};
diff --git a/app/assets/javascripts/vue_shared/components/mr_more_dropdown.vue b/app/assets/javascripts/vue_shared/components/mr_more_dropdown.vue
index 7871721f38b..5c6766bbe45 100644
--- a/app/assets/javascripts/vue_shared/components/mr_more_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/mr_more_dropdown.vue
@@ -19,7 +19,6 @@ import MergeRequest from '~/merge_request';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue';
import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
-import NewHeaderActionsPopover from '~/issues/show/components/new_header_actions_popover.vue';
import { TYPE_MERGE_REQUEST } from '~/issues/constants';
Vue.use(VueApollo);
@@ -50,7 +49,6 @@ export default {
GlDisclosureDropdownGroup,
SidebarSubscriptionsWidget,
AbuseCategorySelector,
- NewHeaderActionsPopover,
SummaryNotesToggle: () =>
import('ee_component/merge_requests/components/summary_notes_toggle.vue'),
},
@@ -143,6 +141,9 @@ export default {
isMovedMrSidebar() {
return this.glFeatures.movedMrSidebar;
},
+ isNotificationsTodosButtons() {
+ return this.glFeatures.notificationsTodosButtons && this.glFeatures.movedMrSidebar;
+ },
draftLabel() {
return this.draft ? this.$options.i18n.markAsReady : this.$options.i18n.markAsDraft;
},
@@ -250,7 +251,9 @@ export default {
/>
</div>
</template>
- <gl-disclosure-dropdown-group v-if="isLoggedIn && isMovedMrSidebar">
+ <gl-disclosure-dropdown-group
+ v-if="isLoggedIn && isMovedMrSidebar && !isNotificationsTodosButtons"
+ >
<sidebar-subscriptions-widget
:iid="String(mr.iid)"
:full-path="fullPath"
@@ -261,7 +264,10 @@ export default {
<gl-disclosure-dropdown-group
bordered
- :class="{ 'gl-mt-0! gl-pt-0! gl-border-t-0!': !(isLoggedIn && isMovedMrSidebar) }"
+ :class="{
+ 'gl-mt-0! gl-pt-0! gl-border-t-0!':
+ !(isLoggedIn && isMovedMrSidebar) || isNotificationsTodosButtons,
+ }"
>
<gl-disclosure-dropdown-item
v-if="canUpdateMergeRequest"
@@ -358,8 +364,6 @@ export default {
</gl-disclosure-dropdown-group>
</gl-disclosure-dropdown>
- <new-header-actions-popover v-if="isMovedMrSidebar" :issue-type="issuableType" />
-
<abuse-category-selector
v-if="!isCurrentUser && isReportAbuseDrawerOpen"
:reported-user-id="reportedUserId"
diff --git a/app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue b/app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue
index fac32bfdb24..cb9b85b9ef3 100644
--- a/app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue
@@ -12,7 +12,7 @@ export default {
</script>
<template>
- <timeline-entry-item class="note note-wrapper" data-qa-selector="skeleton_note_placeholder">
+ <timeline-entry-item class="note note-wrapper">
<div
class="gl-float-left gl--flex-center gl-rounded-full gl-mt-n1 gl-ml-2 gl-w-6 gl-h-6 gl-bg-gray-50 gl-text-gray-600"
></div>
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_cli_instructions.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_cli_instructions.vue
index 36e608a068b..f59664e8d1d 100644
--- a/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_cli_instructions.vue
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_cli_instructions.vue
@@ -1,5 +1,5 @@
<script>
-import { GlButton, GlDropdown, GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
+import { GlButton, GlCollapsibleListbox, GlLoadingIcon } from '@gitlab/ui';
import { s__ } from '~/locale';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
import { REGISTRATION_TOKEN_PLACEHOLDER } from '../constants';
@@ -8,8 +8,7 @@ import getRunnerSetupInstructionsQuery from '../graphql/get_runner_setup.query.g
export default {
components: {
GlButton,
- GlDropdown,
- GlDropdownItem,
+ GlCollapsibleListbox,
GlLoadingIcon,
ModalCopyButton,
},
@@ -27,7 +26,7 @@ export default {
},
data() {
return {
- selectedArchitecture: this.platform?.architectures[0] || null,
+ selectedArchName: this.platform?.architectures[0]?.name || null,
instructions: null,
};
},
@@ -55,6 +54,9 @@ export default {
architectures() {
return this.platform?.architectures || [];
},
+ selectedArchitecture() {
+ return this.architectures.find(({ name }) => name === this.selectedArchName) || null;
+ },
binaryUrl() {
return this.selectedArchitecture?.downloadLocation;
},
@@ -69,20 +71,22 @@ export default {
}
return registerInstructions;
},
+ listboxItems() {
+ return this.architectures.map(({ name }) => {
+ return { text: name, value: name };
+ });
+ },
},
watch: {
platform() {
// reset selection if architecture is not in this list
- const arch = this.architectures.find(({ name }) => name === this.selectedArchitecture.name);
+ const arch = this.architectures.find(({ name }) => name === this.selectedArchName);
if (!arch) {
- this.selectArchitecture(this.architectures[0]);
+ this.selectedArchName = this.architectures[0]?.name || null;
}
},
},
methods: {
- selectArchitecture(architecture) {
- this.selectedArchitecture = architecture;
- },
onClose() {
this.$emit('close');
},
@@ -104,18 +108,7 @@ export default {
<gl-loading-icon v-if="$apollo.loading" size="sm" inline />
</h5>
- <gl-dropdown class="gl-mb-3" :text="selectedArchitecture.name">
- <gl-dropdown-item
- v-for="architecture in architectures"
- :key="architecture.name"
- is-check-item
- :is-checked="selectedArchitecture.name === architecture.name"
- data-testid="architecture-dropdown-item"
- @click="selectArchitecture(architecture)"
- >
- {{ architecture.name }}
- </gl-dropdown-item>
- </gl-dropdown>
+ <gl-collapsible-listbox v-model="selectedArchName" class="gl-mb-3" :items="listboxItems" />
<div class="gl-sm-display-flex gl-align-items-center gl-mb-3">
<h5>{{ $options.i18n.downloadInstallBinary }}</h5>
<gl-button
diff --git a/app/assets/javascripts/vue_shared/components/segmented_control_button_group.vue b/app/assets/javascripts/vue_shared/components/segmented_control_button_group.vue
index f50706b6de8..e0e8200580a 100644
--- a/app/assets/javascripts/vue_shared/components/segmented_control_button_group.vue
+++ b/app/assets/javascripts/vue_shared/components/segmented_control_button_group.vue
@@ -1,6 +1,21 @@
<script>
import { GlButtonGroup, GlButton } from '@gitlab/ui';
+const validateOptionsProp = (options) => {
+ const requiredOptionPropType = {
+ value: ['string', 'number', 'boolean'],
+ disabled: ['boolean', 'undefined'],
+ };
+ const optionProps = Object.keys(requiredOptionPropType);
+
+ return options.every((option) => {
+ if (!option) {
+ return false;
+ }
+ return optionProps.every((name) => requiredOptionPropType[name].includes(typeof option[name]));
+ });
+};
+
// TODO: We're planning to move this component to GitLab UI
// https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1787
export default {
@@ -12,6 +27,7 @@ export default {
options: {
type: Array,
required: true,
+ validator: validateOptionsProp,
},
value: {
type: [String, Number, Boolean],
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/components/blame_info.vue b/app/assets/javascripts/vue_shared/components/source_viewer/components/blame_info.vue
new file mode 100644
index 00000000000..9bce9402afa
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/components/blame_info.vue
@@ -0,0 +1,51 @@
+<script>
+import { GlTooltipDirective } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
+import CommitInfo from '~/repository/components/commit_info.vue';
+import { calculateBlameOffset, toggleBlameClasses } from '../utils';
+
+export default {
+ name: 'BlameInfo',
+ components: {
+ CommitInfo,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ SafeHtml,
+ },
+ props: {
+ blameData: {
+ type: Array,
+ required: true,
+ },
+ },
+ computed: {
+ blameInfo() {
+ return this.blameData.map((blame, index) => ({
+ ...blame,
+ blameOffset: calculateBlameOffset(blame.lineno, index),
+ }));
+ },
+ },
+ mounted() {
+ toggleBlameClasses(this.blameData, true);
+ },
+ destroyed() {
+ toggleBlameClasses(this.blameData, false);
+ },
+};
+</script>
+<template>
+ <div class="blame gl-bg-gray-10">
+ <div class="blame-commit gl-border-none!">
+ <commit-info
+ v-for="(blame, index) in blameInfo"
+ :key="index"
+ :class="{ 'gl-border-t': index !== 0 }"
+ class="gl-display-flex gl-absolute gl-px-3"
+ :style="{ top: blame.blameOffset }"
+ :commit="blame.commit"
+ />
+ </div>
+ </div>
+</template>
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 797a38d8171..4d5d877d43b 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
@@ -258,7 +258,7 @@ export default {
:class="$options.userColorScheme"
data-type="simple"
:data-path="blob.path"
- data-qa-selector="blob_viewer_file_content"
+ data-testid="blob-viewer-file-content"
>
<codeowners-validation
v-if="isCodeownersFile"
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/utils.js b/app/assets/javascripts/vue_shared/components/source_viewer/utils.js
new file mode 100644
index 00000000000..af01653fc0d
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/utils.js
@@ -0,0 +1,37 @@
+const BLAME_INFO_CLASSLIST = ['gl-border-t', 'gl-border-gray-500', 'gl-pt-3!'];
+const PADDING_BOTTOM_LARGE = 'gl-pb-6!';
+const PADDING_BOTTOM_SMALL = 'gl-pb-3!';
+
+const findLineNumberElement = (lineNumber) => document.getElementById(`L${lineNumber}`);
+
+const findLineContentElement = (lineNumber) => document.getElementById(`LC${lineNumber}`);
+
+export const calculateBlameOffset = (lineNumber) => {
+ if (lineNumber === 1) return '0px';
+ const lineContentOffset = findLineContentElement(lineNumber)?.offsetTop;
+ return `${lineContentOffset}px`;
+};
+
+export const toggleBlameClasses = (blameData, isVisible) => {
+ /**
+ * Adds/removes classes to line number/content elements to match the line with the blame info
+ * */
+ const method = isVisible ? 'add' : 'remove';
+ blameData.forEach(({ lineno, span }) => {
+ const lineNumberEl = findLineNumberElement(lineno)?.parentElement;
+ const lineContentEl = findLineContentElement(lineno);
+ const lineNumberSpanEl = findLineNumberElement(lineno + span - 1)?.parentElement;
+ const lineContentSpanEl = findLineContentElement(lineno + span - 1);
+
+ lineNumberEl?.classList[method](...BLAME_INFO_CLASSLIST);
+ lineContentEl?.classList[method](...BLAME_INFO_CLASSLIST);
+
+ if (span === 1) {
+ lineNumberSpanEl?.classList[method](PADDING_BOTTOM_LARGE);
+ lineContentSpanEl?.classList[method](PADDING_BOTTOM_LARGE);
+ } else {
+ lineNumberSpanEl?.classList[method](PADDING_BOTTOM_SMALL);
+ lineContentSpanEl?.classList[method](PADDING_BOTTOM_SMALL);
+ }
+ });
+};
diff --git a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
index 7c9a1bcd8cc..058a00e169a 100644
--- a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
+++ b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
@@ -1,5 +1,5 @@
<script>
-import { GlTooltipDirective } from '@gitlab/ui';
+import { GlTruncate, GlTooltipDirective } from '@gitlab/ui';
import { DATE_TIME_FORMATS, DEFAULT_DATE_TIME_FORMAT } from '~/lib/utils/datetime_utility';
import timeagoMixin from '../mixins/timeago';
@@ -12,6 +12,9 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
+ components: {
+ GlTruncate,
+ },
mixins: [timeagoMixin],
props: {
time: {
@@ -34,11 +37,19 @@ export default {
default: DEFAULT_DATE_TIME_FORMAT,
validator: (timeFormat) => DATE_TIME_FORMATS.includes(timeFormat),
},
+ enableTruncation: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
timeAgo() {
return this.timeFormatted(this.time, this.dateTimeFormat);
},
+ tooltipText() {
+ return this.enableTruncation ? undefined : this.tooltipTitle(this.time);
+ },
},
};
</script>
@@ -46,8 +57,11 @@ export default {
<time
v-gl-tooltip.viewport="{ placement: tooltipPlacement }"
:class="cssClass"
- :title="tooltipTitle(time)"
+ :title="tooltipText"
:datetime="time"
- ><slot :time-ago="timeAgo">{{ timeAgo }}</slot></time
+ ><slot :time-ago="timeAgo"
+ ><template v-if="enableTruncation"><gl-truncate :text="timeAgo" with-tooltip /></template
+ ><template v-else>{{ timeAgo }}</template></slot
+ ></time
>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/toggle_labels.vue b/app/assets/javascripts/vue_shared/components/toggle_labels.vue
new file mode 100644
index 00000000000..05c837e32f0
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/toggle_labels.vue
@@ -0,0 +1,62 @@
+<script>
+import { GlToggle } from '@gitlab/ui';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
+import isShowingLabelsQuery from '~/graphql_shared/client/is_showing_labels.query.graphql';
+import setIsShowingLabelsMutation from '~/graphql_shared/client/set_is_showing_labels.mutation.graphql';
+
+export default {
+ components: {
+ GlToggle,
+ LocalStorageSync,
+ },
+ data() {
+ return {
+ isShowingLabels: null,
+ };
+ },
+ apollo: {
+ isShowingLabels: {
+ query: isShowingLabelsQuery,
+ update: (data) => data.isShowingLabels,
+ },
+ },
+ computed: {
+ trackProperty() {
+ return this.isShowingLabels ? 'on' : 'off';
+ },
+ },
+ methods: {
+ setShowLabels(val) {
+ this.$apollo.mutate({
+ mutation: setIsShowingLabelsMutation,
+ variables: {
+ isShowingLabels: val,
+ },
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="board-labels-toggle-wrapper gl-display-flex gl-align-items-center gl-ml-3 gl-h-7">
+ <local-storage-sync
+ :value="isShowingLabels"
+ storage-key="gl-show-board-labels"
+ @input="setShowLabels"
+ />
+ <gl-toggle
+ :value="isShowingLabels"
+ :label="__('Show labels')"
+ :data-track-property="trackProperty"
+ data-track-action="toggle"
+ data-track-label="show_labels"
+ label-position="left"
+ aria-describedby="board-labels-toggle-text"
+ data-testid="show-labels-toggle"
+ data-qa-selector="show_labels_toggle"
+ class="gl-flex-direction-row"
+ @change="setShowLabels"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/vuex_module_provider.vue b/app/assets/javascripts/vue_shared/components/vuex_module_provider.vue
index 9665e188469..46496d2e483 100644
--- a/app/assets/javascripts/vue_shared/components/vuex_module_provider.vue
+++ b/app/assets/javascripts/vue_shared/components/vuex_module_provider.vue
@@ -2,12 +2,7 @@
export default {
provide() {
return {
- // We can't use this.vuexModule due to bug in vue-apollo when
- // provide is called in beforeCreate
- // See https://github.com/vuejs/vue-apollo/pull/1153 for details
-
- // @vue-compat does not care to normalize propsData fields
- vuexModule: this.$options.propsData.vuexModule ?? this.$options.propsData['vuex-module'],
+ vuexModule: this.vuexModule,
};
},
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 beb8321a271..9fb0add5522 100644
--- a/app/assets/javascripts/vue_shared/components/web_ide_link.vue
+++ b/app/assets/javascripts/vue_shared/components/web_ide_link.vue
@@ -355,7 +355,7 @@ export default {
<span data-testid="action-primary-text" class="gl-font-weight-bold gl-mb-2">{{
action.text
}}</span>
- <span data-testid="action-secondary-text" class="gl-text-gray-700">
+ <span data-testid="action-secondary-text" class="gl-font-sm gl-text-secondary">
{{ action.secondaryText }}
</span>
</div>
diff --git a/app/assets/javascripts/vue_shared/constants.js b/app/assets/javascripts/vue_shared/constants.js
index 9c001fa2e9a..81e75c4e1d5 100644
--- a/app/assets/javascripts/vue_shared/constants.js
+++ b/app/assets/javascripts/vue_shared/constants.js
@@ -97,4 +97,7 @@ export const confidentialityInfoText = (workspaceType, issuableType) =>
export const EDITING_MODE_KEY = 'gl-markdown-editor-mode';
export const EDITING_MODE_MARKDOWN_FIELD = 'markdownField';
export const EDITING_MODE_CONTENT_EDITOR = 'contentEditor';
+
export const CLEAR_AUTOSAVE_ENTRY_EVENT = 'markdown_clear_autosave_entry';
+export const CONTENT_EDITOR_READY_EVENT = 'content_editor_ready';
+export const MARKDOWN_EDITOR_READY_EVENT = 'markdown_editor_ready';
diff --git a/app/assets/javascripts/vue_shared/global_search/constants.js b/app/assets/javascripts/vue_shared/global_search/constants.js
index 43110c0c9af..14ea0389bad 100644
--- a/app/assets/javascripts/vue_shared/global_search/constants.js
+++ b/app/assets/javascripts/vue_shared/global_search/constants.js
@@ -8,6 +8,7 @@ export const ALL_GITLAB = __('All GitLab');
export const SEARCH_GITLAB = s__('GlobalSearch|Search GitLab');
export const PLACES = s__('GlobalSearch|Places');
+export const COMMAND_PALETTE = s__('GlobalSearch|Command palette');
export const SEARCH_DESCRIBED_BY_DEFAULT = s__(
'GlobalSearch|%{count} default results provided. Use the up and down arrow keys to navigate search results list.',
);
diff --git a/app/assets/javascripts/vue_shared/issuable/create/components/issuable_create_root.vue b/app/assets/javascripts/vue_shared/issuable/create/components/issuable_create_root.vue
index 033bb8c3885..679332163b5 100644
--- a/app/assets/javascripts/vue_shared/issuable/create/components/issuable_create_root.vue
+++ b/app/assets/javascripts/vue_shared/issuable/create/components/issuable_create_root.vue
@@ -22,6 +22,10 @@ export default {
type: String,
required: true,
},
+ issuableType: {
+ type: String,
+ required: true,
+ },
},
};
</script>
@@ -34,6 +38,7 @@ export default {
:description-help-path="descriptionHelpPath"
:labels-fetch-path="labelsFetchPath"
:labels-manage-path="labelsManagePath"
+ :issuable-type="issuableType"
>
<template #actions="issuableMeta">
<slot name="actions" v-bind="issuableMeta"></slot>
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 1cfa3f6d3d7..64f0ec3fbc7 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,15 +1,17 @@
<script>
-import { GlForm, GlFormInput, GlFormGroup } from '@gitlab/ui';
+import { GlForm, GlFormInput, GlFormCheckbox, GlFormGroup } from '@gitlab/ui';
import LabelsSelect from '~/sidebar/components/labels/labels_select_vue/labels_select_root.vue';
import { VARIANT_EMBEDDED } from '~/sidebar/components/labels/labels_select_widget/constants';
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
-import { __ } from '~/locale';
+import { __, sprintf } from '~/locale';
+import { issuableTypeText } from '~/issues/constants';
export default {
VARIANT_EMBEDDED,
components: {
GlForm,
GlFormInput,
+ GlFormCheckbox,
GlFormGroup,
MarkdownEditor,
LabelsSelect,
@@ -31,6 +33,10 @@ export default {
type: String,
required: true,
},
+ issuableType: {
+ type: String,
+ required: true,
+ },
},
descriptionFormFieldProps: {
ariaLabel: __('Description'),
@@ -44,10 +50,20 @@ export default {
return {
issuableTitle: '',
issuableDescription: '',
+ issuableConfidential: false,
selectedLabels: [],
};
},
- computed: {},
+ computed: {
+ confidentialityText() {
+ return sprintf(
+ __(
+ 'This %{issuableType} is confidential and should only be visible to team members with at least Reporter access.',
+ ),
+ { issuableType: issuableTypeText[this.issuableType] },
+ );
+ },
+ },
methods: {
handleUpdateSelectedLabels(labels) {
if (labels.length) {
@@ -85,6 +101,15 @@ export default {
/>
</div>
</div>
+ <div data-testid="issuable-confidential" class="form-group row">
+ <div class="col-12">
+ <gl-form-group :label="__('Confidentiality')" label-for="issuable-confidential">
+ <gl-form-checkbox id="issuable-confidential" v-model="issuableConfidential">
+ {{ confidentialityText }}
+ </gl-form-checkbox>
+ </gl-form-group>
+ </div>
+ </div>
<div data-testid="issuable-labels" class="form-group row">
<label for="issuable-labels" class="col-12">{{ __('Labels') }}</label>
<div class="col-12">
@@ -111,6 +136,7 @@ export default {
name="actions"
:issuable-title="issuableTitle"
:issuable-description="issuableDescription"
+ :issuable-confidential="issuableConfidential"
:selected-labels="selectedLabels"
></slot>
</div>
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 690d9523a63..bb36df0a778 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
@@ -8,7 +8,6 @@ import { isExternal, setUrlFragment } from '~/lib/utils/url_utility';
import { __, n__, sprintf } from '~/locale';
import IssuableAssignees from '~/issuable/components/issue_assignees.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
-import SafeHtml from '~/vue_shared/directives/safe_html';
import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
import { STATE_CLOSED } from '~/work_items/constants';
import { isAssigneesWidget, isLabelsWidget } from '~/work_items/utils';
@@ -25,7 +24,6 @@ export default {
},
directives: {
GlTooltip: GlTooltipDirective,
- SafeHtml,
},
mixins: [timeagoMixin],
props: {
@@ -91,9 +89,6 @@ export default {
authorId() {
return getIdFromGraphQLId(this.author.id);
},
- isIssueTrackerExternal() {
- return Boolean(this.issuable.externalTracker);
- },
isIssuableUrlExternal() {
return isExternal(this.webUrl ?? '');
},
@@ -266,36 +261,20 @@ export default {
v-if="issuable.hidden"
v-gl-tooltip
name="spam"
- :title="__('This issue is hidden because its author has been banned')"
+ :title="__('This issue is hidden because its author has been banned.')"
:aria-label="__('Hidden')"
/>
- <template v-if="isIssueTrackerExternal">
- <gl-link
- class="issue-title-text"
- dir="auto"
- :href="webUrl"
- data-qa-selector="issuable_title_link"
- data-testid="issuable-title-link"
- v-bind="issuableTitleProps"
- @click="handleIssuableItemClick"
- >
- {{ issuable.title }}
- <gl-icon v-if="isIssuableUrlExternal" name="external-link" class="gl-ml-2" />
- </gl-link>
- </template>
- <template v-else>
- <gl-link
- v-safe-html="issuable.titleHtml || issuable.title"
- class="issue-title-text"
- dir="auto"
- :href="webUrl"
- data-qa-selector="issuable_title_link"
- data-testid="issuable-title-link"
- v-bind="issuableTitleProps"
- @click="handleIssuableItemClick"
- />
+ <gl-link
+ class="issue-title-text"
+ dir="auto"
+ :href="webUrl"
+ data-testid="issuable-title-link"
+ v-bind="issuableTitleProps"
+ @click="handleIssuableItemClick"
+ >
+ {{ issuable.title }}
<gl-icon v-if="isIssuableUrlExternal" name="external-link" class="gl-ml-2" />
- </template>
+ </gl-link>
<span
v-if="taskStatus"
class="task-status gl-display-none gl-sm-display-inline-block! gl-ml-2 gl-font-sm"
diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue
index c4b92454ac0..a9b5e3a66a8 100644
--- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue
+++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue
@@ -1,6 +1,8 @@
<script>
import { GlIcon, GlBadge, GlButton, GlLink, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import HiddenBadge from '~/issuable/components/hidden_badge.vue';
+import LockedBadge from '~/issuable/components/locked_badge.vue';
import { issuableStatusText, STATUS_OPEN, STATUS_REOPENED } from '~/issues/constants';
import { isExternal } from '~/lib/utils/url_utility';
import { __, n__, sprintf } from '~/locale';
@@ -16,6 +18,8 @@ export default {
GlButton,
GlLink,
GlSprintf,
+ HiddenBadge,
+ LockedBadge,
TimeAgoTooltip,
WorkItemTypeIcon,
},
@@ -101,16 +105,6 @@ export default {
? 'success'
: 'info';
},
- blockedTooltip() {
- return sprintf(__('This %{issuable} is locked. Only project members can comment.'), {
- issuable: this.issuableType,
- });
- },
- hiddenTooltip() {
- return sprintf(__('This %{issuable} is hidden because its author has been banned'), {
- issuable: this.issuableType,
- });
- },
shouldShowWorkItemTypeIcon() {
return this.showWorkItemTypeIcon && this.issuableType;
},
@@ -174,22 +168,8 @@ export default {
:issuable-type="issuableType"
:workspace-type="workspaceType"
/>
- <span v-if="blocked" class="issuable-warning-icon">
- <gl-icon
- v-gl-tooltip.bottom
- name="lock"
- :title="blockedTooltip"
- :aria-label="__('Blocked')"
- />
- </span>
- <span v-if="isHidden" class="issuable-warning-icon">
- <gl-icon
- v-gl-tooltip.bottom
- name="spam"
- :title="hiddenTooltip"
- :aria-label="__('Hidden')"
- />
- </span>
+ <locked-badge v-if="blocked" :issuable-type="issuableType" />
+ <hidden-badge v-if="isHidden" :issuable-type="issuableType" />
<work-item-type-icon
v-if="shouldShowWorkItemTypeIcon"
show-text
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 f54c4c52743..3412848a9b7 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
@@ -138,7 +138,10 @@ export default {
</div>
<template v-if="activePanel">
- <div class="gl-display-flex gl-align-items-center gl-py-5">
+ <div
+ data-testid="active-panel-template"
+ class="gl-display-flex gl-align-items-center gl-py-5"
+ >
<div class="col-auto">
<img aria-hidden :src="activePanel.imageSrc" />
</div>