@portone/docx-editor 0.5.1 → 0.6.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 (146) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/CONTRIBUTING.md +8 -2
  3. package/README.md +1 -1
  4. package/dist/DocxEditor.js +87 -29
  5. package/dist/docx/exportDocx.d.ts +2 -1
  6. package/dist/docx/exportDocx.js +3 -9
  7. package/dist/docx/headersFooters.js +35 -23
  8. package/dist/docx/identities.d.ts +28 -0
  9. package/dist/docx/identities.js +42 -10
  10. package/dist/docx/importDocx.js +25 -26
  11. package/dist/docx/importParagraph.d.ts +1 -4
  12. package/dist/docx/importParagraph.js +4 -18
  13. package/dist/docx/invariants.d.ts +1 -1
  14. package/dist/docx/invariants.js +45 -4
  15. package/dist/docx/notes/newNote.d.ts +39 -0
  16. package/dist/docx/notes/newNote.js +122 -0
  17. package/dist/docx/notes/numbering.d.ts +27 -0
  18. package/dist/docx/notes/numbering.js +120 -0
  19. package/dist/docx/notes/reading.d.ts +58 -0
  20. package/dist/docx/notes/reading.js +98 -0
  21. package/dist/docx/notes/writing.d.ts +19 -0
  22. package/dist/docx/notes/writing.js +96 -0
  23. package/dist/docx/partPlan.d.ts +3 -0
  24. package/dist/docx/partPlanners.d.ts +13 -0
  25. package/dist/docx/partPlanners.js +48 -0
  26. package/dist/docx/sections.d.ts +5 -0
  27. package/dist/docx/sections.js +7 -2
  28. package/dist/docx/serializeParagraph.js +7 -2
  29. package/dist/docx/session.d.ts +5 -0
  30. package/dist/docx/session.js +6 -0
  31. package/dist/docx/storyParts.d.ts +112 -0
  32. package/dist/docx/storyParts.js +289 -0
  33. package/dist/editor/clipboard/htmlReader.d.ts +24 -4
  34. package/dist/editor/clipboard/htmlReader.js +39 -4
  35. package/dist/editor/clipboard/internalChannel.d.ts +30 -3
  36. package/dist/editor/clipboard/internalChannel.js +43 -3
  37. package/dist/editor/clipboard/normalizers.d.ts +35 -8
  38. package/dist/editor/clipboard/normalizers.js +60 -11
  39. package/dist/editor/clipboard/plugin.js +42 -22
  40. package/dist/editor/commands/comments/editing.d.ts +4 -1
  41. package/dist/editor/commands/comments/editing.js +1 -3
  42. package/dist/editor/commands/comments/model.d.ts +7 -0
  43. package/dist/editor/commands/index.d.ts +1 -0
  44. package/dist/editor/commands/index.js +18 -0
  45. package/dist/editor/commands/noteCommands.d.ts +47 -0
  46. package/dist/editor/commands/noteCommands.js +95 -0
  47. package/dist/editor/commands/noteQueries.d.ts +22 -3
  48. package/dist/editor/commands/noteQueries.js +21 -13
  49. package/dist/editor/createEditor.js +25 -3
  50. package/dist/editor/documentStyles.d.ts +3 -0
  51. package/dist/editor/documentStyles.js +2 -1
  52. package/dist/editor/editorDocument.d.ts +24 -0
  53. package/dist/editor/editorDocument.js +34 -4
  54. package/dist/editor/notes/noteSurface.d.ts +53 -0
  55. package/dist/editor/notes/noteSurface.js +246 -0
  56. package/dist/editor/plugins/commentDecorations.js +5 -4
  57. package/dist/editor/plugins/commentRestoration.d.ts +20 -0
  58. package/dist/editor/plugins/commentRestoration.js +168 -0
  59. package/dist/editor/plugins/compositionSelection.d.ts +20 -0
  60. package/dist/editor/plugins/compositionSelection.js +19 -0
  61. package/dist/editor/plugins/keymap.d.ts +14 -0
  62. package/dist/editor/plugins/keymap.js +13 -1
  63. package/dist/editor/plugins/noteLifecycle.d.ts +18 -0
  64. package/dist/editor/plugins/noteLifecycle.js +117 -0
  65. package/dist/editor/plugins/noteNavigation.d.ts +45 -0
  66. package/dist/editor/plugins/noteNavigation.js +86 -0
  67. package/dist/editor/plugins/noteNumbering.d.ts +13 -0
  68. package/dist/editor/plugins/noteNumbering.js +58 -0
  69. package/dist/editor/plugins/rowResize.js +2 -7
  70. package/dist/editor/plugins/tabLayout.js +2 -7
  71. package/dist/editor/stories/storyMarkup.d.ts +24 -0
  72. package/dist/editor/stories/storyMarkup.js +35 -0
  73. package/dist/editor/stories/storyState.d.ts +34 -0
  74. package/dist/editor/stories/storyState.js +74 -0
  75. package/dist/editor/stories/storyView.d.ts +115 -0
  76. package/dist/editor/stories/storyView.js +144 -0
  77. package/dist/numbering/spellers.d.ts +1 -1
  78. package/dist/numbering/spellers.js +6 -1
  79. package/dist/page/blockKinds.d.ts +23 -4
  80. package/dist/page/demands/footnoteDemands.d.ts +18 -0
  81. package/dist/page/demands/footnoteDemands.js +51 -0
  82. package/dist/page/demands/index.d.ts +43 -0
  83. package/dist/page/demands/index.js +8 -0
  84. package/dist/page/kinds/paragraphKind.js +7 -1
  85. package/dist/page/kinds/tableKind.js +9 -1
  86. package/dist/page/measureBlocks.d.ts +2 -0
  87. package/dist/page/measureBlocks.js +31 -10
  88. package/dist/page/pageDecorations.d.ts +9 -2
  89. package/dist/page/pageDecorations.js +18 -5
  90. package/dist/page/pageLayout.d.ts +77 -1
  91. package/dist/page/pageLayout.js +186 -9
  92. package/dist/page/usePageLayout.d.ts +57 -2
  93. package/dist/page/usePageLayout.js +50 -5
  94. package/dist/schema/attrRoles.js +3 -2
  95. package/dist/schema/docxSchema.js +5 -2
  96. package/dist/schema/editGuard.d.ts +1 -1
  97. package/dist/schema/guards.js +1 -2
  98. package/dist/schema/preservedGuards.d.ts +4 -6
  99. package/dist/schema/preservedGuards.js +0 -9
  100. package/dist/schema/stories.d.ts +34 -2
  101. package/dist/schema/stories.js +22 -5
  102. package/dist/styles/classNames.d.ts +23 -6
  103. package/dist/styles/classNames.js +23 -6
  104. package/dist/styles/inlineStyle.d.ts +7 -0
  105. package/dist/styles/inlineStyle.js +11 -5
  106. package/dist/styles/visualScale.d.ts +19 -0
  107. package/dist/styles/visualScale.js +26 -0
  108. package/dist/styles.css +96 -36
  109. package/dist/ui/CommentsPanel.d.ts +6 -1
  110. package/dist/ui/CommentsPanel.js +26 -8
  111. package/dist/ui/InsertImageButton.d.ts +3 -1
  112. package/dist/ui/InsertImageButton.js +3 -2
  113. package/dist/ui/TableMenu.d.ts +5 -2
  114. package/dist/ui/TableMenu.js +18 -1
  115. package/dist/ui/TextMenu.d.ts +5 -2
  116. package/dist/ui/TextMenu.js +21 -6
  117. package/dist/ui/Toolbar.d.ts +17 -2
  118. package/dist/ui/Toolbar.js +23 -10
  119. package/dist/ui/comments/useCommentRailLayout.js +2 -4
  120. package/dist/ui/noteItems.d.ts +21 -0
  121. package/dist/ui/noteItems.js +30 -0
  122. package/dist/ui/notes/NoteAreas.d.ts +73 -0
  123. package/dist/ui/notes/NoteAreas.js +131 -0
  124. package/dist/ui/notes/NoteList.d.ts +25 -0
  125. package/dist/ui/notes/NoteList.js +58 -0
  126. package/dist/ui/notes/StoryRow.d.ts +75 -0
  127. package/dist/ui/notes/StoryRow.js +122 -0
  128. package/dist/ui/notes/noteBands.d.ts +100 -0
  129. package/dist/ui/notes/noteBands.js +142 -0
  130. package/dist/ui/notes/noteHeights.d.ts +10 -0
  131. package/dist/ui/notes/noteHeights.js +24 -0
  132. package/dist/ui/notes/noteSeparator.d.ts +12 -0
  133. package/dist/ui/notes/noteSeparator.js +9 -0
  134. package/dist/ui/notes/useStorySurface.d.ts +68 -0
  135. package/dist/ui/notes/useStorySurface.js +95 -0
  136. package/dist/ui/runCommand.d.ts +8 -1
  137. package/dist/ui/runCommand.js +2 -2
  138. package/dist/ui/shortcutLabels.d.ts +16 -0
  139. package/dist/ui/shortcutLabels.js +9 -0
  140. package/dist/ui/usePageRoom.d.ts +13 -0
  141. package/dist/ui/usePageRoom.js +31 -0
  142. package/package.json +2 -1
  143. package/dist/docx/notes.d.ts +0 -27
  144. package/dist/docx/notes.js +0 -55
  145. package/dist/ui/NotesPanel.d.ts +0 -6
  146. package/dist/ui/NotesPanel.js +0 -33
