camox 0.51.0 → 0.52.2
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/dist/core/createApp.d.ts +1 -0
- package/dist/core/createApp.js +1 -0
- package/dist/core/editing/contentEdits.js +78 -0
- package/dist/core/editing/createEditableBlock.js +277 -311
- package/dist/core/lib/contentType.js +2 -2
- package/dist/core/recordItem.js +3 -3
- package/dist/features/canvas/CamoxCanvas.js +15 -15
- package/dist/features/canvas/CanvasLoadingScreen.js +15 -11
- package/dist/features/canvas/CanvasPageFrame.js +81 -87
- package/dist/features/canvas/CanvasPreviewPageSlot.js +1 -1
- package/dist/features/canvas/DerivedPagePathInput.js +12 -12
- package/dist/features/canvas/canvasOverlayGeometry.js +1 -1
- package/dist/features/canvas/canvasStore.js +2 -2
- package/dist/features/canvas/sidebar/CuratedPageInspector.js +1 -1
- package/dist/features/canvas/sidebar/LayoutPageBlocks.js +23 -31
- package/dist/features/canvas/useCanvasCamera.js +1 -1
- package/dist/features/canvas/useDiscoveredPreviewPaths.js +16 -9
- package/dist/features/page/CuratedPageContent.js +1 -1
- package/dist/features/preview/CamoxPreview.d.ts +8 -4
- package/dist/features/preview/CamoxPreview.js +257 -442
- package/dist/features/preview/EditablePageContent.js +244 -2
- package/dist/features/preview/EditablePageExperience.js +2 -4
- package/dist/features/preview/components/AddBlockSheet.js +55 -15
- package/dist/features/preview/components/BlockActionsPopover.js +12 -16
- package/dist/features/preview/components/BlockPeek.js +66 -0
- package/dist/features/preview/components/BlockThumbnail.js +134 -119
- package/dist/features/preview/components/CanvasPreview.js +1 -1
- package/dist/features/preview/components/CreatePageModal.js +150 -156
- package/dist/features/preview/components/Frame.js +27 -4
- package/dist/features/preview/components/MobilePreviewDrawer.js +1 -1
- package/dist/features/preview/components/PageInfo.js +161 -126
- package/dist/features/preview/components/PagePicker.js +2 -2
- package/dist/features/preview/components/PageTree.js +174 -190
- package/dist/features/preview/components/PreviewPanel.js +3 -3
- package/dist/features/preview/components/ReferenceFieldEditor.js +4 -4
- package/dist/features/preview/components/ReferenceListFieldEditor.js +4 -4
- package/dist/features/preview/components/RemovedBlockPlaceholder.js +37 -70
- package/dist/features/preview/components/RepeatableItemsList.js +1 -1
- package/dist/features/preview/components/SelectionEditor.js +248 -256
- package/dist/features/preview/components/StudioEditingFallback.js +22 -26
- package/dist/features/preview/components/useFieldWriter.js +19 -19
- package/dist/features/preview/components/useRecordView.js +105 -147
- package/dist/features/preview/components/useRepeatableItemActions.js +0 -2
- package/dist/features/preview/pageRecord.js +30 -24
- package/dist/features/preview/previewStore.js +2 -0
- package/dist/features/preview/studioNotice.js +20 -0
- package/dist/features/provider/AuthenticatedCamoxProvider.js +46 -30
- package/dist/features/renderer/RendererApp.js +542 -0
- package/dist/features/renderer/RendererFrames.js +450 -0
- package/dist/features/renderer/definitionAnswers.js +26 -0
- package/dist/features/renderer/protocol.js +35 -0
- package/dist/features/renderer/rendererHost.js +249 -0
- package/dist/features/renderer/studioConnection.js +186 -0
- package/dist/features/routes/pageRuntime.js +7 -2
- package/dist/features/runtime/RuntimeChrome.js +2 -5
- package/dist/features/runtime/assetPrefix.js +14 -0
- package/dist/features/runtime/breadcrumbs.js +1 -1
- package/dist/features/runtime/editingFallback.js +4 -8
- package/dist/features/runtime/livePages.js +1 -1
- package/dist/features/runtime/pageApp.js +57 -83
- package/dist/features/runtime/pageClient.js +1 -1
- package/dist/features/runtime/pageNavigation.js +3 -2
- package/dist/features/runtime/publicCache.js +15 -3
- package/dist/features/runtime/publishingDraft.js +23 -0
- package/dist/features/runtime/rendererClient.d.ts +8 -0
- package/dist/features/runtime/rendererClient.js +77 -0
- package/dist/features/runtime/runtime.d.ts +9 -0
- package/dist/features/runtime/runtime.js +36 -17
- package/dist/features/runtime/runtimeRoutes.d.ts +2 -0
- package/dist/features/runtime/runtimeRoutes.js +2 -0
- package/dist/features/runtime/worker.d.ts +10 -1
- package/dist/features/runtime/worker.js +20 -7
- package/dist/features/studio/FollowAgentProvider.js +10 -10
- package/dist/features/studio/components/ProjectMenu.js +1 -1
- package/dist/features/studio/components/ReviewDraftSheet.js +242 -138
- package/dist/features/studio/components/ThreadMessages.js +100 -65
- package/dist/features/studio/components/UserButton.js +1 -1
- package/dist/features/vite/definitionsSync.js +1 -1
- package/dist/features/vite/hostedChecks.js +127 -0
- package/dist/features/vite/runtimeDev.js +26 -0
- package/dist/features/vite/vite.js +33 -5
- package/dist/hooks/use-page-destinations.js +10 -10
- package/dist/lib/revisions.js +20 -0
- package/dist/lib/studio-definitions.js +160 -0
- package/dist/lib/studio-reads.js +65 -21
- package/dist/lib/use-project-room.js +16 -10
- package/dist/package.js +1 -1
- package/dist/studio.css +1 -1
- package/package.json +8 -4
- package/skills/camox/SKILL.md +4 -0
- package/skills/camox/references/artifacts.md +1 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useAuthState, useSignInRedirect } from "../../lib/auth.js";
|
|
2
2
|
import { DraftScopeContext } from "../../lib/draft-scope.js";
|
|
3
|
-
import { DerivedPageContent } from "../page/DerivedPageContent.js";
|
|
4
3
|
import { useSignInShortcut } from "../provider/signInShortcut.js";
|
|
5
4
|
import { PreviewDocumentContext } from "./PreviewDocumentContext.js";
|
|
6
5
|
import { PreviewActivation } from "../preview/components/PreviewActivation.js";
|
|
@@ -8,6 +7,7 @@ import { Frame } from "../preview/components/Frame.js";
|
|
|
8
7
|
import { PreviewSignInPage } from "../provider/PreviewSignInPage.js";
|
|
9
8
|
import { CoreCamoxProvider } from "../provider/CoreCamoxProvider.js";
|
|
10
9
|
import { CuratedPageContent } from "../page/CuratedPageContent.js";
|
|
10
|
+
import { DerivedPageContent } from "../page/DerivedPageContent.js";
|
|
11
11
|
import { EditingFallbackSlot } from "./editingFallback.js";
|
|
12
12
|
import { PageNavigationProvider } from "./pageNavigation.js";
|
|
13
13
|
import { RuntimeChromeSlot } from "./runtimeChromeLoader.js";
|
|
@@ -97,11 +97,6 @@ function PageApp(t0) {
|
|
|
97
97
|
return t6;
|
|
98
98
|
}
|
|
99
99
|
const LazyEditablePageExperience = React.lazy(() => import("../preview/EditablePageExperience.js").then((module) => ({ default: module.EditablePageExperience })));
|
|
100
|
-
/** Editing lists and adds any block, so it waits for every definition, not only the page's. */
|
|
101
|
-
function AllBlocks({ camoxApp, children }) {
|
|
102
|
-
React.use(camoxApp.loadAllBlocks());
|
|
103
|
-
return children;
|
|
104
|
-
}
|
|
105
100
|
var EditingActivationBoundary = class extends React.Component {
|
|
106
101
|
state = { failed: false };
|
|
107
102
|
static getDerivedStateFromError() {
|
|
@@ -116,7 +111,7 @@ var EditingActivationBoundary = class extends React.Component {
|
|
|
116
111
|
};
|
|
117
112
|
const subscribeToHydration = () => () => {};
|
|
118
113
|
function PageExperience(t0) {
|
|
119
|
-
const $ = c(
|
|
114
|
+
const $ = c(31);
|
|
120
115
|
const { camoxApp, input, queryClient, studioDocument } = t0;
|
|
121
116
|
const { isAuthenticated, isLoading } = useAuthState();
|
|
122
117
|
const hasHydrated = React.useSyncExternalStore(subscribeToHydration, _temp, _temp2);
|
|
@@ -175,96 +170,75 @@ function PageExperience(t0) {
|
|
|
175
170
|
$[13] = t5;
|
|
176
171
|
} else t5 = $[13];
|
|
177
172
|
const isolatedPage = t5;
|
|
178
|
-
let t6;
|
|
179
|
-
if ($[14] !== input.routeKind || $[15] !== isolatedPage) {
|
|
180
|
-
t6 = input.routeKind ? /* @__PURE__ */ jsx(StudioLoadError, {}) : isolatedPage;
|
|
181
|
-
$[14] = input.routeKind;
|
|
182
|
-
$[15] = isolatedPage;
|
|
183
|
-
$[16] = t6;
|
|
184
|
-
} else t6 = $[16];
|
|
185
|
-
const fallback = t6;
|
|
186
173
|
if (isAuthenticated) {
|
|
187
|
-
let
|
|
188
|
-
if ($[
|
|
189
|
-
|
|
174
|
+
let t6;
|
|
175
|
+
if ($[14] !== camoxApp || $[15] !== hasHydrated || $[16] !== input || $[17] !== queryClient) {
|
|
176
|
+
t6 = hasHydrated ? /* @__PURE__ */ jsx(LazyEditablePageExperience, {
|
|
190
177
|
camoxApp,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
input,
|
|
194
|
-
queryClient
|
|
195
|
-
})
|
|
178
|
+
input,
|
|
179
|
+
queryClient
|
|
196
180
|
}) : null;
|
|
197
|
-
$[
|
|
198
|
-
$[
|
|
199
|
-
$[
|
|
200
|
-
$[
|
|
201
|
-
$[
|
|
202
|
-
} else
|
|
203
|
-
const editor =
|
|
204
|
-
const
|
|
181
|
+
$[14] = camoxApp;
|
|
182
|
+
$[15] = hasHydrated;
|
|
183
|
+
$[16] = input;
|
|
184
|
+
$[17] = queryClient;
|
|
185
|
+
$[18] = t6;
|
|
186
|
+
} else t6 = $[18];
|
|
187
|
+
const editor = t6;
|
|
188
|
+
const t7 = input.routeKind ? loading : studioDocument ? null : isolatedPage;
|
|
189
|
+
let t8;
|
|
190
|
+
if ($[19] === Symbol.for("react.memo_cache_sentinel")) {
|
|
191
|
+
t8 = /* @__PURE__ */ jsx(StudioLoadError, {});
|
|
192
|
+
$[19] = t8;
|
|
193
|
+
} else t8 = $[19];
|
|
205
194
|
let t9;
|
|
206
|
-
if ($[
|
|
207
|
-
t9 =
|
|
208
|
-
fallback:
|
|
209
|
-
children:
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
})
|
|
217
|
-
}),
|
|
218
|
-
children: editor
|
|
195
|
+
if ($[20] !== editor || $[21] !== input.derived || $[22] !== input.routeKind) {
|
|
196
|
+
t9 = /* @__PURE__ */ jsx(EditingActivationBoundary, {
|
|
197
|
+
fallback: t8,
|
|
198
|
+
children: input.routeKind ? /* @__PURE__ */ jsx(React.Suspense, {
|
|
199
|
+
fallback: loading,
|
|
200
|
+
children: editor ?? loading
|
|
201
|
+
}) : /* @__PURE__ */ jsx(PreviewActivation, {
|
|
202
|
+
fallback: /* @__PURE__ */ jsx(EditingFallbackSlot, { layoutPage: !!input.derived }),
|
|
203
|
+
children: editor
|
|
204
|
+
})
|
|
219
205
|
});
|
|
220
|
-
$[
|
|
221
|
-
$[
|
|
222
|
-
$[
|
|
223
|
-
$[
|
|
224
|
-
|
|
225
|
-
$[27] = t9;
|
|
226
|
-
} else t9 = $[27];
|
|
206
|
+
$[20] = editor;
|
|
207
|
+
$[21] = input.derived;
|
|
208
|
+
$[22] = input.routeKind;
|
|
209
|
+
$[23] = t9;
|
|
210
|
+
} else t9 = $[23];
|
|
227
211
|
let t10;
|
|
228
|
-
if ($[
|
|
229
|
-
t10 = /* @__PURE__ */ jsx(
|
|
230
|
-
fallback,
|
|
231
|
-
children: t9
|
|
232
|
-
});
|
|
233
|
-
$[28] = fallback;
|
|
234
|
-
$[29] = t9;
|
|
235
|
-
$[30] = t10;
|
|
236
|
-
} else t10 = $[30];
|
|
237
|
-
let t11;
|
|
238
|
-
if ($[31] !== input || $[32] !== t10 || $[33] !== t8) {
|
|
239
|
-
t11 = /* @__PURE__ */ jsx(RuntimeChromeSlot, {
|
|
212
|
+
if ($[24] !== input || $[25] !== t7 || $[26] !== t9) {
|
|
213
|
+
t10 = /* @__PURE__ */ jsx(RuntimeChromeSlot, {
|
|
240
214
|
input,
|
|
241
|
-
fallback:
|
|
242
|
-
children:
|
|
215
|
+
fallback: t7,
|
|
216
|
+
children: t9
|
|
243
217
|
});
|
|
244
|
-
$[
|
|
245
|
-
$[
|
|
246
|
-
$[
|
|
247
|
-
$[
|
|
248
|
-
} else
|
|
249
|
-
return
|
|
218
|
+
$[24] = input;
|
|
219
|
+
$[25] = t7;
|
|
220
|
+
$[26] = t9;
|
|
221
|
+
$[27] = t10;
|
|
222
|
+
} else t10 = $[27];
|
|
223
|
+
return t10;
|
|
250
224
|
}
|
|
251
225
|
if (studioOnly) return null;
|
|
252
226
|
if (studioDocument) {
|
|
227
|
+
let t6;
|
|
228
|
+
if ($[28] === Symbol.for("react.memo_cache_sentinel")) {
|
|
229
|
+
t6 = { height: "100vh" };
|
|
230
|
+
$[28] = t6;
|
|
231
|
+
} else t6 = $[28];
|
|
253
232
|
let t7;
|
|
254
|
-
if ($[
|
|
255
|
-
t7 =
|
|
256
|
-
|
|
257
|
-
} else t7 = $[35];
|
|
258
|
-
let t8;
|
|
259
|
-
if ($[36] !== isolatedPage) {
|
|
260
|
-
t8 = /* @__PURE__ */ jsx("div", {
|
|
261
|
-
style: t7,
|
|
233
|
+
if ($[29] !== isolatedPage) {
|
|
234
|
+
t7 = /* @__PURE__ */ jsx("div", {
|
|
235
|
+
style: t6,
|
|
262
236
|
children: isolatedPage
|
|
263
237
|
});
|
|
264
|
-
$[
|
|
265
|
-
$[
|
|
266
|
-
} else
|
|
267
|
-
return
|
|
238
|
+
$[29] = isolatedPage;
|
|
239
|
+
$[30] = t7;
|
|
240
|
+
} else t7 = $[30];
|
|
241
|
+
return t7;
|
|
268
242
|
}
|
|
269
243
|
return readOnlyPage;
|
|
270
244
|
}
|
|
@@ -18,7 +18,7 @@ async function hydratePageWithApp(camoxApp) {
|
|
|
18
18
|
...hydrationData,
|
|
19
19
|
dehydratedState: restoreArtifactSources(hydrationData.dehydratedState, root)
|
|
20
20
|
};
|
|
21
|
-
await
|
|
21
|
+
await (input.presentation === "studio" ? loadRuntimeChrome() : camoxApp.loadBlocks(getPageBlockTypes(input.dehydratedState)).catch((error) => console.error("Camox: a block of this page failed to load.", error)));
|
|
22
22
|
const draftScope = createDraftScope({
|
|
23
23
|
queryClient,
|
|
24
24
|
browser: window
|
|
@@ -93,12 +93,13 @@ function PageNavigationProvider({ camoxApp, children, initialInput, queryClient
|
|
|
93
93
|
if (!response.ok) throw new Error(`Navigation failed: ${response.status}`);
|
|
94
94
|
const next = await response.json();
|
|
95
95
|
if (next.pathname !== pathname || !next.dehydratedState) throw new Error("Invalid navigation response");
|
|
96
|
-
await camoxApp.loadBlocks(getPageBlockTypes(next.dehydratedState));
|
|
96
|
+
if (initialInput.presentation !== "studio") await camoxApp.loadBlocks(getPageBlockTypes(next.dehydratedState));
|
|
97
97
|
return next;
|
|
98
98
|
}, [
|
|
99
99
|
camoxApp,
|
|
100
100
|
draftScope,
|
|
101
|
-
initialInput.runtimeBasePath
|
|
101
|
+
initialInput.runtimeBasePath,
|
|
102
|
+
initialInput.presentation
|
|
102
103
|
]);
|
|
103
104
|
const [prefetched] = React.useState(() => createPrefetchCache());
|
|
104
105
|
const headManager = React.useRef(null);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { prefersMarkdown } from "../routes/pageRuntime.js";
|
|
2
2
|
|
|
3
3
|
//#region src/features/runtime/publicCache.ts
|
|
4
|
+
/** The named cache public renders are kept in. */
|
|
5
|
+
const PUBLIC_CACHE_NAME = "camox-public-renders";
|
|
4
6
|
/** How long the edge keeps a render. Live changing replaces its key long before. */
|
|
5
7
|
const EDGE_MAX_AGE = 86400;
|
|
6
8
|
/** Browsers can't be purged, so they ask again each time; the edge answers them. */
|
|
@@ -24,11 +26,21 @@ async function respondThroughPublicCache(request, publicCache, render) {
|
|
|
24
26
|
const liveCacheKey = await publicCache.liveCacheKey();
|
|
25
27
|
if (!liveCacheKey) return render();
|
|
26
28
|
const key = cacheKeyOf(request, `${liveCacheKey}.${publicCache.build}`);
|
|
27
|
-
|
|
29
|
+
let cache;
|
|
30
|
+
let cached;
|
|
31
|
+
try {
|
|
32
|
+
cache = await publicCache.cache();
|
|
33
|
+
cached = await cache.match(withValidators(key, request));
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error("The edge cache couldn't be read: the page renders without it", error);
|
|
36
|
+
return render();
|
|
37
|
+
}
|
|
28
38
|
if (cached) return forVisitors(cached, request);
|
|
29
39
|
const response = await render();
|
|
30
40
|
if (!response || !mayBeShared(response)) return response;
|
|
31
|
-
publicCache.waitUntil(
|
|
41
|
+
publicCache.waitUntil((async () => cache.put(key, forEdge(response.clone())))().catch((error) => {
|
|
42
|
+
console.error("The edge cache couldn't keep a render", error);
|
|
43
|
+
}));
|
|
32
44
|
return forVisitors(response, request);
|
|
33
45
|
}
|
|
34
46
|
/**
|
|
@@ -80,4 +92,4 @@ function forVisitors(response, request) {
|
|
|
80
92
|
}
|
|
81
93
|
|
|
82
94
|
//#endregion
|
|
83
|
-
export { readLiveCacheKey, respondThroughPublicCache };
|
|
95
|
+
export { PUBLIC_CACHE_NAME, readLiveCacheKey, respondThroughPublicCache };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { PUBLISHING_DRAFT_VAR } from "@camox/api-contract/drafts";
|
|
2
|
+
|
|
3
|
+
//#region src/features/runtime/publishingDraft.ts
|
|
4
|
+
/**
|
|
5
|
+
* The Worker a publish with code uploaded carries the publishing draft's ID as a variable
|
|
6
|
+
* (`PUBLISHING_DRAFT_VAR`). Its reads of the API name that draft, so while the draft publishes
|
|
7
|
+
* they see Live overlaid with it, the content its code was previewed with. Once the draft has
|
|
8
|
+
* ended, the API reads Live for them. The variable is the same for every request a Worker
|
|
9
|
+
* serves, so it's kept from the first.
|
|
10
|
+
*/
|
|
11
|
+
const PUBLISHING_DRAFT_GLOBAL = "__CAMOX_PUBLISHING_DRAFT__";
|
|
12
|
+
/** Keeps the publishing draft a Worker's environment names, if it names one. */
|
|
13
|
+
function rememberPublishingDraft(env) {
|
|
14
|
+
const draft = env?.[PUBLISHING_DRAFT_VAR];
|
|
15
|
+
if (typeof draft === "string" && draft) globalThis[PUBLISHING_DRAFT_GLOBAL] = draft;
|
|
16
|
+
}
|
|
17
|
+
/** The draft this Worker publishes, if a publish with code uploaded it. */
|
|
18
|
+
function publishingDraft() {
|
|
19
|
+
return globalThis[PUBLISHING_DRAFT_GLOBAL];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
export { publishingDraft, rememberPublishingDraft };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { CamoxApp } from "../../core/createApp.js";
|
|
2
|
+
//#region src/features/runtime/rendererClient.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Starts the renderer frame's document (ADR 0023): connects to the studio that loaded it, and
|
|
5
|
+
* renders the pages of the frames it is given with the site's code, in a cache of its own.
|
|
6
|
+
*/
|
|
7
|
+
export declare function startRendererWithApp(camoxApp: CamoxApp): Promise<void>;
|
|
8
|
+
//#endregion
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { getDraftReadClient } from "../../lib/api-client.js";
|
|
2
|
+
import { DraftScopeContext, createDraftScope, followUrl } from "../../lib/draft-scope.js";
|
|
3
|
+
import { StudioCacheContext, createStudioCache } from "../../lib/studio-reads.js";
|
|
4
|
+
import { STUDIO_HANDLE_KEY } from "../renderer/protocol.js";
|
|
5
|
+
import { CoreCamoxProvider } from "../provider/CoreCamoxProvider.js";
|
|
6
|
+
import { readHydrationData } from "./hydrationData.js";
|
|
7
|
+
import { CompleteBlockEditingRuntimeProvider } from "../../core/editing/CompleteBlockEditingRuntime.js";
|
|
8
|
+
import { RendererApp } from "../renderer/RendererApp.js";
|
|
9
|
+
import { connectToStudio } from "../renderer/studioConnection.js";
|
|
10
|
+
import { jsx } from "react/jsx-runtime";
|
|
11
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
12
|
+
import { createRoot } from "react-dom/client";
|
|
13
|
+
|
|
14
|
+
//#region src/features/runtime/rendererClient.tsx
|
|
15
|
+
/**
|
|
16
|
+
* Starts the renderer frame's document (ADR 0023): connects to the studio that loaded it, and
|
|
17
|
+
* renders the pages of the frames it is given with the site's code, in a cache of its own.
|
|
18
|
+
*/
|
|
19
|
+
async function startRendererWithApp(camoxApp) {
|
|
20
|
+
const handle = window.frameElement?.[STUDIO_HANDLE_KEY];
|
|
21
|
+
if (!handle) {
|
|
22
|
+
console.error("Camox: the renderer document only runs in the studio's renderer frame.");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const input = readHydrationData();
|
|
26
|
+
if (!input.apiUrl || !input.authenticationUrl || !input.projectUuid) {
|
|
27
|
+
console.error("Camox: the renderer document is missing its API or project.");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const codeLoaded = camoxApp.loadAllBlocks();
|
|
31
|
+
codeLoaded.catch((error) => console.error("Camox: the site's code failed to load.", error));
|
|
32
|
+
const queryClient = new QueryClient();
|
|
33
|
+
const draftScope = createDraftScope({
|
|
34
|
+
queryClient,
|
|
35
|
+
browser: window
|
|
36
|
+
});
|
|
37
|
+
followUrl(draftScope, window.location.href);
|
|
38
|
+
const studioCache = createStudioCache({
|
|
39
|
+
queryClient,
|
|
40
|
+
scope: draftScope,
|
|
41
|
+
api: getDraftReadClient
|
|
42
|
+
});
|
|
43
|
+
const connection = connectToStudio(handle, {
|
|
44
|
+
queryClient,
|
|
45
|
+
studioCache,
|
|
46
|
+
draftScope,
|
|
47
|
+
camoxApp
|
|
48
|
+
});
|
|
49
|
+
const container = document.createElement("div");
|
|
50
|
+
document.body.append(container);
|
|
51
|
+
createRoot(container).render(/* @__PURE__ */ jsx(QueryClientProvider, {
|
|
52
|
+
client: queryClient,
|
|
53
|
+
children: /* @__PURE__ */ jsx(DraftScopeContext.Provider, {
|
|
54
|
+
value: draftScope,
|
|
55
|
+
children: /* @__PURE__ */ jsx(StudioCacheContext.Provider, {
|
|
56
|
+
value: studioCache,
|
|
57
|
+
children: /* @__PURE__ */ jsx(CoreCamoxProvider, {
|
|
58
|
+
apiUrl: input.apiUrl,
|
|
59
|
+
authenticationUrl: input.authenticationUrl,
|
|
60
|
+
camoxApp,
|
|
61
|
+
projectUuid: input.projectUuid,
|
|
62
|
+
initialAuthenticated: true,
|
|
63
|
+
children: /* @__PURE__ */ jsx(CompleteBlockEditingRuntimeProvider, { children: /* @__PURE__ */ jsx(RendererApp, {
|
|
64
|
+
connection,
|
|
65
|
+
camoxApp,
|
|
66
|
+
codeLoaded,
|
|
67
|
+
projectUuid: input.projectUuid,
|
|
68
|
+
runtimeBasePath: input.runtimeBasePath
|
|
69
|
+
}) })
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
export { startRendererWithApp };
|
|
@@ -89,6 +89,8 @@ export interface RuntimeOptions {
|
|
|
89
89
|
developmentDraft?: boolean;
|
|
90
90
|
pageClientEntryUrl?: string;
|
|
91
91
|
studioClientEntryUrl?: string;
|
|
92
|
+
/** The renderer frame's client entry, which renders the studio's pages (ADR 0023). */
|
|
93
|
+
rendererClientEntryUrl?: string;
|
|
92
94
|
getCamoxApp?: () => Promise<CamoxApp>;
|
|
93
95
|
getDocument?: () => Promise<CamoxDocument>;
|
|
94
96
|
projectUuid?: string;
|
|
@@ -107,6 +109,13 @@ export declare function getRuntimePathname(url: string, runtimeBasePath?: string
|
|
|
107
109
|
export declare function isPublicRequest(request: Request, options?: RuntimeOptions): boolean;
|
|
108
110
|
export declare function matchRuntimeRoute(pathname: string): RuntimeRouteMatch;
|
|
109
111
|
export declare function createPageHeadInput(head: PageHead): UseHeadInput;
|
|
112
|
+
/** What the renderer document tells its client: where the API, sign-in and runtime are. */
|
|
113
|
+
export interface RendererDocumentInput {
|
|
114
|
+
apiUrl?: string;
|
|
115
|
+
authenticationUrl?: string;
|
|
116
|
+
projectUuid?: string;
|
|
117
|
+
runtimeBasePath: string;
|
|
118
|
+
}
|
|
110
119
|
export declare function handleCamoxRequest(request: Request, options?: RuntimeOptions): Promise<Response | null>;
|
|
111
120
|
//#endregion
|
|
112
121
|
export {};
|
|
@@ -6,13 +6,14 @@ import { draftNamedBy } from "../../lib/draft-scope.js";
|
|
|
6
6
|
import { RUNTIME_ROUTE_PATHS, matchRuntimeOwnedRoute } from "./runtimeRoutes.js";
|
|
7
7
|
import { reads } from "../../lib/studio-reads.js";
|
|
8
8
|
import { asCollectionItem, recordMarkdownTemplate, recordPageValues } from "../../core/recordItem.js";
|
|
9
|
-
import { buildLayoutContext, curatedLayoutContext } from "../../core/layoutContext.js";
|
|
10
9
|
import { PLATFORM_SCRIPT } from "../../lib/platform.js";
|
|
11
10
|
import { STUDIO_ASSETS_PATH, STUDIO_CANVAS_PATH, STUDIO_CONTENT_PATH, STUDIO_SETTINGS_PATH, matchCollectionContentPath } from "../studio/routes.js";
|
|
12
11
|
import { STUDIO_THEME_SCRIPT } from "../studio/studioTheme.js";
|
|
13
12
|
import { DefaultPageImage, renderPageImage } from "../og/pageImage.js";
|
|
14
13
|
import { discoverLayoutPages, matchDerivedLayout } from "../../core/derivedRoutes.js";
|
|
14
|
+
import { buildLayoutContext, curatedLayoutContext } from "../../core/layoutContext.js";
|
|
15
15
|
import { buildCamoxPageHead, buildFaviconUrl, buildPageHead, buildPageMeta, createServerApiClient, createServerStudioCache, curatedMarkdownResponse, declarePage, isNotFoundError, loadCamoxPageForRequest, parseMarkdownPath, prefersMarkdown, readDraftOrLive, readPageMarkdown, readScopeLocales, renderedHref, resolveMemberProject, withFrontMatter } from "../routes/pageRuntime.js";
|
|
16
|
+
import { assetPrefix } from "./assetPrefix.js";
|
|
16
17
|
import { alternatesByPage, findAlternates, hreflangLinks } from "./alternates.js";
|
|
17
18
|
import { configuredProviders, renderAnalyticsScripts } from "./analytics.js";
|
|
18
19
|
import { omitArtifactSources } from "./artifactSources.js";
|
|
@@ -459,8 +460,9 @@ function renderHead(document, pageHead, stylesheetUrl, studioStylesheetUrl, loca
|
|
|
459
460
|
return renderSSRHead(head);
|
|
460
461
|
}
|
|
461
462
|
/**
|
|
462
|
-
*
|
|
463
|
-
* script avoids a request waterfall.
|
|
463
|
+
* A visitor's browser loads a page's blocks before hydrating it. Preloading them alongside the
|
|
464
|
+
* entry script avoids a request waterfall. A member's document loads none: only its renderer
|
|
465
|
+
* frame runs the site's code.
|
|
464
466
|
*/
|
|
465
467
|
function renderBlockPreloads(options, dehydratedState) {
|
|
466
468
|
const preloads = options.blockPreloads;
|
|
@@ -470,24 +472,40 @@ function renderBlockPreloads(options, dehydratedState) {
|
|
|
470
472
|
const modulePath = preloads.modulePaths[type];
|
|
471
473
|
if (!modulePath) continue;
|
|
472
474
|
if (!preloads.chunks) urls.add(modulePath);
|
|
473
|
-
for (const chunk of preloads.chunks?.[modulePath] ?? []) urls.add(
|
|
475
|
+
for (const chunk of preloads.chunks?.[modulePath] ?? []) urls.add(`${assetPrefix()}${chunk}`);
|
|
474
476
|
}
|
|
475
477
|
return [...urls].map((url) => `<link rel="modulepreload" href="${escapeXml(url)}">`).join("");
|
|
476
478
|
}
|
|
479
|
+
/**
|
|
480
|
+
* The renderer frame's document (ADR 0023): an empty document running the renderer's client,
|
|
481
|
+
* which renders the studio's pages into its frames, reading them itself. It holds no content, but
|
|
482
|
+
* is only ever the studio's, so no shared cache keeps it.
|
|
483
|
+
*/
|
|
484
|
+
function createRendererDocumentResponse(options) {
|
|
485
|
+
const input = {
|
|
486
|
+
apiUrl: options.apiUrl,
|
|
487
|
+
authenticationUrl: options.authenticationUrl,
|
|
488
|
+
projectUuid: options.projectUuid,
|
|
489
|
+
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath)
|
|
490
|
+
};
|
|
491
|
+
return new Response(`<!doctype html><html><head><meta charset="utf-8"><meta name="robots" content="noindex"><title>Camox renderer</title><script id="__CAMOX_DATA__" type="application/json">${serializeJsonForHtml(input)}<\/script></head><body>${renderClientEntryScript(options.rendererClientEntryUrl)}</body></html>`, { headers: {
|
|
492
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
493
|
+
"Cache-Control": "private, no-store",
|
|
494
|
+
"X-Robots-Tag": "noindex"
|
|
495
|
+
} });
|
|
496
|
+
}
|
|
477
497
|
function renderClientEntryScript(src) {
|
|
478
498
|
if (!src) return "";
|
|
479
499
|
return `<script type="module" src="${escapeXml(src)}"><\/script>`;
|
|
480
500
|
}
|
|
481
|
-
|
|
501
|
+
/**
|
|
502
|
+
* The site document the studio's frames load: the site's head and an empty root, which the
|
|
503
|
+
* renderer frame renders the page into. The studio never renders the site's code itself.
|
|
504
|
+
*/
|
|
505
|
+
function preparePreviewDocument(input, options, document) {
|
|
482
506
|
const siteHead = renderHead(document, createPageHeadInput(input.head ?? {}), options.stylesheetUrl, void 0, input.locale);
|
|
483
507
|
const href = new URL(withRuntimeBasePath(input.pathname, options.runtimeBasePath), input.href).href;
|
|
484
|
-
|
|
485
|
-
...input,
|
|
486
|
-
href,
|
|
487
|
-
presentation: "public",
|
|
488
|
-
previewDocument: void 0
|
|
489
|
-
}) : "");
|
|
490
|
-
input.previewDocument = `<!doctype html><html${siteHead.htmlAttrs}><head><base href="${escapeXml(href)}">${siteHead.headTags}</head><body${siteHead.bodyAttrs}>${siteHead.bodyTagsOpen}<div id="root" data-camox-preview-root>${siteHtml}</div>${siteHead.bodyTags}</body></html>`;
|
|
508
|
+
input.previewDocument = `<!doctype html><html${siteHead.htmlAttrs}><head><base href="${escapeXml(href)}">${siteHead.headTags}</head><body${siteHead.bodyAttrs}>${siteHead.bodyTagsOpen}<div id="root" data-camox-preview-root></div>${siteHead.bodyTags}</body></html>`;
|
|
491
509
|
}
|
|
492
510
|
/**
|
|
493
511
|
* The render input a page's HTML embeds for hydration. Visitors load public pages, whose HTML
|
|
@@ -832,7 +850,7 @@ async function createPageHtmlResponse({ options, pathname, located, request, pag
|
|
|
832
850
|
locale: located.locale,
|
|
833
851
|
locales: located.locales
|
|
834
852
|
};
|
|
835
|
-
|
|
853
|
+
preparePreviewDocument(pageRenderInput, options, document);
|
|
836
854
|
const appHtml = await options.renderPage(pageRenderInput);
|
|
837
855
|
const renderedHead = renderHead(studio ? {} : document, createPageHeadInput(head), studio ? void 0 : options.stylesheetUrl, studio ? options.studioStylesheetUrl : void 0, studio ? void 0 : located.locale);
|
|
838
856
|
return new Response(`<!doctype html>
|
|
@@ -841,7 +859,7 @@ async function createPageHtmlResponse({ options, pathname, located, request, pag
|
|
|
841
859
|
${renderStudioThemeScript(studio)}
|
|
842
860
|
${renderedHead.headTags}
|
|
843
861
|
${renderAnalyticsScripts(analytics)}
|
|
844
|
-
${renderBlockPreloads(options, dehydratedState)}
|
|
862
|
+
${studio ? "" : renderBlockPreloads(options, dehydratedState)}
|
|
845
863
|
<script id="__CAMOX_DATA__" type="application/json">${serializeJsonForHtml(hydrationInputFor(pageRenderInput, appHtml))}<\/script>
|
|
846
864
|
</head>
|
|
847
865
|
<body${renderedHead.bodyAttrs}>
|
|
@@ -1116,7 +1134,7 @@ async function createDerivedResponse(options, pathname, located, request, { data
|
|
|
1116
1134
|
}
|
|
1117
1135
|
};
|
|
1118
1136
|
const document = await options.getDocument?.() ?? {};
|
|
1119
|
-
|
|
1137
|
+
preparePreviewDocument(input, options, document);
|
|
1120
1138
|
if (dataOnly) return Response.json(input, { headers: {
|
|
1121
1139
|
...isPrivateRender(request, options) ? { "Cache-Control": "private, no-store" } : {},
|
|
1122
1140
|
Vary: "Cookie",
|
|
@@ -1124,7 +1142,7 @@ async function createDerivedResponse(options, pathname, located, request, { data
|
|
|
1124
1142
|
} });
|
|
1125
1143
|
const html = await options.renderPage(input);
|
|
1126
1144
|
const head = renderHead(studio ? {} : document, createPageHeadInput(pageHead), studio ? void 0 : options.stylesheetUrl, studio ? options.studioStylesheetUrl : void 0, studio ? void 0 : located.locale);
|
|
1127
|
-
return new Response(`<!doctype html><html${head.htmlAttrs}><head>${renderStudioThemeScript(studio)}${head.headTags}${renderAnalyticsScripts(analytics)}${renderBlockPreloads(options, input.dehydratedState)}<script id="__CAMOX_DATA__" type="application/json">${serializeJsonForHtml(hydrationInputFor(input, html))}<\/script></head><body${head.bodyAttrs}>${head.bodyTagsOpen}<div id="root">${html}</div>${head.bodyTags}${renderClientEntryScript(options.pageClientEntryUrl)}</body></html>`, { headers: {
|
|
1145
|
+
return new Response(`<!doctype html><html${head.htmlAttrs}><head>${renderStudioThemeScript(studio)}${head.headTags}${renderAnalyticsScripts(analytics)}${studio ? "" : renderBlockPreloads(options, input.dehydratedState)}<script id="__CAMOX_DATA__" type="application/json">${serializeJsonForHtml(hydrationInputFor(input, html))}<\/script></head><body${head.bodyAttrs}>${head.bodyTagsOpen}<div id="root">${html}</div>${head.bodyTags}${renderClientEntryScript(options.pageClientEntryUrl)}</body></html>`, { headers: {
|
|
1128
1146
|
"Content-Type": "text/html; charset=utf-8",
|
|
1129
1147
|
Vary: "Cookie",
|
|
1130
1148
|
Link: llmsTxtLinkHeader(options),
|
|
@@ -1284,7 +1302,7 @@ async function createPageDataResponse({ options, pathname, located, request, pag
|
|
|
1284
1302
|
head,
|
|
1285
1303
|
dehydratedState
|
|
1286
1304
|
};
|
|
1287
|
-
|
|
1305
|
+
preparePreviewDocument(input, options, await options.getDocument?.() ?? {});
|
|
1288
1306
|
return Response.json(input, { headers });
|
|
1289
1307
|
} catch (error) {
|
|
1290
1308
|
if (isNotFoundError(error)) {
|
|
@@ -1313,6 +1331,7 @@ async function handleCamoxRequest(request, options = {}) {
|
|
|
1313
1331
|
});
|
|
1314
1332
|
}
|
|
1315
1333
|
if (match.kind === "og") return createOgResponse(request, options);
|
|
1334
|
+
if (match.kind === "renderer") return createRendererDocumentResponse(options);
|
|
1316
1335
|
if (match.kind === "logo") return createProjectLogoResponse(request, options);
|
|
1317
1336
|
if (match.kind === "sitemap") return createSitemapResponse(request, options);
|
|
1318
1337
|
if (match.kind === "llms-txt") return createLlmsTxtResponse(request, options);
|
|
@@ -10,6 +10,8 @@ export declare const RUNTIME_ROUTE_PATHS: {
|
|
|
10
10
|
readonly data: "/_camox/data";
|
|
11
11
|
readonly health: "/_camox/health";
|
|
12
12
|
readonly registry: "/_camox/registry";
|
|
13
|
+
/** The renderer frame's document (ADR 0023), which the studio loads to render its pages. */
|
|
14
|
+
readonly renderer: "/_camox/renderer";
|
|
13
15
|
readonly routes: "/_camox/routes";
|
|
14
16
|
readonly logo: "/_camox/logo.png";
|
|
15
17
|
readonly og: "/og";
|
|
@@ -10,6 +10,8 @@ const RUNTIME_ROUTE_PATHS = {
|
|
|
10
10
|
data: "/_camox/data",
|
|
11
11
|
health: "/_camox/health",
|
|
12
12
|
registry: "/_camox/registry",
|
|
13
|
+
/** The renderer frame's document (ADR 0023), which the studio loads to render its pages. */
|
|
14
|
+
renderer: "/_camox/renderer",
|
|
13
15
|
routes: "/_camox/routes",
|
|
14
16
|
logo: "/_camox/logo.png",
|
|
15
17
|
og: "/og",
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
import { WorkerEntrypoint } from "cloudflare:workers";
|
|
1
2
|
//#region src/features/runtime/worker.d.ts
|
|
2
3
|
/** What the Worker gets with each request: only `waitUntil` matters here. */
|
|
3
4
|
interface WorkerContext {
|
|
4
5
|
waitUntil(promise: Promise<unknown>): void;
|
|
5
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* The website's Definition Set, for the Worker that loads a revision, which calls it by the name
|
|
9
|
+
* `DEFINITION_SET_ENTRYPOINT` holds. A named `WorkerEntrypoint` takes RPC calls only: no HTTP
|
|
10
|
+
* request reaches it.
|
|
11
|
+
*/
|
|
12
|
+
export declare class CamoxDefinitionSet extends WorkerEntrypoint {
|
|
13
|
+
read(): Promise<unknown>;
|
|
14
|
+
}
|
|
6
15
|
declare const _default: {
|
|
7
|
-
fetch(request: Request,
|
|
16
|
+
fetch(request: Request, env?: unknown, context?: WorkerContext): Promise<Response>;
|
|
8
17
|
};
|
|
9
18
|
//#endregion
|
|
10
19
|
export { _default as default };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { rememberPublishingDraft } from "./publishingDraft.js";
|
|
1
2
|
import { fetchLocalhostOnLoopback } from "./loopbackFetch.js";
|
|
2
|
-
import { readLiveCacheKey, respondThroughPublicCache } from "./publicCache.js";
|
|
3
|
+
import { PUBLIC_CACHE_NAME, readLiveCacheKey, respondThroughPublicCache } from "./publicCache.js";
|
|
3
4
|
import { shouldHandleRequest, withDevHtml } from "./workerRequest.js";
|
|
4
|
-
import {
|
|
5
|
+
import { WorkerEntrypoint } from "cloudflare:workers";
|
|
6
|
+
import { devHtml, handleCamoxRequest, isPublicRequest, publicCacheSource, readDefinitionSet, runtimeBasePath } from "virtual:camox/server";
|
|
5
7
|
|
|
6
8
|
//#region src/features/runtime/worker.ts
|
|
7
9
|
/**
|
|
@@ -19,17 +21,28 @@ function notFound() {
|
|
|
19
21
|
* its context can't keep anything past the response.
|
|
20
22
|
*/
|
|
21
23
|
function respond(request, context) {
|
|
22
|
-
const
|
|
23
|
-
if (devHtml || !context || !
|
|
24
|
+
const caches = globalThis.caches;
|
|
25
|
+
if (devHtml || !context || !caches || !isPublicRequest(request)) return handleCamoxRequest(request);
|
|
24
26
|
const { apiUrl, projectUuid, build } = publicCacheSource;
|
|
25
27
|
return respondThroughPublicCache(request, {
|
|
26
|
-
cache,
|
|
28
|
+
cache: () => caches.open(PUBLIC_CACHE_NAME),
|
|
27
29
|
liveCacheKey: () => readLiveCacheKey(apiUrl, projectUuid),
|
|
28
30
|
build,
|
|
29
31
|
waitUntil: (promise) => context.waitUntil(promise)
|
|
30
32
|
}, () => handleCamoxRequest(request));
|
|
31
33
|
}
|
|
32
|
-
|
|
34
|
+
/**
|
|
35
|
+
* The website's Definition Set, for the Worker that loads a revision, which calls it by the name
|
|
36
|
+
* `DEFINITION_SET_ENTRYPOINT` holds. A named `WorkerEntrypoint` takes RPC calls only: no HTTP
|
|
37
|
+
* request reaches it.
|
|
38
|
+
*/
|
|
39
|
+
var CamoxDefinitionSet = class extends WorkerEntrypoint {
|
|
40
|
+
read() {
|
|
41
|
+
return readDefinitionSet();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var worker_default = { async fetch(request, env, context) {
|
|
45
|
+
rememberPublishingDraft(env);
|
|
33
46
|
if (!shouldHandleRequest(request, runtimeBasePath)) return notFound();
|
|
34
47
|
const response = await respond(request, context);
|
|
35
48
|
if (!response) return notFound();
|
|
@@ -37,4 +50,4 @@ var worker_default = { async fetch(request, _env, context) {
|
|
|
37
50
|
} };
|
|
38
51
|
|
|
39
52
|
//#endregion
|
|
40
|
-
export { worker_default as default };
|
|
53
|
+
export { CamoxDefinitionSet, worker_default as default };
|