@portone/docx-editor 0.6.0 → 0.6.2

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 +40 -0
  2. package/dist/DocxEditor.js +84 -26
  3. package/dist/core.d.ts +4 -3
  4. package/dist/docx/exportDocx.d.ts +3 -2
  5. package/dist/docx/exportDocx.js +6 -10
  6. package/dist/docx/headersFooters.js +35 -23
  7. package/dist/docx/identities.d.ts +30 -0
  8. package/dist/docx/identities.js +43 -10
  9. package/dist/docx/importDocx.js +25 -26
  10. package/dist/docx/importParagraph.d.ts +1 -4
  11. package/dist/docx/importParagraph.js +4 -18
  12. package/dist/docx/invariants.d.ts +13 -10
  13. package/dist/docx/invariants.js +300 -116
  14. package/dist/docx/newLists.js +12 -3
  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 +113 -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/references.d.ts +33 -0
  22. package/dist/docx/notes/references.js +37 -0
  23. package/dist/docx/notes/writing.d.ts +19 -0
  24. package/dist/docx/notes/writing.js +95 -0
  25. package/dist/docx/partPlan.d.ts +3 -0
  26. package/dist/docx/partPlanners.d.ts +13 -0
  27. package/dist/docx/partPlanners.js +48 -0
  28. package/dist/docx/sections.d.ts +5 -0
  29. package/dist/docx/sections.js +7 -2
  30. package/dist/docx/serializeParagraph.js +7 -2
  31. package/dist/docx/serializePreserved.d.ts +5 -1
  32. package/dist/docx/serializePreserved.js +24 -7
  33. package/dist/docx/session.d.ts +5 -0
  34. package/dist/docx/session.js +6 -0
  35. package/dist/docx/storyParts.d.ts +133 -0
  36. package/dist/docx/storyParts.js +314 -0
  37. package/dist/editor/clipboard/htmlReader.d.ts +24 -4
  38. package/dist/editor/clipboard/htmlReader.js +39 -4
  39. package/dist/editor/clipboard/internalChannel.d.ts +30 -3
  40. package/dist/editor/clipboard/internalChannel.js +43 -3
  41. package/dist/editor/clipboard/normalizers.d.ts +35 -8
  42. package/dist/editor/clipboard/normalizers.js +60 -11
  43. package/dist/editor/clipboard/plugin.js +42 -22
  44. package/dist/editor/commands/exportQueries.d.ts +4 -3
  45. package/dist/editor/commands/index.d.ts +2 -1
  46. package/dist/editor/commands/index.js +18 -0
  47. package/dist/editor/commands/noteCommands.d.ts +47 -0
  48. package/dist/editor/commands/noteCommands.js +95 -0
  49. package/dist/editor/commands/noteQueries.d.ts +22 -3
  50. package/dist/editor/commands/noteQueries.js +21 -13
  51. package/dist/editor/createEditor.js +21 -3
  52. package/dist/editor/documentStyles.d.ts +3 -0
  53. package/dist/editor/documentStyles.js +2 -1
  54. package/dist/editor/editorDocument.d.ts +24 -0
  55. package/dist/editor/editorDocument.js +34 -4
  56. package/dist/editor/notes/noteSurface.d.ts +53 -0
  57. package/dist/editor/notes/noteSurface.js +246 -0
  58. package/dist/editor/plugins/compositionSelection.d.ts +20 -0
  59. package/dist/editor/plugins/compositionSelection.js +19 -0
  60. package/dist/editor/plugins/keymap.d.ts +14 -0
  61. package/dist/editor/plugins/keymap.js +13 -1
  62. package/dist/editor/plugins/noteLifecycle.d.ts +18 -0
  63. package/dist/editor/plugins/noteLifecycle.js +117 -0
  64. package/dist/editor/plugins/noteNavigation.d.ts +45 -0
  65. package/dist/editor/plugins/noteNavigation.js +86 -0
  66. package/dist/editor/plugins/noteNumbering.d.ts +13 -0
  67. package/dist/editor/plugins/noteNumbering.js +58 -0
  68. package/dist/editor/plugins/rowResize.js +2 -7
  69. package/dist/editor/plugins/tabLayout.js +2 -7
  70. package/dist/editor/stories/storyMarkup.d.ts +24 -0
  71. package/dist/editor/stories/storyMarkup.js +35 -0
  72. package/dist/editor/stories/storyState.d.ts +34 -0
  73. package/dist/editor/stories/storyState.js +74 -0
  74. package/dist/editor/stories/storyView.d.ts +115 -0
  75. package/dist/editor/stories/storyView.js +144 -0
  76. package/dist/index.d.ts +2 -2
  77. package/dist/numbering/spellers.d.ts +1 -1
  78. package/dist/numbering/spellers.js +6 -1
  79. package/dist/ooxml/errors.d.ts +135 -2
  80. package/dist/ooxml/errors.js +6 -0
  81. package/dist/page/blockKinds.d.ts +23 -4
  82. package/dist/page/demands/footnoteDemands.d.ts +18 -0
  83. package/dist/page/demands/footnoteDemands.js +51 -0
  84. package/dist/page/demands/index.d.ts +43 -0
  85. package/dist/page/demands/index.js +8 -0
  86. package/dist/page/kinds/paragraphKind.js +7 -1
  87. package/dist/page/kinds/tableKind.js +9 -1
  88. package/dist/page/measureBlocks.d.ts +2 -0
  89. package/dist/page/measureBlocks.js +31 -10
  90. package/dist/page/pageDecorations.d.ts +9 -2
  91. package/dist/page/pageDecorations.js +18 -5
  92. package/dist/page/pageLayout.d.ts +77 -1
  93. package/dist/page/pageLayout.js +186 -9
  94. package/dist/page/usePageLayout.d.ts +57 -2
  95. package/dist/page/usePageLayout.js +50 -5
  96. package/dist/schema/attrRoles.js +3 -2
  97. package/dist/schema/docxSchema.js +5 -2
  98. package/dist/schema/editGuard.d.ts +1 -1
  99. package/dist/schema/guards.js +1 -2
  100. package/dist/schema/preservedGuards.d.ts +4 -6
  101. package/dist/schema/preservedGuards.js +0 -9
  102. package/dist/schema/stories.d.ts +42 -2
  103. package/dist/schema/stories.js +28 -5
  104. package/dist/styles/classNames.d.ts +21 -6
  105. package/dist/styles/classNames.js +21 -6
  106. package/dist/styles/inlineStyle.d.ts +7 -0
  107. package/dist/styles/inlineStyle.js +11 -5
  108. package/dist/styles/visualScale.d.ts +19 -0
  109. package/dist/styles/visualScale.js +26 -0
  110. package/dist/styles.css +89 -32
  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 +1 -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
