Skip to content

Commit

Permalink
Fix cut/copy and paste from composer
Browse files Browse the repository at this point in the history
Co-authored-by: Scott Nonnenberg <scott@signal.org>
  • Loading branch information
automated-signal and scottnonnenberg-signal committed May 31, 2023
1 parent 4880189 commit 13ad950
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 38 deletions.
24 changes: 17 additions & 7 deletions 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));

Expand All @@ -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') {
Expand All @@ -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);
Expand All @@ -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) {
Expand All @@ -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);
Expand All @@ -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) {
Expand Down
11 changes: 9 additions & 2 deletions ts/background.ts
Expand Up @@ -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);
Expand Down Expand Up @@ -551,7 +551,14 @@ export async function startApp(): Promise<void> {
);

// 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();

Expand Down
7 changes: 0 additions & 7 deletions ts/quill/signal-clipboard/index.ts
Expand Up @@ -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 {
Expand Down
63 changes: 41 additions & 22 deletions ts/quill/signal-clipboard/util.ts
@@ -1,44 +1,63 @@
// 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;
}

// 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());
}

// 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);

// 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.clipboardData?.setData('text/signal', container.innerHTML);

event.clipboardData?.setData('text/signal', container.innerHTML);
if (deleteSelection) {
selection.deleteFromDocument();
}

event.preventDefault();
event.stopPropagation();
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) {
return '';
}

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') {
Expand Down

0 comments on commit 13ad950

Please sign in to comment.