@beyondwork/docx-react-component 1.0.18 → 1.0.20
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 +8 -2
- package/package.json +24 -34
- package/src/api/README.md +5 -1
- package/src/api/public-types.ts +710 -4
- package/src/api/session-state.ts +60 -0
- package/src/core/commands/formatting-commands.ts +2 -1
- package/src/core/commands/image-commands.ts +147 -0
- package/src/core/commands/index.ts +19 -3
- package/src/core/commands/list-commands.ts +231 -36
- package/src/core/commands/paragraph-layout-commands.ts +339 -0
- package/src/core/commands/section-layout-commands.ts +680 -0
- package/src/core/commands/style-commands.ts +262 -0
- package/src/core/search/search-text.ts +357 -0
- package/src/core/selection/mapping.ts +41 -0
- package/src/core/state/editor-state.ts +4 -1
- package/src/index.ts +51 -0
- package/src/io/docx-session.ts +623 -56
- package/src/io/export/serialize-comments.ts +104 -34
- package/src/io/export/serialize-footnotes.ts +198 -1
- package/src/io/export/serialize-headers-footers.ts +203 -10
- package/src/io/export/serialize-main-document.ts +285 -8
- package/src/io/export/serialize-numbering.ts +28 -7
- package/src/io/export/split-review-boundaries.ts +181 -19
- package/src/io/normalize/normalize-text.ts +144 -32
- package/src/io/ooxml/highlight-colors.ts +39 -0
- package/src/io/ooxml/numbering-sentinels.ts +44 -0
- package/src/io/ooxml/parse-comments.ts +85 -19
- package/src/io/ooxml/parse-fields.ts +396 -0
- package/src/io/ooxml/parse-footnotes.ts +452 -22
- package/src/io/ooxml/parse-headers-footers.ts +657 -29
- package/src/io/ooxml/parse-inline-media.ts +30 -0
- package/src/io/ooxml/parse-main-document.ts +807 -20
- package/src/io/ooxml/parse-numbering.ts +7 -0
- package/src/io/ooxml/parse-revisions.ts +317 -38
- package/src/io/ooxml/parse-settings.ts +184 -0
- package/src/io/ooxml/parse-shapes.ts +25 -0
- package/src/io/ooxml/parse-styles.ts +463 -0
- package/src/io/ooxml/parse-theme.ts +32 -0
- package/src/legal/bookmarks.ts +44 -0
- package/src/legal/cross-references.ts +59 -1
- package/src/model/canonical-document.ts +250 -4
- package/src/model/cds-1.0.0.ts +13 -0
- package/src/model/snapshot.ts +87 -2
- package/src/review/store/revision-store.ts +6 -0
- package/src/review/store/revision-types.ts +1 -0
- package/src/runtime/document-layout.ts +332 -0
- package/src/runtime/document-navigation.ts +603 -0
- package/src/runtime/document-runtime.ts +1754 -78
- package/src/runtime/document-search.ts +145 -0
- package/src/runtime/numbering-prefix.ts +47 -26
- package/src/runtime/page-layout-estimation.ts +212 -0
- package/src/runtime/read-only-diagnostics-runtime.ts +9 -0
- package/src/runtime/session-capabilities.ts +35 -3
- package/src/runtime/story-context.ts +164 -0
- package/src/runtime/story-targeting.ts +162 -0
- package/src/runtime/surface-projection.ts +324 -36
- package/src/runtime/table-schema.ts +89 -7
- package/src/runtime/view-state.ts +477 -0
- package/src/runtime/workflow-markup.ts +349 -0
- package/src/ui/WordReviewEditor.tsx +2469 -1344
- package/src/ui/browser-export.ts +52 -0
- package/src/ui/editor-command-bag.ts +120 -0
- package/src/ui/editor-runtime-boundary.ts +1422 -0
- package/src/ui/editor-shell-view.tsx +134 -0
- package/src/ui/editor-surface-controller.tsx +51 -0
- package/src/ui/headless/preserve-editor-selection.ts +5 -0
- package/src/ui/headless/revision-decoration-model.ts +4 -4
- package/src/ui/headless/selection-helpers.ts +20 -0
- package/src/ui/headless/selection-toolbar-model.ts +22 -0
- package/src/ui/headless/use-editor-keyboard.ts +6 -1
- package/src/ui/runtime-snapshot-selectors.ts +197 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +18 -2
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +129 -0
- package/src/ui-tailwind/chrome/tw-layout-panel.tsx +114 -0
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +34 -0
- package/src/ui-tailwind/chrome/tw-page-ruler.tsx +386 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +150 -14
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +128 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +179 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +46 -7
- package/src/ui-tailwind/editor-surface/pm-contextual-ui.ts +31 -0
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +35 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +3 -3
- package/src/ui-tailwind/editor-surface/pm-schema.ts +186 -13
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +191 -68
- package/src/ui-tailwind/editor-surface/search-plugin.ts +19 -68
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +51 -0
- package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +11 -0
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +7 -1
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +528 -85
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +0 -1
- package/src/ui-tailwind/index.ts +2 -1
- package/src/ui-tailwind/page-chrome-model.ts +27 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +277 -147
- package/src/ui-tailwind/review/tw-health-panel.tsx +31 -2
- package/src/ui-tailwind/review/tw-review-rail.tsx +8 -8
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +15 -15
- package/src/ui-tailwind/theme/editor-theme.css +127 -0
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +4 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +829 -12
- package/src/ui-tailwind/tw-review-workspace.tsx +1238 -42
- package/src/validation/compatibility-engine.ts +119 -24
- package/src/validation/compatibility-report.ts +1 -0
- package/src/validation/diagnostics.ts +1 -0
- package/src/validation/docx-comment-proof.ts +707 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment, type Node as PMNode } from "prosemirror-model";
|
|
2
|
-
import { EditorState, type Plugin, Selection, TextSelection } from "prosemirror-state";
|
|
2
|
+
import { EditorState, NodeSelection, type Plugin, Selection, TextSelection } from "prosemirror-state";
|
|
3
3
|
|
|
4
4
|
import type {
|
|
5
5
|
EditorSurfaceSnapshot,
|
|
@@ -17,6 +17,12 @@ export interface PMStateResult {
|
|
|
17
17
|
positionMap: PositionMap;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export interface MediaPreviewDescriptor {
|
|
21
|
+
src: string;
|
|
22
|
+
widthEmu?: number;
|
|
23
|
+
heightEmu?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
/**
|
|
21
27
|
* Create a ProseMirror EditorState from a runtime surface snapshot.
|
|
22
28
|
*
|
|
@@ -27,30 +33,11 @@ export function createPMStateFromSnapshot(
|
|
|
27
33
|
surface: EditorSurfaceSnapshot,
|
|
28
34
|
selection: SelectionSnapshot,
|
|
29
35
|
plugins: Plugin[],
|
|
36
|
+
mediaPreviews: Record<string, MediaPreviewDescriptor> = {},
|
|
30
37
|
): PMStateResult {
|
|
31
|
-
const doc = buildPMDoc(surface);
|
|
38
|
+
const doc = buildPMDoc(surface, mediaPreviews);
|
|
32
39
|
const positionMap = buildPositionMap(surface);
|
|
33
|
-
|
|
34
|
-
// Convert runtime selection to PM selection
|
|
35
|
-
const pmAnchor = clamp(
|
|
36
|
-
positionMap.runtimeToPm(selection.anchor),
|
|
37
|
-
1,
|
|
38
|
-
positionMap.pmDocSize - 1,
|
|
39
|
-
);
|
|
40
|
-
const pmHead = clamp(
|
|
41
|
-
positionMap.runtimeToPm(selection.head),
|
|
42
|
-
1,
|
|
43
|
-
positionMap.pmDocSize - 1,
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
let pmSelection: Selection;
|
|
47
|
-
try {
|
|
48
|
-
pmSelection = TextSelection.between(doc.resolve(pmAnchor), doc.resolve(pmHead));
|
|
49
|
-
} catch {
|
|
50
|
-
// If the mapped runtime selection is invalid or lands in a non-text block,
|
|
51
|
-
// let ProseMirror choose the nearest valid starting selection.
|
|
52
|
-
pmSelection = Selection.atStart(doc);
|
|
53
|
-
}
|
|
40
|
+
const pmSelection = createPMSelectionFromSnapshot(doc, positionMap, selection);
|
|
54
41
|
|
|
55
42
|
const state = EditorState.create({
|
|
56
43
|
doc,
|
|
@@ -61,21 +48,70 @@ export function createPMStateFromSnapshot(
|
|
|
61
48
|
return { state, positionMap };
|
|
62
49
|
}
|
|
63
50
|
|
|
64
|
-
function
|
|
65
|
-
|
|
51
|
+
export function createPMSelectionFromSnapshot(
|
|
52
|
+
doc: PMNode,
|
|
53
|
+
positionMap: PositionMap,
|
|
54
|
+
selection: SelectionSnapshot,
|
|
55
|
+
): Selection {
|
|
56
|
+
const pmAnchor = clamp(positionMap.runtimeToPm(selection.anchor), 1, positionMap.pmDocSize - 1);
|
|
57
|
+
const pmHead = clamp(positionMap.runtimeToPm(selection.head), 1, positionMap.pmDocSize - 1);
|
|
58
|
+
try {
|
|
59
|
+
if (selection.activeRange.kind === "node") {
|
|
60
|
+
const pmNodePos = clamp(pmAnchor - 1, 0, positionMap.pmDocSize - 2);
|
|
61
|
+
return NodeSelection.create(doc, pmNodePos);
|
|
62
|
+
}
|
|
66
63
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
} else if (block.kind === "sdt_block") {
|
|
73
|
-
blocks.push(buildSdtBlock(block));
|
|
74
|
-
} else {
|
|
75
|
-
blocks.push(buildOpaqueBlock(block));
|
|
64
|
+
const forward = selection.head >= selection.anchor;
|
|
65
|
+
const resolvedAnchor = resolveInlineBoundary(doc, pmAnchor, forward ? 1 : -1);
|
|
66
|
+
const resolvedHead = resolveInlineBoundary(doc, pmHead, forward ? -1 : 1);
|
|
67
|
+
if (resolvedAnchor !== null && resolvedHead !== null) {
|
|
68
|
+
return TextSelection.create(doc, resolvedAnchor, resolvedHead);
|
|
76
69
|
}
|
|
70
|
+
|
|
71
|
+
const $anchor = doc.resolve(pmAnchor);
|
|
72
|
+
const $head = doc.resolve(pmHead);
|
|
73
|
+
return (
|
|
74
|
+
Selection.findFrom($anchor, 1, true) ??
|
|
75
|
+
Selection.findFrom($anchor, -1, true) ??
|
|
76
|
+
Selection.findFrom($head, 1, true) ??
|
|
77
|
+
Selection.findFrom($head, -1, true) ??
|
|
78
|
+
Selection.near($anchor, 1)
|
|
79
|
+
);
|
|
80
|
+
} catch {
|
|
81
|
+
// If the mapped runtime selection is invalid or lands in a non-text block,
|
|
82
|
+
// let ProseMirror choose the nearest valid starting selection.
|
|
83
|
+
return Selection.atStart(doc);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function resolveInlineBoundary(
|
|
88
|
+
doc: PMNode,
|
|
89
|
+
pos: number,
|
|
90
|
+
bias: -1 | 1,
|
|
91
|
+
): number | null {
|
|
92
|
+
const $pos = doc.resolve(pos);
|
|
93
|
+
if ($pos.parent.inlineContent) {
|
|
94
|
+
return pos;
|
|
77
95
|
}
|
|
78
96
|
|
|
97
|
+
const candidate =
|
|
98
|
+
Selection.findFrom($pos, bias, true) ??
|
|
99
|
+
Selection.findFrom($pos, -bias, true) ??
|
|
100
|
+
Selection.near($pos, bias);
|
|
101
|
+
|
|
102
|
+
if (candidate.$from.parent.inlineContent || candidate.$to.parent.inlineContent) {
|
|
103
|
+
return bias < 0 ? candidate.to : candidate.from;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function buildPMDoc(
|
|
110
|
+
surface: EditorSurfaceSnapshot,
|
|
111
|
+
mediaPreviews: Record<string, MediaPreviewDescriptor>,
|
|
112
|
+
): PMNode {
|
|
113
|
+
const blocks = buildPMBlocks(surface.blocks, mediaPreviews);
|
|
114
|
+
|
|
79
115
|
// Ensure at least one block (PM requires non-empty doc)
|
|
80
116
|
if (blocks.length === 0) {
|
|
81
117
|
blocks.push(editorSchema.nodes.paragraph.create());
|
|
@@ -84,8 +120,38 @@ function buildPMDoc(surface: EditorSurfaceSnapshot): PMNode {
|
|
|
84
120
|
return editorSchema.nodes.doc.create(null, Fragment.from(blocks));
|
|
85
121
|
}
|
|
86
122
|
|
|
123
|
+
function buildPMBlocks(
|
|
124
|
+
blocks: SurfaceBlockSnapshot[],
|
|
125
|
+
mediaPreviews: Record<string, MediaPreviewDescriptor>,
|
|
126
|
+
): PMNode[] {
|
|
127
|
+
const nodes: PMNode[] = [];
|
|
128
|
+
|
|
129
|
+
for (let index = 0; index < blocks.length; index += 1) {
|
|
130
|
+
const block = blocks[index];
|
|
131
|
+
const previousBlock = index > 0 ? blocks[index - 1] : null;
|
|
132
|
+
const nextBlock = blocks[index + 1];
|
|
133
|
+
const previousParagraph = previousBlock?.kind === "paragraph" ? previousBlock : null;
|
|
134
|
+
const nextParagraph = nextBlock?.kind === "paragraph" ? nextBlock : null;
|
|
135
|
+
|
|
136
|
+
if (block.kind === "paragraph") {
|
|
137
|
+
nodes.push(buildParagraph(block, previousParagraph, nextParagraph, mediaPreviews));
|
|
138
|
+
} else if (block.kind === "table") {
|
|
139
|
+
nodes.push(buildTable(block, mediaPreviews));
|
|
140
|
+
} else if (block.kind === "sdt_block") {
|
|
141
|
+
nodes.push(buildSdtBlock(block, mediaPreviews));
|
|
142
|
+
} else {
|
|
143
|
+
nodes.push(buildOpaqueBlock(block));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return nodes;
|
|
148
|
+
}
|
|
149
|
+
|
|
87
150
|
function buildParagraph(
|
|
88
151
|
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
152
|
+
previousParagraph: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }> | null,
|
|
153
|
+
nextParagraph: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }> | null,
|
|
154
|
+
mediaPreviews: Record<string, MediaPreviewDescriptor>,
|
|
89
155
|
): PMNode {
|
|
90
156
|
const content: PMNode[] = [];
|
|
91
157
|
const tabStops = block.tabStops ?? [];
|
|
@@ -111,11 +177,28 @@ function buildParagraph(
|
|
|
111
177
|
);
|
|
112
178
|
tabIndex++;
|
|
113
179
|
} else {
|
|
114
|
-
const nodes = buildInlineContent(segment);
|
|
180
|
+
const nodes = buildInlineContent(segment, mediaPreviews);
|
|
115
181
|
content.push(...nodes);
|
|
116
182
|
}
|
|
117
183
|
}
|
|
118
184
|
|
|
185
|
+
const listContinuation = Boolean(
|
|
186
|
+
block.numbering &&
|
|
187
|
+
nextParagraph?.numbering &&
|
|
188
|
+
nextParagraph.numbering.numberingInstanceId === block.numbering.numberingInstanceId &&
|
|
189
|
+
nextParagraph.numbering.level === block.numbering.level,
|
|
190
|
+
);
|
|
191
|
+
const contextualSpacingAfter = Boolean(
|
|
192
|
+
block.contextualSpacing &&
|
|
193
|
+
nextParagraph?.contextualSpacing &&
|
|
194
|
+
(nextParagraph.styleId ?? "__default__") === (block.styleId ?? "__default__"),
|
|
195
|
+
);
|
|
196
|
+
const contextualSpacingBefore = Boolean(
|
|
197
|
+
block.contextualSpacing &&
|
|
198
|
+
previousParagraph?.contextualSpacing &&
|
|
199
|
+
(previousParagraph.styleId ?? "__default__") === (block.styleId ?? "__default__"),
|
|
200
|
+
);
|
|
201
|
+
|
|
119
202
|
return editorSchema.nodes.paragraph.create(
|
|
120
203
|
{
|
|
121
204
|
styleId: block.styleId ?? null,
|
|
@@ -124,14 +207,22 @@ function buildParagraph(
|
|
|
124
207
|
numberingPrefix:
|
|
125
208
|
(block as typeof block & { numberingPrefix?: string }).numberingPrefix ??
|
|
126
209
|
null,
|
|
210
|
+
numberingSuffix:
|
|
211
|
+
(block as typeof block & { numberingSuffix?: string }).numberingSuffix ??
|
|
212
|
+
null,
|
|
127
213
|
alignment: block.alignment ?? null,
|
|
128
214
|
spacingBefore: block.spacing?.before ?? null,
|
|
129
215
|
spacingAfter: block.spacing?.after ?? null,
|
|
130
216
|
lineSpacing: block.spacing?.line ?? null,
|
|
131
217
|
lineRule: block.spacing?.lineRule ?? null,
|
|
218
|
+
contextualSpacing: block.contextualSpacing ?? null,
|
|
219
|
+
listContinuation: listContinuation || null,
|
|
220
|
+
contextualSpacingBefore: contextualSpacingBefore || null,
|
|
221
|
+
contextualSpacingAfter: contextualSpacingAfter || null,
|
|
132
222
|
indentLeft: block.indentation?.left ?? null,
|
|
133
223
|
indentRight: block.indentation?.right ?? null,
|
|
134
224
|
indentFirstLine: block.indentation?.firstLine ?? null,
|
|
225
|
+
indentHanging: block.indentation?.hanging ?? null,
|
|
135
226
|
shadingFill: block.shading?.fill ?? null,
|
|
136
227
|
borderTop: (block.borders as Record<string, unknown>)?.top ?? null,
|
|
137
228
|
borderBottom: (block.borders as Record<string, unknown>)?.bottom ?? null,
|
|
@@ -145,7 +236,10 @@ function buildParagraph(
|
|
|
145
236
|
);
|
|
146
237
|
}
|
|
147
238
|
|
|
148
|
-
function buildInlineContent(
|
|
239
|
+
function buildInlineContent(
|
|
240
|
+
segment: SurfaceInlineSegment,
|
|
241
|
+
mediaPreviews: Record<string, MediaPreviewDescriptor>,
|
|
242
|
+
): PMNode[] {
|
|
149
243
|
switch (segment.kind) {
|
|
150
244
|
case "text": {
|
|
151
245
|
if (!segment.text) return [];
|
|
@@ -213,6 +307,8 @@ function buildInlineContent(segment: SurfaceInlineSegment): PMNode[] {
|
|
|
213
307
|
return [editorSchema.nodes.tab_char.create()];
|
|
214
308
|
|
|
215
309
|
case "image":
|
|
310
|
+
{
|
|
311
|
+
const preview = mediaPreviews[segment.mediaId];
|
|
216
312
|
return [
|
|
217
313
|
editorSchema.nodes.image_atom.create({
|
|
218
314
|
mediaId: segment.mediaId,
|
|
@@ -220,12 +316,35 @@ function buildInlineContent(segment: SurfaceInlineSegment): PMNode[] {
|
|
|
220
316
|
state: segment.state,
|
|
221
317
|
display: segment.display ?? "inline",
|
|
222
318
|
detail: segment.detail ?? null,
|
|
319
|
+
src: preview?.src ?? null,
|
|
320
|
+
widthEmu: preview?.widthEmu ?? null,
|
|
321
|
+
heightEmu: preview?.heightEmu ?? null,
|
|
223
322
|
}),
|
|
224
323
|
];
|
|
324
|
+
}
|
|
225
325
|
|
|
226
326
|
case "opaque_inline":
|
|
227
327
|
return [buildOpaqueInlineOrComplexAtom(segment)];
|
|
228
328
|
|
|
329
|
+
case "note_ref": {
|
|
330
|
+
const text = editorSchema.text(
|
|
331
|
+
segment.label,
|
|
332
|
+
[editorSchema.marks.superscript.create()],
|
|
333
|
+
);
|
|
334
|
+
return [text];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
case "field_ref":
|
|
338
|
+
return [
|
|
339
|
+
editorSchema.nodes.field_ref_atom.create({
|
|
340
|
+
fieldFamily: segment.fieldFamily,
|
|
341
|
+
fieldTarget: segment.fieldTarget ?? null,
|
|
342
|
+
instruction: segment.instruction,
|
|
343
|
+
refreshStatus: segment.refreshStatus,
|
|
344
|
+
label: segment.label,
|
|
345
|
+
}),
|
|
346
|
+
];
|
|
347
|
+
|
|
229
348
|
default:
|
|
230
349
|
return [];
|
|
231
350
|
}
|
|
@@ -233,23 +352,13 @@ function buildInlineContent(segment: SurfaceInlineSegment): PMNode[] {
|
|
|
233
352
|
|
|
234
353
|
function buildTable(
|
|
235
354
|
block: Extract<SurfaceBlockSnapshot, { kind: "table" }>,
|
|
355
|
+
mediaPreviews: Record<string, MediaPreviewDescriptor>,
|
|
236
356
|
): PMNode {
|
|
237
357
|
const rows: PMNode[] = [];
|
|
238
358
|
for (const row of block.rows) {
|
|
239
359
|
const cells: PMNode[] = [];
|
|
240
360
|
for (const cell of row.cells) {
|
|
241
|
-
const cellContent
|
|
242
|
-
for (const child of cell.content) {
|
|
243
|
-
if (child.kind === "paragraph") {
|
|
244
|
-
cellContent.push(buildParagraph(child as Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>));
|
|
245
|
-
} else if (child.kind === "table") {
|
|
246
|
-
cellContent.push(buildTable(child as Extract<SurfaceBlockSnapshot, { kind: "table" }>));
|
|
247
|
-
} else if (child.kind === "sdt_block") {
|
|
248
|
-
cellContent.push(buildSdtBlock(child as Extract<SurfaceBlockSnapshot, { kind: "sdt_block" }>));
|
|
249
|
-
} else if (child.kind === "opaque_block") {
|
|
250
|
-
cellContent.push(buildOpaqueBlock(child as Extract<SurfaceBlockSnapshot, { kind: "opaque_block" }>));
|
|
251
|
-
}
|
|
252
|
-
}
|
|
361
|
+
const cellContent = buildPMBlocks(cell.content, mediaPreviews);
|
|
253
362
|
// Ensure at least one paragraph in cell (PM requires non-empty)
|
|
254
363
|
if (cellContent.length === 0) {
|
|
255
364
|
cellContent.push(editorSchema.nodes.paragraph.create());
|
|
@@ -262,17 +371,36 @@ function buildTable(
|
|
|
262
371
|
gridSpan: cell.gridSpan,
|
|
263
372
|
verticalMerge: cell.verticalMerge,
|
|
264
373
|
backgroundColor: cell.backgroundColor ?? null,
|
|
374
|
+
verticalAlign: cell.verticalAlign ?? null,
|
|
375
|
+
borderTop: cell.borderTop ?? null,
|
|
376
|
+
borderRight: cell.borderRight ?? null,
|
|
377
|
+
borderBottom: cell.borderBottom ?? null,
|
|
378
|
+
borderLeft: cell.borderLeft ?? null,
|
|
265
379
|
},
|
|
266
380
|
Fragment.from(cellContent),
|
|
267
381
|
),
|
|
268
382
|
);
|
|
269
383
|
}
|
|
270
|
-
rows.push(editorSchema.nodes.table_row.create(
|
|
384
|
+
rows.push(editorSchema.nodes.table_row.create(
|
|
385
|
+
{
|
|
386
|
+
height: row.height ?? null,
|
|
387
|
+
heightRule: row.heightRule ?? null,
|
|
388
|
+
isHeader: row.isHeader ?? false,
|
|
389
|
+
},
|
|
390
|
+
Fragment.from(cells),
|
|
391
|
+
));
|
|
271
392
|
}
|
|
272
393
|
return editorSchema.nodes.table.create(
|
|
273
394
|
{
|
|
274
395
|
styleId: block.styleId ?? null,
|
|
275
396
|
gridColumns: block.gridColumns,
|
|
397
|
+
alignment: block.alignment ?? null,
|
|
398
|
+
tblLookFirstRow: block.tblLook?.firstRow ?? false,
|
|
399
|
+
tblLookLastRow: block.tblLook?.lastRow ?? false,
|
|
400
|
+
tblLookFirstColumn: block.tblLook?.firstColumn ?? false,
|
|
401
|
+
tblLookLastColumn: block.tblLook?.lastColumn ?? false,
|
|
402
|
+
tblLookNoHBand: block.tblLook?.noHBand ?? false,
|
|
403
|
+
tblLookNoVBand: block.tblLook?.noVBand ?? false,
|
|
276
404
|
},
|
|
277
405
|
Fragment.from(rows),
|
|
278
406
|
);
|
|
@@ -280,19 +408,9 @@ function buildTable(
|
|
|
280
408
|
|
|
281
409
|
function buildSdtBlock(
|
|
282
410
|
block: Extract<SurfaceBlockSnapshot, { kind: "sdt_block" }>,
|
|
411
|
+
mediaPreviews: Record<string, MediaPreviewDescriptor>,
|
|
283
412
|
): PMNode {
|
|
284
|
-
const children = block.children
|
|
285
|
-
if (child.kind === "paragraph") {
|
|
286
|
-
return buildParagraph(child);
|
|
287
|
-
}
|
|
288
|
-
if (child.kind === "table") {
|
|
289
|
-
return buildTable(child);
|
|
290
|
-
}
|
|
291
|
-
if (child.kind === "sdt_block") {
|
|
292
|
-
return buildSdtBlock(child);
|
|
293
|
-
}
|
|
294
|
-
return buildOpaqueBlock(child);
|
|
295
|
-
});
|
|
413
|
+
const children = buildPMBlocks(block.children, mediaPreviews);
|
|
296
414
|
|
|
297
415
|
if (children.length === 0) {
|
|
298
416
|
children.push(editorSchema.nodes.paragraph.create());
|
|
@@ -304,6 +422,11 @@ function buildSdtBlock(
|
|
|
304
422
|
alias: block.alias ?? null,
|
|
305
423
|
tag: block.tag ?? null,
|
|
306
424
|
lock: block.lock ?? null,
|
|
425
|
+
checkboxChecked: block.checkboxChecked ?? null,
|
|
426
|
+
dateValue: block.dateValue ?? null,
|
|
427
|
+
dropdownItems: block.dropdownItems ?? null,
|
|
428
|
+
comboBoxItems: block.comboBoxItems ?? null,
|
|
429
|
+
showingPlcHdr: block.showingPlcHdr ?? false,
|
|
307
430
|
},
|
|
308
431
|
Fragment.from(children),
|
|
309
432
|
);
|
|
@@ -320,15 +443,14 @@ function buildOpaqueInlineOrComplexAtom(
|
|
|
320
443
|
const label = segment.label;
|
|
321
444
|
const detail = segment.detail;
|
|
322
445
|
|
|
323
|
-
if (label === "
|
|
446
|
+
if (label === "Embedded chart") {
|
|
324
447
|
return editorSchema.nodes.chart_atom.create({ detail });
|
|
325
448
|
}
|
|
326
|
-
if (label === "SmartArt") {
|
|
449
|
+
if (label === "SmartArt diagram") {
|
|
327
450
|
return editorSchema.nodes.smartart_atom.create({ detail });
|
|
328
451
|
}
|
|
329
|
-
if (label === "
|
|
330
|
-
|
|
331
|
-
const textMatch = /Text: "([^"]+)"/.exec(detail);
|
|
452
|
+
if (label === "Drawing shape" || label === "Text box") {
|
|
453
|
+
const textMatch = /(?:Text content|Content): "([^"]+)"/.exec(detail);
|
|
332
454
|
const geometryMatch = /Geometry: ([^.]+)\./.exec(detail);
|
|
333
455
|
return editorSchema.nodes.shape_atom.create({
|
|
334
456
|
text: textMatch ? textMatch[1] : null,
|
|
@@ -345,8 +467,8 @@ function buildOpaqueInlineOrComplexAtom(
|
|
|
345
467
|
detail,
|
|
346
468
|
});
|
|
347
469
|
}
|
|
348
|
-
if (label === "VML
|
|
349
|
-
const textMatch = /Text: "([^"]+)"/.exec(detail);
|
|
470
|
+
if (label === "Legacy VML drawing") {
|
|
471
|
+
const textMatch = /Text content: "([^"]+)"/.exec(detail);
|
|
350
472
|
const typeMatch = /Type: ([^.]+)\./.exec(detail);
|
|
351
473
|
return editorSchema.nodes.vml_atom.create({
|
|
352
474
|
text: textMatch ? textMatch[1] : null,
|
|
@@ -360,6 +482,7 @@ function buildOpaqueInlineOrComplexAtom(
|
|
|
360
482
|
warningId: segment.warningId,
|
|
361
483
|
label,
|
|
362
484
|
detail,
|
|
485
|
+
presentation: segment.presentation ?? "inline-chip",
|
|
363
486
|
});
|
|
364
487
|
}
|
|
365
488
|
|
|
@@ -15,7 +15,17 @@ import { Plugin, PluginKey } from "prosemirror-state";
|
|
|
15
15
|
import type { EditorState, Transaction } from "prosemirror-state";
|
|
16
16
|
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
17
17
|
|
|
18
|
-
import type {
|
|
18
|
+
import type {
|
|
19
|
+
SearchOptions as PublicSearchOptions,
|
|
20
|
+
} from "../../api/public-types";
|
|
21
|
+
import {
|
|
22
|
+
buildSearchPattern,
|
|
23
|
+
createSearchExcerpt,
|
|
24
|
+
findSearchMatches,
|
|
25
|
+
searchSecondaryStories,
|
|
26
|
+
type SecondaryStorySearchResult,
|
|
27
|
+
type SearchTextOptions,
|
|
28
|
+
} from "../../core/search/search-text.ts";
|
|
19
29
|
|
|
20
30
|
// ---------------------------------------------------------------------------
|
|
21
31
|
// Public types
|
|
@@ -28,8 +38,7 @@ export interface SearchResult {
|
|
|
28
38
|
index: number;
|
|
29
39
|
}
|
|
30
40
|
|
|
31
|
-
export interface SearchOptions extends PublicSearchOptions {
|
|
32
|
-
caseSensitive?: boolean;
|
|
41
|
+
export interface SearchOptions extends PublicSearchOptions, SearchTextOptions {
|
|
33
42
|
regex?: boolean;
|
|
34
43
|
highlightColor?: string;
|
|
35
44
|
}
|
|
@@ -133,71 +142,6 @@ export function performSearch(
|
|
|
133
142
|
return results;
|
|
134
143
|
}
|
|
135
144
|
|
|
136
|
-
export function findSearchMatches(
|
|
137
|
-
text: string,
|
|
138
|
-
query: string,
|
|
139
|
-
options: SearchOptions = {},
|
|
140
|
-
): SearchResult[] {
|
|
141
|
-
const pattern = buildSearchPattern(query, options);
|
|
142
|
-
if (!pattern) return [];
|
|
143
|
-
|
|
144
|
-
const results: SearchResult[] = [];
|
|
145
|
-
let match: RegExpExecArray | null;
|
|
146
|
-
pattern.lastIndex = 0;
|
|
147
|
-
while ((match = pattern.exec(text)) !== null) {
|
|
148
|
-
results.push({
|
|
149
|
-
from: match.index,
|
|
150
|
-
to: match.index + match[0].length,
|
|
151
|
-
text: match[0],
|
|
152
|
-
index: results.length,
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
if (match[0].length === 0) {
|
|
156
|
-
pattern.lastIndex += 1;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return results;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function createSearchExcerpt(
|
|
164
|
-
text: string,
|
|
165
|
-
from: number,
|
|
166
|
-
to: number,
|
|
167
|
-
radius = 24,
|
|
168
|
-
): string {
|
|
169
|
-
const safeFrom = Math.max(0, Math.min(from, text.length));
|
|
170
|
-
const safeTo = Math.max(safeFrom, Math.min(to, text.length));
|
|
171
|
-
const start = Math.max(0, safeFrom - radius);
|
|
172
|
-
const end = Math.min(text.length, safeTo + radius);
|
|
173
|
-
const prefix = start > 0 ? "…" : "";
|
|
174
|
-
const suffix = end < text.length ? "…" : "";
|
|
175
|
-
return `${prefix}${text.slice(start, end)}${suffix}`;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function buildSearchPattern(
|
|
179
|
-
query: string,
|
|
180
|
-
options: SearchOptions,
|
|
181
|
-
): RegExp | null {
|
|
182
|
-
if (!query) {
|
|
183
|
-
return null;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const caseSensitive = options.matchCase ?? options.caseSensitive ?? false;
|
|
187
|
-
const regex = options.regex ?? false;
|
|
188
|
-
const wholeWord = options.wholeWord ?? false;
|
|
189
|
-
|
|
190
|
-
try {
|
|
191
|
-
const source = regex
|
|
192
|
-
? query
|
|
193
|
-
: query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
194
|
-
const wrapped = wholeWord ? `\\b${source}\\b` : source;
|
|
195
|
-
return new RegExp(wrapped, caseSensitive ? "g" : "gi");
|
|
196
|
-
} catch {
|
|
197
|
-
return null;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
145
|
// ---------------------------------------------------------------------------
|
|
202
146
|
// Clear helper (ProseMirror Command signature)
|
|
203
147
|
// ---------------------------------------------------------------------------
|
|
@@ -215,3 +159,10 @@ export function clearSearch(
|
|
|
215
159
|
);
|
|
216
160
|
return true;
|
|
217
161
|
}
|
|
162
|
+
|
|
163
|
+
export {
|
|
164
|
+
createSearchExcerpt,
|
|
165
|
+
findSearchMatches,
|
|
166
|
+
searchSecondaryStories,
|
|
167
|
+
};
|
|
168
|
+
export type { SecondaryStorySearchResult };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EditorStoryTarget,
|
|
3
|
+
EditorSurfaceSnapshot,
|
|
4
|
+
} from "../../api/public-types.ts";
|
|
5
|
+
|
|
6
|
+
const surfaceIdentityMap = new WeakMap<EditorSurfaceSnapshot, number>();
|
|
7
|
+
let nextSurfaceIdentity = 0;
|
|
8
|
+
|
|
9
|
+
function getSurfaceIdentity(surface: EditorSurfaceSnapshot): number {
|
|
10
|
+
const cached = surfaceIdentityMap.get(surface);
|
|
11
|
+
if (cached !== undefined) {
|
|
12
|
+
return cached;
|
|
13
|
+
}
|
|
14
|
+
const identity = nextSurfaceIdentity;
|
|
15
|
+
nextSurfaceIdentity += 1;
|
|
16
|
+
surfaceIdentityMap.set(surface, identity);
|
|
17
|
+
return identity;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createSurfaceDocumentBuildKey(input: {
|
|
21
|
+
surface: EditorSurfaceSnapshot | null | undefined;
|
|
22
|
+
activeStory: EditorStoryTarget;
|
|
23
|
+
mediaPreviewKey: string;
|
|
24
|
+
}): string {
|
|
25
|
+
return JSON.stringify({
|
|
26
|
+
surfaceIdentity:
|
|
27
|
+
input.surface === undefined || input.surface === null
|
|
28
|
+
? "loading"
|
|
29
|
+
: getSurfaceIdentity(input.surface),
|
|
30
|
+
activeStory: input.activeStory,
|
|
31
|
+
mediaPreviewKey: input.mediaPreviewKey,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createSurfaceDecorationKey(input: {
|
|
36
|
+
markupDisplay: string;
|
|
37
|
+
showTrackedChanges: boolean;
|
|
38
|
+
canEdit: boolean;
|
|
39
|
+
activeCommentId?: string;
|
|
40
|
+
activeRevisionId?: string;
|
|
41
|
+
workflowScopeSignature?: string;
|
|
42
|
+
}): string {
|
|
43
|
+
return JSON.stringify({
|
|
44
|
+
markupDisplay: input.markupDisplay,
|
|
45
|
+
showTrackedChanges: input.showTrackedChanges,
|
|
46
|
+
canEdit: input.canEdit,
|
|
47
|
+
activeCommentId: input.activeCommentId ?? null,
|
|
48
|
+
activeRevisionId: input.activeRevisionId ?? null,
|
|
49
|
+
workflowScopeSignature: input.workflowScopeSignature ?? null,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -95,6 +95,17 @@ export function TwInlineToken(props: TwInlineTokenProps) {
|
|
|
95
95
|
|
|
96
96
|
// opaque_inline
|
|
97
97
|
if (segment.kind === "opaque_inline") {
|
|
98
|
+
if (segment.presentation === "quiet-marker") {
|
|
99
|
+
return (
|
|
100
|
+
<span
|
|
101
|
+
aria-label={segment.label}
|
|
102
|
+
title={segment.detail}
|
|
103
|
+
className="inline-block h-0 w-0 overflow-hidden align-baseline"
|
|
104
|
+
data-inline-presentation="quiet-marker"
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
98
109
|
return (
|
|
99
110
|
<button
|
|
100
111
|
type="button"
|
|
@@ -7,13 +7,14 @@ export interface TwOpaqueBlockProps {
|
|
|
7
7
|
block: Extract<SurfaceBlockSnapshot, { kind: "opaque_block" }>;
|
|
8
8
|
selection: SelectionSnapshot;
|
|
9
9
|
onSelectionChange?: (selection: SelectionSnapshot) => void;
|
|
10
|
+
workflowTargeted?: boolean;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
const focusRingClass =
|
|
13
14
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-canvas";
|
|
14
15
|
|
|
15
16
|
export function TwOpaqueBlock(props: TwOpaqueBlockProps) {
|
|
16
|
-
const { block, selection } = props;
|
|
17
|
+
const { block, selection, workflowTargeted } = props;
|
|
17
18
|
const selected = selectionTouchesRange(selection, block.from, block.to);
|
|
18
19
|
|
|
19
20
|
return (
|
|
@@ -44,6 +45,11 @@ export function TwOpaqueBlock(props: TwOpaqueBlockProps) {
|
|
|
44
45
|
<span className="inline-flex items-center px-1.5 py-0.5 rounded-full text-[10px] font-semibold text-comment bg-warning-soft">
|
|
45
46
|
preserve-only
|
|
46
47
|
</span>
|
|
48
|
+
{workflowTargeted && (
|
|
49
|
+
<span className="inline-flex items-center px-1.5 py-0.5 rounded-full text-[10px] font-semibold text-amber-700 bg-amber-100">
|
|
50
|
+
workflow-targeted
|
|
51
|
+
</span>
|
|
52
|
+
)}
|
|
47
53
|
</div>
|
|
48
54
|
<p className="text-sm text-secondary">{block.detail}</p>
|
|
49
55
|
</button>
|