@portone/docx-editor 0.3.0 → 0.5.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 (217) hide show
  1. package/CHANGELOG.md +205 -0
  2. package/assets/editor.png +0 -0
  3. package/dist/DocxEditor.d.ts +1 -1
  4. package/dist/DocxEditor.js +45 -25
  5. package/dist/core.d.ts +1 -1
  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 +78 -35
  20. package/dist/docx/exportDocx.js +30 -18
  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/attrs.d.ts +20 -2
  27. package/dist/docx/formatting/attrs.js +57 -2
  28. package/dist/docx/formatting/context.d.ts +10 -2
  29. package/dist/docx/formatting/context.js +23 -5
  30. package/dist/docx/formatting/resolve.d.ts +10 -4
  31. package/dist/docx/formatting/resolve.js +31 -11
  32. package/dist/docx/formatting/styles.d.ts +25 -2
  33. package/dist/docx/formatting/styles.js +84 -3
  34. package/dist/docx/headersFooters.d.ts +89 -20
  35. package/dist/docx/headersFooters.js +163 -172
  36. package/dist/docx/hyperlink.d.ts +14 -4
  37. package/dist/docx/hyperlink.js +4 -1
  38. package/dist/docx/importDocx.d.ts +1 -1
  39. package/dist/docx/importDocx.js +135 -74
  40. package/dist/docx/importParagraph.d.ts +16 -4
  41. package/dist/docx/importParagraph.js +87 -93
  42. package/dist/docx/importPolicy.d.ts +70 -0
  43. package/dist/docx/importPolicy.js +190 -0
  44. package/dist/docx/importPreserved.d.ts +30 -0
  45. package/dist/docx/importPreserved.js +54 -0
  46. package/dist/docx/importTable.d.ts +2 -2
  47. package/dist/docx/importTable.js +107 -66
  48. package/dist/docx/invariants.js +42 -31
  49. package/dist/docx/newLists.d.ts +30 -0
  50. package/dist/docx/newLists.js +24 -1
  51. package/dist/docx/notes.d.ts +6 -1
  52. package/dist/docx/notes.js +6 -16
  53. package/dist/docx/numberingPlanner.d.ts +6 -3
  54. package/dist/docx/numberingPlanner.js +38 -7
  55. package/dist/docx/pageGeometry.d.ts +5 -14
  56. package/dist/docx/pageGeometry.js +0 -12
  57. package/dist/docx/protectionPolicy.d.ts +13 -5
  58. package/dist/docx/protectionPolicy.js +32 -22
  59. package/dist/docx/scan.d.ts +18 -8
  60. package/dist/docx/scan.js +17 -11
  61. package/dist/docx/sections.d.ts +126 -0
  62. package/dist/docx/sections.js +207 -0
  63. package/dist/docx/serializeBlock.d.ts +13 -4
  64. package/dist/docx/serializeBlock.js +16 -18
  65. package/dist/docx/serializeParagraph.d.ts +4 -0
  66. package/dist/docx/serializeParagraph.js +1 -0
  67. package/dist/docx/serializePreserved.d.ts +14 -0
  68. package/dist/docx/serializePreserved.js +24 -0
  69. package/dist/docx/serializeStory.d.ts +17 -0
  70. package/dist/docx/serializeStory.js +17 -0
  71. package/dist/docx/serializeTable.js +10 -13
  72. package/dist/docx/session.d.ts +24 -7
  73. package/dist/docx/session.js +37 -6
  74. package/dist/docx/story.d.ts +140 -0
  75. package/dist/docx/story.js +237 -0
  76. package/dist/docx/storyProjection.js +6 -1
  77. package/dist/docx/tableFormatting/conditions.d.ts +76 -0
  78. package/dist/docx/tableFormatting/conditions.js +196 -0
  79. package/dist/docx/tableFormatting/editing.d.ts +7 -3
  80. package/dist/docx/tableFormatting/editing.js +4 -5
  81. package/dist/docx/tableFormatting/reading.d.ts +49 -19
  82. package/dist/docx/tableFormatting/reading.js +109 -35
  83. package/dist/docx/tableFormatting.d.ts +1 -0
  84. package/dist/docx/tableFormatting.js +1 -0
  85. package/dist/docx/tableTemplate.js +12 -12
  86. package/dist/editor/clipboard/htmlReader.d.ts +30 -0
  87. package/dist/editor/clipboard/htmlReader.js +302 -0
  88. package/dist/editor/clipboard/internalChannel.d.ts +57 -0
  89. package/dist/editor/clipboard/internalChannel.js +58 -0
  90. package/dist/editor/clipboard/normalizers.d.ts +99 -0
  91. package/dist/editor/clipboard/normalizers.js +199 -0
  92. package/dist/editor/clipboard/parser.d.ts +34 -0
  93. package/dist/editor/clipboard/parser.js +58 -0
  94. package/dist/editor/clipboard/plugin.d.ts +21 -0
  95. package/dist/editor/clipboard/plugin.js +320 -0
  96. package/dist/editor/clipboard/readContext.d.ts +24 -0
  97. package/dist/editor/clipboard/readContext.js +19 -0
  98. package/dist/editor/clipboard/readers.d.ts +33 -0
  99. package/dist/editor/clipboard/readers.js +37 -0
  100. package/dist/editor/commands/comments/editing.d.ts +26 -6
  101. package/dist/editor/commands/comments/editing.js +175 -105
  102. package/dist/editor/commands/comments/model.d.ts +34 -17
  103. package/dist/editor/commands/comments/model.js +11 -0
  104. package/dist/editor/commands/comments/reading.d.ts +5 -3
  105. package/dist/editor/commands/comments/reading.js +8 -58
  106. package/dist/editor/commands/formatting/propertyCommands.js +6 -1
  107. package/dist/editor/commands/index.d.ts +2 -2
  108. package/dist/editor/commands/index.js +2 -0
  109. package/dist/editor/commands/listCommands.d.ts +12 -3
  110. package/dist/editor/commands/listCommands.js +76 -30
  111. package/dist/editor/commands/noteQueries.d.ts +22 -5
  112. package/dist/editor/commands/noteQueries.js +34 -6
  113. package/dist/editor/commands/paragraphCommands.js +6 -1
  114. package/dist/editor/createEditor.js +14 -4
  115. package/dist/editor/documentStyles.d.ts +26 -4
  116. package/dist/editor/documentStyles.js +9 -4
  117. package/dist/editor/editorDocument.d.ts +7 -6
  118. package/dist/editor/editorDocument.js +18 -6
  119. package/dist/editor/imageFiles.d.ts +2 -2
  120. package/dist/editor/imageFiles.js +2 -0
  121. package/dist/editor/insertTable.d.ts +1 -1
  122. package/dist/editor/insertTable.js +2 -2
  123. package/dist/editor/paragraphEdits.d.ts +16 -1
  124. package/dist/editor/paragraphEdits.js +13 -5
  125. package/dist/editor/paragraphPlacement.d.ts +11 -0
  126. package/dist/editor/paragraphPlacement.js +18 -0
  127. package/dist/editor/plainText.d.ts +12 -0
  128. package/dist/editor/plainText.js +2 -1
  129. package/dist/editor/plugins/columnResize.js +6 -3
  130. package/dist/editor/plugins/commentComposer.d.ts +28 -0
  131. package/dist/editor/plugins/commentComposer.js +46 -0
  132. package/dist/editor/plugins/commentDecorations.d.ts +29 -2
  133. package/dist/editor/plugins/commentDecorations.js +89 -15
  134. package/dist/editor/plugins/documentProjection.d.ts +31 -0
  135. package/dist/editor/plugins/documentProjection.js +22 -0
  136. package/dist/editor/plugins/imagePaste.js +20 -10
  137. package/dist/editor/plugins/linkPanel.d.ts +7 -3
  138. package/dist/editor/plugins/linkPanel.js +13 -27
  139. package/dist/editor/plugins/numberingDecorations.d.ts +10 -4
  140. package/dist/editor/plugins/numberingDecorations.js +43 -2
  141. package/dist/editor/plugins/panelState.d.ts +67 -0
  142. package/dist/editor/plugins/panelState.js +54 -0
  143. package/dist/editor/plugins/paragraphDisplay.js +9 -3
  144. package/dist/editor/plugins/tableContextMenu.d.ts +1 -1
  145. package/dist/editor/plugins/tableContextMenu.js +30 -47
  146. package/dist/editor/plugins/tableDisplay.js +19 -5
  147. package/dist/editor/plugins/textContextMenu.d.ts +1 -1
  148. package/dist/editor/plugins/textContextMenu.js +30 -47
  149. package/dist/model/format.d.ts +50 -0
  150. package/dist/model/format.js +65 -0
  151. package/dist/numbering/listRegistry.d.ts +37 -0
  152. package/dist/numbering/listRegistry.js +112 -0
  153. package/dist/numbering/listTemplate.d.ts +26 -17
  154. package/dist/numbering/listTemplate.js +66 -20
  155. package/dist/numbering/markers.d.ts +15 -6
  156. package/dist/numbering/markers.js +26 -59
  157. package/dist/numbering/parseNumbering.d.ts +70 -5
  158. package/dist/numbering/parseNumbering.js +100 -26
  159. package/dist/numbering/spellers.d.ts +15 -0
  160. package/dist/numbering/spellers.js +96 -0
  161. package/dist/numbering/writeNumbering.d.ts +5 -3
  162. package/dist/numbering/writeNumbering.js +15 -15
  163. package/dist/ooxml/conformance.d.ts +28 -0
  164. package/dist/ooxml/conformance.js +20 -0
  165. package/dist/ooxml/errors.d.ts +5 -5
  166. package/dist/ooxml/fragment.d.ts +11 -0
  167. package/dist/ooxml/fragment.js +14 -4
  168. package/dist/ooxml/image.d.ts +3 -4
  169. package/dist/ooxml/image.js +50 -5
  170. package/dist/ooxml/names.d.ts +15 -0
  171. package/dist/ooxml/names.js +10 -1
  172. package/dist/ooxml/partSplice.d.ts +1 -1
  173. package/dist/ooxml/partSplice.js +10 -3
  174. package/dist/ooxml/props.d.ts +8 -0
  175. package/dist/ooxml/props.js +14 -4
  176. package/dist/ooxml/rangeMarkers.d.ts +5 -0
  177. package/dist/ooxml/rangeMarkers.js +24 -0
  178. package/dist/ooxml/xml.d.ts +8 -9
  179. package/dist/ooxml/xml.js +6 -6
  180. package/dist/page/PageGuides.d.ts +8 -4
  181. package/dist/page/PageGuides.js +13 -37
  182. package/dist/page/pageLayout.d.ts +62 -13
  183. package/dist/page/pageLayout.js +91 -37
  184. package/dist/page/usePageLayout.d.ts +14 -11
  185. package/dist/page/usePageLayout.js +21 -29
  186. package/dist/schema/attrRoles.js +44 -19
  187. package/dist/schema/docxSchema.d.ts +9 -1
  188. package/dist/schema/docxSchema.js +220 -139
  189. package/dist/schema/editGuard.d.ts +1 -1
  190. package/dist/schema/guards.js +2 -2
  191. package/dist/schema/index.d.ts +1 -0
  192. package/dist/schema/preservedFragments.d.ts +12 -0
  193. package/dist/schema/preservedFragments.js +38 -0
  194. package/dist/schema/preservedGuards.d.ts +9 -7
  195. package/dist/schema/preservedGuards.js +112 -11
  196. package/dist/schema/protection.d.ts +34 -5
  197. package/dist/schema/protection.js +42 -14
  198. package/dist/schema/rendering.js +3 -1
  199. package/dist/schema/stories.d.ts +49 -0
  200. package/dist/schema/stories.js +78 -0
  201. package/dist/styles/classNames.d.ts +16 -7
  202. package/dist/styles/classNames.js +16 -7
  203. package/dist/styles.css +90 -30
  204. package/dist/table/cellFormatting.js +1 -2
  205. package/dist/table/gridBorders.d.ts +11 -15
  206. package/dist/table/gridBorders.js +32 -18
  207. package/dist/table/resize.d.ts +5 -3
  208. package/dist/ui/CommentsPanel.d.ts +1 -3
  209. package/dist/ui/CommentsPanel.js +24 -15
  210. package/dist/ui/NotesPanel.js +2 -2
  211. package/dist/ui/TextMenu.d.ts +1 -3
  212. package/dist/ui/TextMenu.js +10 -6
  213. package/dist/ui/comments/CommentComposer.d.ts +6 -1
  214. package/dist/ui/comments/CommentComposer.js +26 -3
  215. package/package.json +1 -1
  216. package/dist/editor/externalClipboard.d.ts +0 -11
  217. package/dist/editor/externalClipboard.js +0 -456
