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) {
/**
* 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}
* This function may throw an error if the `clipboard-write` permission was
* not allowed.
*/
export async function copyHTML(text: string, navigator_ = navigator) {
if (!navigator_.clipboard.write) {
await copyPlainText(text, navigator_);
} else {
export async function copyHTML(
text: string,
/* istanbul ignore next - test seam */
navigator_ = navigator,
/* istanbul ignore next - test seam */
document_ = document,
) {
if (navigator_.clipboard.write) {
const type = 'text/html';
const blob = new Blob([text], { type });
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';
describe('copy-to-clipboard', () => {
const createFakeNavigator = ({ supportsWrite = true } = {}) => ({
clipboard: {
writeText: sinon.stub(),
write: supportsWrite ? sinon.stub() : undefined,
},
});
const createFakeNavigator = clipboard => ({ clipboard });
describe('copyText', () => {
beforeEach(() => {
......@@ -62,33 +57,41 @@ describe('copy-to-clipboard', () => {
describe('copyPlainText', () => {
it('writes provided text to clipboard', async () => {
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.notCalled(navigator.clipboard.write);
assert.calledWith(writeText, text);
});
});
describe('copyHTML', () => {
it('writes provided text to clipboard', async () => {
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.notCalled(navigator.clipboard.writeText);
assert.called(write);
});
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 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