@@ -11,6 +11,7 @@
11
11
  * arrived as, and a rewritten child is swapped into the spot `CT_SectPr` lays down.
12
12
  */
13
13
  import type { Node as PMNode } from "prosemirror-model";
14
+ import { type NoteNumberingProps } from "./notes/reading";
14
15
  import { type PageGeometry } from "./pageGeometry";
15
16
  export type HeaderFooterVariant = "default" | "first" | "even";
16
17
  /** The variants a `w:headerReference` or a `w:footerReference` may name. §17.6.12, §17.6.5 */
@@ -35,6 +36,10 @@ export interface SectionProperties {
35
36
  pageNumberStart: number | null;
36
37
  /** §17.6.22 `w:type/@w:val`; null when omitted (nextPage) */
37
38
  type: "continuous" | "evenPage" | "nextPage" | "oddPage" | "nextColumn" | null;
39
+ /** §17.11.11 `w:footnotePr`; null where the section counts footnotes as the settings do */
40
+ footnotePr: Partial<NoteNumberingProps> | null;
41
+ /** §17.11.5 `w:endnotePr`; null where the section counts endnotes as the settings do */
42
+ endnotePr: Partial<NoteNumberingProps> | null;
38
43
  }
39
44
  /** What a section the document does not spell out lays down: the paper every document used to be drawn on */
40
45
  export declare const DEFAULT_SECTION: SectionProperties;
@@ -15,6 +15,7 @@ import {
15
15
  R_NS,
16
16
  W_NS
17
17
  } from "../ooxml/xml.js";
