@beyondwork/docx-react-component 1.0.41 → 1.0.43
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 +38 -37
- package/src/api/awareness-identity-types.ts +35 -0
- package/src/api/comment-negotiation-types.ts +130 -0
- package/src/api/comment-presentation-types.ts +106 -0
- package/src/api/editor-state-types.ts +110 -0
- package/src/api/external-custody-types.ts +74 -0
- package/src/api/participants-types.ts +18 -0
- package/src/api/public-types.ts +541 -5
- package/src/api/scope-metadata-resolver-types.ts +88 -0
- package/src/core/commands/formatting-commands.ts +1 -1
- package/src/core/commands/index.ts +601 -9
- package/src/core/search/search-text.ts +15 -2
- package/src/index.ts +131 -1
- package/src/io/docx-session.ts +672 -2
- package/src/io/export/escape-xml-attribute.ts +26 -0
- package/src/io/export/external-send.ts +188 -0
- package/src/io/export/serialize-comments.ts +13 -16
- package/src/io/export/serialize-footnotes.ts +17 -24
- package/src/io/export/serialize-headers-footers.ts +17 -24
- package/src/io/export/serialize-main-document.ts +59 -62
- package/src/io/export/serialize-numbering.ts +20 -27
- package/src/io/export/serialize-runtime-revisions.ts +2 -9
- package/src/io/export/serialize-tables.ts +8 -15
- package/src/io/export/table-properties-xml.ts +25 -32
- package/src/io/import/external-reimport.ts +40 -0
- package/src/io/load-scheduler.ts +230 -0
- package/src/io/normalize/normalize-text.ts +83 -0
- package/src/io/ooxml/bw-xml.ts +244 -0
- package/src/io/ooxml/canonicalize-payload.ts +301 -0
- package/src/io/ooxml/comment-negotiation-payload.ts +288 -0
- package/src/io/ooxml/comment-presentation-payload.ts +311 -0
- package/src/io/ooxml/external-custody-payload.ts +102 -0
- package/src/io/ooxml/participants-payload.ts +97 -0
- package/src/io/ooxml/payload-signature.ts +112 -0
- package/src/io/ooxml/workflow-payload-validator.ts +367 -0
- package/src/io/ooxml/workflow-payload.ts +317 -7
- package/src/runtime/awareness-identity.ts +173 -0
- package/src/runtime/collab/event-types.ts +27 -0
- package/src/runtime/collab-session-bridge.ts +157 -0
- package/src/runtime/collab-session-facet.ts +193 -0
- package/src/runtime/collab-session.ts +273 -0
- package/src/runtime/comment-negotiation-sync.ts +91 -0
- package/src/runtime/comment-negotiation.ts +158 -0
- package/src/runtime/comment-presentation.ts +223 -0
- package/src/runtime/document-runtime.ts +639 -124
- package/src/runtime/editor-state-channel.ts +544 -0
- package/src/runtime/editor-state-integration.ts +217 -0
- package/src/runtime/external-send-runtime.ts +117 -0
- package/src/runtime/layout/docx-font-loader.ts +11 -30
- package/src/runtime/layout/index.ts +2 -0
- package/src/runtime/layout/inert-layout-facet.ts +4 -0
- package/src/runtime/layout/layout-engine-instance.ts +139 -14
- package/src/runtime/layout/page-graph.ts +79 -7
- package/src/runtime/layout/paginated-layout-engine.ts +441 -48
- package/src/runtime/layout/public-facet.ts +585 -14
- package/src/runtime/layout/table-row-split.ts +316 -0
- package/src/runtime/markdown-sanitizer.ts +132 -0
- package/src/runtime/participants.ts +134 -0
- package/src/runtime/perf-counters.ts +28 -0
- package/src/runtime/render/render-frame-types.ts +17 -0
- package/src/runtime/render/render-kernel.ts +172 -29
- package/src/runtime/resign-payload.ts +120 -0
- package/src/runtime/surface-projection.ts +10 -5
- package/src/runtime/tamper-gate.ts +157 -0
- package/src/runtime/workflow-markup.ts +80 -16
- package/src/runtime/workflow-rail-segments.ts +244 -5
- package/src/ui/WordReviewEditor.tsx +654 -45
- package/src/ui/editor-command-bag.ts +14 -0
- package/src/ui/editor-runtime-boundary.ts +111 -11
- package/src/ui/editor-shell-view.tsx +21 -0
- package/src/ui/editor-surface-controller.tsx +5 -0
- package/src/ui/headless/selection-helpers.ts +10 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +28 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +62 -2
- package/src/ui-tailwind/chrome/collab-audience-chip.tsx +73 -0
- package/src/ui-tailwind/chrome/collab-negotiation-action-bar.tsx +244 -0
- package/src/ui-tailwind/chrome/collab-presence-strip.tsx +150 -0
- package/src/ui-tailwind/chrome/collab-role-chip.tsx +62 -0
- package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +68 -0
- package/src/ui-tailwind/chrome/collab-send-to-supplier-modal.tsx +149 -0
- package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +68 -0
- package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +281 -0
- package/src/ui-tailwind/chrome/forward-non-drag-click.ts +104 -0
- package/src/ui-tailwind/chrome/tw-mode-dock.tsx +1 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +7 -1
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +1 -38
- package/src/ui-tailwind/chrome-overlay/index.ts +6 -0
- package/src/ui-tailwind/chrome-overlay/scope-card-role-model.ts +78 -0
- package/src/ui-tailwind/chrome-overlay/scope-keyboard-cycle.ts +49 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +106 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +527 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +120 -22
- package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +310 -32
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +93 -14
- package/src/ui-tailwind/editor-surface/paste-plain-text.ts +72 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +118 -8
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +35 -1
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +86 -3
- package/src/ui-tailwind/editor-surface/pm-schema.ts +167 -17
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +35 -7
- package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +20 -3
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +265 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +10 -256
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +9 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +66 -0
- package/src/ui-tailwind/index.ts +37 -1
- package/src/ui-tailwind/page-stack/tw-endnote-area.tsx +57 -0
- package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +71 -0
- package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +73 -0
- package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +74 -0
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +477 -0
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +374 -0
- package/src/ui-tailwind/review/comment-markdown-renderer.tsx +155 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +77 -16
- package/src/ui-tailwind/review/tw-review-rail-footer.tsx +29 -3
- package/src/ui-tailwind/status/tw-status-bar.tsx +52 -1
- package/src/ui-tailwind/theme/editor-theme.css +25 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +455 -118
|
@@ -178,24 +178,24 @@ export const editorSchema = new Schema({
|
|
|
178
178
|
const spacingBefore = node.attrs.spacingBefore as number | null;
|
|
179
179
|
const contextualSpacingBefore = node.attrs.contextualSpacingBefore as boolean | null;
|
|
180
180
|
if (contextualSpacingBefore) styles.push("margin-top: 0");
|
|
181
|
-
else if (spacingBefore) styles.push(`margin-top: ${spacingBefore / 20}
|
|
181
|
+
else if (spacingBefore) styles.push(`margin-top: ${spacingBefore / 20}pt`);
|
|
182
182
|
const contextualSpacingAfter = node.attrs.contextualSpacingAfter as boolean | null;
|
|
183
183
|
const spacingAfter = node.attrs.spacingAfter as number | null;
|
|
184
184
|
if (contextualSpacingAfter) styles.push("margin-bottom: 0");
|
|
185
|
-
else if (spacingAfter) styles.push(`margin-bottom: ${spacingAfter / 20}
|
|
185
|
+
else if (spacingAfter) styles.push(`margin-bottom: ${spacingAfter / 20}pt`);
|
|
186
186
|
const lineSpacing = node.attrs.lineSpacing as number | null;
|
|
187
187
|
const lineRule = node.attrs.lineRule as string | null;
|
|
188
188
|
if (lineSpacing && lineRule === "auto") styles.push(`line-height: ${lineSpacing / 240}`);
|
|
189
|
-
else if (lineSpacing && lineRule === "exact") styles.push(`line-height: ${lineSpacing / 20}
|
|
190
|
-
else if (lineSpacing && lineRule === "atLeast") styles.push(`min-height: ${lineSpacing / 20}
|
|
189
|
+
else if (lineSpacing && lineRule === "exact") styles.push(`line-height: ${lineSpacing / 20}pt`);
|
|
190
|
+
else if (lineSpacing && lineRule === "atLeast") styles.push(`min-height: ${lineSpacing / 20}pt`);
|
|
191
191
|
const indentLeft = node.attrs.indentLeft as number | null;
|
|
192
|
-
if (indentLeft) styles.push(`padding-left: ${indentLeft / 20}
|
|
192
|
+
if (indentLeft) styles.push(`padding-left: ${indentLeft / 20}pt`);
|
|
193
193
|
const indentRight = node.attrs.indentRight as number | null;
|
|
194
|
-
if (indentRight) styles.push(`padding-right: ${indentRight / 20}
|
|
194
|
+
if (indentRight) styles.push(`padding-right: ${indentRight / 20}pt`);
|
|
195
195
|
const indentFirstLine = node.attrs.indentFirstLine as number | null;
|
|
196
196
|
const indentHanging = node.attrs.indentHanging as number | null;
|
|
197
|
-
if (indentHanging) styles.push(`text-indent: -${indentHanging / 20}
|
|
198
|
-
else if (indentFirstLine) styles.push(`text-indent: ${indentFirstLine / 20}
|
|
197
|
+
if (indentHanging) styles.push(`text-indent: -${indentHanging / 20}pt`);
|
|
198
|
+
else if (indentFirstLine) styles.push(`text-indent: ${indentFirstLine / 20}pt`);
|
|
199
199
|
const shadingColor = safeHexColor(node.attrs.shadingFill as string | null);
|
|
200
200
|
if (shadingColor) styles.push(`background-color: ${shadingColor}`);
|
|
201
201
|
for (const [side, attrName] of [["top", "borderTop"], ["bottom", "borderBottom"], ["left", "borderLeft"], ["right", "borderRight"]] as const) {
|
|
@@ -309,11 +309,13 @@ export const editorSchema = new Schema({
|
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
if (hasResolvedMarkerWidth) {
|
|
312
|
-
|
|
312
|
+
// P13.a: emit marker geometry in pt (twips/20 == pt) so it
|
|
313
|
+
// self-scales under CSS `zoom` and matches Word's intent.
|
|
314
|
+
const markerWidthPt = Math.max(1, numberingMarkerWidth / 20);
|
|
313
315
|
prefixStyles.push(
|
|
314
|
-
`width: ${
|
|
315
|
-
`min-width: ${
|
|
316
|
-
`flex-basis: ${
|
|
316
|
+
`width: ${markerWidthPt}pt`,
|
|
317
|
+
`min-width: ${markerWidthPt}pt`,
|
|
318
|
+
`flex-basis: ${markerWidthPt}pt`,
|
|
317
319
|
`margin-right: 0`,
|
|
318
320
|
`overflow: visible`,
|
|
319
321
|
);
|
|
@@ -632,16 +634,51 @@ export const editorSchema = new Schema({
|
|
|
632
634
|
selectable: false,
|
|
633
635
|
attrs: {
|
|
634
636
|
previewMediaId: { default: null },
|
|
637
|
+
previewSrc: { default: null },
|
|
635
638
|
detail: { default: null },
|
|
636
639
|
},
|
|
637
640
|
toDOM(node) {
|
|
641
|
+
const previewSrc = node.attrs.previewSrc as string | null;
|
|
642
|
+
const detail = (node.attrs.detail as string) ?? "Chart";
|
|
643
|
+
if (previewSrc) {
|
|
644
|
+
// Bitmap-backed: render the fallback image Word cached in mc:Fallback.
|
|
645
|
+
// The corner chip preserves the typed identity so agents and humans
|
|
646
|
+
// still see "this is a chart" at a glance.
|
|
647
|
+
return [
|
|
648
|
+
"span",
|
|
649
|
+
{
|
|
650
|
+
class: "relative inline-block align-baseline mx-0.5 max-w-full",
|
|
651
|
+
"data-node-type": "chart_atom",
|
|
652
|
+
"data-preview-media-id": (node.attrs.previewMediaId as string) ?? "",
|
|
653
|
+
contenteditable: "false",
|
|
654
|
+
title: detail,
|
|
655
|
+
},
|
|
656
|
+
[
|
|
657
|
+
"img",
|
|
658
|
+
{
|
|
659
|
+
src: previewSrc,
|
|
660
|
+
alt: detail,
|
|
661
|
+
class: "block max-w-full h-auto",
|
|
662
|
+
draggable: "false",
|
|
663
|
+
},
|
|
664
|
+
],
|
|
665
|
+
[
|
|
666
|
+
"span",
|
|
667
|
+
{
|
|
668
|
+
class: "absolute top-1 right-1 inline-flex items-center gap-0.5 rounded border border-blue-200 bg-blue-50/90 px-1 py-0.5 text-[10px] text-blue-700",
|
|
669
|
+
"aria-hidden": "true",
|
|
670
|
+
},
|
|
671
|
+
"\uD83D\uDCC8 Chart",
|
|
672
|
+
],
|
|
673
|
+
];
|
|
674
|
+
}
|
|
638
675
|
return [
|
|
639
676
|
"span",
|
|
640
677
|
{
|
|
641
678
|
class: "inline-flex items-center gap-1 mx-0.5 px-1.5 py-0.5 rounded text-xs text-blue-700 bg-blue-50 border border-blue-200",
|
|
642
679
|
"data-node-type": "chart_atom",
|
|
643
680
|
contenteditable: "false",
|
|
644
|
-
title:
|
|
681
|
+
title: detail,
|
|
645
682
|
},
|
|
646
683
|
"\uD83D\uDCC8 Chart",
|
|
647
684
|
];
|
|
@@ -655,16 +692,48 @@ export const editorSchema = new Schema({
|
|
|
655
692
|
selectable: false,
|
|
656
693
|
attrs: {
|
|
657
694
|
previewMediaId: { default: null },
|
|
695
|
+
previewSrc: { default: null },
|
|
658
696
|
detail: { default: null },
|
|
659
697
|
},
|
|
660
698
|
toDOM(node) {
|
|
699
|
+
const previewSrc = node.attrs.previewSrc as string | null;
|
|
700
|
+
const detail = (node.attrs.detail as string) ?? "SmartArt";
|
|
701
|
+
if (previewSrc) {
|
|
702
|
+
return [
|
|
703
|
+
"span",
|
|
704
|
+
{
|
|
705
|
+
class: "relative inline-block align-baseline mx-0.5 max-w-full",
|
|
706
|
+
"data-node-type": "smartart_atom",
|
|
707
|
+
"data-preview-media-id": (node.attrs.previewMediaId as string) ?? "",
|
|
708
|
+
contenteditable: "false",
|
|
709
|
+
title: detail,
|
|
710
|
+
},
|
|
711
|
+
[
|
|
712
|
+
"img",
|
|
713
|
+
{
|
|
714
|
+
src: previewSrc,
|
|
715
|
+
alt: detail,
|
|
716
|
+
class: "block max-w-full h-auto",
|
|
717
|
+
draggable: "false",
|
|
718
|
+
},
|
|
719
|
+
],
|
|
720
|
+
[
|
|
721
|
+
"span",
|
|
722
|
+
{
|
|
723
|
+
class: "absolute top-1 right-1 inline-flex items-center gap-0.5 rounded border border-purple-200 bg-purple-50/90 px-1 py-0.5 text-[10px] text-purple-700",
|
|
724
|
+
"aria-hidden": "true",
|
|
725
|
+
},
|
|
726
|
+
"\uD83D\uDDFA SmartArt",
|
|
727
|
+
],
|
|
728
|
+
];
|
|
729
|
+
}
|
|
661
730
|
return [
|
|
662
731
|
"span",
|
|
663
732
|
{
|
|
664
733
|
class: "inline-flex items-center gap-1 mx-0.5 px-1.5 py-0.5 rounded text-xs text-purple-700 bg-purple-50 border border-purple-200",
|
|
665
734
|
"data-node-type": "smartart_atom",
|
|
666
735
|
contenteditable: "false",
|
|
667
|
-
title:
|
|
736
|
+
title: detail,
|
|
668
737
|
},
|
|
669
738
|
"\uD83D\uDDFA SmartArt",
|
|
670
739
|
];
|
|
@@ -705,17 +774,50 @@ export const editorSchema = new Schema({
|
|
|
705
774
|
attrs: {
|
|
706
775
|
text: { default: "" },
|
|
707
776
|
geometry: { default: null },
|
|
777
|
+
previewMediaId: { default: null },
|
|
778
|
+
previewSrc: { default: null },
|
|
708
779
|
detail: { default: null },
|
|
709
780
|
},
|
|
710
781
|
toDOM(node) {
|
|
711
782
|
const text = node.attrs.text as string;
|
|
783
|
+
const previewSrc = node.attrs.previewSrc as string | null;
|
|
784
|
+
const detail = (node.attrs.detail as string) ?? (text ? `WordArt: ${text}` : "WordArt");
|
|
785
|
+
if (previewSrc) {
|
|
786
|
+
return [
|
|
787
|
+
"span",
|
|
788
|
+
{
|
|
789
|
+
class: "relative inline-block align-baseline mx-0.5 max-w-full",
|
|
790
|
+
"data-node-type": "wordart_atom",
|
|
791
|
+
"data-preview-media-id": (node.attrs.previewMediaId as string) ?? "",
|
|
792
|
+
contenteditable: "false",
|
|
793
|
+
title: detail,
|
|
794
|
+
},
|
|
795
|
+
[
|
|
796
|
+
"img",
|
|
797
|
+
{
|
|
798
|
+
src: previewSrc,
|
|
799
|
+
alt: detail,
|
|
800
|
+
class: "block max-w-full h-auto",
|
|
801
|
+
draggable: "false",
|
|
802
|
+
},
|
|
803
|
+
],
|
|
804
|
+
[
|
|
805
|
+
"span",
|
|
806
|
+
{
|
|
807
|
+
class: "absolute top-1 right-1 inline-flex items-center gap-0.5 rounded border border-orange-200 bg-orange-50/90 px-1 py-0.5 text-[10px] text-orange-700",
|
|
808
|
+
"aria-hidden": "true",
|
|
809
|
+
},
|
|
810
|
+
"\u2728 WordArt",
|
|
811
|
+
],
|
|
812
|
+
];
|
|
813
|
+
}
|
|
712
814
|
return [
|
|
713
815
|
"span",
|
|
714
816
|
{
|
|
715
817
|
class: "inline-flex items-center gap-1 mx-0.5 px-1.5 py-0.5 rounded text-xs text-orange-700 bg-orange-50 border border-orange-200 font-medium italic",
|
|
716
818
|
"data-node-type": "wordart_atom",
|
|
717
819
|
contenteditable: "false",
|
|
718
|
-
title:
|
|
820
|
+
title: detail,
|
|
719
821
|
},
|
|
720
822
|
"\u2728 " + (text || "WordArt"),
|
|
721
823
|
];
|
|
@@ -730,18 +832,51 @@ export const editorSchema = new Schema({
|
|
|
730
832
|
attrs: {
|
|
731
833
|
text: { default: null },
|
|
732
834
|
shapeType: { default: null },
|
|
835
|
+
previewMediaId: { default: null },
|
|
836
|
+
previewSrc: { default: null },
|
|
733
837
|
detail: { default: null },
|
|
734
838
|
},
|
|
735
839
|
toDOM(node) {
|
|
736
840
|
const text = node.attrs.text as string | null;
|
|
737
841
|
const label = text ? `VML: ${text}` : "VML shape";
|
|
842
|
+
const previewSrc = node.attrs.previewSrc as string | null;
|
|
843
|
+
const detail = (node.attrs.detail as string) ?? label;
|
|
844
|
+
if (previewSrc) {
|
|
845
|
+
return [
|
|
846
|
+
"span",
|
|
847
|
+
{
|
|
848
|
+
class: "relative inline-block align-baseline mx-0.5 max-w-full",
|
|
849
|
+
"data-node-type": "vml_atom",
|
|
850
|
+
"data-preview-media-id": (node.attrs.previewMediaId as string) ?? "",
|
|
851
|
+
contenteditable: "false",
|
|
852
|
+
title: detail,
|
|
853
|
+
},
|
|
854
|
+
[
|
|
855
|
+
"img",
|
|
856
|
+
{
|
|
857
|
+
src: previewSrc,
|
|
858
|
+
alt: detail,
|
|
859
|
+
class: "block max-w-full h-auto",
|
|
860
|
+
draggable: "false",
|
|
861
|
+
},
|
|
862
|
+
],
|
|
863
|
+
[
|
|
864
|
+
"span",
|
|
865
|
+
{
|
|
866
|
+
class: "absolute top-1 right-1 inline-flex items-center gap-0.5 rounded border border-gray-300 bg-gray-100/90 px-1 py-0.5 text-[10px] text-gray-600",
|
|
867
|
+
"aria-hidden": "true",
|
|
868
|
+
},
|
|
869
|
+
"\u25A6 VML",
|
|
870
|
+
],
|
|
871
|
+
];
|
|
872
|
+
}
|
|
738
873
|
return [
|
|
739
874
|
"span",
|
|
740
875
|
{
|
|
741
876
|
class: "inline-flex items-center gap-1 mx-0.5 px-1.5 py-0.5 rounded text-xs text-gray-600 bg-gray-100 border border-gray-300",
|
|
742
877
|
"data-node-type": "vml_atom",
|
|
743
878
|
contenteditable: "false",
|
|
744
|
-
title:
|
|
879
|
+
title: detail,
|
|
745
880
|
},
|
|
746
881
|
"\u25A6 " + label,
|
|
747
882
|
];
|
|
@@ -757,10 +892,25 @@ export const editorSchema = new Schema({
|
|
|
757
892
|
warningId: { default: "" },
|
|
758
893
|
label: { default: "Locked" },
|
|
759
894
|
detail: { default: "" },
|
|
895
|
+
presentation: { default: "callout" },
|
|
760
896
|
},
|
|
761
897
|
toDOM(node) {
|
|
762
898
|
const fragmentId = node.attrs.fragmentId as string;
|
|
763
899
|
const isPreview = fragmentId.startsWith("preview:");
|
|
900
|
+
const presentation = node.attrs.presentation as string;
|
|
901
|
+
if (presentation === "quiet-marker") {
|
|
902
|
+
return [
|
|
903
|
+
"div",
|
|
904
|
+
{
|
|
905
|
+
class: "block h-0 w-0 overflow-hidden",
|
|
906
|
+
contenteditable: "false",
|
|
907
|
+
"data-node-type": "opaque_block",
|
|
908
|
+
"data-block-presentation": "quiet-marker",
|
|
909
|
+
title: node.attrs.detail as string,
|
|
910
|
+
"aria-label": node.attrs.label as string,
|
|
911
|
+
},
|
|
912
|
+
];
|
|
913
|
+
}
|
|
764
914
|
return [
|
|
765
915
|
"div",
|
|
766
916
|
{
|
|
@@ -889,7 +1039,7 @@ export const editorSchema = new Schema({
|
|
|
889
1039
|
attrs: { value: { default: 0 } },
|
|
890
1040
|
toDOM(mark) {
|
|
891
1041
|
const twips = mark.attrs.value as number;
|
|
892
|
-
return ["span", { style: `letter-spacing: ${twips / 20}
|
|
1042
|
+
return ["span", { style: `letter-spacing: ${twips / 20}pt` }, 0];
|
|
893
1043
|
},
|
|
894
1044
|
},
|
|
895
1045
|
font_kerning: {
|
|
@@ -268,7 +268,7 @@ function buildPMBlocks(
|
|
|
268
268
|
} else if (block.kind === "sdt_block") {
|
|
269
269
|
nodes.push(buildSdtBlock(block, mediaPreviews, showUnsupportedObjectPreviews));
|
|
270
270
|
} else {
|
|
271
|
-
nodes.push(buildOpaqueBlock(block));
|
|
271
|
+
nodes.push(buildOpaqueBlock(block, showUnsupportedObjectPreviews));
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
|
|
@@ -366,7 +366,13 @@ function buildParagraph(
|
|
|
366
366
|
indentRight: paragraphLayout.indentation?.right ?? null,
|
|
367
367
|
indentFirstLine: paragraphLayout.indentation?.firstLine ?? null,
|
|
368
368
|
indentHanging: paragraphLayout.indentation?.hanging ?? null,
|
|
369
|
-
numberingMarkerWidth:
|
|
369
|
+
numberingMarkerWidth:
|
|
370
|
+
paragraphLayout.markerLane?.width ??
|
|
371
|
+
paragraphLayout.indentation?.hanging ??
|
|
372
|
+
(paragraphLayout.indentation?.firstLine !== undefined &&
|
|
373
|
+
paragraphLayout.indentation.firstLine < 0
|
|
374
|
+
? Math.abs(paragraphLayout.indentation.firstLine)
|
|
375
|
+
: null),
|
|
370
376
|
numberingMarkerJustification: paragraphLayout.markerJustification ?? null,
|
|
371
377
|
numberingMarkerRunProperties: block.resolvedNumbering?.markerRunProperties ?? null,
|
|
372
378
|
shadingFill: block.shading?.fill ?? cascade?.shading?.fill ?? null,
|
|
@@ -453,7 +459,7 @@ function buildInlineContent(
|
|
|
453
459
|
}
|
|
454
460
|
|
|
455
461
|
case "opaque_inline":
|
|
456
|
-
return [buildOpaqueInlineOrComplexAtom(segment, showUnsupportedObjectPreviews)];
|
|
462
|
+
return [buildOpaqueInlineOrComplexAtom(segment, mediaPreviews, showUnsupportedObjectPreviews)];
|
|
457
463
|
|
|
458
464
|
case "note_ref": {
|
|
459
465
|
const text = editorSchema.text(
|
|
@@ -602,6 +608,7 @@ const UNSUPPORTED_COMPLEX_PREVIEW_LABELS = new Set<string>([
|
|
|
602
608
|
*/
|
|
603
609
|
function buildOpaqueInlineOrComplexAtom(
|
|
604
610
|
segment: Extract<import("../../api/public-types").SurfaceInlineSegment, { kind: "opaque_inline" }>,
|
|
611
|
+
mediaPreviews: Record<string, MediaPreviewDescriptor>,
|
|
605
612
|
showUnsupportedObjectPreviews: boolean,
|
|
606
613
|
): PMNode {
|
|
607
614
|
const label = segment.label;
|
|
@@ -618,11 +625,30 @@ function buildOpaqueInlineOrComplexAtom(
|
|
|
618
625
|
});
|
|
619
626
|
}
|
|
620
627
|
|
|
621
|
-
|
|
622
|
-
|
|
628
|
+
// Bitmap-backed complex objects always upgrade to the typed atom so the
|
|
629
|
+
// reviewer sees Word's own cached rendering regardless of the debug-preview
|
|
630
|
+
// flag. The flag still gates the badge-only path for shape/wordart/vml
|
|
631
|
+
// families (below) which are decoration-weight. Chart and SmartArt are
|
|
632
|
+
// *always* rendered as typed atoms regardless of flag — a silent quiet
|
|
633
|
+
// marker over a chart leaves the reviewer with no signal that data is
|
|
634
|
+
// missing, which is worse than the small cost of an always-visible chip.
|
|
635
|
+
const previewSrc = segment.previewMediaId
|
|
636
|
+
? mediaPreviews[segment.previewMediaId]?.src ?? null
|
|
637
|
+
: null;
|
|
638
|
+
|
|
639
|
+
if (label === "Embedded chart") {
|
|
640
|
+
return editorSchema.nodes.chart_atom.create({
|
|
641
|
+
previewMediaId: segment.previewMediaId ?? null,
|
|
642
|
+
previewSrc,
|
|
643
|
+
detail,
|
|
644
|
+
});
|
|
623
645
|
}
|
|
624
|
-
if (
|
|
625
|
-
return editorSchema.nodes.smartart_atom.create({
|
|
646
|
+
if (label === "SmartArt diagram") {
|
|
647
|
+
return editorSchema.nodes.smartart_atom.create({
|
|
648
|
+
previewMediaId: segment.previewMediaId ?? null,
|
|
649
|
+
previewSrc,
|
|
650
|
+
detail,
|
|
651
|
+
});
|
|
626
652
|
}
|
|
627
653
|
if (showUnsupportedObjectPreviews && (label === "Drawing shape" || label === "Text box")) {
|
|
628
654
|
const textMatch = /(?:Text content|Content): "([^"]+)"/.exec(detail);
|
|
@@ -672,12 +698,14 @@ function buildOpaqueInlineOrComplexAtom(
|
|
|
672
698
|
|
|
673
699
|
function buildOpaqueBlock(
|
|
674
700
|
block: Extract<SurfaceBlockSnapshot, { kind: "opaque_block" }>,
|
|
701
|
+
showUnsupportedObjectPreviews: boolean,
|
|
675
702
|
): PMNode {
|
|
676
703
|
return editorSchema.nodes.opaque_block.create({
|
|
677
704
|
fragmentId: block.fragmentId,
|
|
678
705
|
warningId: block.warningId,
|
|
679
706
|
label: block.label,
|
|
680
707
|
detail: block.detail,
|
|
708
|
+
presentation: showUnsupportedObjectPreviews ? "callout" : "quiet-marker",
|
|
681
709
|
});
|
|
682
710
|
}
|
|
683
711
|
|
|
@@ -12,6 +12,23 @@ import {
|
|
|
12
12
|
type RemoteCursorState,
|
|
13
13
|
} from "../../runtime/collab/remote-cursor-awareness";
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Remote `cursor.color` comes straight off Yjs Awareness — another peer
|
|
17
|
+
* can set it to anything. Validate to a narrow CSS-color allowlist
|
|
18
|
+
* before interpolating into an inline style, otherwise a malicious peer
|
|
19
|
+
* could inject arbitrary CSS declarations via `; background: url(...)`.
|
|
20
|
+
* Returns a fallback neutral color when the input is rejected.
|
|
21
|
+
*/
|
|
22
|
+
const SAFE_REMOTE_CURSOR_FALLBACK = "#6b7280";
|
|
23
|
+
function safeRemoteCursorColor(raw: unknown): string {
|
|
24
|
+
if (typeof raw !== "string") return SAFE_REMOTE_CURSOR_FALLBACK;
|
|
25
|
+
const value = raw.trim();
|
|
26
|
+
if (value.length === 0 || value.length > 32) return SAFE_REMOTE_CURSOR_FALLBACK;
|
|
27
|
+
if (/^#[0-9a-fA-F]{3}$|^#[0-9a-fA-F]{6}$|^#[0-9a-fA-F]{8}$/.test(value)) return value;
|
|
28
|
+
if (/^rgba?\(\s*\d+(\.\d+)?%?(\s*,\s*\d+(\.\d+)?%?){2,3}\s*\)$/.test(value)) return value;
|
|
29
|
+
return SAFE_REMOTE_CURSOR_FALLBACK;
|
|
30
|
+
}
|
|
31
|
+
|
|
15
32
|
interface RemoteCursorPluginState {
|
|
16
33
|
cursors: RemoteCursorState[];
|
|
17
34
|
}
|
|
@@ -68,7 +85,7 @@ export function createRemoteCursorPlugin(
|
|
|
68
85
|
if (from !== to) {
|
|
69
86
|
decorations.push(
|
|
70
87
|
Decoration.inline(from, to, {
|
|
71
|
-
style: `background-color: ${cursor.color}33;`,
|
|
88
|
+
style: `background-color: ${safeRemoteCursorColor(cursor.color)}33;`,
|
|
72
89
|
class: "remote-cursor-selection",
|
|
73
90
|
}),
|
|
74
91
|
);
|
|
@@ -149,7 +166,7 @@ function createCursorWidget(cursor: RemoteCursorState): HTMLElement {
|
|
|
149
166
|
top: -0.2em;
|
|
150
167
|
width: 2px;
|
|
151
168
|
height: 1.2em;
|
|
152
|
-
background-color: ${cursor.color};
|
|
169
|
+
background-color: ${safeRemoteCursorColor(cursor.color)};
|
|
153
170
|
border-radius: 1px;
|
|
154
171
|
`;
|
|
155
172
|
container.appendChild(caret);
|
|
@@ -163,7 +180,7 @@ function createCursorWidget(cursor: RemoteCursorState): HTMLElement {
|
|
|
163
180
|
font-family: system-ui, -apple-system, sans-serif;
|
|
164
181
|
font-weight: 500;
|
|
165
182
|
color: white;
|
|
166
|
-
background-color: ${cursor.color};
|
|
183
|
+
background-color: ${safeRemoteCursorColor(cursor.color)};
|
|
167
184
|
padding: 1px 4px;
|
|
168
185
|
border-radius: 3px;
|
|
169
186
|
white-space: nowrap;
|