@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
@@ -1,5 +1,4 @@
1
1
  // src/docx/importDocx.ts
2
- import { Fragment } from "prosemirror-model";
3
2
  import {
4
3
  bindsWritingPrefix,
5
4
  conformanceOf,
@@ -16,7 +15,10 @@ import {
16
15
  withXmlParser
17
16
  } from "../ooxml/xml.js";
18
17
  import { docxSchema } from "../schema/index.js";
19
- import { commentReferencesIn, readComments } from "./comments.js";
18
+ import {
19
+ commentReferencesIn,
20
+ readComments
21
+ } from "./comments.js";
20
22
  import { openParts } from "./container.js";
21
23
  import {
22
24
  DEFAULT_TAB_STOP_PT,
@@ -28,26 +30,35 @@ import {
28
30
  formattingContextOf,
29
31
  NO_DOCUMENT_DEFAULTS,
30
32
  readDocumentDefaults,
31
- readParagraphStyles,
32
- styledParagraph
33
+ readParagraphStyles
33
34
  } from "./formatting.js";
34
- import { readHeadersFooters } from "./headersFooters.js";
35
+ import { readHeaderFooterStories } from "./headersFooters.js";
35
36
  import { readLinkTargets } from "./hyperlink.js";
36
- import { buildParagraph } from "./importParagraph.js";
37
- import { buildTable } from "./importTable.js";
37
+ import { NO_IMPORT_SOURCES } from "./importParagraph.js";
38
38
  import { readImageSources } from "./media.js";
39
39
  import { NUMBERING_REL_TYPE } from "./newLists.js";
40
40
  import { readNotes } from "./notes.js";
41
41
  import { readPart, relatedPartPath } from "./packageParts.js";
42
- import { readBodyGeometry } from "./pageGeometry.js";
42
+ import { A4_PORTRAIT } from "./pageGeometry.js";
43
43
  import { readRelationships } from "./relationships.js";
44
44
  import { scanBody } from "./scan.js";
45
+ import {
46
+ firstSectPrElement,
47
+ readSectionProperties,
48
+ storyReferenceIds
49
+ } from "./sections.js";
45
50
  import {
46
51
  BODY_STORY_KEY,
47
52
  blockKey,
48
53
  newSessionId,
49
54
  SessionStore
50
55
  } from "./session.js";
56
+ import {
57
+ buildBlock,
58
+ readStories,
59
+ storiesByKey,
60
+ withStyleFormats
61
+ } from "./story.js";
51
62
  import { NO_THEME_FONTS, readThemeFonts } from "./theme.js";
52
63
  var OFFICE_DOCUMENT_REL = `${R_NS}/officeDocument`;
53
64
  var STYLES_REL = `${R_NS}/styles`;
@@ -92,36 +103,6 @@ function assertWritableMainPart(root) {
92
103
  );
93
104
  }
94
105
  }
95
- function buildBlock(el, srcId, sources, context) {
96
- if (el.localName === "bookmarkStart" || el.localName === "bookmarkEnd") {
97
- return docxSchema.nodes.bookmarkBlock.create({
98
- srcId,
99
- name: el.nodeName
100
- });
101
- }
102
- if (el.localName === "p") {
103
- const paragraph = buildParagraph(el, srcId, sources);
104
- if (paragraph) return paragraph;
105
- }
106
- if (el.localName === "tbl") {
107
- const table = buildTable(el, srcId, sources, context);
108
- if (table) return table;
109
- }
110
- return docxSchema.nodes.docxRaw.create({ srcId, name: el.nodeName });
111
- }
112
- function withStyleFormats(node, context) {
113
- if (node.type === docxSchema.nodes.paragraph) {
114
- return styledParagraph(node, context);
115
- }
116
- if (node.childCount === 0 || node.type === docxSchema.nodes.table) {
117
- return node;
118
- }
119
- const children = node.children.map(
120
- (child) => withStyleFormats(child, context)
121
- );
122
- if (children.every((child, i) => child === node.child(i))) return node;
123
- return node.copy(Fragment.fromArray(children));
124
- }
125
106
  function assertScanMatchesDom(children, scan) {
126
107
  if (children.length !== scan.blocks.length) {
127
108
  throw new DocxImportError(
@@ -138,16 +119,46 @@ function assertScanMatchesDom(children, scan) {
138
119
  }
139
120
  });
140
121
  }
