@stll/folio-core 0.53.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/dist/ai-edits/apply.js +390 -166
  2. package/dist/ai-edits/batch-claims.d.ts +21 -6
  3. package/dist/ai-edits/batch-claims.js +47 -16
  4. package/dist/ai-edits/headless.d.ts +20 -0
  5. package/dist/ai-edits/headless.js +140 -11
  6. package/dist/ai-edits/pending-suggestions.js +1 -1
  7. package/dist/ai-edits/read.d.ts +5 -3
  8. package/dist/ai-edits/read.js +97 -47
  9. package/dist/ai-edits/revisionStretches.d.ts +17 -0
  10. package/dist/ai-edits/revisionStretches.js +80 -0
  11. package/dist/ai-edits/snapshot.js +3 -2
  12. package/dist/ai-edits/table-cell-mutations.d.ts +3 -1
  13. package/dist/ai-edits/table-cell-mutations.js +2 -1
  14. package/dist/ai-edits/table-row-column-mutations.d.ts +6 -1
  15. package/dist/ai-edits/table-row-column-mutations.js +27 -10
  16. package/dist/compare/content-alignment.d.ts +5 -1
  17. package/dist/compare/content-alignment.js +39 -32
  18. package/dist/compare/inline-provenance.js +1 -1
  19. package/dist/controller/layoutPipeline.js +3 -2
  20. package/dist/docx/archiveInflation.d.ts +127 -0
  21. package/dist/docx/archiveInflation.js +181 -0
  22. package/dist/docx/metadataPrivacy.js +24 -1
  23. package/dist/docx/noteReferenceMark.d.ts +15 -0
  24. package/dist/docx/noteReferenceMark.js +48 -0
  25. package/dist/docx/paragraphParser.js +5 -3
  26. package/dist/docx/paragraphPropertySource.d.ts +1 -1
  27. package/dist/docx/paragraphPropertySource.js +3 -1
  28. package/dist/docx/parser.js +2 -2
  29. package/dist/docx/rezip.js +4 -2
  30. package/dist/docx/selectiveXmlPatch.d.ts +3 -1
  31. package/dist/docx/selectiveXmlPatch.js +81 -3
  32. package/dist/docx/server/boundedArchive.d.ts +8 -13
  33. package/dist/docx/server/boundedArchive.js +70 -59
  34. package/dist/docx/server/extractDocxText.d.ts +8 -2
  35. package/dist/docx/server/extractDocxText.js +2 -2
  36. package/dist/docx/server/validateDocxConformance.d.ts +1 -1
  37. package/dist/docx/server/validateDocxConformance.js +1 -0
  38. package/dist/docx/unzip.d.ts +19 -2
  39. package/dist/docx/unzip.js +135 -24
  40. package/dist/fonts/embeddedFonts.js +1 -1
  41. package/dist/i18n/messages/catalogs.gen.d.ts +34 -34
  42. package/dist/i18n/messages/catalogs.gen.js +34 -34
  43. package/dist/i18n/messages/messages.gen.d.ts +2 -2
  44. package/dist/internal/wholeStoryRevisionResolution.js +95 -25
  45. package/dist/layout-engine/measure/measureParagraph.js +2 -2
  46. package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +12 -1
  47. package/dist/layout-engine/measure/paragraphMeasureShared.js +14 -1
  48. package/dist/markdown/escape.d.ts +2 -2
  49. package/dist/markdown/escape.js +4 -5
  50. package/dist/markdown/renderBlock.js +91 -25
  51. package/dist/markdown/renderRuns.d.ts +10 -1
  52. package/dist/markdown/renderRuns.js +320 -59
  53. package/dist/prosemirror/clearRunColor.d.ts +6 -0
  54. package/dist/prosemirror/clearRunColor.js +46 -0
  55. package/dist/prosemirror/commands/comments.js +56 -21
  56. package/dist/prosemirror/commands/formatPainter.js +1 -1
  57. package/dist/prosemirror/commands/hyperlink.js +2 -1
  58. package/dist/prosemirror/commands/propertyChangeScope.d.ts +3 -2
  59. package/dist/prosemirror/commands/propertyChangeScope.js +24 -3
  60. package/dist/prosemirror/commands/resolveAllTableChanges.js +46 -41
  61. package/dist/prosemirror/commands/resolveParagraphProperties.js +20 -6
  62. package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +8 -1
  63. package/dist/prosemirror/commands/tableCellMergeResolution.js +23 -7
  64. package/dist/prosemirror/containerFinalParagraph.d.ts +11 -5
  65. package/dist/prosemirror/containerFinalParagraph.js +7 -6
  66. package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -13
  67. package/dist/prosemirror/conversion/fromProseDoc.js +12 -590
  68. package/dist/prosemirror/conversion/toProseDoc.d.ts +4 -11
  69. package/dist/prosemirror/conversion/toProseDoc.js +31 -150
  70. package/dist/prosemirror/documentSchema.d.ts +8 -0
  71. package/dist/prosemirror/documentSchema.js +18 -0
  72. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  73. package/dist/prosemirror/extensions/core/ParagraphExtension.js +104 -63
  74. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -1
  75. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +24 -3
  76. package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.d.ts +20 -0
  77. package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.js +43 -0
  78. package/dist/prosemirror/extensions/features/ListExtension.js +10 -9
  79. package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +1 -1
  80. package/dist/prosemirror/extensions/features/pastedHtmlLists.js +2 -4
  81. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +14 -8
  82. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +8 -4
  83. package/dist/prosemirror/extensions/marks/StrikeExtension.js +2 -2
  84. package/dist/prosemirror/extensions/marks/SubscriptExtension.js +2 -2
  85. package/dist/prosemirror/extensions/marks/SuperscriptExtension.js +2 -2
  86. package/dist/prosemirror/extensions/marks/TextColorExtension.js +4 -3
  87. package/dist/prosemirror/extensions/marks/markUtils.d.ts +8 -6
  88. package/dist/prosemirror/extensions/marks/markUtils.js +17 -8
  89. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +3 -1
  90. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +12 -1
  91. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +2 -2
  92. package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +5 -3
  93. package/dist/prosemirror/extensions/nodes/TableExtension.js +104 -210
  94. package/dist/prosemirror/extensions/types.d.ts +7 -0
  95. package/dist/prosemirror/hyperlinkRemoval.d.ts +9 -0
  96. package/dist/prosemirror/hyperlinkRemoval.js +129 -0
  97. package/dist/prosemirror/index.d.ts +2 -1
  98. package/dist/prosemirror/index.js +2 -1
  99. package/dist/prosemirror/listNumbering.d.ts +3 -21
  100. package/dist/prosemirror/listNumbering.js +10 -25
  101. package/dist/prosemirror/listRendering.d.ts +27 -0
  102. package/dist/prosemirror/listRendering.js +27 -0
  103. package/dist/prosemirror/markupViewNotes.d.ts +8 -0
  104. package/dist/prosemirror/markupViewNotes.js +49 -0
  105. package/dist/prosemirror/markupViewProjection.d.ts +3 -1
  106. package/dist/prosemirror/markupViewProjection.js +7 -2
  107. package/dist/prosemirror/noteReferenceReview.d.ts +77 -0
  108. package/dist/prosemirror/noteReferenceReview.js +332 -0
  109. package/dist/prosemirror/paragraphIndentation.js +5 -2
  110. package/dist/prosemirror/paragraphMarkJoin.d.ts +6 -4
  111. package/dist/prosemirror/paragraphMarkJoin.js +21 -19
  112. package/dist/prosemirror/paragraphPropertyCarry.d.ts +64 -0
  113. package/dist/prosemirror/paragraphPropertyCarry.js +158 -0
  114. package/dist/prosemirror/plugins/documentStyleState.d.ts +26 -0
  115. package/dist/prosemirror/plugins/documentStyleState.js +32 -0
  116. package/dist/prosemirror/plugins/documentStyles.d.ts +2 -21
  117. package/dist/prosemirror/plugins/documentStyles.js +66 -29
  118. package/dist/prosemirror/plugins/index.d.ts +2 -1
  119. package/dist/prosemirror/plugins/index.js +2 -1
  120. package/dist/prosemirror/plugins/paragraphStyleResolution.d.ts +15 -0
  121. package/dist/prosemirror/plugins/paragraphStyleResolution.js +230 -0
  122. package/dist/prosemirror/plugins/suggestionMode.d.ts +12 -2
  123. package/dist/prosemirror/plugins/suggestionMode.js +391 -28
  124. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +22 -2
  125. package/dist/prosemirror/rebaseParagraphRunFormatting.js +39 -13
  126. package/dist/prosemirror/rebaseParagraphRuns.d.ts +36 -0
  127. package/dist/prosemirror/rebaseParagraphRuns.js +99 -0
  128. package/dist/prosemirror/runFormattingFromMarks.d.ts +18 -0
  129. package/dist/prosemirror/runFormattingFromMarks.js +567 -0
  130. package/dist/prosemirror/runFormattingReconciliation.js +10 -9
  131. package/dist/prosemirror/schema/nodes.d.ts +2 -0
  132. package/dist/prosemirror/styles/paragraphStyleCascade.d.ts +59 -0
  133. package/dist/prosemirror/styles/paragraphStyleCascade.js +145 -0
  134. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +21 -2
  135. package/dist/prosemirror/styles/resolvedStyleAttrs.js +38 -1
  136. package/dist/prosemirror/styles/tableStyleRegions.d.ts +31 -0
  137. package/dist/prosemirror/styles/tableStyleRegions.js +55 -0
  138. package/dist/prosemirror/tableCellPaste.d.ts +74 -0
  139. package/dist/prosemirror/tableCellPaste.js +525 -0
  140. package/dist/prosemirror/tableGridMutation.d.ts +53 -14
  141. package/dist/prosemirror/tableGridMutation.js +166 -18
  142. package/dist/prosemirror/tableRunIn.d.ts +51 -0
  143. package/dist/prosemirror/tableRunIn.js +165 -0
  144. package/dist/prosemirror/textInput.js +11 -2
  145. package/dist/prosemirror/trackedRevisionPath.d.ts +17 -0
  146. package/dist/prosemirror/trackedRevisionPath.js +32 -0
  147. package/dist/server.d.ts +2 -2
  148. package/package.json +5 -5
