@stll/folio-core 0.47.5 → 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/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/selectiveSave.js +11 -2
- package/dist/prosemirror/attrs/index.d.ts +3 -2
- package/dist/prosemirror/attrs/index.js +18 -1
- package/dist/prosemirror/commands/comments.js +61 -3
- package/dist/prosemirror/conversion/fromProseDoc.js +107 -37
- package/dist/prosemirror/conversion/toProseDoc.js +33 -13
- package/dist/prosemirror/extensions/markRegistry.d.ts +5 -8
- package/dist/prosemirror/extensions/markRegistry.js +5 -8
- package/dist/prosemirror/extensions/marks/RunIdentityExtension.js +2 -1
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +32 -7
- 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 +17 -2
- package/dist/prosemirror/utils/extractTrackedChanges.js +21 -6
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { document_d_exports } from "../types/document.js";
|
|
2
|
-
import { EditorView } from "prosemirror-view";
|
|
2
|
+
import { DirectEditorProps, EditorView } from "prosemirror-view";
|
|
3
3
|
//#region src/controller/headerFooterEditorManager.d.ts
|
|
4
4
|
type HeaderFooterPartKind = "header" | "footer";
|
|
5
5
|
type HeaderFooterPartKey = {
|
|
@@ -14,6 +14,7 @@ type HeaderFooterEditorTransaction = {
|
|
|
14
14
|
view: EditorView;
|
|
15
15
|
};
|
|
16
16
|
type HeaderFooterEditorManagerDeps = {
|
|
17
|
+
createView?: ((mountNode: HTMLElement, props: DirectEditorProps) => EditorView) | undefined;
|
|
17
18
|
getDocument: () => document_d_exports.Document | null;
|
|
18
19
|
getHost: () => HTMLElement | null;
|
|
19
20
|
getStyles: () => document_d_exports.StyleDefinitions | null | undefined;
|
|
@@ -12,6 +12,7 @@ import { schema } from "../prosemirror/schema/index.js";
|
|
|
12
12
|
import { EditorState } from "prosemirror-state";
|
|
13
13
|
import { EditorView } from "prosemirror-view";
|
|
14
14
|
//#region src/controller/headerFooterEditorManager.ts
|
|
15
|
+
const createEditorView = (mountNode, props) => new EditorView(mountNode, props);
|
|
15
16
|
const DETACHED_WATERMARK_HOST = Symbol.for("stll.detachedWatermarkHost");
|
|
16
17
|
const headerFooterToProseDocWithDetachedWatermarkHost = (headerFooter, options) => {
|
|
17
18
|
return headerFooterToProseDoc(headerFooter.content.map((block, blockIndex) => {
|
|
@@ -109,7 +110,7 @@ const createHeaderFooterEditorManager = (deps) => {
|
|
|
109
110
|
mountNode.dataset["hfRId"] = part.rId;
|
|
110
111
|
mountNode.dataset["hfKind"] = part.kind;
|
|
111
112
|
host.append(mountNode);
|
|
112
|
-
const view =
|
|
113
|
+
const view = (deps.createView ?? createEditorView)(mountNode, {
|
|
113
114
|
state: buildInitialState(headerFooter, styles, theme, numbering, manager),
|
|
114
115
|
dispatchTransaction(transaction) {
|
|
115
116
|
const nextState = view.state.apply(transaction);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { document_d_exports } from "../types/document.js";
|
|
2
2
|
import { NoteStoryKey } from "../types/editor-story.js";
|
|
3
3
|
import { Plugin } from "prosemirror-state";
|
|
4
|
-
import { EditorView } from "prosemirror-view";
|
|
4
|
+
import { DirectEditorProps, EditorView } from "prosemirror-view";
|
|
5
5
|
//#region src/controller/noteEditorManager.d.ts
|
|
6
6
|
type NoteStoryKind = NoteStoryKey["kind"];
|
|
7
7
|
type NoteEditorTransaction = NoteStoryKey & {
|
|
@@ -10,6 +10,7 @@ type NoteEditorTransaction = NoteStoryKey & {
|
|
|
10
10
|
view: EditorView;
|
|
11
11
|
};
|
|
12
12
|
type NoteEditorManagerDeps = {
|
|
13
|
+
createView?: ((mountNode: HTMLElement, props: DirectEditorProps) => EditorView) | undefined;
|
|
13
14
|
getDocument: () => document_d_exports.Document | null;
|
|
14
15
|
getHost: () => HTMLElement | null;
|
|
15
16
|
getPlugins?: (() => Plugin[]) | undefined;
|
|
@@ -12,6 +12,7 @@ import { EditorState } from "prosemirror-state";
|
|
|
12
12
|
import { EditorView } from "prosemirror-view";
|
|
13
13
|
//#region src/controller/noteEditorManager.ts
|
|
14
14
|
/** Framework-neutral lifecycle owner for editable footnote and endnote stories. */
|
|
15
|
+
const createEditorView = (mountNode, props) => new EditorView(mountNode, props);
|
|
15
16
|
const EMPTY_PLUGINS = [];
|
|
16
17
|
const storyMapKey = ({ kind, noteId }) => `${kind}:${String(noteId)}`;
|
|
17
18
|
const isNormalFootnote = (note) => !isSeparatorFootnote(note);
|
|
@@ -110,7 +111,7 @@ const createNoteEditorManager = (deps) => {
|
|
|
110
111
|
mountNode.dataset["noteId"] = String(storyKey.noteId);
|
|
111
112
|
host.append(mountNode);
|
|
112
113
|
const proseDocument = noteToProseDocument(note, styles, theme);
|
|
113
|
-
const view =
|
|
114
|
+
const view = (deps.createView ?? createEditorView)(mountNode, {
|
|
114
115
|
state: buildInitialState(proseDocument, styles, numbering, manager, externalPlugins),
|
|
115
116
|
dispatchTransaction(transaction) {
|
|
116
117
|
view.updateState(view.state.apply(transaction));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseCommentsExtended } from "./commentParser.js";
|
|
2
2
|
import { withoutOrphanCommentRanges } from "./commentRangeIntegrity.js";
|
|
3
3
|
import { hasUnsynthesizedReplyRanges } from "./commentReplyMarkers.js";
|
|
4
|
+
import { detectDocxConformanceClass } from "./conformance.js";
|
|
4
5
|
import { validateFolioDocumentModel } from "./modelValidation.js";
|
|
5
6
|
import { parseNumbering } from "./numberingParser.js";
|
|
6
7
|
import { isUnsafePackagePath } from "./packageParts.js";
|
|
@@ -13,6 +14,7 @@ import { serializeDocument } from "./serializer/documentSerializer.js";
|
|
|
13
14
|
import { serializeEndnotes, serializeFootnotes } from "./serializer/noteSerializer.js";
|
|
14
15
|
import { serializeNumberingXml } from "./serializer/numberingSerializer.js";
|
|
15
16
|
import { readRootNamespaceBindings } from "./serializer/partNamespaces.js";
|
|
17
|
+
import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
|
|
16
18
|
//#region src/docx/selectiveSave.ts
|
|
17
19
|
/**
|
|
18
20
|
* Splice edited footnote/endnote paragraphs into their parts and return the
|
|
@@ -217,8 +219,9 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
217
219
|
}
|
|
218
220
|
const sourceCommentsFile = zip.file("word/comments.xml");
|
|
219
221
|
if (hasComments || sourceCommentsFile) {
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
+
const sourceCommentsXml = sourceCommentsFile ? await sourceCommentsFile.async("text") : void 0;
|
|
223
|
+
const commentsXml = serializeComments(commentPlan, sourceCommentsXml === void 0 ? void 0 : readRootNamespaceBindings(sourceCommentsXml));
|
|
224
|
+
if (commentsXml !== sourceCommentsXml) updates.set("word/comments.xml", commentsXml);
|
|
222
225
|
}
|
|
223
226
|
if (hasComments) {
|
|
224
227
|
const ctFile = zip.file("[Content_Types].xml");
|
|
@@ -245,6 +248,12 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
245
248
|
const corePropsXml = await corePropsFile.async("text");
|
|
246
249
|
updates.set("docProps/core.xml", updateCoreProperties(corePropsXml, { updateModifiedDate: true }));
|
|
247
250
|
}
|
|
251
|
+
if ([...updates.keys()].some((path) => {
|
|
252
|
+
const lowerPath = path.toLowerCase();
|
|
253
|
+
return lowerPath.startsWith("word/") && (lowerPath.endsWith(".xml") || lowerPath.endsWith(".rels"));
|
|
254
|
+
})) {
|
|
255
|
+
if (detectDocxConformanceClass(await findZipEntryCaseInsensitive(zip, "word/document.xml")?.async("text") ?? null) === DOCX_CONFORMANCE_CLASSES.STRICT) return null;
|
|
256
|
+
}
|
|
248
257
|
return await applyUpdatesToZip(zip, updates);
|
|
249
258
|
} catch {
|
|
250
259
|
return null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CharacterSpacingAttrs, CharacterStyleAttrs, CommentAttrs, EmphasisMarkAttrs, FontFamilyAttrs, FontSizeAttrs, FootnoteRefAttrs, HighlightAttrs, HyperlinkAttrs, InlineWrapperAttrs, InlineWrapperLayer, LanguageAttrs, RunFormattingOverrideAttrs, RunIdentityMarkAttrs, RunPropertyChangeMarkAttrs, RunShadingAttrs, StrikeAttrs, TextColorAttrs, TextEffectAttrs, TrackedChangeMarkAttrs, UnderlineAttrs } from "../schema/marks.js";
|
|
1
|
+
import { CharacterSpacingAttrs, CharacterStyleAttrs, CommentAttrs, EmphasisMarkAttrs, FontFamilyAttrs, FontSizeAttrs, FootnoteRefAttrs, HighlightAttrs, HyperlinkAttrs, InlineWrapperAttrs, InlineWrapperLayer, LanguageAttrs, RunFormattingOverrideAttrs, RunIdentityMarkAttrs, RunPropertyChangeMarkAttrs, RunShadingAttrs, StrikeAttrs, TextColorAttrs, TextEffectAttrs, TrackedChangeMarkAttrs, TrackedRevisionAncestor, UnderlineAttrs } from "../schema/marks.js";
|
|
2
2
|
import { BlockSdtAttrs, FieldAttrs, HardBreakAttrs, ImageAttrs, MathAttrs, PageBreakRunAttrs, ParagraphAttrs, PreservedBlockAttrs, PreservedXmlAttrs, SdtAttrs, ShapeAttrs, SymbolAttrs, TabAttrs, TableAttrs, TableCellAttrs, TableRowAttrs, TextBoxAttrs } from "../schema/nodes.js";
|
|
3
3
|
import "../schema/index.js";
|
|
4
4
|
import { Mark, Node } from "prosemirror-model";
|
|
@@ -76,6 +76,7 @@ declare const readFootnoteRefMarkAttrs: (mark: Mark) => ReadProseMirrorAttrsResu
|
|
|
76
76
|
declare const expectFootnoteRefMarkAttrs: (mark: Mark) => FootnoteRefAttrs;
|
|
77
77
|
declare const readCommentMarkAttrs: (mark: Mark) => ReadProseMirrorAttrsResult<CommentAttrs>;
|
|
78
78
|
declare const expectCommentMarkAttrs: (mark: Mark) => CommentAttrs;
|
|
79
|
+
declare const isTrackedRevisionAncestorArray: (value: unknown) => value is readonly TrackedRevisionAncestor[];
|
|
79
80
|
declare const readTrackedChangeMarkAttrs: (mark: Mark) => ReadProseMirrorAttrsResult<TrackedChangeMarkAttrs>;
|
|
80
81
|
declare const expectTrackedChangeMarkAttrs: (mark: Mark) => TrackedChangeMarkAttrs;
|
|
81
82
|
declare const readRunPropertyChangeMarkAttrs: (mark: Mark) => ReadProseMirrorAttrsResult<RunPropertyChangeMarkAttrs>;
|
|
@@ -118,4 +119,4 @@ declare const mergeTableRowAttrs: (node: Node, patch: NodeAttrPatch<TableRowAttr
|
|
|
118
119
|
*/
|
|
119
120
|
declare const mergeTableCellAttrs: (node: Node, patch: NodeAttrPatch<TableCellAttrs>) => TableCellAttrs;
|
|
120
121
|
//#endregion
|
|
121
|
-
export { ProseMirrorAttrIssue, ReadProseMirrorAttrsResult, expectBlockSdtAttrs, expectCharacterSpacingMarkAttrs, expectCharacterStyleMarkAttrs, expectCommentMarkAttrs, expectEmphasisMarkAttrs, expectFieldAttrs, expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectFootnoteRefMarkAttrs, expectHardBreakAttrs, expectHighlightMarkAttrs, expectHyperlinkMarkAttrs, expectImageAttrs, expectInlineWrapperMarkAttrs, expectLanguageMarkAttrs, expectMathAttrs, expectPageBreakRunAttrs, expectParagraphAttrs, expectPreservedBlockAttrs, expectPreservedXmlAttrs, expectRunFormattingOverrideMarkAttrs, expectRunIdentityMarkAttrs, expectRunPropertyChangeMarkAttrs, expectRunShadingMarkAttrs, expectSdtAttrs, expectShapeAttrs, expectStrikeMarkAttrs, expectSymbolAttrs, expectTabAttrs, expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, expectTextBoxAttrs, expectTextColorMarkAttrs, expectTextEffectMarkAttrs, expectTrackedChangeMarkAttrs, expectUnderlineMarkAttrs, mergeImageAttrs, mergeParagraphAttrs, mergeTableAttrs, mergeTableCellAttrs, mergeTableRowAttrs, readBlockSdtAttrs, readCharacterSpacingMarkAttrs, readCharacterStyleMarkAttrs, readCommentMarkAttrs, readEmphasisMarkAttrs, readFieldAttrs, readFontFamilyMarkAttrs, readFontSizeMarkAttrs, readFootnoteRefMarkAttrs, readHardBreakAttrs, readHighlightMarkAttrs, readHyperlinkMarkAttrs, readImageAttrs, readInlineWrapperMarkAttrs, readInlineWrapperStack, readLanguageMarkAttrs, readMathAttrs, readPageBreakRunAttrs, readParagraphAttrs, readPreservedBlockAttrs, readPreservedXmlAttrs, readRunFormattingOverrideMarkAttrs, readRunIdentityMarkAttrs, readRunPropertyChangeMarkAttrs, readRunShadingMarkAttrs, readSdtAttrs, readShapeAttrs, readStrikeMarkAttrs, readSymbolAttrs, readTabAttrs, readTableAttrs, readTableCellAttrs, readTableRowAttrs, readTextBoxAttrs, readTextColorMarkAttrs, readTextEffectMarkAttrs, readTrackedChangeMarkAttrs, readUnderlineMarkAttrs };
|
|
122
|
+
export { ProseMirrorAttrIssue, ReadProseMirrorAttrsResult, expectBlockSdtAttrs, expectCharacterSpacingMarkAttrs, expectCharacterStyleMarkAttrs, expectCommentMarkAttrs, expectEmphasisMarkAttrs, expectFieldAttrs, expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectFootnoteRefMarkAttrs, expectHardBreakAttrs, expectHighlightMarkAttrs, expectHyperlinkMarkAttrs, expectImageAttrs, expectInlineWrapperMarkAttrs, expectLanguageMarkAttrs, expectMathAttrs, expectPageBreakRunAttrs, expectParagraphAttrs, expectPreservedBlockAttrs, expectPreservedXmlAttrs, expectRunFormattingOverrideMarkAttrs, expectRunIdentityMarkAttrs, expectRunPropertyChangeMarkAttrs, expectRunShadingMarkAttrs, expectSdtAttrs, expectShapeAttrs, expectStrikeMarkAttrs, expectSymbolAttrs, expectTabAttrs, expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, expectTextBoxAttrs, expectTextColorMarkAttrs, expectTextEffectMarkAttrs, expectTrackedChangeMarkAttrs, expectUnderlineMarkAttrs, isTrackedRevisionAncestorArray, mergeImageAttrs, mergeParagraphAttrs, mergeTableAttrs, mergeTableCellAttrs, mergeTableRowAttrs, readBlockSdtAttrs, readCharacterSpacingMarkAttrs, readCharacterStyleMarkAttrs, readCommentMarkAttrs, readEmphasisMarkAttrs, readFieldAttrs, readFontFamilyMarkAttrs, readFontSizeMarkAttrs, readFootnoteRefMarkAttrs, readHardBreakAttrs, readHighlightMarkAttrs, readHyperlinkMarkAttrs, readImageAttrs, readInlineWrapperMarkAttrs, readInlineWrapperStack, readLanguageMarkAttrs, readMathAttrs, readPageBreakRunAttrs, readParagraphAttrs, readPreservedBlockAttrs, readPreservedXmlAttrs, readRunFormattingOverrideMarkAttrs, readRunIdentityMarkAttrs, readRunPropertyChangeMarkAttrs, readRunShadingMarkAttrs, readSdtAttrs, readShapeAttrs, readStrikeMarkAttrs, readSymbolAttrs, readTabAttrs, readTableAttrs, readTableCellAttrs, readTableRowAttrs, readTextBoxAttrs, readTextColorMarkAttrs, readTextEffectMarkAttrs, readTrackedChangeMarkAttrs, readUnderlineMarkAttrs };
|
|
@@ -82,6 +82,12 @@ const NOTE_TYPES = ["footnote", "endnote"];
|
|
|
82
82
|
const NOTE_REF_VERT_ALIGNS = ["baseline", "superscript"];
|
|
83
83
|
const TEXT_BOX_DOCX_PLACEMENTS = ["standalone", "inlineWithPrevious"];
|
|
84
84
|
const TRACKED_CHANGE_MOVE_KINDS = ["moveTo", "moveFrom"];
|
|
85
|
+
const TRACKED_REVISION_ANCESTOR_TYPES = [
|
|
86
|
+
"insertion",
|
|
87
|
+
"deletion",
|
|
88
|
+
"moveFrom",
|
|
89
|
+
"moveTo"
|
|
90
|
+
];
|
|
85
91
|
const TEXT_BOX_TRACKED_CHANGE_TYPES = [
|
|
86
92
|
"insertion",
|
|
87
93
|
"deletion",
|
|
@@ -792,6 +798,7 @@ const readCommentMarkAttrs = (mark) => {
|
|
|
792
798
|
return attrsResult(attrs, issues);
|
|
793
799
|
};
|
|
794
800
|
const expectCommentMarkAttrs = (mark) => expectCachedMarkAttrs(mark, commentAttrsCache, readCommentMarkAttrs, "comment attrs");
|
|
801
|
+
const isTrackedRevisionAncestorArray = (value) => Array.isArray(value) && value.length > 0 && value.every((ancestor) => isRecord(ancestor) && TRACKED_REVISION_ANCESTOR_TYPES.some((type) => type === ancestor["type"]) && typeof ancestor["revisionId"] === "number" && Number.isSafeInteger(ancestor["revisionId"]) && ancestor["revisionId"] >= 0 && typeof ancestor["author"] === "string" && (ancestor["date"] === void 0 || typeof ancestor["date"] === "string") && (ancestor["utcDate"] === void 0 || typeof ancestor["utcDate"] === "string") && (ancestor["initials"] === void 0 || typeof ancestor["initials"] === "string") && typeof ancestor["outerWrapperCount"] === "number" && Number.isSafeInteger(ancestor["outerWrapperCount"]) && ancestor["outerWrapperCount"] >= 0);
|
|
795
802
|
const readTrackedChangeMarkAttrs = (mark) => {
|
|
796
803
|
const attrs = attrsRecord(mark.attrs);
|
|
797
804
|
const issues = [];
|
|
@@ -801,6 +808,16 @@ const readTrackedChangeMarkAttrs = (mark) => {
|
|
|
801
808
|
optionalString(attrs, "date", `${mark.type.name}.attrs.date`, issues);
|
|
802
809
|
optionalString(attrs, "utcDate", `${mark.type.name}.attrs.utcDate`, issues);
|
|
803
810
|
optionalString(attrs, "initials", `${mark.type.name}.attrs.initials`, issues);
|
|
811
|
+
const outerWrapperCount = attrs["_docxOuterWrapperCount"];
|
|
812
|
+
if (outerWrapperCount !== void 0 && outerWrapperCount !== null && (typeof outerWrapperCount !== "number" || !Number.isSafeInteger(outerWrapperCount) || outerWrapperCount < 0)) issues.push({
|
|
813
|
+
path: `${mark.type.name}.attrs._docxOuterWrapperCount`,
|
|
814
|
+
message: "Expected a non-negative safe integer."
|
|
815
|
+
});
|
|
816
|
+
const ancestors = attrs["_docxRevisionAncestors"];
|
|
817
|
+
if (ancestors !== void 0 && ancestors !== null && !isTrackedRevisionAncestorArray(ancestors)) issues.push({
|
|
818
|
+
path: `${mark.type.name}.attrs._docxRevisionAncestors`,
|
|
819
|
+
message: "Expected a non-empty array of valid revision ancestors."
|
|
820
|
+
});
|
|
804
821
|
optionalOneOf(attrs, "moveKind", `${mark.type.name}.attrs.moveKind`, issues, TRACKED_CHANGE_MOVE_KINDS);
|
|
805
822
|
optionalOneOf(attrs, "provenance", `${mark.type.name}.attrs.provenance`, issues, TRACKED_CHANGE_PROVENANCE_VALUES);
|
|
806
823
|
optionalString(attrs, "suggestionId", `${mark.type.name}.attrs.suggestionId`, issues);
|
|
@@ -2700,4 +2717,4 @@ const optionalEmptyHyperlinkArray = (value, issues) => {
|
|
|
2700
2717
|
}
|
|
2701
2718
|
};
|
|
2702
2719
|
//#endregion
|
|
2703
|
-
export { expectBlockSdtAttrs, expectCharacterSpacingMarkAttrs, expectCharacterStyleMarkAttrs, expectCommentMarkAttrs, expectEmphasisMarkAttrs, expectFieldAttrs, expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectFootnoteRefMarkAttrs, expectHardBreakAttrs, expectHighlightMarkAttrs, expectHyperlinkMarkAttrs, expectImageAttrs, expectInlineWrapperMarkAttrs, expectLanguageMarkAttrs, expectMathAttrs, expectPageBreakRunAttrs, expectParagraphAttrs, expectPreservedBlockAttrs, expectPreservedXmlAttrs, expectRunFormattingOverrideMarkAttrs, expectRunIdentityMarkAttrs, expectRunPropertyChangeMarkAttrs, expectRunShadingMarkAttrs, expectSdtAttrs, expectShapeAttrs, expectStrikeMarkAttrs, expectSymbolAttrs, expectTabAttrs, expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, expectTextBoxAttrs, expectTextColorMarkAttrs, expectTextEffectMarkAttrs, expectTrackedChangeMarkAttrs, expectUnderlineMarkAttrs, mergeImageAttrs, mergeParagraphAttrs, mergeTableAttrs, mergeTableCellAttrs, mergeTableRowAttrs, readBlockSdtAttrs, readCharacterSpacingMarkAttrs, readCharacterStyleMarkAttrs, readCommentMarkAttrs, readEmphasisMarkAttrs, readFieldAttrs, readFontFamilyMarkAttrs, readFontSizeMarkAttrs, readFootnoteRefMarkAttrs, readHardBreakAttrs, readHighlightMarkAttrs, readHyperlinkMarkAttrs, readImageAttrs, readInlineWrapperMarkAttrs, readInlineWrapperStack, readLanguageMarkAttrs, readMathAttrs, readPageBreakRunAttrs, readParagraphAttrs, readPreservedBlockAttrs, readPreservedXmlAttrs, readRunFormattingOverrideMarkAttrs, readRunIdentityMarkAttrs, readRunPropertyChangeMarkAttrs, readRunShadingMarkAttrs, readSdtAttrs, readShapeAttrs, readStrikeMarkAttrs, readSymbolAttrs, readTabAttrs, readTableAttrs, readTableCellAttrs, readTableRowAttrs, readTextBoxAttrs, readTextColorMarkAttrs, readTextEffectMarkAttrs, readTrackedChangeMarkAttrs, readUnderlineMarkAttrs };
|
|
2720
|
+
export { expectBlockSdtAttrs, expectCharacterSpacingMarkAttrs, expectCharacterStyleMarkAttrs, expectCommentMarkAttrs, expectEmphasisMarkAttrs, expectFieldAttrs, expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectFootnoteRefMarkAttrs, expectHardBreakAttrs, expectHighlightMarkAttrs, expectHyperlinkMarkAttrs, expectImageAttrs, expectInlineWrapperMarkAttrs, expectLanguageMarkAttrs, expectMathAttrs, expectPageBreakRunAttrs, expectParagraphAttrs, expectPreservedBlockAttrs, expectPreservedXmlAttrs, expectRunFormattingOverrideMarkAttrs, expectRunIdentityMarkAttrs, expectRunPropertyChangeMarkAttrs, expectRunShadingMarkAttrs, expectSdtAttrs, expectShapeAttrs, expectStrikeMarkAttrs, expectSymbolAttrs, expectTabAttrs, expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, expectTextBoxAttrs, expectTextColorMarkAttrs, expectTextEffectMarkAttrs, expectTrackedChangeMarkAttrs, expectUnderlineMarkAttrs, isTrackedRevisionAncestorArray, mergeImageAttrs, mergeParagraphAttrs, mergeTableAttrs, mergeTableCellAttrs, mergeTableRowAttrs, readBlockSdtAttrs, readCharacterSpacingMarkAttrs, readCharacterStyleMarkAttrs, readCommentMarkAttrs, readEmphasisMarkAttrs, readFieldAttrs, readFontFamilyMarkAttrs, readFontSizeMarkAttrs, readFootnoteRefMarkAttrs, readHardBreakAttrs, readHighlightMarkAttrs, readHyperlinkMarkAttrs, readImageAttrs, readInlineWrapperMarkAttrs, readInlineWrapperStack, readLanguageMarkAttrs, readMathAttrs, readPageBreakRunAttrs, readParagraphAttrs, readPreservedBlockAttrs, readPreservedXmlAttrs, readRunFormattingOverrideMarkAttrs, readRunIdentityMarkAttrs, readRunPropertyChangeMarkAttrs, readRunShadingMarkAttrs, readSdtAttrs, readShapeAttrs, readStrikeMarkAttrs, readSymbolAttrs, readTabAttrs, readTableAttrs, readTableCellAttrs, readTableRowAttrs, readTextBoxAttrs, readTextColorMarkAttrs, readTextEffectMarkAttrs, readTrackedChangeMarkAttrs, readUnderlineMarkAttrs };
|
|
@@ -2,7 +2,7 @@ import { sameStatedParagraphNumbering as sameStatedParagraphNumbering$1 } from "
|
|
|
2
2
|
import { joinProseParagraphsWithRightPropertySource } from "../../docx/paragraphPropertySource.js";
|
|
3
3
|
import { appendHeadlessInlineResolution } from "../../internal/headlessRevisionResolution.js";
|
|
4
4
|
import { stateAllowsHeadlessRevisionResolution } from "../../internal/headlessRevisionResolutionGuard.js";
|
|
5
|
-
import { expectParagraphAttrs, expectRunPropertyChangeMarkAttrs } from "../attrs/index.js";
|
|
5
|
+
import { expectParagraphAttrs, expectRunPropertyChangeMarkAttrs, expectTrackedChangeMarkAttrs } from "../attrs/index.js";
|
|
6
6
|
import { addedBreakCarrierBefore, finalParagraphsOf, paragraphEndsItsContainer } from "../containerFinalParagraph.js";
|
|
7
7
|
import { resolveParagraphDefaultTextFormatting } from "../conversion/toProseDoc.js";
|
|
8
8
|
import { markChangedParagraphRanges, markStructuralChange, markTrackedSectionEndpointRemoval } from "../extensions/features/ParagraphChangeTrackerExtension.js";
|
|
@@ -19,6 +19,7 @@ import { reconcileTableGridAfterColumnRemoval } from "../tableGridMutation.js";
|
|
|
19
19
|
import { holdsNoContent } from "../zeroWidthAnchors.js";
|
|
20
20
|
import { hasSerializableParagraphPropertyChange, paragraphPropertiesSnapshot, paragraphRejectAttrPatch, paragraphRejectOriginalFormatting, removeParagraphPropertyChanges, sectionRejectProperties, tableCellRejectAttrPatch, tablePropertyExceptionsRejectAttrPatch, tableRejectAttrPatch, tableRowRejectAttrPatch } from "./propertyChangeScope.js";
|
|
21
21
|
import { hasMatchingCollapsedTableCellMerge, resolveCollapsedTableCellMerge, resolveVisibleTableCellMerge } from "./tableCellMergeResolution.js";
|
|
22
|
+
import { panic } from "better-result";
|
|
22
23
|
import { PARAGRAPH_MARK_CHANGE_KINDS, REVIEW_CARRIERS } from "@stll/docx-core/model";
|
|
23
24
|
import { TableMap, removeRow } from "prosemirror-tables";
|
|
24
25
|
import { Mapping } from "prosemirror-transform";
|
|
@@ -55,6 +56,33 @@ function removeCommentMark(commentId) {
|
|
|
55
56
|
return true;
|
|
56
57
|
};
|
|
57
58
|
}
|
|
59
|
+
const revisionLayerOf = (mark) => {
|
|
60
|
+
const attrs = expectTrackedChangeMarkAttrs(mark);
|
|
61
|
+
let type;
|
|
62
|
+
if (mark.type.name === "insertion") type = attrs.moveKind === "moveTo" ? "moveTo" : "insertion";
|
|
63
|
+
else type = attrs.moveKind === "moveFrom" ? "moveFrom" : "deletion";
|
|
64
|
+
return {
|
|
65
|
+
type,
|
|
66
|
+
revisionId: attrs.revisionId,
|
|
67
|
+
author: attrs.author,
|
|
68
|
+
...attrs.date ? { date: attrs.date } : {},
|
|
69
|
+
...attrs.utcDate ? { utcDate: attrs.utcDate } : {},
|
|
70
|
+
...attrs.initials ? { initials: attrs.initials } : {},
|
|
71
|
+
outerWrapperCount: attrs._docxOuterWrapperCount ?? 0
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
const revisionLayerRemovesContent = (layer, mode) => mode === "accept" ? layer.type === "deletion" || layer.type === "moveFrom" : layer.type === "insertion" || layer.type === "moveTo";
|
|
75
|
+
const containsNestedRevision = (doc) => {
|
|
76
|
+
let found = false;
|
|
77
|
+
doc.descendants((node) => {
|
|
78
|
+
if (found) return false;
|
|
79
|
+
for (const mark of node.marks) if ((mark.type.name === "insertion" || mark.type.name === "deletion") && (expectTrackedChangeMarkAttrs(mark)._docxRevisionAncestors?.length ?? 0) > 0) {
|
|
80
|
+
found = true;
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return found;
|
|
85
|
+
};
|
|
58
86
|
/**
|
|
59
87
|
* Resolve a tracked change: accept or reject.
|
|
60
88
|
* - Accept: keep insertions (remove mark), delete deletions (remove text)
|
|
@@ -76,7 +104,7 @@ function resolveChange(from, to, mode, revisionIds, execution = "legacy") {
|
|
|
76
104
|
const removeType = mode === "accept" ? deletionType : insertionType;
|
|
77
105
|
const revisionSet = revisionIds === void 0 ? null : new Set(revisionIds);
|
|
78
106
|
const removeKeptMarksInBulk = revisionSet === null && keepType !== void 0;
|
|
79
|
-
const canBulkInlineResolution = execution === "headless-bulk-inline" && revisionSet === null && from === 0 && to === state.doc.content.size && stateAllowsHeadlessRevisionResolution(state);
|
|
107
|
+
const canBulkInlineResolution = execution === "headless-bulk-inline" && revisionSet === null && from === 0 && to === state.doc.content.size && stateAllowsHeadlessRevisionResolution(state) && !containsNestedRevision(state.doc);
|
|
80
108
|
const matchesRevision = (mark) => revisionSet === null || typeof mark.attrs["revisionId"] === "number" && revisionSet.has(mark.attrs["revisionId"]);
|
|
81
109
|
if (dispatch) {
|
|
82
110
|
const tr = state.tr;
|
|
@@ -189,6 +217,36 @@ function resolveChange(from, to, mode, revisionIds, execution = "legacy") {
|
|
|
189
217
|
revisionSet,
|
|
190
218
|
styleResolver
|
|
191
219
|
});
|
|
220
|
+
const nestedMark = node.marks.find((mark) => (mark.type === insertionType || mark.type === deletionType) && (expectTrackedChangeMarkAttrs(mark)._docxRevisionAncestors?.length ?? 0) > 0);
|
|
221
|
+
if (nestedMark) {
|
|
222
|
+
const path = [...expectTrackedChangeMarkAttrs(nestedMark)._docxRevisionAncestors ?? [], revisionLayerOf(nestedMark)];
|
|
223
|
+
const selected = (layer) => revisionSet === null || revisionSet.has(layer.revisionId);
|
|
224
|
+
if (path.some(selected)) if (path.some((layer) => selected(layer) && revisionLayerRemovesContent(layer, mode))) deleteRanges.push({
|
|
225
|
+
from: rangeFrom,
|
|
226
|
+
to: rangeTo
|
|
227
|
+
});
|
|
228
|
+
else {
|
|
229
|
+
tr.removeMark(rangeFrom, rangeTo, nestedMark);
|
|
230
|
+
const remaining = path.filter((layer) => !selected(layer));
|
|
231
|
+
const active = remaining.at(-1);
|
|
232
|
+
if (active) {
|
|
233
|
+
const type = active.type === "insertion" || active.type === "moveTo" ? insertionType : deletionType;
|
|
234
|
+
if (!type) panic("A nested tracked revision has no editor mark type");
|
|
235
|
+
tr.addMark(rangeFrom, rangeTo, type.create({
|
|
236
|
+
revisionId: active.revisionId,
|
|
237
|
+
author: active.author,
|
|
238
|
+
date: active.date ?? null,
|
|
239
|
+
utcDate: active.utcDate ?? null,
|
|
240
|
+
initials: active.initials ?? null,
|
|
241
|
+
moveKind: active.type === "moveTo" || active.type === "moveFrom" ? active.type : null,
|
|
242
|
+
_historicalFormatting: active.type === "deletion" || active.type === "moveFrom" ? true : null,
|
|
243
|
+
_docxOuterWrapperCount: active.outerWrapperCount,
|
|
244
|
+
_docxRevisionAncestors: remaining.length > 1 ? remaining.slice(0, -1) : null
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
192
250
|
if (removesNode) deleteRanges.push({
|
|
193
251
|
from: rangeFrom,
|
|
194
252
|
to: rangeTo
|
|
@@ -794,7 +852,7 @@ function findAIEditRevisionRange(state, revisionIds) {
|
|
|
794
852
|
includeRange(pos, pos + node.nodeSize);
|
|
795
853
|
break;
|
|
796
854
|
}
|
|
797
|
-
if ((mark.type === insertionType || mark.type === deletionType) &&
|
|
855
|
+
if ((mark.type === insertionType || mark.type === deletionType) && (idSet.has(expectTrackedChangeMarkAttrs(mark).revisionId) || (expectTrackedChangeMarkAttrs(mark)._docxRevisionAncestors ?? []).some((ancestor) => idSet.has(ancestor.revisionId)))) {
|
|
798
856
|
includeRange(pos, pos + node.nodeSize);
|
|
799
857
|
break;
|
|
800
858
|
}
|
|
@@ -1162,12 +1162,10 @@ const nestInlineWrappers = (stack, content) => {
|
|
|
1162
1162
|
* The emitted sequence with each wrapper group nested back into the wrappers
|
|
1163
1163
|
* `toProseDoc` lifted off it.
|
|
1164
1164
|
*
|
|
1165
|
-
*
|
|
1166
|
-
*
|
|
1167
|
-
*
|
|
1168
|
-
*
|
|
1169
|
-
* per wrapper. So a revision in the group keeps its place and takes the nest
|
|
1170
|
-
* inside it, and the items around it share a nest of their own.
|
|
1165
|
+
* A revision may be inside some wrapper layers and outside others. Its mark
|
|
1166
|
+
* records how many layers preceded it in the source, since a ProseMirror mark
|
|
1167
|
+
* set cannot express that order. Adjacent items with the same outer layers
|
|
1168
|
+
* share one wrapper rather than splitting it around each revision.
|
|
1171
1169
|
*
|
|
1172
1170
|
* A group with nothing in it writes no wrapper: an authored wrapper whose
|
|
1173
1171
|
* content was deleted or rejected is gone, and an empty one says nothing.
|
|
@@ -1185,10 +1183,83 @@ const nestInsideHyperlink = (item, stack, hyperlinkIndex, indices) => {
|
|
|
1185
1183
|
}
|
|
1186
1184
|
return false;
|
|
1187
1185
|
};
|
|
1188
|
-
const
|
|
1186
|
+
const nestRevisionAwareInlineWrappers = (items, stack, revisionOuterWrapperCounts, layerIndex = 0) => {
|
|
1187
|
+
if (layerIndex === stack.length) return [...items];
|
|
1188
|
+
const nested = [];
|
|
1189
|
+
let pending = [];
|
|
1190
|
+
const closeLayer = () => {
|
|
1191
|
+
if (pending.length === 0) return;
|
|
1192
|
+
const layer = stack[layerIndex];
|
|
1193
|
+
if (layer === void 0) panic("An inline wrapper layer disappeared during reconstruction");
|
|
1194
|
+
nested.push(inlineWrapperMember(layer, nestRevisionAwareInlineWrappers(pending, stack, revisionOuterWrapperCounts, layerIndex + 1)));
|
|
1195
|
+
pending = [];
|
|
1196
|
+
};
|
|
1197
|
+
for (const item of items) {
|
|
1198
|
+
if (!isRevisionWrapper(item) || (revisionOuterWrapperCounts.get(item) ?? 0) > layerIndex) {
|
|
1199
|
+
pending.push(item);
|
|
1200
|
+
continue;
|
|
1201
|
+
}
|
|
1202
|
+
closeLayer();
|
|
1203
|
+
item.content = [nestInlineWrappers(stack.slice(layerIndex), item.content)];
|
|
1204
|
+
nested.push(item);
|
|
1205
|
+
}
|
|
1206
|
+
closeLayer();
|
|
1207
|
+
return nested;
|
|
1208
|
+
};
|
|
1209
|
+
const sameRevisionLayer = (left, right) => left.type === right.type && left.revisionId === right.revisionId && left.author === right.author && left.date === right.date && left.utcDate === right.utcDate && left.initials === right.initials && left.outerWrapperCount === right.outerWrapperCount;
|
|
1210
|
+
const revisionInfoFromLayer = (layer) => ({
|
|
1211
|
+
id: layer.revisionId,
|
|
1212
|
+
author: layer.author,
|
|
1213
|
+
...layer.date ? { date: layer.date } : {},
|
|
1214
|
+
...layer.utcDate ? { utcDate: {
|
|
1215
|
+
attribute: DATE_UTC_ATTRIBUTE,
|
|
1216
|
+
value: layer.utcDate
|
|
1217
|
+
} } : {},
|
|
1218
|
+
...layer.initials ? { initials: layer.initials } : {}
|
|
1219
|
+
});
|
|
1220
|
+
const nestRevisionAncestors = (items, ancestorsByWrapper, revisionOuterWrapperCounts) => {
|
|
1221
|
+
const nested = [];
|
|
1222
|
+
const open = [];
|
|
1223
|
+
for (const item of items) {
|
|
1224
|
+
if (!isRevisionWrapper(item)) {
|
|
1225
|
+
open.length = 0;
|
|
1226
|
+
nested.push(item);
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
const ownLayer = {
|
|
1230
|
+
type: item.type,
|
|
1231
|
+
revisionId: item.info.id,
|
|
1232
|
+
author: item.info.author,
|
|
1233
|
+
...item.info.date ? { date: item.info.date } : {},
|
|
1234
|
+
...item.info.utcDate ? { utcDate: item.info.utcDate.value } : {},
|
|
1235
|
+
...item.info.initials ? { initials: item.info.initials } : {},
|
|
1236
|
+
outerWrapperCount: revisionOuterWrapperCounts.get(item) ?? 0
|
|
1237
|
+
};
|
|
1238
|
+
const path = [...ancestorsByWrapper.get(item) ?? [], ownLayer];
|
|
1239
|
+
let shared = 0;
|
|
1240
|
+
while (shared < open.length && path[shared] && sameRevisionLayer(open[shared].layer, path[shared])) shared += 1;
|
|
1241
|
+
open.length = shared;
|
|
1242
|
+
for (const layer of path.slice(shared)) {
|
|
1243
|
+
const wrapper = createTrackedRunWrapper(layer.type, revisionInfoFromLayer(layer));
|
|
1244
|
+
revisionOuterWrapperCounts.set(wrapper, layer.outerWrapperCount);
|
|
1245
|
+
const parent = open.at(-1)?.wrapper;
|
|
1246
|
+
if (parent) parent.content.push(wrapper);
|
|
1247
|
+
else nested.push(wrapper);
|
|
1248
|
+
open.push({
|
|
1249
|
+
layer,
|
|
1250
|
+
wrapper
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
const owner = open.at(-1)?.wrapper;
|
|
1254
|
+
if (!owner) panic("A tracked revision path has no owner");
|
|
1255
|
+
owner.content.push(...item.content);
|
|
1256
|
+
}
|
|
1257
|
+
return nested;
|
|
1258
|
+
};
|
|
1259
|
+
const nestInlineWrapperGroups = (items, groups, hyperlinkIndices, revisionOuterWrapperCounts, ancestorsByWrapper) => {
|
|
1189
1260
|
const nested = [];
|
|
1190
1261
|
for (const [index, group] of groups.entries()) {
|
|
1191
|
-
const slice = items.slice(group.start, groups[index + 1]?.start ?? items.length);
|
|
1262
|
+
const slice = nestRevisionAncestors(items.slice(group.start, groups[index + 1]?.start ?? items.length), ancestorsByWrapper, revisionOuterWrapperCounts);
|
|
1192
1263
|
if (group.stack.length === 0) {
|
|
1193
1264
|
nested.push(...slice);
|
|
1194
1265
|
continue;
|
|
@@ -1205,24 +1276,18 @@ const nestInlineWrapperGroups = (items, groups, hyperlinkIndices) => {
|
|
|
1205
1276
|
nested.push(...slice);
|
|
1206
1277
|
continue;
|
|
1207
1278
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
pending.push(item);
|
|
1217
|
-
continue;
|
|
1218
|
-
}
|
|
1219
|
-
closeNest();
|
|
1220
|
-
if (item.content.length > 0) item.content = [nestInlineWrappers(outerStack, item.content)];
|
|
1221
|
-
nested.push(item);
|
|
1279
|
+
nested.push(...nestRevisionAwareInlineWrappers(slice, outerStack, revisionOuterWrapperCounts));
|
|
1280
|
+
}
|
|
1281
|
+
const coalesced = [];
|
|
1282
|
+
for (const item of nested) {
|
|
1283
|
+
const previous = coalesced.at(-1);
|
|
1284
|
+
if (previous && isRevisionWrapper(previous) && isRevisionWrapper(item) && previous.type === item.type && JSON.stringify(previous.info) === JSON.stringify(item.info) && revisionOuterWrapperCounts.get(previous) === revisionOuterWrapperCounts.get(item)) {
|
|
1285
|
+
previous.content.push(...item.content);
|
|
1286
|
+
continue;
|
|
1222
1287
|
}
|
|
1223
|
-
|
|
1288
|
+
coalesced.push(item);
|
|
1224
1289
|
}
|
|
1225
|
-
return
|
|
1290
|
+
return coalesced;
|
|
1226
1291
|
};
|
|
1227
1292
|
function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, textBoxAnchorMarkers, skipLeadingRenderedPageBreak = false, inheritedFormattingOverride) {
|
|
1228
1293
|
const content = [];
|
|
@@ -1252,6 +1317,8 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
1252
1317
|
let currentTrackedChange;
|
|
1253
1318
|
const sourceRunOwners = /* @__PURE__ */ new WeakMap();
|
|
1254
1319
|
const sourceHyperlinkIndices = /* @__PURE__ */ new WeakMap();
|
|
1320
|
+
const revisionOuterWrapperCounts = /* @__PURE__ */ new WeakMap();
|
|
1321
|
+
const revisionAncestorsByWrapper = /* @__PURE__ */ new WeakMap();
|
|
1255
1322
|
const openedComments = /* @__PURE__ */ new Set();
|
|
1256
1323
|
const createIndexedHyperlink = (mark) => {
|
|
1257
1324
|
const hyperlink = createHyperlink(mark);
|
|
@@ -1416,16 +1483,13 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
1416
1483
|
} } : {},
|
|
1417
1484
|
...changeAttrs.initials ? { initials: changeAttrs.initials } : {}
|
|
1418
1485
|
};
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
info,
|
|
1427
|
-
content: [anchoredContent]
|
|
1428
|
-
});
|
|
1486
|
+
let type;
|
|
1487
|
+
if (insertionMark) type = changeAttrs.moveKind === "moveTo" ? "moveTo" : "insertion";
|
|
1488
|
+
else type = changeAttrs.moveKind === "moveFrom" ? "moveFrom" : "deletion";
|
|
1489
|
+
const wrapper = createTrackedRunWrapper(type, info, anchoredContent);
|
|
1490
|
+
revisionOuterWrapperCounts.set(wrapper, changeAttrs._docxOuterWrapperCount ?? inlineWrapperStackOf(node).length);
|
|
1491
|
+
revisionAncestorsByWrapper.set(wrapper, changeAttrs._docxRevisionAncestors ?? []);
|
|
1492
|
+
content.push(wrapper);
|
|
1429
1493
|
return;
|
|
1430
1494
|
}
|
|
1431
1495
|
if (insertionMark || deletionMark) {
|
|
@@ -1447,12 +1511,15 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
1447
1511
|
let type;
|
|
1448
1512
|
if (insertionMark) type = changeAttrs.moveKind === "moveTo" ? "moveTo" : "insertion";
|
|
1449
1513
|
else type = changeAttrs.moveKind === "moveFrom" ? "moveFrom" : "deletion";
|
|
1450
|
-
const
|
|
1514
|
+
const outerWrapperCount = changeAttrs._docxOuterWrapperCount ?? inlineWrapperStackOf(node).length;
|
|
1515
|
+
const trackedChangeKey = `${type}:${JSON.stringify(info)}:${outerWrapperCount}:${JSON.stringify(changeAttrs._docxRevisionAncestors ?? [])}`;
|
|
1451
1516
|
if (linkMark) {
|
|
1452
1517
|
const linkKey = getLinkKey(linkMark);
|
|
1453
1518
|
if (!currentTrackedChange || currentTrackedChange.type !== "hyperlink" || currentTrackedChange.key !== trackedChangeKey || currentTrackedChange.hyperlinkKey !== linkKey) {
|
|
1454
1519
|
const hyperlink = createIndexedHyperlink(linkMark);
|
|
1455
1520
|
const wrapper = createTrackedRunWrapper(type, info, hyperlink);
|
|
1521
|
+
revisionOuterWrapperCounts.set(wrapper, outerWrapperCount);
|
|
1522
|
+
revisionAncestorsByWrapper.set(wrapper, changeAttrs._docxRevisionAncestors ?? []);
|
|
1456
1523
|
content.push(wrapper);
|
|
1457
1524
|
currentTrackedChange = {
|
|
1458
1525
|
type: "hyperlink",
|
|
@@ -1486,6 +1553,8 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
1486
1553
|
}
|
|
1487
1554
|
if (!currentTrackedChange || currentTrackedChange.type !== "direct" || currentTrackedChange.key !== trackedChangeKey) {
|
|
1488
1555
|
const wrapper = createTrackedRunWrapper(type, info);
|
|
1556
|
+
revisionOuterWrapperCounts.set(wrapper, outerWrapperCount);
|
|
1557
|
+
revisionAncestorsByWrapper.set(wrapper, changeAttrs._docxRevisionAncestors ?? []);
|
|
1489
1558
|
content.push(wrapper);
|
|
1490
1559
|
currentTrackedChange = {
|
|
1491
1560
|
type: "direct",
|
|
@@ -1640,7 +1709,7 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
1640
1709
|
type: "commentRangeEnd",
|
|
1641
1710
|
id: commentId
|
|
1642
1711
|
});
|
|
1643
|
-
return nestInlineWrapperGroups(content, wrapperGroups, sourceHyperlinkIndices);
|
|
1712
|
+
return nestInlineWrapperGroups(content, wrapperGroups, sourceHyperlinkIndices, revisionOuterWrapperCounts, revisionAncestorsByWrapper);
|
|
1644
1713
|
}
|
|
1645
1714
|
function createTrackedChangeRun({ baseParagraphFormatting, inheritedFormatting, marks, node, paragraphMarkFormatting, paragraphMarkPrecedesStyle, styleResolver }) {
|
|
1646
1715
|
const formattingContext = {
|
|
@@ -2001,7 +2070,7 @@ function restoreRunRecord(run, marks) {
|
|
|
2001
2070
|
}
|
|
2002
2071
|
const identityMark = marks.find((mark) => mark.type.name === RUN_IDENTITY_MARK_NAME);
|
|
2003
2072
|
if (!identityMark) return;
|
|
2004
|
-
const { preservedAttributes, preserved } = expectRunIdentityMarkAttrs(identityMark);
|
|
2073
|
+
const { preservedAttributes, preserved, emptyFormatting } = expectRunIdentityMarkAttrs(identityMark);
|
|
2005
2074
|
if (preservedAttributes && preservedAttributes.length > 0) run.preservedAttributes = preservedAttributes.map(({ namespace, name, value }) => namespace === void 0 ? {
|
|
2006
2075
|
name,
|
|
2007
2076
|
value
|
|
@@ -2014,6 +2083,7 @@ function restoreRunRecord(run, marks) {
|
|
|
2014
2083
|
...run.formatting,
|
|
2015
2084
|
preserved
|
|
2016
2085
|
};
|
|
2086
|
+
if (emptyFormatting && run.formatting === void 0) run.formatting = {};
|
|
2017
2087
|
}
|
|
2018
2088
|
function getRunFormattingFromMarks(marks, options) {
|
|
2019
2089
|
if (!marks || marks.length === 0 && !options) return;
|
|
@@ -16,7 +16,7 @@ import { mergeParagraphFormatting, mergeParagraphTabStops } from "../../utils/pa
|
|
|
16
16
|
import { tableOfContentsStyleLevel } from "../../utils/tableOfContentsStyle.js";
|
|
17
17
|
import { mergeTextFormatting } from "../../utils/textFormattingMerge.js";
|
|
18
18
|
import { emuToPixels, emuToStrokePixels } from "../../utils/units.js";
|
|
19
|
-
import { expectInlineWrapperMarkAttrs } from "../attrs/index.js";
|
|
19
|
+
import { expectInlineWrapperMarkAttrs, expectTrackedChangeMarkAttrs } from "../attrs/index.js";
|
|
20
20
|
import { authoredTransformAttrs } from "../authoredTransformAttrs.js";
|
|
21
21
|
import { setAutospacingBaseValue } from "../autospacingBase.js";
|
|
22
22
|
import { planEmptyRanges } from "../emptyRangeAnchor.js";
|
|
@@ -77,16 +77,17 @@ const createTextBoxGroupIdFactory = () => {
|
|
|
77
77
|
* paragraph's and the hyperlink's — so the next payload field is added once
|
|
78
78
|
* rather than to whichever site the author happened to open.
|
|
79
79
|
*
|
|
80
|
-
* Minted when the run holds a page break
|
|
81
|
-
* rejoined into one `w:r` on save), an
|
|
82
|
-
*
|
|
80
|
+
* Minted when the run holds a page break or multiple content items including
|
|
81
|
+
* an inline atom (the leaves have to be rejoined into one `w:r` on save), an
|
|
82
|
+
* attribute remainder, a `w:rPr` sink, or an authored empty `w:rPr`.
|
|
83
83
|
*/
|
|
84
84
|
const runIdentityMark = (run, nextRunIdentityId) => {
|
|
85
85
|
const payload = {
|
|
86
86
|
preservedAttributes: run.preservedAttributes,
|
|
87
|
-
preserved: run.formatting?.preserved
|
|
87
|
+
preserved: run.formatting?.preserved,
|
|
88
|
+
emptyFormatting: run.formatting !== void 0 && Object.keys(run.formatting).length === 0 ? true : void 0
|
|
88
89
|
};
|
|
89
|
-
if (!runHasPageBreakContent(run) && !hasRunIdentityPayload(payload)) return null;
|
|
90
|
+
if (!runHasPageBreakContent(run) && !runHasMixedContent(run) && !hasRunIdentityPayload(payload)) return null;
|
|
90
91
|
return schema.mark(RUN_IDENTITY_MARK_NAME, runIdentityAttrs(nextRunIdentityId(), payload));
|
|
91
92
|
};
|
|
92
93
|
/** Keep imported hyperlink identity unique across every nested conversion scope. */
|
|
@@ -396,8 +397,8 @@ function convertParagraph(paragraph, styleResolver, context, extraRunFormatting,
|
|
|
396
397
|
type: "direct"
|
|
397
398
|
}], { ordinaryFormatting: ordinaryDefaultRunFormatting });
|
|
398
399
|
const defaultRunFormatting = defaultToggleCascade.formatting;
|
|
399
|
-
if (extraRunFormatting !== void 0) {
|
|
400
|
-
attrs._tableRunFormatting = extraRunFormatting;
|
|
400
|
+
if (extraRunFormatting !== void 0 || paragraph.content.length > 0) {
|
|
401
|
+
if (extraRunFormatting !== void 0) attrs._tableRunFormatting = extraRunFormatting;
|
|
401
402
|
if (defaultRunFormatting) attrs.defaultTextFormatting = defaultRunFormatting;
|
|
402
403
|
else delete attrs.defaultTextFormatting;
|
|
403
404
|
}
|
|
@@ -606,13 +607,21 @@ function anchorPointComment(nodes, commentId) {
|
|
|
606
607
|
* and it is what lets the save leg hoist a revision covering all of them back
|
|
607
608
|
* around the control instead of losing it.
|
|
608
609
|
*/
|
|
609
|
-
const withTrackedRunMark = (node, mark) => {
|
|
610
|
-
if (node.marks.some(({ type }) => type.name === "insertion" || type.name === "deletion")) return node;
|
|
610
|
+
const withTrackedRunMark = (node, mark, ancestor) => {
|
|
611
611
|
if (node.type.name === "sdt") {
|
|
612
612
|
const marked = [];
|
|
613
|
-
for (let index = 0; index < node.childCount; index += 1) marked.push(withTrackedRunMark(node.child(index), mark));
|
|
613
|
+
for (let index = 0; index < node.childCount; index += 1) marked.push(withTrackedRunMark(node.child(index), mark, ancestor));
|
|
614
614
|
return recreateProseNodeWithParagraphPropertySource(node, { content: marked });
|
|
615
615
|
}
|
|
616
|
+
const nestedMark = node.marks.find(({ type }) => type.name === "insertion" || type.name === "deletion");
|
|
617
|
+
if (nestedMark) {
|
|
618
|
+
const nestedAttrs = expectTrackedChangeMarkAttrs(nestedMark);
|
|
619
|
+
const updatedMark = schema.mark(nestedMark.type.name, {
|
|
620
|
+
...nestedMark.attrs,
|
|
621
|
+
_docxRevisionAncestors: [ancestor, ...nestedAttrs._docxRevisionAncestors ?? []]
|
|
622
|
+
});
|
|
623
|
+
return node.mark(updatedMark.addToSet(node.marks));
|
|
624
|
+
}
|
|
616
625
|
if (trackedRunInlineAtomDisposition(node) === "outside-wrapper") panic(`Inline atom ${JSON.stringify(node.type.name)} cannot occur in a tracked-run wrapper`);
|
|
617
626
|
if (canCarryTrackedRunMark(node)) return node.mark(mark.addToSet(node.marks));
|
|
618
627
|
return node;
|
|
@@ -652,7 +661,7 @@ function convertTrackedChange(change, markType, nextHyperlinkInstanceIndex, next
|
|
|
652
661
|
} else if (item.type === "insertion" || item.type === "deletion" || item.type === "moveFrom" || item.type === "moveTo") {
|
|
653
662
|
const nestedMarkType = item.type === "insertion" || item.type === "moveTo" ? "insertion" : "deletion";
|
|
654
663
|
const nestedMoveKind = item.type === "moveFrom" || item.type === "moveTo" ? item.type : null;
|
|
655
|
-
itemNodes.push(...convertTrackedChange(item, nestedMarkType, nextHyperlinkInstanceIndex, nextRunIdentityId, runFormattingResolvers, styleResolver, nestedMoveKind, textBoxAnchors));
|
|
664
|
+
itemNodes.push(...convertTrackedChange(item, nestedMarkType, nextHyperlinkInstanceIndex, nextRunIdentityId, runFormattingResolvers, styleResolver, nestedMoveKind, textBoxAnchors, [...wrappedBy, ...stack]));
|
|
656
665
|
} else if (item.type === "inlineSdt") {
|
|
657
666
|
const sdtNode = convertInlineSdt(item, nextHyperlinkInstanceIndex, nextRunIdentityId, getTrackedRunFormatting, runFormattingResolvers, styleResolver, textBoxAnchors, [...wrappedBy, ...stack]);
|
|
658
667
|
if (sdtNode) itemNodes.push(sdtNode);
|
|
@@ -681,9 +690,19 @@ function convertTrackedChange(change, markType, nextHyperlinkInstanceIndex, next
|
|
|
681
690
|
utcDate: change.info.utcDate?.value ?? null,
|
|
682
691
|
initials: change.info.initials ?? null,
|
|
683
692
|
moveKind,
|
|
693
|
+
_docxOuterWrapperCount: wrappedBy.length,
|
|
684
694
|
...markType === "deletion" ? { _historicalFormatting: true } : {}
|
|
685
695
|
});
|
|
686
|
-
|
|
696
|
+
const ancestor = {
|
|
697
|
+
type: change.type,
|
|
698
|
+
revisionId: change.info.id,
|
|
699
|
+
author: change.info.author,
|
|
700
|
+
...change.info.date ? { date: change.info.date } : {},
|
|
701
|
+
...change.info.utcDate ? { utcDate: change.info.utcDate.value } : {},
|
|
702
|
+
...change.info.initials ? { initials: change.info.initials } : {},
|
|
703
|
+
outerWrapperCount: wrappedBy.length
|
|
704
|
+
};
|
|
705
|
+
return nodes.map((node) => withTrackedRunMark(node, mark, ancestor));
|
|
687
706
|
}
|
|
688
707
|
function paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOverlay) {
|
|
689
708
|
const formatting = paragraph.formatting;
|
|
@@ -1691,6 +1710,7 @@ function convertRun(run, resolvedStyleFormatting, nextRunIdentityId, styleResolv
|
|
|
1691
1710
|
return nodes;
|
|
1692
1711
|
}
|
|
1693
1712
|
const runHasPageBreakContent = (run) => run.content.some((content) => content.type === "break" && content.breakType === "page");
|
|
1713
|
+
const runHasMixedContent = (run) => run.content.length > 1 && run.content.some((content) => content.type !== "text");
|
|
1694
1714
|
/**
|
|
1695
1715
|
* Whether a field's result holds an explicit page break.
|
|
1696
1716
|
*
|
|
@@ -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"
|
|
@@ -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,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
|
|
@@ -289,4 +304,4 @@ type HyperlinkAttrs = {
|
|
|
289
304
|
_docxHyperlinkIndex?: number;
|
|
290
305
|
};
|
|
291
306
|
//#endregion
|
|
292
|
-
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,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,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/folio-core",
|
|
3
|
-
"version": "0.47.
|
|
3
|
+
"version": "0.47.6",
|
|
4
4
|
"description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"document-model",
|