diff --git a/app/assets/javascripts/behaviors/shortcuts/keybindings.js b/app/assets/javascripts/behaviors/shortcuts/keybindings.js index a6faa04b440a24da3b3eada7cee69476ca6ba326..689f2f0898e0eb312aced585e1e905f57b236be5 100644 --- a/app/assets/javascripts/behaviors/shortcuts/keybindings.js +++ b/app/assets/javascripts/behaviors/shortcuts/keybindings.js @@ -399,6 +399,12 @@ export const ISSUABLE_CHANGE_LABEL = { defaultKeys: ['l'], }; +export const ISSUABLE_COPY_REF = { + id: 'issuables.copyIssuableRef', + description: __('Copy reference'), + defaultKeys: ['c r'], // eslint-disable-line @gitlab/require-i18n-strings +}; + export const ISSUE_MR_CHANGE_ASSIGNEE = { id: 'issuesMRs.changeAssignee', description: __('Change assignee'), @@ -606,7 +612,12 @@ const PROJECT_FILES_SHORTCUTS_GROUP = { const ISSUABLE_SHORTCUTS_GROUP = { id: 'issuables', name: __('Epics, issues, and merge requests'), - keybindings: [ISSUABLE_COMMENT_OR_REPLY, ISSUABLE_EDIT_DESCRIPTION, ISSUABLE_CHANGE_LABEL], + keybindings: [ + ISSUABLE_COMMENT_OR_REPLY, + ISSUABLE_EDIT_DESCRIPTION, + ISSUABLE_CHANGE_LABEL, + ISSUABLE_COPY_REF, + ], }; const ISSUE_MR_SHORTCUTS_GROUP = { diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js index 0c882ff9ea28c9eaeb3d6cde9caba219180701e9..b0e515ac19d88486d5d969684242bb1f3bdbc4c2 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js @@ -14,6 +14,7 @@ import { ISSUABLE_COMMENT_OR_REPLY, ISSUABLE_EDIT_DESCRIPTION, MR_COPY_SOURCE_BRANCH_NAME, + ISSUABLE_COPY_REF, } from './keybindings'; import Shortcuts from './shortcuts'; @@ -21,15 +22,24 @@ export default class ShortcutsIssuable extends Shortcuts { constructor() { super(); - this.inMemoryButton = document.createElement('button'); - this.clipboardInstance = new ClipboardJS(this.inMemoryButton); - this.clipboardInstance.on('success', () => { + this.branchInMemoryButton = document.createElement('button'); + this.branchClipboardInstance = new ClipboardJS(this.branchInMemoryButton); + this.branchClipboardInstance.on('success', () => { toast(s__('GlobalShortcuts|Copied source branch name to clipboard.')); }); - this.clipboardInstance.on('error', () => { + this.branchClipboardInstance.on('error', () => { toast(s__('GlobalShortcuts|Unable to copy the source branch name at this time.')); }); + this.refInMemoryButton = document.createElement('button'); + this.refClipboardInstance = new ClipboardJS(this.refInMemoryButton); + this.refClipboardInstance.on('success', () => { + toast(s__('GlobalShortcuts|Copied reference to clipboard.')); + }); + this.refClipboardInstance.on('error', () => { + toast(s__('GlobalShortcuts|Unable to copy the reference at this time.')); + }); + this.bindCommands([ [ISSUE_MR_CHANGE_ASSIGNEE, () => ShortcutsIssuable.openSidebarDropdown('assignee')], [ISSUE_MR_CHANGE_MILESTONE, () => ShortcutsIssuable.openSidebarDropdown('milestone')], @@ -37,6 +47,7 @@ export default class ShortcutsIssuable extends Shortcuts { [ISSUABLE_COMMENT_OR_REPLY, ShortcutsIssuable.replyWithSelectedText], [ISSUABLE_EDIT_DESCRIPTION, ShortcutsIssuable.editIssue], [MR_COPY_SOURCE_BRANCH_NAME, () => this.copyBranchName()], + [ISSUABLE_COPY_REF, () => this.copyIssuableRef()], ]); /** @@ -163,9 +174,20 @@ export default class ShortcutsIssuable extends Shortcuts { const branchName = button?.dataset.clipboardText; if (branchName) { - this.inMemoryButton.dataset.clipboardText = branchName; + this.branchInMemoryButton.dataset.clipboardText = branchName; + + this.branchInMemoryButton.dispatchEvent(new CustomEvent('click')); + } + } + + async copyIssuableRef() { + const refButton = document.querySelector('.js-copy-reference'); + const copiedRef = refButton?.dataset.clipboardText; + + if (copiedRef) { + this.refInMemoryButton.dataset.clipboardText = copiedRef; - this.inMemoryButton.dispatchEvent(new CustomEvent('click')); + this.refInMemoryButton.dispatchEvent(new CustomEvent('click')); } } } diff --git a/app/assets/javascripts/issues/show/components/header_actions.vue b/app/assets/javascripts/issues/show/components/header_actions.vue index d05311cb1dbd5fff7f34981f0f6acab69abfcc7b..8177720b1721a96d5525604e216fe842041dae3d 100644 --- a/app/assets/javascripts/issues/show/components/header_actions.vue +++ b/app/assets/javascripts/issues/show/components/header_actions.vue @@ -374,6 +374,7 @@ export default { <template v-if="isMrSidebarMoved"> <gl-dropdown-item :data-clipboard-text="issuableReference" + button-class="js-copy-reference" data-testid="copy-reference" @click="copyReference" >{{ $options.i18n.copyReferenceText }}</gl-dropdown-item @@ -486,6 +487,7 @@ export default { <template v-if="isMrSidebarMoved"> <gl-dropdown-item :data-clipboard-text="issuableReference" + button-class="js-copy-reference" data-testid="copy-reference" @click="copyReference" >{{ $options.i18n.copyReferenceText }}</gl-dropdown-item diff --git a/ee/app/assets/javascripts/epic/components/epic_header_actions.vue b/ee/app/assets/javascripts/epic/components/epic_header_actions.vue index 771d207e1070d8e2404edd7778d7a120f6d94baa..6eecad097317e0d81b8fdd5f310deae602c77ace 100644 --- a/ee/app/assets/javascripts/epic/components/epic_header_actions.vue +++ b/ee/app/assets/javascripts/epic/components/epic_header_actions.vue @@ -79,6 +79,7 @@ export default { action: this.closeDropdownAfterAction.bind(this, this.copyReference), extraAttrs: { 'data-clipboard-text': this.reference, + class: 'js-copy-reference', }, }; }, diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 720f5a1f879b62bf6f1f6848e7a64696c3a16e3e..8d3af341a653b6e85e264d2b1e45d43082eec720 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -21757,9 +21757,15 @@ msgstr "" msgid "GlobalSearch|in %{scope}" msgstr "" +msgid "GlobalShortcuts|Copied reference to clipboard." +msgstr "" + msgid "GlobalShortcuts|Copied source branch name to clipboard." msgstr "" +msgid "GlobalShortcuts|Unable to copy the reference at this time." +msgstr "" + msgid "GlobalShortcuts|Unable to copy the source branch name at this time." msgstr ""