@@ -0,0 +1,112 @@
1
+ // src/numbering/listRegistry.ts
2
+ import { ST_SignedTwipsMeasure, ST_TwipsMeasure } from "../ooxml/simpleTypes.js";
3
+ import { MAX_ILVL } from "./listTemplate.js";
4
+ import {
5
+ LEVEL_ALIGNS,
6
+ LEVEL_SUFFIXES
7
+ } from "./parseNumbering.js";
8
+ import { isNumberFormat } from "./spellers.js";
9
+ var NO_NEW_LISTS = /* @__PURE__ */ new Map();
10
+ var NEW_LISTS_ATTR = "newLists";
11
+ function isRecord(value) {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13
+ }
14
+ function toLevelIndent(value) {
15
+ if (!isRecord(value)) return null;
16
+ const { startTwips, endTwips, hangingTwips, firstLineTwips } = value;
17
+ const signed = (slot) => slot === null || typeof slot === "number" && ST_SignedTwipsMeasure.format(slot) !== null;
18
+ const unsigned = (slot) => slot === null || typeof slot === "number" && ST_TwipsMeasure.format(slot) !== null;
19
+ if (!signed(startTwips) || !signed(endTwips) || !unsigned(hangingTwips) || !unsigned(firstLineTwips))
20
+ return null;
21
+ return { startTwips, endTwips, hangingTwips, firstLineTwips };
22
+ }
23
+ function levelValue(ilvl, level) {
24
+ return {
25
+ ilvl,
26
+ format: level.format,
27
+ text: level.text,
28
+ start: level.start,
29
+ indent: level.indent,
30
+ restartAfterLevel: level.restartAfterLevel,
31
+ legal: level.legal,
32
+ suffix: level.suffix,
33
+ align: level.align
34
+ };
35
+ }
36
+ function toLevel(value) {
37
+ if (!isRecord(value)) return null;
38
+ const { ilvl, format, text, start } = value;
39
+ if (typeof ilvl !== "number" || !Number.isSafeInteger(ilvl) || ilvl < 0 || ilvl > MAX_ILVL)
40
+ return null;
41
+ if (typeof format !== "string" || !isNumberFormat(format)) return null;
42
+ if (typeof text !== "string") return null;
43
+ if (typeof start !== "number" || !Number.isSafeInteger(start)) return null;
44
+ const suffix = LEVEL_SUFFIXES.find((known) => known === value.suffix);
45
+ const align = LEVEL_ALIGNS.find((known) => known === value.align);
46
+ if (suffix === void 0 || align === void 0) return null;
47
+ const restartAfterLevel = value.restartAfterLevel;
48
+ if (restartAfterLevel !== null && (typeof restartAfterLevel !== "number" || !Number.isSafeInteger(restartAfterLevel) || restartAfterLevel < -1 || restartAfterLevel >= MAX_ILVL))
49
+ return null;
50
+ if (typeof value.legal !== "boolean") return null;
51
+ if (value.run !== void 0 && value.run !== null) return null;
52
+ if (value.tabStops !== void 0) return null;
53
+ const indent = toLevelIndent(value.indent);
54
+ if (value.indent !== null && indent === null) return null;
55
+ return {
56
+ ilvl,
57
+ level: {
58
+ format,
59
+ text,
60
+ start,
61
+ indent,
62
+ restartAfterLevel,
63
+ legal: value.legal,
64
+ suffix,
65
+ align,
66
+ run: null
67
+ }
68
+ };
69
+ }
70
+ function toList(value) {
71
+ if (!isRecord(value)) return null;
72
+ const { numId, levels } = value;
73
+ if (typeof numId !== "number" || !Number.isSafeInteger(numId) || numId <= 0)
74
+ return null;
75
+ if (!Array.isArray(levels)) return null;
76
+ const read = /* @__PURE__ */ new Map();
77
+ for (const entry of levels) {
78
+ const level = toLevel(entry);
79
+ if (level === null || read.has(level.ilvl)) return null;
80
+ read.set(level.ilvl, level.level);
81
+ }
82
+ return read.size === 0 ? null : { numId, list: { levels: read } };
83
+ }
84
+ function newListsValue(lists) {
85
+ if (lists.size === 0) return null;
86
+ return Array.from(lists).sort(([left], [right]) => left - right).map(([numId, list]) => ({
87
+ numId,
88
+ levels: Array.from(list.levels).sort(([left], [right]) => left - right).map(([ilvl, level]) => levelValue(ilvl, level))
89
+ }));
90
+ }
91
+ function newListsOf(value) {
92
+ if (!Array.isArray(value)) return NO_NEW_LISTS;
93
+ const lists = /* @__PURE__ */ new Map();
94
+ for (const entry of value) {
95
+ const read = toList(entry);
96
+ if (read === null || lists.has(read.numId)) return NO_NEW_LISTS;
97
+ lists.set(read.numId, read.list);
98
+ }
99
+ return lists;
100
+ }
101
+ function listsWorn(lists, worn) {
102
+ const dropped = [...lists.keys()].filter((numId) => !worn.has(numId));
103
+ if (dropped.length === 0) return lists;
104
+ return new Map([...lists].filter(([numId]) => worn.has(numId)));
105
+ }
106
+ export {
107
+ NEW_LISTS_ATTR,
108
+ NO_NEW_LISTS,
109
+ listsWorn,
110
+ newListsOf,
111
+ newListsValue
112
+ };
@@ -1,29 +1,38 @@
1
1
  /**
2
- * The standard shape of a newly started list.
2
+ * A list started while editing: the definition it is started with, the number it takes, and the
3
+ * `<w:abstractNum>` that definition is written out as.
3
4
  *
4
- * The definition of a new list is not kept in the document model.
5
- * So its shape has to be derivable from the list number (numId) alone, and that rule
6
- * lives here. Even numbers are numbered lists, odd numbers are bullet lists.
7
- *
8
- * Drawing the markers on screen and writing the definitions into numbering.xml both look
9
- * at the same template.
5
+ * Nothing here reads anything into the number. The definition is a value the editor registers
6
+ * (`./listRegistry`), the same value the markers are drawn from and the one written into
7
+ * numbering.xml, so a list goes out as what it was started as.
10
8
  */
