Better handling of formatting in pasted text
This commit is contained in:
parent
d012779e87
commit
a31cf5645e
12 changed files with 105 additions and 43 deletions
|
@ -6,9 +6,8 @@ import { insertEmojiOps } from '../util';
|
|||
|
||||
export const matchEmojiImage = (node: Element, delta: Delta): Delta => {
|
||||
if (
|
||||
(node.classList.contains('emoji') ||
|
||||
node.classList.contains('module-emoji__image--16px')) &&
|
||||
!node.classList.contains('emoji--invisible')
|
||||
node.classList.contains('emoji') ||
|
||||
node.classList.contains('module-emoji__image--16px')
|
||||
) {
|
||||
const emoji = node.getAttribute('aria-label');
|
||||
return new Delta().insert({ emoji });
|
||||
|
|
|
@ -64,8 +64,8 @@ export const matchMonospace = (node: HTMLElement, delta: Delta): Delta => {
|
|||
export const matchSpoiler = (node: HTMLElement, delta: Delta): Delta => {
|
||||
const classes = [
|
||||
'quill--spoiler',
|
||||
'MessageTextRenderer__formatting--spoiler',
|
||||
'MessageTextRenderer__formatting--spoiler--revealed',
|
||||
// Note: we don't match on hidden spoilers in message body; we use copy-target text
|
||||
];
|
||||
|
||||
if (
|
||||
|
|
|
@ -13,10 +13,7 @@ export const matchMention =
|
|||
if (memberRepository) {
|
||||
const { title } = node.dataset;
|
||||
|
||||
if (
|
||||
node.classList.contains('MessageBody__at-mention') &&
|
||||
!node.classList.contains('MessageBody__at-mention--invisible')
|
||||
) {
|
||||
if (node.classList.contains('MessageBody__at-mention')) {
|
||||
const { id } = node.dataset;
|
||||
const conversation = memberRepository.getMemberById(id);
|
||||
|
||||
|
|
|
@ -46,17 +46,17 @@ export class SignalClipboard {
|
|||
}
|
||||
|
||||
const text = event.clipboardData.getData('text/plain');
|
||||
const html = event.clipboardData.getData('text/html');
|
||||
const signal = event.clipboardData.getData('text/signal');
|
||||
|
||||
if (!text && !html) {
|
||||
if (!text && !signal) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const clipboardDelta = html
|
||||
? clipboard.convert(html)
|
||||
const clipboardDelta = signal
|
||||
? clipboard.convert(signal)
|
||||
: clipboard.convert(replaceAngleBrackets(text));
|
||||
|
||||
const { scrollTop } = this.quill.scrollingContainer;
|
||||
|
|
64
ts/quill/signal-clipboard/util.ts
Normal file
64
ts/quill/signal-clipboard/util.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
export function handleCopyEvent(event: ClipboardEvent): void {
|
||||
if (!event.clipboardData) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
event.clipboardData?.setData('text/signal', container.innerHTML);
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function getStringFromNode(node: Node): string {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
return node.textContent || '';
|
||||
}
|
||||
if (node.nodeType !== Node.ELEMENT_NODE) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const element = node as Element;
|
||||
if (element.nodeName === 'IMG' && element.classList.contains('emoji')) {
|
||||
return element.ariaLabel || '';
|
||||
}
|
||||
if (element.nodeName === 'BR') {
|
||||
return '\n';
|
||||
}
|
||||
if (element.childNodes.length === 0) {
|
||||
return element.textContent || '';
|
||||
}
|
||||
let result = '';
|
||||
for (const child of element.childNodes) {
|
||||
result += getStringFromNode(child);
|
||||
}
|
||||
if (
|
||||
element.nodeName === 'P' ||
|
||||
element.nodeName === 'DIV' ||
|
||||
element.nodeName === 'TIME'
|
||||
) {
|
||||
if (result.length > 0 && !result.endsWith('\n\n')) {
|
||||
result += '\n';
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue