@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
|
@@ -12,6 +12,54 @@ import type {
|
|
|
12
12
|
WorkflowScope,
|
|
13
13
|
WorkflowWorkItem,
|
|
14
14
|
} from "../../api/public-types.ts";
|
|
15
|
+
import type { EditorStateNamespace, EditorStateLocation } from "../../api/editor-state-types.ts";
|
|
16
|
+
import {
|
|
17
|
+
validateWorkflowPayloadEnvelope,
|
|
18
|
+
type ValidatorIssue,
|
|
19
|
+
} from "./workflow-payload-validator.ts";
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Schema 1.2 editor-state types (edge-of-module shape, channel-free)
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
export interface EditorStatePayloadNamespaceEntry {
|
|
26
|
+
namespace: EditorStateNamespace;
|
|
27
|
+
schemaVersion: string;
|
|
28
|
+
/** JSON-serializable blob (CDATA-wrapped on serialize). Exactly one of inline/storageRef. */
|
|
29
|
+
inline?: unknown;
|
|
30
|
+
storageRef?: { location: Exclude<EditorStateLocation, "in-document">; entryKey: string };
|
|
31
|
+
/**
|
|
32
|
+
* Parser-internal flag: set when the `<bw:inline>` CDATA block contained
|
|
33
|
+
* malformed JSON. Hydration surfaces this as `editor_state_part_load_failed`
|
|
34
|
+
* rather than silently dropping the entry.
|
|
35
|
+
*/
|
|
36
|
+
malformedInline?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface EditorStatePayload {
|
|
40
|
+
entries: EditorStatePayloadNamespaceEntry[];
|
|
41
|
+
/** Unknown namespaces preserved for round-trip — raw XML fragment per entry, keyed by name. */
|
|
42
|
+
unknownNamespaces?: Array<{ name: string; rawXml: string }>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Schema 1.1 parser helpers (fail-closed per spec §8.2)
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
function parseClosedEnum<T extends string>(
|
|
50
|
+
raw: string | undefined,
|
|
51
|
+
allowed: readonly T[],
|
|
52
|
+
): T | undefined {
|
|
53
|
+
if (raw === undefined) return undefined;
|
|
54
|
+
return (allowed as readonly string[]).includes(raw) ? (raw as T) : undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parseNonNegativeInt(raw: string | undefined): number | undefined {
|
|
58
|
+
if (raw === undefined) return undefined;
|
|
59
|
+
const n = Number(raw);
|
|
60
|
+
if (!Number.isInteger(n) || n < 0) return undefined;
|
|
61
|
+
return n;
|
|
62
|
+
}
|
|
15
63
|
|
|
16
64
|
export const WORKFLOW_PAYLOAD_PART_PATH = "/customXml/item1.xml";
|
|
17
65
|
export const WORKFLOW_PAYLOAD_ITEM_PROPS_PART_PATH = "/customXml/itemProps1.xml";
|
|
@@ -39,6 +87,9 @@ type EmbeddedWorkflowPayloadDescriptor = {
|
|
|
39
87
|
export interface WorkflowPayloadEnvelope {
|
|
40
88
|
workflowMetadata: WorkflowMetadataSnapshot;
|
|
41
89
|
workflowOverlay?: WorkflowOverlay;
|
|
90
|
+
/** Present only when the payload was version 1.2+ and carried a non-empty <bw:editorState> block. */
|
|
91
|
+
editorState?: EditorStatePayload;
|
|
92
|
+
validatorIssues?: readonly ValidatorIssue[];
|
|
42
93
|
}
|
|
43
94
|
|
|
44
95
|
export function getDocumentBackedWorkflowMetadata(
|
|
@@ -63,6 +114,8 @@ export function buildWorkflowPayloadParts(input: {
|
|
|
63
114
|
sourcePackage: OpcPackage;
|
|
64
115
|
workflowMetadata: WorkflowMetadataSnapshot | undefined;
|
|
65
116
|
workflowOverlay?: WorkflowOverlay;
|
|
117
|
+
/** Optional schema 1.2 editor-state block. Omitted when empty. */
|
|
118
|
+
editorState?: EditorStatePayload;
|
|
66
119
|
documentId: string;
|
|
67
120
|
createdAt: string;
|
|
68
121
|
updatedAt: string;
|
|
@@ -102,6 +155,7 @@ export function buildWorkflowPayloadParts(input: {
|
|
|
102
155
|
producerVersion: input.producerVersion,
|
|
103
156
|
workflowMetadata,
|
|
104
157
|
workflowOverlay: input.workflowOverlay,
|
|
158
|
+
editorState: input.editorState,
|
|
105
159
|
preservedExtensionsXml: getPreservedExtensionsXml(input.sourcePackage, descriptor.payloadPartPath),
|
|
106
160
|
});
|
|
107
161
|
const itemPropsXml = buildItemPropsXml(descriptor.itemId);
|
|
@@ -144,7 +198,8 @@ export function parseWorkflowPayloadEnvelopeFromPackage(
|
|
|
144
198
|
}
|
|
145
199
|
|
|
146
200
|
const xml = decodeUtf8(payloadPart.bytes);
|
|
147
|
-
|
|
201
|
+
// Match both self-closing (<bw:entry .../>) and body-bearing (<bw:entry ...>body</bw:entry>)
|
|
202
|
+
const entryMatches = [...xml.matchAll(/<bw:entry\b([^>]*)(?:\/>|>([\s\S]*?)<\/bw:entry>)/g)];
|
|
148
203
|
const definitions: WorkflowMetadataDefinition[] = [];
|
|
149
204
|
const entries: WorkflowMetadataEntry[] = [];
|
|
150
205
|
|
|
@@ -183,6 +238,14 @@ export function parseWorkflowPayloadEnvelopeFromPackage(
|
|
|
183
238
|
if (!entryId || !metadataId) {
|
|
184
239
|
continue;
|
|
185
240
|
}
|
|
241
|
+
|
|
242
|
+
const entryMetadataPersistence = parseClosedEnum(
|
|
243
|
+
attributes.metadataPersistence,
|
|
244
|
+
["internal", "external", "inherit"] as const,
|
|
245
|
+
);
|
|
246
|
+
const entryStorageRef = attributes.storageRef;
|
|
247
|
+
const entryMetadataVersion = parseNonNegativeInt(attributes.metadataVersion);
|
|
248
|
+
|
|
186
249
|
entries.push({
|
|
187
250
|
entryId,
|
|
188
251
|
metadataId,
|
|
@@ -198,15 +261,191 @@ export function parseWorkflowPayloadEnvelopeFromPackage(
|
|
|
198
261
|
...(attributes.scope?.startsWith("workItem:")
|
|
199
262
|
? { workItemId: attributes.scope.slice("workItem:".length) }
|
|
200
263
|
: {}),
|
|
264
|
+
...(entryMetadataPersistence !== undefined ? { metadataPersistence: entryMetadataPersistence } : {}),
|
|
265
|
+
...(entryStorageRef !== undefined ? { storageRef: entryStorageRef } : {}),
|
|
266
|
+
...(entryMetadataVersion !== undefined ? { metadataVersion: entryMetadataVersion } : {}),
|
|
201
267
|
});
|
|
202
268
|
}
|
|
203
269
|
|
|
270
|
+
const validatorIssues = validateWorkflowPayloadEnvelope(xml);
|
|
271
|
+
const editorState = parseEditorStateXml(xml);
|
|
204
272
|
return {
|
|
205
273
|
workflowMetadata: {
|
|
206
274
|
definitions,
|
|
207
275
|
entries,
|
|
208
276
|
},
|
|
209
277
|
workflowOverlay: parseWorkflowOverlay(xml),
|
|
278
|
+
...(editorState !== undefined ? { editorState } : {}),
|
|
279
|
+
...(validatorIssues.length > 0 ? { validatorIssues } : {}),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function needsSchemaV11(input: {
|
|
284
|
+
workflowMetadata: WorkflowMetadataSnapshot;
|
|
285
|
+
workflowOverlay?: WorkflowOverlay;
|
|
286
|
+
}): boolean {
|
|
287
|
+
if (input.workflowOverlay?.metadataPersistence !== undefined) {
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
if (
|
|
291
|
+
input.workflowOverlay?.scopes?.some(
|
|
292
|
+
(s) => s.metadataPersistence !== undefined,
|
|
293
|
+
)
|
|
294
|
+
) {
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
if (
|
|
298
|
+
input.workflowOverlay?.scopes?.some((s) =>
|
|
299
|
+
s.metadata?.some(
|
|
300
|
+
(f) =>
|
|
301
|
+
f.metadataPersistence !== undefined ||
|
|
302
|
+
f.storageRef !== undefined ||
|
|
303
|
+
f.metadataVersion !== undefined,
|
|
304
|
+
),
|
|
305
|
+
)
|
|
306
|
+
) {
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
if (
|
|
310
|
+
input.workflowMetadata.entries.some(
|
|
311
|
+
(e) =>
|
|
312
|
+
e.metadataPersistence !== undefined ||
|
|
313
|
+
e.storageRef !== undefined ||
|
|
314
|
+
e.metadataVersion !== undefined,
|
|
315
|
+
)
|
|
316
|
+
) {
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Returns true when the payload has at least one namespace entry or unknown namespace to emit. */
|
|
323
|
+
function hasNonEmptyEditorState(es: EditorStatePayload | undefined): boolean {
|
|
324
|
+
if (!es) return false;
|
|
325
|
+
return (es.entries.length > 0) || ((es.unknownNamespaces?.length ?? 0) > 0);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
// Known namespace names (closed set for round-trip; unknown names are opaque)
|
|
330
|
+
// ---------------------------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
const KNOWN_EDITOR_STATE_NAMESPACES: readonly EditorStateNamespace[] = [
|
|
333
|
+
"hostAnnotations",
|
|
334
|
+
"workflowOverlay",
|
|
335
|
+
"workflowMetadata",
|
|
336
|
+
"workItems",
|
|
337
|
+
];
|
|
338
|
+
|
|
339
|
+
const KNOWN_STORAGE_LOCATIONS: ReadonlyArray<Exclude<EditorStateLocation, "in-document">> = [
|
|
340
|
+
"rowstore",
|
|
341
|
+
"key-only",
|
|
342
|
+
];
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Escapes `]]>` inside CDATA content using the standard XML split:
|
|
346
|
+
* `]]>` → `]]]]><![CDATA[>`
|
|
347
|
+
*/
|
|
348
|
+
function escapeCdata(text: string): string {
|
|
349
|
+
return text.replace(/\]\]>/g, "]]]]><![CDATA[>");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Builds the `<bw:editorState>…</bw:editorState>` block.
|
|
354
|
+
* Returns an empty string when both entries and unknownNamespaces are empty.
|
|
355
|
+
*/
|
|
356
|
+
export function buildEditorStateXml(payload: EditorStatePayload): string {
|
|
357
|
+
if (!hasNonEmptyEditorState(payload)) {
|
|
358
|
+
return "";
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const knownLines = payload.entries.map((entry) => {
|
|
362
|
+
const nsOpen = `<bw:namespace name="${escapeXml(entry.namespace)}" schemaVersion="${escapeXml(entry.schemaVersion)}">`;
|
|
363
|
+
let content: string;
|
|
364
|
+
if (entry.storageRef !== undefined) {
|
|
365
|
+
content = `<bw:storageRef location="${escapeXml(entry.storageRef.location)}" entryKey="${escapeXml(entry.storageRef.entryKey)}" />`;
|
|
366
|
+
} else {
|
|
367
|
+
const json = escapeCdata(JSON.stringify(entry.inline));
|
|
368
|
+
content = `<bw:inline><![CDATA[${json}]]></bw:inline>`;
|
|
369
|
+
}
|
|
370
|
+
return `${nsOpen}${content}</bw:namespace>`;
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const unknownLines = (payload.unknownNamespaces ?? []).map((u) => u.rawXml);
|
|
374
|
+
|
|
375
|
+
const innerXml = [...knownLines, ...unknownLines].join("\n");
|
|
376
|
+
return `<bw:editorState>\n${indentLines(innerXml, 2)}\n</bw:editorState>`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Parses the `<bw:editorState>` block from a full workflow payload XML string.
|
|
381
|
+
* Returns `undefined` when no block is present.
|
|
382
|
+
* Malformed JSON is silently skipped (validator flags it separately).
|
|
383
|
+
*/
|
|
384
|
+
export function parseEditorStateXml(xml: string): EditorStatePayload | undefined {
|
|
385
|
+
const blockMatch = xml.match(/<bw:editorState\b[^>]*>([\s\S]*?)<\/bw:editorState>/u);
|
|
386
|
+
if (!blockMatch) {
|
|
387
|
+
return undefined;
|
|
388
|
+
}
|
|
389
|
+
const blockBody = blockMatch[1] ?? "";
|
|
390
|
+
|
|
391
|
+
const entries: EditorStatePayloadNamespaceEntry[] = [];
|
|
392
|
+
const unknownNamespaces: Array<{ name: string; rawXml: string }> = [];
|
|
393
|
+
|
|
394
|
+
// Match each <bw:namespace ... > ... </bw:namespace>
|
|
395
|
+
const nsRe = /<bw:namespace\b([^>]*)>([\s\S]*?)<\/bw:namespace>/gu;
|
|
396
|
+
for (const nsMatch of blockBody.matchAll(nsRe)) {
|
|
397
|
+
const attrsStr = nsMatch[1] ?? "";
|
|
398
|
+
const nsBody = nsMatch[2] ?? "";
|
|
399
|
+
const rawXml = nsMatch[0] ?? "";
|
|
400
|
+
const attrs = parseAttributes(attrsStr);
|
|
401
|
+
const name = attrs.name ?? "";
|
|
402
|
+
const schemaVersion = attrs.schemaVersion ?? "";
|
|
403
|
+
|
|
404
|
+
if (!(KNOWN_EDITOR_STATE_NAMESPACES as readonly string[]).includes(name)) {
|
|
405
|
+
unknownNamespaces.push({ name, rawXml });
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const namespace = name as EditorStateNamespace;
|
|
410
|
+
|
|
411
|
+
// Parse <bw:storageRef ... />
|
|
412
|
+
const storageRefMatch = nsBody.match(/<bw:storageRef\b([^>]*)\/>/u);
|
|
413
|
+
if (storageRefMatch) {
|
|
414
|
+
const refAttrs = parseAttributes(storageRefMatch[1] ?? "");
|
|
415
|
+
const location = refAttrs.location as Exclude<EditorStateLocation, "in-document"> | undefined;
|
|
416
|
+
const entryKey = refAttrs.entryKey ?? "";
|
|
417
|
+
entries.push({
|
|
418
|
+
namespace,
|
|
419
|
+
schemaVersion,
|
|
420
|
+
storageRef: { location: location ?? "rowstore", entryKey },
|
|
421
|
+
});
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Parse <bw:inline>...</bw:inline> — extract CDATA content
|
|
426
|
+
const inlineMatch = nsBody.match(/<bw:inline\b[^>]*>([\s\S]*?)<\/bw:inline>/u);
|
|
427
|
+
if (inlineMatch) {
|
|
428
|
+
const raw = inlineMatch[1] ?? "";
|
|
429
|
+
// Extract CDATA content — handles split CDATA sections from ]]> escaping
|
|
430
|
+
const cdataText = raw.replace(/<!\[CDATA\[|\]\]>/g, "").trim();
|
|
431
|
+
try {
|
|
432
|
+
const parsed = JSON.parse(cdataText) as unknown;
|
|
433
|
+
entries.push({ namespace, schemaVersion, inline: parsed });
|
|
434
|
+
} catch {
|
|
435
|
+
// Malformed JSON: surface to the runtime so the host sees a
|
|
436
|
+
// load-failure event rather than silently losing the entry.
|
|
437
|
+
entries.push({ namespace, schemaVersion, malformedInline: true });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (entries.length === 0 && unknownNamespaces.length === 0) {
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return {
|
|
447
|
+
entries,
|
|
448
|
+
...(unknownNamespaces.length > 0 ? { unknownNamespaces } : {}),
|
|
210
449
|
};
|
|
211
450
|
}
|
|
212
451
|
|
|
@@ -217,8 +456,16 @@ function buildPayloadXml(input: {
|
|
|
217
456
|
producerVersion: string;
|
|
218
457
|
workflowMetadata: WorkflowMetadataSnapshot;
|
|
219
458
|
workflowOverlay?: WorkflowOverlay;
|
|
459
|
+
editorState?: EditorStatePayload;
|
|
220
460
|
preservedExtensionsXml: string;
|
|
221
461
|
}): string {
|
|
462
|
+
const hasEditorState = hasNonEmptyEditorState(input.editorState);
|
|
463
|
+
const schemaVersion = hasEditorState
|
|
464
|
+
? "1.2"
|
|
465
|
+
: needsSchemaV11(input)
|
|
466
|
+
? "1.1"
|
|
467
|
+
: "1.0";
|
|
468
|
+
|
|
222
469
|
const definitionEntriesXml = input.workflowMetadata.definitions
|
|
223
470
|
.map((definition) => [
|
|
224
471
|
`<bw:entry`,
|
|
@@ -237,7 +484,7 @@ function buildPayloadXml(input: {
|
|
|
237
484
|
const serializedValue = serializeWorkflowMetadataValue(entry.value);
|
|
238
485
|
const storyTargetAttributes = serializeWorkflowStoryTarget(entry.storyTarget);
|
|
239
486
|
|
|
240
|
-
|
|
487
|
+
const baseAttrs = [
|
|
241
488
|
`<bw:entry`,
|
|
242
489
|
` key="${escapeXml(entry.entryId)}"`,
|
|
243
490
|
` metadataId="${escapeXml(entry.metadataId)}"`,
|
|
@@ -245,8 +492,24 @@ function buildPayloadXml(input: {
|
|
|
245
492
|
` valueType="${escapeXml(serializedValue.type)}"`,
|
|
246
493
|
` scope="${escapeXml(entry.scopeId ? `scope:${entry.scopeId}` : entry.workItemId ? `workItem:${entry.workItemId}` : "document")}"`,
|
|
247
494
|
storyTargetAttributes,
|
|
248
|
-
|
|
495
|
+
entry.metadataPersistence && entry.metadataPersistence !== "inherit"
|
|
496
|
+
? ` metadataPersistence="${escapeXml(entry.metadataPersistence)}"`
|
|
497
|
+
: "",
|
|
498
|
+
entry.metadataPersistence === "external" && entry.storageRef
|
|
499
|
+
? ` storageRef="${escapeXml(entry.storageRef)}"`
|
|
500
|
+
: "",
|
|
501
|
+
typeof entry.metadataVersion === "number" &&
|
|
502
|
+
Number.isInteger(entry.metadataVersion) &&
|
|
503
|
+
entry.metadataVersion >= 0
|
|
504
|
+
? ` metadataVersion="${entry.metadataVersion.toString()}"`
|
|
505
|
+
: "",
|
|
249
506
|
].join("");
|
|
507
|
+
|
|
508
|
+
if (entry.metadataPersistence === "external") {
|
|
509
|
+
return `${baseAttrs}/>`;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return `${baseAttrs}>${escapeXml(serializedValue.text)}</bw:entry>`;
|
|
250
513
|
})
|
|
251
514
|
.filter((value) => value.length > 0)
|
|
252
515
|
.join("\n");
|
|
@@ -256,9 +519,11 @@ function buildPayloadXml(input: {
|
|
|
256
519
|
.filter((value) => value.trim().length > 0)
|
|
257
520
|
.join("\n");
|
|
258
521
|
|
|
522
|
+
const editorStateXml = hasEditorState ? buildEditorStateXml(input.editorState!) : "";
|
|
523
|
+
|
|
259
524
|
return [
|
|
260
525
|
`<?xml version="1.0" encoding="UTF-8"?>`,
|
|
261
|
-
`<bw:workflowPayload xmlns:bw="urn:beyondwork:workflow-payload:1" version="
|
|
526
|
+
`<bw:workflowPayload xmlns:bw="urn:beyondwork:workflow-payload:1" version="${schemaVersion}" payloadId="${escapeXml(input.descriptor.payloadId)}" itemId="${escapeXml(input.descriptor.itemId)}" documentId="${escapeXml(input.descriptor.documentId)}" createdAt="${escapeXml(input.createdAt)}" updatedAt="${escapeXml(input.updatedAt)}">`,
|
|
262
527
|
` <bw:manifest>`,
|
|
263
528
|
` <bw:producer name="@beyondwork/docx-react-component" version="${escapeXml(input.producerVersion)}" />`,
|
|
264
529
|
` <bw:compatibility rebindMode="best-effort" preserveUnknownExtensions="true" />`,
|
|
@@ -267,6 +532,7 @@ function buildPayloadXml(input: {
|
|
|
267
532
|
definitionEntriesXml ? indentLines(definitionEntriesXml, 4) : "",
|
|
268
533
|
metadataEntriesXml ? indentLines(metadataEntriesXml, 4) : "",
|
|
269
534
|
` </bw:metadata>`,
|
|
535
|
+
editorStateXml,
|
|
270
536
|
extensionsXml
|
|
271
537
|
? ` <bw:extensions>\n${indentLines(extensionsXml, 4)}\n </bw:extensions>`
|
|
272
538
|
: ` <bw:extensions />`,
|
|
@@ -357,6 +623,9 @@ function buildWorkblockExtensionXml(workflowOverlay: WorkflowOverlay | undefined
|
|
|
357
623
|
workflowOverlay.activeWorkItemId
|
|
358
624
|
? ` activeWorkItemId="${escapeXml(workflowOverlay.activeWorkItemId)}"`
|
|
359
625
|
: "",
|
|
626
|
+
workflowOverlay.metadataPersistence
|
|
627
|
+
? ` metadataPersistence="${escapeXml(workflowOverlay.metadataPersistence)}"`
|
|
628
|
+
: "",
|
|
360
629
|
`>`,
|
|
361
630
|
].join(""),
|
|
362
631
|
` <bw:workItems>`,
|
|
@@ -380,6 +649,9 @@ function buildWorkflowScopeXml(scope: WorkflowScope): string {
|
|
|
380
649
|
scope.workItemId ? ` workItemRef="${escapeXml(scope.workItemId)}"` : "",
|
|
381
650
|
scope.label ? ` label="${escapeXml(scope.label)}"` : "",
|
|
382
651
|
scope.domain ? ` domain="${escapeXml(scope.domain)}"` : "",
|
|
652
|
+
scope.metadataPersistence && scope.metadataPersistence !== "inherit"
|
|
653
|
+
? ` metadataPersistence="${escapeXml(scope.metadataPersistence)}"`
|
|
654
|
+
: "",
|
|
383
655
|
`>`,
|
|
384
656
|
indentLines(buildWorkflowAnchorXml(scope.anchor, scope.storyTarget), 2),
|
|
385
657
|
scopeMetadataXml ? indentLines(scopeMetadataXml, 2) : "",
|
|
@@ -397,12 +669,28 @@ function buildWorkflowScopeMetadataXml(scopeMetadata: WorkflowScope["metadata"])
|
|
|
397
669
|
scopeMetadata
|
|
398
670
|
.map((field) => {
|
|
399
671
|
const serialized = serializeWorkflowScopeMetadataValue(field.valueType, field.value);
|
|
400
|
-
|
|
672
|
+
const baseAttrs = [
|
|
401
673
|
`<bw:field`,
|
|
402
674
|
` key="${escapeXml(field.key)}"`,
|
|
403
675
|
serialized.valueType ? ` type="${escapeXml(serialized.valueType)}"` : "",
|
|
404
|
-
|
|
676
|
+
field.metadataPersistence && field.metadataPersistence !== "inherit"
|
|
677
|
+
? ` metadataPersistence="${escapeXml(field.metadataPersistence)}"`
|
|
678
|
+
: "",
|
|
679
|
+
field.metadataPersistence === "external" && field.storageRef
|
|
680
|
+
? ` storageRef="${escapeXml(field.storageRef)}"`
|
|
681
|
+
: "",
|
|
682
|
+
typeof field.metadataVersion === "number" &&
|
|
683
|
+
Number.isInteger(field.metadataVersion) &&
|
|
684
|
+
field.metadataVersion >= 0
|
|
685
|
+
? ` metadataVersion="${field.metadataVersion.toString()}"`
|
|
686
|
+
: "",
|
|
405
687
|
].join("");
|
|
688
|
+
|
|
689
|
+
if (field.metadataPersistence === "external") {
|
|
690
|
+
return `${baseAttrs}/>`;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return `${baseAttrs}>${escapeXml(serialized.text)}</bw:field>`;
|
|
406
694
|
})
|
|
407
695
|
.join("\n"),
|
|
408
696
|
2,
|
|
@@ -571,11 +859,17 @@ function parseWorkflowOverlay(xml: string): WorkflowOverlay | undefined {
|
|
|
571
859
|
.map((match) => parseWorkflowWorkItem(match[1] ?? "", match[2] ?? ""))
|
|
572
860
|
.filter((item): item is WorkflowWorkItem => item !== null);
|
|
573
861
|
|
|
862
|
+
const overlayMetadataPersistence = parseClosedEnum(
|
|
863
|
+
overlayAttributes.metadataPersistence,
|
|
864
|
+
["internal", "external"] as const,
|
|
865
|
+
);
|
|
866
|
+
|
|
574
867
|
return {
|
|
575
868
|
overlayVersion: (overlayAttributes.overlayVersion as WorkflowOverlay["overlayVersion"]) ?? "workflow-overlay/1",
|
|
576
869
|
activeWorkItemId: overlayAttributes.activeWorkItemId ?? null,
|
|
577
870
|
scopes,
|
|
578
871
|
...(workItems.length > 0 ? { workItems } : {}),
|
|
872
|
+
...(overlayMetadataPersistence !== undefined ? { metadataPersistence: overlayMetadataPersistence } : {}),
|
|
579
873
|
};
|
|
580
874
|
}
|
|
581
875
|
|
|
@@ -588,6 +882,11 @@ function parseWorkflowScope(attributesSource: string, body: string): WorkflowSco
|
|
|
588
882
|
const anchor = anchorMatch
|
|
589
883
|
? parseWorkflowOverlayAnchor(anchorMatch[1] ?? "", anchorMatch[2] ?? "")
|
|
590
884
|
: createDefaultRangeAnchor(0, 0);
|
|
885
|
+
const scopeMetadataPersistence = parseClosedEnum(
|
|
886
|
+
attributes.metadataPersistence,
|
|
887
|
+
["internal", "external", "inherit"] as const,
|
|
888
|
+
);
|
|
889
|
+
|
|
591
890
|
return {
|
|
592
891
|
scopeId: attributes.id,
|
|
593
892
|
version: attributes.version !== undefined ? Number(attributes.version) : undefined,
|
|
@@ -598,6 +897,7 @@ function parseWorkflowScope(attributesSource: string, body: string): WorkflowSco
|
|
|
598
897
|
label: attributes.label,
|
|
599
898
|
domain: attributes.domain as WorkflowScope["domain"],
|
|
600
899
|
metadata: parseWorkflowScopeMetadata(body),
|
|
900
|
+
...(scopeMetadataPersistence !== undefined ? { metadataPersistence: scopeMetadataPersistence } : {}),
|
|
601
901
|
};
|
|
602
902
|
}
|
|
603
903
|
|
|
@@ -606,7 +906,8 @@ function parseWorkflowScopeMetadata(body: string): WorkflowScope["metadata"] | u
|
|
|
606
906
|
if (!metadataMatch) {
|
|
607
907
|
return undefined;
|
|
608
908
|
}
|
|
609
|
-
|
|
909
|
+
// Match both self-closing (<bw:field .../>) and body-bearing (<bw:field ...>body</bw:field>)
|
|
910
|
+
const fields = [...(metadataMatch[1] ?? "").matchAll(/<bw:field\b([^>]*)(?:\/>|>([\s\S]*?)<\/bw:field>)/gu)]
|
|
610
911
|
.map((match) => parseWorkflowScopeMetadataField(match[1] ?? "", match[2] ?? ""))
|
|
611
912
|
.filter((field): field is NonNullable<WorkflowScope["metadata"]>[number] => field !== null);
|
|
612
913
|
return fields.length > 0 ? fields : undefined;
|
|
@@ -621,10 +922,19 @@ function parseWorkflowScopeMetadataField(
|
|
|
621
922
|
return null;
|
|
622
923
|
}
|
|
623
924
|
const valueType = attributes.type as NonNullable<WorkflowScope["metadata"]>[number]["valueType"] | undefined;
|
|
925
|
+
const fieldMetadataPersistence = parseClosedEnum(
|
|
926
|
+
attributes.metadataPersistence,
|
|
927
|
+
["internal", "external", "inherit"] as const,
|
|
928
|
+
);
|
|
929
|
+
const fieldStorageRef = attributes.storageRef;
|
|
930
|
+
const fieldMetadataVersion = parseNonNegativeInt(attributes.metadataVersion);
|
|
624
931
|
return {
|
|
625
932
|
key: attributes.key,
|
|
626
933
|
...(valueType ? { valueType } : {}),
|
|
627
934
|
value: parseWorkflowScopeMetadataValue(valueType, body),
|
|
935
|
+
...(fieldMetadataPersistence !== undefined ? { metadataPersistence: fieldMetadataPersistence } : {}),
|
|
936
|
+
...(fieldStorageRef !== undefined ? { storageRef: fieldStorageRef } : {}),
|
|
937
|
+
...(fieldMetadataVersion !== undefined ? { metadataVersion: fieldMetadataVersion } : {}),
|
|
628
938
|
};
|
|
629
939
|
}
|
|
630
940
|
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { Awareness } from "y-protocols/awareness";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
AwarenessIdentity,
|
|
5
|
+
AwarenessPeer,
|
|
6
|
+
CollabPosture,
|
|
7
|
+
PresenceSnapshot,
|
|
8
|
+
TransportStatus,
|
|
9
|
+
} from "../api/awareness-identity-types.ts";
|
|
10
|
+
|
|
11
|
+
const IDENTITY_FIELD = "identity";
|
|
12
|
+
|
|
13
|
+
const ROLE_VOCAB = new Set<AwarenessIdentity["role"]>([
|
|
14
|
+
"author",
|
|
15
|
+
"reviewer",
|
|
16
|
+
"observer",
|
|
17
|
+
]);
|
|
18
|
+
const KIND_VOCAB = new Set<AwarenessIdentity["authorKind"]>([
|
|
19
|
+
"human",
|
|
20
|
+
"agent",
|
|
21
|
+
"system",
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Publishes the local client's `identity` record onto the Awareness
|
|
26
|
+
* channel. Every connected peer sees it through
|
|
27
|
+
* `getPresenceSnapshot`. Safe to call repeatedly — re-writes the
|
|
28
|
+
* field, which Awareness treats as a full replacement.
|
|
29
|
+
*/
|
|
30
|
+
export function setLocalIdentity(
|
|
31
|
+
awareness: Awareness,
|
|
32
|
+
identity: AwarenessIdentity,
|
|
33
|
+
): void {
|
|
34
|
+
awareness.setLocalStateField(IDENTITY_FIELD, validate(identity));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Clears the local `identity` field. Typically called on disconnect or
|
|
39
|
+
* role downgrade so stale presence rows don't linger.
|
|
40
|
+
*/
|
|
41
|
+
export function clearLocalIdentity(awareness: Awareness): void {
|
|
42
|
+
awareness.setLocalStateField(IDENTITY_FIELD, null);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface PresenceSnapshotArgs {
|
|
46
|
+
awareness?: Awareness;
|
|
47
|
+
transportStatus?: TransportStatus;
|
|
48
|
+
queuedLocalEvents?: number;
|
|
49
|
+
/** Used to filter presence by the active story. When omitted, every peer is returned. */
|
|
50
|
+
activeStoryFilter?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Builds a `PresenceSnapshot` from the current Awareness map + a
|
|
55
|
+
* host-supplied transport + queue signal. Unknown or malformed
|
|
56
|
+
* identity entries are silently dropped (fail-closed — never render a
|
|
57
|
+
* partially-trusted peer).
|
|
58
|
+
*/
|
|
59
|
+
export function getPresenceSnapshot(args: PresenceSnapshotArgs = {}): PresenceSnapshot {
|
|
60
|
+
const peers: AwarenessPeer[] = [];
|
|
61
|
+
if (args.awareness) {
|
|
62
|
+
for (const [clientId, state] of args.awareness.getStates()) {
|
|
63
|
+
const raw = (state as Record<string, unknown>)[IDENTITY_FIELD];
|
|
64
|
+
const identity = coerceIdentity(raw);
|
|
65
|
+
if (!identity) continue;
|
|
66
|
+
if (
|
|
67
|
+
args.activeStoryFilter !== undefined &&
|
|
68
|
+
identity.activeStoryId !== undefined &&
|
|
69
|
+
identity.activeStoryId !== args.activeStoryFilter
|
|
70
|
+
) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
peers.push({ clientId, ...identity });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
peers,
|
|
78
|
+
transportStatus: args.transportStatus ?? "offline",
|
|
79
|
+
queuedLocalEvents: args.queuedLocalEvents ?? 0,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface CollabPostureArgs {
|
|
84
|
+
awareness?: Awareness;
|
|
85
|
+
transportStatus?: TransportStatus;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Derives the local user's posture from the Awareness map. Role comes
|
|
90
|
+
* from the local-state `identity`; transport comes from the
|
|
91
|
+
* host-supplied signal; peer count is the presence size minus self.
|
|
92
|
+
*
|
|
93
|
+
* Returns a default "unattached author" posture when no Awareness is
|
|
94
|
+
* wired — this matches the fail-open behaviour of the facet (host
|
|
95
|
+
* without a Y.Doc gets an author posture so single-user docs work).
|
|
96
|
+
*/
|
|
97
|
+
export function getCollabPosture(args: CollabPostureArgs = {}): CollabPosture {
|
|
98
|
+
if (!args.awareness) {
|
|
99
|
+
return { role: "author", transport: "none", peers: 0 };
|
|
100
|
+
}
|
|
101
|
+
const localClientId = args.awareness.clientID;
|
|
102
|
+
const localState = args.awareness.getLocalState() as
|
|
103
|
+
| Record<string, unknown>
|
|
104
|
+
| null;
|
|
105
|
+
const localIdentity = coerceIdentity(localState?.[IDENTITY_FIELD]);
|
|
106
|
+
const role = localIdentity?.role ?? "author";
|
|
107
|
+
|
|
108
|
+
let peers = 0;
|
|
109
|
+
for (const [clientId, state] of args.awareness.getStates()) {
|
|
110
|
+
if (clientId === localClientId) continue;
|
|
111
|
+
const identity = coerceIdentity(
|
|
112
|
+
(state as Record<string, unknown>)[IDENTITY_FIELD],
|
|
113
|
+
);
|
|
114
|
+
if (identity) peers += 1;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
role,
|
|
119
|
+
transport: args.transportStatus === "offline" ? "attached" : "attached",
|
|
120
|
+
peers,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function coerceIdentity(raw: unknown): AwarenessIdentity | undefined {
|
|
125
|
+
if (!raw || typeof raw !== "object") return undefined;
|
|
126
|
+
const candidate = raw as Record<string, unknown>;
|
|
127
|
+
const userId = candidate["userId"];
|
|
128
|
+
const displayName = candidate["displayName"];
|
|
129
|
+
const role = candidate["role"];
|
|
130
|
+
const authorKind = candidate["authorKind"];
|
|
131
|
+
if (
|
|
132
|
+
typeof userId !== "string" ||
|
|
133
|
+
userId === "" ||
|
|
134
|
+
typeof displayName !== "string" ||
|
|
135
|
+
displayName === "" ||
|
|
136
|
+
typeof role !== "string" ||
|
|
137
|
+
!ROLE_VOCAB.has(role as AwarenessIdentity["role"]) ||
|
|
138
|
+
typeof authorKind !== "string" ||
|
|
139
|
+
!KIND_VOCAB.has(authorKind as AwarenessIdentity["authorKind"])
|
|
140
|
+
) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
const identity: AwarenessIdentity = {
|
|
144
|
+
userId,
|
|
145
|
+
displayName,
|
|
146
|
+
role: role as AwarenessIdentity["role"],
|
|
147
|
+
authorKind: authorKind as AwarenessIdentity["authorKind"],
|
|
148
|
+
};
|
|
149
|
+
const collabIdentity = candidate["collabIdentity"];
|
|
150
|
+
if (typeof collabIdentity === "string" && collabIdentity !== "") {
|
|
151
|
+
identity.collabIdentity = collabIdentity;
|
|
152
|
+
}
|
|
153
|
+
const activeStoryId = candidate["activeStoryId"];
|
|
154
|
+
if (typeof activeStoryId === "string" && activeStoryId !== "") {
|
|
155
|
+
identity.activeStoryId = activeStoryId;
|
|
156
|
+
}
|
|
157
|
+
return identity;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function validate(identity: AwarenessIdentity): AwarenessIdentity {
|
|
161
|
+
if (!identity.userId) throw new Error("awareness identity: userId required");
|
|
162
|
+
if (!identity.displayName)
|
|
163
|
+
throw new Error("awareness identity: displayName required");
|
|
164
|
+
if (!ROLE_VOCAB.has(identity.role)) {
|
|
165
|
+
throw new Error(`awareness identity: unknown role ${identity.role}`);
|
|
166
|
+
}
|
|
167
|
+
if (!KIND_VOCAB.has(identity.authorKind)) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`awareness identity: unknown authorKind ${identity.authorKind}`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return identity;
|
|
173
|
+
}
|
|
@@ -83,6 +83,33 @@ export const BROADCAST_COMMAND_TYPES: ReadonlySet<EditorCommand["type"]> = new S
|
|
|
83
83
|
"change.reject",
|
|
84
84
|
"change.accept-all",
|
|
85
85
|
"change.reject-all",
|
|
86
|
+
"workflow.set-overlay",
|
|
87
|
+
"workflow.clear-overlay",
|
|
88
|
+
"workflow.set-metadata-definitions",
|
|
89
|
+
"workflow.clear-metadata-definitions",
|
|
90
|
+
"workflow.set-metadata-entries",
|
|
91
|
+
"workflow.clear-metadata-entries",
|
|
92
|
+
"host-annotation.set-overlay",
|
|
93
|
+
"host-annotation.clear-overlay",
|
|
94
|
+
"formatting.apply",
|
|
95
|
+
"style.set-paragraph",
|
|
96
|
+
"style.set-table",
|
|
97
|
+
"list.toggle",
|
|
98
|
+
"list.indent",
|
|
99
|
+
"list.outdent",
|
|
100
|
+
"list.restart-numbering",
|
|
101
|
+
"list.continue-numbering",
|
|
102
|
+
"table.apply-structure",
|
|
103
|
+
"image.insert",
|
|
104
|
+
"image.set-layout",
|
|
105
|
+
"image.set-frame",
|
|
106
|
+
"section.insert-break",
|
|
107
|
+
"section.delete-break",
|
|
108
|
+
"section.update-layout",
|
|
109
|
+
"section.set-page-numbering",
|
|
110
|
+
"section.set-header-footer-link",
|
|
111
|
+
"content.insert-page-break",
|
|
112
|
+
"content.insert-table",
|
|
86
113
|
]);
|
|
87
114
|
|
|
88
115
|
/**
|