@beyondwork/docx-react-component 1.0.40 → 1.0.42

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.
Files changed (88) hide show
  1. package/package.json +13 -1
  2. package/src/api/awareness-identity-types.ts +35 -0
  3. package/src/api/comment-negotiation-types.ts +130 -0
  4. package/src/api/comment-presentation-types.ts +106 -0
  5. package/src/api/external-custody-types.ts +74 -0
  6. package/src/api/participants-types.ts +18 -0
  7. package/src/api/public-types.ts +347 -4
  8. package/src/api/scope-metadata-resolver-types.ts +88 -0
  9. package/src/core/commands/formatting-commands.ts +1 -1
  10. package/src/core/commands/index.ts +568 -1
  11. package/src/index.ts +118 -1
  12. package/src/io/export/escape-xml-attribute.ts +26 -0
  13. package/src/io/export/external-send.ts +188 -0
  14. package/src/io/export/serialize-comments.ts +13 -16
  15. package/src/io/export/serialize-footnotes.ts +17 -24
  16. package/src/io/export/serialize-headers-footers.ts +17 -24
  17. package/src/io/export/serialize-main-document.ts +59 -62
  18. package/src/io/export/serialize-numbering.ts +20 -27
  19. package/src/io/export/serialize-runtime-revisions.ts +2 -9
  20. package/src/io/export/serialize-tables.ts +8 -15
  21. package/src/io/export/table-properties-xml.ts +25 -32
  22. package/src/io/import/external-reimport.ts +40 -0
  23. package/src/io/ooxml/bw-xml.ts +244 -0
  24. package/src/io/ooxml/canonicalize-payload.ts +301 -0
  25. package/src/io/ooxml/comment-negotiation-payload.ts +288 -0
  26. package/src/io/ooxml/comment-presentation-payload.ts +311 -0
  27. package/src/io/ooxml/external-custody-payload.ts +102 -0
  28. package/src/io/ooxml/participants-payload.ts +97 -0
  29. package/src/io/ooxml/payload-signature.ts +112 -0
  30. package/src/io/ooxml/workflow-payload-validator.ts +271 -0
  31. package/src/io/ooxml/workflow-payload.ts +146 -7
  32. package/src/runtime/awareness-identity.ts +173 -0
  33. package/src/runtime/collab/event-types.ts +27 -0
  34. package/src/runtime/collab-session-bridge.ts +157 -0
  35. package/src/runtime/collab-session-facet.ts +193 -0
  36. package/src/runtime/collab-session.ts +273 -0
  37. package/src/runtime/comment-negotiation-sync.ts +91 -0
  38. package/src/runtime/comment-negotiation.ts +158 -0
  39. package/src/runtime/comment-presentation.ts +223 -0
  40. package/src/runtime/document-runtime.ts +280 -93
  41. package/src/runtime/external-send-runtime.ts +117 -0
  42. package/src/runtime/layout/docx-font-loader.ts +11 -30
  43. package/src/runtime/layout/inert-layout-facet.ts +2 -0
  44. package/src/runtime/layout/layout-engine-instance.ts +122 -12
  45. package/src/runtime/layout/page-graph.ts +79 -7
  46. package/src/runtime/layout/paginated-layout-engine.ts +230 -34
  47. package/src/runtime/layout/public-facet.ts +185 -13
  48. package/src/runtime/layout/table-row-split.ts +316 -0
  49. package/src/runtime/markdown-sanitizer.ts +132 -0
  50. package/src/runtime/participants.ts +134 -0
  51. package/src/runtime/resign-payload.ts +120 -0
  52. package/src/runtime/tamper-gate.ts +157 -0
  53. package/src/runtime/workflow-markup.ts +9 -0
  54. package/src/runtime/workflow-rail-segments.ts +244 -5
  55. package/src/ui/WordReviewEditor.tsx +587 -0
  56. package/src/ui/editor-runtime-boundary.ts +1 -0
  57. package/src/ui/editor-shell-view.tsx +11 -0
  58. package/src/ui-tailwind/chrome/chrome-preset-model.ts +28 -0
  59. package/src/ui-tailwind/chrome/collab-audience-chip.tsx +73 -0
  60. package/src/ui-tailwind/chrome/collab-negotiation-action-bar.tsx +244 -0
  61. package/src/ui-tailwind/chrome/collab-presence-strip.tsx +150 -0
  62. package/src/ui-tailwind/chrome/collab-role-chip.tsx +62 -0
  63. package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +68 -0
  64. package/src/ui-tailwind/chrome/collab-send-to-supplier-modal.tsx +149 -0
  65. package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +68 -0
  66. package/src/ui-tailwind/chrome/forward-non-drag-click.ts +104 -0
  67. package/src/ui-tailwind/chrome/tw-mode-dock.tsx +1 -0
  68. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +7 -1
  69. package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +1 -38
  70. package/src/ui-tailwind/chrome-overlay/index.ts +6 -0
  71. package/src/ui-tailwind/chrome-overlay/scope-card-role-model.ts +78 -0
  72. package/src/ui-tailwind/chrome-overlay/scope-keyboard-cycle.ts +49 -0
  73. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +58 -0
  74. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +527 -0
  75. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +120 -22
  76. package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +310 -32
  77. package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +93 -14
  78. package/src/ui-tailwind/editor-surface/pm-decorations.ts +35 -1
  79. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +86 -3
  80. package/src/ui-tailwind/editor-surface/pm-schema.ts +15 -13
  81. package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +20 -3
  82. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +15 -14
  83. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +66 -0
  84. package/src/ui-tailwind/index.ts +32 -0
  85. package/src/ui-tailwind/review/tw-review-rail-footer.tsx +29 -3
  86. package/src/ui-tailwind/status/tw-status-bar.tsx +52 -1
  87. package/src/ui-tailwind/theme/editor-theme.css +25 -0
  88. package/src/ui-tailwind/tw-review-workspace.tsx +293 -34
