@stll/folio-core 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-edits/headless.d.ts +52 -12
- package/dist/ai-edits/headless.js +181 -55
- package/dist/ai-edits/index.d.ts +2 -2
- package/dist/ai-edits/index.js +2 -2
- package/dist/controller/collaborationModules.d.ts +7 -0
- package/dist/controller/collaborationModules.js +15 -0
- package/dist/controller/folioEditor.d.ts +1 -1
- package/dist/controller/headerFooterEditorManager.d.ts +41 -0
- package/dist/controller/headerFooterEditorManager.js +182 -0
- package/dist/controller/hiddenEditorManager.d.ts +1 -1
- package/dist/controller/layoutPipeline.js +2 -1
- package/dist/document-operations.d.ts +11 -2
- package/dist/document-operations.js +7 -1
- package/dist/document-stories.d.ts +10 -0
- package/dist/document-stories.js +27 -0
- package/dist/docx/corePropertiesParser.d.ts +8 -0
- package/dist/docx/corePropertiesParser.js +53 -0
- package/dist/docx/index.d.ts +2 -1
- package/dist/docx/index.js +2 -1
- package/dist/docx/metadataPrivacy.d.ts +40 -0
- package/dist/docx/metadataPrivacy.js +131 -0
- package/dist/docx/parser.js +4 -1
- package/dist/docx/rezip.d.ts +5 -4
- package/dist/docx/rezip.js +6 -8
- package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
- package/dist/layout-engine/measure/cache.js +2 -7
- package/dist/layout-engine/measure/effectiveLineBreakPolicy.d.ts +29 -0
- package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +60 -0
- package/dist/layout-engine/measure/measureParagraph.js +32 -36
- package/dist/layout-engine/measure/tableCellFloating.js +39 -33
- package/dist/layout-engine/types.d.ts +5 -3
- package/dist/managers/FindReplaceManager.d.ts +8 -0
- package/dist/managers/FindReplaceManager.js +18 -0
- package/dist/managers/TableSelectionManager.d.ts +6 -0
- package/dist/managers/TableSelectionManager.js +14 -0
- package/dist/prosemirror/attrs/index.js +1 -0
- package/dist/prosemirror/conversion/effectiveTableCellFormatting.d.ts +62 -0
- package/dist/prosemirror/conversion/effectiveTableCellFormatting.js +131 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +1 -0
- package/dist/prosemirror/conversion/toProseDoc.js +40 -68
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
- package/dist/prosemirror/extensions/nodes/ImageExtension.js +1 -0
- package/dist/prosemirror/extensions/nodes/TableExtension.js +1 -1
- package/dist/prosemirror/findReplaceSelection.d.ts +6 -1
- package/dist/prosemirror/findReplaceSelection.js +57 -13
- package/dist/prosemirror/schema/nodes.d.ts +2 -1
- package/dist/redline.d.ts +26 -11
- package/dist/redline.js +95 -62
- package/dist/render-dom/BodySelectionOverlay.d.ts +31 -0
- package/dist/render-dom/BodySelectionOverlay.js +76 -0
- package/dist/render-dom/HeaderFooterSelectionOverlay.d.ts +28 -0
- package/dist/render-dom/HeaderFooterSelectionOverlay.js +121 -0
- package/dist/render-dom/RemoteSelectionOverlay.d.ts +30 -0
- package/dist/render-dom/RemoteSelectionOverlay.js +67 -0
- package/dist/render-dom/RenderedDomContext.d.ts +40 -0
- package/dist/render-dom/RenderedDomContext.js +126 -0
- package/dist/render-dom/resolveSidebarItemPositions.d.ts +33 -0
- package/dist/render-dom/resolveSidebarItemPositions.js +45 -0
- package/dist/server.d.ts +5 -4
- package/dist/server.js +5 -4
- package/dist/utils/findReplace.d.ts +32 -0
- package/dist/utils/findReplace.js +118 -0
- package/dist/utils/headerFooter.js +24 -1
- package/dist/version-comparison.d.ts +65 -11
- package/dist/version-comparison.js +187 -29
- package/package.json +1 -1
|
@@ -7,10 +7,9 @@ import { assertValidProseMirrorDocument } from "../validation.js";
|
|
|
7
7
|
import { shadingToRunShadingAttrs } from "./runShadingMark.js";
|
|
8
8
|
import { marksToTextFormatting } from "./fromProseDoc.js";
|
|
9
9
|
import { createStyleEngine } from "../../style-engine/styleEngine.js";
|
|
10
|
-
import { resolveColor } from "../../utils/colorResolver.js";
|
|
11
|
-
import { resolveShadingFill } from "../../utils/formatToStyle.js";
|
|
12
10
|
import { buildRunFormattingOverrideAttrs } from "../extensions/marks/RunFormattingOverrideExtension.js";
|
|
13
11
|
import { schema } from "../schema/index.js";
|
|
12
|
+
import { resolveEffectiveTableCellFormatting } from "./effectiveTableCellFormatting.js";
|
|
14
13
|
//#region src/prosemirror/conversion/toProseDoc.ts
|
|
15
14
|
const TOC_STYLE_ID = /^TOC\d*$/iu;
|
|
16
15
|
/**
|
|
@@ -725,88 +724,60 @@ function convertTableRow(row, styleResolver, context, isHeaderRow, columnWidths,
|
|
|
725
724
|
if (cellIsFirstRow && cellIsLastCol && (tableLook?.firstRow || rowCnf?.firstRow || cellCnf?.firstRow) && (tableLook?.lastColumn || rowCnf?.lastColumn || cellCnf?.lastColumn)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.neCell);
|
|
726
725
|
if (cellIsLastRow && cellIsFirstCol && (tableLook?.lastRow || rowCnf?.lastRow || cellCnf?.lastRow) && (tableLook?.firstColumn || rowCnf?.firstColumn || cellCnf?.firstColumn)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.swCell);
|
|
727
726
|
if (cellIsLastRow && cellIsLastCol && (tableLook?.lastRow || rowCnf?.lastRow || cellCnf?.lastRow) && (tableLook?.lastColumn || rowCnf?.lastColumn || cellCnf?.lastColumn)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.seCell);
|
|
728
|
-
cells.push(convertTableCell(
|
|
727
|
+
cells.push(convertTableCell({
|
|
728
|
+
cell,
|
|
729
|
+
styleResolver,
|
|
730
|
+
context,
|
|
731
|
+
isHeader: isHeaderRow,
|
|
732
|
+
gridWidthPercent: gridWidth,
|
|
733
|
+
conditionalStyle: cellConditionalStyle,
|
|
734
|
+
tableBorders,
|
|
735
|
+
position: {
|
|
736
|
+
isFirstRow,
|
|
737
|
+
isLastRow,
|
|
738
|
+
isFirstColumn: isFirstCol,
|
|
739
|
+
isLastColumn: isLastCol
|
|
740
|
+
},
|
|
741
|
+
calculatedRowSpan,
|
|
742
|
+
preserveVMergeRestart,
|
|
743
|
+
vMergeContinuationCells: rowSpanInfo?.continuationCells,
|
|
744
|
+
defaultCellMargins
|
|
745
|
+
}));
|
|
729
746
|
}
|
|
730
747
|
return schema.node("tableRow", attrs, cells);
|
|
731
748
|
}
|
|
732
|
-
const TABLE_BORDER_SIDES = [
|
|
733
|
-
"top",
|
|
734
|
-
"bottom",
|
|
735
|
-
"left",
|
|
736
|
-
"right",
|
|
737
|
-
"insideH",
|
|
738
|
-
"insideV",
|
|
739
|
-
"topLeftToBottomRight",
|
|
740
|
-
"topRightToBottomLeft"
|
|
741
|
-
];
|
|
742
|
-
function resolveThemedBorderColors(borders, theme) {
|
|
743
|
-
if (!borders || !theme?.colorScheme) return borders;
|
|
744
|
-
let resolved;
|
|
745
|
-
for (const side of TABLE_BORDER_SIDES) {
|
|
746
|
-
const border = borders[side];
|
|
747
|
-
if (!border?.color?.themeColor || border.color.auto) continue;
|
|
748
|
-
resolved ??= { ...borders };
|
|
749
|
-
resolved[side] = {
|
|
750
|
-
...border,
|
|
751
|
-
color: { rgb: resolveColor(border.color, theme).replace(/^#/u, "") }
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
return resolved ?? borders;
|
|
755
|
-
}
|
|
756
749
|
/**
|
|
757
750
|
* Convert a TableCell to a ProseMirror table cell node
|
|
758
751
|
*/
|
|
759
|
-
function convertTableCell(cell, styleResolver, context, isHeader, gridWidthPercent, conditionalStyle, tableBorders,
|
|
760
|
-
const { theme } = context;
|
|
752
|
+
function convertTableCell({ cell, styleResolver, context, isHeader, gridWidthPercent, conditionalStyle, tableBorders, position, calculatedRowSpan, preserveVMergeRestart, vMergeContinuationCells, defaultCellMargins }) {
|
|
761
753
|
const formatting = cell.formatting;
|
|
762
754
|
const rowspan = calculatedRowSpan ?? 1;
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
top: isFirstRow ? tableBorders.top : tableBorders.insideH,
|
|
773
|
-
bottom: isLastRow ? tableBorders.bottom : tableBorders.insideH,
|
|
774
|
-
left: isFirstCol ? tableBorders.left : tableBorders.insideV,
|
|
775
|
-
right: isLastCol ? tableBorders.right : tableBorders.insideV
|
|
776
|
-
};
|
|
777
|
-
})();
|
|
778
|
-
const conditionalBorders = conditionalStyle?.tcPr?.borders;
|
|
779
|
-
const cellBorders = formatting?.borders;
|
|
780
|
-
const borders = resolveThemedBorderColors(baseBorders || conditionalBorders || cellBorders ? {
|
|
781
|
-
...baseBorders,
|
|
782
|
-
...conditionalBorders,
|
|
783
|
-
...cellBorders
|
|
784
|
-
} : void 0, theme);
|
|
785
|
-
const buildMarginsAttr = (src) => {
|
|
786
|
-
const m = {};
|
|
787
|
-
if (src.top?.value !== void 0) m.top = src.top.value;
|
|
788
|
-
if (src.bottom?.value !== void 0) m.bottom = src.bottom.value;
|
|
789
|
-
if (src.left?.value !== void 0) m.left = src.left.value;
|
|
790
|
-
if (src.right?.value !== void 0) m.right = src.right.value;
|
|
791
|
-
return m;
|
|
792
|
-
};
|
|
755
|
+
const effectiveFormatting = resolveEffectiveTableCellFormatting({
|
|
756
|
+
directFormatting: formatting,
|
|
757
|
+
styleFormatting: conditionalStyle?.tcPr,
|
|
758
|
+
tableBorders,
|
|
759
|
+
position,
|
|
760
|
+
gridWidthPercent,
|
|
761
|
+
defaultMargins: defaultCellMargins,
|
|
762
|
+
theme: context.theme
|
|
763
|
+
});
|
|
793
764
|
const attrs = {
|
|
794
765
|
colspan: formatting?.gridSpan ?? 1,
|
|
795
766
|
rowspan
|
|
796
767
|
};
|
|
797
|
-
if (width
|
|
798
|
-
|
|
768
|
+
if (effectiveFormatting.width.type === "value") {
|
|
769
|
+
attrs.width = effectiveFormatting.width.value;
|
|
770
|
+
if (effectiveFormatting.width.widthType) attrs.widthType = effectiveFormatting.width.widthType;
|
|
771
|
+
}
|
|
799
772
|
if (formatting?.verticalAlign) attrs.verticalAlign = formatting.verticalAlign;
|
|
800
|
-
if (
|
|
801
|
-
attrs.backgroundColor =
|
|
802
|
-
attrs._resolvedBackgroundColor =
|
|
773
|
+
if (effectiveFormatting.background.type === "color") {
|
|
774
|
+
attrs.backgroundColor = effectiveFormatting.background.rgb;
|
|
775
|
+
attrs._resolvedBackgroundColor = effectiveFormatting.background.rgb;
|
|
803
776
|
}
|
|
804
777
|
if (formatting?.textDirection) attrs.textDirection = formatting.textDirection;
|
|
805
778
|
if (formatting?.noWrap !== void 0) attrs.noWrap = formatting.noWrap;
|
|
806
|
-
if (borders) attrs.borders = borders;
|
|
807
|
-
if (
|
|
808
|
-
else if (conditionalStyle?.tcPr?.margins) attrs.margins = buildMarginsAttr(conditionalStyle.tcPr.margins);
|
|
809
|
-
else if (defaultCellMargins) attrs.margins = defaultCellMargins;
|
|
779
|
+
if (effectiveFormatting.borders) attrs.borders = effectiveFormatting.borders;
|
|
780
|
+
if (effectiveFormatting.margins) attrs.margins = effectiveFormatting.margins;
|
|
810
781
|
if (formatting) attrs._originalFormatting = formatting;
|
|
811
782
|
if (cell.propertyChanges && cell.propertyChanges.length > 0) attrs.tcPrChange = [...cell.propertyChanges];
|
|
812
783
|
if (preserveVMergeRestart) attrs._preserveVMergeRestart = true;
|
|
@@ -1072,6 +1043,7 @@ function convertImage(image, rawXml) {
|
|
|
1072
1043
|
cropBottom: image.crop?.bottom,
|
|
1073
1044
|
cropLeft: image.crop?.left,
|
|
1074
1045
|
position,
|
|
1046
|
+
layoutInCell: image.layoutInCell,
|
|
1075
1047
|
borderWidth,
|
|
1076
1048
|
borderColor,
|
|
1077
1049
|
borderStyle,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { autospacingMatchesBase } from "../../autospacingBase.js";
|
|
2
2
|
import { directionIsRtl } from "../../paragraphDirection.js";
|
|
3
3
|
import { expectParagraphAttrs } from "../../attrs/index.js";
|
|
4
|
-
import { paragraphToStyle } from "../../../utils/formatToStyle.js";
|
|
5
4
|
import { createNodeExtension } from "../create.js";
|
|
5
|
+
import { paragraphToStyle } from "../../../utils/formatToStyle.js";
|
|
6
6
|
import { collectHeadings } from "../../../utils/headingCollector.js";
|
|
7
7
|
import { listAttrsFromResolvedStyle, paragraphAttrsFromResolvedStyle } from "../../styles/resolvedStyleAttrs.js";
|
|
8
8
|
import { Fragment } from "prosemirror-model";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { HIGHLIGHT_COLOR_VALUES } from "../../../types/documentEnumValues.js";
|
|
2
2
|
import { expectHighlightMarkAttrs } from "../../attrs/index.js";
|
|
3
|
-
import { resolveHighlightToCss } from "../../../utils/colorResolver.js";
|
|
4
3
|
import { createMarkExtension } from "../create.js";
|
|
4
|
+
import { resolveHighlightToCss } from "../../../utils/colorResolver.js";
|
|
5
5
|
import { removeMark, setMark } from "./markUtils.js";
|
|
6
6
|
import { panic } from "better-result";
|
|
7
7
|
//#region src/prosemirror/extensions/marks/HighlightExtension.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { expectRunShadingMarkAttrs } from "../../attrs/index.js";
|
|
2
|
-
import { ensureHexPrefix } from "../../../utils/colorResolver.js";
|
|
3
2
|
import { createMarkExtension } from "../create.js";
|
|
3
|
+
import { ensureHexPrefix } from "../../../utils/colorResolver.js";
|
|
4
4
|
import { normalizeCssColorKey, parseDOMHighlightColor } from "./HighlightExtension.js";
|
|
5
5
|
//#region src/prosemirror/extensions/marks/RunShadingExtension.ts
|
|
6
6
|
const RunShadingExtension = createMarkExtension({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { expectTextColorMarkAttrs } from "../../attrs/index.js";
|
|
2
|
-
import { textToStyle } from "../../../utils/formatToStyle.js";
|
|
3
2
|
import { createMarkExtension } from "../create.js";
|
|
3
|
+
import { textToStyle } from "../../../utils/formatToStyle.js";
|
|
4
4
|
import { removeMark, setMark } from "./markUtils.js";
|
|
5
5
|
import { panic } from "better-result";
|
|
6
6
|
//#region src/prosemirror/extensions/marks/TextColorExtension.ts
|
|
@@ -33,6 +33,7 @@ const ImageExtension = createNodeExtension({
|
|
|
33
33
|
cropBottom: { default: null },
|
|
34
34
|
cropLeft: { default: null },
|
|
35
35
|
position: { default: null },
|
|
36
|
+
layoutInCell: { default: null },
|
|
36
37
|
borderWidth: { default: null },
|
|
37
38
|
borderColor: { default: null },
|
|
38
39
|
borderStyle: { default: null },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TABLE_CELL_TEXT_DIRECTION_VALUES, TABLE_WIDTH_TYPE_VALUES } from "../../../types/documentEnumValues.js";
|
|
2
2
|
import { expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, mergeTableAttrs, mergeTableCellAttrs, mergeTableRowAttrs } from "../../attrs/index.js";
|
|
3
|
-
import { resolveColor } from "../../../utils/colorResolver.js";
|
|
4
3
|
import { createExtension, createNodeExtension } from "../create.js";
|
|
4
|
+
import { resolveColor } from "../../../utils/colorResolver.js";
|
|
5
5
|
import { panic } from "better-result";
|
|
6
6
|
import { Plugin, PluginKey, Selection, TextSelection } from "prosemirror-state";
|
|
7
7
|
import { CellSelection, TableMap, columnResizing, mergeCells, removeRow, selectedRect, splitCell, tableEditing } from "prosemirror-tables";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FindOptions } from "../utils/findReplace.js";
|
|
1
2
|
import { Node } from "prosemirror-model";
|
|
2
3
|
|
|
3
4
|
//#region src/prosemirror/findReplaceSelection.d.ts
|
|
@@ -18,6 +19,10 @@ type FindMatchRange = {
|
|
|
18
19
|
from: number;
|
|
19
20
|
to: number;
|
|
20
21
|
};
|
|
22
|
+
type ProseMirrorFindMatch = FindMatchPosition & FindMatchRange & {
|
|
23
|
+
text: string;
|
|
24
|
+
};
|
|
21
25
|
declare function resolveFindMatchRange(doc: Node, match: FindMatchPosition): FindMatchRange | null;
|
|
26
|
+
declare function findInProseMirrorDocument(doc: Node, searchText: string, options: FindOptions): ProseMirrorFindMatch[];
|
|
22
27
|
//#endregion
|
|
23
|
-
export { FindMatchPosition, FindMatchRange, resolveFindMatchRange };
|
|
28
|
+
export { FindMatchPosition, FindMatchRange, ProseMirrorFindMatch, findInProseMirrorDocument, resolveFindMatchRange };
|
|
@@ -1,29 +1,55 @@
|
|
|
1
|
+
import { findAllMatches } from "../utils/findReplace.js";
|
|
1
2
|
//#region src/prosemirror/findReplaceSelection.ts
|
|
2
3
|
function resolveFindMatchRange(doc, match) {
|
|
3
|
-
let paragraphIndex = 0;
|
|
4
4
|
let resolved = null;
|
|
5
|
-
|
|
6
|
-
if (
|
|
7
|
-
if (paragraphIndex !== match.paragraphIndex) {
|
|
8
|
-
paragraphIndex++;
|
|
9
|
-
return true;
|
|
10
|
-
}
|
|
5
|
+
forEachSearchParagraph(doc, (paragraph, paragraphPos, paragraphIndex) => {
|
|
6
|
+
if (paragraphIndex !== match.paragraphIndex) return true;
|
|
11
7
|
resolved = resolveTextRangeInParagraph({
|
|
12
|
-
paragraph
|
|
13
|
-
paragraphPos
|
|
8
|
+
paragraph,
|
|
9
|
+
paragraphPos,
|
|
14
10
|
startOffset: match.startOffset,
|
|
15
11
|
endOffset: match.endOffset
|
|
16
12
|
});
|
|
17
13
|
return false;
|
|
18
|
-
};
|
|
14
|
+
});
|
|
15
|
+
return resolved;
|
|
16
|
+
}
|
|
17
|
+
function findInProseMirrorDocument(doc, searchText, options) {
|
|
18
|
+
if (!searchText) return [];
|
|
19
|
+
const matches = [];
|
|
20
|
+
forEachSearchParagraph(doc, (paragraph, paragraphPos, paragraphIndex) => {
|
|
21
|
+
const text = getSearchableParagraphText(paragraph);
|
|
22
|
+
for (const { start, end } of findAllMatches(text, searchText, options)) {
|
|
23
|
+
const range = resolveTextRangeInParagraph({
|
|
24
|
+
paragraph,
|
|
25
|
+
paragraphPos,
|
|
26
|
+
startOffset: start,
|
|
27
|
+
endOffset: end
|
|
28
|
+
});
|
|
29
|
+
if (range) matches.push({
|
|
30
|
+
paragraphIndex,
|
|
31
|
+
startOffset: start,
|
|
32
|
+
endOffset: end,
|
|
33
|
+
text: text.slice(start, end),
|
|
34
|
+
...range
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
});
|
|
39
|
+
return matches;
|
|
40
|
+
}
|
|
41
|
+
function forEachSearchParagraph(doc, visit) {
|
|
42
|
+
let paragraphIndex = 0;
|
|
19
43
|
const walkBlocks = (container, contentStart) => {
|
|
20
44
|
let offset = 0;
|
|
21
45
|
for (let childIndex = 0; childIndex < container.childCount; childIndex++) {
|
|
22
46
|
const child = container.child(childIndex);
|
|
23
47
|
const childPos = contentStart + offset;
|
|
24
48
|
if (child.type.name === "paragraph") {
|
|
25
|
-
if (!
|
|
49
|
+
if (!visit(child, childPos, paragraphIndex)) return false;
|
|
50
|
+
paragraphIndex++;
|
|
26
51
|
} else if (child.type.name === "table" && !walkTable(child, childPos)) return false;
|
|
52
|
+
else if (child.type.name === "blockSdt" && !walkBlocks(child, childPos + 1)) return false;
|
|
27
53
|
offset += child.nodeSize;
|
|
28
54
|
}
|
|
29
55
|
return true;
|
|
@@ -53,7 +79,6 @@ function resolveFindMatchRange(doc, match) {
|
|
|
53
79
|
return true;
|
|
54
80
|
};
|
|
55
81
|
walkBlocks(doc, 0);
|
|
56
|
-
return resolved;
|
|
57
82
|
}
|
|
58
83
|
function resolveTextRangeInParagraph({ paragraph, paragraphPos, startOffset, endOffset }) {
|
|
59
84
|
let textOffset = 0;
|
|
@@ -83,5 +108,24 @@ function getSearchTextTokenLength(node) {
|
|
|
83
108
|
if (node.type.name === "tab" || node.type.name === "hardBreak") return 1;
|
|
84
109
|
return 0;
|
|
85
110
|
}
|
|
111
|
+
function getSearchableParagraphText(paragraph) {
|
|
112
|
+
let text = "";
|
|
113
|
+
paragraph.descendants((node) => {
|
|
114
|
+
if (node.isText) {
|
|
115
|
+
text += node.text ?? "";
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (node.type.name === "tab") {
|
|
119
|
+
text += " ";
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
if (node.type.name === "hardBreak") {
|
|
123
|
+
text += "\n";
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
});
|
|
128
|
+
return text;
|
|
129
|
+
}
|
|
86
130
|
//#endregion
|
|
87
|
-
export { resolveFindMatchRange };
|
|
131
|
+
export { findInProseMirrorDocument, resolveFindMatchRange };
|
|
@@ -197,7 +197,8 @@ type ImageAttrs = {
|
|
|
197
197
|
cropRight?: number;
|
|
198
198
|
cropBottom?: number;
|
|
199
199
|
cropLeft?: number; /** Position for floating images (horizontal and vertical alignment) */
|
|
200
|
-
position?: ImagePositionAttrs; /**
|
|
200
|
+
position?: ImagePositionAttrs; /** Use the containing table cell as the anchor's positioning scope (the OOXML default). */
|
|
201
|
+
layoutInCell?: boolean; /** Border width in pixels */
|
|
201
202
|
borderWidth?: number; /** Border color as CSS color string */
|
|
202
203
|
borderColor?: string; /** Border style (CSS border-style value) */
|
|
203
204
|
borderStyle?: string; /** Wrap text setting from DOCX (left, right, bothSides, largest) for round-trip */
|
package/dist/redline.d.ts
CHANGED
|
@@ -1,20 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FolioAIEditAppliedOperation, FolioAIEditSkippedOperation } from "./ai-edits/types.js";
|
|
2
|
+
import { FolioDocumentStoryHandle, FolioResolvedReviewedView } from "./ai-edits/headless.js";
|
|
3
|
+
import { FolioDocumentPrivacyOptions, FolioDocumentPrivacyReport } from "./docx/metadataPrivacy.js";
|
|
2
4
|
|
|
3
5
|
//#region src/redline.d.ts
|
|
4
6
|
/** Options for {@link generateRedlineDocx}. */
|
|
5
7
|
type GenerateRedlineDocxOptions = {
|
|
6
|
-
/** Author recorded on the generated tracked changes. (default: `"folio compare"`) */author?: string;
|
|
8
|
+
/** Author recorded on the generated tracked changes. (default: `"folio compare"`) */author?: string; /** Resolved base input state. (default: `"final"`) */
|
|
9
|
+
baseView?: FolioResolvedReviewedView; /** Resolved revised input state. (default: `"final"`) */
|
|
10
|
+
revisedView?: FolioResolvedReviewedView; /** Optional output-only package-metadata privacy transforms. */
|
|
11
|
+
privacy?: FolioDocumentPrivacyOptions;
|
|
12
|
+
};
|
|
13
|
+
declare const InvalidGenerateRedlineDocxOptionsError_base: import("better-result").TaggedErrorClass<"InvalidGenerateRedlineDocxOptionsError", {
|
|
14
|
+
message: string;
|
|
15
|
+
option: "baseView" | "revisedView";
|
|
16
|
+
receivedValue: unknown;
|
|
17
|
+
}>;
|
|
18
|
+
declare class InvalidGenerateRedlineDocxOptionsError extends InvalidGenerateRedlineDocxOptionsError_base {}
|
|
19
|
+
type GenerateRedlineUnprocessedStory = {
|
|
20
|
+
baseStory: FolioDocumentStoryHandle | null;
|
|
21
|
+
revisedStory: FolioDocumentStoryHandle | null;
|
|
22
|
+
reason: "missing-base-story" | "missing-revised-story";
|
|
7
23
|
};
|
|
8
24
|
/** Result of {@link generateRedlineDocx}. */
|
|
9
|
-
type GenerateRedlineDocxResult =
|
|
10
|
-
/** The
|
|
25
|
+
type GenerateRedlineDocxResult = {
|
|
26
|
+
/** The base package with generated tracked changes. */buffer: ArrayBuffer; /** Operations applied across every matched story. */
|
|
27
|
+
applied: FolioAIEditAppliedOperation[]; /** Block operations that could not be applied. */
|
|
28
|
+
skipped: FolioAIEditSkippedOperation[]; /** Package parts that could not be represented as story-scoped text edits. */
|
|
29
|
+
unprocessedStories: GenerateRedlineUnprocessedStory[]; /** Privacy transforms applied to the generated package. */
|
|
30
|
+
privacyReport: FolioDocumentPrivacyReport;
|
|
11
31
|
};
|
|
12
|
-
/**
|
|
13
|
-
* Compare `base` and `revised` and return the base document with every
|
|
14
|
-
* difference recorded as a tracked change. See the module doc comment for
|
|
15
|
-
* semantics and limitations; `skipped` reports any block the generator could
|
|
16
|
-
* not redline (e.g. additions with no anchor in an empty base document).
|
|
17
|
-
*/
|
|
32
|
+
/** Compare two buffers and return tracked changes for every matched editable story. */
|
|
18
33
|
declare const generateRedlineDocx: (base: ArrayBuffer, revised: ArrayBuffer, options?: GenerateRedlineDocxOptions) => Promise<GenerateRedlineDocxResult>;
|
|
19
34
|
//#endregion
|
|
20
|
-
export { GenerateRedlineDocxOptions, GenerateRedlineDocxResult, generateRedlineDocx };
|
|
35
|
+
export { GenerateRedlineDocxOptions, GenerateRedlineDocxResult, GenerateRedlineUnprocessedStory, InvalidGenerateRedlineDocxOptionsError, generateRedlineDocx };
|
package/dist/redline.js
CHANGED
|
@@ -1,75 +1,35 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "./document-operations.js";
|
|
2
|
+
import { pairFolioDocumentStories } from "./document-stories.js";
|
|
3
|
+
import { FolioDocxReviewer, isFolioResolvedReviewedView } from "./ai-edits/headless.js";
|
|
4
|
+
import { resolveFolioDocumentPrivacyTransforms, rewriteDocxMetadataPrivacy } from "./docx/metadataPrivacy.js";
|
|
2
5
|
import { alignFolioBlocks } from "./version-comparison.js";
|
|
6
|
+
import { TaggedError, panic } from "better-result";
|
|
3
7
|
//#region src/redline.ts
|
|
4
8
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `w:del`), ready for review in Word or the folio editor — the
|
|
8
|
-
* document-producing counterpart to {@link compareDocxVersions}'s structured
|
|
9
|
-
* diff.
|
|
9
|
+
* Compare two `.docx` buffers and produce a third buffer whose text
|
|
10
|
+
* differences are represented as tracked changes.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* tracked-changes {@link FolioAIEditOperation}, and the reviewer's shared
|
|
16
|
-
* apply path records them as redlines. Word-level minimality comes free:
|
|
17
|
-
* the apply engine word-diffs a `replaceBlock` and only marks the divergent
|
|
18
|
-
* tokens.
|
|
19
|
-
*
|
|
20
|
-
* ## Semantics and limitations
|
|
21
|
-
*
|
|
22
|
-
* - **As-accepted inputs.** Pending tracked changes in either input count as
|
|
23
|
-
* applied before comparison (mirroring {@link compareDocxVersions}): the
|
|
24
|
-
* base's own pending redlines are accepted in the output, so the tracked
|
|
25
|
-
* changes it carries are exactly base → revised. This is intentionally
|
|
26
|
-
* lossy about the inputs' own revision history and authorship.
|
|
27
|
-
* - **Text redlines only.** Format-only changes and relocated blocks are
|
|
28
|
-
* redlined as plain edits (a move becomes a delete + insert, like Word
|
|
29
|
-
* compare with move detection off); block-level formatting is carried via
|
|
30
|
-
* the revised block's `styleId` on inserted blocks.
|
|
31
|
-
* - **Anchoring.** Insertions anchor `before` the next surviving base block
|
|
32
|
-
* so consecutive additions keep their order; additions past the last base
|
|
33
|
-
* block anchor `after` it. A base document with no content blocks offers
|
|
34
|
-
* no anchor at all — such additions surface in `skipped` rather than
|
|
35
|
-
* silently vanishing.
|
|
36
|
-
*/
|
|
37
|
-
/**
|
|
38
|
-
* For each event index, the base block id the next `revisedOnly` event
|
|
39
|
-
* should anchor before: the base side of the nearest later `pair` or
|
|
40
|
-
* `baseOnly` event, or `null` when only additions remain until the end of
|
|
41
|
-
* the document. One backward pass, so a long run of trailing additions
|
|
42
|
-
* (huge revised document vs. a small base) stays linear instead of
|
|
43
|
-
* re-scanning the tail per added block.
|
|
12
|
+
* Each matched editable story is processed independently through the shared
|
|
13
|
+
* block alignment and document-operation paths. Package parts that exist on
|
|
14
|
+
* only one side are reported because creating or removing those parts is a
|
|
15
|
+
* distinct package-level operation.
|
|
44
16
|
*/
|
|
17
|
+
var InvalidGenerateRedlineDocxOptionsError = class extends TaggedError("InvalidGenerateRedlineDocxOptionsError")() {};
|
|
45
18
|
const nextBaseBlockIdByIndex = (events) => {
|
|
46
19
|
const nextIds = Array.from({ length: events.length });
|
|
47
20
|
let nextId = null;
|
|
48
|
-
for (let
|
|
49
|
-
nextIds[
|
|
50
|
-
const event = events[
|
|
21
|
+
for (let index = events.length - 1; index >= 0; index--) {
|
|
22
|
+
nextIds[index] = nextId;
|
|
23
|
+
const event = events[index];
|
|
51
24
|
if (event?.type === "pair") nextId = event.baseBlock.id;
|
|
52
25
|
else if (event?.type === "baseOnly") nextId = event.block.id;
|
|
53
26
|
}
|
|
54
27
|
return nextIds;
|
|
55
28
|
};
|
|
56
|
-
|
|
57
|
-
* Compare `base` and `revised` and return the base document with every
|
|
58
|
-
* difference recorded as a tracked change. See the module doc comment for
|
|
59
|
-
* semantics and limitations; `skipped` reports any block the generator could
|
|
60
|
-
* not redline (e.g. additions with no anchor in an empty base document).
|
|
61
|
-
*/
|
|
62
|
-
const generateRedlineDocx = async (base, revised, options = {}) => {
|
|
63
|
-
const [baseReviewer, revisedReviewer] = await Promise.all([FolioDocxReviewer.fromBuffer(base, { author: options.author ?? "folio compare" }), FolioDocxReviewer.fromBuffer(revised)]);
|
|
64
|
-
baseReviewer.acceptAll();
|
|
65
|
-
const baseSnapshot = baseReviewer.snapshot();
|
|
66
|
-
const revisedBlocks = revisedReviewer.snapshot().blocks;
|
|
29
|
+
const buildRedlineOperations = ({ baseSnapshot, revisedBlocks, nextOperationId }) => {
|
|
67
30
|
const events = alignFolioBlocks(baseSnapshot.blocks, revisedBlocks);
|
|
68
31
|
const anchorIds = nextBaseBlockIdByIndex(events);
|
|
69
32
|
const operations = [];
|
|
70
|
-
let operationSeq = 0;
|
|
71
|
-
const nextOperationId = () => `redline-${++operationSeq}`;
|
|
72
|
-
/** Additions past the last base block, kept in revised-document order. */
|
|
73
33
|
const trailingAdditions = [];
|
|
74
34
|
const lastBaseBlockId = baseSnapshot.blocks.at(-1)?.id ?? null;
|
|
75
35
|
events.forEach((event, eventIndex) => {
|
|
@@ -123,15 +83,88 @@ const generateRedlineDocx = async (base, revised, options = {}) => {
|
|
|
123
83
|
text: addition.text,
|
|
124
84
|
...addition.styleId !== void 0 && { styleId: addition.styleId }
|
|
125
85
|
});
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
86
|
+
return operations;
|
|
87
|
+
};
|
|
88
|
+
const resolveInputView = (value, option) => {
|
|
89
|
+
if (value === void 0) return "final";
|
|
90
|
+
if (!isFolioResolvedReviewedView(value)) throw new InvalidGenerateRedlineDocxOptionsError({
|
|
91
|
+
message: `${option} must be original or final.`,
|
|
92
|
+
option,
|
|
93
|
+
receivedValue: value
|
|
129
94
|
});
|
|
95
|
+
return value;
|
|
96
|
+
};
|
|
97
|
+
/** Compare two buffers and return tracked changes for every matched editable story. */
|
|
98
|
+
const generateRedlineDocx = async (base, revised, options = {}) => {
|
|
99
|
+
const baseView = resolveInputView(options.baseView, "baseView");
|
|
100
|
+
const revisedView = resolveInputView(options.revisedView, "revisedView");
|
|
101
|
+
const privacyTransforms = resolveFolioDocumentPrivacyTransforms(options.privacy?.transforms ?? []);
|
|
102
|
+
const [baseReviewer, revisedReviewer] = await Promise.all([FolioDocxReviewer.fromBuffer(base, { author: options.author ?? "folio compare" }), FolioDocxReviewer.fromBuffer(revised)]);
|
|
103
|
+
const baseStories = baseReviewer.listStories().map(({ handle }) => handle);
|
|
104
|
+
const revisedStories = revisedReviewer.listStories().map(({ handle }) => handle);
|
|
105
|
+
for (const story of baseStories) if (!baseReviewer.resolveReviewedStory({
|
|
106
|
+
story,
|
|
107
|
+
view: baseView
|
|
108
|
+
})) panic("A listed base story could not be resolved");
|
|
109
|
+
const applied = [];
|
|
110
|
+
const skipped = [];
|
|
111
|
+
const unprocessedStories = [];
|
|
112
|
+
let operationSequence = 0;
|
|
113
|
+
const nextOperationId = () => `redline-${++operationSequence}`;
|
|
114
|
+
for (const pair of pairFolioDocumentStories(baseStories, revisedStories)) {
|
|
115
|
+
if (!pair.baseStory) {
|
|
116
|
+
unprocessedStories.push({
|
|
117
|
+
...pair,
|
|
118
|
+
reason: "missing-base-story"
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (!pair.revisedStory) {
|
|
123
|
+
unprocessedStories.push({
|
|
124
|
+
...pair,
|
|
125
|
+
reason: "missing-revised-story"
|
|
126
|
+
});
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const baseSnapshot = baseReviewer.snapshotStory(pair.baseStory);
|
|
130
|
+
const revisedSnapshot = revisedReviewer.readReviewedStory({
|
|
131
|
+
story: pair.revisedStory,
|
|
132
|
+
view: revisedView
|
|
133
|
+
})?.snapshot;
|
|
134
|
+
if (!baseSnapshot || !revisedSnapshot) panic("A matched document story could not be read");
|
|
135
|
+
const operations = buildRedlineOperations({
|
|
136
|
+
baseSnapshot,
|
|
137
|
+
revisedBlocks: revisedSnapshot.blocks,
|
|
138
|
+
nextOperationId
|
|
139
|
+
});
|
|
140
|
+
if (operations.length === 0) continue;
|
|
141
|
+
const result = baseReviewer.applyDocumentOperationsToStory({
|
|
142
|
+
story: pair.baseStory,
|
|
143
|
+
snapshot: baseSnapshot,
|
|
144
|
+
batch: {
|
|
145
|
+
version: 1,
|
|
146
|
+
mode: "tracked-changes",
|
|
147
|
+
operations
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
applied.push(...result.applied);
|
|
151
|
+
skipped.push(...result.skipped);
|
|
152
|
+
}
|
|
153
|
+
const redlineBuffer = await baseReviewer.toBuffer();
|
|
154
|
+
const privacyResult = privacyTransforms.length === 0 ? {
|
|
155
|
+
buffer: redlineBuffer,
|
|
156
|
+
privacyReport: {
|
|
157
|
+
appliedTransforms: [],
|
|
158
|
+
removedMetadataProperties: []
|
|
159
|
+
}
|
|
160
|
+
} : await rewriteDocxMetadataPrivacy(redlineBuffer, { transforms: privacyTransforms });
|
|
130
161
|
return {
|
|
131
|
-
buffer:
|
|
162
|
+
buffer: privacyResult.buffer,
|
|
132
163
|
applied,
|
|
133
|
-
skipped
|
|
164
|
+
skipped,
|
|
165
|
+
unprocessedStories,
|
|
166
|
+
privacyReport: privacyResult.privacyReport
|
|
134
167
|
};
|
|
135
168
|
};
|
|
136
169
|
//#endregion
|
|
137
|
-
export { generateRedlineDocx };
|
|
170
|
+
export { InvalidGenerateRedlineDocxOptionsError, generateRedlineDocx };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { EditorState } from "prosemirror-state";
|
|
2
|
+
|
|
3
|
+
//#region src/render-dom/BodySelectionOverlay.d.ts
|
|
4
|
+
type BodySelectionOverlayResult = {
|
|
5
|
+
type: "text";
|
|
6
|
+
} | {
|
|
7
|
+
type: "image";
|
|
8
|
+
element: HTMLElement;
|
|
9
|
+
pmPos: number;
|
|
10
|
+
};
|
|
11
|
+
type SyncBodySelectionOverlayOptions = {
|
|
12
|
+
pagesContainer: HTMLElement;
|
|
13
|
+
state: EditorState;
|
|
14
|
+
zoom: number;
|
|
15
|
+
zIndex?: number;
|
|
16
|
+
caretColor?: string;
|
|
17
|
+
selectionColor?: string;
|
|
18
|
+
};
|
|
19
|
+
declare class BodySelectionOverlay {
|
|
20
|
+
clear(pagesContainer: HTMLElement): void;
|
|
21
|
+
sync({
|
|
22
|
+
pagesContainer,
|
|
23
|
+
state,
|
|
24
|
+
zoom,
|
|
25
|
+
zIndex,
|
|
26
|
+
caretColor,
|
|
27
|
+
selectionColor
|
|
28
|
+
}: SyncBodySelectionOverlayOptions): BodySelectionOverlayResult;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { BodySelectionOverlay, BodySelectionOverlayResult, SyncBodySelectionOverlayOptions };
|