1import { ComposerContextId } from "@t3tools/contracts";
2import type { ComposerContextClipboardFragment } from "@t3tools/contracts";
3import {
4 COMPOSER_CONTEXT_CLIPBOARD_MIME,
5 decodeComposerContextFragment,
6 decodeComposerContextClipboardHtml,
7} from "@t3tools/shared/composerContextClipboard";
8import {
9 collectComposerContextReferences,
10 formatComposerContextReference,
11 replaceComposerContextReferences,
12} from "@t3tools/shared/composerContextReferences";
13/** Clipboard records referenced by the copied text, including dependent screenshots. */
14export function readPastedComposerContext(
15 clipboardData: Pick<DataTransfer, "getData">,
16): ComposerContextClipboardFragment | null {
17 const pastedText = clipboardData.getData("text/plain");
18 // Only records whose links are in the pasted text get imported; a fragment may carry
19 // more (it was built for a larger copy) and must not start transfers for those.
20 const decodedFragment =
21 decodeComposerContextFragment(clipboardData.getData(COMPOSER_CONTEXT_CLIPBOARD_MIME)) ??
22 decodeComposerContextClipboardHtml(clipboardData.getData("text/html"));
23 if (decodedFragment === null) return null;
24 const pastedIds = new Set<string>(
25 collectComposerContextReferences(pastedText).map((occurrence) => occurrence.contextId),
26 );
27 for (const record of decodedFragment.records) {
28 if (
29 record.kind === "preview-annotation" &&
30 !("payload" in record) &&
31 pastedIds.has(record.contextId) &&
32 record.screenshotContextId
33 ) {
34 pastedIds.add(record.screenshotContextId);
35 }
36 }
37 return {
38 ...decodedFragment,
39 records: decodedFragment.records.filter((record) => pastedIds.has(record.contextId)),
40 };
41}
42
43/** Imports the same structured clipboard payload for focused paste and paste-to-focus. */
44export function importPastedComposerText(
45 clipboardData: Pick<DataTransfer, "getData">,
46 importContextFragment?: (
47 fragment: ComposerContextClipboardFragment,
48 ) => ReadonlyMap<string, string>,
49): string {
50 const pastedText = clipboardData.getData("text/plain");
51 const fragment = importContextFragment ? readPastedComposerContext(clipboardData) : null;
52 const rewrittenIds =
53 fragment && fragment.records.length > 0 ? importContextFragment!(fragment) : null;
54 const text =
55 rewrittenIds && rewrittenIds.size > 0
56 ? replaceComposerContextReferences(pastedText, (occurrence) => {
57 const nextId = rewrittenIds.get(occurrence.contextId);
58 return nextId
59 ? formatComposerContextReference({
60 ...occurrence,
61 contextId: ComposerContextId.make(nextId),
62 kind: occurrence.kind === "element" ? "preview-annotation" : occurrence.kind,
63 })
64 : occurrence.source;
65 })
66 : pastedText;
67 return text;
68}