@@ -0,0 +1,332 @@
1
+ import { withoutLostCommentThreads } from "../ai-edits/comment-lifecycle.js";
2
+ import { visitCommentMarkers } from "../docx/commentAnchorIndex.js";
3
+ import { isSeparatorEndnote, isSeparatorFootnote } from "../docx/footnoteParser.js";
4
+ import { pluginsForHeadlessRevisionResolution } from "../internal/headlessRevisionResolutionGuard.js";
5
+ import { expectFootnoteRefMarkAttrs, expectParagraphAttrs } from "./attrs/index.js";
6
+ import { rejectAIEditRevision } from "./commands/comments.js";
7
+ import { proseDocToBlocks } from "./conversion/fromProseDoc.js";
8
+ import { footnoteToProseDoc } from "./conversion/toProseDoc.js";
9
+ import { createDocumentNumberingPlugin } from "./plugins/documentNumbering.js";
10
+ import { createDocumentStylesPlugin } from "./plugins/documentStyles.js";
11
+ import { mintRevisionId } from "./plugins/revisionIds.js";
12
+ import { schema, singletonManager } from "./schema/index.js";
13
+ import { Transform } from "prosemirror-transform";
14
+ import { EditorState } from "prosemirror-state";
15
+ //#region src/prosemirror/noteReferenceReview.ts
16
+ const noteKey = (kind, id) => `${kind}:${id}`;
17
+ /**
18
+ * The notes a story's text refers to, and whether each reference is pending
19
+ * deletion: a reference is deleted when every character of it is.
20
+ */
21
+ const noteReferenceStates = (doc) => {
22
+ const states = /* @__PURE__ */ new Map();
23
+ doc.descendants((node) => {
24
+ if (!node.isText) return true;
25
+ const reference = node.marks.find((mark) => mark.type.name === "footnoteRef");
26
+ if (!reference) return false;
27
+ const { id, noteType } = expectFootnoteRefMarkAttrs(reference);
28
+ const key = noteKey(noteType === "endnote" ? "endnote" : "footnote", Number(id));
29
+ if (!node.marks.some((mark) => mark.type.name === "deletion") || states.get(key) === "live") states.set(key, "live");
30
+ else states.set(key, "deleted");
31
+ return false;
32
+ });
33
+ return states;
34
+ };
35
+ /** The notes whose reference a change took from pending deletion back to live. */
36
+ const noteReferencesRestored = (before, after) => {
37
+ const was = noteReferenceStates(before);
38
+ return [...noteReferenceStates(after)].flatMap(([key, state]) => state === "live" && was.get(key) === "deleted" ? [key] : []);
39
+ };
40
+ /**
41
+ * The revisions that deleted a note along with its reference: its text and
42
+ * its paragraph marks, deleted by the reference deletion's author at its
43
+ * date. A deletion in the note made some other time is a change of its own,
44
+ * which resolving the reference leaves alone.
45
+ */
46
+ const noteDeletionRevisions = (doc, reference) => {
47
+ const author = String(reference.attrs["author"] ?? "");
48
+ const date = String(reference.attrs["date"] ?? "");
49
+ const ids = /* @__PURE__ */ new Set();
50
+ doc.descendants((node) => {
51
+ if (node.type.name === "paragraph") {
52
+ const mark = expectParagraphAttrs(node).pPrMark;
53
+ if (mark?.kind === "del" && mark.info.author === author && mark.info.date === date) ids.add(mark.info.id);
54
+ }
55
+ for (const mark of node.marks) if (mark.type.name === "deletion" && String(mark.attrs["author"] ?? "") === author && String(mark.attrs["date"] ?? "") === date) {
56
+ const id = mark.attrs["revisionId"];
57
+ if (typeof id === "number") ids.add(id);
58
+ }
59
+ return true;
60
+ });
61
+ return [...ids];
62
+ };
63
+ const restoredNote = (note, document, reference) => {
64
+ const { styles, theme, numbering } = document.package;
65
+ const doc = footnoteToProseDoc(note.content, {
66
+ ...styles !== void 0 && { styles },
67
+ ...theme !== void 0 && { theme }
68
+ });
69
+ const revisions = noteDeletionRevisions(doc, reference);
70
+ if (revisions.length === 0) return note;
71
+ let state = EditorState.create({
72
+ schema,
73
+ doc,
74
+ plugins: [
75
+ ...pluginsForHeadlessRevisionResolution(singletonManager.getPlugins()),
76
+ createDocumentStylesPlugin(styles),
77
+ createDocumentNumberingPlugin(numbering)
78
+ ]
79
+ });
80
+ rejectAIEditRevision(revisions)(state, (transaction) => {
81
+ state = state.apply(transaction);
82
+ });
83
+ if (state.doc.eq(doc)) return note;
84
+ return {
85
+ ...note,
86
+ content: proseDocToBlocks(state.doc, note.content, styles)
87
+ };
88
+ };
89
+ /**
90
+ * The document with the named notes' deletions that went with their
91
+ * reference rejected: the notes whose reference's deletion was rejected get
92
+ * their text back. `before` is the body that still had those deletions.
93
+ */
94
+ const restoreNotes = (document, keys, before) => {
95
+ const deletions = referenceDeletions(before);
96
+ const wanted = new Set([...keys].filter((key) => deletions.has(key)));
97
+ if (wanted.size === 0) return document;
98
+ const restore = (kind, notes) => {
99
+ if (!notes?.some((note) => wanted.has(noteKey(kind, note.id)))) return notes;
100
+ return notes.map((note) => {
101
+ const reference = deletions.get(noteKey(kind, note.id));
102
+ return reference ? restoredNote(note, document, reference) : note;
103
+ });
104
+ };
105
+ const footnotes = restore("footnote", document.package.footnotes);
106
+ const endnotes = restore("endnote", document.package.endnotes);
107
+ if (footnotes === document.package.footnotes && endnotes === document.package.endnotes) return document;
108
+ return {
109
+ ...document,
110
+ package: {
111
+ ...document.package,
112
+ ...footnotes !== void 0 && { footnotes },
113
+ ...endnotes !== void 0 && { endnotes }
114
+ }
115
+ };
116
+ };
117
+ /** The deletion each pending-deleted note reference carries. */
118
+ const referenceDeletions = (doc) => {
119
+ const deletions = /* @__PURE__ */ new Map();
120
+ doc.descendants((node) => {
121
+ if (!node.isText) return true;
122
+ const reference = node.marks.find((mark) => mark.type.name === "footnoteRef");
123
+ const deletion = node.marks.find((mark) => mark.type.name === "deletion");
124
+ if (reference && deletion) {
125
+ const { id, noteType } = expectFootnoteRefMarkAttrs(reference);
126
+ deletions.set(noteKey(noteType === "endnote" ? "endnote" : "footnote", Number(id)), deletion);
127
+ }
128
+ return false;
129
+ });
130
+ return deletions;
131
+ };
132
+ /**
133
+ * Delete a note's text and its paragraph marks along with its reference: by
134
+ * the reference deletion's author at its date, so resolving the reference
135
+ * resolves them too (see {@link noteDeletionRevisions}).
136
+ */
137
+ const deleteNoteWithReference = (transform, reference) => {
138
+ const doc = transform.doc;
139
+ const markInfo = {
140
+ id: mintRevisionId(),
141
+ author: String(reference.attrs["author"] ?? ""),
142
+ date: String(reference.attrs["date"] ?? "")
143
+ };
144
+ const { utcDate, initials } = reference.attrs;
145
+ const deletion = (doc.type.schema.marks["deletion"] ?? reference.type).create({
146
+ revisionId: mintRevisionId(),
147
+ author: markInfo.author,
148
+ date: markInfo.date,
149
+ ...utcDate != null && { utcDate },
150
+ ...initials != null && { initials }
151
+ });
152
+ doc.descendants((node, position) => {
153
+ if (node.type.name === "paragraph" && expectParagraphAttrs(node).pPrMark == null) transform.setNodeAttribute(position, "pPrMark", {
154
+ kind: "del",
155
+ info: markInfo
156
+ });
157
+ if (node.isInline && !deletion.type.isInSet(node.marks)) transform.addMark(position, position + node.nodeSize, deletion);
158
+ return !node.isInline;
159
+ });
160
+ return transform;
161
+ };
162
+ /**
163
+ * A note whose reference was deleted: its text and its paragraph marks are
164
+ * deleted too, by the reference's author at the reference's date.
165
+ */
166
+ const deletedNote = (note, document, reference) => {
167
+ const { styles, theme } = document.package;
168
+ const doc = footnoteToProseDoc(note.content, {
169
+ ...styles !== void 0 && { styles },
170
+ ...theme !== void 0 && { theme }
171
+ });
172
+ const transform = deleteNoteWithReference(new Transform(doc), reference);
173
+ if (!transform.docChanged) return note;
174
+ return {
175
+ ...note,
176
+ content: proseDocToBlocks(transform.doc, note.content, styles)
177
+ };
178
+ };
179
+ const editNotes = (document, edits) => {
180
+ if (edits.size === 0) return document;
181
+ const edit = (kind, notes) => {
182
+ if (!notes?.some((note) => edits.has(noteKey(kind, note.id)))) return notes;
183
+ return notes.map((note) => edits.get(noteKey(kind, note.id))?.(note) ?? note);
184
+ };
185
+ const footnotes = edit("footnote", document.package.footnotes);
186
+ const endnotes = edit("endnote", document.package.endnotes);
187
+ if (footnotes === document.package.footnotes && endnotes === document.package.endnotes) return document;
188
+ return {
189
+ ...document,
190
+ package: {
191
+ ...document.package,
192
+ ...footnotes !== void 0 && { footnotes },
193
+ ...endnotes !== void 0 && { endnotes }
194
+ }
195
+ };
196
+ };
197
+ const createNoteReferenceFollower = () => {
198
+ let base = null;
199
+ const replaced = /* @__PURE__ */ new Map();
200
+ const pending = /* @__PURE__ */ new Map();
201
+ const recorded = (key, edit) => (note) => {
202
+ const result = edit(note);
203
+ if (result !== note) pending.set(key, {
204
+ from: note,
205
+ to: result
206
+ });
207
+ return result;
208
+ };
209
+ return {
210
+ noteBase: (before) => {
211
+ base ??= before;
212
+ },
213
+ reconcile: (document, body) => {
214
+ const before = base;
215
+ base = null;
216
+ if (!before) return document;
217
+ const was = noteReferenceStates(before);
218
+ const now = noteReferenceStates(body);
219
+ const deletions = referenceDeletions(body);
220
+ const rejected = referenceDeletions(before);
221
+ const edits = /* @__PURE__ */ new Map();
222
+ for (const [key, state] of now) {
223
+ const previous = was.get(key);
224
+ const rejectedDeletion = rejected.get(key);
225
+ if (state === "live" && previous === "deleted" && rejectedDeletion) edits.set(key, recorded(key, (note) => {
226
+ const restored = restoredNote(note, document, rejectedDeletion);
227
+ if (restored !== note) replaced.set(key, {
228
+ restored,
229
+ previous: note
230
+ });
231
+ return restored;
232
+ }));
233
+ const reference = deletions.get(key);
234
+ if (state === "deleted" && previous === "live" && reference) edits.set(key, recorded(key, (note) => {
235
+ const earlier = replaced.get(key);
236
+ replaced.delete(key);
237
+ return earlier?.restored === note ? earlier.previous : deletedNote(note, document, reference);
238
+ }));
239
+ }
240
+ return editNotes(document, edits);
241
+ },
242
+ withPending: (document) => {
243
+ const edits = /* @__PURE__ */ new Map();
244
+ for (const [key, { from, to }] of pending) edits.set(key, (note) => {
245
+ if (note !== from) pending.delete(key);
246
+ return note === from ? to : note;
247
+ });
248
+ return editNotes(document, edits);
249
+ },
250
+ reset: () => {
251
+ base = null;
252
+ replaced.clear();
253
+ pending.clear();
254
+ }
255
+ };
256
+ };
257
+ /** The notes model content refers to, live or pending deletion. */
258
+ const noteKeysReferencedIn = (content) => {
259
+ const keys = /* @__PURE__ */ new Set();
260
+ const visit = (node) => {
261
+ if (Array.isArray(node)) {
262
+ for (const item of node) visit(item);
263
+ return;
264
+ }
265
+ if (node === null || typeof node !== "object") return;
266
+ const { type, id } = node;
267
+ if ((type === "footnoteRef" || type === "endnoteRef") && typeof id === "number") keys.add(noteKey(type === "footnoteRef" ? "footnote" : "endnote", id));
268
+ for (const value of Object.values(node)) visit(value);
269
+ };
270
+ visit(content);
271
+ return keys;
272
+ };
273
+ /**
274
+ * The notes anything refers to, live or pending deletion: the main story, and
275
+ * the stories the body does not hold (headers, footers, comments, notes).
276
+ * References inside the `skipped` notes do not count: those notes are going.
277
+ */
278
+ const referencedNotes = (document, skipped = /* @__PURE__ */ new Set()) => {
279
+ const { headers, footers, footnotes, endnotes } = document.package;
280
+ const kept = (kind) => (note) => !skipped.has(noteKey(kind, note.id));
281
+ return noteKeysReferencedIn([
282
+ document.package.document.content,
283
+ [...headers?.values() ?? [], ...footers?.values() ?? []],
284
+ document.package.document.comments ?? [],
285
+ (footnotes ?? []).filter(kept("footnote")),
286
+ (endnotes ?? []).filter(kept("endnote"))
287
+ ]);
288
+ };
289
+ /** Ids of the comments anchored anywhere in these stories. */
290
+ const anchoredComments = (stories) => {
291
+ const ids = /* @__PURE__ */ new Set();
292
+ for (const blocks of stories) visitCommentMarkers(blocks, ({ item }) => ids.add(item.id));
293
+ return ids;
294
+ };
295
+ /**
296
+ * The document without the notes nothing refers to any more: a note goes
297
+ * with its reference, so accepting the reference's deletion leaves a note no
298
+ * reader can reach, and saving drops it, with the comments anchored only in
299
+ * it. Separators stay.
300
+ */
301
+ const withoutUnreferencedNotes = (document) => {
302
+ const referenced = referencedNotes(document);
303
+ const { footnotes, endnotes, headers, footers } = document.package;
304
+ const keeps = (kind) => (note) => (kind === "footnote" ? isSeparatorFootnote(note) : isSeparatorEndnote(note)) || referenced.has(noteKey(kind, note.id));
305
+ const keptFootnotes = footnotes?.filter(keeps("footnote"));
306
+ const keptEndnotes = endnotes?.filter(keeps("endnote"));
307
+ if (keptFootnotes?.length === footnotes?.length && keptEndnotes?.length === endnotes?.length) return document;
308
+ const removed = [...(footnotes ?? []).filter((note) => !keeps("footnote")(note)), ...(endnotes ?? []).filter((note) => !keeps("endnote")(note))];
309
+ const comments = document.package.document.comments;
310
+ const keptComments = comments ? withoutLostCommentThreads(comments, {
311
+ anchoredBefore: anchoredComments(removed.map((note) => note.content)),
312
+ anchoredNow: anchoredComments([
313
+ document.package.document.content,
314
+ ...[...headers?.values() ?? [], ...footers?.values() ?? []].map((part) => part.content),
315
+ ...[...keptFootnotes ?? [], ...keptEndnotes ?? []].map((note) => note.content)
316
+ ])
317
+ }) : void 0;
318
+ return {
319
+ ...document,
320
+ package: {
321
+ ...document.package,
322
+ ...keptFootnotes !== void 0 && { footnotes: keptFootnotes },
323
+ ...keptEndnotes !== void 0 && { endnotes: keptEndnotes },
324
+ ...keptComments !== void 0 && keptComments.length !== comments?.length && { document: {
325
+ ...document.package.document,
326
+ comments: keptComments
327
+ } }
328
+ }
329
+ };
330
+ };
331
+ //#endregion
332
+ export { createNoteReferenceFollower, deleteNoteWithReference, noteDeletionRevisions, noteKeysReferencedIn, noteReferenceStates, noteReferencesRestored, referenceDeletions, referencedNotes, restoreNotes, withoutUnreferencedNotes };
@@ -1,3 +1,4 @@
1
+ import { paragraphNumberingReferenceId } from "../docx/numberingReference.js";
1
2
  import { panic } from "better-result";
