@stll/folio-core 0.36.0 → 0.37.1
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/README.md +59 -1
- package/dist/ai-edits/apply.js +689 -367
- package/dist/ai-edits/block-identity.d.ts +4 -1
- package/dist/ai-edits/block-identity.js +4 -1
- package/dist/ai-edits/blockRange.js +14 -14
- package/dist/ai-edits/clean-text.d.ts +40 -1
- package/dist/ai-edits/clean-text.js +52 -2
- package/dist/ai-edits/headless.d.ts +3 -1
- package/dist/ai-edits/headless.js +133 -71
- package/dist/ai-edits/index.d.ts +3 -3
- package/dist/ai-edits/index.js +2 -2
- package/dist/ai-edits/read.js +38 -20
- package/dist/ai-edits/snapshot.d.ts +14 -6
- package/dist/ai-edits/snapshot.js +99 -19
- package/dist/ai-edits/table-cell-mutations.js +1 -1
- package/dist/ai-edits/table-row-column-mutations.js +7 -0
- package/dist/ai-edits/table-template.d.ts +11 -1
- package/dist/ai-edits/table-template.js +49 -17
- package/dist/ai-edits/types.d.ts +60 -57
- package/dist/ai-suggestions/conflict.js +1 -4
- package/dist/ai-suggestions/text-positions.d.ts +8 -0
- package/dist/ai-suggestions/text-positions.js +27 -5
- package/dist/compare/__fixtures__/body-sequence.js +3 -4
- package/dist/compare/column-alignment.d.ts +13 -13
- package/dist/compare/column-alignment.js +78 -39
- package/dist/compare/compare.d.ts +4 -3
- package/dist/compare/compare.js +41 -5
- package/dist/compare/content-alignment.d.ts +95 -0
- package/dist/compare/content-alignment.js +1450 -0
- package/dist/compare/content-types.d.ts +100 -0
- package/dist/compare/content-types.js +0 -0
- package/dist/compare/content.d.ts +209 -0
- package/dist/compare/content.js +885 -0
- package/dist/compare/formatting.d.ts +5 -5
- package/dist/compare/formatting.js +6 -4
- package/dist/compare/plan.d.ts +6 -1
- package/dist/compare/plan.js +201 -569
- package/dist/compare/scenario.d.ts +2 -2
- package/dist/compare/types.d.ts +3 -3
- package/dist/compare/verification.d.ts +2 -2
- package/dist/compare/verification.js +25 -8
- package/dist/compat/eigenpal.d.ts +5 -3
- package/dist/compat/eigenpal.js +3 -2
- package/dist/controller/headerFooterEditorManager.js +14 -6
- package/dist/controller/hiddenEditorManager.d.ts +7 -2
- package/dist/controller/hiddenEditorManager.js +15 -6
- package/dist/controller/layoutPipeline.js +5 -3
- package/dist/controller/noteEditorManager.js +12 -7
- package/dist/display-list/build/headerFooterPrimitives.js +1 -0
- package/dist/display-list/build/storyPrimitives.d.ts +5 -2
- package/dist/display-list/build/storyPrimitives.js +60 -3
- package/dist/display-list/build/tablePrimitives.js +30 -0
- package/dist/display-list/primitives.d.ts +1 -1
- package/dist/document-operations.d.ts +8 -6
- package/dist/document-operations.js +121 -26
- package/dist/docx/blockContentParser.js +6 -27
- package/dist/docx/ensureParaIds.js +9 -4
- package/dist/docx/numberingParser.d.ts +3 -1
- package/dist/docx/numberingParser.js +4 -1
- package/dist/docx/paragraphParser.js +7 -6
- package/dist/docx/paragraphTraversal.js +30 -34
- package/dist/docx/rezip.d.ts +0 -8
- package/dist/docx/rezip.js +41 -16
- package/dist/docx/runParser.js +4 -2
- package/dist/docx/serializer/paragraphSerializer.js +2 -0
- package/dist/docx/serializer/runSerializer.js +5 -3
- package/dist/docx/serializer/trackedChangeAttributes.d.ts +3 -1
- package/dist/docx/serializer/trackedChangeAttributes.js +11 -1
- package/dist/docx/server/build.d.ts +1 -1
- package/dist/headless-layout.js +5 -4
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -2
- package/dist/internal/headlessRevisionResolution.d.ts +23 -0
- package/dist/internal/headlessRevisionResolution.js +176 -0
- package/dist/internal/headlessRevisionResolutionGuard.d.ts +12 -0
- package/dist/internal/headlessRevisionResolutionGuard.js +28 -0
- package/dist/internal/pageBreakRunDescendantIndex.d.ts +9 -0
- package/dist/internal/pageBreakRunDescendantIndex.js +27 -0
- package/dist/internal/pageBreakRunPartition.d.ts +26 -0
- package/dist/internal/pageBreakRunPartition.js +35 -0
- package/dist/internal/pageBreakRunSourceDescendantIndex.d.ts +15 -0
- package/dist/internal/pageBreakRunSourceDescendantIndex.js +126 -0
- package/dist/internal/sectionEndpointResolution.d.ts +32 -0
- package/dist/internal/sectionEndpointResolution.js +38 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +16 -4
- package/dist/layout-bridge/convert/headerFooterLayout.js +48 -5
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -0
- package/dist/layout-bridge/convert/toFlowBlocks.js +379 -75
- package/dist/layout-bridge/dom/clickToPositionDom.d.ts +8 -1
- package/dist/layout-bridge/dom/clickToPositionDom.js +114 -40
- package/dist/layout-bridge/dom/textStreamDom.d.ts +24 -0
- package/dist/layout-bridge/dom/textStreamDom.js +75 -0
- package/dist/layout-bridge/headerFooterLayout.js +16 -30
- package/dist/layout-engine/index.js +12 -3
- package/dist/layout-engine/measure/tableFragmentBorderGeometry.d.ts +17 -0
- package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +24 -0
- package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
- package/dist/layout-engine/types.d.ts +7 -0
- package/dist/layout-painter/imageLayout.d.ts +1 -1
- package/dist/layout-painter/renderTable.js +20 -0
- package/dist/model.d.ts +1 -1
- package/dist/paged-layout/rangeProjection.js +6 -9
- package/dist/paged-layout/transactionDirtyRange.d.ts +3 -1
- package/dist/paged-layout/transactionDirtyRange.js +23 -1
- package/dist/prosemirror/attrs/index.d.ts +7 -3
- package/dist/prosemirror/attrs/index.js +113 -7
- package/dist/prosemirror/bookmarkBoundaryIntegrity.d.ts +9 -0
- package/dist/prosemirror/bookmarkBoundaryIntegrity.js +22 -0
- package/dist/prosemirror/commands/comments.d.ts +10 -1
- package/dist/prosemirror/commands/comments.js +167 -59
- package/dist/prosemirror/commands/formatPainter.d.ts +45 -10
- package/dist/prosemirror/commands/formatPainter.js +319 -63
- package/dist/prosemirror/commands/index.d.ts +2 -2
- package/dist/prosemirror/commands/pageBreak.d.ts +5 -2
- package/dist/prosemirror/commands/pageBreak.js +18 -36
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +7 -5
- package/dist/prosemirror/commands/propertyChangeScope.js +27 -18
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +7 -2
- package/dist/prosemirror/conversion/fromProseDoc.js +809 -209
- package/dist/prosemirror/conversion/runShadingMark.js +2 -2
- package/dist/prosemirror/conversion/toProseDoc.d.ts +26 -1
- package/dist/prosemirror/conversion/toProseDoc.js +416 -283
- package/dist/prosemirror/extensions/StarterKit.js +4 -0
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +12 -8
- package/dist/prosemirror/extensions/features/ListExtension.js +95 -123
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +1 -1
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +27 -1
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +134 -30
- package/dist/prosemirror/extensions/features/pasteCleanup.js +9 -15
- package/dist/prosemirror/extensions/marks/BoldExtension.js +4 -3
- package/dist/prosemirror/extensions/marks/CharacterStyleExtension.d.ts +4 -7
- package/dist/prosemirror/extensions/marks/CharacterStyleExtension.js +5 -11
- package/dist/prosemirror/extensions/marks/FontSizeExtension.js +3 -3
- package/dist/prosemirror/extensions/marks/ItalicExtension.js +4 -3
- package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.d.ts +13 -0
- package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.js +39 -0
- package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.d.ts +7 -1
- package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +67 -21
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +8 -1
- package/dist/prosemirror/extensions/marks/markUtils.js +88 -2
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +44 -3
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
- package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +15 -4
- package/dist/prosemirror/extensions/nodes/PageBreakExtension.d.ts +6 -0
- package/dist/prosemirror/extensions/nodes/PageBreakExtension.js +6 -0
- package/dist/prosemirror/extensions/nodes/PageBreakRunExtension.d.ts +6 -0
- package/dist/prosemirror/extensions/nodes/PageBreakRunExtension.js +36 -0
- package/dist/prosemirror/findReplaceSelection.js +8 -3
- package/dist/prosemirror/index.d.ts +3 -3
- package/dist/prosemirror/listMarker.d.ts +26 -1
- package/dist/prosemirror/listMarker.js +108 -14
- package/dist/prosemirror/pageBreakRunProjection.d.ts +28 -0
- package/dist/prosemirror/pageBreakRunProjection.js +47 -0
- package/dist/prosemirror/paraText.js +6 -0
- package/dist/prosemirror/paragraphSpacing.d.ts +29 -0
- package/dist/prosemirror/paragraphSpacing.js +125 -0
- package/dist/prosemirror/plugins/documentNumbering.d.ts +8 -0
- package/dist/prosemirror/plugins/documentNumbering.js +18 -0
- package/dist/prosemirror/plugins/pmTextScan.d.ts +3 -2
- package/dist/prosemirror/plugins/pmTextScan.js +14 -4
- package/dist/prosemirror/plugins/suggestionMode.js +8 -10
- package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +16 -0
- package/dist/prosemirror/rebaseParagraphRunFormatting.js +61 -0
- package/dist/prosemirror/runFormattingInlineCarriers.d.ts +64 -0
- package/dist/prosemirror/runFormattingInlineCarriers.js +121 -0
- package/dist/prosemirror/runFormattingProvenance.d.ts +12 -0
- package/dist/prosemirror/runFormattingProvenance.js +48 -0
- package/dist/prosemirror/runFormattingReconciliation.d.ts +25 -0
- package/dist/prosemirror/runFormattingReconciliation.js +54 -0
- package/dist/prosemirror/runPropertyChangeResolution.d.ts +18 -0
- package/dist/prosemirror/runPropertyChangeResolution.js +18 -0
- package/dist/prosemirror/runStyleFormatting.d.ts +50 -0
- package/dist/prosemirror/runStyleFormatting.js +105 -0
- package/dist/prosemirror/schema/index.d.ts +3 -3
- package/dist/prosemirror/schema/marks.d.ts +82 -9
- package/dist/prosemirror/schema/marks.js +73 -1
- package/dist/prosemirror/schema/nodes.d.ts +18 -5
- package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +14 -2
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +44 -23
- package/dist/prosemirror/styles/styleResolver.d.ts +10 -13
- package/dist/prosemirror/styles/styleResolver.js +3 -3
- package/dist/prosemirror/styles/styleToggleCascade.d.ts +8 -1
- package/dist/prosemirror/styles/styleToggleCascade.js +24 -1
- package/dist/prosemirror/tableGridMutation.d.ts +20 -0
- package/dist/prosemirror/tableGridMutation.js +30 -0
- package/dist/prosemirror/trackedRunInlineAtoms.d.ts +31 -0
- package/dist/prosemirror/trackedRunInlineAtoms.js +39 -0
- package/dist/prosemirror/utils/visualLineNavigation.js +27 -31
- package/dist/prosemirror/validation.js +23 -4
- package/dist/render-dom/HeaderFooterSelectionOverlay.js +20 -20
- package/dist/render-dom/RenderedDomContext.js +17 -34
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/style-engine/styleEngine.d.ts +1 -1
- package/dist/utils/clipboard.js +1 -1
- package/dist/utils/findReplace.js +38 -21
- package/dist/version-comparison.d.ts +38 -44
- package/dist/version-comparison.js +270 -401
- package/package.json +3 -2
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { FolioAIBlock } from "./types.js";
|
|
1
2
|
//#region src/ai-edits/block-identity.d.ts
|
|
3
|
+
/** Resolve explicit snapshot provenance, falling back to the legacy id-shape convention. */
|
|
4
|
+
declare const folioAIBlockIdStability: ({ id, idStability }: FolioAIBlock) => "stable" | "positional";
|
|
2
5
|
declare const stripBlockIdentityAttrs: (attrs: Record<string, unknown>) => Record<string, unknown>;
|
|
3
6
|
//#endregion
|
|
4
|
-
export { stripBlockIdentityAttrs };
|
|
7
|
+
export { folioAIBlockIdStability, stripBlockIdentityAttrs };
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { getFolioParaIdFromBlockId } from "../types/block-id.js";
|
|
1
2
|
//#region src/ai-edits/block-identity.ts
|
|
2
3
|
const IDENTITY_BLOCK_ATTRS = /* @__PURE__ */ new Set(["paraId", "textId"]);
|
|
4
|
+
/** Resolve explicit snapshot provenance, falling back to the legacy id-shape convention. */
|
|
5
|
+
const folioAIBlockIdStability = ({ id, idStability }) => idStability ?? (getFolioParaIdFromBlockId(id) === null ? "positional" : "stable");
|
|
3
6
|
const stripBlockIdentityAttrs = (attrs) => {
|
|
4
7
|
const next = {};
|
|
5
8
|
for (const [key, value] of Object.entries(attrs)) next[key] = IDENTITY_BLOCK_ATTRS.has(key) ? null : value;
|
|
6
9
|
return next;
|
|
7
10
|
};
|
|
8
11
|
//#endregion
|
|
9
|
-
export { stripBlockIdentityAttrs };
|
|
12
|
+
export { folioAIBlockIdStability, stripBlockIdentityAttrs };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { findParagraphByParaId } from "../prosemirror/utils/findParagraphByParaId.js";
|
|
2
2
|
import { getFolioParaIdFromBlockId, getSequentialFolioBlockIdIndex } from "../types/block-id.js";
|
|
3
|
-
import { buildCleanBlockText } from "./clean-text.js";
|
|
3
|
+
import { buildCleanBlockText, resolveCleanTextRange } from "./clean-text.js";
|
|
4
4
|
import { createFolioAIEditSnapshot, hashFolioAIBlockText, isFolioAIContentBlock } from "./snapshot.js";
|
|
5
5
|
//#region src/ai-edits/blockRange.ts
|
|
6
6
|
const resolveFolioAIBlockRange = ({ blockId, doc, snapshot }) => {
|
|
@@ -28,14 +28,14 @@ const resolveFolioAITextRange = ({ range, doc, snapshot }) => {
|
|
|
28
28
|
const blockNode = doc.nodeAt(blockRange.from);
|
|
29
29
|
if (!blockNode?.isTextblock) return null;
|
|
30
30
|
const cleanBlock = buildCleanBlockText(blockNode, blockRange.from);
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return clampRangeToDocSize(doc.content.size, {
|
|
36
|
-
from,
|
|
37
|
-
to
|
|
31
|
+
const resolved = resolveCleanTextRange({
|
|
32
|
+
cleanBlock,
|
|
33
|
+
startOffset: range.startOffset,
|
|
34
|
+
endOffset: range.endOffset
|
|
38
35
|
});
|
|
36
|
+
if (resolved === null) return null;
|
|
37
|
+
if (hashFolioAIBlockText(cleanBlock.text.slice(range.startOffset, range.endOffset)) !== range.selectedTextHash) return null;
|
|
38
|
+
return clampRangeToDocSize(doc.content.size, resolved);
|
|
39
39
|
};
|
|
40
40
|
/**
|
|
41
41
|
* Resolve a free-text passage inside a block to a `{ from, to }` PM range.
|
|
@@ -66,13 +66,13 @@ const resolvePassageRange = ({ blockId, text, doc, snapshot }) => {
|
|
|
66
66
|
const cleanBlock = buildCleanBlockText(blockNode, blockRange.from);
|
|
67
67
|
const match = matchPassage(cleanBlock.text, needleSource);
|
|
68
68
|
if (match === null) return null;
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
from,
|
|
74
|
-
to
|
|
69
|
+
const resolved = resolveCleanTextRange({
|
|
70
|
+
cleanBlock,
|
|
71
|
+
startOffset: match.start,
|
|
72
|
+
endOffset: match.end
|
|
75
73
|
});
|
|
74
|
+
if (resolved === null) return null;
|
|
75
|
+
return clampRangeToDocSize(doc.content.size, resolved);
|
|
76
76
|
};
|
|
77
77
|
/**
|
|
78
78
|
* Turn a caller-supplied passage into a regex source that tolerates whitespace
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PageBreakRunAttrs } from "../prosemirror/schema/nodes.js";
|
|
1
2
|
import { Node } from "prosemirror-model";
|
|
2
3
|
//#region src/ai-edits/clean-text.d.ts
|
|
3
4
|
/**
|
|
@@ -21,7 +22,45 @@ import { Node } from "prosemirror-model";
|
|
|
21
22
|
type CleanBlockText = {
|
|
22
23
|
text: string;
|
|
23
24
|
offsets: number[];
|
|
25
|
+
/**
|
|
26
|
+
* Zero-width structural carriers projected at their clean-text boundary.
|
|
27
|
+
*
|
|
28
|
+
* A single clean offset cannot identify both sides of an atom: the PM
|
|
29
|
+
* position immediately after the preceding character is before the atom,
|
|
30
|
+
* while the next character starts after it. Callers that select text must
|
|
31
|
+
* resolve through {@link resolveCleanTextRange}; indexing `offsets`
|
|
32
|
+
* directly can accidentally absorb the carrier into an adjacent range.
|
|
33
|
+
*/
|
|
34
|
+
structuralBoundaries: readonly CleanTextStructuralBoundary[];
|
|
24
35
|
};
|
|
36
|
+
type CleanTextStructuralBoundary = {
|
|
37
|
+
type: "pageBreakRun";
|
|
38
|
+
/** Clean-text offset at which the zero-width carrier occurs. */
|
|
39
|
+
offset: number;
|
|
40
|
+
/** PM range owned by the carrier. */
|
|
41
|
+
from: number;
|
|
42
|
+
to: number;
|
|
43
|
+
/** Preserved even though `clear` does not alter page-break layout. */
|
|
44
|
+
clear?: PageBreakRunAttrs["clear"];
|
|
45
|
+
/** Whether the post-tracked-changes projection retains this carrier. */
|
|
46
|
+
presentInCleanView: boolean;
|
|
47
|
+
};
|
|
48
|
+
type ResolveCleanTextRangeOptions = {
|
|
49
|
+
cleanBlock: CleanBlockText;
|
|
50
|
+
startOffset: number;
|
|
51
|
+
endOffset: number;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Resolve clean-text offsets without selecting a zero-width structural atom.
|
|
55
|
+
*
|
|
56
|
+
* A range wholly on one side of a boundary is biased away from the atom. A
|
|
57
|
+
* range spanning both sides is unrepresentable as a generic text selection
|
|
58
|
+
* and returns `null`; a structural operation must own that mutation instead.
|
|
59
|
+
*/
|
|
60
|
+
declare const resolveCleanTextRange: ({ cleanBlock, startOffset, endOffset }: ResolveCleanTextRangeOptions) => {
|
|
61
|
+
from: number;
|
|
62
|
+
to: number;
|
|
63
|
+
} | null;
|
|
25
64
|
declare const buildCleanBlockText: (blockNode: Node, blockFrom: number) => CleanBlockText;
|
|
26
65
|
/**
|
|
27
66
|
* A "redline-aware" view of a textblock: the same left-to-right traversal as
|
|
@@ -44,4 +83,4 @@ declare const buildCleanBlockText: (blockNode: Node, blockFrom: number) => Clean
|
|
|
44
83
|
*/
|
|
45
84
|
declare const buildAnnotatedBlockText: (blockNode: Node) => string;
|
|
46
85
|
//#endregion
|
|
47
|
-
export { CleanBlockText, buildAnnotatedBlockText, buildCleanBlockText };
|
|
86
|
+
export { CleanBlockText, CleanTextStructuralBoundary, buildAnnotatedBlockText, buildCleanBlockText, resolveCleanTextRange };
|
|
@@ -1,4 +1,39 @@
|
|
|
1
|
+
import { expectPageBreakRunAttrs } from "../prosemirror/attrs/index.js";
|
|
1
2
|
//#region src/ai-edits/clean-text.ts
|
|
3
|
+
const EMPTY_CLEAN_TEXT_STRUCTURAL_BOUNDARIES = Object.freeze([]);
|
|
4
|
+
/**
|
|
5
|
+
* Resolve clean-text offsets without selecting a zero-width structural atom.
|
|
6
|
+
*
|
|
7
|
+
* A range wholly on one side of a boundary is biased away from the atom. A
|
|
8
|
+
* range spanning both sides is unrepresentable as a generic text selection
|
|
9
|
+
* and returns `null`; a structural operation must own that mutation instead.
|
|
10
|
+
*/
|
|
11
|
+
const resolveCleanTextRange = ({ cleanBlock, startOffset, endOffset }) => {
|
|
12
|
+
if (!Number.isInteger(startOffset) || !Number.isInteger(endOffset) || startOffset < 0 || endOffset < startOffset || endOffset > cleanBlock.text.length) return null;
|
|
13
|
+
const baseFrom = cleanBlock.offsets[startOffset];
|
|
14
|
+
const baseTo = cleanBlock.offsets[endOffset];
|
|
15
|
+
if (baseFrom === void 0 || baseTo === void 0) return null;
|
|
16
|
+
const { structuralBoundaries } = cleanBlock;
|
|
17
|
+
if (structuralBoundaries.length === 0) return baseFrom <= baseTo ? {
|
|
18
|
+
from: baseFrom,
|
|
19
|
+
to: baseTo
|
|
20
|
+
} : null;
|
|
21
|
+
let from = baseFrom;
|
|
22
|
+
let to = baseTo;
|
|
23
|
+
for (const boundary of structuralBoundaries) {
|
|
24
|
+
if (boundary.offset > startOffset && boundary.offset < endOffset) return null;
|
|
25
|
+
if (boundary.offset === startOffset) from = Math.max(from, boundary.to);
|
|
26
|
+
if (startOffset !== endOffset && boundary.offset === endOffset) to = Math.min(to, boundary.from);
|
|
27
|
+
}
|
|
28
|
+
if (startOffset === endOffset) return {
|
|
29
|
+
from,
|
|
30
|
+
to: from
|
|
31
|
+
};
|
|
32
|
+
return from <= to ? {
|
|
33
|
+
from,
|
|
34
|
+
to
|
|
35
|
+
} : null;
|
|
36
|
+
};
|
|
2
37
|
const DELETION_MARK = "deletion";
|
|
3
38
|
const INSERTION_MARK = "insertion";
|
|
4
39
|
const COMMENT_MARK = "comment";
|
|
@@ -6,8 +41,22 @@ const HIDDEN_MARK = "hidden";
|
|
|
6
41
|
const buildCleanBlockText = (blockNode, blockFrom) => {
|
|
7
42
|
let text = "";
|
|
8
43
|
const offsets = [];
|
|
44
|
+
let structuralBoundaries;
|
|
9
45
|
let lastEnd = blockFrom + 1;
|
|
10
46
|
blockNode.descendants((node, pos) => {
|
|
47
|
+
if (node.type.name === "pageBreakRun") {
|
|
48
|
+
const from = blockFrom + 1 + pos;
|
|
49
|
+
const { clear } = expectPageBreakRunAttrs(node);
|
|
50
|
+
(structuralBoundaries ??= []).push({
|
|
51
|
+
type: "pageBreakRun",
|
|
52
|
+
offset: text.length,
|
|
53
|
+
from,
|
|
54
|
+
to: from + node.nodeSize,
|
|
55
|
+
...clear !== void 0 ? { clear } : {},
|
|
56
|
+
presentInCleanView: !node.marks.some(({ type }) => type.name === DELETION_MARK)
|
|
57
|
+
});
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
11
60
|
if (!node.isText || node.text === void 0) return true;
|
|
12
61
|
if (node.marks.some((mark) => mark.type.name === DELETION_MARK || mark.type.name === HIDDEN_MARK)) return false;
|
|
13
62
|
const startPos = blockFrom + 1 + pos;
|
|
@@ -19,7 +68,8 @@ const buildCleanBlockText = (blockNode, blockFrom) => {
|
|
|
19
68
|
offsets.push(lastEnd);
|
|
20
69
|
return {
|
|
21
70
|
text,
|
|
22
|
-
offsets
|
|
71
|
+
offsets,
|
|
72
|
+
structuralBoundaries: structuralBoundaries ?? EMPTY_CLEAN_TEXT_STRUCTURAL_BOUNDARIES
|
|
23
73
|
};
|
|
24
74
|
};
|
|
25
75
|
/**
|
|
@@ -89,4 +139,4 @@ const renderAnnotatedSegment = ({ annotation, text }) => {
|
|
|
89
139
|
return inner;
|
|
90
140
|
};
|
|
91
141
|
//#endregion
|
|
92
|
-
export { buildAnnotatedBlockText, buildCleanBlockText };
|
|
142
|
+
export { buildAnnotatedBlockText, buildCleanBlockText, resolveCleanTextRange };
|
|
@@ -361,6 +361,9 @@ declare class FolioDocxReviewer {
|
|
|
361
361
|
private resolveEveryStory;
|
|
362
362
|
/** The current document model with edits merged back in. */
|
|
363
363
|
toDocument(): document_d_exports.Document;
|
|
364
|
+
private captureReviewerState;
|
|
365
|
+
private documentFromStateSnapshot;
|
|
366
|
+
private captureSaveSnapshot;
|
|
364
367
|
/**
|
|
365
368
|
* Serialise the reviewed document to a new `.docx` buffer. Tries a selective
|
|
366
369
|
* patch first (only changed paragraphs are rewritten; every other byte of the
|
|
@@ -369,7 +372,6 @@ declare class FolioDocxReviewer {
|
|
|
369
372
|
*/
|
|
370
373
|
toBuffer(): Promise<ArrayBuffer>;
|
|
371
374
|
private assertResolvedStoriesSerialized;
|
|
372
|
-
private getChangedNoteParaIds;
|
|
373
375
|
/**
|
|
374
376
|
* Attempt the selective patch, treating a throw the same as a decline. Odd
|
|
375
377
|
* source XML can make the paragraph diff throw rather than return `null`; a
|
|
@@ -6,21 +6,24 @@ import { parseDocx } from "../docx/parser.js";
|
|
|
6
6
|
import { createReply } from "../docx/replyToComment.js";
|
|
7
7
|
import { repackDocx } from "../docx/rezip.js";
|
|
8
8
|
import { attemptSelectiveSave } from "../docx/selectiveSave.js";
|
|
9
|
-
import {
|
|
9
|
+
import { pluginsForHeadlessRevisionResolution } from "../internal/headlessRevisionResolutionGuard.js";
|
|
10
|
+
import { withTrackedSectionEndpointRemoval } from "../internal/sectionEndpointResolution.js";
|
|
11
|
+
import { acceptAIEditRevision, rejectAIEditRevision, resolveAllChangesInHeadlessState } from "../prosemirror/commands/comments.js";
|
|
10
12
|
import { proseDocToBlocks, updateDocumentContent } from "../prosemirror/conversion/fromProseDoc.js";
|
|
11
13
|
import { footnoteToProseDoc, headerFooterToProseDoc, toProseDoc } from "../prosemirror/conversion/toProseDoc.js";
|
|
12
14
|
import { ensureBaseDirectionInState } from "../prosemirror/extensions/features/AutoBidiDetectionExtension.js";
|
|
13
|
-
import { getChangedParagraphIds, hasStructuralChanges, hasUntrackedChanges } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
|
|
14
|
-
import {
|
|
15
|
+
import { getChangedParagraphIds, getTrackedSectionEndpointRemoval, hasStructuralChanges, hasUntrackedChanges } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
|
|
16
|
+
import { createDocumentNumberingPlugin } from "../prosemirror/plugins/documentNumbering.js";
|
|
17
|
+
import { createDocumentStylesPlugin, getDocumentStyleResolver } from "../prosemirror/plugins/documentStyles.js";
|
|
15
18
|
import { schema, singletonManager } from "../prosemirror/schema/index.js";
|
|
16
19
|
import { deterministicHexId } from "../utils/hexId.js";
|
|
17
20
|
import { buildAnnotatedBlockText } from "./clean-text.js";
|
|
18
21
|
import { getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
|
|
19
|
-
import {
|
|
22
|
+
import { createFolioAIEditSnapshotWithStyleResolver, folioStoryTables, isFolioAIContentBlock, normalizeFolioAIBlockText } from "./snapshot.js";
|
|
20
23
|
import { matchTableGeometry } from "./table-geometry.js";
|
|
21
24
|
import { TaggedError } from "better-result";
|
|
22
|
-
import { EditorState } from "prosemirror-state";
|
|
23
25
|
import { Fragment } from "prosemirror-model";
|
|
26
|
+
import { EditorState } from "prosemirror-state";
|
|
24
27
|
//#region src/ai-edits/headless.ts
|
|
25
28
|
/**
|
|
26
29
|
* Headless `.docx` review path: buffer -> apply AI edits -> buffer, with
|
|
@@ -113,7 +116,8 @@ const ensureDeterministicParaIdsInDoc = (doc) => {
|
|
|
113
116
|
seen.add(paraId);
|
|
114
117
|
next = recreateProseNodeWithParagraphPropertySource(child, { attrs: {
|
|
115
118
|
...child.attrs,
|
|
116
|
-
paraId
|
|
119
|
+
paraId,
|
|
120
|
+
idStability: "positional"
|
|
117
121
|
} });
|
|
118
122
|
}
|
|
119
123
|
const paraId = next.attrs["paraId"];
|
|
@@ -138,7 +142,24 @@ const FOLIO_REVIEWED_VIEWS = Object.freeze([
|
|
|
138
142
|
const FOLIO_RESOLVED_REVIEWED_VIEWS = Object.freeze(["original", "final"]);
|
|
139
143
|
var UnsupportedFolioReviewedViewError = class extends TaggedError("UnsupportedFolioReviewedViewError") {};
|
|
140
144
|
var FolioDocumentStoryNotFoundError = class extends TaggedError("FolioDocumentStoryNotFoundError") {};
|
|
145
|
+
const FOLIO_RESOLVED_STORY_SERIALIZATION_MISMATCHES = Object.freeze({
|
|
146
|
+
storyMissing: "story-missing",
|
|
147
|
+
revisionMarkupRemains: "revision-markup-remains",
|
|
148
|
+
textProjection: "text-projection",
|
|
149
|
+
blockProjection: "block-projection"
|
|
150
|
+
});
|
|
141
151
|
var FolioResolvedStorySerializationError = class extends TaggedError("FolioResolvedStorySerializationError") {};
|
|
152
|
+
/**
|
|
153
|
+
* `idStability` records how an id entered the current snapshot. A synthesized
|
|
154
|
+
* paraId is positional until save writes it into the package, then becomes an
|
|
155
|
+
* authored stable id when reopened. Compare the persisted block projection
|
|
156
|
+
* without changing that live snapshot identity contract.
|
|
157
|
+
*/
|
|
158
|
+
const resolvedStoryBlockProjection = (block) => {
|
|
159
|
+
const persisted = { ...block };
|
|
160
|
+
delete persisted.idStability;
|
|
161
|
+
return persisted;
|
|
162
|
+
};
|
|
142
163
|
const MAIN_STORY = Object.freeze({ type: "main" });
|
|
143
164
|
const headerFooterStoryKey = ({ type, relationshipId }) => `${type}:${relationshipId}`;
|
|
144
165
|
const noteStoryKey = ({ type, noteId }) => `${type}:${noteId}`;
|
|
@@ -146,19 +167,18 @@ const secondaryStoryKey = (story) => story.type === "header" || story.type === "
|
|
|
146
167
|
const editableStoryKey = (story) => story.type === "main" ? "main" : secondaryStoryKey(story);
|
|
147
168
|
const isFolioReviewedView = (value) => FOLIO_REVIEWED_VIEWS.some((view) => view === value);
|
|
148
169
|
const isFolioResolvedReviewedView = (value) => FOLIO_RESOLVED_REVIEWED_VIEWS.some((view) => view === value);
|
|
149
|
-
const applyCommandToState = (state, command) => {
|
|
150
|
-
let nextState = state;
|
|
151
|
-
command(state, (transaction) => {
|
|
152
|
-
nextState = nextState.apply(transaction);
|
|
153
|
-
});
|
|
154
|
-
return nextState;
|
|
155
|
-
};
|
|
156
170
|
const resolveReviewedState = (state, view) => {
|
|
157
171
|
if (view === "current-markup") return state;
|
|
158
|
-
return
|
|
172
|
+
return resolveAllChangesInHeadlessState(state, view === "original" ? "reject" : "accept");
|
|
159
173
|
};
|
|
174
|
+
const createHeadlessPlugins = (styles, numbering) => [
|
|
175
|
+
...pluginsForHeadlessRevisionResolution(singletonManager.getPlugins()),
|
|
176
|
+
createDocumentStylesPlugin(styles),
|
|
177
|
+
createDocumentNumberingPlugin(numbering)
|
|
178
|
+
];
|
|
179
|
+
const createStateSnapshot = (state) => createFolioAIEditSnapshotWithStyleResolver(state.doc, getDocumentStyleResolver(state));
|
|
160
180
|
const formatStoryStateForLLM = (state, annotated) => {
|
|
161
|
-
const snapshot =
|
|
181
|
+
const snapshot = createStateSnapshot(state);
|
|
162
182
|
const blocks = snapshot.blocks.filter(isFolioAIContentBlock);
|
|
163
183
|
if (!annotated) return blocks.map(formatBlockForLLM).join("\n");
|
|
164
184
|
const nodeByStart = /* @__PURE__ */ new Map();
|
|
@@ -252,7 +272,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
252
272
|
preloadFonts: false,
|
|
253
273
|
password: options.password
|
|
254
274
|
});
|
|
255
|
-
const plugins =
|
|
275
|
+
const plugins = createHeadlessPlugins(baseDocument.package.styles, baseDocument.package.numbering);
|
|
256
276
|
const state = ensureBaseDirectionInState(EditorState.create({
|
|
257
277
|
schema,
|
|
258
278
|
doc: ensureDeterministicParaIdsInDoc(toProseDoc(baseDocument)),
|
|
@@ -270,7 +290,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
270
290
|
* the anchor map the apply layer resolves operations against.
|
|
271
291
|
*/
|
|
272
292
|
snapshot() {
|
|
273
|
-
return
|
|
293
|
+
return createStateSnapshot(this.state);
|
|
274
294
|
}
|
|
275
295
|
/**
|
|
276
296
|
* The package's numbering, flattened to what a reader sees: one entry per
|
|
@@ -317,7 +337,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
317
337
|
/** Snapshot one editable story into stable, operation-ready blocks. */
|
|
318
338
|
snapshotStory(story) {
|
|
319
339
|
const state = this.getEditableStoryState(story);
|
|
320
|
-
return state ?
|
|
340
|
+
return state ? createStateSnapshot(state) : null;
|
|
321
341
|
}
|
|
322
342
|
/**
|
|
323
343
|
* One story's tables, in the document order {@link snapshotStory} numbers
|
|
@@ -380,7 +400,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
380
400
|
return {
|
|
381
401
|
story,
|
|
382
402
|
view,
|
|
383
|
-
snapshot:
|
|
403
|
+
snapshot: createStateSnapshot(state),
|
|
384
404
|
text: formatStoryStateForLLM(state, view === "current-markup"),
|
|
385
405
|
changes: getTrackedChangesFromDoc(state.doc)
|
|
386
406
|
};
|
|
@@ -398,7 +418,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
398
418
|
this.resolvedStoryExpectations.set(editableStoryKey(story), {
|
|
399
419
|
story,
|
|
400
420
|
text: formatStoryStateForLLM(resolvedState, false),
|
|
401
|
-
blocks:
|
|
421
|
+
blocks: createStateSnapshot(resolvedState).blocks.map(resolvedStoryBlockProjection)
|
|
402
422
|
});
|
|
403
423
|
return true;
|
|
404
424
|
}
|
|
@@ -464,7 +484,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
464
484
|
};
|
|
465
485
|
const result = applyFolioDocumentOperations({
|
|
466
486
|
view,
|
|
467
|
-
snapshot: snapshot ??
|
|
487
|
+
snapshot: snapshot ?? createStateSnapshot(beforeState),
|
|
468
488
|
batch,
|
|
469
489
|
story: story.type === "main" ? "main" : story,
|
|
470
490
|
author: this.author,
|
|
@@ -530,7 +550,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
530
550
|
* feed straight back into {@link applyOperations} and accept / reject.
|
|
531
551
|
*/
|
|
532
552
|
getContent() {
|
|
533
|
-
return this.snapshot().blocks;
|
|
553
|
+
return [...this.snapshot().blocks];
|
|
534
554
|
}
|
|
535
555
|
/**
|
|
536
556
|
* The body as LLM-ready plain text: one line per block, each prefixed with
|
|
@@ -767,29 +787,73 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
767
787
|
* document still carries a redline nobody can see from the body.
|
|
768
788
|
*/
|
|
769
789
|
acceptAll() {
|
|
770
|
-
return this.resolveEveryStory(
|
|
790
|
+
return this.resolveEveryStory("accept");
|
|
771
791
|
}
|
|
772
792
|
/** Reject every tracked change in the package. See {@link acceptAll}. */
|
|
773
793
|
rejectAll() {
|
|
774
|
-
return this.resolveEveryStory(
|
|
794
|
+
return this.resolveEveryStory("reject");
|
|
775
795
|
}
|
|
776
|
-
resolveEveryStory(
|
|
796
|
+
resolveEveryStory(mode) {
|
|
777
797
|
let count = 0;
|
|
778
798
|
for (const { handle } of this.listStories()) {
|
|
779
799
|
const state = this.getEditableStoryState(handle);
|
|
780
800
|
if (!state) continue;
|
|
781
801
|
count += getTrackedChangesFromDoc(state.doc).length;
|
|
782
|
-
this.
|
|
802
|
+
this.setEditableStoryState(handle, resolveAllChangesInHeadlessState(state, mode));
|
|
783
803
|
}
|
|
784
804
|
return count;
|
|
785
805
|
}
|
|
786
806
|
/** The current document model with edits merged back in. */
|
|
787
807
|
toDocument() {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
808
|
+
return this.documentFromStateSnapshot(this.captureReviewerState());
|
|
809
|
+
}
|
|
810
|
+
captureReviewerState() {
|
|
811
|
+
const secondaryStoryStates = [];
|
|
812
|
+
for (const { handle, initialState, state } of this.secondaryStoryStates.values()) secondaryStoryStates.push({
|
|
813
|
+
handle,
|
|
814
|
+
initialState,
|
|
815
|
+
state
|
|
816
|
+
});
|
|
817
|
+
return {
|
|
818
|
+
mainState: this.state,
|
|
819
|
+
secondaryStoryStates,
|
|
820
|
+
createdComments: [...this.createdComments],
|
|
821
|
+
resolvedOverrides: new Map(this.resolvedOverrides),
|
|
822
|
+
resolvedStoryExpectations: [...this.resolvedStoryExpectations.values()]
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
documentFromStateSnapshot(snapshot) {
|
|
826
|
+
const document = updateDocumentContent(this.baseDocument, snapshot.mainState.doc);
|
|
827
|
+
this.mergeEditedSecondaryStories(document, snapshot.secondaryStoryStates);
|
|
828
|
+
if (snapshot.createdComments.length > 0 || snapshot.resolvedOverrides.size > 0) document.package.document.comments = this.withResolvedOverrides([...document.package.document.comments ?? [], ...snapshot.createdComments], snapshot.resolvedOverrides);
|
|
791
829
|
return document;
|
|
792
830
|
}
|
|
831
|
+
captureSaveSnapshot() {
|
|
832
|
+
const snapshot = this.captureReviewerState();
|
|
833
|
+
const changedParaIds = new Set(getChangedParagraphIds(snapshot.mainState));
|
|
834
|
+
let structuralChange = hasStructuralChanges(snapshot.mainState);
|
|
835
|
+
let untrackedChanges = hasUntrackedChanges(snapshot.mainState);
|
|
836
|
+
const changedNoteParaIds = /* @__PURE__ */ new Set();
|
|
837
|
+
for (const entry of snapshot.secondaryStoryStates) {
|
|
838
|
+
if (entry.handle.type !== "footnote" && entry.handle.type !== "endnote") continue;
|
|
839
|
+
for (const paraId of getChangedParagraphIds(entry.state)) {
|
|
840
|
+
changedParaIds.add(paraId);
|
|
841
|
+
changedNoteParaIds.add(paraId);
|
|
842
|
+
}
|
|
843
|
+
structuralChange ||= hasStructuralChanges(entry.state);
|
|
844
|
+
untrackedChanges ||= hasUntrackedChanges(entry.state);
|
|
845
|
+
}
|
|
846
|
+
return {
|
|
847
|
+
document: this.documentFromStateSnapshot(snapshot),
|
|
848
|
+
path: structuralChange || untrackedChanges ? { type: "full-repack" } : {
|
|
849
|
+
type: "selective-first",
|
|
850
|
+
changedParaIds
|
|
851
|
+
},
|
|
852
|
+
changedNoteParaIds,
|
|
853
|
+
sectionEndpointRemoval: getTrackedSectionEndpointRemoval(snapshot.mainState),
|
|
854
|
+
resolvedStoryExpectations: snapshot.resolvedStoryExpectations
|
|
855
|
+
};
|
|
856
|
+
}
|
|
793
857
|
/**
|
|
794
858
|
* Serialise the reviewed document to a new `.docx` buffer. Tries a selective
|
|
795
859
|
* patch first (only changed paragraphs are rewritten; every other byte of the
|
|
@@ -797,67 +861,65 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
797
861
|
* structural edits — the same two-tier path the editor's save uses.
|
|
798
862
|
*/
|
|
799
863
|
async toBuffer() {
|
|
800
|
-
const
|
|
801
|
-
const selective = await this.trySelectiveSave(document);
|
|
864
|
+
const save = this.captureSaveSnapshot();
|
|
865
|
+
const selective = save.path.type === "selective-first" ? await this.trySelectiveSave(save.document, save.path.changedParaIds) : null;
|
|
802
866
|
if (selective) {
|
|
803
|
-
await this.assertResolvedStoriesSerialized(selective);
|
|
867
|
+
await this.assertResolvedStoriesSerialized(selective, save.resolvedStoryExpectations);
|
|
804
868
|
return selective;
|
|
805
869
|
}
|
|
806
|
-
const
|
|
807
|
-
...document,
|
|
870
|
+
const repackDocument = {
|
|
871
|
+
...save.document,
|
|
808
872
|
originalBuffer: this.originalBuffer
|
|
809
|
-
}
|
|
810
|
-
await
|
|
873
|
+
};
|
|
874
|
+
const buffer = save.sectionEndpointRemoval ? await withTrackedSectionEndpointRemoval({
|
|
875
|
+
document: repackDocument,
|
|
876
|
+
resolution: save.sectionEndpointRemoval,
|
|
877
|
+
repack: () => repackDocx(repackDocument, { changedNoteParaIds: save.changedNoteParaIds })
|
|
878
|
+
}) : await repackDocx(repackDocument, { changedNoteParaIds: save.changedNoteParaIds });
|
|
879
|
+
await this.assertResolvedStoriesSerialized(buffer, save.resolvedStoryExpectations);
|
|
811
880
|
return buffer;
|
|
812
881
|
}
|
|
813
|
-
async assertResolvedStoriesSerialized(buffer) {
|
|
814
|
-
if (
|
|
882
|
+
async assertResolvedStoriesSerialized(buffer, expectations) {
|
|
883
|
+
if (expectations.length === 0) return;
|
|
815
884
|
const reopened = await FolioDocxReviewer.fromBuffer(buffer);
|
|
816
|
-
for (const { story, text, blocks } of
|
|
885
|
+
for (const { story, text, blocks } of expectations) {
|
|
817
886
|
const serialized = reopened.readReviewedStory({
|
|
818
887
|
story,
|
|
819
888
|
view: "current-markup"
|
|
820
889
|
});
|
|
821
890
|
const serializedState = reopened.getEditableStoryState(story);
|
|
822
|
-
|
|
891
|
+
const serializedText = serializedState ? formatStoryStateForLLM(serializedState, false) : null;
|
|
892
|
+
const serializedBlocks = serializedState ? createStateSnapshot(serializedState).blocks.map(resolvedStoryBlockProjection) : null;
|
|
893
|
+
const mismatches = [];
|
|
894
|
+
if (!serialized || !serializedState) mismatches.push(FOLIO_RESOLVED_STORY_SERIALIZATION_MISMATCHES.storyMissing);
|
|
895
|
+
else {
|
|
896
|
+
if (serialized.changes.length > 0) mismatches.push(FOLIO_RESOLVED_STORY_SERIALIZATION_MISMATCHES.revisionMarkupRemains);
|
|
897
|
+
if (serializedText !== text) mismatches.push(FOLIO_RESOLVED_STORY_SERIALIZATION_MISMATCHES.textProjection);
|
|
898
|
+
if (JSON.stringify(serializedBlocks) !== JSON.stringify(blocks)) mismatches.push(FOLIO_RESOLVED_STORY_SERIALIZATION_MISMATCHES.blockProjection);
|
|
899
|
+
}
|
|
900
|
+
if (mismatches.length === 0) continue;
|
|
823
901
|
throw new FolioResolvedStorySerializationError({
|
|
824
902
|
message: "Resolved document story did not persist to the serialized DOCX.",
|
|
825
903
|
story,
|
|
826
|
-
|
|
827
|
-
|
|
904
|
+
mismatches,
|
|
905
|
+
expectedBlockCount: blocks.length,
|
|
906
|
+
actualBlockCount: serializedBlocks?.length ?? null,
|
|
828
907
|
remainingChangeCount: serialized?.changes.length ?? null
|
|
829
908
|
});
|
|
830
909
|
}
|
|
831
910
|
}
|
|
832
|
-
getChangedNoteParaIds() {
|
|
833
|
-
const changed = /* @__PURE__ */ new Set();
|
|
834
|
-
for (const entry of this.secondaryStoryStates.values()) {
|
|
835
|
-
if (entry.handle.type !== "footnote" && entry.handle.type !== "endnote") continue;
|
|
836
|
-
for (const paraId of getChangedParagraphIds(entry.state)) changed.add(paraId);
|
|
837
|
-
}
|
|
838
|
-
return changed;
|
|
839
|
-
}
|
|
840
911
|
/**
|
|
841
912
|
* Attempt the selective patch, treating a throw the same as a decline. Odd
|
|
842
913
|
* source XML can make the paragraph diff throw rather than return `null`; a
|
|
843
914
|
* full repack is the correct lossless fallback in both cases, so the fallback
|
|
844
915
|
* is the graceful handling — no separate error surface is needed here.
|
|
845
916
|
*/
|
|
846
|
-
async trySelectiveSave(document) {
|
|
847
|
-
const changedParaIds = new Set(getChangedParagraphIds(this.state));
|
|
848
|
-
let structuralChange = hasStructuralChanges(this.state);
|
|
849
|
-
let untrackedChanges = hasUntrackedChanges(this.state);
|
|
850
|
-
for (const entry of this.secondaryStoryStates.values()) {
|
|
851
|
-
if (entry.handle.type !== "footnote" && entry.handle.type !== "endnote") continue;
|
|
852
|
-
for (const paraId of getChangedParagraphIds(entry.state)) changedParaIds.add(paraId);
|
|
853
|
-
structuralChange ||= hasStructuralChanges(entry.state);
|
|
854
|
-
untrackedChanges ||= hasUntrackedChanges(entry.state);
|
|
855
|
-
}
|
|
917
|
+
async trySelectiveSave(document, changedParaIds) {
|
|
856
918
|
try {
|
|
857
919
|
return await attemptSelectiveSave(document, this.originalBuffer, {
|
|
858
920
|
changedParaIds,
|
|
859
|
-
structuralChange,
|
|
860
|
-
hasUntrackedChanges:
|
|
921
|
+
structuralChange: false,
|
|
922
|
+
hasUntrackedChanges: false
|
|
861
923
|
});
|
|
862
924
|
} catch {
|
|
863
925
|
return null;
|
|
@@ -878,7 +940,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
878
940
|
const state = ensureBaseDirectionInState(EditorState.create({
|
|
879
941
|
schema,
|
|
880
942
|
doc: ensureDeterministicParaIdsInDoc(storyDoc),
|
|
881
|
-
plugins:
|
|
943
|
+
plugins: createHeadlessPlugins(this.baseDocument.package.styles, this.baseDocument.package.numbering)
|
|
882
944
|
}));
|
|
883
945
|
this.secondaryStoryStates.set(key, {
|
|
884
946
|
handle: story,
|
|
@@ -924,19 +986,19 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
924
986
|
const sourceText = source.type === "footnote" ? getFootnoteText(source) : getEndnoteText(source);
|
|
925
987
|
return normalizeFolioAIBlockText(state?.doc.textContent ?? sourceText);
|
|
926
988
|
}
|
|
927
|
-
mergeEditedSecondaryStories(document) {
|
|
989
|
+
mergeEditedSecondaryStories(document, secondaryStoryStates = this.secondaryStoryStates.values()) {
|
|
928
990
|
let headers;
|
|
929
991
|
let footers;
|
|
930
992
|
let footnotes;
|
|
931
993
|
let endnotes;
|
|
932
|
-
for (const entry of
|
|
994
|
+
for (const entry of secondaryStoryStates) {
|
|
933
995
|
if (entry.state === entry.initialState) continue;
|
|
934
996
|
if (entry.handle.type === "header" || entry.handle.type === "footer") {
|
|
935
997
|
const source = this.getHeaderFooterStory(entry.handle);
|
|
936
998
|
if (!source) continue;
|
|
937
999
|
const edited = {
|
|
938
1000
|
...source,
|
|
939
|
-
content: proseDocToBlocks(entry.state.doc, source.content)
|
|
1001
|
+
content: proseDocToBlocks(entry.state.doc, source.content, document.package.styles)
|
|
940
1002
|
};
|
|
941
1003
|
if (entry.handle.type === "header") {
|
|
942
1004
|
headers ??= new Map(document.package.headers);
|
|
@@ -953,7 +1015,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
953
1015
|
if (!source) continue;
|
|
954
1016
|
const edited = {
|
|
955
1017
|
...source,
|
|
956
|
-
content: proseDocToBlocks(entry.state.doc, source.content)
|
|
1018
|
+
content: proseDocToBlocks(entry.state.doc, source.content, document.package.styles)
|
|
957
1019
|
};
|
|
958
1020
|
footnotes ??= [...document.package.footnotes ?? []];
|
|
959
1021
|
const index = footnotes.findIndex((note) => note.id === noteId);
|
|
@@ -965,7 +1027,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
965
1027
|
if (!source) continue;
|
|
966
1028
|
const edited = {
|
|
967
1029
|
...source,
|
|
968
|
-
content: proseDocToBlocks(entry.state.doc, source.content)
|
|
1030
|
+
content: proseDocToBlocks(entry.state.doc, source.content, document.package.styles)
|
|
969
1031
|
};
|
|
970
1032
|
endnotes ??= [...document.package.endnotes ?? []];
|
|
971
1033
|
const index = endnotes.findIndex((note) => note.id === noteId);
|
|
@@ -977,10 +1039,10 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
977
1039
|
if (endnotes) document.package.endnotes = endnotes;
|
|
978
1040
|
}
|
|
979
1041
|
/** Apply any {@link resolveComment} overrides recorded for these comments. */
|
|
980
|
-
withResolvedOverrides(comments) {
|
|
981
|
-
if (
|
|
1042
|
+
withResolvedOverrides(comments, resolvedOverrides = this.resolvedOverrides) {
|
|
1043
|
+
if (resolvedOverrides.size === 0) return [...comments];
|
|
982
1044
|
return comments.map((comment) => {
|
|
983
|
-
const override =
|
|
1045
|
+
const override = resolvedOverrides.get(comment.id);
|
|
984
1046
|
return override === void 0 ? comment : {
|
|
985
1047
|
...comment,
|
|
986
1048
|
done: override
|