@beyondwork/docx-react-component 1.0.20 → 1.0.21
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 +44 -25
- package/src/io/docx-session.ts +1 -1
- package/src/runtime/document-runtime.ts +2 -2
- package/src/ui/WordReviewEditor.tsx +66 -0
- package/src/ui/editor-runtime-boundary.ts +27 -1
- package/src/ui/editor-surface-controller.tsx +4 -0
- package/src/ui/workflow-surface-blocked-rails.ts +94 -0
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +225 -23
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +4 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +16 -0
- package/src/ui-tailwind/theme/editor-theme.css +126 -0
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beyondwork/docx-react-component",
|
|
3
3
|
"publisher": "beyondwork",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.21",
|
|
5
5
|
"description": "Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.",
|
|
6
|
+
"packageManager": "pnpm@10.30.3",
|
|
6
7
|
"type": "module",
|
|
7
8
|
"sideEffects": [
|
|
8
9
|
"**/*.css"
|
|
@@ -73,6 +74,14 @@
|
|
|
73
74
|
"types": "./src/core/commands/table-structure-commands.ts",
|
|
74
75
|
"import": "./src/core/commands/table-structure-commands.ts"
|
|
75
76
|
},
|
|
77
|
+
"./core/commands/style-commands": {
|
|
78
|
+
"types": "./src/core/commands/style-commands.ts",
|
|
79
|
+
"import": "./src/core/commands/style-commands.ts"
|
|
80
|
+
},
|
|
81
|
+
"./core/commands/section-layout-commands": {
|
|
82
|
+
"types": "./src/core/commands/section-layout-commands.ts",
|
|
83
|
+
"import": "./src/core/commands/section-layout-commands.ts"
|
|
84
|
+
},
|
|
76
85
|
"./core/state/editor-state": {
|
|
77
86
|
"types": "./src/core/state/editor-state.ts",
|
|
78
87
|
"import": "./src/core/state/editor-state.ts"
|
|
@@ -80,6 +89,30 @@
|
|
|
80
89
|
"./ui-tailwind/theme/editor-theme.css": "./src/ui-tailwind/theme/editor-theme.css"
|
|
81
90
|
},
|
|
82
91
|
"types": "./src/index.ts",
|
|
92
|
+
"scripts": {
|
|
93
|
+
"build": "tsup",
|
|
94
|
+
"test": "bash scripts/run-workspace-tests.sh",
|
|
95
|
+
"test:repo": "node scripts/run-repo-tests.mjs core",
|
|
96
|
+
"test:repo:all": "node scripts/run-repo-tests.mjs all",
|
|
97
|
+
"test:repo:optional": "node scripts/run-repo-tests.mjs optional",
|
|
98
|
+
"test:wcag-audit": "node scripts/run-repo-tests.mjs wcag-audit",
|
|
99
|
+
"test:harness": "pnpm --filter @docx-react-component/react-word-editor-harness test",
|
|
100
|
+
"lint": "pnpm run lint:no-authored-js && pnpm run lint:docs-contracts && pnpm run lint:tsgo && pnpm run lint:tsgo:harness",
|
|
101
|
+
"lint:docs-contracts": "bash scripts/check-reference-load-contract.sh",
|
|
102
|
+
"lint:no-authored-js": "bash scripts/check-no-authored-js.sh",
|
|
103
|
+
"lint:tsgo": "tsgo --noEmit -p tsconfig.build.json",
|
|
104
|
+
"lint:tsgo:harness": "pnpm --filter @docx-react-component/react-word-editor-harness lint:tsgo",
|
|
105
|
+
"context7:api-check": "bash scripts/context7-export-env.sh run bash scripts/context7-api-check.sh",
|
|
106
|
+
"wave:doctor": "bash scripts/context7-export-env.sh run pnpm exec wave doctor --json",
|
|
107
|
+
"wave:dry-run": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main --dry-run --no-dashboard",
|
|
108
|
+
"wave:launch": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main",
|
|
109
|
+
"wave:launch:managed": "bash scripts/wave-launch.sh",
|
|
110
|
+
"wave:status": "bash scripts/wave-status.sh",
|
|
111
|
+
"wave:watch": "bash scripts/wave-watch.sh --follow",
|
|
112
|
+
"wave:dashboard:current": "bash scripts/wave-dashboard-attach.sh current",
|
|
113
|
+
"wave:dashboard:global": "bash scripts/wave-dashboard-attach.sh global",
|
|
114
|
+
"harness:dev": "pnpm --filter @docx-react-component/react-word-editor-harness dev"
|
|
115
|
+
},
|
|
83
116
|
"keywords": [
|
|
84
117
|
"docx",
|
|
85
118
|
"word",
|
|
@@ -142,28 +175,14 @@
|
|
|
142
175
|
"tsup": "^8.3.0",
|
|
143
176
|
"tsx": "^4.21.0"
|
|
144
177
|
},
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
"
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
"lint:docs-contracts": "bash scripts/check-reference-load-contract.sh",
|
|
155
|
-
"lint:no-authored-js": "bash scripts/check-no-authored-js.sh",
|
|
156
|
-
"lint:tsgo": "tsgo --noEmit -p tsconfig.build.json",
|
|
157
|
-
"lint:tsgo:harness": "pnpm --filter @docx-react-component/react-word-editor-harness lint:tsgo",
|
|
158
|
-
"context7:api-check": "bash scripts/context7-export-env.sh run bash scripts/context7-api-check.sh",
|
|
159
|
-
"wave:doctor": "bash scripts/context7-export-env.sh run pnpm exec wave doctor --json",
|
|
160
|
-
"wave:dry-run": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main --dry-run --no-dashboard",
|
|
161
|
-
"wave:launch": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main",
|
|
162
|
-
"wave:launch:managed": "bash scripts/wave-launch.sh",
|
|
163
|
-
"wave:status": "bash scripts/wave-status.sh",
|
|
164
|
-
"wave:watch": "bash scripts/wave-watch.sh --follow",
|
|
165
|
-
"wave:dashboard:current": "bash scripts/wave-dashboard-attach.sh current",
|
|
166
|
-
"wave:dashboard:global": "bash scripts/wave-dashboard-attach.sh global",
|
|
167
|
-
"harness:dev": "pnpm --filter @docx-react-component/react-word-editor-harness dev"
|
|
178
|
+
"pnpm": {
|
|
179
|
+
"onlyBuiltDependencies": [
|
|
180
|
+
"esbuild",
|
|
181
|
+
"sharp"
|
|
182
|
+
],
|
|
183
|
+
"overrides": {
|
|
184
|
+
"react": "19.2.4",
|
|
185
|
+
"react-dom": "19.2.4"
|
|
186
|
+
}
|
|
168
187
|
}
|
|
169
|
-
}
|
|
188
|
+
}
|
package/src/io/docx-session.ts
CHANGED
|
@@ -726,7 +726,7 @@ function exportDocxEditorSession(
|
|
|
726
726
|
state.initialCanonicalSignature;
|
|
727
727
|
const canReuse = canReuseSourceBytesForCurrentDocument(state, currentDocument);
|
|
728
728
|
const commentCount = Object.keys(currentDocument.review?.comments ?? {}).length;
|
|
729
|
-
|
|
729
|
+
|
|
730
730
|
if (signatureMatch && canReuse) {
|
|
731
731
|
return {
|
|
732
732
|
bytes: new Uint8Array(state.sourceBytes),
|
|
@@ -2442,12 +2442,12 @@ function collectFieldsFromSubParts(
|
|
|
2442
2442
|
return index;
|
|
2443
2443
|
}
|
|
2444
2444
|
let nextIndex = index;
|
|
2445
|
-
for (const header of subParts.headers) {
|
|
2445
|
+
for (const header of subParts.headers ?? []) {
|
|
2446
2446
|
for (const block of header.blocks) {
|
|
2447
2447
|
nextIndex = collectFieldsFromBlock(block, entries, nextIndex);
|
|
2448
2448
|
}
|
|
2449
2449
|
}
|
|
2450
|
-
for (const footer of subParts.footers) {
|
|
2450
|
+
for (const footer of subParts.footers ?? []) {
|
|
2451
2451
|
for (const block of footer.blocks) {
|
|
2452
2452
|
nextIndex = collectFieldsFromBlock(block, entries, nextIndex);
|
|
2453
2453
|
}
|
|
@@ -45,6 +45,7 @@ import type {
|
|
|
45
45
|
UpdateFieldsResult,
|
|
46
46
|
ViewMode as EditorViewMode,
|
|
47
47
|
WorkflowBlockedCommandReason,
|
|
48
|
+
WorkflowMarkupSnapshot,
|
|
48
49
|
WorkflowScopeSnapshot,
|
|
49
50
|
WordReviewEditorEvent,
|
|
50
51
|
WordReviewEditorProps,
|
|
@@ -151,6 +152,7 @@ import type {
|
|
|
151
152
|
SelectionToolbarModel,
|
|
152
153
|
} from "./headless/selection-toolbar-model";
|
|
153
154
|
import { type EditorCommandBag, useCommandBag } from "./editor-command-bag.ts";
|
|
155
|
+
import { deriveVisibleWorkflowBlockedRails } from "./workflow-surface-blocked-rails.ts";
|
|
154
156
|
import {
|
|
155
157
|
type WordReviewEditorRuntime,
|
|
156
158
|
persistAndExport as persistAndExportFromBoundary,
|
|
@@ -645,6 +647,14 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
645
647
|
{ blockedReasons: [] } satisfies InteractionGuardSnapshot,
|
|
646
648
|
interactionGuardSnapshotsEqual,
|
|
647
649
|
);
|
|
650
|
+
const workflowMarkupSnapshot = useMemo(
|
|
651
|
+
() => (runtime ? runtime.getWorkflowMarkupSnapshot() : null),
|
|
652
|
+
[runtime, snapshot.revisionToken],
|
|
653
|
+
);
|
|
654
|
+
const workflowBlockedRails = useMemo(
|
|
655
|
+
() => deriveVisibleWorkflowBlockedRails(snapshot.surface, workflowMarkupSnapshot),
|
|
656
|
+
[snapshot.surface, workflowMarkupSnapshot],
|
|
657
|
+
);
|
|
648
658
|
const sessionState = useMemo(
|
|
649
659
|
() => (runtime ? runtime.getSessionState() : loadingSessionState),
|
|
650
660
|
[loadingSessionState, runtime, snapshot.revisionToken],
|
|
@@ -1611,6 +1621,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1611
1621
|
mediaPreviews={mediaPreviews}
|
|
1612
1622
|
isPageWorkspace={isPageWorkspace}
|
|
1613
1623
|
workflowScopes={workflowScopeSnapshot?.scopes}
|
|
1624
|
+
workflowCandidates={workflowScopeSnapshot?.candidates}
|
|
1625
|
+
workflowBlockedReasons={workflowBlockedRails}
|
|
1614
1626
|
onSelectionToolbarAnchorChange={handleSelectionToolbarAnchorChange}
|
|
1615
1627
|
{...editorCallbacks}
|
|
1616
1628
|
onCommentActivated={(commentId) => {
|
|
@@ -1904,6 +1916,13 @@ function applyRuntimeInsertSectionBreak(
|
|
|
1904
1916
|
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
1905
1917
|
return;
|
|
1906
1918
|
}
|
|
1919
|
+
if (snapshot.documentMode === "suggesting") {
|
|
1920
|
+
runtime.emitBlockedCommand("insertSectionBreak", [{
|
|
1921
|
+
code: "unsupported_surface",
|
|
1922
|
+
message: "Section break insertion is not supported in suggesting mode.",
|
|
1923
|
+
}]);
|
|
1924
|
+
return;
|
|
1925
|
+
}
|
|
1907
1926
|
|
|
1908
1927
|
const sessionState = runtime.getSessionState();
|
|
1909
1928
|
const timestamp = new Date().toISOString();
|
|
@@ -2075,6 +2094,14 @@ function applyRuntimeSetHeaderFooterLink(
|
|
|
2075
2094
|
}
|
|
2076
2095
|
|
|
2077
2096
|
function applyRuntimeInsertPageBreak(runtime: WordReviewEditorRuntime): void {
|
|
2097
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
2098
|
+
if (snapshot.documentMode === "suggesting") {
|
|
2099
|
+
runtime.emitBlockedCommand("insertPageBreak", [{
|
|
2100
|
+
code: "unsupported_surface",
|
|
2101
|
+
message: "Page break insertion is not supported in suggesting mode.",
|
|
2102
|
+
}]);
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2078
2105
|
const context = getStoryMutationContext(runtime);
|
|
2079
2106
|
if (!context) {
|
|
2080
2107
|
return;
|
|
@@ -2092,6 +2119,14 @@ function applyRuntimeInsertTable(
|
|
|
2092
2119
|
runtime: WordReviewEditorRuntime,
|
|
2093
2120
|
options: InsertTableOptions,
|
|
2094
2121
|
): void {
|
|
2122
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
2123
|
+
if (snapshot.documentMode === "suggesting") {
|
|
2124
|
+
runtime.emitBlockedCommand("insertTable", [{
|
|
2125
|
+
code: "unsupported_surface",
|
|
2126
|
+
message: "Table insertion is not supported in suggesting mode.",
|
|
2127
|
+
}]);
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2095
2130
|
const context = getStoryMutationContext(runtime);
|
|
2096
2131
|
if (!context) {
|
|
2097
2132
|
return;
|
|
@@ -2110,6 +2145,14 @@ function applyRuntimeInsertImage(
|
|
|
2110
2145
|
runtime: WordReviewEditorRuntime,
|
|
2111
2146
|
options: InsertImageOptions,
|
|
2112
2147
|
): void {
|
|
2148
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
2149
|
+
if (snapshot.documentMode === "suggesting") {
|
|
2150
|
+
runtime.emitBlockedCommand("insertImage", [{
|
|
2151
|
+
code: "unsupported_surface",
|
|
2152
|
+
message: "Image insertion is not supported in suggesting mode.",
|
|
2153
|
+
}]);
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2113
2156
|
const context = getStoryMutationContext(runtime);
|
|
2114
2157
|
if (!context) {
|
|
2115
2158
|
return;
|
|
@@ -2148,6 +2191,13 @@ function applyRuntimeImageResize(
|
|
|
2148
2191
|
if (!canApplyRuntimeMutation(snapshot)) {
|
|
2149
2192
|
return;
|
|
2150
2193
|
}
|
|
2194
|
+
if (snapshot.documentMode === "suggesting") {
|
|
2195
|
+
runtime.emitBlockedCommand("setImageLayout", [{
|
|
2196
|
+
code: "unsupported_surface",
|
|
2197
|
+
message: "Image resize is not supported in suggesting mode.",
|
|
2198
|
+
}]);
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2151
2201
|
|
|
2152
2202
|
try {
|
|
2153
2203
|
const sessionState = runtime.getSessionState();
|
|
@@ -2172,6 +2222,14 @@ function applyRuntimeImageReposition(
|
|
|
2172
2222
|
mediaId: string,
|
|
2173
2223
|
offsets: { horizontalOffsetEmu?: number; verticalOffsetEmu?: number },
|
|
2174
2224
|
): void {
|
|
2225
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
2226
|
+
if (snapshot.documentMode === "suggesting") {
|
|
2227
|
+
runtime.emitBlockedCommand("setImageFrame", [{
|
|
2228
|
+
code: "unsupported_surface",
|
|
2229
|
+
message: "Image reposition is not supported in suggesting mode.",
|
|
2230
|
+
}]);
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2175
2233
|
const context = getStoryMutationContext(runtime);
|
|
2176
2234
|
if (!context) {
|
|
2177
2235
|
return;
|
|
@@ -2217,6 +2275,14 @@ function applyRuntimeTableStructureOperation(
|
|
|
2217
2275
|
| { type: "split-cell" }
|
|
2218
2276
|
| { type: "set-cell-background"; color: string },
|
|
2219
2277
|
): void {
|
|
2278
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
2279
|
+
if (snapshot.documentMode === "suggesting") {
|
|
2280
|
+
runtime.emitBlockedCommand(`table.${operation.type}`, [{
|
|
2281
|
+
code: "unsupported_surface",
|
|
2282
|
+
message: `Table operation "${operation.type}" is not supported in suggesting mode.`,
|
|
2283
|
+
}]);
|
|
2284
|
+
return;
|
|
2285
|
+
}
|
|
2220
2286
|
const context = getStoryMutationContext(runtime);
|
|
2221
2287
|
if (!context) {
|
|
2222
2288
|
return;
|
|
@@ -73,12 +73,17 @@ export interface CreateRuntimeArgs {
|
|
|
73
73
|
interface RuntimeLifecycleHandlers {
|
|
74
74
|
onWarning?: (warning: EditorWarning) => void;
|
|
75
75
|
onError?: (error: EditorError) => void;
|
|
76
|
+
onEvent?: (event: WordReviewEditorEvent) => void;
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
export interface WordReviewEditorRuntime extends DocumentRuntime {
|
|
79
80
|
getFatalError?(): EditorError | undefined;
|
|
80
81
|
dispose?(): void;
|
|
81
82
|
setDefaultAuthorId?(authorId?: string): void;
|
|
83
|
+
emitBlockedCommand(
|
|
84
|
+
command: string,
|
|
85
|
+
reasons: Extract<WordReviewEditorEvent, { type: "command_blocked" }>["reasons"],
|
|
86
|
+
): void;
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
type PackageBackedDocxSession = ReturnType<typeof loadDocxEditorSession>;
|
|
@@ -357,6 +362,7 @@ export function useEditorRuntimeBoundary(
|
|
|
357
362
|
{
|
|
358
363
|
onWarning: onWarningRef.current,
|
|
359
364
|
onError: onErrorRef.current,
|
|
365
|
+
onEvent: onEventRef.current,
|
|
360
366
|
},
|
|
361
367
|
);
|
|
362
368
|
recordPerfSample("runtime.create");
|
|
@@ -538,7 +544,7 @@ function createRuntime(
|
|
|
538
544
|
? applySessionExportBarrier(initialSessionState, snapshotExportResolution.barrier)
|
|
539
545
|
: initialSessionState;
|
|
540
546
|
|
|
541
|
-
|
|
547
|
+
const runtime: WordReviewEditorRuntime = Object.assign(createDocumentRuntime({
|
|
542
548
|
documentId: args.documentId,
|
|
543
549
|
initialSessionState: runtimeSessionState,
|
|
544
550
|
sourceKind: args.source.source,
|
|
@@ -569,7 +575,26 @@ function createRuntime(
|
|
|
569
575
|
onWarning: handlers.onWarning,
|
|
570
576
|
onError: handlers.onError,
|
|
571
577
|
defaultAuthorId: args.currentUserId,
|
|
578
|
+
}), {
|
|
579
|
+
emitBlockedCommand: (
|
|
580
|
+
command: string,
|
|
581
|
+
reasons: Extract<WordReviewEditorEvent, { type: "command_blocked" }>["reasons"],
|
|
582
|
+
) => {
|
|
583
|
+
emitEditorEvent({
|
|
584
|
+
hostAdapter: args.hostAdapter,
|
|
585
|
+
datastore: args.datastore,
|
|
586
|
+
onEvent: handlers.onEvent,
|
|
587
|
+
event: {
|
|
588
|
+
type: "command_blocked",
|
|
589
|
+
documentId: args.documentId,
|
|
590
|
+
command,
|
|
591
|
+
reasons,
|
|
592
|
+
},
|
|
593
|
+
});
|
|
594
|
+
},
|
|
572
595
|
});
|
|
596
|
+
|
|
597
|
+
return runtime;
|
|
573
598
|
}
|
|
574
599
|
|
|
575
600
|
function createLoadingSnapshot(
|
|
@@ -691,6 +716,7 @@ function createLoadingRuntimeBridge(input: {
|
|
|
691
716
|
return {
|
|
692
717
|
subscribe: () => () => undefined,
|
|
693
718
|
subscribeToEvents: () => () => undefined,
|
|
719
|
+
emitBlockedCommand: () => undefined,
|
|
694
720
|
getRenderSnapshot: () => input.snapshot,
|
|
695
721
|
replaceText: () => undefined,
|
|
696
722
|
dispatch: () => undefined,
|
|
@@ -5,6 +5,8 @@ import type {
|
|
|
5
5
|
EditorUser,
|
|
6
6
|
RuntimeRenderSnapshot,
|
|
7
7
|
SelectionSnapshot,
|
|
8
|
+
WorkflowBlockedCommandReason,
|
|
9
|
+
WorkflowCandidateRange,
|
|
8
10
|
WorkflowScope,
|
|
9
11
|
} from "../api/public-types.ts";
|
|
10
12
|
import type { CanonicalDocumentEnvelope } from "../core/state/editor-state.ts";
|
|
@@ -41,6 +43,8 @@ export interface EditorSurfaceControllerProps {
|
|
|
41
43
|
onCommentActivated?: (commentId: string) => void;
|
|
42
44
|
onRevisionActivated?: (revisionId: string) => void;
|
|
43
45
|
workflowScopes?: readonly WorkflowScope[];
|
|
46
|
+
workflowCandidates?: readonly WorkflowCandidateRange[];
|
|
47
|
+
workflowBlockedReasons?: readonly WorkflowBlockedCommandReason[];
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
export const EditorSurfaceController = forwardRef<
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EditorStoryTarget,
|
|
3
|
+
RuntimeRenderSnapshot,
|
|
4
|
+
SurfaceBlockSnapshot,
|
|
5
|
+
WorkflowBlockedCommandReason,
|
|
6
|
+
WorkflowMarkupSnapshot,
|
|
7
|
+
} from "../api/public-types";
|
|
8
|
+
|
|
9
|
+
export function deriveVisibleWorkflowBlockedRails(
|
|
10
|
+
surface: RuntimeRenderSnapshot["surface"] | undefined,
|
|
11
|
+
markupSnapshot: WorkflowMarkupSnapshot | null,
|
|
12
|
+
): WorkflowBlockedCommandReason[] {
|
|
13
|
+
if (!surface || !markupSnapshot) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const visibleFragments = collectVisibleOpaqueFragmentStoryKeys(surface);
|
|
18
|
+
return markupSnapshot.opaqueFragments
|
|
19
|
+
.filter((fragment) =>
|
|
20
|
+
visibleFragments.has(
|
|
21
|
+
createOpaqueFragmentStoryKey(fragment.fragmentId, fragment.storyTarget ?? { kind: "main" }),
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
.map((fragment) => ({
|
|
25
|
+
code: fragment.blockedReasonCode,
|
|
26
|
+
message: fragment.detail,
|
|
27
|
+
anchor: fragment.anchor,
|
|
28
|
+
storyTarget: fragment.storyTarget,
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function collectVisibleOpaqueFragmentStoryKeys(
|
|
33
|
+
surface: NonNullable<RuntimeRenderSnapshot["surface"]>,
|
|
34
|
+
): Set<string> {
|
|
35
|
+
const keys = new Set<string>();
|
|
36
|
+
collectVisibleOpaqueFragmentStoryKeysInBlocks(surface.blocks, { kind: "main" }, keys);
|
|
37
|
+
for (const story of surface.secondaryStories) {
|
|
38
|
+
collectVisibleOpaqueFragmentStoryKeysInBlocks(story.blocks, story.target, keys);
|
|
39
|
+
}
|
|
40
|
+
return keys;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function collectVisibleOpaqueFragmentStoryKeysInBlocks(
|
|
44
|
+
blocks: readonly SurfaceBlockSnapshot[],
|
|
45
|
+
storyTarget: EditorStoryTarget,
|
|
46
|
+
keys: Set<string>,
|
|
47
|
+
): void {
|
|
48
|
+
for (const block of blocks) {
|
|
49
|
+
if (block.kind === "paragraph") {
|
|
50
|
+
for (const segment of block.segments) {
|
|
51
|
+
if (segment.kind === "opaque_inline") {
|
|
52
|
+
keys.add(createOpaqueFragmentStoryKey(segment.fragmentId, storyTarget));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (block.kind === "table") {
|
|
59
|
+
for (const row of block.rows) {
|
|
60
|
+
for (const cell of row.cells) {
|
|
61
|
+
collectVisibleOpaqueFragmentStoryKeysInBlocks(cell.content, storyTarget, keys);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (block.kind === "sdt_block") {
|
|
68
|
+
collectVisibleOpaqueFragmentStoryKeysInBlocks(block.children, storyTarget, keys);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
keys.add(createOpaqueFragmentStoryKey(block.fragmentId, storyTarget));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function createOpaqueFragmentStoryKey(
|
|
77
|
+
fragmentId: string,
|
|
78
|
+
storyTarget: EditorStoryTarget,
|
|
79
|
+
): string {
|
|
80
|
+
return `${fragmentId}:${serializeStoryTargetKey(storyTarget)}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function serializeStoryTargetKey(storyTarget: EditorStoryTarget): string {
|
|
84
|
+
switch (storyTarget.kind) {
|
|
85
|
+
case "main":
|
|
86
|
+
return "main";
|
|
87
|
+
case "header":
|
|
88
|
+
case "footer":
|
|
89
|
+
return `${storyTarget.kind}:${storyTarget.relationshipId}:${storyTarget.variant}:${storyTarget.sectionIndex ?? "none"}`;
|
|
90
|
+
case "footnote":
|
|
91
|
+
case "endnote":
|
|
92
|
+
return `${storyTarget.kind}:${storyTarget.noteId}`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -4,11 +4,156 @@ import type { CommentDecorationModel } from "../../ui/headless/comment-decoratio
|
|
|
4
4
|
import { getCommentHighlightClass, type MarkupDisplay } from "../../ui/headless/comment-decoration-model";
|
|
5
5
|
import type { RevisionDecorationModel } from "../../ui/headless/revision-decoration-model";
|
|
6
6
|
import { getRevisionHighlightClass } from "../../ui/headless/revision-decoration-model";
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
EditorAnchorProjection,
|
|
9
|
+
EditorStoryTarget,
|
|
10
|
+
WorkflowBlockedCommandReason,
|
|
11
|
+
WorkflowCandidateRange,
|
|
12
|
+
WorkflowScope,
|
|
13
|
+
} from "../../api/public-types";
|
|
8
14
|
import { MAIN_STORY_TARGET, storyTargetsEqual } from "../../core/selection/mapping.ts";
|
|
9
15
|
import type { PositionMap } from "./pm-position-map";
|
|
10
16
|
import type { Node as PMNode } from "prosemirror-model";
|
|
11
17
|
|
|
18
|
+
type RailDecorationSpec = {
|
|
19
|
+
railKind: "scope" | "candidate" | "blocked";
|
|
20
|
+
className: string;
|
|
21
|
+
attrs: Record<string, string>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function getWorkflowInlineClass(scope: WorkflowScope): string {
|
|
25
|
+
if (scope.mode === "edit") return "wre-workflow-inline wre-workflow-inline-edit";
|
|
26
|
+
if (scope.mode === "suggest") return "wre-workflow-inline wre-workflow-inline-suggest";
|
|
27
|
+
if (scope.mode === "comment") return "wre-workflow-inline wre-workflow-inline-comment";
|
|
28
|
+
return "wre-workflow-inline wre-workflow-inline-view";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getWorkflowRailClass(scope: WorkflowScope): string {
|
|
32
|
+
if (scope.mode === "edit") return "wre-workflow-rail wre-workflow-rail-edit";
|
|
33
|
+
if (scope.mode === "suggest") return "wre-workflow-rail wre-workflow-rail-suggest";
|
|
34
|
+
if (scope.mode === "comment") return "wre-workflow-rail wre-workflow-rail-comment";
|
|
35
|
+
return "wre-workflow-rail wre-workflow-rail-view";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getWorkflowCandidateInlineClass(): string {
|
|
39
|
+
return "wre-workflow-inline wre-workflow-inline-candidate";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getWorkflowCandidateRailClass(): string {
|
|
43
|
+
return "wre-workflow-rail wre-workflow-rail-candidate";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getWorkflowBlockedInlineClass(reason: WorkflowBlockedCommandReason): string {
|
|
47
|
+
if (reason.code === "workflow_blocked_import") {
|
|
48
|
+
return "wre-workflow-inline wre-workflow-inline-blocked-import";
|
|
49
|
+
}
|
|
50
|
+
return "wre-workflow-inline wre-workflow-inline-preserve-only";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getWorkflowBlockedRailClass(reason: WorkflowBlockedCommandReason): string {
|
|
54
|
+
if (reason.code === "workflow_blocked_import") {
|
|
55
|
+
return "wre-workflow-rail wre-workflow-rail-blocked-import";
|
|
56
|
+
}
|
|
57
|
+
return "wre-workflow-rail wre-workflow-rail-preserve-only";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function hasBlockChildren(node: PMNode): boolean {
|
|
61
|
+
for (let index = 0; index < node.childCount; index += 1) {
|
|
62
|
+
if (node.child(index).isBlock) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function collectRailRanges(doc: PMNode, from: number, to: number): Array<{ from: number; to: number }> {
|
|
70
|
+
const effectiveTo = Math.max(to, from + 1);
|
|
71
|
+
const ranges = new Map<string, { from: number; to: number }>();
|
|
72
|
+
let fallbackFrom: number | null = null;
|
|
73
|
+
let fallbackTo: number | null = null;
|
|
74
|
+
let fallbackSize: number | null = null;
|
|
75
|
+
|
|
76
|
+
doc.descendants((node, pos) => {
|
|
77
|
+
if (!node.isBlock || node.type.name === "doc") {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const nodeFrom = pos;
|
|
82
|
+
const nodeTo = pos + node.nodeSize;
|
|
83
|
+
if (nodeTo <= from || nodeFrom >= effectiveTo) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!hasBlockChildren(node)) {
|
|
88
|
+
ranges.set(`${nodeFrom}:${nodeTo}`, { from: nodeFrom, to: nodeTo });
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (nodeFrom <= from && nodeTo >= effectiveTo) {
|
|
93
|
+
const size = nodeTo - nodeFrom;
|
|
94
|
+
if (fallbackSize === null || size < fallbackSize) {
|
|
95
|
+
fallbackFrom = nodeFrom;
|
|
96
|
+
fallbackTo = nodeTo;
|
|
97
|
+
fallbackSize = size;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return true;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (ranges.size > 0) {
|
|
105
|
+
return [...ranges.values()];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (fallbackFrom !== null && fallbackTo !== null) {
|
|
109
|
+
return [{ from: fallbackFrom, to: fallbackTo }];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function buildAnchorPmRange(
|
|
116
|
+
anchor: EditorAnchorProjection,
|
|
117
|
+
positionMap: PositionMap,
|
|
118
|
+
): { from: number; to: number; allowInline: boolean } | null {
|
|
119
|
+
if (anchor.kind === "detached") {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (anchor.kind === "range") {
|
|
124
|
+
return {
|
|
125
|
+
from: positionMap.runtimeToPm(anchor.from),
|
|
126
|
+
to: positionMap.runtimeToPm(anchor.to),
|
|
127
|
+
allowInline: true,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const pmAt = positionMap.runtimeToPm(anchor.at);
|
|
132
|
+
return {
|
|
133
|
+
from: pmAt,
|
|
134
|
+
to: pmAt + 1,
|
|
135
|
+
allowInline: false,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function pushRailDecorations(
|
|
140
|
+
decorations: Decoration[],
|
|
141
|
+
doc: PMNode,
|
|
142
|
+
from: number,
|
|
143
|
+
to: number,
|
|
144
|
+
spec: RailDecorationSpec,
|
|
145
|
+
): void {
|
|
146
|
+
for (const range of collectRailRanges(doc, from, to)) {
|
|
147
|
+
decorations.push(
|
|
148
|
+
Decoration.node(range.from, range.to, {
|
|
149
|
+
class: spec.className,
|
|
150
|
+
"data-workflow-rail": spec.railKind,
|
|
151
|
+
...spec.attrs,
|
|
152
|
+
}),
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
12
157
|
/**
|
|
13
158
|
* Build ProseMirror DecorationSet from runtime comment and revision models.
|
|
14
159
|
*
|
|
@@ -25,6 +170,8 @@ export function buildDecorations(
|
|
|
25
170
|
showTrackedChanges = true,
|
|
26
171
|
workflowScopes?: readonly WorkflowScope[],
|
|
27
172
|
activeStory: EditorStoryTarget = MAIN_STORY_TARGET,
|
|
173
|
+
workflowCandidates?: readonly WorkflowCandidateRange[],
|
|
174
|
+
workflowBlockedReasons?: readonly WorkflowBlockedCommandReason[],
|
|
28
175
|
): DecorationSet {
|
|
29
176
|
const decorations: Decoration[] = [];
|
|
30
177
|
|
|
@@ -98,34 +245,89 @@ export function buildDecorations(
|
|
|
98
245
|
}
|
|
99
246
|
}
|
|
100
247
|
|
|
101
|
-
// Walk workflow scopes and create inline decorations for scope emphasis.
|
|
102
248
|
if (workflowScopes) {
|
|
103
249
|
for (const scope of workflowScopes) {
|
|
104
250
|
const scopeStoryTarget = scope.storyTarget ?? MAIN_STORY_TARGET;
|
|
105
251
|
if (!storyTargetsEqual(scopeStoryTarget, activeStory)) continue;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
Decoration.inline(pmFrom, pmTo, {
|
|
124
|
-
class: modeClass,
|
|
252
|
+
const pmRange = buildAnchorPmRange(scope.anchor, positionMap);
|
|
253
|
+
if (!pmRange) continue;
|
|
254
|
+
|
|
255
|
+
if (pmRange.allowInline && pmRange.from < pmRange.to) {
|
|
256
|
+
decorations.push(
|
|
257
|
+
Decoration.inline(pmRange.from, pmRange.to, {
|
|
258
|
+
class: getWorkflowInlineClass(scope),
|
|
259
|
+
"data-workflow-scope-id": scope.scopeId,
|
|
260
|
+
"data-workflow-scope-mode": scope.mode,
|
|
261
|
+
}),
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
pushRailDecorations(decorations, doc, pmRange.from, pmRange.to, {
|
|
266
|
+
railKind: "scope",
|
|
267
|
+
className: getWorkflowRailClass(scope),
|
|
268
|
+
attrs: {
|
|
125
269
|
"data-workflow-scope-id": scope.scopeId,
|
|
126
270
|
"data-workflow-scope-mode": scope.mode,
|
|
127
|
-
}
|
|
128
|
-
);
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (workflowCandidates) {
|
|
277
|
+
for (const candidate of workflowCandidates) {
|
|
278
|
+
const candidateStoryTarget = candidate.storyTarget ?? MAIN_STORY_TARGET;
|
|
279
|
+
if (!storyTargetsEqual(candidateStoryTarget, activeStory)) continue;
|
|
280
|
+
const pmRange = buildAnchorPmRange(candidate.anchor, positionMap);
|
|
281
|
+
if (!pmRange) continue;
|
|
282
|
+
|
|
283
|
+
if (pmRange.allowInline && pmRange.from < pmRange.to) {
|
|
284
|
+
decorations.push(
|
|
285
|
+
Decoration.inline(pmRange.from, pmRange.to, {
|
|
286
|
+
class: getWorkflowCandidateInlineClass(),
|
|
287
|
+
"data-workflow-candidate-id": candidate.candidateId,
|
|
288
|
+
}),
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
pushRailDecorations(decorations, doc, pmRange.from, pmRange.to, {
|
|
293
|
+
railKind: "candidate",
|
|
294
|
+
className: getWorkflowCandidateRailClass(),
|
|
295
|
+
attrs: {
|
|
296
|
+
"data-workflow-candidate-id": candidate.candidateId,
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (workflowBlockedReasons) {
|
|
303
|
+
for (const reason of workflowBlockedReasons) {
|
|
304
|
+
if (
|
|
305
|
+
reason.code !== "workflow_preserve_only" &&
|
|
306
|
+
reason.code !== "workflow_blocked_import"
|
|
307
|
+
) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const reasonStoryTarget = reason.storyTarget ?? MAIN_STORY_TARGET;
|
|
311
|
+
if (!storyTargetsEqual(reasonStoryTarget, activeStory) || !reason.anchor) continue;
|
|
312
|
+
const pmRange = buildAnchorPmRange(reason.anchor, positionMap);
|
|
313
|
+
if (!pmRange) continue;
|
|
314
|
+
|
|
315
|
+
if (pmRange.allowInline && pmRange.from < pmRange.to) {
|
|
316
|
+
decorations.push(
|
|
317
|
+
Decoration.inline(pmRange.from, pmRange.to, {
|
|
318
|
+
class: getWorkflowBlockedInlineClass(reason),
|
|
319
|
+
"data-workflow-blocked-code": reason.code,
|
|
320
|
+
}),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
pushRailDecorations(decorations, doc, pmRange.from, pmRange.to, {
|
|
325
|
+
railKind: "blocked",
|
|
326
|
+
className: getWorkflowBlockedRailClass(reason),
|
|
327
|
+
attrs: {
|
|
328
|
+
"data-workflow-blocked-code": reason.code,
|
|
329
|
+
},
|
|
330
|
+
});
|
|
129
331
|
}
|
|
130
332
|
}
|
|
131
333
|
|
|
@@ -39,6 +39,8 @@ export function createSurfaceDecorationKey(input: {
|
|
|
39
39
|
activeCommentId?: string;
|
|
40
40
|
activeRevisionId?: string;
|
|
41
41
|
workflowScopeSignature?: string;
|
|
42
|
+
workflowCandidateSignature?: string;
|
|
43
|
+
workflowBlockedSignature?: string;
|
|
42
44
|
}): string {
|
|
43
45
|
return JSON.stringify({
|
|
44
46
|
markupDisplay: input.markupDisplay,
|
|
@@ -47,5 +49,7 @@ export function createSurfaceDecorationKey(input: {
|
|
|
47
49
|
activeCommentId: input.activeCommentId ?? null,
|
|
48
50
|
activeRevisionId: input.activeRevisionId ?? null,
|
|
49
51
|
workflowScopeSignature: input.workflowScopeSignature ?? null,
|
|
52
|
+
workflowCandidateSignature: input.workflowCandidateSignature ?? null,
|
|
53
|
+
workflowBlockedSignature: input.workflowBlockedSignature ?? null,
|
|
50
54
|
});
|
|
51
55
|
}
|
|
@@ -16,6 +16,8 @@ import type {
|
|
|
16
16
|
SearchOptions,
|
|
17
17
|
SearchResultSnapshot,
|
|
18
18
|
SelectionSnapshot,
|
|
19
|
+
WorkflowBlockedCommandReason,
|
|
20
|
+
WorkflowCandidateRange,
|
|
19
21
|
WorkflowScope,
|
|
20
22
|
} from "../../api/public-types";
|
|
21
23
|
import type { CanonicalDocumentEnvelope } from "../../core/state/editor-state.ts";
|
|
@@ -90,6 +92,8 @@ export interface TwProseMirrorSurfaceProps {
|
|
|
90
92
|
onSelectionToolbarAnchorChange?: (anchor: SelectionToolbarAnchor | null) => void;
|
|
91
93
|
mediaPreviews?: Record<string, MediaPreviewDescriptor>;
|
|
92
94
|
workflowScopes?: readonly WorkflowScope[];
|
|
95
|
+
workflowCandidates?: readonly WorkflowCandidateRange[];
|
|
96
|
+
workflowBlockedReasons?: readonly WorkflowBlockedCommandReason[];
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
export interface TwProseMirrorSurfaceRef {
|
|
@@ -201,11 +205,15 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
201
205
|
activeCommentId: snapshot.comments.activeCommentId,
|
|
202
206
|
activeRevisionId: props.activeRevisionId,
|
|
203
207
|
workflowScopeSignature: JSON.stringify(props.workflowScopes ?? []),
|
|
208
|
+
workflowCandidateSignature: JSON.stringify(props.workflowCandidates ?? []),
|
|
209
|
+
workflowBlockedSignature: JSON.stringify(props.workflowBlockedReasons ?? []),
|
|
204
210
|
}),
|
|
205
211
|
[
|
|
206
212
|
canEdit,
|
|
207
213
|
markupDisplay,
|
|
208
214
|
props.activeRevisionId,
|
|
215
|
+
props.workflowCandidates,
|
|
216
|
+
props.workflowBlockedReasons,
|
|
209
217
|
props.workflowScopes,
|
|
210
218
|
showTrackedChanges,
|
|
211
219
|
snapshot.comments.activeCommentId,
|
|
@@ -249,6 +257,8 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
249
257
|
showTrackedChanges,
|
|
250
258
|
props.workflowScopes,
|
|
251
259
|
snapshot.activeStory,
|
|
260
|
+
props.workflowCandidates,
|
|
261
|
+
props.workflowBlockedReasons,
|
|
252
262
|
);
|
|
253
263
|
view.setProps({
|
|
254
264
|
editable: () => canEdit,
|
|
@@ -265,7 +275,11 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
265
275
|
markupDisplay,
|
|
266
276
|
revisionModel,
|
|
267
277
|
showTrackedChanges,
|
|
278
|
+
props.workflowBlockedReasons,
|
|
279
|
+
props.workflowCandidates,
|
|
268
280
|
props.workflowScopes,
|
|
281
|
+
props.workflowCandidates,
|
|
282
|
+
props.workflowBlockedReasons,
|
|
269
283
|
],
|
|
270
284
|
);
|
|
271
285
|
|
|
@@ -293,6 +307,8 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
293
307
|
showTrackedChanges,
|
|
294
308
|
props.workflowScopes,
|
|
295
309
|
snapshot.activeStory,
|
|
310
|
+
props.workflowCandidates,
|
|
311
|
+
props.workflowBlockedReasons,
|
|
296
312
|
);
|
|
297
313
|
recordPerfSample("pm.rebuild");
|
|
298
314
|
incrementInvalidationCounter("pm.laneA.rebuilds");
|
|
@@ -258,6 +258,132 @@
|
|
|
258
258
|
outline: none;
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
.prosemirror-surface .ProseMirror .wre-workflow-inline {
|
|
262
|
+
border-radius: 0.25rem;
|
|
263
|
+
box-shadow: inset 0 0 0 1px transparent;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.prosemirror-surface .ProseMirror .wre-workflow-inline-edit {
|
|
267
|
+
background: color-mix(in srgb, var(--color-accent) 10%, transparent);
|
|
268
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-accent) 18%, transparent);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.prosemirror-surface .ProseMirror .wre-workflow-inline-suggest {
|
|
272
|
+
background: color-mix(in srgb, var(--color-warning) 12%, transparent);
|
|
273
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-warning) 20%, transparent);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.prosemirror-surface .ProseMirror .wre-workflow-inline-comment {
|
|
277
|
+
background: color-mix(in srgb, var(--color-insert) 10%, transparent);
|
|
278
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-insert) 18%, transparent);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.prosemirror-surface .ProseMirror .wre-workflow-inline-view {
|
|
282
|
+
background: color-mix(in srgb, var(--color-secondary) 7%, transparent);
|
|
283
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-secondary) 14%, transparent);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.prosemirror-surface .ProseMirror .wre-workflow-inline-candidate {
|
|
287
|
+
background: color-mix(in srgb, var(--color-warning) 6%, transparent);
|
|
288
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-warning) 18%, transparent);
|
|
289
|
+
text-decoration: underline;
|
|
290
|
+
text-decoration-style: dashed;
|
|
291
|
+
text-decoration-color: color-mix(in srgb, var(--color-warning) 55%, transparent);
|
|
292
|
+
text-underline-offset: 0.18em;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.prosemirror-surface .ProseMirror .wre-workflow-inline-preserve-only {
|
|
296
|
+
background: color-mix(in srgb, var(--color-danger) 7%, transparent);
|
|
297
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-danger) 18%, transparent);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.prosemirror-surface .ProseMirror .wre-workflow-inline-blocked-import {
|
|
301
|
+
background: color-mix(in srgb, var(--color-danger) 8%, transparent);
|
|
302
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-danger) 24%, transparent);
|
|
303
|
+
text-decoration: underline;
|
|
304
|
+
text-decoration-style: wavy;
|
|
305
|
+
text-decoration-color: color-mix(in srgb, var(--color-danger) 70%, transparent);
|
|
306
|
+
text-underline-offset: 0.18em;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.prosemirror-surface .ProseMirror .wre-workflow-rail {
|
|
310
|
+
position: relative;
|
|
311
|
+
padding-left: 0.875rem;
|
|
312
|
+
border-radius: 0.25rem;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.prosemirror-surface .ProseMirror .wre-workflow-rail::before {
|
|
316
|
+
content: "";
|
|
317
|
+
position: absolute;
|
|
318
|
+
left: 0;
|
|
319
|
+
top: 0.2rem;
|
|
320
|
+
bottom: 0.2rem;
|
|
321
|
+
width: 0.3rem;
|
|
322
|
+
border-radius: 999px;
|
|
323
|
+
background: var(--wre-workflow-rail-color, var(--color-border-strong));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.prosemirror-surface .ProseMirror .wre-workflow-rail-edit {
|
|
327
|
+
--wre-workflow-rail-color: var(--color-accent);
|
|
328
|
+
background: color-mix(in srgb, var(--color-accent) 7%, transparent);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.prosemirror-surface .ProseMirror .wre-workflow-rail-suggest {
|
|
332
|
+
--wre-workflow-rail-color: var(--color-warning);
|
|
333
|
+
background: color-mix(in srgb, var(--color-warning) 8%, transparent);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.prosemirror-surface .ProseMirror .wre-workflow-rail-comment {
|
|
337
|
+
--wre-workflow-rail-color: var(--color-insert);
|
|
338
|
+
background: color-mix(in srgb, var(--color-insert) 7%, transparent);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.prosemirror-surface .ProseMirror .wre-workflow-rail-view {
|
|
342
|
+
--wre-workflow-rail-color: var(--color-secondary);
|
|
343
|
+
background: color-mix(in srgb, var(--color-secondary) 6%, transparent);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.prosemirror-surface .ProseMirror .wre-workflow-rail-candidate {
|
|
347
|
+
--wre-workflow-rail-color: var(--color-warning);
|
|
348
|
+
background: color-mix(in srgb, var(--color-warning) 4%, transparent);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.prosemirror-surface .ProseMirror .wre-workflow-rail-candidate::before {
|
|
352
|
+
background:
|
|
353
|
+
repeating-linear-gradient(
|
|
354
|
+
to bottom,
|
|
355
|
+
var(--wre-workflow-rail-color, var(--color-warning)) 0,
|
|
356
|
+
var(--wre-workflow-rail-color, var(--color-warning)) 6px,
|
|
357
|
+
transparent 6px,
|
|
358
|
+
transparent 10px
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.prosemirror-surface .ProseMirror .wre-workflow-rail-preserve-only {
|
|
363
|
+
--wre-workflow-rail-color: var(--color-danger);
|
|
364
|
+
background: color-mix(in srgb, var(--color-danger) 5%, transparent);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.prosemirror-surface .ProseMirror .wre-workflow-rail-preserve-only::before {
|
|
368
|
+
opacity: 0.85;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.prosemirror-surface .ProseMirror .wre-workflow-rail-blocked-import {
|
|
372
|
+
--wre-workflow-rail-color: var(--color-danger);
|
|
373
|
+
background: color-mix(in srgb, var(--color-danger) 8%, transparent);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.prosemirror-surface .ProseMirror .wre-workflow-rail-blocked-import::before {
|
|
377
|
+
background:
|
|
378
|
+
repeating-linear-gradient(
|
|
379
|
+
to bottom,
|
|
380
|
+
var(--wre-workflow-rail-color, var(--color-danger)) 0,
|
|
381
|
+
var(--wre-workflow-rail-color, var(--color-danger)) 4px,
|
|
382
|
+
transparent 4px,
|
|
383
|
+
transparent 8px
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
261
387
|
.prosemirror-surface:focus-visible {
|
|
262
388
|
outline: none;
|
|
263
389
|
}
|