@stll/folio-core 0.47.0 → 0.47.2
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 +17 -1
- package/dist/ai-edits/apply.js +5 -3
- package/dist/ai-edits/headless.d.ts +9 -2
- package/dist/ai-edits/headless.js +4 -2
- package/dist/compare/compare.js +2 -1
- package/dist/compare/content-alignment.js +84 -34
- package/dist/document-operations.d.ts +9 -2
- package/dist/document-operations.js +3 -2
- package/dist/docx/notePropertiesParser.js +10 -2
- package/dist/docx/numberingParser.d.ts +2 -10
- package/dist/docx/numberingParser.js +6 -14
- package/dist/docx/runParser.js +10 -4
- package/dist/docx/serializer/numberingSerializer.js +2 -3
- package/dist/docx/serializer/runSerializer.js +5 -2
- package/dist/docx/serializer/sectionPropertiesSerializer.js +10 -2
- package/dist/docx/serializer/tableSerializer.js +2 -1
- package/dist/docx/tableParser.js +4 -2
- package/dist/prosemirror/attrs/index.js +20 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +31 -15
- package/dist/prosemirror/conversion/toProseDoc.js +13 -3
- package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +2 -1
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +7 -3
- package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +5 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.js +40 -1
- package/dist/prosemirror/runFormattingInlineCarriers.js +18 -1
- package/dist/prosemirror/schema/marks.d.ts +4 -0
- package/dist/prosemirror/schema/nodes.d.ts +11 -0
- package/dist/utils/tableOperations.d.ts +1 -1
- package/package.json +2 -2
package/dist/ai-edits/apply.d.ts
CHANGED
|
@@ -57,6 +57,8 @@ type ApplyFolioAIEditOperationsOptions = {
|
|
|
57
57
|
* still describes a table by its cell texts.
|
|
58
58
|
*/
|
|
59
59
|
tableTemplates?: FolioTableTemplates;
|
|
60
|
+
/** What a replacement does with a background the text it replaces carries. */
|
|
61
|
+
replacementBackground?: FolioReplacementBackground;
|
|
60
62
|
};
|
|
61
63
|
/**
|
|
62
64
|
* How an apply batch cuts the redline for a replacement. Word-level by
|
|
@@ -68,6 +70,20 @@ type ApplyFolioAIEditOperationsOptions = {
|
|
|
68
70
|
type FolioWordDiffOptions = {
|
|
69
71
|
granularity?: WordDiffGranularity;
|
|
70
72
|
};
|
|
73
|
+
/**
|
|
74
|
+
* What a replacement does with a highlight or `w:shd` the text it replaces
|
|
75
|
+
* carries.
|
|
76
|
+
*
|
|
77
|
+
* `clear` is the authoring default: text typed over a highlighted placeholder
|
|
78
|
+
* is new text, and keeping the marker that said "fill this in" would carry it
|
|
79
|
+
* into the finished document. `keep` is for a caller that states the
|
|
80
|
+
* replacement's own run properties rather than letting it inherit them — a
|
|
81
|
+
* comparison reproducing a revised document holds the properties that document
|
|
82
|
+
* has, and clearing them first records a run-property change it takes straight
|
|
83
|
+
* back, which reaches the reader as a revision whose before and after are the
|
|
84
|
+
* same.
|
|
85
|
+
*/
|
|
86
|
+
type FolioReplacementBackground = "clear" | "keep";
|
|
71
87
|
/**
|
|
72
88
|
* An apply result plus where the batch left the revision-id counter.
|
|
73
89
|
*
|
|
@@ -88,4 +104,4 @@ type FolioAIEditApplyOutcome = FolioAIEditApplyResult & {
|
|
|
88
104
|
declare const applyFolioAIEditOperations: (options: ApplyFolioAIEditOperationsOptions) => FolioAIEditApplyOutcome;
|
|
89
105
|
declare const previewFolioAIEditOperations: (options: ApplyFolioAIEditOperationsOptions) => FolioAIEditApplyOutcome;
|
|
90
106
|
//#endregion
|
|
91
|
-
export { FolioAIEditApplyOutcome, FolioAIEditView, FolioRevisionStamp, FolioWordDiffOptions, applyFolioAIEditOperations, previewFolioAIEditOperations };
|
|
107
|
+
export { FolioAIEditApplyOutcome, FolioAIEditView, FolioReplacementBackground, FolioRevisionStamp, FolioWordDiffOptions, applyFolioAIEditOperations, previewFolioAIEditOperations };
|
package/dist/ai-edits/apply.js
CHANGED
|
@@ -578,8 +578,8 @@ const formattingChangesForAuthoredFormatting = (authoredFormatting, formatting)
|
|
|
578
578
|
}
|
|
579
579
|
return changes;
|
|
580
580
|
};
|
|
581
|
-
const clearReplacementBackground = ({ tr, schema, from, to, mode, revisionIdSeed, author, date, initials, suggestionId = null, styleResolver }) => {
|
|
582
|
-
if (![schema.marks["highlight"], schema.marks["runShading"]].some((markType) => markType !== void 0 && tr.doc.rangeHasMark(from, to, markType))) return {
|
|
581
|
+
const clearReplacementBackground = ({ tr, schema, from, to, mode, replacementBackground, revisionIdSeed, author, date, initials, suggestionId = null, styleResolver }) => {
|
|
582
|
+
if (!(replacementBackground === "clear" && [schema.marks["highlight"], schema.marks["runShading"]].some((markType) => markType !== void 0 && tr.doc.rangeHasMark(from, to, markType)))) return {
|
|
583
583
|
type: "applied",
|
|
584
584
|
transaction: tr,
|
|
585
585
|
revisionIds: [],
|
|
@@ -1241,7 +1241,7 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
1241
1241
|
nextRevisionId
|
|
1242
1242
|
};
|
|
1243
1243
|
};
|
|
1244
|
-
const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode = "tracked-changes", author = "AI", initials, createCommentId, revisionStamp, revisionIdSeed, wordDiff, wordDiffMode = "bounded", tableTemplates }) => {
|
|
1244
|
+
const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode = "tracked-changes", author = "AI", initials, createCommentId, revisionStamp, revisionIdSeed, wordDiff, wordDiffMode = "bounded", tableTemplates, replacementBackground = "clear" }) => {
|
|
1245
1245
|
const applied = [];
|
|
1246
1246
|
const skipped = [];
|
|
1247
1247
|
const normalizations = [];
|
|
@@ -1532,6 +1532,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1532
1532
|
from: item.from,
|
|
1533
1533
|
to: item.to,
|
|
1534
1534
|
mode,
|
|
1535
|
+
replacementBackground,
|
|
1535
1536
|
revisionIdSeed: revisionIdBackgroundSeed,
|
|
1536
1537
|
author,
|
|
1537
1538
|
date,
|
|
@@ -1659,6 +1660,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
|
|
|
1659
1660
|
from: item.from,
|
|
1660
1661
|
to: item.to,
|
|
1661
1662
|
mode,
|
|
1663
|
+
replacementBackground,
|
|
1662
1664
|
revisionIdSeed: revisionIdBackgroundSeed,
|
|
1663
1665
|
author,
|
|
1664
1666
|
date,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { document_d_exports } from "../types/document.js";
|
|
2
2
|
import { FolioTableTemplates } from "./table-template.js";
|
|
3
3
|
import { FolioAIBlock, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditOperation, FolioAIEditSnapshot } from "./types.js";
|
|
4
|
-
import { FolioRevisionStamp, FolioWordDiffOptions } from "./apply.js";
|
|
4
|
+
import { FolioReplacementBackground, FolioRevisionStamp, FolioWordDiffOptions } from "./apply.js";
|
|
5
5
|
import { ImportReferencedStyleDefinitionsResult } from "../compare/style-resources.js";
|
|
6
6
|
import { InlineProvenanceTargetOptions } from "../compare/inline-provenance.js";
|
|
7
7
|
import { MatchInlineAtomsOptions } from "../compare/inline-atoms.js";
|
|
@@ -124,6 +124,13 @@ type FolioApplyDocumentOperationsToStoryOptions = FolioApplyDocumentOperationsOp
|
|
|
124
124
|
* every table, row and cell property on the way.
|
|
125
125
|
*/
|
|
126
126
|
tableTemplates?: FolioTableTemplates;
|
|
127
|
+
/**
|
|
128
|
+
* What a replacement in the batch does with a highlight or `w:shd` the text
|
|
129
|
+
* it replaces carries. Clearing it is the authoring default; a caller that
|
|
130
|
+
* states the replacement's own run properties — `compareDocx` reproducing
|
|
131
|
+
* the target document — keeps them instead.
|
|
132
|
+
*/
|
|
133
|
+
replacementBackground?: FolioReplacementBackground;
|
|
127
134
|
};
|
|
128
135
|
type FolioHeaderFooterStoryHandle = Extract<FolioEditableDocumentStoryHandle, {
|
|
129
136
|
type: "header" | "footer";
|
|
@@ -382,7 +389,7 @@ declare class FolioDocxReviewer {
|
|
|
382
389
|
*/
|
|
383
390
|
applyDocumentOperations(batch: FolioDocumentOperationBatch, options?: FolioApplyDocumentOperationsOptions): FolioDocumentOperationResult;
|
|
384
391
|
/** Apply a versioned operation batch to one editable document story. */
|
|
385
|
-
applyDocumentOperationsToStory({ story, batch, snapshot, revisionStamp, wordDiff, tableTemplates }: FolioApplyDocumentOperationsToStoryOptions): FolioDocumentOperationResult;
|
|
392
|
+
applyDocumentOperationsToStory({ story, batch, snapshot, revisionStamp, wordDiff, tableTemplates, replacementBackground }: FolioApplyDocumentOperationsToStoryOptions): FolioDocumentOperationResult;
|
|
386
393
|
private applyDocumentOperationsInternal;
|
|
387
394
|
/** Undo the latest unchanged document-operation batch and its created comments. */
|
|
388
395
|
undoDocumentOperations(undoHandle: FolioDocumentOperationUndoHandle): FolioDocumentOperationUndoResult;
|
|
@@ -782,7 +782,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
782
782
|
});
|
|
783
783
|
}
|
|
784
784
|
/** Apply a versioned operation batch to one editable document story. */
|
|
785
|
-
applyDocumentOperationsToStory({ story, batch, snapshot, revisionStamp, wordDiff, tableTemplates }) {
|
|
785
|
+
applyDocumentOperationsToStory({ story, batch, snapshot, revisionStamp, wordDiff, tableTemplates, replacementBackground }) {
|
|
786
786
|
return this.applyDocumentOperationsInternal({
|
|
787
787
|
story,
|
|
788
788
|
batch,
|
|
@@ -790,10 +790,11 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
790
790
|
...revisionStamp !== void 0 && { revisionStamp },
|
|
791
791
|
...wordDiff !== void 0 && { wordDiff },
|
|
792
792
|
...tableTemplates !== void 0 && { tableTemplates },
|
|
793
|
+
...replacementBackground !== void 0 && { replacementBackground },
|
|
793
794
|
createUndoEntry: true
|
|
794
795
|
});
|
|
795
796
|
}
|
|
796
|
-
applyDocumentOperationsInternal({ story, batch, snapshot, revisionStamp, wordDiff, tableTemplates, createUndoEntry }) {
|
|
797
|
+
applyDocumentOperationsInternal({ story, batch, snapshot, revisionStamp, wordDiff, tableTemplates, replacementBackground, createUndoEntry }) {
|
|
797
798
|
const beforeState = this.requireEditableStoryState(story);
|
|
798
799
|
const createdCommentsLengthBefore = this.createdComments.length;
|
|
799
800
|
const view = {
|
|
@@ -811,6 +812,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
811
812
|
...revisionStamp !== void 0 && { revisionStamp },
|
|
812
813
|
...wordDiff !== void 0 && { wordDiff },
|
|
813
814
|
...tableTemplates !== void 0 && { tableTemplates },
|
|
815
|
+
...replacementBackground !== void 0 && { replacementBackground },
|
|
814
816
|
createCommentId: (text) => {
|
|
815
817
|
const comment = createReviewerComment(this.nextCommentId(), text, this.author);
|
|
816
818
|
this.createdComments.push(comment);
|
package/dist/compare/compare.js
CHANGED
|
@@ -573,7 +573,8 @@ const applyComparison = ({ reviewer, revisionStamp, granularity, revisionFormat,
|
|
|
573
573
|
mode: "tracked-changes",
|
|
574
574
|
operations: plan.operations
|
|
575
575
|
},
|
|
576
|
-
tableTemplates: resolveTableTemplates(targetTables, plan.tableTemplates)
|
|
576
|
+
tableTemplates: resolveTableTemplates(targetTables, plan.tableTemplates),
|
|
577
|
+
replacementBackground: "keep"
|
|
577
578
|
});
|
|
578
579
|
if (nextRevisionId === void 0) panic("The applier did not report where it left the revision-id counter", { story: pair.baseStory });
|
|
579
580
|
idSeed = nextRevisionId;
|
|
@@ -482,6 +482,37 @@ const groupFolioContentTableRows = (blocks) => {
|
|
|
482
482
|
}
|
|
483
483
|
return [...rows.values()];
|
|
484
484
|
};
|
|
485
|
+
/**
|
|
486
|
+
* Which uninterrupted run of its own cell each block belongs to.
|
|
487
|
+
*
|
|
488
|
+
* A cell holds paragraphs and nested tables, and the nested table splits the
|
|
489
|
+
* cell's paragraphs into runs: the ones before it and the ones after it are
|
|
490
|
+
* separate sequences, because nothing moves a block from one side of a table
|
|
491
|
+
* to the other. Aligned as one sequence they pair across it, and the plan that
|
|
492
|
+
* follows can neither move the base paragraph past the nested table nor delete
|
|
493
|
+
* the one it left behind when that paragraph ends the cell. So the runs are
|
|
494
|
+
* aligned separately, the way rows and cells already are.
|
|
495
|
+
*
|
|
496
|
+
* Document order is the whole input: a cell's blocks are contiguous until a
|
|
497
|
+
* block of another cell interrupts them, and inside one table segment the only
|
|
498
|
+
* cell that can interrupt another is one of a table nested in it.
|
|
499
|
+
*/
|
|
500
|
+
const cellRunOrdinals = (blocks) => {
|
|
501
|
+
const ordinals = /* @__PURE__ */ new Map();
|
|
502
|
+
const runByCell = /* @__PURE__ */ new Map();
|
|
503
|
+
let previousKey = null;
|
|
504
|
+
for (const block of blocks) {
|
|
505
|
+
const { table } = block;
|
|
506
|
+
if (!table) continue;
|
|
507
|
+
const key = `${String(table.tableIndex)}:${String(table.rowIndex)}:${String(table.cellIndex)}`;
|
|
508
|
+
const seen = runByCell.get(key) ?? -1;
|
|
509
|
+
const run = key === previousKey ? seen : seen + 1;
|
|
510
|
+
runByCell.set(key, run);
|
|
511
|
+
ordinals.set(block, run);
|
|
512
|
+
previousKey = key;
|
|
513
|
+
}
|
|
514
|
+
return ordinals;
|
|
515
|
+
};
|
|
485
516
|
const groupTables = (blocks) => {
|
|
486
517
|
const tables = /* @__PURE__ */ new Map();
|
|
487
518
|
for (const block of blocks) {
|
|
@@ -1013,7 +1044,7 @@ const alignProfiledContentSequence = ({ base, revised, workSession, canPair = ()
|
|
|
1013
1044
|
return aligned;
|
|
1014
1045
|
};
|
|
1015
1046
|
const rowLocation = (row) => row.at(0)?.table ?? null;
|
|
1016
|
-
const alignRowCells = ({ baseRow, revisedRow, workSession, moveScopeContext, stableIdMismatch, idStability, baseColumnKeys, revisedColumnKeys }) => {
|
|
1047
|
+
const alignRowCells = ({ baseRow, revisedRow, workSession, moveScopeContext, stableIdMismatch, idStability, baseColumnKeys, revisedColumnKeys, cellRuns }) => {
|
|
1017
1048
|
const byCell = (row, columnKeys) => {
|
|
1018
1049
|
const cells = /* @__PURE__ */ new Map();
|
|
1019
1050
|
for (const block of row) {
|
|
@@ -1024,47 +1055,62 @@ const alignRowCells = ({ baseRow, revisedRow, workSession, moveScopeContext, sta
|
|
|
1024
1055
|
if (alignedColumn === void 0) return panic("A paired table cell has no aligned grid column", { gridColumnIndex: table.gridColumnIndex });
|
|
1025
1056
|
cellIndex = alignedColumn;
|
|
1026
1057
|
}
|
|
1027
|
-
const
|
|
1058
|
+
const runs = cells.get(cellIndex) ?? /* @__PURE__ */ new Map();
|
|
1059
|
+
cells.set(cellIndex, runs);
|
|
1060
|
+
const run = cellRuns.get(block) ?? 0;
|
|
1061
|
+
const blocks = runs.get(run);
|
|
1028
1062
|
if (blocks) blocks.push(block);
|
|
1029
|
-
else
|
|
1063
|
+
else runs.set(run, [block]);
|
|
1030
1064
|
}
|
|
1031
1065
|
return cells;
|
|
1032
1066
|
};
|
|
1067
|
+
const ascending = (left, right) => left - right;
|
|
1033
1068
|
const baseCells = byCell(baseRow, baseColumnKeys);
|
|
1034
1069
|
const revisedCells = byCell(revisedRow, revisedColumnKeys);
|
|
1035
|
-
const cellIndexes = [.../* @__PURE__ */ new Set([...baseCells.keys(), ...revisedCells.keys()])].toSorted(
|
|
1070
|
+
const cellIndexes = [.../* @__PURE__ */ new Set([...baseCells.keys(), ...revisedCells.keys()])].toSorted(ascending);
|
|
1036
1071
|
const steps = [];
|
|
1037
1072
|
for (const cellIndex of cellIndexes) {
|
|
1038
|
-
const
|
|
1039
|
-
const
|
|
1040
|
-
const
|
|
1073
|
+
const baseRuns = baseCells.get(cellIndex) ?? /* @__PURE__ */ new Map();
|
|
1074
|
+
const revisedRuns = revisedCells.get(cellIndex) ?? /* @__PURE__ */ new Map();
|
|
1075
|
+
const runOrdinals = [.../* @__PURE__ */ new Set([...baseRuns.keys(), ...revisedRuns.keys()])].toSorted(ascending);
|
|
1076
|
+
for (const run of runOrdinals) steps.push(...alignCellRun({
|
|
1077
|
+
baseBlocks: baseRuns.get(run) ?? [],
|
|
1078
|
+
revisedBlocks: revisedRuns.get(run) ?? [],
|
|
1041
1079
|
workSession,
|
|
1042
|
-
|
|
1080
|
+
moveScopeContext,
|
|
1043
1081
|
stableIdMismatch,
|
|
1044
|
-
|
|
1045
|
-
})
|
|
1046
|
-
if (event.type === "pair" && !contentBlocksShareContainerPath(event.baseBlock, event.revisedBlock)) return [{
|
|
1047
|
-
type: "baseOnly",
|
|
1048
|
-
block: event.baseBlock
|
|
1049
|
-
}, {
|
|
1050
|
-
type: "revisedOnly",
|
|
1051
|
-
block: event.revisedBlock
|
|
1052
|
-
}];
|
|
1053
|
-
return [event];
|
|
1054
|
-
});
|
|
1055
|
-
const bucketByContainerPath = /* @__PURE__ */ new Map();
|
|
1056
|
-
const bucketForBlock = (block) => {
|
|
1057
|
-
const path = containerPathKeyOf(block);
|
|
1058
|
-
const existing = bucketByContainerPath.get(path);
|
|
1059
|
-
if (existing !== void 0) return existing;
|
|
1060
|
-
const bucket = moveScopeContext.nextTableCellBucket++;
|
|
1061
|
-
bucketByContainerPath.set(path, bucket);
|
|
1062
|
-
return bucket;
|
|
1063
|
-
};
|
|
1064
|
-
steps.push(...scopedAlignmentSteps(aligned, moveScopeContext, bucketForBlock));
|
|
1082
|
+
idStability
|
|
1083
|
+
}));
|
|
1065
1084
|
}
|
|
1066
1085
|
return steps;
|
|
1067
1086
|
};
|
|
1087
|
+
const alignCellRun = ({ baseBlocks, revisedBlocks, workSession, moveScopeContext, stableIdMismatch, idStability }) => {
|
|
1088
|
+
const aligned = alignFolioContentBlocksInScope(baseBlocks, revisedBlocks, {
|
|
1089
|
+
workSession,
|
|
1090
|
+
idStability,
|
|
1091
|
+
stableIdMismatch,
|
|
1092
|
+
structuralScope: "pairedTableCell"
|
|
1093
|
+
}).flatMap((event) => {
|
|
1094
|
+
if (event.type === "pair" && !contentBlocksShareContainerPath(event.baseBlock, event.revisedBlock)) return [{
|
|
1095
|
+
type: "baseOnly",
|
|
1096
|
+
block: event.baseBlock
|
|
1097
|
+
}, {
|
|
1098
|
+
type: "revisedOnly",
|
|
1099
|
+
block: event.revisedBlock
|
|
1100
|
+
}];
|
|
1101
|
+
return [event];
|
|
1102
|
+
});
|
|
1103
|
+
const bucketByContainerPath = /* @__PURE__ */ new Map();
|
|
1104
|
+
const bucketForBlock = (block) => {
|
|
1105
|
+
const path = containerPathKeyOf(block);
|
|
1106
|
+
const existing = bucketByContainerPath.get(path);
|
|
1107
|
+
if (existing !== void 0) return existing;
|
|
1108
|
+
const bucket = moveScopeContext.nextTableCellBucket++;
|
|
1109
|
+
bucketByContainerPath.set(path, bucket);
|
|
1110
|
+
return bucket;
|
|
1111
|
+
};
|
|
1112
|
+
return scopedAlignmentSteps(aligned, moveScopeContext, bucketForBlock);
|
|
1113
|
+
};
|
|
1068
1114
|
const pairTableRows = ({ baseRows, revisedRows, workSession, idStability }) => {
|
|
1069
1115
|
const profile = (row) => ({
|
|
1070
1116
|
item: row,
|
|
@@ -1097,7 +1143,7 @@ const pairTableRows = ({ baseRows, revisedRows, workSession, idStability }) => {
|
|
|
1097
1143
|
}
|
|
1098
1144
|
});
|
|
1099
1145
|
};
|
|
1100
|
-
const alignTableRows = ({ rows, workSession, moveScopeContext, stableIdMismatch, idStability, baseColumnKeys, revisedColumnKeys }) => {
|
|
1146
|
+
const alignTableRows = ({ rows, workSession, moveScopeContext, stableIdMismatch, idStability, baseColumnKeys, revisedColumnKeys, cellRuns }) => {
|
|
1101
1147
|
const steps = [];
|
|
1102
1148
|
const pushRow = (row, side) => {
|
|
1103
1149
|
const location = rowLocation(row);
|
|
@@ -1123,7 +1169,8 @@ const alignTableRows = ({ rows, workSession, moveScopeContext, stableIdMismatch,
|
|
|
1123
1169
|
stableIdMismatch,
|
|
1124
1170
|
idStability,
|
|
1125
1171
|
baseColumnKeys,
|
|
1126
|
-
revisedColumnKeys
|
|
1172
|
+
revisedColumnKeys,
|
|
1173
|
+
cellRuns
|
|
1127
1174
|
}));
|
|
1128
1175
|
break;
|
|
1129
1176
|
default: panic("Unhandled table row alignment", { alignment });
|
|
@@ -1148,7 +1195,7 @@ const rowCellSpansEqual = (baseRow, revisedRow) => {
|
|
|
1148
1195
|
});
|
|
1149
1196
|
};
|
|
1150
1197
|
const rowHasVerticalSpan = (row) => row.some(({ table }) => table !== void 0 && table.rowSpan > 1);
|
|
1151
|
-
const buildTablePlan = ({ baseBlocks, revisedBlocks, workSession, moveScopeContext, stableIdMismatch, idStability }) => {
|
|
1198
|
+
const buildTablePlan = ({ baseBlocks, revisedBlocks, workSession, moveScopeContext, stableIdMismatch, idStability, cellRuns }) => {
|
|
1152
1199
|
const resolveIdStability = idStability ?? folioContentIdStability;
|
|
1153
1200
|
const columns = alignTableColumns(baseBlocks, revisedBlocks);
|
|
1154
1201
|
const rows = pairTableRows({
|
|
@@ -1169,13 +1216,15 @@ const buildTablePlan = ({ baseBlocks, revisedBlocks, workSession, moveScopeConte
|
|
|
1169
1216
|
stableIdMismatch,
|
|
1170
1217
|
idStability: resolveIdStability,
|
|
1171
1218
|
baseColumnKeys: columns?.baseColumnKeys,
|
|
1172
|
-
revisedColumnKeys: columns?.revisedColumnKeys
|
|
1219
|
+
revisedColumnKeys: columns?.revisedColumnKeys,
|
|
1220
|
+
cellRuns
|
|
1173
1221
|
})]
|
|
1174
1222
|
};
|
|
1175
1223
|
};
|
|
1176
1224
|
const buildTableSegmentPlan = ({ baseBlocks, revisedBlocks, workSession, moveScopeContext, stableIdMismatch, idStability }) => {
|
|
1177
1225
|
const baseTables = groupTables(baseBlocks);
|
|
1178
1226
|
const revisedTables = groupTables(revisedBlocks);
|
|
1227
|
+
const cellRuns = new Map([...cellRunOrdinals(baseBlocks), ...cellRunOrdinals(revisedBlocks)]);
|
|
1179
1228
|
const resolveIdStability = idStability ?? folioContentIdStability;
|
|
1180
1229
|
const profile = (blocks) => ({
|
|
1181
1230
|
item: blocks,
|
|
@@ -1197,7 +1246,8 @@ const buildTableSegmentPlan = ({ baseBlocks, revisedBlocks, workSession, moveSco
|
|
|
1197
1246
|
workSession,
|
|
1198
1247
|
moveScopeContext,
|
|
1199
1248
|
stableIdMismatch,
|
|
1200
|
-
idStability: resolveIdStability
|
|
1249
|
+
idStability: resolveIdStability,
|
|
1250
|
+
cellRuns
|
|
1201
1251
|
});
|
|
1202
1252
|
steps.push(...table.steps);
|
|
1203
1253
|
representable &&= table.representable;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { document_d_exports } from "./types/document.js";
|
|
2
2
|
import { FolioTableTemplates } from "./ai-edits/table-template.js";
|
|
3
3
|
import { FolioAIEditAppliedOperation, FolioAIEditApplyMode, FolioAIEditNormalization, FolioAIEditOperation, FolioAIEditPrecondition, FolioAIEditSkippedOperation, FolioAIEditSnapshot, FolioAITextRangeHandle } from "./ai-edits/types.js";
|
|
4
|
-
import { FolioAIEditView, FolioRevisionStamp, FolioWordDiffOptions } from "./ai-edits/apply.js";
|
|
4
|
+
import { FolioAIEditView, FolioReplacementBackground, FolioRevisionStamp, FolioWordDiffOptions } from "./ai-edits/apply.js";
|
|
5
5
|
//#region src/document-operations.d.ts
|
|
6
6
|
declare const FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION: 1;
|
|
7
7
|
/** Direct paragraph-alignment values accepted by the operation contract. */
|
|
@@ -282,7 +282,14 @@ type ApplyFolioDocumentOperationsOptions = {
|
|
|
282
282
|
* copying the target document's — hands the whole thing over instead.
|
|
283
283
|
*/
|
|
284
284
|
tableTemplates?: FolioTableTemplates;
|
|
285
|
+
/**
|
|
286
|
+
* What a replacement in the batch does with a highlight or `w:shd` the text
|
|
287
|
+
* it replaces carries. Clearing it is the authoring default; a caller that
|
|
288
|
+
* states the replacement's own run properties — a comparison reproducing the
|
|
289
|
+
* revised document — keeps them instead.
|
|
290
|
+
*/
|
|
291
|
+
replacementBackground?: FolioReplacementBackground;
|
|
285
292
|
};
|
|
286
|
-
declare const applyFolioDocumentOperations: ({ view, snapshot, batch, story, author, createCommentId, createUndoHandle, revisionStamp, wordDiff, tableTemplates }: ApplyFolioDocumentOperationsOptions) => FolioDocumentOperationResult;
|
|
293
|
+
declare const applyFolioDocumentOperations: ({ view, snapshot, batch, story, author, createCommentId, createUndoHandle, revisionStamp, wordDiff, tableTemplates, replacementBackground }: ApplyFolioDocumentOperationsOptions) => FolioDocumentOperationResult;
|
|
287
294
|
//#endregion
|
|
288
295
|
export { ApplyFolioDocumentOperationsOptions, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PAGE_BREAK_CLEAR_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FolioDocumentOperation, FolioDocumentOperationAffectedTarget, FolioDocumentOperationBatch, FolioDocumentOperationBatchPrecondition, FolioDocumentOperationCapabilities, FolioDocumentOperationIssue, FolioDocumentOperationMode, FolioDocumentOperationPrecondition, FolioDocumentOperationQueuedOperation, FolioDocumentOperationReceipt, FolioDocumentOperationRecovery, FolioDocumentOperationResult, FolioDocumentOperationResultBase, FolioDocumentOperationStatus, FolioDocumentOperationStory, FolioDocumentOperationType, FolioDocumentOperationUndoFailureReason, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch };
|
|
@@ -1183,7 +1183,7 @@ const getFolioDocumentOperationReceiptsForStory = ({ operations, applied, story
|
|
|
1183
1183
|
});
|
|
1184
1184
|
return receipts;
|
|
1185
1185
|
};
|
|
1186
|
-
const applyFolioDocumentOperations = ({ view, snapshot, batch, story = "main", author, createCommentId, createUndoHandle, revisionStamp, wordDiff, tableTemplates }) => {
|
|
1186
|
+
const applyFolioDocumentOperations = ({ view, snapshot, batch, story = "main", author, createCommentId, createUndoHandle, revisionStamp, wordDiff, tableTemplates, replacementBackground }) => {
|
|
1187
1187
|
const parsedBatch = parseFolioDocumentOperationBatch(batch);
|
|
1188
1188
|
const apply = ({ targetView, targetCreateCommentId = createCommentId, preview = false }) => {
|
|
1189
1189
|
return (preview ? previewFolioAIEditOperations : applyFolioAIEditOperations)({
|
|
@@ -1195,7 +1195,8 @@ const applyFolioDocumentOperations = ({ view, snapshot, batch, story = "main", a
|
|
|
1195
1195
|
...targetCreateCommentId !== void 0 && { createCommentId: targetCreateCommentId },
|
|
1196
1196
|
...revisionStamp !== void 0 && { revisionStamp },
|
|
1197
1197
|
...wordDiff !== void 0 && { wordDiff },
|
|
1198
|
-
...tableTemplates !== void 0 && { tableTemplates }
|
|
1198
|
+
...tableTemplates !== void 0 && { tableTemplates },
|
|
1199
|
+
...replacementBackground !== void 0 && { replacementBackground }
|
|
1199
1200
|
});
|
|
1200
1201
|
};
|
|
1201
1202
|
const preview = () => apply({
|
|
@@ -52,7 +52,11 @@ function parseFootnoteProperties(element) {
|
|
|
52
52
|
const numFmtEl = findChild(element, "w", "numFmt");
|
|
53
53
|
if (numFmtEl) {
|
|
54
54
|
const numFmt = parseNumberFormat(getAttribute(numFmtEl, "w", "val"));
|
|
55
|
-
if (numFmt !== void 0)
|
|
55
|
+
if (numFmt !== void 0) {
|
|
56
|
+
props.numFmt = numFmt;
|
|
57
|
+
const format = getAttribute(numFmtEl, "w", "format");
|
|
58
|
+
if (format !== null) props.numFmtFormat = format;
|
|
59
|
+
}
|
|
56
60
|
}
|
|
57
61
|
const numStartEl = findChild(element, "w", "numStart");
|
|
58
62
|
if (numStartEl) {
|
|
@@ -81,7 +85,11 @@ function parseEndnoteProperties(element) {
|
|
|
81
85
|
const numFmtEl = findChild(element, "w", "numFmt");
|
|
82
86
|
if (numFmtEl) {
|
|
83
87
|
const numFmt = parseNumberFormat(getAttribute(numFmtEl, "w", "val"));
|
|
84
|
-
if (numFmt !== void 0)
|
|
88
|
+
if (numFmt !== void 0) {
|
|
89
|
+
props.numFmt = numFmt;
|
|
90
|
+
const format = getAttribute(numFmtEl, "w", "format");
|
|
91
|
+
if (format !== null) props.numFmtFormat = format;
|
|
92
|
+
}
|
|
85
93
|
}
|
|
86
94
|
const numStartEl = findChild(element, "w", "numStart");
|
|
87
95
|
if (numStartEl) {
|
|
@@ -24,7 +24,7 @@ type NumberingMap = {
|
|
|
24
24
|
* @returns NumberingMap with definitions and helper functions
|
|
25
25
|
*/
|
|
26
26
|
declare function parseNumbering(numberingXml: string | null): NumberingMap;
|
|
27
|
-
/** A `w:numFmt` as the model holds it:
|
|
27
|
+
/** A `w:numFmt` as the model holds it: its token and authored format metadata. */
|
|
28
28
|
type ResolvedNumFmt = Pick<document_d_exports.ListLevel, "numFmt" | "numFmtFormat">;
|
|
29
29
|
/**
|
|
30
30
|
* The width a `custom` format's zero-padded first token counts to, or
|
|
@@ -35,14 +35,6 @@ type ResolvedNumFmt = Pick<document_d_exports.ListLevel, "numFmt" | "numFmtForma
|
|
|
35
35
|
* way; the width is the digit count of the first token, clamped to five.
|
|
36
36
|
*/
|
|
37
37
|
declare const customNumberFormatPadWidth: (format: string | undefined) => number | undefined;
|
|
38
|
-
/**
|
|
39
|
-
* The `@w:format` a level's `w:numFmt` writes, which only `custom` carries.
|
|
40
|
-
*
|
|
41
|
-
* `custom` is the reserved member that defers to another attribute, so the
|
|
42
|
-
* comparison lives here, in the module the reserved-value registry names as
|
|
43
|
-
* its owner, rather than in the serializer that needs the answer.
|
|
44
|
-
*/
|
|
45
|
-
declare const customNumberFormatOf: (level: ResolvedNumFmt) => string | undefined;
|
|
46
38
|
/** What the marker renderer counts a level in; `custom` decides by pad width. */
|
|
47
39
|
declare const counterFormatOf: (level: ResolvedNumFmt) => document_d_exports.CounterFormat;
|
|
48
40
|
/** {@link MARKER_ALIGNMENT} for a level that may not state a justification. */
|
|
@@ -92,4 +84,4 @@ declare function isBulletLevel(level: document_d_exports.ListLevel): boolean;
|
|
|
92
84
|
/** Whether a numbering level reserves horizontal space for a visible marker. */
|
|
93
85
|
declare const numberingLevelHasMarkerSlot: (level: Pick<document_d_exports.ListLevel, "numFmt">) => boolean;
|
|
94
86
|
//#endregion
|
|
95
|
-
export { NumberingMap, computeListRendering, counterFormatOf, createNumberingMap,
|
|
87
|
+
export { NumberingMap, computeListRendering, counterFormatOf, createNumberingMap, customNumberFormatPadWidth, formatOoxmlCounter as formatNumber, getBulletCharacter, getCachedNumberingMap, isBulletLevel, markerAlignmentForLevel, markerFormattingFromLevel, numberingLevelHasMarkerSlot, padDecimal, parseNumbering, renderListMarker };
|
|
@@ -248,16 +248,16 @@ function parseListLevel(element) {
|
|
|
248
248
|
* Resolve a `<w:numFmt>` element, or null when it is absent or carries a token
|
|
249
249
|
* outside `ST_NumberFormat` (an mc:Fallback can then supply the rendering).
|
|
250
250
|
*
|
|
251
|
-
*
|
|
252
|
-
* decoded here into a synthetic `decimalZero{3,4,5}` the
|
|
253
|
-
* declare, which the serializer wrote back as a `w:val`
|
|
254
|
-
* The pad
|
|
251
|
+
* `@w:format` stays beside every admitted token exactly as authored. `custom`
|
|
252
|
+
* used to be decoded here into a synthetic `decimalZero{3,4,5}` the
|
|
253
|
+
* enumeration does not declare, which the serializer wrote back as a `w:val`
|
|
254
|
+
* no consumer can read. The pad-width interpretation belongs to the renderer,
|
|
255
|
+
* not to the model; preservation belongs to every schema-valid `CT_NumFmt`.
|
|
255
256
|
*/
|
|
256
257
|
function resolveNumFmt(numFmtEl) {
|
|
257
258
|
if (!numFmtEl) return null;
|
|
258
259
|
const numFmt = narrowEnum(getAttribute(numFmtEl, "w", "val"), NumberFormatSchema);
|
|
259
260
|
if (numFmt === void 0) return null;
|
|
260
|
-
if (numFmt !== "custom") return { numFmt };
|
|
261
261
|
const format = getAttribute(numFmtEl, "w", "format");
|
|
262
262
|
return format === null ? { numFmt } : {
|
|
263
263
|
numFmt,
|
|
@@ -293,14 +293,6 @@ const customNumberFormatPadWidth = (format) => {
|
|
|
293
293
|
if (!/^0+1$/u.test(firstToken)) return;
|
|
294
294
|
return Math.min(firstToken.length, 5);
|
|
295
295
|
};
|
|
296
|
-
/**
|
|
297
|
-
* The `@w:format` a level's `w:numFmt` writes, which only `custom` carries.
|
|
298
|
-
*
|
|
299
|
-
* `custom` is the reserved member that defers to another attribute, so the
|
|
300
|
-
* comparison lives here, in the module the reserved-value registry names as
|
|
301
|
-
* its owner, rather than in the serializer that needs the answer.
|
|
302
|
-
*/
|
|
303
|
-
const customNumberFormatOf = (level) => level.numFmt === "custom" ? level.numFmtFormat : void 0;
|
|
304
296
|
/** What the marker renderer counts a level in; `custom` decides by pad width. */
|
|
305
297
|
const counterFormatOf = (level) => {
|
|
306
298
|
if (level.numFmt !== "custom") return level.numFmt;
|
|
@@ -511,4 +503,4 @@ function isBulletLevel(level) {
|
|
|
511
503
|
/** Whether a numbering level reserves horizontal space for a visible marker. */
|
|
512
504
|
const numberingLevelHasMarkerSlot = (level) => level.numFmt !== "none";
|
|
513
505
|
//#endregion
|
|
514
|
-
export { computeListRendering, counterFormatOf, createNumberingMap,
|
|
506
|
+
export { computeListRendering, counterFormatOf, createNumberingMap, customNumberFormatPadWidth, formatOoxmlCounter as formatNumber, getBulletCharacter, getCachedNumberingMap, isBulletLevel, markerAlignmentForLevel, markerFormattingFromLevel, numberingLevelHasMarkerSlot, padDecimal, parseNumbering, renderListMarker };
|
package/dist/docx/runParser.js
CHANGED
|
@@ -16,7 +16,7 @@ import { resolveThemeFontRef } from "./themeParser.js";
|
|
|
16
16
|
import { parsePropertyChangeInfo } from "./trackedChangeInfo.js";
|
|
17
17
|
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
18
18
|
import { parseVmlImageContent, shouldPreserveRawVmlPict } from "./vmlImageParser.js";
|
|
19
|
-
import { WORDPROCESSINGML_NAMESPACE_URIS, cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getNamespaceUri, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, selectAlternateContentBranch } from "./xmlParser.js";
|
|
19
|
+
import { WORDPROCESSINGML_NAMESPACE_URIS, cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getNamespaceUri, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, parseOnOffAttribute, selectAlternateContentBranch } from "./xmlParser.js";
|
|
20
20
|
import { DRAWING_RAW_XML_MODES } from "@stll/docx-core/model";
|
|
21
21
|
//#region src/docx/runParser.ts
|
|
22
22
|
/**
|
|
@@ -486,19 +486,25 @@ function parseSymbolContent(element) {
|
|
|
486
486
|
* Parse footnote reference (w:footnoteReference)
|
|
487
487
|
*/
|
|
488
488
|
function parseFootnoteReference(element) {
|
|
489
|
-
|
|
489
|
+
const reference = {
|
|
490
490
|
type: "footnoteRef",
|
|
491
491
|
id: parseNumericAttribute(element, "w", "id") ?? 0
|
|
492
492
|
};
|
|
493
|
+
const customMarkFollows = parseOnOffAttribute(element, "w", "customMarkFollows");
|
|
494
|
+
if (customMarkFollows !== void 0) reference.customMarkFollows = customMarkFollows;
|
|
495
|
+
return reference;
|
|
493
496
|
}
|
|
494
497
|
/**
|
|
495
498
|
* Parse endnote reference (w:endnoteReference)
|
|
496
499
|
*/
|
|
497
500
|
function parseEndnoteReference(element) {
|
|
498
|
-
|
|
501
|
+
const reference = {
|
|
499
502
|
type: "endnoteRef",
|
|
500
503
|
id: parseNumericAttribute(element, "w", "id") ?? 0
|
|
501
504
|
};
|
|
505
|
+
const customMarkFollows = parseOnOffAttribute(element, "w", "customMarkFollows");
|
|
506
|
+
if (customMarkFollows !== void 0) reference.customMarkFollows = customMarkFollows;
|
|
507
|
+
return reference;
|
|
502
508
|
}
|
|
503
509
|
/**
|
|
504
510
|
* Parse field character (w:fldChar)
|
|
@@ -670,7 +676,7 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
|
|
|
670
676
|
case "cr":
|
|
671
677
|
contents.push({
|
|
672
678
|
type: "break",
|
|
673
|
-
|
|
679
|
+
sourceElement: "cr"
|
|
674
680
|
});
|
|
675
681
|
break;
|
|
676
682
|
case "AlternateContent": {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { serializeParagraphPropertySet } from "../../internal/paragraphFormattingSerialization.js";
|
|
2
2
|
import { serializePreservedAttributes } from "../attributeRemainder.js";
|
|
3
|
-
import { customNumberFormatOf } from "../numberingParser.js";
|
|
4
3
|
import { serializePartElement } from "./partNamespaces.js";
|
|
5
4
|
import { serializeTextFormatting } from "./textFormattingSerializer.js";
|
|
6
5
|
import { intAttr } from "./xmlUtils.js";
|
|
@@ -9,8 +8,8 @@ import { serializeSequenceChildren } from "@stll/docx-core/schema";
|
|
|
9
8
|
//#region src/docx/serializer/numberingSerializer.ts
|
|
10
9
|
/** Serialize one `w:lvl`. */
|
|
11
10
|
function serializeLevel(level) {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
let numFmtFormat = "";
|
|
12
|
+
if (level.numFmtFormat !== void 0) numFmtFormat = ` w:format="${escapeXmlAttribute(level.numFmtFormat)}"`;
|
|
14
13
|
const pPr = level.pPr === void 0 ? "" : serializeParagraphPropertySet({ formatting: level.pPr }) || "<w:pPr/>";
|
|
15
14
|
const rPr = level.rPr === void 0 ? "" : serializeTextFormatting(level.rPr) || "<w:rPr/>";
|
|
16
15
|
const lvlTextAttrs = [`w:val="${escapeXmlAttribute(level.lvlText)}"`];
|
|
@@ -76,6 +76,7 @@ function serializeTabContent(content) {
|
|
|
76
76
|
* Serialize break content (w:br)
|
|
77
77
|
*/
|
|
78
78
|
function serializeBreakContent(content) {
|
|
79
|
+
if (content.sourceElement === "cr") return "<w:cr/>";
|
|
79
80
|
const attrs = [];
|
|
80
81
|
if (content.breakType === "page") attrs.push("w:type=\"page\"");
|
|
81
82
|
else if (content.breakType === "column") attrs.push("w:type=\"column\"");
|
|
@@ -94,8 +95,10 @@ function serializeSymbolContent(content) {
|
|
|
94
95
|
* Serialize footnote/endnote reference
|
|
95
96
|
*/
|
|
96
97
|
function serializeNoteReference(content) {
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
let customMarkFollows = "";
|
|
99
|
+
if (content.customMarkFollows !== void 0) customMarkFollows = ` w:customMarkFollows="${content.customMarkFollows ? "1" : "0"}"`;
|
|
100
|
+
if (content.type === "footnoteRef") return `<w:footnoteReference w:id="${content.id}"${customMarkFollows}/>`;
|
|
101
|
+
return `<w:endnoteReference w:id="${content.id}"${customMarkFollows}/>`;
|
|
99
102
|
}
|
|
100
103
|
/**
|
|
101
104
|
* Serialize field character (w:fldChar)
|
|
@@ -13,7 +13,11 @@ function serializeFootnoteProperties(props) {
|
|
|
13
13
|
if (!props) return "";
|
|
14
14
|
const parts = [];
|
|
15
15
|
if (props.position) parts.push(`<w:pos w:val="${props.position}"/>`);
|
|
16
|
-
if (props.numFmt)
|
|
16
|
+
if (props.numFmt) {
|
|
17
|
+
let format = "";
|
|
18
|
+
if (props.numFmtFormat !== void 0) format = ` w:format="${escapeXmlAttribute(props.numFmtFormat)}"`;
|
|
19
|
+
parts.push(`<w:numFmt w:val="${props.numFmt}"${format}/>`);
|
|
20
|
+
}
|
|
17
21
|
if (props.numStart !== void 0) parts.push(`<w:numStart w:val="${intAttr(props.numStart)}"/>`);
|
|
18
22
|
if (props.numRestart) parts.push(`<w:numRestart w:val="${props.numRestart}"/>`);
|
|
19
23
|
return parts.length > 0 ? `<w:footnotePr>${parts.join("")}</w:footnotePr>` : "";
|
|
@@ -25,7 +29,11 @@ function serializeEndnoteProperties(props) {
|
|
|
25
29
|
if (!props) return "";
|
|
26
30
|
const parts = [];
|
|
27
31
|
if (props.position) parts.push(`<w:pos w:val="${props.position}"/>`);
|
|
28
|
-
if (props.numFmt)
|
|
32
|
+
if (props.numFmt) {
|
|
33
|
+
let format = "";
|
|
34
|
+
if (props.numFmtFormat !== void 0) format = ` w:format="${escapeXmlAttribute(props.numFmtFormat)}"`;
|
|
35
|
+
parts.push(`<w:numFmt w:val="${props.numFmt}"${format}/>`);
|
|
36
|
+
}
|
|
29
37
|
if (props.numStart !== void 0) parts.push(`<w:numStart w:val="${intAttr(props.numStart)}"/>`);
|
|
30
38
|
if (props.numRestart) parts.push(`<w:numRestart w:val="${props.numRestart}"/>`);
|
|
31
39
|
return parts.length > 0 ? `<w:endnotePr>${parts.join("")}</w:endnotePr>` : "";
|
|
@@ -529,10 +529,11 @@ function serializeCellContent(content, serializeParagraph) {
|
|
|
529
529
|
*/
|
|
530
530
|
function serializeTableCell(cell, serializeParagraph) {
|
|
531
531
|
const parts = [];
|
|
532
|
+
const id = cell.id === void 0 ? "" : ` w:id="${escapeXmlAttribute(cell.id)}"`;
|
|
532
533
|
const tcPrXml = serializeTableCellFormatting(cell.formatting, cell.propertyChanges, cell.structuralChange);
|
|
533
534
|
if (tcPrXml) parts.push(tcPrXml);
|
|
534
535
|
parts.push(serializeCellContent(cell.content, serializeParagraph));
|
|
535
|
-
return `<w:tc>${parts.join("")}</w:tc>`;
|
|
536
|
+
return `<w:tc${id}>${parts.join("")}</w:tc>`;
|
|
536
537
|
}
|
|
537
538
|
/**
|
|
538
539
|
* A container's verbatim sink and its bookmark markers, as one positioned list.
|
package/dist/docx/tableParser.js
CHANGED
|
@@ -12,7 +12,7 @@ import { TABLE_LOOK_FLAGS } from "./tableLook.js";
|
|
|
12
12
|
import { parsePropertyChangeInfo, parseTrackedChangeInfo } from "./trackedChangeInfo.js";
|
|
13
13
|
import { percentageSpelling, transitionalSlotEncoding } from "./transitionalSpelling.js";
|
|
14
14
|
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
15
|
-
import { cloneElement, findChild, findChildren, findWordprocessingChild, getAttribute, getLocalName, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, parseOnOffAttribute, parseTableMeasurementValue, selectAlternateContentBranch } from "./xmlParser.js";
|
|
15
|
+
import { WORDPROCESSINGML_NAMESPACE_URIS, cloneElement, findChild, findChildren, findWordprocessingChild, getAttribute, getAttributeByNamespaceUri, getLocalName, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, parseOnOffAttribute, parseTableMeasurementValue, selectAlternateContentBranch } from "./xmlParser.js";
|
|
16
16
|
//#region src/docx/tableParser.ts
|
|
17
17
|
/**
|
|
18
18
|
* Sanity cap on `w:gridSpan` (and the derived table column count). Word's
|
|
@@ -833,7 +833,7 @@ function parseCellContent(tcElement, styles, theme, numbering, rels, media, opti
|
|
|
833
833
|
if (preserved?.children) captured.push(...preserved.children);
|
|
834
834
|
};
|
|
835
835
|
dispatchCellChildren(tcElement, options);
|
|
836
|
-
if (
|
|
836
|
+
if (modelled.findLast((block) => block.type === "paragraph" || block.type === "table")?.type !== "paragraph") modelled.push({
|
|
837
837
|
type: "paragraph",
|
|
838
838
|
content: []
|
|
839
839
|
});
|
|
@@ -858,6 +858,8 @@ function parseTableCell(tcElement, styles, theme, numbering, rels, media, option
|
|
|
858
858
|
type: "tableCell",
|
|
859
859
|
content: []
|
|
860
860
|
};
|
|
861
|
+
const id = getAttributeByNamespaceUri(tcElement, WORDPROCESSINGML_NAMESPACE_URIS, "id");
|
|
862
|
+
if (id !== null) cell.id = id;
|
|
861
863
|
const tcPrElement = findChild(tcElement, "w", "tcPr");
|
|
862
864
|
const formatting = parseTableCellProperties(tcPrElement);
|
|
863
865
|
if (formatting) cell.formatting = formatting;
|
|
@@ -89,6 +89,7 @@ const TEXT_BOX_TRACKED_CHANGE_TYPES = [
|
|
|
89
89
|
"moveTo"
|
|
90
90
|
];
|
|
91
91
|
const HARD_BREAK_TYPES = ["column", "textWrapping"];
|
|
92
|
+
const HARD_BREAK_SOURCE_ELEMENTS = ["br", "cr"];
|
|
92
93
|
const PAGE_BREAK_CLEAR_VALUES = [
|
|
93
94
|
"none",
|
|
94
95
|
"left",
|
|
@@ -269,8 +270,19 @@ const readHardBreakAttrs = (node) => {
|
|
|
269
270
|
const attrs = attrsRecord(node.attrs);
|
|
270
271
|
const issues = [];
|
|
271
272
|
expectNodeType(node, "hardBreak", issues);
|
|
273
|
+
optionalOneOf(attrs, "sourceElement", "hardBreak.attrs.sourceElement", issues, HARD_BREAK_SOURCE_ELEMENTS);
|
|
272
274
|
optionalOneOf(attrs, "breakType", "hardBreak.attrs.breakType", issues, HARD_BREAK_TYPES);
|
|
273
275
|
optionalOneOf(attrs, "clear", "hardBreak.attrs.clear", issues, PAGE_BREAK_CLEAR_VALUES);
|
|
276
|
+
if (attrs["sourceElement"] === "cr") {
|
|
277
|
+
if (attrs["breakType"] !== void 0 && attrs["breakType"] !== null) issues.push({
|
|
278
|
+
path: "hardBreak.attrs.breakType",
|
|
279
|
+
message: "Not allowed on a carriage-return element."
|
|
280
|
+
});
|
|
281
|
+
if (attrs["clear"] !== void 0 && attrs["clear"] !== null) issues.push({
|
|
282
|
+
path: "hardBreak.attrs.clear",
|
|
283
|
+
message: "Not allowed on a carriage-return element."
|
|
284
|
+
});
|
|
285
|
+
}
|
|
274
286
|
return attrsResult(attrs, issues);
|
|
275
287
|
};
|
|
276
288
|
const expectHardBreakAttrs = (node) => expectCachedNodeAttrs(node, hardBreakAttrsCache, readHardBreakAttrs, "hard break attrs");
|
|
@@ -386,6 +398,7 @@ const readTableCellAttrs = (node) => {
|
|
|
386
398
|
const attrs = attrsRecord(node.attrs);
|
|
387
399
|
const issues = [];
|
|
388
400
|
expectNodeTypeOneOf(node, ["tableCell", "tableHeader"], issues);
|
|
401
|
+
optionalString(attrs, "_docxCellId", "tableCell.attrs._docxCellId", issues);
|
|
389
402
|
requiredNumber(attrs, "colspan", "tableCell.attrs.colspan", issues);
|
|
390
403
|
requiredNumber(attrs, "rowspan", "tableCell.attrs.rowspan", issues);
|
|
391
404
|
optionalOneOf(attrs, "_omittedGridSlot", "tableCell.attrs._omittedGridSlot", issues, OMITTED_GRID_SLOT_VALUES);
|
|
@@ -767,6 +780,7 @@ const readFootnoteRefMarkAttrs = (mark) => {
|
|
|
767
780
|
requiredStringOrNumber(attrs, "id", "footnoteRef.attrs.id", issues);
|
|
768
781
|
optionalOneOf(attrs, "noteType", "footnoteRef.attrs.noteType", issues, NOTE_TYPES);
|
|
769
782
|
optionalOneOf(attrs, "vertAlign", "footnoteRef.attrs.vertAlign", issues, NOTE_REF_VERT_ALIGNS);
|
|
783
|
+
optionalBoolean(attrs, "customMarkFollows", "footnoteRef.attrs.customMarkFollows", issues);
|
|
770
784
|
return attrsResult(attrs, issues);
|
|
771
785
|
};
|
|
772
786
|
const expectFootnoteRefMarkAttrs = (mark) => expectCachedMarkAttrs(mark, footnoteRefAttrsCache, readFootnoteRefMarkAttrs, "footnote reference attrs");
|
|
@@ -1018,6 +1032,9 @@ const readHyperlinkMarkAttrs = (mark) => {
|
|
|
1018
1032
|
requiredString(attrs, "href", "hyperlink.attrs.href", issues);
|
|
1019
1033
|
optionalString(attrs, "tooltip", "hyperlink.attrs.tooltip", issues);
|
|
1020
1034
|
optionalString(attrs, "rId", "hyperlink.attrs.rId", issues);
|
|
1035
|
+
optionalString(attrs, "target", "hyperlink.attrs.target", issues);
|
|
1036
|
+
optionalBoolean(attrs, "history", "hyperlink.attrs.history", issues);
|
|
1037
|
+
optionalString(attrs, "docLocation", "hyperlink.attrs.docLocation", issues);
|
|
1021
1038
|
validateNonNegativeInteger(attrs["_docxHyperlinkIndex"], "hyperlink.attrs._docxHyperlinkIndex", issues);
|
|
1022
1039
|
return attrsResult(attrs, issues);
|
|
1023
1040
|
};
|
|
@@ -2612,6 +2629,9 @@ const optionalEmptyHyperlinkArray = (value, issues) => {
|
|
|
2612
2629
|
optionalString(item, "anchor", `${itemPath}.anchor`, issues);
|
|
2613
2630
|
optionalString(item, "tooltip", `${itemPath}.tooltip`, issues);
|
|
2614
2631
|
optionalString(item, "rId", `${itemPath}.rId`, issues);
|
|
2632
|
+
optionalString(item, "target", `${itemPath}.target`, issues);
|
|
2633
|
+
optionalBoolean(item, "history", `${itemPath}.history`, issues);
|
|
2634
|
+
optionalString(item, "docLocation", `${itemPath}.docLocation`, issues);
|
|
2615
2635
|
}
|
|
2616
2636
|
};
|
|
2617
2637
|
//#endregion
|
|
@@ -1643,6 +1643,9 @@ function createEmptyHyperlink(attrs) {
|
|
|
1643
1643
|
if (attrs.anchor !== void 0) hyperlink.anchor = attrs.anchor;
|
|
1644
1644
|
if (attrs.tooltip !== void 0) hyperlink.tooltip = attrs.tooltip;
|
|
1645
1645
|
if (attrs.rId !== void 0) hyperlink.rId = attrs.rId;
|
|
1646
|
+
if (attrs.target !== void 0) hyperlink.target = attrs.target;
|
|
1647
|
+
if (attrs.history !== void 0) hyperlink.history = attrs.history;
|
|
1648
|
+
if (attrs.docLocation !== void 0) hyperlink.docLocation = attrs.docLocation;
|
|
1646
1649
|
return hyperlink;
|
|
1647
1650
|
}
|
|
1648
1651
|
function getCommentMarkIds(marks) {
|
|
@@ -1684,6 +1687,9 @@ function getLinkKey(mark) {
|
|
|
1684
1687
|
attrs.href,
|
|
1685
1688
|
attrs.rId ?? "",
|
|
1686
1689
|
attrs.tooltip ?? "",
|
|
1690
|
+
attrs.target ?? "",
|
|
1691
|
+
attrs.history === void 0 ? "" : String(attrs.history),
|
|
1692
|
+
attrs.docLocation ?? "",
|
|
1687
1693
|
attrs._docxHyperlinkIndex ?? ""
|
|
1688
1694
|
].join("\0");
|
|
1689
1695
|
}
|
|
@@ -1701,21 +1707,24 @@ function getMarksKey(marks) {
|
|
|
1701
1707
|
function createHyperlink(linkMark) {
|
|
1702
1708
|
const attrs = expectHyperlinkMarkAttrs(linkMark);
|
|
1703
1709
|
const href = attrs.href;
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1710
|
+
const metadata = {
|
|
1711
|
+
...attrs.tooltip === void 0 ? {} : { tooltip: attrs.tooltip },
|
|
1712
|
+
...attrs.target === void 0 ? {} : { target: attrs.target },
|
|
1713
|
+
...attrs.history === void 0 ? {} : { history: attrs.history },
|
|
1714
|
+
...attrs.docLocation === void 0 ? {} : { docLocation: attrs.docLocation }
|
|
1715
|
+
};
|
|
1716
|
+
if (href.startsWith("#")) return {
|
|
1717
|
+
type: "hyperlink",
|
|
1718
|
+
anchor: href.slice(1),
|
|
1719
|
+
...metadata,
|
|
1720
|
+
children: []
|
|
1721
|
+
};
|
|
1713
1722
|
const hyperlink = {
|
|
1714
1723
|
type: "hyperlink",
|
|
1715
1724
|
href,
|
|
1725
|
+
...metadata,
|
|
1716
1726
|
children: []
|
|
1717
1727
|
};
|
|
1718
|
-
if (attrs.tooltip) hyperlink.tooltip = attrs.tooltip;
|
|
1719
1728
|
if (attrs.rId) hyperlink.rId = attrs.rId;
|
|
1720
1729
|
return hyperlink;
|
|
1721
1730
|
}
|
|
@@ -1831,12 +1840,14 @@ function getNoteReferenceVertAlign(noteAttrs, marks) {
|
|
|
1831
1840
|
}
|
|
1832
1841
|
function createNoteReferenceRun(noteRefMark, marks, formattingContext) {
|
|
1833
1842
|
const noteAttrs = expectFootnoteRefMarkAttrs(noteRefMark);
|
|
1843
|
+
const noteRef = {
|
|
1844
|
+
type: noteAttrs.noteType === "endnote" ? "endnoteRef" : "footnoteRef",
|
|
1845
|
+
id: typeof noteAttrs.id === "string" ? Number.parseInt(noteAttrs.id, 10) || 0 : noteAttrs.id
|
|
1846
|
+
};
|
|
1847
|
+
if (noteAttrs.customMarkFollows !== void 0) noteRef.customMarkFollows = noteAttrs.customMarkFollows;
|
|
1834
1848
|
const run = {
|
|
1835
1849
|
type: "run",
|
|
1836
|
-
content: [
|
|
1837
|
-
type: noteAttrs.noteType === "endnote" ? "endnoteRef" : "footnoteRef",
|
|
1838
|
-
id: typeof noteAttrs.id === "string" ? Number.parseInt(noteAttrs.id, 10) || 0 : noteAttrs.id
|
|
1839
|
-
}]
|
|
1850
|
+
content: [noteRef]
|
|
1840
1851
|
};
|
|
1841
1852
|
const formatting = getAtomRunFormattingFromMarks(marks, formattingContext);
|
|
1842
1853
|
const vertAlign = getNoteReferenceVertAlign(noteAttrs, marks);
|
|
@@ -2045,8 +2056,12 @@ function createPageBreakCarrierRun({ baseParagraphFormatting, clear, inheritedFo
|
|
|
2045
2056
|
function createBreakRun(attrs, marks, formattingContext) {
|
|
2046
2057
|
const run = {
|
|
2047
2058
|
type: "run",
|
|
2048
|
-
content: [{
|
|
2059
|
+
content: [attrs.sourceElement === "cr" ? {
|
|
2060
|
+
type: "break",
|
|
2061
|
+
sourceElement: "cr"
|
|
2062
|
+
} : {
|
|
2049
2063
|
type: "break",
|
|
2064
|
+
...attrs.sourceElement !== void 0 ? { sourceElement: attrs.sourceElement } : {},
|
|
2050
2065
|
...attrs.breakType !== void 0 ? { breakType: attrs.breakType } : {},
|
|
2051
2066
|
...attrs.clear !== void 0 ? { clear: attrs.clear } : {}
|
|
2052
2067
|
}]
|
|
@@ -3305,6 +3320,7 @@ function convertPMTableCell(node, documentCounts, styleResolver = null) {
|
|
|
3305
3320
|
type: "tableCell",
|
|
3306
3321
|
content
|
|
3307
3322
|
};
|
|
3323
|
+
if (attrs._docxCellId !== void 0) cell.id = attrs._docxCellId;
|
|
3308
3324
|
const cellFormatting = tableCellAttrsToFormatting(attrs);
|
|
3309
3325
|
if (cellFormatting) cell.formatting = cellFormatting;
|
|
3310
3326
|
if (Array.isArray(attrs.tcPrChange) && attrs.tcPrChange.length > 0) cell.propertyChanges = [...attrs.tcPrChange];
|
|
@@ -492,7 +492,10 @@ function convertParagraph(paragraph, styleResolver, context, extraRunFormatting,
|
|
|
492
492
|
...content.href !== void 0 ? { href: content.href } : {},
|
|
493
493
|
...content.anchor !== void 0 ? { anchor: content.anchor } : {},
|
|
494
494
|
...content.tooltip !== void 0 ? { tooltip: content.tooltip } : {},
|
|
495
|
-
...content.rId !== void 0 ? { rId: content.rId } : {}
|
|
495
|
+
...content.rId !== void 0 ? { rId: content.rId } : {},
|
|
496
|
+
...content.target !== void 0 ? { target: content.target } : {},
|
|
497
|
+
...content.history !== void 0 ? { history: content.history } : {},
|
|
498
|
+
...content.docLocation !== void 0 ? { docLocation: content.docLocation } : {}
|
|
496
499
|
});
|
|
497
500
|
break;
|
|
498
501
|
}
|
|
@@ -1325,6 +1328,7 @@ function convertTableCell({ cell, styleResolver, context, isHeader, gridWidthPer
|
|
|
1325
1328
|
colspan: formatting?.gridSpan ?? 1,
|
|
1326
1329
|
rowspan
|
|
1327
1330
|
};
|
|
1331
|
+
if (cell.id !== void 0) attrs._docxCellId = cell.id;
|
|
1328
1332
|
if (effectiveFormatting.width.type === "value") {
|
|
1329
1333
|
attrs.width = effectiveFormatting.width.value;
|
|
1330
1334
|
if (effectiveFormatting.width.widthType) attrs.widthType = effectiveFormatting.width.widthType;
|
|
@@ -2064,6 +2068,7 @@ function convertRunContent(content, marks, formatting, textBoxAnchors, authoredF
|
|
|
2064
2068
|
case "break":
|
|
2065
2069
|
if (content.breakType === "textWrapping" || !content.breakType) {
|
|
2066
2070
|
const attrs = {
|
|
2071
|
+
...content.sourceElement !== void 0 ? { sourceElement: content.sourceElement } : {},
|
|
2067
2072
|
...content.breakType !== void 0 ? { breakType: content.breakType } : {},
|
|
2068
2073
|
...content.clear !== void 0 ? { clear: content.clear } : {}
|
|
2069
2074
|
};
|
|
@@ -2095,7 +2100,8 @@ function convertRunContent(content, marks, formatting, textBoxAnchors, authoredF
|
|
|
2095
2100
|
const footnoteMark = schema.mark("footnoteRef", {
|
|
2096
2101
|
id: content.id.toString(),
|
|
2097
2102
|
noteType: "footnote",
|
|
2098
|
-
vertAlign: noteReferenceVertAlign(formatting?.vertAlign)
|
|
2103
|
+
vertAlign: noteReferenceVertAlign(formatting?.vertAlign),
|
|
2104
|
+
customMarkFollows: content.customMarkFollows
|
|
2099
2105
|
});
|
|
2100
2106
|
return [schema.text(content.id.toString(), [...marks, footnoteMark])];
|
|
2101
2107
|
}
|
|
@@ -2103,7 +2109,8 @@ function convertRunContent(content, marks, formatting, textBoxAnchors, authoredF
|
|
|
2103
2109
|
const endnoteMark = schema.mark("footnoteRef", {
|
|
2104
2110
|
id: content.id.toString(),
|
|
2105
2111
|
noteType: "endnote",
|
|
2106
|
-
vertAlign: noteReferenceVertAlign(formatting?.vertAlign)
|
|
2112
|
+
vertAlign: noteReferenceVertAlign(formatting?.vertAlign),
|
|
2113
|
+
customMarkFollows: content.customMarkFollows
|
|
2107
2114
|
});
|
|
2108
2115
|
return [schema.text(content.id.toString(), [...marks, endnoteMark])];
|
|
2109
2116
|
}
|
|
@@ -2284,6 +2291,9 @@ function convertHyperlink(hyperlink, { getInheritedRunFormatting, styleResolver,
|
|
|
2284
2291
|
href,
|
|
2285
2292
|
tooltip: hyperlink.tooltip,
|
|
2286
2293
|
rId: hyperlink.rId,
|
|
2294
|
+
target: hyperlink.target,
|
|
2295
|
+
history: hyperlink.history,
|
|
2296
|
+
docLocation: hyperlink.docLocation,
|
|
2287
2297
|
_docxHyperlinkIndex: hyperlinkIndex
|
|
2288
2298
|
});
|
|
2289
2299
|
for (const { content: child, stack } of withInlineWrapperStacks(hyperlink.children)) {
|
|
@@ -82,6 +82,9 @@ const HyperlinkExtension = createMarkExtension({
|
|
|
82
82
|
href: {},
|
|
83
83
|
tooltip: { default: null },
|
|
84
84
|
rId: { default: null },
|
|
85
|
+
target: { default: null },
|
|
86
|
+
history: { default: null },
|
|
87
|
+
docLocation: { default: null },
|
|
85
88
|
_docxHyperlinkIndex: { default: null }
|
|
86
89
|
},
|
|
87
90
|
inclusive: false,
|
|
@@ -89,14 +92,15 @@ const HyperlinkExtension = createMarkExtension({
|
|
|
89
92
|
tag: "a[href]",
|
|
90
93
|
getAttrs: (dom) => ({
|
|
91
94
|
href: sanitizeStoredHref(dom.getAttribute("href") ?? void 0),
|
|
92
|
-
tooltip: dom.getAttribute("title") ?? void 0
|
|
95
|
+
tooltip: dom.getAttribute("title") ?? void 0,
|
|
96
|
+
target: dom.getAttribute("target") ?? void 0
|
|
93
97
|
})
|
|
94
98
|
}],
|
|
95
99
|
toDOM(mark) {
|
|
96
|
-
const { href, tooltip } = expectHyperlinkMarkAttrs(mark);
|
|
100
|
+
const { href, target, tooltip } = expectHyperlinkMarkAttrs(mark);
|
|
97
101
|
const domAttrs = {
|
|
98
102
|
href: sanitizeStoredHref(href),
|
|
99
|
-
...anchorTargetAttrs(
|
|
103
|
+
...anchorTargetAttrs(target)
|
|
100
104
|
};
|
|
101
105
|
if (tooltip) domAttrs["title"] = tooltip;
|
|
102
106
|
return [
|
|
@@ -12,6 +12,7 @@ const HardBreakExtension = createNodeExtension({
|
|
|
12
12
|
inline: true,
|
|
13
13
|
group: "inline",
|
|
14
14
|
attrs: {
|
|
15
|
+
sourceElement: { default: null },
|
|
15
16
|
breakType: { default: null },
|
|
16
17
|
clear: { default: null }
|
|
17
18
|
},
|
|
@@ -22,15 +23,18 @@ const HardBreakExtension = createNodeExtension({
|
|
|
22
23
|
if (!(node instanceof HTMLElement)) return null;
|
|
23
24
|
const breakType = node.dataset["docxBreakType"];
|
|
24
25
|
const clear = node.dataset["docxBreakClear"];
|
|
26
|
+
const sourceElement = node.dataset["docxSourceElement"];
|
|
25
27
|
const attrs = {};
|
|
28
|
+
if (sourceElement === "br" || sourceElement === "cr") attrs["sourceElement"] = sourceElement;
|
|
26
29
|
if (breakType === "column" || breakType === "textWrapping") attrs["breakType"] = breakType;
|
|
27
30
|
if (clear === "none" || clear === "left" || clear === "right" || clear === "all") attrs["clear"] = clear;
|
|
28
31
|
return Object.keys(attrs).length > 0 ? attrs : null;
|
|
29
32
|
}
|
|
30
33
|
}],
|
|
31
34
|
toDOM(node) {
|
|
32
|
-
const { breakType, clear } = expectHardBreakAttrs(node);
|
|
35
|
+
const { sourceElement, breakType, clear } = expectHardBreakAttrs(node);
|
|
33
36
|
const attrs = {};
|
|
37
|
+
if (sourceElement !== void 0) attrs["data-docx-source-element"] = sourceElement;
|
|
34
38
|
if (breakType !== void 0) attrs["data-docx-break-type"] = breakType;
|
|
35
39
|
if (clear !== void 0) attrs["data-docx-break-clear"] = clear;
|
|
36
40
|
return Object.keys(attrs).length > 0 ? ["br", attrs] : ["br"];
|
|
@@ -39,6 +39,43 @@ const preserveOmittedGridSlots = (command) => (state, dispatch, view) => {
|
|
|
39
39
|
if (table && hasOmittedGridSlots(table)) return false;
|
|
40
40
|
return command(state, dispatch, view);
|
|
41
41
|
};
|
|
42
|
+
const preserveSourceCellIdentity = (command) => (state, dispatch, view) => {
|
|
43
|
+
if (!command(state, void 0, view)) return false;
|
|
44
|
+
if (!dispatch) return true;
|
|
45
|
+
const rect = selectedRect(state);
|
|
46
|
+
const sourceRelativePosition = rect.map.map.at(rect.top * rect.map.width + rect.left);
|
|
47
|
+
if (sourceRelativePosition === void 0) return panic("The selected table cell is absent from its table map");
|
|
48
|
+
const sourceCell = rect.table.nodeAt(sourceRelativePosition);
|
|
49
|
+
if (!sourceCell) return panic("The selected table cell is absent from its table map");
|
|
50
|
+
const sourceId = expectTableCellAttrs(sourceCell)._docxCellId;
|
|
51
|
+
return command(state, (tr) => {
|
|
52
|
+
if (sourceId === void 0 || sourceId === null) {
|
|
53
|
+
dispatch(tr);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const mappedTablePosition = tr.mapping.map(rect.tableStart - 1, -1);
|
|
57
|
+
const table = tr.doc.nodeAt(mappedTablePosition);
|
|
58
|
+
if (!table) return panic("The split-cell transaction lost its table");
|
|
59
|
+
const tableStart = mappedTablePosition + 1;
|
|
60
|
+
const map = TableMap.get(table);
|
|
61
|
+
const survivingRelativePosition = map.map.at(rect.top * map.width + rect.left);
|
|
62
|
+
if (survivingRelativePosition === void 0) return panic("The surviving table cell is absent from its table map");
|
|
63
|
+
const cleared = /* @__PURE__ */ new Set();
|
|
64
|
+
for (let row = rect.top; row < rect.bottom; row++) for (let column = rect.left; column < rect.right; column++) {
|
|
65
|
+
const relativePosition = map.map.at(row * map.width + column);
|
|
66
|
+
if (relativePosition === void 0) return panic("A split table cell is absent from its table map");
|
|
67
|
+
if (relativePosition === survivingRelativePosition || cleared.has(relativePosition)) continue;
|
|
68
|
+
cleared.add(relativePosition);
|
|
69
|
+
const cell = table.nodeAt(relativePosition);
|
|
70
|
+
if (!cell) return panic("A split table cell is absent from its table map");
|
|
71
|
+
tr.setNodeMarkup(tableStart + relativePosition, null, {
|
|
72
|
+
...cell.attrs,
|
|
73
|
+
_docxCellId: null
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
dispatch(tr);
|
|
77
|
+
}, view);
|
|
78
|
+
};
|
|
42
79
|
const isOneOf = (value, values) => value !== null && value !== void 0 && values.some((allowed) => allowed === value);
|
|
43
80
|
/** Map CSS border-style to OOXML border style. */
|
|
44
81
|
function cssBorderStyleToOoxml(cssStyle) {
|
|
@@ -334,6 +371,7 @@ const tableCellSpec = {
|
|
|
334
371
|
tableRole: "cell",
|
|
335
372
|
isolating: true,
|
|
336
373
|
attrs: {
|
|
374
|
+
_docxCellId: { default: null },
|
|
337
375
|
colspan: { default: 1 },
|
|
338
376
|
rowspan: { default: 1 },
|
|
339
377
|
_omittedGridSlot: { default: null },
|
|
@@ -402,6 +440,7 @@ const tableHeaderSpec = {
|
|
|
402
440
|
tableRole: "header_cell",
|
|
403
441
|
isolating: true,
|
|
404
442
|
attrs: {
|
|
443
|
+
_docxCellId: { default: null },
|
|
405
444
|
colspan: { default: 1 },
|
|
406
445
|
rowspan: { default: 1 },
|
|
407
446
|
_omittedGridSlot: { default: null },
|
|
@@ -2004,7 +2043,7 @@ const TablePluginExtension = createExtension({
|
|
|
2004
2043
|
selectRow: () => selectRow,
|
|
2005
2044
|
selectColumn: () => selectColumn,
|
|
2006
2045
|
mergeCells: () => preserveOmittedGridSlots(mergeCells),
|
|
2007
|
-
splitCell: () => preserveOmittedGridSlots(splitCell),
|
|
2046
|
+
splitCell: () => preserveOmittedGridSlots(preserveSourceCellIdentity(splitCell)),
|
|
2008
2047
|
setCellBorder: (side, spec, clearOthers) => setCellBorder(side, spec, clearOthers),
|
|
2009
2048
|
setTableBorders: (preset, borderSpec) => setTableBorders(preset, borderSpec),
|
|
2010
2049
|
setTableBorderPreset: (preset) => setTableBorderPreset(preset),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { decodeOoxmlSymbolCharacter } from "../utils/ooxmlSymbol.js";
|
|
2
2
|
import { expectFieldAttrs, expectPreservedXmlAttrs, expectSymbolAttrs } from "./attrs/index.js";
|
|
3
|
+
import { PRESERVED_XML_LEVELS } from "./schema/nodes.js";
|
|
3
4
|
import { panic } from "better-result";
|
|
4
5
|
import { Mark } from "prosemirror-model";
|
|
5
6
|
//#region src/prosemirror/runFormattingInlineCarriers.ts
|
|
@@ -31,6 +32,22 @@ const RUN_FORMATTING_INLINE_ATOM_DISPOSITIONS = Object.freeze({
|
|
|
31
32
|
textBoxAnchor: "not-a-run"
|
|
32
33
|
});
|
|
33
34
|
const dispositionsByName = new Map(Object.entries(RUN_FORMATTING_INLINE_ATOM_DISPOSITIONS));
|
|
35
|
+
/**
|
|
36
|
+
* One preserved capture is a `w:r` child and the other is a `w:p` child, and
|
|
37
|
+
* the map above can only name the node type.
|
|
38
|
+
*
|
|
39
|
+
* Only the run-level capture serializes inside a `w:r`, so only it has a
|
|
40
|
+
* `w:rPr` to carry; the save path already branches on the same level. A
|
|
41
|
+
* paragraph-level capture is the markup Word writes BETWEEN runs — `w:proofErr`
|
|
42
|
+
* above all — and treating it as a carrier makes every consumer of this map
|
|
43
|
+
* wrong in the same way: formatting marks put on it are dropped on save, and a
|
|
44
|
+
* comparison is asked to line up the base's proofing annotations with the
|
|
45
|
+
* revised document's own, which no redline can do.
|
|
46
|
+
*/
|
|
47
|
+
const PRESERVED_XML_DISPOSITIONS = {
|
|
48
|
+
[PRESERVED_XML_LEVELS.run]: "preserved-xml-run",
|
|
49
|
+
[PRESERVED_XML_LEVELS.inline]: "not-a-run"
|
|
50
|
+
};
|
|
34
51
|
const CONTROL_CHARACTER_BY_DISPOSITION = Object.freeze({
|
|
35
52
|
"break-run": "\n",
|
|
36
53
|
"field-run": null,
|
|
@@ -73,7 +90,7 @@ const runFormattingInlineAtomDisposition = (node) => {
|
|
|
73
90
|
if (!node.isInline || !node.isAtom) return null;
|
|
74
91
|
const disposition = dispositionsByName.get(node.type.name);
|
|
75
92
|
if (!disposition) return panic(`Inline atom ${JSON.stringify(node.type.name)} has no run-formatting disposition`);
|
|
76
|
-
return disposition;
|
|
93
|
+
return disposition === "preserved-xml-run" ? PRESERVED_XML_DISPOSITIONS[expectPreservedXmlAttrs(node).level] : disposition;
|
|
77
94
|
};
|
|
78
95
|
/** Apply a mark set without changing a carrier's text or serialized ownership. */
|
|
79
96
|
const applyMarksToRunFormattingRepresentation = ({ tr, representation, marks }) => {
|
|
@@ -81,6 +81,7 @@ type FootnoteRefAttrs = {
|
|
|
81
81
|
id: string | number;
|
|
82
82
|
noteType?: "footnote" | "endnote";
|
|
83
83
|
vertAlign?: "baseline" | "superscript";
|
|
84
|
+
customMarkFollows?: document_d_exports.NoteReferenceContent["customMarkFollows"];
|
|
84
85
|
};
|
|
85
86
|
type CommentAttrs = {
|
|
86
87
|
commentId: number;
|
|
@@ -271,6 +272,9 @@ type HyperlinkAttrs = {
|
|
|
271
272
|
href: string;
|
|
272
273
|
tooltip?: string;
|
|
273
274
|
rId?: string;
|
|
275
|
+
target?: document_d_exports.Hyperlink["target"];
|
|
276
|
+
history?: document_d_exports.Hyperlink["history"];
|
|
277
|
+
docLocation?: document_d_exports.Hyperlink["docLocation"];
|
|
274
278
|
_docxHyperlinkIndex?: number;
|
|
275
279
|
};
|
|
276
280
|
//#endregion
|
|
@@ -7,6 +7,12 @@ import { ParagraphNumberingAttr } from "../numberingAttr.js";
|
|
|
7
7
|
import { ParagraphDirection } from "../paragraphDirection.js";
|
|
8
8
|
//#region src/prosemirror/schema/nodes.d.ts
|
|
9
9
|
type HardBreakAttrs = {
|
|
10
|
+
sourceElement: "cr";
|
|
11
|
+
breakType?: never;
|
|
12
|
+
clear?: never;
|
|
13
|
+
} | {
|
|
14
|
+
/** Absent and `br` both serialize as the standard break element. */
|
|
15
|
+
sourceElement?: "br";
|
|
10
16
|
/** Absent means the source `w:br` omitted `w:type`. */
|
|
11
17
|
breakType?: "column" | "textWrapping";
|
|
12
18
|
clear?: "none" | "left" | "right" | "all";
|
|
@@ -216,6 +222,9 @@ type ParagraphAttrs = {
|
|
|
216
222
|
anchor?: document_d_exports.Hyperlink["anchor"];
|
|
217
223
|
tooltip?: document_d_exports.Hyperlink["tooltip"];
|
|
218
224
|
rId?: document_d_exports.Hyperlink["rId"];
|
|
225
|
+
target?: document_d_exports.Hyperlink["target"];
|
|
226
|
+
history?: document_d_exports.Hyperlink["history"];
|
|
227
|
+
docLocation?: document_d_exports.Hyperlink["docLocation"];
|
|
219
228
|
}[];
|
|
220
229
|
/**
|
|
221
230
|
* Run-in heading flag: this paragraph's mark carries
|
|
@@ -1056,6 +1065,8 @@ type SuggestedStructuralMarker = {
|
|
|
1056
1065
|
* Table cell attributes
|
|
1057
1066
|
*/
|
|
1058
1067
|
type TableCellAttrs = {
|
|
1068
|
+
/** Authored OOXML cell identifier. */
|
|
1069
|
+
_docxCellId?: document_d_exports.TableCell["id"];
|
|
1059
1070
|
/** Column span */
|
|
1060
1071
|
colspan: number;
|
|
1061
1072
|
/** Row span */
|
|
@@ -5,7 +5,7 @@ type TablePropertiesCommand = {
|
|
|
5
5
|
widthType?: document_d_exports.TableWidthType | null;
|
|
6
6
|
justification?: document_d_exports.TableAlignment | null;
|
|
7
7
|
};
|
|
8
|
-
declare const TABLE_PROPERTY_JUSTIFICATIONS: readonly
|
|
8
|
+
declare const TABLE_PROPERTY_JUSTIFICATIONS: readonly document_d_exports.TableAlignment[];
|
|
9
9
|
declare const toTablePropertyJustification: (value: string | null | undefined) => NonNullable<TablePropertiesCommand["justification"]>;
|
|
10
10
|
type TableAction = "addRowAbove" | "addRowBelow" | "addColumnLeft" | "addColumnRight" | "deleteRow" | "deleteColumn" | "mergeCells" | "splitCell" | "deleteTable" | "selectTable" | "selectRow" | "selectColumn" | "borderAll" | "borderOutside" | "borderInside" | "borderNone" | "borderTop" | "borderBottom" | "borderLeft" | "borderRight" | {
|
|
11
11
|
type: "cellFillColor";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/folio-core",
|
|
3
|
-
"version": "0.47.
|
|
3
|
+
"version": "0.47.2",
|
|
4
4
|
"description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"document-model",
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"perf": "bun scripts/profile-editor.ts"
|
|
129
129
|
},
|
|
130
130
|
"dependencies": {
|
|
131
|
-
"@stll/docx-core": "^0.25.
|
|
131
|
+
"@stll/docx-core": "^0.25.1",
|
|
132
132
|
"@stll/docx-utils": "^0.1.0",
|
|
133
133
|
"@stll/template-conditions": ">=0.4.0 <1.0.0",
|
|
134
134
|
"better-result": "3.0.1",
|