@@ -37,7 +37,12 @@ import { readLinkTargets } from "./hyperlink.js";
37
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
- import { readNotes } from "./notes.js";
40
+ import { withNoteLabels } from "./notes/numbering.js";
41
+ import {
42
+ readNoteNumbering,
43
+ readNotes,
44
+ specialNotesOf
45
+ } from "./notes/reading.js";
41
46
  import { readPart, relatedPartPath } from "./packageParts.js";
42
47
  import { A4_PORTRAIT } from "./pageGeometry.js";
43
48
  import { readRelationships } from "./relationships.js";
@@ -215,25 +220,13 @@ function readDocx(input) {
215
220
  );
216
221
  const comments = readComments(parts, mainPartPath);
217
222
  const notes = readNotes(parts, mainPartPath);
218
- const noteLabels = {
219
- footnote: /* @__PURE__ */ new Map(),
220
- endnote: /* @__PURE__ */ new Map()
221
- };
222
- const noteLabel = (kind, id) => {
223
- const labels = noteLabels[kind];
224
- const existing = labels.get(id);
225
- if (existing) return existing;
226
- const label = `${labels.size + 1}`;
227
- labels.set(id, label);
228
- return label;
229
- };
223
+ const noteNumbering = readNoteNumbering(settingsDom);
224
+ const specialNotes = specialNotesOf(notes);
230
225
  const sources = {
231
226
  images: readImageSources(parts, mainPartPath),
232
227
  themeFonts,
233
228
  links: readLinkTargets(parts, mainPartPath),
234
- comments,
235
- notes,
236
- noteLabel
229
+ comments
237
230
  };
