@stll/folio-core 0.32.2 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-edits/__fixtures__/paragraphs.d.ts +17 -0
- package/dist/ai-edits/__fixtures__/paragraphs.js +33 -0
- package/dist/ai-edits/apply.d.ts +49 -3
- package/dist/ai-edits/apply.js +649 -85
- package/dist/ai-edits/blockRange.d.ts +7 -3
- package/dist/ai-edits/blockRange.js +14 -6
- package/dist/ai-edits/headless.d.ts +39 -5
- package/dist/ai-edits/headless.js +76 -17
- package/dist/ai-edits/index.d.ts +6 -6
- package/dist/ai-edits/index.js +3 -3
- package/dist/ai-edits/read.d.ts +4 -0
- package/dist/ai-edits/read.js +30 -6
- package/dist/ai-edits/scoped-reading.js +4 -3
- package/dist/ai-edits/snapshot.d.ts +42 -2
- package/dist/ai-edits/snapshot.js +127 -62
- package/dist/ai-edits/table-row-column-mutations.d.ts +24 -1
- package/dist/ai-edits/table-row-column-mutations.js +98 -6
- package/dist/ai-edits/table-targets.d.ts +12 -3
- package/dist/ai-edits/table-targets.js +15 -5
- package/dist/ai-edits/types.d.ts +219 -15
- package/dist/ai-edits/word-diff.d.ts +54 -11
- package/dist/ai-edits/word-diff.js +221 -61
- package/dist/compare/__fixtures__/body-sequence.d.ts +33 -0
- package/dist/compare/__fixtures__/body-sequence.js +67 -0
- package/dist/compare/__fixtures__/nested-table.d.ts +16 -0
- package/dist/compare/__fixtures__/nested-table.js +50 -0
- package/dist/compare/__fixtures__/numbered-list.d.ts +34 -0
- package/dist/compare/__fixtures__/numbered-list.js +85 -0
- package/dist/compare/column-alignment.d.ts +27 -0
- package/dist/compare/column-alignment.js +162 -0
- package/dist/compare/compare.d.ts +107 -0
- package/dist/compare/compare.js +406 -0
- package/dist/compare/formatting.d.ts +29 -0
- package/dist/compare/formatting.js +84 -0
- package/dist/compare/plan.d.ts +22 -0
- package/dist/compare/plan.js +1059 -0
- package/dist/compare/reproducible-package.d.ts +17 -0
- package/dist/compare/reproducible-package.js +30 -0
- package/dist/compare/scenario.d.ts +77 -0
- package/dist/compare/scenario.js +263 -0
- package/dist/compare/types.d.ts +282 -0
- package/dist/compare/types.js +32 -0
- package/dist/compare/verification.d.ts +55 -0
- package/dist/compare/verification.js +146 -0
- package/dist/compat/eigenpal.d.ts +9 -6
- package/dist/compat/eigenpal.js +6 -3
- package/dist/controller/layoutPipeline.d.ts +13 -0
- package/dist/controller/layoutPipeline.js +15 -1
- package/dist/display-list/build/buildContext.d.ts +39 -0
- package/dist/display-list/build/buildContext.js +31 -0
- package/dist/display-list/build/buildDisplayList.d.ts +79 -0
- package/dist/display-list/build/buildDisplayList.js +283 -0
- package/dist/display-list/build/colors.d.ts +20 -0
- package/dist/display-list/build/colors.js +136 -0
- package/dist/display-list/build/floatingImages.d.ts +24 -0
- package/dist/display-list/build/floatingImages.js +41 -0
- package/dist/display-list/build/fontTable.d.ts +31 -0
- package/dist/display-list/build/fontTable.js +97 -0
- package/dist/display-list/build/furniture.d.ts +86 -0
- package/dist/display-list/build/furniture.js +98 -0
- package/dist/display-list/build/glyphs.d.ts +55 -0
- package/dist/display-list/build/glyphs.js +103 -0
- package/dist/display-list/build/headerFooterPrimitives.d.ts +17 -0
- package/dist/display-list/build/headerFooterPrimitives.js +40 -0
- package/dist/display-list/build/imagePrimitives.d.ts +35 -0
- package/dist/display-list/build/imagePrimitives.js +264 -0
- package/dist/display-list/build/pageBorderPrimitives.d.ts +23 -0
- package/dist/display-list/build/pageBorderPrimitives.js +111 -0
- package/dist/display-list/build/pageFurniture.d.ts +35 -0
- package/dist/display-list/build/pageFurniture.js +141 -0
- package/dist/display-list/build/paragraphPrimitives.d.ts +19 -0
- package/dist/display-list/build/paragraphPrimitives.js +819 -0
- package/dist/display-list/build/regions.d.ts +46 -0
- package/dist/display-list/build/regions.js +56 -0
- package/dist/display-list/build/storyPrimitives.d.ts +29 -0
- package/dist/display-list/build/storyPrimitives.js +279 -0
- package/dist/display-list/build/strokes.d.ts +19 -0
- package/dist/display-list/build/strokes.js +78 -0
- package/dist/display-list/build/tablePrimitives.d.ts +38 -0
- package/dist/display-list/build/tablePrimitives.js +409 -0
- package/dist/display-list/build/textBoxPrimitives.d.ts +14 -0
- package/dist/display-list/build/textBoxPrimitives.js +129 -0
- package/dist/display-list/build/textDecorations.d.ts +29 -0
- package/dist/display-list/build/textDecorations.js +29 -0
- package/dist/display-list/build/unsupported.d.ts +57 -0
- package/dist/display-list/build/unsupported.js +0 -0
- package/dist/display-list/build/watermarkPrimitives.d.ts +15 -0
- package/dist/display-list/build/watermarkPrimitives.js +128 -0
- package/dist/display-list/dom/renderDisplayListToDom.d.ts +26 -0
- package/dist/display-list/dom/renderDisplayListToDom.js +620 -0
- package/dist/display-list/editor/displayListPagePainter.d.ts +22 -0
- package/dist/display-list/editor/displayListPagePainter.js +63 -0
- package/dist/display-list/editor/pageRenderer.d.ts +24 -0
- package/dist/display-list/editor/pageRenderer.js +36 -0
- package/dist/display-list/primitives.d.ts +95 -0
- package/dist/display-list/primitives.js +127 -0
- package/dist/display-list/types.d.ts +472 -0
- package/dist/display-list/types.js +0 -0
- package/dist/document-operations.d.ts +39 -8
- package/dist/document-operations.js +169 -16
- package/dist/document-stories.d.ts +16 -0
- package/dist/document-stories.js +48 -4
- package/dist/docx/packageParts.d.ts +35 -0
- package/dist/docx/packageParts.js +120 -0
- package/dist/docx/paragraphParser.js +22 -22
- package/dist/docx/paragraphTraversal.js +1 -1
- package/dist/docx/renderedPageBreakNormalization.js +3 -1
- package/dist/docx/revisionIdNormalization.d.ts +13 -0
- package/dist/docx/revisionIdNormalization.js +103 -0
- package/dist/docx/rezip.js +26 -17
- package/dist/docx/selectiveSave.js +2 -2
- package/dist/docx/serializer/commentSerializer.js +6 -1
- package/dist/docx/serializer/paragraphSerializer.js +33 -4
- package/dist/docx/serializer/runSerializer.js +11 -10
- package/dist/docx/serializer/tableSerializer.js +2 -1
- package/dist/docx/server/applyDocxXmlPatchProposal.js +1 -1
- package/dist/docx/server/createBilingualDocx.js +3 -2
- package/dist/docx/streamingXmlParser.d.ts +13 -1
- package/dist/docx/streamingXmlParser.js +49 -11
- package/dist/docx/unzip.d.ts +1 -2
- package/dist/docx/unzip.js +25 -6
- package/dist/docx/xmlParser.d.ts +3 -1
- package/dist/docx/xmlParser.js +33 -27
- package/dist/export-pdf.d.ts +43 -0
- package/dist/export-pdf.js +98 -0
- package/dist/fonts/headlessMeasure.d.ts +76 -0
- package/dist/fonts/headlessMeasure.js +0 -0
- package/dist/fonts/sfnt/parse.d.ts +51 -0
- package/dist/fonts/sfnt/parse.js +525 -0
- package/dist/fonts/sfnt/subset.d.ts +20 -0
- package/dist/fonts/sfnt/subset.js +350 -0
- package/dist/fonts/sfnt/tables.d.ts +88 -0
- package/dist/fonts/sfnt/tables.js +131 -0
- package/dist/fonts/sfnt/woff.d.ts +14 -0
- package/dist/fonts/sfnt/woff.js +163 -0
- package/dist/generated/text_shaper.js +324 -0
- package/dist/generated/text_shaper_bg.wasm +0 -0
- package/dist/headless-layout.d.ts +75 -0
- package/dist/headless-layout.js +350 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.js +6 -3
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +2 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +2 -0
- package/dist/layout-engine/measure/advanceComposition.d.ts +62 -0
- package/dist/layout-engine/measure/advanceComposition.js +148 -0
- package/dist/layout-engine/measure/cache.d.ts +2 -0
- package/dist/layout-engine/measure/measureContainer.js +13 -0
- package/dist/layout-engine/measure/measureParagraph.js +1 -2
- package/dist/layout-engine/measure/measureTypes.d.ts +12 -0
- package/dist/layout-engine/measure/tableInlinePlacement.d.ts +3 -2
- package/dist/layout-engine/measure/tableInlinePlacement.js +14 -1
- package/dist/layout-engine/tableIndentCompatibility.d.ts +5 -0
- package/dist/layout-engine/tableIndentCompatibility.js +15 -0
- package/dist/layout-engine/types.d.ts +8 -0
- package/dist/layout-painter/renderPage.d.ts +29 -1
- package/dist/layout-painter/renderPage.js +47 -19
- package/dist/layout-painter/renderParagraph.d.ts +21 -2
- package/dist/layout-painter/renderParagraph.js +1 -1
- package/dist/layout-painter/renderTable.js +37 -4
- package/dist/managers/editorShortcuts.d.ts +33 -1
- package/dist/managers/editorShortcuts.js +20 -1
- package/dist/markdown/renderRuns.js +7 -2
- package/dist/markdown/renderTable.js +5 -1
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/pdf/contentStream.d.ts +73 -0
- package/dist/pdf/contentStream.js +143 -0
- package/dist/pdf/fonts.d.ts +96 -0
- package/dist/pdf/fonts.js +593 -0
- package/dist/pdf/images.d.ts +28 -0
- package/dist/pdf/images.js +439 -0
- package/dist/pdf/objects.d.ts +93 -0
- package/dist/pdf/objects.js +237 -0
- package/dist/pdf/pageSpace.d.ts +43 -0
- package/dist/pdf/pageSpace.js +57 -0
- package/dist/pdf/paint.d.ts +32 -0
- package/dist/pdf/paint.js +442 -0
- package/dist/pdf/writePdf.d.ts +56 -0
- package/dist/pdf/writePdf.js +345 -0
- package/dist/prosemirror/commands/comments.js +91 -6
- package/dist/prosemirror/conversion/fromProseDoc.js +41 -7
- package/dist/prosemirror/conversion/toProseDoc.js +41 -4
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +15 -2
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +35 -6
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +2 -2
- package/dist/prosemirror/extensions/features/pasteCleanup.js +4 -3
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +3 -1
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +5 -3
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -1
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +9 -3
- package/dist/prosemirror/extensions/nodes/TableExtension.js +56 -39
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +3 -1
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +5 -3
- package/dist/prosemirror/plugins/suggestionMode.js +1 -0
- package/dist/prosemirror/revisionCarriers.js +19 -2
- package/dist/prosemirror/utils/extractTrackedChanges.d.ts +2 -2
- package/dist/prosemirror/utils/extractTrackedChanges.js +59 -23
- package/dist/prosemirror/validation.js +55 -27
- package/dist/prosemirror/zeroWidthAnchors.d.ts +10 -0
- package/dist/prosemirror/zeroWidthAnchors.js +23 -0
- package/dist/redline.js +12 -79
- package/dist/server.d.ts +3 -3
- package/dist/server.js +2 -2
- package/dist/shaping/placeRun.d.ts +51 -0
- package/dist/shaping/placeRun.js +84 -0
- package/dist/shaping/shaper.d.ts +92 -0
- package/dist/shaping/shaper.js +102 -0
- package/dist/types/block-id.d.ts +18 -2
- package/dist/types/block-id.js +49 -5
- package/dist/types/content.d.ts +2 -2
- package/dist/utils/clipboard.js +4 -3
- package/dist/utils/fontResolver.js +183 -0
- package/dist/utils/htmlComments.d.ts +17 -0
- package/dist/utils/htmlComments.js +22 -0
- package/dist/utils/scriptSegments.d.ts +12 -1
- package/dist/utils/scriptSegments.js +17 -1
- package/dist/version-comparison.d.ts +1 -1
- package/package.json +7 -3
|
@@ -16,6 +16,7 @@ import { resolveEffectiveTableCellFormatting } from "./effectiveTableCellFormatt
|
|
|
16
16
|
import { marksToTextFormatting } from "./fromProseDoc.js";
|
|
17
17
|
import { shadingToRunShadingAttrs } from "./runShadingMark.js";
|
|
18
18
|
import { sdtAttrsFromProperties } from "./sdtAttrs.js";
|
|
19
|
+
import { panic } from "better-result";
|
|
19
20
|
//#region src/prosemirror/conversion/toProseDoc.ts
|
|
20
21
|
/**
|
|
21
22
|
* Build a `nextTextBoxGroupId()` generator salted with a random per-load
|
|
@@ -358,7 +359,7 @@ function anchorPointComment(nodes, commentId) {
|
|
|
358
359
|
function convertTrackedChange(change, markType, nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, moveKind = null, textBoxAnchors) {
|
|
359
360
|
const nodes = [];
|
|
360
361
|
for (const item of change.content) if (item.type === "run") nodes.push(...convertRun(item, getInheritedRunFormatting(item.formatting), styleResolver, textBoxAnchors));
|
|
361
|
-
else {
|
|
362
|
+
else if (item.type === "hyperlink") {
|
|
362
363
|
const currentHyperlinkIndex = nextHyperlinkInstanceIndex();
|
|
363
364
|
nodes.push(...convertHyperlink(item, {
|
|
364
365
|
getInheritedRunFormatting,
|
|
@@ -366,7 +367,30 @@ function convertTrackedChange(change, markType, nextHyperlinkInstanceIndex, getI
|
|
|
366
367
|
hyperlinkIndex: currentHyperlinkIndex,
|
|
367
368
|
textBoxAnchors
|
|
368
369
|
}));
|
|
369
|
-
}
|
|
370
|
+
} else if (item.type === "simpleField" || item.type === "complexField") {
|
|
371
|
+
const fieldNode = convertField(item, {
|
|
372
|
+
getInheritedRunFormatting,
|
|
373
|
+
styleResolver,
|
|
374
|
+
nextHyperlinkInstanceIndex,
|
|
375
|
+
textBoxAnchors
|
|
376
|
+
});
|
|
377
|
+
if (fieldNode) nodes.push(fieldNode);
|
|
378
|
+
} else if (item.type === "insertion" || item.type === "deletion" || item.type === "moveFrom" || item.type === "moveTo") {
|
|
379
|
+
const nestedMarkType = item.type === "insertion" || item.type === "moveTo" ? "insertion" : "deletion";
|
|
380
|
+
const nestedMoveKind = item.type === "moveFrom" || item.type === "moveTo" ? item.type : null;
|
|
381
|
+
nodes.push(...convertTrackedChange(item, nestedMarkType, nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, nestedMoveKind, textBoxAnchors));
|
|
382
|
+
} else if (item.type === "bookmarkStart") nodes.push(schema.node("bookmarkBoundary", {
|
|
383
|
+
type: "start",
|
|
384
|
+
id: item.id,
|
|
385
|
+
name: item.name,
|
|
386
|
+
colFirst: item.colFirst,
|
|
387
|
+
colLast: item.colLast
|
|
388
|
+
}));
|
|
389
|
+
else if (item.type === "bookmarkEnd") nodes.push(schema.node("bookmarkBoundary", {
|
|
390
|
+
type: "end",
|
|
391
|
+
id: item.id
|
|
392
|
+
}));
|
|
393
|
+
else panic(`Unsupported tracked-run content: ${JSON.stringify(item)}`);
|
|
370
394
|
const mark = schema.marks[markType].create({
|
|
371
395
|
revisionId: change.info.id,
|
|
372
396
|
author: change.info.author,
|
|
@@ -375,12 +399,16 @@ function convertTrackedChange(change, markType, nextHyperlinkInstanceIndex, getI
|
|
|
375
399
|
moveKind
|
|
376
400
|
});
|
|
377
401
|
return nodes.map((node) => {
|
|
402
|
+
if (node.marks.some(({ type }) => type.name === "insertion" || type.name === "deletion")) return node;
|
|
378
403
|
if (canCarryTrackedRunMark(node, mark.type)) return node.mark(mark.addToSet(node.marks));
|
|
379
404
|
return node;
|
|
380
405
|
});
|
|
381
406
|
}
|
|
382
407
|
function canCarryTrackedRunMark(node, markType) {
|
|
383
|
-
|
|
408
|
+
if (node.isText) return true;
|
|
409
|
+
if (!node.isInline) return false;
|
|
410
|
+
if (node.type.name === "field" || node.type.name === "structuredField") return true;
|
|
411
|
+
return node.type.allowsMarkType(markType) && (node.type.name === "image" || node.type.name === "shape" || node.type.name === "hardBreak" || node.type.name === "tab" || node.type.name === "symbol" || node.type.name === "bookmarkBoundary" || node.type.name === "textBoxAnchor");
|
|
384
412
|
}
|
|
385
413
|
/**
|
|
386
414
|
* Convert ParagraphFormatting to ProseMirror paragraph attrs
|
|
@@ -1706,7 +1734,7 @@ function extractTextBoxes(paragraph, textBoxGroupId) {
|
|
|
1706
1734
|
});
|
|
1707
1735
|
continue;
|
|
1708
1736
|
}
|
|
1709
|
-
visitHyperlink(item, {
|
|
1737
|
+
if (item.type === "hyperlink") visitHyperlink(item, {
|
|
1710
1738
|
...context,
|
|
1711
1739
|
trackedChange
|
|
1712
1740
|
});
|
|
@@ -1965,6 +1993,15 @@ function findParagraphPageBreakPosition(paragraph) {
|
|
|
1965
1993
|
for (const child of children) {
|
|
1966
1994
|
if (child.type === "run" && visitRun(child)) return true;
|
|
1967
1995
|
if (child.type === "hyperlink" && visitHyperlinkChildren(child)) return true;
|
|
1996
|
+
if (child.type === "simpleField") for (const fieldChild of child.content) {
|
|
1997
|
+
if (fieldChild.type === "run" && visitRun(fieldChild)) return true;
|
|
1998
|
+
if (fieldChild.type === "hyperlink" && visitHyperlinkChildren(fieldChild)) return true;
|
|
1999
|
+
}
|
|
2000
|
+
if (child.type === "complexField") {
|
|
2001
|
+
for (const run of child.fieldCode) if (visitRun(run)) return true;
|
|
2002
|
+
for (const run of child.fieldResult) if (visitRun(run)) return true;
|
|
2003
|
+
}
|
|
2004
|
+
if ((child.type === "insertion" || child.type === "deletion" || child.type === "moveFrom" || child.type === "moveTo") && visitRunOrHyperlinkList(child.content)) return true;
|
|
1968
2005
|
}
|
|
1969
2006
|
return false;
|
|
1970
2007
|
}
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import { Extension } from "../types.js";
|
|
2
|
-
import { EditorState, PluginKey } from "prosemirror-state";
|
|
2
|
+
import { EditorState, PluginKey, Transaction } from "prosemirror-state";
|
|
3
3
|
import { Node } from "prosemirror-model";
|
|
4
4
|
//#region src/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts
|
|
5
5
|
declare const paraIdAllocatorKey: PluginKey<any>;
|
|
6
|
+
/**
|
|
7
|
+
* Transaction meta asking this plugin to derive the ids it mints from a
|
|
8
|
+
* caller-supplied seed instead of `Math.random()`.
|
|
9
|
+
*
|
|
10
|
+
* A live editor wants random ids: two people typing must not collide. A
|
|
11
|
+
* generator whose whole output has to be reproducible — the document compare —
|
|
12
|
+
* cannot afford them, and it is the only party that knows its run is meant to
|
|
13
|
+
* be deterministic, so it says so on its own transaction rather than leaving
|
|
14
|
+
* the plugin to guess.
|
|
15
|
+
*/
|
|
16
|
+
declare const deterministicParaIdSeedKey: PluginKey<string>;
|
|
17
|
+
/** Ask the allocator to mint reproducible ids for the paragraphs `tr` creates. */
|
|
18
|
+
declare const requestDeterministicParaIds: (tr: Transaction, seed: string) => Transaction;
|
|
6
19
|
/**
|
|
7
20
|
* Allocate initial paragraph IDs before constructing editor state.
|
|
8
21
|
*
|
|
@@ -14,4 +27,4 @@ declare const ensureParaIdsInDoc: (doc: Node) => Node;
|
|
|
14
27
|
declare const ensureParaIdsInState: (state: EditorState) => EditorState;
|
|
15
28
|
declare const ParaIdAllocatorExtension: (options?: Partial<{}> | undefined) => Extension;
|
|
16
29
|
//#endregion
|
|
17
|
-
export { ParaIdAllocatorExtension, ensureParaIdsInDoc, ensureParaIdsInState, paraIdAllocatorKey };
|
|
30
|
+
export { ParaIdAllocatorExtension, deterministicParaIdSeedKey, ensureParaIdsInDoc, ensureParaIdsInState, paraIdAllocatorKey, requestDeterministicParaIds };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { generateHexId } from "../../../utils/hexId.js";
|
|
1
|
+
import { deterministicHexId, generateHexId } from "../../../utils/hexId.js";
|
|
2
2
|
import { createExtension } from "../create.js";
|
|
3
3
|
import { ignoreTrackedChanges } from "./ParagraphChangeTrackerExtension.js";
|
|
4
4
|
import { Fragment } from "prosemirror-model";
|
|
@@ -42,6 +42,35 @@ import { Plugin, PluginKey } from "prosemirror-state";
|
|
|
42
42
|
* another one.
|
|
43
43
|
*/
|
|
44
44
|
const paraIdAllocatorKey = new PluginKey("paraIdAllocator");
|
|
45
|
+
/**
|
|
46
|
+
* Transaction meta asking this plugin to derive the ids it mints from a
|
|
47
|
+
* caller-supplied seed instead of `Math.random()`.
|
|
48
|
+
*
|
|
49
|
+
* A live editor wants random ids: two people typing must not collide. A
|
|
50
|
+
* generator whose whole output has to be reproducible — the document compare —
|
|
51
|
+
* cannot afford them, and it is the only party that knows its run is meant to
|
|
52
|
+
* be deterministic, so it says so on its own transaction rather than leaving
|
|
53
|
+
* the plugin to guess.
|
|
54
|
+
*/
|
|
55
|
+
const deterministicParaIdSeedKey = new PluginKey("deterministicParaIdSeed");
|
|
56
|
+
/** Ask the allocator to mint reproducible ids for the paragraphs `tr` creates. */
|
|
57
|
+
const requestDeterministicParaIds = (tr, seed) => tr.setMeta(deterministicParaIdSeedKey, seed);
|
|
58
|
+
/**
|
|
59
|
+
* A fresh id not already in `taken`: derived from `seed` and the paragraph's
|
|
60
|
+
* position when the caller asked for reproducibility, random otherwise. Both
|
|
61
|
+
* loops terminate — the id space is 2^31 wide and `taken` holds at most one
|
|
62
|
+
* entry per paragraph.
|
|
63
|
+
*/
|
|
64
|
+
const mintParaId = (taken, seed, pos) => {
|
|
65
|
+
if (seed === null) {
|
|
66
|
+
let id = generateHexId();
|
|
67
|
+
while (taken.has(id)) id = generateHexId();
|
|
68
|
+
return id;
|
|
69
|
+
}
|
|
70
|
+
let id = deterministicHexId(`${seed}:${String(pos)}`);
|
|
71
|
+
for (let salt = 1; taken.has(id); salt++) id = deterministicHexId(`${seed}:${String(pos)}:${String(salt)}`);
|
|
72
|
+
return id;
|
|
73
|
+
};
|
|
45
74
|
const isUsableParaId = (value) => typeof value === "string" && value.length > 0 && value !== "00000000";
|
|
46
75
|
/**
|
|
47
76
|
* For every valid paraId in the pre-transaction doc, the position its
|
|
@@ -70,7 +99,7 @@ const mapKeeperPositions = (oldDoc, transactions) => {
|
|
|
70
99
|
});
|
|
71
100
|
return keepers;
|
|
72
101
|
};
|
|
73
|
-
const collectParaIdUpdates = (doc, keeperPositions) => {
|
|
102
|
+
const collectParaIdUpdates = (doc, keeperPositions, deterministicSeed = null) => {
|
|
74
103
|
const missing = [];
|
|
75
104
|
const occurrencesById = /* @__PURE__ */ new Map();
|
|
76
105
|
doc.descendants((node, pos) => {
|
|
@@ -100,8 +129,7 @@ const collectParaIdUpdates = (doc, keeperPositions) => {
|
|
|
100
129
|
const taken = new Set(occurrencesById.keys());
|
|
101
130
|
const updates = [];
|
|
102
131
|
for (const { pos, attrs } of needFreshId) {
|
|
103
|
-
|
|
104
|
-
while (taken.has(newId)) newId = generateHexId();
|
|
132
|
+
const newId = mintParaId(taken, deterministicSeed, pos);
|
|
105
133
|
taken.add(newId);
|
|
106
134
|
updates.push({
|
|
107
135
|
pos,
|
|
@@ -182,7 +210,8 @@ const createParaIdAllocatorPlugin = () => new Plugin({
|
|
|
182
210
|
appendTransaction(transactions, oldState, newState) {
|
|
183
211
|
if (!transactions.some((t) => t.docChanged)) return null;
|
|
184
212
|
const keeperPositions = mapKeeperPositions(oldState.doc, transactions);
|
|
185
|
-
const
|
|
213
|
+
const seed = transactions.map((transaction) => transaction.getMeta(deterministicParaIdSeedKey)).find((value) => typeof value === "string") ?? null;
|
|
214
|
+
const updates = collectParaIdUpdates(newState.doc, keeperPositions, seed);
|
|
186
215
|
if (updates.length === 0) return null;
|
|
187
216
|
const tr = newState.tr;
|
|
188
217
|
for (const u of updates) tr.setNodeMarkup(u.pos, void 0, u.attrs);
|
|
@@ -200,4 +229,4 @@ const ParaIdAllocatorExtension = createExtension({
|
|
|
200
229
|
}
|
|
201
230
|
});
|
|
202
231
|
//#endregion
|
|
203
|
-
export { ParaIdAllocatorExtension, ensureParaIdsInDoc, ensureParaIdsInState, paraIdAllocatorKey };
|
|
232
|
+
export { ParaIdAllocatorExtension, deterministicParaIdSeedKey, ensureParaIdsInDoc, ensureParaIdsInState, paraIdAllocatorKey, requestDeterministicParaIds };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createExtension } from "../create.js";
|
|
2
|
+
import { AddMarkStep, AddNodeMarkStep, AttrStep, RemoveMarkStep, RemoveNodeMarkStep } from "prosemirror-transform";
|
|
2
3
|
import { Plugin, PluginKey } from "prosemirror-state";
|
|
3
|
-
import { AddMarkStep, AddNodeMarkStep, RemoveMarkStep, RemoveNodeMarkStep } from "prosemirror-transform";
|
|
4
4
|
//#region src/prosemirror/extensions/features/ParagraphChangeTrackerExtension.ts
|
|
5
5
|
/**
|
|
6
6
|
* Paragraph Change Tracker Extension
|
|
@@ -121,7 +121,7 @@ function createParagraphChangeTrackerPlugin() {
|
|
|
121
121
|
if (hasUntracked) newState.hasUntrackedChanges = true;
|
|
122
122
|
continue;
|
|
123
123
|
}
|
|
124
|
-
if (step instanceof AddNodeMarkStep || step instanceof RemoveNodeMarkStep) {
|
|
124
|
+
if (step instanceof AddNodeMarkStep || step instanceof RemoveNodeMarkStep || step instanceof AttrStep) {
|
|
125
125
|
const pos = mapStepPosition(remap, step.pos, 1);
|
|
126
126
|
const node = tr.doc.nodeAt(pos);
|
|
127
127
|
if (!node) continue;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { htmlCommentEnd } from "../../../utils/htmlComments.js";
|
|
1
2
|
import { stripXmlDeclarations } from "../../../utils/stripXmlDeclarations.js";
|
|
2
3
|
import { readBookmarkBoundaryAttrs } from "../../bookmarkBoundaryAttrs.js";
|
|
3
4
|
import { Fragment, Slice } from "prosemirror-model";
|
|
@@ -61,9 +62,9 @@ function stripHtmlComments(html) {
|
|
|
61
62
|
continue;
|
|
62
63
|
}
|
|
63
64
|
result += html.slice(cursor, commentStart);
|
|
64
|
-
const commentEnd = html
|
|
65
|
-
if (commentEnd ===
|
|
66
|
-
cursor = commentEnd
|
|
65
|
+
const commentEnd = htmlCommentEnd(html, commentStart);
|
|
66
|
+
if (commentEnd === null) break;
|
|
67
|
+
cursor = commentEnd;
|
|
67
68
|
}
|
|
68
69
|
return result;
|
|
69
70
|
}
|
|
@@ -4,6 +4,8 @@ import { NodeExtension } from "../types.js";
|
|
|
4
4
|
type BookmarkBoundaryOptions = {
|
|
5
5
|
getInternalClipboardToken?: () => string;
|
|
6
6
|
};
|
|
7
|
+
/** The node name, for callers asking whether a paragraph holds any content. */
|
|
8
|
+
declare const BOOKMARK_BOUNDARY_NODE_NAME = "bookmarkBoundary";
|
|
7
9
|
declare const BookmarkBoundaryExtension: (options?: Partial<BookmarkBoundaryOptions> | undefined) => NodeExtension;
|
|
8
10
|
//#endregion
|
|
9
|
-
export { BookmarkBoundaryExtension };
|
|
11
|
+
export { BOOKMARK_BOUNDARY_NODE_NAME, BookmarkBoundaryExtension };
|
|
@@ -11,9 +11,11 @@ function readOptionalColumn(dom, attribute) {
|
|
|
11
11
|
const value = dom.getAttribute(attribute);
|
|
12
12
|
return value === null ? void 0 : readNonnegativeInteger(value);
|
|
13
13
|
}
|
|
14
|
+
/** The node name, for callers asking whether a paragraph holds any content. */
|
|
15
|
+
const BOOKMARK_BOUNDARY_NODE_NAME = "bookmarkBoundary";
|
|
14
16
|
const BookmarkBoundaryExtension = createNodeExtension({
|
|
15
|
-
name:
|
|
16
|
-
schemaNodeName:
|
|
17
|
+
name: BOOKMARK_BOUNDARY_NODE_NAME,
|
|
18
|
+
schemaNodeName: BOOKMARK_BOUNDARY_NODE_NAME,
|
|
17
19
|
nodeSpec: (options) => ({
|
|
18
20
|
inline: true,
|
|
19
21
|
group: "inline",
|
|
@@ -64,4 +66,4 @@ const BookmarkBoundaryExtension = createNodeExtension({
|
|
|
64
66
|
})
|
|
65
67
|
});
|
|
66
68
|
//#endregion
|
|
67
|
-
export { BookmarkBoundaryExtension };
|
|
69
|
+
export { BOOKMARK_BOUNDARY_NODE_NAME, BookmarkBoundaryExtension };
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { NodeExtension } from "../types.js";
|
|
2
2
|
//#region src/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts
|
|
3
3
|
/** Zero-width cached pagination boundary preserved from DOCX. */
|
|
4
|
+
/**
|
|
5
|
+
* The node name, for callers that have to ask whether anything is LEFT in a
|
|
6
|
+
* paragraph. This node is zero-width and carries no revision of its own, so a
|
|
7
|
+
* paragraph holding only these holds nothing a reader would see.
|
|
8
|
+
*/
|
|
9
|
+
declare const RENDERED_PAGE_BREAK_NODE_NAME = "renderedPageBreak";
|
|
4
10
|
declare const RenderedPageBreakExtension: (options?: Partial<Record<string, unknown>> | undefined) => NodeExtension;
|
|
5
11
|
//#endregion
|
|
6
|
-
export { RenderedPageBreakExtension };
|
|
12
|
+
export { RENDERED_PAGE_BREAK_NODE_NAME, RenderedPageBreakExtension };
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { createNodeExtension } from "../create.js";
|
|
2
2
|
//#region src/prosemirror/extensions/nodes/RenderedPageBreakExtension.ts
|
|
3
3
|
/** Zero-width cached pagination boundary preserved from DOCX. */
|
|
4
|
+
/**
|
|
5
|
+
* The node name, for callers that have to ask whether anything is LEFT in a
|
|
6
|
+
* paragraph. This node is zero-width and carries no revision of its own, so a
|
|
7
|
+
* paragraph holding only these holds nothing a reader would see.
|
|
8
|
+
*/
|
|
9
|
+
const RENDERED_PAGE_BREAK_NODE_NAME = "renderedPageBreak";
|
|
4
10
|
const RenderedPageBreakExtension = createNodeExtension({
|
|
5
|
-
name:
|
|
6
|
-
schemaNodeName:
|
|
11
|
+
name: RENDERED_PAGE_BREAK_NODE_NAME,
|
|
12
|
+
schemaNodeName: RENDERED_PAGE_BREAK_NODE_NAME,
|
|
7
13
|
nodeSpec: {
|
|
8
14
|
inline: true,
|
|
9
15
|
group: "inline",
|
|
@@ -19,4 +25,4 @@ const RenderedPageBreakExtension = createNodeExtension({
|
|
|
19
25
|
}
|
|
20
26
|
});
|
|
21
27
|
//#endregion
|
|
22
|
-
export { RenderedPageBreakExtension };
|
|
28
|
+
export { RENDERED_PAGE_BREAK_NODE_NAME, RenderedPageBreakExtension };
|
|
@@ -730,51 +730,68 @@ const TablePluginExtension = createExtension({
|
|
|
730
730
|
return true;
|
|
731
731
|
};
|
|
732
732
|
}
|
|
733
|
-
|
|
733
|
+
/**
|
|
734
|
+
* Insert an empty row so that it lands ABOVE grid row `boundaryRow`.
|
|
735
|
+
*
|
|
736
|
+
* A vertically merged cell that covers both sides of the boundary is
|
|
737
|
+
* EXTENDED, not duplicated: Word grows the merge through the inserted row
|
|
738
|
+
* (`w:vMerge="continue"` gains one row) rather than splitting it. Cloning
|
|
739
|
+
* the caret row cell-for-cell instead emitted a cell for a column the merge
|
|
740
|
+
* already occupies, which widens the grid and desynchronises every later
|
|
741
|
+
* row — the opposite of `deleteRow`, which is merge-correct because it
|
|
742
|
+
* delegates to prosemirror-tables.
|
|
743
|
+
*
|
|
744
|
+
* Only the columns the boundary actually opens get a new cell, and each is
|
|
745
|
+
* shaped from the cell covering that column so widths, borders, margins and
|
|
746
|
+
* text direction survive.
|
|
747
|
+
*/
|
|
748
|
+
function insertTableRow(state, dispatch, boundaryOffset) {
|
|
734
749
|
const context = getTableContext(state);
|
|
735
750
|
if (!context.isInTable || context.rowIndex === void 0 || !context.table || context.tablePos === void 0) return false;
|
|
736
|
-
if (dispatch)
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
751
|
+
if (!dispatch) return true;
|
|
752
|
+
const { table, tablePos, rowIndex } = context;
|
|
753
|
+
const boundaryRow = rowIndex + boundaryOffset;
|
|
754
|
+
const map = TableMap.get(table);
|
|
755
|
+
const tableStart = tablePos + 1;
|
|
756
|
+
const tr = state.tr;
|
|
757
|
+
const cells = [];
|
|
758
|
+
const extended = /* @__PURE__ */ new Set();
|
|
759
|
+
let lastTemplatePos = -1;
|
|
760
|
+
for (let column = 0; column < map.width; column += 1) {
|
|
761
|
+
const below = boundaryRow < map.height ? map.map[boundaryRow * map.width + column] : void 0;
|
|
762
|
+
const above = boundaryRow > 0 ? map.map[(boundaryRow - 1) * map.width + column] : void 0;
|
|
763
|
+
if (below !== void 0 && below === above) {
|
|
764
|
+
if (!extended.has(below)) {
|
|
765
|
+
extended.add(below);
|
|
766
|
+
const merged = table.nodeAt(below);
|
|
767
|
+
if (merged) tr.setNodeMarkup(tableStart + below, void 0, {
|
|
768
|
+
...merged.attrs,
|
|
769
|
+
rowspan: (Number(merged.attrs["rowspan"]) || 1) + 1
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
const templatePos = below ?? above;
|
|
775
|
+
if (templatePos === void 0 || templatePos === lastTemplatePos) continue;
|
|
776
|
+
lastTemplatePos = templatePos;
|
|
777
|
+
cells.push(nodeTypeTableCell.create(buildCellAttrsFromTemplate(table.nodeAt(templatePos)), nodeTypeParagraph.create()));
|
|
753
778
|
}
|
|
779
|
+
const rowNode = table.child(rowIndex);
|
|
780
|
+
const newRow = nodeTypeTableRow.create({
|
|
781
|
+
height: rowNode.attrs["height"] ?? 360,
|
|
782
|
+
heightRule: rowNode.attrs["heightRule"] ?? "atLeast"
|
|
783
|
+
}, cells);
|
|
784
|
+
let rowPos = tableStart;
|
|
785
|
+
for (let i = 0; i < boundaryRow; i += 1) rowPos += table.child(i).nodeSize;
|
|
786
|
+
tr.insert(rowPos, newRow);
|
|
787
|
+
dispatch(tr.scrollIntoView());
|
|
754
788
|
return true;
|
|
755
789
|
}
|
|
790
|
+
function addRowAbove(state, dispatch) {
|
|
791
|
+
return insertTableRow(state, dispatch, 0);
|
|
792
|
+
}
|
|
756
793
|
function addRowBelow(state, dispatch) {
|
|
757
|
-
|
|
758
|
-
if (!context.isInTable || context.rowIndex === void 0 || !context.table || context.tablePos === void 0) return false;
|
|
759
|
-
if (dispatch) {
|
|
760
|
-
const tr = state.tr;
|
|
761
|
-
const rowNode = context.table.child(context.rowIndex);
|
|
762
|
-
const cells = [];
|
|
763
|
-
rowNode.forEach((cell) => {
|
|
764
|
-
const paragraph = nodeTypeParagraph.create();
|
|
765
|
-
const cellAttrs = buildCellAttrsFromTemplate(cell);
|
|
766
|
-
cells.push(nodeTypeTableCell.create(cellAttrs, paragraph));
|
|
767
|
-
});
|
|
768
|
-
const newRow = nodeTypeTableRow.create({
|
|
769
|
-
height: rowNode.attrs["height"] ?? 360,
|
|
770
|
-
heightRule: rowNode.attrs["heightRule"] ?? "atLeast"
|
|
771
|
-
}, cells);
|
|
772
|
-
let rowPos = context.tablePos + 1;
|
|
773
|
-
for (let i = 0; i <= context.rowIndex; i++) rowPos += context.table.child(i).nodeSize;
|
|
774
|
-
tr.insert(rowPos, newRow);
|
|
775
|
-
dispatch(tr.scrollIntoView());
|
|
776
|
-
}
|
|
777
|
-
return true;
|
|
794
|
+
return insertTableRow(state, dispatch, 1);
|
|
778
795
|
}
|
|
779
796
|
function deleteRow(state, dispatch) {
|
|
780
797
|
const context = getTableContext(state);
|
|
@@ -10,6 +10,8 @@ import { NodeExtension } from "../types.js";
|
|
|
10
10
|
type TextBoxAnchorOptions = {
|
|
11
11
|
getInternalClipboardToken?: () => string;
|
|
12
12
|
};
|
|
13
|
+
/** The node name, for callers asking whether a paragraph holds any content. */
|
|
14
|
+
declare const TEXT_BOX_ANCHOR_NODE_NAME = "textBoxAnchor";
|
|
13
15
|
declare const TextBoxAnchorExtension: (options?: Partial<TextBoxAnchorOptions> | undefined) => NodeExtension;
|
|
14
16
|
//#endregion
|
|
15
|
-
export { TextBoxAnchorExtension };
|
|
17
|
+
export { TEXT_BOX_ANCHOR_NODE_NAME, TextBoxAnchorExtension };
|
|
@@ -8,9 +8,11 @@ import { createNodeExtension } from "../create.js";
|
|
|
8
8
|
* map its position as surrounding text is edited. The save conversion consumes
|
|
9
9
|
* it when the sibling text box is restored to the DOCX run stream.
|
|
10
10
|
*/
|
|
11
|
+
/** The node name, for callers asking whether a paragraph holds any content. */
|
|
12
|
+
const TEXT_BOX_ANCHOR_NODE_NAME = "textBoxAnchor";
|
|
11
13
|
const TextBoxAnchorExtension = createNodeExtension({
|
|
12
|
-
name:
|
|
13
|
-
schemaNodeName:
|
|
14
|
+
name: TEXT_BOX_ANCHOR_NODE_NAME,
|
|
15
|
+
schemaNodeName: TEXT_BOX_ANCHOR_NODE_NAME,
|
|
14
16
|
nodeSpec: (options) => ({
|
|
15
17
|
inline: true,
|
|
16
18
|
group: "inline",
|
|
@@ -39,4 +41,4 @@ const TextBoxAnchorExtension = createNodeExtension({
|
|
|
39
41
|
})
|
|
40
42
|
});
|
|
41
43
|
//#endregion
|
|
42
|
-
export { TextBoxAnchorExtension };
|
|
44
|
+
export { TEXT_BOX_ANCHOR_NODE_NAME, TextBoxAnchorExtension };
|
|
@@ -76,6 +76,7 @@ function markRangeAsDeleted(tr, doc, from, to, insertionType, deletionType, plug
|
|
|
76
76
|
const end = Math.min(pos + node.nodeSize, to);
|
|
77
77
|
if (start >= end) return;
|
|
78
78
|
const isOwnInsert = node.marks.some((m) => m.type === insertionType && m.attrs["author"] === pluginState.author);
|
|
79
|
+
if (node.marks.some((m) => m.type === deletionType)) return;
|
|
79
80
|
ranges.push({
|
|
80
81
|
from: start,
|
|
81
82
|
to: end,
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { expectParagraphAttrs } from "./attrs/index.js";
|
|
2
|
+
import { PARAGRAPH_MARK_CHANGE_KINDS } from "@stll/docx-core/model";
|
|
2
3
|
//#region src/prosemirror/revisionCarriers.ts
|
|
4
|
+
/**
|
|
5
|
+
* Which carrier each paragraph-mark kind reports as. A relocation's break
|
|
6
|
+
* resolves as the insertion or deletion it is; the kind exists so a reader is
|
|
7
|
+
* told the two ends belong together, not to change what resolving does.
|
|
8
|
+
*
|
|
9
|
+
* Total over the kinds by construction, so a kind the model gains has to be
|
|
10
|
+
* given a carrier here rather than silently reporting none.
|
|
11
|
+
*/
|
|
12
|
+
const PARAGRAPH_MARK_KIND_CARRIERS = {
|
|
13
|
+
ins: "paragraphMarkInserted",
|
|
14
|
+
moveTo: "paragraphMarkInserted",
|
|
15
|
+
del: "paragraphMarkDeleted",
|
|
16
|
+
moveFrom: "paragraphMarkDeleted"
|
|
17
|
+
};
|
|
18
|
+
const isParagraphMarkChangeKind = (value) => PARAGRAPH_MARK_CHANGE_KINDS.some((kind) => kind === value);
|
|
3
19
|
const isObjectRecord = (value) => typeof value === "object" && value !== null;
|
|
4
20
|
const revisionMetadata = (value) => {
|
|
5
21
|
if (!isObjectRecord(value) || typeof value["id"] !== "number") return null;
|
|
@@ -37,11 +53,12 @@ const getFolioNodeRevisionCarriers = (node, nodePos) => {
|
|
|
37
53
|
const to = nodePos + node.nodeSize;
|
|
38
54
|
const paragraphAttrs = expectParagraphAttrs(node);
|
|
39
55
|
const paragraphMark = node.attrs["pPrMark"];
|
|
40
|
-
|
|
56
|
+
const paragraphMarkKind = isObjectRecord(paragraphMark) && isParagraphMarkChangeKind(paragraphMark["kind"]) ? PARAGRAPH_MARK_KIND_CARRIERS[paragraphMark["kind"]] : void 0;
|
|
57
|
+
if (paragraphMarkKind !== void 0 && isObjectRecord(paragraphMark)) {
|
|
41
58
|
const metadata = revisionMetadata(paragraphMark["info"]);
|
|
42
59
|
if (metadata) carriers.push({
|
|
43
60
|
...metadata,
|
|
44
|
-
type:
|
|
61
|
+
type: paragraphMarkKind,
|
|
45
62
|
text: node.textContent,
|
|
46
63
|
from,
|
|
47
64
|
to
|
|
@@ -94,8 +94,8 @@ type TrackedChangesResult = {
|
|
|
94
94
|
* Walk the PM doc and extract every tracked change as a flat list of
|
|
95
95
|
* `TrackedChangeEntry` plus a comment→revision overlap map. Adjacent
|
|
96
96
|
* inline marks coalesce by `(type, revisionId, author, date)`; a
|
|
97
|
-
* deletion immediately followed by
|
|
98
|
-
*
|
|
97
|
+
* deletion immediately followed by the same author's insertion
|
|
98
|
+
* collapses into a single `replacement` entry; paragraph-mark
|
|
99
99
|
* cards (`paragraphMarkInsertion` / `paragraphMarkDeletion`) are
|
|
100
100
|
* hidden when an inline entry already covers their revision triple
|
|
101
101
|
* (one Accept clears every site of one conceptual change).
|
|
@@ -6,11 +6,28 @@ const EMPTY_RESULT = {
|
|
|
6
6
|
commentToRevision: /* @__PURE__ */ new Map()
|
|
7
7
|
};
|
|
8
8
|
/**
|
|
9
|
+
* Fold a run's revision mark into the enclosing row's card when both halves
|
|
10
|
+
* are the same change. Folio writes both under one revision id; Word mints a
|
|
11
|
+
* fresh `w:id` per element, so an author + timestamp match counts too. Returns
|
|
12
|
+
* false for a third party's edit inside the row, which keeps its own card.
|
|
13
|
+
*/
|
|
14
|
+
const foldIntoRowRevision = (scope, mark) => {
|
|
15
|
+
if (!scope || mark.type !== scope.markType) return false;
|
|
16
|
+
const revisionId = mark.attrs["revisionId"];
|
|
17
|
+
if (!(revisionId === scope.entry.revisionId || mark.attrs["author"] === scope.entry.author && mark.attrs["date"] === scope.entry.date)) return false;
|
|
18
|
+
if (revisionId !== scope.entry.revisionId) {
|
|
19
|
+
const ids = new Set(scope.entry.coalescedRevisionIds ?? []);
|
|
20
|
+
ids.add(revisionId);
|
|
21
|
+
scope.entry.coalescedRevisionIds = [...ids];
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
9
26
|
* Walk the PM doc and extract every tracked change as a flat list of
|
|
10
27
|
* `TrackedChangeEntry` plus a comment→revision overlap map. Adjacent
|
|
11
28
|
* inline marks coalesce by `(type, revisionId, author, date)`; a
|
|
12
|
-
* deletion immediately followed by
|
|
13
|
-
*
|
|
29
|
+
* deletion immediately followed by the same author's insertion
|
|
30
|
+
* collapses into a single `replacement` entry; paragraph-mark
|
|
14
31
|
* cards (`paragraphMarkInsertion` / `paragraphMarkDeletion`) are
|
|
15
32
|
* hidden when an inline entry already covers their revision triple
|
|
16
33
|
* (one Accept clears every site of one conceptual change).
|
|
@@ -30,7 +47,9 @@ function extractTrackedChanges(state) {
|
|
|
30
47
|
const raw = [];
|
|
31
48
|
const commentToRevision = /* @__PURE__ */ new Map();
|
|
32
49
|
const commentMetadata = /* @__PURE__ */ new Map();
|
|
50
|
+
const rowRevisionScopes = [];
|
|
33
51
|
doc.descendants((node, pos) => {
|
|
52
|
+
while (pos >= (rowRevisionScopes.at(-1)?.end ?? Number.POSITIVE_INFINITY)) rowRevisionScopes.pop();
|
|
34
53
|
if (node.type.name === "paragraph") {
|
|
35
54
|
const ins = node.attrs["pPrIns"];
|
|
36
55
|
const del = node.attrs["pPrDel"];
|
|
@@ -69,24 +88,40 @@ function extractTrackedChanges(state) {
|
|
|
69
88
|
if (node.type.name === "tableRow") {
|
|
70
89
|
const trIns = node.attrs["trIns"];
|
|
71
90
|
const trDel = node.attrs["trDel"];
|
|
72
|
-
if (trIns)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
if (trIns) {
|
|
92
|
+
const entry = {
|
|
93
|
+
type: "rowInserted",
|
|
94
|
+
text: node.textContent || "",
|
|
95
|
+
author: trIns.author || "",
|
|
96
|
+
date: trIns.date ?? void 0,
|
|
97
|
+
from: pos,
|
|
98
|
+
to: pos + node.nodeSize,
|
|
99
|
+
revisionId: trIns.revisionId
|
|
100
|
+
};
|
|
101
|
+
raw.push(entry);
|
|
102
|
+
rowRevisionScopes.push({
|
|
103
|
+
end: pos + node.nodeSize,
|
|
104
|
+
markType: insertionType,
|
|
105
|
+
entry
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (trDel) {
|
|
109
|
+
const entry = {
|
|
110
|
+
type: "rowDeleted",
|
|
111
|
+
text: node.textContent || "",
|
|
112
|
+
author: trDel.author || "",
|
|
113
|
+
date: trDel.date ?? void 0,
|
|
114
|
+
from: pos,
|
|
115
|
+
to: pos + node.nodeSize,
|
|
116
|
+
revisionId: trDel.revisionId
|
|
117
|
+
};
|
|
118
|
+
raw.push(entry);
|
|
119
|
+
rowRevisionScopes.push({
|
|
120
|
+
end: pos + node.nodeSize,
|
|
121
|
+
markType: deletionType,
|
|
122
|
+
entry
|
|
123
|
+
});
|
|
124
|
+
}
|
|
90
125
|
const trPrChange = node.attrs["trPrChange"];
|
|
91
126
|
if (Array.isArray(trPrChange)) for (const entry of trPrChange) {
|
|
92
127
|
if (!entry?.info || typeof entry.info.id !== "number") continue;
|
|
@@ -185,7 +220,7 @@ function extractTrackedChanges(state) {
|
|
|
185
220
|
});
|
|
186
221
|
if (allShare) {
|
|
187
222
|
let visibleText = "";
|
|
188
|
-
if (deletionType) node.descendants((child) => {
|
|
223
|
+
if (deletionType && sharedAttr === "trIns") node.descendants((child) => {
|
|
189
224
|
if (child.isText && !child.marks.some((m) => m.type === deletionType)) visibleText += child.text || "";
|
|
190
225
|
});
|
|
191
226
|
else visibleText = node.textContent || "";
|
|
@@ -224,6 +259,8 @@ function extractTrackedChanges(state) {
|
|
|
224
259
|
continue;
|
|
225
260
|
}
|
|
226
261
|
if (mark.type === insertionType || mark.type === deletionType) {
|
|
262
|
+
tcMark = mark;
|
|
263
|
+
if (foldIntoRowRevision(rowRevisionScopes.at(-1), mark)) continue;
|
|
227
264
|
raw.push({
|
|
228
265
|
type: mark.type === insertionType ? "insertion" : "deletion",
|
|
229
266
|
text: inlineText,
|
|
@@ -233,7 +270,6 @@ function extractTrackedChanges(state) {
|
|
|
233
270
|
to: pos + node.nodeSize,
|
|
234
271
|
revisionId: mark.attrs["revisionId"]
|
|
235
272
|
});
|
|
236
|
-
tcMark = mark;
|
|
237
273
|
}
|
|
238
274
|
}
|
|
239
275
|
if (commentType) {
|
|
@@ -350,7 +386,7 @@ function extractTrackedChanges(state) {
|
|
|
350
386
|
for (let i = 0; i < merged.length; i++) {
|
|
351
387
|
const curr = merged[i];
|
|
352
388
|
const next = merged[i + 1];
|
|
353
|
-
if (curr.type === "deletion" && next && next.type === "insertion" && curr.author === next.author && curr.
|
|
389
|
+
if (curr.type === "deletion" && next && next.type === "insertion" && curr.author === next.author && curr.to === next.from) {
|
|
354
390
|
final.push({
|
|
355
391
|
type: "replacement",
|
|
356
392
|
text: next.text,
|