@beyondwork/docx-react-component 1.0.17 → 1.0.19
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 +32 -34
- package/src/api/README.md +5 -1
- package/src/api/public-types.ts +374 -4
- package/src/api/session-state.ts +58 -0
- package/src/core/commands/formatting-commands.ts +1 -0
- package/src/core/commands/image-commands.ts +147 -0
- package/src/core/commands/index.ts +5 -1
- 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 +329 -0
- package/src/core/selection/mapping.ts +41 -0
- package/src/core/state/editor-state.ts +1 -1
- package/src/index.ts +30 -0
- package/src/io/docx-session.ts +260 -39
- package/src/io/export/serialize-main-document.ts +202 -5
- package/src/io/export/serialize-numbering.ts +28 -7
- package/src/io/normalize/normalize-text.ts +63 -25
- package/src/io/ooxml/numbering-sentinels.ts +44 -0
- package/src/io/ooxml/parse-footnotes.ts +212 -20
- package/src/io/ooxml/parse-headers-footers.ts +229 -25
- package/src/io/ooxml/parse-inline-media.ts +16 -0
- package/src/io/ooxml/parse-main-document.ts +411 -6
- package/src/io/ooxml/parse-numbering.ts +7 -0
- 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/model/canonical-document.ts +133 -3
- package/src/model/cds-1.0.0.ts +13 -0
- package/src/model/snapshot.ts +2 -1
- package/src/runtime/document-layout.ts +332 -0
- package/src/runtime/document-navigation.ts +564 -0
- package/src/runtime/document-runtime.ts +265 -35
- 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 +1 -0
- package/src/runtime/session-capabilities.ts +2 -0
- package/src/runtime/story-context.ts +164 -0
- package/src/runtime/story-targeting.ts +162 -0
- package/src/runtime/surface-projection.ts +239 -12
- package/src/runtime/table-schema.ts +87 -5
- package/src/runtime/view-state.ts +459 -0
- package/src/ui/WordReviewEditor.tsx +1902 -312
- package/src/ui/browser-export.ts +52 -0
- package/src/ui/headless/preserve-editor-selection.ts +5 -0
- 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-tailwind/chrome/tw-page-ruler.tsx +386 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +125 -14
- package/src/ui-tailwind/editor-surface/perf-probe.ts +107 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +45 -6
- package/src/ui-tailwind/editor-surface/pm-contextual-ui.ts +31 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +2 -2
- package/src/ui-tailwind/editor-surface/pm-schema.ts +47 -5
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +95 -22
- package/src/ui-tailwind/editor-surface/search-plugin.ts +19 -68
- package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +11 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +394 -77
- 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/review/tw-comment-sidebar.tsx +277 -147
- package/src/ui-tailwind/review/tw-review-rail.tsx +6 -6
- package/src/ui-tailwind/theme/editor-theme.css +123 -0
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +4 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +291 -12
- package/src/ui-tailwind/tw-review-workspace.tsx +926 -27
- package/src/validation/compatibility-engine.ts +92 -20
- package/src/validation/diagnostics.ts +1 -0
- package/src/validation/docx-comment-proof.ts +487 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export type PerfProbeKind = "typing" | "selection";
|
|
2
|
+
|
|
3
|
+
export interface PerfProbeSample {
|
|
4
|
+
token: string;
|
|
5
|
+
kind: PerfProbeKind;
|
|
6
|
+
durationMs: number;
|
|
7
|
+
recordedAt: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface PendingProbe {
|
|
11
|
+
kind: PerfProbeKind;
|
|
12
|
+
startedAt: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface PerfProbeState {
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
nextToken?: number;
|
|
18
|
+
pending?: Record<string, PendingProbe>;
|
|
19
|
+
samples?: PerfProbeSample[];
|
|
20
|
+
maxSamples?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PerfProbeSummary {
|
|
24
|
+
samples: PerfProbeSample[];
|
|
25
|
+
latest: {
|
|
26
|
+
typing: PerfProbeSample | null;
|
|
27
|
+
selection: PerfProbeSample | null;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare global {
|
|
32
|
+
interface Window {
|
|
33
|
+
__DOCX_REACT_PERF_PROBE__?: PerfProbeState;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function startPerfProbe(kind: PerfProbeKind): string | null {
|
|
38
|
+
const state = getEnabledState();
|
|
39
|
+
if (!state) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const token = `${kind}-${state.nextToken ?? 0}`;
|
|
44
|
+
state.nextToken = (state.nextToken ?? 0) + 1;
|
|
45
|
+
state.pending ??= {};
|
|
46
|
+
state.pending[token] = {
|
|
47
|
+
kind,
|
|
48
|
+
startedAt: performance.now(),
|
|
49
|
+
};
|
|
50
|
+
return token;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function finishPerfProbe(token: string | null | undefined): PerfProbeSample | null {
|
|
54
|
+
if (!token) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const state = getEnabledState();
|
|
58
|
+
if (!state?.pending?.[token]) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const pending = state.pending[token];
|
|
63
|
+
delete state.pending[token];
|
|
64
|
+
|
|
65
|
+
const sample: PerfProbeSample = {
|
|
66
|
+
token,
|
|
67
|
+
kind: pending.kind,
|
|
68
|
+
durationMs: performance.now() - pending.startedAt,
|
|
69
|
+
recordedAt: Date.now(),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
state.samples ??= [];
|
|
73
|
+
state.samples.push(sample);
|
|
74
|
+
const maxSamples = state.maxSamples ?? 20;
|
|
75
|
+
if (state.samples.length > maxSamples) {
|
|
76
|
+
state.samples.splice(0, state.samples.length - maxSamples);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return sample;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function getLatestPerfSummary(): PerfProbeSummary | null {
|
|
83
|
+
const state = getEnabledState();
|
|
84
|
+
const samples = state?.samples ?? [];
|
|
85
|
+
if (!state || samples.length === 0) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
samples: [...samples],
|
|
91
|
+
latest: {
|
|
92
|
+
typing: [...samples].reverse().find((sample) => sample.kind === "typing") ?? null,
|
|
93
|
+
selection: [...samples].reverse().find((sample) => sample.kind === "selection") ?? null,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getEnabledState(): PerfProbeState | null {
|
|
99
|
+
if (typeof window === "undefined") {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const state = window.__DOCX_REACT_PERF_PROBE__;
|
|
103
|
+
if (!state?.enabled) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return state;
|
|
107
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { Plugin, PluginKey } from "prosemirror-state";
|
|
1
|
+
import { NodeSelection, Plugin, PluginKey } from "prosemirror-state";
|
|
2
2
|
import { keymap } from "prosemirror-keymap";
|
|
3
3
|
import { columnResizing, goToNextCell, isInTable, tableEditing } from "prosemirror-tables";
|
|
4
4
|
|
|
5
5
|
import type { SelectionSnapshot } from "../../api/public-types";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
createNodeSelectionSnapshot,
|
|
8
|
+
createSelectionSnapshot,
|
|
9
|
+
} from "../../ui/headless/selection-helpers";
|
|
7
10
|
import type { PositionMap } from "./pm-position-map";
|
|
8
11
|
|
|
9
12
|
export interface CommandBridgeCallbacks {
|
|
@@ -13,10 +16,12 @@ export interface CommandBridgeCallbacks {
|
|
|
13
16
|
onSplitParagraph: () => void;
|
|
14
17
|
onInsertHardBreak: () => void;
|
|
15
18
|
onInsertTab: () => void;
|
|
19
|
+
onOutdentTab?: () => void;
|
|
16
20
|
onUndo: () => void;
|
|
17
21
|
onRedo: () => void;
|
|
18
22
|
onSelectionChange: (selection: SelectionSnapshot) => void;
|
|
19
23
|
getPositionMap: () => PositionMap | null;
|
|
24
|
+
isSelectionSyncSuppressed?: () => boolean;
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
const bridgeKey = new PluginKey("command-bridge");
|
|
@@ -31,6 +36,8 @@ const bridgeKey = new PluginKey("command-bridge");
|
|
|
31
36
|
export function createCommandBridgePlugins(
|
|
32
37
|
callbacks: CommandBridgeCallbacks,
|
|
33
38
|
): Plugin[] {
|
|
39
|
+
let isComposing = false;
|
|
40
|
+
|
|
34
41
|
// Transaction filter: block ALL doc-changing transactions.
|
|
35
42
|
// The runtime is the sole authority for document mutations.
|
|
36
43
|
const filterPlugin = new Plugin({
|
|
@@ -48,15 +55,26 @@ export function createCommandBridgePlugins(
|
|
|
48
55
|
view() {
|
|
49
56
|
return {
|
|
50
57
|
update(view, prevState) {
|
|
58
|
+
if (callbacks.isSelectionSyncSuppressed?.()) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (isComposing) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
51
64
|
if (!view.state.selection.eq(prevState.selection)) {
|
|
52
65
|
const posMap = callbacks.getPositionMap();
|
|
53
66
|
if (!posMap) return;
|
|
54
67
|
|
|
68
|
+
if (view.state.selection instanceof NodeSelection) {
|
|
69
|
+
callbacks.onSelectionChange(
|
|
70
|
+
createNodeSelectionSnapshot(posMap.pmToRuntime(view.state.selection.from), 1),
|
|
71
|
+
);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
55
75
|
const { from, to } = view.state.selection;
|
|
56
|
-
const runtimeAnchor = posMap.pmToRuntime(from);
|
|
57
|
-
const runtimeHead = posMap.pmToRuntime(to);
|
|
58
76
|
callbacks.onSelectionChange(
|
|
59
|
-
createSelectionSnapshot(
|
|
77
|
+
createSelectionSnapshot(posMap.pmToRuntime(from), posMap.pmToRuntime(to)),
|
|
60
78
|
);
|
|
61
79
|
}
|
|
62
80
|
},
|
|
@@ -67,6 +85,20 @@ export function createCommandBridgePlugins(
|
|
|
67
85
|
// Text input hook: intercept typed characters.
|
|
68
86
|
const inputPlugin = new Plugin({
|
|
69
87
|
props: {
|
|
88
|
+
handleDOMEvents: {
|
|
89
|
+
blur() {
|
|
90
|
+
isComposing = false;
|
|
91
|
+
return false;
|
|
92
|
+
},
|
|
93
|
+
compositionstart() {
|
|
94
|
+
isComposing = true;
|
|
95
|
+
return false;
|
|
96
|
+
},
|
|
97
|
+
compositionend() {
|
|
98
|
+
isComposing = false;
|
|
99
|
+
return false;
|
|
100
|
+
},
|
|
101
|
+
},
|
|
70
102
|
handleTextInput(_view, _from, _to, text) {
|
|
71
103
|
callbacks.onInsertText(text);
|
|
72
104
|
return true; // Block PM from processing
|
|
@@ -87,22 +119,27 @@ export function createCommandBridgePlugins(
|
|
|
87
119
|
// Keymap: intercept editing keys and dispatch runtime commands.
|
|
88
120
|
const keymapPlugin = keymap({
|
|
89
121
|
Backspace: () => {
|
|
122
|
+
if (isComposing) return false;
|
|
90
123
|
callbacks.onDeleteBackward();
|
|
91
124
|
return true;
|
|
92
125
|
},
|
|
93
126
|
Delete: () => {
|
|
127
|
+
if (isComposing) return false;
|
|
94
128
|
callbacks.onDeleteForward();
|
|
95
129
|
return true;
|
|
96
130
|
},
|
|
97
131
|
Enter: () => {
|
|
132
|
+
if (isComposing) return false;
|
|
98
133
|
callbacks.onSplitParagraph();
|
|
99
134
|
return true;
|
|
100
135
|
},
|
|
101
136
|
"Shift-Enter": () => {
|
|
137
|
+
if (isComposing) return false;
|
|
102
138
|
callbacks.onInsertHardBreak();
|
|
103
139
|
return true;
|
|
104
140
|
},
|
|
105
141
|
Tab: (state, dispatch, view) => {
|
|
142
|
+
if (isComposing) return false;
|
|
106
143
|
if (isInTable(state)) {
|
|
107
144
|
return goToNextCell(1)(state, dispatch, view);
|
|
108
145
|
}
|
|
@@ -110,10 +147,12 @@ export function createCommandBridgePlugins(
|
|
|
110
147
|
return true;
|
|
111
148
|
},
|
|
112
149
|
"Shift-Tab": (state, dispatch, view) => {
|
|
150
|
+
if (isComposing) return false;
|
|
113
151
|
if (isInTable(state)) {
|
|
114
152
|
return goToNextCell(-1)(state, dispatch, view);
|
|
115
153
|
}
|
|
116
|
-
|
|
154
|
+
callbacks.onOutdentTab?.();
|
|
155
|
+
return true;
|
|
117
156
|
},
|
|
118
157
|
"Mod-z": () => {
|
|
119
158
|
callbacks.onUndo();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Plugin } from "prosemirror-state";
|
|
2
|
+
|
|
3
|
+
export interface ContextualInteractionCallbacks {
|
|
4
|
+
onCommentActivated?: (commentId: string) => void;
|
|
5
|
+
onRevisionActivated?: (revisionId: string) => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function createContextualInteractionPlugin(
|
|
9
|
+
callbacks: ContextualInteractionCallbacks,
|
|
10
|
+
): Plugin {
|
|
11
|
+
return new Plugin({
|
|
12
|
+
props: {
|
|
13
|
+
handleClick(_view, _pos, event) {
|
|
14
|
+
const target = event.target as HTMLElement | null;
|
|
15
|
+
const commentId = target?.closest?.("[data-comment-id]")?.getAttribute("data-comment-id");
|
|
16
|
+
if (commentId) {
|
|
17
|
+
callbacks.onCommentActivated?.(commentId);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const revisionId = target?.closest?.("[data-revision-id]")?.getAttribute("data-revision-id");
|
|
22
|
+
if (revisionId) {
|
|
23
|
+
callbacks.onRevisionActivated?.(revisionId);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return false;
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -25,7 +25,7 @@ export function buildPositionMap(surface: EditorSurfaceSnapshot): PositionMap {
|
|
|
25
25
|
return entries[0]?.pmStart ?? 1;
|
|
26
26
|
}
|
|
27
27
|
if (runtimePos >= runtimeStorySize) {
|
|
28
|
-
return pmDocSize - 1;
|
|
28
|
+
return entries[entries.length - 1]?.pmEnd ?? pmDocSize - 1;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
for (const entry of entries) {
|
|
@@ -75,7 +75,7 @@ function walkBlocks(
|
|
|
75
75
|
for (const block of blocks) {
|
|
76
76
|
switch (block.kind) {
|
|
77
77
|
case "paragraph": {
|
|
78
|
-
const pmContentStart = nextPmCursor
|
|
78
|
+
const pmContentStart = nextPmCursor;
|
|
79
79
|
const runtimeLength = block.to - block.from;
|
|
80
80
|
entries.push({
|
|
81
81
|
runtimeStart: block.from,
|
|
@@ -95,6 +95,7 @@ export const editorSchema = new Schema({
|
|
|
95
95
|
numberingInstanceId: { default: null },
|
|
96
96
|
numberingLevel: { default: null },
|
|
97
97
|
numberingPrefix: { default: null },
|
|
98
|
+
numberingSuffix: { default: null },
|
|
98
99
|
alignment: { default: null },
|
|
99
100
|
spacingBefore: { default: null },
|
|
100
101
|
spacingAfter: { default: null },
|
|
@@ -162,6 +163,7 @@ export const editorSchema = new Schema({
|
|
|
162
163
|
if (styles.length > 0) attrs.style = styles.join("; ");
|
|
163
164
|
const numberingPrefix = node.attrs.numberingPrefix as string | null;
|
|
164
165
|
const numberingLevel = node.attrs.numberingLevel as number | null;
|
|
166
|
+
const numberingSuffix = node.attrs.numberingSuffix as "tab" | "space" | "nothing" | null;
|
|
165
167
|
const children: Array<string | number | readonly unknown[]> = [];
|
|
166
168
|
if (pageBreak) {
|
|
167
169
|
children.push([
|
|
@@ -177,6 +179,7 @@ export const editorSchema = new Schema({
|
|
|
177
179
|
}
|
|
178
180
|
if (numberingPrefix) {
|
|
179
181
|
const minWidth = Math.min(Math.max(numberingPrefix.length + 1, 4), 14);
|
|
182
|
+
const marginRight = numberingSuffix === "nothing" ? "0.25rem" : numberingSuffix === "space" ? "0.5rem" : "0.75rem";
|
|
180
183
|
children.push([
|
|
181
184
|
"span",
|
|
182
185
|
{
|
|
@@ -187,7 +190,8 @@ export const editorSchema = new Schema({
|
|
|
187
190
|
...(typeof numberingLevel === "number"
|
|
188
191
|
? { "data-numbering-level": String(numberingLevel) }
|
|
189
192
|
: {}),
|
|
190
|
-
|
|
193
|
+
...(numberingSuffix ? { "data-numbering-suffix": numberingSuffix } : {}),
|
|
194
|
+
style: `min-width: ${minWidth}ch; margin-right: ${marginRight}; font-variant-numeric: tabular-nums;`,
|
|
191
195
|
},
|
|
192
196
|
numberingPrefix,
|
|
193
197
|
]);
|
|
@@ -300,14 +304,28 @@ export const editorSchema = new Schema({
|
|
|
300
304
|
alias: { default: null },
|
|
301
305
|
tag: { default: null },
|
|
302
306
|
lock: { default: null },
|
|
307
|
+
checkboxChecked: { default: null },
|
|
308
|
+
dateValue: { default: null },
|
|
309
|
+
dropdownItems: { default: null },
|
|
310
|
+
comboBoxItems: { default: null },
|
|
311
|
+
showingPlcHdr: { default: false },
|
|
303
312
|
},
|
|
304
313
|
toDOM(node) {
|
|
305
|
-
const
|
|
314
|
+
const sdtType = node.attrs.sdtType as string | null;
|
|
315
|
+
const typeLabel = sdtType === "checkbox" ? "\u2611 Checkbox"
|
|
316
|
+
: sdtType === "date" ? "\uD83D\uDCC5 Date"
|
|
317
|
+
: sdtType === "dropDownList" ? "\u25BE Dropdown"
|
|
318
|
+
: sdtType === "comboBox" ? "\u25BE Combo box"
|
|
319
|
+
: sdtType === "plainText" ? "\u270E Plain text"
|
|
320
|
+
: sdtType === "richText" ? "\u270E Rich text"
|
|
321
|
+
: sdtType ?? undefined;
|
|
322
|
+
const meta = [node.attrs.alias, node.attrs.tag, typeLabel].filter(Boolean).join(" \u00B7 ");
|
|
306
323
|
return [
|
|
307
324
|
"section",
|
|
308
325
|
{
|
|
309
326
|
class: "my-2 rounded-xl border border-primary/15 bg-surface-raised/60 px-3 py-2",
|
|
310
327
|
"data-node-type": "sdt_block",
|
|
328
|
+
...(sdtType ? { "data-sdt-type": sdtType } : {}),
|
|
311
329
|
},
|
|
312
330
|
[
|
|
313
331
|
"div",
|
|
@@ -332,13 +350,29 @@ export const editorSchema = new Schema({
|
|
|
332
350
|
warningId: { default: "" },
|
|
333
351
|
label: { default: "Locked" },
|
|
334
352
|
detail: { default: "" },
|
|
353
|
+
presentation: { default: "inline-chip" },
|
|
335
354
|
},
|
|
336
355
|
toDOM(node) {
|
|
356
|
+
const presentation = node.attrs.presentation as string;
|
|
357
|
+
if (presentation === "quiet-marker") {
|
|
358
|
+
return [
|
|
359
|
+
"span",
|
|
360
|
+
{
|
|
361
|
+
class: "inline-block h-0 w-0 overflow-hidden align-baseline",
|
|
362
|
+
"data-node-type": "opaque_inline",
|
|
363
|
+
"data-inline-presentation": "quiet-marker",
|
|
364
|
+
contenteditable: "false",
|
|
365
|
+
title: node.attrs.detail as string,
|
|
366
|
+
"aria-label": node.attrs.label as string,
|
|
367
|
+
},
|
|
368
|
+
];
|
|
369
|
+
}
|
|
337
370
|
return [
|
|
338
371
|
"span",
|
|
339
372
|
{
|
|
340
373
|
class: "inline-flex items-center gap-1 mx-0.5 px-1.5 py-0.5 rounded text-xs text-comment bg-warning-soft",
|
|
341
374
|
"data-node-type": "opaque_inline",
|
|
375
|
+
"data-inline-presentation": "inline-chip",
|
|
342
376
|
title: node.attrs.detail as string,
|
|
343
377
|
},
|
|
344
378
|
"\uD83D\uDD12 " + (node.attrs.label as string),
|
|
@@ -403,7 +437,7 @@ export const editorSchema = new Schema({
|
|
|
403
437
|
inline: true,
|
|
404
438
|
group: "inline",
|
|
405
439
|
atom: true,
|
|
406
|
-
selectable:
|
|
440
|
+
selectable: true,
|
|
407
441
|
attrs: {
|
|
408
442
|
text: { default: null },
|
|
409
443
|
geometry: { default: null },
|
|
@@ -454,7 +488,7 @@ export const editorSchema = new Schema({
|
|
|
454
488
|
inline: true,
|
|
455
489
|
group: "inline",
|
|
456
490
|
atom: true,
|
|
457
|
-
selectable:
|
|
491
|
+
selectable: true,
|
|
458
492
|
attrs: {
|
|
459
493
|
text: { default: null },
|
|
460
494
|
shapeType: { default: null },
|
|
@@ -551,7 +585,15 @@ export const editorSchema = new Schema({
|
|
|
551
585
|
},
|
|
552
586
|
vanish: {
|
|
553
587
|
toDOM() {
|
|
554
|
-
return [
|
|
588
|
+
return [
|
|
589
|
+
"span",
|
|
590
|
+
{
|
|
591
|
+
style: "display: none",
|
|
592
|
+
"data-hidden-text": "true",
|
|
593
|
+
"aria-hidden": "true",
|
|
594
|
+
},
|
|
595
|
+
0,
|
|
596
|
+
];
|
|
555
597
|
},
|
|
556
598
|
},
|
|
557
599
|
emboss: {
|
|
@@ -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,
|
|
@@ -30,35 +30,72 @@ export function createPMStateFromSnapshot(
|
|
|
30
30
|
): PMStateResult {
|
|
31
31
|
const doc = buildPMDoc(surface);
|
|
32
32
|
const positionMap = buildPositionMap(surface);
|
|
33
|
+
const pmSelection = createPMSelectionFromSnapshot(doc, positionMap, selection);
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
1,
|
|
43
|
-
positionMap.pmDocSize - 1,
|
|
44
|
-
);
|
|
35
|
+
const state = EditorState.create({
|
|
36
|
+
doc,
|
|
37
|
+
selection: pmSelection,
|
|
38
|
+
plugins,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return { state, positionMap };
|
|
42
|
+
}
|
|
45
43
|
|
|
46
|
-
|
|
44
|
+
export function createPMSelectionFromSnapshot(
|
|
45
|
+
doc: PMNode,
|
|
46
|
+
positionMap: PositionMap,
|
|
47
|
+
selection: SelectionSnapshot,
|
|
48
|
+
): Selection {
|
|
49
|
+
const pmAnchor = clamp(positionMap.runtimeToPm(selection.anchor), 1, positionMap.pmDocSize - 1);
|
|
50
|
+
const pmHead = clamp(positionMap.runtimeToPm(selection.head), 1, positionMap.pmDocSize - 1);
|
|
47
51
|
try {
|
|
48
|
-
|
|
52
|
+
if (selection.activeRange.kind === "node") {
|
|
53
|
+
return NodeSelection.create(doc, pmAnchor);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const forward = selection.head >= selection.anchor;
|
|
57
|
+
const resolvedAnchor = resolveInlineBoundary(doc, pmAnchor, forward ? 1 : -1);
|
|
58
|
+
const resolvedHead = resolveInlineBoundary(doc, pmHead, forward ? -1 : 1);
|
|
59
|
+
if (resolvedAnchor !== null && resolvedHead !== null) {
|
|
60
|
+
return TextSelection.create(doc, resolvedAnchor, resolvedHead);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const $anchor = doc.resolve(pmAnchor);
|
|
64
|
+
const $head = doc.resolve(pmHead);
|
|
65
|
+
return (
|
|
66
|
+
Selection.findFrom($anchor, 1, true) ??
|
|
67
|
+
Selection.findFrom($anchor, -1, true) ??
|
|
68
|
+
Selection.findFrom($head, 1, true) ??
|
|
69
|
+
Selection.findFrom($head, -1, true) ??
|
|
70
|
+
Selection.near($anchor, 1)
|
|
71
|
+
);
|
|
49
72
|
} catch {
|
|
50
73
|
// If the mapped runtime selection is invalid or lands in a non-text block,
|
|
51
74
|
// let ProseMirror choose the nearest valid starting selection.
|
|
52
|
-
|
|
75
|
+
return Selection.atStart(doc);
|
|
53
76
|
}
|
|
77
|
+
}
|
|
54
78
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
79
|
+
function resolveInlineBoundary(
|
|
80
|
+
doc: PMNode,
|
|
81
|
+
pos: number,
|
|
82
|
+
bias: -1 | 1,
|
|
83
|
+
): number | null {
|
|
84
|
+
const $pos = doc.resolve(pos);
|
|
85
|
+
if ($pos.parent.inlineContent) {
|
|
86
|
+
return pos;
|
|
87
|
+
}
|
|
60
88
|
|
|
61
|
-
|
|
89
|
+
const candidate =
|
|
90
|
+
Selection.findFrom($pos, bias, true) ??
|
|
91
|
+
Selection.findFrom($pos, -bias, true) ??
|
|
92
|
+
Selection.near($pos, bias);
|
|
93
|
+
|
|
94
|
+
if (candidate.$from.parent.inlineContent || candidate.$to.parent.inlineContent) {
|
|
95
|
+
return bias < 0 ? candidate.to : candidate.from;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return null;
|
|
62
99
|
}
|
|
63
100
|
|
|
64
101
|
function buildPMDoc(surface: EditorSurfaceSnapshot): PMNode {
|
|
@@ -124,6 +161,9 @@ function buildParagraph(
|
|
|
124
161
|
numberingPrefix:
|
|
125
162
|
(block as typeof block & { numberingPrefix?: string }).numberingPrefix ??
|
|
126
163
|
null,
|
|
164
|
+
numberingSuffix:
|
|
165
|
+
(block as typeof block & { numberingSuffix?: string }).numberingSuffix ??
|
|
166
|
+
null,
|
|
127
167
|
alignment: block.alignment ?? null,
|
|
128
168
|
spacingBefore: block.spacing?.before ?? null,
|
|
129
169
|
spacingAfter: block.spacing?.after ?? null,
|
|
@@ -226,6 +266,14 @@ function buildInlineContent(segment: SurfaceInlineSegment): PMNode[] {
|
|
|
226
266
|
case "opaque_inline":
|
|
227
267
|
return [buildOpaqueInlineOrComplexAtom(segment)];
|
|
228
268
|
|
|
269
|
+
case "note_ref": {
|
|
270
|
+
const text = editorSchema.text(
|
|
271
|
+
segment.label,
|
|
272
|
+
[editorSchema.marks.superscript.create()],
|
|
273
|
+
);
|
|
274
|
+
return [text];
|
|
275
|
+
}
|
|
276
|
+
|
|
229
277
|
default:
|
|
230
278
|
return [];
|
|
231
279
|
}
|
|
@@ -262,17 +310,36 @@ function buildTable(
|
|
|
262
310
|
gridSpan: cell.gridSpan,
|
|
263
311
|
verticalMerge: cell.verticalMerge,
|
|
264
312
|
backgroundColor: cell.backgroundColor ?? null,
|
|
313
|
+
verticalAlign: cell.verticalAlign ?? null,
|
|
314
|
+
borderTop: cell.borderTop ?? null,
|
|
315
|
+
borderRight: cell.borderRight ?? null,
|
|
316
|
+
borderBottom: cell.borderBottom ?? null,
|
|
317
|
+
borderLeft: cell.borderLeft ?? null,
|
|
265
318
|
},
|
|
266
319
|
Fragment.from(cellContent),
|
|
267
320
|
),
|
|
268
321
|
);
|
|
269
322
|
}
|
|
270
|
-
rows.push(editorSchema.nodes.table_row.create(
|
|
323
|
+
rows.push(editorSchema.nodes.table_row.create(
|
|
324
|
+
{
|
|
325
|
+
height: row.height ?? null,
|
|
326
|
+
heightRule: row.heightRule ?? null,
|
|
327
|
+
isHeader: row.isHeader ?? false,
|
|
328
|
+
},
|
|
329
|
+
Fragment.from(cells),
|
|
330
|
+
));
|
|
271
331
|
}
|
|
272
332
|
return editorSchema.nodes.table.create(
|
|
273
333
|
{
|
|
274
334
|
styleId: block.styleId ?? null,
|
|
275
335
|
gridColumns: block.gridColumns,
|
|
336
|
+
alignment: block.alignment ?? null,
|
|
337
|
+
tblLookFirstRow: block.tblLook?.firstRow ?? false,
|
|
338
|
+
tblLookLastRow: block.tblLook?.lastRow ?? false,
|
|
339
|
+
tblLookFirstColumn: block.tblLook?.firstColumn ?? false,
|
|
340
|
+
tblLookLastColumn: block.tblLook?.lastColumn ?? false,
|
|
341
|
+
tblLookNoHBand: block.tblLook?.noHBand ?? false,
|
|
342
|
+
tblLookNoVBand: block.tblLook?.noVBand ?? false,
|
|
276
343
|
},
|
|
277
344
|
Fragment.from(rows),
|
|
278
345
|
);
|
|
@@ -304,6 +371,11 @@ function buildSdtBlock(
|
|
|
304
371
|
alias: block.alias ?? null,
|
|
305
372
|
tag: block.tag ?? null,
|
|
306
373
|
lock: block.lock ?? null,
|
|
374
|
+
checkboxChecked: block.checkboxChecked ?? null,
|
|
375
|
+
dateValue: block.dateValue ?? null,
|
|
376
|
+
dropdownItems: block.dropdownItems ?? null,
|
|
377
|
+
comboBoxItems: block.comboBoxItems ?? null,
|
|
378
|
+
showingPlcHdr: block.showingPlcHdr ?? false,
|
|
307
379
|
},
|
|
308
380
|
Fragment.from(children),
|
|
309
381
|
);
|
|
@@ -360,6 +432,7 @@ function buildOpaqueInlineOrComplexAtom(
|
|
|
360
432
|
warningId: segment.warningId,
|
|
361
433
|
label,
|
|
362
434
|
detail,
|
|
435
|
+
presentation: segment.presentation ?? "inline-chip",
|
|
363
436
|
});
|
|
364
437
|
}
|
|
365
438
|
|