18
+ import { readNoteProps } from "./notes/reading.js";
18
19
  import {
19
20
  A4_PORTRAIT,
20
21
  readPageGeometry
@@ -32,7 +33,9 @@ var DEFAULT_SECTION = {
32
33
  footerRefs: NO_REFS,
33
34
  titlePg: false,
34
35
  pageNumberStart: null,
35
- type: null
36
+ type: null,
37
+ footnotePr: null,
38
+ endnotePr: null
36
39
  };
37
40
  var SECTION_TYPES = [
38
41
  "continuous",
@@ -86,7 +89,9 @@ function readSectionProperties(sectPr) {
86
89
  footerRefs: storyRefs(sectPr, "footer"),
87
90
  titlePg: isOnElement(childByLocalName(sectPr, "titlePg")),
88
91
  pageNumberStart: pageNumberStart(sectPr),
89
- type: sectionType(sectPr)
92
+ type: sectionType(sectPr),
93
+ footnotePr: readNoteProps(childByLocalName(sectPr, "footnotePr")),
94
+ endnotePr: readNoteProps(childByLocalName(sectPr, "endnotePr"))
90
95
  };
91
96
  }
92
97
  function parseSectionProperties(xml) {
@@ -1,5 +1,9 @@
1
1
  // src/docx/serializeParagraph.ts
2
- import { elementXml, emptyTagXml, openTagXml } from "../ooxml/element.js";
2
+ import {
3
+ elementXml,
4
+ emptyTagXml,
5
+ openTagXml
6
+ } from "../ooxml/element.js";
3
7
  import { DocxExportError } from "../ooxml/errors.js";
4
8
  import {
5
9
  imageDrawingXml,
@@ -85,8 +89,9 @@ function renderInline(node, images) {
85
89
  if (typeof original === "string") return original;
86
90
  const id = node.attrs.id;
87
91
  const name = node.attrs.kind === "endnote" ? "endnoteReference" : "footnoteReference";
92
+ const customMark = node.attrs.customMarkFollows === true ? [[wName("customMarkFollows"), "1"]] : [];
88
93
  if (typeof id === "string") {
89
- return elementXml(wName(name), [[wName("id"), id]]);
94
+ return elementXml(wName(name), [...customMark, [wName("id"), id]]);
90
95
  }
91
96
  throw new DocxExportError(
92
97
  "lost-original",
@@ -7,8 +7,12 @@
7
7
  * the same way, from here.
8
8
  */
9
9
  import type { Node as PMNode } from "prosemirror-model";
10
+ import { type ExportProblemReason, type ExportProblemStory } from "../ooxml/errors";
10
11
  import type { ExportRefs } from "./exportRefs";
11
12
  import { type SessionStore } from "./session";
12
13
  /** Why there is no original to write, which a block that came in from another document answers differently */
13
- export declare function lostOriginal(node: PMNode, session: SessionStore | null): string;
14
+ export declare function lostOriginal(node: PMNode, session: SessionStore | null, story: ExportProblemStory | null): {
15
+ readonly message: string;
16
+ readonly reason: ExportProblemReason;
17
+ };
14
18
  export declare function serializePreservedBlock(node: PMNode, refs: ExportRefs): string;
@@ -1,20 +1,37 @@
1
1
  // src/docx/serializePreserved.ts
2
- import { DocxExportError } from "../ooxml/errors.js";
2
+ import {
3
+ DocxExportError
4
+ } from "../ooxml/errors.js";
3
5
  import { originalBlock, splitBlockKey } from "./session.js";
4
- function lostOriginal(node, session) {
6
+ function lostOriginal(node, session, story) {
5
7
  const srcId = node.attrs.srcId;
6
8
  const key = typeof srcId === "string" ? splitBlockKey(srcId) : null;
7
- return key === null || key.sessionId === session?.sessionId ? "a preserved block has lost its original XML" : `a preserved block comes from another document (${key.sessionId})`;
9
+ const name = node.type.name;
10
+ if (key === null || key.sessionId === session?.sessionId) {
11
+ return {
12
+ message: "a preserved block has lost its original XML",
13
+ reason: { kind: "lost-preserved-xml", node: name, story }
14
+ };
15
+ }
16
+ return {
17
+ message: `a preserved block comes from another document (${key.sessionId})`,
18
+ reason: {
19
+ kind: "preserved-from-another-document",
20
+ node: name,
21
+ sessionId: key.sessionId,
22
+ story
23
+ }
24
+ };
8
25
  }
9
26
  function serializePreservedBlock(node, refs) {
10
27
  const xml = node.attrs.xml;
11
28
  if (typeof xml === "string") return xml;
12
29
  const imported = refs.session ? originalBlock(node, refs.session) : void 0;
13
30
  if (!imported) {
14
- throw new DocxExportError(
15
- "lost-original",
16
- lostOriginal(node, refs.session)
17
- );
31
+ const { message, reason } = lostOriginal(node, refs.session, null);
32
+ throw new DocxExportError("lost-original", message, {
33
+ problem: { code: "lost-original", message, reason }
34
+ });
18
35
  }
19
36
  return imported.xml;
20
37
  }
@@ -14,6 +14,7 @@ import { type StoryKey } from "../schema/stories";
14
14
  import type { ImportedComments } from "./comments/reading";
15
15
  import { type FormattingContext, type ParagraphStyleOption } from "./formatting";
16
16
  import type { HeaderFooterStories } from "./headersFooters";
17
+ import type { NoteNumbering } from "./notes/reading";
17
18
  import type { PageGeometry } from "./pageGeometry";
18
19
  import type { ImportedStory } from "./story";
19
20
  export interface ImportedBlock {
@@ -83,6 +84,10 @@ export declare class SessionStore implements DocxSession, SessionIdentity {
83
84
  * instead (`docx/story`), so the two compare the way a body block and its original do.
84
85
  */
85
86
  readonly stories: ReadonlyMap<StoryKey, ImportedStory>;
87
+ /** How the settings count each kind of note, which a section may override (`docx/notes/numbering`) */
88
+ readonly noteNumbering: NoteNumbering;
89
+ /** The note entries that lay out the page rather than say anything: separators and the continuation notice */
90
+ readonly specialNotes: ReadonlySet<StoryKey>;
86
91
  constructor(opened: Omit<SessionStore, "kind" | "blocksOf">);
87
92
  /**
88
93
  * The blocks of one story as it arrived, which is what a block key of that story indexes into.
@@ -51,6 +51,10 @@ var SessionStore = class {
51
51
  * instead (`docx/story`), so the two compare the way a body block and its original do.
52
52
  */
53
53
  stories;
54
+ /** How the settings count each kind of note, which a section may override (`docx/notes/numbering`) */
55
+ noteNumbering;
56
+ /** The note entries that lay out the page rather than say anything: separators and the continuation notice */
57
+ specialNotes;
54
58
  constructor(opened) {
55
59
  this.sessionId = opened.sessionId;
56
60
  this.parts = opened.parts;
@@ -70,6 +74,8 @@ var SessionStore = class {
70
74
  this.commentReferenceIds = opened.commentReferenceIds;
71
75
  this.headerFooterStories = opened.headerFooterStories;
72
76
  this.stories = opened.stories;
77
+ this.noteNumbering = opened.noteNumbering;
78
+ this.specialNotes = opened.specialNotes;
73
79
  }
74
80
  /**
75
81
  * The blocks of one story as it arrived, which is what a block key of that story indexes into.
@@ -0,0 +1,133 @@
1
+ /**
2
+ * The side stories a part beside the body is written from, told apart by what an edit did to each.
3
+ *
4
+ * Whether a story has to be written again is one question every story writer asks, so it is asked
5
+ * here once. A writer answering it for itself would be free to keep a story another writer drops,
6
+ * and the export invariants would have a third answer of their own.
7
+ */
8
+ import type { Node as PMNode } from "prosemirror-model";
9
+ import type { DocxExportErrorCode, ExportPartName, ExportProblemReason } from "../ooxml/errors";
10
+ import { type StoryKey, type StoryKind } from "../schema/stories";
11
+ import { type StoryToSettle } from "./identities";
12
+ import type { PartPlanner } from "./partPlan";
13
+ import type { SessionStore } from "./session";
14
+ import type { ImportedStory } from "./story";
15
+ export type StoryChange = {
16
+ readonly change: "kept";
17
+ readonly imported: ImportedStory;
18
+ } | {
19
+ readonly change: "edited";
20
+ readonly imported: ImportedStory;
21
+ readonly current: PMNode;
22
+ } | {
23
+ readonly change: "removed";
24
+ readonly imported: ImportedStory;
25
+ } | {
26
+ readonly change: "added";
27
+ readonly key: StoryKey;
28
+ readonly current: PMNode;
29
+ };
30
+ /** Every story of this kind the package arrived with or the document now holds, in part order then id order */
31
+ export declare function storyChangesOf(doc: PMNode, session: SessionStore, kind: StoryKind): readonly StoryChange[];
32
+ export type StoryChanged = Exclude<StoryChange["change"], "kept">;
33
+ export declare const EVERY_STORY_CHANGE: readonly StoryChanged[];
34
+ /**
35
+ * What one part writer carries into the file: the kind of story it writes and the changes to one
36
+ * it writes back. A change it leaves out has nowhere to go, and `docx/invariants` refuses it.
37
+ */
38
+ export interface StoryWriting {
39
+ readonly kind: StoryKind;
40
+ readonly changes: readonly StoryChanged[];
41
+ /** The stories it writes as the bytes they arrived as, whose change it therefore carries nowhere */
42
+ frozenEntries(session: SessionStore): ReadonlySet<StoryKey>;
43
+ }
44
+ /** What a part holding one entry per story writes, which is every change to a story of its kind */
45
+ export declare function storyEntriesWriting(part: StoryEntriesPart): StoryWriting;
46
+ /** A writer that takes these changes to every story of the kind, freezing none of them */
47
+ export declare function storyWriting(kind: StoryKind, changes: readonly StoryChanged[]): StoryWriting;
48
+ /** A change to a side story that no part writer carries into the file */
49
+ export interface UnwrittenStoryChange {
50
+ readonly key: StoryKey;
51
+ readonly change: StoryChanged;
52
+ }
53
+ /**
54
+ * Every change the document made to a side story that no part writer carries into the file, as the
55
+ * key it stands under and what was done to it.
56
+ *
57
+ * A story changes on the document node whether or not a writer carries it, so a kind with no
58
+ * writer at all, a change a writer leaves out - a header story added or removed, which the header
59
+ * writer does not write - and a change to an entry written as it arrived are all reported here
60
+ * rather than dropped from the file without a word.
61
+ */
62
+ export declare function unwrittenStoryChanges(writings: readonly StoryWriting[], doc: PMNode, session: SessionStore): readonly UnwrittenStoryChange[];
63
+ /** One side story the export writes block by block, under the key it stands at */
64
+ export interface RewrittenStory {
65
+ readonly key: StoryKey;
66
+ readonly story: PMNode;
67
+ }
68
+ /**
69
+ * Every side story the export writes block by block, which is every one a part writer carries an
70
+ * addition or an edit of.
71
+ *
72
+ * A story the writer hands back as the bytes it arrived as - one nobody touched, one its part
73
+ * freezes, one whose change no writer carries, which `unwrittenStoryChanges` refuses instead -
74
+ * goes out holding exactly what it came in holding, so nothing about it can newly be refused. The
75
+ * same "changed and written" answer decides both, so a writer and the invariants cannot disagree
76
+ * about which story is written from a node.
77
+ */
78
+ export declare function rewrittenStories(writings: readonly StoryWriting[], doc: PMNode, session: SessionStore): readonly RewrittenStory[];
79
+ /** A part holding one entry per story, e.g. `w:footnotes` holding a `w:footnote` apiece */
80
+ export interface StoryEntriesPart {
81
+ readonly name: ExportPartName;
82
+ readonly kind: StoryKind;
83
+ readonly relType: string;
84
+ readonly contentType: string;
85
+ /** The file name a part this planner creates takes beside the main part, ahead of any number */
86
+ readonly stem: string;
87
+ /** The local name of the part's root element */
88
+ readonly root: string;
89
+ /** The local name of the element each story stands in */
90
+ readonly entry: string;
91
+ /** The ids the document's references to this part name, which a part this planner creates keeps clear of */
92
+ referencedIds(doc: PMNode): ReadonlySet<string>;
93
+ /**
94
+ * The stories this part writes as the bytes they arrived as, whatever the document now says of
95
+ * them, and none for a part that writes every story it holds. `docx/invariants` refuses a change
96
+ * to one before anything is written.
97
+ */
98
+ frozenEntries(session: SessionStore): ReadonlySet<StoryKey>;
99
+ /** The entries a part this planner creates opens with, before any story */
100
+ prelude(taken: ReadonlySet<string>): string;
101
+ }
102
+ /** One story a part writes, beside the key it stands under */
103
+ export interface StoryEntry extends StoryToSettle {
104
+ readonly key: StoryKey;
105
+ }
106
+ /**
107
+ * The stories a part is written with, in the order it writes them, and none where no story of its
108
+ * kind changed and the part is not written at all. The identity pass runs over this list as one
109
+ * part, and the export invariants ask the same list.
110
+ */
111
+ export declare function storyEntriesOf(part: StoryEntriesPart, doc: PMNode, session: SessionStore): readonly StoryEntry[];
112
+ /** One reason the part cannot take the changes its stories went through */
113
+ export interface StoryPartProblem {
114
+ readonly code: DocxExportErrorCode;
115
+ readonly message: string;
116
+ readonly reason: ExportProblemReason;
117
+ }
118
+ /**
119
+ * Why the part cannot take what the document did to its stories, or none when it can. The part the
120
+ * package holds is rewritten around its root element, and a part the package lacks is declared in
121
+ * the content types part, which the export does not write from nothing.
122
+ */
123
+ export declare function storyEntriesProblems(part: StoryEntriesPart, doc: PMNode, session: SessionStore): readonly StoryPartProblem[];
124
+ /**
125
+ * A planner for a part holding one entry per story.
126
+ *
127
+ * Nothing is written while every story of the kind stands as it arrived, so an untouched package
128
+ * hands the part back as its own bytes. Once one changed, the part is written in the order it holds
129
+ * its entries: an untouched entry as it arrived, an edited one again, a removed one not at all,
130
+ * and every story added since after them in id order. A package holding no such part gets one,
131
+ * with its relationship, its content type, and the entries `prelude` opens it with.
132
+ */
133
+ export declare function storyEntriesPlanner(part: StoryEntriesPart): PartPlanner;
@@ -0,0 +1,314 @@
1
+ // src/docx/storyParts.ts
2
+ import { attrsText, openTagXml } from "../ooxml/element.js";
3
+ import { NAMESPACES, wName } from "../ooxml/names.js";
4
+ import {
5
+ ensureRootDeclarations,
6
+ partRootProblem,
7
+ splicePart
8
+ } from "../ooxml/partSplice.js";
9
+ import { ST_DecimalNumber } from "../ooxml/simpleTypes.js";
10
+ import { parseAttrs, readTag, rootTagAt } from "../ooxml/tagScan.js";
11
+ import {
12
+ attributeByLocalName,
13
+ decodeUtf8,
14
+ elementChildren,
15
+ encodeUtf8,
16
+ parseXml
17
+ } from "../ooxml/xml.js";
18
+ import { sameSource } from "../schema/sourceEquality.js";
19
+ import {
20
+ asStoryKey,
21
+ STORY_KINDS,
22
+ splitStoryKey,
23
+ storiesOf,
24
+ storyKey,
25
+ storyNodeOf
26
+ } from "../schema/stories.js";
27
+ import { NO_EXPORT_REFS } from "./exportRefs.js";
28
+ import { withUniqueStoryIdentities } from "./identities.js";
29
+ import {
30
+ availablePartPath,
31
+ CONTENT_TYPES_PATH,
32
+ readPart,
33
+ relatedPartPath
34
+ } from "./packageParts.js";
35
+ import { directoryOf } from "./relationships.js";
36
+ import { scanBlocksIn } from "./scan.js";
37
+ import { serializeStory } from "./serializeStory.js";
38
+ var DECIMAL = /^-?\d+$/;
39
+ function byId(a, b) {
40
+ if (DECIMAL.test(a) && DECIMAL.test(b)) return Number(a) - Number(b);
41
+ if (a === b) return 0;
42
+ return a < b ? -1 : 1;
43
+ }
44
+ function arrivedChange(doc, imported) {
45
+ const current = storyNodeOf(doc, imported.key);
46
+ if (current === null) return { change: "removed", imported };
47
+ return sameSource(current, imported.doc) ? { change: "kept", imported } : { change: "edited", imported, current };
48
+ }
49
+ function storyChangesOf(doc, session, kind) {
50
+ const arrived = Array.from(session.stories.values()).filter((imported) => imported.kind === kind).map((imported) => arrivedChange(doc, imported));
51
+ const added = Object.keys(storiesOf(doc)).flatMap((text) => {
52
+ const key = asStoryKey(text);
53
+ return key === null || !key.startsWith(`${kind}:`) || session.stories.has(key) ? [] : [key];
54
+ }).sort((a, b) => byId(splitStoryKey(a).id, splitStoryKey(b).id)).flatMap((key) => {
55
+ const current = storyNodeOf(doc, key);
56
+ return current === null ? [] : [{ change: "added", key, current }];
57
+ });
58
+ return [...arrived, ...added];
59
+ }
60
+ function changed(changes) {
61
+ return changes.some((change) => change.change !== "kept");
62
+ }
63
+ var EVERY_STORY_CHANGE = [
64
+ "edited",
65
+ "removed",
66
+ "added"
67
+ ];
68
+ var NO_FROZEN_ENTRIES = /* @__PURE__ */ new Set();
69
+ function storyEntriesWriting(part) {
70
+ return {
71
+ kind: part.kind,
72
+ changes: EVERY_STORY_CHANGE,
73
+ frozenEntries: (session) => part.frozenEntries(session)
74
+ };
75
+ }
76
+ function storyWriting(kind, changes) {
77
+ return { kind, changes, frozenEntries: () => NO_FROZEN_ENTRIES };
78
+ }
79
+ function unwrittenStoryChanges(writings, doc, session) {
80
+ const byKind = new Map(writings.map((writing) => [writing.kind, writing]));
81
+ return STORY_KINDS.flatMap((kind) => {
82
+ const writing = byKind.get(kind);
83
+ const frozen = writing?.frozenEntries(session) ?? NO_FROZEN_ENTRIES;
84
+ return storyChangesOf(doc, session, kind).flatMap((entry) => {
85
+ if (entry.change === "kept") return [];
86
+ const key = entry.change === "added" ? entry.key : entry.imported.key;
87
+ const written = writing !== void 0 && writing.changes.includes(entry.change) && !frozen.has(key);
88
+ return written ? [] : [{ key, change: entry.change }];
89
+ });
90
+ });
91
+ }
92
+ function rewrittenStories(writings, doc, session) {
93
+ const byKind = new Map(writings.map((writing) => [writing.kind, writing]));
94
+ return STORY_KINDS.flatMap((kind) => {
95
+ const writing = byKind.get(kind);
96
+ if (writing === void 0) return [];
97
+ const frozen = writing.frozenEntries(session);
98
+ return storyChangesOf(doc, session, kind).flatMap(
99
+ (entry) => {
100
+ if (entry.change !== "added" && entry.change !== "edited") return [];
101
+ const key = entry.change === "added" ? entry.key : entry.imported.key;
102
+ return writing.changes.includes(entry.change) && !frozen.has(key) ? [{ key, story: entry.current }] : [];
103
+ }
104
+ );
105
+ });
106
+ }
107
+ function writtenStoryOf(change, frozen) {
108
+ if (change.change === "added") return change.current;
109
+ if (change.change === "kept" || frozen.has(change.imported.key)) {
110
+ return change.imported.doc;
111
+ }
112
+ return change.change === "edited" ? change.current : null;
113
+ }
114
+ function writtenStories(changes, frozen) {
115
+ return changes.flatMap((change) => {
116
+ const story = writtenStoryOf(change, frozen);
117
+ if (story === null) return [];
118
+ const key = change.change === "added" ? change.key : change.imported.key;
119
+ return [
120
+ {
121
+ change,
122
+ key,
123
+ story,
124
+ frozen: change.change !== "added" && frozen.has(key)
125
+ }
126
+ ];
127
+ });
128
+ }
129
+ function storyEntriesOf(part, doc, session) {
130
+ const changes = storyChangesOf(doc, session, part.kind);
131
+ return changed(changes) ? writtenStories(changes, part.frozenEntries(session)) : [];
132
+ }
133
+ function addedIdProblems(part, changes) {
134
+ return changes.flatMap((change) => {
135
+ if (change.change !== "added") return [];
136
+ const story = splitStoryKey(change.key);
137
+ return ST_DecimalNumber.parse(story.id) === null ? [
138
+ {
139
+ code: "unsupported-content",
140
+ message: `the ${change.key} story is named by no whole number, and a ${wName(part.entry)} is identified by one`,
141
+ reason: { kind: "story-id-not-a-number", story }
142
+ }
143
+ ] : [];
144
+ });
145
+ }
146
+ function storyEntriesProblems(part, doc, session) {
147
+ const changes = storyChangesOf(doc, session, part.kind);
148
+ if (!changed(changes)) return [];
149
+ const added = addedIdProblems(part, changes);
150
+ if (added.length > 0) return added;
151
+ const xml = readPart(
152
+ session.parts,
153
+ relatedPartPath(session.parts, session.mainPartPath, part.relType)
154
+ );
155
+ if (xml !== null) {
156
+ const problem = partRootProblem(xml, part.root);
157
+ return problem === null ? [] : [
158
+ {
159
+ code: "malformed-xml",
160
+ message: problem,
161
+ reason: { kind: "unwritable-part-root", part: part.name }
162
+ }
163
+ ];
164
+ }
165
+ return session.parts.has(CONTENT_TYPES_PATH) ? [] : [
166
+ {
167
+ code: "missing-content-types",
168
+ message: `cannot add a part to a package that has no ${CONTENT_TYPES_PATH}`,
169
+ reason: { kind: "missing-content-types", part: part.name }
170
+ }
171
+ ];
172
+ }
173
+ var ENTRY_MARKUP = { namespaces: { w: NAMESPACES.w } };
174
+ var XML_DECLARATION = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
175
+ function containerOf(part, id) {
176
+ const name = wName(part.entry);
177
+ return {
178
+ open: openTagXml(name, attrsText([[wName("id"), id]])),
179
+ close: `</${name}>`
180
+ };
181
+ }
182
+ function entryXml(part, { change, frozen }, settled, refs) {
183
+ if (change.change === "added") {
184
+ return serializeStory(
185
+ settled,
186
+ null,
187
+ containerOf(part, splitStoryKey(change.key).id),
188
+ refs
189
+ );
190
+ }
191
+ const { imported } = change;
192
+ return frozen || settled === imported.doc ? imported.xml : serializeStory(settled, imported, imported, refs);
193
+ }
194
+ function beforeClosingTag(suffix) {
195
+ let at = suffix.indexOf("<");
196
+ while (at !== -1) {
197
+ const tag = readTag(suffix, at);
198
+ if (tag === null || tag.kind === "close") return suffix.slice(0, at);
199
+ at = suffix.indexOf("<", tag.end);
200
+ }
201
+ return suffix;
202
+ }
203
+ function rewrittenPart(part, xml, arrived, appended) {
204
+ const scan = scanBlocksIn(xml, (_tag, depth) => depth === 0);
205
+ const seen = /* @__PURE__ */ new Set();
206
+ const entries = elementChildren(parseXml(xml).documentElement).map(
207
+ (el, at) => {
208
+ const slice = scan?.blocks[at]?.xml ?? "";
209
+ const id = el.localName === part.entry ? attributeByLocalName(el, "id") : null;
210
+ if (id === null) return slice;
211
+ if (seen.has(id)) return "";
212
+ seen.add(id);
213
+ const entry = arrived.get(storyKey(part.kind, id));
214
+ return entry === void 0 ? "" : slice.slice(0, slice.indexOf("<")) + entry;
215
+ }
216
+ );
217
+ return ensureRootDeclarations(
218
+ splicePart(xml, {
219
+ root: part.root,
220
+ replaceChildren: entries.join("") + appended + beforeClosingTag(scan?.suffix ?? "")
221
+ }),
222
+ ENTRY_MARKUP
223
+ );
224
+ }
225
+ function mainDeclarations(documentPrefix) {
226
+ const at = rootTagAt(documentPrefix);
227
+ const tag = at === -1 ? null : readTag(documentPrefix, at);
228
+ if (tag === null || tag.kind !== "open") return [];
229
+ const attrs = parseAttrs(documentPrefix.slice(tag.nameEnd, tag.end - 1));
230
+ return (attrs ?? []).filter(
231
+ ([name]) => name.startsWith("xmlns:") && name !== "xmlns:w" || name === "mc:Ignorable"
232
+ );
233
+ }
234
+ function createdPart(part, session, children) {
235
+ const name = wName(part.root);
236
+ const declarations = attrsText(mainDeclarations(session.documentPrefix));
237
+ return ensureRootDeclarations(
238
+ `${XML_DECLARATION}${openTagXml(name, declarations === "" ? null : declarations)}${children}</${name}>`,
239
+ ENTRY_MARKUP
240
+ );
241
+ }
242
+ function planEntries(part, doc, session, context) {
243
+ const changes = storyChangesOf(doc, session, part.kind);
244
+ if (!changed(changes)) return null;
245
+ const refs = {
246
+ ...NO_EXPORT_REFS,
247
+ notes: context.notes,
248
+ session
249
+ };
250
+ const written = writtenStories(changes, part.frozenEntries(session));
251
+ const settled = withUniqueStoryIdentities(written);
252
+ const entries = written.map((entry, at) => ({
253
+ change: entry.change,
254
+ xml: entryXml(part, entry, settled[at] ?? entry.story, refs)
255
+ }));
256
+ const appended = entries.flatMap(({ change, xml }) => change.change === "added" ? [xml] : []).join("");
257
+ const related = relatedPartPath(
258
+ session.parts,
259
+ session.mainPartPath,
260
+ part.relType
261
+ );
262
+ const path = related ?? availablePartPath(session.parts, session.mainPartPath, part.stem);
263
+ const bytes = session.parts.get(path);
264
+ if (bytes !== void 0) {
265
+ const { text, hadBom } = decodeUtf8(bytes);
266
+ const arrived = new Map(
267
+ entries.flatMap(
268
+ ({ change, xml }) => change.change === "added" ? [] : [[change.imported.key, xml]]
269
+ )
270
+ );
271
+ return /* @__PURE__ */ new Map([
272
+ [path, encodeUtf8(rewrittenPart(part, text, arrived, appended), hadBom)]
273
+ ]);
274
+ }
275
+ if (related === null) {
276
+ context.relationships.add({
277
+ type: part.relType,
278
+ target: path.slice(directoryOf(session.mainPartPath).length)
279
+ });
280
+ }
281
+ context.contentTypes.addOverride(path, part.contentType);
282
+ const taken = /* @__PURE__ */ new Set([
283
+ ...changes.map(
284
+ (change) => change.change === "added" ? splitStoryKey(change.key).id : change.imported.id
285
+ ),
286
+ ...part.referencedIds(doc)
287
+ ]);
288
+ return /* @__PURE__ */ new Map([
289
+ [
290
+ path,
291
+ encodeUtf8(
292
+ createdPart(part, session, part.prelude(taken) + appended),
293
+ false
294
+ )
295
+ ]
296
+ ]);
297
+ }
298
+ function storyEntriesPlanner(part) {
299
+ return {
300
+ name: part.name,
301
+ plan: (doc, session, context) => planEntries(part, doc, session, context)
302
+ };
303
+ }
304
+ export {
305
+ EVERY_STORY_CHANGE,
306
+ rewrittenStories,
307
+ storyChangesOf,
308
+ storyEntriesOf,
309
+ storyEntriesPlanner,
310
+ storyEntriesProblems,
311
+ storyEntriesWriting,
312
+ storyWriting,
313
+ unwrittenStoryChanges
314
+ };
@@ -2,9 +2,9 @@ import { Slice } from "prosemirror-model";
2
2
  import type { Transaction } from "prosemirror-state";
3
3
  import { type NewLists } from "../../numbering/listRegistry";
4
4
  import { type HtmlBlockReader } from "./blockReaders";
5
- import type { ListKinds } from "./internalChannel";
5
+ import { type ListKinds, type NoteStories } from "./internalChannel";
6
6
  import type { HtmlReadContext } from "./readContext";
7
- /** What a paste puts in: the content, and the definitions of the lists it started */
7
+ /** What a paste puts in: the content, the definitions of the lists it started, and its notes */
8
8
  export interface PastedContent {
9
9
  slice: Slice;
10
10
  newLists: NewLists;
@@ -14,13 +14,33 @@ export interface PastedContent {
14
14
  * which knows what kind each is from the markup it read them out of.
15
15
  */
16
16
  listKinds?: ListKinds;
17
+ /**
18
+ * What the notes this slice's references call said where it was copied (`./internalChannel`).
19
+ * Absent for a slice that arrived any other way, whose references this document drops.
20
+ */
21
+ noteStories?: NoteStories;
22
+ /**
23
+ * The notes the paste puts in, under the ids its references were given (`./normalizers`).
24
+ * Absent until the content has been normalized against the document it is going into.
25
+ */
26
+ newStories?: NoteStories;
17
27
  }
18
28
  /** The content one piece of already parsed markup reads as, or null when it reads as nothing */
19
29
  export declare function readHtml(root: ParentNode, context: HtmlReadContext, blockReaders?: readonly HtmlBlockReader[]): PastedContent | null;
20
30
  /** The same reading, for a caller holding the markup as a string rather than as elements */
21
31
  export declare function readHtmlSlice(context: HtmlReadContext, source: string): PastedContent | null;
22
32
  /**
23
- * The transaction a paste is put in with: the content, and the definitions of the lists it started
24
- * recorded on the document node so that the export writes them out and undo takes them back.
33
+ * The notes a paste brought, written under the ids its references were given.
34
+ *
35
+ * They go in the transaction the paste itself is in, so the export writes them out and one undo
36
+ * takes the text and its notes back together. A note whose reference did not land - a paste a
37
+ * table or a command turned into something else - would be an entry of the file nothing calls, so
38
+ * only the ones the document now refers to are written.
39
+ */
40
+ export declare function withPastedStories(tr: Transaction, stories: NoteStories): Transaction;
41
+ /**
42
+ * The transaction a paste is put in with: the content, the definitions of the lists it started
43
+ * recorded on the document node so that the export writes them out and undo takes them back, and
44
+ * the notes its references call.
25
45
  */
26
46
  export declare function withPastedContent(tr: Transaction, content: PastedContent): Transaction;