238
231
  const sessionId = newSessionId();
239
232
  const headerFooters = readHeaderFooterStories(
@@ -275,14 +268,18 @@ function readDocx(input) {
275
268
  )
276
269
  );
277
270
  }
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
271
+ const doc = withNoteLabels(
272
+ docxSchema.nodes.doc.create(
273
+ {
274
+ sectPr: scan.sectPr,
275
+ stories: Object.fromEntries(
276
+ Array.from(stories, ([key, story]) => [key, story.doc.toJSON()])
277
+ )
278
+ },
279
+ blockNodes
280
+ ),
281
+ noteNumbering,
282
+ specialNotes
286
283
  );
287
284
  return {
288
285
  doc,
@@ -294,7 +291,7 @@ function readDocx(input) {
294
291
  documentPrefix: scan.prefix,
295
292
  documentSuffix: scan.suffix,
296
293
  documentHadBom: hadBom,
297
- blocks: blockNodes.map((node, i) => ({
294
+ blocks: doc.children.map((node, i) => ({
298
295
  xml: scan.blocks[i]?.xml ?? "",
299
296
  node
300
297
  })),
@@ -308,7 +305,9 @@ function readDocx(input) {
308
305
  comments,
309
306
  commentReferenceIds: new Set(commentReferencesIn(doc).keys()),
310
307
  headerFooterStories: headerFooters.refs,
311
- stories
308
+ stories,
309
+ noteNumbering,
310
+ specialNotes
312
311
  })
313
312
  };
314
313
  }
@@ -19,11 +19,10 @@ import type { Node as PMNode } from "prosemirror-model";
19
19
  import { type ImportedComments } from "./comments/reading";
20
20
  import { type LinkTargets } from "./hyperlink";
21
21
  import type { ImageSources } from "./media";
22
- import { type ImportedNotes, type NoteKind } from "./notes";
23
22
  import { type ThemeFonts } from "./theme";
24
23
  /**
25
24
  * What the body is read with: the images its drawings can point at, the fonts a theme reference
26
- * resolves to, the addresses its links can point at, and the bodies of its comments and notes.
25
+ * resolves to, the addresses its links can point at, and the comments its references name.
27
26
  * They travel together from `docx/importDocx` down through the tables to here.
28
27
  */
29
28
  export interface ImportSources {
@@ -31,8 +30,6 @@ export interface ImportSources {
31
30
  themeFonts: ThemeFonts;
32
31
  links: LinkTargets;
33
32
  comments: ImportedComments;
34
- notes: ImportedNotes;
35
- noteLabel: (kind: NoteKind, id: string) => string;
36
33
  }
37
34
  export declare const NO_IMPORT_SOURCES: ImportSources;
38
35
  /**
@@ -20,7 +20,6 @@ import {
20
20
  preservationOf
21
21
  } from "./importPreserved.js";
22
22
  import { NO_IMAGES } from "./media.js";
23
- import { NO_NOTES, noteById } from "./notes.js";
24
23
  import { NO_THEME_FONTS } from "./theme.js";
25
24
  import { wrapperFits, wrapperKindFor } from "./wrappers.js";
26
25
  function runMark(run, themeFonts) {
@@ -47,7 +46,7 @@ function buildImage(el, marks, images) {
47
46
  marks
48
47
  );
49
48
  }
50
- function buildModelledRunChild(el, marks, images, comments, notes, noteLabel) {
49
+ function buildModelledRunChild(el, marks, images, comments) {
51
50
  switch (el.localName) {
52
51
  case "t": {
53
52
  const text = el.textContent ?? "";
@@ -102,7 +101,6 @@ function buildModelledRunChild(el, marks, images, comments, notes, noteLabel) {
102
101
  const id = annotationId(el);
103
102
  if (id === null) return null;
104
103
  const kind = el.localName === "footnoteReference" ? "footnote" : "endnote";
105
- const note = noteById(notes, kind, id);
106
104
  const written = attributeByLocalName(el, "customMarkFollows");
107
105
  const customMarkFollows = written !== null && (ST_OnOff.parse(written) ?? true);
108
106
  return [
@@ -110,7 +108,6 @@ function buildModelledRunChild(el, marks, images, comments, notes, noteLabel) {
110
108
  {
111
109
  kind,
112
110
  id,
113
- label: note ? noteLabel(kind, id) : "?",
114
111
  customMarkFollows,
115
112
  referenceXml: serializeXml(el)
116
113
  },
@@ -123,7 +120,7 @@ function buildModelledRunChild(el, marks, images, comments, notes, noteLabel) {
123
120
  return null;
124
121
  }
125
122
  }
126
- function buildRunNodes(run, images, themeFonts, comments, notes, noteLabel, wrappers = []) {
123
+ function buildRunNodes(run, images, themeFonts, comments, wrappers = []) {
127
124
  const mark = runMark(run, themeFonts);
128
125
  const marks = wrappers.reduce(
129
126
  (set, wrapper) => wrapper.addToSet(set),
@@ -134,14 +131,7 @@ function buildRunNodes(run, images, themeFonts, comments, notes, noteLabel, wrap
134
131
  const policy = policyFor(child, "r");
135
132
  if (policy.tier === "model") {
136
133
  if (child.localName === "rPr") continue;
137
- const built = buildModelledRunChild(
138
- child,
139
- marks,
140
- images,
141
- comments,
142
- notes,
143
- noteLabel
144
- );
134
+ const built = buildModelledRunChild(child, marks, images, comments);
145
135
  if (built !== null) {
146
136
  nodes.push(...built);
147
137
  continue;
@@ -176,9 +166,7 @@ var NO_IMPORT_SOURCES = {
176
166
  images: NO_IMAGES,
177
167
  themeFonts: NO_THEME_FONTS,
178
168
  links: NO_LINK_TARGETS,
179
- comments: NO_COMMENTS,
180
- notes: NO_NOTES,
181
- noteLabel: () => "?"
169
+ comments: NO_COMMENTS
182
170
  };
183
171
  function buildModelledInline(child, sources, depth, wrappers) {
184
172
  if (child.localName === "r") {
@@ -187,8 +175,6 @@ function buildModelledInline(child, sources, depth, wrappers) {
187
175
  sources.images,
188
176
  sources.themeFonts,
189
177
  sources.comments,
190
- sources.notes,
191
- sources.noteLabel,
192
178
  wrappers
193
179
  );
194
180
  }
@@ -17,7 +17,7 @@ import { type DocxSession, type SessionStore } from "./session";
17
17
  export interface ExportProblem {
18
18
  readonly code: DocxExportErrorCode;
19
19
  readonly message: string;
20
- /** Where the problem stands in the document. Absent for a problem of the package or of the session */
20
+ /** Where the problem stands in the document. Absent for a problem of the package, of the session, or of a side story */
21
21
  readonly pos?: number;
22
22
  }
23
23
  /** The problems as the writer sees them, for a caller already inside the writer's parser scope */
@@ -8,6 +8,7 @@ import {
8
8
  } from "../ooxml/xml.js";
9
9
  import { visitPreservedFragments } from "../schema/preservedFragments.js";
10
10
  import { unattributedCommentAuthors } from "../schema/protection.js";
11
+ import { HEADER_FOOTER_KINDS } from "../schema/stories.js";
11
12
  import {
12
13
  commentReferencesIn,
13
14
  commentsChanged,
@@ -22,15 +23,25 @@ import {
22
23
  } from "./comments/parts.js";
23
24
  import { unrecordedAuthors } from "./comments/people.js";
24
25
  import { currentCommentBodies } from "./comments/writing.js";
25
- import { identityProblems } from "./identities.js";
26
+ import {
27
+ identityProblems,
28
+ identityProblemsInStories
29
+ } from "./identities.js";
26
30
  import { insertedImageSrcs } from "./media.js";
27
31
  import { canDefineNewList, newNumIds, startedLists } from "./newLists.js";
28
32
  import { CONTENT_TYPES_PATH } from "./packageParts.js";
33
+ import { STORY_ENTRIES_PARTS, STORY_WRITINGS } from "./partPlanners.js";
29
34
  import { lostOriginal } from "./serializePreserved.js";
30
35
  import {
31
36
  originalBlock,
32
37
  sessionOf
33
38
  } from "./session.js";
39
+ import {
40
+ storyChangesOf,
41
+ storyEntriesOf,
42
+ storyEntriesProblems,
43
+ unwrittenStoryChanges
44
+ } from "./storyParts.js";
34
45
  var bookmarkPairs = {
35
46
  name: "bookmarkPairs",
36
47
  check(doc, session) {
@@ -153,8 +164,30 @@ var preservedOriginals = {
153
164
  };
154
165
  var uniqueIdentities = {
155
166
  name: "uniqueIdentities",
156
- check(doc) {
157
- return identityProblems(doc);
167
+ check(doc, session) {
168
+ const parts = [
169
+ ...HEADER_FOOTER_KINDS.flatMap(
170
+ (kind) => storyChangesOf(doc, session, kind)
171
+ ).flatMap(
172
+ (change) => change.change === "edited" ? [[{ story: change.current, frozen: false }]] : []
173
+ ),
174
+ ...STORY_ENTRIES_PARTS.map((part) => storyEntriesOf(part, doc, session))
175
+ ];
176
+ return [
177
+ ...identityProblems(doc),
178
+ ...parts.flatMap((entries) => identityProblemsInStories(entries)).map(({ code, message }) => ({ code, message }))
179
+ ];
180
+ }
181
+ };
182
+ var storiesHaveWriters = {
183
+ name: "storiesHaveWriters",
184
+ check(doc, session) {
185
+ return unwrittenStoryChanges(STORY_WRITINGS, doc, session).map(
186
+ ({ key, change }) => ({
187
+ code: "unsupported-content",
188
+ message: `the ${key} story was ${change}, and no part writer carries that into the file`
189
+ })
190
+ );
158
191
  }
159
192
  };
160
193
  var listDefinitions = {
@@ -236,14 +269,22 @@ var commentPartRoots = {
236
269
  return problems;
237
270
  }
238
271
  };
272
+ var notePartRoots = {
273
+ name: "notePartRoots",
274
+ check: (doc, session) => STORY_ENTRIES_PARTS.flatMap(
275
+ (part) => storyEntriesProblems(part, doc, session)
276
+ )
277
+ };
239
278
  var EXPORT_INVARIANTS = [
240
279
  bookmarkPairs,
241
280
  tableGrids,
242
281
  preservedOriginals,
243
282
  uniqueIdentities,
244
283
  listDefinitions,
284
+ storiesHaveWriters,
245
285
  mediaContentTypes,
246
- commentPartRoots
286
+ commentPartRoots,
287
+ notePartRoots
247
288
  ];
248
289
  var answered = /* @__PURE__ */ new WeakMap();
249
290
  function problemsOf(doc, session) {
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The note an edit puts into a document: a new, empty one, and a copy of one the document holds.
3
+ *
4
+ * Both are side stories (`docx/story`), written back by the notes part writer (`./writing`) as an
5
+ * entry of their own, which is why a copy names none of the blocks its original was read from.
6
+ */
7
+ import { type Node as PMNode } from "prosemirror-model";
8
+ import { type NoteKind, type StoryKey } from "../../schema/stories";
9
+ import type { FormattingContext } from "../formatting";
10
+ import { type SessionStore } from "../session";
11
+ /**
12
+ * The elements that stand for a note's own number inside its entry, which is what Word draws the
13
+ * number from and what a note that keeps its number keeps (`editor/notes/noteSurface`).
14
+ */
15
+ export declare const NOTE_NUMBER_ELEMENTS: readonly string[];
16
+ /**
17
+ * The run properties a note's number is written with: the document's own style for it where the
18
+ * document defines one, and superscript where it does not.
19
+ */
20
+ export declare function noteNumberRunProps(kind: NoteKind, formatting: FormattingContext): string;
21
+ /**
22
+ * The story of a new note holding no text yet: one paragraph in the document's style for note
23
+ * text, opening with the note's number the way Word writes a note it inserts.
24
+ */
25
+ export declare function newNoteStory(kind: NoteKind, formatting: FormattingContext): PMNode;
26
+ /**
27
+ * Every id a note of this kind already answers to: the entries the opened part holds, the stories
28
+ * the document holds, and the ids the references name, whether or not a note stands behind them.
29
+ */
30
+ export declare function takenNoteIds(doc: PMNode, kind: NoteKind, reserved: ReadonlySet<StoryKey>): Set<string>;
31
+ /** The id one above the largest of these, and never below one */
32
+ export declare function nextNoteId(taken: Iterable<string>): string;
33
+ /**
34
+ * The story a copied reference's note is given: the same content, naming none of the blocks the
35
+ * original was read from and none of its paragraph ids, so the export writes it as an entry of its
36
+ * own rather than as the bytes of the original a second time (`docx/cloning`). A preserved block
37
+ * that only names its fragment takes the fragment's XML along.
38
+ */
39
+ export declare function copiedNoteStory(story: PMNode, session: SessionStore | null): PMNode;
@@ -0,0 +1,122 @@
1
+ // src/docx/notes/newNote.ts
2
+ import { Fragment } from "prosemirror-model";
3
+ import { elementXml } from "../../ooxml/element.js";
4
+ import { wName } from "../../ooxml/names.js";
5
+ import { docxSchema } from "../../schema/index.js";
6
+ import {
7
+ NOTE_KINDS,
8
+ storiesOf,
9
+ storyKey
10
+ } from "../../schema/stories.js";
11
+ import { CLONE_POLICIES } from "../cloning.js";
12
+ import { originalBlock } from "../session.js";
13
+ import { withStyleFormats } from "../story.js";
14
+ var NOTE_MARKUP = {
15
+ footnote: {
16
+ textStyle: "FootnoteText",
17
+ referenceStyle: "FootnoteReference",
18
+ numberElement: "footnoteRef"
19
+ },
20
+ endnote: {
21
+ textStyle: "EndnoteText",
22
+ referenceStyle: "EndnoteReference",
23
+ numberElement: "endnoteRef"
24
+ }
25
+ };
26
+ var NOTE_NUMBER_ELEMENTS = NOTE_KINDS.map(
27
+ (kind) => NOTE_MARKUP[kind].numberElement
28
+ );
29
+ function noteNumberRunProps(kind, formatting) {
30
+ const { referenceStyle } = NOTE_MARKUP[kind];
31
+ const property = formatting.styles.get(referenceStyle)?.type === "character" ? elementXml(wName("rStyle"), [[wName("val"), referenceStyle]]) : elementXml(wName("vertAlign"), [[wName("val"), "superscript"]]);
32
+ return elementXml(wName("rPr"), [], [property]);
33
+ }
34
+ function noteTextProps(kind, formatting) {
35
+ const { textStyle } = NOTE_MARKUP[kind];
36
+ return formatting.styles.get(textStyle)?.type === "paragraph" ? elementXml(
37
+ wName("pPr"),
38
+ [],
39
+ [elementXml(wName("pStyle"), [[wName("val"), textStyle]])]
40
+ ) : null;
41
+ }
42
+ function newNoteStory(kind, formatting) {
43
+ const { numberElement } = NOTE_MARKUP[kind];
44
+ const number = docxSchema.nodes.rawRunContent.create(
45
+ {
46
+ xml: elementXml(wName(numberElement), []),
47
+ element: numberElement,
48
+ display: "chip",
49
+ text: null,
50
+ guarded: false
51
+ },
52
+ null,
53
+ [
54
+ docxSchema.marks.run.create({
55
+ rPr: noteNumberRunProps(kind, formatting)
56
+ })
57
+ ]
58
+ );
59
+ const paragraph = docxSchema.nodes.paragraph.create(
60
+ { pPr: noteTextProps(kind, formatting) },
61
+ number
62
+ );
63
+ return withStyleFormats(
64
+ docxSchema.nodes.doc.create(null, [paragraph]),
65
+ formatting
66
+ );
67
+ }
68
+ var DECIMAL = /^-?\d+$/;
69
+ function takenNoteIds(doc, kind, reserved) {
70
+ const prefix = storyKey(kind, "");
71
+ const ids = new Set(
72
+ [...reserved, ...Object.keys(storiesOf(doc))].flatMap(
73
+ (key) => key.startsWith(prefix) ? [key.slice(prefix.length)] : []
74
+ )
75
+ );
76
+ doc.descendants((node) => {
77
+ const id = node.attrs.id;
78
+ if (node.type === docxSchema.nodes.noteReference && node.attrs.kind === kind && typeof id === "string") {
79
+ ids.add(id);
80
+ }
81
+ return true;
82
+ });
83
+ return ids;
84
+ }
85
+ function nextNoteId(taken) {
86
+ const largest = Array.from(taken).filter((id) => DECIMAL.test(id)).map((id) => BigInt(id)).reduce((max, id) => id > max ? id : max, 0n);
87
+ return (largest + 1n).toString();
88
+ }
89
+ function copiedBlock(node, session) {
90
+ if (node.isInline) return node;
91
+ if (node.type === docxSchema.nodes.paragraph) {
92
+ return node.type.create(
93
+ CLONE_POLICIES.paragraph.attrs(node, "copy"),
94
+ node.content,
95
+ node.marks
96
+ );
97
+ }
98
+ if (node.type === docxSchema.nodes.rawBlock) {
99
+ const held = node.attrs.xml;
100
+ const xml = typeof held === "string" ? held : session === null ? void 0 : originalBlock(node, session)?.xml;
101
+ return xml === void 0 ? node : node.type.create({ ...node.attrs, xml, srcId: null }, null, node.marks);
102
+ }
103
+ const attrs = node.attrs.srcId === void 0 || node.attrs.srcId === null ? node.attrs : { ...node.attrs, srcId: null };
104
+ return node.type.create(
105
+ attrs,
106
+ Fragment.fromArray(
107
+ node.children.map((child) => copiedBlock(child, session))
108
+ ),
109
+ node.marks
110
+ );
111
+ }
112
+ function copiedNoteStory(story, session) {
113
+ return copiedBlock(story, session);
114
+ }
115
+ export {
116
+ NOTE_NUMBER_ELEMENTS,
117
+ copiedNoteStory,
118
+ newNoteStory,
119
+ nextNoteId,
120
+ noteNumberRunProps,
121
+ takenNoteIds
122
+ };
@@ -0,0 +1,27 @@
1
+ import { type Node as PMNode } from "prosemirror-model";
2
+ import { type StoryKey } from "../../schema/stories";
3
+ import { type NoteNumbering } from "./reading";
4
+ /**
5
+ * The label each note reference of the body is drawn with, by position.
6
+ *
7
+ * Each kind is counted on its own, in the order its references stand: a repeated id takes the
8
+ * label of its first reference since the count last started, a reference whose custom mark follows
9
+ * takes neither a label nor a number (§17.11.14), and one calling a separator entry is no note to
10
+ * count. A section's own properties lay over the settings, and a section that restarts each section
11
+ * counts from its start again (§17.11.19, §17.11.20), so a note referred to in an earlier section
12
+ * and again in this one is drawn with this section's own number, as Word draws it. A restart on
13
+ * each page is counted straight through, since the label would then depend on the pages, which
14
+ * depend in turn on how wide the labels are drawn.
15
+ *
16
+ * It reads the document and what it is handed and nothing else, so the file an edited document
17
+ * exports opens with the labels the editor drew.
18
+ */
19
+ export declare function noteLabelsIn(doc: PMNode, numbering: NoteNumbering, special: ReadonlySet<StoryKey>): ReadonlyMap<number, string>;
20
+ /**
21
+ * What `noteLabelsIn` reads off the document, as one string: the kind, id, and custom mark of each
22
+ * reference, which section it stands in, and what each section lays over the settings. Two
23
+ * documents with the same signature are labelled alike under the same numbering.
24
+ */
25
+ export declare function noteLabelSignature(doc: PMNode): string;
26
+ /** The document with every note reference labelled, and the same node where every label already stood */
27
+ export declare function withNoteLabels(doc: PMNode, numbering: NoteNumbering, special: ReadonlySet<StoryKey>): PMNode;
@@ -0,0 +1,120 @@
1
+ // src/docx/notes/numbering.ts
2
+ import { Fragment } from "prosemirror-model";
3
+ import { spellNumber } from "../../numbering/spellers.js";
4
+ import {
5
+ NOTE_KINDS,
6
+ storyKey
7
+ } from "../../schema/stories.js";
8
+ import { sectionsOf } from "../sections.js";
9
+ import {
10
+ withNoteProps
11
+ } from "./reading.js";
12
+ var MAX_LABEL_CHARS = 64;
13
+ function referencesBySection(doc) {
14
+ const found = [];
15
+ doc.forEach((block, offset, index) => {
16
+ block.descendants((node, pos) => {
17
+ if (node.type.name === "noteReference") {
18
+ found.push({
19
+ block: index,
20
+ reference: { pos: offset + 1 + pos, node }
21
+ });
22
+ }
23
+ return true;
24
+ });
25
+ });
26
+ if (found.length === 0) return [];
27
+ const sections = sectionsOf(doc);
28
+ const references = sections.map(() => []);
29
+ let at = 0;
30
+ for (const { block, reference } of found) {
31
+ while (block > sections[at].lastBlock) at += 1;
32
+ references[at].push(reference);
33
+ }
34
+ return sections.map((section, index) => ({
35
+ section,
36
+ references: references[index]
37
+ }));
38
+ }
39
+ function kindOf(node) {
40
+ return node.attrs.kind === "endnote" ? "endnote" : "footnote";
41
+ }
42
+ function idOf(node) {
43
+ const id = node.attrs.id;
44
+ return typeof id === "string" ? id : null;
45
+ }
46
+ function propsIn(section, numbering) {
47
+ return {
48
+ footnote: withNoteProps(numbering.footnote, section.props.footnotePr),
49
+ endnote: withNoteProps(numbering.endnote, section.props.endnotePr)
50
+ };
51
+ }
52
+ function restarted(count, props) {
53
+ count.next = props.start;
54
+ count.taken.clear();
55
+ }
56
+ function labelOf(node, count, props, special) {
57
+ if (node.attrs.customMarkFollows === true) return "";
58
+ const id = idOf(node);
59
+ if (id === null || special.has(storyKey(kindOf(node), id))) return "?";
60
+ const taken = count.taken.get(id);
61
+ if (taken !== void 0) return taken;
62
+ const label = spellNumber(count.next, props.format, MAX_LABEL_CHARS);
63
+ count.next += 1;
64
+ count.taken.set(id, label);
65
+ return label;
66
+ }
67
+ function noteLabelsIn(doc, numbering, special) {
68
+ const labels = /* @__PURE__ */ new Map();
69
+ const counts = {
70
+ footnote: { next: 1, taken: /* @__PURE__ */ new Map() },
71
+ endnote: { next: 1, taken: /* @__PURE__ */ new Map() }
72
+ };
73
+ referencesBySection(doc).forEach(({ section, references }, index) => {
74
+ const props = propsIn(section, numbering);
75
+ for (const kind of NOTE_KINDS) {
76
+ if (index === 0 || props[kind].restart === "eachSect") {
77
+ restarted(counts[kind], props[kind]);
78
+ }
79
+ }
80
+ for (const { pos, node } of references) {
81
+ const kind = kindOf(node);
82
+ labels.set(pos, labelOf(node, counts[kind], props[kind], special));
83
+ }
84
+ });
85
+ return labels;
86
+ }
87
+ function noteLabelSignature(doc) {
88
+ return JSON.stringify(
89
+ referencesBySection(doc).map(({ section, references }) => [
90
+ section.props.footnotePr,
91
+ section.props.endnotePr,
92
+ references.map(({ node }) => [
93
+ kindOf(node),
94
+ idOf(node),
95
+ node.attrs.customMarkFollows === true
96
+ ])
97
+ ])
98
+ );
99
+ }
100
+ function relabelled(node, contentStart, labels) {
101
+ const children = [];
102
+ let changed = false;
103
+ node.forEach((child, offset) => {
104
+ const at = contentStart + offset;
105
+ const label = labels.get(at);
106
+ const next = label !== void 0 && child.attrs.label !== label ? child.type.create({ ...child.attrs, label }, null, child.marks) : child.isLeaf ? child : relabelled(child, at + 1, labels);
107
+ changed ||= next !== child;
108
+ children.push(next);
109
+ });
110
+ return changed ? node.copy(Fragment.from(children)) : node;
111
+ }
112
+ function withNoteLabels(doc, numbering, special) {
113
+ const labels = noteLabelsIn(doc, numbering, special);
114
+ return labels.size === 0 ? doc : relabelled(doc, 0, labels);
115
+ }
116
+ export {
117
+ noteLabelSignature,
118
+ noteLabelsIn,
119
+ withNoteLabels
120
+ };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Reads the footnotes and endnotes parts the main part relates to: the entry each holds, which of
3
+ * them lays the page out rather than says anything, and how each kind is numbered.
4
+ *
5
+ * What a note says is a story of its own, read by `docx/story` out of the same part text.
6
+ */
7
+ import { type NumberFormat } from "../../numbering/spellers";
8
+ import { type NoteKind, type StoryKey } from "../../schema/stories";
9
+ export type { NoteKind } from "../../schema/stories";
10
+ /**
11
+ * One entry of a notes part as it arrived. What the note says is a story of its own, held on the
12
+ * document node under `footnote:<id>` or `endnote:<id>` (`docx/story`).
13
+ */
14
+ export interface ImportedNote {
15
+ kind: NoteKind;
16
+ id: string;
17
+ type: string | null;
18
+ }
19
+ export interface ImportedNotePart {
20
+ partPath: string | null;
21
+ /** The raw text of the part, which is what the note stories are sliced out of. null for none */
22
+ xml: string | null;
23
+ ordered: readonly ImportedNote[];
24
+ byId: ReadonlyMap<string, ImportedNote>;
25
+ }
26
+ export interface ImportedNotes {
27
+ footnotes: ImportedNotePart;
28
+ endnotes: ImportedNotePart;
29
+ }
30
+ /** Reads both note parts without modifying their original package bytes. */
31
+ export declare function readNotes(parts: Map<string, Uint8Array>, mainPartPath: string): ImportedNotes;
32
+ /**
33
+ * The entries a notes part lays the page out with rather than numbers - a separator, a
34
+ * continuation separator, a continuation notice (§17.11.1, §17.11.23) - as the keys their stories
35
+ * stand under.
36
+ */
37
+ export declare function specialNotesOf(notes: ImportedNotes): ReadonlySet<StoryKey>;
38
+ /** Where the count of one kind of note goes back to its start (§17.18.74) */
39
+ export type NoteRestart = "continuous" | "eachSect" | "eachPage";
40
+ /** How one kind of note is counted (§17.11.17-20) */
41
+ export interface NoteNumberingProps {
42
+ readonly format: NumberFormat;
43
+ readonly start: number;
44
+ readonly restart: NoteRestart;
45
+ }
46
+ export type NoteNumbering = Readonly<Record<NoteKind, NoteNumberingProps>>;
47
+ export declare const DEFAULT_NOTE_NUMBERING: NoteNumbering;
48
+ /**
49
+ * What one `w:footnotePr` or `w:endnotePr` names, leaving out every property it does not, and null
50
+ * for no element at all.
51
+ *
52
+ * A format this editor has no speller for is counted in decimal, as a list marker is.
53
+ */
54
+ export declare function readNoteProps(props: Element | null): Partial<NoteNumberingProps> | null;
55
+ /** The properties with whatever an override names laid over them (§17.11.5, §17.11.11) */
56
+ export declare function withNoteProps(base: NoteNumberingProps, override: Partial<NoteNumberingProps> | null): NoteNumberingProps;
57
+ /** The document-wide note properties settings.xml names (§17.11.4, §17.11.12) */
58
+ export declare function readNoteNumbering(settings: Document | null): NoteNumbering;