@beyondwork/docx-react-component 1.0.28 → 1.0.30
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/package.json +26 -37
- package/src/api/public-types.ts +531 -0
- package/src/api/session-state.ts +2 -0
- package/src/core/commands/index.ts +201 -79
- package/src/core/commands/table-structure-commands.ts +138 -5
- package/src/core/state/text-transaction.ts +370 -3
- package/src/index.ts +41 -0
- package/src/io/docx-session.ts +318 -25
- package/src/io/export/serialize-footnotes.ts +41 -46
- package/src/io/export/serialize-headers-footers.ts +36 -40
- package/src/io/export/serialize-main-document.ts +55 -89
- package/src/io/export/serialize-numbering.ts +104 -4
- package/src/io/export/serialize-runtime-revisions.ts +196 -2
- package/src/io/export/split-story-blocks-for-runtime-revisions.ts +252 -0
- package/src/io/export/table-properties-xml.ts +318 -0
- package/src/io/normalize/normalize-text.ts +34 -3
- package/src/io/ooxml/parse-comments.ts +6 -0
- package/src/io/ooxml/parse-footnotes.ts +69 -13
- package/src/io/ooxml/parse-headers-footers.ts +54 -11
- package/src/io/ooxml/parse-main-document.ts +112 -42
- package/src/io/ooxml/parse-numbering.ts +341 -26
- package/src/io/ooxml/parse-revisions.ts +118 -4
- package/src/io/ooxml/parse-styles.ts +176 -0
- package/src/io/ooxml/parse-tables.ts +34 -25
- package/src/io/ooxml/revision-boundaries.ts +127 -3
- package/src/io/ooxml/workflow-payload.ts +544 -0
- package/src/model/canonical-document.ts +91 -1
- package/src/model/snapshot.ts +112 -1
- package/src/preservation/store.ts +73 -3
- package/src/review/store/comment-store.ts +19 -1
- package/src/review/store/revision-actions.ts +29 -0
- package/src/review/store/revision-store.ts +12 -1
- package/src/review/store/revision-types.ts +11 -0
- package/src/runtime/context-analytics.ts +824 -0
- package/src/runtime/document-locations.ts +521 -0
- package/src/runtime/document-navigation.ts +14 -1
- package/src/runtime/document-outline.ts +440 -0
- package/src/runtime/document-runtime.ts +941 -45
- package/src/runtime/event-refresh-hints.ts +137 -0
- package/src/runtime/numbering-prefix.ts +67 -39
- package/src/runtime/page-layout-estimation.ts +100 -7
- package/src/runtime/resolved-numbering-geometry.ts +293 -0
- package/src/runtime/session-capabilities.ts +2 -2
- package/src/runtime/suggestions-snapshot.ts +137 -0
- package/src/runtime/surface-projection.ts +223 -27
- package/src/runtime/table-style-resolver.ts +409 -0
- package/src/runtime/view-state.ts +17 -1
- package/src/runtime/workflow-markup.ts +54 -14
- package/src/ui/WordReviewEditor.tsx +1269 -87
- package/src/ui/editor-command-bag.ts +7 -0
- package/src/ui/editor-runtime-boundary.ts +111 -10
- package/src/ui/editor-shell-view.tsx +17 -15
- package/src/ui/editor-surface-controller.tsx +5 -0
- package/src/ui/headless/selection-tool-context.ts +19 -0
- package/src/ui/headless/selection-tool-resolver.ts +752 -0
- package/src/ui/headless/selection-tool-types.ts +129 -0
- package/src/ui/headless/selection-toolbar-model.ts +10 -33
- package/src/ui/runtime-shortcut-dispatch.ts +365 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +107 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +15 -0
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +97 -0
- package/src/ui-tailwind/chrome/tw-context-analytics-summary.tsx +122 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +1 -9
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +1 -5
- package/src/ui-tailwind/chrome/tw-page-ruler.tsx +8 -29
- package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +23 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +35 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-formatting.tsx +37 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +298 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +116 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-suggestion.tsx +29 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +27 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +3 -3
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +3 -3
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +86 -14
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +57 -52
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +36 -52
- package/src/ui-tailwind/editor-surface/pm-schema.ts +56 -5
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +87 -24
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +4 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +135 -32
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +74 -7
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +17 -17
- package/src/ui-tailwind/review/tw-review-rail.tsx +19 -17
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +10 -10
- package/src/ui-tailwind/status/tw-status-bar.tsx +10 -6
- package/src/ui-tailwind/theme/editor-theme.css +58 -40
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +4 -4
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +250 -181
- package/src/ui-tailwind/tw-review-workspace.tsx +323 -280
- package/src/validation/compatibility-engine.ts +246 -2
- package/src/validation/docx-comment-proof.ts +24 -11
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BorderSpec,
|
|
3
|
+
CellShading,
|
|
4
|
+
TableBorders,
|
|
5
|
+
TableCellBorders,
|
|
6
|
+
TableCellMargins,
|
|
7
|
+
TableLook,
|
|
8
|
+
TableNode,
|
|
9
|
+
TableStyleConditionalRegion,
|
|
10
|
+
TableStyleDefinition,
|
|
11
|
+
TableStyleFormatting,
|
|
12
|
+
TableWidth,
|
|
13
|
+
} from "../model/canonical-document.ts";
|
|
14
|
+
|
|
15
|
+
export interface ResolvedTableCellStyle {
|
|
16
|
+
width?: TableWidth;
|
|
17
|
+
borders?: TableCellBorders;
|
|
18
|
+
shading?: CellShading;
|
|
19
|
+
verticalAlign?: "top" | "center" | "bottom";
|
|
20
|
+
activeConditionalRegions: TableStyleConditionalRegion[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ResolvedTableRowStyle {
|
|
24
|
+
height?: number;
|
|
25
|
+
heightRule?: "auto" | "atLeast" | "exact";
|
|
26
|
+
isHeader?: boolean;
|
|
27
|
+
activeConditionalRegions: TableStyleConditionalRegion[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ResolvedTableStyleResolution {
|
|
31
|
+
rawTblLook?: TableLook;
|
|
32
|
+
effectiveTblLook: TableLook;
|
|
33
|
+
table?: TableStyleFormatting["table"];
|
|
34
|
+
rows: Array<{
|
|
35
|
+
style: ResolvedTableRowStyle;
|
|
36
|
+
cells: ResolvedTableCellStyle[];
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const DEFAULT_EFFECTIVE_TABLE_LOOK: TableLook = {
|
|
41
|
+
val: "04A0",
|
|
42
|
+
firstRow: true,
|
|
43
|
+
lastRow: false,
|
|
44
|
+
firstColumn: true,
|
|
45
|
+
lastColumn: false,
|
|
46
|
+
noHBand: false,
|
|
47
|
+
noVBand: true,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const CONDITIONAL_REGION_ORDER: TableStyleConditionalRegion[] = [
|
|
51
|
+
"band1Horz",
|
|
52
|
+
"band2Horz",
|
|
53
|
+
"band1Vert",
|
|
54
|
+
"band2Vert",
|
|
55
|
+
"firstColumn",
|
|
56
|
+
"lastColumn",
|
|
57
|
+
"firstRow",
|
|
58
|
+
"lastRow",
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
export function resolveTableStyleResolution(
|
|
62
|
+
table: TableNode,
|
|
63
|
+
tableStyles: Record<string, TableStyleDefinition>,
|
|
64
|
+
): ResolvedTableStyleResolution {
|
|
65
|
+
const resolvedStyle = table.styleId ? resolveTableStyleDefinition(table.styleId, tableStyles) : {};
|
|
66
|
+
const effectiveTblLook = resolveEffectiveTableLook(table.tblLook, resolvedStyle.formatting?.table?.tblLook);
|
|
67
|
+
const columnCount = table.gridColumns.length > 0
|
|
68
|
+
? table.gridColumns.length
|
|
69
|
+
: table.rows.reduce(
|
|
70
|
+
(max, row) =>
|
|
71
|
+
Math.max(
|
|
72
|
+
max,
|
|
73
|
+
(row.gridBefore ?? 0) +
|
|
74
|
+
row.cells.reduce((sum, cell) => sum + (cell.gridSpan ?? 1), 0) +
|
|
75
|
+
(row.gridAfter ?? 0),
|
|
76
|
+
),
|
|
77
|
+
0,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const rows = table.rows.map((row, rowIndex) => {
|
|
81
|
+
const rowRegions = getActiveRowRegions(rowIndex, table.rows.length, effectiveTblLook);
|
|
82
|
+
const rowStyleFormatting = mergeStyleFormattingRow(
|
|
83
|
+
resolvedStyle.formatting?.row,
|
|
84
|
+
rowRegions.map((region) => resolvedStyle.conditionalFormatting?.[region]?.row),
|
|
85
|
+
{
|
|
86
|
+
...(row.height !== undefined ? { height: row.height } : {}),
|
|
87
|
+
...(row.heightRule ? { heightRule: row.heightRule } : {}),
|
|
88
|
+
...(row.isHeader !== undefined ? { isHeader: row.isHeader } : {}),
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
let columnCursor = row.gridBefore ?? 0;
|
|
93
|
+
const cells = row.cells.map((cell) => {
|
|
94
|
+
const startColumn = columnCursor;
|
|
95
|
+
const span = cell.gridSpan ?? 1;
|
|
96
|
+
const endColumn = startColumn + span - 1;
|
|
97
|
+
columnCursor += span;
|
|
98
|
+
|
|
99
|
+
const cellRegions = getActiveCellRegions(
|
|
100
|
+
rowRegions,
|
|
101
|
+
startColumn,
|
|
102
|
+
endColumn,
|
|
103
|
+
columnCount,
|
|
104
|
+
effectiveTblLook,
|
|
105
|
+
);
|
|
106
|
+
const cellStyleFormatting = mergeStyleFormattingCell(
|
|
107
|
+
resolvedStyle.formatting?.cell,
|
|
108
|
+
cellRegions.map((region) => resolvedStyle.conditionalFormatting?.[region]?.cell),
|
|
109
|
+
{
|
|
110
|
+
...(cell.width ? { width: cell.width } : {}),
|
|
111
|
+
...(cell.borders ? { borders: cell.borders } : {}),
|
|
112
|
+
...(cell.shading ? { shading: cell.shading } : {}),
|
|
113
|
+
...(cell.verticalAlign ? { verticalAlign: cell.verticalAlign } : {}),
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
...(cellStyleFormatting ?? {}),
|
|
119
|
+
activeConditionalRegions: cellRegions,
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
style: {
|
|
125
|
+
...(rowStyleFormatting ?? {}),
|
|
126
|
+
activeConditionalRegions: rowRegions,
|
|
127
|
+
},
|
|
128
|
+
cells,
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const tableFormatting = mergeTableFormatting(
|
|
133
|
+
resolvedStyle.formatting?.table,
|
|
134
|
+
{
|
|
135
|
+
...(table.width ? { width: table.width } : {}),
|
|
136
|
+
...(table.alignment ? { alignment: table.alignment } : {}),
|
|
137
|
+
...(table.borders ? { borders: table.borders } : {}),
|
|
138
|
+
...(table.cellMargins ? { cellMargins: table.cellMargins } : {}),
|
|
139
|
+
...(table.tblLook ? { tblLook: table.tblLook } : {}),
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
...(table.tblLook ? { rawTblLook: table.tblLook } : {}),
|
|
145
|
+
effectiveTblLook,
|
|
146
|
+
...(tableFormatting ? { table: tableFormatting } : {}),
|
|
147
|
+
rows,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function resolveTableStyleDefinition(
|
|
152
|
+
styleId: string,
|
|
153
|
+
tableStyles: Record<string, TableStyleDefinition>,
|
|
154
|
+
visited = new Set<string>(),
|
|
155
|
+
): {
|
|
156
|
+
formatting?: TableStyleFormatting;
|
|
157
|
+
conditionalFormatting?: Partial<Record<TableStyleConditionalRegion, TableStyleFormatting>>;
|
|
158
|
+
} {
|
|
159
|
+
if (visited.has(styleId)) {
|
|
160
|
+
return {};
|
|
161
|
+
}
|
|
162
|
+
visited.add(styleId);
|
|
163
|
+
|
|
164
|
+
const style = tableStyles[styleId];
|
|
165
|
+
if (!style) {
|
|
166
|
+
return {};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const inherited = style.basedOn
|
|
170
|
+
? resolveTableStyleDefinition(style.basedOn, tableStyles, visited)
|
|
171
|
+
: {};
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
formatting: mergeWholeStyleFormatting(inherited.formatting, style.formatting),
|
|
175
|
+
conditionalFormatting: mergeConditionalFormatting(
|
|
176
|
+
inherited.conditionalFormatting,
|
|
177
|
+
style.conditionalFormatting,
|
|
178
|
+
),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function resolveEffectiveTableLook(rawTblLook: TableLook | undefined, styleTblLook: TableLook | undefined): TableLook {
|
|
183
|
+
const decodedStyleLook = decodeTableLookMask(styleTblLook?.val);
|
|
184
|
+
const decodedRawLook = decodeTableLookMask(rawTblLook?.val);
|
|
185
|
+
return {
|
|
186
|
+
...DEFAULT_EFFECTIVE_TABLE_LOOK,
|
|
187
|
+
...(decodedStyleLook ?? {}),
|
|
188
|
+
...(styleTblLook ?? {}),
|
|
189
|
+
...(decodedRawLook ?? {}),
|
|
190
|
+
...(rawTblLook ?? {}),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function decodeTableLookMask(val: string | undefined): TableLook | undefined {
|
|
195
|
+
if (!val) {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const mask = Number.parseInt(val, 16);
|
|
200
|
+
if (Number.isNaN(mask)) {
|
|
201
|
+
return { val };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
val,
|
|
206
|
+
firstRow: (mask & 0x0020) !== 0,
|
|
207
|
+
lastRow: (mask & 0x0040) !== 0,
|
|
208
|
+
firstColumn: (mask & 0x0080) !== 0,
|
|
209
|
+
lastColumn: (mask & 0x0100) !== 0,
|
|
210
|
+
noHBand: (mask & 0x0200) !== 0,
|
|
211
|
+
noVBand: (mask & 0x0400) !== 0,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function getActiveRowRegions(
|
|
216
|
+
rowIndex: number,
|
|
217
|
+
rowCount: number,
|
|
218
|
+
effectiveTblLook: TableLook,
|
|
219
|
+
): TableStyleConditionalRegion[] {
|
|
220
|
+
const active = new Set<TableStyleConditionalRegion>();
|
|
221
|
+
|
|
222
|
+
if (effectiveTblLook.firstRow && rowIndex === 0) {
|
|
223
|
+
active.add("firstRow");
|
|
224
|
+
}
|
|
225
|
+
if (effectiveTblLook.lastRow && rowIndex === rowCount - 1) {
|
|
226
|
+
active.add("lastRow");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!effectiveTblLook.noHBand) {
|
|
230
|
+
const bandStart = effectiveTblLook.firstRow ? 1 : 0;
|
|
231
|
+
const bandEndExclusive = effectiveTblLook.lastRow ? rowCount - 1 : rowCount;
|
|
232
|
+
if (rowIndex >= bandStart && rowIndex < bandEndExclusive) {
|
|
233
|
+
const bandIndex = rowIndex - bandStart;
|
|
234
|
+
active.add(bandIndex % 2 === 0 ? "band1Horz" : "band2Horz");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return CONDITIONAL_REGION_ORDER.filter((region) => active.has(region));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function getActiveCellRegions(
|
|
242
|
+
rowRegions: TableStyleConditionalRegion[],
|
|
243
|
+
startColumn: number,
|
|
244
|
+
endColumn: number,
|
|
245
|
+
columnCount: number,
|
|
246
|
+
effectiveTblLook: TableLook,
|
|
247
|
+
): TableStyleConditionalRegion[] {
|
|
248
|
+
const active = new Set<TableStyleConditionalRegion>(rowRegions);
|
|
249
|
+
|
|
250
|
+
if (effectiveTblLook.firstColumn && startColumn === 0) {
|
|
251
|
+
active.add("firstColumn");
|
|
252
|
+
}
|
|
253
|
+
if (effectiveTblLook.lastColumn && endColumn === columnCount - 1) {
|
|
254
|
+
active.add("lastColumn");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!effectiveTblLook.noVBand) {
|
|
258
|
+
const bandStart = effectiveTblLook.firstColumn ? 1 : 0;
|
|
259
|
+
const bandEndExclusive = effectiveTblLook.lastColumn ? columnCount - 1 : columnCount;
|
|
260
|
+
if (startColumn >= bandStart && startColumn < bandEndExclusive) {
|
|
261
|
+
const bandIndex = startColumn - bandStart;
|
|
262
|
+
active.add(bandIndex % 2 === 0 ? "band1Vert" : "band2Vert");
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return CONDITIONAL_REGION_ORDER.filter((region) => active.has(region));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function mergeConditionalFormatting(
|
|
270
|
+
base: Partial<Record<TableStyleConditionalRegion, TableStyleFormatting>> | undefined,
|
|
271
|
+
override: Partial<Record<TableStyleConditionalRegion, TableStyleFormatting>> | undefined,
|
|
272
|
+
): Partial<Record<TableStyleConditionalRegion, TableStyleFormatting>> | undefined {
|
|
273
|
+
if (!base && !override) {
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const merged: Partial<Record<TableStyleConditionalRegion, TableStyleFormatting>> = {};
|
|
278
|
+
for (const region of CONDITIONAL_REGION_ORDER) {
|
|
279
|
+
const formatting = mergeWholeStyleFormatting(base?.[region], override?.[region]);
|
|
280
|
+
if (formatting) {
|
|
281
|
+
merged[region] = formatting;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function mergeWholeStyleFormatting(
|
|
288
|
+
base: TableStyleFormatting | undefined,
|
|
289
|
+
override: TableStyleFormatting | undefined,
|
|
290
|
+
): TableStyleFormatting | undefined {
|
|
291
|
+
if (!base && !override) {
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const merged: TableStyleFormatting = {};
|
|
296
|
+
const table = mergeTableFormatting(base?.table, override?.table);
|
|
297
|
+
const row = mergeRowFormatting(base?.row, override?.row);
|
|
298
|
+
const cell = mergeCellFormatting(base?.cell, override?.cell);
|
|
299
|
+
|
|
300
|
+
if (table) merged.table = table;
|
|
301
|
+
if (row) merged.row = row;
|
|
302
|
+
if (cell) merged.cell = cell;
|
|
303
|
+
|
|
304
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function mergeStyleFormattingRow(
|
|
308
|
+
base: TableStyleFormatting["row"],
|
|
309
|
+
conditionals: Array<TableStyleFormatting["row"] | undefined>,
|
|
310
|
+
direct: TableStyleFormatting["row"],
|
|
311
|
+
): TableStyleFormatting["row"] {
|
|
312
|
+
const styleResolved = conditionals.reduce(
|
|
313
|
+
(current, formatting) => mergeRowFormatting(current, formatting),
|
|
314
|
+
base,
|
|
315
|
+
);
|
|
316
|
+
return mergeRowFormatting(styleResolved, direct);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function mergeStyleFormattingCell(
|
|
320
|
+
base: TableStyleFormatting["cell"],
|
|
321
|
+
conditionals: Array<TableStyleFormatting["cell"] | undefined>,
|
|
322
|
+
direct: TableStyleFormatting["cell"],
|
|
323
|
+
): TableStyleFormatting["cell"] {
|
|
324
|
+
const styleResolved = conditionals.reduce(
|
|
325
|
+
(current, formatting) => mergeCellFormatting(current, formatting),
|
|
326
|
+
base,
|
|
327
|
+
);
|
|
328
|
+
return mergeCellFormatting(styleResolved, direct);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function mergeTableFormatting(
|
|
332
|
+
base: TableStyleFormatting["table"],
|
|
333
|
+
override: TableStyleFormatting["table"],
|
|
334
|
+
): TableStyleFormatting["table"] {
|
|
335
|
+
if (!base && !override) {
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
...(base ?? {}),
|
|
340
|
+
...(override ?? {}),
|
|
341
|
+
borders: mergeBorderMap(base?.borders, override?.borders),
|
|
342
|
+
cellMargins: mergePlainObject(base?.cellMargins, override?.cellMargins),
|
|
343
|
+
tblLook: mergeTableLook(base?.tblLook, override?.tblLook),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function mergeRowFormatting(
|
|
348
|
+
base: TableStyleFormatting["row"],
|
|
349
|
+
override: TableStyleFormatting["row"],
|
|
350
|
+
): TableStyleFormatting["row"] {
|
|
351
|
+
if (!base && !override) {
|
|
352
|
+
return undefined;
|
|
353
|
+
}
|
|
354
|
+
return { ...(base ?? {}), ...(override ?? {}) };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function mergeCellFormatting(
|
|
358
|
+
base: TableStyleFormatting["cell"],
|
|
359
|
+
override: TableStyleFormatting["cell"],
|
|
360
|
+
): TableStyleFormatting["cell"] {
|
|
361
|
+
if (!base && !override) {
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
...(base ?? {}),
|
|
366
|
+
...(override ?? {}),
|
|
367
|
+
borders: mergeBorderMap(base?.borders, override?.borders),
|
|
368
|
+
shading: mergePlainObject(base?.shading, override?.shading),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function mergeBorderMap<T extends TableBorders | TableCellBorders>(
|
|
373
|
+
base: T | undefined,
|
|
374
|
+
override: T | undefined,
|
|
375
|
+
): T | undefined {
|
|
376
|
+
if (!base && !override) {
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const sides = ["top", "left", "bottom", "right", "insideH", "insideV"] as const;
|
|
381
|
+
const merged = {} as T;
|
|
382
|
+
for (const side of sides) {
|
|
383
|
+
const spec = mergePlainObject(base?.[side], override?.[side]) as BorderSpec | undefined;
|
|
384
|
+
if (spec) {
|
|
385
|
+
merged[side] = spec as T[typeof side];
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function mergePlainObject<T extends object>(base: T | undefined, override: T | undefined): T | undefined {
|
|
392
|
+
if (!base && !override) {
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
return { ...(base ?? {}), ...(override ?? {}) } as T;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function mergeTableLook(base: TableLook | undefined, override: TableLook | undefined): TableLook | undefined {
|
|
399
|
+
if (!base && !override) {
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
if (!override) {
|
|
403
|
+
return base ? { ...base } : undefined;
|
|
404
|
+
}
|
|
405
|
+
if (override.val !== undefined) {
|
|
406
|
+
return { ...override };
|
|
407
|
+
}
|
|
408
|
+
return { ...(base ?? {}), ...override };
|
|
409
|
+
}
|
|
@@ -168,7 +168,7 @@ export function deriveLayoutMeasurement(
|
|
|
168
168
|
? selectionOrPosition.activeRange.at
|
|
169
169
|
: selectionOrPosition.head;
|
|
170
170
|
const block = surface ? findBlockAtPosition(surface.blocks, selectionHead) : null;
|
|
171
|
-
const tabStops = block
|
|
171
|
+
const tabStops = resolveMeasurementTabStops(block);
|
|
172
172
|
const listMarkerLane = deriveListMarkerLane(block);
|
|
173
173
|
|
|
174
174
|
return {
|
|
@@ -469,9 +469,25 @@ function deriveListMarkerLane(
|
|
|
469
469
|
block: SurfaceBlockSnapshot | null,
|
|
470
470
|
): { indent: number; markerWidth: number } | null {
|
|
471
471
|
if (!block || block.kind !== "paragraph" || !block.numbering) return null;
|
|
472
|
+
const resolvedMarkerLane = block.resolvedNumbering?.geometry.markerLane;
|
|
473
|
+
if (resolvedMarkerLane) {
|
|
474
|
+
return {
|
|
475
|
+
indent: resolvedMarkerLane.textStart,
|
|
476
|
+
markerWidth: resolvedMarkerLane.width,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
472
479
|
const indent = block.indentation?.hanging ?? block.indentation?.left ?? 360;
|
|
473
480
|
return {
|
|
474
481
|
indent,
|
|
475
482
|
markerWidth: Math.min(indent, 360),
|
|
476
483
|
};
|
|
477
484
|
}
|
|
485
|
+
|
|
486
|
+
function resolveMeasurementTabStops(
|
|
487
|
+
block: SurfaceBlockSnapshot | null,
|
|
488
|
+
): Array<{ pos: number; val?: string; leader?: string }> {
|
|
489
|
+
if (!block || block.kind !== "paragraph") {
|
|
490
|
+
return [];
|
|
491
|
+
}
|
|
492
|
+
return block.resolvedNumbering?.geometry.tabStops ?? block.tabStops ?? [];
|
|
493
|
+
}
|
|
@@ -12,6 +12,8 @@ import type {
|
|
|
12
12
|
WorkflowCandidateRangeOptions,
|
|
13
13
|
WorkflowFieldMarkup,
|
|
14
14
|
WorkflowHighlightMarkup,
|
|
15
|
+
WorkflowMetadataMarkup,
|
|
16
|
+
WorkflowMetadataSnapshot,
|
|
15
17
|
WorkflowMarkupItem,
|
|
16
18
|
WorkflowMarkupSnapshot,
|
|
17
19
|
WorkflowOpaqueFragmentMarkup,
|
|
@@ -21,18 +23,18 @@ import type {
|
|
|
21
23
|
} from "../api/public-types";
|
|
22
24
|
import { MAIN_STORY_TARGET } from "../core/selection/mapping.ts";
|
|
23
25
|
import { searchSurfaceBlocks } from "../core/search/search-text.ts";
|
|
24
|
-
import { describeOpaqueFragment } from "../preservation/store.ts";
|
|
26
|
+
import { describeOpaqueFragment, isBlockedImportFeatureKey } from "../preservation/store.ts";
|
|
25
27
|
import type { CanonicalDocumentEnvelope } from "../core/state/editor-state.ts";
|
|
26
28
|
|
|
27
|
-
const BLOCKED_IMPORT_FEATURE_KEYS = new Set(["alt-chunk", "alternate-content", "custom-xml"]);
|
|
28
|
-
|
|
29
29
|
export function collectWorkflowMarkupSnapshot(input: {
|
|
30
30
|
renderSnapshot: RuntimeRenderSnapshot;
|
|
31
31
|
fieldSnapshot: FieldSnapshot;
|
|
32
32
|
protectionSnapshot: ProtectionSnapshot;
|
|
33
33
|
preservation: CanonicalDocumentEnvelope["preservation"];
|
|
34
|
+
workflowMetadataSnapshot?: WorkflowMetadataSnapshot;
|
|
34
35
|
}): WorkflowMarkupSnapshot {
|
|
35
36
|
const highlights: WorkflowHighlightMarkup[] = [];
|
|
37
|
+
const metadata = collectWorkflowMetadataMarkup(input.workflowMetadataSnapshot);
|
|
36
38
|
const fields: WorkflowFieldMarkup[] = [];
|
|
37
39
|
const opaqueFragments: WorkflowOpaqueFragmentMarkup[] = [];
|
|
38
40
|
const surface = input.renderSnapshot.surface;
|
|
@@ -113,6 +115,7 @@ export function collectWorkflowMarkupSnapshot(input: {
|
|
|
113
115
|
|
|
114
116
|
const items: WorkflowMarkupItem[] = [
|
|
115
117
|
...highlights,
|
|
118
|
+
...metadata,
|
|
116
119
|
...comments,
|
|
117
120
|
...revisions,
|
|
118
121
|
...fields,
|
|
@@ -124,6 +127,7 @@ export function collectWorkflowMarkupSnapshot(input: {
|
|
|
124
127
|
totalCount: items.length,
|
|
125
128
|
items,
|
|
126
129
|
highlights,
|
|
130
|
+
metadata,
|
|
127
131
|
comments,
|
|
128
132
|
revisions,
|
|
129
133
|
fields,
|
|
@@ -132,6 +136,42 @@ export function collectWorkflowMarkupSnapshot(input: {
|
|
|
132
136
|
};
|
|
133
137
|
}
|
|
134
138
|
|
|
139
|
+
function collectWorkflowMetadataMarkup(
|
|
140
|
+
snapshot?: WorkflowMetadataSnapshot,
|
|
141
|
+
): WorkflowMetadataMarkup[] {
|
|
142
|
+
if (!snapshot) {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const definitionsById = new Map(
|
|
147
|
+
snapshot.definitions.map((definition) => [definition.metadataId, definition] as const),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return snapshot.entries.flatMap((entry) => {
|
|
151
|
+
const definition = definitionsById.get(entry.metadataId);
|
|
152
|
+
if (!definition) {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return [{
|
|
157
|
+
markupId: `metadata:${entry.entryId}`,
|
|
158
|
+
kind: "metadata",
|
|
159
|
+
entryId: entry.entryId,
|
|
160
|
+
metadataId: entry.metadataId,
|
|
161
|
+
anchor: entry.anchor,
|
|
162
|
+
storyTarget: entry.storyTarget,
|
|
163
|
+
label: definition.label,
|
|
164
|
+
excerpt: definition.kind,
|
|
165
|
+
color: definition.color,
|
|
166
|
+
icon: definition.icon,
|
|
167
|
+
persistence: definition.persistence,
|
|
168
|
+
value: entry.value,
|
|
169
|
+
scopeId: entry.scopeId,
|
|
170
|
+
workItemId: entry.workItemId,
|
|
171
|
+
} satisfies WorkflowMetadataMarkup];
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
135
175
|
export function deriveWorkflowCandidateRangesFromMarkup(
|
|
136
176
|
snapshot: WorkflowMarkupSnapshot,
|
|
137
177
|
options: WorkflowCandidateRangeOptions = {},
|
|
@@ -184,10 +224,6 @@ function collectSurfaceMarkup(
|
|
|
184
224
|
|
|
185
225
|
const fragment = preservation.opaqueFragments[block.fragmentId];
|
|
186
226
|
const descriptor = fragment ? describeOpaqueFragment(fragment) : null;
|
|
187
|
-
const blockedReasonCode =
|
|
188
|
-
fragment && BLOCKED_IMPORT_FEATURE_KEYS.has(descriptor?.featureKey ?? "")
|
|
189
|
-
? "workflow_blocked_import"
|
|
190
|
-
: "workflow_preserve_only";
|
|
191
227
|
opaqueFragments.push({
|
|
192
228
|
markupId: `opaque:${block.fragmentId}`,
|
|
193
229
|
kind: "opaque_fragment",
|
|
@@ -198,7 +234,11 @@ function collectSurfaceMarkup(
|
|
|
198
234
|
label: block.label,
|
|
199
235
|
excerpt: block.detail,
|
|
200
236
|
detail: block.detail,
|
|
201
|
-
blockedReasonCode
|
|
237
|
+
blockedReasonCode:
|
|
238
|
+
block.blockedReasonCode ??
|
|
239
|
+
(fragment && descriptor && isBlockedImportFeatureKey(descriptor.featureKey)
|
|
240
|
+
? "workflow_blocked_import"
|
|
241
|
+
: "workflow_preserve_only"),
|
|
202
242
|
});
|
|
203
243
|
}
|
|
204
244
|
}
|
|
@@ -227,10 +267,6 @@ function collectSegmentMarkup(
|
|
|
227
267
|
if (segment.kind === "opaque_inline") {
|
|
228
268
|
const fragment = preservation.opaqueFragments[segment.fragmentId];
|
|
229
269
|
const descriptor = fragment ? describeOpaqueFragment(fragment) : null;
|
|
230
|
-
const blockedReasonCode =
|
|
231
|
-
fragment && BLOCKED_IMPORT_FEATURE_KEYS.has(descriptor?.featureKey ?? "")
|
|
232
|
-
? "workflow_blocked_import"
|
|
233
|
-
: "workflow_preserve_only";
|
|
234
270
|
opaqueFragments.push({
|
|
235
271
|
markupId: `opaque:${segment.fragmentId}`,
|
|
236
272
|
kind: "opaque_fragment",
|
|
@@ -241,7 +277,11 @@ function collectSegmentMarkup(
|
|
|
241
277
|
label: segment.label,
|
|
242
278
|
excerpt: segment.detail,
|
|
243
279
|
detail: segment.detail,
|
|
244
|
-
blockedReasonCode
|
|
280
|
+
blockedReasonCode:
|
|
281
|
+
segment.blockedReasonCode ??
|
|
282
|
+
(fragment && descriptor && isBlockedImportFeatureKey(descriptor.featureKey)
|
|
283
|
+
? "workflow_blocked_import"
|
|
284
|
+
: "workflow_preserve_only"),
|
|
245
285
|
});
|
|
246
286
|
}
|
|
247
287
|
}
|
|
@@ -308,7 +348,7 @@ function collectOpaqueFragmentMarkup(
|
|
|
308
348
|
)
|
|
309
349
|
.map((fragment) => {
|
|
310
350
|
const descriptor = describeOpaqueFragment(fragment);
|
|
311
|
-
const blockedReasonCode =
|
|
351
|
+
const blockedReasonCode = isBlockedImportFeatureKey(descriptor.featureKey)
|
|
312
352
|
? "workflow_blocked_import"
|
|
313
353
|
: "workflow_preserve_only";
|
|
314
354
|
|