@stll/folio-core 0.55.0 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/ai-edits/apply.js +169 -38
  2. package/dist/ai-edits/headless.js +2 -2
  3. package/dist/ai-edits/types.d.ts +7 -1
  4. package/dist/compare/compare.js +1 -0
  5. package/dist/compare/reproducible-package.d.ts +0 -17
  6. package/dist/compare/reproducible-package.js +12 -18
  7. package/dist/controller/canonicalClipboard.d.ts +23 -0
  8. package/dist/controller/canonicalClipboard.js +476 -0
  9. package/dist/controller/canonicalClipboardResources.d.ts +21 -0
  10. package/dist/controller/canonicalClipboardResources.js +382 -0
  11. package/dist/controller/canonicalComposition.d.ts +20 -3
  12. package/dist/controller/canonicalComposition.js +166 -38
  13. package/dist/controller/canonicalInlineProjection.d.ts +113 -0
  14. package/dist/controller/canonicalInlineProjection.js +393 -0
  15. package/dist/controller/canonicalInput.d.ts +13 -4
  16. package/dist/controller/canonicalInput.js +52 -13
  17. package/dist/controller/canonicalInputTimer.d.ts +7 -0
  18. package/dist/controller/canonicalInputTimer.js +11 -0
  19. package/dist/controller/canonicalPublicOperations.d.ts +48 -0
  20. package/dist/controller/canonicalPublicOperations.js +556 -0
  21. package/dist/controller/canonicalSession.d.ts +24 -6
  22. package/dist/controller/canonicalSession.js +259 -111
  23. package/dist/controller/canonicalStoryEditor.d.ts +5 -3
  24. package/dist/controller/canonicalStoryEditor.js +7 -2
  25. package/dist/controller/canonicalStructure.js +57 -4
  26. package/dist/controller/canonicalTocSelection.d.ts +11 -0
  27. package/dist/controller/canonicalTocSelection.js +43 -0
  28. package/dist/controller/folioEditor.d.ts +2 -9
  29. package/dist/controller/folioEditor.js +5 -4
  30. package/dist/controller/headerFooterEditorManager.d.ts +2 -1
  31. package/dist/controller/headerFooterEditorManager.js +8 -7
  32. package/dist/controller/hiddenEditorApi.d.ts +12 -2
  33. package/dist/controller/hiddenEditorApi.js +4 -0
  34. package/dist/controller/hiddenEditorManager.d.ts +3 -1
  35. package/dist/controller/hiddenEditorManager.js +216 -34
  36. package/dist/controller/noteEditorManager.d.ts +2 -1
  37. package/dist/controller/noteEditorManager.js +8 -7
  38. package/dist/document-operations.d.ts +8 -1
  39. package/dist/document-operations.js +6 -4
  40. package/dist/docx/blockPlainText.d.ts +1 -1
  41. package/dist/docx/blockPlainText.js +11 -3
  42. package/dist/docx/canonicalResourceSave.d.ts +16 -0
  43. package/dist/docx/canonicalResourceSave.js +27 -0
  44. package/dist/docx/canonicalSave.d.ts +29 -0
  45. package/dist/docx/canonicalSave.js +101 -0
  46. package/dist/docx/canonicalStoryRepack.d.ts +1 -1
  47. package/dist/docx/canonicalStoryRepack.js +38 -5
  48. package/dist/docx/documentParser.d.ts +1 -13
  49. package/dist/docx/documentParser.js +1 -47
  50. package/dist/docx/ensureParaIds.js +10 -4
  51. package/dist/docx/footnoteParser.d.ts +1 -11
  52. package/dist/docx/footnoteParser.js +1 -16
  53. package/dist/docx/headerFooterParser.d.ts +1 -9
  54. package/dist/docx/headerFooterParser.js +1 -12
  55. package/dist/docx/headerFooterVerbatim.d.ts +26 -1
  56. package/dist/docx/headerFooterVerbatim.js +81 -3
  57. package/dist/docx/imageParser.d.ts +13 -1
  58. package/dist/docx/imageParser.js +20 -11
  59. package/dist/docx/metadataPrivacy.js +6 -1
  60. package/dist/docx/packageParts.js +9 -3
  61. package/dist/docx/paragraphPropertySource.js +2 -1
  62. package/dist/docx/parser.js +2 -1
  63. package/dist/docx/removeHeaderFooterParts.js +17 -6
  64. package/dist/docx/rezip.d.ts +49 -5
  65. package/dist/docx/rezip.js +745 -229
  66. package/dist/docx/saveDiagnostics.d.ts +11 -0
  67. package/dist/docx/selectiveSave.d.ts +2 -1
  68. package/dist/docx/selectiveSave.js +32 -3
  69. package/dist/docx/selectiveXmlPatch.js +7 -5
  70. package/dist/docx/serializer/documentSerializer.d.ts +9 -2
  71. package/dist/docx/serializer/documentSerializer.js +29 -10
  72. package/dist/docx/server/applyDocxXmlPatchProposal.js +14 -9
  73. package/dist/docx/storyBlockReplay.d.ts +21 -1
  74. package/dist/docx/storyBlockReplay.js +113 -9
  75. package/dist/docx/storyPlainText.d.ts +20 -0
  76. package/dist/docx/storyPlainText.js +23 -0
  77. package/dist/docx/structuralXmlPatch.js +10 -22
  78. package/dist/docx/styleParser.d.ts +5 -1
  79. package/dist/docx/styleParser.js +1 -1
  80. package/dist/docx/tableParser.d.ts +1 -8
  81. package/dist/docx/tableParser.js +1 -28
  82. package/dist/docx/textBoxParser.d.ts +1 -5
  83. package/dist/docx/textBoxParser.js +1 -20
  84. package/dist/docx/unzip.js +6 -1
  85. package/dist/internal/acceptedBlockProjection.d.ts +12 -0
  86. package/dist/internal/acceptedBlockProjection.js +45 -0
  87. package/dist/managers/DocumentLoaderManager.js +2 -1
  88. package/dist/markdown/escape.d.ts +3 -1
  89. package/dist/markdown/escape.js +9 -2
  90. package/dist/markdown/renderBlock.js +5 -120
  91. package/dist/markdown/renderParagraph.js +4 -2
  92. package/dist/markdown/renderRuns.d.ts +7 -1
  93. package/dist/markdown/renderRuns.js +32 -13
  94. package/dist/prosemirror/canonicalCommands.d.ts +28 -4
  95. package/dist/prosemirror/canonicalCommands.js +11 -4
  96. package/dist/prosemirror/canonicalSelectionRange.d.ts +9 -0
  97. package/dist/prosemirror/canonicalSelectionRange.js +15 -0
  98. package/dist/prosemirror/clipboardIntent.d.ts +7 -0
  99. package/dist/prosemirror/clipboardIntent.js +9 -0
  100. package/dist/prosemirror/commands/pageBreak.js +2 -2
  101. package/dist/prosemirror/commands/pastePlainText.js +2 -1
  102. package/dist/prosemirror/conversion/hyphenTextCarriers.d.ts +8 -0
  103. package/dist/prosemirror/conversion/hyphenTextCarriers.js +8 -0
  104. package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -1
  105. package/dist/prosemirror/conversion/toProseDoc.js +5 -4
  106. package/dist/prosemirror/extensions/core/ParagraphExtension.js +39 -30
  107. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +56 -28
  108. package/dist/prosemirror/extensions/marks/markUtils.js +3 -1
  109. package/dist/prosemirror/insertOperations.js +2 -1
  110. package/dist/prosemirror/paragraphPropertyCarry.d.ts +7 -1
  111. package/dist/prosemirror/paragraphPropertyCarry.js +8 -3
  112. package/dist/prosemirror/plugins/suggestionMode.js +7 -5
  113. package/dist/types/canonicalCapabilities.d.ts +215 -0
  114. package/dist/types/canonicalCapabilities.js +212 -0
  115. package/dist/types/canonicalSave.d.ts +11 -0
  116. package/dist/types/canonicalSave.js +0 -0
  117. package/dist/types/docxSerialization.d.ts +14 -0
  118. package/dist/types/docxSerialization.js +7 -0
  119. package/package.json +4 -4
