@stll/folio-core 0.33.1 → 0.34.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/apply.d.ts +15 -0
- package/dist/ai-edits/apply.js +271 -48
- package/dist/ai-edits/headless.d.ts +47 -3
- package/dist/ai-edits/headless.js +53 -3
- package/dist/ai-edits/index.d.ts +1 -1
- package/dist/ai-edits/snapshot.d.ts +19 -1
- package/dist/ai-edits/snapshot.js +49 -5
- package/dist/ai-edits/table-geometry.d.ts +56 -0
- package/dist/ai-edits/table-geometry.js +217 -0
- package/dist/ai-edits/table-row-column-mutations.d.ts +34 -16
- package/dist/ai-edits/table-row-column-mutations.js +12 -4
- package/dist/ai-edits/table-template.d.ts +60 -0
- package/dist/ai-edits/table-template.js +176 -0
- package/dist/ai-edits/types.d.ts +13 -6
- package/dist/compare/__fixtures__/body-sequence.d.ts +61 -2
- package/dist/compare/__fixtures__/body-sequence.js +94 -6
- package/dist/compare/compare.d.ts +14 -7
- package/dist/compare/compare.js +136 -38
- package/dist/compare/formatting.d.ts +1 -1
- package/dist/compare/formatting.js +38 -9
- package/dist/compare/plan.d.ts +31 -1
- package/dist/compare/plan.js +233 -66
- package/dist/compare/types.d.ts +9 -7
- package/dist/compare/types.js +7 -5
- package/dist/compare/verification.d.ts +31 -12
- package/dist/compare/verification.js +106 -18
- package/dist/compat/eigenpal.d.ts +4 -4
- package/dist/compat/eigenpal.js +2 -2
- package/dist/controller/headerFooterEditorManager.js +11 -9
- package/dist/controller/layoutPipeline.js +24 -3
- package/dist/display-list/build/watermarkPrimitives.js +15 -3
- package/dist/display-list/primitives.d.ts +1 -1
- package/dist/document-operations.d.ts +10 -1
- package/dist/document-operations.js +33 -5
- package/dist/docx/appVersionNormalization.d.ts +49 -0
- package/dist/docx/appVersionNormalization.js +74 -0
- package/dist/docx/blockContentParser.js +3 -2
- package/dist/docx/drawingUtils.js +4 -3
- package/dist/docx/headerFooterParser.js +8 -14
- package/dist/docx/paragraphParser.js +48 -2
- package/dist/docx/rezip.d.ts +10 -4
- package/dist/docx/rezip.js +54 -18
- package/dist/docx/runParser.js +9 -8
- package/dist/docx/sdtProperties.js +4 -3
- package/dist/docx/selectiveSave.js +7 -6
- package/dist/docx/serializer/commentSerializer.d.ts +1 -1
- package/dist/docx/serializer/commentSerializer.js +43 -19
- package/dist/docx/serializer/documentSerializer.d.ts +3 -1
- package/dist/docx/serializer/documentSerializer.js +42 -78
- package/dist/docx/serializer/fontTableSerializer.js +8 -5
- package/dist/docx/serializer/headerFooterSerializer.d.ts +4 -1
- package/dist/docx/serializer/headerFooterSerializer.js +55 -31
- package/dist/docx/serializer/noteSerializer.js +34 -30
- package/dist/docx/serializer/numberingSerializer.js +8 -4
- package/dist/docx/serializer/paragraphSerializer.d.ts +1 -1
- package/dist/docx/serializer/paragraphSerializer.js +22 -12
- package/dist/docx/serializer/partNamespaces.d.ts +78 -0
- package/dist/docx/serializer/partNamespaces.js +324 -0
- package/dist/docx/serializer/runSerializer.js +1 -1
- package/dist/docx/serializer/settingsSerializer.js +8 -2
- package/dist/docx/serializer/stylesSerializer.js +8 -5
- package/dist/docx/serializer/tableSerializer.js +83 -18
- package/dist/docx/serializer/themeSerializer.js +10 -2
- package/dist/docx/server/build.d.ts +1 -1
- package/dist/docx/settingsParser.js +3 -0
- package/dist/docx/strictValueEncodings.gen.d.ts +15 -0
- package/dist/docx/strictValueEncodings.gen.js +275 -0
- package/dist/docx/tableParser.d.ts +0 -6
- package/dist/docx/tableParser.js +52 -7
- package/dist/docx/transitionalSpelling.d.ts +23 -0
- package/dist/docx/transitionalSpelling.js +36 -0
- package/dist/docx/universalMeasure.d.ts +20 -0
- package/dist/docx/universalMeasure.js +32 -0
- package/dist/docx/verbatimCapture.d.ts +20 -0
- package/dist/docx/verbatimCapture.js +131 -0
- package/dist/docx/vmlImageParser.js +4 -3
- package/dist/docx/watermarkParser.d.ts +2 -4
- package/dist/docx/watermarkParser.js +8 -9
- package/dist/docx/xmlParser.d.ts +23 -2
- package/dist/docx/xmlParser.js +43 -11
- package/dist/headless-layout.js +14 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +2 -2
- package/dist/layout-bridge/convert/footnoteLayout.js +23 -10
- package/dist/layout-bridge/convert/headerFooterLayout.js +13 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +96 -13
- package/dist/layout-engine/index.js +11 -4
- package/dist/layout-engine/justifiedLineFit.d.ts +4 -4
- package/dist/layout-engine/justifiedLineFit.js +4 -4
- package/dist/layout-engine/measure/lineBreakProvider.js +1 -0
- package/dist/layout-engine/measure/measureBlocks.js +1 -1
- package/dist/layout-engine/measure/measureParagraph.js +29 -29
- package/dist/layout-painter/renderPage.js +6 -1
- package/dist/layout-painter/renderParagraph.js +18 -8
- package/dist/layout-painter/renderWatermark.js +11 -4
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/prosemirror/attrs/index.js +11 -0
- package/dist/prosemirror/commands/comments.js +24 -3
- package/dist/prosemirror/containerFinalParagraph.d.ts +47 -0
- package/dist/prosemirror/containerFinalParagraph.js +92 -0
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +47 -9
- package/dist/prosemirror/conversion/toProseDoc.js +61 -14
- package/dist/prosemirror/extensions/core/DocExtension.js +7 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
- package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +1 -0
- package/dist/prosemirror/extensions/nodes/TableExtension.js +5 -0
- package/dist/prosemirror/schema/marks.d.ts +3 -9
- package/dist/prosemirror/schema/nodes.d.ts +22 -0
- package/dist/server.d.ts +1 -1
- package/dist/types/block-id.d.ts +12 -1
- package/dist/types/block-id.js +17 -1
- package/dist/utils/canonicalJson.d.ts +14 -0
- package/dist/utils/canonicalJson.js +19 -0
- package/dist/utils/fontResolver.js +51 -0
- package/dist/utils/formatToStyle.d.ts +1 -1
- package/dist/utils/formatToStyle.js +41 -1
- package/dist/watermark/index.js +7 -0
- package/package.json +2 -2
package/dist/ai-edits/index.d.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { DocPositionRange, clampRangeToDocSize, resolveFolioAIBlockRange, resolv
|
|
|
5
5
|
import { buildAnnotatedBlockText } from "./clean-text.js";
|
|
6
6
|
import { ApplyFolioDocumentOperationsOptions, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FolioDocumentOperation, FolioDocumentOperationAffectedTarget, FolioDocumentOperationBatch, FolioDocumentOperationCapabilities, FolioDocumentOperationIssue, FolioDocumentOperationMode, FolioDocumentOperationPrecondition, FolioDocumentOperationReceipt, FolioDocumentOperationRecovery, FolioDocumentOperationResult, FolioDocumentOperationResultBase, FolioDocumentOperationStatus, FolioDocumentOperationStory, FolioDocumentOperationType, FolioDocumentOperationUndoFailureReason, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "../document-operations.js";
|
|
7
7
|
import { FolioCommentAnchor, FolioReviewChange, FolioReviewChangeKind, getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
|
|
8
|
-
import { FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioApplyDocumentOperationsToStoryOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioEditableDocumentStoryHandle, FolioReadReviewedStoryOptions, FolioResolveReviewedStoryOptions, FolioResolvedReviewedView, FolioReviewedStory, FolioReviewedView, UnsupportedFolioReviewedViewError, isFolioResolvedReviewedView, isFolioReviewedView } from "./headless.js";
|
|
9
8
|
import { createFolioAIEditSnapshot, createFolioAITextRangeHandle, hashFolioAIBlockText, isFolioAIContentBlock, normalizeFolioAIBlockText } from "./snapshot.js";
|
|
9
|
+
import { FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioApplyDocumentOperationsToStoryOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioEditableDocumentStoryHandle, FolioReadReviewedStoryOptions, FolioResolveReviewedStoryOptions, FolioResolvedReviewedView, FolioReviewedStory, FolioReviewedView, UnsupportedFolioReviewedViewError, isFolioResolvedReviewedView, isFolioReviewedView } from "./headless.js";
|
|
10
10
|
import { getFolioDocumentOutline, readFolioDocumentSection } from "./scoped-reading.js";
|
|
11
11
|
import { getFolioParaIdFromBlockId } from "../types/block-id.js";
|
|
12
12
|
export { type ApplyFolioDocumentOperationsOptions, type DocPositionRange, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIBlockTableLocation, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyOutcome, type FolioAIEditApplyResult, type FolioAIEditNormalization, type FolioAIEditNormalizationCode, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAIEditView, type FolioAIInlineFormatting, type FolioAISignatureParty, type FolioAITextRangeHandle, type FolioApplyDocumentOperationsToStoryOptions, type FolioCommentAnchor, type FolioDocumentNavigationTarget, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationResultBase, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocumentOutline, type FolioDocumentOutlineEntry, type FolioDocumentSection, type FolioDocumentSectionHandle, type FolioDocumentSectionReadResult, type FolioDocumentStory, type FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, type FolioEditableDocumentStoryHandle, type FolioReadReviewedStoryOptions, type FolioResolveReviewedStoryOptions, type FolioResolvedReviewedView, type FolioReviewChange, type FolioReviewChangeKind, type FolioReviewedStory, type FolioReviewedView, type FolioRevisionStamp, type FolioWordDiffOptions, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioReviewedViewError, WORD_DIFF_GRANULARITIES, type WordDiffGranularity, type WordDiffNormalization, type WordDiffOptions, type WordDiffSegment, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioAIContentBlock, isFolioDocumentOperationModeSupported, isFolioResolvedReviewedView, isFolioReviewedView, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange };
|
|
@@ -50,6 +50,24 @@ declare const createFolioAITextRangeHandle: ({ blockId, text, startOffset, endOf
|
|
|
50
50
|
* whole subtree, so a table nested inside a hidden row is hidden with it.
|
|
51
51
|
*/
|
|
52
52
|
declare const isHiddenTableRow: (node: Node) => boolean;
|
|
53
|
+
/** One table of a story, numbered the way {@link createFolioAIEditSnapshot} numbers it. */
|
|
54
|
+
type FolioStoryTable = {
|
|
55
|
+
/** Document-order index over every table, nested ones included. */
|
|
56
|
+
index: number;
|
|
57
|
+
/** The table node's position in the story document. */
|
|
58
|
+
start: number;
|
|
59
|
+
node: Node;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Every table of one story in document order, nested tables included and
|
|
63
|
+
* hidden rows' subtrees excluded.
|
|
64
|
+
*
|
|
65
|
+
* The single place a story's tables are numbered. `table.tableIndex` on a
|
|
66
|
+
* block, the geometry a comparison copies out of the target and the geometry
|
|
67
|
+
* it checks its own work against all read this list, so no second walk can
|
|
68
|
+
* number the same document differently.
|
|
69
|
+
*/
|
|
70
|
+
declare const folioStoryTables: (doc: Node) => FolioStoryTable[];
|
|
53
71
|
declare const createFolioAIEditSnapshot: (doc: Node) => FolioAIEditSnapshot;
|
|
54
72
|
//#endregion
|
|
55
|
-
export { createFolioAIEditSnapshot, createFolioAITextRangeHandle, hashFolioAIBlockText, isFolioAIContentBlock, isHiddenTableRow, normalizeFolioAIBlockText, trailingBodyBlockId };
|
|
73
|
+
export { FolioStoryTable, createFolioAIEditSnapshot, createFolioAITextRangeHandle, folioStoryTables, hashFolioAIBlockText, isFolioAIContentBlock, isHiddenTableRow, normalizeFolioAIBlockText, trailingBodyBlockId };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { expectRunFormattingOverrideMarkAttrs } from "../prosemirror/attrs/index.js";
|
|
1
2
|
import { deriveBlankBlockId, deriveBlockId } from "../types/block-id.js";
|
|
2
3
|
import { buildCleanBlockText } from "./clean-text.js";
|
|
3
4
|
import { TableMap } from "prosemirror-tables";
|
|
@@ -69,6 +70,29 @@ const TABLE_ROLE_ROW = "row";
|
|
|
69
70
|
*/
|
|
70
71
|
const isHiddenTableRow = (node) => node.type.name === TABLE_ROW_NODE_NAME && node.attrs["hidden"] === true;
|
|
71
72
|
/**
|
|
73
|
+
* Every table of one story in document order, nested tables included and
|
|
74
|
+
* hidden rows' subtrees excluded.
|
|
75
|
+
*
|
|
76
|
+
* The single place a story's tables are numbered. `table.tableIndex` on a
|
|
77
|
+
* block, the geometry a comparison copies out of the target and the geometry
|
|
78
|
+
* it checks its own work against all read this list, so no second walk can
|
|
79
|
+
* number the same document differently.
|
|
80
|
+
*/
|
|
81
|
+
const folioStoryTables = (doc) => {
|
|
82
|
+
const tables = [];
|
|
83
|
+
doc.descendants((node, pos) => {
|
|
84
|
+
if (node.isTextblock) return false;
|
|
85
|
+
if (isHiddenTableRow(node)) return false;
|
|
86
|
+
if (node.type.spec["tableRole"] === TABLE_ROLE_TABLE) tables.push({
|
|
87
|
+
index: tables.length,
|
|
88
|
+
start: pos,
|
|
89
|
+
node
|
|
90
|
+
});
|
|
91
|
+
return true;
|
|
92
|
+
});
|
|
93
|
+
return tables;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
72
96
|
* Locate the block inside its innermost enclosing table, or `undefined` when
|
|
73
97
|
* it sits outside every table.
|
|
74
98
|
*/
|
|
@@ -102,7 +126,7 @@ const createFolioAIEditSnapshot = (doc) => {
|
|
|
102
126
|
const draftBlocks = [];
|
|
103
127
|
const hashCounts = /* @__PURE__ */ new Map();
|
|
104
128
|
const usedBlockIds = /* @__PURE__ */ new Set();
|
|
105
|
-
const tableIndexByStart =
|
|
129
|
+
const tableIndexByStart = new Map(folioStoryTables(doc).map(({ start, index }) => [start, index]));
|
|
106
130
|
const path = [];
|
|
107
131
|
let blockIndex = 0;
|
|
108
132
|
let blankIndex = 0;
|
|
@@ -110,7 +134,6 @@ const createFolioAIEditSnapshot = (doc) => {
|
|
|
110
134
|
while (pos >= (path.at(-1)?.end ?? Number.POSITIVE_INFINITY)) path.pop();
|
|
111
135
|
if (!node.isTextblock) {
|
|
112
136
|
if (isHiddenTableRow(node)) return false;
|
|
113
|
-
if (node.type.spec["tableRole"] === TABLE_ROLE_TABLE) tableIndexByStart.set(pos, tableIndexByStart.size);
|
|
114
137
|
if (!node.isLeaf) path.push({
|
|
115
138
|
node,
|
|
116
139
|
start: pos,
|
|
@@ -230,14 +253,16 @@ const getPreviewRuns = (node) => {
|
|
|
230
253
|
if (!child.isText || child.text === void 0) return true;
|
|
231
254
|
if (child.marks.some((mark) => mark.type.name === DELETION_MARK)) return false;
|
|
232
255
|
const style = getPreviewRunStyle(child.marks, defaultStyle);
|
|
256
|
+
const directFormatting = getDirectPreviewRunStyle(child.marks, defaultStyle);
|
|
233
257
|
const previous = runs.at(-1);
|
|
234
|
-
if (previous && samePreviewRunStyle(previous, style)) {
|
|
258
|
+
if (previous && samePreviewRunStyle(previous, style) && sameDirectFormatting(previous.directFormatting, directFormatting)) {
|
|
235
259
|
previous.text += child.text;
|
|
236
260
|
return false;
|
|
237
261
|
}
|
|
238
262
|
runs.push({
|
|
239
263
|
text: child.text,
|
|
240
|
-
...style
|
|
264
|
+
...style,
|
|
265
|
+
...!isEmptyPreviewRunStyle(directFormatting) && { directFormatting }
|
|
241
266
|
});
|
|
242
267
|
return false;
|
|
243
268
|
});
|
|
@@ -288,6 +313,23 @@ const getPreviewRunStyle = (marks, defaultStyle) => {
|
|
|
288
313
|
}
|
|
289
314
|
return style;
|
|
290
315
|
};
|
|
316
|
+
const getDirectPreviewRunStyle = (marks, inheritedStyle) => {
|
|
317
|
+
const markedStyle = getPreviewRunStyle(marks, {});
|
|
318
|
+
const directStyle = {};
|
|
319
|
+
const overrideMark = marks.find(({ type }) => type.name === "runFormattingOverride");
|
|
320
|
+
const hasFormattingProvenance = overrideMark !== void 0 || marks.some(({ type }) => type.name === "characterStyle");
|
|
321
|
+
const directFontProperties = overrideMark ? expectRunFormattingOverrideMarkAttrs(overrideMark).directFontProperties : void 0;
|
|
322
|
+
for (const property of [
|
|
323
|
+
"bold",
|
|
324
|
+
"italic",
|
|
325
|
+
"underline",
|
|
326
|
+
"strike"
|
|
327
|
+
]) if (Boolean(markedStyle[property]) !== Boolean(inheritedStyle[property])) directStyle[property] = Boolean(markedStyle[property]);
|
|
328
|
+
if (markedStyle.fontFamily !== void 0 && (hasFormattingProvenance ? directFontProperties?.includes("fontFamily") : markedStyle.fontFamily !== inheritedStyle.fontFamily)) directStyle.fontFamily = markedStyle.fontFamily;
|
|
329
|
+
if (markedStyle.fontSizePt !== void 0 && (hasFormattingProvenance ? directFontProperties?.includes("fontSize") : markedStyle.fontSizePt !== inheritedStyle.fontSizePt)) directStyle.fontSizePt = markedStyle.fontSizePt;
|
|
330
|
+
if (markedStyle.color !== void 0 && (hasFormattingProvenance ? directFontProperties?.includes("color") : markedStyle.color !== inheritedStyle.color)) directStyle.color = markedStyle.color;
|
|
331
|
+
return directStyle;
|
|
332
|
+
};
|
|
291
333
|
const getBooleanTextFormatting = (formatting) => ({
|
|
292
334
|
...Reflect.get(formatting, "bold") === true && { bold: true },
|
|
293
335
|
...Reflect.get(formatting, "italic") === true && { italic: true },
|
|
@@ -330,6 +372,8 @@ const getColorFromAttrs = (attrs) => {
|
|
|
330
372
|
return `#${rgb}`;
|
|
331
373
|
};
|
|
332
374
|
const samePreviewRunStyle = (run, style) => run.bold === style.bold && run.italic === style.italic && run.underline === style.underline && run.strike === style.strike && run.fontFamily === style.fontFamily && run.fontSizePt === style.fontSizePt && run.color === style.color;
|
|
375
|
+
const isEmptyPreviewRunStyle = ({ bold, italic, underline, strike, fontFamily, fontSizePt, color }) => bold === void 0 && italic === void 0 && underline === void 0 && strike === void 0 && fontFamily === void 0 && fontSizePt === void 0 && color === void 0;
|
|
376
|
+
const sameDirectFormatting = (left, right) => left === void 0 && isEmptyPreviewRunStyle(right) || left !== void 0 && left.bold === right.bold && left.italic === right.italic && left.underline === right.underline && left.strike === right.strike && left.fontFamily === right.fontFamily && left.fontSizePt === right.fontSizePt && left.color === right.color;
|
|
333
377
|
const isUnstyledPreviewRun = ({ bold, italic, underline, strike, fontFamily, fontSizePt, color }) => bold === void 0 && italic === void 0 && underline === void 0 && strike === void 0 && fontFamily === void 0 && fontSizePt === void 0 && color === void 0;
|
|
334
378
|
//#endregion
|
|
335
|
-
export { createFolioAIEditSnapshot, createFolioAITextRangeHandle, hashFolioAIBlockText, isFolioAIContentBlock, isHiddenTableRow, normalizeFolioAIBlockText, trailingBodyBlockId };
|
|
379
|
+
export { createFolioAIEditSnapshot, createFolioAITextRangeHandle, folioStoryTables, hashFolioAIBlockText, isFolioAIContentBlock, isHiddenTableRow, normalizeFolioAIBlockText, trailingBodyBlockId };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { FolioStoryTable } from "./snapshot.js";
|
|
2
|
+
import { Transaction } from "prosemirror-state";
|
|
3
|
+
import { Node } from "prosemirror-model";
|
|
4
|
+
//#region src/ai-edits/table-geometry.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* One line per table, in the document order tables are numbered in: the
|
|
7
|
+
* table's own properties, then each row's and each cell's. Nested tables get
|
|
8
|
+
* their own lines rather than being folded into their parent's, so a
|
|
9
|
+
* difference names the table it is in.
|
|
10
|
+
*/
|
|
11
|
+
declare const projectTableGeometry: (tables: readonly FolioStoryTable[]) => string[];
|
|
12
|
+
/** Where a cell sits: which table, which row of it, which cell of that row. */
|
|
13
|
+
type TableCellCoordinate = {
|
|
14
|
+
tableIndex: number;
|
|
15
|
+
rowIndex: number;
|
|
16
|
+
cellIndex: number;
|
|
17
|
+
};
|
|
18
|
+
/** One base cell and the target cell it was aligned with. */
|
|
19
|
+
type TableGeometryPairing = {
|
|
20
|
+
base: TableCellCoordinate;
|
|
21
|
+
target: TableCellCoordinate;
|
|
22
|
+
};
|
|
23
|
+
type MatchTableGeometryOptions = {
|
|
24
|
+
tr: Transaction;
|
|
25
|
+
/** The base story's tables, with the positions the transaction will write at. */
|
|
26
|
+
baseTables: readonly FolioStoryTable[];
|
|
27
|
+
/** The target story's tables, by the same index the pairings name. */
|
|
28
|
+
targetTables: ReadonlyMap<number, Node>;
|
|
29
|
+
pairings: readonly TableGeometryPairing[];
|
|
30
|
+
revision: {
|
|
31
|
+
author: string;
|
|
32
|
+
date: string;
|
|
33
|
+
idSeed: number;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
type MatchTableGeometryResult = {
|
|
37
|
+
/** First revision id a following batch may allocate. */
|
|
38
|
+
nextRevisionId: number;
|
|
39
|
+
/** Tables, rows and cells whose properties the match moved. */
|
|
40
|
+
matched: number;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Copy a paired table's, row's and cell's properties from the document it was
|
|
44
|
+
* compared against, recording the previous set so a reject restores it.
|
|
45
|
+
*
|
|
46
|
+
* Pairings name cells, because that is what the block alignment resolves; the
|
|
47
|
+
* row and the table each cell sits in are matched with it. A property set that
|
|
48
|
+
* already agrees is left alone, so an unchanged table produces no revision.
|
|
49
|
+
*
|
|
50
|
+
* `colspan` / `rowspan` never move: they shape the table map, and changing one
|
|
51
|
+
* without restructuring the rows around it leaves the map inconsistent with
|
|
52
|
+
* its own grid. That is the rule a reject of a `w:tcPrChange` follows too.
|
|
53
|
+
*/
|
|
54
|
+
declare const matchTableGeometry: ({ tr, baseTables, targetTables, pairings, revision }: MatchTableGeometryOptions) => MatchTableGeometryResult;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { MatchTableGeometryResult, TableCellCoordinate, TableGeometryPairing, matchTableGeometry, projectTableGeometry };
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { tableCellRejectAttrPatch, tableRejectAttrPatch, tableRowRejectAttrPatch } from "../prosemirror/commands/propertyChangeScope.js";
|
|
2
|
+
import { tableAttrsToFormatting, tableCellAttrsToFormatting, tableRowAttrsToFormatting } from "../prosemirror/conversion/fromProseDoc.js";
|
|
3
|
+
import { markStructuralChange } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
|
|
4
|
+
import { canonicalJson } from "../utils/canonicalJson.js";
|
|
5
|
+
//#region src/ai-edits/table-geometry.ts
|
|
6
|
+
const ORIGINAL_FORMATTING = "_originalFormatting";
|
|
7
|
+
/**
|
|
8
|
+
* The formatting a parser stored verbatim for the save path. It is deliberately
|
|
9
|
+
* outside every projection below: a document saved by an editor materializes
|
|
10
|
+
* style-resolved properties into its own `w:tcPr`, so two packages that render
|
|
11
|
+
* the same table can store different formatting for it, and comparing what was
|
|
12
|
+
* stored would report a difference no redline can or should represent. What is
|
|
13
|
+
* compared is the effective set the properties resolve to.
|
|
14
|
+
*/
|
|
15
|
+
const withoutOriginalFormatting = (keys) => keys.filter((key) => key !== ORIGINAL_FORMATTING);
|
|
16
|
+
/**
|
|
17
|
+
* Attrs a `w:tblPrChange` reject restores, and therefore the attrs a match
|
|
18
|
+
* writes and the projection reads. `columnWidths` is deliberately outside
|
|
19
|
+
* them: the grid is `w:tblGrid`, not `w:tblPr`, and the editable model has no
|
|
20
|
+
* `w:tblGridChange` to record a change of it against.
|
|
21
|
+
*/
|
|
22
|
+
const TABLE_SCOPED_ATTRS = withoutOriginalFormatting(Object.keys(tableRejectAttrPatch(void 0)));
|
|
23
|
+
const ROW_SCOPED_ATTRS = withoutOriginalFormatting(Object.keys(tableRowRejectAttrPatch(void 0)));
|
|
24
|
+
const CELL_SCOPED_ATTRS = withoutOriginalFormatting(Object.keys(tableCellRejectAttrPatch(void 0, void 0)));
|
|
25
|
+
/**
|
|
26
|
+
* `false` and absent are the same thing for every property in scope here: they
|
|
27
|
+
* are all presence flags (`w:tblHeader`, `w:hidden`, `w:noWrap`), and a parser
|
|
28
|
+
* that materializes an absent one as `false` must not read as a difference
|
|
29
|
+
* from one that leaves it unset.
|
|
30
|
+
*/
|
|
31
|
+
const scopedAttrs = (attrs, keys) => {
|
|
32
|
+
const scoped = {};
|
|
33
|
+
for (const key of keys) {
|
|
34
|
+
const value = attrs[key];
|
|
35
|
+
scoped[key] = value === false ? null : value ?? null;
|
|
36
|
+
}
|
|
37
|
+
return scoped;
|
|
38
|
+
};
|
|
39
|
+
const cellProjection = (cell) => canonicalJson({
|
|
40
|
+
colspan: cell.attrs["colspan"],
|
|
41
|
+
rowspan: cell.attrs["rowspan"],
|
|
42
|
+
...scopedAttrs(cell.attrs, CELL_SCOPED_ATTRS)
|
|
43
|
+
});
|
|
44
|
+
const rowProjection = (row) => {
|
|
45
|
+
const cells = [];
|
|
46
|
+
row.forEach((cell) => {
|
|
47
|
+
cells.push(cellProjection(cell));
|
|
48
|
+
});
|
|
49
|
+
return `${canonicalJson(scopedAttrs(row.attrs, ROW_SCOPED_ATTRS))}|${cells.join("|")}`;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* One line per table, in the document order tables are numbered in: the
|
|
53
|
+
* table's own properties, then each row's and each cell's. Nested tables get
|
|
54
|
+
* their own lines rather than being folded into their parent's, so a
|
|
55
|
+
* difference names the table it is in.
|
|
56
|
+
*/
|
|
57
|
+
const projectTableGeometry = (tables) => tables.map(({ node }) => {
|
|
58
|
+
const rows = [];
|
|
59
|
+
node.forEach((row) => {
|
|
60
|
+
rows.push(rowProjection(row));
|
|
61
|
+
});
|
|
62
|
+
return `${canonicalJson(scopedAttrs(node.attrs, TABLE_SCOPED_ATTRS))}#${rows.join("#")}`;
|
|
63
|
+
});
|
|
64
|
+
const childPositions = (node, start) => {
|
|
65
|
+
const positions = [];
|
|
66
|
+
let offset = start + 1;
|
|
67
|
+
node.forEach((child) => {
|
|
68
|
+
positions.push(offset);
|
|
69
|
+
offset += child.nodeSize;
|
|
70
|
+
});
|
|
71
|
+
return positions;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* `Node["attrs"]` is an open record, and the converters below want the node
|
|
75
|
+
* type's own shape. The two span counts are the only members the schema always
|
|
76
|
+
* carries a value for, so naming them is what turns the record into one.
|
|
77
|
+
*/
|
|
78
|
+
const cellAttrsOf = (node) => ({
|
|
79
|
+
...effectiveAttrs(node),
|
|
80
|
+
colspan: typeof node.attrs["colspan"] === "number" ? node.attrs["colspan"] : 1,
|
|
81
|
+
rowspan: typeof node.attrs["rowspan"] === "number" ? node.attrs["rowspan"] : 1
|
|
82
|
+
});
|
|
83
|
+
/**
|
|
84
|
+
* The node's attrs with the style cascade's own values cleared, so the
|
|
85
|
+
* converter treats every effective value as one the node states.
|
|
86
|
+
*
|
|
87
|
+
* A change element stores the COMPLETE previous property set and a reject
|
|
88
|
+
* rebuilds the live properties from it alone, so the record has to hold what
|
|
89
|
+
* the node renders with — including a border its table style supplied. The
|
|
90
|
+
* save path wants the opposite (write only what the node states, or the
|
|
91
|
+
* inherited value becomes an override), which is what the resolved companions
|
|
92
|
+
* are for; a record is the one place they get in the way.
|
|
93
|
+
*/
|
|
94
|
+
const effectiveAttrs = (node) => ({
|
|
95
|
+
...node.attrs,
|
|
96
|
+
_resolvedBorders: null,
|
|
97
|
+
_resolvedMargins: null,
|
|
98
|
+
_resolvedCellMargins: null
|
|
99
|
+
});
|
|
100
|
+
const TABLE_SCOPE = {
|
|
101
|
+
keys: TABLE_SCOPED_ATTRS,
|
|
102
|
+
formattingOf: (node) => tableAttrsToFormatting(effectiveAttrs(node)),
|
|
103
|
+
rejectPatch: (previousFormatting) => tableRejectAttrPatch(previousFormatting),
|
|
104
|
+
changeAttr: "tblPrChange",
|
|
105
|
+
changeType: "tablePropertyChange"
|
|
106
|
+
};
|
|
107
|
+
const ROW_SCOPE = {
|
|
108
|
+
keys: ROW_SCOPED_ATTRS,
|
|
109
|
+
formattingOf: (node) => tableRowAttrsToFormatting(node.attrs),
|
|
110
|
+
rejectPatch: (previousFormatting) => tableRowRejectAttrPatch(previousFormatting),
|
|
111
|
+
changeAttr: "trPrChange",
|
|
112
|
+
changeType: "tableRowPropertyChange"
|
|
113
|
+
};
|
|
114
|
+
const CELL_SCOPE = {
|
|
115
|
+
keys: CELL_SCOPED_ATTRS,
|
|
116
|
+
formattingOf: (node) => tableCellAttrsToFormatting(cellAttrsOf(node)),
|
|
117
|
+
rejectPatch: (previousFormatting, liveFormatting) => tableCellRejectAttrPatch(previousFormatting, liveFormatting),
|
|
118
|
+
changeAttr: "tcPrChange",
|
|
119
|
+
changeType: "tableCellPropertyChange"
|
|
120
|
+
};
|
|
121
|
+
const scopedValues = (attrs, keys) => canonicalJson(scopedAttrs(attrs, keys));
|
|
122
|
+
/**
|
|
123
|
+
* One node's properties, matched to the node it was paired with, or `null`
|
|
124
|
+
* when the difference cannot be recorded.
|
|
125
|
+
*
|
|
126
|
+
* A change element stores the COMPLETE previous property set, and rejecting it
|
|
127
|
+
* rebuilds the live properties from that record alone. So the record is built
|
|
128
|
+
* from what the node would SERIALIZE, not from what its parser stored: a cell
|
|
129
|
+
* inherits borders and margins from the table and from a table style, and a
|
|
130
|
+
* record that omitted them would reject to a third document.
|
|
131
|
+
*
|
|
132
|
+
* The check is direct, and it is what keeps the round trip exact: rebuild the
|
|
133
|
+
* live values from the record and see whether they come back. Where they do
|
|
134
|
+
* not, the difference is left alone rather than written as a revision that
|
|
135
|
+
* cannot be undone.
|
|
136
|
+
*/
|
|
137
|
+
const propertyChangeFor = (base, target, position, scope) => {
|
|
138
|
+
const live = scopedValues(base.attrs, scope.keys);
|
|
139
|
+
if (live === scopedValues(target.attrs, scope.keys)) return null;
|
|
140
|
+
const previousFormatting = scope.formattingOf(base);
|
|
141
|
+
if (scopedValues(scope.rejectPatch(previousFormatting, previousFormatting), scope.keys) !== live) return null;
|
|
142
|
+
return {
|
|
143
|
+
position,
|
|
144
|
+
attrs: {
|
|
145
|
+
...scopedAttrs(target.attrs, scope.keys),
|
|
146
|
+
[ORIGINAL_FORMATTING]: scope.formattingOf(target) ?? null
|
|
147
|
+
},
|
|
148
|
+
changeAttr: scope.changeAttr,
|
|
149
|
+
changeType: scope.changeType,
|
|
150
|
+
previousFormatting
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Copy a paired table's, row's and cell's properties from the document it was
|
|
155
|
+
* compared against, recording the previous set so a reject restores it.
|
|
156
|
+
*
|
|
157
|
+
* Pairings name cells, because that is what the block alignment resolves; the
|
|
158
|
+
* row and the table each cell sits in are matched with it. A property set that
|
|
159
|
+
* already agrees is left alone, so an unchanged table produces no revision.
|
|
160
|
+
*
|
|
161
|
+
* `colspan` / `rowspan` never move: they shape the table map, and changing one
|
|
162
|
+
* without restructuring the rows around it leaves the map inconsistent with
|
|
163
|
+
* its own grid. That is the rule a reject of a `w:tcPrChange` follows too.
|
|
164
|
+
*/
|
|
165
|
+
const matchTableGeometry = ({ tr, baseTables, targetTables, pairings, revision }) => {
|
|
166
|
+
const baseByIndex = new Map(baseTables.map((table) => [table.index, table]));
|
|
167
|
+
const targets = [];
|
|
168
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
169
|
+
const consider = (base, target, position, scope) => {
|
|
170
|
+
if (claimed.has(position)) return;
|
|
171
|
+
claimed.add(position);
|
|
172
|
+
const change = propertyChangeFor(base, target, position, scope);
|
|
173
|
+
if (change) targets.push(change);
|
|
174
|
+
};
|
|
175
|
+
for (const { base, target } of pairings) {
|
|
176
|
+
const baseTable = baseByIndex.get(base.tableIndex);
|
|
177
|
+
const targetTable = targetTables.get(target.tableIndex);
|
|
178
|
+
if (!baseTable || !targetTable) continue;
|
|
179
|
+
const baseRow = baseTable.node.maybeChild(base.rowIndex);
|
|
180
|
+
const targetRow = targetTable.maybeChild(target.rowIndex);
|
|
181
|
+
const rowPosition = childPositions(baseTable.node, baseTable.start)[base.rowIndex];
|
|
182
|
+
if (!baseRow || !targetRow || rowPosition === void 0) continue;
|
|
183
|
+
const baseCell = baseRow.maybeChild(base.cellIndex);
|
|
184
|
+
const targetCell = targetRow.maybeChild(target.cellIndex);
|
|
185
|
+
const cellPosition = childPositions(baseRow, rowPosition)[base.cellIndex];
|
|
186
|
+
if (!baseCell || !targetCell || cellPosition === void 0) continue;
|
|
187
|
+
consider(baseTable.node, targetTable, baseTable.start, TABLE_SCOPE);
|
|
188
|
+
consider(baseRow, targetRow, rowPosition, ROW_SCOPE);
|
|
189
|
+
consider(baseCell, targetCell, cellPosition, CELL_SCOPE);
|
|
190
|
+
}
|
|
191
|
+
let revisionId = revision.idSeed;
|
|
192
|
+
for (const target of targets) {
|
|
193
|
+
const node = tr.doc.nodeAt(target.position);
|
|
194
|
+
if (!node) continue;
|
|
195
|
+
tr.setNodeMarkup(target.position, void 0, {
|
|
196
|
+
...node.attrs,
|
|
197
|
+
...target.attrs,
|
|
198
|
+
[target.changeAttr]: [{
|
|
199
|
+
type: target.changeType,
|
|
200
|
+
info: {
|
|
201
|
+
id: revisionId,
|
|
202
|
+
author: revision.author,
|
|
203
|
+
date: revision.date
|
|
204
|
+
},
|
|
205
|
+
...target.previousFormatting != null && { previousFormatting: target.previousFormatting }
|
|
206
|
+
}]
|
|
207
|
+
});
|
|
208
|
+
revisionId += 1;
|
|
209
|
+
}
|
|
210
|
+
if (revisionId > revision.idSeed) markStructuralChange(tr);
|
|
211
|
+
return {
|
|
212
|
+
nextRevisionId: revisionId,
|
|
213
|
+
matched: targets.length
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
//#endregion
|
|
217
|
+
export { matchTableGeometry, projectTableGeometry };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TableStructureRevision } from "./table-template.js";
|
|
2
2
|
import { TableRowTarget } from "./table-targets.js";
|
|
3
3
|
import { Transaction } from "prosemirror-state";
|
|
4
4
|
import { Node } from "prosemirror-model";
|
|
@@ -27,19 +27,6 @@ type TableColumnInsertion = {
|
|
|
27
27
|
columnIndex: number;
|
|
28
28
|
};
|
|
29
29
|
type TableColumnDeletion = TableColumnInsertion;
|
|
30
|
-
type TableStructureRevision = {
|
|
31
|
-
revisionId: number;
|
|
32
|
-
author: string;
|
|
33
|
-
date: string;
|
|
34
|
-
/** Optional author initials (w:initials), carried for round-trip. */
|
|
35
|
-
initials?: string;
|
|
36
|
-
/**
|
|
37
|
-
* `"suggested"` marks the produced `trIns`/`trDel`/`cellMarker` as an AI
|
|
38
|
-
* proposal that is stripped from serialized DOCX until accepted.
|
|
39
|
-
*/
|
|
40
|
-
provenance?: TrackedChangeProvenance;
|
|
41
|
-
suggestionId?: string;
|
|
42
|
-
};
|
|
43
30
|
type TableRowColumnMutationResult = {
|
|
44
31
|
type: "applied";
|
|
45
32
|
transaction: Transaction;
|
|
@@ -63,8 +50,39 @@ type ApplyTableRowInsertionOptions = {
|
|
|
63
50
|
insertion: TableRowInsertion;
|
|
64
51
|
cellTexts: readonly string[] | undefined;
|
|
65
52
|
revision: TableStructureRevision | null;
|
|
53
|
+
/**
|
|
54
|
+
* The row to place, when the caller has one — a comparison adding a row the
|
|
55
|
+
* target document already holds. Its `w:trPr` and per-cell `w:tcPr` travel
|
|
56
|
+
* with it; `cellTexts` builds the row from the neighbouring one when it is
|
|
57
|
+
* absent or does not fit this table's grid.
|
|
58
|
+
*/
|
|
59
|
+
template?: Node;
|
|
60
|
+
};
|
|
61
|
+
declare const applyTableRowInsertion: ({ tr, insertion, cellTexts, revision, template }: ApplyTableRowInsertionOptions) => TableRowColumnMutationResult;
|
|
62
|
+
type MarkTableRowContentOptions = {
|
|
63
|
+
tr: Transaction;
|
|
64
|
+
rowPosition: number;
|
|
65
|
+
kind: "insertion" | "deletion";
|
|
66
|
+
revision: TableStructureRevision;
|
|
66
67
|
};
|
|
67
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Mark the runs inside a row whose structural revision was just written.
|
|
70
|
+
*
|
|
71
|
+
* Word records a tracked row insertion or deletion twice: on the row
|
|
72
|
+
* (`w:trPr/w:ins` | `w:trPr/w:del`) AND around every run in its cells
|
|
73
|
+
* (`w:ins` | `w:del`, the latter with `w:delText`). A consumer that reads only
|
|
74
|
+
* run-level revisions — which is most of them — keeps a deleted row's text on
|
|
75
|
+
* accept and an inserted row's text on reject when the row marker stands
|
|
76
|
+
* alone, so the two must always be written together.
|
|
77
|
+
*
|
|
78
|
+
* Two kinds of run stay unmarked. A run that already carries a revision keeps
|
|
79
|
+
* it, because OOXML nests `w:ins`/`w:del` but the editable model holds one
|
|
80
|
+
* wrapper per run and overwriting would drop the earlier revision. A cell that
|
|
81
|
+
* spans into the row below survives the deletion — `removeRow` moves it down
|
|
82
|
+
* and shortens its span — so marking its text would delete content the
|
|
83
|
+
* accepted document must still hold.
|
|
84
|
+
*/
|
|
85
|
+
declare const markTableRowContent: ({ tr, rowPosition, kind, revision }: MarkTableRowContentOptions) => void;
|
|
68
86
|
type ApplyTableColumnInsertionOptions = {
|
|
69
87
|
tr: Transaction;
|
|
70
88
|
insertion: TableColumnInsertion;
|
|
@@ -98,4 +116,4 @@ declare const applyTableRowDeletion: ({ tr, deletion, revision }: ApplyTableRowD
|
|
|
98
116
|
*/
|
|
99
117
|
declare const splitCellParagraphTexts: (text: string) => string[];
|
|
100
118
|
//#endregion
|
|
101
|
-
export { TableColumnDeletion, TableColumnInsertion, TableRowDeletion, TableRowInsertion,
|
|
119
|
+
export { MarkTableRowContentOptions, TableColumnDeletion, TableColumnInsertion, TableRowDeletion, TableRowInsertion, applyTableColumnDeletion, applyTableColumnInsertion, applyTableRowDeletion, applyTableRowInsertion, findTableColumnInsertion, findTableRowInsertion, getTableColumnCoordinateKey, markTableRowContent, splitCellParagraphTexts };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { markStructuralChange } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
|
|
2
2
|
import { stripBlockIdentityAttrs } from "./block-identity.js";
|
|
3
3
|
import { findEnclosingTableCell, findEnclosingTableRow } from "./table-targets.js";
|
|
4
|
+
import { tableRowFromTemplate } from "./table-template.js";
|
|
4
5
|
import { TableMap, columnIsHeader, removeColumn, removeRow, rowIsHeader, tableNodeTypes } from "prosemirror-tables";
|
|
5
6
|
//#region src/ai-edits/table-row-column-mutations.ts
|
|
6
7
|
const getTableColumnCoordinateKey = ({ tablePosition, columnIndex }) => `${tablePosition}:${columnIndex}`;
|
|
@@ -25,7 +26,7 @@ const findTableRowInsertion = ({ doc, blockFrom, position }) => {
|
|
|
25
26
|
rowIndex
|
|
26
27
|
});
|
|
27
28
|
};
|
|
28
|
-
const applyTableRowInsertion = ({ tr, insertion, cellTexts, revision }) => {
|
|
29
|
+
const applyTableRowInsertion = ({ tr, insertion, cellTexts, revision, template }) => {
|
|
29
30
|
if (revision && insertion.rowspanUpdates.length > 0) return { type: "unsupported" };
|
|
30
31
|
const liveRowspanUpdates = [];
|
|
31
32
|
for (const updatePosition of insertion.rowspanUpdates) {
|
|
@@ -44,8 +45,15 @@ const applyTableRowInsertion = ({ tr, insertion, cellTexts, revision }) => {
|
|
|
44
45
|
rowspan: update.rowspan + 1
|
|
45
46
|
});
|
|
46
47
|
const rowAttrs = revision ? { trIns: revision } : null;
|
|
47
|
-
const
|
|
48
|
-
|
|
48
|
+
const templated = template === void 0 ? null : tableRowFromTemplate({
|
|
49
|
+
template,
|
|
50
|
+
columnCount: insertion.columnCount
|
|
51
|
+
});
|
|
52
|
+
const row = templated ? templated.type.create({
|
|
53
|
+
...templated.attrs,
|
|
54
|
+
...rowAttrs
|
|
55
|
+
}, templated.content) : populateTableRow(insertion.rowType.create(rowAttrs, insertion.cells), cellTexts);
|
|
56
|
+
tr.insert(insertion.rowPosition, row);
|
|
49
57
|
if (revision) markTableRowContent({
|
|
50
58
|
tr,
|
|
51
59
|
rowPosition: insertion.rowPosition,
|
|
@@ -389,4 +397,4 @@ const getTableColumnDeletionCellPositions = ({ map, table, columnIndex }) => {
|
|
|
389
397
|
return [...positions];
|
|
390
398
|
};
|
|
391
399
|
//#endregion
|
|
392
|
-
export { applyTableColumnDeletion, applyTableColumnInsertion, applyTableRowDeletion, applyTableRowInsertion, findTableColumnInsertion, findTableRowInsertion, getTableColumnCoordinateKey, splitCellParagraphTexts };
|
|
400
|
+
export { applyTableColumnDeletion, applyTableColumnInsertion, applyTableRowDeletion, applyTableRowInsertion, findTableColumnInsertion, findTableRowInsertion, getTableColumnCoordinateKey, markTableRowContent, splitCellParagraphTexts };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { TrackedChangeProvenance } from "../prosemirror/schema/marks.js";
|
|
2
|
+
import { Node, Schema } from "prosemirror-model";
|
|
3
|
+
//#region src/ai-edits/table-template.d.ts
|
|
4
|
+
/** The node an operation should place, by the id of the operation placing it. */
|
|
5
|
+
type FolioTableTemplates = ReadonlyMap<string, Node>;
|
|
6
|
+
/**
|
|
7
|
+
* A structural tracked change on a table row.
|
|
8
|
+
*
|
|
9
|
+
* A whole-table insertion has no element of its own in the format: every row
|
|
10
|
+
* carries `w:trPr/w:ins`, and every run inside it carries `w:ins` as well. A
|
|
11
|
+
* consumer that reads only run-level revisions keeps the text of a rejected
|
|
12
|
+
* insertion when the row marker stands alone, so the two are always written
|
|
13
|
+
* together.
|
|
14
|
+
*/
|
|
15
|
+
type TableStructureRevision = {
|
|
16
|
+
revisionId: number;
|
|
17
|
+
author: string;
|
|
18
|
+
date: string;
|
|
19
|
+
/** Optional author initials (w:initials), carried for round-trip. */
|
|
20
|
+
initials?: string;
|
|
21
|
+
/**
|
|
22
|
+
* `"suggested"` marks the produced `trIns`/`trDel`/`cellMarker` as an AI
|
|
23
|
+
* proposal that is stripped from serialized DOCX until accepted.
|
|
24
|
+
*/
|
|
25
|
+
provenance?: TrackedChangeProvenance;
|
|
26
|
+
suggestionId?: string;
|
|
27
|
+
};
|
|
28
|
+
type TableFromTemplateOptions = {
|
|
29
|
+
schema: Schema;
|
|
30
|
+
template: Node;
|
|
31
|
+
/** Present in tracked mode: the whole table is stamped as one insertion. */
|
|
32
|
+
revision?: TableStructureRevision;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The template table, ready to insert: its properties, grid, rows and cells
|
|
36
|
+
* carried verbatim, nested tables included, and every row plus every run
|
|
37
|
+
* stamped as an insertion when the caller is tracking changes.
|
|
38
|
+
*
|
|
39
|
+
* `null` when the template is not a table or holds no rows, so the caller can
|
|
40
|
+
* fall back to the operation's own cell texts rather than place something that
|
|
41
|
+
* is not one.
|
|
42
|
+
*/
|
|
43
|
+
declare const tableFromTemplate: ({ schema, template, revision }: TableFromTemplateOptions) => Node | null;
|
|
44
|
+
type TableRowFromTemplateOptions = {
|
|
45
|
+
template: Node;
|
|
46
|
+
/** Grid columns the row has to occupy, from the table receiving it. */
|
|
47
|
+
columnCount: number;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* The template row, ready to insert into an existing table.
|
|
51
|
+
*
|
|
52
|
+
* Refused — `null`, so the caller falls back to the operation's cell texts —
|
|
53
|
+
* when the row's cells do not span exactly the receiving table's grid: a row
|
|
54
|
+
* of a different width would leave the table's map inconsistent with its own
|
|
55
|
+
* grid. A cell that merged vertically in the document it came from is placed
|
|
56
|
+
* unmerged, because the rows it reached into are not this table's.
|
|
57
|
+
*/
|
|
58
|
+
declare const tableRowFromTemplate: ({ template, columnCount }: TableRowFromTemplateOptions) => Node | null;
|
|
59
|
+
//#endregion
|
|
60
|
+
export { FolioTableTemplates, TableStructureRevision, tableFromTemplate, tableRowFromTemplate };
|