11
- import type { LevelIndent, Numbering, NumberingList } from "./parseNumbering";
9
+ import type { LevelIndent, NewList, Numbering, NumberingList } from "./parseNumbering";
12
10
  export type ListKind = "numbered" | "bullet";
13
11
  /** A list has nine levels (the count OOXML defines) */
14
12
  export declare const LEVEL_COUNT = 9;
15
13
  export declare const MAX_ILVL: number;
16
14
  export declare function templateIndent(ilvl: number): LevelIndent;
17
- export declare function listKindOf(numId: number): ListKind;
15
+ /** The definition a list of this kind is started with */
16
+ export declare function templateList(kind: ListKind): NewList;
17
+ /**
18
+ * The definition standing behind this number: the one the document wrote down, or the one a list
19
+ * started while editing was registered with. Undefined for a number nothing defines, which is a
20
+ * list neither drawn nor written.
21
+ */
22
+ export declare function listFor(numbering: Numbering, numId: number): NumberingList | undefined;
23
+ /** Reserves safe positive ids, using gaps if incrementing the highest id would lose precision. */
24
+ export declare function numberingIdAllocator(ids: Iterable<number>): () => number;
18
25
  /**
19
- * The list definition for this number.
20
- * A number the document's numbering.xml does not know is read as the standard template
21
- * (which is the case for a list that was just started).
26
+ * A number for a new list, and the numbering that now defines it under that number.
27
+ *
28
+ * `used` names the numbers the document already spends, the ones no definition stands behind
29
+ * included: a number a paragraph already wears would otherwise join that paragraph to the new list.
22
30
  */