@@ -0,0 +1,476 @@
1
+ import { createNumberingIdAllocator } from "../docx/numberingIds.js";
2
+ import { visitInlineContentSlots, visitParagraphRuns } from "../docx/paragraphTraversal.js";
3
+ import { captureVerbatimXml } from "../docx/verbatimCapture.js";
4
+ import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, OOXML_NAMESPACE_SCOPE, findAttributeByNamespaceUri, getLocalName, parseXml } from "../docx/xmlParser.js";
5
+ import { proseDocToBlocks } from "../prosemirror/conversion/fromProseDoc.js";
6
+ import { completeNumberingForDoc } from "../prosemirror/listInstanceReferences.js";
7
+ import { marksToTextFormatting } from "../prosemirror/runFormattingFromMarks.js";
8
+ import { CANONICAL_GAP } from "../types/canonicalCapabilities.js";
9
+ import { bytesToDataUrl } from "../utils/base64.js";
10
+ import { flattenClipboardStyleReferences, importClipboardStyles } from "./canonicalClipboardResources.js";
11
+ import { CanonicalSessionError } from "./canonicalSession.js";
12
+ import { Result } from "better-result";
13
+ import { NUMBER_FORMATS, paragraphNumberingReference, relationshipIdOf } from "@stll/docx-core/model";
14
+ import { Fragment, Slice } from "prosemirror-model";
15
+ import { packageResourcesOpOf } from "@stll/docx-core/ops";
16
+ //#region src/controller/canonicalClipboard.ts
17
+ const refuse = (message) => Result.err(new CanonicalSessionError({
18
+ gap: CANONICAL_GAP.dispatch,
19
+ message,
20
+ reason: "refused"
21
+ }));
22
+ const IMAGE_RELATIONSHIP = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
23
+ const HYPERLINK_RELATIONSHIP = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
24
+ const MAX_CLIPBOARD_IMAGE_BYTES = 16 * 1024 * 1024;
25
+ const isNumberFormat = (format) => NUMBER_FORMATS.some((value) => value === format);
26
+ const numberFormatFields = (format) => {
27
+ if (isNumberFormat(format)) return { numFmt: format };
28
+ switch (format) {
29
+ case "decimalZero3": return {
30
+ numFmt: "custom",
31
+ numFmtFormat: "000"
32
+ };
33
+ case "decimalZero4": return {
34
+ numFmt: "custom",
35
+ numFmtFormat: "0000"
36
+ };
37
+ case "decimalZero5": return {
38
+ numFmt: "custom",
39
+ numFmtFormat: "00000"
40
+ };
41
+ default: return format;
42
+ }
43
+ };
44
+ /** Clipboard instance ids belong to their source package, so imported lists get fresh ids. */
45
+ const importNumbering = ({ destination, source, paragraphs, mode, sourceKind, extraIds, styleIds }) => {
46
+ if (mode === "move") return Result.ok({
47
+ numbering: destination,
48
+ numIds: /* @__PURE__ */ new Map()
49
+ });
50
+ const references = paragraphs.flatMap((paragraph) => {
51
+ return [paragraph.formatting, ...(paragraph.propertyChanges ?? []).map((change) => change.previousFormatting)].flatMap((formatting) => formatting?.numPr?.kind === "reference" ? [formatting.numPr.numId] : []);
52
+ });
53
+ references.push(...extraIds ?? []);
54
+ if (references.length === 0) return Result.ok({
55
+ numbering: destination,
56
+ numIds: /* @__PURE__ */ new Map()
57
+ });
58
+ if (source === void 0) return refuse("The clipboard list has no numbering definition.");
59
+ const sourceDefinitions = source;
60
+ if (references.some((id) => !sourceDefinitions.nums.some(({ numId }) => numId === id))) return refuse("The clipboard list has no numbering definition.");
61
+ const used = new Set(references);
62
+ const sourceNums = source.nums.filter(({ numId }) => used.has(numId));
63
+ const usedAbstracts = new Set(sourceNums.map(({ abstractNumId }) => abstractNumId));
64
+ source = {
65
+ ...source,
66
+ nums: structuredClone(sourceNums),
67
+ abstractNums: structuredClone(source.abstractNums.filter(({ abstractNumId }) => usedAbstracts.has(abstractNumId)))
68
+ };
69
+ if (sourceKind === "normalizedSlice") for (const paragraph of paragraphs) {
70
+ const rendering = paragraph.listRendering;
71
+ if (rendering === void 0) continue;
72
+ const instance = source.nums.find(({ numId }) => numId === rendering.numId);
73
+ const definition = source.abstractNums.find(({ abstractNumId }) => abstractNumId === instance?.abstractNumId);
74
+ if (definition === void 0) return refuse("The clipboard list has no abstract definition.");
75
+ const level = definition.levels.find(({ ilvl }) => ilvl === rendering.level);
76
+ if (level === void 0) return refuse("The clipboard list level could not be imported.");
77
+ Object.assign(level, {
78
+ ...numberFormatFields(rendering.numFmt ?? (rendering.isBullet ? "bullet" : "decimal")),
79
+ lvlText: rendering.markerTemplate ?? rendering.marker,
80
+ start: rendering.levelStarts?.at(rendering.level) ?? level.start ?? 1,
81
+ ...rendering.isLegal === void 0 ? {} : { isLgl: rendering.isLegal },
82
+ ...rendering.markerAlignment === void 0 ? {} : { lvlJc: rendering.markerAlignment },
83
+ ...rendering.markerSuffix === void 0 ? {} : { suffix: rendering.markerSuffix },
84
+ ...rendering.levelTabs === void 0 ? {} : { pPr: {
85
+ ...level.pPr,
86
+ tabs: rendering.levelTabs
87
+ } },
88
+ ...rendering.markerFormatting === void 0 ? {} : { rPr: rendering.markerFormatting }
89
+ });
90
+ if (rendering.markerAllCaps !== void 0 || rendering.markerHidden !== void 0) level.rPr = {
91
+ ...level.rPr,
92
+ ...rendering.markerAllCaps === void 0 ? {} : { allCaps: rendering.markerAllCaps },
93
+ ...rendering.markerHidden === void 0 ? {} : { hidden: rendering.markerHidden }
94
+ };
95
+ }
96
+ const numAllocator = createNumberingIdAllocator("num", (destination?.nums ?? []).map(({ numId }) => numId));
97
+ const abstractAllocator = createNumberingIdAllocator("abstract", (destination?.abstractNums ?? []).map(({ abstractNumId }) => abstractNumId));
98
+ const nums = new Map(source.nums.map(({ numId }) => [numId, numAllocator.next()]));
99
+ const abstracts = new Map(source.abstractNums.map(({ abstractNumId }) => [abstractNumId, abstractAllocator.next()]));
100
+ for (const paragraph of paragraphs) {
101
+ const formats = [paragraph.formatting, ...(paragraph.propertyChanges ?? []).map((change) => change.previousFormatting)];
102
+ for (const formatting of formats) {
103
+ if (formatting?.numPr?.kind !== "reference") continue;
104
+ const numId = nums.get(formatting.numPr.numId);
105
+ if (numId === void 0) return refuse("The clipboard list instance could not be imported.");
106
+ formatting.numPr = paragraphNumberingReference({
107
+ numId,
108
+ ...formatting.numPr.ilvl === void 0 ? {} : { ilvl: formatting.numPr.ilvl }
109
+ });
110
+ }
111
+ if (paragraph.listRendering !== void 0) {
112
+ const numId = nums.get(paragraph.listRendering.numId);
113
+ if (numId !== void 0) paragraph.listRendering.numId = numId;
114
+ const abstractId = paragraph.listRendering.abstractNumId;
115
+ if (abstractId !== void 0) {
116
+ const abstractNumId = abstracts.get(abstractId);
117
+ if (abstractNumId !== void 0) paragraph.listRendering.abstractNumId = abstractNumId;
118
+ }
119
+ }
120
+ }
121
+ const importedNums = [];
122
+ for (const instance of source.nums) {
123
+ const numId = nums.get(instance.numId);
124
+ const abstractNumId = abstracts.get(instance.abstractNumId);
125
+ if (numId === void 0 || abstractNumId === void 0) return refuse("The clipboard numbering definition could not be imported.");
126
+ const imported = {
127
+ ...instance,
128
+ numId,
129
+ abstractNumId
130
+ };
131
+ for (const override of imported.levelOverrides ?? []) {
132
+ if (override.lvl?.pStyle === void 0 || styleIds === void 0) continue;
133
+ const mapped = styleIds.get(override.lvl.pStyle);
134
+ if (mapped === void 0) return refuse("A clipboard numbering style dependency could not be imported.");
135
+ override.lvl.pStyle = mapped;
136
+ }
137
+ importedNums.push(imported);
138
+ }
139
+ const importedAbstracts = [];
140
+ for (const definition of source.abstractNums) {
141
+ const abstractNumId = abstracts.get(definition.abstractNumId);
142
+ if (abstractNumId === void 0) return refuse("The clipboard numbering definition could not be imported.");
143
+ const imported = {
144
+ ...definition,
145
+ abstractNumId
146
+ };
147
+ if (styleIds !== void 0) {
148
+ if (imported.numStyleLink !== void 0) {
149
+ const mapped = styleIds.get(imported.numStyleLink);
150
+ if (mapped === void 0) return refuse("A clipboard numbering style dependency could not be imported.");
151
+ imported.numStyleLink = mapped;
152
+ }
153
+ if (imported.styleLink !== void 0) {
154
+ const mapped = styleIds.get(imported.styleLink);
155
+ if (mapped === void 0) return refuse("A clipboard numbering style dependency could not be imported.");
156
+ imported.styleLink = mapped;
157
+ }
158
+ for (const level of imported.levels) {
159
+ if (level.pStyle === void 0) continue;
160
+ const mapped = styleIds.get(level.pStyle);
161
+ if (mapped === void 0) return refuse("A clipboard numbering style dependency could not be imported.");
162
+ level.pStyle = mapped;
163
+ }
164
+ }
165
+ importedAbstracts.push(imported);
166
+ }
167
+ return Result.ok({
168
+ numbering: {
169
+ ...destination,
170
+ nums: [...destination?.nums ?? [], ...importedNums],
171
+ abstractNums: [...destination?.abstractNums ?? [], ...importedAbstracts]
172
+ },
173
+ numIds: nums
174
+ });
175
+ };
176
+ const prepareCanonicalPaste = ({ session, state, slice, plain = false, moveTarget, pasteTarget, sourceDocument, moveSource }) => {
177
+ if (session.isComposing) return refuse("Composition must finish before using the clipboard.");
178
+ if (slice.content.size === 0) return Result.err(new CanonicalSessionError({
179
+ gap: CANONICAL_GAP.dispatch,
180
+ message: "The clipboard contains no content.",
181
+ reason: "noChange"
182
+ }));
183
+ if (slice.openStart > 1 || slice.openEnd > 1) return refuse("Clipboard tables and nested block containers require canonical table editing.");
184
+ const paragraphType = state.schema.nodes["paragraph"];
185
+ if (paragraphType === void 0) return refuse("Clipboard input requires a paragraph schema.");
186
+ let inline = true;
187
+ let paragraphsOnly = true;
188
+ slice.content.forEach((node) => {
189
+ inline &&= node.isInline;
190
+ paragraphsOnly &&= node.type === paragraphType;
191
+ });
192
+ if (!inline && !paragraphsOnly) return refuse("Clipboard tables and embedded blocks require canonical table editing.");
193
+ const importedSlice = inline ? new Slice(Fragment.from(paragraphType.create(null, slice.content)), 1, 1) : slice;
194
+ const converted = Result.try({
195
+ try: () => proseDocToBlocks(state.schema.topNodeType.create(null, importedSlice.content), []),
196
+ catch: (cause) => new CanonicalSessionError({
197
+ gap: CANONICAL_GAP.dispatch,
198
+ reason: "refused",
199
+ message: `Clipboard normalization failed: ${cause instanceof Error ? cause.message : String(cause)}`
200
+ })
201
+ });
202
+ if (converted.isErr()) return converted;
203
+ const paragraphs = [];
204
+ for (const block of converted.value) {
205
+ if (block.type !== "paragraph") return refuse("The clipboard contains an unsupported block.");
206
+ paragraphs.push(structuredClone(block));
207
+ }
208
+ if (moveTarget === void 0 && (plain || sourceDocument === void 0)) flattenClipboardStyleReferences(paragraphs);
209
+ const from = session.projection.addressAt(pasteTarget ?? moveSource?.from ?? state.selection.from);
210
+ if (from.isErr()) return from;
211
+ const to = session.projection.addressAt(pasteTarget ?? moveSource?.to ?? state.selection.to);
212
+ if (to.isErr()) return to;
213
+ let styles = session.document.package.styles;
214
+ let styleIds;
215
+ let extraIds;
216
+ if (sourceDocument !== void 0 && moveTarget === void 0 && !plain) {
217
+ const importedStyles = importClipboardStyles({
218
+ destination: session.document,
219
+ source: sourceDocument,
220
+ paragraphs
221
+ });
222
+ if (importedStyles.isErr()) return refuse(importedStyles.error.message);
223
+ styles = importedStyles.value.styles;
224
+ paragraphs.splice(0, paragraphs.length, ...importedStyles.value.paragraphs);
225
+ styleIds = importedStyles.value.styleIds;
226
+ extraIds = importedStyles.value.numberingIds;
227
+ }
228
+ const sourceNumbering = sourceDocument?.package.numbering ?? completeNumberingForDoc(void 0, state.schema.topNodeType.create(null, importedSlice.content));
229
+ const importedNumbering = importNumbering({
230
+ destination: session.document.package.numbering,
231
+ source: sourceNumbering,
232
+ paragraphs,
233
+ mode: moveTarget === void 0 ? "copy" : "move",
234
+ sourceKind: sourceDocument === void 0 ? "normalizedSlice" : "package",
235
+ ...extraIds === void 0 ? {} : { extraIds },
236
+ ...styleIds === void 0 ? {} : { styleIds }
237
+ });
238
+ if (importedNumbering.isErr()) return importedNumbering;
239
+ const numbering = importedNumbering.value.numbering;
240
+ if (styles !== void 0 && styleIds !== void 0) {
241
+ const importedStyleIds = new Set(styleIds.values());
242
+ for (const style of styles.styles) {
243
+ if (!importedStyleIds.has(style.styleId) || style.pPr?.numPr?.kind !== "reference") continue;
244
+ const numId = importedNumbering.value.numIds.get(style.pPr.numPr.numId);
245
+ if (numId === void 0) return refuse("A clipboard style numbering dependency could not be imported.");
246
+ style.pPr.numPr = paragraphNumberingReference({
247
+ numId,
248
+ ...style.pPr.numPr.ilvl === void 0 ? {} : { ilvl: style.pPr.numPr.ilvl }
249
+ });
250
+ }
251
+ }
252
+ const relationships = new Map(session.document.package.relationships);
253
+ const media = new Map(session.document.package.media);
254
+ let importedResource = false;
255
+ let imageIndex = 1;
256
+ let relationshipIndex = 1;
257
+ const importExternalLink = (href, previousId) => {
258
+ const source = previousId === void 0 ? void 0 : sourceDocument?.package.relationships?.get(previousId);
259
+ let target = href;
260
+ if (target === void 0 || target.length === 0) target = source?.targetMode === "External" ? source.target : void 0;
261
+ if (target === void 0 || target.length === 0) return refuse("The clipboard hyperlink has no resolved target.");
262
+ while (relationships.has(`rId${relationshipIndex}`)) relationshipIndex += 1;
263
+ const id = `rId${relationshipIndex++}`;
264
+ relationships.set(id, {
265
+ id,
266
+ type: HYPERLINK_RELATIONSHIP,
267
+ target,
268
+ targetMode: "External"
269
+ });
270
+ importedResource = true;
271
+ return Result.ok({
272
+ id,
273
+ href: target
274
+ });
275
+ };
276
+ const importCapturedLinks = (xml, remapped) => {
277
+ const parsed = Result.try({
278
+ try: () => parseXml(xml, OOXML_NAMESPACE_SCOPE),
279
+ catch: () => new CanonicalSessionError({
280
+ gap: CANONICAL_GAP.dispatch,
281
+ reason: "refused",
282
+ message: "The clipboard hyperlink metadata is malformed."
283
+ })
284
+ });
285
+ if (parsed.isErr()) return parsed;
286
+ const visit = (element) => {
287
+ for (const name of Object.keys(element.attributes ?? {})) {
288
+ const attribute = findAttributeByNamespaceUri(element, OFFICE_RELATIONSHIP_NAMESPACE_URIS, getLocalName(name));
289
+ if (attribute === null || attribute.name !== name) continue;
290
+ if (getLocalName(name) !== "id") return refuse("Clipboard hyperlink metadata references an unsupported package part.");
291
+ let id = remapped.get(attribute.value);
292
+ if (id === void 0) {
293
+ const imported = importExternalLink(void 0, attribute.value);
294
+ if (imported.isErr()) return imported;
295
+ id = imported.value.id;
296
+ remapped.set(attribute.value, id);
297
+ }
298
+ if (element.attributes !== void 0) element.attributes[name] = id;
299
+ }
300
+ for (const child of element.elements ?? []) {
301
+ const result = visit(child);
302
+ if (result.isErr()) return result;
303
+ }
304
+ return Result.ok(void 0);
305
+ };
306
+ const imported = visit(parsed.value);
307
+ if (imported.isErr()) return imported;
308
+ return Result.ok((parsed.value.elements ?? []).map(captureVerbatimXml).join(""));
309
+ };
310
+ for (const paragraph of paragraphs) {
311
+ if (moveTarget === void 0) {
312
+ let requiresStory = false;
313
+ visitInlineContentSlots(paragraph, ({ item }) => {
314
+ if (item.type === "commentRangeStart" || item.type === "commentRangeEnd" || item.type === "commentReference") requiresStory = true;
315
+ });
316
+ visitParagraphRuns(paragraph, (run) => {
317
+ if (run.content.some((item) => item.type === "footnoteRef" || item.type === "endnoteRef")) requiresStory = true;
318
+ });
319
+ if (requiresStory) return refuse("Clipboard comments and note references require importing their source story parts.");
320
+ const links = [];
321
+ visitInlineContentSlots(paragraph, ({ item }) => {
322
+ if (item.type === "hyperlink") links.push(item);
323
+ });
324
+ for (const link of links) {
325
+ if (link.anchor !== void 0) {
326
+ delete link.rId;
327
+ continue;
328
+ }
329
+ const imported = importExternalLink(link.href, link.rId);
330
+ if (imported.isErr()) return imported;
331
+ link.href = imported.value.href;
332
+ link.rId = imported.value.id;
333
+ }
334
+ }
335
+ const runs = [];
336
+ visitParagraphRuns(paragraph, (run) => runs.push(run));
337
+ for (const item of runs) {
338
+ if (plain) item.formatting = marksToTextFormatting(state.storedMarks ?? state.doc.resolve(pasteTarget ?? moveSource?.from ?? state.selection.from).marks());
339
+ for (const child of item.content) {
340
+ if (child.type !== "drawing") continue;
341
+ if (child.rawXmlMode !== void 0) return refuse("Opaque drawings and embedded objects require their source package parts.");
342
+ delete child.rawXml;
343
+ delete child.rawImageFingerprint;
344
+ const image = child.image;
345
+ delete image.id;
346
+ if (moveTarget === void 0 && (image.hlinkRId !== void 0 || image.hlinkHref !== void 0)) {
347
+ const previousId = image.hlinkRId;
348
+ const imported = importExternalLink(image.hlinkHref, previousId);
349
+ if (imported.isErr()) return imported;
350
+ image.hlinkHref = imported.value.href;
351
+ image.hlinkRId = imported.value.id;
352
+ const remapped = /* @__PURE__ */ new Map();
353
+ if (previousId !== void 0) remapped.set(previousId, imported.value.id);
354
+ if (image.hlinkClickSource !== void 0) {
355
+ const rebound = importCapturedLinks(image.hlinkClickSource.xml, remapped);
356
+ if (rebound.isErr()) return rebound;
357
+ image.hlinkClickSource = {
358
+ xml: rebound.value,
359
+ rId: imported.value.id
360
+ };
361
+ }
362
+ if (image.hlinkHoverXml !== void 0) {
363
+ const rebound = importCapturedLinks(image.hlinkHoverXml, remapped);
364
+ if (rebound.isErr()) return rebound;
365
+ image.hlinkHoverXml = rebound.value;
366
+ }
367
+ } else if (moveTarget === void 0 && image.hlinkHoverXml !== void 0) {
368
+ const rebound = importCapturedLinks(image.hlinkHoverXml, /* @__PURE__ */ new Map());
369
+ if (rebound.isErr()) return rebound;
370
+ image.hlinkHoverXml = rebound.value;
371
+ }
372
+ const relationship = image.rId === void 0 ? void 0 : relationships.get(image.rId);
373
+ const ownedPath = relationship?.target.startsWith("/") ? relationship.target.slice(1) : `word/${relationship?.target ?? ""}`;
374
+ const ownedMedia = relationship === void 0 ? void 0 : media.get(ownedPath);
375
+ if (ownedMedia !== void 0 && (moveTarget !== void 0 || image.src !== void 0 && ownedMedia.dataUrl === image.src)) continue;
376
+ const sourceRelationship = image.rId === void 0 ? void 0 : sourceDocument?.package.relationships?.get(image.rId);
377
+ const sourcePath = sourceRelationship?.target.startsWith("/") ? sourceRelationship.target.slice(1) : `word/${sourceRelationship?.target ?? ""}`;
378
+ const sourceMedia = sourceDocument?.package.media?.get(sourcePath);
379
+ if (sourceMedia !== void 0 && sourceMedia.data.byteLength > MAX_CLIPBOARD_IMAGE_BYTES) return refuse("The clipboard image exceeds the import size limit.");
380
+ const src = image.src ?? (sourceMedia === void 0 ? void 0 : bytesToDataUrl(new Uint8Array(sourceMedia.data), sourceMedia.mimeType)) ?? (sourceRelationship?.targetMode === "External" ? sourceRelationship.target : void 0);
381
+ if (src === void 0) return refuse("The clipboard image has no source bytes or URL.");
382
+ image.src = src;
383
+ while (relationships.has(`rId${relationshipIndex}`)) relationshipIndex += 1;
384
+ const id = `rId${relationshipIndex++}`;
385
+ const data = /^data:(image\/(?:png|jpeg|gif|webp));base64,([A-Za-z0-9+/=\s]+)$/u.exec(src);
386
+ if (data !== null) {
387
+ const mimeType = data.at(1);
388
+ const payload = data.at(2);
389
+ if (mimeType === void 0 || payload === void 0) return refuse("The clipboard image data is malformed.");
390
+ if (payload.length > Math.ceil(MAX_CLIPBOARD_IMAGE_BYTES * 4 / 3)) return refuse("The clipboard image exceeds the import size limit.");
391
+ const decoded = Result.try({
392
+ try: () => Uint8Array.from(atob(payload), (char) => char.charCodeAt(0)),
393
+ catch: () => new CanonicalSessionError({
394
+ gap: CANONICAL_GAP.dispatch,
395
+ message: "The clipboard image data is malformed.",
396
+ reason: "refused"
397
+ })
398
+ });
399
+ if (decoded.isErr()) return decoded;
400
+ const extension = mimeType === "image/jpeg" ? "jpg" : mimeType.slice(6);
401
+ while (media.has(`word/media/clipboard${imageIndex}.${extension}`)) imageIndex += 1;
402
+ const path = `word/media/clipboard${imageIndex++}.${extension}`;
403
+ media.set(path, {
404
+ path,
405
+ mimeType,
406
+ data: decoded.value.buffer,
407
+ dataUrl: src
408
+ });
409
+ relationships.set(id, {
410
+ id,
411
+ type: IMAGE_RELATIONSHIP,
412
+ target: path.slice(5)
413
+ });
414
+ } else if (/^https?:\/\//u.test(src)) relationships.set(id, {
415
+ id,
416
+ type: IMAGE_RELATIONSHIP,
417
+ target: src,
418
+ targetMode: "External"
419
+ });
420
+ else return refuse("Clipboard images require image bytes or an HTTP image URL.");
421
+ const relationshipId = relationshipIdOf(id);
422
+ if (relationshipId === void 0) return refuse("The clipboard image relationship id is invalid.");
423
+ image.rId = relationshipId;
424
+ delete image.id;
425
+ importedResource = true;
426
+ }
427
+ }
428
+ }
429
+ const resourceOps = [];
430
+ if (importedResource || numbering !== session.document.package.numbering || styles !== session.document.package.styles) {
431
+ const imported = {
432
+ ...session.document,
433
+ package: {
434
+ ...session.document.package,
435
+ ...numbering === void 0 ? {} : { numbering },
436
+ ...styles === void 0 ? {} : { styles },
437
+ ...importedResource ? {
438
+ relationships,
439
+ media
440
+ } : {}
441
+ }
442
+ };
443
+ resourceOps.push(packageResourcesOpOf({
444
+ before: session.document,
445
+ after: imported
446
+ }));
447
+ }
448
+ const fragment = {
449
+ from: from.value,
450
+ to: to.value,
451
+ paragraphs,
452
+ openStart: importedSlice.openStart === 0 ? 0 : 1,
453
+ openEnd: importedSlice.openEnd === 0 ? 0 : 1
454
+ };
455
+ if (moveTarget === void 0) return session.prepareIntents(state, {
456
+ intents: [{
457
+ type: "replaceFragment",
458
+ ...fragment
459
+ }],
460
+ resourceOps,
461
+ semantic: "paste"
462
+ });
463
+ const target = session.projection.addressAt(moveTarget);
464
+ if (target.isErr()) return target;
465
+ return session.prepareIntents(state, {
466
+ intents: [{
467
+ type: "moveFragment",
468
+ target: target.value,
469
+ ...fragment
470
+ }],
471
+ resourceOps,
472
+ semantic: "paste"
473
+ });
474
+ };
475
+ //#endregion
476
+ export { prepareCanonicalPaste };
@@ -0,0 +1,21 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ import { Result } from "better-result";
3
+ import { DocumentOpRefusal } from "@stll/docx-core/ops";
4
+ //#region src/controller/canonicalClipboardResources.d.ts
5
+ /** Normalized HTML states effective CSS; foreign style names carry no package ownership. */
6
+ declare const flattenClipboardStyleReferences: (paragraphs: readonly document_d_exports.Paragraph[]) => void;
7
+ type ImportClipboardStylesOptions = {
8
+ destination: document_d_exports.Document;
9
+ source: document_d_exports.Document;
10
+ paragraphs: readonly document_d_exports.Paragraph[];
11
+ };
12
+ type ImportedClipboardStyles = {
13
+ styles: document_d_exports.StyleDefinitions | undefined;
14
+ paragraphs: document_d_exports.Paragraph[];
15
+ styleIds: ReadonlyMap<string, string>;
16
+ numberingIds: ReadonlySet<number>;
17
+ };
18
+ /** Import the transitive style/numbering dependencies of the clipboard, retaining destination defaults. */
19
+ declare const importClipboardStyles: ({ destination, source, paragraphs }: ImportClipboardStylesOptions) => Result<ImportedClipboardStyles, DocumentOpRefusal>;
20
+ //#endregion
21
+ export { flattenClipboardStyleReferences, importClipboardStyles };