@@ -0,0 +1,223 @@
1
+ import * as Y from "yjs";
2
+
3
+ import type {
4
+ CommentAudience,
5
+ CommentAttachment,
6
+ CommentBody,
7
+ CommentLabel,
8
+ CommentMention,
9
+ CommentPresentation,
10
+ CommentPresentationAction,
11
+ CommentPresentationReply,
12
+ CommentPresentationSnapshot,
13
+ CommentReaction,
14
+ } from "../api/comment-presentation-types.ts";
15
+ import { sanitizeMarkdown, sha256Hex } from "./markdown-sanitizer.ts";
16
+
17
+ const MAP_KEY = "commentPresentation";
18
+
19
+ export interface PresentationStoreHandle {
20
+ get(commentId: string): CommentPresentation | undefined;
21
+ snapshot(): CommentPresentationSnapshot;
22
+ apply(action: CommentPresentationAction): Promise<CommentPresentation>;
23
+ ingestRemote(entry: CommentPresentation): void;
24
+ subscribe(fn: (changedIds: string[]) => void): () => void;
25
+ destroy(): void;
26
+ }
27
+
28
+ export function createPresentationStore(ydoc: Y.Doc): PresentationStoreHandle {
29
+ const yMap = ydoc.getMap<CommentPresentation>(MAP_KEY);
30
+ const listeners = new Set<(ids: string[]) => void>();
31
+
32
+ const onChange = (event: Y.YMapEvent<CommentPresentation>): void => {
33
+ if (listeners.size === 0) return;
34
+ const ids = Array.from(event.keysChanged);
35
+ for (const fn of listeners) fn(ids);
36
+ };
37
+ yMap.observe(onChange);
38
+
39
+ const seed = (commentId: string): CommentPresentation =>
40
+ yMap.get(commentId) ?? {
41
+ commentId,
42
+ audience: "internal",
43
+ body: emptyBody(),
44
+ replies: [],
45
+ mentions: [],
46
+ attachments: [],
47
+ reactions: [],
48
+ labels: [],
49
+ };
50
+
51
+ return {
52
+ get: (id) => cloneMaybe(yMap.get(id)),
53
+ snapshot: () => ({
54
+ schemaVersion: 1,
55
+ entries: Array.from(yMap.values()).map(clone),
56
+ }),
57
+ ingestRemote: (entry) => {
58
+ yMap.set(entry.commentId, clone(entry));
59
+ },
60
+ subscribe: (fn) => {
61
+ listeners.add(fn);
62
+ return () => {
63
+ listeners.delete(fn);
64
+ };
65
+ },
66
+ destroy: () => {
67
+ yMap.unobserve(onChange);
68
+ listeners.clear();
69
+ },
70
+ async apply(action) {
71
+ const prev = seed(action.commentId);
72
+ const next = await reducePresentation(prev, action);
73
+ yMap.set(action.commentId, next);
74
+ return clone(next);
75
+ },
76
+ };
77
+ }
78
+
79
+ export async function reducePresentation(
80
+ prev: CommentPresentation,
81
+ action: CommentPresentationAction,
82
+ ): Promise<CommentPresentation> {
83
+ switch (action.type) {
84
+ case "set-body": {
85
+ const body = await buildBody(action.text);
86
+ const next: CommentPresentation = { ...prev, body };
87
+ if (action.audience) next.audience = action.audience;
88
+ return next;
89
+ }
90
+ case "set-audience":
91
+ return { ...prev, audience: action.audience };
92
+ case "set-reply-body": {
93
+ const body = await buildBody(action.text);
94
+ return { ...prev, replies: upsertReply(prev.replies, action.entryId, body) };
95
+ }
96
+ case "add-mention":
97
+ return { ...prev, mentions: [...prev.mentions, { ...action.mention }] };
98
+ case "add-attachment":
99
+ return {
100
+ ...prev,
101
+ attachments: [...prev.attachments, { ...action.attachment }],
102
+ };
103
+ case "remove-attachment":
104
+ return {
105
+ ...prev,
106
+ attachments: prev.attachments.filter((a) => a.id !== action.attachmentId),
107
+ };
108
+ case "toggle-reaction": {
109
+ const match = (r: CommentReaction): boolean =>
110
+ r.emoji === action.emoji && r.authorId === action.authorId;
111
+ if (prev.reactions.some(match)) {
112
+ return { ...prev, reactions: prev.reactions.filter((r) => !match(r)) };
113
+ }
114
+ return {
115
+ ...prev,
116
+ reactions: [
117
+ ...prev.reactions,
118
+ {
119
+ emoji: action.emoji,
120
+ authorId: action.authorId,
121
+ reactedAt: action.now,
122
+ },
123
+ ],
124
+ };
125
+ }
126
+ case "set-labels":
127
+ return { ...prev, labels: action.labels.map((l) => ({ ...l })) };
128
+ }
129
+ }
130
+
131
+ async function buildBody(raw: string): Promise<CommentBody> {
132
+ const { text, sanitized } = sanitizeMarkdown(raw);
133
+ const digest = `sha256:${await sha256Hex(text)}`;
134
+ const body: CommentBody = { format: "markdown", text, digest };
135
+ if (sanitized) body.sanitized = true;
136
+ return body;
137
+ }
138
+
139
+ function emptyBody(): CommentBody {
140
+ return {
141
+ format: "markdown",
142
+ text: "",
143
+ digest:
144
+ "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
145
+ };
146
+ }
147
+
148
+ function upsertReply(
149
+ replies: CommentPresentationReply[],
150
+ entryId: string,
151
+ body: CommentBody,
152
+ ): CommentPresentationReply[] {
153
+ const idx = replies.findIndex((r) => r.entryId === entryId);
154
+ if (idx < 0) return [...replies, { entryId, body }];
155
+ const next = replies.slice();
156
+ next[idx] = { entryId, body };
157
+ return next;
158
+ }
159
+
160
+ function clone(entry: CommentPresentation): CommentPresentation {
161
+ return {
162
+ commentId: entry.commentId,
163
+ audience: entry.audience,
164
+ body: cloneBody(entry.body),
165
+ replies: entry.replies.map((r) => ({
166
+ entryId: r.entryId,
167
+ body: cloneBody(r.body),
168
+ })),
169
+ mentions: entry.mentions.map(cloneMention),
170
+ attachments: entry.attachments.map(cloneAttachment),
171
+ reactions: entry.reactions.map((r) => ({ ...r })),
172
+ labels: entry.labels.map(cloneLabel),
173
+ };
174
+ }
175
+
176
+ function cloneMaybe(
177
+ entry: CommentPresentation | undefined,
178
+ ): CommentPresentation | undefined {
179
+ return entry ? clone(entry) : undefined;
180
+ }
181
+
182
+ function cloneBody(body: CommentBody): CommentBody {
183
+ const copy: CommentBody = {
184
+ format: body.format,
185
+ text: body.text,
186
+ digest: body.digest,
187
+ };
188
+ if (body.sanitized) copy.sanitized = true;
189
+ return copy;
190
+ }
191
+
192
+ function cloneMention(mention: CommentMention): CommentMention {
193
+ const copy: CommentMention = {
194
+ userId: mention.userId,
195
+ displayName: mention.displayName,
196
+ offsetInBody: mention.offsetInBody,
197
+ };
198
+ if (mention.entryId !== undefined) copy.entryId = mention.entryId;
199
+ return copy;
200
+ }
201
+
202
+ function cloneAttachment(a: CommentAttachment): CommentAttachment {
203
+ const copy: CommentAttachment = {
204
+ id: a.id,
205
+ kind: a.kind,
206
+ displayName: a.displayName,
207
+ };
208
+ if (a.mimeType !== undefined) copy.mimeType = a.mimeType;
209
+ if (a.relationshipId !== undefined) copy.relationshipId = a.relationshipId;
210
+ if (a.href !== undefined) copy.href = a.href;
211
+ if (a.byteLength !== undefined) copy.byteLength = a.byteLength;
212
+ if (a.width !== undefined) copy.width = a.width;
213
+ if (a.height !== undefined) copy.height = a.height;
214
+ return copy;
215
+ }
216
+
217
+ function cloneLabel(label: CommentLabel): CommentLabel {
218
+ const copy: CommentLabel = { key: label.key, text: label.text };
219
+ if (label.color !== undefined) copy.color = label.color;
220
+ return copy;
221
+ }
222
+
223
+ export type { CommentAudience };