diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-13 21:09:34 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-13 21:09:34 +0300 |
commit | 53d77359a0e6bf78bfc8ef8c72995eebe1f9e63b (patch) | |
tree | 444eb2851a18271c20ea6fd23fab0b02b7e4d9b2 /app | |
parent | dd7da8bd31926a1210011856f7d66ee43fa65156 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
11 files changed, 277 insertions, 341 deletions
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue index f92c06956a8..020edcb01b8 100644 --- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue +++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue @@ -5,6 +5,7 @@ import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.v import { joinPaths } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; +import { titleQueries } from 'ee_else_ce/boards/constants'; export default { components: { @@ -19,7 +20,7 @@ export default { directives: { autofocusonshow, }, - inject: ['isApolloBoard'], + inject: ['fullPath', 'issuableType', 'isEpicBoard', 'isApolloBoard'], props: { activeItem: { type: Object, @@ -77,8 +78,9 @@ export default { }, async setPendingState() { const pendingChanges = localStorage.getItem(this.pendingChangesStorageKey); + const shouldOpen = pendingChanges !== this.title; - if (pendingChanges) { + if (pendingChanges && shouldOpen) { this.title = pendingChanges; this.showChangesAlert = true; await this.$nextTick(); @@ -93,6 +95,26 @@ export default { this.showChangesAlert = false; localStorage.removeItem(this.pendingChangesStorageKey); }, + async setActiveBoardItemTitle() { + if (!this.isApolloBoard) { + await this.setActiveItemTitle({ title: this.title, projectPath: this.projectPath }); + return; + } + const { fullPath, issuableType, isEpicBoard, title } = this; + const workspacePath = isEpicBoard + ? { groupPath: fullPath } + : { projectPath: this.projectPath }; + await this.$apollo.mutate({ + mutation: titleQueries[issuableType].mutation, + variables: { + input: { + ...workspacePath, + iid: String(this.item.iid), + title, + }, + }, + }); + }, async setTitle() { this.$refs.sidebarItem.collapse(); @@ -102,7 +124,7 @@ export default { try { this.loading = true; - await this.setActiveItemTitle({ title: this.title, projectPath: this.projectPath }); + await this.setActiveBoardItemTitle(); localStorage.removeItem(this.pendingChangesStorageKey); this.showChangesAlert = false; } catch (e) { diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/formatting_bubble_menu.vue b/app/assets/javascripts/content_editor/components/bubble_menus/formatting_bubble_menu.vue index 06b80a65528..cef446c4cf8 100644 --- a/app/assets/javascripts/content_editor/components/bubble_menus/formatting_bubble_menu.vue +++ b/app/assets/javascripts/content_editor/components/bubble_menus/formatting_bubble_menu.vue @@ -35,9 +35,6 @@ export default { ); }, }, - toggleLinkCommandParams: { - href: '', - }, }; </script> <template> @@ -122,8 +119,7 @@ export default { data-testid="link" content-type="link" icon-name="link" - editor-command="toggleLink" - :editor-command-params="$options.toggleLinkCommandParams" + editor-command="editLink" category="tertiary" size="medium" :label="__('Insert link')" diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/link_bubble_menu.vue b/app/assets/javascripts/content_editor/components/bubble_menus/link_bubble_menu.vue index a4713eb3275..a3065be3772 100644 --- a/app/assets/javascripts/content_editor/components/bubble_menus/link_bubble_menu.vue +++ b/app/assets/javascripts/content_editor/components/bubble_menus/link_bubble_menu.vue @@ -8,6 +8,7 @@ import { GlButtonGroup, GlTooltipDirective as GlTooltip, } from '@gitlab/ui'; +import { getMarkType, getMarkRange } from '@tiptap/core'; import Link from '../../extensions/link'; import EditorStateObserver from '../editor_state_observer.vue'; import BubbleMenu from './bubble_menu.vue'; @@ -31,12 +32,36 @@ export default { return { linkHref: undefined, linkCanonicalSrc: undefined, - linkTitle: undefined, + linkText: undefined, isEditing: false, }; }, methods: { + linkIsEmpty() { + return ( + !this.linkCanonicalSrc && + !this.linkHref && + (!this.linkText || this.linkText === this.linkTextInDoc()) + ); + }, + + linkTextInDoc() { + const { state } = this.tiptapEditor; + const type = getMarkType(Link.name, state.schema); + let { selection: range } = state; + if (range.from === range.to) { + range = + getMarkRange(state.selection.$from, type) || + getMarkRange(state.selection.$to, type) || + {}; + } + + if (!range.from || !range.to) return ''; + + return state.doc.textBetween(range.from, range.to, ' '); + }, + shouldShow() { return this.tiptapEditor.isActive(Link.name); }, @@ -52,31 +77,51 @@ export default { this.isEditing = false; this.linkHref = await this.contentEditor.resolveUrl(this.linkCanonicalSrc); - - if (!this.linkCanonicalSrc && !this.linkHref) { - this.removeLink(); - } }, cancelEditingLink() { this.endEditingLink(); - this.updateLinkToState(); + + if (this.linkIsEmpty()) { + this.removeLink(); + } else { + this.updateLinkToState(); + } }, async saveEditedLink() { - if (!this.linkCanonicalSrc) { + const chain = this.tiptapEditor.chain().focus(); + + const attrs = { + href: this.linkCanonicalSrc, + canonicalSrc: this.linkCanonicalSrc, + }; + + // if nothing was entered by the user and the link is empty, remove it + // since we don't want to insert an empty link + if (this.linkIsEmpty()) { this.removeLink(); - } else { - this.tiptapEditor - .chain() - .focus() + return; + } + + if (!this.linkText) { + this.linkText = this.linkCanonicalSrc; + } + + // if link text was updated, insert a new link in the doc with the new text + if (this.linkTextInDoc() !== this.linkText) { + chain .extendMarkRange(Link.name) - .updateAttributes(Link.name, { - href: this.linkCanonicalSrc, - canonicalSrc: this.linkCanonicalSrc, - title: this.linkTitle, + .setMeta('preventAutolink', true) + .insertContent({ + marks: [{ type: Link.name, attrs }], + type: 'text', + text: this.linkText, }) .run(); + } else { + // if link text was not updated, just update the attributes + chain.updateAttributes(Link.name, attrs).run(); } this.endEditingLink(); @@ -84,22 +129,27 @@ export default { updateLinkToState() { const editor = this.tiptapEditor; - - const { href, title, canonicalSrc } = editor.getAttributes(Link.name); + const { href, canonicalSrc } = editor.getAttributes(Link.name); + const text = this.linkTextInDoc(); if ( canonicalSrc === this.linkCanonicalSrc && href === this.linkHref && - title === this.linkTitle + text === this.linkText ) { return; } - this.linkTitle = title; + this.linkText = text; this.linkHref = href; this.linkCanonicalSrc = canonicalSrc || href; + }, - this.isEditing = !this.linkCanonicalSrc; + onTransaction({ transaction }) { + this.linkText = this.linkTextInDoc(); + if (transaction.getMeta('creatingLink')) { + this.isEditing = true; + } }, copyLinkHref() { @@ -107,31 +157,49 @@ export default { }, removeLink() { - this.tiptapEditor.chain().focus().extendMarkRange(Link.name).unsetLink().run(); + const chain = this.tiptapEditor.chain().focus(); + if (this.linkTextInDoc()) { + chain.unsetLink().run(); + } else { + chain + .insertContent({ + type: 'text', + text: ' ', + }) + .extendMarkRange(Link.name) + .unsetLink() + .deleteSelection() + .run(); + } }, resetBubbleMenuState() { - this.linkTitle = undefined; + this.linkText = undefined; this.linkHref = undefined; this.linkCanonicalSrc = undefined; }, }, tippyOptions: { placement: 'bottom', + appendTo: () => document.body, }, }; </script> <template> - <bubble-menu - data-testid="link-bubble-menu" - class="gl-shadow gl-rounded-base gl-bg-white" - plugin-key="bubbleMenuLink" - :should-show="shouldShow" - :tippy-options="$options.tippyOptions" - @show="updateLinkToState" - @hidden="resetBubbleMenuState" + <editor-state-observer + :debounce="0" + @transaction="onTransaction" + @selectionUpdate="updateLinkToState" > - <editor-state-observer @selectionUpdate="updateLinkToState"> + <bubble-menu + data-testid="link-bubble-menu" + class="gl-shadow gl-rounded-base gl-bg-white" + plugin-key="bubbleMenuLink" + :should-show="shouldShow" + :tippy-options="$options.tippyOptions" + @show="updateLinkToState" + @hidden="resetBubbleMenuState" + > <gl-button-group v-if="!isEditing" class="gl-display-flex gl-align-items-center"> <gl-link v-gl-tooltip @@ -178,12 +246,12 @@ export default { /> </gl-button-group> <gl-form v-else class="bubble-menu-form gl-p-4 gl-w-100" @submit.prevent="saveEditedLink"> + <gl-form-group :label="__('Text')" label-for="link-text"> + <gl-form-input id="link-text" v-model="linkText" data-testid="link-text" /> + </gl-form-group> <gl-form-group :label="__('URL')" label-for="link-href"> <gl-form-input id="link-href" v-model="linkCanonicalSrc" data-testid="link-href" /> </gl-form-group> - <gl-form-group :label="__('Title')" label-for="link-title"> - <gl-form-input id="link-title" v-model="linkTitle" data-testid="link-title" /> - </gl-form-group> <div class="gl-display-flex gl-justify-content-end"> <gl-button class="gl-mr-3" data-testid="cancel-link" @click="cancelEditingLink"> {{ __('Cancel') }} @@ -193,6 +261,6 @@ export default { </gl-button> </div> </gl-form> - </editor-state-observer> - </bubble-menu> + </bubble-menu> + </editor-state-observer> </template> diff --git a/app/assets/javascripts/content_editor/components/editor_state_observer.vue b/app/assets/javascripts/content_editor/components/editor_state_observer.vue index ccb46e3b593..62f2113a8f4 100644 --- a/app/assets/javascripts/content_editor/components/editor_state_observer.vue +++ b/app/assets/javascripts/content_editor/components/editor_state_observer.vue @@ -16,14 +16,21 @@ const getComponentEventName = (tiptapEventName) => tiptapToComponentMap[tiptapEv export default { inject: ['tiptapEditor', 'eventHub'], + props: { + debounce: { + type: Number, + required: false, + default: 100, + }, + }, created() { this.disposables = []; Object.keys(tiptapToComponentMap).forEach((tiptapEvent) => { - const eventHandler = debounce( - (params) => this.bubbleEvent(getComponentEventName(tiptapEvent), params), - 100, - ); + let eventHandler = (params) => this.bubbleEvent(getComponentEventName(tiptapEvent), params); + if (this.debounce) { + eventHandler = debounce(eventHandler, this.debounce); + } this.tiptapEditor?.on(tiptapEvent, eventHandler); diff --git a/app/assets/javascripts/content_editor/components/formatting_toolbar.vue b/app/assets/javascripts/content_editor/components/formatting_toolbar.vue index 11b3a5a3069..230141fe1c1 100644 --- a/app/assets/javascripts/content_editor/components/formatting_toolbar.vue +++ b/app/assets/javascripts/content_editor/components/formatting_toolbar.vue @@ -2,7 +2,6 @@ import trackUIControl from '../services/track_ui_control'; import ToolbarButton from './toolbar_button.vue'; import ToolbarAttachmentButton from './toolbar_attachment_button.vue'; -import ToolbarLinkButton from './toolbar_link_button.vue'; import ToolbarTableButton from './toolbar_table_button.vue'; import ToolbarTextStyleDropdown from './toolbar_text_style_dropdown.vue'; import ToolbarMoreDropdown from './toolbar_more_dropdown.vue'; @@ -11,7 +10,6 @@ export default { components: { ToolbarButton, ToolbarTextStyleDropdown, - ToolbarLinkButton, ToolbarTableButton, ToolbarAttachmentButton, ToolbarMoreDropdown, @@ -24,7 +22,10 @@ export default { }; </script> <template> - <div class="gl-w-full gl-border-b gl-display-flex gl-justify-content-end"> + <div + class="gl-w-full gl-border-b gl-display-flex gl-justify-content-end" + data-testid="formatting-toolbar" + > <div class="gl-py-2 gl-display-flex gl-flex-wrap-wrap gl-align-items-end"> <toolbar-text-style-dropdown data-testid="text-styles" @@ -62,7 +63,14 @@ export default { :label="__('Code')" @execute="trackToolbarControlExecution" /> - <toolbar-link-button data-testid="link" @execute="trackToolbarControlExecution" /> + <toolbar-button + data-testid="link" + content-type="link" + icon-name="link" + editor-command="editLink" + :label="__('Insert link')" + @execute="trackToolbarControlExecution" + /> <toolbar-button data-testid="bullet-list" content-type="bulletList" diff --git a/app/assets/javascripts/content_editor/components/toolbar_link_button.vue b/app/assets/javascripts/content_editor/components/toolbar_link_button.vue deleted file mode 100644 index ff1a52264f0..00000000000 --- a/app/assets/javascripts/content_editor/components/toolbar_link_button.vue +++ /dev/null @@ -1,125 +0,0 @@ -<script> -import { - GlDropdown, - GlDropdownForm, - GlButton, - GlFormInputGroup, - GlDropdownDivider, - GlDropdownItem, - GlTooltipDirective as GlTooltip, -} from '@gitlab/ui'; -import Link from '../extensions/link'; -import { hasSelection } from '../services/utils'; -import EditorStateObserver from './editor_state_observer.vue'; - -export default { - components: { - GlDropdown, - GlDropdownForm, - GlFormInputGroup, - GlDropdownDivider, - GlDropdownItem, - GlButton, - EditorStateObserver, - }, - directives: { - GlTooltip, - }, - inject: ['tiptapEditor'], - data() { - return { - linkHref: '', - isActive: false, - }; - }, - methods: { - resetFields() { - this.imgSrc = ''; - this.$refs.fileSelector.value = ''; - }, - updateLinkState({ editor }) { - const { canonicalSrc, href } = editor.getAttributes(Link.name); - - this.isActive = editor.isActive(Link.name); - this.linkHref = canonicalSrc || href; - }, - updateLink() { - this.tiptapEditor - .chain() - .focus() - .unsetLink() - .setLink({ - href: this.linkHref, - canonicalSrc: this.linkHref, - }) - .run(); - - this.$emit('execute', { contentType: Link.name }); - }, - selectLink() { - const { tiptapEditor } = this; - - // a selection has already been made by the user, so do nothing - if (!hasSelection(tiptapEditor)) { - tiptapEditor.chain().focus().extendMarkRange(Link.name).run(); - } - }, - removeLink() { - this.tiptapEditor.chain().focus().unsetLink().run(); - - this.$emit('execute', { contentType: Link.name }); - }, - onFileSelect(e) { - this.tiptapEditor - .chain() - .focus() - .uploadAttachment({ - file: e.target.files[0], - }) - .run(); - - this.resetFields(); - this.$emit('execute', { contentType: Link.name }); - }, - }, -}; -</script> -<template> - <editor-state-observer @transaction="updateLinkState"> - <span class="gl-display-inline-flex"> - <gl-dropdown - v-gl-tooltip - :title="__('Insert link')" - :text="__('Insert link')" - :toggle-class="{ active: isActive }" - size="small" - category="tertiary" - icon="link" - text-sr-only - lazy - @show="selectLink()" - > - <gl-dropdown-form class="gl-px-3!" :class="{ 'gl-pb-2!': isActive }"> - <gl-form-input-group v-model="linkHref" :placeholder="__('Link URL')"> - <template #append> - <gl-button variant="confirm" @click="updateLink">{{ __('Apply') }}</gl-button> - </template> - </gl-form-input-group> - </gl-dropdown-form> - <div v-if="isActive"> - <gl-dropdown-divider /> - <gl-dropdown-item @click="removeLink"> - {{ __('Remove link') }} - </gl-dropdown-item> - </div> - </gl-dropdown> - <input - ref="fileSelector" - type="file" - name="content_editor_attachment" - class="gl-display-none" - @change="onFileSelect" - /> - </span> - </editor-state-observer> -</template> diff --git a/app/assets/javascripts/content_editor/extensions/link.js b/app/assets/javascripts/content_editor/extensions/link.js index e985e561fda..314d5230b01 100644 --- a/app/assets/javascripts/content_editor/extensions/link.js +++ b/app/assets/javascripts/content_editor/extensions/link.js @@ -18,6 +18,8 @@ export const extractHrefFromMarkdownLink = (match) => { }; export default Link.extend({ + inclusive: false, + addOptions() { return { ...this.parent?.(), @@ -64,4 +66,18 @@ export default Link.extend({ }, }; }, + addCommands() { + return { + ...this.parent?.(), + editLink: (attrs) => ({ chain }) => { + chain().setMeta('creatingLink', true).setLink(attrs).run(); + }, + }; + }, + + addKeyboardShortcuts() { + return { + 'Mod-k': () => this.editor.commands.editLink(), + }; + }, }); diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js index ab8cf7014d9..664473fccfe 100644 --- a/app/assets/javascripts/content_editor/services/serialization_helpers.js +++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js @@ -607,7 +607,7 @@ export const link = { return '['; } - const attrs = { href: state.esc(href || canonicalSrc) }; + const attrs = { href: state.esc(href || canonicalSrc || '') }; if (title) { attrs.title = title; @@ -623,14 +623,14 @@ export const link = { const { canonicalSrc, href, title, sourceMarkdown, isReference } = mark.attrs; if (isReference) { - return `][${state.esc(canonicalSrc || href)}]`; + return `][${state.esc(canonicalSrc || href || '')}]`; } if (linkType(sourceMarkdown) === LINK_HTML) { return closeTag('a'); } - return `](${state.esc(canonicalSrc || href)}${title ? ` ${state.quote(title)}` : ''})`; + return `](${state.esc(canonicalSrc || href || '')}${title ? ` ${state.quote(title)}` : ''})`; }, }; diff --git a/app/assets/javascripts/lib/utils/vue3compat/vue_router.js b/app/assets/javascripts/lib/utils/vue3compat/vue_router.js new file mode 100644 index 00000000000..006ed920ef0 --- /dev/null +++ b/app/assets/javascripts/lib/utils/vue3compat/vue_router.js @@ -0,0 +1,105 @@ +import Vue from 'vue'; +import { + createRouter, + createMemoryHistory, + createWebHistory, + createWebHashHistory, +} from 'vue-router-vue3'; + +const mode = (value, options) => { + if (!value) return null; + let history; + // eslint-disable-next-line default-case + switch (value) { + case 'history': + history = createWebHistory(options.base); + break; + case 'hash': + history = createWebHashHistory(); + break; + case 'abstract': + history = createMemoryHistory(); + break; + } + return { history }; +}; + +const base = () => null; + +const toNewCatchAllPath = (path) => { + if (path === '*') return '/:pathMatch(.*)*'; + return path; +}; + +const routes = (value) => { + if (!value) return null; + const newRoutes = value.reduce(function handleRoutes(acc, route) { + const newRoute = { + ...route, + path: toNewCatchAllPath(route.path), + }; + if (route.children) { + newRoute.children = route.children.reduce(handleRoutes, []); + } + acc.push(newRoute); + return acc; + }, []); + return { routes: newRoutes }; +}; + +const scrollBehavior = (value) => { + return { + scrollBehavior(...args) { + const { x, y, left, top } = value(...args); + return { left: x || left, top: y || top }; + }, + }; +}; + +const transformers = { + mode, + base, + routes, + scrollBehavior, +}; + +const transformOptions = (options) => { + const defaultConfig = { + routes: null, + history: createWebHashHistory(), + }; + return Object.keys(options).reduce((acc, key) => { + const value = options[key]; + if (key in transformers) { + Object.assign(acc, transformers[key](value, options)); + } else { + acc[key] = value; + } + return acc; + }, defaultConfig); +}; + +const installed = new WeakMap(); + +export default class VueRouterCompat { + constructor(options) { + // eslint-disable-next-line no-constructor-return + return createRouter(transformOptions(options)); + } + + static install() { + Vue.mixin({ + beforeCreate() { + const { app } = this.$.appContext; + const { router } = this.$options; + if (router && !installed.get(app)?.has(router)) { + if (!installed.has(app)) { + installed.set(app, new WeakSet()); + } + installed.get(app).add(router); + this.$.appContext.app.use(this.$options.router); + } + }, + }); + } +} diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js index 5f15a11e708..e6ee6b702bb 100644 --- a/app/assets/javascripts/pages/projects/project.js +++ b/app/assets/javascripts/pages/projects/project.js @@ -3,28 +3,10 @@ import $ from 'jquery'; import { setCookie } from '~/lib/utils/common_utils'; import initClonePanel from '~/clone_panel'; -import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown'; -import { createAlert } from '~/alert'; -import axios from '~/lib/utils/axios_utils'; -import { serializeForm } from '~/lib/utils/forms'; -import { mergeUrlParams } from '~/lib/utils/url_utility'; -import { __ } from '~/locale'; - -const BRANCH_REF_TYPE = 'heads'; -const TAG_REF_TYPE = 'tags'; -const BRANCH_GROUP_NAME = __('Branches'); -const TAG_GROUP_NAME = __('Tags'); export default class Project { constructor() { initClonePanel(); - // Ref switcher - if (document.querySelector('.js-project-refs-dropdown')) { - Project.initRefSwitcher(); - $('.project-refs-select').on('change', function () { - return $(this).parents('form').trigger('submit'); - }); - } $('.js-hide-no-ssh-message').on('click', function (e) { setCookie('hide_no_ssh_message', 'false'); @@ -48,125 +30,4 @@ export default class Project { static changeProject(url) { return (window.location = url); } - - static initRefSwitcher() { - const refListItem = document.createElement('li'); - const refLink = document.createElement('a'); - - refLink.href = '#'; - - return $('.js-project-refs-dropdown').each(function () { - const $dropdown = $(this); - const selected = $dropdown.data('selected'); - const refType = $dropdown.data('refType'); - const fieldName = $dropdown.data('fieldName'); - const shouldVisit = Boolean($dropdown.data('visit')); - const $form = $dropdown.closest('form'); - const path = $form.find('#path').val(); - const action = $form.attr('action'); - const linkTarget = mergeUrlParams(serializeForm($form[0]), action); - - return initDeprecatedJQueryDropdown($dropdown, { - data(term, callback) { - axios - .get($dropdown.data('refsUrl'), { - params: { - ref: $dropdown.data('ref'), - search: term, - }, - }) - .then(({ data }) => callback(data)) - .catch(() => - createAlert({ - message: __('An error occurred while getting projects'), - }), - ); - }, - selectable: true, - filterable: true, - filterRemote: true, - filterByText: true, - inputFieldName: $dropdown.data('inputFieldName'), - fieldName, - renderRow(ref, _, params) { - const li = refListItem.cloneNode(false); - - const link = refLink.cloneNode(false); - - if (ref === selected) { - // Check group and current ref type to avoid adding a class when tags and branches share the same name - if ( - (refType === BRANCH_REF_TYPE && params.group === BRANCH_GROUP_NAME) || - (refType === TAG_REF_TYPE && params.group === TAG_GROUP_NAME) || - !refType - ) { - link.className = 'is-active'; - } - } - - link.textContent = ref; - link.dataset.ref = ref; - if (ref.length > 0 && shouldVisit) { - const urlParams = { [fieldName]: ref }; - if (params.group === BRANCH_GROUP_NAME) { - urlParams.ref_type = BRANCH_REF_TYPE; - } else { - urlParams.ref_type = TAG_REF_TYPE; - } - link.href = mergeUrlParams(urlParams, linkTarget); - } - - li.appendChild(link); - - return li; - }, - id(obj, $el) { - return $el.attr('data-ref'); - }, - toggleLabel(obj, $el) { - return $el.text().trim(); - }, - clicked(options) { - const { e } = options; - - if (!shouldVisit) { - e.preventDefault(); - } - - // Some pages need to dynamically get the current path - // so they can opt-in to JS getting the path from the - // current URL by not setting a path in the dropdown form - if (shouldVisit && path === undefined) { - e.preventDefault(); - - const selectedUrl = new URL(e.target.href); - const loc = window.location.href; - - if (loc.includes('/-/')) { - const currentRef = $dropdown.data('ref'); - // The split and startWith is to ensure an exact word match - // and avoid partial match ie. currentRef is "dev" and loc is "development" - const splitPathAfterRefPortion = loc.split('/-/')[1].split(currentRef)[1]; - const doesPathContainRef = splitPathAfterRefPortion?.startsWith('/'); - - if (doesPathContainRef) { - // We are ignoring the url containing the ref portion - // and plucking the thereafter portion to reconstructure the url that is correct - const targetPath = splitPathAfterRefPortion?.slice(1).split('#')[0].split('?')[0]; - selectedUrl.searchParams.set('path', targetPath); - selectedUrl.hash = window.location.hash; - } - } - - // Open in new window if "meta" key is pressed - if (e.metaKey) { - window.open(selectedUrl.href, '_blank'); - } else { - window.location.href = selectedUrl.href; - } - } - }, - }); - }); - } } diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml deleted file mode 100644 index 48633ce0ce7..00000000000 --- a/app/views/shared/_ref_switcher.html.haml +++ /dev/null @@ -1,22 +0,0 @@ -- return unless @project - -- ref = local_assigns.fetch(:ref, @ref) -- form_path = local_assigns.fetch(:form_path, switch_project_refs_path(@project)) -- dropdown_toggle_text = ref || @project.default_branch -- field_name = local_assigns.fetch(:field_name, 'ref') - -= form_tag form_path, method: :get, class: "project-refs-form" do - - if defined?(destination) - = hidden_field_tag :destination, destination - - if defined?(path) - = hidden_field_tag :path, path - - @options && @options.each do |key, value| - = hidden_field_tag key, value, id: nil - .dropdown - = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: ref, ref_type: @ref_type, refs_url: refs_project_path(@project, sort: 'updated_desc'), field_name: field_name, submit_form_on_click: true, visit: true, testid: "branches-select" }, { toggle_class: "js-project-refs-dropdown" } - .dropdown-menu.dropdown-menu-selectable.git-revision-dropdown.dropdown-menu-paging{ class: ("dropdown-menu-right" if local_assigns[:align_right]) } - .dropdown-page-one - = dropdown_title _("Switch branch/tag") - = dropdown_filter _("Search branches and tags") - = dropdown_content - = dropdown_loading |