diff --git a/patches/quill+1.3.7.patch b/patches/quill+1.3.7.patch index 75a0f54f0..7205d7352 100644 --- a/patches/quill+1.3.7.patch +++ b/patches/quill+1.3.7.patch @@ -1,8 +1,18 @@ 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 +++ 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)); @@ -17,7 +27,7 @@ index 811b3d0..b31c7fd 100644 _this.matchers = []; CLIPBOARD_CONFIG.concat(_this.options.matchers).forEach(function (_ref) { var _ref2 = _slicedToArray(_ref, 2), -@@ -8941,15 +8941,18 @@ var Clipboard = function (_Module) { +@@ -8941,15 +8942,18 @@ var Clipboard = function (_Module) { key: 'convert', value: function convert(html) { if (typeof html === 'string') { @@ -42,7 +52,7 @@ index 811b3d0..b31c7fd 100644 var _prepareMatching = this.prepareMatching(), _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)); } debug.log('convert', this.container.innerHTML, delta); @@ -52,7 +62,7 @@ index 811b3d0..b31c7fd 100644 return delta; } }, { -@@ -9056,9 +9060,10 @@ function applyFormat(delta, format, value) { +@@ -9056,9 +9061,10 @@ function applyFormat(delta, format, value) { } function computeStyle(node) { @@ -66,7 +76,7 @@ index 811b3d0..b31c7fd 100644 } function deltaEndsWith(delta, text) { -@@ -9074,7 +9079,8 @@ function deltaEndsWith(delta, text) { +@@ -9074,7 +9080,8 @@ function deltaEndsWith(delta, text) { function isLine(node) { if (node.childNodes.length === 0) return false; // Exclude embed blocks var style = computeStyle(node); @@ -76,7 +86,7 @@ index 811b3d0..b31c7fd 100644 } 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) { diff --git a/ts/background.ts b/ts/background.ts index 63e842dbf..b7ba0aede 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -187,7 +187,7 @@ import { setBatchingStrategy } from './util/messageBatcher'; import { parseRemoteClientExpiration } from './util/parseRemoteClientExpiration'; import { makeLookup } from './util/makeLookup'; 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 { return isNumber(timestamp) && isOlderThan(timestamp, HOUR); @@ -551,7 +551,14 @@ export async function startApp(): Promise { ); // 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(); diff --git a/ts/quill/signal-clipboard/index.ts b/ts/quill/signal-clipboard/index.ts index 291382329..ffd86c907 100644 --- a/ts/quill/signal-clipboard/index.ts +++ b/ts/quill/signal-clipboard/index.ts @@ -24,13 +24,6 @@ export class SignalClipboard { this.quill = quill; 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 { diff --git a/ts/quill/signal-clipboard/util.ts b/ts/quill/signal-clipboard/util.ts index f2da5487d..355ecf0c5 100644 --- a/ts/quill/signal-clipboard/util.ts +++ b/ts/quill/signal-clipboard/util.ts @@ -1,36 +1,51 @@ // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -export function handleCopyEvent(event: ClipboardEvent): void { - if (!event.clipboardData) { - return; - } +const QUILL_EMBED_GUARD = '\uFEFF'; - const selection = window.getSelection(); - if (!selection) { - return; - } +export function createEventHandler({ + deleteSelection, +}: { + deleteSelection: boolean; +}) { + return (event: ClipboardEvent): void => { + if (!event.clipboardData) { + return; + } - // Create synthetic html with the full selection we can put into clipboard - const container = document.createElement('div'); - for (let i = 0, max = selection.rangeCount; i < max; i += 1) { - const range = selection.getRangeAt(i); - container.appendChild(range.cloneContents()); - } + const selection = window.getSelection(); + if (!selection) { + return; + } - // 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); + // Create synthetic html with the full selection we can put into clipboard + const container = document.createElement('div'); + for (let i = 0, max = selection.rangeCount; i < max; i += 1) { + 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.stopPropagation(); + event.clipboardData?.setData('text/signal', container.innerHTML); + + if (deleteSelection) { + selection.deleteFromDocument(); + } + + event.preventDefault(); + event.stopPropagation(); + }; } function getStringFromNode(node: Node): string { if (node.nodeType === Node.TEXT_NODE) { + if (node.textContent === QUILL_EMBED_GUARD) { + return ''; + } return node.textContent || ''; } if (node.nodeType !== Node.ELEMENT_NODE) { @@ -38,7 +53,11 @@ function getStringFromNode(node: Node): string { } 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 || ''; } if (element.nodeName === 'BR') {