@beyondwork/docx-react-component 1.0.81 → 1.0.82
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 +1 -1
- package/src/api/public-types.ts +2 -1
- package/src/api/v3/_runtime-handle.ts +4 -0
- package/src/api/v3/runtime/document.ts +61 -3
- package/src/api/v3/runtime/review.ts +55 -2
- package/src/io/normalize/normalize-text.ts +4 -1
- package/src/io/ooxml/parse-drawing.ts +4 -0
- package/src/model/canonical-document.ts +2 -0
- package/src/ui/WordReviewEditor.tsx +71 -1
- package/src/ui-tailwind/chrome/editor-action-registry.ts +220 -0
- package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +59 -35
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +1 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +256 -37
- package/src/ui-tailwind/editor-surface/pm-schema.ts +54 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +31 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +15 -0
- package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +24 -5
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +35 -6
- package/src/ui-tailwind/page-stack/use-visible-block-range.ts +333 -43
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +273 -24
- package/src/ui-tailwind/theme/editor-theme.css +3 -5
- package/src/ui-tailwind/tw-review-workspace.tsx +3 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beyondwork/docx-react-component",
|
|
3
3
|
"publisher": "beyondwork",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.82",
|
|
5
5
|
"description": "Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": [
|
package/src/api/public-types.ts
CHANGED
|
@@ -5614,7 +5614,8 @@ export interface WordReviewEditorProps {
|
|
|
5614
5614
|
* Optional host-callback extension bag for workspace command chrome.
|
|
5615
5615
|
* The default `<WordReviewEditor />` path now mounts
|
|
5616
5616
|
* `TwWorkspaceChromeHost` with product-backed commands for formatting,
|
|
5617
|
-
* paragraph/list actions,
|
|
5617
|
+
* paragraph/list/style/font/color actions, search/navigation host
|
|
5618
|
+
* delegation, comments, and table insertion/structure.
|
|
5618
5619
|
* Supplying this bag overrides or extends those defaults for host-owned
|
|
5619
5620
|
* actions such as custom table properties, hyperlink handling, or
|
|
5620
5621
|
* object metadata. Actions without a wired callback are hidden from
|
|
@@ -43,6 +43,7 @@ export type RuntimeApiHandle = Pick<
|
|
|
43
43
|
DocumentRuntime,
|
|
44
44
|
// Session + export (runtime.document family)
|
|
45
45
|
| "getSessionState"
|
|
46
|
+
| "setDocumentMode"
|
|
46
47
|
| "exportDocx"
|
|
47
48
|
| "getCompatibilityReport"
|
|
48
49
|
| "getWarnings"
|
|
@@ -54,6 +55,7 @@ export type RuntimeApiHandle = Pick<
|
|
|
54
55
|
| "findAllText"
|
|
55
56
|
// Review (runtime.review family)
|
|
56
57
|
| "getReviewWorkSnapshot"
|
|
58
|
+
| "getSuggestionsSnapshot"
|
|
57
59
|
| "acceptChange"
|
|
58
60
|
| "rejectChange"
|
|
59
61
|
| "resolveComment"
|
|
@@ -136,6 +138,7 @@ export type RuntimeApiHandle = Pick<
|
|
|
136
138
|
*/
|
|
137
139
|
export const RUNTIME_API_HANDLE_SHAPE_CHECK: Record<keyof RuntimeApiHandle, true> = {
|
|
138
140
|
getSessionState: true,
|
|
141
|
+
setDocumentMode: true,
|
|
139
142
|
exportDocx: true,
|
|
140
143
|
getCompatibilityReport: true,
|
|
141
144
|
getWarnings: true,
|
|
@@ -143,6 +146,7 @@ export const RUNTIME_API_HANDLE_SHAPE_CHECK: Record<keyof RuntimeApiHandle, true
|
|
|
143
146
|
getCanonicalDocument: true,
|
|
144
147
|
findAllText: true,
|
|
145
148
|
getReviewWorkSnapshot: true,
|
|
149
|
+
getSuggestionsSnapshot: true,
|
|
146
150
|
acceptChange: true,
|
|
147
151
|
rejectChange: true,
|
|
148
152
|
resolveComment: true,
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @endStateApi v3 — `runtime.document` family.
|
|
3
3
|
*
|
|
4
|
-
* See docs/reference/public-api.md § runtime.document.
|
|
4
|
+
* See docs/reference/public-api.md § runtime.document.
|
|
5
5
|
* `load` (partial — runtime pre-load is the caller's responsibility; v3
|
|
6
|
-
* exposes a re-mount semantic later), `
|
|
7
|
-
*
|
|
6
|
+
* exposes a re-mount semantic later), `getMode` / `setMode` (live;
|
|
7
|
+
* delegates to the runtime view-state posture), `export` (live; delegates
|
|
8
|
+
* to `runtime.exportDocx`), `validate` (partial; read live, write mock).
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
import type { RuntimeApiHandle } from "../_runtime-handle.ts";
|
|
11
12
|
import type {
|
|
13
|
+
DocumentMode,
|
|
12
14
|
EditorError,
|
|
13
15
|
ExportDocxOptions,
|
|
14
16
|
ExportResult,
|
|
@@ -88,6 +90,42 @@ export const loadMetadata: ApiV3FnMetadata = {
|
|
|
88
90
|
"§Runtime API § runtime.document.load. Graduation (2026-04-22, post-eb7d14fa): `live` via direct delegation to `loadDocxSessionAsync` (src/session/import/loader.ts). Returns a PersistedEditorSnapshot the caller can pass to DocxSession.reopenFromSnapshot or persist for later rehydrate. Note per arch §R8 Option B: v3 does NOT construct the receiving DocumentRuntime — that's the caller's job via createDocumentRuntime(initialSessionState).",
|
|
89
91
|
};
|
|
90
92
|
|
|
93
|
+
/* ================================================================== */
|
|
94
|
+
/* mode */
|
|
95
|
+
/* ================================================================== */
|
|
96
|
+
|
|
97
|
+
export const getModeMetadata: ApiV3FnMetadata = {
|
|
98
|
+
name: "runtime.document.getMode",
|
|
99
|
+
status: "live",
|
|
100
|
+
sourceLayer: "runtime-core",
|
|
101
|
+
liveEvidence: {
|
|
102
|
+
runnerTest: "test/api/v3/create-accepts-handle.test.ts",
|
|
103
|
+
commit: "refactor-03-tracked-changes-v1-api-adapter",
|
|
104
|
+
},
|
|
105
|
+
uxIntent: { uiVisible: false, expectsUxResponse: "none" },
|
|
106
|
+
agentMetadata: { readOrMutate: "read", boundedScope: "document", auditCategory: "document-mode" },
|
|
107
|
+
stateClass: "C-local",
|
|
108
|
+
persistsTo: "none",
|
|
109
|
+
rwdReference:
|
|
110
|
+
"§Runtime API § runtime.document.getMode. Live adapter over the runtime render snapshot's DocumentMode; suggesting remains the tracked-change authoring posture.",
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const setModeMetadata: ApiV3FnMetadata = {
|
|
114
|
+
name: "runtime.document.setMode",
|
|
115
|
+
status: "live",
|
|
116
|
+
sourceLayer: "runtime-core",
|
|
117
|
+
liveEvidence: {
|
|
118
|
+
runnerTest: "test/api/v3/create-accepts-handle.test.ts",
|
|
119
|
+
commit: "refactor-03-tracked-changes-v1-api-adapter",
|
|
120
|
+
},
|
|
121
|
+
uxIntent: { uiVisible: true, expectsUxResponse: "surface-refresh", expectedDelta: "document mode changes" },
|
|
122
|
+
agentMetadata: { readOrMutate: "mutate", boundedScope: "document", auditCategory: "document-mode" },
|
|
123
|
+
stateClass: "C-local",
|
|
124
|
+
persistsTo: "none",
|
|
125
|
+
rwdReference:
|
|
126
|
+
"§Runtime API § runtime.document.setMode. Live adapter over runtime.setDocumentMode(); mode 'suggesting' is the v3 entry to tracked-change authoring.",
|
|
127
|
+
};
|
|
128
|
+
|
|
91
129
|
/* ================================================================== */
|
|
92
130
|
/* export */
|
|
93
131
|
/* ================================================================== */
|
|
@@ -196,6 +234,26 @@ export function createDocumentFamily(runtime: RuntimeApiHandle) {
|
|
|
196
234
|
return result;
|
|
197
235
|
},
|
|
198
236
|
|
|
237
|
+
getMode(): DocumentMode {
|
|
238
|
+
// @endStateApi — live. Reads the runtime view-state posture that
|
|
239
|
+
// render snapshots already expose.
|
|
240
|
+
return runtime.getRenderSnapshot().documentMode;
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
setMode(mode: DocumentMode): void {
|
|
244
|
+
// @endStateApi — live. Delegates to the runtime's document-mode
|
|
245
|
+
// setter; `suggesting` is the tracked-change authoring posture.
|
|
246
|
+
runtime.setDocumentMode(mode);
|
|
247
|
+
emitUxResponse(runtime, {
|
|
248
|
+
apiFn: setModeMetadata.name,
|
|
249
|
+
intent: setModeMetadata.uxIntent.expectedDelta ?? "",
|
|
250
|
+
mockOrLive: "live",
|
|
251
|
+
uiVisible: true,
|
|
252
|
+
expectedDelta: setModeMetadata.uxIntent.expectedDelta,
|
|
253
|
+
actualDelta: { kind: "surface-refresh", payload: { mode } },
|
|
254
|
+
});
|
|
255
|
+
},
|
|
256
|
+
|
|
199
257
|
async export(options?: ExportDocxOptions): Promise<ExportResult> {
|
|
200
258
|
// @endStateApi — live. Delegates to the shipped runtime export path.
|
|
201
259
|
const result = await runtime.exportDocx(options);
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @endStateApi v3 — `runtime.review` family.
|
|
3
3
|
*
|
|
4
|
-
* getComments (live) / getChanges (live) /
|
|
5
|
-
* resolveComment (live).
|
|
4
|
+
* getComments (live) / getChanges (live) / getSuggestions (live) /
|
|
5
|
+
* acceptChange (live) / rejectChange (live) / resolveComment (live).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { RuntimeApiHandle } from "../_runtime-handle.ts";
|
|
9
9
|
import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
|
|
10
10
|
import type {
|
|
11
11
|
CommentSidebarThreadSnapshot,
|
|
12
|
+
SuggestionsSnapshot,
|
|
12
13
|
TrackedChangeEntrySnapshot,
|
|
13
14
|
} from "../../public-types.ts";
|
|
14
15
|
import { emitUxResponse } from "../_ux-response.ts";
|
|
@@ -51,6 +52,22 @@ export const getChangesMetadata: ApiV3FnMetadata = {
|
|
|
51
52
|
rwdReference: "§Runtime API § runtime.review.getChanges",
|
|
52
53
|
};
|
|
53
54
|
|
|
55
|
+
export const getSuggestionsMetadata: ApiV3FnMetadata = {
|
|
56
|
+
name: "runtime.review.getSuggestions",
|
|
57
|
+
status: "live",
|
|
58
|
+
sourceLayer: "workflow-review",
|
|
59
|
+
liveEvidence: {
|
|
60
|
+
runnerTest: "test/api/v3/create-accepts-handle.test.ts",
|
|
61
|
+
commit: "refactor-03-tracked-changes-v1-api-adapter",
|
|
62
|
+
},
|
|
63
|
+
uxIntent: { uiVisible: false, expectsUxResponse: "none" },
|
|
64
|
+
agentMetadata: { readOrMutate: "read", boundedScope: "document", auditCategory: "review-read" },
|
|
65
|
+
stateClass: "A-canonical",
|
|
66
|
+
persistsTo: "canonical",
|
|
67
|
+
rwdReference:
|
|
68
|
+
"§Runtime API § runtime.review.getSuggestions. Live adapter over runtime.getSuggestionsSnapshot(); semantic suggestion readback is grouped by the runtime, not by v3.",
|
|
69
|
+
};
|
|
70
|
+
|
|
54
71
|
export const acceptChangeMetadata: ApiV3FnMetadata = {
|
|
55
72
|
name: "runtime.review.acceptChange",
|
|
56
73
|
status: "live",
|
|
@@ -70,6 +87,23 @@ export const acceptChangeMetadata: ApiV3FnMetadata = {
|
|
|
70
87
|
rwdReference: "§Runtime API § runtime.review.acceptChange",
|
|
71
88
|
};
|
|
72
89
|
|
|
90
|
+
export const rejectChangeMetadata: ApiV3FnMetadata = {
|
|
91
|
+
name: "runtime.review.rejectChange",
|
|
92
|
+
status: "live",
|
|
93
|
+
sourceLayer: "workflow-review",
|
|
94
|
+
liveEvidence: {
|
|
95
|
+
runnerTest: "test/api/v3/create-accepts-handle.test.ts",
|
|
96
|
+
commit: "refactor-03-tracked-changes-v1-api-adapter",
|
|
97
|
+
},
|
|
98
|
+
uxIntent: { uiVisible: true, expectsUxResponse: "inline-change", expectedDelta: "change mark disappears and text restores" },
|
|
99
|
+
agentMetadata: { readOrMutate: "mutate", boundedScope: "scope", auditCategory: "change-reject" },
|
|
100
|
+
stateClass: "A-canonical",
|
|
101
|
+
persistsTo: "canonical",
|
|
102
|
+
broadcastsVia: "crdt",
|
|
103
|
+
rwdReference:
|
|
104
|
+
"§Runtime API § runtime.review.rejectChange. Live adapter over runtime.rejectChange; mirrors acceptChange for individual tracked-change review.",
|
|
105
|
+
};
|
|
106
|
+
|
|
73
107
|
export const resolveCommentMetadata: ApiV3FnMetadata = {
|
|
74
108
|
name: "runtime.review.resolveComment",
|
|
75
109
|
status: "live",
|
|
@@ -100,6 +134,12 @@ export function createReviewFamily(runtime: RuntimeApiHandle) {
|
|
|
100
134
|
return runtime.getRenderSnapshot().trackedChanges.revisions;
|
|
101
135
|
},
|
|
102
136
|
|
|
137
|
+
getSuggestions(): SuggestionsSnapshot {
|
|
138
|
+
// @endStateApi — live. Delegates to the runtime's semantic
|
|
139
|
+
// suggestion grouping rather than regrouping raw revisions here.
|
|
140
|
+
return runtime.getSuggestionsSnapshot();
|
|
141
|
+
},
|
|
142
|
+
|
|
103
143
|
acceptChange(changeId: string): void {
|
|
104
144
|
// @endStateApi — live. Delegates.
|
|
105
145
|
runtime.acceptChange(changeId);
|
|
@@ -113,6 +153,19 @@ export function createReviewFamily(runtime: RuntimeApiHandle) {
|
|
|
113
153
|
});
|
|
114
154
|
},
|
|
115
155
|
|
|
156
|
+
rejectChange(changeId: string): void {
|
|
157
|
+
// @endStateApi — live. Delegates.
|
|
158
|
+
runtime.rejectChange(changeId);
|
|
159
|
+
emitUxResponse(runtime, {
|
|
160
|
+
apiFn: rejectChangeMetadata.name,
|
|
161
|
+
intent: rejectChangeMetadata.uxIntent.expectedDelta ?? "",
|
|
162
|
+
mockOrLive: "live",
|
|
163
|
+
uiVisible: true,
|
|
164
|
+
expectedDelta: rejectChangeMetadata.uxIntent.expectedDelta,
|
|
165
|
+
actualDelta: { kind: "inline-change", payload: { changeId } },
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
|
|
116
169
|
resolveComment(commentId: string): void {
|
|
117
170
|
// @endStateApi — live.
|
|
118
171
|
runtime.resolveComment(commentId);
|
|
@@ -671,7 +671,10 @@ function normalizeDrawingFrameNode(
|
|
|
671
671
|
const filename = packagePartName.slice(packagePartName.lastIndexOf("/") + 1) || "image.bin";
|
|
672
672
|
state.media.items[node.content.mediaId] = {
|
|
673
673
|
mediaId: node.content.mediaId,
|
|
674
|
-
contentType:
|
|
674
|
+
contentType:
|
|
675
|
+
node.content.contentType ??
|
|
676
|
+
existingMediaItem?.contentType ??
|
|
677
|
+
"application/octet-stream",
|
|
675
678
|
filename,
|
|
676
679
|
packagePartName,
|
|
677
680
|
relationshipId: node.content.blipRef,
|
|
@@ -188,8 +188,12 @@ function resolveContent(
|
|
|
188
188
|
const partPath = normalizePartPath(
|
|
189
189
|
resolveRelationshipTarget(opts.sourcePartPath ?? "/word/document.xml", rel),
|
|
190
190
|
);
|
|
191
|
+
const mediaPart = opts.mediaParts?.get(partPath);
|
|
191
192
|
pic.packagePartName = partPath;
|
|
192
193
|
pic.mediaId = `media:${partPath.slice(1)}`;
|
|
194
|
+
if (mediaPart?.contentType) {
|
|
195
|
+
pic.contentType = mediaPart.contentType;
|
|
196
|
+
}
|
|
193
197
|
}
|
|
194
198
|
// F4.1 — preserve outer drawing XML for lossless round-trip serialization
|
|
195
199
|
pic.rawXml = rawXml;
|
|
@@ -1922,6 +1922,8 @@ export interface PictureContent {
|
|
|
1922
1922
|
mediaId?: string;
|
|
1923
1923
|
/** Absolute package path for media catalog lookup. */
|
|
1924
1924
|
packagePartName?: string;
|
|
1925
|
+
/** MIME resolved from the OPC media part, when known. */
|
|
1926
|
+
contentType?: string;
|
|
1925
1927
|
srcRect?: { top: number; bottom: number; left: number; right: number };
|
|
1926
1928
|
stretch?: boolean;
|
|
1927
1929
|
/**
|
|
@@ -3341,13 +3341,40 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3341
3341
|
onToggleItalic: commands.onToggleItalic,
|
|
3342
3342
|
onToggleUnderline: commands.onToggleUnderline,
|
|
3343
3343
|
onToggleStrikethrough: commands.onToggleStrikethrough,
|
|
3344
|
+
onSetParagraphStyle: (styleId) => {
|
|
3345
|
+
const resolvedStyleId = resolveProductParagraphStyleId(styleCatalog, styleId);
|
|
3346
|
+
if (!resolvedStyleId) {
|
|
3347
|
+
activeRuntime.emitBlockedCommand("setParagraphStyle", [{
|
|
3348
|
+
code: "unsupported_surface",
|
|
3349
|
+
message: `${styleId} is not available in this document's style catalog.`,
|
|
3350
|
+
}]);
|
|
3351
|
+
return;
|
|
3352
|
+
}
|
|
3353
|
+
commands.onSetParagraphStyle?.(resolvedStyleId);
|
|
3354
|
+
},
|
|
3355
|
+
onSetFontFamily: commands.onSetFontFamily,
|
|
3356
|
+
onSetFontSize: commands.onSetFontSize,
|
|
3357
|
+
onSetTextColor: commands.onSetTextColor,
|
|
3358
|
+
onSetHighlightColor: commands.onSetHighlightColor,
|
|
3344
3359
|
onToggleBulletedList: commands.onToggleBulletedList,
|
|
3345
3360
|
onToggleNumberedList: commands.onToggleNumberedList,
|
|
3346
3361
|
onOutdent: commands.onOutdent,
|
|
3347
3362
|
onIndent: commands.onIndent,
|
|
3348
3363
|
onSetAlignment: (alignment) => commands.onSetAlignment?.(alignment),
|
|
3364
|
+
onInsertPageBreak: commands.onInsertPageBreak,
|
|
3365
|
+
onInsertSectionBreak: (type) => commands.onInsertSectionBreak?.(type),
|
|
3349
3366
|
onInsertTable: commands.onInsertTable,
|
|
3350
3367
|
onAddComment: commands.onAddComment,
|
|
3368
|
+
onFindRequested: onFindRequested
|
|
3369
|
+
? () => onFindRequested({ selectionText: "", selectionRange: snapshot.selection })
|
|
3370
|
+
: undefined,
|
|
3371
|
+
onReplaceRequested: onReplaceRequested
|
|
3372
|
+
? () => onReplaceRequested({ selectionText: "", selectionRange: snapshot.selection })
|
|
3373
|
+
: undefined,
|
|
3374
|
+
onPrintRequested,
|
|
3375
|
+
onGoToRequested: onGoToRequested
|
|
3376
|
+
? () => onGoToRequested({ selectionText: "", selectionRange: snapshot.selection })
|
|
3377
|
+
: undefined,
|
|
3351
3378
|
onInsertRowAbove: commands.onAddRowBefore,
|
|
3352
3379
|
onInsertRowBelow: commands.onAddRowAfter,
|
|
3353
3380
|
onInsertColumnBefore: commands.onAddColumnBefore,
|
|
@@ -3362,7 +3389,17 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3362
3389
|
return editorActionHost
|
|
3363
3390
|
? { ...defaultHost, ...editorActionHost }
|
|
3364
3391
|
: defaultHost;
|
|
3365
|
-
}, [
|
|
3392
|
+
}, [
|
|
3393
|
+
activeRuntime,
|
|
3394
|
+
commands,
|
|
3395
|
+
editorActionHost,
|
|
3396
|
+
onFindRequested,
|
|
3397
|
+
onGoToRequested,
|
|
3398
|
+
onPrintRequested,
|
|
3399
|
+
onReplaceRequested,
|
|
3400
|
+
snapshot.selection,
|
|
3401
|
+
styleCatalog,
|
|
3402
|
+
]);
|
|
3366
3403
|
|
|
3367
3404
|
const harnessShowUnsupportedPreviews = readHarnessDebugPortsFlag(__harnessDebugPorts, "unsupportedObjectPreviews");
|
|
3368
3405
|
const effectiveShowUnsupportedPreviews = computeEffectiveShowUnsupportedPreviews({
|
|
@@ -4574,6 +4611,39 @@ function createSelectionToolbarStyleBadge(
|
|
|
4574
4611
|
return { label: styleEntry.displayName };
|
|
4575
4612
|
}
|
|
4576
4613
|
|
|
4614
|
+
function resolveProductParagraphStyleId(
|
|
4615
|
+
styleCatalog: StyleCatalogSnapshot,
|
|
4616
|
+
requestedStyleId: string,
|
|
4617
|
+
): string | null {
|
|
4618
|
+
switch (requestedStyleId) {
|
|
4619
|
+
case "Heading1":
|
|
4620
|
+
return resolveHeadingShortcutStyleId(styleCatalog, 1);
|
|
4621
|
+
case "Heading2":
|
|
4622
|
+
return resolveHeadingShortcutStyleId(styleCatalog, 2);
|
|
4623
|
+
case "Heading3":
|
|
4624
|
+
return resolveHeadingShortcutStyleId(styleCatalog, 3);
|
|
4625
|
+
case "Normal": {
|
|
4626
|
+
const defaultStyle = styleCatalog.paragraphs.find((entry) => entry.isDefault);
|
|
4627
|
+
if (defaultStyle) return defaultStyle.styleId;
|
|
4628
|
+
break;
|
|
4629
|
+
}
|
|
4630
|
+
default:
|
|
4631
|
+
break;
|
|
4632
|
+
}
|
|
4633
|
+
|
|
4634
|
+
const requestedToken = normalizeProductStyleToken(requestedStyleId);
|
|
4635
|
+
const match = styleCatalog.paragraphs.find(
|
|
4636
|
+
(entry) =>
|
|
4637
|
+
normalizeProductStyleToken(entry.styleId) === requestedToken ||
|
|
4638
|
+
normalizeProductStyleToken(entry.displayName) === requestedToken,
|
|
4639
|
+
);
|
|
4640
|
+
return match?.styleId ?? null;
|
|
4641
|
+
}
|
|
4642
|
+
|
|
4643
|
+
function normalizeProductStyleToken(value: string): string {
|
|
4644
|
+
return value.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
4645
|
+
}
|
|
4646
|
+
|
|
4577
4647
|
function createSelectionToolbarListBadge(
|
|
4578
4648
|
viewState: ReturnType<WordReviewEditorRuntime["getViewState"]>,
|
|
4579
4649
|
): SelectionToolbarModel["badges"][number] | null {
|
|
@@ -24,6 +24,8 @@ import type { EditorChromeMode } from "../../api/v3/ui/chrome-composition";
|
|
|
24
24
|
import type { ContextMenuGroupId } from "./tw-context-menu";
|
|
25
25
|
import type { ShortcutKey } from "./tw-shortcut-hint";
|
|
26
26
|
|
|
27
|
+
type ProductSectionBreakType = "nextPage" | "continuous" | "evenPage" | "oddPage";
|
|
28
|
+
|
|
27
29
|
/**
|
|
28
30
|
* Target kinds roughly mirror DESIGN-EDITOR.md §4 "Local context" cells.
|
|
29
31
|
* A contextmenu event resolves its target to one (or more) of these
|
|
@@ -80,6 +82,11 @@ export interface EditorActionHostCallbacks {
|
|
|
80
82
|
readonly onToggleItalic?: () => void;
|
|
81
83
|
readonly onToggleUnderline?: () => void;
|
|
82
84
|
readonly onToggleStrikethrough?: () => void;
|
|
85
|
+
readonly onSetParagraphStyle?: (styleId: string) => void;
|
|
86
|
+
readonly onSetFontFamily?: (fontFamily: string) => void;
|
|
87
|
+
readonly onSetFontSize?: (fontSize: number) => void;
|
|
88
|
+
readonly onSetTextColor?: (color: string) => void;
|
|
89
|
+
readonly onSetHighlightColor?: (color: string | null) => void;
|
|
83
90
|
readonly onToggleBulletedList?: () => void;
|
|
84
91
|
readonly onToggleNumberedList?: () => void;
|
|
85
92
|
readonly onOutdent?: () => void;
|
|
@@ -87,9 +94,18 @@ export interface EditorActionHostCallbacks {
|
|
|
87
94
|
readonly onSetAlignment?: (
|
|
88
95
|
alignment: "left" | "center" | "right" | "justify",
|
|
89
96
|
) => void;
|
|
97
|
+
readonly onInsertPageBreak?: () => void;
|
|
98
|
+
readonly onInsertSectionBreak?: (type: ProductSectionBreakType) => void;
|
|
90
99
|
readonly onInsertTable?: () => void;
|
|
100
|
+
readonly onInsertImage?: () => void;
|
|
91
101
|
readonly onAddComment?: () => void;
|
|
92
102
|
|
|
103
|
+
// Search / app-level host-delegated commands
|
|
104
|
+
readonly onFindRequested?: () => void;
|
|
105
|
+
readonly onReplaceRequested?: () => void;
|
|
106
|
+
readonly onPrintRequested?: () => void;
|
|
107
|
+
readonly onGoToRequested?: () => void;
|
|
108
|
+
|
|
93
109
|
// Tracked-change operations (suggestion target)
|
|
94
110
|
readonly onAcceptSuggestion?: () => void;
|
|
95
111
|
readonly onRejectSuggestion?: () => void;
|
|
@@ -134,6 +150,7 @@ export interface EditorActionHostCallbacks {
|
|
|
134
150
|
export interface EditorAction {
|
|
135
151
|
readonly id: string;
|
|
136
152
|
readonly label: string;
|
|
153
|
+
readonly description?: string;
|
|
137
154
|
readonly shortcut?: readonly ShortcutKey[];
|
|
138
155
|
readonly group: ContextMenuGroupId;
|
|
139
156
|
/** Targets for which this action is relevant. */
|
|
@@ -167,6 +184,7 @@ export interface EditorAction {
|
|
|
167
184
|
function mk<K extends keyof EditorActionHostCallbacks>(opts: {
|
|
168
185
|
id: string;
|
|
169
186
|
label: string;
|
|
187
|
+
description?: string;
|
|
170
188
|
shortcut?: readonly ShortcutKey[];
|
|
171
189
|
group: ContextMenuGroupId;
|
|
172
190
|
targetKinds: readonly TargetKind[];
|
|
@@ -178,6 +196,7 @@ function mk<K extends keyof EditorActionHostCallbacks>(opts: {
|
|
|
178
196
|
return {
|
|
179
197
|
id: opts.id,
|
|
180
198
|
label: opts.label,
|
|
199
|
+
...(opts.description ? { description: opts.description } : {}),
|
|
181
200
|
...(opts.shortcut ? { shortcut: opts.shortcut } : {}),
|
|
182
201
|
group: opts.group,
|
|
183
202
|
targetKinds: new Set(opts.targetKinds),
|
|
@@ -211,6 +230,7 @@ function mk<K extends keyof EditorActionHostCallbacks>(opts: {
|
|
|
211
230
|
function mkArg<A, K extends keyof EditorActionHostCallbacks>(opts: {
|
|
212
231
|
id: string;
|
|
213
232
|
label: string;
|
|
233
|
+
description?: string;
|
|
214
234
|
shortcut?: readonly ShortcutKey[];
|
|
215
235
|
group: ContextMenuGroupId;
|
|
216
236
|
targetKinds: readonly TargetKind[];
|
|
@@ -222,6 +242,7 @@ function mkArg<A, K extends keyof EditorActionHostCallbacks>(opts: {
|
|
|
222
242
|
return {
|
|
223
243
|
id: opts.id,
|
|
224
244
|
label: opts.label,
|
|
245
|
+
...(opts.description ? { description: opts.description } : {}),
|
|
225
246
|
...(opts.shortcut ? { shortcut: opts.shortcut } : {}),
|
|
226
247
|
group: opts.group,
|
|
227
248
|
targetKinds: new Set(opts.targetKinds),
|
|
@@ -239,6 +260,41 @@ function mkArg<A, K extends keyof EditorActionHostCallbacks>(opts: {
|
|
|
239
260
|
};
|
|
240
261
|
}
|
|
241
262
|
|
|
263
|
+
/**
|
|
264
|
+
* Build an important command that should remain visible as a disabled
|
|
265
|
+
* row when the host/runtime has not wired the implementation yet. Use
|
|
266
|
+
* sparingly for product-signpost commands such as Insert Image or
|
|
267
|
+
* Replace; most callback-less registry rows should stay hidden.
|
|
268
|
+
*/
|
|
269
|
+
function mkImportant<K extends keyof EditorActionHostCallbacks>(opts: {
|
|
270
|
+
id: string;
|
|
271
|
+
label: string;
|
|
272
|
+
description: string;
|
|
273
|
+
shortcut?: readonly ShortcutKey[];
|
|
274
|
+
group: ContextMenuGroupId;
|
|
275
|
+
targetKinds: readonly TargetKind[];
|
|
276
|
+
modes?: readonly EditorChromeMode[];
|
|
277
|
+
callback: K;
|
|
278
|
+
}): EditorAction {
|
|
279
|
+
return {
|
|
280
|
+
id: opts.id,
|
|
281
|
+
label: opts.label,
|
|
282
|
+
description: opts.description,
|
|
283
|
+
...(opts.shortcut ? { shortcut: opts.shortcut } : {}),
|
|
284
|
+
group: opts.group,
|
|
285
|
+
targetKinds: new Set(opts.targetKinds),
|
|
286
|
+
...(opts.modes ? { modes: new Set(opts.modes) } : {}),
|
|
287
|
+
when: (ctx) =>
|
|
288
|
+
typeof ctx.host[opts.callback] === "function" ? true : "disabled",
|
|
289
|
+
run: (ctx) => {
|
|
290
|
+
const fn = ctx.host[opts.callback] as (() => void) | undefined;
|
|
291
|
+
if (!fn) return;
|
|
292
|
+
fn();
|
|
293
|
+
ctx.dismiss();
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
242
298
|
export const EDITOR_ACTION_REGISTRY: readonly EditorAction[] = [
|
|
243
299
|
// -------- History --------
|
|
244
300
|
mk({
|
|
@@ -341,6 +397,105 @@ export const EDITOR_ACTION_REGISTRY: readonly EditorAction[] = [
|
|
|
341
397
|
targetKinds: ["plain-text", "table-cell"],
|
|
342
398
|
callback: "onToggleStrikethrough",
|
|
343
399
|
}),
|
|
400
|
+
mkArg<string, "onSetParagraphStyle">({
|
|
401
|
+
id: "style-normal",
|
|
402
|
+
label: "Normal text",
|
|
403
|
+
group: "formatting",
|
|
404
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
405
|
+
callback: "onSetParagraphStyle",
|
|
406
|
+
payload: "Normal",
|
|
407
|
+
}),
|
|
408
|
+
mkArg<string, "onSetParagraphStyle">({
|
|
409
|
+
id: "style-heading-1",
|
|
410
|
+
label: "Heading 1",
|
|
411
|
+
shortcut: ["Mod", "Alt", "1"],
|
|
412
|
+
group: "formatting",
|
|
413
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
414
|
+
callback: "onSetParagraphStyle",
|
|
415
|
+
payload: "Heading1",
|
|
416
|
+
}),
|
|
417
|
+
mkArg<string, "onSetParagraphStyle">({
|
|
418
|
+
id: "style-heading-2",
|
|
419
|
+
label: "Heading 2",
|
|
420
|
+
shortcut: ["Mod", "Alt", "2"],
|
|
421
|
+
group: "formatting",
|
|
422
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
423
|
+
callback: "onSetParagraphStyle",
|
|
424
|
+
payload: "Heading2",
|
|
425
|
+
}),
|
|
426
|
+
mkArg<string, "onSetParagraphStyle">({
|
|
427
|
+
id: "style-heading-3",
|
|
428
|
+
label: "Heading 3",
|
|
429
|
+
shortcut: ["Mod", "Alt", "3"],
|
|
430
|
+
group: "formatting",
|
|
431
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
432
|
+
callback: "onSetParagraphStyle",
|
|
433
|
+
payload: "Heading3",
|
|
434
|
+
}),
|
|
435
|
+
mkArg<string, "onSetFontFamily">({
|
|
436
|
+
id: "font-family-aptos",
|
|
437
|
+
label: "Font: Aptos",
|
|
438
|
+
group: "formatting",
|
|
439
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
440
|
+
callback: "onSetFontFamily",
|
|
441
|
+
payload: "Aptos",
|
|
442
|
+
}),
|
|
443
|
+
mkArg<string, "onSetFontFamily">({
|
|
444
|
+
id: "font-family-calibri",
|
|
445
|
+
label: "Font: Calibri",
|
|
446
|
+
group: "formatting",
|
|
447
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
448
|
+
callback: "onSetFontFamily",
|
|
449
|
+
payload: "Calibri",
|
|
450
|
+
}),
|
|
451
|
+
mkArg<number, "onSetFontSize">({
|
|
452
|
+
id: "font-size-11",
|
|
453
|
+
label: "Font size: 11",
|
|
454
|
+
group: "formatting",
|
|
455
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
456
|
+
callback: "onSetFontSize",
|
|
457
|
+
payload: 11,
|
|
458
|
+
}),
|
|
459
|
+
mkArg<number, "onSetFontSize">({
|
|
460
|
+
id: "font-size-12",
|
|
461
|
+
label: "Font size: 12",
|
|
462
|
+
group: "formatting",
|
|
463
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
464
|
+
callback: "onSetFontSize",
|
|
465
|
+
payload: 12,
|
|
466
|
+
}),
|
|
467
|
+
mkArg<string, "onSetTextColor">({
|
|
468
|
+
id: "text-color-black",
|
|
469
|
+
label: "Text color: Black",
|
|
470
|
+
group: "formatting",
|
|
471
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
472
|
+
callback: "onSetTextColor",
|
|
473
|
+
payload: "#000000",
|
|
474
|
+
}),
|
|
475
|
+
mkArg<string, "onSetTextColor">({
|
|
476
|
+
id: "text-color-accent",
|
|
477
|
+
label: "Text color: Accent",
|
|
478
|
+
group: "formatting",
|
|
479
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
480
|
+
callback: "onSetTextColor",
|
|
481
|
+
payload: "#1F6B4F",
|
|
482
|
+
}),
|
|
483
|
+
mkArg<string | null, "onSetHighlightColor">({
|
|
484
|
+
id: "highlight-yellow",
|
|
485
|
+
label: "Highlight: Yellow",
|
|
486
|
+
group: "formatting",
|
|
487
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
488
|
+
callback: "onSetHighlightColor",
|
|
489
|
+
payload: "#FFF2A8",
|
|
490
|
+
}),
|
|
491
|
+
mkArg<string | null, "onSetHighlightColor">({
|
|
492
|
+
id: "highlight-clear",
|
|
493
|
+
label: "Clear highlight",
|
|
494
|
+
group: "formatting",
|
|
495
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
496
|
+
callback: "onSetHighlightColor",
|
|
497
|
+
payload: null,
|
|
498
|
+
}),
|
|
344
499
|
mk({
|
|
345
500
|
id: "list-bulleted",
|
|
346
501
|
label: "Bulleted list",
|
|
@@ -401,6 +556,31 @@ export const EDITOR_ACTION_REGISTRY: readonly EditorAction[] = [
|
|
|
401
556
|
callback: "onSetAlignment",
|
|
402
557
|
payload: "justify",
|
|
403
558
|
}),
|
|
559
|
+
mk({
|
|
560
|
+
id: "insert-page-break",
|
|
561
|
+
label: "Insert page break",
|
|
562
|
+
shortcut: ["Mod", "Enter"],
|
|
563
|
+
group: "misc",
|
|
564
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
565
|
+
callback: "onInsertPageBreak",
|
|
566
|
+
}),
|
|
567
|
+
mkArg<ProductSectionBreakType, "onInsertSectionBreak">({
|
|
568
|
+
id: "insert-section-break-next-page",
|
|
569
|
+
label: "Insert section break",
|
|
570
|
+
description: "Next page section break",
|
|
571
|
+
group: "misc",
|
|
572
|
+
targetKinds: ["plain-text", "table-cell"],
|
|
573
|
+
callback: "onInsertSectionBreak",
|
|
574
|
+
payload: "nextPage",
|
|
575
|
+
}),
|
|
576
|
+
mkImportant({
|
|
577
|
+
id: "insert-image",
|
|
578
|
+
label: "Insert image…",
|
|
579
|
+
description: "Needs a host file picker before it can run.",
|
|
580
|
+
group: "misc",
|
|
581
|
+
targetKinds: [],
|
|
582
|
+
callback: "onInsertImage",
|
|
583
|
+
}),
|
|
404
584
|
mk({
|
|
405
585
|
id: "insert-table",
|
|
406
586
|
label: "Insert table",
|
|
@@ -416,6 +596,46 @@ export const EDITOR_ACTION_REGISTRY: readonly EditorAction[] = [
|
|
|
416
596
|
callback: "onAddComment",
|
|
417
597
|
}),
|
|
418
598
|
|
|
599
|
+
// -------- Search / navigation / app-level commands --------
|
|
600
|
+
// Empty targetKinds keeps these out of right-click local menus. The
|
|
601
|
+
// global command palette still projects them from the shared registry.
|
|
602
|
+
mkImportant({
|
|
603
|
+
id: "find",
|
|
604
|
+
label: "Find…",
|
|
605
|
+
description: "Host search panel is not wired.",
|
|
606
|
+
shortcut: ["Mod", "F"],
|
|
607
|
+
group: "misc",
|
|
608
|
+
targetKinds: [],
|
|
609
|
+
callback: "onFindRequested",
|
|
610
|
+
}),
|
|
611
|
+
mkImportant({
|
|
612
|
+
id: "replace",
|
|
613
|
+
label: "Replace…",
|
|
614
|
+
description: "Host replace panel is not wired.",
|
|
615
|
+
shortcut: ["Ctrl", "H"],
|
|
616
|
+
group: "misc",
|
|
617
|
+
targetKinds: [],
|
|
618
|
+
callback: "onReplaceRequested",
|
|
619
|
+
}),
|
|
620
|
+
mkImportant({
|
|
621
|
+
id: "go-to",
|
|
622
|
+
label: "Go to…",
|
|
623
|
+
description: "Host navigation panel is not wired.",
|
|
624
|
+
shortcut: ["Ctrl", "G"],
|
|
625
|
+
group: "misc",
|
|
626
|
+
targetKinds: [],
|
|
627
|
+
callback: "onGoToRequested",
|
|
628
|
+
}),
|
|
629
|
+
mkImportant({
|
|
630
|
+
id: "print",
|
|
631
|
+
label: "Print…",
|
|
632
|
+
description: "Host print/export flow is not wired.",
|
|
633
|
+
shortcut: ["Mod", "P"],
|
|
634
|
+
group: "misc",
|
|
635
|
+
targetKinds: [],
|
|
636
|
+
callback: "onPrintRequested",
|
|
637
|
+
}),
|
|
638
|
+
|
|
419
639
|
// -------- Suggestion / tracked change --------
|
|
420
640
|
mk({
|
|
421
641
|
id: "accept-suggestion",
|