diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/code_block_bubble_menu.vue b/app/assets/javascripts/content_editor/components/bubble_menus/code_block_bubble_menu.vue index efb66f07710b8e40be1f42db206c15ff910c3c2b..8c745d7627d1b92e04880b680a96efe28f4c86d8 100644 --- a/app/assets/javascripts/content_editor/components/bubble_menus/code_block_bubble_menu.vue +++ b/app/assets/javascripts/content_editor/components/bubble_menus/code_block_bubble_menu.vue @@ -106,6 +106,7 @@ export default { }, copyCodeBlockText() { + // eslint-disable-next-line no-restricted-properties navigator.clipboard.writeText(this.getCodeBlockText()); }, 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 ba82407f700347621ea6b3442ddc77d24b49ba9a..f7a4ac43904fa878d9d7a69cd8b7afca179c4a45 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 @@ -172,6 +172,7 @@ export default { ? this.linkCanonicalSrc : joinPaths(gon.gitlab_url, this.linkHref); + // eslint-disable-next-line no-restricted-properties navigator.clipboard.writeText(fullUrl); }, diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/reference_bubble_menu.vue b/app/assets/javascripts/content_editor/components/bubble_menus/reference_bubble_menu.vue index 2f0eb8a6bef7c5eb26fedd981d5713d3f7d0bf9f..f0f3c134a4d43c04fd29167c6d25081b665b1371 100644 --- a/app/assets/javascripts/content_editor/components/bubble_menus/reference_bubble_menu.vue +++ b/app/assets/javascripts/content_editor/components/bubble_menus/reference_bubble_menu.vue @@ -136,6 +136,7 @@ export default { this.tiptapEditor.chain().focus().deleteSelection().run(); }, copyReferenceURL() { + // eslint-disable-next-line no-restricted-properties navigator.clipboard.writeText(this.href); }, applyFormat(value) { diff --git a/app/assets/javascripts/glql/components/common/facade.vue b/app/assets/javascripts/glql/components/common/facade.vue index 6bc4831466137bdf1997b2229f194883a8762b2f..c9ed315f05c5f1843944786700591514908fd338 100644 --- a/app/assets/javascripts/glql/components/common/facade.vue +++ b/app/assets/javascripts/glql/components/common/facade.vue @@ -122,6 +122,7 @@ export default { }, copySource() { + // eslint-disable-next-line no-restricted-properties navigator.clipboard.writeText(this.wrappedQuery); }, diff --git a/app/assets/javascripts/glql/utils/copy_as_gfm.js b/app/assets/javascripts/glql/utils/copy_as_gfm.js index 754d067857d1015f43d73e64a2af3fd75dfaf807..3090e8b5b71d0d537eccd66101405539f0c832ef 100644 --- a/app/assets/javascripts/glql/utils/copy_as_gfm.js +++ b/app/assets/javascripts/glql/utils/copy_as_gfm.js @@ -20,5 +20,6 @@ export async function copyGLQLNodeAsGFM(el) { 'text/html': new Blob([html], { type: 'text/html' }), }); + // eslint-disable-next-line no-restricted-properties -- navigator.clipboard intentionally used here navigator.clipboard.write([clipboardItem]); } diff --git a/app/assets/javascripts/lib/utils/copy_to_clipboard.js b/app/assets/javascripts/lib/utils/copy_to_clipboard.js index d1789a350cc9a88fab78b80730bd0f388212c042..975f299fb1674d7ca2ee9b0f2016fe423c31392b 100644 --- a/app/assets/javascripts/lib/utils/copy_to_clipboard.js +++ b/app/assets/javascripts/lib/utils/copy_to_clipboard.js @@ -9,7 +9,10 @@ */ export const copyToClipboard = (text, container = document.body) => { // First, try a simple clipboard.writeText (works on https and localhost) + + // eslint-disable-next-line no-restricted-properties -- navigator.clipboard intentionally used here if (navigator.clipboard && window.isSecureContext) { + // eslint-disable-next-line no-restricted-properties -- navigator.clipboard intentionally used here return navigator.clipboard.writeText(text); } diff --git a/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/candidate_detail.vue b/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/candidate_detail.vue index 54808a17422bb34be4efb0bdbff6e128e3b50e30..a067a0eea18c26b9edb07f25ba0e2d3f2bfb3d61 100644 --- a/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/candidate_detail.vue +++ b/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/candidate_detail.vue @@ -97,6 +97,7 @@ export default { }, methods: { copyMlflowId() { + // eslint-disable-next-line no-restricted-properties navigator.clipboard.writeText(this.info.eid); }, }, diff --git a/app/assets/javascripts/ml/experiment_tracking/routes/experiments/index/components/mlflow_usage_modal.vue b/app/assets/javascripts/ml/experiment_tracking/routes/experiments/index/components/mlflow_usage_modal.vue index 42caae74571526ad4b1725f88f2942e40305df96..6dac7175e8c0277695b0c1d0fbc4abb2823f00b7 100644 --- a/app/assets/javascripts/ml/experiment_tracking/routes/experiments/index/components/mlflow_usage_modal.vue +++ b/app/assets/javascripts/ml/experiment_tracking/routes/experiments/index/components/mlflow_usage_modal.vue @@ -33,6 +33,7 @@ export default { }, methods: { copyInstructions() { + // eslint-disable-next-line no-restricted-properties navigator.clipboard.writeText(this.instruction.cmd); }, }, diff --git a/app/assets/javascripts/ml/model_registry/apps/show_ml_model_version.vue b/app/assets/javascripts/ml/model_registry/apps/show_ml_model_version.vue index a32c8019f567160ca824111535cd11fb0827f1ee..6cb4e1e924245b1d5582115d8ebb85640881bb92 100644 --- a/app/assets/javascripts/ml/model_registry/apps/show_ml_model_version.vue +++ b/app/assets/javascripts/ml/model_registry/apps/show_ml_model_version.vue @@ -256,6 +256,7 @@ export default { } }, copyMlflowId() { + // eslint-disable-next-line no-restricted-properties navigator.clipboard.writeText(this.candidate.info.eid); }, }, diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue index 69caadccc5b681b998c72600dc8ab3fd75fbea8c..0e186a2404031cd91018db22fc3ebd86016ee0f3 100644 --- a/app/assets/javascripts/repository/components/blob_content_viewer.vue +++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue @@ -379,6 +379,7 @@ export default { this.forkTarget = target; }, onCopy() { + // eslint-disable-next-line no-restricted-properties navigator.clipboard.writeText(this.blobInfo.rawTextBlob); }, handleToggleBlame() { diff --git a/app/assets/javascripts/repository/components/header_area/blob_controls.vue b/app/assets/javascripts/repository/components/header_area/blob_controls.vue index 5a3abbc5872dbc3a6a57362b86b8c4070ead12f5..6f7c8f245d654617f5792aa775fb85f98c941209 100644 --- a/app/assets/javascripts/repository/components/header_area/blob_controls.vue +++ b/app/assets/javascripts/repository/components/header_area/blob_controls.vue @@ -243,6 +243,7 @@ export default { this.trackEvent(BLAME_BUTTON_CLICK); }, onCopy() { + // eslint-disable-next-line no-restricted-properties navigator.clipboard.writeText(this.blobInfo.rawTextBlob); }, onShowForkSuggestion() { diff --git a/app/assets/javascripts/vue_shared/components/input_copy_toggle_visibility/input_copy_toggle_visibility.vue b/app/assets/javascripts/vue_shared/components/input_copy_toggle_visibility/input_copy_toggle_visibility.vue index ffcb23ae391fd6bb572fe58cf13f4d3d3393ebd3..015e26c4b11ddfbf425c56589d1a8a2126a1ece9 100644 --- a/app/assets/javascripts/vue_shared/components/input_copy_toggle_visibility/input_copy_toggle_visibility.vue +++ b/app/assets/javascripts/vue_shared/components/input_copy_toggle_visibility/input_copy_toggle_visibility.vue @@ -148,6 +148,7 @@ export default { try { // user is trying to copy from the password input, set their clipboard for them + // eslint-disable-next-line no-restricted-properties await navigator.clipboard?.writeText(this.value); this.handleCopyButtonClick(); } catch (e) { diff --git a/app/assets/javascripts/vue_shared/components/modal_copy_button.vue b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue index 93b13aaa4cac062659103d6a10e07565234d329e..8573c60510dae711d30d45b4a6341d15227484f8 100644 --- a/app/assets/javascripts/vue_shared/components/modal_copy_button.vue +++ b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue @@ -5,6 +5,11 @@ import { uniqueId } from 'lodash'; import { __ } from '~/locale'; import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; +/** + * Creates an instance of Clipboard that can works inside modals. + * + * Consider using `~/vue_shared/components/simple_copy_button.vue` instead. + */ export default { components: { GlButton, diff --git a/app/assets/javascripts/work_items/components/notes/work_item_note.vue b/app/assets/javascripts/work_items/components/notes/work_item_note.vue index 306e64e5434c1b1dd9c0335d089e8ba187b11f76..a384ba77ce4f419bffe86b6c1e299e1da7cd50fe 100644 --- a/app/assets/javascripts/work_items/components/notes/work_item_note.vue +++ b/app/assets/javascripts/work_items/components/notes/work_item_note.vue @@ -332,6 +332,7 @@ export default { }, notifyCopyDone() { if (this.isModal) { + // eslint-disable-next-line no-restricted-properties navigator.clipboard.writeText(this.noteUrl); } toast(__('Link copied to clipboard.')); diff --git a/app/assets/javascripts/work_items/components/work_item_actions.vue b/app/assets/javascripts/work_items/components/work_item_actions.vue index 8923ac93d457480d36767cd241a4dd7cbf6bfc31..de22855f53b2db54a5ea51fff793a00abd72acc3 100644 --- a/app/assets/javascripts/work_items/components/work_item_actions.vue +++ b/app/assets/javascripts/work_items/components/work_item_actions.vue @@ -428,6 +428,7 @@ export default { methods: { copyToClipboard(text, message) { if (this.isModal) { + // eslint-disable-next-line no-restricted-properties navigator.clipboard.writeText(text); } toast(message); diff --git a/app/assets/javascripts/work_items/components/work_item_development/work_item_development_branch_item.vue b/app/assets/javascripts/work_items/components/work_item_development/work_item_development_branch_item.vue index a361828e8d3a7101bd39a35e2583f1ee1f4fb6a6..aee2d8b7b0033cfe7fab23e6fd94f4b59cfe0c4c 100644 --- a/app/assets/javascripts/work_items/components/work_item_development/work_item_development_branch_item.vue +++ b/app/assets/javascripts/work_items/components/work_item_development/work_item_development_branch_item.vue @@ -63,6 +63,7 @@ export default { methods: { copyToClipboard(text, message) { if (this.isModal) { + // eslint-disable-next-line no-restricted-properties navigator.clipboard.writeText(text); } toast(message); diff --git a/app/assets/javascripts/work_items/components/work_item_development/work_item_development_mr_item.vue b/app/assets/javascripts/work_items/components/work_item_development/work_item_development_mr_item.vue index 8caa8e1057a440920270c05cca5c1c89748678f8..711f9c23e04ccecf3255108ef3a6a492caa1d6cf 100644 --- a/app/assets/javascripts/work_items/components/work_item_development/work_item_development_mr_item.vue +++ b/app/assets/javascripts/work_items/components/work_item_development/work_item_development_mr_item.vue @@ -113,6 +113,7 @@ export default { methods: { copyToClipboard(text, message) { if (this.isModal) { + // eslint-disable-next-line no-restricted-properties navigator.clipboard.writeText(text); } toast(message); diff --git a/ee/app/assets/javascripts/compliance_dashboard/components/frameworks_report/framework_info_drawer.vue b/ee/app/assets/javascripts/compliance_dashboard/components/frameworks_report/framework_info_drawer.vue index acf66d86be51d92d82dc47618d541f8e520a0959..c5b884c4bf82ea582e7afc1999416143c536497d 100644 --- a/ee/app/assets/javascripts/compliance_dashboard/components/frameworks_report/framework_info_drawer.vue +++ b/ee/app/assets/javascripts/compliance_dashboard/components/frameworks_report/framework_info_drawer.vue @@ -202,10 +202,12 @@ export default { this.after = this.projects.pageInfo.endCursor; }, copyFrameworkIdToClipboard() { + // eslint-disable-next-line no-restricted-properties navigator?.clipboard?.writeText(this.normalisedFrameworkId); this.$toast.show(this.$options.i18n.copyFrameworkIdToastText); }, copyControlIdToClipboard(control) { + // eslint-disable-next-line no-restricted-properties navigator?.clipboard?.writeText(getIdFromGraphQLId(control.id)); this.$toast.show(this.$options.i18n.copyControlIdToastText); }, diff --git a/ee/app/assets/javascripts/compliance_dashboard/components/frameworks_report/frameworks_table.vue b/ee/app/assets/javascripts/compliance_dashboard/components/frameworks_report/frameworks_table.vue index f88dbc17f289995e72f1476024060a641a9a475a..b164b0a18d27250c7b0b8fe639a0347e53d32ad9 100644 --- a/ee/app/assets/javascripts/compliance_dashboard/components/frameworks_report/frameworks_table.vue +++ b/ee/app/assets/javascripts/compliance_dashboard/components/frameworks_report/frameworks_table.vue @@ -124,6 +124,7 @@ export default { } }, copyFrameworkId(id) { + // eslint-disable-next-line no-restricted-properties navigator?.clipboard?.writeText(getIdFromGraphQLId(id)); this.$toast.show(this.$options.i18n.copyIdToastText); this.$refs[`framework-dropdown-${id}`].closeAndFocus(); diff --git a/ee/app/assets/javascripts/compliance_violations/components/discussion_note.vue b/ee/app/assets/javascripts/compliance_violations/components/discussion_note.vue index cdd47679fec833b7d79f4ccad86ff9c6050f09af..820160fdfa4a818ff8d67ff26f6283eecc548df3 100644 --- a/ee/app/assets/javascripts/compliance_violations/components/discussion_note.vue +++ b/ee/app/assets/javascripts/compliance_violations/components/discussion_note.vue @@ -90,6 +90,7 @@ export default { }, methods: { copyNoteLink() { + // eslint-disable-next-line no-restricted-properties navigator.clipboard.writeText(this.fullNoteUrl); toast(__('Link copied to clipboard.')); this.$refs.dropdown.close(); diff --git a/ee/app/assets/javascripts/pages/projects/get_started/components/command_line_modal.vue b/ee/app/assets/javascripts/pages/projects/get_started/components/command_line_modal.vue index 26c60aeaca520c618b7f5b831eb4899df330075c..b9702c74e8849e24d29be896f120a729d7af2602 100644 --- a/ee/app/assets/javascripts/pages/projects/get_started/components/command_line_modal.vue +++ b/ee/app/assets/javascripts/pages/projects/get_started/components/command_line_modal.vue @@ -101,6 +101,7 @@ export default { }, methods: { copyToClipboard(text) { + // eslint-disable-next-line no-restricted-properties navigator.clipboard.writeText(text); }, handleCopy(text, trackingLabel) { diff --git a/ee/app/assets/javascripts/product_analytics/shared/analytics_clipboard_input.vue b/ee/app/assets/javascripts/product_analytics/shared/analytics_clipboard_input.vue index ad08f746f527f837549efc9241cc3e7706e1d235..16eed08b7d2129d4cce7c212de48708a94ae1b57 100644 --- a/ee/app/assets/javascripts/product_analytics/shared/analytics_clipboard_input.vue +++ b/ee/app/assets/javascripts/product_analytics/shared/analytics_clipboard_input.vue @@ -30,6 +30,7 @@ export default { methods: { async copyValue() { try { + // eslint-disable-next-line no-restricted-properties await navigator.clipboard.writeText(this.value); this.tooltipText = this.$options.i18n.copied; } catch (error) { diff --git a/eslint.config.mjs b/eslint.config.mjs index 969e2cb09527dd4c9ae12a1fba2cda0963c17c97..7e54a8985fbc91f13deb0d71ce49de5d08099ade 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -296,7 +296,8 @@ export default [ }, { selector: "ImportSpecifier[imported.name='GlBreakpointInstance']", - message: 'GlBreakpointInstance only checks viewport breakpoints. You may want the breakpoints of a panel. Use PanelBreakpointInstance at ~/panel_breakpoint_instance instead (or add eslint-ignore here).', + message: + 'GlBreakpointInstance only checks viewport breakpoints. You may want the breakpoints of a panel. Use PanelBreakpointInstance at ~/panel_breakpoint_instance instead (or add eslint-ignore here).', }, { selector: 'Literal[value=/docs.gitlab.+\\u002Fee/]', @@ -358,6 +359,12 @@ export default [ message: 'Use `scrollTo` in `~/lib/utils/scroll_utils.js` to ensure scrolling inside your scrolling containers or panels.', }, + { + object: 'navigator', + property: 'clipboard', + message: + 'Use `copyToClipboard` in `~/lib/utils/copy_to_clipboard.js` to support copying in secure and non-secure environments.', + }, { object: 'vm', property: '$delete',