@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.
- package/dist/ai-edits/apply.js +390 -166
- package/dist/ai-edits/batch-claims.d.ts +21 -6
- package/dist/ai-edits/batch-claims.js +47 -16
- package/dist/ai-edits/headless.d.ts +20 -0
- package/dist/ai-edits/headless.js +140 -11
- package/dist/ai-edits/pending-suggestions.js +1 -1
- package/dist/ai-edits/read.d.ts +5 -3
- package/dist/ai-edits/read.js +97 -47
- package/dist/ai-edits/revisionStretches.d.ts +17 -0
- package/dist/ai-edits/revisionStretches.js +80 -0
- package/dist/ai-edits/snapshot.js +3 -2
- package/dist/ai-edits/table-cell-mutations.d.ts +3 -1
- package/dist/ai-edits/table-cell-mutations.js +2 -1
- package/dist/ai-edits/table-row-column-mutations.d.ts +6 -1
- package/dist/ai-edits/table-row-column-mutations.js +27 -10
- package/dist/compare/content-alignment.d.ts +5 -1
- package/dist/compare/content-alignment.js +39 -32
- package/dist/compare/inline-provenance.js +1 -1
- package/dist/controller/layoutPipeline.js +3 -2
- package/dist/docx/archiveInflation.d.ts +127 -0
- package/dist/docx/archiveInflation.js +181 -0
- package/dist/docx/metadataPrivacy.js +24 -1
- package/dist/docx/noteReferenceMark.d.ts +15 -0
- package/dist/docx/noteReferenceMark.js +48 -0
- package/dist/docx/paragraphParser.js +5 -3
- package/dist/docx/paragraphPropertySource.d.ts +1 -1
- package/dist/docx/paragraphPropertySource.js +3 -1
- package/dist/docx/parser.js +2 -2
- package/dist/docx/rezip.js +4 -2
- package/dist/docx/selectiveXmlPatch.d.ts +3 -1
- package/dist/docx/selectiveXmlPatch.js +81 -3
- package/dist/docx/server/boundedArchive.d.ts +8 -13
- package/dist/docx/server/boundedArchive.js +70 -59
- package/dist/docx/server/extractDocxText.d.ts +8 -2
- package/dist/docx/server/extractDocxText.js +2 -2
- package/dist/docx/server/validateDocxConformance.d.ts +1 -1
- package/dist/docx/server/validateDocxConformance.js +1 -0
- package/dist/docx/unzip.d.ts +19 -2
- package/dist/docx/unzip.js +135 -24
- package/dist/fonts/embeddedFonts.js +1 -1
- package/dist/i18n/messages/catalogs.gen.d.ts +34 -34
- package/dist/i18n/messages/catalogs.gen.js +34 -34
- package/dist/i18n/messages/messages.gen.d.ts +2 -2
- package/dist/internal/wholeStoryRevisionResolution.js +95 -25
- package/dist/layout-engine/measure/measureParagraph.js +2 -2
- package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +12 -1
- package/dist/layout-engine/measure/paragraphMeasureShared.js +14 -1
- package/dist/markdown/escape.d.ts +2 -2
- package/dist/markdown/escape.js +4 -5
- package/dist/markdown/renderBlock.js +91 -25
- package/dist/markdown/renderRuns.d.ts +10 -1
- package/dist/markdown/renderRuns.js +320 -59
- package/dist/prosemirror/clearRunColor.d.ts +6 -0
- package/dist/prosemirror/clearRunColor.js +46 -0
- package/dist/prosemirror/commands/comments.js +56 -21
- package/dist/prosemirror/commands/formatPainter.js +1 -1
- package/dist/prosemirror/commands/hyperlink.js +2 -1
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +3 -2
- package/dist/prosemirror/commands/propertyChangeScope.js +24 -3
- package/dist/prosemirror/commands/resolveAllTableChanges.js +46 -41
- package/dist/prosemirror/commands/resolveParagraphProperties.js +20 -6
- package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +8 -1
- package/dist/prosemirror/commands/tableCellMergeResolution.js +23 -7
- package/dist/prosemirror/containerFinalParagraph.d.ts +11 -5
- package/dist/prosemirror/containerFinalParagraph.js +7 -6
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -13
- package/dist/prosemirror/conversion/fromProseDoc.js +12 -590
- package/dist/prosemirror/conversion/toProseDoc.d.ts +4 -11
- package/dist/prosemirror/conversion/toProseDoc.js +31 -150
- package/dist/prosemirror/documentSchema.d.ts +8 -0
- package/dist/prosemirror/documentSchema.js +18 -0
- package/dist/prosemirror/extensions/StarterKit.js +2 -0
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +104 -63
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -1
- package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +24 -3
- package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.d.ts +20 -0
- package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.js +43 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +10 -9
- package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +1 -1
- package/dist/prosemirror/extensions/features/pastedHtmlLists.js +2 -4
- package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +14 -8
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +8 -4
- package/dist/prosemirror/extensions/marks/StrikeExtension.js +2 -2
- package/dist/prosemirror/extensions/marks/SubscriptExtension.js +2 -2
- package/dist/prosemirror/extensions/marks/SuperscriptExtension.js +2 -2
- package/dist/prosemirror/extensions/marks/TextColorExtension.js +4 -3
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +8 -6
- package/dist/prosemirror/extensions/marks/markUtils.js +17 -8
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +3 -1
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +12 -1
- package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +2 -2
- package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +5 -3
- package/dist/prosemirror/extensions/nodes/TableExtension.js +104 -210
- package/dist/prosemirror/extensions/types.d.ts +7 -0
- package/dist/prosemirror/hyperlinkRemoval.d.ts +9 -0
- package/dist/prosemirror/hyperlinkRemoval.js +129 -0
- package/dist/prosemirror/index.d.ts +2 -1
- package/dist/prosemirror/index.js +2 -1
- package/dist/prosemirror/listNumbering.d.ts +3 -21
- package/dist/prosemirror/listNumbering.js +10 -25
- package/dist/prosemirror/listRendering.d.ts +27 -0
- package/dist/prosemirror/listRendering.js +27 -0
- package/dist/prosemirror/markupViewNotes.d.ts +8 -0
- package/dist/prosemirror/markupViewNotes.js +49 -0
- package/dist/prosemirror/markupViewProjection.d.ts +3 -1
- package/dist/prosemirror/markupViewProjection.js +7 -2
- package/dist/prosemirror/noteReferenceReview.d.ts +77 -0
- package/dist/prosemirror/noteReferenceReview.js +332 -0
- package/dist/prosemirror/paragraphIndentation.js +5 -2
- package/dist/prosemirror/paragraphMarkJoin.d.ts +6 -4
- package/dist/prosemirror/paragraphMarkJoin.js +21 -19
- package/dist/prosemirror/paragraphPropertyCarry.d.ts +64 -0
- package/dist/prosemirror/paragraphPropertyCarry.js +158 -0
- package/dist/prosemirror/plugins/documentStyleState.d.ts +26 -0
- package/dist/prosemirror/plugins/documentStyleState.js +32 -0
- package/dist/prosemirror/plugins/documentStyles.d.ts +2 -21
- package/dist/prosemirror/plugins/documentStyles.js +66 -29
- package/dist/prosemirror/plugins/index.d.ts +2 -1
- package/dist/prosemirror/plugins/index.js +2 -1
- package/dist/prosemirror/plugins/paragraphStyleResolution.d.ts +15 -0
- package/dist/prosemirror/plugins/paragraphStyleResolution.js +230 -0
- package/dist/prosemirror/plugins/suggestionMode.d.ts +12 -2
- package/dist/prosemirror/plugins/suggestionMode.js +391 -28
- package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +22 -2
- package/dist/prosemirror/rebaseParagraphRunFormatting.js +39 -13
- package/dist/prosemirror/rebaseParagraphRuns.d.ts +36 -0
- package/dist/prosemirror/rebaseParagraphRuns.js +99 -0
- package/dist/prosemirror/runFormattingFromMarks.d.ts +18 -0
- package/dist/prosemirror/runFormattingFromMarks.js +567 -0
- package/dist/prosemirror/runFormattingReconciliation.js +10 -9
- package/dist/prosemirror/schema/nodes.d.ts +2 -0
- package/dist/prosemirror/styles/paragraphStyleCascade.d.ts +59 -0
- package/dist/prosemirror/styles/paragraphStyleCascade.js +145 -0
- package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +21 -2
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +38 -1
- package/dist/prosemirror/styles/tableStyleRegions.d.ts +31 -0
- package/dist/prosemirror/styles/tableStyleRegions.js +55 -0
- package/dist/prosemirror/tableCellPaste.d.ts +74 -0
- package/dist/prosemirror/tableCellPaste.js +525 -0
- package/dist/prosemirror/tableGridMutation.d.ts +53 -14
- package/dist/prosemirror/tableGridMutation.js +166 -18
- package/dist/prosemirror/tableRunIn.d.ts +51 -0
- package/dist/prosemirror/tableRunIn.js +165 -0
- package/dist/prosemirror/textInput.js +11 -2
- package/dist/prosemirror/trackedRevisionPath.d.ts +17 -0
- package/dist/prosemirror/trackedRevisionPath.js +32 -0
- package/dist/server.d.ts +2 -2
- 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
|
|
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
|
-
*
|
|
13
|
-
*
|
|
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
|
-
|
|
17
|
+
styleResolver: RunStyleResolver | null;
|
|
16
18
|
};
|
|
17
|
-
declare const joinAtParagraphMark: ({ tr, paragraphPos, paragraph, next,
|
|
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 {
|
|
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,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
tr.
|
|
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 };
|