@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
@@ -9,6 +9,11 @@ import {
9
9
  resolveParagraph,
10
10
  resolveRun
11
11
  } from "../../docx/formatting.js";
12
+ import {
13
+ copiedNoteStory,
14
+ nextNoteId,
15
+ takenNoteIds
16
+ } from "../../docx/notes/newNote.js";
12
17
  import { withListNumbering } from "../../docx/paraProps.js";
13
18
  import {
14
19
  allocateList,
@@ -20,13 +25,21 @@ import {
20
25
  RANGE_MARKERS
21
26
  } from "../../ooxml/rangeMarkers.js";
22
27
  import { docxSchema } from "../../schema/index.js";
28
+ import {
29
+ EDITABLE_NOTE_KINDS,
30
+ storyKey
31
+ } from "../../schema/stories.js";
23
32
  import { listRefOf, numIdsIn } from "../commands/listCommands.js";
24
33
  import { documentFormatting } from "../documentStyles.js";
34
+ import { documentOf } from "../editorDocument.js";
25
35
  import {
26
36
  canStartNewList,
27
37
  documentNumbering
28
38
  } from "../plugins/numberingDecorations.js";
29
- import { NO_LIST_KINDS } from "./internalChannel.js";
39
+ import {
40
+ NO_LIST_KINDS,
41
+ NO_NOTE_STORIES
42
+ } from "./internalChannel.js";
30
43
  function xmlAttr(value) {
31
44
  return typeof value === "string" ? value : null;
32
45
  }
@@ -90,22 +103,25 @@ var rekeyNumbering = (slice, context) => {
90
103
  return props === null ? node : withAttrs(node, { ...node.attrs, pPr: props.pPr });
91
104
  });
92
105
  };
93
- var ANCHOR_NODES = /* @__PURE__ */ new Set([
94
- "commentStart",
95
- "commentEnd",
96
- "commentReference",
97
- "noteReference"
98
- ]);
106
+ var ANCHORS = {
107
+ commentStart: () => true,
108
+ commentEnd: () => true,
109
+ commentReference: () => true
110
+ };
99
111
  var REFERENCE_ELEMENTS = [
100
112
  "commentReference",
101
113
  "footnoteReference",
102
- "endnoteReference"
114
+ "endnoteReference",
115
+ "footnoteRef",
116
+ "endnoteRef"
103
117
  ];
118
+ var NOTE_OWN_MARKS = ["footnoteRef", "endnoteRef"];
104
119
  var ANCHOR_ELEMENTS = /* @__PURE__ */ new Set([
105
120
  ...RANGE_MARKERS,
106
121
  ...PERMISSION_MARKERS,
107
122
  ...COMMENT_RANGE_MARKERS,
108
- ...REFERENCE_ELEMENTS
123
+ ...REFERENCE_ELEMENTS,
124
+ ...NOTE_OWN_MARKS
109
125
  ]);
