@opengeni/react 0.48.0 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -2
- package/dist/artifacts-document.d.ts +3 -0
- package/dist/artifacts-document.js +17 -0
- package/dist/artifacts-document.js.map +1 -0
- package/dist/artifacts-presentation.d.ts +3 -0
- package/dist/artifacts-presentation.js +17 -0
- package/dist/artifacts-presentation.js.map +1 -0
- package/dist/artifacts-spreadsheet.d.ts +3 -0
- package/dist/artifacts-spreadsheet.js +17 -0
- package/dist/artifacts-spreadsheet.js.map +1 -0
- package/dist/artifacts.d.ts +2 -0
- package/dist/artifacts.js +199 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/{chunk-NJ7M2B6Z.js → chunk-5UQPNLIO.js} +16 -8
- package/dist/{chunk-NJ7M2B6Z.js.map → chunk-5UQPNLIO.js.map} +1 -1
- package/dist/{chunk-LHLYWG5R.js → chunk-6KLX2RH5.js} +461 -38
- package/dist/chunk-6KLX2RH5.js.map +1 -0
- package/dist/chunk-7WCLAEFV.js +2828 -0
- package/dist/chunk-7WCLAEFV.js.map +1 -0
- package/dist/{chunk-I4MPFDRP.js → chunk-B6WPZSTT.js} +104 -104
- package/dist/{chunk-I4MPFDRP.js.map → chunk-B6WPZSTT.js.map} +1 -1
- package/dist/{chunk-EKZH6IDG.js → chunk-C5ZPSSS5.js} +111 -1
- package/dist/chunk-C5ZPSSS5.js.map +1 -0
- package/dist/{chunk-OL6QVXGF.js → chunk-HA46CYQK.js} +152 -8
- package/dist/chunk-HA46CYQK.js.map +1 -0
- package/dist/chunk-HUIKIDM5.js +2148 -0
- package/dist/chunk-HUIKIDM5.js.map +1 -0
- package/dist/{chunk-FWXGSNXT.js → chunk-IGC6A2ZI.js} +585 -34
- package/dist/chunk-IGC6A2ZI.js.map +1 -0
- package/dist/chunk-JVYQRRC4.js +53 -0
- package/dist/chunk-JVYQRRC4.js.map +1 -0
- package/dist/chunk-KCGXPP5V.js +157 -0
- package/dist/chunk-KCGXPP5V.js.map +1 -0
- package/dist/chunk-LLOLUHVG.js +5937 -0
- package/dist/chunk-LLOLUHVG.js.map +1 -0
- package/dist/{chunk-WYA65Y3F.js → chunk-MKPIAGVL.js} +13 -3
- package/dist/chunk-MKPIAGVL.js.map +1 -0
- package/dist/chunk-PEI6RANI.js +3234 -0
- package/dist/chunk-PEI6RANI.js.map +1 -0
- package/dist/chunk-R3AZOAB5.js +933 -0
- package/dist/chunk-R3AZOAB5.js.map +1 -0
- package/dist/{chunk-O4VXOBGF.js → chunk-WSK7G5LE.js} +70 -2
- package/dist/chunk-WSK7G5LE.js.map +1 -0
- package/dist/{chunk-23EJ676W.js → chunk-XBZWOM2K.js} +9 -4
- package/dist/chunk-XBZWOM2K.js.map +1 -0
- package/dist/{chunk-XCNTXWBB.js → chunk-Y7OEDUJ5.js} +856 -338
- package/dist/chunk-Y7OEDUJ5.js.map +1 -0
- package/dist/client.d.ts +10 -2
- package/dist/clipboard.d.ts +1 -0
- package/dist/clipboard.js +7 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/commands/index.d.ts +3 -3
- package/dist/commands/registry.d.ts +1 -1
- package/dist/commands/types.d.ts +1 -1
- package/dist/components/approval-surface.d.ts +2 -2
- package/dist/components/artifacts/artifact-surface.d.ts +18 -0
- package/dist/components/artifacts/document-editor.d.ts +172 -0
- package/dist/components/artifacts/editable-artifact-ui.d.ts +13 -0
- package/dist/components/artifacts/editable-artifact-workbench.d.ts +49 -0
- package/dist/components/artifacts/editable-document.d.ts +24 -0
- package/dist/components/artifacts/editable-presentation.d.ts +24 -0
- package/dist/components/artifacts/editable-spreadsheet.d.ts +32 -0
- package/dist/components/artifacts/index.d.ts +9 -0
- package/dist/components/artifacts/presentation-editor.d.ts +297 -0
- package/dist/components/artifacts/published-html-artifact-frame.d.ts +14 -0
- package/dist/components/artifacts/spreadsheet-canvas.d.ts +120 -0
- package/dist/components/artifacts/spreadsheet-grid.d.ts +113 -0
- package/dist/components/browser-input.d.ts +7 -0
- package/dist/components/browser-viewer.d.ts +30 -0
- package/dist/components/chat-composer.d.ts +8 -8
- package/dist/components/code-editor.d.ts +25 -3
- package/dist/components/command-palette.d.ts +2 -2
- package/dist/components/composer-transcription-control.d.ts +2 -2
- package/dist/components/composer.d.ts +17 -17
- package/dist/components/computer-viewer.d.ts +24 -0
- package/dist/components/copy-button.d.ts +2 -2
- package/dist/components/desktop-viewer.d.ts +50 -22
- package/dist/components/diff-view.d.ts +1 -1
- package/dist/components/enrollment-consent.d.ts +1 -1
- package/dist/components/enrollment-device-flow.d.ts +1 -1
- package/dist/components/file-browser.d.ts +2 -2
- package/dist/components/fleet-tile.d.ts +1 -1
- package/dist/components/generated-video-player.d.ts +10 -0
- package/dist/components/human-input-form.d.ts +1 -1
- package/dist/components/human-input-surface.d.ts +2 -2
- package/dist/components/interaction-intervention-banner.d.ts +11 -0
- package/dist/components/interactive-workbench-viewers.d.ts +2 -0
- package/dist/components/machine-card.d.ts +2 -2
- package/dist/components/machine-dock-bar.d.ts +3 -3
- package/dist/components/machine-health-pill.d.ts +2 -2
- package/dist/components/machine-input-display.d.ts +1 -1
- package/dist/components/machine-metrics.d.ts +2 -2
- package/dist/components/machine-status-pill.d.ts +4 -4
- package/dist/components/machines/health.d.ts +1 -1
- package/dist/components/machines/machine-detail.d.ts +3 -3
- package/dist/components/machines/metric-history-chart.d.ts +1 -1
- package/dist/components/machines/metric-sparkline.d.ts +2 -2
- package/dist/components/machines/series.d.ts +2 -2
- package/dist/components/machines-dashboard.d.ts +2 -2
- package/dist/components/markdown.d.ts +1 -1
- package/dist/components/message-timeline.d.ts +12 -5
- package/dist/components/model-picker.d.ts +2 -2
- package/dist/components/model-policy-picker.d.ts +7 -7
- package/dist/components/pierre-diff.d.ts +1 -1
- package/dist/components/pierre-file.d.ts +1 -1
- package/dist/components/queue-draft-policy.d.ts +1 -1
- package/dist/components/queue-surface-implementation.d.ts +3 -3
- package/dist/components/queue-surface-state.d.ts +4 -4
- package/dist/components/queue-surface.d.ts +5 -5
- package/dist/components/sandbox-files.d.ts +3 -3
- package/dist/components/sandbox-terminal.d.ts +24 -4
- package/dist/components/sandbox-workspace.d.ts +28 -9
- package/dist/components/session-chrome.d.ts +4 -4
- package/dist/components/session-status.d.ts +2 -2
- package/dist/components/timeline-annotation-selection.d.ts +9 -0
- package/dist/components/timeline-annotations-dialog.d.ts +16 -0
- package/dist/components/timeline-annotations.d.ts +13 -0
- package/dist/components/tooltip.d.ts +4 -4
- package/dist/components/user-message-body.d.ts +2 -2
- package/dist/components/workbench-changes.d.ts +2 -2
- package/dist/components/workspace-dock.d.ts +1 -1
- package/dist/composer.d.ts +11 -11
- package/dist/composer.js +7 -7
- package/dist/hooks/internal.d.ts +1 -1
- package/dist/hooks/use-attached-browsers.d.ts +21 -0
- package/dist/hooks/use-auth-runs.d.ts +32 -0
- package/dist/hooks/use-available-models.d.ts +2 -2
- package/dist/hooks/use-billing-usage.d.ts +1 -1
- package/dist/hooks/use-browser-downloads.d.ts +25 -0
- package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
- package/dist/hooks/use-browser-identities.d.ts +30 -0
- package/dist/hooks/use-browser-session.d.ts +29 -0
- package/dist/hooks/use-browser-sessions.d.ts +32 -0
- package/dist/hooks/use-channels.d.ts +26 -0
- package/dist/hooks/use-codex-accounts.d.ts +2 -2
- package/dist/hooks/use-composer.d.ts +36 -9
- package/dist/hooks/use-computer-frame-stream.d.ts +24 -0
- package/dist/hooks/use-computer-session.d.ts +29 -0
- package/dist/hooks/use-computer-sessions.d.ts +28 -0
- package/dist/hooks/use-desktop-stream.d.ts +5 -1
- package/dist/hooks/use-environments.d.ts +1 -1
- package/dist/hooks/use-file-attachments.d.ts +1 -1
- package/dist/hooks/use-goal.d.ts +2 -2
- package/dist/hooks/use-human-input.d.ts +2 -2
- package/dist/hooks/use-interaction-interventions.d.ts +27 -0
- package/dist/hooks/use-interaction-invalidation.d.ts +19 -0
- package/dist/hooks/use-last-started-turn-policy.d.ts +2 -2
- package/dist/hooks/use-machine-chip.d.ts +2 -2
- package/dist/hooks/use-machines.d.ts +4 -4
- package/dist/hooks/use-network-routes.d.ts +25 -0
- package/dist/hooks/use-packs.d.ts +7 -2
- package/dist/hooks/use-rigs.d.ts +1 -1
- package/dist/hooks/use-sandbox-files.d.ts +6 -4
- package/dist/hooks/use-sandbox-git.d.ts +6 -5
- package/dist/hooks/use-sandbox-terminal.d.ts +1 -1
- package/dist/hooks/use-scheduled-tasks.d.ts +1 -1
- package/dist/hooks/use-session-capabilities.d.ts +5 -4
- package/dist/hooks/use-session-control.d.ts +1 -1
- package/dist/hooks/use-session-events.d.ts +2 -2
- package/dist/hooks/use-session-lineage.d.ts +1 -1
- package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
- package/dist/hooks/use-session.d.ts +2 -2
- package/dist/hooks/use-site-auth-connections.d.ts +25 -0
- package/dist/hooks/use-slash-commands.d.ts +1 -1
- package/dist/hooks/use-supergrok-accounts.d.ts +26 -0
- package/dist/hooks/use-terminal-stream.d.ts +11 -2
- package/dist/hooks/use-turn-queue.d.ts +2 -2
- package/dist/hooks/use-video-artifact-playback.d.ts +9 -0
- package/dist/hooks/use-voice-input.d.ts +1 -1
- package/dist/hooks/use-workspace-capture.d.ts +1 -1
- package/dist/hooks/use-workspace-edit.d.ts +1 -1
- package/dist/hooks/use-workspace-sessions.d.ts +1 -1
- package/dist/hooks/use-workspaces.d.ts +1 -1
- package/dist/index.d.ts +162 -155
- package/dist/index.js +1822 -1703
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +33 -0
- package/dist/interaction.js +550 -0
- package/dist/interaction.js.map +1 -0
- package/dist/interactive-workbench-viewers-TOHBXJEE.js +15 -0
- package/dist/interactive-workbench-viewers-TOHBXJEE.js.map +1 -0
- package/dist/lib/host-platform.d.ts +5 -0
- package/dist/lib/relay-wire.d.ts +24 -1
- package/dist/lib/sandbox-liveness.d.ts +9 -0
- package/dist/lib/terminal-capability.d.ts +5 -0
- package/dist/lib/xterm-theme.d.ts +1 -1
- package/dist/machines.d.ts +30 -30
- package/dist/machines.js +2 -2
- package/dist/model-policy.d.ts +1 -1
- package/dist/model-policy.js +1 -1
- package/dist/provider.d.ts +6 -5
- package/dist/{queue-surface-implementation-U5JFG7HT.js → queue-surface-implementation-2KVJTYXH.js} +100 -48
- package/dist/queue-surface-implementation-2KVJTYXH.js.map +1 -0
- package/dist/realtime/dropdown-menu.d.ts +8 -8
- package/dist/realtime/realtime-control.d.ts +8 -8
- package/dist/realtime.d.ts +4 -4
- package/dist/realtime.js +38 -11
- package/dist/realtime.js.map +1 -1
- package/dist/session-context.d.ts +28 -2
- package/dist/session-ui.d.ts +14 -14
- package/dist/session-ui.js +5 -4
- package/dist/session.d.ts +28 -28
- package/dist/session.js +10 -10
- package/dist/timeline/activity-rail.d.ts +5 -3
- package/dist/timeline/disclosure-context.d.ts +1 -1
- package/dist/timeline/fleet-decision-projection.d.ts +1 -1
- package/dist/timeline/fleet-decision-row.d.ts +3 -3
- package/dist/timeline/index.d.ts +19 -19
- package/dist/timeline/parsers.d.ts +4 -1
- package/dist/timeline/projection.d.ts +3 -3
- package/dist/timeline/registry.d.ts +8 -2
- package/dist/timeline/screenshot-lightbox.d.ts +2 -2
- package/dist/timeline/shared.d.ts +21 -10
- package/dist/timeline/tool-diff.d.ts +2 -2
- package/dist/timeline/tool-renderers.d.ts +1 -1
- package/dist/timeline/turn-summary.d.ts +3 -3
- package/dist/timeline/types.d.ts +44 -3
- package/dist/timeline-annotation-selection-3L7LHGG2.js +187 -0
- package/dist/timeline-annotation-selection-3L7LHGG2.js.map +1 -0
- package/dist/timeline-annotations-dialog-U7EVLR75.js +196 -0
- package/dist/timeline-annotations-dialog-U7EVLR75.js.map +1 -0
- package/package.json +36 -7
- package/src/artifacts-document.ts +31 -0
- package/src/artifacts-presentation.ts +36 -0
- package/src/artifacts-spreadsheet.ts +21 -0
- package/src/artifacts.ts +89 -0
- package/src/client.ts +157 -1
- package/src/clipboard.ts +2 -0
- package/src/components/artifacts/artifact-surface.tsx +93 -0
- package/src/components/artifacts/document-editor.tsx +2255 -0
- package/src/components/artifacts/editable-artifact-ui.tsx +89 -0
- package/src/components/artifacts/editable-artifact-workbench.tsx +203 -0
- package/src/components/artifacts/editable-document.tsx +657 -0
- package/src/components/artifacts/editable-presentation.tsx +1020 -0
- package/src/components/artifacts/editable-spreadsheet.tsx +733 -0
- package/src/components/artifacts/index.ts +104 -0
- package/src/components/artifacts/presentation-editor.tsx +3148 -0
- package/src/components/artifacts/published-html-artifact-frame.tsx +38 -0
- package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
- package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
- package/src/components/browser-input.ts +65 -0
- package/src/components/browser-viewer.tsx +2702 -0
- package/src/components/chat-composer.tsx +13 -0
- package/src/components/code-editor.tsx +89 -30
- package/src/components/composer-transcription-control.tsx +10 -6
- package/src/components/computer-viewer.tsx +1636 -0
- package/src/components/desktop-viewer.tsx +215 -74
- package/src/components/generated-video-player.tsx +89 -0
- package/src/components/interaction-intervention-banner.tsx +99 -0
- package/src/components/interactive-workbench-viewers.ts +2 -0
- package/src/components/machine-input-display.ts +3 -0
- package/src/components/machines/machine-detail.tsx +1 -1
- package/src/components/message-timeline.tsx +289 -32
- package/src/components/model-policy-picker.tsx +15 -0
- package/src/components/queue-surface-implementation.tsx +116 -60
- package/src/components/sandbox-files.tsx +2 -2
- package/src/components/sandbox-terminal.tsx +128 -46
- package/src/components/sandbox-workspace.tsx +256 -90
- package/src/components/session-chrome.tsx +53 -13
- package/src/components/timeline-annotation-selection.tsx +221 -0
- package/src/components/timeline-annotations-dialog.tsx +213 -0
- package/src/components/timeline-annotations.tsx +84 -0
- package/src/components/workbench-changes.tsx +188 -31
- package/src/components/workspace-dock.tsx +59 -5
- package/src/hooks/use-attached-browsers.ts +154 -0
- package/src/hooks/use-auth-runs.ts +278 -0
- package/src/hooks/use-browser-downloads.ts +218 -0
- package/src/hooks/use-browser-frame-stream.ts +425 -0
- package/src/hooks/use-browser-identities.ts +266 -0
- package/src/hooks/use-browser-session.ts +543 -0
- package/src/hooks/use-browser-sessions.ts +286 -0
- package/src/hooks/use-channels.ts +99 -0
- package/src/hooks/use-composer.ts +769 -24
- package/src/hooks/use-computer-frame-stream.ts +434 -0
- package/src/hooks/use-computer-session.ts +462 -0
- package/src/hooks/use-computer-sessions.ts +251 -0
- package/src/hooks/use-desktop-stream.ts +39 -7
- package/src/hooks/use-interaction-interventions.ts +242 -0
- package/src/hooks/use-interaction-invalidation.ts +66 -0
- package/src/hooks/use-machine-chip.ts +15 -3
- package/src/hooks/use-machines.ts +33 -3
- package/src/hooks/use-network-routes.ts +214 -0
- package/src/hooks/use-packs.ts +51 -0
- package/src/hooks/use-sandbox-files.ts +98 -55
- package/src/hooks/use-sandbox-git.ts +93 -38
- package/src/hooks/use-sandbox-terminal.ts +3 -2
- package/src/hooks/use-session-capabilities.ts +59 -46
- package/src/hooks/use-site-auth-connections.ts +226 -0
- package/src/hooks/use-supergrok-accounts.ts +81 -0
- package/src/hooks/use-terminal-stream.ts +359 -47
- package/src/hooks/use-video-artifact-playback.ts +28 -0
- package/src/hooks/use-workspace-edit.ts +2 -1
- package/src/index.ts +18 -0
- package/src/interaction.ts +101 -0
- package/src/lib/host-platform.ts +10 -0
- package/src/lib/relay-wire.ts +47 -4
- package/src/lib/sandbox-liveness.ts +11 -0
- package/src/lib/terminal-capability.ts +10 -0
- package/src/model-policy.ts +19 -4
- package/src/provider.tsx +133 -1
- package/src/realtime/realtime-control.tsx +34 -6
- package/src/session-context.ts +156 -1
- package/src/timeline/activity-rail.tsx +25 -3
- package/src/timeline/index.ts +3 -0
- package/src/timeline/parsers.ts +14 -1
- package/src/timeline/projection.ts +269 -14
- package/src/timeline/registry.ts +13 -1
- package/src/timeline/screenshot-lightbox.tsx +19 -9
- package/src/timeline/shared.tsx +56 -8
- package/src/timeline/tool-renderers.tsx +303 -50
- package/src/timeline/types.ts +57 -3
- package/styles/compiled.css +312 -21
- package/styles/index.css +15 -0
- package/styles/responsive.css +13 -1
- package/dist/chunk-23EJ676W.js.map +0 -1
- package/dist/chunk-EKZH6IDG.js.map +0 -1
- package/dist/chunk-FWXGSNXT.js.map +0 -1
- package/dist/chunk-LHLYWG5R.js.map +0 -1
- package/dist/chunk-O4VXOBGF.js.map +0 -1
- package/dist/chunk-OL6QVXGF.js.map +0 -1
- package/dist/chunk-WYA65Y3F.js.map +0 -1
- package/dist/chunk-XCNTXWBB.js.map +0 -1
- package/dist/fleet-decision-projection-SLNZMS42.js +0 -241
- package/dist/fleet-decision-projection-SLNZMS42.js.map +0 -1
- package/dist/queue-surface-implementation-U5JFG7HT.js.map +0 -1
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DOCUMENT_ARTIFACT_COMMAND_VERSION,
|
|
3
|
+
type DocumentArtifactCommand,
|
|
4
|
+
type DocumentArtifactPageGeometryProjection,
|
|
5
|
+
type DocumentArtifactProjection,
|
|
6
|
+
type DocumentArtifactProjectionItem,
|
|
7
|
+
type DocumentArtifactQuery,
|
|
8
|
+
type DocumentArtifactTextStylePatch,
|
|
9
|
+
type EditableArtifactSession,
|
|
10
|
+
} from "@opengeni/sdk/editable-artifacts";
|
|
11
|
+
import { type ReactNode, useCallback, useEffect, useRef, useState } from "react";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
DocumentProjectionArtifactSurface,
|
|
15
|
+
type DocumentCommit,
|
|
16
|
+
type DocumentCommitHandler,
|
|
17
|
+
type DocumentEditorProjection,
|
|
18
|
+
type DocumentProjectionArtifactSurfaceProps,
|
|
19
|
+
type DocumentProjectionPageGeometry,
|
|
20
|
+
} from "./document-editor";
|
|
21
|
+
import { ArtifactSurface } from "./artifact-surface";
|
|
22
|
+
import {
|
|
23
|
+
asEditableArtifactError,
|
|
24
|
+
editableArtifactAccessRevoked,
|
|
25
|
+
editableArtifactProjectionKey,
|
|
26
|
+
editableArtifactStatusLabel,
|
|
27
|
+
EditableArtifactMessage,
|
|
28
|
+
useEditableArtifactView,
|
|
29
|
+
} from "./editable-artifact-ui";
|
|
30
|
+
|
|
31
|
+
const DOCUMENT_PAGE_ITEMS = 512;
|
|
32
|
+
const DOCUMENT_MAX_BLOCKS = 4_096;
|
|
33
|
+
const DOCUMENT_MAX_SECTIONS = 1_024;
|
|
34
|
+
const DOCUMENT_MAX_REVIEW_ITEMS = 4_096;
|
|
35
|
+
const DOCUMENT_MAX_TEXT_UTF16 = 1_000_000;
|
|
36
|
+
const DOCUMENT_MAX_TABLE_CELLS = 100_000;
|
|
37
|
+
const DOCUMENT_MAX_QUERY_PAGES = 64;
|
|
38
|
+
const DOCUMENT_COMPOSITION_ATTEMPTS = 3;
|
|
39
|
+
|
|
40
|
+
export type EditableDocumentArtifactSurfaceProps = Omit<
|
|
41
|
+
DocumentProjectionArtifactSurfaceProps,
|
|
42
|
+
"projection" | "commit" | "readOnly" | "busy" | "subtitle"
|
|
43
|
+
> & {
|
|
44
|
+
session: EditableArtifactSession;
|
|
45
|
+
subtitle?: ReactNode | undefined;
|
|
46
|
+
readOnly?: boolean | undefined;
|
|
47
|
+
/** Called only after the durable SDK command has been accepted locally. */
|
|
48
|
+
onCommit?: DocumentCommitHandler | undefined;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type DocumentProjectionState = {
|
|
52
|
+
session: EditableArtifactSession;
|
|
53
|
+
projection: DocumentEditorProjection | null;
|
|
54
|
+
loading: boolean;
|
|
55
|
+
error: Error | null;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
type DocumentCommandState = {
|
|
59
|
+
session: EditableArtifactSession;
|
|
60
|
+
tail: Promise<void>;
|
|
61
|
+
paragraphText: Map<string, string>;
|
|
62
|
+
localParagraphIds: Set<string>;
|
|
63
|
+
optimisticParagraphIds: Map<string, string>;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Durable document editor over the public SDK session. Canonical state and
|
|
68
|
+
* speculative replay remain Worker-owned; React receives bounded projections.
|
|
69
|
+
*/
|
|
70
|
+
export function EditableDocumentArtifactSurface({
|
|
71
|
+
session,
|
|
72
|
+
title,
|
|
73
|
+
subtitle,
|
|
74
|
+
readOnly = false,
|
|
75
|
+
onCommit,
|
|
76
|
+
onCommandError,
|
|
77
|
+
...surfaceProps
|
|
78
|
+
}: EditableDocumentArtifactSurfaceProps) {
|
|
79
|
+
const view = useEditableArtifactView(session);
|
|
80
|
+
const invalidator = editableArtifactProjectionKey(view);
|
|
81
|
+
const [retryEpoch, setRetryEpoch] = useState(0);
|
|
82
|
+
const [state, setState] = useState<DocumentProjectionState>({
|
|
83
|
+
session,
|
|
84
|
+
projection: null,
|
|
85
|
+
loading: true,
|
|
86
|
+
error: null,
|
|
87
|
+
});
|
|
88
|
+
const loadGeneration = useRef(0);
|
|
89
|
+
const commandStateRef = useRef<DocumentCommandState | null>(null);
|
|
90
|
+
if (commandStateRef.current?.session !== session) {
|
|
91
|
+
commandStateRef.current = {
|
|
92
|
+
session,
|
|
93
|
+
tail: Promise.resolve(),
|
|
94
|
+
paragraphText: new Map(),
|
|
95
|
+
localParagraphIds: new Set(),
|
|
96
|
+
optimisticParagraphIds: new Map(),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const commandState = commandStateRef.current;
|
|
100
|
+
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
const generation = ++loadGeneration.current;
|
|
103
|
+
let cancelled = false;
|
|
104
|
+
setState((current) =>
|
|
105
|
+
current.session === session
|
|
106
|
+
? { ...current, loading: true, error: null }
|
|
107
|
+
: { session, projection: null, loading: true, error: null },
|
|
108
|
+
);
|
|
109
|
+
void composeDocumentEditorProjection(session).then(
|
|
110
|
+
(projection) => {
|
|
111
|
+
if (
|
|
112
|
+
cancelled ||
|
|
113
|
+
generation !== loadGeneration.current ||
|
|
114
|
+
commandStateRef.current !== commandState
|
|
115
|
+
) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const nextParagraphText = paragraphTextById(projection);
|
|
119
|
+
for (const [localId, canonicalId] of commandState.optimisticParagraphIds) {
|
|
120
|
+
if (nextParagraphText.has(canonicalId)) continue;
|
|
121
|
+
const optimisticText =
|
|
122
|
+
commandState.paragraphText.get(canonicalId) ?? commandState.paragraphText.get(localId);
|
|
123
|
+
if (optimisticText !== undefined) nextParagraphText.set(canonicalId, optimisticText);
|
|
124
|
+
}
|
|
125
|
+
commandState.paragraphText = nextParagraphText;
|
|
126
|
+
setState({ session, projection, loading: false, error: null });
|
|
127
|
+
},
|
|
128
|
+
(cause) => {
|
|
129
|
+
if (
|
|
130
|
+
cancelled ||
|
|
131
|
+
generation !== loadGeneration.current ||
|
|
132
|
+
commandStateRef.current !== commandState
|
|
133
|
+
) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
setState({
|
|
137
|
+
session,
|
|
138
|
+
projection: null,
|
|
139
|
+
loading: false,
|
|
140
|
+
error: asEditableArtifactError(cause, "Could not open this document"),
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
return () => {
|
|
145
|
+
cancelled = true;
|
|
146
|
+
};
|
|
147
|
+
}, [commandState, invalidator, retryEpoch, session]);
|
|
148
|
+
|
|
149
|
+
const refresh = useCallback(() => setRetryEpoch((value) => value + 1), []);
|
|
150
|
+
const writable = !readOnly && view.writable;
|
|
151
|
+
const applyCommit = useCallback(
|
|
152
|
+
(commit: DocumentCommit): Promise<void> => {
|
|
153
|
+
const projection = state.session === session ? state.projection : null;
|
|
154
|
+
if (!projection) return Promise.reject(new Error("Document projection is not ready"));
|
|
155
|
+
if (!writable) return Promise.reject(new Error("This document is read only"));
|
|
156
|
+
if (String(commit.revision) !== String(projection.revision)) {
|
|
157
|
+
return Promise.reject(
|
|
158
|
+
new Error("The document changed; retry this edit on the latest version"),
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const execute = async () => {
|
|
163
|
+
if (commandStateRef.current !== commandState) {
|
|
164
|
+
throw new Error("The document session changed before this edit could be saved");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const allocateLocalParagraph = async (localId: string): Promise<string> => {
|
|
168
|
+
const allocated = commandState.optimisticParagraphIds.get(localId);
|
|
169
|
+
if (allocated !== undefined) return allocated;
|
|
170
|
+
if (!commandState.localParagraphIds.has(localId)) {
|
|
171
|
+
throw new Error("The edited paragraph is no longer available");
|
|
172
|
+
}
|
|
173
|
+
const created = await session.createDocumentParagraph();
|
|
174
|
+
if (commandStateRef.current !== commandState) {
|
|
175
|
+
throw new Error("The document session changed before this edit could be saved");
|
|
176
|
+
}
|
|
177
|
+
commandState.optimisticParagraphIds.set(localId, created.paragraphId);
|
|
178
|
+
commandState.paragraphText.set(created.paragraphId, "");
|
|
179
|
+
return created.paragraphId;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
if (commit.kind === "insert-paragraph") {
|
|
183
|
+
commandState.localParagraphIds.add(commit.blockId);
|
|
184
|
+
await allocateLocalParagraph(commit.blockId);
|
|
185
|
+
notifyDocumentCommit(onCommit, onCommandError, commit);
|
|
186
|
+
refresh();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const targetId = commandState.localParagraphIds.has(commit.blockId)
|
|
191
|
+
? await allocateLocalParagraph(commit.blockId)
|
|
192
|
+
: commit.blockId;
|
|
193
|
+
let command: DocumentArtifactCommand | null = null;
|
|
194
|
+
let rollbackText: (() => void) | null = null;
|
|
195
|
+
if (commit.kind === "text") {
|
|
196
|
+
const before = commandState.paragraphText.get(targetId);
|
|
197
|
+
if (before === undefined) {
|
|
198
|
+
throw new Error("The edited paragraph is no longer available");
|
|
199
|
+
}
|
|
200
|
+
const edit = minimalUtf16TextEdit(before, commit.text);
|
|
201
|
+
if (!edit) return;
|
|
202
|
+
commandState.paragraphText.set(targetId, commit.text);
|
|
203
|
+
rollbackText = () => {
|
|
204
|
+
if (commandState.paragraphText.get(targetId) === commit.text) {
|
|
205
|
+
commandState.paragraphText.set(targetId, before);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
command = {
|
|
209
|
+
kind: "paragraph.edit",
|
|
210
|
+
id: targetId,
|
|
211
|
+
range: edit.range,
|
|
212
|
+
replacement: edit.replacement,
|
|
213
|
+
style: null,
|
|
214
|
+
};
|
|
215
|
+
} else if (commit.kind === "format") {
|
|
216
|
+
command = {
|
|
217
|
+
kind: "paragraph.format",
|
|
218
|
+
id: targetId,
|
|
219
|
+
range: commit.range,
|
|
220
|
+
style: documentTextStylePatch(commit.style),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (!command) throw new Error("Unsupported document edit");
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
await session.applyDocumentCommands({
|
|
227
|
+
version: DOCUMENT_ARTIFACT_COMMAND_VERSION,
|
|
228
|
+
commands: [command],
|
|
229
|
+
});
|
|
230
|
+
if (commandStateRef.current !== commandState) return;
|
|
231
|
+
notifyDocumentCommit(onCommit, onCommandError, commit);
|
|
232
|
+
refresh();
|
|
233
|
+
} catch (cause) {
|
|
234
|
+
rollbackText?.();
|
|
235
|
+
throw cause;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// A rejected edit must not poison later user input. Each operation still
|
|
240
|
+
// runs serially, but starts after the prior operation settles.
|
|
241
|
+
const operation = commandState.tail.then(execute, execute);
|
|
242
|
+
commandState.tail = operation;
|
|
243
|
+
void operation.then(
|
|
244
|
+
() => {
|
|
245
|
+
if (commandState.tail === operation) commandState.tail = Promise.resolve();
|
|
246
|
+
},
|
|
247
|
+
() => {
|
|
248
|
+
if (commandState.tail === operation) commandState.tail = Promise.resolve();
|
|
249
|
+
},
|
|
250
|
+
);
|
|
251
|
+
return operation;
|
|
252
|
+
},
|
|
253
|
+
[commandState, onCommandError, onCommit, refresh, session, state, writable],
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
const projection = state.session === session ? state.projection : null;
|
|
257
|
+
const status = editableArtifactStatusLabel(view);
|
|
258
|
+
const accessRevoked = editableArtifactAccessRevoked(view);
|
|
259
|
+
return projection && !accessRevoked ? (
|
|
260
|
+
<DocumentProjectionArtifactSurface
|
|
261
|
+
{...surfaceProps}
|
|
262
|
+
title={title}
|
|
263
|
+
subtitle={
|
|
264
|
+
subtitle ??
|
|
265
|
+
`${projection.blocks.length} block${projection.blocks.length === 1 ? "" : "s"}${writable ? "" : " · Read only"}`
|
|
266
|
+
}
|
|
267
|
+
projection={projection}
|
|
268
|
+
commit={writable ? applyCommit : undefined}
|
|
269
|
+
readOnly={!writable}
|
|
270
|
+
busy={state.loading}
|
|
271
|
+
onCommandError={onCommandError}
|
|
272
|
+
/>
|
|
273
|
+
) : (
|
|
274
|
+
<ArtifactSurface
|
|
275
|
+
modality="document"
|
|
276
|
+
title={title}
|
|
277
|
+
subtitle={subtitle ?? status}
|
|
278
|
+
busy={!accessRevoked && !state.error}
|
|
279
|
+
className={surfaceProps.className}
|
|
280
|
+
>
|
|
281
|
+
<EditableArtifactMessage
|
|
282
|
+
title={
|
|
283
|
+
accessRevoked
|
|
284
|
+
? "Access changed"
|
|
285
|
+
: state.error
|
|
286
|
+
? "Could not open this document"
|
|
287
|
+
: "Opening document"
|
|
288
|
+
}
|
|
289
|
+
detail={accessRevoked ? status : (state.error?.message ?? status)}
|
|
290
|
+
retry={!accessRevoked && state.error ? refresh : undefined}
|
|
291
|
+
/>
|
|
292
|
+
</ArtifactSurface>
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Deterministically composes summary/body/sections/review at one Worker revision. */
|
|
297
|
+
export async function composeDocumentEditorProjection(
|
|
298
|
+
session: EditableArtifactSession,
|
|
299
|
+
): Promise<DocumentEditorProjection> {
|
|
300
|
+
if (session.modality !== "document") throw new Error("Expected a document artifact session");
|
|
301
|
+
let lastRevisionError: Error | null = null;
|
|
302
|
+
for (let attempt = 0; attempt < DOCUMENT_COMPOSITION_ATTEMPTS; attempt += 1) {
|
|
303
|
+
try {
|
|
304
|
+
return await composeDocumentAttempt(session);
|
|
305
|
+
} catch (cause) {
|
|
306
|
+
const error = asEditableArtifactError(cause, "Could not project this document");
|
|
307
|
+
if (!isRevisionDrift(error) || attempt === DOCUMENT_COMPOSITION_ATTEMPTS - 1) throw error;
|
|
308
|
+
lastRevisionError = error;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
throw lastRevisionError ?? new Error("Document changed while it was opening");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function composeDocumentAttempt(
|
|
315
|
+
session: EditableArtifactSession,
|
|
316
|
+
): Promise<DocumentEditorProjection> {
|
|
317
|
+
const summaryPage = await session.queryDocument({ kind: "summary" });
|
|
318
|
+
const summary = summaryItem(summaryPage);
|
|
319
|
+
const revision = summaryPage.revision;
|
|
320
|
+
if (summary.revision !== revision) throw new Error("Document projection revision drifted");
|
|
321
|
+
if (summary.blockCount > DOCUMENT_MAX_BLOCKS) {
|
|
322
|
+
throw new Error(`Document exceeds the interactive limit of ${DOCUMENT_MAX_BLOCKS} blocks`);
|
|
323
|
+
}
|
|
324
|
+
if (summary.sectionCount > DOCUMENT_MAX_SECTIONS) {
|
|
325
|
+
throw new Error(`Document exceeds the interactive limit of ${DOCUMENT_MAX_SECTIONS} sections`);
|
|
326
|
+
}
|
|
327
|
+
if (summary.commentCount + summary.trackedChangeCount > DOCUMENT_MAX_REVIEW_ITEMS) {
|
|
328
|
+
throw new Error(
|
|
329
|
+
`Document exceeds the interactive limit of ${DOCUMENT_MAX_REVIEW_ITEMS} review items`,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const body = await collectDocumentPages(
|
|
334
|
+
session,
|
|
335
|
+
"body",
|
|
336
|
+
revision,
|
|
337
|
+
DOCUMENT_MAX_BLOCKS,
|
|
338
|
+
DOCUMENT_MAX_TEXT_UTF16,
|
|
339
|
+
DOCUMENT_MAX_TABLE_CELLS,
|
|
340
|
+
(cursor, limits) => ({ kind: "body", startBlock: cursor, limits }),
|
|
341
|
+
);
|
|
342
|
+
const sections = await collectDocumentPages(
|
|
343
|
+
session,
|
|
344
|
+
"sections",
|
|
345
|
+
revision,
|
|
346
|
+
DOCUMENT_MAX_SECTIONS,
|
|
347
|
+
DOCUMENT_MAX_TEXT_UTF16 - body.text,
|
|
348
|
+
DOCUMENT_MAX_TABLE_CELLS - body.cells,
|
|
349
|
+
(cursor, limits) => ({ kind: "sections", startSection: cursor, limits }),
|
|
350
|
+
);
|
|
351
|
+
const review = await collectDocumentPages(
|
|
352
|
+
session,
|
|
353
|
+
"review",
|
|
354
|
+
revision,
|
|
355
|
+
DOCUMENT_MAX_REVIEW_ITEMS,
|
|
356
|
+
DOCUMENT_MAX_TEXT_UTF16 - body.text - sections.text,
|
|
357
|
+
DOCUMENT_MAX_TABLE_CELLS - body.cells - sections.cells,
|
|
358
|
+
(cursor, limits) => ({ kind: "review", startItem: cursor, limits }),
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
if (body.items.length !== summary.blockCount) {
|
|
362
|
+
throw new Error("Document body changed while it was opening");
|
|
363
|
+
}
|
|
364
|
+
if (sections.items.length !== summary.sectionCount) {
|
|
365
|
+
throw new Error("Document sections changed while they were opening");
|
|
366
|
+
}
|
|
367
|
+
if (review.items.length !== summary.commentCount + summary.trackedChangeCount) {
|
|
368
|
+
throw new Error("Document review state changed while it was opening");
|
|
369
|
+
}
|
|
370
|
+
const totalText = body.text + sections.text + review.text;
|
|
371
|
+
const totalCells = body.cells + sections.cells + review.cells;
|
|
372
|
+
if (totalText > DOCUMENT_MAX_TEXT_UTF16) {
|
|
373
|
+
throw new Error(
|
|
374
|
+
`Document exceeds the interactive limit of ${DOCUMENT_MAX_TEXT_UTF16} UTF-16 text units`,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
if (totalCells > DOCUMENT_MAX_TABLE_CELLS) {
|
|
378
|
+
throw new Error(
|
|
379
|
+
`Document exceeds the interactive limit of ${DOCUMENT_MAX_TABLE_CELLS} table cells`,
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return Object.freeze({
|
|
384
|
+
revision: revision.toString(),
|
|
385
|
+
page: projectDocumentPage(summary.page),
|
|
386
|
+
blocks: Object.freeze(body.items.map(projectDocumentBlock)),
|
|
387
|
+
sections: Object.freeze(sections.items.map(projectDocumentSection)),
|
|
388
|
+
comments: Object.freeze(
|
|
389
|
+
review.items.filter(isProjectionKind("comment")).map((item) => ({
|
|
390
|
+
id: item.id,
|
|
391
|
+
blockId: item.paragraphId,
|
|
392
|
+
start: item.range.start,
|
|
393
|
+
end: item.range.end,
|
|
394
|
+
resolved: item.resolved,
|
|
395
|
+
replies: item.replies.map((reply) => ({ ...reply })),
|
|
396
|
+
})),
|
|
397
|
+
),
|
|
398
|
+
changes: Object.freeze(
|
|
399
|
+
review.items.filter(isProjectionKind("tracked-change")).map((item) => ({
|
|
400
|
+
id: item.id,
|
|
401
|
+
blockId: item.paragraphId,
|
|
402
|
+
kind: item.changeKind,
|
|
403
|
+
start: item.range.start,
|
|
404
|
+
end: item.range.end,
|
|
405
|
+
author: item.author,
|
|
406
|
+
createdAt: item.createdAt,
|
|
407
|
+
})),
|
|
408
|
+
),
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
type DocumentPageCollection = {
|
|
413
|
+
items: DocumentArtifactProjectionItem[];
|
|
414
|
+
text: number;
|
|
415
|
+
cells: number;
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
async function collectDocumentPages(
|
|
419
|
+
session: EditableArtifactSession,
|
|
420
|
+
label: "body" | "sections" | "review",
|
|
421
|
+
revision: bigint,
|
|
422
|
+
maxItems: number,
|
|
423
|
+
maxText: number,
|
|
424
|
+
maxCells: number,
|
|
425
|
+
query: (
|
|
426
|
+
cursor: number,
|
|
427
|
+
limits: { maxItems: number; maxTextUtf16: number; maxTableCells: number },
|
|
428
|
+
) => DocumentArtifactQuery,
|
|
429
|
+
): Promise<DocumentPageCollection> {
|
|
430
|
+
const output: DocumentPageCollection = { items: [], text: 0, cells: 0 };
|
|
431
|
+
let cursor = 0;
|
|
432
|
+
for (let pageIndex = 0; pageIndex < DOCUMENT_MAX_QUERY_PAGES; pageIndex += 1) {
|
|
433
|
+
if (output.items.length >= maxItems && cursor > 0) {
|
|
434
|
+
throw new Error(`Document ${label} exceeds its interactive item limit`);
|
|
435
|
+
}
|
|
436
|
+
const page = await session.queryDocument(
|
|
437
|
+
query(cursor, {
|
|
438
|
+
maxItems: Math.min(DOCUMENT_PAGE_ITEMS, Math.max(1, maxItems - output.items.length)),
|
|
439
|
+
maxTextUtf16: Math.max(1, maxText - output.text),
|
|
440
|
+
maxTableCells: Math.max(1, maxCells - output.cells),
|
|
441
|
+
}),
|
|
442
|
+
);
|
|
443
|
+
assertDocumentRevision(page, revision);
|
|
444
|
+
assertDocumentPageKinds(label, page.items);
|
|
445
|
+
if (output.items.length + page.items.length > maxItems) {
|
|
446
|
+
throw new Error(`Document ${label} exceeds its interactive item limit`);
|
|
447
|
+
}
|
|
448
|
+
output.items.push(...page.items);
|
|
449
|
+
output.text += page.projectedTextUtf16;
|
|
450
|
+
output.cells += page.projectedTableCells;
|
|
451
|
+
if (output.text > maxText || output.cells > maxCells) {
|
|
452
|
+
throw new Error(`Document ${label} exceeds its interactive content limit`);
|
|
453
|
+
}
|
|
454
|
+
if (page.nextCursor === null) return output;
|
|
455
|
+
if (page.nextCursor <= cursor || page.items.length === 0) {
|
|
456
|
+
throw new Error(`Document ${label} pagination did not advance`);
|
|
457
|
+
}
|
|
458
|
+
cursor = page.nextCursor;
|
|
459
|
+
}
|
|
460
|
+
throw new Error(`Document ${label} exceeds ${DOCUMENT_MAX_QUERY_PAGES} query pages`);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function summaryItem(
|
|
464
|
+
projection: DocumentArtifactProjection,
|
|
465
|
+
): Extract<DocumentArtifactProjectionItem, { kind: "summary" }> {
|
|
466
|
+
if (
|
|
467
|
+
projection.items.length !== 1 ||
|
|
468
|
+
projection.items[0]?.kind !== "summary" ||
|
|
469
|
+
projection.nextCursor !== null ||
|
|
470
|
+
projection.truncated
|
|
471
|
+
) {
|
|
472
|
+
throw new Error("Document summary projection is malformed");
|
|
473
|
+
}
|
|
474
|
+
return projection.items[0];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function assertDocumentRevision(page: DocumentArtifactProjection, revision: bigint): void {
|
|
478
|
+
if (page.revision !== revision) throw new Error("Document projection revision drifted");
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function assertDocumentPageKinds(
|
|
482
|
+
label: "body" | "sections" | "review",
|
|
483
|
+
items: readonly DocumentArtifactProjectionItem[],
|
|
484
|
+
): void {
|
|
485
|
+
const valid =
|
|
486
|
+
label === "body"
|
|
487
|
+
? new Set(["paragraph", "table", "page-break"])
|
|
488
|
+
: label === "sections"
|
|
489
|
+
? new Set(["section"])
|
|
490
|
+
: new Set(["comment", "tracked-change"]);
|
|
491
|
+
if (items.some((item) => !valid.has(item.kind))) {
|
|
492
|
+
throw new Error(`Document ${label} projection contains an unexpected item`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function projectDocumentBlock(
|
|
497
|
+
item: DocumentArtifactProjectionItem,
|
|
498
|
+
): DocumentEditorProjection["blocks"][number] {
|
|
499
|
+
if (item.kind === "paragraph") {
|
|
500
|
+
const { headingLevel, list, ...paragraphStyle } = item.style;
|
|
501
|
+
return {
|
|
502
|
+
kind: "paragraph",
|
|
503
|
+
id: item.id,
|
|
504
|
+
runs: item.runs.map((run) => ({ text: run.text, style: { ...run.style } })),
|
|
505
|
+
style: {
|
|
506
|
+
...paragraphStyle,
|
|
507
|
+
...(headingLevel === undefined ? {} : { headingLevel: documentHeadingLevel(headingLevel) }),
|
|
508
|
+
...(list
|
|
509
|
+
? {
|
|
510
|
+
list: {
|
|
511
|
+
kind: list.kind,
|
|
512
|
+
...(list.level === null ? {} : { level: list.level }),
|
|
513
|
+
...(list.instanceId === null ? {} : { instanceId: list.instanceId }),
|
|
514
|
+
},
|
|
515
|
+
}
|
|
516
|
+
: {}),
|
|
517
|
+
},
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
if (item.kind === "table") {
|
|
521
|
+
return {
|
|
522
|
+
kind: "table",
|
|
523
|
+
id: item.id,
|
|
524
|
+
rows: item.rows.map((row) =>
|
|
525
|
+
row.map((cell) => cell.map((run) => ({ text: run.text, style: { ...run.style } }))),
|
|
526
|
+
),
|
|
527
|
+
style: { ...item.style },
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
if (item.kind === "page-break") return { kind: "pageBreak", id: item.id };
|
|
531
|
+
throw new Error("Document body projection contains an unexpected item");
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function documentHeadingLevel(value: number): 1 | 2 | 3 | 4 | 5 | 6 {
|
|
535
|
+
if (value < 1 || value > 6 || !Number.isInteger(value)) {
|
|
536
|
+
throw new Error("Document projection contains an invalid heading level");
|
|
537
|
+
}
|
|
538
|
+
return value as 1 | 2 | 3 | 4 | 5 | 6;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function projectDocumentSection(item: DocumentArtifactProjectionItem) {
|
|
542
|
+
if (item.kind !== "section") {
|
|
543
|
+
throw new Error("Document section projection contains an unexpected item");
|
|
544
|
+
}
|
|
545
|
+
return {
|
|
546
|
+
id: item.id,
|
|
547
|
+
startBlockIndex: item.startBlockIndex,
|
|
548
|
+
page: projectDocumentPage(item.page),
|
|
549
|
+
titlePage: item.titlePage,
|
|
550
|
+
headerBlockCounts: item.headerBlockCounts,
|
|
551
|
+
footerBlockCounts: item.footerBlockCounts,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function projectDocumentPage(
|
|
556
|
+
page: DocumentArtifactPageGeometryProjection,
|
|
557
|
+
): DocumentProjectionPageGeometry {
|
|
558
|
+
const points = (value: bigint) => Number(value) / 1_000;
|
|
559
|
+
return {
|
|
560
|
+
widthPt: points(page.widthMillipoints),
|
|
561
|
+
heightPt: points(page.heightMillipoints),
|
|
562
|
+
marginTopPt: points(page.marginTopMillipoints),
|
|
563
|
+
marginRightPt: points(page.marginRightMillipoints),
|
|
564
|
+
marginBottomPt: points(page.marginBottomMillipoints),
|
|
565
|
+
marginLeftPt: points(page.marginLeftMillipoints),
|
|
566
|
+
headerPt: points(page.headerMillipoints),
|
|
567
|
+
footerPt: points(page.footerMillipoints),
|
|
568
|
+
gutterPt: points(page.gutterMillipoints),
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function isProjectionKind<K extends DocumentArtifactProjectionItem["kind"]>(kind: K) {
|
|
573
|
+
return (
|
|
574
|
+
item: DocumentArtifactProjectionItem,
|
|
575
|
+
): item is Extract<DocumentArtifactProjectionItem, { kind: K }> => item.kind === kind;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function paragraphTextById(projection: DocumentEditorProjection): Map<string, string> {
|
|
579
|
+
return new Map(
|
|
580
|
+
projection.blocks
|
|
581
|
+
.filter((block) => block.kind === "paragraph")
|
|
582
|
+
.map((paragraph) => [paragraph.id, paragraph.runs.map((run) => run.text).join("")] as const),
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export function minimalUtf16TextEdit(
|
|
587
|
+
before: string,
|
|
588
|
+
after: string,
|
|
589
|
+
): { range: { start: number; end: number }; replacement: string } | null {
|
|
590
|
+
if (before === after) return null;
|
|
591
|
+
let start = 0;
|
|
592
|
+
const maximumStart = Math.min(before.length, after.length);
|
|
593
|
+
while (start < maximumStart && before.charCodeAt(start) === after.charCodeAt(start)) start += 1;
|
|
594
|
+
while (start > 0 && (splitsSurrogate(before, start) || splitsSurrogate(after, start))) start -= 1;
|
|
595
|
+
|
|
596
|
+
let suffix = 0;
|
|
597
|
+
while (
|
|
598
|
+
suffix < before.length - start &&
|
|
599
|
+
suffix < after.length - start &&
|
|
600
|
+
before.charCodeAt(before.length - suffix - 1) === after.charCodeAt(after.length - suffix - 1)
|
|
601
|
+
) {
|
|
602
|
+
suffix += 1;
|
|
603
|
+
}
|
|
604
|
+
while (
|
|
605
|
+
suffix > 0 &&
|
|
606
|
+
(splitsSurrogate(before, before.length - suffix) ||
|
|
607
|
+
splitsSurrogate(after, after.length - suffix))
|
|
608
|
+
) {
|
|
609
|
+
suffix -= 1;
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
range: { start, end: before.length - suffix },
|
|
613
|
+
replacement: after.slice(start, after.length - suffix),
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function splitsSurrogate(value: string, index: number): boolean {
|
|
618
|
+
if (index <= 0 || index >= value.length) return false;
|
|
619
|
+
const left = value.charCodeAt(index - 1);
|
|
620
|
+
const right = value.charCodeAt(index);
|
|
621
|
+
return left >= 0xd800 && left <= 0xdbff && right >= 0xdc00 && right <= 0xdfff;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function documentTextStylePatch(
|
|
625
|
+
style: Extract<DocumentCommit, { kind: "format" }>["style"],
|
|
626
|
+
): DocumentArtifactTextStylePatch {
|
|
627
|
+
return {
|
|
628
|
+
...(style.fontFamily === undefined ? {} : { fontFamily: style.fontFamily }),
|
|
629
|
+
...(style.fontSizePt === undefined ? {} : { fontSizePt: style.fontSizePt }),
|
|
630
|
+
...(style.color === undefined ? {} : { color: style.color }),
|
|
631
|
+
...(style.bold === undefined ? {} : { bold: style.bold }),
|
|
632
|
+
...(style.italic === undefined ? {} : { italic: style.italic }),
|
|
633
|
+
...(style.underline === undefined ? {} : { underline: style.underline }),
|
|
634
|
+
...(style.strike === undefined ? {} : { strike: style.strike }),
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function isRevisionDrift(error: Error): boolean {
|
|
639
|
+
return error.message.includes("changed while") || error.message.includes("revision drifted");
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function notifyDocumentCommit(
|
|
643
|
+
observer: DocumentCommitHandler | undefined,
|
|
644
|
+
onError: ((error: Error) => void) | undefined,
|
|
645
|
+
commit: DocumentCommit,
|
|
646
|
+
): void {
|
|
647
|
+
try {
|
|
648
|
+
const result = observer?.(commit);
|
|
649
|
+
if (result && typeof result.then === "function") {
|
|
650
|
+
void Promise.resolve(result).catch((cause) =>
|
|
651
|
+
onError?.(asEditableArtifactError(cause, "Document commit observer failed")),
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
} catch (cause) {
|
|
655
|
+
onError?.(asEditableArtifactError(cause, "Document commit observer failed"));
|
|
656
|
+
}
|
|
657
|
+
}
|