camox 0.35.0 → 0.36.1
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/features/preview/CamoxPreview.js +127 -95
- package/dist/features/preview/components/AssetLightbox.js +1 -1
- package/dist/features/preview/components/PageEditorSidebar.js +35 -25
- package/dist/features/preview/components/PageInfoSidebar.js +1 -1
- package/dist/features/preview/components/PageNavigatorSidebar.js +241 -313
- package/dist/features/preview/components/PreviewToolbar.js +85 -55
- package/dist/features/preview/previewStore.js +13 -6
- package/dist/features/routes/pageRuntime.js +240 -2
- package/dist/features/runtime/runtime.d.ts +30 -1
- package/dist/features/runtime/runtime.js +343 -4
- package/dist/features/runtime/studioNavigation.js +7 -2
- package/dist/features/studio/components/Navbar.js +5 -5
- package/dist/features/studio/components/ProjectMenu.js +5 -5
- package/dist/features/vite/vite.d.ts +1 -1
- package/dist/features/vite/vite.js +2 -2
- package/dist/lib/telemetry-client.js +2 -2
- package/dist/lib/telemetry.js +24 -1
- package/dist/studio.css +1 -1
- package/package.json +4 -4
|
@@ -8,25 +8,26 @@ import { Button } from "@camox/ui/button";
|
|
|
8
8
|
import { Monitor, Smartphone, Tablet, X } from "lucide-react";
|
|
9
9
|
import { Label } from "@camox/ui/label";
|
|
10
10
|
import { ButtonGroup } from "@camox/ui/button-group";
|
|
11
|
-
import { Switch } from "@camox/ui/switch";
|
|
12
11
|
import * as Tooltip$1 from "@camox/ui/tooltip";
|
|
12
|
+
import { Switch } from "@camox/ui/switch";
|
|
13
13
|
import { FloatingToolbar } from "@camox/ui/floating-toolbar";
|
|
14
14
|
import { Toggle } from "@camox/ui/toggle";
|
|
15
15
|
|
|
16
16
|
//#region src/features/preview/components/PreviewToolbar.tsx
|
|
17
17
|
const PreviewToolbar = (t0) => {
|
|
18
|
-
const $ = c(
|
|
19
|
-
if ($[0] !== "
|
|
20
|
-
for (let $i = 0; $i <
|
|
21
|
-
$[0] = "
|
|
18
|
+
const $ = c(74);
|
|
19
|
+
if ($[0] !== "88648f3760cc744b4acae6ce6f9f000de00b69d7e7f65268463e98389ba90d90") {
|
|
20
|
+
for (let $i = 0; $i < 74; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
21
|
+
$[0] = "88648f3760cc744b4acae6ce6f9f000de00b69d7e7f65268463e98389ba90d90";
|
|
22
22
|
}
|
|
23
|
-
const { onEditModeChange } = t0;
|
|
23
|
+
const { onEditModeChange, pageStatus, hasLiveVersion } = t0;
|
|
24
24
|
const isEditMode = useSelector(previewStore, _temp);
|
|
25
25
|
const isToolbarHidden = useSelector(previewStore, _temp2);
|
|
26
|
-
const
|
|
27
|
-
const
|
|
26
|
+
const peekedBlock = useSelector(previewStore, _temp3);
|
|
27
|
+
const actions = useSelector(actionsStore, _temp4);
|
|
28
|
+
const viewportMode = useSelector(previewStore, _temp5);
|
|
28
29
|
const editModeActionId = isEditMode ? "exit-edit-mode" : "enter-edit-mode";
|
|
29
|
-
if (isToolbarHidden) return null;
|
|
30
|
+
if (isToolbarHidden || peekedBlock) return null;
|
|
30
31
|
let t1;
|
|
31
32
|
if ($[1] !== onEditModeChange) {
|
|
32
33
|
t1 = (checked) => {
|
|
@@ -70,7 +71,7 @@ const PreviewToolbar = (t0) => {
|
|
|
70
71
|
let t5;
|
|
71
72
|
if ($[11] !== t2 || $[12] !== t4) {
|
|
72
73
|
t5 = /* @__PURE__ */ jsxs("div", {
|
|
73
|
-
className: "flex items-center gap-2 px-2",
|
|
74
|
+
className: "flex shrink-0 items-center gap-2 px-2",
|
|
74
75
|
children: [t2, t4]
|
|
75
76
|
});
|
|
76
77
|
$[11] = t2;
|
|
@@ -246,65 +247,88 @@ const PreviewToolbar = (t0) => {
|
|
|
246
247
|
$[53] = t30;
|
|
247
248
|
} else t30 = $[53];
|
|
248
249
|
let t31;
|
|
249
|
-
if ($[54] !==
|
|
250
|
-
t31 = /* @__PURE__ */ jsx(Button, {
|
|
250
|
+
if ($[54] !== hasLiveVersion || $[55] !== pageStatus) {
|
|
251
|
+
t31 = pageStatus && /* @__PURE__ */ jsx(Button, {
|
|
252
|
+
type: "button",
|
|
253
|
+
variant: "outline",
|
|
254
|
+
disabled: pageStatus === "published" || !hasLiveVersion,
|
|
255
|
+
onClick: _temp6,
|
|
256
|
+
children: "View live site"
|
|
257
|
+
});
|
|
258
|
+
$[54] = hasLiveVersion;
|
|
259
|
+
$[55] = pageStatus;
|
|
260
|
+
$[56] = t31;
|
|
261
|
+
} else t31 = $[56];
|
|
262
|
+
let t32;
|
|
263
|
+
if ($[57] !== isEditMode) {
|
|
264
|
+
t32 = /* @__PURE__ */ jsx(Button, {
|
|
251
265
|
type: "button",
|
|
252
266
|
variant: "ghost",
|
|
253
267
|
size: "icon",
|
|
254
268
|
disabled: isEditMode,
|
|
255
|
-
onClick:
|
|
269
|
+
onClick: _temp7,
|
|
256
270
|
"aria-label": "Hide toolbar",
|
|
257
271
|
className: "text-muted-foreground"
|
|
258
272
|
});
|
|
259
|
-
$[
|
|
260
|
-
$[
|
|
261
|
-
} else
|
|
262
|
-
let t32;
|
|
263
|
-
if ($[56] === Symbol.for("react.memo_cache_sentinel")) {
|
|
264
|
-
t32 = /* @__PURE__ */ jsx(X, {});
|
|
265
|
-
$[56] = t32;
|
|
266
|
-
} else t32 = $[56];
|
|
273
|
+
$[57] = isEditMode;
|
|
274
|
+
$[58] = t32;
|
|
275
|
+
} else t32 = $[58];
|
|
267
276
|
let t33;
|
|
268
|
-
if ($[57] !== t31) {
|
|
269
|
-
t33 = /* @__PURE__ */ jsx(Tooltip$1.TooltipTrigger, {
|
|
270
|
-
render: t31,
|
|
271
|
-
children: t32
|
|
272
|
-
});
|
|
273
|
-
$[57] = t31;
|
|
274
|
-
$[58] = t33;
|
|
275
|
-
} else t33 = $[58];
|
|
276
|
-
let t34;
|
|
277
277
|
if ($[59] === Symbol.for("react.memo_cache_sentinel")) {
|
|
278
|
-
|
|
279
|
-
$[59] =
|
|
280
|
-
} else
|
|
278
|
+
t33 = /* @__PURE__ */ jsx(X, {});
|
|
279
|
+
$[59] = t33;
|
|
280
|
+
} else t33 = $[59];
|
|
281
|
+
let t34;
|
|
282
|
+
if ($[60] !== t32) {
|
|
283
|
+
t34 = /* @__PURE__ */ jsx(Tooltip$1.TooltipTrigger, {
|
|
284
|
+
render: t32,
|
|
285
|
+
children: t33
|
|
286
|
+
});
|
|
287
|
+
$[60] = t32;
|
|
288
|
+
$[61] = t34;
|
|
289
|
+
} else t34 = $[61];
|
|
281
290
|
let t35;
|
|
282
|
-
if ($[
|
|
283
|
-
t35 = /* @__PURE__ */
|
|
284
|
-
$[
|
|
285
|
-
|
|
286
|
-
} else t35 = $[61];
|
|
291
|
+
if ($[62] === Symbol.for("react.memo_cache_sentinel")) {
|
|
292
|
+
t35 = /* @__PURE__ */ jsx(Tooltip$1.TooltipContent, { children: "Hide toolbar" });
|
|
293
|
+
$[62] = t35;
|
|
294
|
+
} else t35 = $[62];
|
|
287
295
|
let t36;
|
|
288
|
-
if ($[
|
|
289
|
-
t36 = /* @__PURE__ */ jsxs(
|
|
290
|
-
|
|
291
|
-
children: [t30, t35]
|
|
292
|
-
});
|
|
293
|
-
$[62] = t30;
|
|
294
|
-
$[63] = t35;
|
|
296
|
+
if ($[63] !== t34) {
|
|
297
|
+
t36 = /* @__PURE__ */ jsxs(Tooltip$1.Tooltip, { children: [t34, t35] });
|
|
298
|
+
$[63] = t34;
|
|
295
299
|
$[64] = t36;
|
|
296
300
|
} else t36 = $[64];
|
|
297
301
|
let t37;
|
|
298
|
-
if ($[65] !==
|
|
299
|
-
t37 = /* @__PURE__ */ jsxs(
|
|
300
|
-
className: "
|
|
301
|
-
children: [
|
|
302
|
+
if ($[65] !== t31 || $[66] !== t36) {
|
|
303
|
+
t37 = /* @__PURE__ */ jsxs("div", {
|
|
304
|
+
className: "flex items-center gap-2",
|
|
305
|
+
children: [t31, t36]
|
|
302
306
|
});
|
|
303
|
-
$[65] =
|
|
304
|
-
$[66] =
|
|
307
|
+
$[65] = t31;
|
|
308
|
+
$[66] = t36;
|
|
305
309
|
$[67] = t37;
|
|
306
310
|
} else t37 = $[67];
|
|
307
|
-
|
|
311
|
+
let t38;
|
|
312
|
+
if ($[68] !== t30 || $[69] !== t37) {
|
|
313
|
+
t38 = /* @__PURE__ */ jsxs("div", {
|
|
314
|
+
className: "flex shrink-0 items-center gap-8 self-stretch",
|
|
315
|
+
children: [t30, t37]
|
|
316
|
+
});
|
|
317
|
+
$[68] = t30;
|
|
318
|
+
$[69] = t37;
|
|
319
|
+
$[70] = t38;
|
|
320
|
+
} else t38 = $[70];
|
|
321
|
+
let t39;
|
|
322
|
+
if ($[71] !== t38 || $[72] !== t5) {
|
|
323
|
+
t39 = /* @__PURE__ */ jsxs(FloatingToolbar, {
|
|
324
|
+
className: "bottom-2 w-max justify-between gap-8 transition-none",
|
|
325
|
+
children: [t5, t38]
|
|
326
|
+
});
|
|
327
|
+
$[71] = t38;
|
|
328
|
+
$[72] = t5;
|
|
329
|
+
$[73] = t39;
|
|
330
|
+
} else t39 = $[73];
|
|
331
|
+
return t39;
|
|
308
332
|
};
|
|
309
333
|
function _temp(state) {
|
|
310
334
|
return state.context.isEditMode;
|
|
@@ -313,12 +337,18 @@ function _temp2(state_0) {
|
|
|
313
337
|
return state_0.context.isToolbarHidden;
|
|
314
338
|
}
|
|
315
339
|
function _temp3(state_1) {
|
|
316
|
-
return state_1.context.
|
|
340
|
+
return state_1.context.peekedBlock;
|
|
317
341
|
}
|
|
318
342
|
function _temp4(state_2) {
|
|
319
|
-
return state_2.context.
|
|
343
|
+
return state_2.context.actions;
|
|
344
|
+
}
|
|
345
|
+
function _temp5(state_3) {
|
|
346
|
+
return state_3.context.viewportMode;
|
|
347
|
+
}
|
|
348
|
+
function _temp6() {
|
|
349
|
+
return previewStore.send({ type: "viewLivePage" });
|
|
320
350
|
}
|
|
321
|
-
function
|
|
351
|
+
function _temp7() {
|
|
322
352
|
return previewStore.send({ type: "hideToolbar" });
|
|
323
353
|
}
|
|
324
354
|
|
|
@@ -26,7 +26,6 @@ const previewStore = createStore({
|
|
|
26
26
|
context: {
|
|
27
27
|
isEditMode: false,
|
|
28
28
|
isToolbarHidden: false,
|
|
29
|
-
isSidebarOpen: true,
|
|
30
29
|
isPageEditorSidebarOpen: false,
|
|
31
30
|
isAddBlockSidebarOpen: false,
|
|
32
31
|
addBlockSource: null,
|
|
@@ -64,17 +63,25 @@ const previewStore = createStore({
|
|
|
64
63
|
return {
|
|
65
64
|
...context,
|
|
66
65
|
isEditMode: true,
|
|
67
|
-
|
|
66
|
+
isToolbarHidden: false
|
|
68
67
|
};
|
|
69
68
|
},
|
|
70
69
|
hideToolbar: (context) => ({
|
|
71
70
|
...context,
|
|
72
71
|
isToolbarHidden: true
|
|
73
72
|
}),
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
viewLivePage: (context, _, enqueue) => {
|
|
74
|
+
enqueue.effect(() => {
|
|
75
|
+
if (context.isEditMode) trackClientEvent("edit_mode_toggled", { enabled: false });
|
|
76
|
+
toast("Viewing live version of the site");
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
...context,
|
|
80
|
+
isEditMode: false,
|
|
81
|
+
isToolbarHidden: true,
|
|
82
|
+
previewSource: "live"
|
|
83
|
+
};
|
|
84
|
+
},
|
|
78
85
|
setViewportMode: (context, event) => {
|
|
79
86
|
if (context.viewportMode === event.mode) return context;
|
|
80
87
|
return {
|
|
@@ -1,6 +1,244 @@
|
|
|
1
|
-
import "../../lib/telemetry.js";
|
|
1
|
+
import { trackEvent } from "../../lib/telemetry.js";
|
|
2
2
|
import { queryKeys } from "@camox/api-contract/query-keys";
|
|
3
3
|
import { createORPCClient } from "@orpc/client";
|
|
4
4
|
import { RPCLink } from "@orpc/client/fetch";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
//#region src/features/routes/pageRuntime.ts
|
|
7
|
+
function parseQuality(part) {
|
|
8
|
+
const match = part.match(/;\s*q=([0-9.]+)/);
|
|
9
|
+
return match ? parseFloat(match[1]) : 1;
|
|
10
|
+
}
|
|
11
|
+
function prefersMarkdown(accept) {
|
|
12
|
+
let markdownQ = -1;
|
|
13
|
+
let htmlQ = -1;
|
|
14
|
+
for (const part of accept.split(",")) {
|
|
15
|
+
const trimmed = part.trim();
|
|
16
|
+
if (trimmed.startsWith("text/markdown")) markdownQ = parseQuality(trimmed);
|
|
17
|
+
else if (trimmed.startsWith("text/html")) htmlQ = parseQuality(trimmed);
|
|
18
|
+
}
|
|
19
|
+
return markdownQ > 0 && markdownQ >= htmlQ;
|
|
20
|
+
}
|
|
21
|
+
function createServerApiClient(apiUrl, environmentName, options) {
|
|
22
|
+
const headers = {};
|
|
23
|
+
if (environmentName) headers["x-environment-name"] = environmentName;
|
|
24
|
+
return createORPCClient(new RPCLink({
|
|
25
|
+
url: `${apiUrl}/rpc`,
|
|
26
|
+
headers,
|
|
27
|
+
fetch: (request, init) => {
|
|
28
|
+
if (options?.authCookieHeader && request instanceof Request) request.headers.set("Better-Auth-Cookie", options.authCookieHeader);
|
|
29
|
+
return fetch(request, init);
|
|
30
|
+
}
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
function getErrorDetails(error) {
|
|
34
|
+
if (!error || typeof error !== "object") return { message: String(error) };
|
|
35
|
+
const errorRecord = error;
|
|
36
|
+
return {
|
|
37
|
+
code: typeof errorRecord.code === "string" ? errorRecord.code : void 0,
|
|
38
|
+
status: typeof errorRecord.status === "number" ? errorRecord.status : void 0,
|
|
39
|
+
message: error instanceof Error ? error.message : typeof errorRecord.message === "string" ? errorRecord.message : ""
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function isAuthSessionError(error) {
|
|
43
|
+
const { code, status, message } = getErrorDetails(error);
|
|
44
|
+
if (code === "UNAUTHORIZED" || code === "FORBIDDEN") return true;
|
|
45
|
+
if (status === 401 || status === 403) return true;
|
|
46
|
+
const lowerMessage = message.toLowerCase();
|
|
47
|
+
return lowerMessage.includes("unauthorized") || lowerMessage.includes("forbidden") || lowerMessage.includes("invalid session") || lowerMessage.includes("session expired") || lowerMessage.includes("expired session");
|
|
48
|
+
}
|
|
49
|
+
function isNotFoundError(error) {
|
|
50
|
+
const { code, status } = getErrorDetails(error);
|
|
51
|
+
return code === "NOT_FOUND" || status === 404;
|
|
52
|
+
}
|
|
53
|
+
function visitForFileIds(value, ids) {
|
|
54
|
+
if (value == null || typeof value !== "object") return;
|
|
55
|
+
if ("_fileId" in value && typeof value._fileId === "number") {
|
|
56
|
+
ids.add(value._fileId);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
for (const item of value) visitForFileIds(item, ids);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
for (const child of Object.values(value)) visitForFileIds(child, ids);
|
|
64
|
+
}
|
|
65
|
+
function collectFileIdsFromContent(content, ids) {
|
|
66
|
+
for (const value of Object.values(content)) visitForFileIds(value, ids);
|
|
67
|
+
}
|
|
68
|
+
function seedBlockCaches(queryClient, pageData, source = "draft") {
|
|
69
|
+
const filesById = new Map(pageData.files.map((f) => [f.id, f]));
|
|
70
|
+
for (const block of pageData.blocks) {
|
|
71
|
+
const blockItems = pageData.repeatableItems.filter((i) => i.blockId === block.id);
|
|
72
|
+
const fileIds = /* @__PURE__ */ new Set();
|
|
73
|
+
collectFileIdsFromContent(block.content, fileIds);
|
|
74
|
+
for (const item of blockItems) collectFileIdsFromContent(item.content, fileIds);
|
|
75
|
+
const blockFiles = [...fileIds].map((id) => filesById.get(id)).filter((f) => f != null);
|
|
76
|
+
queryClient.setQueryData(queryKeys.blocks.get(block.id, source), {
|
|
77
|
+
block,
|
|
78
|
+
repeatableItems: blockItems,
|
|
79
|
+
files: blockFiles
|
|
80
|
+
});
|
|
81
|
+
for (const file of blockFiles) queryClient.setQueryData(queryKeys.files.get(file.id), file);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function loadCamoxPage({ apiUrl, authCookieHeader, environmentName, pathname, projectSlug, queryClient, source }) {
|
|
85
|
+
const serverApi = createServerApiClient(apiUrl, environmentName, { authCookieHeader });
|
|
86
|
+
return queryClient.ensureQueryData({
|
|
87
|
+
queryKey: queryKeys.pages.getByPath(pathname, source),
|
|
88
|
+
queryFn: async () => {
|
|
89
|
+
const [data, pagesList] = await Promise.all([serverApi.pages.getByPath({
|
|
90
|
+
path: pathname,
|
|
91
|
+
projectSlug,
|
|
92
|
+
source
|
|
93
|
+
}), serverApi.pages.listBySlug({ projectSlug }).catch(() => null)]);
|
|
94
|
+
seedBlockCaches(queryClient, data, source);
|
|
95
|
+
queryClient.setQueryData(queryKeys.pages.getByPath(pathname, source), {
|
|
96
|
+
page: data.page,
|
|
97
|
+
layout: data.layout,
|
|
98
|
+
projectName: data.projectName,
|
|
99
|
+
project: data.project
|
|
100
|
+
});
|
|
101
|
+
if (source === "live") {
|
|
102
|
+
seedBlockCaches(queryClient, data, "draft");
|
|
103
|
+
queryClient.setQueryData(queryKeys.pages.getByPath(pathname, "draft"), {
|
|
104
|
+
page: data.page,
|
|
105
|
+
layout: data.layout,
|
|
106
|
+
projectName: data.projectName,
|
|
107
|
+
project: data.project
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (pagesList) queryClient.setQueryData(queryKeys.pages.list, pagesList);
|
|
111
|
+
return {
|
|
112
|
+
page: data.page,
|
|
113
|
+
layout: data.layout,
|
|
114
|
+
projectName: data.projectName,
|
|
115
|
+
project: data.project
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
staleTime: Infinity
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
async function loadLivePage(options) {
|
|
122
|
+
return loadCamoxPage({
|
|
123
|
+
...options,
|
|
124
|
+
source: "live"
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function buildCamoxPageLoaderData(apiUrl, page, origin) {
|
|
128
|
+
return {
|
|
129
|
+
page,
|
|
130
|
+
origin,
|
|
131
|
+
faviconUrl: `${apiUrl}/favicons/${page.project.id}?v=${page.project.updatedAt}`
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async function loadCamoxPageForRequest({ apiUrl, authCookieHeader, environmentName, origin, pathname, projectSlug, queryClient }) {
|
|
135
|
+
const loadOptions = {
|
|
136
|
+
apiUrl,
|
|
137
|
+
environmentName,
|
|
138
|
+
pathname,
|
|
139
|
+
projectSlug,
|
|
140
|
+
queryClient
|
|
141
|
+
};
|
|
142
|
+
if (!authCookieHeader) return {
|
|
143
|
+
data: buildCamoxPageLoaderData(apiUrl, await loadLivePage(loadOptions), origin),
|
|
144
|
+
shouldClearAuthCookie: false,
|
|
145
|
+
source: "live"
|
|
146
|
+
};
|
|
147
|
+
try {
|
|
148
|
+
return {
|
|
149
|
+
data: buildCamoxPageLoaderData(apiUrl, await loadCamoxPage({
|
|
150
|
+
...loadOptions,
|
|
151
|
+
authCookieHeader,
|
|
152
|
+
source: "draft"
|
|
153
|
+
}), origin),
|
|
154
|
+
shouldClearAuthCookie: false,
|
|
155
|
+
source: "draft"
|
|
156
|
+
};
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (!isAuthSessionError(error)) throw error;
|
|
159
|
+
console.warn("[camox] Ignoring stale camox_auth_cookie and retrying published page load.");
|
|
160
|
+
return {
|
|
161
|
+
data: buildCamoxPageLoaderData(apiUrl, await loadLivePage(loadOptions), origin),
|
|
162
|
+
shouldClearAuthCookie: true,
|
|
163
|
+
source: "live"
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function createMarkdownResponse({ apiUrl, environmentName, pathname, projectSlug, request }) {
|
|
168
|
+
if (!prefersMarkdown(request.headers.get("Accept") ?? "")) return null;
|
|
169
|
+
const api = createServerApiClient(apiUrl, environmentName);
|
|
170
|
+
const requestPathname = pathname ?? new URL(request.url).pathname;
|
|
171
|
+
try {
|
|
172
|
+
const page = await api.pages.getByPath({
|
|
173
|
+
path: requestPathname,
|
|
174
|
+
projectSlug,
|
|
175
|
+
source: "live"
|
|
176
|
+
});
|
|
177
|
+
const { markdown } = await api.blocks.getPageMarkdown({ pageId: page.page.id });
|
|
178
|
+
if (!markdown) return null;
|
|
179
|
+
trackEvent("markdown_served", {
|
|
180
|
+
pathname: requestPathname,
|
|
181
|
+
projectId: page.page.projectId,
|
|
182
|
+
projectName: page.projectName
|
|
183
|
+
});
|
|
184
|
+
return new Response(markdown, { headers: { "Content-Type": "text/markdown; charset=utf-8" } });
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function buildCamoxPageHead(camoxApp, loaderData) {
|
|
190
|
+
if (!loaderData) return {};
|
|
191
|
+
const { page, origin, faviconUrl } = loaderData;
|
|
192
|
+
const pageMetaTitle = page.page.metaTitle ?? page.page.pathSegment;
|
|
193
|
+
const meta = [];
|
|
194
|
+
let title = pageMetaTitle;
|
|
195
|
+
if (page.layout) {
|
|
196
|
+
const layout = camoxApp.getLayoutById(page.layout.layoutId);
|
|
197
|
+
if (layout) {
|
|
198
|
+
title = layout._internal.buildMetaTitle({
|
|
199
|
+
pageMetaTitle,
|
|
200
|
+
projectName: page.projectName,
|
|
201
|
+
pageFullPath: page.page.fullPath
|
|
202
|
+
});
|
|
203
|
+
meta.push({ title });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (page.page.metaDescription) meta.push({
|
|
207
|
+
name: "description",
|
|
208
|
+
content: page.page.metaDescription
|
|
209
|
+
});
|
|
210
|
+
const ogImageParams = new URLSearchParams({
|
|
211
|
+
...page.layout && { layoutId: page.layout.layoutId },
|
|
212
|
+
title: pageMetaTitle,
|
|
213
|
+
...page.page.metaDescription && { description: page.page.metaDescription },
|
|
214
|
+
...page.projectName && { projectName: page.projectName }
|
|
215
|
+
});
|
|
216
|
+
const ogImageUrl = page.page.customOgImageUrl ?? `${origin}/og?${ogImageParams.toString()}`;
|
|
217
|
+
meta.push({
|
|
218
|
+
property: "og:title",
|
|
219
|
+
content: title
|
|
220
|
+
}, {
|
|
221
|
+
property: "og:image",
|
|
222
|
+
content: ogImageUrl
|
|
223
|
+
}, {
|
|
224
|
+
property: "og:image:width",
|
|
225
|
+
content: "1200"
|
|
226
|
+
}, {
|
|
227
|
+
property: "og:image:height",
|
|
228
|
+
content: "630"
|
|
229
|
+
});
|
|
230
|
+
if (page.page.metaDescription) meta.push({
|
|
231
|
+
property: "og:description",
|
|
232
|
+
content: page.page.metaDescription
|
|
233
|
+
});
|
|
234
|
+
return {
|
|
235
|
+
meta,
|
|
236
|
+
links: [{
|
|
237
|
+
rel: "icon",
|
|
238
|
+
href: faviconUrl
|
|
239
|
+
}]
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
//#endregion
|
|
244
|
+
export { buildCamoxPageHead, createMarkdownResponse, createServerApiClient, isNotFoundError, loadCamoxPageForRequest };
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
import { CamoxApp } from "../../core/createApp.js";
|
|
2
|
+
import { CamoxDocument } from "../../core/defineDocument.js";
|
|
3
|
+
import { StudioRenderInput } from "./studioApp.js";
|
|
1
4
|
import { UseHeadInput } from "unhead/types";
|
|
2
5
|
|
|
3
6
|
//#region src/features/runtime/runtime.d.ts
|
|
7
|
+
type RuntimeRouteKind = "data" | "health" | "og" | "page" | "registry" | "sitemap" | "studio" | "studio-content" | "studio-nested";
|
|
8
|
+
interface RuntimeRouteMatch {
|
|
9
|
+
kind: RuntimeRouteKind;
|
|
10
|
+
pathname: string;
|
|
11
|
+
}
|
|
4
12
|
interface LayoutIdentity {
|
|
5
13
|
blockIds: number[];
|
|
6
14
|
id: number;
|
|
@@ -21,5 +29,26 @@ interface PageRenderInput {
|
|
|
21
29
|
runtimeBasePath: string;
|
|
22
30
|
source: "live" | "draft";
|
|
23
31
|
}
|
|
32
|
+
interface RuntimeOptions {
|
|
33
|
+
apiUrl?: string;
|
|
34
|
+
authenticationUrl?: string;
|
|
35
|
+
environmentName?: string;
|
|
36
|
+
pageClientEntryUrl?: string;
|
|
37
|
+
studioClientEntryUrl?: string;
|
|
38
|
+
getCamoxApp?: () => Promise<CamoxApp>;
|
|
39
|
+
getDocument?: () => Promise<CamoxDocument>;
|
|
40
|
+
projectSlug?: string;
|
|
41
|
+
renderPage?: (input: PageRenderInput) => Promise<string>;
|
|
42
|
+
renderStudio?: (input: StudioRenderInput) => Promise<string>;
|
|
43
|
+
runtimeBasePath?: string;
|
|
44
|
+
stylesheetUrl?: string;
|
|
45
|
+
}
|
|
46
|
+
declare function getRuntimePathname(url: string, runtimeBasePath?: string): string | null;
|
|
47
|
+
declare function matchRuntimeRoute(pathname: string): RuntimeRouteMatch;
|
|
48
|
+
declare function createPageHeadInput(head: {
|
|
49
|
+
meta?: Array<Record<string, string>>;
|
|
50
|
+
links?: Array<Record<string, string>>;
|
|
51
|
+
}): UseHeadInput;
|
|
52
|
+
declare function handleCamoxRequest(request: Request, options?: RuntimeOptions): Promise<Response | null>;
|
|
24
53
|
//#endregion
|
|
25
|
-
export { PageRenderInput };
|
|
54
|
+
export { LayoutIdentity, PageRenderInput, RuntimeOptions, RuntimeRouteKind, RuntimeRouteMatch, createPageHeadInput, getRuntimePathname, handleCamoxRequest, matchRuntimeRoute };
|