@beyondwork/docx-react-component 1.0.58 → 1.0.59
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/README.md +2 -2
- package/package.json +2 -1
- package/src/api/awareness-identity-types.ts +4 -2
- package/src/api/comment-negotiation-types.ts +4 -1
- package/src/api/external-custody-types.ts +16 -0
- package/src/api/internal/build-ref-projections.ts +108 -0
- package/src/api/package-version.ts +1 -1
- package/src/api/participants-types.ts +11 -1
- package/src/api/public-types.ts +978 -10
- package/src/api/scope-metadata-resolver-types.ts +6 -0
- package/src/compare/diff-engine.ts +3 -0
- package/src/core/commands/formatting-commands.ts +1 -0
- package/src/core/commands/index.ts +225 -16
- package/src/core/commands/legacy-form-field-commands.ts +181 -0
- package/src/core/commands/table-structure-commands.ts +149 -31
- package/src/core/selection/mapping.ts +20 -0
- package/src/core/state/editor-state.ts +2 -1
- package/src/index.ts +28 -0
- package/src/io/docx-session.ts +22 -3
- package/src/io/export/export-session.ts +11 -7
- package/src/io/export/ooxml-namespaces.ts +47 -0
- package/src/io/export/reattach-preserved-parts.ts +4 -16
- package/src/io/export/serialize-comments.ts +3 -131
- package/src/io/export/serialize-ffdata.ts +89 -0
- package/src/io/export/serialize-headers-footers.ts +5 -0
- package/src/io/export/serialize-main-document.ts +224 -34
- package/src/io/export/serialize-numbering.ts +22 -2
- package/src/io/export/serialize-revisions.ts +99 -0
- package/src/io/export/serialize-tables.ts +9 -0
- package/src/io/export/split-review-boundaries.ts +1 -0
- package/src/io/export/table-properties-xml.ts +14 -0
- package/src/io/load-scheduler.ts +70 -28
- package/src/io/normalize/normalize-text.ts +13 -0
- package/src/io/ooxml/_mini-xml.ts +198 -0
- package/src/io/ooxml/canonicalize-payload.ts +1 -4
- package/src/io/ooxml/chart/chart-style-table.ts +4 -3
- package/src/io/ooxml/chart/parse-chart-space.ts +2 -4
- package/src/io/ooxml/chart/parse-series.ts +2 -1
- package/src/io/ooxml/chart/resolve-color.ts +2 -2
- package/src/io/ooxml/chart/types.ts +6 -434
- package/src/io/ooxml/comment-presentation-payload.ts +6 -5
- package/src/io/ooxml/highlight-colors.ts +8 -5
- package/src/io/ooxml/parse-anchor.ts +68 -53
- package/src/io/ooxml/parse-comments.ts +14 -142
- package/src/io/ooxml/parse-complex-content.ts +3 -106
- package/src/io/ooxml/parse-drawing.ts +100 -195
- package/src/io/ooxml/parse-ffdata.ts +93 -0
- package/src/io/ooxml/parse-fields.ts +7 -146
- package/src/io/ooxml/parse-fill.ts +88 -8
- package/src/io/ooxml/parse-font-table.ts +5 -105
- package/src/io/ooxml/parse-footnotes.ts +28 -152
- package/src/io/ooxml/parse-headers-footers.ts +106 -212
- package/src/io/ooxml/parse-inline-media.ts +3 -200
- package/src/io/ooxml/parse-main-document.ts +180 -217
- package/src/io/ooxml/parse-numbering.ts +154 -335
- package/src/io/ooxml/parse-object.ts +147 -0
- package/src/io/ooxml/parse-ole-relationship.ts +82 -0
- package/src/io/ooxml/parse-paragraph-formatting.ts +7 -10
- package/src/io/ooxml/parse-picture-sdt.ts +85 -0
- package/src/io/ooxml/parse-picture.ts +72 -42
- package/src/io/ooxml/parse-revisions.ts +285 -51
- package/src/io/ooxml/parse-settings.ts +6 -99
- package/src/io/ooxml/parse-shapes.ts +25 -140
- package/src/io/ooxml/parse-styles.ts +3 -218
- package/src/io/ooxml/parse-tables.ts +76 -256
- package/src/io/ooxml/parse-theme.ts +1 -4
- package/src/io/ooxml/property-grab-bag.ts +5 -47
- package/src/io/ooxml/xml-element-serialize.ts +32 -0
- package/src/io/ooxml/xml-parser.ts +183 -0
- package/src/legal/bookmarks.ts +1 -1
- package/src/legal/cross-references.ts +1 -1
- package/src/legal/defined-terms.ts +1 -1
- package/src/legal/{_document-root.ts → document-root.ts} +8 -0
- package/src/legal/signature-blocks.ts +1 -1
- package/src/model/canonical-document.ts +159 -6
- package/src/model/chart-types.ts +439 -0
- package/src/model/snapshot.ts +3 -1
- package/src/review/store/comment-remapping.ts +24 -11
- package/src/review/store/revision-actions.ts +482 -2
- package/src/review/store/revision-store.ts +15 -0
- package/src/review/store/revision-types.ts +76 -0
- package/src/runtime/collab/remote-cursor-awareness.ts +24 -0
- package/src/runtime/collab/runtime-collab-sync.ts +33 -0
- package/src/runtime/diagnostics/build-diagnostic.ts +151 -0
- package/src/runtime/diagnostics/code-metadata-table.ts +221 -0
- package/src/runtime/document-runtime.ts +476 -34
- package/src/runtime/document-search.ts +115 -0
- package/src/runtime/edit-ops/index.ts +18 -2
- package/src/runtime/footnote-resolver.ts +130 -0
- package/src/runtime/layout/layout-engine-instance.ts +31 -4
- package/src/runtime/layout/layout-engine-version.ts +37 -1
- package/src/runtime/layout/page-graph.ts +14 -1
- package/src/runtime/layout/resolved-formatting-state.ts +21 -0
- package/src/runtime/numbering-prefix.ts +17 -0
- package/src/runtime/query-scopes.ts +5 -8
- package/src/runtime/resolved-numbering-geometry.ts +37 -6
- package/src/runtime/revision-runtime.ts +27 -1
- package/src/runtime/selection/post-edit-validator.ts +60 -6
- package/src/runtime/structure-ops/index.ts +20 -4
- package/src/runtime/surface-projection.ts +290 -21
- package/src/runtime/table-schema.ts +6 -0
- package/src/runtime/theme-color-resolver.ts +2 -2
- package/src/runtime/units.ts +9 -0
- package/src/runtime/workflow-rail-segments.ts +4 -0
- package/src/ui/WordReviewEditor.tsx +187 -43
- package/src/ui/editor-runtime-boundary.ts +10 -0
- package/src/ui/editor-shell-view.tsx +4 -1
- package/src/ui/headless/chrome-registry.ts +53 -0
- package/src/ui/headless/selection-tool-resolver.ts +11 -1
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +13 -0
- package/src/ui-tailwind/chrome/tw-command-palette-mount.tsx +96 -0
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +2 -1
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +5 -4
- package/src/ui-tailwind/chrome/tw-mode-dock.tsx +6 -2
- package/src/ui-tailwind/chrome/use-container-breakpoint.ts +111 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +0 -9
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +1 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +6 -7
- package/src/ui-tailwind/editor-surface/pm-schema.ts +87 -25
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +9 -0
- package/src/ui-tailwind/editor-surface/shape-renderer.ts +76 -14
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +18 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +2 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +18 -2
- package/src/ui-tailwind/index.ts +9 -0
- package/src/ui-tailwind/page-chrome-model.ts +77 -5
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +56 -1
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +2 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +116 -113
- package/src/ui-tailwind/review/tw-review-rail-footer.tsx +2 -2
- package/src/ui-tailwind/theme/tokens.ts +14 -0
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +5 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +29 -87
- package/src/validation/diagnostics.ts +1 -0
|
@@ -509,17 +509,38 @@ function deleteRow(
|
|
|
509
509
|
}
|
|
510
510
|
|
|
511
511
|
const deleteIndex = selection.anchorCell.rowIndex;
|
|
512
|
-
const
|
|
512
|
+
const nextTable = removeTableRowPure(table, deleteIndex);
|
|
513
|
+
const focusRowIndex = Math.max(0, Math.min(deleteIndex, nextTable.rows.length - 1));
|
|
514
|
+
|
|
515
|
+
return commitTableChange(
|
|
516
|
+
document,
|
|
517
|
+
root,
|
|
518
|
+
selection.tableBlockIndex,
|
|
519
|
+
nextTable,
|
|
520
|
+
fallbackSelection,
|
|
521
|
+
focusRowIndex,
|
|
522
|
+
selection.anchorCell.columnIndex,
|
|
523
|
+
);
|
|
524
|
+
}
|
|
513
525
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
526
|
+
/**
|
|
527
|
+
* Pure model-level row removal for a table node.
|
|
528
|
+
*
|
|
529
|
+
* Removes `table.rows[rowIndex]` while preserving the vMerge chain invariant:
|
|
530
|
+
* if the deleted row is the origin of a chain that extends beyond it, the
|
|
531
|
+
* first following continue cell is promoted to "restart" and inherits the
|
|
532
|
+
* origin's content/properties. For mid-chain continues, nothing needs to be
|
|
533
|
+
* rewritten because the chain's origin stays in place.
|
|
534
|
+
*
|
|
535
|
+
* Used by `deleteRow` above (UI command path) and by Lane 7b's row-insertion
|
|
536
|
+
* reject path in `src/review/store/revision-actions.ts`.
|
|
537
|
+
*/
|
|
538
|
+
export function removeTableRowPure(table: TableNode, rowIndex: number): TableNode {
|
|
539
|
+
if (rowIndex < 0 || rowIndex >= table.rows.length) {
|
|
540
|
+
return table;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const grid = buildLogicalGrid(table);
|
|
523
544
|
const promotions: Array<{
|
|
524
545
|
originColumnIndex: number;
|
|
525
546
|
originColumnSpan: number;
|
|
@@ -528,43 +549,140 @@ function deleteRow(
|
|
|
528
549
|
}> = [];
|
|
529
550
|
|
|
530
551
|
for (let column = 0; column < grid.columnCount; ) {
|
|
531
|
-
const origin = originAt(grid,
|
|
552
|
+
const origin = originAt(grid, rowIndex, column);
|
|
532
553
|
if (!origin) {
|
|
533
554
|
column += 1;
|
|
534
555
|
continue;
|
|
535
556
|
}
|
|
536
|
-
if (origin.rowIndex ===
|
|
557
|
+
if (origin.rowIndex === rowIndex && origin.rowSpan > 1) {
|
|
537
558
|
promotions.push({
|
|
538
559
|
originColumnIndex: origin.columnIndex,
|
|
539
560
|
originColumnSpan: origin.columnSpan,
|
|
540
|
-
promotedRowIndex:
|
|
561
|
+
promotedRowIndex: rowIndex + 1,
|
|
541
562
|
originCell: origin.cell,
|
|
542
563
|
});
|
|
543
564
|
}
|
|
544
565
|
column = origin.columnIndex + origin.columnSpan;
|
|
545
566
|
}
|
|
546
567
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
568
|
+
const nextRows = table.rows
|
|
569
|
+
.map((row, index) => {
|
|
570
|
+
if (index !== rowIndex + 1 || promotions.length === 0) return row;
|
|
571
|
+
return applyVMergePromotions(row, promotions);
|
|
572
|
+
})
|
|
573
|
+
.filter((_, index) => index !== rowIndex);
|
|
552
574
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
575
|
+
return { ...table, rows: nextRows };
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Pure model-level cell removal for a row within a table.
|
|
580
|
+
*
|
|
581
|
+
* Removes `table.rows[rowIndex].cells[cellIndex]` while preserving:
|
|
582
|
+
*
|
|
583
|
+
* 1. **Row logical-column footprint.** The row occupies the same total
|
|
584
|
+
* logical column count as before — if the removed cell had
|
|
585
|
+
* `gridSpan = N`, an adjacent cell absorbs +N via its own `gridSpan`.
|
|
586
|
+
* Preference: the immediately preceding cell absorbs the delta; if
|
|
587
|
+
* the removed cell is the first in the row, the immediately following
|
|
588
|
+
* cell absorbs instead. This keeps the table-level `gridColumns` array
|
|
589
|
+
* unchanged (important — shrinking `gridColumns` would cascade-change
|
|
590
|
+
* every sibling row).
|
|
591
|
+
*
|
|
592
|
+
* 2. **vMerge chain integrity.** If the removed cell is the origin of a
|
|
593
|
+
* vMerge chain that extends past this row, the chain's first
|
|
594
|
+
* following continue cell is promoted to `"restart"` (inheriting the
|
|
595
|
+
* origin's content/properties). Mid-chain continues and tail
|
|
596
|
+
* continues are unaffected — when the origin's content moves, the
|
|
597
|
+
* chain shape stays valid by construction.
|
|
598
|
+
*
|
|
599
|
+
* 3. **Single-cell row invariant.** If the row contains only one cell,
|
|
600
|
+
* removing it is equivalent to removing the row entirely — a
|
|
601
|
+
* different operation with different grid semantics. This helper
|
|
602
|
+
* declines the operation and returns `table` unchanged; callers must
|
|
603
|
+
* use `removeTableRowPure` instead.
|
|
604
|
+
*
|
|
605
|
+
* Used by Lane 7b's cellIns reject path in
|
|
606
|
+
* `src/review/store/revision-actions.ts`. The Lane 7b parser stamps
|
|
607
|
+
* `metadata.tableRevisionCoordinates.cellIndex` at parse time so the
|
|
608
|
+
* reject path never needs a runtime position → cell reverse lookup.
|
|
609
|
+
*/
|
|
610
|
+
export function removeCellFromRow(
|
|
611
|
+
table: TableNode,
|
|
612
|
+
rowIndex: number,
|
|
613
|
+
cellIndex: number,
|
|
614
|
+
): TableNode {
|
|
615
|
+
if (rowIndex < 0 || rowIndex >= table.rows.length) {
|
|
616
|
+
return table;
|
|
617
|
+
}
|
|
618
|
+
const row = table.rows[rowIndex];
|
|
619
|
+
if (!row || cellIndex < 0 || cellIndex >= row.cells.length) {
|
|
620
|
+
return table;
|
|
621
|
+
}
|
|
622
|
+
if (row.cells.length <= 1) {
|
|
623
|
+
// Last cell in row — out of scope. Caller should use removeTableRowPure.
|
|
624
|
+
return table;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const removed = row.cells[cellIndex]!;
|
|
628
|
+
const removedSpan = Math.max(1, removed.gridSpan ?? 1);
|
|
629
|
+
|
|
630
|
+
// Compute vMerge promotions for the removed cell's logical column range,
|
|
631
|
+
// mirroring the row-removal logic but scoped to one row.
|
|
632
|
+
const grid = buildLogicalGrid(table);
|
|
633
|
+
const promotions: Array<{
|
|
634
|
+
originColumnIndex: number;
|
|
635
|
+
originColumnSpan: number;
|
|
636
|
+
promotedRowIndex: number;
|
|
637
|
+
originCell: TableCellNode;
|
|
638
|
+
}> = [];
|
|
639
|
+
|
|
640
|
+
// Walk the logical columns the removed cell covers. If it's a vMerge origin
|
|
641
|
+
// spanning rows, promote the next-row continue.
|
|
642
|
+
let cursor = row.gridBefore ?? 0;
|
|
643
|
+
for (let i = 0; i < cellIndex; i += 1) {
|
|
644
|
+
cursor += Math.max(1, row.cells[i]!.gridSpan ?? 1);
|
|
645
|
+
}
|
|
646
|
+
const columnStart = cursor;
|
|
647
|
+
const columnEnd = columnStart + removedSpan;
|
|
648
|
+
for (let column = columnStart; column < columnEnd; ) {
|
|
649
|
+
const origin = originAt(grid, rowIndex, column);
|
|
650
|
+
if (!origin) {
|
|
651
|
+
column += 1;
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
if (origin.rowIndex === rowIndex && origin.rowSpan > 1) {
|
|
655
|
+
promotions.push({
|
|
656
|
+
originColumnIndex: origin.columnIndex,
|
|
657
|
+
originColumnSpan: origin.columnSpan,
|
|
658
|
+
promotedRowIndex: rowIndex + 1,
|
|
659
|
+
originCell: origin.cell,
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
column = origin.columnIndex + origin.columnSpan;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const nextRowCells = row.cells.filter((_, index) => index !== cellIndex);
|
|
666
|
+
// Rebalance gridSpan: preceding cell absorbs the delta if it exists,
|
|
667
|
+
// otherwise the new first cell (which was the following cell pre-removal).
|
|
668
|
+
const absorberIndex = cellIndex > 0 ? cellIndex - 1 : 0;
|
|
669
|
+
const absorber = nextRowCells[absorberIndex]!;
|
|
670
|
+
const absorberSpan = Math.max(1, absorber.gridSpan ?? 1);
|
|
671
|
+
nextRowCells[absorberIndex] = {
|
|
672
|
+
...absorber,
|
|
673
|
+
gridSpan: absorberSpan + removedSpan,
|
|
557
674
|
};
|
|
558
675
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
676
|
+
const nextRow: TableRowNode = { ...row, cells: nextRowCells };
|
|
677
|
+
const nextRows = table.rows.map((candidate, index) => {
|
|
678
|
+
if (index === rowIndex) return nextRow;
|
|
679
|
+
if (index === rowIndex + 1 && promotions.length > 0) {
|
|
680
|
+
return applyVMergePromotions(candidate, promotions);
|
|
681
|
+
}
|
|
682
|
+
return candidate;
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
return { ...table, rows: nextRows };
|
|
568
686
|
}
|
|
569
687
|
|
|
570
688
|
function applyVMergePromotions(
|
|
@@ -236,6 +236,26 @@ export function areAnchorsEqual(
|
|
|
236
236
|
return false;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Returns `true` when no step in the mapping can change this anchor's
|
|
241
|
+
* positions. Safe to use as a pre-check before calling `mapAnchor` to avoid
|
|
242
|
+
* allocating a new anchor object in hot per-revision / per-comment remap loops.
|
|
243
|
+
*
|
|
244
|
+
* Conservative: if any step starts at or before the anchor's end position the
|
|
245
|
+
* function returns `false` and the caller must run the full `mapAnchor` path.
|
|
246
|
+
* Detached anchors always return `true` — `mapAnchor` already short-circuits
|
|
247
|
+
* them by reference.
|
|
248
|
+
*/
|
|
249
|
+
export function anchorUnaffectedByMapping(
|
|
250
|
+
anchor: EditorAnchorProjection,
|
|
251
|
+
mapping: TransactionMapping,
|
|
252
|
+
): boolean {
|
|
253
|
+
if (anchor.kind === "detached") return true;
|
|
254
|
+
if (mapping.steps.length === 0) return true;
|
|
255
|
+
const end = anchor.kind === "range" ? anchor.range.to : anchor.at;
|
|
256
|
+
return mapping.steps.every((s) => s.from > end);
|
|
257
|
+
}
|
|
258
|
+
|
|
239
259
|
export function storyTargetsEqual(
|
|
240
260
|
left: EditorStoryTarget | undefined,
|
|
241
261
|
right: EditorStoryTarget | undefined,
|
package/src/index.ts
CHANGED
|
@@ -288,6 +288,22 @@ export type {
|
|
|
288
288
|
EditorStatePolicyMigration,
|
|
289
289
|
EditorStatePartLoadFailure,
|
|
290
290
|
EditorStatePartPersistFailure,
|
|
291
|
+
// Lane 8 Track H (v2.x) — agent-helper widening types. Runtime wiring
|
|
292
|
+
// lands in Phase 2; re-exported from the root as stable type surface so
|
|
293
|
+
// workblocks and wrappers can start consuming the shapes in Phase 1.
|
|
294
|
+
TextProjection,
|
|
295
|
+
TextProjectionLine,
|
|
296
|
+
TextProjectionStoryEntry,
|
|
297
|
+
TextProjectionOptions,
|
|
298
|
+
ChangeAnchor,
|
|
299
|
+
ChangeFilter,
|
|
300
|
+
BatchEditOperation,
|
|
301
|
+
BatchEditOptions,
|
|
302
|
+
BatchEditResult,
|
|
303
|
+
BatchEditEntryResult,
|
|
304
|
+
WorkflowOverlayPatch,
|
|
305
|
+
AiExplanationScopeInput,
|
|
306
|
+
AiExplanationScopeResult,
|
|
291
307
|
} from "./api/public-types.ts";
|
|
292
308
|
|
|
293
309
|
// L7 Phase 2.5 — prerender cache public API. Platforms / ingest workers
|
|
@@ -301,3 +317,15 @@ export type {
|
|
|
301
317
|
PrerenderCounters,
|
|
302
318
|
} from "./runtime/prerender/prerender-document.ts";
|
|
303
319
|
export type { CacheEnvelope } from "./runtime/prerender/cache-envelope.ts";
|
|
320
|
+
|
|
321
|
+
// design-close-chrome Phase 4 / R9 — selection-tool registry extensibility.
|
|
322
|
+
// Hosts can inject entries into the floating selection-tool precedence
|
|
323
|
+
// chain by passing `customSelectionTools` on `WordReviewEditorProps`.
|
|
324
|
+
// Render-side widening (host entries carrying a custom render function)
|
|
325
|
+
// is deferred to Lane 8 API ergonomics.
|
|
326
|
+
export {
|
|
327
|
+
resolveSelectionToolRegistry,
|
|
328
|
+
SELECTION_TOOL_REGISTRY,
|
|
329
|
+
} from "./ui/headless/chrome-registry.ts";
|
|
330
|
+
export type { SelectionToolRegistryEntry } from "./ui/headless/chrome-registry.ts";
|
|
331
|
+
export type { SelectionToolKind } from "./ui/headless/selection-tool-types.ts";
|
package/src/io/docx-session.ts
CHANGED
|
@@ -32,6 +32,15 @@ import {
|
|
|
32
32
|
createDefaultCanonicalDocument,
|
|
33
33
|
createSelectionSnapshot,
|
|
34
34
|
} from "../core/state/editor-state.ts";
|
|
35
|
+
// NOTE: docx-session.ts is intentionally an integration orchestrator, not a
|
|
36
|
+
// pure io module. It coordinates io (OOXML parse/export) with runtime
|
|
37
|
+
// (surface projection, cache envelopes, read-only diagnostics) and
|
|
38
|
+
// presentation. The runtime imports below (surface-projection,
|
|
39
|
+
// prerender/cache-envelope, read-only-diagnostics-runtime) are load-bearing
|
|
40
|
+
// and violate the nominal io→runtime boundary on purpose. The proper
|
|
41
|
+
// long-term fix is to relocate this file to src/session/ (see
|
|
42
|
+
// docs/plans/architecture-lane.md §F2). Do NOT add more runtime imports
|
|
43
|
+
// here without first reading that deferral rationale.
|
|
35
44
|
import { createEditorSurfaceSnapshot } from "../runtime/surface-projection.ts";
|
|
36
45
|
import { MAIN_STORY_TARGET } from "../core/selection/mapping.ts";
|
|
37
46
|
import {
|
|
@@ -73,8 +82,6 @@ import {
|
|
|
73
82
|
WORKFLOW_PAYLOAD_CUSTOM_PROPS_PART_PATH,
|
|
74
83
|
WORKFLOW_PAYLOAD_CUSTOM_PROPS_RELATIONSHIP_TYPE,
|
|
75
84
|
WORKFLOW_PAYLOAD_ITEM_PROPS_CONTENT_TYPE,
|
|
76
|
-
WORKFLOW_PAYLOAD_ITEM_PROPS_PART_PATH,
|
|
77
|
-
WORKFLOW_PAYLOAD_PART_PATH,
|
|
78
85
|
WORKFLOW_PAYLOAD_RELATIONSHIP_TYPE,
|
|
79
86
|
} from "./ooxml/workflow-payload.ts";
|
|
80
87
|
import {
|
|
@@ -457,6 +464,10 @@ export function loadDocxEditorSession(
|
|
|
457
464
|
const parsedNumbering = numberingPartPath
|
|
458
465
|
? parseNumberingXml(
|
|
459
466
|
decodeUtf8(sourcePackage.parts.get(numberingPartPath)?.bytes ?? new Uint8Array()),
|
|
467
|
+
{
|
|
468
|
+
relationships: sourcePackage.parts.get(numberingPartPath)?.relationships,
|
|
469
|
+
partPath: numberingPartPath,
|
|
470
|
+
},
|
|
460
471
|
)
|
|
461
472
|
: createEmptyNumberingCatalog();
|
|
462
473
|
const mediaParts = collectInlineMediaParts(sourcePackage);
|
|
@@ -1329,6 +1340,10 @@ export async function loadDocxEditorSessionAsync(
|
|
|
1329
1340
|
const parsedNumbering = numberingPartPath
|
|
1330
1341
|
? parseNumberingXml(
|
|
1331
1342
|
decodeUtf8(sourcePackage.parts.get(numberingPartPath)?.bytes ?? new Uint8Array()),
|
|
1343
|
+
{
|
|
1344
|
+
relationships: sourcePackage.parts.get(numberingPartPath)?.relationships,
|
|
1345
|
+
partPath: numberingPartPath,
|
|
1346
|
+
},
|
|
1332
1347
|
)
|
|
1333
1348
|
: createEmptyNumberingCatalog();
|
|
1334
1349
|
const mediaParts = collectInlineMediaParts(sourcePackage);
|
|
@@ -2017,6 +2032,7 @@ function exportDocxEditorSession(
|
|
|
2017
2032
|
documentAttributes: state.sourceDocumentAttributes,
|
|
2018
2033
|
media: currentDocument.media as MediaCatalog,
|
|
2019
2034
|
finalSectionProperties: currentDocument.subParts?.finalSectionProperties,
|
|
2035
|
+
namespaceFlavor: options?.exportStrictOoxml ? "strict" : "transitional",
|
|
2020
2036
|
},
|
|
2021
2037
|
);
|
|
2022
2038
|
const revisionDocument = serializeRuntimeRevisionsIntoDocumentXml(
|
|
@@ -2163,6 +2179,9 @@ function exportDocxEditorSession(
|
|
|
2163
2179
|
state.sourcePackage,
|
|
2164
2180
|
sessionState.documentId,
|
|
2165
2181
|
);
|
|
2182
|
+
const internalEditorState = (
|
|
2183
|
+
options as { _editorState?: import("./ooxml/workflow-payload.ts").EditorStatePayload } | undefined
|
|
2184
|
+
)?._editorState;
|
|
2166
2185
|
|
|
2167
2186
|
const exportSession = createExportSession(state.sourcePackage, [
|
|
2168
2187
|
state.sourceDocumentPartPath,
|
|
@@ -2418,7 +2437,6 @@ function exportDocxEditorSession(
|
|
|
2418
2437
|
|
|
2419
2438
|
ensureHostMetadataParts(exportSession, state.sourcePackage, currentDocument);
|
|
2420
2439
|
// Schema 1.2: pass through editorState payload collected by the runtime channel.
|
|
2421
|
-
const internalEditorState = (options as { _editorState?: import("./ooxml/workflow-payload.ts").EditorStatePayload } | undefined)?._editorState;
|
|
2422
2440
|
ensureWorkflowPayloadParts(
|
|
2423
2441
|
exportSession,
|
|
2424
2442
|
sessionState,
|
|
@@ -2728,6 +2746,7 @@ function ensureImportedNumberingCatalogSupportsContent(
|
|
|
2728
2746
|
...catalog.instances,
|
|
2729
2747
|
...syntheticNullCatalog.instances,
|
|
2730
2748
|
},
|
|
2749
|
+
...(catalog.numPicBullets !== undefined ? { numPicBullets: catalog.numPicBullets } : {}),
|
|
2731
2750
|
};
|
|
2732
2751
|
}
|
|
2733
2752
|
|
|
@@ -42,7 +42,10 @@ export class ExportSession {
|
|
|
42
42
|
return normalized;
|
|
43
43
|
}),
|
|
44
44
|
);
|
|
45
|
-
|
|
45
|
+
// Clone bytes only for owned paths; non-owned paths get their bytes from
|
|
46
|
+
// reattachPreservedParts (which always clones from sourcePackage) so the
|
|
47
|
+
// up-front copy here is wasted work for the majority of parts.
|
|
48
|
+
this.workingParts = clonePartsForExport(sourcePackage.parts, this.ownedPaths);
|
|
46
49
|
this.packageRelationships = sourcePackage.manifest.packageRelationships.map(cloneRelationship);
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -137,15 +140,16 @@ export function createExportSession(
|
|
|
137
140
|
return new ExportSession(sourcePackage, ownedOutputPaths);
|
|
138
141
|
}
|
|
139
142
|
|
|
140
|
-
function
|
|
143
|
+
function clonePartsForExport(
|
|
144
|
+
parts: Map<string, OpcPackagePart>,
|
|
145
|
+
ownedPaths: ReadonlySet<string>,
|
|
146
|
+
): Map<string, OpcPackagePart> {
|
|
141
147
|
return new Map(
|
|
142
148
|
[...parts.entries()].map(([path, part]) => [
|
|
143
149
|
path,
|
|
144
|
-
|
|
145
|
-
...part,
|
|
146
|
-
relationships: part.relationships.map(cloneRelationship),
|
|
147
|
-
bytes: new Uint8Array(part.bytes),
|
|
148
|
-
},
|
|
150
|
+
ownedPaths.has(path)
|
|
151
|
+
? { ...part, relationships: part.relationships.map(cloneRelationship), bytes: new Uint8Array(part.bytes) }
|
|
152
|
+
: { ...part, relationships: part.relationships.map(cloneRelationship) },
|
|
149
153
|
]),
|
|
150
154
|
);
|
|
151
155
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lane 7c Slice 7c.4 — O6 Strict-flavor namespace URI constants.
|
|
3
|
+
*
|
|
4
|
+
* OOXML has two URI flavors (ECMA-376 Annex L):
|
|
5
|
+
* - Transitional: classic "http://schemas.openxmlformats.org/…" URIs
|
|
6
|
+
* - Strict: "http://purl.oclc.org/ooxml/…" URIs (ISO 29500 Strict)
|
|
7
|
+
*
|
|
8
|
+
* `nsUris(flavor)` returns the correct set for the requested flavor.
|
|
9
|
+
* All serializers default to "transitional" to preserve backward compatibility.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const TRANSITIONAL_NS = {
|
|
13
|
+
w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
14
|
+
r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
|
15
|
+
a: "http://schemas.openxmlformats.org/drawingml/2006/main",
|
|
16
|
+
pic: "http://schemas.openxmlformats.org/drawingml/2006/picture",
|
|
17
|
+
wp: "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
|
|
18
|
+
mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
export const STRICT_NS = {
|
|
22
|
+
w: "http://purl.oclc.org/ooxml/wordprocessingml/main",
|
|
23
|
+
r: "http://purl.oclc.org/ooxml/officeDocument/relationships",
|
|
24
|
+
a: "http://purl.oclc.org/ooxml/drawingml/main",
|
|
25
|
+
pic: "http://purl.oclc.org/ooxml/drawingml/picture",
|
|
26
|
+
wp: "http://purl.oclc.org/ooxml/drawingml/wordprocessingDrawing",
|
|
27
|
+
mc: "http://purl.oclc.org/ooxml/markup-compatibility",
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
export type NamespaceFlavor = "transitional" | "strict";
|
|
31
|
+
|
|
32
|
+
export type NsUriSet = { readonly w: string; readonly r: string; readonly a: string; readonly pic: string; readonly wp: string; readonly mc: string };
|
|
33
|
+
|
|
34
|
+
export function nsUris(flavor: NamespaceFlavor): NsUriSet {
|
|
35
|
+
return flavor === "strict" ? STRICT_NS : TRANSITIONAL_NS;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Extension namespaces present in Transitional but omitted in Strict (ECMA-376 Annex B). */
|
|
39
|
+
export const TRANSITIONAL_EXTENSION_NAMESPACES = {
|
|
40
|
+
w14: "http://schemas.microsoft.com/office/word/2010/wordml",
|
|
41
|
+
w15: "http://schemas.microsoft.com/office/word/2012/wordml",
|
|
42
|
+
w16cid: "http://schemas.microsoft.com/office/word/2016/wordml/cid",
|
|
43
|
+
w16se: "http://schemas.microsoft.com/office/word/2015/wordml/symex",
|
|
44
|
+
wp14: "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
|
|
45
|
+
wps: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape",
|
|
46
|
+
wpg: "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
|
|
47
|
+
} as const;
|
|
@@ -12,22 +12,10 @@ export function reattachPreservedParts(
|
|
|
12
12
|
continue;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const currentPart = workingParts.get(path);
|
|
21
|
-
if (!currentPart) {
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
currentPart.contentType = sourcePart.contentType;
|
|
26
|
-
currentPart.relationships = sourcePart.relationships.map(cloneRelationship);
|
|
27
|
-
currentPart.relationshipsPartPath = sourcePart.relationshipsPartPath;
|
|
28
|
-
currentPart.compression = sourcePart.compression;
|
|
29
|
-
currentPart.bytes = new Uint8Array(sourcePart.bytes);
|
|
30
|
-
currentPart.crc32 = sourcePart.crc32;
|
|
15
|
+
// Always replace with a fresh clone so round-trip bytes are always the
|
|
16
|
+
// originals, regardless of whether the working entry has a bytes copy or
|
|
17
|
+
// a reference from the copy-on-write constructor path.
|
|
18
|
+
workingParts.set(path, clonePart(sourcePart));
|
|
31
19
|
}
|
|
32
20
|
|
|
33
21
|
const knownPackageRelationshipIds = new Set(packageRelationships.map((relationship) => relationship.id));
|
|
@@ -2,6 +2,8 @@ import type { CommentEntry, CommentThread } from "../../review/store/comment-sto
|
|
|
2
2
|
import type { RevisionParagraphBoundary } from "../ooxml/revision-boundaries.ts";
|
|
3
3
|
import type { ImportedCommentDefinition } from "../ooxml/parse-comments.ts";
|
|
4
4
|
import { escapeXmlAttribute } from "./escape-xml-attribute.ts";
|
|
5
|
+
import { parseXmlWithOffsets as parseXml } from "../ooxml/xml-parser.ts";
|
|
6
|
+
import { localName } from "../ooxml/xml-attr-helpers.ts";
|
|
5
7
|
|
|
6
8
|
interface XmlElementNode {
|
|
7
9
|
type: "element";
|
|
@@ -600,7 +602,7 @@ function parseOoxmlNumericId(value: string): number | undefined {
|
|
|
600
602
|
}
|
|
601
603
|
|
|
602
604
|
export function mapParagraphBoundaries(documentXml: string): ParagraphBoundaryMap[] {
|
|
603
|
-
const root = parseXml(documentXml);
|
|
605
|
+
const root = parseXml(documentXml) as XmlElementNode;
|
|
604
606
|
const documentElement = findChildElement(root, "document");
|
|
605
607
|
const bodyElement = findChildElement(documentElement, "body");
|
|
606
608
|
const paragraphs: ParagraphBoundaryMap[] = [];
|
|
@@ -878,103 +880,6 @@ function openingTagLength(xml: string, start: number): number {
|
|
|
878
880
|
return end - start + 1;
|
|
879
881
|
}
|
|
880
882
|
|
|
881
|
-
function parseXml(xml: string): XmlElementNode {
|
|
882
|
-
const root: XmlElementNode = {
|
|
883
|
-
type: "element",
|
|
884
|
-
name: "#document",
|
|
885
|
-
attributes: {},
|
|
886
|
-
children: [],
|
|
887
|
-
start: 0,
|
|
888
|
-
end: xml.length,
|
|
889
|
-
};
|
|
890
|
-
const stack: XmlElementNode[] = [root];
|
|
891
|
-
const tokenPattern =
|
|
892
|
-
/<!--[\s\S]*?-->|<\?[\s\S]*?\?>|<!DOCTYPE[\s\S]*?>|<!\[CDATA\[[\s\S]*?\]\]>|<[^>]+>|[^<]+/gu;
|
|
893
|
-
|
|
894
|
-
for (const match of xml.matchAll(tokenPattern)) {
|
|
895
|
-
const token = match[0] ?? "";
|
|
896
|
-
const start = match.index ?? 0;
|
|
897
|
-
const end = start + token.length;
|
|
898
|
-
|
|
899
|
-
if (token.startsWith("<?") || token.startsWith("<!DOCTYPE") || token.startsWith("<!--")) {
|
|
900
|
-
continue;
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
if (token.startsWith("<![CDATA[")) {
|
|
904
|
-
const text = token.slice(9, -3);
|
|
905
|
-
stack[stack.length - 1]?.children.push({
|
|
906
|
-
type: "text",
|
|
907
|
-
text,
|
|
908
|
-
start,
|
|
909
|
-
end,
|
|
910
|
-
});
|
|
911
|
-
continue;
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
if (token.startsWith("</")) {
|
|
915
|
-
const node = stack.pop();
|
|
916
|
-
if (!node) {
|
|
917
|
-
throw new Error("Malformed XML: unexpected closing tag.");
|
|
918
|
-
}
|
|
919
|
-
node.end = end;
|
|
920
|
-
continue;
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
if (token.startsWith("<")) {
|
|
924
|
-
const selfClosing = /\/>$/.test(token);
|
|
925
|
-
const tagBody = token.slice(1, token.length - (selfClosing ? 2 : 1)).trim();
|
|
926
|
-
const { name, attributes } = parseTag(tagBody);
|
|
927
|
-
const node: XmlElementNode = {
|
|
928
|
-
type: "element",
|
|
929
|
-
name,
|
|
930
|
-
attributes,
|
|
931
|
-
children: [],
|
|
932
|
-
start,
|
|
933
|
-
end,
|
|
934
|
-
};
|
|
935
|
-
stack[stack.length - 1]?.children.push(node);
|
|
936
|
-
if (!selfClosing) {
|
|
937
|
-
stack.push(node);
|
|
938
|
-
}
|
|
939
|
-
continue;
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
const text = decodeXmlText(token);
|
|
943
|
-
if (text.length > 0) {
|
|
944
|
-
stack[stack.length - 1]?.children.push({
|
|
945
|
-
type: "text",
|
|
946
|
-
text,
|
|
947
|
-
start,
|
|
948
|
-
end,
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
if (stack.length !== 1) {
|
|
954
|
-
throw new Error("Malformed XML: unclosed tag.");
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
return root;
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
function parseTag(tagBody: string): { name: string; attributes: Record<string, string> } {
|
|
961
|
-
const whitespaceIndex = tagBody.search(/\s/u);
|
|
962
|
-
const name = whitespaceIndex === -1 ? tagBody : tagBody.slice(0, whitespaceIndex);
|
|
963
|
-
const rawAttributes = whitespaceIndex === -1 ? "" : tagBody.slice(whitespaceIndex + 1);
|
|
964
|
-
const attributes: Record<string, string> = {};
|
|
965
|
-
const pattern = /([A-Za-z_][A-Za-z0-9:._-]*)\s*=\s*("([^"]*)"|'([^']*)')/gu;
|
|
966
|
-
|
|
967
|
-
for (const match of rawAttributes.matchAll(pattern)) {
|
|
968
|
-
const key = match[1];
|
|
969
|
-
const value = match[3] ?? match[4] ?? "";
|
|
970
|
-
if (key) {
|
|
971
|
-
attributes[key] = decodeXmlText(value);
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
return { name, attributes };
|
|
976
|
-
}
|
|
977
|
-
|
|
978
883
|
function findChildElement(node: XmlElementNode, name: string): XmlElementNode {
|
|
979
884
|
const match = node.children.find(
|
|
980
885
|
(child): child is XmlElementNode =>
|
|
@@ -988,36 +893,3 @@ function findChildElement(node: XmlElementNode, name: string): XmlElementNode {
|
|
|
988
893
|
return match;
|
|
989
894
|
}
|
|
990
895
|
|
|
991
|
-
function localName(name: string): string {
|
|
992
|
-
const index = name.indexOf(":");
|
|
993
|
-
return index === -1 ? name : name.slice(index + 1);
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
function decodeXmlText(text: string): string {
|
|
997
|
-
return text.replace(
|
|
998
|
-
/&(?:#x([0-9A-Fa-f]+)|#([0-9]+)|([A-Za-z]+));/gu,
|
|
999
|
-
(_, hex, dec, named) => {
|
|
1000
|
-
if (hex) {
|
|
1001
|
-
return String.fromCodePoint(Number.parseInt(hex, 16));
|
|
1002
|
-
}
|
|
1003
|
-
if (dec) {
|
|
1004
|
-
return String.fromCodePoint(Number.parseInt(dec, 10));
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
switch (named) {
|
|
1008
|
-
case "amp":
|
|
1009
|
-
return "&";
|
|
1010
|
-
case "lt":
|
|
1011
|
-
return "<";
|
|
1012
|
-
case "gt":
|
|
1013
|
-
return ">";
|
|
1014
|
-
case "quot":
|
|
1015
|
-
return "\"";
|
|
1016
|
-
case "apos":
|
|
1017
|
-
return "'";
|
|
1018
|
-
default:
|
|
1019
|
-
return `&${named};`;
|
|
1020
|
-
}
|
|
1021
|
-
},
|
|
1022
|
-
);
|
|
1023
|
-
}
|