2
3
  //#region src/prosemirror/paragraphIndentation.ts
3
4
  const DIRECT_PARAGRAPH_INDENTATION_KEYS = [
@@ -49,12 +50,14 @@ const PARAGRAPH_INDENTATION_VALUE_KEYS = [
49
50
  const directParagraphIndentation = (attrs) => {
50
51
  const original = attrs._originalFormatting ?? void 0;
51
52
  const resolved = attrs._resolvedFormatting;
53
+ const numbered = paragraphNumberingReferenceId(attrs.numPr ?? void 0) !== void 0;
52
54
  const indentation = {};
53
55
  for (const key of PARAGRAPH_INDENTATION_VALUE_KEYS) {
54
56
  const value = attrs[key];
55
57
  if (typeof value !== "number") continue;
56
58
  const stated = original?.[key] !== void 0;
57
- const inherited = resolved?.[key] === value || original === void 0 && value === 0;
59
+ const zeroIsDefault = key === "indentRight" || !numbered;
60
+ const inherited = resolved?.[key] === value || original === void 0 && value === 0 && zeroIsDefault;
58
61
  if (stated || !inherited) copyIndentationValue({
59
62
  target: indentation,
60
63
  key,
@@ -65,7 +68,7 @@ const directParagraphIndentation = (attrs) => {
65
68
  const hanging = attrs.hangingIndent === true;
66
69
  indentation.hangingIndent = hanging;
67
70
  if (hanging) indentation.indentFirstLine = -Math.abs(indentation.indentFirstLine);
68
- } else if (original?.hangingIndent !== void 0 && typeof attrs.hangingIndent === "boolean") indentation.hangingIndent = attrs.hangingIndent;
71
+ } else if (original?.hangingIndent !== void 0 && original.indentFirstLine === void 0 && typeof attrs.hangingIndent === "boolean") indentation.hangingIndent = attrs.hangingIndent;
69
72
  return Object.keys(indentation).length > 0 ? indentation : void 0;
70
73
  };
71
74
  const paragraphIndentationEqual = (left, right) => DIRECT_PARAGRAPH_INDENTATION_KEYS.every((key) => left?.[key] === right?.[key]);
@@ -1,3 +1,4 @@
1
+ import { RunStyleResolver } from "./runStyleFormatting.js";
1
2
  import { Node } from "prosemirror-model";
2
3
  import { Transaction } from "prosemirror-state";
3
4
  //#region src/prosemirror/paragraphMarkJoin.d.ts
@@ -9,11 +10,12 @@ type JoinAtParagraphMarkOptions = {
9
10
  /** The paragraph directly after it, which the join runs into. */
10
11
  next: Node;
11
12
  /**
12
- * The first paragraph's content is going too (a deleted paragraph), so
13
- * only the join survives of it, as when it is already empty.
13
+ * Re-reads the moved runs' inherited formatting in the next paragraph's
14
+ * style. Without one, the editor's join restyler does it when the
15
+ * transaction is dispatched.
14
16
  */
15
- firstIsGoing?: boolean;
17
+ styleResolver: RunStyleResolver | null;
16
18
  };
17
- declare const joinAtParagraphMark: ({ tr, paragraphPos, paragraph, next, firstIsGoing }: JoinAtParagraphMarkOptions) => void;
19
+ declare const joinAtParagraphMark: ({ tr, paragraphPos, paragraph, next, styleResolver }: JoinAtParagraphMarkOptions) => void;
18
20
  //#endregion
19
21
  export { joinAtParagraphMark };
@@ -1,24 +1,26 @@
1
1
  import { joinProseParagraphsWithRightPropertySource } from "../docx/paragraphPropertySource.js";
2
- import { holdsNoContent } from "./zeroWidthAnchors.js";
2
+ import { JOINED_RUNS_RESTYLED_META } from "./extensions/features/JoinedRunStyleExtension.js";
3
+ import { rebaseParagraphRuns } from "./rebaseParagraphRunFormatting.js";
4
+ import { paragraphRunStyleContext } from "./runStyleFormatting.js";
3
5
  //#region src/prosemirror/paragraphMarkJoin.ts
4
- const joinAtParagraphMark = ({ tr, paragraphPos, paragraph, next, firstIsGoing = false }) => {
5
- const joinPos = paragraphPos + paragraph.nodeSize;
6
- const emptyFirstParagraph = firstIsGoing || holdsNoContent(paragraph);
7
- const joinedAttrs = {
8
- ...(emptyFirstParagraph ? next : paragraph).attrs,
9
- pPrMark: next.attrs["pPrMark"],
10
- _sectionProperties: next.attrs["_sectionProperties"]
11
- };
12
- if (emptyFirstParagraph) {
13
- joinProseParagraphsWithRightPropertySource({
14
- attrs: joinedAttrs,
15
- pos: joinPos,
16
- transaction: tr
17
- });
18
- return;
19
- }
20
- tr.join(joinPos);
21
- tr.setNodeMarkup(paragraphPos, void 0, joinedAttrs);
6
+ const joinAtParagraphMark = ({ tr, paragraphPos, paragraph, next, styleResolver }) => {
7
+ joinProseParagraphsWithRightPropertySource({
8
+ attrs: next.attrs,
9
+ pos: paragraphPos + paragraph.nodeSize,
10
+ transaction: tr
11
+ });
12
+ if (!styleResolver) return;
13
+ if (paragraph.content.size > 0) rebaseParagraphRuns({
14
+ previousContext: paragraphRunStyleContext(paragraph, styleResolver),
15
+ paragraphPosition: paragraphPos,
16
+ range: {
17
+ from: 0,
18
+ to: paragraph.content.size
19
+ },
20
+ styleResolver,
21
+ tr
22
+ });
23
+ tr.setMeta(JOINED_RUNS_RESTYLED_META, true);
22
24
  };
23
25
  //#endregion
24
26
  export { joinAtParagraphMark };
@@ -0,0 +1,64 @@
1
+ import { ParagraphPropertyChangeAttrs } from "./schema/nodes.js";
2
+ import { RunStyleResolver } from "./runStyleFormatting.js";
3
+ import { NumberingMap } from "../docx/numberingParser.js";
4
+ import { Node } from "prosemirror-model";
5
+ import { Transaction } from "prosemirror-state";
6
+ //#region src/prosemirror/paragraphPropertyCarry.d.ts
7
+ type CarryParagraphPropertiesOptions = {
8
+ tr: Transaction;
9
+ /** The paragraph that takes the properties. */
10
+ position: number;
11
+ /** The paragraph whose properties it takes. */
12
+ source: Node;
13
+ styleResolver: RunStyleResolver | null;
14
+ numbering: NumberingMap | null;
15
+ /** Records the change as a tracked property change; applied directly when omitted. */
16
+ revision?: ParagraphPropertyChangeAttrs["info"];
17
+ /** Properties the paragraph keeps its own values of: ones its own batch set. */
18
+ keep?: ReadonlySet<string>;
19
+ };
20
+ /**
21
+ * Give a paragraph another one's properties: the pPr a property change
22
+ * covers. Its identity, mark, mark run properties and section stay its own,
23
+ * and its runs are re-read in the new style. `tracked` says whether a property
24
+ * change now records what it had: a paragraph holds one, and a pending one
25
+ * already records it.
26
+ */
27
+ declare const carryParagraphProperties: ({ tr, position, source, styleResolver, numbering, revision, keep }: CarryParagraphPropertiesOptions) => {
28
+ tr: Transaction;
29
+ changed: boolean;
30
+ tracked: boolean;
31
+ };
32
+ type ParagraphLeftAfterOptions = {
33
+ doc: Node;
34
+ paragraphPos: number;
35
+ /** Breaks a batch's deferred final-paragraph deletion will remove. */
36
+ removedBreakPositions?: ReadonlySet<number>;
37
+ };
38
+ /**
39
+ * The paragraph left once the break of the paragraph at `paragraphPos` goes:
40
+ * the next one, or, when its own break is pending deletion too, the first
41
+ * paragraph after it whose break stays. Null when no paragraph follows.
42
+ */
43
+ declare const paragraphLeftAfter: ({ doc, paragraphPos, removedBreakPositions }: ParagraphLeftAfterOptions) => number | null;
44
+ /**
45
+ * A paragraph now holds the break another one had (a split, or a paste into
46
+ * it, leaves that break on the last part) but reads with other properties:
47
+ * record the other's as its property change. Rejecting the inserted breaks
48
+ * leaves this paragraph, which then reads as the one that was there. A
49
+ * pending property change on the other one travels as it is. Returns whether
50
+ * a change under `revision` was written.
51
+ */
52
+ declare const recordReplacedParagraphProperties: ({ tr, position, replaced, revision }: {
53
+ tr: Transaction;
54
+ position: number;
55
+ replaced: Node;
56
+ revision: ParagraphPropertyChangeAttrs["info"];
57
+ }) => boolean;
58
+ /**
59
+ * The properties a batch's own property change set on a paragraph: the ones
60
+ * whose value differs from what that change records as before.
61
+ */
62
+ declare const propertiesSetInBatch: (node: Node, batchRevisionIds: ReadonlySet<number>) => ReadonlySet<string>;
63
+ //#endregion
64
+ export { carryParagraphProperties, paragraphLeftAfter, propertiesSetInBatch, recordReplacedParagraphProperties };
@@ -0,0 +1,158 @@
1
+ import { expectParagraphAttrs } from "./attrs/index.js";
2
+ import { hasSerializableParagraphPropertyChange, paragraphPropertiesSnapshot } from "./commands/propertyChangeScope.js";
3
+ import { resolveParagraphChangeAttrs } from "./commands/resolveParagraphProperties.js";
4
+ import { rebaseParagraphRuns } from "./rebaseParagraphRunFormatting.js";
5
+ import { paragraphRunStyleContext } from "./runStyleFormatting.js";
6
+ import { panic } from "better-result";
7
+ //#region src/prosemirror/paragraphPropertyCarry.ts
8
+ /**
9
+ * Handing one paragraph's properties to another, as a removed paragraph mark
10
+ * requires.
11
+ *
12
+ * A paragraph's properties live on its mark, and a removed mark leaves the
13
+ * paragraph after it: that is the paragraph left once the change is accepted.
14
+ * An edit that should read as the FIRST paragraph (a merge, a Backspace at a
15
+ * paragraph's start, a deletion across paragraphs) therefore gives the second
16
+ * the first's properties, recorded as a property change under the same
17
+ * revision, so accepting reads as the direct edit and rejecting restores the
18
+ * second's own.
19
+ */
20
+ /**
21
+ * Give a paragraph another one's properties: the pPr a property change
22
+ * covers. Its identity, mark, mark run properties and section stay its own,
23
+ * and its runs are re-read in the new style. `tracked` says whether a property
24
+ * change now records what it had: a paragraph holds one, and a pending one
25
+ * already records it.
26
+ */
27
+ const carryParagraphProperties = ({ tr, position, source, styleResolver, numbering, revision, keep }) => {
28
+ const target = tr.doc.nodeAt(position);
29
+ if (!target || target.type !== source.type) return {
30
+ tr,
31
+ changed: false,
32
+ tracked: false
33
+ };
34
+ const previousFormatting = paragraphPropertiesSnapshot(target);
35
+ const own = previousFormatting;
36
+ const formatting = Object.fromEntries([...Object.entries(paragraphPropertiesSnapshot(source)).filter(([key]) => !keep?.has(key)), ...Object.entries(own).filter(([key]) => keep?.has(key))]);
37
+ if (JSON.stringify(previousFormatting) === JSON.stringify(formatting)) return {
38
+ tr,
39
+ changed: false,
40
+ tracked: false
41
+ };
42
+ const existing = expectParagraphAttrs(target)._propertyChanges;
43
+ const tracked = revision !== void 0 && !hasSerializableParagraphPropertyChange(existing);
44
+ const carried = resolveParagraphChangeAttrs({
45
+ node: target.type.create({
46
+ ...target.attrs,
47
+ _propertyChanges: [{
48
+ type: "paragraphPropertyChange",
49
+ info: {
50
+ id: -1,
51
+ author: "",
52
+ date: "1970-01-01T00:00:00Z"
53
+ },
54
+ previousFormatting: formatting
55
+ }]
56
+ }, target.content, target.marks),
57
+ mode: "reject",
58
+ boundaryCovered: true,
59
+ revisionSet: null,
60
+ styleResolver,
61
+ numbering
62
+ });
63
+ if (!carried) return panic("Paragraph properties did not pass to the paragraph", { position });
64
+ const changes = [...Array.isArray(existing) ? existing : [], ...tracked && revision ? [{
65
+ type: "paragraphPropertyChange",
66
+ info: revision,
67
+ previousFormatting
68
+ }] : []];
69
+ tr.setNodeMarkup(position, void 0, {
70
+ ...carried,
71
+ ...carriedNumberingProvenance(source, target, keep),
72
+ _sectionProperties: target.attrs["_sectionProperties"],
73
+ _propertyChanges: changes.length > 0 ? changes : null
74
+ });
75
+ if (styleResolver) rebaseParagraphRuns({
76
+ previousContext: paragraphRunStyleContext(target, styleResolver),
77
+ paragraphPosition: position,
78
+ styleResolver,
79
+ tr
80
+ });
81
+ return {
82
+ tr,
83
+ changed: true,
84
+ tracked
85
+ };
86
+ };
87
+ /**
88
+ * Whether the carried numbering is the paragraph's own or its style's: the
89
+ * record holds `numPr` as the paragraph reads it, and a style's numbering
90
+ * stays the style's rather than becoming the paragraph's own `w:numPr`.
91
+ */
92
+ const carriedNumberingProvenance = (source, target, keep) => ({ numPrFromStyle: expectParagraphAttrs(keep?.has("numPr") ? target : source).numPrFromStyle });
93
+ /**
94
+ * The paragraph left once the break of the paragraph at `paragraphPos` goes:
95
+ * the next one, or, when its own break is pending deletion too, the first
96
+ * paragraph after it whose break stays. Null when no paragraph follows.
97
+ */
98
+ const paragraphLeftAfter = ({ doc, paragraphPos, removedBreakPositions }) => {
99
+ let position = paragraphPos;
100
+ const start = doc.nodeAt(position);
101
+ if (!start) return null;
102
+ let node = start;
103
+ for (;;) {
104
+ const nextPos = position + node.nodeSize;
105
+ const next = doc.resolve(nextPos).nodeAfter;
106
+ if (!next || next.type !== node.type) return null;
107
+ const mark = expectParagraphAttrs(next).pPrMark;
108
+ const nextGoes = removedBreakPositions?.has(nextPos) || mark != null && (mark.kind === "del" || mark.kind === "moveFrom");
109
+ const afterNext = doc.resolve(nextPos + next.nodeSize).nodeAfter;
110
+ if (!nextGoes || afterNext?.type !== node.type) return nextPos;
111
+ position = nextPos;
112
+ node = next;
113
+ }
114
+ };
115
+ const recordedFormatting = (node) => ({
116
+ ...paragraphPropertiesSnapshot(node),
117
+ numPr: expectParagraphAttrs(node).numPr ?? null
118
+ });
119
+ /**
120
+ * A paragraph now holds the break another one had (a split, or a paste into
121
+ * it, leaves that break on the last part) but reads with other properties:
122
+ * record the other's as its property change. Rejecting the inserted breaks
123
+ * leaves this paragraph, which then reads as the one that was there. A
124
+ * pending property change on the other one travels as it is. Returns whether
125
+ * a change under `revision` was written.
126
+ */
127
+ const recordReplacedParagraphProperties = ({ tr, position, replaced, revision }) => {
128
+ const target = tr.doc.nodeAt(position);
129
+ if (!target || target.type !== replaced.type) return false;
130
+ if (hasSerializableParagraphPropertyChange(expectParagraphAttrs(target)._propertyChanges)) return false;
131
+ const pending = expectParagraphAttrs(replaced)._propertyChanges;
132
+ if (hasSerializableParagraphPropertyChange(pending)) {
133
+ tr.setNodeAttribute(position, "_propertyChanges", pending);
134
+ return false;
135
+ }
136
+ const previousFormatting = recordedFormatting(replaced);
137
+ if (JSON.stringify(previousFormatting) === JSON.stringify(recordedFormatting(target))) return false;
138
+ tr.setNodeAttribute(position, "_propertyChanges", [{
139
+ type: "paragraphPropertyChange",
140
+ info: revision,
141
+ previousFormatting
142
+ }]);
143
+ return true;
144
+ };
145
+ /**
146
+ * The properties a batch's own property change set on a paragraph: the ones
147
+ * whose value differs from what that change records as before.
148
+ */
149
+ const propertiesSetInBatch = (node, batchRevisionIds) => {
150
+ const changes = expectParagraphAttrs(node)._propertyChanges;
151
+ const change = Array.isArray(changes) ? changes.find(({ info }) => batchRevisionIds.has(info.id)) : void 0;
152
+ if (change === void 0) return /* @__PURE__ */ new Set();
153
+ const before = change.previousFormatting ?? {};
154
+ const now = paragraphPropertiesSnapshot(node);
155
+ return new Set([.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(now)])].filter((key) => JSON.stringify(before[key]) !== JSON.stringify(now[key])));
156
+ };
157
+ //#endregion
158
+ export { carryParagraphProperties, paragraphLeftAfter, propertiesSetInBatch, recordReplacedParagraphProperties };