23
- export declare function listFor(numbering: Numbering, numId: number): NumberingList;
24
- /** The next number not yet in use. Picks the even or odd one matching the shape */
25
- export declare function nextNumId(used: Iterable<number>, kind: ListKind): number;
26
- /** Writes one template out as a `<w:abstractNum>` definition */
27
- export declare function abstractNumXml(abstractNumId: number, kind: ListKind): string;
31
+ export declare function allocateList(numbering: Numbering, used: Iterable<number>, list: NewList): {
32
+ numId: number;
33
+ numbering: Numbering;
34
+ };
35
+ /** Writes one registered definition out as a `<w:abstractNum>` */
36
+ export declare function abstractNumXml(abstractNumId: number, list: NewList): string;
28
37
  /** The `<w:num>` that ties a list number to a definition */
29
38
  export declare function numXml(numId: number, abstractNumId: number): string;
@@ -29,7 +29,12 @@ function templateLevel(kind, ilvl) {
29
29
  format: numbered ? cycled(NUMBER_CYCLE, ilvl, "decimal") : "bullet",
30
30
  text: numbered ? `%${ilvl + 1}.` : cycled(BULLET_CYCLE, ilvl, "\u25CF"),
31
31
  start: 1,
32
- indent: templateIndent(ilvl)
32
+ indent: templateIndent(ilvl),
33
+ restartAfterLevel: null,
34
+ legal: false,
35
+ suffix: "tab",
36
+ align: "left",
37
+ run: null
33
38
  };
