Commit 5e632d59 authored by Alejandro Celaya's avatar Alejandro Celaya Committed by Alejandro Celaya

Fallback to document.execCommand when copying HTML to clipboard

parent 5c969487
...@@ -47,19 +47,35 @@ export async function copyPlainText(text: string, navigator_ = navigator) { ...@@ -47,19 +47,35 @@ export async function copyPlainText(text: string, navigator_ = navigator) {
/** /**
* Copy the string `text` to the clipboard with an HTML media type. * Copy the string `text` to the clipboard with an HTML media type.
* *
* If the browser does not support this, it will fall back to copy the string
* as plain text.
*
* @throws {Error} * @throws {Error}
* This function may throw an error if the `clipboard-write` permission was * This function may throw an error if the `clipboard-write` permission was
* not allowed. * not allowed.
*/ */
export async function copyHTML(text: string, navigator_ = navigator) { export async function copyHTML(
if (!navigator_.clipboard.write) { text: string,
await copyPlainText(text, navigator_); /* istanbul ignore next - test seam */
} else { navigator_ = navigator,
/* istanbul ignore next - test seam */
document_ = document,
) {
if (navigator_.clipboard.write) {
const type = 'text/html'; const type = 'text/html';
const blob = new Blob([text], { type }); const blob = new Blob([text], { type });
await navigator_.clipboard.write([new ClipboardItem({ [type]: blob })]); await navigator_.clipboard.write([new ClipboardItem({ [type]: blob })]);
} else {
// Fallback to deprecated document.execCommand('copy') on the assumptions
// that all browsers will implement the new clipboard API before removing
// the deprecated one.
const copyHandler = (e: ClipboardEvent) => {
e.clipboardData?.setData('text/html', text);
e.preventDefault();
};
document_.addEventListener('copy', copyHandler);
try {
document_.execCommand('copy');
} finally {
document_.removeEventListener('copy', copyHandler);
}
} }
} }
import { copyPlainText, copyHTML, copyText } from '../copy-to-clipboard'; import { copyPlainText, copyHTML, copyText } from '../copy-to-clipboard';
describe('copy-to-clipboard', () => { describe('copy-to-clipboard', () => {
const createFakeNavigator = ({ supportsWrite = true } = {}) => ({ const createFakeNavigator = clipboard => ({ clipboard });
clipboard: {
writeText: sinon.stub(),
write: supportsWrite ? sinon.stub() : undefined,
},
});
describe('copyText', () => { describe('copyText', () => {
beforeEach(() => { beforeEach(() => {
...@@ -62,33 +57,41 @@ describe('copy-to-clipboard', () => { ...@@ -62,33 +57,41 @@ describe('copy-to-clipboard', () => {
describe('copyPlainText', () => { describe('copyPlainText', () => {
it('writes provided text to clipboard', async () => { it('writes provided text to clipboard', async () => {
const text = 'Lorem ipsum dolor sit amet'; const text = 'Lorem ipsum dolor sit amet';
const navigator = createFakeNavigator(); const writeText = sinon.stub();
await copyPlainText(text, navigator); await copyPlainText(text, createFakeNavigator({ writeText }));
assert.calledWith(navigator.clipboard.writeText, text); assert.calledWith(writeText, text);
assert.notCalled(navigator.clipboard.write);
}); });
}); });
describe('copyHTML', () => { describe('copyHTML', () => {
it('writes provided text to clipboard', async () => { it('writes provided text to clipboard', async () => {
const text = 'Lorem ipsum dolor sit amet'; const text = 'Lorem ipsum dolor sit amet';
const navigator = createFakeNavigator(); const write = sinon.stub();
await copyHTML(text, navigator); await copyHTML(text, createFakeNavigator({ write }));
assert.called(navigator.clipboard.write); assert.called(write);
assert.notCalled(navigator.clipboard.writeText);
}); });
it('falls back to plain text if rich text is not supported', async () => { it('falls back to execCommand if clipboard API is not supported', async () => {
const text = 'Lorem ipsum dolor sit amet'; const text = 'Lorem ipsum dolor sit amet';
const navigator = createFakeNavigator({ supportsWrite: false }); const clipboardData = new DataTransfer();
const document = Object.assign(new EventTarget(), {
execCommand: sinon.stub().callsFake(command => {
if (command === 'copy') {
document.dispatchEvent(
new ClipboardEvent('copy', { clipboardData }),
);
}
}),
});
await copyHTML(text, navigator); await copyHTML(text, createFakeNavigator({}), document);
assert.calledWith(navigator.clipboard.writeText, text); assert.calledWith(document.execCommand, 'copy');
assert.equal(clipboardData.getData('text/html'), text);
}); });
}); });
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment