@portone/docx-editor 0.4.0 → 0.5.1

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 (180) hide show
  1. package/CHANGELOG.md +201 -0
  2. package/CONTRIBUTING.md +2 -0
  3. package/assets/editor.png +0 -0
  4. package/dist/DocxEditor.d.ts +1 -1
  5. package/dist/DocxEditor.js +44 -25
  6. package/dist/docx/cloning.js +4 -9
  7. package/dist/docx/commentOnlyChange.d.ts +5 -3
  8. package/dist/docx/comments/grammar.d.ts +27 -10
  9. package/dist/docx/comments/grammar.js +18 -67
  10. package/dist/docx/comments/model.d.ts +5 -6
  11. package/dist/docx/comments/model.js +0 -9
  12. package/dist/docx/comments/parts.d.ts +16 -10
  13. package/dist/docx/comments/parts.js +65 -13
  14. package/dist/docx/comments/people.d.ts +11 -3
  15. package/dist/docx/comments/people.js +20 -6
  16. package/dist/docx/comments/policy.js +7 -1
  17. package/dist/docx/comments/reading.d.ts +4 -2
  18. package/dist/docx/comments/reading.js +1 -14
  19. package/dist/docx/comments/writing.js +65 -28
  20. package/dist/docx/exportDocx.js +10 -14
  21. package/dist/docx/exportRefs.d.ts +8 -3
  22. package/dist/docx/exportRefs.js +2 -1
  23. package/dist/docx/fidelity.js +24 -10
  24. package/dist/docx/fields.d.ts +42 -0
  25. package/dist/docx/fields.js +76 -0
  26. package/dist/docx/formatting/styles.d.ts +0 -2
  27. package/dist/docx/formatting/styles.js +2 -15
  28. package/dist/docx/headersFooters.d.ts +89 -20
  29. package/dist/docx/headersFooters.js +163 -172
  30. package/dist/docx/identities.js +17 -26
  31. package/dist/docx/importDocx.d.ts +1 -1
  32. package/dist/docx/importDocx.js +100 -48
  33. package/dist/docx/importParagraph.d.ts +18 -5
  34. package/dist/docx/importParagraph.js +75 -128
  35. package/dist/docx/importPolicy.d.ts +70 -0
  36. package/dist/docx/importPolicy.js +195 -0
  37. package/dist/docx/importPreserved.d.ts +30 -0
  38. package/dist/docx/importPreserved.js +54 -0
  39. package/dist/docx/importTable.js +56 -12
  40. package/dist/docx/invariants.js +16 -18
  41. package/dist/docx/notes.d.ts +6 -1
  42. package/dist/docx/notes.js +6 -16
  43. package/dist/docx/pageGeometry.d.ts +5 -14
  44. package/dist/docx/pageGeometry.js +0 -12
  45. package/dist/docx/protectionPolicy.d.ts +13 -5
  46. package/dist/docx/protectionPolicy.js +32 -22
  47. package/dist/docx/scan.d.ts +18 -8
  48. package/dist/docx/scan.js +17 -11
  49. package/dist/docx/sections.d.ts +126 -0
  50. package/dist/docx/sections.js +207 -0
  51. package/dist/docx/serializeBlock.d.ts +13 -4
  52. package/dist/docx/serializeBlock.js +16 -18
  53. package/dist/docx/serializeParagraph.d.ts +9 -4
  54. package/dist/docx/serializeParagraph.js +23 -58
  55. package/dist/docx/serializePreserved.d.ts +14 -0
  56. package/dist/docx/serializePreserved.js +24 -0
  57. package/dist/docx/serializeStory.d.ts +17 -0
  58. package/dist/docx/serializeStory.js +17 -0
  59. package/dist/docx/serializeTable.js +10 -13
  60. package/dist/docx/session.d.ts +23 -6
  61. package/dist/docx/session.js +26 -5
  62. package/dist/docx/story.d.ts +140 -0
  63. package/dist/docx/story.js +237 -0
  64. package/dist/docx/storyProjection.js +6 -1
  65. package/dist/docx/tableTemplate.d.ts +25 -0
  66. package/dist/docx/tableTemplate.js +30 -16
  67. package/dist/docx/wrappers.d.ts +67 -0
  68. package/dist/docx/wrappers.js +117 -0
  69. package/dist/editor/clipboard/blockReaders.d.ts +74 -0
  70. package/dist/editor/clipboard/blockReaders.js +193 -0
  71. package/dist/editor/clipboard/htmlReader.d.ts +26 -0
  72. package/dist/editor/clipboard/htmlReader.js +376 -0
  73. package/dist/editor/clipboard/inlineFormatting.d.ts +0 -7
  74. package/dist/editor/clipboard/inlineFormatting.js +1 -7
  75. package/dist/editor/clipboard/internalChannel.d.ts +57 -0
  76. package/dist/editor/clipboard/internalChannel.js +58 -0
  77. package/dist/editor/clipboard/normalizers.d.ts +99 -0
  78. package/dist/editor/clipboard/normalizers.js +199 -0
  79. package/dist/editor/clipboard/parser.d.ts +34 -0
  80. package/dist/editor/clipboard/parser.js +58 -0
  81. package/dist/editor/clipboard/plugin.d.ts +21 -0
  82. package/dist/editor/clipboard/plugin.js +188 -0
  83. package/dist/editor/clipboard/readContext.d.ts +34 -0
  84. package/dist/editor/clipboard/readContext.js +25 -0
  85. package/dist/editor/clipboard/readers.d.ts +33 -0
  86. package/dist/editor/clipboard/readers.js +37 -0
  87. package/dist/editor/clipboard/source.d.ts +12 -0
  88. package/dist/editor/clipboard/source.js +21 -0
  89. package/dist/editor/commands/comments/editing.d.ts +26 -6
  90. package/dist/editor/commands/comments/editing.js +177 -106
  91. package/dist/editor/commands/comments/model.d.ts +34 -17
  92. package/dist/editor/commands/comments/model.js +11 -0
  93. package/dist/editor/commands/comments/reading.d.ts +5 -3
  94. package/dist/editor/commands/comments/reading.js +8 -58
  95. package/dist/editor/commands/index.d.ts +2 -2
  96. package/dist/editor/commands/index.js +2 -0
  97. package/dist/editor/commands/linkCommands.js +20 -5
  98. package/dist/editor/commands/listCommands.d.ts +6 -0
  99. package/dist/editor/commands/listCommands.js +3 -2
  100. package/dist/editor/commands/lockCommands.js +59 -16
  101. package/dist/editor/commands/noteQueries.d.ts +22 -5
  102. package/dist/editor/commands/noteQueries.js +34 -6
  103. package/dist/editor/commands/paragraphCommands.js +2 -2
  104. package/dist/editor/createEditor.js +13 -3
  105. package/dist/editor/documentStyles.d.ts +26 -4
  106. package/dist/editor/documentStyles.js +9 -4
  107. package/dist/editor/imageFiles.d.ts +2 -2
  108. package/dist/editor/imageFiles.js +2 -0
  109. package/dist/editor/insertTable.d.ts +1 -1
  110. package/dist/editor/insertTable.js +2 -2
  111. package/dist/editor/plainText.d.ts +12 -0
  112. package/dist/editor/plainText.js +2 -1
  113. package/dist/editor/plugins/columnResize.js +6 -3
  114. package/dist/editor/plugins/commentComposer.d.ts +28 -0
  115. package/dist/editor/plugins/commentComposer.js +46 -0
  116. package/dist/editor/plugins/commentDecorations.d.ts +29 -2
  117. package/dist/editor/plugins/commentDecorations.js +89 -15
  118. package/dist/editor/plugins/documentProjection.d.ts +31 -0
  119. package/dist/editor/plugins/documentProjection.js +22 -0
  120. package/dist/editor/plugins/imagePaste.js +15 -8
  121. package/dist/editor/plugins/linkPanel.d.ts +7 -3
  122. package/dist/editor/plugins/linkPanel.js +13 -27
  123. package/dist/editor/plugins/panelState.d.ts +67 -0
  124. package/dist/editor/plugins/panelState.js +54 -0
  125. package/dist/editor/plugins/tableContextMenu.d.ts +1 -1
  126. package/dist/editor/plugins/tableContextMenu.js +30 -47
  127. package/dist/editor/plugins/textContextMenu.d.ts +1 -1
  128. package/dist/editor/plugins/textContextMenu.js +30 -47
  129. package/dist/ooxml/fragment.d.ts +11 -0
  130. package/dist/ooxml/fragment.js +14 -4
  131. package/dist/ooxml/image.d.ts +3 -4
  132. package/dist/ooxml/image.js +50 -5
  133. package/dist/ooxml/names.d.ts +6 -0
  134. package/dist/ooxml/names.js +2 -0
  135. package/dist/ooxml/props.d.ts +10 -0
  136. package/dist/ooxml/props.js +28 -4
  137. package/dist/ooxml/rangeMarkers.d.ts +5 -0
  138. package/dist/ooxml/rangeMarkers.js +24 -0
  139. package/dist/page/PageGuides.d.ts +7 -3
  140. package/dist/page/PageGuides.js +13 -24
  141. package/dist/page/pageLayout.d.ts +64 -5
  142. package/dist/page/pageLayout.js +90 -29
  143. package/dist/page/usePageLayout.d.ts +14 -4
  144. package/dist/page/usePageLayout.js +19 -20
  145. package/dist/schema/attrRoles.js +46 -23
  146. package/dist/schema/clipboard.d.ts +72 -0
  147. package/dist/schema/clipboard.js +288 -0
  148. package/dist/schema/docxSchema.d.ts +9 -1
  149. package/dist/schema/docxSchema.js +217 -165
  150. package/dist/schema/editGuard.d.ts +1 -1
  151. package/dist/schema/guards.js +2 -2
  152. package/dist/schema/index.d.ts +1 -0
  153. package/dist/schema/locks.d.ts +3 -0
  154. package/dist/schema/locks.js +29 -12
  155. package/dist/schema/preservedFragments.d.ts +12 -0
  156. package/dist/schema/preservedFragments.js +38 -0
  157. package/dist/schema/preservedGuards.d.ts +9 -7
  158. package/dist/schema/preservedGuards.js +112 -11
  159. package/dist/schema/protection.d.ts +34 -5
  160. package/dist/schema/protection.js +42 -14
  161. package/dist/schema/rendering.d.ts +14 -0
  162. package/dist/schema/rendering.js +27 -12
  163. package/dist/schema/stories.d.ts +49 -0
  164. package/dist/schema/stories.js +78 -0
  165. package/dist/schema/wrappers.d.ts +58 -0
  166. package/dist/schema/wrappers.js +77 -0
  167. package/dist/styles/classNames.d.ts +3 -5
  168. package/dist/styles/classNames.js +3 -5
  169. package/dist/styles.css +74 -12
  170. package/dist/table/resize.d.ts +5 -3
  171. package/dist/ui/CommentsPanel.d.ts +1 -3
  172. package/dist/ui/CommentsPanel.js +24 -15
  173. package/dist/ui/NotesPanel.js +2 -2
  174. package/dist/ui/TextMenu.d.ts +1 -3
  175. package/dist/ui/TextMenu.js +10 -6
  176. package/dist/ui/comments/CommentComposer.d.ts +6 -1
  177. package/dist/ui/comments/CommentComposer.js +26 -3
  178. package/package.json +5 -4
  179. package/dist/editor/externalClipboard.d.ts +0 -22
  180. package/dist/editor/externalClipboard.js +0 -486
