@stll/folio-core 0.60.0 → 0.61.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 (126) hide show
  1. package/README.md +13 -0
  2. package/dist/ai-edits/apply.js +139 -32
  3. package/dist/ai-edits/headless.d.ts +3 -0
  4. package/dist/ai-edits/headless.js +57 -63
  5. package/dist/ai-edits/note-story-inventory.d.ts +7 -0
  6. package/dist/ai-edits/note-story-inventory.js +11 -0
  7. package/dist/ai-edits/read.js +16 -9
  8. package/dist/ai-edits/revisionStretches.js +52 -1
  9. package/dist/ai-edits/table-row-column-mutations.js +6 -1
  10. package/dist/compare/reproducible-package.js +2 -1
  11. package/dist/compare/verification.js +1 -0
  12. package/dist/compat/eigenpal.d.ts +3 -1
  13. package/dist/controller/canonicalClipboard.js +2 -1
  14. package/dist/controller/canonicalListRendering.d.ts +4 -6
  15. package/dist/controller/canonicalListRendering.js +3 -157
  16. package/dist/controller/headerFooterEditorManager.d.ts +1 -0
  17. package/dist/controller/headerFooterEditorManager.js +11 -0
  18. package/dist/controller/hiddenEditorManager.d.ts +1 -1
  19. package/dist/controller/hiddenEditorManager.js +3 -1
  20. package/dist/controller/layoutPipeline.d.ts +3 -1
  21. package/dist/controller/layoutPipeline.js +13 -4
  22. package/dist/controller/layoutSession.d.ts +2 -1
  23. package/dist/display-list/build/imagePrimitives.js +3 -6
  24. package/dist/docx/canonicalStoryRepack.js +3 -2
  25. package/dist/docx/commentTextParagraph.d.ts +6 -0
  26. package/dist/docx/commentTextParagraph.js +15 -0
  27. package/dist/docx/conformance.js +6 -1
  28. package/dist/docx/diagramPreview.js +2 -1
  29. package/dist/docx/documentParser.d.ts +13 -1
  30. package/dist/docx/documentParser.js +1 -1
  31. package/dist/docx/encryption/compoundFile.d.ts +0 -8
  32. package/dist/docx/encryption/compoundFile.js +2 -1
  33. package/dist/docx/encryption/cryptoBytes.d.ts +2 -2
  34. package/dist/docx/encryption/cryptoBytes.js +3 -18
  35. package/dist/docx/encryption/encryptionInfo.d.ts +0 -5
  36. package/dist/docx/encryption/encryptionInfo.js +2 -1
  37. package/dist/docx/ensureParaIds.js +2 -1
  38. package/dist/docx/groupDrawingParser.js +6 -1
  39. package/dist/docx/listModelNormalization.d.ts +10 -0
  40. package/dist/docx/listModelNormalization.js +166 -0
  41. package/dist/docx/metadataPrivacy.js +3 -2
  42. package/dist/docx/metafileRaster.js +2 -1
  43. package/dist/docx/packageParts.js +6 -5
  44. package/dist/docx/paragraphParser.js +6 -4
  45. package/dist/docx/parser.js +5 -4
  46. package/dist/docx/preservedRunContent.js +11 -2
  47. package/dist/docx/removeHeaderFooterParts.js +8 -7
  48. package/dist/docx/replyToComment.js +3 -13
  49. package/dist/docx/rezip.js +88 -89
  50. package/dist/docx/runParser.js +12 -6
  51. package/dist/docx/runPropertyPresence.d.ts +11 -0
  52. package/dist/docx/runPropertyPresence.js +14 -0
  53. package/dist/docx/selectiveSave.js +15 -14
  54. package/dist/docx/serializer/runSerializer.js +3 -2
  55. package/dist/docx/serializer/tableSerializer.js +2 -1
  56. package/dist/docx/server/applyDocxXmlPatchProposal.js +2 -2
  57. package/dist/docx/server/boundedArchive.js +2 -1
  58. package/dist/docx/server/evaluateDocxXmlPatchProposal.js +2 -1
  59. package/dist/docx/server/inspectDocxPackage.js +5 -1
  60. package/dist/docx/server/validateDocxConformance.js +2 -1
  61. package/dist/docx/tableParser.js +7 -4
  62. package/dist/docx/tableRowPropertyPresence.d.ts +9 -0
  63. package/dist/docx/tableRowPropertyPresence.js +15 -0
  64. package/dist/docx/unzip.js +2 -1
  65. package/dist/docx/xmlEncoding.js +6 -5
  66. package/dist/fonts/fontsourceFaces.js +2 -1
  67. package/dist/fonts/sfnt/parse.js +6 -9
  68. package/dist/fonts/sfnt/subset.js +2 -1
  69. package/dist/fonts/sfnt/tables.js +2 -3
  70. package/dist/fonts/sfnt/woff.js +2 -1
  71. package/dist/index.d.ts +3 -1
  72. package/dist/internal/revisionResolutionInline.d.ts +2 -1
  73. package/dist/internal/revisionResolutionInline.js +22 -1
  74. package/dist/internal/wholeStoryRevisionResolution.js +24 -7
  75. package/dist/layout-bridge/convert/flowConversionShared.d.ts +2 -0
  76. package/dist/layout-bridge/convert/footnoteLayout.d.ts +4 -0
  77. package/dist/layout-bridge/convert/footnoteLayout.js +13 -3
  78. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +5 -0
  79. package/dist/layout-bridge/convert/headerFooterLayout.js +12 -15
  80. package/dist/layout-bridge/convert/paragraphRuns.js +2 -1
  81. package/dist/layout-engine/measure/crossRunWords.js +12 -2
  82. package/dist/managers/AutoSaveManager.js +2 -1
  83. package/dist/managers/EditorModeManager.d.ts +10 -1
  84. package/dist/managers/EditorModeManager.js +15 -1
  85. package/dist/managers/TableSelectionManager.d.ts +1 -1
  86. package/dist/managers/autoSaveCodec.js +2 -6
  87. package/dist/managers/reviewDisplayMode.d.ts +8 -0
  88. package/dist/managers/reviewDisplayMode.js +15 -0
  89. package/dist/paged-layout/pageText.d.ts +11 -2
  90. package/dist/paged-layout/pageText.js +8 -3
  91. package/dist/pdf/fonts.js +5 -14
  92. package/dist/pdf/images.js +3 -2
  93. package/dist/pdf/objects.d.ts +0 -11
  94. package/dist/pdf/objects.js +7 -9
  95. package/dist/pdf/writePdf.js +2 -1
  96. package/dist/prosemirror/commands/comments.js +51 -13
  97. package/dist/prosemirror/containerFinalParagraph.d.ts +8 -8
  98. package/dist/prosemirror/containerFinalParagraph.js +8 -8
  99. package/dist/prosemirror/conversion/fromProseDoc.js +9 -4
  100. package/dist/prosemirror/conversion/toProseDoc.js +4 -4
  101. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +16 -2
  102. package/dist/prosemirror/findReplaceSelection.d.ts +11 -1
  103. package/dist/prosemirror/findReplaceSelection.js +15 -1
  104. package/dist/prosemirror/markupViewProjection.d.ts +10 -1
  105. package/dist/prosemirror/markupViewProjection.js +21 -3
  106. package/dist/prosemirror/plugins/transactionInvariants.js +6 -1
  107. package/dist/prosemirror/replacedAnnotations.d.ts +15 -1
  108. package/dist/prosemirror/replacedAnnotations.js +71 -12
  109. package/dist/prosemirror/runFormattingReconciliation.d.ts +3 -1
  110. package/dist/prosemirror/runFormattingReconciliation.js +10 -2
  111. package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +5 -7
  112. package/dist/prosemirror/runIdentityAcrossRevisions.js +66 -38
  113. package/dist/prosemirror/runPropertyChangeResolution.js +13 -1
  114. package/dist/utils/base64.d.ts +1 -31
  115. package/dist/utils/base64.js +19 -33
  116. package/dist/utils/clipboard.js +5 -4
  117. package/dist/utils/colorResolver.js +11 -2
  118. package/dist/utils/findReplace.d.ts +5 -0
  119. package/dist/utils/replaceText.js +21 -4
  120. package/dist/utils/sliceText.d.ts +2 -0
  121. package/dist/utils/sliceText.js +2 -0
  122. package/dist/utils/textEncoding.d.ts +2 -0
  123. package/dist/utils/textEncoding.js +2 -0
  124. package/dist/utils/tiffConverter.d.ts +0 -9
  125. package/dist/utils/tiffConverter.js +2 -4
  126. package/package.json +51 -25
