@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
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { stripBlockIdentityAttrs } from "./block-identity.js";
|
|
2
|
+
import { Fragment } from "prosemirror-model";
|
|
3
|
+
//#region src/ai-edits/table-template.ts
|
|
4
|
+
/**
|
|
5
|
+
* Building a table from another document's table rather than from a grid of
|
|
6
|
+
* strings.
|
|
7
|
+
*
|
|
8
|
+
* `insertTable` and `insertTableRow` describe their content as cell texts,
|
|
9
|
+
* which is the right shape for a caller that writes a table from nothing. A
|
|
10
|
+
* comparison is not that caller: the table it adds already exists in the
|
|
11
|
+
* document it is comparing against, complete with its `w:tblPr`, its
|
|
12
|
+
* `w:tblGrid` widths, per-row `w:trPr`, per-cell `w:tcPr` — spans, vertical
|
|
13
|
+
* merges, shading, borders, margins, alignment — and cell paragraphs carrying
|
|
14
|
+
* their own properties. Rebuilding that from text loses every one of them, and
|
|
15
|
+
* a consumer accepting the redline then gets a table that is not the one it
|
|
16
|
+
* was compared to.
|
|
17
|
+
*
|
|
18
|
+
* So the operation may be handed the node it should place. The copy is
|
|
19
|
+
* structural and keeps everything by default; what it cannot bring is what
|
|
20
|
+
* only the package it came from can resolve — a relationship id (a drawing, a
|
|
21
|
+
* hyperlink), a note or comment id, a bookmark, a paragraph identity, or a
|
|
22
|
+
* tracked change belonging to the other document's history. Everything that
|
|
23
|
+
* describes the table itself travels.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Nodes that name something the other package owns: a drawing's relationship,
|
|
27
|
+
* an anchored frame's, a bookmark's id. They resolve to nothing once the table
|
|
28
|
+
* has crossed, so they are dropped rather than copied into a dangling
|
|
29
|
+
* reference.
|
|
30
|
+
*/
|
|
31
|
+
const PACKAGE_BOUND_NODE_NAMES = /* @__PURE__ */ new Set([
|
|
32
|
+
"image",
|
|
33
|
+
"textBox",
|
|
34
|
+
"textBoxAnchor",
|
|
35
|
+
"shape",
|
|
36
|
+
"bookmarkBoundary"
|
|
37
|
+
]);
|
|
38
|
+
/**
|
|
39
|
+
* Marks that name something the other package owns. Run properties are the
|
|
40
|
+
* point of copying the runs at all, so the denial is narrow: a relationship, a
|
|
41
|
+
* note, a comment, and the other document's revisions.
|
|
42
|
+
*/
|
|
43
|
+
const PACKAGE_BOUND_MARK_NAMES = /* @__PURE__ */ new Set([
|
|
44
|
+
"hyperlink",
|
|
45
|
+
"footnoteRef",
|
|
46
|
+
"comment",
|
|
47
|
+
"insertion",
|
|
48
|
+
"deletion",
|
|
49
|
+
"runPropertyChange"
|
|
50
|
+
]);
|
|
51
|
+
/**
|
|
52
|
+
* Paragraph attrs cleared on a copied paragraph: its identity, the bookmarks
|
|
53
|
+
* and empty hyperlinks that name package-scoped ids, the section it ended, and
|
|
54
|
+
* the tracked-change history of the document it came from. Its formatting —
|
|
55
|
+
* style, alignment, spacing, indentation, borders, shading, tabs — is what the
|
|
56
|
+
* copy exists to carry, and stays.
|
|
57
|
+
*/
|
|
58
|
+
const CLEARED_PARAGRAPH_ATTRS = [
|
|
59
|
+
"bookmarks",
|
|
60
|
+
"_emptyHyperlinks",
|
|
61
|
+
"_propertyChanges",
|
|
62
|
+
"pPrMark",
|
|
63
|
+
"_suggestedInsert",
|
|
64
|
+
"_sectionProperties",
|
|
65
|
+
"sectionBreakType",
|
|
66
|
+
"renderedPageBreakBefore"
|
|
67
|
+
];
|
|
68
|
+
/** Table, row and cell attrs that record the other document's revisions. */
|
|
69
|
+
const CLEARED_TABLE_ATTRS = ["tblPrChange", "_suggestedInsert"];
|
|
70
|
+
const CLEARED_ROW_ATTRS = [
|
|
71
|
+
"trIns",
|
|
72
|
+
"trDel",
|
|
73
|
+
"trPrChange"
|
|
74
|
+
];
|
|
75
|
+
const CLEARED_CELL_ATTRS = ["cellMarker", "tcPrChange"];
|
|
76
|
+
const withoutAttrs = (attrs, cleared) => {
|
|
77
|
+
const next = { ...attrs };
|
|
78
|
+
for (const key of cleared) next[key] = null;
|
|
79
|
+
return next;
|
|
80
|
+
};
|
|
81
|
+
const insertionMarkOf = (schema, revision) => {
|
|
82
|
+
const markType = schema.marks["insertion"];
|
|
83
|
+
if (!revision || !markType) return null;
|
|
84
|
+
const { revisionId, author, date, initials, provenance, suggestionId } = revision;
|
|
85
|
+
return markType.create({
|
|
86
|
+
revisionId,
|
|
87
|
+
author,
|
|
88
|
+
date,
|
|
89
|
+
...initials !== void 0 && { initials },
|
|
90
|
+
...provenance !== void 0 && { provenance },
|
|
91
|
+
...suggestionId !== void 0 && { suggestionId }
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
const copiedAttrs = (node, context) => {
|
|
95
|
+
switch (node.type.spec["tableRole"]) {
|
|
96
|
+
case "table": return withoutAttrs(node.attrs, CLEARED_TABLE_ATTRS);
|
|
97
|
+
case "row": {
|
|
98
|
+
const attrs = withoutAttrs(node.attrs, CLEARED_ROW_ATTRS);
|
|
99
|
+
return context.revision ? {
|
|
100
|
+
...attrs,
|
|
101
|
+
trIns: context.revision
|
|
102
|
+
} : attrs;
|
|
103
|
+
}
|
|
104
|
+
case "cell":
|
|
105
|
+
case "header_cell": {
|
|
106
|
+
const attrs = withoutAttrs(node.attrs, CLEARED_CELL_ATTRS);
|
|
107
|
+
return context.clampRowSpan ? {
|
|
108
|
+
...attrs,
|
|
109
|
+
rowspan: 1
|
|
110
|
+
} : attrs;
|
|
111
|
+
}
|
|
112
|
+
default: return node.isTextblock ? withoutAttrs(stripBlockIdentityAttrs(node.attrs), CLEARED_PARAGRAPH_ATTRS) : node.attrs;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* One node of the template, or `null` when it names something the package it
|
|
117
|
+
* came from owns. Inline content carries the insertion mark; a nested table is
|
|
118
|
+
* copied by the same rules as the one holding it.
|
|
119
|
+
*/
|
|
120
|
+
const copyNode = (node, context) => {
|
|
121
|
+
if (PACKAGE_BOUND_NODE_NAMES.has(node.type.name)) return null;
|
|
122
|
+
const marks = node.marks.filter(({ type }) => !PACKAGE_BOUND_MARK_NAMES.has(type.name));
|
|
123
|
+
if (node.isLeaf) return node.mark(node.isInline && context.insertion ? [...marks, context.insertion] : marks);
|
|
124
|
+
const inner = node.type.spec["tableRole"] === "table" ? {
|
|
125
|
+
...context,
|
|
126
|
+
clampRowSpan: false
|
|
127
|
+
} : context;
|
|
128
|
+
const content = [];
|
|
129
|
+
node.forEach((child) => {
|
|
130
|
+
const copied = copyNode(child, inner);
|
|
131
|
+
if (copied) content.push(copied);
|
|
132
|
+
});
|
|
133
|
+
return node.type.create(copiedAttrs(node, context), Fragment.fromArray(content), marks);
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* The template table, ready to insert: its properties, grid, rows and cells
|
|
137
|
+
* carried verbatim, nested tables included, and every row plus every run
|
|
138
|
+
* stamped as an insertion when the caller is tracking changes.
|
|
139
|
+
*
|
|
140
|
+
* `null` when the template is not a table or holds no rows, so the caller can
|
|
141
|
+
* fall back to the operation's own cell texts rather than place something that
|
|
142
|
+
* is not one.
|
|
143
|
+
*/
|
|
144
|
+
const tableFromTemplate = ({ schema, template, revision }) => {
|
|
145
|
+
if (template.type.spec["tableRole"] !== "table" || template.childCount === 0) return null;
|
|
146
|
+
return copyNode(template, {
|
|
147
|
+
revision: revision ?? null,
|
|
148
|
+
insertion: insertionMarkOf(schema, revision ?? null),
|
|
149
|
+
clampRowSpan: false
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* The template row, ready to insert into an existing table.
|
|
154
|
+
*
|
|
155
|
+
* Refused — `null`, so the caller falls back to the operation's cell texts —
|
|
156
|
+
* when the row's cells do not span exactly the receiving table's grid: a row
|
|
157
|
+
* of a different width would leave the table's map inconsistent with its own
|
|
158
|
+
* grid. A cell that merged vertically in the document it came from is placed
|
|
159
|
+
* unmerged, because the rows it reached into are not this table's.
|
|
160
|
+
*/
|
|
161
|
+
const tableRowFromTemplate = ({ template, columnCount }) => {
|
|
162
|
+
if (template.type.spec["tableRole"] !== "row" || template.childCount === 0) return null;
|
|
163
|
+
let spanned = 0;
|
|
164
|
+
template.forEach((cell) => {
|
|
165
|
+
const colspan = cell.attrs["colspan"];
|
|
166
|
+
spanned += typeof colspan === "number" ? colspan : 1;
|
|
167
|
+
});
|
|
168
|
+
if (spanned !== columnCount) return null;
|
|
169
|
+
return copyNode(template, {
|
|
170
|
+
revision: null,
|
|
171
|
+
insertion: null,
|
|
172
|
+
clampRowSpan: true
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
//#endregion
|
|
176
|
+
export { tableFromTemplate, tableRowFromTemplate };
|
package/dist/ai-edits/types.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
//#region src/ai-edits/types.d.ts
|
|
2
2
|
type FolioAIBlockKind = "heading" | "listItem" | "paragraph";
|
|
3
|
+
type FolioAIInlineFormatting = Partial<Record<"bold" | "italic" | "underline" | "strike", boolean>> & {
|
|
4
|
+
fontFamily?: string | null;
|
|
5
|
+
fontSizePt?: number | null;
|
|
6
|
+
color?: string | null;
|
|
7
|
+
};
|
|
3
8
|
type FolioAIBlockPreviewRun = {
|
|
4
9
|
text: string;
|
|
5
10
|
bold?: boolean;
|
|
@@ -9,6 +14,7 @@ type FolioAIBlockPreviewRun = {
|
|
|
9
14
|
fontFamily?: string;
|
|
10
15
|
fontSizePt?: number;
|
|
11
16
|
color?: string;
|
|
17
|
+
directFormatting?: FolioAIInlineFormatting;
|
|
12
18
|
};
|
|
13
19
|
/**
|
|
14
20
|
* Where a block sits inside its innermost enclosing table. Every index is
|
|
@@ -165,12 +171,6 @@ type FolioDocumentNavigationTarget = {
|
|
|
165
171
|
story: "main";
|
|
166
172
|
blockId: string;
|
|
167
173
|
} | FolioAITextRangeHandle;
|
|
168
|
-
type FolioAIInlineFormatting = {
|
|
169
|
-
bold?: boolean;
|
|
170
|
-
italic?: boolean;
|
|
171
|
-
underline?: boolean;
|
|
172
|
-
strike?: boolean;
|
|
173
|
-
};
|
|
174
174
|
/**
|
|
175
175
|
* A party in an `insertSignatureTable` op. Mirrors the
|
|
176
176
|
* `signatureTable` helper in `docx-core/legal-source/compile.ts`:
|
|
@@ -276,6 +276,13 @@ type FolioAIEditOperation = FolioAIEditReviewMeta & {
|
|
|
276
276
|
* Delete the whole block. A block with words loses them and its paragraph
|
|
277
277
|
* mark; a BLANK block has only a paragraph mark to lose, and loses it, so
|
|
278
278
|
* the empty line goes away rather than the operation doing nothing.
|
|
279
|
+
*
|
|
280
|
+
* The block that ENDS its container is the exception, in both modes: a
|
|
281
|
+
* body, a cell, a header or footer, a note and a text box each end with a
|
|
282
|
+
* paragraph, and a deleted mark there would say "join with the paragraph
|
|
283
|
+
* after this one" where there is none. It loses its words and keeps its
|
|
284
|
+
* place. To remove it, delete the mark of the block BEFORE it, which
|
|
285
|
+
* merges forward into it.
|
|
279
286
|
*/
|
|
280
287
|
{
|
|
281
288
|
id: string;
|
|
@@ -13,6 +13,61 @@
|
|
|
13
13
|
* gets written.
|
|
14
14
|
*/
|
|
15
15
|
type CellContent = string | readonly BodyItem[];
|
|
16
|
+
/** `w:tcPr` children a fixture can author, each named after the element. */
|
|
17
|
+
type CellProperties = {
|
|
18
|
+
/** `w:gridSpan`: grid columns this cell occupies. */
|
|
19
|
+
gridSpan?: number;
|
|
20
|
+
/** `w:vMerge`: the cell starts a vertical merge, or continues one. */
|
|
21
|
+
verticalMerge?: "restart" | "continue";
|
|
22
|
+
/** `w:tcW` in twips. */
|
|
23
|
+
width?: number;
|
|
24
|
+
/** `w:shd` fill colour, as six hex digits. */
|
|
25
|
+
shadingFill?: string;
|
|
26
|
+
/** `w:vAlign`. */
|
|
27
|
+
verticalAlign?: "top" | "center" | "bottom";
|
|
28
|
+
/** `w:tcBorders`, single style on all four sides, in eighths of a point. */
|
|
29
|
+
borderSize?: number;
|
|
30
|
+
/** `w:tcMar`, the same margin on all four sides, in twips. */
|
|
31
|
+
margin?: number;
|
|
32
|
+
};
|
|
33
|
+
/** A cell: its content alone, or its content and its own `w:tcPr`. */
|
|
34
|
+
type Cell = CellContent | ({
|
|
35
|
+
content: CellContent;
|
|
36
|
+
} & CellProperties);
|
|
37
|
+
/** A row: its cells alone, or its cells and its own `w:trPr`. */
|
|
38
|
+
type TableRow = readonly Cell[] | {
|
|
39
|
+
cells: readonly Cell[];
|
|
40
|
+
/** `w:trHeight` in twips. */
|
|
41
|
+
height?: number;
|
|
42
|
+
/** `w:tblHeader`: the row repeats at the top of every page. */
|
|
43
|
+
header?: boolean;
|
|
44
|
+
/** `w:jc` on the row. */
|
|
45
|
+
justification?: "left" | "center" | "right";
|
|
46
|
+
};
|
|
47
|
+
/** `w:tblPr` children a fixture can author, each named after the element. */
|
|
48
|
+
type TableProperties = {
|
|
49
|
+
/** `w:tblStyle`. */
|
|
50
|
+
styleId?: string;
|
|
51
|
+
/** `w:tblW`. */
|
|
52
|
+
width?: {
|
|
53
|
+
value: number;
|
|
54
|
+
type: "auto" | "dxa" | "pct";
|
|
55
|
+
};
|
|
56
|
+
/** `w:jc` on the table. */
|
|
57
|
+
justification?: "left" | "center" | "right";
|
|
58
|
+
/** `w:tblInd` in twips. */
|
|
59
|
+
indent?: number;
|
|
60
|
+
/** `w:tblBorders`, single style on every side, in eighths of a point. */
|
|
61
|
+
borderSize?: number;
|
|
62
|
+
/** `w:shd` fill colour on the table, as six hex digits. */
|
|
63
|
+
shadingFill?: string;
|
|
64
|
+
/** `w:tblLayout`. */
|
|
65
|
+
layout?: "fixed" | "autofit";
|
|
66
|
+
/** `w:tblCellMar`, the same margin on all four sides, in twips. */
|
|
67
|
+
cellMargin?: number;
|
|
68
|
+
/** `w:tblLook` value, as four hex digits. */
|
|
69
|
+
look?: string;
|
|
70
|
+
};
|
|
16
71
|
/**
|
|
17
72
|
* One inline of a paragraph: plain text, or text carrying an external
|
|
18
73
|
* hyperlink. A link is the case where a revision wrapper and the linked runs
|
|
@@ -35,7 +90,11 @@ type BodyItem = {
|
|
|
35
90
|
paraId?: string;
|
|
36
91
|
} | {
|
|
37
92
|
kind: "table";
|
|
38
|
-
rows: readonly
|
|
93
|
+
rows: readonly TableRow[];
|
|
94
|
+
/** `w:tblPr` children, written in the order the schema requires. */
|
|
95
|
+
properties?: TableProperties;
|
|
96
|
+
/** `w:tblGrid` column widths, in twips. One per grid column. */
|
|
97
|
+
columnWidths?: readonly number[];
|
|
39
98
|
/**
|
|
40
99
|
* Rows a package hides with `w:hidden`. The snapshot skips their whole
|
|
41
100
|
* subtree, so a document that has one is the case where the snapshot
|
|
@@ -53,4 +112,4 @@ type BodySequenceOptions = {
|
|
|
53
112
|
};
|
|
54
113
|
declare const buildBodySequenceDocx: (items: readonly BodyItem[], { header }?: BodySequenceOptions) => Promise<ArrayBuffer>;
|
|
55
114
|
//#endregion
|
|
56
|
-
export { BodyItem, BodySequenceOptions, CellContent, ParagraphInline, buildBodySequenceDocx };
|
|
115
|
+
export { BodyItem, BodySequenceOptions, Cell, CellContent, CellProperties, ParagraphInline, TableProperties, TableRow, buildBodySequenceDocx };
|
|
@@ -15,6 +15,8 @@ const WORDPROCESSING = "application/vnd.openxmlformats-officedocument.wordproces
|
|
|
15
15
|
const MARKUP_COMPATIBILITY = "http://schemas.openxmlformats.org/markup-compatibility/2006";
|
|
16
16
|
const PACKAGE_RELATIONSHIPS = "http://schemas.openxmlformats.org/package/2006";
|
|
17
17
|
const CORE_PROPERTIES_TYPE = "application/vnd.openxmlformats-package.core-properties+xml";
|
|
18
|
+
const EXTENDED_PROPERTIES = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties";
|
|
19
|
+
const EXTENDED_PROPERTIES_TYPE = "application/vnd.openxmlformats-officedocument.extended-properties+xml";
|
|
18
20
|
const WORDML_2010 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
19
21
|
/**
|
|
20
22
|
* `createFolders: false` because JSZip stamps the folder entries it
|
|
@@ -24,6 +26,12 @@ const ZIP_ENTRY_OPTIONS = {
|
|
|
24
26
|
date: new Date(Date.UTC(2e3, 0, 1)),
|
|
25
27
|
createFolders: false
|
|
26
28
|
};
|
|
29
|
+
const isRowOptions = (row) => "cells" in row;
|
|
30
|
+
const isCellSpec = (cell) => typeof cell === "object" && "content" in cell;
|
|
31
|
+
const rowCells = (row) => isRowOptions(row) ? row.cells : row;
|
|
32
|
+
const rowOptions = (row) => isRowOptions(row) ? row : {};
|
|
33
|
+
const cellContent = (cell) => isCellSpec(cell) ? cell.content : cell;
|
|
34
|
+
const cellProperties = (cell) => isCellSpec(cell) ? cell : {};
|
|
27
35
|
/**
|
|
28
36
|
* A paragraph's id is derived from its own content, not from its position.
|
|
29
37
|
*
|
|
@@ -69,7 +77,10 @@ const collectHrefs = (items, hrefs) => {
|
|
|
69
77
|
for (const inline of item.text) if (typeof inline !== "string" && !hrefs.includes(inline.href)) hrefs.push(inline.href);
|
|
70
78
|
continue;
|
|
71
79
|
}
|
|
72
|
-
for (const row of item.rows) for (const cell of row)
|
|
80
|
+
for (const row of item.rows) for (const cell of rowCells(row)) {
|
|
81
|
+
const content = cellContent(cell);
|
|
82
|
+
if (typeof content !== "string") collectHrefs(content, hrefs);
|
|
83
|
+
}
|
|
73
84
|
}
|
|
74
85
|
};
|
|
75
86
|
const EMPTY_PARAGRAPH = {
|
|
@@ -89,14 +100,90 @@ const closedSequence = (items) => {
|
|
|
89
100
|
/** A cell must also contain a paragraph, which the empty sequence supplies. */
|
|
90
101
|
const cellXml = (content, context) => typeof content === "string" ? paragraph(content, context) : itemsXml(closedSequence(content), context);
|
|
91
102
|
/** `w:tbl` is `w:tblPr, w:tblGrid, rows`: a fixture without the grid is not one. */
|
|
92
|
-
const tableGrid = (
|
|
103
|
+
const tableGrid = (item) => {
|
|
104
|
+
if (item.columnWidths) return `<w:tblGrid>${item.columnWidths.map((width) => `<w:gridCol w:w="${String(width)}"/>`).join("")}</w:tblGrid>`;
|
|
93
105
|
let columns = 0;
|
|
94
|
-
for (const
|
|
106
|
+
for (const row of item.rows) {
|
|
107
|
+
let spanned = 0;
|
|
108
|
+
for (const cell of rowCells(row)) spanned += cellProperties(cell).gridSpan ?? 1;
|
|
109
|
+
columns = Math.max(columns, spanned);
|
|
110
|
+
}
|
|
95
111
|
return `<w:tblGrid>${`<w:gridCol w:w="2000"/>`.repeat(columns)}</w:tblGrid>`;
|
|
96
112
|
};
|
|
113
|
+
const BORDER_SIDES = [
|
|
114
|
+
"top",
|
|
115
|
+
"left",
|
|
116
|
+
"bottom",
|
|
117
|
+
"right",
|
|
118
|
+
"insideH",
|
|
119
|
+
"insideV"
|
|
120
|
+
];
|
|
121
|
+
const CELL_BORDER_SIDES = [
|
|
122
|
+
"top",
|
|
123
|
+
"left",
|
|
124
|
+
"bottom",
|
|
125
|
+
"right"
|
|
126
|
+
];
|
|
127
|
+
const MARGIN_SIDES = [
|
|
128
|
+
"top",
|
|
129
|
+
"left",
|
|
130
|
+
"bottom",
|
|
131
|
+
"right"
|
|
132
|
+
];
|
|
133
|
+
const borders = (element, sides, size) => `<w:${element}>${sides.map((side) => `<w:${side} w:val="single" w:sz="${String(size)}" w:space="0" w:color="000000"/>`).join("")}</w:${element}>`;
|
|
134
|
+
const margins = (element, value) => `<w:${element}>${MARGIN_SIDES.map((side) => `<w:${side} w:w="${String(value)}" w:type="dxa"/>`).join("")}</w:${element}>`;
|
|
135
|
+
/**
|
|
136
|
+
* `w:tblPr` children in the order CT_TblPrBase declares: tblStyle, tblW, jc,
|
|
137
|
+
* tblInd, tblBorders, shd, tblLayout, tblCellMar, tblLook. `w:tblW` is always
|
|
138
|
+
* written.
|
|
139
|
+
*/
|
|
140
|
+
const tableProperties = ({ properties }) => {
|
|
141
|
+
const width = properties?.width ?? {
|
|
142
|
+
value: 0,
|
|
143
|
+
type: "auto"
|
|
144
|
+
};
|
|
145
|
+
return `<w:tblPr>${[
|
|
146
|
+
...properties?.styleId === void 0 ? [] : [`<w:tblStyle w:val="${properties.styleId}"/>`],
|
|
147
|
+
`<w:tblW w:w="${String(width.value)}" w:type="${width.type}"/>`,
|
|
148
|
+
...properties?.justification === void 0 ? [] : [`<w:jc w:val="${properties.justification}"/>`],
|
|
149
|
+
...properties?.indent === void 0 ? [] : [`<w:tblInd w:w="${String(properties.indent)}" w:type="dxa"/>`],
|
|
150
|
+
...properties?.borderSize === void 0 ? [] : [borders("tblBorders", BORDER_SIDES, properties.borderSize)],
|
|
151
|
+
...properties?.shadingFill === void 0 ? [] : [`<w:shd w:val="clear" w:color="auto" w:fill="${properties.shadingFill}"/>`],
|
|
152
|
+
...properties?.layout === void 0 ? [] : [`<w:tblLayout w:type="${properties.layout}"/>`],
|
|
153
|
+
...properties?.cellMargin === void 0 ? [] : [margins("tblCellMar", properties.cellMargin)],
|
|
154
|
+
...properties?.look === void 0 ? [] : [`<w:tblLook w:val="${properties.look}"/>`]
|
|
155
|
+
].join("")}</w:tblPr>`;
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* `w:trPr` children. `CT_TrPrBase` is a repeated choice rather than a
|
|
159
|
+
* sequence, so the order here is the readable one rather than a required one.
|
|
160
|
+
*/
|
|
161
|
+
const rowProperties = (row, hidden) => {
|
|
162
|
+
const { header, height, justification } = rowOptions(row);
|
|
163
|
+
const parts = [
|
|
164
|
+
...height === void 0 ? [] : [`<w:trHeight w:val="${String(height)}"/>`],
|
|
165
|
+
...header === true ? ["<w:tblHeader/>"] : [],
|
|
166
|
+
...justification === void 0 ? [] : [`<w:jc w:val="${justification}"/>`],
|
|
167
|
+
...hidden ? ["<w:hidden/>"] : []
|
|
168
|
+
];
|
|
169
|
+
return parts.length === 0 ? "" : `<w:trPr>${parts.join("")}</w:trPr>`;
|
|
170
|
+
};
|
|
171
|
+
/** `w:tcPr` children in schema order; `w:tcW` is always written. */
|
|
172
|
+
const cellPropertiesXml = (cell) => {
|
|
173
|
+
const properties = cellProperties(cell);
|
|
174
|
+
return `<w:tcPr>${[
|
|
175
|
+
`<w:tcW w:w="${String(properties.width ?? 2e3)}" w:type="dxa"/>`,
|
|
176
|
+
...properties.gridSpan === void 0 ? [] : [`<w:gridSpan w:val="${String(properties.gridSpan)}"/>`],
|
|
177
|
+
...properties.verticalMerge === void 0 ? [] : [properties.verticalMerge === "restart" ? `<w:vMerge w:val="restart"/>` : `<w:vMerge/>`],
|
|
178
|
+
...properties.borderSize === void 0 ? [] : [borders("tcBorders", CELL_BORDER_SIDES, properties.borderSize)],
|
|
179
|
+
...properties.shadingFill === void 0 ? [] : [`<w:shd w:val="clear" w:color="auto" w:fill="${properties.shadingFill}"/>`],
|
|
180
|
+
...properties.margin === void 0 ? [] : [margins("tcMar", properties.margin)],
|
|
181
|
+
...properties.verticalAlign === void 0 ? [] : [`<w:vAlign w:val="${properties.verticalAlign}"/>`]
|
|
182
|
+
].join("")}</w:tcPr>`;
|
|
183
|
+
};
|
|
97
184
|
const table = (item, context) => {
|
|
98
185
|
const hidden = new Set(item.hiddenRows ?? []);
|
|
99
|
-
return `<w:tbl
|
|
186
|
+
return `<w:tbl>${tableProperties(item)}` + tableGrid(item) + item.rows.map((row, rowIndex) => `<w:tr>${rowProperties(row, hidden.has(rowIndex))}${rowCells(row).map((cell) => `<w:tc>${cellPropertiesXml(cell)}${cellXml(cellContent(cell), context)}</w:tc>`).join("")}</w:tr>`).join("") + `</w:tbl>`;
|
|
100
187
|
};
|
|
101
188
|
const itemsXml = (items, context) => items.map((item) => item.kind === "paragraph" ? paragraph(item.text, context, {
|
|
102
189
|
...item.styleId === void 0 ? {} : { styleId: item.styleId },
|
|
@@ -114,9 +201,10 @@ const buildBodySequenceDocx = async (items, { header } = {}) => {
|
|
|
114
201
|
const linkRelationships = hrefs.map((href, index) => `<Relationship Id="rId${index + firstLinkRelationship}" Type="${OFFICE_RELATIONSHIPS}/hyperlink" Target="${href}" TargetMode="External"/>`).join("");
|
|
115
202
|
const paraId = createParaIdAllocator();
|
|
116
203
|
const parts = {
|
|
117
|
-
"[Content_Types].xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="${WORDPROCESSING}.document.main+xml"/><Override PartName="/word/styles.xml" ContentType="${WORDPROCESSING}.styles+xml"/>` + (header ? `<Override PartName="/word/header1.xml" ContentType="${WORDPROCESSING}.header+xml"/>` : "") + `<Override PartName="/docProps/core.xml" ContentType="${CORE_PROPERTIES_TYPE}"/></Types>`,
|
|
118
|
-
"_rels/.rels": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="${RELATIONSHIPS}"><Relationship Id="rId1" Type="${OFFICE_RELATIONSHIPS}/officeDocument" Target="word/document.xml"/><Relationship Id="rId2" Type="${PACKAGE_RELATIONSHIPS}/metadata/core-properties" Target="docProps/core.xml"/></Relationships>`,
|
|
204
|
+
"[Content_Types].xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="${WORDPROCESSING}.document.main+xml"/><Override PartName="/word/styles.xml" ContentType="${WORDPROCESSING}.styles+xml"/>` + (header ? `<Override PartName="/word/header1.xml" ContentType="${WORDPROCESSING}.header+xml"/>` : "") + `<Override PartName="/docProps/core.xml" ContentType="${CORE_PROPERTIES_TYPE}"/><Override PartName="/docProps/app.xml" ContentType="${EXTENDED_PROPERTIES_TYPE}"/></Types>`,
|
|
205
|
+
"_rels/.rels": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="${RELATIONSHIPS}"><Relationship Id="rId1" Type="${OFFICE_RELATIONSHIPS}/officeDocument" Target="word/document.xml"/><Relationship Id="rId2" Type="${PACKAGE_RELATIONSHIPS}/metadata/core-properties" Target="docProps/core.xml"/><Relationship Id="rId3" Type="${OFFICE_RELATIONSHIPS}/extended-properties" Target="docProps/app.xml"/></Relationships>`,
|
|
119
206
|
"docProps/core.xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><cp:coreProperties xmlns:cp="${PACKAGE_RELATIONSHIPS}/metadata/core-properties" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><dcterms:created xsi:type="dcterms:W3CDTF">2000-01-01T00:00:00Z</dcterms:created><dcterms:modified xsi:type="dcterms:W3CDTF">2000-01-01T00:00:00Z</dcterms:modified></cp:coreProperties>`,
|
|
207
|
+
"docProps/app.xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Properties xmlns="${EXTENDED_PROPERTIES}"><AppVersion>1.0.0</AppVersion></Properties>`,
|
|
120
208
|
"word/_rels/document.xml.rels": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="${RELATIONSHIPS}"><Relationship Id="rId1" Type="${OFFICE_RELATIONSHIPS}/styles" Target="styles.xml"/>` + (header ? `<Relationship Id="${HEADER_RELATIONSHIP_ID}" Type="${OFFICE_RELATIONSHIPS}/header" Target="header1.xml"/>` : "") + linkRelationships + `</Relationships>`,
|
|
121
209
|
"word/styles.xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:styles xmlns:w="${NAMESPACE}"><w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/></w:style><w:style w:type="paragraph" w:styleId="Heading1"><w:name w:val="heading 1"/></w:style></w:styles>`,
|
|
122
210
|
"word/document.xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document xmlns:w="${NAMESPACE}" xmlns:r="${OFFICE_RELATIONSHIPS}" xmlns:mc="${MARKUP_COMPATIBILITY}" xmlns:w14="${WORDML_2010}" mc:Ignorable="w14"><w:body>` + bodyXml(items, {
|
|
@@ -51,25 +51,32 @@ type PlannedStoryComparison = {
|
|
|
51
51
|
* parsing, no serialization, no clock.
|
|
52
52
|
*/
|
|
53
53
|
declare const planComparison: ({ pairs }: ParsedComparison) => Result<readonly PlannedStoryComparison[], CompareDocxOperationLimitError>;
|
|
54
|
-
/** What stage 3 produced: the change list, and whether it
|
|
54
|
+
/** What stage 3 produced: the change list, whether it was proven, and whether it wrote anything. */
|
|
55
55
|
type AppliedComparison = {
|
|
56
56
|
changes: readonly CompareChange[];
|
|
57
57
|
verification: CompareVerification;
|
|
58
|
+
/**
|
|
59
|
+
* Whether the stage wrote into the base document at all. A comparison that
|
|
60
|
+
* found nothing writes nothing, and the serialize stage then hands the
|
|
61
|
+
* arriving bytes back rather than reproducing them.
|
|
62
|
+
*/
|
|
63
|
+
documentChanged: boolean;
|
|
58
64
|
};
|
|
59
65
|
/**
|
|
60
66
|
* Stage 3: write the planned operations into the base document as tracked
|
|
61
67
|
* changes, then check the work rather than trust it. Both directions of the
|
|
62
|
-
* round trip are checked, structure included: accepting the
|
|
63
|
-
* revisions must reproduce the target, and rejecting them
|
|
64
|
-
* base it was compared from. A difference the operation
|
|
65
|
-
* express would otherwise leave a redline that reads
|
|
68
|
+
* round trip are checked, structure and table geometry included: accepting the
|
|
69
|
+
* story's generated revisions must reproduce the target, and rejecting them
|
|
70
|
+
* must reproduce the base it was compared from. A difference the operation
|
|
71
|
+
* vocabulary cannot express would otherwise leave a redline that reads
|
|
72
|
+
* plausibly and is wrong.
|
|
66
73
|
*
|
|
67
74
|
* The check reports rather than throws. {@link compareDocx} decides what to do
|
|
68
75
|
* with an unverified result, because "give me your best attempt and tell me
|
|
69
76
|
* what you could not represent" and "give me nothing unless you can prove it"
|
|
70
77
|
* are both legitimate asks and only the caller knows which one it is making.
|
|
71
78
|
*/
|
|
72
|
-
declare const applyComparison: ({ reviewer, revisionStamp, granularity, numberingChanges }: ParsedComparison, planned: readonly PlannedStoryComparison[]) => Result<AppliedComparison, CompareDocxApplyError>;
|
|
79
|
+
declare const applyComparison: ({ reviewer, targetReviewer, revisionStamp, granularity, numberingChanges }: ParsedComparison, planned: readonly PlannedStoryComparison[]) => Result<AppliedComparison, CompareDocxApplyError>;
|
|
73
80
|
/**
|
|
74
81
|
* Stage 4: the result package, with every ZIP entry date pinned.
|
|
75
82
|
*
|
|
@@ -89,7 +96,7 @@ declare const applyComparison: ({ reviewer, revisionStamp, granularity, numberin
|
|
|
89
96
|
* composition let the benchmark's own composition disagree with the shipped
|
|
90
97
|
* one within a single run.
|
|
91
98
|
*/
|
|
92
|
-
declare const serializeComparison: ({ baseBuffer, baseCarriedRevisions, reviewer, packageDate }: ParsedComparison,
|
|
99
|
+
declare const serializeComparison: ({ baseBuffer, baseCarriedRevisions, reviewer, packageDate, revisionStamp }: ParsedComparison, { documentChanged }: AppliedComparison) => Promise<Result<ArrayBuffer, CompareDocxSerializeError | CompareDocxFinalParagraphMarkError>>;
|
|
93
100
|
/**
|
|
94
101
|
* Compare `base` against `target` and return `base` carrying the tracked
|
|
95
102
|
* changes that turn it into `target`, alongside the change list describing
|