Merge "Hide suggest edit button in permanent editing mode"
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index b079dec..04a9fea 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {BehaviorSubject} from 'rxjs';
+import '../gr-copy-links/gr-copy-links';
import '@polymer/paper-tabs/paper-tabs';
import '../../../styles/gr-a11y-styles';
import '../../../styles/gr-paper-styles';
@@ -178,6 +179,9 @@
import {subscribe} from '../../lit/subscription-controller';
import {configModelToken} from '../../../models/config/config-model';
import {filesModelToken} from '../../../models/change/files-model';
+import {getBaseUrl, prependOrigin} from '../../../utils/url-util';
+import {CopyLink, GrCopyLinks} from '../gr-copy-links/gr-copy-links';
+import {KnownExperimentId} from '../../../services/flags/flags';
const MIN_LINES_FOR_COMMIT_COLLAPSE = 18;
@@ -267,6 +271,8 @@
@query('gr-thread-list') threadList?: GrThreadList;
+ @query('gr-copy-links') private copyLinksDropdown?: GrCopyLinks;
+
/**
* URL params passed from the router.
* Use params getter/setter.
@@ -529,6 +535,8 @@
readonly restApiService = getAppContext().restApiService;
+ private readonly flagsService = getAppContext().flagsService;
+
// Private but used in tests.
readonly userModel = getAppContext().userModel;
@@ -619,6 +627,7 @@
// TODO: Do we still need docOnly bindings?
this.shortcutsController.addAbstract(Shortcut.SEND_REPLY, () => {}); // docOnly
this.shortcutsController.addAbstract(Shortcut.EMOJI_DROPDOWN, () => {}); // docOnly
+ this.shortcutsController.addAbstract(Shortcut.MENTIONS_DROPDOWN, () => {}); // docOnly
this.shortcutsController.addAbstract(Shortcut.REFRESH_CHANGE, () =>
fireReload(this, true)
);
@@ -675,6 +684,10 @@
this.shortcutsController.addAbstract(Shortcut.TOGGLE_ATTENTION_SET, () =>
this.handleToggleAttentionSet()
);
+ this.shortcutsController.addAbstract(
+ Shortcut.OPEN_COPY_LINKS_DROPDOWN,
+ () => this.copyLinksDropdown?.openDropdown()
+ );
}
private setupSubscriptions() {
@@ -891,6 +904,9 @@
.changeCopyClipboard {
margin-left: var(--spacing-s);
}
+ .showCopyLinkDialogButton {
+ --gr-button-padding: var(--spacing-m) var(--spacing-xs);
+ }
#replyBtn {
margin-bottom: var(--spacing-m);
}
@@ -1256,23 +1272,73 @@
?hidden=${!this.loggedIn}
></gr-change-star>
- <a
- class="changeNumber"
- aria-label=${`Change ${this.change?._number}`}
- href=${ifDefined(this.computeChangeUrl(true))}
- >${this.change?._number}</a
- >
- <span class="changeNumberColon">: </span>
- <span class="headerSubject">${this.change?.subject}</span>
- <gr-copy-clipboard
- class="changeCopyClipboard"
- hideInput=""
- text=${this.computeCopyTextForTitle()}
- >
- </gr-copy-clipboard>
+ ${when(
+ this.flagsService.isEnabled(KnownExperimentId.COPY_LINK_DIALOG),
+ () => html`<a
+ class="changeNumber"
+ aria-label=${`Change ${this.change?._number}`}
+ href=${ifDefined(this.computeChangeUrl(true))}
+ >${this.change?._number}</a
+ ><gr-button
+ flatten
+ down-arrow
+ class="showCopyLinkDialogButton"
+ @click=${() => this.copyLinksDropdown?.toggleDropdown()}
+ ></gr-button>
+ ${this.renderCopyLinksDropdown()}
+ <span class="headerSubject">${this.change?.subject}</span>`,
+ () => html`<a
+ class="changeNumber"
+ aria-label=${`Change ${this.change?._number}`}
+ href=${ifDefined(this.computeChangeUrl(true))}
+ >${this.change?._number}</a
+ >
+ <span class="changeNumberColon">: </span>
+ <span class="headerSubject">${this.change?.subject}</span>
+ <gr-copy-clipboard
+ class="changeCopyClipboard"
+ hideInput=""
+ text=${this.computeCopyTextForTitle()}
+ >
+ </gr-copy-clipboard>`
+ )}
</div>`;
}
+ private renderCopyLinksDropdown() {
+ const url = this.computeChangeUrl();
+ if (!url) return;
+ const changeURL = prependOrigin(getBaseUrl() + url);
+ const links: CopyLink[] = [
+ {
+ label: 'Change Number',
+ shortcut: 'n',
+ value: `${this.change?._number}`,
+ },
+ {
+ label: 'Change URL',
+ shortcut: 'u',
+ value: changeURL,
+ },
+ {
+ label: 'Title and URL',
+ shortcut: 't',
+ value: `${this.change?._number}: ${this.change?.subject} | ${changeURL}`,
+ },
+ {
+ label: 'URL and title',
+ shortcut: 'r',
+ value: `${changeURL}: ${this.change?.subject}`,
+ },
+ {
+ label: 'Change-Id',
+ shortcut: 'd',
+ value: `${this.change?.id.split('~').pop()}`,
+ },
+ ];
+ return html`<gr-copy-links .copyLinks=${links}> </gr-copy-links>`;
+ }
+
private renderCommitActions() {
return html` <div class="commitActions">
<!-- always show gr-change-actions regardless if logged in or not -->
diff --git a/polygerrit-ui/app/elements/change/gr-copy-links/gr-copy-links.ts b/polygerrit-ui/app/elements/change/gr-copy-links/gr-copy-links.ts
new file mode 100644
index 0000000..4b76625
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-copy-links/gr-copy-links.ts
@@ -0,0 +1,160 @@
+/**
+ * @license
+ * Copyright 2022 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import '@polymer/iron-dropdown/iron-dropdown';
+import '../../shared/gr-copy-clipboard/gr-copy-clipboard';
+import {LitElement, html, css, nothing} from 'lit';
+import {customElement, property, query, state} from 'lit/decorators.js';
+import {strToClassName} from '../../../utils/dom-util';
+import {IronDropdownElement} from '@polymer/iron-dropdown/iron-dropdown';
+import {copyToClipbard, queryAndAssert} from '../../../utils/common-util';
+import {ValueChangedEvent} from '../../../types/events';
+
+export interface CopyLink {
+ label: string;
+ shortcut: string;
+ value: string;
+}
+
+const AWAIT_MAX_ITERS = 10;
+const AWAIT_STEP = 5;
+
+@customElement('gr-copy-links')
+export class GrCopyLinks extends LitElement {
+ @property({type: Array})
+ copyLinks: CopyLink[] = [];
+
+ @state() isDropdownOpen = false;
+
+ @query('iron-dropdown') private dropdown?: IronDropdownElement;
+
+ static override get styles() {
+ return [
+ css`
+ iron-dropdown {
+ box-shadow: var(--elevation-level-2);
+ width: min(90vw, 540px);
+ background-color: var(--dialog-background-color);
+ border-radius: var(--border-radius);
+ }
+ [slot='dropdown-content'] {
+ padding: var(--spacing-m) var(--spacing-l) var(--spacing-m);
+ }
+ .copy-link-row {
+ margin-bottom: var(--spacing-m);
+ display: flex;
+ align-items: center;
+ }
+ .copy-link-row label {
+ flex: 0 0 120px;
+ color: var(--deemphasized-text-color);
+ }
+ .copy-link-row input {
+ width: 320px;
+ }
+ .copy-link-row .shortcut {
+ width: 25px;
+ margin: 0 var(--spacing-m);
+ color: var(--deemphasized-text-color);
+ }
+ .copy-link-row gr-copy-clipboard {
+ flex: 0 0 20px;
+ }
+ /* TODO(milutin): It's from shared styles, move it to input styles */
+ input {
+ background-color: var(--background-color-primary);
+ border: 1px solid var(--border-color);
+ border-radius: var(--border-radius);
+ box-sizing: border-box;
+ color: var(--primary-text-color);
+ margin: 0;
+ padding: var(--spacing-s);
+ font: inherit;
+ }
+ `,
+ ];
+ }
+
+ override render() {
+ if (!this.copyLinks) return nothing;
+ return html`<iron-dropdown
+ .horizontalAlign=${'left'}
+ .verticalAlign=${'top'}
+ .verticalOffset=${24}
+ @keydown=${this.handleKeydown}
+ @opened-changed=${(e: ValueChangedEvent<boolean>) =>
+ (this.isDropdownOpen = e.detail.value)}
+ >
+ ${this.renderCopyLinks()}
+ </iron-dropdown>`;
+ }
+
+ private renderCopyLinks() {
+ return html`<div slot="dropdown-content">
+ ${this.copyLinks?.map(link => this.renderCopyLinkRow(link))}
+ </div>`;
+ }
+
+ private renderCopyLinkRow(copyLink: CopyLink) {
+ const {label, shortcut, value} = copyLink;
+ const id = `${strToClassName(label, '')}-field`;
+ // TODO(milutin): Use input in gr-copy-clipboard instead of creating new
+ // one. Move shorcut to gr-copy-clipboard.
+ return html`<div class="copy-link-row">
+ <label for=${id}>${label}</label
+ ><input type="text" readonly="" id=${id} class="input" .value=${value} />
+ <span class="shortcut">${`l - ${shortcut}`}</span>
+ <gr-copy-clipboard hideInput="" text=${value}></gr-copy-clipboard>
+ </div>`;
+ }
+
+ private async handleKeydown(e: KeyboardEvent) {
+ const copyLink = this.copyLinks?.find(link => link.shortcut === e.key);
+ if (!copyLink) return;
+ await copyToClipbard(copyLink.value, copyLink.label);
+ this.closeDropdown();
+ }
+
+ toggleDropdown() {
+ this.isDropdownOpen ? this.closeDropdown() : this.openDropdown();
+ }
+
+ private closeDropdown() {
+ this.dropdown?.close();
+ }
+
+ openDropdown() {
+ this.dropdown?.open();
+ this.awaitOpen(() => {
+ queryAndAssert<HTMLInputElement>(this.dropdown, 'input')?.select();
+ });
+ }
+
+ /**
+ * NOTE: (milutin) Slightly hacky way to listen to the overlay actually
+ * opening. It's from gr-editable-label. It will be removed when we
+ * migrate out of iron-* components.
+ */
+ private awaitOpen(fn: () => void) {
+ let iters = 0;
+ const step = () => {
+ setTimeout(() => {
+ if (this.dropdown?.style.display !== 'none') {
+ fn.call(this);
+ } else if (iters++ < AWAIT_MAX_ITERS) {
+ step.call(this);
+ }
+ }, AWAIT_STEP);
+ };
+ step.call(this);
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'gr-copy-links': GrCopyLinks;
+ }
+}
diff --git a/polygerrit-ui/app/elements/change/gr-copy-links/gr-copy-links_test.ts b/polygerrit-ui/app/elements/change/gr-copy-links/gr-copy-links_test.ts
new file mode 100644
index 0000000..f184acd
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-copy-links/gr-copy-links_test.ts
@@ -0,0 +1,85 @@
+/**
+ * @license
+ * Copyright 2022 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import '../../../test/common-test-setup-karma';
+import {fixture, html, assert} from '@open-wc/testing';
+import './gr-copy-links';
+import {GrCopyLinks} from './gr-copy-links';
+import {pressKey, queryAndAssert, waitUntil} from '../../../test/test-utils';
+import {IronDropdownElement} from '@polymer/iron-dropdown';
+import {GrButton} from '../../shared/gr-button/gr-button';
+import {GrCopyClipboard} from '../../shared/gr-copy-clipboard/gr-copy-clipboard';
+
+suite('gr-copy-links tests', () => {
+ let element: GrCopyLinks;
+ setup(async () => {
+ const links = [
+ {
+ label: 'Change ID',
+ shortcut: 'd',
+ value: '123456',
+ },
+ ];
+ element = await fixture<GrCopyLinks>(
+ html`<gr-copy-links .copyLinks=${links}></gr-copy-links>`
+ );
+ await element.updateComplete;
+ element.openDropdown();
+ await waitUntil(() => element.isDropdownOpen);
+ await element.updateComplete;
+ });
+
+ test('renders', () => {
+ assert.shadowDom.equal(
+ element,
+ /* HTML */ `<iron-dropdown
+ aria-disabled="false"
+ horizontal-align="left"
+ vertical-align="top"
+ >
+ <div slot="dropdown-content">
+ <div class="copy-link-row">
+ <label for="Change_ID-field">Change ID</label>
+ <input
+ class="input"
+ id="Change_ID-field"
+ readonly=""
+ type="text"
+ >
+ <span class="shortcut">l - d</span>
+ <gr-copy-clipboard hideinput="" text="123456">
+ </gr-copy-clipboard>
+ </div>
+ </iron-dropdown>`,
+ {
+ // iron-dropdown sizing seems to vary between local & CI
+ ignoreAttributes: [{tags: ['iron-dropdown'], attributes: ['style']}],
+ }
+ );
+ });
+
+ test('click writes to clipboard', () => {
+ const clipboardStub = sinon.stub(navigator.clipboard, 'writeText');
+ const copyClipboard = queryAndAssert<GrCopyClipboard>(
+ element,
+ 'gr-copy-clipboard'
+ );
+ const copyBtn = queryAndAssert<GrButton>(copyClipboard, '.copyToClipboard');
+ copyBtn.click();
+ assert.isTrue(clipboardStub.called);
+ assert.isTrue(clipboardStub.calledWith('123456'));
+ });
+
+ test('shorcuts writes to clipboard', () => {
+ const clipboardStub = sinon.stub(window.navigator.clipboard, 'writeText');
+ const ironDropdown = queryAndAssert<IronDropdownElement>(
+ element,
+ 'iron-dropdown'
+ );
+ pressKey(ironDropdown, 'd');
+ assert.isTrue(clipboardStub.called);
+ assert.isTrue(clipboardStub.calledWith('123456'));
+ });
+});
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
index 5758ef2..6a1e451 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
@@ -8,9 +8,13 @@
import {ChangeInfo, DownloadInfo, PatchSetNum} from '../../../types/common';
import {GrDownloadCommands} from '../../shared/gr-download-commands/gr-download-commands';
import {GrButton} from '../../shared/gr-button/gr-button';
-import {hasOwnProperty, queryAndAssert} from '../../../utils/common-util';
+import {
+ copyToClipbard,
+ hasOwnProperty,
+ queryAndAssert,
+} from '../../../utils/common-util';
import {GrOverlayStops} from '../../shared/gr-overlay/gr-overlay';
-import {fireAlert, fireEvent} from '../../../utils/event-util';
+import {fireEvent} from '../../../utils/event-util';
import {addShortcut} from '../../../utils/dom-util';
import {fontStyles} from '../../../styles/gr-font-styles';
import {sharedStyles} from '../../../styles/shared-styles';
@@ -227,14 +231,15 @@
return [];
}
- private handleNumberKey(e: KeyboardEvent) {
+ private async handleNumberKey(e: KeyboardEvent) {
const index = Number(e.key) - 1;
const commands = this.computeDownloadCommands();
if (index > commands.length) return;
- navigator.clipboard.writeText(commands[index].command).then(() => {
- fireAlert(this, `${commands[index].title} command copied to clipboard`);
- fireEvent(this, 'close');
- });
+ await copyToClipbard(
+ commands[index].command,
+ `${commands[index].title} command`
+ );
+ fireEvent(this, 'close');
}
override focus() {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index 5b0b83b..2773675 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -993,6 +993,7 @@
@comment-text-changed=${(e: ValueChangedEvent<string>) => {
this.patchsetLevelDraftMessage = e.detail.value;
}}
+ .messagePlaceholder=${this.messagePlaceholder}
hide-header
permanent-editing-mode
></gr-comment>
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
index 8f23070..6dc33ce 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
@@ -52,8 +52,8 @@
import {GrButton} from '../gr-button/gr-button';
import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
import {DiffLayer, RenderPreferences} from '../../../api/diff';
-import {assertIsDefined} from '../../../utils/common-util';
-import {fire, fireAlert} from '../../../utils/event-util';
+import {assertIsDefined, copyToClipbard} from '../../../utils/common-util';
+import {fire} from '../../../utils/event-util';
import {GrSyntaxLayerWorker} from '../../../embed/diff/gr-syntax-layer/gr-syntax-layer-worker';
import {TokenHighlightLayer} from '../../../embed/diff/gr-diff-builder/token-highlight-layer';
import {anyLineTooLong} from '../../../embed/diff/gr-diff/gr-diff-utils';
@@ -777,9 +777,7 @@
GerritNav.getUrlForCommentsTab(this.changeNum, this.repoName, comment.id)
);
assertIsDefined(url, 'url for comment');
- navigator.clipboard.writeText(generateAbsoluteUrl(url)).then(() => {
- fireAlert(this, 'Link copied to clipboard');
- });
+ copyToClipbard(generateAbsoluteUrl(url), 'Link');
}
private getDisplayPath() {
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index a552e3d..72721c3 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -164,6 +164,9 @@
@property({type: Boolean, attribute: 'robot-button-disabled'})
robotButtonDisabled = false;
+ @property({type: String})
+ messagePlaceholder?: string;
+
/* private, but used in css rules */
@property({type: Boolean, reflect: true})
saving = false;
@@ -679,6 +682,7 @@
code=""
?disabled=${this.saving}
rows="4"
+ .placeholder=${this.messagePlaceholder}
text=${this.messageText}
@text-changed=${(e: ValueChangedEvent) => {
// TODO: This is causing a re-render of <gr-comment> on every key
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
index f870278..0e9b874 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
@@ -6,7 +6,11 @@
import '@polymer/iron-input/iron-input';
import '../gr-button/gr-button';
import '../gr-icon/gr-icon';
-import {assertIsDefined, queryAndAssert} from '../../../utils/common-util';
+import {
+ assertIsDefined,
+ copyToClipbard,
+ queryAndAssert,
+} from '../../../utils/common-util';
import {classMap} from 'lit/directives/class-map.js';
import {ifDefined} from 'lit/directives/if-defined.js';
import {LitElement, css, html} from 'lit';
@@ -58,6 +62,8 @@
font-size: var(--font-size-mono);
line-height: var(--line-height-mono);
width: 100%;
+ background-color: var(--view-background-color);
+ color: var(--primary-text-color);
}
gr-icon {
color: var(--deemphasized-text-color);
@@ -127,7 +133,7 @@
this.text = queryAndAssert<HTMLInputElement>(this, '#input').value;
assertIsDefined(this.text, 'text');
this.iconEl.icon = 'check';
- navigator.clipboard.writeText(this.text);
+ copyToClipbard(this.text, 'Link');
setTimeout(() => (this.iconEl.icon = 'content_copy'), COPY_TIMEOUT_MS);
}
}
diff --git a/polygerrit-ui/app/services/flags/flags.ts b/polygerrit-ui/app/services/flags/flags.ts
index 9ca2cef..25c057e 100644
--- a/polygerrit-ui/app/services/flags/flags.ts
+++ b/polygerrit-ui/app/services/flags/flags.ts
@@ -25,4 +25,5 @@
PATCHSET_LEVEL_COMMENT_USES_GRCOMMENT = 'UiFeature__patchset_level_comment_uses_GrComment',
RENDER_MARKDOWN = 'UiFeature__render_markdown',
AUTO_APP_THEME = 'UiFeature__auto_app_theme',
+ COPY_LINK_DIALOG = 'UiFeature__copy_link_dialog',
}
diff --git a/polygerrit-ui/app/services/shortcuts/shortcuts-config.ts b/polygerrit-ui/app/services/shortcuts/shortcuts-config.ts
index 2996e99..a7f4d19 100644
--- a/polygerrit-ui/app/services/shortcuts/shortcuts-config.ts
+++ b/polygerrit-ui/app/services/shortcuts/shortcuts-config.ts
@@ -49,6 +49,7 @@
OPEN_REPLY_DIALOG = 'OPEN_REPLY_DIALOG',
OPEN_DOWNLOAD_DIALOG = 'OPEN_DOWNLOAD_DIALOG',
+ OPEN_COPY_LINKS_DROPDOWN = 'OPEN_COPY_LINKS_DROPDOWN',
EXPAND_ALL_MESSAGES = 'EXPAND_ALL_MESSAGES',
COLLAPSE_ALL_MESSAGES = 'COLLAPSE_ALL_MESSAGES',
UP_TO_DASHBOARD = 'UP_TO_DASHBOARD',
@@ -100,6 +101,7 @@
SEARCH = 'SEARCH',
SEND_REPLY = 'SEND_REPLY',
EMOJI_DROPDOWN = 'EMOJI_DROPDOWN',
+ MENTIONS_DROPDOWN = 'MENTIONS_DROPDOWN',
TOGGLE_BLAME = 'TOGGLE_BLAME',
TOGGLE_CHECKBOX = 'TOGGLE_CHECKBOX',
@@ -222,6 +224,12 @@
{key: 'd'}
);
describe(
+ Shortcut.OPEN_COPY_LINKS_DROPDOWN,
+ ShortcutSection.ACTIONS,
+ 'Open link dialog',
+ {key: 'l'}
+ );
+ describe(
Shortcut.EXPAND_ALL_MESSAGES,
ShortcutSection.ACTIONS,
'Expand all messages',
@@ -523,5 +531,11 @@
'Emoji dropdown',
{key: ':', docOnly: true}
);
+ describe(
+ Shortcut.MENTIONS_DROPDOWN,
+ ShortcutSection.REPLY_DIALOG,
+ 'Mentions dropdown',
+ {key: '@', docOnly: true}
+ );
return config;
}
diff --git a/polygerrit-ui/app/utils/common-util.ts b/polygerrit-ui/app/utils/common-util.ts
index 0c99269..05c054b 100644
--- a/polygerrit-ui/app/utils/common-util.ts
+++ b/polygerrit-ui/app/utils/common-util.ts
@@ -4,6 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
+import {fireAlert} from './event-util';
+
/**
* @fileoverview Functions in this file contains some widely used
* code patterns. If you noticed a repeated code and none of the existing util
@@ -160,3 +162,8 @@
): T[] {
return a.filter(aVal => !b.some(bVal => compareBy(aVal, bVal)));
}
+
+export async function copyToClipbard(text: string, copyTargetName?: string) {
+ await navigator.clipboard.writeText(text);
+ fireAlert(document, `${copyTargetName ?? text} was copied to clipboard`);
+}