34
39
  }
35
40
  function templateList(kind) {
@@ -39,19 +44,35 @@ function templateList(kind) {
39
44
  }
40
45
  return { levels };
41
46
  }
42
- var TEMPLATES = {
43
- numbered: templateList("numbered"),
44
- bullet: templateList("bullet")
45
- };
46
- function listKindOf(numId) {
47
- return numId % 2 === 0 ? "numbered" : "bullet";
48
- }
49
47
  function listFor(numbering, numId) {
50
- return numbering.lists.get(numId) ?? TEMPLATES[listKindOf(numId)];
48
+ return numbering.lists.get(numId) ?? numbering.added.get(numId);
51
49
  }
52
- function nextNumId(used, kind) {
53
- const from = Math.max(0, ...used) + 1;
54
- return listKindOf(from) === kind ? from : from + 1;
50
+ function numberingIdAllocator(ids) {
51
+ const used = new Set(ids);
52
+ let highest = 0;
53
+ for (const id of used) if (id > highest) highest = id;
54
+ let next = Number.isSafeInteger(highest + 1) ? highest + 1 : 1;
55
+ return () => {
56
+ while (used.has(next)) next += 1;
57
+ const id = next;
58
+ used.add(id);
59
+ next = Number.isSafeInteger(id + 1) ? id + 1 : 1;
60
+ return id;
61
+ };
62
+ }
63
+ function allocateList(numbering, used, list) {
64
+ const numId = numberingIdAllocator([
65
+ ...numbering.lists.keys(),
66
+ ...numbering.added.keys(),
67
+ ...used
68
+ ])();
69
+ return {
70
+ numId,
71
+ numbering: {
72
+ ...numbering,
73
+ added: new Map([...numbering.added, [numId, list]])
74
+ }
75
+ };
55
76
  }
56
77
  function indXml(indent) {
57
78
  const slots = [
@@ -68,8 +89,33 @@ function indXml(indent) {
68
89
  [{ name: "ind", xml: elementXml(wName("ind"), attrs) }]
69
90
  );
70
91
  }
92
+ function lvlRestartXml(restartAfterLevel) {
93
+ if (restartAfterLevel === null) return null;
94
+ return {
95
+ name: "lvlRestart",
96
+ xml: elementXml(wName("lvlRestart"), [
97
+ [wName("val"), `${restartAfterLevel + 1}`]
98
+ ])
99
+ };
100
+ }
101
+ function isLglXml(legal) {
102
+ return legal ? { name: "isLgl", xml: elementXml(wName("isLgl"), []) } : null;
103
+ }
104
+ function suffXml(suffix) {
105
+ if (suffix === "tab") return null;
106
+ return {
107
+ name: "suff",
108
+ xml: elementXml(wName("suff"), [[wName("val"), suffix]])
109
+ };
110
+ }
71
111
  function levelXml(ilvl, level) {
72
112
  const ind = indXml(level.indent);
113
+ const optional = [
114
+ lvlRestartXml(level.restartAfterLevel),
115
+ isLglXml(level.legal),
116
+ suffXml(level.suffix),
117
+ ind === "" ? null : { name: "pPr", xml: ind }
118
+ ];
73
119
  return orderedElement(
74
120
  wName("lvl"),
75
121
  [[wName("ilvl"), `${ilvl}`]],
@@ -88,15 +134,14 @@ function levelXml(ilvl, level) {
88
134
  },
89
135
  {
90
136
  name: "lvlJc",
91
- xml: elementXml(wName("lvlJc"), [[wName("val"), "left"]])
137
+ xml: elementXml(wName("lvlJc"), [[wName("val"), level.align]])
92
138
  },
93
- ...ind === "" ? [] : [{ name: "pPr", xml: ind }]
139
+ ...optional.filter((child) => child !== null)
94
140
  ]
95
141
  );
96
142
  }
97
- function abstractNumXml(abstractNumId, kind) {
98
- const levels = TEMPLATES[kind].levels;
99
- const body = Array.from(levels.entries()).map(([ilvl, level]) => levelXml(ilvl, level)).join("");
143
+ function abstractNumXml(abstractNumId, list) {
144
+ const body = Array.from(list.levels.entries()).map(([ilvl, level]) => levelXml(ilvl, level)).join("");
100
145
  return elementXml(
101
146
  wName("abstractNum"),
102
147
  [[wName("abstractNumId"), `${abstractNumId}`]],
@@ -114,9 +159,10 @@ export {
114
159
  LEVEL_COUNT,
115
160
  MAX_ILVL,
116
161
  abstractNumXml,
162
+ allocateList,
117
163
  listFor,
118
- listKindOf,
119
- nextNumId,
120
164
  numXml,
121
- templateIndent
165
+ numberingIdAllocator,
166
+ templateIndent,
167
+ templateList
122
168
  };
@@ -4,21 +4,30 @@
4
4
  * A pure computation that never touches the document model: the same paragraphs with the
5
5
  * same numbering definitions always yield the same numbers.
6
6
  */
7
- import type { NumberingRef } from "../model/format";
8
- import { type LevelIndentPt, type Numbering } from "./parseNumbering";
7
+ import type { NumberingRef, RunFormat } from "../model/format";
8
+ import { type LevelAlign, type LevelIndentPt, type LevelSuffix, type Numbering } from "./parseNumbering";
9
9
  /** The number to draw in front of one paragraph */
10
10
  export interface ListMarker {
11
+ /**
12
+ * The number as it is drawn, the space a level asking for one (`w:suff`) included: a separator
13
+ * made of a character belongs to the marker, while the tab every other level asks for is the
14
+ * width the number sits in rather than anything to draw
15
+ */
11
16
  text: string;
12
17
  /**
13
18
  * The indent defined by the level this paragraph belongs to. Used when the paragraph
14
19
  * records none of its own
15
20
  */
16
21
  indent: LevelIndentPt;
22
+ /** Whether the number sits in a width of its own, which is what a tab suffix asks for */
23
+ suffix: LevelSuffix;
24
+ align: LevelAlign;
25
+ /** The formatting the level puts on the number, which dresses the number and nothing else */
26
+ run: RunFormat | null;
17
27
  }
18
28
  /**
19
- * A slot is null for a paragraph that is not a list item, or whose level shape could not
20
- * be found.
21
- * A number the document does not know (a list started fresh while editing) is drawn with
22
- * the standard template's shape.
29
+ * A slot is null for a paragraph that is not a list item, or whose level shape could not be found.
30
+ * A number nothing defines - neither numbering.xml nor a list started while editing - draws no
31
+ * marker, which is what Word does with a `w:numId` no `w:num` answers.
23
32
  */
24
33
  export declare function computeMarkers(paragraphs: readonly (NumberingRef | null)[], numbering: Numbering): (ListMarker | null)[];
@@ -3,71 +3,31 @@ import { listFor } from "./listTemplate.js";
3
3
  import {
4
4
  levelIndentPt
5
5
  } from "./parseNumbering.js";
6
- var ROMAN = [
7
- [1e3, "M"],
8
- [900, "CM"],
9
- [500, "D"],
10
- [400, "CD"],
11
- [100, "C"],
12
- [90, "XC"],
13
- [50, "L"],
14
- [40, "XL"],
15
- [10, "X"],
16
- [9, "IX"],
17
- [5, "V"],
18
- [4, "IV"],
19
- [1, "I"]
20
- ];
21
- function toRoman(value) {
22
- let rest = value;
23
- let out = "";
24
- for (const [amount, sign] of ROMAN) {
25
- while (rest >= amount) {
26
- out += sign;
27
- rest -= amount;
28
- }
29
- }
30
- return out;
31
- }
32
- function toLetters(value) {
33
- const index = (value - 1) % 26;
34
- const repeat = Math.floor((value - 1) / 26) + 1;
35
- return String.fromCharCode(65 + index).repeat(repeat);
36
- }
6
+ import { spellNumber } from "./spellers.js";
37
7
  var MAX_MARKER_CHARS = 64;
38
- var MAX_SPELLED_VALUE = {
39
- decimal: Number.POSITIVE_INFINITY,
40
- bullet: Number.POSITIVE_INFINITY,
41
- lowerLetter: 26 * MAX_MARKER_CHARS,
42
- upperLetter: 26 * MAX_MARKER_CHARS,
43
- lowerRoman: 1e3 * MAX_MARKER_CHARS
44
- };
45
- function formatNumber(value, format) {
46
- if (value < 1 || value > MAX_SPELLED_VALUE[format]) return `${value}`;
47
- switch (format) {
48
- case "lowerLetter":
49
- return toLetters(value).toLowerCase();
50
- case "upperLetter":
51
- return toLetters(value);
52
- case "lowerRoman":
53
- return toRoman(value).toLowerCase();
54
- default:
55
- return `${value}`;
56
- }
57
- }
58
- function fillLevels(text, list, counters) {
8
+ function fillLevels(text, list, counters, legal) {
59
9
  return text.replaceAll(/%([1-9])/g, (_, digit) => {
60
10
  const ilvl = Number(digit) - 1;
61
11
  const level = list.levels.get(ilvl);
62
12
  if (!level) return "";
63
- return formatNumber(counters.get(ilvl) ?? level.start, level.format);
13
+ const count = counters.get(ilvl) ?? level.start;
14
+ return spellNumber(
15
+ count,
16
+ legal ? "decimal" : level.format,
17
+ MAX_MARKER_CHARS
18
+ );
64
19
  });
65
20
  }
21
+ function restartedBy(level, ilvl) {
22
+ return level?.restartAfterLevel ?? ilvl - 1;
23
+ }
66
24
  function advance(counters, list, ilvl, start) {
67
25
  const current = counters.get(ilvl);
68
26
  counters.set(ilvl, current === void 0 ? start : current + 1);
69
27
  for (const deeper of [...counters.keys(), ...list.levels.keys()]) {
70
- if (deeper > ilvl) counters.delete(deeper);
28
+ if (deeper > ilvl && restartedBy(list.levels.get(deeper), deeper) >= ilvl) {
29
+ counters.delete(deeper);
30
+ }
71
31
  }
72
32
  }
73
33
  var MARKER_SEGMENTER = new Intl.Segmenter();
@@ -87,17 +47,24 @@ function computeMarkers(paragraphs, numbering) {
87
47
  return paragraphs.map((ref) => {
88
48
  if (!ref) return null;
89
49
  const list = listFor(numbering, ref.numId);
90
- const level = list.levels.get(ref.ilvl);
91
- if (!level) return null;
50
+ const level = list?.levels.get(ref.ilvl);
51
+ if (!list || !level) return null;
92
52
  let counters = countersByList.get(ref.numId);
93
53
  if (!counters) {
94
54
  counters = /* @__PURE__ */ new Map();
95
55
  countersByList.set(ref.numId, counters);
96
56
  }
97
57
  advance(counters, list, ref.ilvl, level.start);
98
- const shape = level.format === "bullet" ? level.text : fillLevels(level.text, list, counters);
99
- const text = capped(shape);
100
- return text ? { text, indent: levelIndentPt(level.indent) } : null;
58
+ const shape = level.format === "bullet" ? level.text : fillLevels(level.text, list, counters, level.legal);
59
+ const drawn = capped(shape);
60
+ if (!drawn) return null;
61
+ return {
62
+ text: level.suffix === "space" ? `${drawn} ` : drawn,
63
+ indent: levelIndentPt(level.indent),
64
+ suffix: level.suffix,
65
+ align: level.align,
66
+ run: level.run
67
+ };
101
68
  });
102
69
  }
103
70
  export {
@@ -5,11 +5,11 @@
5
5
  * numbering.xml itself goes out as the original bytes on export, so this reading never
6
6
  * disturbs the file.
7
7
  */
8
+ import type { RunFormat } from "../model/format";
8
9
  import type { TabStopDirective } from "../model/tabStops";
9
10
  import { type XmlParser } from "../ooxml/xml";
10
- /** The number formats actually used across every fixture */
11
- declare const NUMBER_FORMATS: readonly ["decimal", "bullet", "lowerLetter", "upperLetter", "lowerRoman"];
12
- export type NumberFormat = (typeof NUMBER_FORMATS)[number];
11
+ import { type NumberFormat } from "./spellers";
12
+ export type { NumberFormat } from "./spellers";
13
13
  /**
14
14
  * The indent a level defines. The unit is twips (1/20 of a point), and a slot that is
15
15
  * not recorded is null
@@ -20,6 +20,10 @@ export interface LevelIndent {
20
20
  hangingTwips: number | null;
21
21
  firstLineTwips: number | null;
22
22
  }
23
+ /** What stands between a level's number and the text of the paragraph (`w:suff`, §17.9.28) */
24
+ export type LevelSuffix = "tab" | "space" | "nothing";
25
+ /** Where a level's number sits in the space kept for it (`w:lvlJc`, §17.9.7) */
26
+ export type LevelAlign = "left" | "center" | "right";
23
27
  export interface NumberingLevel {
24
28
  format: NumberFormat;
25
29
  /**
@@ -32,14 +36,58 @@ export interface NumberingLevel {
32
36
  indent: LevelIndent | null;
33
37
  /** Custom stops contributed by this level's paragraph properties. */
34
38
  tabStops?: readonly TabStopDirective[];
39
+ /**
40
+ * The deepest level whose advance sends this one back to its start (`w:lvlRestart`, §17.9.10),
41
+ * as a level number: any level above that one restarts it too. A level of -1 never restarts, and
42
+ * null is the default, the level right above this one.
43
+ */
44
+ restartAfterLevel: number | null;
45
+ /** Every level in this level's text spelled as a decimal, whatever format it counts in (`w:isLgl`, §17.9.4) */
46
+ legal: boolean;
47
+ suffix: LevelSuffix;
48
+ align: LevelAlign;
49
+ /**
50
+ * The formatting of the number itself (`w:rPr`, §17.9.24). It dresses the number alone and
51
+ * never the text of the paragraph the number stands in front of.
52
+ * Null where the level writes none, and where the caller handed in no reader for it.
53
+ */
54
+ run: RunFormat | null;
35
55
  }
56
+ /**
57
+ * How a level's `w:rPr` is read into display values.
58
+ * The vocabulary of run properties belongs with the reader that holds styles.xml and the theme
59
+ * fonts, which this folder cannot reach, so it is handed in.
60
+ */
61
+ export type ReadLevelRun = (rPr: Element) => RunFormat | null;
36
62
  export interface NumberingList {
37
63
  /** The shape for each level (ilvl) */
38
- levels: Map<number, NumberingLevel>;
64
+ levels: ReadonlyMap<number, NumberingLevel>;
65
+ }
66
+ /**
67
+ * A level a list started while editing is registered with.
68
+ *
69
+ * It is a level minus the two things the writer of a new definition cannot spell where it stands:
70
+ * the formatting of the number (`w:rPr`) and the tab stops its paragraphs contribute (`w:tabs`)
71
+ * are written by `docx/`, which stands above this folder. Leaving them out of what may be
72
+ * registered is what keeps a registered definition from reaching the file with a part of it
73
+ * silently dropped.
74
+ */
75
+ export interface NewListLevel extends NumberingLevel {
76
+ readonly run: null;
77
+ readonly tabStops?: undefined;
78
+ }
79
+ /** A list started while editing, whose definition the export writes into numbering.xml as it stands */
80
+ export interface NewList {
81
+ levels: ReadonlyMap<number, NewListLevel>;
39
82
  }
40
83
  export interface Numbering {
41
84
  /** The list for each numId */
42
85
  lists: Map<number, NumberingList>;
86
+ /**
87
+ * The lists started while editing, by the number each was given. numbering.xml defines none of
88
+ * them yet; the export writes the definition registered here.
89
+ */
90
+ added: ReadonlyMap<number, NewList>;
43
91
  }
44
92
  export declare const EMPTY_NUMBERING: Numbering;
45
93
  /**
@@ -62,6 +110,17 @@ export declare const NO_LEVEL_INDENT: LevelIndentPt;
62
110
  * OOXML rule).
63
111
  */
64
112
  export declare function levelIndentPt(indent: LevelIndent | null): LevelIndentPt;
113
+ export declare const LEVEL_SUFFIXES: readonly LevelSuffix[];
114
+ /** The justifications a number can take, which are the ones `w:lvlJc` offers a number */
115
+ export declare const LEVEL_ALIGNS: readonly LevelAlign[];
116
+ /**
117
+ * The list each numbering style names, by the style's id.
118
+ *
119
+ * A numbering style is never worn by a paragraph: it is a name on a list, which an abstract
120
+ * definition reaches through `w:numStyleLink` (§17.9.21). The map is built from styles.xml, which
121
+ * this folder cannot read for itself.
122
+ */
123
+ export type NumberingStyleLinks = ReadonlyMap<string, number>;
65
124
  /** What a caller may say about reading numbering beyond handing over the XML */
66
125
  export interface NumberingOptions {
67
126
  /**
@@ -69,10 +128,16 @@ export interface NumberingOptions {
69
128
  * runtime carrying none refuses the read with `no-xml-parser`.
70
129
  */
71
130
  xmlParser?: XmlParser;
131
+ /**
132
+ * The list each numbering style of the document names. Left out, a definition deferring to a
133
+ * numbering style is read through the definition that declares it stands behind that style.
134
+ */
135
+ links?: NumberingStyleLinks;
136
+ /** How the formatting a level puts on its number is read. Left out, no level carries any */
137
+ readRun?: ReadLevelRun;
72
138
  }
73
139
  /**
74
140
  * The lists this numbering XML defines. `null` is the document that has no numbering part, which
75
141
  * defines none and asks nothing of a parser.
76
142
  */
77
143
  export declare function parseNumbering(xml: string | null, options?: NumberingOptions): Numbering;
78
- export {};