141
- function foldTrailingSectPr(scan) {
122
+ function splitTrailingSectPr(scan) {
142
123
  const last = scan.blocks.at(-1);
143
- if (scan.blocks.length < 2 || !last) return scan;
144
- if (localPart(last.name) !== "sectPr") return scan;
124
+ if (!last || localPart(last.name) !== "sectPr") {
125
+ return { ...scan, sectPr: null };
126
+ }
145
127
  return {
146
128
  prefix: scan.prefix,
147
129
  blocks: scan.blocks.slice(0, -1),
148
- suffix: last.xml + scan.suffix
130
+ suffix: scan.suffix,
131
+ sectPr: last.xml
132
+ };
133
+ }
134
+ function storyDeps(parts, partPath, sessionId, formatting) {
135
+ return {
136
+ session: { sessionId },
137
+ sources: {
138
+ ...NO_IMPORT_SOURCES,
139
+ images: readImageSources(parts, partPath),
140
+ links: readLinkTargets(parts, partPath),
141
+ themeFonts: formatting.themeFonts
142
+ },
143
+ formatting
149
144
  };
150
145
  }
146
+ function readCommentStories(comments, parts, sessionId, formatting) {
147
+ const { partPath, xml } = comments;
148
+ if (partPath === null || xml === null) return [];
149
+ return readStories(
150
+ { kind: "comment", partPath, xml, entryName: "comment", idAttr: "id" },
151
+ storyDeps(parts, partPath, sessionId, formatting)
152
+ );
153
+ }
154
+ function readNoteStories(part, kind, parts, sessionId, formatting) {
155
+ const { partPath, xml } = part;
156
+ if (partPath === null || xml === null) return [];
157
+ return readStories(
158
+ { kind, partPath, xml, entryName: kind, idAttr: "id" },
159
+ storyDeps(parts, partPath, sessionId, formatting)
160
+ );
161
+ }
151
162
  function importDocx(input, options) {
152
163
  return withXmlParser(options?.xmlParser, () => readDocx(input));
153
164
  }
@@ -169,8 +180,10 @@ function readDocx(input) {
169
180
  }
170
181
  const children = elementChildren(body);
171
182
  assertScanMatchesDom(children, scanned);
172
- const geometry = readBodyGeometry(body);
173
- const scan = foldTrailingSectPr(scanned);
183
+ const firstSectPr = firstSectPrElement(body);
184
+ const firstSection = firstSectPr ? readSectionProperties(firstSectPr) : null;
185
+ const geometry = firstSection?.geometry ?? A4_PORTRAIT;
186
+ const scan = splitTrailingSectPr(scanned);
174
187
  const blockElements = children.slice(0, scan.blocks.length);
175
188
  const stylesXml = readPart(
176
189
  parts,
@@ -214,7 +227,6 @@ function readDocx(input) {
214
227
  labels.set(id, label);
215
228
  return label;
216
229
  };
217
- const headersFooters = readHeadersFooters(parts, mainPartPath, body);
218
230
  const sources = {
219
231
  images: readImageSources(parts, mainPartPath),
220
232
  themeFonts,
@@ -224,6 +236,24 @@ function readDocx(input) {
224
236
  noteLabel
225
237
  };
226
238
  const sessionId = newSessionId();
239
+ const headerFooters = readHeaderFooterStories(
240
+ parts,
241
+ mainPartPath,
242
+ storyReferenceIds(body),
243
+ (partPath) => storyDeps(parts, partPath, sessionId, formatting)
244
+ );
245
+ const stories = storiesByKey([
246
+ ...readCommentStories(comments, parts, sessionId, formatting),
247
+ ...readNoteStories(
248
+ notes.footnotes,
249
+ "footnote",
250
+ parts,
251
+ sessionId,
252
+ formatting
253
+ ),
254
+ ...readNoteStories(notes.endnotes, "endnote", parts, sessionId, formatting),
255
+ ...headerFooters.stories
256
+ ]);
227
257
  const blockNodes = blockElements.map(
228
258
  (el, i) => withStyleFormats(
229
259
  buildBlock(
@@ -235,7 +265,25 @@ function readDocx(input) {
235
265
  formatting
236
266
  )
237
267
  );
238
- const doc = docxSchema.nodes.doc.create(null, blockNodes);
268
+ if (blockNodes.length === 0 && scan.sectPr !== null) {
269
+ blockNodes.push(
270
+ withStyleFormats(
271
+ docxSchema.nodes.paragraph.create({
272
+ srcId: blockKey({ sessionId }, BODY_STORY_KEY, 0)
273
+ }),
274
+ formatting
275
+ )
276
+ );
277
+ }
278
+ const doc = docxSchema.nodes.doc.create(
279
+ {
280
+ sectPr: scan.sectPr,
281
+ stories: Object.fromEntries(
282
+ Array.from(stories, ([key, story]) => [key, story.doc.toJSON()])
283
+ )
284
+ },
285
+ blockNodes
286
+ );
239
287
  return {
240
288
  doc,
241
289
  notes: fidelityNotesOf(doc, mainPartPath),
@@ -246,7 +294,10 @@ function readDocx(input) {
246
294
  documentPrefix: scan.prefix,
247
295
  documentSuffix: scan.suffix,
248
296
  documentHadBom: hadBom,
249
- blocks: blockNodes.map((node, i) => ({ xml: scan.blocks[i].xml, node })),
297
+ blocks: blockNodes.map((node, i) => ({
298
+ xml: scan.blocks[i]?.xml ?? "",
299
+ node
300
+ })),
250
301
  defaults: stylesDom ? readDocumentDefaults(stylesDom, themeFonts) : NO_DOCUMENT_DEFAULTS,
251
302
  defaultTabStopPt,
252
303
  geometry,
@@ -256,7 +307,8 @@ function readDocx(input) {
256
307
  numberingPartPath,
257
308
  comments,
258
309
  commentReferenceIds: new Set(commentReferencesIn(doc).keys()),
259
- headersFooters
310
+ headerFooterStories: headerFooters.refs,
311
+ stories
260
312
  })
261
313
  };
262
314
  }
@@ -5,11 +5,18 @@
5
5
  *
6
6
  * A `w:sdt` content control and a `w:hyperlink` standing inside the paragraph are both unwrapped, so
7
7
  * the text they hold stays editable, and the wrapper each came in rides along on a mark to go back
8
- * out around the same text. A control may hold a link; a link holding a control is a nesting the
9
- * marks cannot record in that order, so the paragraph stays preserved.
8
+ * out around the same text. Either may hold the other and a control may hold a control: the nesting
9
+ * is read as the file wrote it and recorded on the marks (`docx/wrappers`). A link inside a link is
10
+ * the one arrangement no mark can record, and the inner one stays whole as it always has.
11
+ *
12
+ * Nothing here demotes a paragraph. What the editor has no model for - a field character, a
13
+ * tracked insertion, a symbol, a drawing nobody could read - is kept where it stood, inside its
14
+ * run or beside it, by the rule `docx/importPolicy` gives it for the level it stands at. The
15
+ * paragraph around it stays editable, which is what a document full of `w:lastRenderedPageBreak`
16
+ * needs.
10
17
  */
11
18
  import type { Node as PMNode } from "prosemirror-model";
12
- import { type ImportedComments } from "./comments";
19
+ import { type ImportedComments } from "./comments/reading";
13
20
  import { type LinkTargets } from "./hyperlink";
14
21
  import type { ImageSources } from "./media";
15
22
  import { type ImportedNotes, type NoteKind } from "./notes";
@@ -28,5 +35,11 @@ export interface ImportSources {
28
35
  noteLabel: (kind: NoteKind, id: string) => string;
29
36
  }
30
37
  export declare const NO_IMPORT_SOURCES: ImportSources;
31
- /** Moves a paragraph into an editable node. null if it holds content we do not model */
32
- export declare function buildParagraph(el: Element, srcId: string | null, sources?: ImportSources): PMNode | null;
38
+ /**
39
+ * Moves a paragraph into an editable node.
40
+ *
41
+ * Every paragraph opens editable. A child the reader has no model for, and a wrapper whose shape
42
+ * it could not put back together, is kept whole where it stood instead of standing the whole
43
+ * paragraph down.
44
+ */
45
+ export declare function buildParagraph(el: Element, srcId: string | null, sources?: ImportSources): PMNode;
@@ -9,20 +9,20 @@ import {
9
9
  serializeXml
10
10
  } from "../ooxml/xml.js";
11
11
  import { docxSchema } from "../schema/index.js";
12
- import {
13
- commentParaId,
14
- importedCommentReplies,
15
- NO_COMMENTS
16
- } from "./comments.js";
12
+ import { commentParaId, importedCommentReplies } from "./comments/model.js";
13
+ import { NO_COMMENTS } from "./comments/reading.js";
17
14
  import { readParagraphFormat, readRunFormat } from "./formatting.js";
15
+ import { NO_LINK_TARGETS } from "./hyperlink.js";
16
+ import { policyFor } from "./importPolicy.js";
18
17
  import {
19
- NO_LINK_TARGETS,
20
- readHyperlinkWrapper
21
- } from "./hyperlink.js";
18
+ buildPreservedInline,
19
+ buildPreservedRunContent,
20
+ preservationOf
21
+ } from "./importPreserved.js";
22
22
  import { NO_IMAGES } from "./media.js";
23
23
  import { NO_NOTES, noteById } from "./notes.js";
24
- import { readSdtWrapper } from "./sdt.js";
25
24
  import { NO_THEME_FONTS } from "./theme.js";
25
+ import { wrapperFits, wrapperKindFor } from "./wrappers.js";
26
26
  function runMark(run, themeFonts) {
27
27
  const rPr = childByLocalName(run, "rPr");
28
28
  return docxSchema.marks.run.create({
@@ -47,7 +47,7 @@ function buildImage(el, marks, images) {
47
47
  marks
48
48
  );
49
49
  }
50
- function buildRunChild(el, marks, images, comments, notes, noteLabel) {
50
+ function buildModelledRunChild(el, marks, images, comments, notes, noteLabel) {
51
51
  switch (el.localName) {
52
52
  case "t": {
53
53
  const text = el.textContent ?? "";
@@ -86,9 +86,6 @@ function buildRunChild(el, marks, images, comments, notes, noteLabel) {
86
86
  authorId: comment?.authorId ?? null,
87
87
  initials: comment?.initials ?? null,
88
88
  date: comment?.date ?? null,
89
- text: comment?.text ?? "",
90
- commentXml: comment?.xml ?? null,
91
- imported: true,
92
89
  paraId,
93
90
  resolved: comment?.resolved ?? false,
94
91
  extensionXml: comment?.extensionXml ?? null,
@@ -114,7 +111,6 @@ function buildRunChild(el, marks, images, comments, notes, noteLabel) {
114
111
  kind,
115
112
  id,
116
113
  label: note ? noteLabel(kind, id) : "?",
117
- text: note?.text ?? "",
118
114
  customMarkFollows,
119
115
  referenceXml: serializeXml(el)
120
116
  },
@@ -135,22 +131,30 @@ function buildRunNodes(run, images, themeFonts, comments, notes, noteLabel, wrap
135
131
  );
136
132
  const nodes = [];
137
133
  for (const child of elementChildren(run)) {
138
- if (child.localName === "rPr") continue;
139
- const built = buildRunChild(
140
- child,
141
- marks,
142
- images,
143
- comments,
144
- notes,
145
- noteLabel
134
+ const policy = policyFor(child, "r");
135
+ if (policy.tier === "model") {
136
+ if (child.localName === "rPr") continue;
137
+ const built = buildModelledRunChild(
138
+ child,
139
+ marks,
140
+ images,
141
+ comments,
142
+ notes,
143
+ noteLabel
144
+ );
145
+ if (built !== null) {
146
+ nodes.push(...built);
147
+ continue;
148
+ }
149
+ }
150
+ nodes.push(
151
+ buildPreservedRunContent(child, marks, preservationOf(policy, "r"))
146
152
  );
147
- if (built === null) return null;
148
- nodes.push(...built);
149
153
  }
150
154
  if (nodes.length === 0) {
151
155
  return [
152
156
  docxSchema.nodes.rawInline.create(
153
- { xml: serializeXml(run) },
157
+ { xml: serializeXml(run), element: run.localName },
154
158
  null,
155
159
  // The wrappers have to close again around this XML on export, so their marks ride along here too
156
160
  wrappers
@@ -159,20 +163,6 @@ function buildRunNodes(run, images, themeFonts, comments, notes, noteLabel, wrap
159
163
  }
160
164
  return nodes;
161
165
  }
162
- var controlCounts = /* @__PURE__ */ new WeakMap();
163
- var linkCounts = /* @__PURE__ */ new WeakMap();
164
- function nextKey(counts, el) {
165
- const key = counts.get(el.ownerDocument) ?? 0;
166
- counts.set(el.ownerDocument, key + 1);
167
- return key;
168
- }
169
- var NOTHING_ON_SCREEN = [
170
- "bookmarkStart",
171
- "bookmarkEnd",
172
- "proofErr",
173
- "permStart",
174
- "permEnd"
175
- ];
176
166
  function annotationId(el) {
177
167
  return Array.from(el.attributes).find((attribute) => attribute.localName === "id")?.value ?? null;
178
168
  }
@@ -190,109 +180,66 @@ var NO_IMPORT_SOURCES = {
190
180
  notes: NO_NOTES,
191
181
  noteLabel: () => "?"
192
182
  };
193
- function buildWrappedNodes(content, sources, wrappers, linkAllowed) {
183
+ function buildModelledInline(child, sources, depth, wrappers) {
184
+ if (child.localName === "r") {
185
+ return buildRunNodes(
186
+ child,
187
+ sources.images,
188
+ sources.themeFonts,
189
+ sources.comments,
190
+ sources.notes,
191
+ sources.noteLabel,
192
+ wrappers
193
+ );
194
+ }
195
+ const kind = wrapperKindFor(child);
196
+ if (kind) {
197
+ if (!wrapperFits(kind, wrappers)) return null;
198
+ const reading = kind.read(child, depth, sources);
199
+ if (!reading) return null;
200
+ return buildContent(reading.content, sources, depth + 1, [
201
+ ...wrappers,
202
+ reading.mark
203
+ ]);
204
+ }
205
+ const commentMarker = commentRangeNode(child, wrappers);
206
+ return commentMarker === null ? null : [commentMarker];
207
+ }
208
+ function buildContent(content, sources, depth, wrappers) {
194
209
  const nodes = [];
195
210
  for (const child of elementChildren(content)) {
196
- if (child.localName === "r") {
197
- const built = buildRunNodes(
198
- child,
199
- sources.images,
200
- sources.themeFonts,
201
- sources.comments,
202
- sources.notes,
203
- sources.noteLabel,
204
- wrappers
205
- );
206
- if (built === null) return null;
207
- nodes.push(...built);
208
- continue;
209
- }
210
- if (child.localName === "hyperlink" && linkAllowed) {
211
- const built = buildHyperlinkNodes(child, sources, wrappers);
212
- if (built === null) return null;
213
- nodes.push(...built);
214
- continue;
211
+ const policy = policyFor(child, "wrapper");
212
+ if (policy.tier === "model") {
213
+ const built = buildModelledInline(child, sources, depth, wrappers);
214
+ if (built !== null) {
215
+ nodes.push(...built);
216
+ continue;
217
+ }
215
218
  }
216
- const commentMarker = commentRangeNode(child, wrappers);
217
- if (commentMarker) {
218
- nodes.push(commentMarker);
219
- continue;
220
- }
221
- if (!NOTHING_ON_SCREEN.includes(child.localName)) return null;
222
219
  nodes.push(
223
- docxSchema.nodes.rawInline.create(
224
- { xml: serializeXml(child) },
225
- null,
226
- wrappers
227
- )
220
+ buildPreservedInline(child, wrappers, preservationOf(policy, "wrapper"))
228
221
  );
229
222
  }
230
223
  if (nodes.length === 0) return null;
231
224
  return nodes;
232
225
  }
233
- function buildHyperlinkNodes(el, sources, wrappers) {
234
- const wrapper = readHyperlinkWrapper(el);
235
- if (!wrapper) return null;
236
- const mark = docxSchema.marks.link.create({
237
- linkPrefix: wrapper.prefix,
238
- href: wrapper.relId === null ? null : sources.links.get(wrapper.relId) ?? null,
239
- linkKey: nextKey(linkCounts, el)
240
- });
241
- return buildWrappedNodes(el, sources, [...wrappers, mark], false);
242
- }
243
- function buildSdtNodes(el, sources) {
244
- const wrapper = readSdtWrapper(el);
245
- if (!wrapper) return null;
246
- const mark = docxSchema.marks.sdt.create({
247
- sdtPrefix: wrapper.prefix,
248
- sdtKey: nextKey(controlCounts, el),
249
- contentsLocked: wrapper.contentsLocked,
250
- deletionLocked: wrapper.deletionLocked
251
- });
252
- return buildWrappedNodes(wrapper.content, sources, [mark], true);
253
- }
254
226
  function buildParagraph(el, srcId, sources = NO_IMPORT_SOURCES) {
255
- const { images, themeFonts } = sources;
256
227
  const inline = [];
257
228
  let pPrElement = null;
258
229
  for (const child of elementChildren(el)) {
259
- if (child.localName === "pPr") {
260
- pPrElement = child;
261
- continue;
230
+ const policy = policyFor(child, "p");
231
+ if (policy.tier === "model") {
232
+ if (child.localName === "pPr") {
233
+ pPrElement = child;
234
+ continue;
235
+ }
236
+ const built = buildModelledInline(child, sources, 0, []);
237
+ if (built !== null) {
238
+ inline.push(...built);
239
+ continue;
240
+ }
262
241
  }
263
- if (child.localName === "r") {
264
- const runNodes = buildRunNodes(
265
- child,
266
- images,
267
- themeFonts,
268
- sources.comments,
269
- sources.notes,
270
- sources.noteLabel
271
- );
272
- if (runNodes === null) return null;
273
- inline.push(...runNodes);
274
- continue;
275
- }
276
- if (child.localName === "sdt") {
277
- const sdtNodes = buildSdtNodes(child, sources);
278
- if (sdtNodes === null) return null;
279
- inline.push(...sdtNodes);
280
- continue;
281
- }
282
- if (child.localName === "hyperlink") {
283
- const linkNodes = buildHyperlinkNodes(child, sources, []);
284
- if (linkNodes === null) return null;
285
- inline.push(...linkNodes);
286
- continue;
287
- }
288
- const commentMarker = commentRangeNode(child);
289
- if (commentMarker) {
290
- inline.push(commentMarker);
291
- continue;
292
- }
293
- inline.push(
294
- docxSchema.nodes.rawInline.create({ xml: serializeXml(child) })
295
- );
242
+ inline.push(buildPreservedInline(child, [], preservationOf(policy, "p")));
296
243
  }
297
244
  return docxSchema.nodes.paragraph.create(
298
245
  {
@@ -0,0 +1,70 @@
1
+ /**
2
+ * What every WordprocessingML element the readers may meet is worth keeping as, one sub-table per
3
+ * level of the document.
4
+ *
5
+ * A reader used to know a handful of elements and answer `null` for the rest, and that `null`
6
+ * travelled up through the run and the wrapper until a whole paragraph stood as a placeholder. The
7
+ * table below turns that around: every level has a narrowest preservation of its own - a run child
8
+ * stays inside its run, a paragraph child inside its paragraph, a block stays a block - so an
9
+ * element nobody modelled costs the element and nothing around it.
10
+ *
11
+ * Only `tbl` and `tr` have no node to hold a stranger (`table` holds rows, `tableRow` holds
12
+ * cells), so those two levels demote the table instead, and that is the whole of what demotion is
13
+ * now: this default and a structure a reader could not take apart.
14
+ *
15
+ * A level is a content model of `wml.xsd` rather than an element name, because the same name means
16
+ * different things in different places: `sdt` is a control the paragraph reader unwraps, a cell
17
+ * wrapper under `tr`, a block placeholder under the body, and a row wrapper nothing reads. Keys are
18
+ * Clark names (`{namespace}localName`) so that `m:oMath` and `w:sdt` sit in one map.
19
+ */
20
+ import type { PreservedDisplay } from "../schema";
21
+ export type { PreservedDisplay };
22
+ export type PreservationTier =
23
+ /** A reader turns it into an editable node */
24
+ "model"
25
+ /** A range marker: invisible, and its pair and order must survive every edit */
26
+ | "marker"
27
+ /** A trace the producer regenerates: invisible, and an edit may drop it */
28
+ | "ignorable"
29
+ /** A `CT_R` child: kept verbatim inside its run, wearing the run mark */
30
+ | "runContent"
31
+ /** A paragraph child: kept verbatim inside the paragraph */
32
+ | "inline"
33
+ /** A body or cell child: kept verbatim as a block placeholder */
34
+ | "block";
35
+ export interface PreservationRule {
36
+ readonly tier: Exclude<PreservationTier, "model">;
37
+ readonly display: PreservedDisplay;
38
+ /** The character a `text` display puts on screen */
39
+ readonly text?: string;
40
+ /**
41
+ * Whether the deletion guard answers for this element: a range marker whose pair must survive,
42
+ * and a field piece whose begin/separate/end order must survive.
43
+ */
44
+ readonly guarded: boolean;
45
+ }
46
+ export type ElementPolicy = {
47
+ readonly tier: "model";
48
+ } | PreservationRule;
49
+ /** What a level with no node to hold a stranger answers: the container around it is demoted */
50
+ export type DemotionPolicy = {
51
+ readonly tier: "demote";
52
+ };
53
+ /** The levels a reader walks. Each is a content model of its own in `wml.xsd` */
54
+ export type ContentLevel = "body" | "tbl" | "tr" | "tc" | "p" | "wrapper" | "r";
55
+ export declare function clarkName(el: Element): string;
56
+ export declare const WML_POLICY: Readonly<Record<ContentLevel, ReadonlyMap<string, ElementPolicy>>>;
57
+ /** The levels that have a node of their own to keep a stranger in */
58
+ export type PreservingLevel = Exclude<ContentLevel, "tbl" | "tr">;
59
+ export declare function defaultPreservation(level: PreservingLevel): PreservationRule;
60
+ export declare function policyFor(el: Element, level: ContentLevel): ElementPolicy | DemotionPolicy;
61
+ /**
62
+ * The text one run child puts on screen, or null for a child that puts nothing there.
63
+ *
64
+ * This is where the table's answer becomes text, and a preserved fragment carries it along as the
65
+ * `text` a reader wrote onto it. Everything reading a story as plain text afterwards - a footnote
66
+ * body, a comment body, a header - asks `docx/story`'s `storyLeafText` for that same answer rather
67
+ * than keeping a vocabulary of its own, so the three of them cannot disagree about what a `w:cr`
68
+ * or a `w:noBreakHyphen` reads as.
69
+ */
70
+ export declare function runContentText(el: Element): string | null;