@@ -3,10 +3,15 @@
3
3
  *
4
4
  * Body paragraphs and paragraphs inside table cells take the same path.
5
5
  *
6
- * The inlines are grouped three times over: neighbours that share their formatting become one run,
7
- * the runs that share a hyperlink (`w:hyperlink`) go back inside it, and the links and runs that
8
- * share a content control (`w:sdt`) go back inside the wrapper that mark carries. The control is
9
- * the outer of the two wrappers, which is the nesting the mark order records (`schema`).
6
+ * A fragment the reader kept whole goes back where it stood: one kept inside a run is written as
7
+ * a piece of that run, and one kept beside the runs is written between them, which is what the
8
+ * two content models admit.
9
+ *
10
+ * The inlines are grouped from the outside in: neighbours sharing the outermost wrapper each stands
11
+ * inside go back into it, that grouping is made again one wrapper deeper, and at the bottom the
12
+ * neighbours that share their formatting become one run. `schema/wrappers` says which wrapper is
13
+ * which depth, and `docx/wrappers` says what each of them opens and closes as, so a wrapper of a
14
+ * new kind is written here without this file knowing anything about it.
10
15
  */
11
16
  import type { Node as PMNode } from "prosemirror-model";
12
17
  import { type ExportRefs } from "./exportRefs";
