@beyondwork/docx-react-component 1.0.54 → 1.0.56
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 +43 -32
- package/src/api/public-types.ts +90 -0
- package/src/index.ts +5 -0
- package/src/io/docx-session.ts +7 -7
- package/src/io/normalize/normalize-text.ts +1 -0
- package/src/io/ooxml/parse-field-switches.ts +134 -0
- package/src/io/ooxml/parse-fields.ts +28 -2
- package/src/model/canonical-document.ts +13 -2
- package/src/runtime/chart/chart-model-store.ts +88 -0
- package/src/runtime/chart/chart-snapshot.ts +239 -0
- package/src/runtime/document-runtime.ts +96 -8
- package/src/runtime/page-number-format.ts +207 -0
- package/src/runtime/surface-projection.ts +32 -3
- package/src/ui/WordReviewEditor.tsx +51 -0
- package/src/ui-tailwind/editor-surface/chart-node-view.tsx +90 -0
- package/src/ui-tailwind/editor-surface/pm-schema.ts +8 -0
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +14 -1
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +2 -1
|
@@ -220,6 +220,12 @@ import {
|
|
|
220
220
|
getCursorColorForUser,
|
|
221
221
|
setLocalCursorState,
|
|
222
222
|
} from "../runtime/collab/remote-cursor-awareness.ts";
|
|
223
|
+
import {
|
|
224
|
+
stableChartId,
|
|
225
|
+
} from "../runtime/chart/chart-model-store.ts";
|
|
226
|
+
import {
|
|
227
|
+
projectChartSnapshot,
|
|
228
|
+
} from "../runtime/chart/chart-snapshot.ts";
|
|
223
229
|
|
|
224
230
|
export {
|
|
225
231
|
__createFallbackRuntime,
|
|
@@ -466,6 +472,40 @@ async function runConvertScopesToExternal(args: {
|
|
|
466
472
|
|
|
467
473
|
// ---------------------------------------------------------------------------
|
|
468
474
|
|
|
475
|
+
type CanonicalDocType = ReturnType<WordReviewEditorRuntime["getCanonicalDocument"]>;
|
|
476
|
+
|
|
477
|
+
function collectChartSnapshots(doc: CanonicalDocType): import("../api/public-types").ChartSnapshot[] {
|
|
478
|
+
const results: import("../api/public-types").ChartSnapshot[] = [];
|
|
479
|
+
collectChartSnapshotsFromBlocks(doc.content.children, results);
|
|
480
|
+
return results;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function collectChartSnapshotsFromBlocks(
|
|
484
|
+
blocks: CanonicalDocType["content"]["children"],
|
|
485
|
+
results: import("../api/public-types").ChartSnapshot[],
|
|
486
|
+
): void {
|
|
487
|
+
for (const block of blocks) {
|
|
488
|
+
if (block.type === "paragraph") {
|
|
489
|
+
for (const inline of block.children) {
|
|
490
|
+
if (inline.type === "chart_preview" && inline.parsedData) {
|
|
491
|
+
const chartId = stableChartId(inline.rawXml);
|
|
492
|
+
results.push(projectChartSnapshot(chartId, inline.parsedData));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
} else if (block.type === "table") {
|
|
496
|
+
for (const row of block.rows) {
|
|
497
|
+
for (const cell of row.cells) {
|
|
498
|
+
collectChartSnapshotsFromBlocks(cell.children, results);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
} else if (block.type === "sdt" || block.type === "custom_xml") {
|
|
502
|
+
collectChartSnapshotsFromBlocks(block.children, results);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// ---------------------------------------------------------------------------
|
|
508
|
+
|
|
469
509
|
export function __createWordReviewEditorRefBridge(
|
|
470
510
|
runtime: WordReviewEditorRuntime,
|
|
471
511
|
mountedSurface?: TwProseMirrorSurfaceRef | null,
|
|
@@ -514,6 +554,12 @@ export function __createWordReviewEditorRefBridge(
|
|
|
514
554
|
getRenderSnapshot: () => clonePublicValue(runtime.getRenderSnapshot()),
|
|
515
555
|
getCompatibilityReport: () => runtime.getCompatibilityReport(),
|
|
516
556
|
getWarnings: () => runtime.getWarnings(),
|
|
557
|
+
getChartSnapshot: (chartId) => {
|
|
558
|
+
return collectChartSnapshots(runtime.getCanonicalDocument()).find(
|
|
559
|
+
(s) => s.chartId === chartId,
|
|
560
|
+
) ?? null;
|
|
561
|
+
},
|
|
562
|
+
getChartSnapshots: () => collectChartSnapshots(runtime.getCanonicalDocument()),
|
|
517
563
|
getCommentSidebarSnapshot: () =>
|
|
518
564
|
clonePublicValue(runtime.getRenderSnapshot().comments),
|
|
519
565
|
getTrackedChangesSnapshot: () =>
|
|
@@ -1537,6 +1583,11 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1537
1583
|
getRenderSnapshot: () => clonePublicValue(activeRuntime.getRenderSnapshot()),
|
|
1538
1584
|
getCompatibilityReport: () => activeRuntime.getCompatibilityReport(),
|
|
1539
1585
|
getWarnings: () => activeRuntime.getWarnings(),
|
|
1586
|
+
getChartSnapshot: (chartId) =>
|
|
1587
|
+
collectChartSnapshots(activeRuntime.getCanonicalDocument()).find(
|
|
1588
|
+
(s) => s.chartId === chartId,
|
|
1589
|
+
) ?? null,
|
|
1590
|
+
getChartSnapshots: () => collectChartSnapshots(activeRuntime.getCanonicalDocument()),
|
|
1540
1591
|
getCommentSidebarSnapshot: () =>
|
|
1541
1592
|
clonePublicValue(activeRuntime.getRenderSnapshot().comments),
|
|
1542
1593
|
getTrackedChangesSnapshot: () =>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProseMirror NodeView for `chart_atom` (Stage 6).
|
|
3
|
+
*
|
|
4
|
+
* When `parsedChartId` is present in the node's attrs, looks up the
|
|
5
|
+
* `ChartStoreEntry` from `chartModelStore` and renders `ChartSurface` into the
|
|
6
|
+
* PM-provided DOM node using `ReactDOM.createRoot`. Falls back to the static
|
|
7
|
+
* `toDOM` output (bitmap preview or badge) when no parsed model is available.
|
|
8
|
+
*
|
|
9
|
+
* Each ChartNodeView instance owns a separate React root so chart renders are
|
|
10
|
+
* isolated and don't interfere with the host React tree. The root is unmounted
|
|
11
|
+
* in `destroy()`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import React from "react";
|
|
15
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
16
|
+
import type { Node as PMNode } from "prosemirror-model";
|
|
17
|
+
import type { NodeViewConstructor } from "prosemirror-view";
|
|
18
|
+
import { ChartSurface } from "../chart/ChartSurface.tsx";
|
|
19
|
+
import { chartModelStore } from "../../runtime/chart/chart-model-store.ts";
|
|
20
|
+
|
|
21
|
+
const DEFAULT_WIDTH = 576;
|
|
22
|
+
const DEFAULT_HEIGHT = 336;
|
|
23
|
+
|
|
24
|
+
class ChartNodeViewInstance {
|
|
25
|
+
readonly dom: HTMLElement;
|
|
26
|
+
private _root: Root | null = null;
|
|
27
|
+
|
|
28
|
+
constructor(node: PMNode) {
|
|
29
|
+
this.dom = document.createElement("span");
|
|
30
|
+
this.dom.setAttribute("contenteditable", "false");
|
|
31
|
+
this.dom.setAttribute("data-node-type", "chart_atom");
|
|
32
|
+
this.dom.className = "inline-block align-baseline mx-0.5 max-w-full";
|
|
33
|
+
this._mount(node);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private _mount(node: PMNode): void {
|
|
37
|
+
const parsedChartId = node.attrs.parsedChartId as string | null;
|
|
38
|
+
if (!parsedChartId) return;
|
|
39
|
+
|
|
40
|
+
const entry = chartModelStore.get(parsedChartId);
|
|
41
|
+
if (!entry) return;
|
|
42
|
+
|
|
43
|
+
const width = (node.attrs.widthPx as number | null) ?? entry.widthPx ?? DEFAULT_WIDTH;
|
|
44
|
+
const height = (node.attrs.heightPx as number | null) ?? entry.heightPx ?? DEFAULT_HEIGHT;
|
|
45
|
+
|
|
46
|
+
const el = React.createElement(ChartSurface, {
|
|
47
|
+
model: entry.model,
|
|
48
|
+
width,
|
|
49
|
+
height,
|
|
50
|
+
theme: entry.theme,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!this._root) {
|
|
54
|
+
this._root = createRoot(this.dom);
|
|
55
|
+
}
|
|
56
|
+
this._root.render(el);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
update(node: PMNode): boolean {
|
|
60
|
+
const parsedChartId = node.attrs.parsedChartId as string | null;
|
|
61
|
+
if (!parsedChartId) return false;
|
|
62
|
+
this._mount(node);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
destroy(): void {
|
|
67
|
+
if (this._root) {
|
|
68
|
+
this._root.unmount();
|
|
69
|
+
this._root = null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
stopEvent(): boolean {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
ignoreMutation(): boolean {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* NodeView factory map for `chart_atom`. Merge with `tableNodeViews` when
|
|
84
|
+
* constructing the `EditorView`:
|
|
85
|
+
*
|
|
86
|
+
* new EditorView(mount, { nodeViews: { ...tableNodeViews, ...chartNodeViews }, ... })
|
|
87
|
+
*/
|
|
88
|
+
export const chartNodeViews: { [node: string]: NodeViewConstructor } = {
|
|
89
|
+
chart_atom: (node: PMNode) => new ChartNodeViewInstance(node),
|
|
90
|
+
};
|
|
@@ -653,9 +653,14 @@ export const editorSchema = new Schema({
|
|
|
653
653
|
previewMediaId: { default: null },
|
|
654
654
|
previewSrc: { default: null },
|
|
655
655
|
detail: { default: null },
|
|
656
|
+
/** Stage 6: stable chart ID keying into chartModelStore for native SVG render. */
|
|
657
|
+
parsedChartId: { default: null },
|
|
658
|
+
widthPx: { default: null },
|
|
659
|
+
heightPx: { default: null },
|
|
656
660
|
},
|
|
657
661
|
toDOM(node) {
|
|
658
662
|
const previewSrc = node.attrs.previewSrc as string | null;
|
|
663
|
+
const previewMediaId = node.attrs.previewMediaId as string | null;
|
|
659
664
|
const detail = (node.attrs.detail as string) ?? "Chart";
|
|
660
665
|
if (previewSrc) {
|
|
661
666
|
// Bitmap-backed: render the fallback image Word cached in mc:Fallback.
|
|
@@ -694,6 +699,7 @@ export const editorSchema = new Schema({
|
|
|
694
699
|
{
|
|
695
700
|
class: "inline-flex items-center gap-1 mx-0.5 px-1.5 py-0.5 rounded text-xs text-blue-700 bg-blue-50 border border-blue-200",
|
|
696
701
|
"data-node-type": "chart_atom",
|
|
702
|
+
...(previewMediaId ? { "data-preview-media-id": previewMediaId } : {}),
|
|
697
703
|
contenteditable: "false",
|
|
698
704
|
title: detail,
|
|
699
705
|
},
|
|
@@ -714,6 +720,7 @@ export const editorSchema = new Schema({
|
|
|
714
720
|
},
|
|
715
721
|
toDOM(node) {
|
|
716
722
|
const previewSrc = node.attrs.previewSrc as string | null;
|
|
723
|
+
const previewMediaId = node.attrs.previewMediaId as string | null;
|
|
717
724
|
const detail = (node.attrs.detail as string) ?? "SmartArt";
|
|
718
725
|
if (previewSrc) {
|
|
719
726
|
return [
|
|
@@ -749,6 +756,7 @@ export const editorSchema = new Schema({
|
|
|
749
756
|
{
|
|
750
757
|
class: "inline-flex items-center gap-1 mx-0.5 px-1.5 py-0.5 rounded text-xs text-purple-700 bg-purple-50 border border-purple-200",
|
|
751
758
|
"data-node-type": "smartart_atom",
|
|
759
|
+
...(previewMediaId ? { "data-preview-media-id": previewMediaId } : {}),
|
|
752
760
|
contenteditable: "false",
|
|
753
761
|
title: detail,
|
|
754
762
|
},
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
} from "../../api/public-types";
|
|
12
12
|
import { editorSchema } from "./pm-schema";
|
|
13
13
|
import { buildPositionMap, type PositionMap } from "./pm-position-map";
|
|
14
|
+
import { chartModelStore } from "../../runtime/chart/chart-model-store.ts";
|
|
14
15
|
|
|
15
16
|
export interface PMStateResult {
|
|
16
17
|
state: EditorState;
|
|
@@ -667,7 +668,6 @@ function buildOpaqueInlineOrComplexAtom(
|
|
|
667
668
|
): PMNode {
|
|
668
669
|
const label = segment.label;
|
|
669
670
|
const detail = segment.detail;
|
|
670
|
-
|
|
671
671
|
if (segment.presentation === "text-box" || segment.presentation === "checkbox") {
|
|
672
672
|
return editorSchema.nodes.opaque_inline.create({
|
|
673
673
|
fragmentId: segment.fragmentId,
|
|
@@ -691,10 +691,23 @@ function buildOpaqueInlineOrComplexAtom(
|
|
|
691
691
|
: null;
|
|
692
692
|
|
|
693
693
|
if (label === "Embedded chart") {
|
|
694
|
+
const parsedChartId = segment.parsedChartId ?? null;
|
|
695
|
+
let widthPx: number | null = null;
|
|
696
|
+
let heightPx: number | null = null;
|
|
697
|
+
if (parsedChartId) {
|
|
698
|
+
const entry = chartModelStore.get(parsedChartId);
|
|
699
|
+
if (entry) {
|
|
700
|
+
widthPx = entry.widthPx;
|
|
701
|
+
heightPx = entry.heightPx;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
694
704
|
return editorSchema.nodes.chart_atom.create({
|
|
695
705
|
previewMediaId: segment.previewMediaId ?? null,
|
|
696
706
|
previewSrc,
|
|
697
707
|
detail,
|
|
708
|
+
parsedChartId,
|
|
709
|
+
widthPx,
|
|
710
|
+
heightPx,
|
|
698
711
|
});
|
|
699
712
|
}
|
|
700
713
|
if (label === "SmartArt diagram") {
|
|
@@ -73,6 +73,7 @@ import {
|
|
|
73
73
|
createSurfaceDocumentBuildKey,
|
|
74
74
|
} from "./surface-build-keys";
|
|
75
75
|
import { tableNodeViews } from "./tw-table-node-view";
|
|
76
|
+
import { chartNodeViews } from "./chart-node-view.tsx";
|
|
76
77
|
import type { SelectionToolbarAnchor } from "../../ui/headless/selection-toolbar-model";
|
|
77
78
|
import type { MediaPreviewDescriptor } from "./pm-state-from-snapshot";
|
|
78
79
|
|
|
@@ -742,7 +743,7 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
742
743
|
if (!viewRef.current) {
|
|
743
744
|
const view = new EditorView(mountRef.current, {
|
|
744
745
|
state,
|
|
745
|
-
nodeViews: tableNodeViews,
|
|
746
|
+
nodeViews: { ...tableNodeViews, ...chartNodeViews },
|
|
746
747
|
editable: () => canEdit,
|
|
747
748
|
decorations: () => decorations,
|
|
748
749
|
});
|