110
126
  var PRESERVED_INLINE = /* @__PURE__ */ new Set([
111
127
  "rawInline",
@@ -119,11 +135,26 @@ function preservedName(node) {
119
135
  return typeof element === "string" ? element : null;
120
136
  }
121
137
  var detachAnchors = (slice, { move }) => move ? slice : mapSliceNodes(slice, (node) => {
122
- if (ANCHOR_NODES.has(node.type.name)) return null;
138
+ if (ANCHORS[node.type.name]?.(node) === true) return null;
123
139
  if (!PRESERVED_INLINE.has(node.type.name)) return node;
124
140
  const name = preservedName(node);
125
141
  return name !== null && ANCHOR_ELEMENTS.has(name) ? null : node;
126
142
  });
143
+ var duplicateNotes = (slice, { move, noteStories, startNote }) => move ? slice : mapSliceNodes(slice, (node) => {
144
+ if (node.type !== docxSchema.nodes.noteReference) return node;
145
+ const kind = EDITABLE_NOTE_KINDS.find(
146
+ (candidate) => candidate === node.attrs.kind
147
+ );
148
+ const id = node.attrs.id;
149
+ if (kind === void 0 || typeof id !== "string") return null;
150
+ const story = noteStories.get(storyKey(kind, id));
151
+ if (story === void 0) return null;
152
+ return withAttrs(node, {
153
+ ...node.attrs,
154
+ id: startNote(kind, story),
155
+ referenceXml: null
156
+ });
157
+ });
127
158
  var rederiveDisplay = (slice, { state }) => {
128
159
  const formatting = documentFormatting(state);
129
160
  return mapSliceNodes(slice, (node) => {
@@ -152,6 +183,7 @@ var rederiveDisplay = (slice, { state }) => {
152
183
  var DEFAULT_NORMALIZERS = [
153
184
  dropSourceIdentity,
154
185
  detachAnchors,
186
+ duplicateNotes,
155
187
  rekeyNumbering,
156
188
  rederiveDisplay
157
189
  ];
@@ -164,11 +196,26 @@ function normalizePasted(content, state, move, normalizers = DEFAULT_NORMALIZERS
164
196
  ...content.newLists.keys()
165
197
  ]);
166
198
  const started = /* @__PURE__ */ new Map();
199
+ const document = documentOf(state);
200
+ const takenNotes = /* @__PURE__ */ new Map();
201
+ const startedNotes = /* @__PURE__ */ new Map();
167
202
  const context = {
168
203
  state,
169
204
  move,
170
205
  knownLists: known,
171
206
  listKinds: content.listKinds ?? NO_LIST_KINDS,
207
+ noteStories: content.noteStories ?? NO_NOTE_STORIES,
208
+ startNote(kind, story) {
209
+ const ids = takenNotes.get(kind) ?? takenNoteIds(state.doc, kind, document.reservedNoteKeys);
210
+ takenNotes.set(kind, ids);
211
+ const id = nextNoteId(ids);
212
+ ids.add(id);
213
+ startedNotes.set(
214
+ storyKey(kind, id),
215
+ copiedNoteStory(story, document.session)
216
+ );
217
+ return id;
218
+ },
172
219
  startList(kind) {
173
220
  if (!canStartNewList(state)) return null;
174
221
  const list = templateList(kind);
@@ -185,13 +232,15 @@ function normalizePasted(content, state, move, normalizers = DEFAULT_NORMALIZERS
185
232
  );
186
233
  return {
187
234
  slice,
188
- newLists: started.size === 0 ? content.newLists : new Map([...content.newLists, ...started])
235
+ newLists: started.size === 0 ? content.newLists : new Map([...content.newLists, ...started]),
236
+ newStories: startedNotes.size === 0 ? content.newStories : startedNotes
189
237
  };
190
238
  }
191
239
  export {
192
240
  DEFAULT_NORMALIZERS,
193
241
  detachAnchors,
194
242
  dropSourceIdentity,
243
+ duplicateNotes,
195
244
  mapSliceNodes,
196
245
  normalizePasted,
197
246
  rederiveDisplay,
@@ -13,7 +13,9 @@ import { documentOf } from "../editorDocument.js";
13
13
  import { insertPlainText } from "../plainText.js";
14
14
  import { moveCaretToDrop } from "../plugins/dropCaret.js";
15
15
  import { documentNumbering } from "../plugins/numberingDecorations.js";
16
+ import { withPastedStories } from "./htmlReader.js";
16
17
  import {
18
+ draggedNotes,
17
19
  INTERNAL_TOKEN_ATTRIBUTE,
18
20
  internalTokenOf,
19
21
  rememberCopied
@@ -78,6 +80,7 @@ function docxClipboard(options = {}) {
78
80
  }
79
81
  );
80
82
  let started = null;
83
+ let startedStories = null;
81
84
  let dropMove = false;
82
85
  let draggingOut = false;
83
86
  return new Plugin({
@@ -90,26 +93,33 @@ function docxClipboard(options = {}) {
90
93
  };
91
94
  },
92
95
  /**
93
- * The definitions of the lists a paste began, registered on the document once the paste is
94
- * in. A list the pasted markup started is numbered as it is read, and only the edit that
95
- * lands says whether that number is worn in the end.
96
+ * What a paste brought beside its text, registered on the document once the paste is in: the
97
+ * definitions of the lists it began and the notes its references call. A list the pasted
98
+ * markup started is numbered as it is read, and only the edit that lands says whether that
99
+ * number is worn in the end.
96
100
  */
97
101
  appendTransaction(transactions, _before, after) {
98
102
  const lists = started;
103
+ const stories = startedStories;
99
104
  started = null;
100
- if (lists === null || lists.size === 0 || !transactions.some((tr) => tr.docChanged)) {
101
- return null;
105
+ startedStories = null;
106
+ if (!transactions.some((tr2) => tr2.docChanged)) return null;
107
+ const tr = after.tr;
108
+ if (lists !== null && lists.size > 0) {
109
+ const worn = numIdsIn(after.doc);
110
+ const registered = newListsOf(after.doc.attrs[NEW_LISTS_ATTR]);
111
+ const missing = [...lists].filter(
112
+ ([numId]) => worn.has(numId) && !registered.has(numId)
113
+ );
114
+ if (missing.length > 0) {
115
+ tr.setDocAttribute(
116
+ NEW_LISTS_ATTR,
117
+ newListsValue(new Map([...registered, ...missing]))
118
+ );
119
+ }
102
120
  }
103
- const worn = numIdsIn(after.doc);
104
- const registered = newListsOf(after.doc.attrs[NEW_LISTS_ATTR]);
105
- const missing = [...lists].filter(
106
- ([numId]) => worn.has(numId) && !registered.has(numId)
107
- );
108
- if (missing.length === 0) return null;
109
- return after.tr.setDocAttribute(
110
- NEW_LISTS_ATTR,
111
- newListsValue(new Map([...registered, ...missing]))
112
- );
121
+ if (stories !== null) withPastedStories(tr, stories);
122
+ return tr.steps.length === 0 ? null : tr;
113
123
  },
114
124
  props: {
115
125
  handleDOMEvents: {
@@ -145,24 +155,34 @@ function docxClipboard(options = {}) {
145
155
  clipboardTextSerializer: clipboardText,
146
156
  transformCopied(slice, view) {
147
157
  const route = draggingOut ? "drag" : "clipboard";
148
- copyToken = rememberCopied(
149
- route,
150
- slice,
151
- documentOf(view.state).session?.sessionId ?? null,
152
- documentNumbering(view.state)
153
- );
158
+ const document = documentOf(view.state);
159
+ copyToken = rememberCopied(route, slice, {
160
+ sessionId: document.session?.sessionId ?? null,
161
+ doc: view.state.doc,
162
+ numbering: documentNumbering(view.state),
163
+ specialNotes: document.specialNotes
164
+ });
154
165
  return copiedSlice(slice);
155
166
  },
156
167
  transformPasted(slice, view, plain) {
157
168
  if (!plain) parser.setPlainText(false);
158
169
  const read = parser.takeRead();
159
170
  const content = normalizePasted(
160
- read ?? { slice, newLists: NO_NEW_LISTS },
171
+ // A drop is handed the dragged slice itself rather than markup, so what that slice
172
+ // carried is looked up by the route it left on rather than by a name written into markup
173
+ read ?? {
174
+ slice,
175
+ newLists: NO_NEW_LISTS,
176
+ noteStories: draggedNotes(
177
+ documentOf(view.state).session?.sessionId ?? null
178
+ )
179
+ },
161
180
  view.state,
162
181
  dropMove,
163
182
  normalizers
164
183
  );
165
184
  started = content.newLists;
185
+ startedStories = content.newStories ?? null;
166
186
  return content.slice;
167
187
  },
168
188
  handlePaste(view, event, slice) {
@@ -41,5 +41,8 @@ export declare function removeCommentReply(commentId: string, replyId: string):
41
41
  * document without standing for anything.
42
42
  */
43
43
  export declare function removeComment(id: string): Command;
44
- /** Selects the text anchored by a comment, or places the caret at a point comment. */
44
+ /**
45
+ * Selects the text a comment is anchored to and scrolls it into view. A comment marking no stretch
46
+ * - one written at a point, or one whose text was deleted - takes the caret to where it stands.
47
+ */
45
48
  export declare function selectComment(id: string): Command;
@@ -342,9 +342,7 @@ function selectComment(id) {
342
342
  const comment = commentById(state, id);
343
343
  if (!comment) return false;
344
344
  dispatch?.(
345
- state.tr.setSelection(
346
- TextSelection.create(state.doc, comment.from, comment.to)
347
- )
345
+ state.tr.setSelection(TextSelection.create(state.doc, comment.from, comment.to)).scrollIntoView()
348
346
  );
349
347
  return true;
350
348
  };
@@ -42,6 +42,13 @@ export interface DocumentComment {
42
42
  readonly from: number;
43
43
  readonly to: number;
44
44
  readonly referencePos: number;
45
+ /**
46
+ * Whether the comment still marks a stretch of the document: both range markers stand, and the
47
+ * opening one stands before the closing one. A comment that lost both markers with the text an
48
+ * edit deleted is detached - `from` and `to` are then both where its reference stands - and is
49
+ * drawn nowhere on the page.
50
+ */
51
+ readonly anchored: boolean;
45
52
  readonly resolved: boolean;
46
53
  readonly replies: readonly DocumentCommentReply[];
47
54
  }
@@ -90,6 +90,7 @@ export { activeLink, activeLinkSpan, canSetLink, removeLink, setLink, } from "./
90
90
  export { activeListKind, decreaseListLevel, increaseListLevel, isInList, toggleBulletList, toggleNumberedList, } from "./listCommands";
91
91
  export type { SelectionLock } from "./lockCommands";
92
92
  export { documentHasLocked, lockSelection, selectionLock, selectionTouchesLocked, unlockSelection, } from "./lockCommands";
93
+ export { canInsertEndnote, canInsertFootnote, insertEndnote, insertFootnote, openEndnote, openFootnote, setEndnoteBody, setFootnoteBody, } from "./noteCommands";
93
94
  export type { DocumentNote } from "./noteQueries";
94
95
  export { documentNotes } from "./noteQueries";
95
96
  export type { ActiveParagraphAlign, ActiveParagraphStyle, } from "./paragraphCommands";
@@ -81,6 +81,16 @@ import {
81
81
  selectionTouchesLocked,
82
82
  unlockSelection
83
83
  } from "./lockCommands.js";
84
+ import {
85
+ canInsertEndnote,
86
+ canInsertFootnote,
87
+ insertEndnote,
88
+ insertFootnote,
89
+ openEndnote,
90
+ openFootnote,
91
+ setEndnoteBody,
92
+ setFootnoteBody
93
+ } from "./noteCommands.js";
84
94
  import { documentNotes } from "./noteQueries.js";
85
95
  import {
86
96
  activeParagraphAlign,
@@ -117,6 +127,8 @@ export {
117
127
  canExport,
118
128
  canFormatText,
119
129
  canIncreaseIndent,
130
+ canInsertEndnote,
131
+ canInsertFootnote,
120
132
  canInsertImage,
121
133
  canInsertTable,
122
134
  canRunCommand,
@@ -139,6 +151,8 @@ export {
139
151
  imageFilesIn,
140
152
  increaseIndent,
141
153
  increaseListLevel,
154
+ insertEndnote,
155
+ insertFootnote,
142
156
  insertImage,
143
157
  insertImageFiles,
144
158
  insertLineBreak,
@@ -151,6 +165,8 @@ export {
151
165
  isStrikeActive,
152
166
  isUnderlineActive,
153
167
  lockSelection,
168
+ openEndnote,
169
+ openFootnote,
154
170
  readImageFile,
155
171
  redo,
156
172
  removeComment,
@@ -161,8 +177,10 @@ export {
161
177
  selectionTouchesLocked,
162
178
  setCommentBody,
163
179
  setCommentResolved,
180
+ setEndnoteBody,
164
181
  setFontFamily,
165
182
  setFontSize,
183
+ setFootnoteBody,
166
184
  setLineSpacing,
167
185
  setLink,
168
186
  setParagraphAlign,
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Puts a note into the document and writes what one says, exported once per kind the way
3
+ * `toggleBold` and `toggleItalic` are rather than as one command taking a kind. Deleting and
4
+ * copying a reference need no command: the edit that does either settles the note it calls
5
+ * (`editor/plugins/noteLifecycle`).
6
+ */
7
+ import type { Node as PMNode } from "prosemirror-model";
8
+ import type { Command, EditorState } from "prosemirror-state";
9
+ import type { NoteKind } from "../../schema/stories";
10
+ /** Writes what one note of this kind says; an editing view's edit reaches the document this way */
11
+ export declare function noteBodyCommand(kind: NoteKind, id: string, body: PMNode): Command;
12
+ /**
13
+ * Puts a footnote reference at the end of the selection, calling a new, empty footnote, and asks
14
+ * for that footnote to be opened. The footnote is written in the document's footnote styles where
15
+ * it defines them, and in superscript where it does not.
16
+ */
17
+ export declare const insertFootnote: Command;
18
+ /** Whether a footnote can go in at the end of the selection */
19
+ export declare function canInsertFootnote(state: EditorState): boolean;
20
+ /**
21
+ * Replaces what one footnote says with a `doc` node of `docxSchema`. It applies where an edit at
22
+ * the footnote's first reference would, and never to a separator, a footnote the text does not
23
+ * refer to, or a body saying what the footnote already says.
24
+ */
25
+ export declare function setFootnoteBody(id: string, body: PMNode): Command;
26
+ /**
27
+ * Puts the caret just after a footnote's reference and opens the footnote for editing, which is
28
+ * what a press on its number runs. It applies under every mode, since opening a note is reading
29
+ * it; a footnote opened where the body is shut takes no typing.
30
+ */
31
+ export declare function openFootnote(id: string): Command;
32
+ /**
33
+ * Puts an endnote reference at the end of the selection, calling a new, empty endnote, and asks
34
+ * for that endnote to be opened; `insertFootnote` for an endnote, which is drawn after the last
35
+ * paragraph of the document rather than at the foot of a page.
36
+ */
37
+ export declare const insertEndnote: Command;
38
+ /** Whether an endnote can go in at the end of the selection */
39
+ export declare function canInsertEndnote(state: EditorState): boolean;
40
+ /** Replaces what one endnote says with a `doc` node of `docxSchema`, as `setFootnoteBody` does */
41
+ export declare function setEndnoteBody(id: string, body: PMNode): Command;
42
+ /**
43
+ * Puts the caret just after an endnote's reference and opens the endnote for editing;
44
+ * `openFootnote` for an endnote, which stands at the end of the document, so this is also what
45
+ * takes a reader to it.
46
+ */
47
+ export declare function openEndnote(id: string): Command;
@@ -0,0 +1,95 @@
1
+ // src/editor/commands/noteCommands.ts
2
+ import {
3
+ newNoteStory,
4
+ nextNoteId,
5
+ noteNumberRunProps,
6
+ takenNoteIds
7
+ } from "../../docx/notes/newNote.js";
8
+ import { sameStory, setStory, storyKey, storyOf } from "../../docx/story.js";
9
+ import { docxSchema } from "../../schema/index.js";
10
+ import { guardedCommand } from "../../schema/guards.js";
11
+ import { isWrapperType } from "../../schema/wrappers.js";
12
+ import { documentOf } from "../editorDocument.js";
13
+ import {
14
+ noteReferenceAt,
15
+ openNoteCommand,
16
+ requestNote
17
+ } from "../plugins/noteNavigation.js";
18
+ function wrappersBefore(doc, pos) {
19
+ const $pos = doc.resolve(pos);
20
+ return ($pos.nodeBefore?.marks ?? $pos.marks()).filter(
21
+ (mark) => isWrapperType(mark.type)
22
+ );
23
+ }
24
+ function insertNoteTransaction(state, kind) {
25
+ const at = state.selection.to;
26
+ const $at = state.doc.resolve(at);
27
+ const type = docxSchema.nodes.noteReference;
28
+ if (!$at.parent.canReplaceWith($at.index(), $at.index(), type)) return null;
29
+ const { formatting, reservedNoteKeys } = documentOf(state);
30
+ const id = nextNoteId(takenNoteIds(state.doc, kind, reservedNoteKeys));
31
+ const reference = type.create({ kind, id }, null, [
32
+ docxSchema.marks.run.create({ rPr: noteNumberRunProps(kind, formatting) }),
33
+ ...wrappersBefore(state.doc, at)
34
+ ]);
35
+ const key = storyKey(kind, id);
36
+ return requestNote(
37
+ setStory(
38
+ state.tr.insert(at, reference),
39
+ key,
40
+ newNoteStory(kind, formatting)
41
+ ),
42
+ key
43
+ );
44
+ }
45
+ function insertNoteCommand(kind) {
46
+ return guardedCommand((state) => insertNoteTransaction(state, kind));
47
+ }
48
+ function noteBodyTransaction(state, kind, id, body) {
49
+ const key = storyKey(kind, id);
50
+ if (body.type !== docxSchema.nodes.doc) return null;
51
+ if (documentOf(state).specialNotes.has(key)) return null;
52
+ const found = noteReferenceAt(state.doc, kind, id);
53
+ if (found === null) return null;
54
+ if (sameStory(storyOf(state.doc, key), body)) return null;
55
+ return setStory(state.tr, key, body).setNodeMarkup(
56
+ found.pos,
57
+ null,
58
+ found.node.attrs,
59
+ found.node.marks
60
+ );
61
+ }
62
+ function noteBodyCommand(kind, id, body) {
63
+ return guardedCommand((state) => noteBodyTransaction(state, kind, id, body));
64
+ }
65
+ var insertFootnote = insertNoteCommand("footnote");
66
+ function canInsertFootnote(state) {
67
+ return insertFootnote(state);
68
+ }
69
+ function setFootnoteBody(id, body) {
70
+ return noteBodyCommand("footnote", id, body);
71
+ }
72
+ function openFootnote(id) {
73
+ return openNoteCommand("footnote", id);
74
+ }
75
+ var insertEndnote = insertNoteCommand("endnote");
76
+ function canInsertEndnote(state) {
77
+ return insertEndnote(state);
78
+ }
79
+ function setEndnoteBody(id, body) {
80
+ return noteBodyCommand("endnote", id, body);
81
+ }
82
+ function openEndnote(id) {
83
+ return openNoteCommand("endnote", id);
84
+ }
85
+ export {
86
+ canInsertEndnote,
87
+ canInsertFootnote,
88
+ insertEndnote,
89
+ insertFootnote,
90
+ noteBodyCommand,
91
+ openEndnote,
92
+ openFootnote,
93
+ setEndnoteBody,
94
+ setFootnoteBody
95
+ };
@@ -1,7 +1,9 @@
1
1
  /** Document-level readers for imported footnotes and endnotes. */
2
+ import type { Node as PMNode } from "prosemirror-model";
2
3
  import type { EditorState } from "prosemirror-state";
3
4
  import { DecorationSet } from "prosemirror-view";
4
- import type { NoteKind } from "../../docx/notes";
5
+ import { type StoryKey } from "../../docx/story";
6
+ import type { NoteKind } from "../../schema/stories";
5
7
  export interface DocumentNote {
6
8
  readonly kind: NoteKind;
7
9
  readonly id: string;
@@ -9,6 +11,17 @@ export interface DocumentNote {
9
11
  readonly text: string;
10
12
  readonly referencePos: number;
11
13
  }
14
+ /** One note the body refers to, as the notes drawn around the page draw it */
15
+ export interface NoteRow {
16
+ readonly key: StoryKey;
17
+ readonly kind: NoteKind;
18
+ readonly id: string;
19
+ readonly label: string;
20
+ /** The note's story, the same node for as long as it says the same thing (`schema/stories`) */
21
+ readonly story: PMNode;
22
+ /** Where the first reference to it stands, which is the place in the text it is called from */
23
+ readonly referencePos: number;
24
+ }
12
25
  interface NoteProjection {
13
26
  notes: readonly DocumentNote[];
14
27
  /**
@@ -17,11 +30,17 @@ interface NoteProjection {
17
30
  * says is a story on the document and not an attr of the reference.
18
31
  */
19
32
  tooltips: DecorationSet;
33
+ /** The footnotes whose story the document holds, by story key, in first-reference order */
34
+ footnotes: ReadonlyMap<StoryKey, NoteRow>;
35
+ /** The endnotes whose story the document holds, in first-reference order */
36
+ endnotes: readonly NoteRow[];
37
+ /** Every note of both kinds by story key, which the surface over the open note reads rows from */
38
+ rows: ReadonlyMap<StoryKey, NoteRow>;
20
39
  }
21
40
  /**
22
41
  * Which notes the document refers to is the document's to decide, so the list is worked out once
23
- * per edit (`editor/plugins/documentProjection`) rather than once per render of the panel under
24
- * the page.
42
+ * per edit (`editor/plugins/documentProjection`) rather than once per render of the notes drawn
43
+ * around the page.
25
44
  */
26
45
  export declare const noteProjection: import("../plugins/documentProjection").DocumentProjection<NoteProjection>;
27
46
  /** The distinct notes referenced by the main document story, in first-reference order. */
@@ -8,35 +8,43 @@ function stringAttr(value) {
8
8
  function noteKindOf(node) {
9
9
  return node.attrs.kind === "endnote" ? "endnote" : "footnote";
10
10
  }
11
- function noteText(doc, kind, id) {
12
- return storyText(storyOf(doc, storyKey(kind, id)));
13
- }
14
11
  function deriveNotes(doc) {
15
12
  const notes = [];
16
13
  const tooltips = [];
14
+ const footnotes = /* @__PURE__ */ new Map();
15
+ const endnotes = [];
16
+ const rows = /* @__PURE__ */ new Map();
17
17
  const seen = /* @__PURE__ */ new Set();
18
18
  doc.descendants((node, pos) => {
19
19
  if (node.type.name !== "noteReference") return true;
20
20
  const id = stringAttr(node.attrs.id);
21
21
  if (id === null) return true;
22
22
  const kind = noteKindOf(node);
23
- const text = noteText(doc, kind, id);
23
+ const key = storyKey(kind, id);
24
+ const story = storyOf(doc, key);
25
+ const text = storyText(story);
24
26
  if (text !== "") {
25
27
  tooltips.push(Decoration.node(pos, pos + node.nodeSize, { title: text }));
26
28
  }
27
- const key = `${kind}:${id}`;
28
29
  if (seen.has(key)) return true;
29
30
  seen.add(key);
30
- notes.push({
31
- kind,
32
- id,
33
- label: stringAttr(node.attrs.label) ?? "?",
34
- text,
35
- referencePos: pos
36
- });
31
+ const label = stringAttr(node.attrs.label) ?? "?";
32
+ notes.push({ kind, id, label, text, referencePos: pos });
33
+ if (story !== null) {
34
+ const row = { key, kind, id, label, story, referencePos: pos };
35
+ rows.set(key, row);
36
+ if (kind === "footnote") footnotes.set(key, row);
37
+ else endnotes.push(row);
38
+ }
37
39
  return true;
38
40
  });
39
- return { notes, tooltips: DecorationSet.create(doc, tooltips) };
41
+ return {
42
+ notes,
43
+ tooltips: DecorationSet.create(doc, tooltips),
44
+ footnotes,
45
+ endnotes,
46
+ rows
47
+ };
40
48
  }
41
49
  var noteProjection = documentProjection(
42
50
  "docxEditorNotes",