@@ -1,10 +1,8 @@
1
1
  import { Node } from "prosemirror-model";
2
2
  //#region src/prosemirror/runIdentityAcrossRevisions.d.ts
3
- /**
4
- * `nodes`, one inline container's content, with each run a revision cut given
5
- * the identity of the run it was cut from. A control is a container of its
6
- * own. `nodes` itself comes back when nothing changes.
7
- */
8
- declare const continueRunIdentitiesAcrossRevisions: (nodes: readonly Node[]) => readonly Node[];
3
+ /** The continuation policy covers every declared source-carrier mark. */
4
+ declare const SOURCE_CARRIER_MARK_NAMES: ("hyperlink" | "runIdentity")[];
5
+ /** Every source-carrier family uses the same revision-boundary continuation policy. */
6
+ declare const continueSourceCarrierIdentitiesAcrossRevisions: (nodes: readonly Node[]) => readonly Node[];
9
7
  //#endregion
10
- export { continueRunIdentitiesAcrossRevisions };
8
+ export { SOURCE_CARRIER_MARK_NAMES, continueSourceCarrierIdentitiesAcrossRevisions };
@@ -1,52 +1,73 @@
1
- import { expectRunIdentityMarkAttrs } from "./attrs/index.js";
1
+ import { expectHyperlinkMarkAttrs, expectRunIdentityMarkAttrs } from "./attrs/index.js";
2
2
  import "./extensions/nodes/SdtExtension.js";
3
3
  import { RUN_IDENTITY_MARK_NAME } from "./runIdentity.js";
4
4
  import { Fragment, Mark } from "prosemirror-model";
5
5
  //#region src/prosemirror/runIdentityAcrossRevisions.ts
