From 3b42cd0e543cad4a28aacfe51610c2c43f5742d7 Mon Sep 17 00:00:00 2001 From: Abdo Date: Wed, 11 Jun 2025 19:21:31 +0300 Subject: [PATCH] Handle internal pastes --- .../editor/rich-text-input/data-transfer.ts | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/ts/routes/editor/rich-text-input/data-transfer.ts b/ts/routes/editor/rich-text-input/data-transfer.ts index 721fd060f..1da56b194 100644 --- a/ts/routes/editor/rich-text-input/data-transfer.ts +++ b/ts/routes/editor/rich-text-input/data-transfer.ts @@ -3,7 +3,6 @@ import { ConfigKey_Bool } from "@generated/anki/config_pb"; import { addMediaFile, convertPastedImage, getConfigBool, retrieveUrl as retrieveUrlBackend } from "@generated/backend"; -import { bridgeCommand } from "@tslib/bridgecommand"; import { shiftPressed } from "@tslib/keys"; import { pasteHTML } from "../old-editor-adapter"; @@ -34,11 +33,8 @@ const audioSuffixes = [ ]; const mediaSuffixes = [...imageSuffixes, ...audioSuffixes]; -function imageDataToUint8Array(data: ImageData): Uint8Array { - return typeof data === "string" ? new TextEncoder().encode(data) : data; -} - let isShiftPressed = false; +let lastInternalFieldText = ""; async function wantsExtendedPaste(event: MouseEvent | KeyboardEvent | null = null): Promise { let stripHtml = (await getConfigBool({ @@ -70,6 +66,10 @@ function getText(data: DataTransfer): string { return data.getData("text/plain") ?? ""; } +function getHtml(data: DataTransfer): string { + return data.getData("text/html") ?? ""; +} + const QIMAGE_FORMATS = [ "image/jpeg", "image/png", @@ -139,6 +139,10 @@ async function urlToLink(url: string): Promise { return filenameToLink(filename); } +function imageDataToUint8Array(data: ImageData): Uint8Array { + return typeof data === "string" ? new TextEncoder().encode(data) : data; +} + async function checksum(data: string | Uint8Array): Promise { const bytes = imageDataToUint8Array(data); const hashBuffer = await crypto.subtle.digest("SHA-1", bytes); @@ -267,11 +271,11 @@ async function processText(data: DataTransfer, extended: Promise): Prom async function processDataTransferEvent( event: ClipboardEvent | DragEvent, extended: Promise, -): Promise { +): Promise<{ html: string | null; internal: boolean }> { const data = event instanceof ClipboardEvent ? event.clipboardData! : event.dataTransfer!; - const html = data.getData("text/html"); + const html = getHtml(data); if (html) { - return html; + return { html, internal: false }; } const urls = getUrls(data); let handlers: ((data: DataTransfer, extended: Promise) => Promise)[]; @@ -284,11 +288,11 @@ async function processDataTransferEvent( for (const handler of handlers) { const html = await handler(data, extended); if (html) { - return html; + return { html, internal: true }; } } - return null; + return { html: null, internal: false }; } async function runPreFilter(html: string, internal = false): Promise { @@ -321,24 +325,27 @@ async function runPreFilter(html: string, internal = false): Promise { return template.innerHTML; } -export async function handlePaste(event: ClipboardEvent) { - // bridgeCommand("paste"); +async function handlePasteOrDrop(event: ClipboardEvent | DragEvent) { event.preventDefault(); - let html = await processDataTransferEvent(event, wantsExtendedPaste()); + let html: string | null = lastInternalFieldText; + let internal = !!lastInternalFieldText; + const extended = wantsExtendedPaste(event instanceof ClipboardEvent ? null : event); + if (!html) { + // `extended` is passed as a promise because the event's data is apparently cleared if we wait here before calling getData() + ({ html, internal } = await processDataTransferEvent(event, extended)); + } if (html) { - html = await runPreFilter(html); - pasteHTML(html, false, false); + html = await runPreFilter(html, internal); + pasteHTML(html, internal, await extended); } } +export async function handlePaste(event: ClipboardEvent) { + await handlePasteOrDrop(event); +} + export async function handleDrop(event: DragEvent) { - event.preventDefault(); - // `extended` is passed as a promise because the event's data is apparently cleared if we wait here before calling getData() - let html = await processDataTransferEvent(event, wantsExtendedPaste(event)); - if (html) { - html = await runPreFilter(html); - pasteHTML(html, false, false); - } + await handlePasteOrDrop(event); } export async function handleDragover(event: DragEvent) { @@ -349,6 +356,6 @@ export async function handleKeydown(event: KeyboardEvent) { isShiftPressed = shiftPressed(event); } -export function handleCutOrCopy() { - bridgeCommand("cutOrCopy"); +export function handleCutOrCopy(event: ClipboardEvent) { + lastInternalFieldText = getHtml(event.clipboardData!); }