Fix cut/copy and paste from composer
This commit is contained in:
parent
b705609341
commit
9c325ea724
4 changed files with 67 additions and 38 deletions
|
@ -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<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();
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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') {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue