2020-11-06 20:11:18 +00:00
|
|
|
// Copyright 2020 Signal Messenger, LLC
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2021-10-26 19:15:33 +00:00
|
|
|
import type Quill from 'quill';
|
2020-11-12 01:01:45 +00:00
|
|
|
import Delta from 'quill-delta';
|
|
|
|
|
2020-11-06 20:11:18 +00:00
|
|
|
import { getTextFromOps } from '../util';
|
|
|
|
|
|
|
|
const getSelectionHTML = () => {
|
|
|
|
const selection = window.getSelection();
|
|
|
|
|
|
|
|
if (selection === null) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
const range = selection.getRangeAt(0);
|
|
|
|
const contents = range.cloneContents();
|
|
|
|
const div = document.createElement('div');
|
|
|
|
|
|
|
|
div.appendChild(contents);
|
|
|
|
|
|
|
|
return div.innerHTML;
|
|
|
|
};
|
|
|
|
|
2020-11-19 17:38:25 +00:00
|
|
|
const replaceAngleBrackets = (text: string) => {
|
|
|
|
const entities: Array<[RegExp, string]> = [
|
|
|
|
[/&/g, '&'],
|
|
|
|
[/</g, '<'],
|
|
|
|
[/>/g, '>'],
|
|
|
|
];
|
|
|
|
|
|
|
|
return entities.reduce(
|
|
|
|
(acc, [re, replaceValue]) => acc.replace(re, replaceValue),
|
|
|
|
text
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2020-11-06 20:11:18 +00:00
|
|
|
export class SignalClipboard {
|
|
|
|
quill: Quill;
|
|
|
|
|
|
|
|
constructor(quill: Quill) {
|
|
|
|
this.quill = quill;
|
|
|
|
|
|
|
|
this.quill.root.addEventListener('copy', e => this.onCaptureCopy(e, false));
|
|
|
|
this.quill.root.addEventListener('cut', e => this.onCaptureCopy(e, true));
|
2020-11-12 01:01:45 +00:00
|
|
|
this.quill.root.addEventListener('paste', e => this.onCapturePaste(e));
|
2020-11-06 20:11:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
onCaptureCopy(event: ClipboardEvent, isCut = false): void {
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
if (event.clipboardData === null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const range = this.quill.getSelection();
|
|
|
|
|
|
|
|
if (range === null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const contents = this.quill.getContents(range.index, range.length);
|
|
|
|
|
|
|
|
if (contents === null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { ops } = contents;
|
|
|
|
|
|
|
|
if (ops === undefined) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const text = getTextFromOps(ops);
|
|
|
|
const html = getSelectionHTML();
|
|
|
|
|
|
|
|
event.clipboardData.setData('text/plain', text);
|
2020-11-12 01:01:45 +00:00
|
|
|
event.clipboardData.setData('text/signal', html);
|
2020-11-06 20:11:18 +00:00
|
|
|
|
|
|
|
if (isCut) {
|
|
|
|
this.quill.deleteText(range.index, range.length, 'user');
|
|
|
|
}
|
|
|
|
}
|
2020-11-12 01:01:45 +00:00
|
|
|
|
|
|
|
onCapturePaste(event: ClipboardEvent): void {
|
|
|
|
if (event.clipboardData === null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.quill.focus();
|
|
|
|
|
|
|
|
const clipboard = this.quill.getModule('clipboard');
|
|
|
|
const selection = this.quill.getSelection();
|
|
|
|
|
|
|
|
if (selection === null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const text = event.clipboardData.getData('text/plain');
|
|
|
|
const html = event.clipboardData.getData('text/signal');
|
|
|
|
|
2020-11-19 17:38:25 +00:00
|
|
|
const clipboardDelta = html
|
|
|
|
? clipboard.convert(html)
|
|
|
|
: clipboard.convert(replaceAngleBrackets(text));
|
|
|
|
|
2020-11-12 01:01:45 +00:00
|
|
|
const { scrollTop } = this.quill.scrollingContainer;
|
|
|
|
|
|
|
|
this.quill.selection.update('silent');
|
|
|
|
|
|
|
|
if (selection) {
|
|
|
|
setTimeout(() => {
|
|
|
|
const delta = new Delta()
|
|
|
|
.retain(selection.index)
|
2020-11-19 17:38:25 +00:00
|
|
|
.concat(clipboardDelta);
|
2020-11-12 01:01:45 +00:00
|
|
|
this.quill.updateContents(delta, 'user');
|
|
|
|
this.quill.setSelection(delta.length(), 0, 'silent');
|
|
|
|
this.quill.scrollingContainer.scrollTop = scrollTop;
|
|
|
|
}, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
2020-11-06 20:11:18 +00:00
|
|
|
}
|