Skip to content

Commit 9472883

Browse files
committed
Create a shared copy-to-clipboard method with web fallback
1 parent 75f7343 commit 9472883

File tree

3 files changed

+37
-21
lines changed

3 files changed

+37
-21
lines changed

src/components/common/copy-button.tsx

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@ import * as React from 'react';
22

33
import { Icon } from "../../icons";
44
import { styled } from '../../styles';
5-
import { logError } from '../../errors';
65

76
import { clickOnEnter } from '../component-utils';
87
import { PillButton } from './pill';
98
import { IconButton } from './icon-button';
10-
11-
const clipboardSupported = !!navigator.clipboard;
9+
import { copyToClipboard } from '../../util/ui';
1210

1311
const CopyIconButton = styled(IconButton)`
1412
color: ${p => p.theme.mainColor};
@@ -49,8 +47,6 @@ export const CopyButtonIcon = (p: {
4947
content: string,
5048
onClick: () => void
5149
}) => {
52-
if (!clipboardSupported) return null;
53-
5450
const [success, showSuccess] = useTemporaryFlag();
5551

5652
return <CopyIconButton
@@ -67,8 +63,6 @@ export const CopyButtonIcon = (p: {
6763
}
6864

6965
export const CopyButtonPill = (p: { content: string, children?: React.ReactNode }) => {
70-
if (!clipboardSupported) return null;
71-
7266
const [success, showSuccess] = useTemporaryFlag();
7367

7468
return <PillButton
@@ -85,13 +79,4 @@ export const CopyButtonPill = (p: { content: string, children?: React.ReactNode
8579
/>
8680
{ p.children }
8781
</PillButton>;
88-
}
89-
90-
async function copyToClipboard(content: string) {
91-
try {
92-
await navigator.clipboard!.writeText(content);
93-
} catch (e) {
94-
console.log('Failed to copy to the clipboard');
95-
logError(e);
96-
}
9782
}

src/components/view/filters/search-filter.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { action, computed, observable } from 'mobx';
55
import { trackUndo } from 'mobx-shallow-undo';
66

77
import { css, styled } from '../../../styles';
8-
import { isCmdCtrlPressed } from '../../../util/ui';
8+
import { copyToClipboard, isCmdCtrlPressed } from '../../../util/ui';
99

1010
import {
1111
Filter,
@@ -390,16 +390,14 @@ export class SearchFilter<T> extends React.Component<{
390390
activeFilters.indexOf(f),
391391
['desc']);
392392

393-
if (filtersToCopy.length > 0 && !!navigator.clipboard) {
393+
if (filtersToCopy.length > 0) {
394394
const serialization = filtersToCopy.map(t => t.serialize()).join(' ');
395-
navigator.clipboard.writeText(serialization);
395+
copyToClipboard(serialization);
396396
e.preventDefault();
397397
}
398398
}
399399

400400
private onCut = (e: React.ClipboardEvent) => {
401-
if (!navigator.clipboard) return;
402-
403401
this.onCopy(e);
404402
this.deleteSelectedFilters();
405403
}

src/util/ui.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,37 @@ export function useSize(ref: React.RefObject<HTMLElement>, defaultValue: number)
180180
}, []);
181181

182182
return spaceAvailable;
183+
}
184+
185+
export async function copyToClipboard(textToCopy: string) {
186+
if (navigator.clipboard) {
187+
// This will be available on secure domains in supported browsers. It requires
188+
// permissions, but not during Electron usage. We ignore permissions here -
189+
// if this fails (on secure web usage outside Electron) we'll use the fallback.
190+
try {
191+
await navigator.clipboard.writeText(textToCopy);
192+
return; // If this succeeds, we're done
193+
} catch (e) {
194+
console.warn('Copy to clipboard with navigator.clipboard failed', e);
195+
// Didn't succeed - keep going
196+
}
197+
}
198+
199+
// This should work everywhere, as long as this method is called from an
200+
// event handler:
201+
const textArea = document.createElement("textarea");
202+
try {
203+
textArea.value = textToCopy;
204+
textArea.style.position = "absolute";
205+
textArea.style.left = "-9999px";
206+
207+
document.body.prepend(textArea);
208+
textArea.select();
209+
document.execCommand('copy');
210+
} catch (e) {
211+
console.warn('Copy to clipboard fallback failed', e);
212+
throw e;
213+
} finally {
214+
textArea.remove();
215+
}
183216
}

0 commit comments

Comments
 (0)