6
6
  /**
7
- * One run identity for the pieces of a `w:r` a tracked change cut.
8
- *
9
- * A revision inside a run splits it: `<w:r a>Sel</w:r><w:del><w:r a>ler</w:r></w:del>`,
10
- * `<w:r a>Sel</w:r><w:ins>…</w:ins><w:r a>ler</w:r>`, or a `w:rPrChange` on
11
- * one piece of the run only. Read back, each
12
- * `w:r` is its own authored run, so resolving the revision would leave two
13
- * runs where the source had one. The package cannot say whether the two were
14
- * one run, but runs that carry the same attributes and that only revised
15
- * content holds apart are what such a cut writes, so the later piece takes the
16
- * earlier one's identity. The pieces stay separate runs while the revision
17
- * holds them apart, and become one run once it is resolved and they state the
18
- * same properties again (a run joins its owner only then, see `fromProseDoc`).
19
- *
20
- * Runs the package writes side by side under the same revision state keep
21
- * their own identities.
7
+ * One source identity for matching runs and hyperlinks a revision cut apart.
8
+ * Serialized fragments mint separate identities on reopen. Matching fragments
9
+ * across a revision boundary or revised-only bridge take the earlier identity,
10
+ * so resolution can rejoin them. Every non-identity attribute must match.
11
+ * Adjacent carriers under the same revision state keep their source identities.
22
12
  */
23
13
  const REVISION_MARK_NAMES = /* @__PURE__ */ new Set([
24
14
  "insertion",
25
15
  "deletion",
26
16
  "runPropertyChange"
27
17
  ]);
28
- const identityOf = (node) => node.marks.find((mark) => mark.type.name === RUN_IDENTITY_MARK_NAME);
18
+ const CARRIER_OWNERS = [{
19
+ markName: RUN_IDENTITY_MARK_NAME,
20
+ read: (mark) => {
21
+ const attrs = expectRunIdentityMarkAttrs(mark);
22
+ return {
23
+ id: attrs.id,
24
+ payloadKey: JSON.stringify({
25
+ ...attrs,
26
+ id: 0
27
+ })
28
+ };
29
+ }
30
+ }, {
31
+ markName: "hyperlink",
32
+ read: (mark) => {
33
+ const attrs = expectHyperlinkMarkAttrs(mark);
34
+ if (typeof attrs._docxHyperlinkIndex !== "number") return void 0;
35
+ return {
36
+ id: attrs._docxHyperlinkIndex,
37
+ payloadKey: JSON.stringify({
38
+ ...attrs,
39
+ _docxHyperlinkIndex: 0
40
+ })
41
+ };
42
+ }
43
+ }];
44
+ /** The continuation policy covers every declared source-carrier mark. */
45
+ const SOURCE_CARRIER_MARK_NAMES = CARRIER_OWNERS.map(({ markName }) => markName);
46
+ const identityOf = (node, owner) => {
47
+ const mark = node.marks.find((candidate) => candidate.type.name === owner.markName);
48
+ if (!mark) return void 0;
49
+ const identity = owner.read(mark);
50
+ return identity === void 0 ? void 0 : {
51
+ mark,
52
+ ...identity
53
+ };
54
+ };
29
55
  const revisionMarksOf = (node) => node.marks.filter((mark) => REVISION_MARK_NAMES.has(mark.type.name));
30
- /** The identity's attrs without the id: what the run carries. */
31
- const payloadKey = (identity) => JSON.stringify({
32
- ...expectRunIdentityMarkAttrs(identity),
33
- id: 0
34
- });
35
56
  /** Whether `right` continues `left`'s run across a revision boundary. */
