Fix cut/copy and paste from composer

This commit is contained in:
Scott Nonnenberg 2023-05-31 11:10:02 -07:00 committed by GitHub
parent b705609341
commit 9c325ea724
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 67 additions and 38 deletions

View file

@ -1,8 +1,18 @@
diff --git a/node_modules/quill/dist/quill.js b/node_modules/quill/dist/quill.js diff --git a/node_modules/quill/dist/quill.js b/node_modules/quill/dist/quill.js
index 811b3d0..b31c7fd 100644 index 811b3d0..1082f2a 100644
--- a/node_modules/quill/dist/quill.js --- a/node_modules/quill/dist/quill.js
+++ b/node_modules/quill/dist/quill.js +++ b/node_modules/quill/dist/quill.js
@@ -8916,10 +8916,10 @@ var Clipboard = function (_Module) { @@ -8896,7 +8896,8 @@ var debug = (0, _logger2.default)('quill:clipboard');
var DOM_KEY = '__ql-matcher';
-var CLIPBOARD_CONFIG = [[Node.TEXT_NODE, matchText], [Node.TEXT_NODE, matchNewline], ['br', matchBreak], [Node.ELEMENT_NODE, matchNewline], [Node.ELEMENT_NODE, matchBlot], [Node.ELEMENT_NODE, matchSpacing], [Node.ELEMENT_NODE, matchAttributor], [Node.ELEMENT_NODE, matchStyles], ['li', matchIndent], ['b', matchAlias.bind(matchAlias, 'bold')], ['i', matchAlias.bind(matchAlias, 'italic')], ['style', matchIgnore]];
+// var CLIPBOARD_CONFIG = [[Node.TEXT_NODE, matchText], [Node.TEXT_NODE, matchNewline], ['br', matchBreak], [Node.ELEMENT_NODE, matchNewline], [Node.ELEMENT_NODE, matchBlot], [Node.ELEMENT_NODE, matchSpacing], [Node.ELEMENT_NODE, matchAttributor], [Node.ELEMENT_NODE, matchStyles], ['li', matchIndent], ['b', matchAlias.bind(matchAlias, 'bold')], ['i', matchAlias.bind(matchAlias, 'italic')], ['style', matchIgnore]];
+var CLIPBOARD_CONFIG = [[Node.TEXT_NODE, matchText], [Node.TEXT_NODE, matchNewline], ['br', matchBreak], [Node.ELEMENT_NODE, matchNewline], [Node.ELEMENT_NODE, matchSpacing]];
var ATTRIBUTE_ATTRIBUTORS = [_align.AlignAttribute, _direction.DirectionAttribute].reduce(function (memo, attr) {
memo[attr.keyName] = attr;
@@ -8916,10 +8917,10 @@ var Clipboard = function (_Module) {
var _this = _possibleConstructorReturn(this, (Clipboard.__proto__ || Object.getPrototypeOf(Clipboard)).call(this, quill, options)); var _this = _possibleConstructorReturn(this, (Clipboard.__proto__ || Object.getPrototypeOf(Clipboard)).call(this, quill, options));
@ -17,7 +27,7 @@ index 811b3d0..b31c7fd 100644
_this.matchers = []; _this.matchers = [];
CLIPBOARD_CONFIG.concat(_this.options.matchers).forEach(function (_ref) { CLIPBOARD_CONFIG.concat(_this.options.matchers).forEach(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2), var _ref2 = _slicedToArray(_ref, 2),
@@ -8941,15 +8941,18 @@ var Clipboard = function (_Module) { @@ -8941,15 +8942,18 @@ var Clipboard = function (_Module) {
key: 'convert', key: 'convert',
value: function convert(html) { value: function convert(html) {
if (typeof html === 'string') { if (typeof html === 'string') {
@ -42,7 +52,7 @@ index 811b3d0..b31c7fd 100644
var _prepareMatching = this.prepareMatching(), var _prepareMatching = this.prepareMatching(),
_prepareMatching2 = _slicedToArray(_prepareMatching, 2), _prepareMatching2 = _slicedToArray(_prepareMatching, 2),
@@ -8962,7 +8965,8 @@ var Clipboard = function (_Module) { @@ -8962,7 +8966,8 @@ var Clipboard = function (_Module) {
delta = delta.compose(new _quillDelta2.default().retain(delta.length() - 1).delete(1)); delta = delta.compose(new _quillDelta2.default().retain(delta.length() - 1).delete(1));
} }
debug.log('convert', this.container.innerHTML, delta); debug.log('convert', this.container.innerHTML, delta);
@ -52,7 +62,7 @@ index 811b3d0..b31c7fd 100644
return delta; return delta;
} }
}, { }, {
@@ -9056,9 +9060,10 @@ function applyFormat(delta, format, value) { @@ -9056,9 +9061,10 @@ function applyFormat(delta, format, value) {
} }
function computeStyle(node) { function computeStyle(node) {
@ -66,7 +76,7 @@ index 811b3d0..b31c7fd 100644
} }
function deltaEndsWith(delta, text) { function deltaEndsWith(delta, text) {
@@ -9074,7 +9079,8 @@ function deltaEndsWith(delta, text) { @@ -9074,7 +9080,8 @@ function deltaEndsWith(delta, text) {
function isLine(node) { function isLine(node) {
if (node.childNodes.length === 0) return false; // Exclude embed blocks if (node.childNodes.length === 0) return false; // Exclude embed blocks
var style = computeStyle(node); var style = computeStyle(node);
@ -76,7 +86,7 @@ index 811b3d0..b31c7fd 100644
} }
function traverse(node, elementMatchers, textMatchers) { function traverse(node, elementMatchers, textMatchers) {
@@ -9177,8 +9183,10 @@ function matchIndent(node, delta) { @@ -9177,8 +9184,10 @@ function matchIndent(node, delta) {
} }
function matchNewline(node, delta) { function matchNewline(node, delta) {

View file

@ -187,7 +187,7 @@ import { setBatchingStrategy } from './util/messageBatcher';
import { parseRemoteClientExpiration } from './util/parseRemoteClientExpiration'; import { parseRemoteClientExpiration } from './util/parseRemoteClientExpiration';
import { makeLookup } from './util/makeLookup'; import { makeLookup } from './util/makeLookup';
import { addGlobalKeyboardShortcuts } from './services/addGlobalKeyboardShortcuts'; import { addGlobalKeyboardShortcuts } from './services/addGlobalKeyboardShortcuts';
import { handleCopyEvent } from './quill/signal-clipboard/util'; import { createEventHandler } from './quill/signal-clipboard/util';
export function isOverHourIntoPast(timestamp: number): boolean { export function isOverHourIntoPast(timestamp: number): boolean {
return isNumber(timestamp) && isOlderThan(timestamp, HOUR); return isNumber(timestamp) && isOlderThan(timestamp, HOUR);
@ -551,7 +551,14 @@ export async function startApp(): Promise<void> {
); );
// Intercept clipboard copies to add our custom text/signal data // Intercept clipboard copies to add our custom text/signal data
document.addEventListener('copy', handleCopyEvent); document.addEventListener(
'copy',
createEventHandler({ deleteSelection: false })
);
document.addEventListener(
'cut',
createEventHandler({ deleteSelection: true })
);
startInteractionMode(); startInteractionMode();

View file

@ -24,13 +24,6 @@ export class SignalClipboard {
this.quill = quill; this.quill = quill;
this.quill.root.addEventListener('paste', e => this.onCapturePaste(e)); this.quill.root.addEventListener('paste', e => this.onCapturePaste(e));
const clipboard = this.quill.getModule('clipboard');
// We keep just the first few matchers (for spacing) then drop the rest!
clipboard.matchers = clipboard.matchers
.slice(0, 4)
.concat(clipboard.matchers.slice(11));
} }
onCapturePaste(event: ClipboardEvent): void { onCapturePaste(event: ClipboardEvent): void {

View file

@ -1,36 +1,51 @@
// Copyright 2023 Signal Messenger, LLC // Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
export function handleCopyEvent(event: ClipboardEvent): void { const QUILL_EMBED_GUARD = '\uFEFF';
if (!event.clipboardData) {
return;
}
const selection = window.getSelection(); export function createEventHandler({
if (!selection) { deleteSelection,
return; }: {
} deleteSelection: boolean;
}) {
return (event: ClipboardEvent): void => {
if (!event.clipboardData) {
return;
}
// Create synthetic html with the full selection we can put into clipboard const selection = window.getSelection();
const container = document.createElement('div'); if (!selection) {
for (let i = 0, max = selection.rangeCount; i < max; i += 1) { return;
const range = selection.getRangeAt(i); }
container.appendChild(range.cloneContents());
}
// Note: we can't leave text/plain alone and just add text/signal; if we update // Create synthetic html with the full selection we can put into clipboard
// clipboardData at all, all other data is reset. const container = document.createElement('div');
const plaintext = getStringFromNode(container); for (let i = 0, max = selection.rangeCount; i < max; i += 1) {
event.clipboardData?.setData('text/plain', plaintext); const range = selection.getRangeAt(i);
container.appendChild(range.cloneContents());
}
event.clipboardData?.setData('text/signal', container.innerHTML); // Note: we can't leave text/plain alone and just add text/signal; if we update
// clipboardData at all, all other data is reset.
const plaintext = getStringFromNode(container);
event.clipboardData?.setData('text/plain', plaintext);
event.preventDefault(); event.clipboardData?.setData('text/signal', container.innerHTML);
event.stopPropagation();
if (deleteSelection) {
selection.deleteFromDocument();
}
event.preventDefault();
event.stopPropagation();
};
} }
function getStringFromNode(node: Node): string { function getStringFromNode(node: Node): string {
if (node.nodeType === Node.TEXT_NODE) { if (node.nodeType === Node.TEXT_NODE) {
if (node.textContent === QUILL_EMBED_GUARD) {
return '';
}
return node.textContent || ''; return node.textContent || '';
} }
if (node.nodeType !== Node.ELEMENT_NODE) { if (node.nodeType !== Node.ELEMENT_NODE) {
@ -38,7 +53,11 @@ function getStringFromNode(node: Node): string {
} }
const element = node as Element; const element = node as Element;
if (element.nodeName === 'IMG' && element.classList.contains('emoji')) { if (
element.nodeName === 'IMG' &&
(element.classList.contains('emoji') ||
element.classList.contains('emoji-blot'))
) {
return element.ariaLabel || ''; return element.ariaLabel || '';
} }
if (element.nodeName === 'BR') { if (element.nodeName === 'BR') {