@stll/folio-core 0.47.4 → 0.47.6
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/compare/inline-atoms.js +218 -31
- package/dist/compare/section-boundary-properties.js +1 -0
- package/dist/content-controls/mutateContentControls.js +1 -3
- package/dist/controller/headerFooterEditorManager.d.ts +2 -1
- package/dist/controller/headerFooterEditorManager.js +2 -1
- package/dist/controller/noteEditorManager.d.ts +2 -1
- package/dist/controller/noteEditorManager.js +2 -1
- package/dist/docx/commentRangeIntegrity.js +7 -0
- package/dist/docx/documentParser.js +55 -3
- package/dist/docx/inlineWrapperContent.d.ts +18 -16
- package/dist/docx/inlineWrapperContent.js +18 -16
- package/dist/docx/normalizeBaseDirection.js +4 -0
- package/dist/docx/rezip.js +1 -0
- package/dist/docx/selectiveSave.js +11 -2
- package/dist/docx/serializer/documentSerializer.js +16 -1
- package/dist/docx/serializer/paragraphSerializer.js +8 -0
- package/dist/docx/serializer/tableSerializer.js +5 -2
- package/dist/docx/server/createBilingualDocument.js +23 -14
- package/dist/docx/tableParser.js +101 -75
- package/dist/markdown/renderRuns.js +4 -0
- package/dist/markdown/renderTable.js +46 -12
- package/dist/prosemirror/attrs/index.d.ts +3 -2
- package/dist/prosemirror/attrs/index.js +85 -4
- package/dist/prosemirror/commands/comments.js +61 -3
- package/dist/prosemirror/commentReferenceAttrs.js +2 -2
- package/dist/prosemirror/commentReferenceIntegrity.d.ts +7 -3
- package/dist/prosemirror/commentReferenceIntegrity.js +9 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +177 -51
- package/dist/prosemirror/conversion/toProseDoc.js +108 -55
- package/dist/prosemirror/extensions/markRegistry.d.ts +5 -8
- package/dist/prosemirror/extensions/markRegistry.js +5 -8
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +13 -6
- package/dist/prosemirror/extensions/marks/InlineWrapperExtension.js +24 -6
- package/dist/prosemirror/extensions/marks/RunIdentityExtension.js +2 -1
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +32 -7
- package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +9 -5
- package/dist/prosemirror/extensions/nodes/TableExtension.js +2 -2
- package/dist/prosemirror/runIdentity.d.ts +5 -5
- package/dist/prosemirror/runIdentity.js +5 -5
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/marks.d.ts +28 -2
- package/dist/prosemirror/schema/nodes.d.ts +6 -4
- package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
- package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
- package/dist/prosemirror/utils/extractTrackedChanges.js +21 -6
- package/dist/prosemirror/validation.js +48 -7
- package/dist/types/content.d.ts +2 -2
- package/dist/types/index.d.ts +3 -1
- package/dist/utils/mergeDocumentContent.js +1 -7
- package/package.json +2 -2
|
@@ -44,13 +44,10 @@ type SchemaMarkName = keyof typeof MARK_EXTENSIONS;
|
|
|
44
44
|
* Five of these marks are containers on the save leg as well, and their
|
|
45
45
|
* relative order is that leg's rather than a choice made here.
|
|
46
46
|
* `extractParagraphContent` opens a comment range around everything a leaf
|
|
47
|
-
* produces
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* the runs it holds (`fromProseDoc.ts:2567`). So the save writes
|
|
52
|
-
* `w:commentRangeStart … w:ins > w:bdo > w:hyperlink > w:r`, and the editor DOM
|
|
53
|
-
* nests `span.docx-comment > span.docx-insertion > bdo > a`.
|
|
47
|
+
* produces. For an editor-created revision, the existing wrapper stack owns
|
|
48
|
+
* the change, followed by the hyperlink and the runs it holds. So the save
|
|
49
|
+
* writes `w:commentRangeStart … w:bdo > w:ins > w:hyperlink > w:r`, and the
|
|
50
|
+
* editor DOM nests `span.docx-comment > bdo > span.docx-insertion > a`.
|
|
54
51
|
* `markNestingOrder.test.ts` holds the two legs to each other.
|
|
55
52
|
*
|
|
56
53
|
* `runIdentity` is the fifth: `w:r` is the element it names, and it is the
|
|
@@ -60,6 +57,6 @@ type SchemaMarkName = keyof typeof MARK_EXTENSIONS;
|
|
|
60
57
|
* own in OOXML, so the save leg ranks it against nothing and its place here is
|
|
61
58
|
* a presentation decision.
|
|
62
59
|
*/
|
|
63
|
-
declare const MARK_NESTING_ORDER: readonly ["bold", "italic", "underline", "strike", "textColor", "runShading", "highlight", "fontSize", "fontFamily", "language", "superscript", "subscript", "allCaps", "smallCaps", "footnoteRef", "characterSpacing", "emboss", "imprint", "hidden", "textShadow", "emphasisMark", "textOutline", "rtl", "textEffect", "runFormattingOverride", "characterStyle", "comment", "
|
|
60
|
+
declare const MARK_NESTING_ORDER: readonly ["bold", "italic", "underline", "strike", "textColor", "runShading", "highlight", "fontSize", "fontFamily", "language", "superscript", "subscript", "allCaps", "smallCaps", "footnoteRef", "characterSpacing", "emboss", "imprint", "hidden", "textShadow", "emphasisMark", "textOutline", "rtl", "textEffect", "runFormattingOverride", "characterStyle", "comment", "inlineWrapper", "insertion", "deletion", "hyperlink", "runPropertyChange", "runIdentity"];
|
|
64
61
|
//#endregion
|
|
65
62
|
export { MARK_EXTENSIONS, MARK_NESTING_ORDER, SchemaMarkName };
|
|
@@ -83,13 +83,10 @@ const markNestingOrder = (names) => names;
|
|
|
83
83
|
* Five of these marks are containers on the save leg as well, and their
|
|
84
84
|
* relative order is that leg's rather than a choice made here.
|
|
85
85
|
* `extractParagraphContent` opens a comment range around everything a leaf
|
|
86
|
-
* produces
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* the runs it holds (`fromProseDoc.ts:2567`). So the save writes
|
|
91
|
-
* `w:commentRangeStart … w:ins > w:bdo > w:hyperlink > w:r`, and the editor DOM
|
|
92
|
-
* nests `span.docx-comment > span.docx-insertion > bdo > a`.
|
|
86
|
+
* produces. For an editor-created revision, the existing wrapper stack owns
|
|
87
|
+
* the change, followed by the hyperlink and the runs it holds. So the save
|
|
88
|
+
* writes `w:commentRangeStart … w:bdo > w:ins > w:hyperlink > w:r`, and the
|
|
89
|
+
* editor DOM nests `span.docx-comment > bdo > span.docx-insertion > a`.
|
|
93
90
|
* `markNestingOrder.test.ts` holds the two legs to each other.
|
|
94
91
|
*
|
|
95
92
|
* `runIdentity` is the fifth: `w:r` is the element it names, and it is the
|
|
@@ -127,9 +124,9 @@ const MARK_NESTING_ORDER = markNestingOrder([
|
|
|
127
124
|
"runFormattingOverride",
|
|
128
125
|
"characterStyle",
|
|
129
126
|
"comment",
|
|
127
|
+
"inlineWrapper",
|
|
130
128
|
"insertion",
|
|
131
129
|
"deletion",
|
|
132
|
-
"inlineWrapper",
|
|
133
130
|
"hyperlink",
|
|
134
131
|
"runPropertyChange",
|
|
135
132
|
"runIdentity"
|
|
@@ -7,6 +7,7 @@ import { panic } from "better-result";
|
|
|
7
7
|
/**
|
|
8
8
|
* Hyperlink Mark Extension
|
|
9
9
|
*/
|
|
10
|
+
const DOCX_HYPERLINK_INDEX_ATTRIBUTE = "data-docx-hyperlink-index";
|
|
10
11
|
/**
|
|
11
12
|
* Sanitize an href arriving from (or being emitted to) the DOM: pasted HTML,
|
|
12
13
|
* clipboard content, or a mark attr already stored on the document. Only
|
|
@@ -90,19 +91,25 @@ const HyperlinkExtension = createMarkExtension({
|
|
|
90
91
|
inclusive: false,
|
|
91
92
|
parseDOM: [{
|
|
92
93
|
tag: "a[href]",
|
|
93
|
-
getAttrs: (dom) =>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
getAttrs: (dom) => {
|
|
95
|
+
const index = dom.getAttribute(DOCX_HYPERLINK_INDEX_ATTRIBUTE);
|
|
96
|
+
const docxHyperlinkIndex = index !== null && /^\d+$/u.test(index) && Number.isSafeInteger(Number(index)) ? Number(index) : void 0;
|
|
97
|
+
return {
|
|
98
|
+
href: sanitizeStoredHref(dom.getAttribute("href") ?? void 0),
|
|
99
|
+
tooltip: dom.getAttribute("title") ?? void 0,
|
|
100
|
+
target: dom.getAttribute("target") ?? void 0,
|
|
101
|
+
...docxHyperlinkIndex === void 0 ? {} : { _docxHyperlinkIndex: docxHyperlinkIndex }
|
|
102
|
+
};
|
|
103
|
+
}
|
|
98
104
|
}],
|
|
99
105
|
toDOM(mark) {
|
|
100
|
-
const { href, target, tooltip } = expectHyperlinkMarkAttrs(mark);
|
|
106
|
+
const { href, target, tooltip, _docxHyperlinkIndex } = expectHyperlinkMarkAttrs(mark);
|
|
101
107
|
const domAttrs = {
|
|
102
108
|
href: sanitizeStoredHref(href),
|
|
103
109
|
...anchorTargetAttrs(target)
|
|
104
110
|
};
|
|
105
111
|
if (tooltip) domAttrs["title"] = tooltip;
|
|
112
|
+
if (typeof _docxHyperlinkIndex === "number") domAttrs[DOCX_HYPERLINK_INDEX_ATTRIBUTE] = String(_docxHyperlinkIndex);
|
|
106
113
|
return [
|
|
107
114
|
"a",
|
|
108
115
|
domAttrs,
|
|
@@ -16,6 +16,7 @@ import { panic } from "better-result";
|
|
|
16
16
|
* extend the wrapper over text the author wrote outside it.
|
|
17
17
|
*/
|
|
18
18
|
const INLINE_WRAPPER_MARK_NAME = "inlineWrapper";
|
|
19
|
+
const INLINE_WRAPPER_HYPERLINK_ORIGIN_ATTRIBUTE = "data-inline-wrapper-hyperlink-origin";
|
|
19
20
|
/** What a tagged layer's element name is spelled as on the rendered span. */
|
|
20
21
|
const TAGGED_ELEMENT_ATTRIBUTES = {
|
|
21
22
|
smartTag: "data-smart-tag-element",
|
|
@@ -54,22 +55,39 @@ const InlineWrapperExtension = createMarkExtension({
|
|
|
54
55
|
name: INLINE_WRAPPER_MARK_NAME,
|
|
55
56
|
schemaMarkName: INLINE_WRAPPER_MARK_NAME,
|
|
56
57
|
markSpec: {
|
|
57
|
-
attrs: {
|
|
58
|
-
|
|
59
|
-
stack: { default: null }
|
|
58
|
+
attrs: {
|
|
59
|
+
/** Outermost first. `null` is no wrapper, which is the absence of the mark. */
|
|
60
|
+
stack: { default: null },
|
|
61
|
+
_docxHyperlinkIndex: { default: null },
|
|
62
|
+
_docxInsideHyperlinkStackStart: { default: null }
|
|
63
|
+
},
|
|
60
64
|
inclusive: false,
|
|
61
65
|
parseDOM: [{
|
|
62
66
|
tag: `[${INLINE_WRAPPER_STACK_ATTRIBUTE}]`,
|
|
63
67
|
getAttrs(dom) {
|
|
64
68
|
const stack = parseInlineWrapperStack(dom.getAttribute(INLINE_WRAPPER_STACK_ATTRIBUTE));
|
|
65
|
-
|
|
69
|
+
if (stack === null) return false;
|
|
70
|
+
const origin = dom.getAttribute(INLINE_WRAPPER_HYPERLINK_ORIGIN_ATTRIBUTE);
|
|
71
|
+
if (origin === null) return { stack };
|
|
72
|
+
const match = /^(\d+):(\d+)$/u.exec(origin);
|
|
73
|
+
if (match === null) return false;
|
|
74
|
+
const hyperlinkIndex = Number(match[1]);
|
|
75
|
+
const stackStart = Number(match[2]);
|
|
76
|
+
if (!Number.isSafeInteger(hyperlinkIndex) || !Number.isSafeInteger(stackStart) || stackStart >= stack.length) return false;
|
|
77
|
+
return {
|
|
78
|
+
stack,
|
|
79
|
+
_docxHyperlinkIndex: hyperlinkIndex,
|
|
80
|
+
_docxInsideHyperlinkStackStart: stackStart
|
|
81
|
+
};
|
|
66
82
|
}
|
|
67
83
|
}],
|
|
68
84
|
toDOM(mark) {
|
|
69
|
-
const { stack } = expectInlineWrapperMarkAttrs(mark);
|
|
85
|
+
const { stack, _docxHyperlinkIndex, _docxInsideHyperlinkStackStart } = expectInlineWrapperMarkAttrs(mark);
|
|
70
86
|
const innermost = stack.at(-1);
|
|
71
87
|
if (innermost === void 0) panic("An inline wrapper mark carries at least one layer");
|
|
72
|
-
|
|
88
|
+
const attributes = { [INLINE_WRAPPER_STACK_ATTRIBUTE]: serializeInlineWrapperStack(stack) };
|
|
89
|
+
if (typeof _docxHyperlinkIndex === "number" && typeof _docxInsideHyperlinkStackStart === "number") attributes[INLINE_WRAPPER_HYPERLINK_ORIGIN_ATTRIBUTE] = `${_docxHyperlinkIndex}:${_docxInsideHyperlinkStackStart}`;
|
|
90
|
+
return layerElement(innermost, attributes);
|
|
73
91
|
}
|
|
74
92
|
}
|
|
75
93
|
});
|
|
@@ -105,7 +105,8 @@ const RunIdentityExtension = createMarkExtension({
|
|
|
105
105
|
attrs: {
|
|
106
106
|
id: {},
|
|
107
107
|
preservedAttributes: { default: null },
|
|
108
|
-
preserved: { default: null }
|
|
108
|
+
preserved: { default: null },
|
|
109
|
+
emptyFormatting: { default: null }
|
|
109
110
|
},
|
|
110
111
|
inclusive: false,
|
|
111
112
|
parseDOM: [{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AUTHOR_COLORS, getAuthorColorIdx } from "../../../utils/authorColors.js";
|
|
2
|
-
import { expectTrackedChangeMarkAttrs } from "../../attrs/index.js";
|
|
2
|
+
import { expectTrackedChangeMarkAttrs, isTrackedRevisionAncestorArray } from "../../attrs/index.js";
|
|
3
3
|
import { createMarkExtension } from "../create.js";
|
|
4
4
|
//#region src/prosemirror/extensions/marks/TrackedChangeExtensions.ts
|
|
5
5
|
/**
|
|
@@ -31,6 +31,15 @@ const SUGGESTION_TINT = "var(--suggestion-bg, color-mix(in oklch, #6d3bd6 12%, t
|
|
|
31
31
|
const SUGGESTION_TINT_LAYER = `linear-gradient(${SUGGESTION_TINT}, ${SUGGESTION_TINT})`;
|
|
32
32
|
const suggestedInsertionStyle = () => `color: ${SUGGESTION_COLOR}; text-decoration: underline; text-decoration-style: dotted; text-decoration-color: ${SUGGESTION_COLOR}; background-image: ${SUGGESTION_TINT_LAYER};`;
|
|
33
33
|
const suggestedDeletionStyle = () => `color: ${SUGGESTION_COLOR}; text-decoration: line-through; text-decoration-style: dotted; text-decoration-color: ${SUGGESTION_COLOR}; background-image: ${SUGGESTION_TINT_LAYER};`;
|
|
34
|
+
const revisionAncestorsFromDom = (serialized) => {
|
|
35
|
+
if (serialized === void 0) return null;
|
|
36
|
+
try {
|
|
37
|
+
const value = JSON.parse(serialized);
|
|
38
|
+
return isTrackedRevisionAncestorArray(value) ? value : false;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
34
43
|
/**
|
|
35
44
|
* Insertion mark — text added in tracked changes
|
|
36
45
|
* Renders with per-author colored underline.
|
|
@@ -47,21 +56,27 @@ const InsertionExtension = createMarkExtension({
|
|
|
47
56
|
moveKind: { default: null },
|
|
48
57
|
initials: { default: null },
|
|
49
58
|
provenance: { default: "user" },
|
|
50
|
-
suggestionId: { default: null }
|
|
59
|
+
suggestionId: { default: null },
|
|
60
|
+
_docxOuterWrapperCount: { default: null },
|
|
61
|
+
_docxRevisionAncestors: { default: null }
|
|
51
62
|
},
|
|
52
63
|
inclusive: false,
|
|
53
64
|
parseDOM: [{
|
|
54
65
|
tag: "span.docx-insertion",
|
|
55
66
|
getAttrs(dom) {
|
|
67
|
+
const ancestors = revisionAncestorsFromDom(dom.dataset["revisionAncestors"]);
|
|
68
|
+
if (ancestors === false) return false;
|
|
56
69
|
return {
|
|
57
70
|
revisionId: Number.parseInt(dom.dataset["revisionId"] ?? "0", 10),
|
|
58
71
|
author: dom.dataset["author"] ?? "",
|
|
59
|
-
date: dom.dataset["date"] ?? null
|
|
72
|
+
date: dom.dataset["date"] ?? null,
|
|
73
|
+
_docxOuterWrapperCount: dom.dataset["outerWrapperCount"] ? Number(dom.dataset["outerWrapperCount"]) : null,
|
|
74
|
+
_docxRevisionAncestors: ancestors
|
|
60
75
|
};
|
|
61
76
|
}
|
|
62
77
|
}],
|
|
63
78
|
toDOM(mark) {
|
|
64
|
-
const { revisionId, author, date, provenance, suggestionId } = expectTrackedChangeMarkAttrs(mark);
|
|
79
|
+
const { revisionId, author, date, provenance, suggestionId, _docxOuterWrapperCount, _docxRevisionAncestors } = expectTrackedChangeMarkAttrs(mark);
|
|
65
80
|
const idx = getAuthorColorIdx(author);
|
|
66
81
|
const color = AUTHOR_COLORS[idx] ?? "#000000";
|
|
67
82
|
const titleParts = [author, date ? new Date(date).toLocaleDateString() : ""].filter(Boolean);
|
|
@@ -76,6 +91,8 @@ const InsertionExtension = createMarkExtension({
|
|
|
76
91
|
...suggested ? { "data-provenance": "suggested" } : {},
|
|
77
92
|
...suggested && suggestionId ? { "data-suggestion-id": suggestionId } : {},
|
|
78
93
|
...date ? { "data-date": date } : {},
|
|
94
|
+
..._docxOuterWrapperCount != null ? { "data-outer-wrapper-count": String(_docxOuterWrapperCount) } : {},
|
|
95
|
+
..._docxRevisionAncestors ? { "data-revision-ancestors": JSON.stringify(_docxRevisionAncestors) } : {},
|
|
79
96
|
...titleParts.length > 0 ? { title: `${suggested ? "Suggested" : "Inserted"}: ${titleParts.join(", ")}` } : {},
|
|
80
97
|
style: suggested ? suggestedInsertionStyle() : insertionStyle(color)
|
|
81
98
|
},
|
|
@@ -101,21 +118,27 @@ const DeletionExtension = createMarkExtension({
|
|
|
101
118
|
initials: { default: null },
|
|
102
119
|
provenance: { default: "user" },
|
|
103
120
|
suggestionId: { default: null },
|
|
104
|
-
_historicalFormatting: { default: null }
|
|
121
|
+
_historicalFormatting: { default: null },
|
|
122
|
+
_docxOuterWrapperCount: { default: null },
|
|
123
|
+
_docxRevisionAncestors: { default: null }
|
|
105
124
|
},
|
|
106
125
|
inclusive: false,
|
|
107
126
|
parseDOM: [{
|
|
108
127
|
tag: "span.docx-deletion",
|
|
109
128
|
getAttrs(dom) {
|
|
129
|
+
const ancestors = revisionAncestorsFromDom(dom.dataset["revisionAncestors"]);
|
|
130
|
+
if (ancestors === false) return false;
|
|
110
131
|
return {
|
|
111
132
|
revisionId: Number.parseInt(dom.dataset["revisionId"] ?? "0", 10),
|
|
112
133
|
author: dom.dataset["author"] ?? "",
|
|
113
|
-
date: dom.dataset["date"] ?? null
|
|
134
|
+
date: dom.dataset["date"] ?? null,
|
|
135
|
+
_docxOuterWrapperCount: dom.dataset["outerWrapperCount"] ? Number(dom.dataset["outerWrapperCount"]) : null,
|
|
136
|
+
_docxRevisionAncestors: ancestors
|
|
114
137
|
};
|
|
115
138
|
}
|
|
116
139
|
}],
|
|
117
140
|
toDOM(mark) {
|
|
118
|
-
const { revisionId, author, date, provenance, suggestionId } = expectTrackedChangeMarkAttrs(mark);
|
|
141
|
+
const { revisionId, author, date, provenance, suggestionId, _docxOuterWrapperCount, _docxRevisionAncestors } = expectTrackedChangeMarkAttrs(mark);
|
|
119
142
|
const idx = getAuthorColorIdx(author);
|
|
120
143
|
const color = AUTHOR_COLORS[idx] ?? "#000000";
|
|
121
144
|
const titleParts = [author, date ? new Date(date).toLocaleDateString() : ""].filter(Boolean);
|
|
@@ -130,6 +153,8 @@ const DeletionExtension = createMarkExtension({
|
|
|
130
153
|
...suggested ? { "data-provenance": "suggested" } : {},
|
|
131
154
|
...suggested && suggestionId ? { "data-suggestion-id": suggestionId } : {},
|
|
132
155
|
...date ? { "data-date": date } : {},
|
|
156
|
+
..._docxOuterWrapperCount != null ? { "data-outer-wrapper-count": String(_docxOuterWrapperCount) } : {},
|
|
157
|
+
..._docxRevisionAncestors ? { "data-revision-ancestors": JSON.stringify(_docxRevisionAncestors) } : {},
|
|
133
158
|
...titleParts.length > 0 ? { title: `${suggested ? "Suggested deletion" : "Deleted"}: ${titleParts.join(", ")}` } : {},
|
|
134
159
|
style: suggested ? suggestedDeletionStyle() : deletionStyle(color)
|
|
135
160
|
},
|
|
@@ -13,11 +13,15 @@ const surveyCommentReferences = (doc) => {
|
|
|
13
13
|
doc.descendants((node, position) => {
|
|
14
14
|
if (node.type.name === "commentReference") {
|
|
15
15
|
const attrs = readCommentReferenceAttrs(node);
|
|
16
|
-
references.push({
|
|
17
|
-
|
|
16
|
+
references.push(attrs.ok ? {
|
|
17
|
+
status: "valid",
|
|
18
|
+
commentId: attrs.value.commentId,
|
|
18
19
|
position,
|
|
19
|
-
size: node.nodeSize
|
|
20
|
-
|
|
20
|
+
size: node.nodeSize
|
|
21
|
+
} : {
|
|
22
|
+
status: "malformed",
|
|
23
|
+
position,
|
|
24
|
+
size: node.nodeSize
|
|
21
25
|
});
|
|
22
26
|
return false;
|
|
23
27
|
}
|
|
@@ -60,7 +64,7 @@ const CommentReferenceExtension = createNodeExtension({
|
|
|
60
64
|
tag: "span[data-docx-comment-reference]",
|
|
61
65
|
getAttrs(dom) {
|
|
62
66
|
const raw = dom.getAttribute("data-docx-comment-reference");
|
|
63
|
-
if (raw === null ||
|
|
67
|
+
if (raw === null || !/^-?(?:0|[1-9]\d*)$/.test(raw)) return false;
|
|
64
68
|
const commentId = Number(raw);
|
|
65
69
|
return Number.isSafeInteger(commentId) ? { commentId } : false;
|
|
66
70
|
}
|
|
@@ -367,7 +367,7 @@ const statedCellWidth = (value, type) => ({
|
|
|
367
367
|
}
|
|
368
368
|
});
|
|
369
369
|
const tableCellSpec = {
|
|
370
|
-
content: "(paragraph | table | textBox | preservedBlock | blockBookmarkBoundary)+",
|
|
370
|
+
content: "(paragraph | table | textBox | blockSdt | preservedBlock | blockBookmarkBoundary)+",
|
|
371
371
|
tableRole: "cell",
|
|
372
372
|
isolating: true,
|
|
373
373
|
attrs: {
|
|
@@ -436,7 +436,7 @@ const tableCellSpec = {
|
|
|
436
436
|
}
|
|
437
437
|
};
|
|
438
438
|
const tableHeaderSpec = {
|
|
439
|
-
content: "(paragraph | table | textBox | preservedBlock | blockBookmarkBoundary)+",
|
|
439
|
+
content: "(paragraph | table | textBox | blockSdt | preservedBlock | blockBookmarkBoundary)+",
|
|
440
440
|
tableRole: "header_cell",
|
|
441
441
|
isolating: true,
|
|
442
442
|
attrs: {
|
|
@@ -13,16 +13,16 @@ declare const RUN_IDENTITY_MARK_NAME = "runIdentity";
|
|
|
13
13
|
type RunIdentityPayload = {
|
|
14
14
|
preservedAttributes?: readonly document_d_exports.PreservedAttribute[] | undefined;
|
|
15
15
|
preserved?: document_d_exports.PreservedMarkup | undefined;
|
|
16
|
+
emptyFormatting?: boolean | undefined;
|
|
16
17
|
};
|
|
17
18
|
/** A run identity's attrs in canonical form. */
|
|
18
|
-
declare const runIdentityAttrs: (id: number, { preservedAttributes, preserved }?: RunIdentityPayload) => RunIdentityMarkAttrs;
|
|
19
|
+
declare const runIdentityAttrs: (id: number, { preservedAttributes, preserved, emptyFormatting }?: RunIdentityPayload) => RunIdentityMarkAttrs;
|
|
19
20
|
/**
|
|
20
21
|
* Whether a run holds markup the mark exists to carry.
|
|
21
22
|
*
|
|
22
|
-
* The
|
|
23
|
-
*
|
|
24
|
-
* of corpus files hold none.
|
|
23
|
+
* The payload condition. The conversion also mints an identity for runs whose
|
|
24
|
+
* page break or mixed inline atoms split one authored run into several leaves.
|
|
25
25
|
*/
|
|
26
|
-
declare const hasRunIdentityPayload: ({ preservedAttributes, preserved }: RunIdentityPayload) => boolean;
|
|
26
|
+
declare const hasRunIdentityPayload: ({ preservedAttributes, preserved, emptyFormatting }: RunIdentityPayload) => boolean;
|
|
27
27
|
//#endregion
|
|
28
28
|
export { RUN_IDENTITY_ATTRIBUTE, RUN_IDENTITY_MARK_NAME, RunIdentityPayload, hasRunIdentityPayload, runIdentityAttrs };
|
|
@@ -18,19 +18,19 @@ const preservedMarkup = ({ children }) => ({ children: (children ?? []).map(({ i
|
|
|
18
18
|
xml
|
|
19
19
|
})) });
|
|
20
20
|
/** A run identity's attrs in canonical form. */
|
|
21
|
-
const runIdentityAttrs = (id, { preservedAttributes, preserved } = {}) => {
|
|
21
|
+
const runIdentityAttrs = (id, { preservedAttributes, preserved, emptyFormatting } = {}) => {
|
|
22
22
|
const attrs = { id };
|
|
23
23
|
if (preservedAttributes && preservedAttributes.length > 0) attrs.preservedAttributes = preservedAttributes.map(preservedAttribute);
|
|
24
24
|
if (preserved && (preserved.children?.length ?? 0) > 0) attrs.preserved = preservedMarkup(preserved);
|
|
25
|
+
if (emptyFormatting) attrs.emptyFormatting = true;
|
|
25
26
|
return attrs;
|
|
26
27
|
};
|
|
27
28
|
/**
|
|
28
29
|
* Whether a run holds markup the mark exists to carry.
|
|
29
30
|
*
|
|
30
|
-
* The
|
|
31
|
-
*
|
|
32
|
-
* of corpus files hold none.
|
|
31
|
+
* The payload condition. The conversion also mints an identity for runs whose
|
|
32
|
+
* page break or mixed inline atoms split one authored run into several leaves.
|
|
33
33
|
*/
|
|
34
|
-
const hasRunIdentityPayload = ({ preservedAttributes, preserved }) => (preservedAttributes?.length ?? 0) > 0 || (preserved?.children?.length ?? 0) > 0;
|
|
34
|
+
const hasRunIdentityPayload = ({ preservedAttributes, preserved, emptyFormatting }) => (preservedAttributes?.length ?? 0) > 0 || (preserved?.children?.length ?? 0) > 0 || emptyFormatting === true;
|
|
35
35
|
//#endregion
|
|
36
36
|
export { RUN_IDENTITY_ATTRIBUTE, RUN_IDENTITY_MARK_NAME, hasRunIdentityPayload, runIdentityAttrs };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CharacterSpacingAttrs, CharacterStyleAttrs, CommentAttrs, EmphasisMarkAttrs, FontFamilyAttrs, FontSizeAttrs, FootnoteRefAttrs, HighlightAttrs, HyperlinkAttrs, InlineWrapperAttrs, InlineWrapperKind, InlineWrapperLayer, LanguageAttrs, RunFormattingOverrideAttrs, RunIdentityMarkAttrs, RunPropertyChangeMarkAttrs, RunShadingAttrs, StrikeAttrs, TextColorAttrs, TextEffectAttrs, TrackedChangeMarkAttrs, UnderlineAttrs } from "./marks.js";
|
|
1
|
+
import { CharacterSpacingAttrs, CharacterStyleAttrs, CommentAttrs, EmphasisMarkAttrs, FontFamilyAttrs, FontSizeAttrs, FootnoteRefAttrs, HighlightAttrs, HyperlinkAttrs, InlineWrapperAttrs, InlineWrapperKind, InlineWrapperLayer, LanguageAttrs, RunFormattingOverrideAttrs, RunIdentityMarkAttrs, RunPropertyChangeMarkAttrs, RunShadingAttrs, StrikeAttrs, TextColorAttrs, TextEffectAttrs, TrackedChangeMarkAttrs, TrackedRevisionAncestor, UnderlineAttrs } from "./marks.js";
|
|
2
2
|
import { BlockSdtAttrs, BookmarkBoundaryAttrs, FieldAttrs, HardBreakAttrs, ImageAttrs, ImagePositionAttrs, MathAttrs, PageBreakRunAttrs, ParagraphAttrs, ParagraphPropertyChangeAttrs, PreservedBlockAttrs, PreservedXmlAttrs, PreservedXmlLevel, SdtAttrs, ShapeAttrs, SymbolAttrs, TabAttrs, TableAttrs, TableCellAttrs, TableRowAttrs, TextBoxAttrs } from "./nodes.js";
|
|
3
3
|
import { ExtensionManager } from "../extensions/ExtensionManager.js";
|
|
4
4
|
//#region src/prosemirror/schema/index.d.ts
|
|
@@ -11,4 +11,4 @@ type DocxSchema = typeof schema;
|
|
|
11
11
|
type DocxNode = ReturnType<typeof schema.node>;
|
|
12
12
|
type DocxMark = ReturnType<typeof schema.mark>;
|
|
13
13
|
//#endregion
|
|
14
|
-
export { type BlockSdtAttrs, type BookmarkBoundaryAttrs, type CharacterSpacingAttrs, type CharacterStyleAttrs, type CommentAttrs, DocxMark, DocxNode, DocxSchema, type EmphasisMarkAttrs, type FieldAttrs, type FontFamilyAttrs, type FontSizeAttrs, type FootnoteRefAttrs, type HardBreakAttrs, type HighlightAttrs, type HyperlinkAttrs, type ImageAttrs, type ImagePositionAttrs, type InlineWrapperAttrs, type InlineWrapperKind, type InlineWrapperLayer, type LanguageAttrs, type MathAttrs, type PageBreakRunAttrs, type ParagraphAttrs, type ParagraphPropertyChangeAttrs, type PreservedBlockAttrs, type PreservedXmlAttrs, type PreservedXmlLevel, type RunFormattingOverrideAttrs, type RunIdentityMarkAttrs, type RunPropertyChangeMarkAttrs, type RunShadingAttrs, type SdtAttrs, type ShapeAttrs, type StrikeAttrs, type SymbolAttrs, type TabAttrs, type TableAttrs, type TableCellAttrs, type TableRowAttrs, type TextBoxAttrs, type TextColorAttrs, type TextEffectAttrs, type TrackedChangeMarkAttrs, type UnderlineAttrs, schema, singletonManager };
|
|
14
|
+
export { type BlockSdtAttrs, type BookmarkBoundaryAttrs, type CharacterSpacingAttrs, type CharacterStyleAttrs, type CommentAttrs, DocxMark, DocxNode, DocxSchema, type EmphasisMarkAttrs, type FieldAttrs, type FontFamilyAttrs, type FontSizeAttrs, type FootnoteRefAttrs, type HardBreakAttrs, type HighlightAttrs, type HyperlinkAttrs, type ImageAttrs, type ImagePositionAttrs, type InlineWrapperAttrs, type InlineWrapperKind, type InlineWrapperLayer, type LanguageAttrs, type MathAttrs, type PageBreakRunAttrs, type ParagraphAttrs, type ParagraphPropertyChangeAttrs, type PreservedBlockAttrs, type PreservedXmlAttrs, type PreservedXmlLevel, type RunFormattingOverrideAttrs, type RunIdentityMarkAttrs, type RunPropertyChangeMarkAttrs, type RunShadingAttrs, type SdtAttrs, type ShapeAttrs, type StrikeAttrs, type SymbolAttrs, type TabAttrs, type TableAttrs, type TableCellAttrs, type TableRowAttrs, type TextBoxAttrs, type TextColorAttrs, type TextEffectAttrs, type TrackedChangeMarkAttrs, type TrackedRevisionAncestor, type UnderlineAttrs, schema, singletonManager };
|
|
@@ -99,6 +99,15 @@ type CommentAttrs = {
|
|
|
99
99
|
*/
|
|
100
100
|
declare const TRACKED_CHANGE_PROVENANCE_VALUES: readonly ["user", "suggested"];
|
|
101
101
|
type TrackedChangeProvenance = (typeof TRACKED_CHANGE_PROVENANCE_VALUES)[number];
|
|
102
|
+
type TrackedRevisionAncestor = {
|
|
103
|
+
type: "insertion" | "deletion" | "moveFrom" | "moveTo";
|
|
104
|
+
revisionId: number;
|
|
105
|
+
author: string;
|
|
106
|
+
date?: string;
|
|
107
|
+
utcDate?: string;
|
|
108
|
+
initials?: string;
|
|
109
|
+
outerWrapperCount: number;
|
|
110
|
+
};
|
|
102
111
|
type TrackedChangeMarkAttrs = {
|
|
103
112
|
revisionId: number;
|
|
104
113
|
author: string;
|
|
@@ -108,6 +117,10 @@ type TrackedChangeMarkAttrs = {
|
|
|
108
117
|
/** Optional author initials (w:initials) carried through the round-trip. */
|
|
109
118
|
initials?: string;
|
|
110
119
|
moveKind?: "moveTo" | "moveFrom";
|
|
120
|
+
/** Number of inline-wrapper layers authored outside this revision. */
|
|
121
|
+
_docxOuterWrapperCount?: number;
|
|
122
|
+
/** Outer revisions, in authored order, that ProseMirror cannot hold as nested marks. */
|
|
123
|
+
_docxRevisionAncestors?: readonly TrackedRevisionAncestor[];
|
|
111
124
|
/** Defaults to `"user"`; `"suggested"` for AI-proposed, non-serialized edits. */
|
|
112
125
|
provenance: TrackedChangeProvenance;
|
|
113
126
|
/**
|
|
@@ -136,7 +149,7 @@ type RunPropertyChangeMarkAttrs = {
|
|
|
136
149
|
* in the schema would force a resolution.
|
|
137
150
|
*
|
|
138
151
|
* Minted only when the run has something to carry — a page break, an
|
|
139
|
-
* attribute remainder, or
|
|
152
|
+
* attribute remainder, or authored run properties — so a document that
|
|
140
153
|
* carries none pays nothing.
|
|
141
154
|
*/
|
|
142
155
|
type RunIdentityMarkAttrs = {
|
|
@@ -146,6 +159,8 @@ type RunIdentityMarkAttrs = {
|
|
|
146
159
|
preservedAttributes?: readonly document_d_exports.PreservedAttribute[];
|
|
147
160
|
/** The `w:rPr` children no reader took a value from, with their ordinals. */
|
|
148
161
|
preserved?: document_d_exports.PreservedMarkup;
|
|
162
|
+
/** Preserve an authored empty `w:rPr`, distinct from no run properties. */
|
|
163
|
+
emptyFormatting?: true;
|
|
149
164
|
};
|
|
150
165
|
/**
|
|
151
166
|
* One transparent wrapper a leaf sits inside, minus the content ProseMirror
|
|
@@ -170,6 +185,17 @@ type InlineWrapperKind = InlineWrapperLayer["kind"];
|
|
|
170
185
|
*/
|
|
171
186
|
type InlineWrapperAttrs = {
|
|
172
187
|
stack: readonly InlineWrapperLayer[];
|
|
188
|
+
/**
|
|
189
|
+
* The imported hyperlink whose children the suffix of `stack` came from.
|
|
190
|
+
*
|
|
191
|
+
* ProseMirror stores marks as an unordered set, so a link mark and a wrapper
|
|
192
|
+
* mark otherwise cannot say whether `<w:bdo>` surrounded `<w:hyperlink>` or
|
|
193
|
+
* was one of its children. This is parser provenance, not editable wrapper
|
|
194
|
+
* data: it is meaningful only alongside the matching hyperlink mark.
|
|
195
|
+
*/
|
|
196
|
+
_docxHyperlinkIndex?: number;
|
|
197
|
+
/** The first stack layer authored inside `_docxHyperlinkIndex`. */
|
|
198
|
+
_docxInsideHyperlinkStackStart?: number;
|
|
173
199
|
};
|
|
174
200
|
declare const COMPLEX_SCRIPT_RUN_PROPERTY_KEYS: readonly ["boldCs", "italicCs", "fontSizeCs"];
|
|
175
201
|
type ComplexScriptRunPropertyKey = (typeof COMPLEX_SCRIPT_RUN_PROPERTY_KEYS)[number];
|
|
@@ -278,4 +304,4 @@ type HyperlinkAttrs = {
|
|
|
278
304
|
_docxHyperlinkIndex?: number;
|
|
279
305
|
};
|
|
280
306
|
//#endregion
|
|
281
|
-
export { AuthoredRunFormattingValues, COMPLEX_SCRIPT_RUN_PROPERTY_KEYS, CharacterSpacingAttrs, CharacterStyleAttrs, CommentAttrs, ComplexScriptRunPropertyKey, EmphasisMarkAttrs, FontFamilyAttrs, FontSizeAttrs, FootnoteRefAttrs, HighlightAttrs, HyperlinkAttrs, InlineWrapperAttrs, InlineWrapperKind, InlineWrapperLayer, LanguageAttrs, RUN_FORMATTING_BOOLEAN_PROPERTIES, RUN_FORMATTING_PROPERTY_SPECS, RUN_FORMATTING_VALUE_PROPERTIES, RunFormattingBooleanProperty, RunFormattingOverrideAttrs, RunFormattingValueProperty, RunIdentityMarkAttrs, RunPropertyChangeMarkAttrs, RunShadingAttrs, StrikeAttrs, TRACKED_CHANGE_PROVENANCE_VALUES, TextColorAttrs, TextEffectAttrs, TrackedChangeMarkAttrs, TrackedChangeProvenance, UnderlineAttrs };
|
|
307
|
+
export { AuthoredRunFormattingValues, COMPLEX_SCRIPT_RUN_PROPERTY_KEYS, CharacterSpacingAttrs, CharacterStyleAttrs, CommentAttrs, ComplexScriptRunPropertyKey, EmphasisMarkAttrs, FontFamilyAttrs, FontSizeAttrs, FootnoteRefAttrs, HighlightAttrs, HyperlinkAttrs, InlineWrapperAttrs, InlineWrapperKind, InlineWrapperLayer, LanguageAttrs, RUN_FORMATTING_BOOLEAN_PROPERTIES, RUN_FORMATTING_PROPERTY_SPECS, RUN_FORMATTING_VALUE_PROPERTIES, RunFormattingBooleanProperty, RunFormattingOverrideAttrs, RunFormattingValueProperty, RunIdentityMarkAttrs, RunPropertyChangeMarkAttrs, RunShadingAttrs, StrikeAttrs, TRACKED_CHANGE_PROVENANCE_VALUES, TextColorAttrs, TextEffectAttrs, TrackedChangeMarkAttrs, TrackedChangeProvenance, TrackedRevisionAncestor, UnderlineAttrs };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { document_d_exports } from "../../types/document.js";
|
|
2
|
-
import { TrackedChangeProvenance } from "./marks.js";
|
|
2
|
+
import { InlineWrapperLayer, TrackedChangeProvenance } from "./marks.js";
|
|
3
3
|
import "../../types/index.js";
|
|
4
4
|
import { OutlineStyleAttr } from "../../types/documentEnumValues.js";
|
|
5
5
|
import { SpacingExplicit } from "../../types/formatting.js";
|
|
@@ -212,9 +212,9 @@ type ParagraphAttrs = {
|
|
|
212
212
|
id: number;
|
|
213
213
|
name: string;
|
|
214
214
|
}[];
|
|
215
|
-
/**
|
|
216
|
-
* Preserve
|
|
217
|
-
*
|
|
215
|
+
/** A `w:hyperlink` with no projectable leaf cannot be represented as a text mark.
|
|
216
|
+
* Preserve its metadata and any empty transparent-wrapper children at the
|
|
217
|
+
* paragraph boundary so an editor round trip does not drop either element. */
|
|
218
218
|
_emptyHyperlinks?: {
|
|
219
219
|
/** ProseMirror inline offset where the zero-width hyperlink appeared. */
|
|
220
220
|
offset: number;
|
|
@@ -225,6 +225,8 @@ type ParagraphAttrs = {
|
|
|
225
225
|
target?: document_d_exports.Hyperlink["target"];
|
|
226
226
|
history?: document_d_exports.Hyperlink["history"];
|
|
227
227
|
docLocation?: document_d_exports.Hyperlink["docLocation"];
|
|
228
|
+
/** Empty transparent-wrapper nests authored as children of this link. */
|
|
229
|
+
_docxEmptyWrapperStacks?: readonly (readonly InlineWrapperLayer[])[];
|
|
228
230
|
}[];
|
|
229
231
|
/**
|
|
230
232
|
* Run-in heading flag: this paragraph's mark carries
|
|
@@ -16,7 +16,7 @@ declare const TRACKED_RUN_INLINE_ATOM_DISPOSITIONS: Readonly<{
|
|
|
16
16
|
readonly hardBreak: "carry";
|
|
17
17
|
readonly image: "carry";
|
|
18
18
|
readonly math: "carry";
|
|
19
|
-
readonly moveRangeBoundary: "
|
|
19
|
+
readonly moveRangeBoundary: "carry";
|
|
20
20
|
readonly pageBreakRun: "page-break-carrier";
|
|
21
21
|
readonly preservedXml: "carry";
|
|
22
22
|
readonly rangeAnchor: "transparent";
|
|
@@ -15,7 +15,7 @@ const TRACKED_RUN_INLINE_ATOM_DISPOSITIONS = Object.freeze({
|
|
|
15
15
|
hardBreak: "carry",
|
|
16
16
|
image: "carry",
|
|
17
17
|
math: "carry",
|
|
18
|
-
moveRangeBoundary: "
|
|
18
|
+
moveRangeBoundary: "carry",
|
|
19
19
|
pageBreakRun: "page-break-carrier",
|
|
20
20
|
preservedXml: "carry",
|
|
21
21
|
rangeAnchor: "transparent",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expectRunPropertyChangeMarkAttrs } from "../attrs/index.js";
|
|
1
|
+
import { expectRunPropertyChangeMarkAttrs, expectTrackedChangeMarkAttrs } from "../attrs/index.js";
|
|
2
2
|
import { nodePropertyRevisionSites, propertyRevisionMetadata, propertyRevisionRecords } from "../revisionCarriers.js";
|
|
3
3
|
import { getTableCellMergeChange } from "../tableCellMergeRevision.js";
|
|
4
4
|
//#region src/prosemirror/utils/extractTrackedChanges.ts
|
|
@@ -46,6 +46,7 @@ function extractTrackedChanges(state) {
|
|
|
46
46
|
const runPropertyChangeType = schema.marks["runPropertyChange"];
|
|
47
47
|
if (!insertionType && !deletionType && !runPropertyChangeType) return EMPTY_RESULT;
|
|
48
48
|
const raw = [];
|
|
49
|
+
const nestedRevisionIds = /* @__PURE__ */ new Set();
|
|
49
50
|
const commentToRevision = /* @__PURE__ */ new Map();
|
|
50
51
|
const commentMetadata = /* @__PURE__ */ new Map();
|
|
51
52
|
const rowRevisionScopes = [];
|
|
@@ -223,14 +224,28 @@ function extractTrackedChanges(state) {
|
|
|
223
224
|
if (mark.type === insertionType || mark.type === deletionType) {
|
|
224
225
|
tcMark = mark;
|
|
225
226
|
if (foldIntoRowRevision(rowRevisionScopes.at(-1), mark)) continue;
|
|
227
|
+
const attrs = expectTrackedChangeMarkAttrs(mark);
|
|
228
|
+
if (attrs._docxRevisionAncestors?.length) nestedRevisionIds.add(attrs.revisionId);
|
|
229
|
+
for (const ancestor of attrs._docxRevisionAncestors ?? []) {
|
|
230
|
+
nestedRevisionIds.add(ancestor.revisionId);
|
|
231
|
+
raw.push({
|
|
232
|
+
type: ancestor.type === "insertion" || ancestor.type === "moveTo" ? "insertion" : "deletion",
|
|
233
|
+
text: inlineText,
|
|
234
|
+
author: ancestor.author,
|
|
235
|
+
date: ancestor.date,
|
|
236
|
+
from: pos,
|
|
237
|
+
to: pos + node.nodeSize,
|
|
238
|
+
revisionId: ancestor.revisionId
|
|
239
|
+
});
|
|
240
|
+
}
|
|
226
241
|
raw.push({
|
|
227
242
|
type: mark.type === insertionType ? "insertion" : "deletion",
|
|
228
243
|
text: inlineText,
|
|
229
|
-
author:
|
|
230
|
-
date:
|
|
244
|
+
author: attrs.author,
|
|
245
|
+
date: attrs.date,
|
|
231
246
|
from: pos,
|
|
232
247
|
to: pos + node.nodeSize,
|
|
233
|
-
revisionId:
|
|
248
|
+
revisionId: attrs.revisionId
|
|
234
249
|
});
|
|
235
250
|
}
|
|
236
251
|
}
|
|
@@ -380,7 +395,7 @@ function extractTrackedChanges(state) {
|
|
|
380
395
|
merged.push({ ...entry });
|
|
381
396
|
continue;
|
|
382
397
|
}
|
|
383
|
-
const key = `${entry.type}|${entry.author}|${entry.date ?? ""}`;
|
|
398
|
+
const key = `${entry.type}|${entry.author}|${entry.date ?? ""}|${nestedRevisionIds.has(entry.revisionId) ? entry.revisionId : ""}`;
|
|
384
399
|
const group = inlineGroups.get(key);
|
|
385
400
|
if (group) {
|
|
386
401
|
const sep = group.to === entry.from ? "" : " ";
|
|
@@ -403,7 +418,7 @@ function extractTrackedChanges(state) {
|
|
|
403
418
|
for (let i = 0; i < merged.length; i++) {
|
|
404
419
|
const curr = merged[i];
|
|
405
420
|
const next = merged[i + 1];
|
|
406
|
-
if (curr.type === "deletion" && next && next.type === "insertion" && curr.author === next.author && curr.to === next.from) {
|
|
421
|
+
if (curr.type === "deletion" && next && next.type === "insertion" && !nestedRevisionIds.has(curr.revisionId) && !nestedRevisionIds.has(next.revisionId) && curr.author === next.author && curr.to === next.from) {
|
|
407
422
|
final.push({
|
|
408
423
|
type: "replacement",
|
|
409
424
|
text: next.text,
|