36
- const continuesRun = ({ left, right, bridged }) => {
37
- const leftIdentity = identityOf(left);
38
- const rightIdentity = identityOf(right);
39
- if (!leftIdentity || !rightIdentity || leftIdentity.eq(rightIdentity)) return false;
40
- return (bridged || !Mark.sameSet(revisionMarksOf(left), revisionMarksOf(right))) && payloadKey(leftIdentity) === payloadKey(rightIdentity);
57
+ const continuesCarrier = ({ left, right, bridged, owner }) => {
58
+ const leftIdentity = identityOf(left, owner);
59
+ const rightIdentity = identityOf(right, owner);
60
+ if (!leftIdentity || !rightIdentity || leftIdentity.mark.eq(rightIdentity.mark)) return false;
61
+ return (bridged || !Mark.sameSet(revisionMarksOf(left), revisionMarksOf(right))) && leftIdentity.payloadKey === rightIdentity.payloadKey;
41
62
  };
42
63
  /**
43
- * `nodes`, one inline container's content, with each run a revision cut given
44
- * the identity of the run it was cut from. A control is a container of its
64
+ * `nodes`, one inline container's content, with each revision fragment given
65
+ * the identity of its matching source carrier. A control is a container of its
45
66
  * own. `nodes` itself comes back when nothing changes.
46
67
  */
47
- const continueRunIdentitiesAcrossRevisions = (nodes) => {
68
+ const continueIdentities = (nodes, owner) => {
48
69
  const renamed = /* @__PURE__ */ new Map();
49
- /** The last run with an identity, while only revised content follows it. */
70
+ /** The last carrier with an identity, while only revised content follows it. */
50
71
  let anchor;
51
72
  let bridged = false;
52
73
  let changed = false;
@@ -57,25 +78,26 @@ const continueRunIdentitiesAcrossRevisions = (nodes) => {
57
78
  node.forEach((child) => {
58
79
  children.push(child);
59
80
  });
60
- const continued = continueRunIdentitiesAcrossRevisions(children);
81
+ const continued = continueIdentities(children, owner);
61
82
  if (continued === children) return node;
62
83
  changed = true;
63
84
  return node.copy(Fragment.fromArray([...continued]));
64
85
  }
65
- const identity = identityOf(node);
86
+ const identity = identityOf(node, owner);
66
87
  if (!identity) {
67
88
  if (anchor && revisionMarksOf(node).length > 0) bridged = true;
68
89
  else anchor = void 0;
69
90
  return node;
70
91
  }
71
- const id = expectRunIdentityMarkAttrs(identity).id;
92
+ const id = identity.id;
72
93
  let replacement = renamed.get(id);
73
- if (!replacement && anchor && continuesRun({
94
+ if (!replacement && anchor && continuesCarrier({
74
95
  left: anchor,
75
96
  right: node,
76
- bridged
97
+ bridged,
98
+ owner
77
99
  })) {
78
- replacement = identityOf(anchor);
100
+ replacement = identityOf(anchor, owner)?.mark;
79
101
  if (replacement) renamed.set(id, replacement);
80
102
  }
81
103
  const next = replacement ? node.mark(replacement.addToSet(node.marks)) : node;
@@ -86,5 +108,11 @@ const continueRunIdentitiesAcrossRevisions = (nodes) => {
86
108
  });
87
109
  return changed ? result : nodes;
88
110
  };
111
+ /** Every source-carrier family uses the same revision-boundary continuation policy. */
112
+ const continueSourceCarrierIdentitiesAcrossRevisions = (nodes) => {
113
+ let continued = nodes;
114
+ for (const owner of CARRIER_OWNERS) continued = continueIdentities(continued, owner);
115
+ return continued;
116
+ };
89
117
  //#endregion
90
- export { continueRunIdentitiesAcrossRevisions };
118
+ export { SOURCE_CARRIER_MARK_NAMES, continueSourceCarrierIdentitiesAcrossRevisions };
@@ -1,6 +1,8 @@
1
+ import { expectRunIdentityMarkAttrs } from "./attrs/index.js";
1
2
  import { runFormattingInlineAtomDisposition } from "./runFormattingInlineCarriers.js";
2
3
  import { RUN_FORMATTING_MARK_NAMES } from "./runFormattingMarkNames.js";
3
4
  import { readAuthoredRunFormatting, reconcileRunFormattingMarks } from "./runFormattingReconciliation.js";
5
+ import { RUN_IDENTITY_MARK_NAME, runIdentityAttrs } from "./runIdentity.js";
4
6
  //#region src/prosemirror/runPropertyChangeResolution.ts
5
7
  /**
6
8
  * Rebuild the pre-change run marks against the paragraph's live style cascade.
@@ -8,12 +10,22 @@ import { readAuthoredRunFormatting, reconcileRunFormattingMarks } from "./runFor
8
10
  * owner so their reject semantics cannot diverge at the bulk threshold.
9
11
  */
10
12
  const reconstructRejectedRunFormattingMarks = ({ node, paragraphContext, previousFormatting, styleResolver }) => {
11
- return reconcileRunFormattingMarks({
13
+ const formatting = reconcileRunFormattingMarks({
12
14
  authoredFormatting: previousFormatting ?? {},
13
15
  context: paragraphContext,
14
16
  node,
15
17
  ...styleResolver !== void 0 ? { styleResolver } : {}
16
18
  }).filter(({ type }) => RUN_FORMATTING_MARK_NAMES.has(type.name));
19
+ if (previousFormatting === void 0 || Object.keys(previousFormatting).length > 0) return formatting;
20
+ const identityType = node.type.schema.marks[RUN_IDENTITY_MARK_NAME];
21
+ if (!identityType) return formatting;
22
+ const identity = node.marks.find(({ type }) => type === identityType);
23
+ const attrs = identity ? expectRunIdentityMarkAttrs(identity) : void 0;
24
+ return [...formatting, identityType.create(runIdentityAttrs(attrs?.id ?? 0, {
25
+ preservedAttributes: attrs?.preservedAttributes ?? void 0,
26
+ preserved: attrs?.preserved ?? void 0,
27
+ emptyFormatting: true
28
+ }))];
17
29
  };
18
30
  /** Restored deletions inherit the live paragraph cascade without authoring their historical visuals. */
19
31
  const restoreHistoricalRunFormatting = ({ node, paragraphContext, styleResolver }) => {
@@ -1,35 +1,5 @@
1
+ import { bytesToBase64, encodeBase64Portable } from "@stll/utf8-boundaries";
1
2
  //#region src/utils/base64.d.ts
2
- /**
3
- * The package's one bytes-to-base64 encoder.
4
- *
5
- * Every call site that spelled this itself got a different answer to the same
6
- * two questions, and one of them got the first question wrong:
7
- *
8
- * - `btoa(new TextDecoder("latin1").decode(bytes))` is not a binary string.
9
- * The Encoding standard maps the `latin1` label to windows-1252, so bytes
10
- * 0x80-0x9F decode to characters outside Latin-1 and `btoa` throws
11
- * `InvalidCharacterError` on them. Ordinary image bytes contain those values,
12
- * so the browser path of that spelling fails on real input.
13
- * - `String.fromCharCode(...chunk)` is correct but spreads a chunk's worth of
14
- * arguments per call, and the intermediate binary string is as large as the
15
- * input.
16
- *
17
- * Neither question has to be asked per call site, so it is asked here once.
18
- * The runtime's own encoder answers it when the runtime has one, and the
19
- * portable encoder below answers it when not; both produce canonical base64,
20
- * so the choice is invisible in the output.
21
- */
22
- /**
23
- * Encode into exactly one intermediate buffer: ASCII codes are written into a
24
- * single array and decoded once, so a megabyte-scale input leaves the output
25
- * string and nothing else.
26
- *
27
- * Exported because the runtimes the tests run on all carry a native encoder,
28
- * and the fallback is the branch the browsers that lack one take.
29
- */
30
- declare const encodeBase64Portable: (bytes: Uint8Array) => string;
31
- /** Encode bytes as canonical, padded, standard-alphabet base64. */
32
- declare const bytesToBase64: (bytes: Uint8Array) => string;
33
3
  /** Encode bytes as a `data:` URL with the given media type. */
34
4
  declare const bytesToDataUrl: (bytes: Uint8Array, mimeType: string) => string;
35
5
  //#endregion
@@ -1,40 +1,26 @@
1
+ import { bytesToBase64, bytesToBase64 as bytesToBase64$1, encodeBase64Portable } from "@stll/utf8-boundaries";
1
2
  //#region src/utils/base64.ts
2
- const BASE64_CODES = Uint8Array.from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", (character) => character.charCodeAt(0));
3
- const BASE64_PAD = 61;
4
3
  /**
5
- * Encode into exactly one intermediate buffer: ASCII codes are written into a
6
- * single array and decoded once, so a megabyte-scale input leaves the output
7
- * string and nothing else.
4
+ * The package's one bytes-to-base64 encoder.
8
5
  *
9
- * Exported because the runtimes the tests run on all carry a native encoder,
10
- * and the fallback is the branch the browsers that lack one take.
6
+ * Every call site that spelled this itself got a different answer to the same
7
+ * two questions, and one of them got the first question wrong:
8
+ *
9
+ * - `btoa(new TextDecoder("latin1").decode(bytes))` is not a binary string.
10
+ * The Encoding standard maps the `latin1` label to windows-1252, so bytes
11
+ * 0x80-0x9F decode to characters outside Latin-1 and `btoa` throws
12
+ * `InvalidCharacterError` on them. Ordinary image bytes contain those values,
13
+ * so the browser path of that spelling fails on real input.
14
+ * - `String.fromCharCode(...chunk)` is correct but spreads a chunk's worth of
15
+ * arguments per call, and the intermediate binary string is as large as the
16
+ * input.
17
+ *
18
+ * Neither question has to be asked per call site, so it is asked here once.
19
+ * The runtime's own encoder answers it when the runtime has one, and the
20
+ * portable encoder in the shared owner answers it when not; both produce canonical base64,
21
+ * so the choice is invisible in the output.
11
22
  */
12
- const encodeBase64Portable = (bytes) => {
13
- const groups = Math.ceil(bytes.length / 3);
14
- const encoded = new Uint8Array(groups * 4);
15
- let read = 0;
16
- let write = 0;
17
- for (; read + 2 < bytes.length; read += 3) {
18
- const triple = bytes[read] << 16 | bytes[read + 1] << 8 | bytes[read + 2];
19
- encoded[write] = BASE64_CODES[triple >> 18 & 63];
20
- encoded[write + 1] = BASE64_CODES[triple >> 12 & 63];
21
- encoded[write + 2] = BASE64_CODES[triple >> 6 & 63];
22
- encoded[write + 3] = BASE64_CODES[triple & 63];
23
- write += 4;
24
- }
25
- const remaining = bytes.length - read;
26
- if (remaining > 0) {
27
- const tail = bytes[read] << 16 | (remaining === 2 ? bytes[read + 1] << 8 : 0);
28
- encoded[write] = BASE64_CODES[tail >> 18 & 63];
29
- encoded[write + 1] = BASE64_CODES[tail >> 12 & 63];
30
- encoded[write + 2] = remaining === 2 ? BASE64_CODES[tail >> 6 & 63] : BASE64_PAD;
31
- encoded[write + 3] = BASE64_PAD;
32
- }
33
- return new TextDecoder().decode(encoded);
34
- };
35
- /** Encode bytes as canonical, padded, standard-alphabet base64. */
36
- const bytesToBase64 = ("toBase64" in Uint8Array.prototype ? (bytes) => bytes.toBase64() : void 0) ?? encodeBase64Portable;
37
23
  /** Encode bytes as a `data:` URL with the given media type. */
38
- const bytesToDataUrl = (bytes, mimeType) => `data:${mimeType};base64,${bytesToBase64(bytes)}`;
24
+ const bytesToDataUrl = (bytes, mimeType) => `data:${mimeType};base64,${bytesToBase64$1(bytes)}`;
39
25
  //#endregion
40
26
  export { bytesToBase64, bytesToDataUrl, encodeBase64Portable };
@@ -1,5 +1,6 @@
1
1
  import { htmlCommentEnd } from "./htmlComments.js";
2
2
  import { stripXmlDeclarations } from "./stripXmlDeclarations.js";
3
+ import { encodeUtf8, readBlobText } from "./textEncoding.js";
3
4
  import { sanitizeXmlCharacters } from "@stll/docx-core";
4
5
  import createDOMPurify from "dompurify";
5
6
  //#region src/utils/clipboard.ts
@@ -171,8 +172,8 @@ function paragraphsToClipboardContent(paragraphs, includeFormatting = true) {
171
172
  async function writeToClipboard(content) {
172
173
  try {
173
174
  const items = [new ClipboardItem({
174
- [CLIPBOARD_TYPES.PLAIN]: new Blob([content.plainText], { type: CLIPBOARD_TYPES.PLAIN }),
175
- [CLIPBOARD_TYPES.HTML]: new Blob([content.html], { type: CLIPBOARD_TYPES.HTML })
175
+ [CLIPBOARD_TYPES.PLAIN]: new Blob([encodeUtf8(content.plainText)], { type: CLIPBOARD_TYPES.PLAIN }),
176
+ [CLIPBOARD_TYPES.HTML]: new Blob([encodeUtf8(content.html)], { type: CLIPBOARD_TYPES.HTML })
176
177
  })];
177
178
  await navigator.clipboard.write(items);
178
179
  return true;
@@ -222,8 +223,8 @@ async function parseClipboardItems(items, cleanWordFormatting) {
222
223
  let html = "";
223
224
  let plainText = "";
224
225
  for (const item of items) {
225
- if (item.types.includes(CLIPBOARD_TYPES.HTML)) html = await (await item.getType(CLIPBOARD_TYPES.HTML)).text();
226
- if (item.types.includes(CLIPBOARD_TYPES.PLAIN)) plainText = await (await item.getType(CLIPBOARD_TYPES.PLAIN)).text();
226
+ if (item.types.includes(CLIPBOARD_TYPES.HTML)) html = await readBlobText(await item.getType(CLIPBOARD_TYPES.HTML));
227
+ if (item.types.includes(CLIPBOARD_TYPES.PLAIN)) plainText = await readBlobText(await item.getType(CLIPBOARD_TYPES.PLAIN));
227
228
  }
228
229
  return parseClipboardHtml(html, plainText, cleanWordFormatting);
229
230
  }
@@ -1,3 +1,4 @@
1
+ import { sliceText } from "./sliceText.js";
1
2
  import { THEME_COLOR_BY_SCHEME_COLOR_VALUE, isSchemeColorValue, isThemeColor, themeColorSlot } from "@stll/docx-core/model";
2
3
  //#region src/utils/colorResolver.ts
3
4
  /**
@@ -109,7 +110,11 @@ function parseModifierValue(hexValue) {
109
110
  * @returns RGB object with r, g, b values 0-255
110
111
  */
111
112
  function hexToRgb(hex) {
112
- const normalized = hex.padStart(6, "0").slice(0, 6);
113
+ const normalized = sliceText({
114
+ text: hex.padStart(6, "0"),
115
+ units: "codeUnits",
116
+ end: 6
117
+ });
113
118
  const r = Number.parseInt(normalized.slice(0, 2), 16);
114
119
  const g = Number.parseInt(normalized.slice(2, 4), 16);
115
120
  const b = Number.parseInt(normalized.slice(4, 6), 16);
@@ -293,7 +298,11 @@ function parseColorString(colorString) {
293
298
  const hex = normalized.replace(/^#/u, "").toUpperCase();
294
299
  if (/^[0-9A-F]{6}$/iu.test(hex)) return { rgb: hex };
295
300
  if (/^[0-9A-F]{3}$/iu.test(hex)) return { rgb: hex.split("").map((c) => c + c).join("") };
296
- return { rgb: hex.padStart(6, "0").slice(0, 6) };
301
+ return { rgb: sliceText({
302
+ text: hex.padStart(6, "0"),
303
+ units: "codeUnits",
304
+ end: 6
305
+ }) };
297
306
  }
298
307
  /**
299
308
  * Create a ColorValue from theme color reference
@@ -7,6 +7,11 @@ type FindMatch = {
7
7
  startOffset: number;
8
8
  endOffset: number;
9
9
  text: string;
10
+ /** Authored editor range when the match was found in a resolved markup view. */
11
+ authoredRange?: {
12
+ from: number;
13
+ to: number;
14
+ };
10
15
  };
11
16
  type FindOptions = {
12
17
  matchCase: boolean;
@@ -1,15 +1,32 @@
1
1
  import { cloneDocumentWithParagraphPropertySources } from "../docx/paragraphPropertySource.js";
2
+ import { textFormattingToMarks } from "../prosemirror/commands/formatting.js";
3
+ import { fromProseDoc } from "../prosemirror/conversion/fromProseDoc.js";
4
+ import { toProseDoc } from "../prosemirror/conversion/toProseDoc.js";
5
+ import { resolveFindMatchRange } from "../prosemirror/findReplaceSelection.js";
6
+ import { schema } from "../prosemirror/schema/index.js";
7
+ import { EditorState } from "prosemirror-state";
2
8
  //#region src/utils/replaceText.ts
9
+ /**
10
+ * Lightweight text replacement on the document model.
11
+ * Used by find & replace. Operates immutably.
12
+ */
3
13
  /** Replace text within a range in the document. Returns a new document. */
4
14
  function replaceTextInDocument(doc, range, text, formatting) {
5
15
  const newDoc = cloneDocumentWithParagraphPropertySources(doc);
6
16
  const body = newDoc.package.document;
7
17
  const { start, end } = range;
8
18
  if (start.paragraphIndex === end.paragraphIndex) {
9
- const paragraph = getParagraphByIndex(body, start.paragraphIndex);
10
- if (!paragraph) return newDoc;
11
- paragraph.content = deleteTextInParagraph(paragraph, start.offset, end.offset);
12
- paragraph.content = insertTextAtOffset(paragraph, start.offset, text, formatting);
19
+ const proseDoc = toProseDoc(newDoc);
20
+ const proseRange = resolveFindMatchRange(proseDoc, {
21
+ paragraphIndex: start.paragraphIndex,
22
+ startOffset: start.offset,
23
+ endOffset: end.offset
24
+ });
25
+ if (proseRange === null) return newDoc;
26
+ const transaction = EditorState.create({ doc: proseDoc }).tr;
27
+ if (text.length > 0 && formatting !== void 0) transaction.replaceWith(proseRange.from, proseRange.to, schema.text(text, textFormattingToMarks(formatting)));
28
+ else transaction.insertText(text, proseRange.from, proseRange.to);
29
+ return fromProseDoc(transaction.doc, newDoc, { stylesheetSource: { type: "package" } });
13
30
  } else {
14
31
  const startParagraph = getParagraphByIndex(body, start.paragraphIndex);
15
32
  if (!startParagraph) return newDoc;
@@ -0,0 +1,2 @@
1
+ import { sliceText } from "@stll/utf8-boundaries";
2
+ export { sliceText };
@@ -0,0 +1,2 @@
1
+ import { sliceText } from "@stll/utf8-boundaries";
2
+ export { sliceText };
@@ -0,0 +1,2 @@
1
+ import { base64ToBytes, bytesToBase64, bytesToBase64Url, bytesToHex, createTextReader, decodeText, encodeBinaryString, encodeUtf16BE, encodeUtf16LE, encodeUtf8, readBlobText } from "@stll/utf8-boundaries";
2
+ export { base64ToBytes, bytesToBase64, bytesToBase64Url, bytesToHex, createTextReader, decodeText, encodeBinaryString, encodeUtf16BE, encodeUtf16LE, encodeUtf8, readBlobText };
@@ -0,0 +1,2 @@
1
+ import { base64ToBytes, bytesToBase64, bytesToBase64Url, bytesToHex, createTextReader, decodeText, encodeBinaryString, encodeUtf16BE, encodeUtf16LE, encodeUtf8, readBlobText } from "@stll/utf8-boundaries";
2
+ export { base64ToBytes, bytesToBase64, bytesToBase64Url, bytesToHex, createTextReader, decodeText, encodeBinaryString, encodeUtf16BE, encodeUtf16LE, encodeUtf8, readBlobText };
@@ -1,13 +1,4 @@
1
1
  //#region src/utils/tiffConverter.d.ts
2
- /**
3
- * TIFF to PNG Converter
4
- *
5
- * Browsers don't support TIFF in `<img>` tags, so DOCX-embedded TIFFs are
6
- * decoded with utif2 and re-encoded as PNG via the Canvas API. Returns
7
- * null in environments without Canvas (Node.js parsing); callers fall
8
- * back to the raw TIFF data URL, which won't render in browsers but is
9
- * fine for headless round-trips.
10
- */
11
2
  /**
12
3
  * Budget shared by every TIFF in one package. The per-image cap bounds a single
13
4
  * decode; without a running total a package of many large TIFFs still adds up.
@@ -1,3 +1,4 @@
1
+ import { base64ToBytes } from "./textEncoding.js";
1
2
  //#region src/utils/tiffConverter.ts
2
3
  /**
3
4
  * TIFF to PNG Converter
@@ -79,10 +80,7 @@ function dataUrlToArrayBuffer(dataUrl) {
79
80
  const base64 = dataUrl.split(",", 2)[1];
80
81
  if (!base64) return null;
81
82
  try {
82
- const binary = atob(base64);
83
- const bytes = new Uint8Array(binary.length);
84
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.codePointAt(i) ?? 0;
85
- return bytes.buffer;
83
+ return base64ToBytes(base64).buffer;
86
84
  } catch {
87
85
  return null;
88
86
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/folio-core",
3
- "version": "0.60.0",
3
+ "version": "0.61.0",
4
4
  "description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
5
5
  "keywords": [
6
6
  "document-model",
@@ -36,98 +36,123 @@
36
36
  "exports": {
37
37
  ".": {
38
38
  "types": "./dist/index.d.ts",
39
- "import": "./dist/index.js"
39
+ "import": "./dist/index.js",
40
+ "default": "./dist/index.js"
40
41
  },
41
42
  "./i18n/messages/*": {
42
43
  "types": "./dist/i18n/messages/*.gen.d.ts",
43
- "import": "./dist/i18n/messages/*.gen.js"
44
+ "import": "./dist/i18n/messages/*.gen.js",
45
+ "default": "./dist/i18n/messages/*.gen.js"
44
46
  },
45
47
  "./i18n/messages/*.gen": {
46
48
  "types": "./dist/i18n/messages/*.gen.d.ts",
47
- "import": "./dist/i18n/messages/*.gen.js"
49
+ "import": "./dist/i18n/messages/*.gen.js",
50
+ "default": "./dist/i18n/messages/*.gen.js"
48
51
  },
49
52
  "./i18n/messages/locales": {
50
53
  "types": "./dist/i18n/messages/locales.d.ts",
51
- "import": "./dist/i18n/messages/locales.js"
54
+ "import": "./dist/i18n/messages/locales.js",
55
+ "default": "./dist/i18n/messages/locales.js"
52
56
  },
53
57
  "./markdown": {
54
58
  "types": "./dist/markdown/index.d.ts",
55
- "import": "./dist/markdown/index.js"
59
+ "import": "./dist/markdown/index.js",
60
+ "default": "./dist/markdown/index.js"
56
61
  },
57
62
  "./server": {
58
63
  "types": "./dist/server.d.ts",
59
- "import": "./dist/server.js"
64
+ "import": "./dist/server.js",
65
+ "default": "./dist/server.js"
60
66
  },
61
67
  "./redline": {
62
68
  "types": "./dist/redline.d.ts",
63
- "import": "./dist/redline.js"
69
+ "import": "./dist/redline.js",
70
+ "default": "./dist/redline.js"
64
71
  },
65
72
  "./ai-edits": {
66
73
  "types": "./dist/ai-edits/index.d.ts",
67
- "import": "./dist/ai-edits/index.js"
74
+ "import": "./dist/ai-edits/index.js",
75
+ "default": "./dist/ai-edits/index.js"
68
76
  },
69
77
  "./content-controls": {
70
78
  "types": "./dist/content-controls/index.d.ts",
71
- "import": "./dist/content-controls/index.js"
79
+ "import": "./dist/content-controls/index.js",
80
+ "default": "./dist/content-controls/index.js"
72
81
  },
73
82
  "./compat/eigenpal": {
74
83
  "types": "./dist/compat/eigenpal.d.ts",
75
- "import": "./dist/compat/eigenpal.js"
84
+ "import": "./dist/compat/eigenpal.js",
85
+ "default": "./dist/compat/eigenpal.js"
76
86
  },
77
87
  "./watermark": {
78
88
  "types": "./dist/watermark/index.d.ts",
79
- "import": "./dist/watermark/index.js"
89
+ "import": "./dist/watermark/index.js",
90
+ "default": "./dist/watermark/index.js"
80
91
  },
81
92
  "./layout-engine": {
82
93
  "types": "./dist/layout-engine/index.d.ts",
83
- "import": "./dist/layout-engine/index.js"
94
+ "import": "./dist/layout-engine/index.js",
95
+ "default": "./dist/layout-engine/index.js"
84
96
  },
85
97
  "./layout-painter": {
86
98
  "types": "./dist/layout-painter/index.d.ts",
87
- "import": "./dist/layout-painter/index.js"
99
+ "import": "./dist/layout-painter/index.js",
100
+ "default": "./dist/layout-painter/index.js"
88
101
  },
89
102
  "./layout-bridge/engine/measuring": {
90
103
  "types": "./dist/layout-bridge/engine/measuring/index.d.ts",
91
- "import": "./dist/layout-bridge/engine/measuring/index.js"
104
+ "import": "./dist/layout-bridge/engine/measuring/index.js",
105
+ "default": "./dist/layout-bridge/engine/measuring/index.js"
92
106
  },
93
107
  "./prosemirror": {
94
108
  "types": "./dist/prosemirror/index.d.ts",
95
- "import": "./dist/prosemirror/index.js"
109
+ "import": "./dist/prosemirror/index.js",
110
+ "default": "./dist/prosemirror/index.js"
96
111
  },
97
112
  "./prosemirror/attrs": {
98
113
  "types": "./dist/prosemirror/attrs/index.d.ts",
99
- "import": "./dist/prosemirror/attrs/index.js"
114
+ "import": "./dist/prosemirror/attrs/index.js",
115
+ "default": "./dist/prosemirror/attrs/index.js"
100
116
  },
101
117
  "./prosemirror/conversion": {
102
118
  "types": "./dist/prosemirror/conversion/index.d.ts",
103
- "import": "./dist/prosemirror/conversion/index.js"
119
+ "import": "./dist/prosemirror/conversion/index.js",
120
+ "default": "./dist/prosemirror/conversion/index.js"
104
121
  },
105
122
  "./prosemirror/extensions": {
106
123
  "types": "./dist/prosemirror/extensions/index.d.ts",
107
- "import": "./dist/prosemirror/extensions/index.js"
124
+ "import": "./dist/prosemirror/extensions/index.js",
125
+ "default": "./dist/prosemirror/extensions/index.js"
108
126
  },
109
127
  "./prosemirror/schema": {
110
128
  "types": "./dist/prosemirror/schema/index.d.ts",
111
- "import": "./dist/prosemirror/schema/index.js"
129
+ "import": "./dist/prosemirror/schema/index.js",
130
+ "default": "./dist/prosemirror/schema/index.js"
112
131
  },
113
132
  "./docx": {
114
133
  "types": "./dist/docx/index.d.ts",
115
- "import": "./dist/docx/index.js"
134
+ "import": "./dist/docx/index.js",
135
+ "default": "./dist/docx/index.js"
116
136
  },
117
137
  "./docx/document-clone": {
118
138
  "types": "./dist/docx/documentClone.d.ts",
119
- "import": "./dist/docx/documentClone.js"
139
+ "import": "./dist/docx/documentClone.js",
140
+ "default": "./dist/docx/documentClone.js"
120
141
  },
121
142
  "./text-shaping": {
122
143
  "types": "./dist/text-shaping.d.ts",
123
- "import": "./dist/text-shaping.js"
144
+ "import": "./dist/text-shaping.js",
145
+ "default": "./dist/text-shaping.js"
124
146
  },
125
147
  "./text-shaping/wasm": "./dist/generated/text_shaper_bg.wasm",
126
148
  "./docx/paragraphPropertySource": null,
127
149
  "./internal/*": null,
150
+ "./_virtual/*": null,
151
+ "./generated/*": null,
128
152
  "./*": {
129
153
  "types": "./dist/*.d.ts",
130
- "import": "./dist/*.js"
154
+ "import": "./dist/*.js",
155
+ "default": "./dist/*.js"
131
156
  }
132
157
  },
133
158
  "publishConfig": {
@@ -146,9 +171,10 @@
146
171
  "perf": "bun scripts/profile-editor.ts"
147
172
  },
148
173
  "dependencies": {
149
- "@stll/docx-core": "^0.37.0",
174
+ "@stll/docx-core": "^0.38.0",
150
175
  "@stll/docx-utils": "^0.1.0",
151
176
  "@stll/template-conditions": "^0.6.2",
177
+ "@stll/utf8-boundaries": "0.1.0",
152
178
  "better-result": "3.0.1",
153
179
  "csstype": "^3.1.3",
154
180
  "dompurify": "^3.4.16",