@stll/folio-core 0.35.1 → 0.37.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/README.md +59 -1
- package/dist/ai-edits/apply.js +878 -377
- 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 +141 -73
- 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 +105 -20
- 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 +62 -61
- package/dist/ai-edits/word-diff.d.ts +18 -3
- package/dist/ai-edits/word-diff.js +556 -67
- 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 +48 -21
- 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 +171 -566
- package/dist/compare/scenario.d.ts +2 -2
- package/dist/compare/types.d.ts +3 -3
- package/dist/compare/verification.d.ts +8 -9
- package/dist/compare/verification.js +72 -50
- package/dist/compat/eigenpal.d.ts +5 -3
- package/dist/compat/eigenpal.js +3 -2
- package/dist/controller/headerFooterEditorManager.js +18 -10
- package/dist/controller/hiddenEditorApi.js +1 -1
- 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 -4
- package/dist/document-operations.js +104 -12
- package/dist/docx/blockContentParser.js +6 -27
- package/dist/docx/commentParser.js +2 -4
- package/dist/docx/commentRangeIntegrity.js +2 -4
- package/dist/docx/documentClone.d.ts +2 -0
- package/dist/docx/documentClone.js +2 -0
- package/dist/docx/ensureParaIds.js +9 -4
- package/dist/docx/headerFooterParser.js +2 -4
- package/dist/docx/normalizeBaseDirection.js +5 -7
- package/dist/docx/numberingParser.d.ts +3 -1
- package/dist/docx/numberingParser.js +4 -1
- package/dist/docx/paragraphParser.js +34 -35
- package/dist/docx/paragraphPropertySource.d.ts +49 -0
- package/dist/docx/paragraphPropertySource.js +113 -0
- 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/runConsolidator.js +2 -4
- package/dist/docx/runParser.js +6 -17
- package/dist/docx/sectionParser.js +1 -15
- package/dist/docx/serializer/paragraphSerializer.js +289 -30
- package/dist/docx/serializer/runSerializer.js +6 -12
- package/dist/docx/serializer/sectionPropertiesSerializer.js +2 -10
- package/dist/docx/serializer/tableSerializer.js +4 -23
- package/dist/docx/serializer/trackedChangeAttributes.d.ts +11 -0
- package/dist/docx/serializer/trackedChangeAttributes.js +31 -0
- package/dist/docx/server/build.d.ts +1 -1
- package/dist/docx/server/createBilingualDocument.js +8 -10
- package/dist/docx/server/evaluateDocxXmlPatchProposal.js +22 -13
- package/dist/docx/server/validateDocxConformance.js +17 -13
- package/dist/docx/tableParser.js +1 -37
- package/dist/docx/trackedChangeInfo.d.ts +11 -0
- package/dist/docx/trackedChangeInfo.js +38 -0
- package/dist/docx/verbatimCapture.d.ts +20 -9
- package/dist/docx/verbatimCapture.js +208 -8
- package/dist/docx/xmlParser.d.ts +3 -1
- package/dist/docx/xmlParser.js +6 -1
- package/dist/docx/xmlSafety.d.ts +1 -1
- package/dist/docx/xmlSafety.js +157 -0
- 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 +155 -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 +52 -9
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -0
- package/dist/layout-bridge/convert/toFlowBlocks.js +391 -82
- 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/markdown/renderBlock.js +7 -10
- 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 +128 -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 +551 -87
- 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 +27 -3
- package/dist/prosemirror/commands/propertyChangeScope.js +85 -14
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -5
- package/dist/prosemirror/conversion/fromProseDoc.js +935 -223
- package/dist/prosemirror/conversion/runShadingMark.js +2 -2
- package/dist/prosemirror/conversion/toProseDoc.d.ts +26 -1
- package/dist/prosemirror/conversion/toProseDoc.js +439 -291
- package/dist/prosemirror/extensions/StarterKit.js +4 -0
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +54 -6
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -1
- package/dist/prosemirror/extensions/features/ListExtension.js +95 -123
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +19 -6
- 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/TrackedChangeExtensions.js +2 -0
- 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/paragraphAlignment.d.ts +12 -0
- package/dist/prosemirror/paragraphAlignment.js +13 -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 +9 -11
- 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 +84 -9
- package/dist/prosemirror/schema/marks.js +73 -1
- package/dist/prosemirror/schema/nodes.d.ts +30 -6
- package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +14 -2
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +45 -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/redline.js +4 -1
- 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/utils/mergeDocumentContent.js +3 -3
- package/dist/utils/replaceText.js +2 -1
- package/dist/version-comparison.d.ts +38 -44
- package/dist/version-comparison.js +270 -399
- package/dist/watermark/index.js +2 -4
- package/package.json +8 -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
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import { applyFolioDocumentOperations } from "../document-operations.js";
|
|
2
2
|
import { getEndnoteText, getFootnoteText, isSeparatorEndnote, isSeparatorFootnote } from "../docx/footnoteParser.js";
|
|
3
3
|
import { getHeaderFooterText } from "../docx/headerFooterParser.js";
|
|
4
|
+
import { recreateProseNodeWithParagraphPropertySource, transferProseParagraphPropertySource } from "../docx/paragraphPropertySource.js";
|
|
4
5
|
import { parseDocx } from "../docx/parser.js";
|
|
5
6
|
import { createReply } from "../docx/replyToComment.js";
|
|
6
7
|
import { repackDocx } from "../docx/rezip.js";
|
|
7
8
|
import { attemptSelectiveSave } from "../docx/selectiveSave.js";
|
|
8
|
-
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";
|
|
9
12
|
import { proseDocToBlocks, updateDocumentContent } from "../prosemirror/conversion/fromProseDoc.js";
|
|
10
13
|
import { footnoteToProseDoc, headerFooterToProseDoc, toProseDoc } from "../prosemirror/conversion/toProseDoc.js";
|
|
11
14
|
import { ensureBaseDirectionInState } from "../prosemirror/extensions/features/AutoBidiDetectionExtension.js";
|
|
12
|
-
import { getChangedParagraphIds, hasStructuralChanges, hasUntrackedChanges } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
|
|
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";
|
|
13
18
|
import { schema, singletonManager } from "../prosemirror/schema/index.js";
|
|
14
19
|
import { deterministicHexId } from "../utils/hexId.js";
|
|
15
20
|
import { buildAnnotatedBlockText } from "./clean-text.js";
|
|
16
21
|
import { getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
|
|
17
|
-
import {
|
|
22
|
+
import { createFolioAIEditSnapshotWithStyleResolver, folioStoryTables, isFolioAIContentBlock, normalizeFolioAIBlockText } from "./snapshot.js";
|
|
18
23
|
import { matchTableGeometry } from "./table-geometry.js";
|
|
19
24
|
import { TaggedError } from "better-result";
|
|
20
25
|
import { Fragment } from "prosemirror-model";
|
|
@@ -109,14 +114,17 @@ const ensureDeterministicParaIdsInDoc = (doc) => {
|
|
|
109
114
|
let paraId = deterministicHexId(`${child.textContent}:${ordinal}`);
|
|
110
115
|
for (let salt = 1; seen.has(paraId); salt++) paraId = deterministicHexId(`${child.textContent}:${ordinal}:${salt}`);
|
|
111
116
|
seen.add(paraId);
|
|
112
|
-
next = child
|
|
117
|
+
next = recreateProseNodeWithParagraphPropertySource(child, { attrs: {
|
|
113
118
|
...child.attrs,
|
|
114
|
-
paraId
|
|
115
|
-
|
|
119
|
+
paraId,
|
|
120
|
+
idStability: "positional"
|
|
121
|
+
} });
|
|
116
122
|
}
|
|
123
|
+
const paraId = next.attrs["paraId"];
|
|
124
|
+
if (typeof paraId === "string") transferProseParagraphPropertySource(next, child, paraId);
|
|
117
125
|
} else if (child.childCount > 0) {
|
|
118
126
|
const content = rewrite(child);
|
|
119
|
-
if (content !== child.content) next = child
|
|
127
|
+
if (content !== child.content) next = recreateProseNodeWithParagraphPropertySource(child, { content });
|
|
120
128
|
}
|
|
121
129
|
if (next !== child) changed = true;
|
|
122
130
|
children.push(next);
|
|
@@ -124,7 +132,7 @@ const ensureDeterministicParaIdsInDoc = (doc) => {
|
|
|
124
132
|
return changed ? Fragment.fromArray(children) : parent.content;
|
|
125
133
|
};
|
|
126
134
|
const content = rewrite(doc);
|
|
127
|
-
return content === doc.content ? doc : doc
|
|
135
|
+
return content === doc.content ? doc : recreateProseNodeWithParagraphPropertySource(doc, { content });
|
|
128
136
|
};
|
|
129
137
|
const FOLIO_REVIEWED_VIEWS = Object.freeze([
|
|
130
138
|
"original",
|
|
@@ -134,7 +142,24 @@ const FOLIO_REVIEWED_VIEWS = Object.freeze([
|
|
|
134
142
|
const FOLIO_RESOLVED_REVIEWED_VIEWS = Object.freeze(["original", "final"]);
|
|
135
143
|
var UnsupportedFolioReviewedViewError = class extends TaggedError("UnsupportedFolioReviewedViewError") {};
|
|
136
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
|
+
});
|
|
137
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
|
+
};
|
|
138
163
|
const MAIN_STORY = Object.freeze({ type: "main" });
|
|
139
164
|
const headerFooterStoryKey = ({ type, relationshipId }) => `${type}:${relationshipId}`;
|
|
140
165
|
const noteStoryKey = ({ type, noteId }) => `${type}:${noteId}`;
|
|
@@ -142,19 +167,18 @@ const secondaryStoryKey = (story) => story.type === "header" || story.type === "
|
|
|
142
167
|
const editableStoryKey = (story) => story.type === "main" ? "main" : secondaryStoryKey(story);
|
|
143
168
|
const isFolioReviewedView = (value) => FOLIO_REVIEWED_VIEWS.some((view) => view === value);
|
|
144
169
|
const isFolioResolvedReviewedView = (value) => FOLIO_RESOLVED_REVIEWED_VIEWS.some((view) => view === value);
|
|
145
|
-
const applyCommandToState = (state, command) => {
|
|
146
|
-
let nextState = state;
|
|
147
|
-
command(state, (transaction) => {
|
|
148
|
-
nextState = nextState.apply(transaction);
|
|
149
|
-
});
|
|
150
|
-
return nextState;
|
|
151
|
-
};
|
|
152
170
|
const resolveReviewedState = (state, view) => {
|
|
153
171
|
if (view === "current-markup") return state;
|
|
154
|
-
return
|
|
172
|
+
return resolveAllChangesInHeadlessState(state, view === "original" ? "reject" : "accept");
|
|
155
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));
|
|
156
180
|
const formatStoryStateForLLM = (state, annotated) => {
|
|
157
|
-
const snapshot =
|
|
181
|
+
const snapshot = createStateSnapshot(state);
|
|
158
182
|
const blocks = snapshot.blocks.filter(isFolioAIContentBlock);
|
|
159
183
|
if (!annotated) return blocks.map(formatBlockForLLM).join("\n");
|
|
160
184
|
const nodeByStart = /* @__PURE__ */ new Map();
|
|
@@ -248,7 +272,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
248
272
|
preloadFonts: false,
|
|
249
273
|
password: options.password
|
|
250
274
|
});
|
|
251
|
-
const plugins =
|
|
275
|
+
const plugins = createHeadlessPlugins(baseDocument.package.styles, baseDocument.package.numbering);
|
|
252
276
|
const state = ensureBaseDirectionInState(EditorState.create({
|
|
253
277
|
schema,
|
|
254
278
|
doc: ensureDeterministicParaIdsInDoc(toProseDoc(baseDocument)),
|
|
@@ -266,7 +290,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
266
290
|
* the anchor map the apply layer resolves operations against.
|
|
267
291
|
*/
|
|
268
292
|
snapshot() {
|
|
269
|
-
return
|
|
293
|
+
return createStateSnapshot(this.state);
|
|
270
294
|
}
|
|
271
295
|
/**
|
|
272
296
|
* The package's numbering, flattened to what a reader sees: one entry per
|
|
@@ -313,7 +337,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
313
337
|
/** Snapshot one editable story into stable, operation-ready blocks. */
|
|
314
338
|
snapshotStory(story) {
|
|
315
339
|
const state = this.getEditableStoryState(story);
|
|
316
|
-
return state ?
|
|
340
|
+
return state ? createStateSnapshot(state) : null;
|
|
317
341
|
}
|
|
318
342
|
/**
|
|
319
343
|
* One story's tables, in the document order {@link snapshotStory} numbers
|
|
@@ -376,7 +400,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
376
400
|
return {
|
|
377
401
|
story,
|
|
378
402
|
view,
|
|
379
|
-
snapshot:
|
|
403
|
+
snapshot: createStateSnapshot(state),
|
|
380
404
|
text: formatStoryStateForLLM(state, view === "current-markup"),
|
|
381
405
|
changes: getTrackedChangesFromDoc(state.doc)
|
|
382
406
|
};
|
|
@@ -394,7 +418,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
394
418
|
this.resolvedStoryExpectations.set(editableStoryKey(story), {
|
|
395
419
|
story,
|
|
396
420
|
text: formatStoryStateForLLM(resolvedState, false),
|
|
397
|
-
blocks:
|
|
421
|
+
blocks: createStateSnapshot(resolvedState).blocks.map(resolvedStoryBlockProjection)
|
|
398
422
|
});
|
|
399
423
|
return true;
|
|
400
424
|
}
|
|
@@ -413,6 +437,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
413
437
|
},
|
|
414
438
|
...options.snapshot !== void 0 && { snapshot: options.snapshot },
|
|
415
439
|
...options.revisionStamp !== void 0 && { revisionStamp: options.revisionStamp },
|
|
440
|
+
...options.wordDiff !== void 0 && { wordDiff: options.wordDiff },
|
|
416
441
|
createUndoEntry: false
|
|
417
442
|
});
|
|
418
443
|
return {
|
|
@@ -432,6 +457,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
432
457
|
batch,
|
|
433
458
|
...options.snapshot !== void 0 && { snapshot: options.snapshot },
|
|
434
459
|
...options.revisionStamp !== void 0 && { revisionStamp: options.revisionStamp },
|
|
460
|
+
...options.wordDiff !== void 0 && { wordDiff: options.wordDiff },
|
|
435
461
|
createUndoEntry: true
|
|
436
462
|
});
|
|
437
463
|
}
|
|
@@ -458,7 +484,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
458
484
|
};
|
|
459
485
|
const result = applyFolioDocumentOperations({
|
|
460
486
|
view,
|
|
461
|
-
snapshot: snapshot ??
|
|
487
|
+
snapshot: snapshot ?? createStateSnapshot(beforeState),
|
|
462
488
|
batch,
|
|
463
489
|
story: story.type === "main" ? "main" : story,
|
|
464
490
|
author: this.author,
|
|
@@ -524,7 +550,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
524
550
|
* feed straight back into {@link applyOperations} and accept / reject.
|
|
525
551
|
*/
|
|
526
552
|
getContent() {
|
|
527
|
-
return this.snapshot().blocks;
|
|
553
|
+
return [...this.snapshot().blocks];
|
|
528
554
|
}
|
|
529
555
|
/**
|
|
530
556
|
* The body as LLM-ready plain text: one line per block, each prefixed with
|
|
@@ -761,29 +787,73 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
761
787
|
* document still carries a redline nobody can see from the body.
|
|
762
788
|
*/
|
|
763
789
|
acceptAll() {
|
|
764
|
-
return this.resolveEveryStory(
|
|
790
|
+
return this.resolveEveryStory("accept");
|
|
765
791
|
}
|
|
766
792
|
/** Reject every tracked change in the package. See {@link acceptAll}. */
|
|
767
793
|
rejectAll() {
|
|
768
|
-
return this.resolveEveryStory(
|
|
794
|
+
return this.resolveEveryStory("reject");
|
|
769
795
|
}
|
|
770
|
-
resolveEveryStory(
|
|
796
|
+
resolveEveryStory(mode) {
|
|
771
797
|
let count = 0;
|
|
772
798
|
for (const { handle } of this.listStories()) {
|
|
773
799
|
const state = this.getEditableStoryState(handle);
|
|
774
800
|
if (!state) continue;
|
|
775
801
|
count += getTrackedChangesFromDoc(state.doc).length;
|
|
776
|
-
this.
|
|
802
|
+
this.setEditableStoryState(handle, resolveAllChangesInHeadlessState(state, mode));
|
|
777
803
|
}
|
|
778
804
|
return count;
|
|
779
805
|
}
|
|
780
806
|
/** The current document model with edits merged back in. */
|
|
781
807
|
toDocument() {
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
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);
|
|
785
829
|
return document;
|
|
786
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
|
+
}
|
|
787
857
|
/**
|
|
788
858
|
* Serialise the reviewed document to a new `.docx` buffer. Tries a selective
|
|
789
859
|
* patch first (only changed paragraphs are rewritten; every other byte of the
|
|
@@ -791,67 +861,65 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
791
861
|
* structural edits — the same two-tier path the editor's save uses.
|
|
792
862
|
*/
|
|
793
863
|
async toBuffer() {
|
|
794
|
-
const
|
|
795
|
-
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;
|
|
796
866
|
if (selective) {
|
|
797
|
-
await this.assertResolvedStoriesSerialized(selective);
|
|
867
|
+
await this.assertResolvedStoriesSerialized(selective, save.resolvedStoryExpectations);
|
|
798
868
|
return selective;
|
|
799
869
|
}
|
|
800
|
-
const
|
|
801
|
-
...document,
|
|
870
|
+
const repackDocument = {
|
|
871
|
+
...save.document,
|
|
802
872
|
originalBuffer: this.originalBuffer
|
|
803
|
-
}
|
|
804
|
-
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);
|
|
805
880
|
return buffer;
|
|
806
881
|
}
|
|
807
|
-
async assertResolvedStoriesSerialized(buffer) {
|
|
808
|
-
if (
|
|
882
|
+
async assertResolvedStoriesSerialized(buffer, expectations) {
|
|
883
|
+
if (expectations.length === 0) return;
|
|
809
884
|
const reopened = await FolioDocxReviewer.fromBuffer(buffer);
|
|
810
|
-
for (const { story, text, blocks } of
|
|
885
|
+
for (const { story, text, blocks } of expectations) {
|
|
811
886
|
const serialized = reopened.readReviewedStory({
|
|
812
887
|
story,
|
|
813
888
|
view: "current-markup"
|
|
814
889
|
});
|
|
815
890
|
const serializedState = reopened.getEditableStoryState(story);
|
|
816
|
-
|
|
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;
|
|
817
901
|
throw new FolioResolvedStorySerializationError({
|
|
818
902
|
message: "Resolved document story did not persist to the serialized DOCX.",
|
|
819
903
|
story,
|
|
820
|
-
|
|
821
|
-
|
|
904
|
+
mismatches,
|
|
905
|
+
expectedBlockCount: blocks.length,
|
|
906
|
+
actualBlockCount: serializedBlocks?.length ?? null,
|
|
822
907
|
remainingChangeCount: serialized?.changes.length ?? null
|
|
823
908
|
});
|
|
824
909
|
}
|
|
825
910
|
}
|
|
826
|
-
getChangedNoteParaIds() {
|
|
827
|
-
const changed = /* @__PURE__ */ new Set();
|
|
828
|
-
for (const entry of this.secondaryStoryStates.values()) {
|
|
829
|
-
if (entry.handle.type !== "footnote" && entry.handle.type !== "endnote") continue;
|
|
830
|
-
for (const paraId of getChangedParagraphIds(entry.state)) changed.add(paraId);
|
|
831
|
-
}
|
|
832
|
-
return changed;
|
|
833
|
-
}
|
|
834
911
|
/**
|
|
835
912
|
* Attempt the selective patch, treating a throw the same as a decline. Odd
|
|
836
913
|
* source XML can make the paragraph diff throw rather than return `null`; a
|
|
837
914
|
* full repack is the correct lossless fallback in both cases, so the fallback
|
|
838
915
|
* is the graceful handling — no separate error surface is needed here.
|
|
839
916
|
*/
|
|
840
|
-
async trySelectiveSave(document) {
|
|
841
|
-
const changedParaIds = new Set(getChangedParagraphIds(this.state));
|
|
842
|
-
let structuralChange = hasStructuralChanges(this.state);
|
|
843
|
-
let untrackedChanges = hasUntrackedChanges(this.state);
|
|
844
|
-
for (const entry of this.secondaryStoryStates.values()) {
|
|
845
|
-
if (entry.handle.type !== "footnote" && entry.handle.type !== "endnote") continue;
|
|
846
|
-
for (const paraId of getChangedParagraphIds(entry.state)) changedParaIds.add(paraId);
|
|
847
|
-
structuralChange ||= hasStructuralChanges(entry.state);
|
|
848
|
-
untrackedChanges ||= hasUntrackedChanges(entry.state);
|
|
849
|
-
}
|
|
917
|
+
async trySelectiveSave(document, changedParaIds) {
|
|
850
918
|
try {
|
|
851
919
|
return await attemptSelectiveSave(document, this.originalBuffer, {
|
|
852
920
|
changedParaIds,
|
|
853
|
-
structuralChange,
|
|
854
|
-
hasUntrackedChanges:
|
|
921
|
+
structuralChange: false,
|
|
922
|
+
hasUntrackedChanges: false
|
|
855
923
|
});
|
|
856
924
|
} catch {
|
|
857
925
|
return null;
|
|
@@ -872,7 +940,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
872
940
|
const state = ensureBaseDirectionInState(EditorState.create({
|
|
873
941
|
schema,
|
|
874
942
|
doc: ensureDeterministicParaIdsInDoc(storyDoc),
|
|
875
|
-
plugins:
|
|
943
|
+
plugins: createHeadlessPlugins(this.baseDocument.package.styles, this.baseDocument.package.numbering)
|
|
876
944
|
}));
|
|
877
945
|
this.secondaryStoryStates.set(key, {
|
|
878
946
|
handle: story,
|
|
@@ -918,19 +986,19 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
918
986
|
const sourceText = source.type === "footnote" ? getFootnoteText(source) : getEndnoteText(source);
|
|
919
987
|
return normalizeFolioAIBlockText(state?.doc.textContent ?? sourceText);
|
|
920
988
|
}
|
|
921
|
-
mergeEditedSecondaryStories(document) {
|
|
989
|
+
mergeEditedSecondaryStories(document, secondaryStoryStates = this.secondaryStoryStates.values()) {
|
|
922
990
|
let headers;
|
|
923
991
|
let footers;
|
|
924
992
|
let footnotes;
|
|
925
993
|
let endnotes;
|
|
926
|
-
for (const entry of
|
|
994
|
+
for (const entry of secondaryStoryStates) {
|
|
927
995
|
if (entry.state === entry.initialState) continue;
|
|
928
996
|
if (entry.handle.type === "header" || entry.handle.type === "footer") {
|
|
929
997
|
const source = this.getHeaderFooterStory(entry.handle);
|
|
930
998
|
if (!source) continue;
|
|
931
999
|
const edited = {
|
|
932
1000
|
...source,
|
|
933
|
-
content: proseDocToBlocks(entry.state.doc)
|
|
1001
|
+
content: proseDocToBlocks(entry.state.doc, source.content, document.package.styles)
|
|
934
1002
|
};
|
|
935
1003
|
if (entry.handle.type === "header") {
|
|
936
1004
|
headers ??= new Map(document.package.headers);
|
|
@@ -947,7 +1015,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
947
1015
|
if (!source) continue;
|
|
948
1016
|
const edited = {
|
|
949
1017
|
...source,
|
|
950
|
-
content: proseDocToBlocks(entry.state.doc)
|
|
1018
|
+
content: proseDocToBlocks(entry.state.doc, source.content, document.package.styles)
|
|
951
1019
|
};
|
|
952
1020
|
footnotes ??= [...document.package.footnotes ?? []];
|
|
953
1021
|
const index = footnotes.findIndex((note) => note.id === noteId);
|
|
@@ -959,7 +1027,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
959
1027
|
if (!source) continue;
|
|
960
1028
|
const edited = {
|
|
961
1029
|
...source,
|
|
962
|
-
content: proseDocToBlocks(entry.state.doc)
|
|
1030
|
+
content: proseDocToBlocks(entry.state.doc, source.content, document.package.styles)
|
|
963
1031
|
};
|
|
964
1032
|
endnotes ??= [...document.package.endnotes ?? []];
|
|
965
1033
|
const index = endnotes.findIndex((note) => note.id === noteId);
|
|
@@ -971,10 +1039,10 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
971
1039
|
if (endnotes) document.package.endnotes = endnotes;
|
|
972
1040
|
}
|
|
973
1041
|
/** Apply any {@link resolveComment} overrides recorded for these comments. */
|
|
974
|
-
withResolvedOverrides(comments) {
|
|
975
|
-
if (
|
|
1042
|
+
withResolvedOverrides(comments, resolvedOverrides = this.resolvedOverrides) {
|
|
1043
|
+
if (resolvedOverrides.size === 0) return [...comments];
|
|
976
1044
|
return comments.map((comment) => {
|
|
977
|
-
const override =
|
|
1045
|
+
const override = resolvedOverrides.get(comment.id);
|
|
978
1046
|
return override === void 0 ? comment : {
|
|
979
1047
|
...comment,
|
|
980
1048
|
done: override
|