@storystick/react 0.0.0-stage → 0.2.0-rc.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/LICENSE +112 -0
- package/NOTICE +17 -0
- package/PUBLIC-API.md +36 -0
- package/README.md +226 -2
- package/dist/.source-hash +1 -0
- package/dist/file.d.ts +18 -0
- package/dist/file.js +44 -0
- package/dist/hydrate-slice.d.ts +3 -0
- package/dist/hydrate-slice.js +1 -0
- package/dist/image-component.d.ts +26 -0
- package/dist/image-component.js +79 -0
- package/dist/image-element.d.ts +6 -0
- package/dist/image-element.js +68 -0
- package/dist/image.d.ts +3 -0
- package/dist/image.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/link-adapter.d.ts +2 -0
- package/dist/link-adapter.js +2 -0
- package/dist/link-component.d.ts +53 -0
- package/dist/link-component.js +97 -0
- package/dist/link.d.ts +3 -0
- package/dist/link.js +3 -0
- package/dist/preview-generation.d.ts +21 -0
- package/dist/preview-generation.js +134 -0
- package/dist/preview-host/preview-assistant-continuation.d.ts +9 -0
- package/dist/preview-host/preview-assistant-continuation.js +39 -0
- package/dist/preview-host/preview-authoring-client.d.ts +12 -0
- package/dist/preview-host/preview-authoring-client.js +100 -0
- package/dist/preview-host/preview-editor-continuation.d.ts +13 -0
- package/dist/preview-host/preview-editor-continuation.js +68 -0
- package/dist/preview-host/preview-standalone-controls.d.ts +18 -0
- package/dist/preview-host/preview-standalone-controls.js +518 -0
- package/dist/preview-host/preview-studio-destination.d.ts +20 -0
- package/dist/preview-host/preview-studio-destination.js +55 -0
- package/dist/preview-host/use-standalone-presentation.d.ts +15 -0
- package/dist/preview-host/use-standalone-presentation.js +223 -0
- package/dist/preview-icons.d.ts +10 -0
- package/dist/preview-icons.js +8 -0
- package/dist/preview-interaction-control.d.ts +6 -0
- package/dist/preview-interaction-control.js +39 -0
- package/dist/preview-panel-state.d.ts +15 -0
- package/dist/preview-panel-state.js +28 -0
- package/dist/preview-provenance-publisher-model.d.ts +5 -0
- package/dist/preview-provenance-publisher-model.js +9 -0
- package/dist/preview-provenance-publisher.d.ts +15 -0
- package/dist/preview-provenance-publisher.js +68 -0
- package/dist/preview-provenance.d.ts +6 -0
- package/dist/preview-provenance.js +4 -0
- package/dist/preview-scope.d.ts +128 -0
- package/dist/preview-scope.js +161 -0
- package/dist/preview-slice-scope.d.ts +32 -0
- package/dist/preview-slice-scope.js +33 -0
- package/dist/preview-text-props.d.ts +18 -0
- package/dist/preview-text-props.js +13 -0
- package/dist/preview-toolbar-layout.d.ts +42 -0
- package/dist/preview-toolbar-layout.js +139 -0
- package/dist/preview-toolbar-panel.d.ts +13 -0
- package/dist/preview-toolbar-panel.js +6 -0
- package/dist/preview-toolbar-resize-handles.d.ts +9 -0
- package/dist/preview-toolbar-resize-handles.js +14 -0
- package/dist/preview-toolbar-shell.d.ts +18 -0
- package/dist/preview-toolbar-shell.js +138 -0
- package/dist/public-rich-text.d.ts +15 -0
- package/dist/public-rich-text.js +11 -0
- package/dist/public-slice-zone.d.ts +7 -0
- package/dist/public-slice-zone.js +6 -0
- package/dist/query-cache.d.ts +14 -0
- package/dist/query-cache.js +289 -0
- package/dist/query-preview-loader.d.ts +9 -0
- package/dist/query-preview-loader.js +28 -0
- package/dist/query-preview-runtime.d.ts +3 -0
- package/dist/query-preview-runtime.js +3 -0
- package/dist/rendering.d.ts +29 -0
- package/dist/rendering.js +17 -0
- package/dist/resize-edge.d.ts +8 -0
- package/dist/resize-edge.js +33 -0
- package/dist/rich-text.d.ts +150 -0
- package/dist/rich-text.js +618 -0
- package/dist/slice-render-failure.d.ts +8 -0
- package/dist/slice-render-failure.js +46 -0
- package/dist/slice-zone.d.ts +82 -0
- package/dist/slice-zone.js +273 -0
- package/dist/slice.d.ts +16 -0
- package/dist/slice.js +46 -0
- package/dist/storystick-mark.d.ts +3 -0
- package/dist/storystick-mark.js +5 -0
- package/dist/use-generated-query.d.ts +37 -0
- package/dist/use-generated-query.js +308 -0
- package/dist/use-momentary-preview-navigate-mode.d.ts +11 -0
- package/dist/use-momentary-preview-navigate-mode.js +158 -0
- package/dist/use-preview-perspective-intent.d.ts +53 -0
- package/dist/use-preview-perspective-intent.js +227 -0
- package/dist/use-preview-pointer-drag.d.ts +16 -0
- package/dist/use-preview-pointer-drag.js +98 -0
- package/dist/use-preview-toolbar-layout.d.ts +36 -0
- package/dist/use-preview-toolbar-layout.js +141 -0
- package/dist/use-render-mode-debounce.d.ts +31 -0
- package/dist/use-render-mode-debounce.js +143 -0
- package/dist/video.d.ts +21 -0
- package/dist/video.js +63 -0
- package/package.json +146 -3
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useMomentaryPreviewInteractionOverride } from '@storystick/react/internal/use-momentary-preview-navigate-mode';
|
|
4
|
+
import { PreviewPanelAction, PreviewPanelState } from '@storystick/react/internal/preview-panel-state';
|
|
5
|
+
import { useStandalonePresentation } from './use-standalone-presentation.js';
|
|
6
|
+
import { PreviewInteractionControl } from '@storystick/react/internal/preview-interaction-control';
|
|
7
|
+
import { PreviewToolbarShell, PreviewEditIcon as EditIcon, PreviewAssistantIcon as ChatIcon, PreviewStudioIcon as StudioIcon, } from '@storystick/react/internal/preview-toolbar-shell';
|
|
8
|
+
import { readStandaloneStudioUrl, readStandaloneInteractionMode, rememberStandaloneInteractionMode, } from '@storystick/preview/identity/browser-context';
|
|
9
|
+
import { readStandaloneOverlaySnapshot, revealStandaloneSource, revealStandaloneSlice, setStandaloneOverlayMode, setStandaloneOverlaySuspended, subscribeStandaloneOverlayIntent, subscribeStandaloneOverlaySnapshot, } from '@storystick/preview/runtime/standalone-overlays';
|
|
10
|
+
import { overlayEditorTarget, overlayEditorLocale, } from '@storystick/preview/provenance';
|
|
11
|
+
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from 'react';
|
|
12
|
+
import { clearPreviewEditorNavigation, readPreviewEditorNavigation, rememberPreviewEditorNavigation, } from './preview-editor-continuation.js';
|
|
13
|
+
import { previewPerspectivesEqual } from '@storystick/preview/runtime/preview-perspective-alignment';
|
|
14
|
+
import { emptyPrimaryPagesSnapshot, previewCurrentPrimaryPage, previewStudioDestination, readPrimaryPagesSnapshot, subscribePrimaryPages, } from './preview-studio-destination.js';
|
|
15
|
+
import { previewRuntime } from '@storystick/preview/runtime/store';
|
|
16
|
+
import { STORYSTICK_PREVIEW_AUTHORING_ROUTE_PATH, storystickRouteForBasePath, } from '@storystick/preview/routes';
|
|
17
|
+
import { authoringRuntimeUrl, loadPreviewAuthoringRuntime, requestPreviewAuthoringAdmission, } from './preview-authoring-client.js';
|
|
18
|
+
import { readPreviewAssistantNavigation, rememberPreviewAssistantNavigation } from './preview-assistant-continuation.js';
|
|
19
|
+
const EMPTY_OVERLAYS = Object.freeze({
|
|
20
|
+
mode: 'browse',
|
|
21
|
+
available: false,
|
|
22
|
+
generation: null,
|
|
23
|
+
rejections: [],
|
|
24
|
+
});
|
|
25
|
+
/** Minimal host: one sleeping bubble, one compact menu and one shared editor. */
|
|
26
|
+
export function PreviewStandaloneControls({ state, projectId, recovery, basePath = '', pathname, }) {
|
|
27
|
+
const presentation = useStandalonePresentation(basePath);
|
|
28
|
+
const host = useRef(null);
|
|
29
|
+
const editorContainer = useRef(null);
|
|
30
|
+
const [headerLeading, setHeaderLeading] = useState(null);
|
|
31
|
+
const [headerTrailing, setHeaderTrailing] = useState(null);
|
|
32
|
+
const [contextContainer, setContextContainer] = useState(null);
|
|
33
|
+
const [interactionMode, setInteractionMode] = useState(() => readStandaloneInteractionMode(projectId, state.sessionId));
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
setInteractionMode(readStandaloneInteractionMode(projectId, state.sessionId));
|
|
36
|
+
}, [projectId, state.sessionId]);
|
|
37
|
+
const chooseInteractionMode = (mode) => {
|
|
38
|
+
setInteractionMode(mode);
|
|
39
|
+
if (projectId && state.sessionId) {
|
|
40
|
+
rememberStandaloneInteractionMode({ projectId, sessionId: state.sessionId }, mode);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const isLocalPreviewEvent = useCallback((event) => !event.composedPath().includes(host.current), []);
|
|
44
|
+
const momentaryMode = useMomentaryPreviewInteractionOverride({
|
|
45
|
+
enabled: state.state === 'authenticated',
|
|
46
|
+
persistentMode: interactionMode,
|
|
47
|
+
isLocalPreviewEvent,
|
|
48
|
+
});
|
|
49
|
+
const effectiveMode = momentaryMode ?? interactionMode;
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
setStandaloneOverlayMode(effectiveMode);
|
|
52
|
+
}, [effectiveMode]);
|
|
53
|
+
const runtimeHandle = useRef(null);
|
|
54
|
+
const runtimeOwner = useRef(null);
|
|
55
|
+
const admission = useRef(null);
|
|
56
|
+
const targetRef = useRef(null);
|
|
57
|
+
const [continuation, setContinuation] = useState(() => readPreviewEditorNavigation(projectId, state.sessionId));
|
|
58
|
+
const navigationHandoff = useRef(continuation);
|
|
59
|
+
const mounted = useRef(true);
|
|
60
|
+
const [root, setRoot] = useState(null);
|
|
61
|
+
const [assistantNavigation] = useState(() => readPreviewAssistantNavigation(projectId, state.sessionId));
|
|
62
|
+
const assistantThreadId = useRef(assistantNavigation?.threadId ?? null);
|
|
63
|
+
const activeSurface = useRef(null);
|
|
64
|
+
const [surface, setSurface] = useState(continuation ? 'editor' : assistantNavigation?.open ? 'assistant' : null);
|
|
65
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
66
|
+
activeSurface.current = surface;
|
|
67
|
+
const [committedTarget, setCommittedTarget] = useState(null);
|
|
68
|
+
const rememberAssistant = useCallback((open) => {
|
|
69
|
+
if (projectId && state.sessionId)
|
|
70
|
+
rememberPreviewAssistantNavigation({ projectId, sessionId: state.sessionId, threadId: assistantThreadId.current, open });
|
|
71
|
+
}, [projectId, state.sessionId]);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
runtimeHandle.current?.setSurface(surface === 'editor' || surface === 'assistant' ? surface : null);
|
|
74
|
+
rememberAssistant(surface === 'assistant');
|
|
75
|
+
}, [surface, rememberAssistant]);
|
|
76
|
+
const [editorStatus, setEditorStatus] = useState('idle');
|
|
77
|
+
const [targetRequest, setTargetRequest] = useState(null);
|
|
78
|
+
const target = targetRequest?.target ?? null;
|
|
79
|
+
const [connectAttempt, setConnectAttempt] = useState(0);
|
|
80
|
+
const [error, setError] = useState(null);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const next = readPreviewEditorNavigation(projectId, state.sessionId);
|
|
83
|
+
if (!next || runtimeHandle.current)
|
|
84
|
+
return;
|
|
85
|
+
navigationHandoff.current = next;
|
|
86
|
+
setContinuation(next);
|
|
87
|
+
setSurface('editor');
|
|
88
|
+
}, [projectId, state.sessionId]);
|
|
89
|
+
const evidence = useSyncExternalStore(subscribePrimaryPages, readPrimaryPagesSnapshot, emptyPrimaryPagesSnapshot);
|
|
90
|
+
const overlays = useSyncExternalStore(subscribeStandaloneOverlaySnapshot, readStandaloneOverlaySnapshot, () => EMPTY_OVERLAYS);
|
|
91
|
+
const browserPathname = typeof window === 'undefined' ? (pathname ?? '/') : window.location.pathname;
|
|
92
|
+
const destination = previewStudioDestination({
|
|
93
|
+
studioUrl: state.standaloneStudioUrl ??
|
|
94
|
+
readStandaloneStudioUrl(projectId, state.sessionId),
|
|
95
|
+
projectId,
|
|
96
|
+
sessionId: state.sessionId,
|
|
97
|
+
context: state.readContext,
|
|
98
|
+
authenticated: state.state === 'authenticated' && state.readContextStatus === 'active',
|
|
99
|
+
browserPathname,
|
|
100
|
+
basePath,
|
|
101
|
+
evidence,
|
|
102
|
+
});
|
|
103
|
+
const page = state.readContext === null
|
|
104
|
+
? null
|
|
105
|
+
: previewCurrentPrimaryPage(evidence, state.sessionId, state.readContext, browserPathname, basePath);
|
|
106
|
+
const currentPageTarget = useMemo(() => page && state.readContext
|
|
107
|
+
? targetForPage(page, state.readContext.perspective)
|
|
108
|
+
: null, [page, state.readContext]);
|
|
109
|
+
const studioUrl = state.standaloneStudioUrl ?? readStandaloneStudioUrl(projectId, state.sessionId);
|
|
110
|
+
const runtimeSource = studioUrl
|
|
111
|
+
? authoringRuntimeUrl(studioUrl)
|
|
112
|
+
: null;
|
|
113
|
+
const authoringUrl = storystickRouteForBasePath(basePath, STORYSTICK_PREVIEW_AUTHORING_ROUTE_PATH);
|
|
114
|
+
const perspective = state.readContext?.perspective;
|
|
115
|
+
const rendered = state.readContext !== null &&
|
|
116
|
+
state.renderedReadContext?.contextId === state.readContext.contextId &&
|
|
117
|
+
state.renderedReadContext?.revision === state.readContext.revision;
|
|
118
|
+
const connected = state.state === 'authenticated' && state.readContextStatus === 'active';
|
|
119
|
+
const connectionNotice = state.state === 'error' || recovery?.error
|
|
120
|
+
? 'Preview unavailable'
|
|
121
|
+
: !connected
|
|
122
|
+
? 'Connecting to Preview…'
|
|
123
|
+
: null;
|
|
124
|
+
const notice = state.readContextNotice === 'release-published'
|
|
125
|
+
? 'This release was published. Preview now follows Main, including its drafts.'
|
|
126
|
+
: state.readContextNotice === 'release-archived'
|
|
127
|
+
? 'This release was archived. Preview now follows Main, including its drafts.'
|
|
128
|
+
: null;
|
|
129
|
+
const interactionSuspended = !connected || !rendered || presentation.transition.kind !== 'idle';
|
|
130
|
+
// Render proof protects page overlays. An accepted editor gesture can load
|
|
131
|
+
// and edit its target while the site catches up, under the shared admission.
|
|
132
|
+
const acceptedEditorContext = state.readContext != null &&
|
|
133
|
+
presentation.acceptedContext?.contextId === state.readContext.contextId &&
|
|
134
|
+
presentation.acceptedContext?.revision === state.readContext.revision;
|
|
135
|
+
const editorSuspended = !connected || (!rendered && !acceptedEditorContext);
|
|
136
|
+
const interactionSuspendedRef = useRef(interactionSuspended);
|
|
137
|
+
interactionSuspendedRef.current = interactionSuspended;
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
setStandaloneOverlaySuspended(interactionSuspended);
|
|
140
|
+
}, [interactionSuspended]);
|
|
141
|
+
const canEdit = !interactionSuspended &&
|
|
142
|
+
connected &&
|
|
143
|
+
rendered &&
|
|
144
|
+
runtimeSource !== null &&
|
|
145
|
+
(currentPageTarget !== null || overlays.available);
|
|
146
|
+
const overlayNotice = overlays.rejections.length > 0
|
|
147
|
+
? overlays.rejections.some(rejection => rejection.reason === 'edit-locator-variant-mismatch')
|
|
148
|
+
? 'Element selection is unavailable because some elements do not match their editing context.'
|
|
149
|
+
: 'Element selection is unavailable because this page has incomplete or conflicting editing information.'
|
|
150
|
+
: null;
|
|
151
|
+
const shouldPreloadEditor = canEdit && (interactionMode === 'edit' || menuOpen);
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (!shouldPreloadEditor || !runtimeSource)
|
|
154
|
+
return;
|
|
155
|
+
// Warm code for either entry point (overlay or menu). Admission and mounting
|
|
156
|
+
// still belong to explicit edit intent; the loader shares in-flight work.
|
|
157
|
+
void loadPreviewAuthoringRuntime(runtimeSource).catch(() => undefined);
|
|
158
|
+
}, [shouldPreloadEditor, runtimeSource]);
|
|
159
|
+
const currentPage = useRef(page);
|
|
160
|
+
currentPage.current = page;
|
|
161
|
+
const presentationRef = useRef(presentation);
|
|
162
|
+
presentationRef.current = presentation;
|
|
163
|
+
const contextRef = useRef(state.readContext);
|
|
164
|
+
contextRef.current = state.readContext;
|
|
165
|
+
const sessionRef = useRef(state.sessionId);
|
|
166
|
+
sessionRef.current = state.sessionId;
|
|
167
|
+
const contextListeners = useRef(new Set());
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
// The authoring controls observe both context and presentation. A locale
|
|
170
|
+
// or native page navigation can change without revising the read context.
|
|
171
|
+
contextListeners.current.forEach(listener => listener());
|
|
172
|
+
}, [state.readContext, page, presentation.acceptedContext, presentation.acceptedPresentation]);
|
|
173
|
+
const requestEditorTarget = useCallback((next) => {
|
|
174
|
+
setTargetRequest(next ? {
|
|
175
|
+
target: next,
|
|
176
|
+
context: contextRef.current,
|
|
177
|
+
sessionId: sessionRef.current,
|
|
178
|
+
location: window.location.href,
|
|
179
|
+
} : null);
|
|
180
|
+
}, []);
|
|
181
|
+
const observedPage = useRef(null);
|
|
182
|
+
const followedGeneration = useRef(0);
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
if (presentation.transition.kind !== 'idle' || !navigationHandoff.current)
|
|
185
|
+
return;
|
|
186
|
+
// Only confirmed rendering consumes a successful hard-navigation hint.
|
|
187
|
+
// Acceptance alone is earlier than a potential document replacement.
|
|
188
|
+
if (presentation.completedGeneration > 0) {
|
|
189
|
+
clearPreviewEditorNavigation(navigationHandoff.current.id);
|
|
190
|
+
navigationHandoff.current = null;
|
|
191
|
+
}
|
|
192
|
+
}, [presentation.transition.kind, presentation.completedGeneration]);
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
if (!page || !state.readContext || !rendered || presentation.isPending)
|
|
195
|
+
return;
|
|
196
|
+
const previous = observedPage.current;
|
|
197
|
+
observedPage.current = {
|
|
198
|
+
documentId: page.documentId,
|
|
199
|
+
locale: page.locale,
|
|
200
|
+
contextId: state.readContext.contextId,
|
|
201
|
+
revision: state.readContext.revision,
|
|
202
|
+
};
|
|
203
|
+
const ownTransition = followedGeneration.current !== presentation.completedGeneration;
|
|
204
|
+
followedGeneration.current = presentation.completedGeneration;
|
|
205
|
+
if (!previous ||
|
|
206
|
+
ownTransition ||
|
|
207
|
+
!runtimeHandle.current ||
|
|
208
|
+
!targetRef.current)
|
|
209
|
+
return;
|
|
210
|
+
const navigated = previous.documentId !== page.documentId || previous.locale !== page.locale;
|
|
211
|
+
const changedScope = previous.contextId !== state.readContext.contextId ||
|
|
212
|
+
previous.revision !== state.readContext.revision;
|
|
213
|
+
if (!navigated && !changedScope)
|
|
214
|
+
return;
|
|
215
|
+
const next = targetForPage(navigated ? page : { ...page,
|
|
216
|
+
documentId: targetRef.current.documentId,
|
|
217
|
+
locale: targetRef.current.locale,
|
|
218
|
+
}, state.readContext.perspective);
|
|
219
|
+
requestEditorTarget(next);
|
|
220
|
+
}, [
|
|
221
|
+
page,
|
|
222
|
+
state.readContext,
|
|
223
|
+
rendered,
|
|
224
|
+
presentation.isPending,
|
|
225
|
+
presentation.completedGeneration,
|
|
226
|
+
requestEditorTarget,
|
|
227
|
+
]);
|
|
228
|
+
const closePanel = useCallback(() => {
|
|
229
|
+
if (navigationHandoff.current) {
|
|
230
|
+
clearPreviewEditorNavigation(navigationHandoff.current.id);
|
|
231
|
+
navigationHandoff.current = null;
|
|
232
|
+
}
|
|
233
|
+
setContinuation(null);
|
|
234
|
+
runtimeHandle.current?.setSurface(null);
|
|
235
|
+
setSurface(null);
|
|
236
|
+
}, []);
|
|
237
|
+
const openEditor = useCallback((next) => {
|
|
238
|
+
if (navigationHandoff.current) {
|
|
239
|
+
clearPreviewEditorNavigation(navigationHandoff.current.id);
|
|
240
|
+
navigationHandoff.current = null;
|
|
241
|
+
}
|
|
242
|
+
setContinuation(null);
|
|
243
|
+
setError(null);
|
|
244
|
+
setMenuOpen(false);
|
|
245
|
+
setSurface('editor');
|
|
246
|
+
runtimeHandle.current?.setSurface('editor');
|
|
247
|
+
requestEditorTarget(next);
|
|
248
|
+
}, [requestEditorTarget]);
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
if (!continuation || !canEdit || !page || !perspective)
|
|
251
|
+
return;
|
|
252
|
+
if (page.documentId !== continuation.destination.documentId ||
|
|
253
|
+
page.locale !== continuation.destination.locale ||
|
|
254
|
+
!previewPerspectivesEqual(perspective, continuation.destination.perspective))
|
|
255
|
+
return;
|
|
256
|
+
// Restore only after the same render proof used by the live transition.
|
|
257
|
+
// The editor still obtains a fresh admission through its normal mount.
|
|
258
|
+
openEditor(continuation.target);
|
|
259
|
+
}, [continuation, canEdit, page, perspective, openEditor]);
|
|
260
|
+
useEffect(() => subscribeStandaloneOverlayIntent((intent) => {
|
|
261
|
+
if (interactionSuspendedRef.current)
|
|
262
|
+
return;
|
|
263
|
+
const next = targetForOverlay(intent, state.readContext?.perspective ?? null, page?.locale);
|
|
264
|
+
if (next)
|
|
265
|
+
openEditor(next);
|
|
266
|
+
}), [openEditor, state.readContext?.perspective, page?.locale]);
|
|
267
|
+
const wantsRuntime = surface !== null || menuOpen;
|
|
268
|
+
useEffect(() => {
|
|
269
|
+
if (!wantsRuntime ||
|
|
270
|
+
!connected ||
|
|
271
|
+
(surface === 'editor' && !targetRequest) ||
|
|
272
|
+
root === null ||
|
|
273
|
+
editorContainer.current === null ||
|
|
274
|
+
contextContainer === null || headerLeading === null || headerTrailing === null ||
|
|
275
|
+
runtimeSource === null ||
|
|
276
|
+
runtimeHandle.current !== null)
|
|
277
|
+
return;
|
|
278
|
+
let cancelled = false;
|
|
279
|
+
const container = editorContainer.current;
|
|
280
|
+
const requestedContext = contextRef.current;
|
|
281
|
+
const requestedSession = sessionRef.current;
|
|
282
|
+
const requestedLocation = window.location.href;
|
|
283
|
+
const connect = async () => {
|
|
284
|
+
setEditorStatus('connecting');
|
|
285
|
+
setError(null);
|
|
286
|
+
try {
|
|
287
|
+
const [runtime, initialAdmission] = await Promise.all([
|
|
288
|
+
loadPreviewAuthoringRuntime(runtimeSource),
|
|
289
|
+
requestPreviewAuthoringAdmission({ url: authoringUrl }),
|
|
290
|
+
]);
|
|
291
|
+
if (cancelled)
|
|
292
|
+
return;
|
|
293
|
+
if ((surface === 'editor' && interactionSuspendedRef.current) ||
|
|
294
|
+
requestedSession !== sessionRef.current ||
|
|
295
|
+
requestedLocation !== window.location.href ||
|
|
296
|
+
requestedContext?.contextId !== contextRef.current?.contextId ||
|
|
297
|
+
requestedContext?.revision !== contextRef.current?.revision) {
|
|
298
|
+
throw new Error('PREVIEW_PRESENTATION_SUPERSEDED');
|
|
299
|
+
}
|
|
300
|
+
admission.current = initialAdmission;
|
|
301
|
+
const owner = Symbol();
|
|
302
|
+
runtimeOwner.current = owner;
|
|
303
|
+
// This is the initial editor destination, before Convex authentication
|
|
304
|
+
// can report onTarget. Later requests only commit through onTarget.
|
|
305
|
+
targetRef.current = surface === 'editor' ? targetRequest.target : null;
|
|
306
|
+
const handle = runtime.mount({
|
|
307
|
+
root,
|
|
308
|
+
container,
|
|
309
|
+
header: { leading: headerLeading, trailing: headerTrailing },
|
|
310
|
+
target: targetRef.current ?? undefined,
|
|
311
|
+
surface: surface === 'assistant' ? 'assistant' : 'editor',
|
|
312
|
+
assistantThreadId: assistantThreadId.current,
|
|
313
|
+
onSurface: next => { if (runtimeOwner.current === owner) {
|
|
314
|
+
setMenuOpen(false);
|
|
315
|
+
setSurface(next);
|
|
316
|
+
} },
|
|
317
|
+
onAssistantThread: id => {
|
|
318
|
+
if (runtimeOwner.current !== owner)
|
|
319
|
+
return;
|
|
320
|
+
assistantThreadId.current = id;
|
|
321
|
+
rememberAssistant(activeSurface.current === 'assistant');
|
|
322
|
+
},
|
|
323
|
+
context: {
|
|
324
|
+
container: contextContainer,
|
|
325
|
+
getSnapshot: () => previewRuntime.getState().session?.readContext ?? null,
|
|
326
|
+
subscribe: listener => {
|
|
327
|
+
contextListeners.current.add(listener);
|
|
328
|
+
return () => { contextListeners.current.delete(listener); };
|
|
329
|
+
},
|
|
330
|
+
change: next => presentationRef.current.changePerspective(next),
|
|
331
|
+
},
|
|
332
|
+
studioUrl: destination?.href,
|
|
333
|
+
revealSource: revealStandaloneSource,
|
|
334
|
+
revealSlice: revealStandaloneSlice,
|
|
335
|
+
onTarget: (next) => {
|
|
336
|
+
if (runtimeOwner.current !== owner)
|
|
337
|
+
return;
|
|
338
|
+
targetRef.current = next;
|
|
339
|
+
setCommittedTarget(next);
|
|
340
|
+
},
|
|
341
|
+
getPresentation: () => {
|
|
342
|
+
const hostPage = currentPage.current;
|
|
343
|
+
const context = contextRef.current;
|
|
344
|
+
const accepted = presentationRef.current;
|
|
345
|
+
if (context && accepted.acceptedContext?.contextId === context.contextId &&
|
|
346
|
+
accepted.acceptedContext.revision === context.revision &&
|
|
347
|
+
accepted.acceptedPresentation)
|
|
348
|
+
return accepted.acceptedPresentation;
|
|
349
|
+
return hostPage && context
|
|
350
|
+
? {
|
|
351
|
+
documentId: hostPage.documentId,
|
|
352
|
+
locale: hostPage.locale,
|
|
353
|
+
pathname: window.location.pathname.slice(basePath.length) || '/',
|
|
354
|
+
perspective: context.perspective,
|
|
355
|
+
}
|
|
356
|
+
: null;
|
|
357
|
+
},
|
|
358
|
+
present: async (request, nextTarget) => {
|
|
359
|
+
const handoff = projectId && state.sessionId && nextTarget
|
|
360
|
+
? rememberPreviewEditorNavigation(projectId, state.sessionId, basePath, request, nextTarget)
|
|
361
|
+
: null;
|
|
362
|
+
navigationHandoff.current = handoff;
|
|
363
|
+
try {
|
|
364
|
+
return await presentationRef.current.present(request);
|
|
365
|
+
}
|
|
366
|
+
catch (cause) {
|
|
367
|
+
// A new browser document owns the handoff after pagehide.
|
|
368
|
+
if (mounted.current && handoff) {
|
|
369
|
+
clearPreviewEditorNavigation(handoff.id);
|
|
370
|
+
if (navigationHandoff.current === handoff)
|
|
371
|
+
navigationHandoff.current = null;
|
|
372
|
+
}
|
|
373
|
+
throw cause;
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
admission: initialAdmission,
|
|
377
|
+
refreshAdmission: async () => {
|
|
378
|
+
const next = await requestPreviewAuthoringAdmission({
|
|
379
|
+
url: authoringUrl,
|
|
380
|
+
previous: admission.current,
|
|
381
|
+
});
|
|
382
|
+
admission.current = next;
|
|
383
|
+
return next;
|
|
384
|
+
},
|
|
385
|
+
closeAdmission: () => {
|
|
386
|
+
admission.current = null;
|
|
387
|
+
},
|
|
388
|
+
onStatus: (status) => {
|
|
389
|
+
if (runtimeOwner.current !== owner)
|
|
390
|
+
return;
|
|
391
|
+
if (status === 'disposed') {
|
|
392
|
+
runtimeOwner.current = null;
|
|
393
|
+
runtimeHandle.current = null;
|
|
394
|
+
admission.current = null;
|
|
395
|
+
}
|
|
396
|
+
if (status === 'ready')
|
|
397
|
+
setEditorStatus('ready');
|
|
398
|
+
if (status === 'connecting')
|
|
399
|
+
setEditorStatus('connecting');
|
|
400
|
+
if (status === 'unauthorized') {
|
|
401
|
+
setEditorStatus('error');
|
|
402
|
+
setError('Editing access ended. Reopen Preview from Studio.');
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
if (cancelled) {
|
|
407
|
+
if (runtimeOwner.current === owner)
|
|
408
|
+
runtimeOwner.current = null;
|
|
409
|
+
await handle.dispose();
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
runtimeHandle.current = handle;
|
|
413
|
+
handle.setSurface(surface === 'editor' || surface === 'assistant' ? surface : null);
|
|
414
|
+
}
|
|
415
|
+
catch (cause) {
|
|
416
|
+
if (!cancelled) {
|
|
417
|
+
setEditorStatus('error');
|
|
418
|
+
setError(authoringErrorMessage(cause));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
void connect();
|
|
423
|
+
return () => {
|
|
424
|
+
cancelled = true;
|
|
425
|
+
};
|
|
426
|
+
}, [authoringUrl, connectAttempt, targetRequest, root, contextContainer, headerLeading, headerTrailing, runtimeSource, surface, wantsRuntime, connected]);
|
|
427
|
+
useEffect(() => {
|
|
428
|
+
if (!targetRequest || !runtimeHandle.current)
|
|
429
|
+
return;
|
|
430
|
+
const handle = runtimeHandle.current;
|
|
431
|
+
let current = true;
|
|
432
|
+
const isCurrent = () => current && runtimeHandle.current === handle &&
|
|
433
|
+
!interactionSuspendedRef.current &&
|
|
434
|
+
sessionRef.current === targetRequest.sessionId &&
|
|
435
|
+
targetRequest.context !== null &&
|
|
436
|
+
contextRef.current?.contextId === targetRequest.context.contextId &&
|
|
437
|
+
contextRef.current.revision === targetRequest.context.revision &&
|
|
438
|
+
window.location.href === targetRequest.location;
|
|
439
|
+
setEditorStatus('connecting');
|
|
440
|
+
void handle.open(targetRequest.target, isCurrent).then(() => {
|
|
441
|
+
// A cancelled opening leaves the retained editor usable. Its onTarget
|
|
442
|
+
// callback, not this transport status, owns the committed destination.
|
|
443
|
+
if (current)
|
|
444
|
+
setEditorStatus('ready');
|
|
445
|
+
}, (cause) => {
|
|
446
|
+
if (!isCurrent())
|
|
447
|
+
return;
|
|
448
|
+
setEditorStatus('error');
|
|
449
|
+
setError(authoringErrorMessage(cause));
|
|
450
|
+
});
|
|
451
|
+
return () => {
|
|
452
|
+
current = false;
|
|
453
|
+
};
|
|
454
|
+
}, [targetRequest]);
|
|
455
|
+
useEffect(() => {
|
|
456
|
+
mounted.current = true;
|
|
457
|
+
const dispose = () => {
|
|
458
|
+
mounted.current = false;
|
|
459
|
+
runtimeOwner.current = null;
|
|
460
|
+
void runtimeHandle.current?.dispose();
|
|
461
|
+
runtimeHandle.current = null;
|
|
462
|
+
};
|
|
463
|
+
window.addEventListener('pagehide', dispose);
|
|
464
|
+
return () => {
|
|
465
|
+
window.removeEventListener('pagehide', dispose);
|
|
466
|
+
dispose();
|
|
467
|
+
};
|
|
468
|
+
}, []);
|
|
469
|
+
return _jsx(PreviewToolbarShell, { hostRef: host, onRoot: setRoot, menuOpen: menuOpen, onMenuOpenChange: setMenuOpen, panelOpen: surface !== null, onPanelClose: closePanel, panelLeading: _jsx("div", { ref: setHeaderLeading, "data-ss-surface": "" }), panelTrailing: _jsx("div", { ref: setHeaderTrailing, "data-ss-surface": "" }), panelLabel: surface === "assistant" ? "Assistant" : "Edit content", menu: _jsxs(_Fragment, { children: [_jsxs("div", { className: "menu-group", role: "group", "aria-label": "Site view", children: [_jsx("span", { className: "menu-group-label", children: "Site view" }), _jsxs("div", { className: "menu-group-content", children: [_jsx("div", { ref: setContextContainer, "data-ss-surface": "" }), editorStatus === 'connecting' ? _jsx("p", { className: "menu-status", role: "status", children: "Loading site context\u2026" }) : null] })] }), _jsx("div", { className: "menu-separator", "aria-hidden": "true" }), _jsxs("div", { className: "menu-group", role: "group", "aria-label": "Interaction", children: [_jsx("span", { className: "menu-group-label", children: "Interaction" }), _jsx("div", { className: "menu-group-content", children: _jsx(PreviewInteractionControl, { interactionMode: effectiveMode, onChange: chooseInteractionMode }) })] }), _jsx("div", { className: "menu-separator", "aria-hidden": "true" }), _jsxs("button", { type: "button", className: "menu-action", disabled: !canEdit, onClick: () => openEditor(currentPageTarget), children: [_jsx(EditIcon, {}), _jsx("span", { children: "Edit content" })] }), _jsxs("button", { type: "button", className: "menu-action", disabled: !connected || !runtimeSource, onClick: () => { setError(null); setMenuOpen(false); setSurface('assistant'); }, children: [_jsx(ChatIcon, {}), _jsx("span", { children: "Assistant" })] }), destination && (_jsxs("a", { className: "menu-action", href: destination.href, children: [_jsx(StudioIcon, {}), _jsx("span", { children: "Open in Studio" })] })), (connectionNotice || notice ||
|
|
470
|
+
overlayNotice ||
|
|
471
|
+
(!rendered && connected) ||
|
|
472
|
+
recovery?.error ||
|
|
473
|
+
error) && (_jsxs("div", { className: "notices", children: [connectionNotice && _jsx("p", { role: "status", children: connectionNotice }), notice && _jsx("p", { children: notice }), overlayNotice && _jsx("p", { role: "status", children: overlayNotice }), connected && !rendered && (_jsxs("p", { children: ["This page has not confirmed its rendered perspective.", ' ', _jsx("button", { type: "button", onClick: () => window.location.reload(), children: "Reload" })] })), recovery?.error && (_jsxs("p", { role: "alert", children: [recovery.error, ' ', _jsx("button", { type: "button", onClick: recovery.retry, disabled: false, children: "Retry" })] })), error && _jsxs("p", { role: "alert", children: [error, ' ', editorStatus === 'error' && !runtimeHandle.current ? (_jsx("button", { type: "button", onClick: () => setConnectAttempt(value => value + 1), children: "Retry" })) : null] })] }))] }), panel: _jsxs(_Fragment, { children: [surface === 'editor' && target === null && committedTarget === null && !continuation && (_jsxs("div", { className: "editor-empty", children: [_jsx(EditIcon, {}), _jsx("strong", { children: "Select an element on the page" }), _jsx("p", { children: "The matching field will open here." })] })), (editorStatus === 'idle' || editorStatus === 'connecting') &&
|
|
474
|
+
(surface === 'assistant' || target || continuation) && (_jsx("div", { className: "editor-loading", role: "status", "aria-label": surface === 'assistant' ? 'Loading assistant' : 'Loading document content', children: _jsxs("div", { "aria-hidden": "true", children: [_jsx("div", {}), _jsx("div", {}), _jsx("div", {})] }) })), editorStatus === 'error' && error && (_jsx("div", { className: "panel-state-overlay", children: _jsx(PreviewPanelState, { title: surface === 'assistant' ? 'Assistant unavailable' : 'Editor unavailable', description: error, actions: _jsx(PreviewPanelAction, { onClick: () => {
|
|
475
|
+
setError(null);
|
|
476
|
+
if (runtimeHandle.current && (committedTarget ?? target))
|
|
477
|
+
requestEditorTarget({ ...(committedTarget ?? target) });
|
|
478
|
+
else
|
|
479
|
+
setConnectAttempt((value) => value + 1);
|
|
480
|
+
}, children: "Retry" }) }) })), surface === 'editor' && presentation.transition.kind === 'unconfirmed' && (_jsx("div", { className: "panel-state-overlay", children: _jsx(PreviewPanelState, { title: "Page not confirmed", description: "The site has not confirmed this page. Reload the preview if it does not catch up.", actions: _jsx(PreviewPanelAction, { onClick: () => window.location.reload(), children: "Reload preview" }) }) })), _jsx("div", { ref: editorContainer, className: "editor-mount", inert: (surface === 'editor' ? editorSuspended : !connected) || editorStatus !== 'ready', "aria-busy": (surface === 'editor' && editorSuspended) || editorStatus === 'connecting' })] }) });
|
|
481
|
+
}
|
|
482
|
+
function targetForPage(page, perspective) {
|
|
483
|
+
return {
|
|
484
|
+
documentId: page.documentId,
|
|
485
|
+
locale: page.locale,
|
|
486
|
+
...(perspective.kind === 'published' ? { published: true } : perspective.kind === 'variant'
|
|
487
|
+
? { variantId: perspective.variantId }
|
|
488
|
+
: perspective.kind === 'release'
|
|
489
|
+
? { releaseId: perspective.releaseId }
|
|
490
|
+
: {}),
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
export function targetForOverlay(target, perspective, presentationLocale) {
|
|
494
|
+
const locale = overlayEditorLocale(target, presentationLocale);
|
|
495
|
+
if (!locale)
|
|
496
|
+
return null;
|
|
497
|
+
return {
|
|
498
|
+
documentId: target.documentId,
|
|
499
|
+
locale,
|
|
500
|
+
...(perspective?.kind === 'published' ? { published: true } : perspective?.kind === 'variant'
|
|
501
|
+
? { variantId: perspective.variantId }
|
|
502
|
+
: perspective?.kind === 'release'
|
|
503
|
+
? { releaseId: perspective.releaseId }
|
|
504
|
+
: {}),
|
|
505
|
+
position: overlayEditorTarget(target),
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
function authoringErrorMessage(error) {
|
|
509
|
+
const code = error instanceof Error ? error.message : '';
|
|
510
|
+
console.warn('[storystick] Preview authoring connection failed:', /^[A-Z][A-Z0-9_]{0,80}$/.test(code) ? code : 'AUTHORING_RUNTIME_ERROR');
|
|
511
|
+
if (code.includes('AUTHORING_WRITE_FORBIDDEN')) {
|
|
512
|
+
return 'You do not have permission to edit this project.';
|
|
513
|
+
}
|
|
514
|
+
if (code.includes('CONTENT_SHAPE_RELOAD_REQUIRED')) {
|
|
515
|
+
return 'Reopen Preview from Studio after the site deployment changed.';
|
|
516
|
+
}
|
|
517
|
+
return 'Editing is unavailable. Reopen Preview from Studio and try again.';
|
|
518
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PreviewReadContext } from '@storystick/types/preview-query-targeting';
|
|
2
|
+
import { type PreviewPrimaryPage } from '@storystick/preview/provenance';
|
|
3
|
+
/** Render evidence is navigation metadata. Studio still authorizes every document. */
|
|
4
|
+
export declare function previewStudioDestination(input: {
|
|
5
|
+
studioUrl: string | null;
|
|
6
|
+
projectId: string | null;
|
|
7
|
+
sessionId: string | null;
|
|
8
|
+
context: PreviewReadContext | null;
|
|
9
|
+
authenticated: boolean;
|
|
10
|
+
browserPathname: string;
|
|
11
|
+
basePath: string;
|
|
12
|
+
evidence: unknown;
|
|
13
|
+
}): {
|
|
14
|
+
href: string;
|
|
15
|
+
kind: 'document' | 'project';
|
|
16
|
+
} | null;
|
|
17
|
+
export declare function previewCurrentPrimaryPage(evidence: unknown, sessionId: string | null, context: PreviewReadContext, browserPathname: string, basePath: string): PreviewPrimaryPage | null;
|
|
18
|
+
export declare function readPrimaryPagesSnapshot(): unknown;
|
|
19
|
+
export declare function subscribePrimaryPages(listener: () => void): () => void;
|
|
20
|
+
export declare const emptyPrimaryPagesSnapshot: () => null;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { isPreviewReadContext } from '@storystick/types/preview-query-targeting';
|
|
2
|
+
import { selectPreviewPrimaryPage } from '@storystick/preview/provenance';
|
|
3
|
+
/** Render evidence is navigation metadata. Studio still authorizes every document. */
|
|
4
|
+
export function previewStudioDestination(input) {
|
|
5
|
+
if (!input.studioUrl || !input.projectId)
|
|
6
|
+
return null;
|
|
7
|
+
let url;
|
|
8
|
+
try {
|
|
9
|
+
url = new URL(input.studioUrl);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const [, prefix, project] = url.pathname.split('/');
|
|
15
|
+
if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password || prefix !== 'p' ||
|
|
16
|
+
!(project === input.projectId || project?.endsWith(`-${input.projectId}`)))
|
|
17
|
+
return null;
|
|
18
|
+
url.pathname = `/p/${project}`;
|
|
19
|
+
url.search = '';
|
|
20
|
+
url.hash = '';
|
|
21
|
+
const context = input.authenticated ? input.context : null;
|
|
22
|
+
if (context && input.sessionId)
|
|
23
|
+
url.searchParams.set('previewSource', input.sessionId);
|
|
24
|
+
if (context?.perspective.kind === 'published')
|
|
25
|
+
url.searchParams.set('perspective', 'published');
|
|
26
|
+
if (context?.perspective.kind === 'release')
|
|
27
|
+
url.searchParams.set('release', context.perspective.releaseId);
|
|
28
|
+
const page = context ? previewCurrentPrimaryPage(input.evidence, input.sessionId, context, input.browserPathname, input.basePath) : null;
|
|
29
|
+
if (page) {
|
|
30
|
+
url.pathname += `/d/${encodeURIComponent(page.documentId)}`;
|
|
31
|
+
url.searchParams.set('locale', page.locale);
|
|
32
|
+
url.searchParams.set('panel', 'preview');
|
|
33
|
+
if (context?.perspective.kind === 'variant')
|
|
34
|
+
url.searchParams.set('previewVariant', context.perspective.variantId);
|
|
35
|
+
}
|
|
36
|
+
return { href: url.href, kind: page ? 'document' : 'project' };
|
|
37
|
+
}
|
|
38
|
+
export function previewCurrentPrimaryPage(evidence, sessionId, context, browserPathname, basePath) {
|
|
39
|
+
if (!evidence || typeof evidence !== 'object')
|
|
40
|
+
return null;
|
|
41
|
+
const value = evidence;
|
|
42
|
+
if (!sessionId || value.sessionId !== sessionId || !isPreviewReadContext(value.readContext) ||
|
|
43
|
+
value.readContext.contextId !== context.contextId || value.readContext.revision !== context.revision ||
|
|
44
|
+
value.pathname !== browserPathname || !Array.isArray(value.pages))
|
|
45
|
+
return null;
|
|
46
|
+
return selectPreviewPrimaryPage(value.pages, browserPathname, basePath);
|
|
47
|
+
}
|
|
48
|
+
export function readPrimaryPagesSnapshot() {
|
|
49
|
+
return typeof window === 'undefined' ? null : window.__ssPreviewPrimaryPages ?? null;
|
|
50
|
+
}
|
|
51
|
+
export function subscribePrimaryPages(listener) {
|
|
52
|
+
window.addEventListener('storystick.preview.primary-pages', listener);
|
|
53
|
+
return () => window.removeEventListener('storystick.preview.primary-pages', listener);
|
|
54
|
+
}
|
|
55
|
+
export const emptyPrimaryPagesSnapshot = () => null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type PreviewTransitionState } from '@storystick/preview/runtime/preview-transition-state';
|
|
2
|
+
import type { PreviewAuthoringPresentation } from '@storystick/preview/authoring/runtime-contract';
|
|
3
|
+
import type { PreviewPerspective, PreviewReadContext } from '@storystick/types/preview-query-targeting';
|
|
4
|
+
/** Acceptance releases the editor; render proof releases the page overlays.
|
|
5
|
+
* No authoring credentials cross this port; the framework uses its Preview cookie. */
|
|
6
|
+
export declare function useStandalonePresentation(basePath: string): {
|
|
7
|
+
present: (request: PreviewAuthoringPresentation) => Promise<PreviewReadContext>;
|
|
8
|
+
/** The project menu also exists on routes without an editable document. */
|
|
9
|
+
changePerspective(next: PreviewPerspective): Promise<PreviewReadContext>;
|
|
10
|
+
transition: PreviewTransitionState;
|
|
11
|
+
completedGeneration: number;
|
|
12
|
+
acceptedContext: PreviewReadContext | null;
|
|
13
|
+
acceptedPresentation: PreviewAuthoringPresentation | null;
|
|
14
|
+
isPending: boolean;
|
|
15
|
+
};
|