@@ -9,8 +9,9 @@ import {
9
9
  } from "../ooxml/image.js";
10
10
  import { wName } from "../ooxml/names.js";
11
11
  import { escapeXml } from "../ooxml/xml.js";
12
+ import { wrapperMarks } from "../schema/wrappers.js";
12
13
  import { NO_EXPORT_REFS } from "./exportRefs.js";
13
- import { relIdIn, withRelId } from "./hyperlink.js";
14
+ import { wrapperKindOf } from "./wrappers.js";
14
15
  function sameMark(a, b) {
15
16
  if (a === null || b === null) return a === b;
16
17
  return a.eq(b);
@@ -65,6 +66,7 @@ function renderInline(node, images) {
65
66
  }
66
67
  if (node.type.name === "hardBreak")
67
68
  return emptyTagXml(wName("br"), rawAttrsOf(node.attrs.brAttrs));
69
+ if (node.type.name === "rawRunContent") return preservedXml(node);
68
70
  if (node.type.name === "image") return renderImage(node, images);
69
71
  if (node.type.name === "commentReference") {
70
72
  const original = node.attrs.referenceXml;
@@ -140,76 +142,39 @@ function addInline(parts, child, images) {
140
142
  parts.push({ kind: "run", mark, pieces: [piece] });
141
143
  }
142
144
  }
143
- function splitParagraphGroups(paragraph, refs) {
144
- const groups = [];
145
- paragraph.forEach((child) => {
146
- const sdt = markOf(child, "sdt");
147
- const link = markOf(child, "link");
148
- let group = groups.at(-1);
149
- if (!group || !sameMark(group.sdt, sdt)) {
150
- group = { sdt, links: [] };
151
- groups.push(group);
152
- }
153
- let inLink = group.links.at(-1);
154
- if (!inLink || !sameMark(inLink.link, link)) {
155
- inLink = { link, parts: [] };
156
- group.links.push(inLink);
157
- }
158
- addInline(inLink.parts, child, refs.images);
159
- });
160
- return groups;
161
- }
162
145
  function renderParagraphPart(part) {
163
146
  if (part.kind === "raw") return part.xml;
164
147
  const open = openTagXml(wName("r"), rawAttrsOf(part.mark?.attrs.rAttrs));
165
148
  const rPr = part.mark?.attrs.rPr;
166
149
  return open + (typeof rPr === "string" ? rPr : "") + part.pieces.join("") + "</w:r>";
167
150
  }
168
- function openLinkTag(mark, links) {
169
- const prefix = mark.attrs.linkPrefix;
170
- const original = typeof prefix === "string" ? prefix : null;
171
- const href = mark.attrs.href;
172
- if (typeof href !== "string") {
173
- if (original !== null) return original;
174
- throw new DocxExportError(
175
- "lost-original",
176
- "a hyperlink carries neither an address nor the opening XML it goes back out as"
177
- );
178
- }
179
- const relId = links.relIdOf(
180
- href,
181
- original === null ? null : relIdIn(original)
182
- );
183
- if (relId === void 0) {
184
- if (original !== null) return original;
185
- throw new DocxExportError(
186
- "unsupported-content",
187
- "an inserted hyperlink has no relationship to point at; export it through exportDocx"
188
- );
151
+ function groupsAtDepth(children, depth) {
152
+ const groups = [];
153
+ for (const child of children) {
154
+ const mark = wrapperMarks(child)[depth] ?? null;
155
+ const last = groups.at(-1);
156
+ if (last && sameMark(last.mark, mark)) last.children.push(child);
157
+ else groups.push({ mark, children: [child] });
189
158
  }
190
- return withRelId(original ?? "<w:hyperlink>", relId);
159
+ return groups;
191
160
  }
192
- function renderLinkGroup(group, links) {
193
- const body = group.parts.map(renderParagraphPart).join("");
194
- if (!group.link) return body;
195
- return `${openLinkTag(group.link, links)}${body}</w:hyperlink>`;
161
+ function renderParts(children, images) {
162
+ const parts = [];
163
+ for (const child of children) addInline(parts, child, images);
164
+ return parts.map(renderParagraphPart).join("");
196
165
  }
197
- function renderParagraphGroup(group, refs) {
198
- const body = group.links.map((inLink) => renderLinkGroup(inLink, refs.links)).join("");
199
- if (!group.sdt) return body;
200
- const prefix = group.sdt.attrs.sdtPrefix;
201
- if (typeof prefix !== "string") {
202
- throw new DocxExportError(
203
- "lost-original",
204
- "a content control has lost the opening XML it goes back out as"
205
- );
206
- }
207
- return `${prefix}<w:sdtContent>${body}</w:sdtContent></w:sdt>`;
166
+ function renderWrapped(children, depth, refs) {
167
+ return groupsAtDepth(children, depth).map((group) => {
168
+ if (group.mark === null) return renderParts(group.children, refs.images);
169
+ const kind = wrapperKindOf(group.mark);
170
+ const body = renderWrapped(group.children, depth + 1, refs);
171
+ return kind.open(group.mark, refs) + body + kind.close(group.mark);
172
+ }).join("");
208
173
  }
209
174
  function serializeParagraph(node, refs = NO_EXPORT_REFS) {
210
175
  const open = openTagXml(wName("p"), rawAttrsOf(node.attrs.pAttrs));
211
176
  const pPr = node.attrs.pPr;
212
- const body = splitParagraphGroups(node, refs).map((group) => renderParagraphGroup(group, refs)).join("");
177
+ const body = renderWrapped(node.children, 0, refs);
213
178
  return open + (typeof pPr === "string" ? pPr : "") + body + "</w:p>";
214
179
  }
215
180
  export {
@@ -0,0 +1,14 @@
1
+ /**
2
+ * How a block the editor never modelled goes back out, wherever in the document it stands.
3
+ *
4
+ * A block opened under the body names the fragment it stands for and the session holds the bytes;
5
+ * one opened inside a table cell was never a fragment of its own and carries its XML along. An
6
+ * edit may move either into the other's place, so the body writer and the cell writer read both
7
+ * the same way, from here.
8
+ */
9
+ import type { Node as PMNode } from "prosemirror-model";
10
+ import type { ExportRefs } from "./exportRefs";
11
+ import { type SessionStore } from "./session";
12
+ /** Why there is no original to write, which a block that came in from another document answers differently */
13
+ export declare function lostOriginal(node: PMNode, session: SessionStore | null): string;
14
+ export declare function serializePreservedBlock(node: PMNode, refs: ExportRefs): string;
@@ -0,0 +1,24 @@
1
+ // src/docx/serializePreserved.ts
2
+ import { DocxExportError } from "../ooxml/errors.js";
3
+ import { originalBlock, splitBlockKey } from "./session.js";
4
+ function lostOriginal(node, session) {
5
+ const srcId = node.attrs.srcId;
6
+ const key = typeof srcId === "string" ? splitBlockKey(srcId) : null;
7
+ return key === null || key.sessionId === session?.sessionId ? "a preserved block has lost its original XML" : `a preserved block comes from another document (${key.sessionId})`;
8
+ }
9
+ function serializePreservedBlock(node, refs) {
10
+ const xml = node.attrs.xml;
11
+ if (typeof xml === "string") return xml;
12
+ const imported = refs.session ? originalBlock(node, refs.session) : void 0;
13
+ if (!imported) {
14
+ throw new DocxExportError(
15
+ "lost-original",
16
+ lostOriginal(node, refs.session)
17
+ );
18
+ }
19
+ return imported.xml;
20
+ }
21
+ export {
22
+ lostOriginal,
23
+ serializePreservedBlock
24
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Writes a side story back into the element it stands in.
3
+ *
4
+ * The rule is the body's rule (`./serializeBlock`): a story nobody edited goes back out as the
5
+ * bytes it arrived as, and one that was edited is written block by block, each untouched block
6
+ * still verbatim. That is what keeps a comment's second paragraph and its run formatting through
7
+ * an edit of the first, and what keeps an untouched Comments part byte-identical.
8
+ */
9
+ import type { Node as PMNode } from "prosemirror-model";
10
+ import type { ExportRefs } from "./exportRefs";
11
+ import type { ImportedStory } from "./story";
12
+ /** The two ends of the element a story stands in, for a story the package did not arrive holding */
13
+ export interface StoryContainer {
14
+ open: string;
15
+ close: string;
16
+ }
17
+ export declare function serializeStory(current: PMNode, imported: ImportedStory | null, container: StoryContainer, refs: ExportRefs): string;
@@ -0,0 +1,17 @@
1
+ // src/docx/serializeStory.ts
2
+ import { sameSource } from "../schema/sourceEquality.js";
3
+ import { blockXml } from "./serializeBlock.js";
4
+ function serializeStory(current, imported, container, refs) {
5
+ const ends = imported ?? container;
6
+ if (imported && sameSource(current, imported.doc)) {
7
+ return ends.open + imported.blocks.map((block) => block.xml).join("") + ends.close;
8
+ }
9
+ const pieces = [];
10
+ current.forEach((block) => {
11
+ pieces.push(blockXml(block, refs, current.childCount === 1));
12
+ });
13
+ return ends.open + pieces.join("") + ends.close;
14
+ }
15
+ export {
16
+ serializeStory
17
+ };
@@ -17,11 +17,8 @@ import {
17
17
  } from "../ooxml/props.js";
18
18
  import { ST_MeasurementOrPercent, ST_TwipsMeasure } from "../ooxml/simpleTypes.js";
19
19
  import { NO_EXPORT_REFS } from "./exportRefs.js";
20
- import {
21
- preservedXml,
22
- rawAttrsOf,
23
- serializeParagraph
24
- } from "./serializeParagraph.js";
20
+ import { rawAttrsOf, serializeParagraph } from "./serializeParagraph.js";
21
+ import { serializePreservedBlock } from "./serializePreserved.js";
25
22
  function propsOf(xml, tag) {
26
23
  if (typeof xml !== "string") return { tag, attrs: null, children: [] };
27
24
  const parsed = parseProps(xml);
@@ -99,12 +96,12 @@ function cellPropsXml(cell, role) {
99
96
  }
100
97
  function cellBlockXml(block, refs) {
101
98
  if (block.type.name === "paragraph") return serializeParagraph(block, refs);
102
- if (block.type.name === "rawBlock") return preservedXml(block);
103
99
  if (block.type.name === "table") return serializeTable(block, refs);
104
- throw new DocxExportError(
105
- "unsupported-content",
106
- `block that cannot go inside a table cell: ${block.type.name}`
107
- );
100
+ return serializePreservedBlock(block, refs);
101
+ }
102
+ function markerXml(node, attr) {
103
+ const xml = node.attrs[attr];
104
+ return typeof xml === "string" ? xml : "";
108
105
  }
109
106
  function wrapInSdt(xml, cell, role) {
110
107
  const prefix = cell.attrs.sdtPrefix;
@@ -114,7 +111,7 @@ function wrapInSdt(xml, cell, role) {
114
111
  function cellXml(cell, role, refs) {
115
112
  const body = role === "continue" ? elementXml(wName("p"), []) : cell.children.map((block) => cellBlockXml(block, refs)).join("");
116
113
  const xml = openTagXml(wName("tc"), rawAttrsOf(cell.attrs.tcAttrs)) + cellPropsXml(cell, role) + body + "</w:tc>";
117
- return wrapInSdt(xml, cell, role);
114
+ return wrapInSdt(xml, cell, role) + (role === "start" ? markerXml(cell, "trailingXml") : "");
118
115
  }
119
116
  function placeRow(row, covering) {
120
117
  const placed = [];
@@ -147,7 +144,7 @@ function rowXml(row, covering, refs) {
147
144
  const cells = placeRow(row, covering).map((placed) => cellXml(placed.cell, placed.role, refs)).join("");
148
145
  const tblPrEx = row.attrs.tblPrEx;
149
146
  const trPr = row.attrs.trPr;
150
- return openTagXml(wName("tr"), rawAttrsOf(row.attrs.trAttrs)) + (typeof tblPrEx === "string" ? tblPrEx : "") + (typeof trPr === "string" ? trPr : "") + cells + "</w:tr>";
147
+ return openTagXml(wName("tr"), rawAttrsOf(row.attrs.trAttrs)) + (typeof tblPrEx === "string" ? tblPrEx : "") + (typeof trPr === "string" ? trPr : "") + markerXml(row, "leadingXml") + cells + "</w:tr>" + markerXml(row, "trailingXml");
151
148
  }
152
149
  function serializeTable(table, refs = NO_EXPORT_REFS) {
153
150
  const covering = [];
@@ -158,7 +155,7 @@ function serializeTable(table, refs = NO_EXPORT_REFS) {
158
155
  "a vertical merge in the table reaches past the last row"
159
156
  );
160
157
  }
161
- return openTagXml(wName("tbl"), rawAttrsOf(table.attrs.tblAttrs)) + tablePropsXml(table) + tableGridXml(table) + rows + "</w:tbl>";
158
+ return openTagXml(wName("tbl"), rawAttrsOf(table.attrs.tblAttrs)) + tablePropsXml(table) + tableGridXml(table) + markerXml(table, "leadingXml") + rows + "</w:tbl>";
162
159
  }
163
160
  export {
164
161
  serializeTable
@@ -10,10 +10,12 @@
10
10
  import type { Node as PMNode } from "prosemirror-model";
11
11
  import type { DocumentDefaults } from "../model/format";
12
12
  import { type Numbering, type NumberingOptions } from "../numbering/parseNumbering";
13
- import type { ImportedComments } from "./comments";
13
+ import { type StoryKey } from "../schema/stories";
14
+ import type { ImportedComments } from "./comments/reading";
14
15
  import { type FormattingContext, type ParagraphStyleOption } from "./formatting";
15
- import type { HeadersFooters } from "./headersFooters";
16
+ import type { HeaderFooterStories } from "./headersFooters";
16
17
  import type { PageGeometry } from "./pageGeometry";
18
+ import type { ImportedStory } from "./story";
17
19
  export interface ImportedBlock {
18
20
  xml: string;
19
21
  /** The node as it was the moment the document was opened. Compared against the current node to tell whether it was edited */
@@ -55,7 +57,7 @@ export declare class SessionStore implements DocxSession, SessionIdentity {
55
57
  readonly defaults: DocumentDefaults;
56
58
  /** The effective automatic tab interval read from settings.xml. Used for display only. */
57
59
  readonly defaultTabStopPt: number;
58
- /** The paper this document is written on, read from the first section. Used for display only: the `w:sectPr` itself goes back out in the preserved tail */
60
+ /** The paper this document is written on, read from the first section. Used for display only: the `w:sectPr` itself goes back out as the text it arrived as */
59
61
  readonly geometry: PageGeometry;
60
62
  /** Everything the display values of a paragraph or a run are resolved against: the style chain, the defaults, the list definitions */
61
63
  readonly formatting: FormattingContext;
@@ -69,9 +71,24 @@ export declare class SessionStore implements DocxSession, SessionIdentity {
69
71
  readonly comments: ImportedComments;
70
72
  /** Comment ids referenced by the original main story. A missing id after editing means deletion. */
71
73
  readonly commentReferenceIds: ReadonlySet<string>;
72
- /** First-section header and footer stories projected for the page preview. */
73
- readonly headersFooters: HeadersFooters;
74
- constructor(opened: Omit<SessionStore, "kind">);
74
+ /**
75
+ * What picking one section's headers and footers takes beyond the section itself: which story
76
+ * each header or footer relationship names, and the document-wide switch between odd and even.
77
+ * The stories themselves stand with every other one, so an edited header is read the same way.
78
+ */
79
+ readonly headerFooterStories: HeaderFooterStories;
80
+ /**
81
+ * The side stories the package arrived holding - a comment's body, a footnote's - each as the
82
+ * blocks it was written in. What the document currently says stands on the document node
83
+ * instead (`docx/story`), so the two compare the way a body block and its original do.
84
+ */
85
+ readonly stories: ReadonlyMap<StoryKey, ImportedStory>;
86
+ constructor(opened: Omit<SessionStore, "kind" | "blocksOf">);
87
+ /**
88
+ * The blocks of one story as it arrived, which is what a block key of that story indexes into.
89
+ * Empty for a story this document does not hold.
90
+ */
91
+ blocksOf(key: string): readonly ImportedBlock[];
75
92
  }
76
93
  /**
77
94
  * The store behind a session token.
@@ -2,6 +2,7 @@
2
2
  import {
3
3
  parseNumbering
4
4
  } from "../numbering/parseNumbering.js";
5
+ import { asStoryKey } from "../schema/stories.js";
5
6
  import {
6
7
  numberingOptionsFor
7
8
  } from "./formatting.js";
@@ -24,7 +25,7 @@ var SessionStore = class {
24
25
  defaults;
25
26
  /** The effective automatic tab interval read from settings.xml. Used for display only. */
26
27
  defaultTabStopPt;
27
- /** The paper this document is written on, read from the first section. Used for display only: the `w:sectPr` itself goes back out in the preserved tail */
28
+ /** The paper this document is written on, read from the first section. Used for display only: the `w:sectPr` itself goes back out as the text it arrived as */
28
29
  geometry;
29
30
  /** Everything the display values of a paragraph or a run are resolved against: the style chain, the defaults, the list definitions */
30
31
  formatting;
@@ -38,8 +39,18 @@ var SessionStore = class {
38
39
  comments;
39
40
  /** Comment ids referenced by the original main story. A missing id after editing means deletion. */
40
41
  commentReferenceIds;
41
- /** First-section header and footer stories projected for the page preview. */
42
- headersFooters;
42
+ /**
43
+ * What picking one section's headers and footers takes beyond the section itself: which story
44
+ * each header or footer relationship names, and the document-wide switch between odd and even.
45
+ * The stories themselves stand with every other one, so an edited header is read the same way.
46
+ */
47
+ headerFooterStories;
48
+ /**
49
+ * The side stories the package arrived holding - a comment's body, a footnote's - each as the
50
+ * blocks it was written in. What the document currently says stands on the document node
51
+ * instead (`docx/story`), so the two compare the way a body block and its original do.
52
+ */
53
+ stories;
43
54
  constructor(opened) {
44
55
  this.sessionId = opened.sessionId;
45
56
  this.parts = opened.parts;
@@ -57,7 +68,17 @@ var SessionStore = class {
57
68
  this.numberingPartPath = opened.numberingPartPath;
58
69
  this.comments = opened.comments;
59
70
  this.commentReferenceIds = opened.commentReferenceIds;
60
- this.headersFooters = opened.headersFooters;
71
+ this.headerFooterStories = opened.headerFooterStories;
72
+ this.stories = opened.stories;
73
+ }
74
+ /**
75
+ * The blocks of one story as it arrived, which is what a block key of that story indexes into.
76
+ * Empty for a story this document does not hold.
77
+ */
78
+ blocksOf(key) {
79
+ if (key === BODY_STORY_KEY) return this.blocks;
80
+ const story = asStoryKey(key);
81
+ return story === null ? [] : this.stories.get(story)?.blocks ?? [];
61
82
  }
62
83
  };
63
84
  function sessionOf(session) {
@@ -100,7 +121,7 @@ function originalBlock(node, session) {
100
121
  if (typeof srcId !== "string") return void 0;
101
122
  const key = splitBlockKey(srcId);
102
123
  if (key === null || key.sessionId !== session.sessionId) return void 0;
103
- return key.storyKey === BODY_STORY_KEY ? session.blocks[key.index] : void 0;
124
+ return session.blocksOf(key.storyKey)[key.index];
104
125
  }
105
126
  export {
106
127
  BODY_STORY_KEY,
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Reads a side story - a comment's body, a footnote's - the way the body of the document is read.
3
+ *
4
+ * A story is a document of the same schema, built by the same block readers, sliced verbatim by
5
+ * the same scanner. That is what lets a comment keep its bold run, its paragraph style and its
6
+ * second paragraph through an edit: the editor no longer holds a body as the plain text it reads
7
+ * as, but as the blocks it was written in, and writes back the ones nobody touched exactly as they
8
+ * arrived (`./serializeStory`).
9
+ *
10
+ * The stories the package arrived holding live on the session; what the document currently says
11
+ * lives on the document node, under `doc.attrs.stories`, so an edit to one rides a transaction and
12
+ * lands in the history beside every other edit.
13
+ */
14
+ import { type Node as PMNode } from "prosemirror-model";
15
+ import { type StoryKey, type StoryKind, storyNodeOf } from "../schema/stories";
16
+ import { type FormattingContext } from "./formatting";
17
+ import { type ImportSources } from "./importParagraph";
18
+ import { type ImportedBlock, type SessionIdentity } from "./session";
19
+ export type { StoryJson, StoryKey, StoryKind } from "../schema/stories";
20
+ export { asStoryKey, sameStory, storiesOf, storyKey, withoutCommentStories, } from "../schema/stories";
21
+ /** The story this key names as a document node, and null where the document holds none */
22
+ export declare const storyOf: typeof storyNodeOf;
23
+ /** One story as the package arrived holding it: where it stands, and the blocks it was written in */
24
+ export interface ImportedStory {
25
+ key: StoryKey;
26
+ kind: StoryKind;
27
+ id: string;
28
+ partPath: string;
29
+ /** The whole element as written, which is what a story nobody edited goes back out as */
30
+ xml: string;
31
+ /**
32
+ * The container's opening tag as written, e.g. `<w:comment w:id="4" w:author="A">`. A container
33
+ * that arrived self-closing is written out as a pair, since an edited story needs one
34
+ */
35
+ open: string;
36
+ /** Everything after the last block, the closing tag included */
37
+ close: string;
38
+ /** The blocks between them, sliced verbatim; a block's `srcId` indexes into this list */
39
+ blocks: readonly ImportedBlock[];
40
+ doc: PMNode;
41
+ }
42
+ /** Where a run of stories of one kind is written down, and what tells one of them from the next */
43
+ export interface StoryPart {
44
+ kind: StoryKind;
45
+ partPath: string;
46
+ xml: string;
47
+ /** The local name of the element each story stands in, e.g. `comment` or `footnote` */
48
+ entryName: string;
49
+ /** The attribute naming which story an element is */
50
+ idAttr: string;
51
+ }
52
+ /** What reading a story takes beyond the text of it */
53
+ export interface StoryDeps {
54
+ session: SessionIdentity;
55
+ /**
56
+ * The images and links of the part the story stands in. A story holds neither a comment nor a
57
+ * note of its own - WordprocessingML puts neither there - so those two are read empty.
58
+ */
59
+ sources: ImportSources;
60
+ formatting: FormattingContext;
61
+ }
62
+ /**
63
+ * Moves a single block into a node.
64
+ *
65
+ * A paragraph always opens editable (`./importParagraph`). What is left over is a table whose rows
66
+ * this reader could not take apart, a range marker standing between blocks, and a block this
67
+ * reader has no reader for at all; each stands as one placeholder naming the original fragment.
68
+ */
69
+ export declare function buildBlock(el: Element, srcId: string, sources: ImportSources, context: FormattingContext): PMNode;
70
+ /**
71
+ * Folds the style chain into the display values.
72
+ *
73
+ * These values are used for display only, so the original XML fragments are left untouched.
74
+ * A table is left alone: which part of it a cell belongs to is what its table style dresses the
75
+ * paragraphs inside by, and `buildTable` is where that is known.
76
+ */
77
+ export declare function withStyleFormats(node: PMNode, context: FormattingContext): PMNode;
78
+ /**
79
+ * One story as it stands in the part, with its blocks read and sliced.
80
+ *
81
+ * A container holding no block at all still opens editable, the way a body holding nothing but a
82
+ * section does: the placeholder paragraph names an empty original fragment, so a story nobody
83
+ * edited goes back out as the bytes it arrived as.
84
+ */
85
+ export declare function readStory(place: {
86
+ kind: StoryKind;
87
+ id: string;
88
+ partPath: string;
89
+ }, container: {
90
+ el: Element;
91
+ xml: string;
92
+ }, deps: StoryDeps): ImportedStory;
93
+ /** Every story one part holds, in the order the part writes them */
94
+ export declare function readStories(part: StoryPart, deps: StoryDeps): readonly ImportedStory[];
95
+ /** The stories of a document keyed the way the session holds them */
96
+ export declare function storiesByKey(stories: Iterable<ImportedStory>): ReadonlyMap<StoryKey, ImportedStory>;
97
+ /**
98
+ * What writing a story takes of a transaction: the document it stands on, and the one step that
99
+ * changes a document attr.
100
+ *
101
+ * A `Transaction` is what a caller passes, and it is named by shape rather than imported because
102
+ * `src/core.ts` must reach neither the editor nor `prosemirror-state` (`src/core.test.ts`), and
103
+ * this module is on the way there.
104
+ */
105
+ interface StoryWriter {
106
+ readonly doc: PMNode;
107
+ setDocAttribute(attr: string, value: unknown): this;
108
+ }
109
+ /** The document with this story written into it, which is the one way a story changes */
110
+ export declare function setStory<T extends StoryWriter>(tr: T, key: StoryKey, story: PMNode): T;
111
+ /** The document with these stories taken out of it, which is what deleting what they say leaves */
112
+ export declare function withoutStories<T extends StoryWriter>(tr: T, keys: Iterable<StoryKey>): T;
113
+ /**
114
+ * A story holding this text, one paragraph broken by a line break wherever the text is.
115
+ *
116
+ * That is the shape a plain-text body has been written in since before there were stories, so a
117
+ * body written through `updateComment` goes out as the bytes it always did.
118
+ */
119
+ export declare function storyFromText(text: string): PMNode;
120
+ /**
121
+ * What one leaf of a story puts on screen.
122
+ *
123
+ * A break ends a line, and a fragment the editor keeps rather than models says what it stands for
124
+ * - the character a `w:noBreakHyphen` draws, the words inside a field - which is the same answer
125
+ * `docx/importPolicy` gave when a body was flattened on the way in. Everything reading a story as
126
+ * plain text - a comment body, a footnote body, a header preview - asks here rather than keeping a
127
+ * vocabulary of its own, so the three of them cannot disagree.
128
+ */
129
+ export declare function storyLeafText(leaf: PMNode): string;
130
+ /** What a story reads as on screen: its paragraphs joined by newlines, its breaks and tabs kept */
131
+ export declare function storyText(story: PMNode | null): string;
132
+ /**
133
+ * The story with the thread key on the last paragraph of it, which is where Word keeps it.
134
+ *
135
+ * The key is written into the paragraph's own attribute text rather than into a model attr,
136
+ * because that text is what the writer puts back and a `w14:paraId` is not a name this schema
137
+ * holds. A paragraph already carrying one keeps it: a key already written is what the thread
138
+ * state elsewhere is keyed by, so re-pointing it would orphan that state.
139
+ */
140
+ export declare function withThreadKeyOn(story: PMNode, paraId: string): PMNode;