@beyondwork/docx-react-component 1.0.37 → 1.0.39
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 +41 -31
- package/src/api/public-types.ts +496 -1
- package/src/core/commands/section-layout-commands.ts +58 -0
- package/src/core/commands/table-grid.ts +431 -0
- package/src/core/commands/table-structure-commands.ts +845 -56
- package/src/core/commands/text-commands.ts +122 -2
- package/src/io/docx-session.ts +1 -0
- package/src/io/export/serialize-main-document.ts +2 -11
- package/src/io/export/serialize-numbering.ts +43 -10
- package/src/io/export/serialize-paragraph-formatting.ts +152 -0
- package/src/io/export/serialize-run-formatting.ts +90 -0
- package/src/io/export/serialize-styles.ts +212 -0
- package/src/io/export/serialize-tables.ts +74 -0
- package/src/io/export/table-properties-xml.ts +139 -4
- package/src/io/normalize/normalize-text.ts +15 -0
- package/src/io/ooxml/parse-fields.ts +10 -3
- package/src/io/ooxml/parse-footnotes.ts +60 -0
- package/src/io/ooxml/parse-headers-footers.ts +60 -0
- package/src/io/ooxml/parse-main-document.ts +137 -0
- package/src/io/ooxml/parse-numbering.ts +41 -1
- package/src/io/ooxml/parse-paragraph-formatting.ts +188 -0
- package/src/io/ooxml/parse-run-formatting.ts +129 -0
- package/src/io/ooxml/parse-styles.ts +31 -0
- package/src/io/ooxml/parse-tables.ts +249 -0
- package/src/io/ooxml/xml-attr-helpers.ts +60 -0
- package/src/io/ooxml/xml-element.ts +19 -0
- package/src/model/canonical-document.ts +117 -3
- package/src/runtime/collab/event-types.ts +165 -0
- package/src/runtime/collab/index.ts +22 -0
- package/src/runtime/collab/remote-cursor-awareness.ts +93 -0
- package/src/runtime/collab/runtime-collab-sync.ts +273 -0
- package/src/runtime/document-layout.ts +4 -2
- package/src/runtime/document-navigation.ts +1 -1
- package/src/runtime/document-runtime.ts +248 -18
- package/src/runtime/layout/default-page-format.ts +96 -0
- package/src/runtime/layout/index.ts +47 -0
- package/src/runtime/layout/inert-layout-facet.ts +16 -0
- package/src/runtime/layout/layout-engine-instance.ts +100 -23
- package/src/runtime/layout/layout-invalidation.ts +14 -5
- package/src/runtime/layout/margin-preset-catalog.ts +178 -0
- package/src/runtime/layout/page-format-catalog.ts +233 -0
- package/src/runtime/layout/page-graph.ts +55 -0
- package/src/runtime/layout/paginate-paragraph-lines.ts +128 -0
- package/src/runtime/layout/paginated-layout-engine.ts +484 -37
- package/src/runtime/layout/project-block-fragments.ts +225 -0
- package/src/runtime/layout/public-facet.ts +748 -16
- package/src/runtime/layout/resolve-page-fields.ts +70 -0
- package/src/runtime/layout/resolve-page-previews.ts +185 -0
- package/src/runtime/layout/resolved-formatting-state.ts +30 -26
- package/src/runtime/layout/table-render-plan.ts +249 -0
- package/src/runtime/numbering-prefix.ts +5 -0
- package/src/runtime/paragraph-style-resolver.ts +194 -0
- package/src/runtime/render/block-fragment-projection.ts +35 -0
- package/src/runtime/render/decoration-resolver.ts +189 -0
- package/src/runtime/render/index.ts +57 -0
- package/src/runtime/render/pending-op-delta-reader.ts +129 -0
- package/src/runtime/render/render-frame-types.ts +317 -0
- package/src/runtime/render/render-kernel.ts +759 -0
- package/src/runtime/resolved-numbering-geometry.ts +9 -1
- package/src/runtime/surface-projection.ts +129 -9
- package/src/runtime/table-schema.ts +11 -0
- package/src/runtime/view-state.ts +67 -0
- package/src/runtime/workflow-markup.ts +1 -5
- package/src/runtime/workflow-rail-segments.ts +280 -0
- package/src/ui/WordReviewEditor.tsx +368 -19
- package/src/ui/editor-command-bag.ts +4 -0
- package/src/ui/editor-runtime-boundary.ts +16 -0
- package/src/ui/editor-shell-view.tsx +10 -0
- package/src/ui/editor-surface-controller.tsx +9 -1
- package/src/ui/headless/chrome-registry.ts +310 -15
- package/src/ui/headless/scoped-chrome-policy.ts +49 -1
- package/src/ui/headless/selection-tool-types.ts +10 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +23 -2
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +2 -14
- package/src/ui-tailwind/chrome/role-action-sets.ts +80 -0
- package/src/ui-tailwind/chrome/tw-detach-handle.tsx +147 -0
- package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +160 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +68 -92
- package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +149 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +11 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +15 -4
- package/src/ui-tailwind/chrome/tw-table-border-picker.tsx +245 -0
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +356 -140
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +284 -0
- package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +94 -0
- package/src/ui-tailwind/chrome-overlay/index.ts +16 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +96 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +178 -0
- package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +4 -0
- package/src/ui-tailwind/editor-surface/local-edit-session-state.ts +11 -0
- package/src/ui-tailwind/editor-surface/page-slice-util.ts +15 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +7 -1
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +40 -4
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +22 -12
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +389 -0
- package/src/ui-tailwind/editor-surface/pm-schema.ts +40 -2
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +144 -62
- package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +179 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +559 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +224 -75
- package/src/ui-tailwind/editor-surface/tw-table-bands.css +61 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +19 -0
- package/src/ui-tailwind/index.ts +29 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +2 -2
- package/src/ui-tailwind/review/tw-rail-card.tsx +150 -0
- package/src/ui-tailwind/review/tw-review-rail-footer.tsx +52 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +166 -11
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +108 -0
- package/src/ui-tailwind/theme/editor-theme.css +498 -163
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +680 -0
- package/src/ui-tailwind/toolbar/tw-scope-posture-menu.tsx +182 -0
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +162 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +104 -2
- package/src/ui-tailwind/tw-review-workspace.tsx +234 -21
- package/src/runtime/collab-review-sync.ts +0 -254
- package/src/ui-tailwind/editor-surface/pm-collab-plugins.ts +0 -40
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fragment, type Node as PMNode } from "prosemirror-model";
|
|
1
|
+
import { Fragment, type Mark, type Node as PMNode, type Schema } from "prosemirror-model";
|
|
2
2
|
import { EditorState, NodeSelection, type Plugin, Selection, TextSelection } from "prosemirror-state";
|
|
3
3
|
|
|
4
4
|
import type {
|
|
@@ -17,6 +17,123 @@ export interface PMStateResult {
|
|
|
17
17
|
positionMap: PositionMap;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Test-friendly wrapper: build a PM EditorState from a minimal snapshot with no
|
|
22
|
+
* selection or plugins. The snapshot is cast to EditorSurfaceSnapshot so callers
|
|
23
|
+
* can pass partial objects in tests.
|
|
24
|
+
*/
|
|
25
|
+
export function buildPmStateFromSnapshot(snapshot: EditorSurfaceSnapshot): EditorState {
|
|
26
|
+
const doc = buildPMDoc(snapshot, {}, false);
|
|
27
|
+
return EditorState.create({ doc, plugins: [] });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Build the set of PM marks for a text segment by merging direct marks with the
|
|
32
|
+
* cascaded resolvedRunFormatting. Direct marks win over cascade values.
|
|
33
|
+
*/
|
|
34
|
+
function applyEffectiveMarks(
|
|
35
|
+
segment: Extract<SurfaceInlineSegment, { kind: "text" }>,
|
|
36
|
+
schema: Schema,
|
|
37
|
+
): Mark[] {
|
|
38
|
+
const marks: Mark[] = [];
|
|
39
|
+
const directList = segment.marks ?? [];
|
|
40
|
+
const directAttrs = segment.markAttrs ?? {};
|
|
41
|
+
const cascade = segment.resolvedRunFormatting ?? {};
|
|
42
|
+
|
|
43
|
+
// Boolean marks: direct presence wins; direct absence (explicit false in markAttrs is not
|
|
44
|
+
// representable in the marks array, so we check the marks array truthiness).
|
|
45
|
+
// If the mark is absent from the direct marks list but cascade supplies it, apply it.
|
|
46
|
+
const hasDirect = (name: string) => directList.includes(name as never);
|
|
47
|
+
|
|
48
|
+
// bold
|
|
49
|
+
const directBold = hasDirect("bold");
|
|
50
|
+
const cascadeBold = cascade.bold === true;
|
|
51
|
+
if (directBold || (!("bold" in directAttrs) && cascadeBold)) {
|
|
52
|
+
marks.push(schema.marks.bold.create());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// italic
|
|
56
|
+
const directItalic = hasDirect("italic");
|
|
57
|
+
const cascadeItalic = cascade.italic === true;
|
|
58
|
+
if (directItalic || (!("italic" in directAttrs) && cascadeItalic)) {
|
|
59
|
+
marks.push(schema.marks.italic.create());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// underline
|
|
63
|
+
const directUnderline = hasDirect("underline");
|
|
64
|
+
const cascadeUnderline = cascade.underline && cascade.underline !== "none";
|
|
65
|
+
if (directUnderline || (!("underline" in directAttrs) && cascadeUnderline)) {
|
|
66
|
+
marks.push(schema.marks.underline.create());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// strikethrough
|
|
70
|
+
const directStrikethrough = hasDirect("strikethrough");
|
|
71
|
+
const cascadeStrikethrough = cascade.strikethrough === true;
|
|
72
|
+
if (directStrikethrough || (!("strikethrough" in directAttrs) && cascadeStrikethrough)) {
|
|
73
|
+
marks.push(schema.marks.strikethrough.create());
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// pass-through marks from marks array (no cascade equivalent)
|
|
77
|
+
if (hasDirect("doubleStrikethrough")) marks.push(schema.marks.doubleStrikethrough.create());
|
|
78
|
+
if (hasDirect("vanish")) marks.push(schema.marks.vanish.create());
|
|
79
|
+
if (hasDirect("emboss")) marks.push(schema.marks.emboss.create());
|
|
80
|
+
if (hasDirect("imprint")) marks.push(schema.marks.imprint.create());
|
|
81
|
+
if (hasDirect("shadow")) marks.push(schema.marks.shadow.create());
|
|
82
|
+
|
|
83
|
+
// smallCaps / allCaps
|
|
84
|
+
if (hasDirect("smallCaps")) marks.push(schema.marks.small_caps.create());
|
|
85
|
+
if (hasDirect("allCaps")) marks.push(schema.marks.all_caps.create());
|
|
86
|
+
|
|
87
|
+
// superscript / subscript from marks array
|
|
88
|
+
if (hasDirect("superscript")) marks.push(schema.marks.superscript.create());
|
|
89
|
+
if (hasDirect("subscript")) marks.push(schema.marks.subscript.create());
|
|
90
|
+
|
|
91
|
+
// fontSize: direct.markAttrs.fontSize is in half-points; cascade.fontSizeHalfPoints is half-points
|
|
92
|
+
const directFontSize = typeof directAttrs.fontSize === "number" ? directAttrs.fontSize / 2 : undefined;
|
|
93
|
+
const cascadeFontSize = typeof cascade.fontSizeHalfPoints === "number" ? cascade.fontSizeHalfPoints / 2 : undefined;
|
|
94
|
+
const effectiveFontSize = directFontSize ?? cascadeFontSize;
|
|
95
|
+
if (typeof effectiveFontSize === "number") {
|
|
96
|
+
marks.push(schema.marks.font_size.create({ size: effectiveFontSize }));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// fontFamily: direct.markAttrs.fontFamily wins over cascade
|
|
100
|
+
const effectiveFontFamily = directAttrs.fontFamily ?? cascade.fontFamily ?? cascade.fontFamilyAscii;
|
|
101
|
+
if (effectiveFontFamily) {
|
|
102
|
+
marks.push(schema.marks.font_family.create({ family: effectiveFontFamily }));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// textColor: direct.markAttrs.textColor wins over cascade.colorHex
|
|
106
|
+
if (directAttrs.textFill && !directAttrs.textColor) {
|
|
107
|
+
const colorMatch = directAttrs.textFill.match(/\bval="([0-9A-Fa-f]{6})"/);
|
|
108
|
+
if (colorMatch) {
|
|
109
|
+
marks.push(schema.marks.text_color.create({ color: `#${colorMatch[1]}` }));
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
const directColor = directAttrs.textColor ? `#${directAttrs.textColor}` : undefined;
|
|
113
|
+
const cascadeColor = cascade.colorHex && cascade.colorHex !== "auto" ? `#${cascade.colorHex}` : undefined;
|
|
114
|
+
const effectiveColor = directColor ?? cascadeColor;
|
|
115
|
+
if (effectiveColor) {
|
|
116
|
+
marks.push(schema.marks.text_color.create({ color: effectiveColor }));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// highlight / background color
|
|
121
|
+
const effectiveBg = directAttrs.backgroundColor;
|
|
122
|
+
if (effectiveBg) {
|
|
123
|
+
marks.push(schema.marks.highlight.create({ color: `#${effectiveBg}` }));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// charSpacing / kerning
|
|
127
|
+
if (typeof directAttrs.charSpacing === "number") {
|
|
128
|
+
marks.push(schema.marks.char_spacing.create({ value: directAttrs.charSpacing }));
|
|
129
|
+
}
|
|
130
|
+
if (typeof directAttrs.kerning === "number") {
|
|
131
|
+
marks.push(schema.marks.font_kerning.create({ threshold: directAttrs.kerning }));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return marks;
|
|
135
|
+
}
|
|
136
|
+
|
|
20
137
|
export interface MediaPreviewDescriptor {
|
|
21
138
|
src: string;
|
|
22
139
|
widthEmu?: number;
|
|
@@ -222,6 +339,9 @@ function buildParagraph(
|
|
|
222
339
|
(previousParagraph.styleId ?? "__default__") === (block.styleId ?? "__default__"),
|
|
223
340
|
);
|
|
224
341
|
|
|
342
|
+
const cascade = block.resolvedParagraphFormatting;
|
|
343
|
+
const cascadeBorders = cascade?.borders as Record<string, unknown> | undefined;
|
|
344
|
+
|
|
225
345
|
return editorSchema.nodes.paragraph.create(
|
|
226
346
|
{
|
|
227
347
|
styleId: block.styleId ?? null,
|
|
@@ -233,12 +353,12 @@ function buildParagraph(
|
|
|
233
353
|
numberingSuffix:
|
|
234
354
|
(block as typeof block & { numberingSuffix?: string }).numberingSuffix ??
|
|
235
355
|
null,
|
|
236
|
-
alignment: block.alignment ?? null,
|
|
356
|
+
alignment: block.alignment ?? cascade?.alignment ?? null,
|
|
237
357
|
spacingBefore: paragraphLayout.spacing?.before ?? null,
|
|
238
358
|
spacingAfter: paragraphLayout.spacing?.after ?? null,
|
|
239
359
|
lineSpacing: paragraphLayout.spacing?.line ?? null,
|
|
240
360
|
lineRule: paragraphLayout.spacing?.lineRule ?? null,
|
|
241
|
-
contextualSpacing: block.contextualSpacing ?? null,
|
|
361
|
+
contextualSpacing: block.contextualSpacing ?? cascade?.contextualSpacing ?? null,
|
|
242
362
|
listContinuation: listContinuation || null,
|
|
243
363
|
contextualSpacingBefore: contextualSpacingBefore || null,
|
|
244
364
|
contextualSpacingAfter: contextualSpacingAfter || null,
|
|
@@ -248,14 +368,15 @@ function buildParagraph(
|
|
|
248
368
|
indentHanging: paragraphLayout.indentation?.hanging ?? null,
|
|
249
369
|
numberingMarkerWidth: paragraphLayout.markerLane?.width ?? null,
|
|
250
370
|
numberingMarkerJustification: paragraphLayout.markerJustification ?? null,
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
371
|
+
numberingMarkerRunProperties: block.resolvedNumbering?.markerRunProperties ?? null,
|
|
372
|
+
shadingFill: block.shading?.fill ?? cascade?.shading?.fill ?? null,
|
|
373
|
+
borderTop: (block.borders as Record<string, unknown>)?.top ?? cascadeBorders?.top ?? null,
|
|
374
|
+
borderBottom: (block.borders as Record<string, unknown>)?.bottom ?? cascadeBorders?.bottom ?? null,
|
|
375
|
+
borderLeft: (block.borders as Record<string, unknown>)?.left ?? cascadeBorders?.left ?? null,
|
|
376
|
+
borderRight: (block.borders as Record<string, unknown>)?.right ?? cascadeBorders?.right ?? null,
|
|
377
|
+
outlineLevel: block.outlineLevel ?? cascade?.outlineLevel ?? null,
|
|
378
|
+
bidi: block.bidi ?? cascade?.bidi ?? null,
|
|
379
|
+
pageBreakBefore: block.pageBreakBefore ?? cascade?.pageBreakBefore ?? null,
|
|
259
380
|
hiddenTextOnly: fullyVanishedParagraph || null,
|
|
260
381
|
},
|
|
261
382
|
content.length > 0 ? Fragment.from(content) : undefined,
|
|
@@ -275,9 +396,16 @@ function resolveParagraphLayout(
|
|
|
275
396
|
NonNullable<Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>["resolvedNumbering"]>["geometry"]["markerJustification"]
|
|
276
397
|
> | undefined;
|
|
277
398
|
} {
|
|
399
|
+
const cascadeFormatting = block.resolvedParagraphFormatting;
|
|
278
400
|
return {
|
|
279
|
-
spacing:
|
|
280
|
-
|
|
401
|
+
spacing:
|
|
402
|
+
block.resolvedNumbering?.geometry.spacing ??
|
|
403
|
+
block.spacing ??
|
|
404
|
+
cascadeFormatting?.spacing,
|
|
405
|
+
indentation:
|
|
406
|
+
block.resolvedNumbering?.geometry.indentation ??
|
|
407
|
+
block.indentation ??
|
|
408
|
+
cascadeFormatting?.indentation,
|
|
281
409
|
tabStops: block.resolvedNumbering?.geometry.tabStops ?? block.tabStops ?? [],
|
|
282
410
|
markerLane: block.resolvedNumbering?.geometry.markerLane,
|
|
283
411
|
markerJustification: block.resolvedNumbering?.geometry.markerJustification,
|
|
@@ -293,55 +421,7 @@ function buildInlineContent(
|
|
|
293
421
|
case "text": {
|
|
294
422
|
if (!segment.text) return [];
|
|
295
423
|
|
|
296
|
-
|
|
297
|
-
let marks = editorSchema.marks.bold.isInSet([])
|
|
298
|
-
? [] // shouldn't happen, just type safety
|
|
299
|
-
: [];
|
|
300
|
-
|
|
301
|
-
const pmMarks = [];
|
|
302
|
-
if (segment.marks) {
|
|
303
|
-
for (const mark of segment.marks) {
|
|
304
|
-
// Map surface mark names that differ from PM schema mark names
|
|
305
|
-
if (mark === "smallCaps") {
|
|
306
|
-
pmMarks.push(editorSchema.marks.small_caps.create());
|
|
307
|
-
continue;
|
|
308
|
-
}
|
|
309
|
-
if (mark === "allCaps") {
|
|
310
|
-
pmMarks.push(editorSchema.marks.all_caps.create());
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
const pmMark = editorSchema.marks[mark];
|
|
314
|
-
if (pmMark) {
|
|
315
|
-
pmMarks.push(pmMark.create());
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
if (segment.kind === "text" && segment.markAttrs) {
|
|
320
|
-
if (segment.markAttrs.backgroundColor) {
|
|
321
|
-
pmMarks.push(editorSchema.marks.highlight.create({ color: `#${segment.markAttrs.backgroundColor}` }));
|
|
322
|
-
}
|
|
323
|
-
if (segment.markAttrs.fontFamily) {
|
|
324
|
-
pmMarks.push(editorSchema.marks.font_family.create({ family: segment.markAttrs.fontFamily }));
|
|
325
|
-
}
|
|
326
|
-
if (segment.markAttrs.fontSize) {
|
|
327
|
-
pmMarks.push(editorSchema.marks.font_size.create({ size: segment.markAttrs.fontSize / 2 }));
|
|
328
|
-
}
|
|
329
|
-
if (segment.markAttrs.textColor) {
|
|
330
|
-
pmMarks.push(editorSchema.marks.text_color.create({ color: `#${segment.markAttrs.textColor}` }));
|
|
331
|
-
}
|
|
332
|
-
if (segment.markAttrs.charSpacing) {
|
|
333
|
-
pmMarks.push(editorSchema.marks.char_spacing.create({ value: segment.markAttrs.charSpacing }));
|
|
334
|
-
}
|
|
335
|
-
if (segment.markAttrs.kerning) {
|
|
336
|
-
pmMarks.push(editorSchema.marks.font_kerning.create({ threshold: segment.markAttrs.kerning }));
|
|
337
|
-
}
|
|
338
|
-
if (segment.markAttrs.textFill && !segment.markAttrs.textColor) {
|
|
339
|
-
const colorMatch = segment.markAttrs.textFill.match(/\bval="([0-9A-Fa-f]{6})"/);
|
|
340
|
-
if (colorMatch) {
|
|
341
|
-
pmMarks.push(editorSchema.marks.text_color.create({ color: `#${colorMatch[1]}` }));
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
424
|
+
const pmMarks = applyEffectiveMarks(segment, editorSchema);
|
|
345
425
|
if (segment.hyperlinkHref) {
|
|
346
426
|
pmMarks.push(editorSchema.marks.link.create({ href: segment.hyperlinkHref }));
|
|
347
427
|
}
|
|
@@ -433,6 +513,7 @@ function buildTable(
|
|
|
433
513
|
borderRight: cell.borderRight ?? null,
|
|
434
514
|
borderBottom: cell.borderBottom ?? null,
|
|
435
515
|
borderLeft: cell.borderLeft ?? null,
|
|
516
|
+
bandClasses: cell.bandClasses ?? null,
|
|
436
517
|
},
|
|
437
518
|
Fragment.from(cellContent),
|
|
438
519
|
),
|
|
@@ -460,6 +541,7 @@ function buildTable(
|
|
|
460
541
|
tblLookLastColumn: block.tblLook?.lastColumn ?? false,
|
|
461
542
|
tblLookNoHBand: block.tblLook?.noHBand ?? false,
|
|
462
543
|
tblLookNoVBand: block.tblLook?.noVBand ?? false,
|
|
544
|
+
tblLookVal: block.tblLook?.val ?? null,
|
|
463
545
|
},
|
|
464
546
|
Fragment.from(rows),
|
|
465
547
|
);
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { Plugin, PluginKey } from "prosemirror-state";
|
|
2
|
+
import type { EditorState, Transaction } from "prosemirror-state";
|
|
3
|
+
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
4
|
+
import type { EditorView } from "prosemirror-view";
|
|
5
|
+
import type { Awareness } from "y-protocols/awareness";
|
|
6
|
+
|
|
7
|
+
import type { EditorStoryTarget } from "../../api/public-types";
|
|
8
|
+
import { storyTargetsEqual } from "../../core/selection/mapping";
|
|
9
|
+
import type { PositionMap } from "./pm-position-map";
|
|
10
|
+
import {
|
|
11
|
+
getRemoteCursorStates,
|
|
12
|
+
type RemoteCursorState,
|
|
13
|
+
} from "../../runtime/collab/remote-cursor-awareness";
|
|
14
|
+
|
|
15
|
+
interface RemoteCursorPluginState {
|
|
16
|
+
cursors: RemoteCursorState[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const remoteCursorPluginKey = new PluginKey<RemoteCursorPluginState>(
|
|
20
|
+
"remoteCursor",
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export interface RemoteCursorPluginOptions {
|
|
24
|
+
awareness: Awareness;
|
|
25
|
+
localClientId: number;
|
|
26
|
+
getPositionMap: () => PositionMap | null;
|
|
27
|
+
getActiveStory: () => EditorStoryTarget;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createRemoteCursorPlugin(
|
|
31
|
+
options: RemoteCursorPluginOptions,
|
|
32
|
+
): Plugin<RemoteCursorPluginState> {
|
|
33
|
+
const { awareness, localClientId, getPositionMap, getActiveStory } = options;
|
|
34
|
+
|
|
35
|
+
function computeCursors(): RemoteCursorState[] {
|
|
36
|
+
const activeStory = getActiveStory();
|
|
37
|
+
const allCursors = getRemoteCursorStates(awareness, localClientId);
|
|
38
|
+
return allCursors.filter((cursor) =>
|
|
39
|
+
storyTargetsEqual(cursor.storyTarget, activeStory),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function buildDecorations(state: EditorState): DecorationSet {
|
|
44
|
+
const positionMap = getPositionMap();
|
|
45
|
+
if (!positionMap) {
|
|
46
|
+
return DecorationSet.empty;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const pluginState = remoteCursorPluginKey.getState(state);
|
|
50
|
+
const cursors = pluginState?.cursors ?? [];
|
|
51
|
+
if (cursors.length === 0) {
|
|
52
|
+
return DecorationSet.empty;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const decorations: Decoration[] = [];
|
|
56
|
+
|
|
57
|
+
for (const cursor of cursors) {
|
|
58
|
+
const pmAnchor = positionMap.runtimeToPm(cursor.anchor);
|
|
59
|
+
const pmHead = positionMap.runtimeToPm(cursor.head);
|
|
60
|
+
|
|
61
|
+
const from = Math.min(pmAnchor, pmHead);
|
|
62
|
+
const to = Math.max(pmAnchor, pmHead);
|
|
63
|
+
|
|
64
|
+
if (from < 1 || to > state.doc.content.size) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (from !== to) {
|
|
69
|
+
decorations.push(
|
|
70
|
+
Decoration.inline(from, to, {
|
|
71
|
+
style: `background-color: ${cursor.color}33;`,
|
|
72
|
+
class: "remote-cursor-selection",
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
decorations.push(
|
|
78
|
+
Decoration.widget(to, () => createCursorWidget(cursor), {
|
|
79
|
+
side: 1,
|
|
80
|
+
key: cursor.userId,
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return DecorationSet.create(state.doc, decorations);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const plugin = new Plugin<RemoteCursorPluginState>({
|
|
89
|
+
key: remoteCursorPluginKey,
|
|
90
|
+
|
|
91
|
+
state: {
|
|
92
|
+
init(): RemoteCursorPluginState {
|
|
93
|
+
return { cursors: computeCursors() };
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
apply(
|
|
97
|
+
tr: Transaction,
|
|
98
|
+
pluginState: RemoteCursorPluginState,
|
|
99
|
+
): RemoteCursorPluginState {
|
|
100
|
+
const meta = tr.getMeta(remoteCursorPluginKey);
|
|
101
|
+
if (meta !== undefined) {
|
|
102
|
+
return meta;
|
|
103
|
+
}
|
|
104
|
+
return pluginState;
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
props: {
|
|
109
|
+
decorations(state: EditorState): DecorationSet {
|
|
110
|
+
return buildDecorations(state);
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
view(editorView: EditorView) {
|
|
115
|
+
const onChange = (): void => {
|
|
116
|
+
const newCursors = computeCursors();
|
|
117
|
+
const tr = editorView.state.tr.setMeta(remoteCursorPluginKey, {
|
|
118
|
+
cursors: newCursors,
|
|
119
|
+
});
|
|
120
|
+
editorView.dispatch(tr);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
awareness.on("change", onChange);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
destroy() {
|
|
127
|
+
awareness.off("change", onChange);
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return plugin;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function createCursorWidget(cursor: RemoteCursorState): HTMLElement {
|
|
137
|
+
const container = document.createElement("span");
|
|
138
|
+
container.className = "remote-cursor-widget";
|
|
139
|
+
container.style.cssText = `
|
|
140
|
+
position: relative;
|
|
141
|
+
display: inline-block;
|
|
142
|
+
pointer-events: none;
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
const caret = document.createElement("span");
|
|
146
|
+
caret.style.cssText = `
|
|
147
|
+
position: absolute;
|
|
148
|
+
left: -1px;
|
|
149
|
+
top: -0.2em;
|
|
150
|
+
width: 2px;
|
|
151
|
+
height: 1.2em;
|
|
152
|
+
background-color: ${cursor.color};
|
|
153
|
+
border-radius: 1px;
|
|
154
|
+
`;
|
|
155
|
+
container.appendChild(caret);
|
|
156
|
+
|
|
157
|
+
const label = document.createElement("span");
|
|
158
|
+
label.style.cssText = `
|
|
159
|
+
position: absolute;
|
|
160
|
+
left: 0;
|
|
161
|
+
top: -1.4em;
|
|
162
|
+
font-size: 11px;
|
|
163
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
164
|
+
font-weight: 500;
|
|
165
|
+
color: white;
|
|
166
|
+
background-color: ${cursor.color};
|
|
167
|
+
padding: 1px 4px;
|
|
168
|
+
border-radius: 3px;
|
|
169
|
+
white-space: nowrap;
|
|
170
|
+
line-height: 1.3;
|
|
171
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
|
172
|
+
`;
|
|
173
|
+
label.textContent = cursor.displayName;
|
|
174
|
+
container.appendChild(label);
|
|
175
|
+
|
|
176
|
+
return container;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export type { RemoteCursorState };
|