camox 0.48.0 → 0.51.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/dist/core/layoutContext.d.ts +1 -0
- package/dist/features/canvas/sidebar/CanvasInspector.js +1 -1
- package/dist/features/content/ContentAssets.js +84 -88
- package/dist/features/preview/CamoxPreview.js +1 -1
- package/dist/features/preview/EditablePageExperience.js +17 -9
- package/dist/features/preview/components/BlockActionsPopover.js +11 -35
- package/dist/features/preview/components/FieldToolbar.js +1 -1
- package/dist/features/preview/components/MobilePreviewDrawer.js +6 -9
- package/dist/features/preview/components/PreviewPanel.js +10 -64
- package/dist/features/preview/components/PreviewToolbar.js +1 -1
- package/dist/features/preview/previewConstants.js +2 -8
- package/dist/features/preview/usePreviewViewportActions.js +1 -1
- package/dist/features/provider/AuthenticatedCamoxProvider.js +1 -1
- package/dist/features/provider/components/CommandPalette.js +2 -5
- package/dist/features/provider/signInShortcut.js +1 -2
- package/dist/features/provider/useAdminShortcuts.js +47 -53
- package/dist/features/routes/pageRuntime.d.ts +2 -1
- package/dist/features/routes/pageRuntime.js +27 -20
- package/dist/features/runtime/publicCache.js +83 -0
- package/dist/features/runtime/runtime.d.ts +13 -3
- package/dist/features/runtime/runtime.js +173 -79
- package/dist/features/runtime/worker.d.ts +5 -1
- package/dist/features/runtime/worker.js +20 -3
- package/dist/features/settings/AnalyticsSettings.js +622 -0
- package/dist/features/settings/CamoxSettings.js +95 -0
- package/dist/features/settings/FaviconSettings.js +254 -0
- package/dist/features/settings/ProjectNameSettings.js +145 -0
- package/dist/features/settings/SettingsPart.js +73 -0
- package/dist/features/settings/SocialProfilesSettings.js +245 -0
- package/dist/features/settings/useProjectMutation.js +61 -0
- package/dist/features/studio/components/DraftsControl.js +136 -131
- package/dist/features/studio/components/LocalePicker.js +178 -174
- package/dist/features/studio/components/Navbar.js +107 -66
- package/dist/features/studio/components/ProjectMenu.js +118 -170
- package/dist/features/studio/components/ReviewDraftSheet.js +1046 -990
- package/dist/features/studio/components/UserButton.js +1 -1
- package/dist/features/studio/routes.js +3 -1
- package/dist/features/studio/showInCanvas.js +129 -0
- package/dist/features/vite/definitionsSync.js +4 -5
- package/dist/features/vite/runtimeDev.js +32 -17
- package/dist/features/vite/staticAssetHeaders.js +52 -0
- package/dist/features/vite/vite.d.ts +1 -1
- package/dist/features/vite/vite.js +16 -4
- package/dist/lib/api-client-server.d.ts +1 -1
- package/dist/lib/api-client.d.ts +2 -1
- package/dist/lib/draft-scope.d.ts +1 -0
- package/dist/lib/locale-path.d.ts +3 -11
- package/dist/lib/locale-path.js +3 -27
- package/dist/lib/queries.js +7 -1
- package/dist/lib/shortcuts.js +29 -0
- package/dist/lib/studio-reads.d.ts +1 -0
- package/dist/lib/utils.js +1 -104
- package/dist/package.js +1 -1
- package/dist/studio.css +1 -1
- package/package.json +5 -4
- package/dist/features/studio/components/FaviconDialog.js +0 -318
- package/dist/features/studio/components/SocialProfilesDialog.js +0 -342
|
@@ -8,11 +8,11 @@ import { reads } from "../../lib/studio-reads.js";
|
|
|
8
8
|
import { asCollectionItem, recordMarkdownTemplate, recordPageValues } from "../../core/recordItem.js";
|
|
9
9
|
import { buildLayoutContext, curatedLayoutContext } from "../../core/layoutContext.js";
|
|
10
10
|
import { PLATFORM_SCRIPT } from "../../lib/platform.js";
|
|
11
|
-
import { STUDIO_ASSETS_PATH, STUDIO_CANVAS_PATH, STUDIO_CONTENT_PATH, matchCollectionContentPath } from "../studio/routes.js";
|
|
11
|
+
import { STUDIO_ASSETS_PATH, STUDIO_CANVAS_PATH, STUDIO_CONTENT_PATH, STUDIO_SETTINGS_PATH, matchCollectionContentPath } from "../studio/routes.js";
|
|
12
12
|
import { STUDIO_THEME_SCRIPT } from "../studio/studioTheme.js";
|
|
13
13
|
import { DefaultPageImage, renderPageImage } from "../og/pageImage.js";
|
|
14
14
|
import { discoverLayoutPages, matchDerivedLayout } from "../../core/derivedRoutes.js";
|
|
15
|
-
import { buildCamoxPageHead, buildFaviconUrl, buildPageHead, buildPageMeta,
|
|
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
16
|
import { alternatesByPage, findAlternates, hreflangLinks } from "./alternates.js";
|
|
17
17
|
import { configuredProviders, renderAnalyticsScripts } from "./analytics.js";
|
|
18
18
|
import { omitArtifactSources } from "./artifactSources.js";
|
|
@@ -29,6 +29,13 @@ import { createHead, renderSSRHead } from "@unhead/react/server";
|
|
|
29
29
|
|
|
30
30
|
//#region src/features/runtime/runtime.ts
|
|
31
31
|
const DEFAULT_RUNTIME_BASE_PATH = "";
|
|
32
|
+
/** The studio's own routes, rather than a page or the Canvas. */
|
|
33
|
+
const STUDIO_ROUTE_KINDS = [
|
|
34
|
+
"studio",
|
|
35
|
+
"studio-content",
|
|
36
|
+
"studio-settings",
|
|
37
|
+
"studio-nested"
|
|
38
|
+
];
|
|
32
39
|
function normalizeRuntimeBasePath(basePath) {
|
|
33
40
|
if (basePath === void 0) return DEFAULT_RUNTIME_BASE_PATH;
|
|
34
41
|
if (!basePath || basePath === "/") return "";
|
|
@@ -57,6 +64,26 @@ function getRequestDraftId(request, options) {
|
|
|
57
64
|
function isPrivateRender(request, options) {
|
|
58
65
|
return !!getServerAuthCookieHeader(request.headers) || getRequestDraftId(request, options) !== void 0;
|
|
59
66
|
}
|
|
67
|
+
/** The routes whose response is the same for every visitor, unless the request is private. */
|
|
68
|
+
const PUBLIC_ROUTE_KINDS = /* @__PURE__ */ new Set([
|
|
69
|
+
"page",
|
|
70
|
+
"data",
|
|
71
|
+
"og",
|
|
72
|
+
"logo",
|
|
73
|
+
"sitemap",
|
|
74
|
+
"llms-txt"
|
|
75
|
+
]);
|
|
76
|
+
/**
|
|
77
|
+
* Whether a request reads what every visitor sees, so a shared cache may answer it: a GET or
|
|
78
|
+
* HEAD of a page or a public output, with no session, no draft named and no preview handoff. A
|
|
79
|
+
* response that comes out private all the same, such as the Studio's, still says so.
|
|
80
|
+
*/
|
|
81
|
+
function isPublicRequest(request, options = {}) {
|
|
82
|
+
if (request.method !== "GET" && request.method !== "HEAD") return false;
|
|
83
|
+
if (isPrivateRender(request, options) || isPreviewHandoff(request, options)) return false;
|
|
84
|
+
const pathname = getRuntimePathname(request.url, options.runtimeBasePath);
|
|
85
|
+
return pathname !== null && PUBLIC_ROUTE_KINDS.has(matchRuntimeRoute(pathname).kind);
|
|
86
|
+
}
|
|
60
87
|
function escapeXml(value) {
|
|
61
88
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
62
89
|
}
|
|
@@ -90,6 +117,10 @@ function matchRuntimeRoute(pathname) {
|
|
|
90
117
|
kind: "studio-content",
|
|
91
118
|
pathname
|
|
92
119
|
};
|
|
120
|
+
if (pathname === "/camox/settings" || pathname === `${"/camox/settings"}/`) return {
|
|
121
|
+
kind: "studio-settings",
|
|
122
|
+
pathname
|
|
123
|
+
};
|
|
93
124
|
if (pathname.startsWith("/camox/")) return {
|
|
94
125
|
kind: "studio-nested",
|
|
95
126
|
pathname
|
|
@@ -184,10 +215,7 @@ function createLlmsTxtResponse(request, options) {
|
|
|
184
215
|
pages: website.localizations[0].pages,
|
|
185
216
|
layouts: (website.app?.getLayouts() ?? []).map(({ _internal }) => _internal),
|
|
186
217
|
url: absoluteUrl(request, options)
|
|
187
|
-
}), {
|
|
188
|
-
"Content-Type": "text/plain; charset=utf-8",
|
|
189
|
-
"Cache-Control": "public, max-age=300, stale-while-revalidate=3600"
|
|
190
|
-
}));
|
|
218
|
+
}), { "Content-Type": "text/plain; charset=utf-8" }));
|
|
191
219
|
}
|
|
192
220
|
/** Tells LLM tools fetching a page's HTML where the website's llms.txt is. */
|
|
193
221
|
function llmsTxtLinkHeader(options) {
|
|
@@ -492,8 +520,7 @@ function isPreviewHandoff(request, options) {
|
|
|
492
520
|
* The alternates of the page a request renders, where it exists in each of the scope's locales,
|
|
493
521
|
* and its head with their `hreflang` links. Without a head, there is none to add them to.
|
|
494
522
|
*/
|
|
495
|
-
async function readAlternates(request, options, { located, identity, head, described,
|
|
496
|
-
const localeReads = requestLocaleReads(options, reading);
|
|
523
|
+
async function readAlternates(request, options, { located, identity, head, described, localeReads }) {
|
|
497
524
|
const [alternates, crumbs] = await Promise.all([findRequestAlternates(options, located, identity, localeReads), head ? readRequestBreadcrumb(options, located, described.title, localeReads) : []]);
|
|
498
525
|
const url = absoluteUrl(request, options);
|
|
499
526
|
const links = hreflangLinks(alternates, located.locales, url);
|
|
@@ -565,7 +592,7 @@ function withLocatedTags(head, { url, locale, alternates }) {
|
|
|
565
592
|
}]
|
|
566
593
|
};
|
|
567
594
|
}
|
|
568
|
-
function requestLocaleReads(options, { authCookieHeader, draftId }) {
|
|
595
|
+
function requestLocaleReads(options, { authCookieHeader, draftId, queryClient }) {
|
|
569
596
|
const { apiUrl, projectUuid } = options;
|
|
570
597
|
const reading = {
|
|
571
598
|
authCookieHeader: draftId ? authCookieHeader : void 0,
|
|
@@ -573,9 +600,15 @@ function requestLocaleReads(options, { authCookieHeader, draftId }) {
|
|
|
573
600
|
};
|
|
574
601
|
const source = draftId ? "draft" : "live";
|
|
575
602
|
const pagesByLocale = /* @__PURE__ */ new Map();
|
|
603
|
+
const studioCache = apiUrl && queryClient ? createServerStudioCache(queryClient, { apiUrl }) : void 0;
|
|
576
604
|
return {
|
|
577
605
|
pages: (locale) => {
|
|
578
606
|
if (!apiUrl || !projectUuid) return Promise.resolve([]);
|
|
607
|
+
const loaded = studioCache?.get(reads.pageList({ projectUuid }), {
|
|
608
|
+
in: draftId ?? null,
|
|
609
|
+
locale
|
|
610
|
+
});
|
|
611
|
+
if (loaded) return Promise.resolve(loaded);
|
|
579
612
|
const read = pagesByLocale.get(locale.tag) ?? createServerApiClient(apiUrl, {
|
|
580
613
|
...reading,
|
|
581
614
|
locale
|
|
@@ -670,27 +703,89 @@ async function createPreviewHandoffResponse({ options, pathname, request }) {
|
|
|
670
703
|
"Referrer-Policy": "no-referrer"
|
|
671
704
|
} });
|
|
672
705
|
}
|
|
673
|
-
|
|
706
|
+
/** Starts reading the curated page at a locale-relative path, as the request reads it. */
|
|
707
|
+
function readCuratedPage(options, request, { locale, path }) {
|
|
708
|
+
const queryClient = new QueryClient();
|
|
709
|
+
return {
|
|
710
|
+
queryClient,
|
|
711
|
+
result: loadCamoxPageForRequest({
|
|
712
|
+
apiUrl: options.apiUrl,
|
|
713
|
+
authCookieHeader: getServerAuthCookieHeader(request.headers),
|
|
714
|
+
draftId: getRequestDraftId(request, options),
|
|
715
|
+
locale,
|
|
716
|
+
origin: new URL(request.url).origin,
|
|
717
|
+
pathname: path,
|
|
718
|
+
projectUuid: options.projectUuid,
|
|
719
|
+
queryClient
|
|
720
|
+
})
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
/** A first path segment that could be a locale's prefix: a language tag, in lowercase. */
|
|
724
|
+
const LOCALE_PREFIX_LIKE = /^\/[a-z]{2,3}(?:-[a-z0-9]{1,8})*(?:\/|$)/;
|
|
725
|
+
/**
|
|
726
|
+
* The read of the curated page a path names in the default locale, started while the request's
|
|
727
|
+
* locales are read, or none where it couldn't be the request's page. A default-locale page sits
|
|
728
|
+
* at its own path, so its read needn't wait for the locales: once read, they only confirm the path
|
|
729
|
+
* names no other locale. A path whose first segment looks like a locale's prefix waits for them
|
|
730
|
+
* instead, rather than reading a page it likely doesn't name, and a singleton's path reads no
|
|
731
|
+
* curated page.
|
|
732
|
+
*/
|
|
733
|
+
async function readDefaultLocalePageEarly(options, request, pathname) {
|
|
734
|
+
const { apiUrl, projectUuid } = options;
|
|
735
|
+
if (!apiUrl || !projectUuid) return void 0;
|
|
736
|
+
if (matchLocalizedRoute(pathname)?.kind !== "page" || LOCALE_PREFIX_LIKE.test(pathname)) return;
|
|
737
|
+
const app = await options.getCamoxApp?.().catch(() => void 0);
|
|
738
|
+
if (app && matchDerivedLayout(app.getLayouts(), pathname)?.layout._internal.kind === "singleton") return void 0;
|
|
739
|
+
const read = readCuratedPage({
|
|
740
|
+
...options,
|
|
741
|
+
apiUrl,
|
|
742
|
+
projectUuid
|
|
743
|
+
}, request, {
|
|
744
|
+
locale: UNNAMED_DEFAULT_LOCALE,
|
|
745
|
+
path: pathname
|
|
746
|
+
});
|
|
747
|
+
read.result.catch(() => void 0);
|
|
748
|
+
return read;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* The reads of each locale the head of the request's curated page makes, and those it can make
|
|
752
|
+
* while the page is read. When the request reads Live, whatever the page's read finds about its
|
|
753
|
+
* draft, the pages of the locales the page's read doesn't load start loading now. Otherwise they
|
|
754
|
+
* wait for the page's read to settle the draft.
|
|
755
|
+
*/
|
|
756
|
+
function curatedPageLocaleReads(options, request, located, queryClient) {
|
|
757
|
+
const authCookieHeader = getServerAuthCookieHeader(request.headers);
|
|
758
|
+
const forDraft = (draftId) => requestLocaleReads(options, {
|
|
759
|
+
authCookieHeader,
|
|
760
|
+
draftId,
|
|
761
|
+
queryClient
|
|
762
|
+
});
|
|
763
|
+
if (!(!getRequestDraftId(request, options) || !authCookieHeader)) return forDraft;
|
|
764
|
+
const live = forDraft(void 0);
|
|
765
|
+
if (located.locales.length > 1) for (const locale of located.locales) {
|
|
766
|
+
if (locale.tag === located.locale.tag || locale.isDefault) continue;
|
|
767
|
+
live.pages(locale).catch(() => void 0);
|
|
768
|
+
}
|
|
769
|
+
return () => live;
|
|
770
|
+
}
|
|
771
|
+
async function createPageHtmlResponse({ options, pathname, located, request, pageRead }) {
|
|
674
772
|
if (!options.apiUrl || !options.authenticationUrl || !options.projectUuid || !options.renderPage) return createPageDataResponse({
|
|
675
773
|
options,
|
|
676
774
|
pathname,
|
|
677
775
|
located,
|
|
678
|
-
request
|
|
776
|
+
request,
|
|
777
|
+
pageRead
|
|
679
778
|
});
|
|
680
779
|
const singleton = await createDerivedResponse(options, pathname, located, request, { singletonOnly: true });
|
|
681
780
|
if (singleton) return singleton;
|
|
682
|
-
const queryClient =
|
|
781
|
+
const { queryClient, result: reading } = pageRead ?? readCuratedPage({
|
|
782
|
+
...options,
|
|
783
|
+
apiUrl: options.apiUrl,
|
|
784
|
+
projectUuid: options.projectUuid
|
|
785
|
+
}, request, located);
|
|
786
|
+
const localeReads = curatedPageLocaleReads(options, request, located, queryClient);
|
|
683
787
|
try {
|
|
684
|
-
const result = await
|
|
685
|
-
apiUrl: options.apiUrl,
|
|
686
|
-
authCookieHeader: getServerAuthCookieHeader(request.headers),
|
|
687
|
-
draftId: getRequestDraftId(request, options),
|
|
688
|
-
locale: located.locale,
|
|
689
|
-
origin: new URL(request.url).origin,
|
|
690
|
-
pathname: located.path,
|
|
691
|
-
projectUuid: options.projectUuid,
|
|
692
|
-
queryClient
|
|
693
|
-
});
|
|
788
|
+
const result = await reading;
|
|
694
789
|
const headers = new Headers({
|
|
695
790
|
"Content-Type": "text/html; charset=utf-8",
|
|
696
791
|
Vary: "Cookie",
|
|
@@ -708,8 +803,7 @@ async function createPageHtmlResponse({ options, pathname, located, request }) {
|
|
|
708
803
|
},
|
|
709
804
|
head: camoxApp ? buildCamoxPageHead(camoxApp, result.data, located.locale) : {},
|
|
710
805
|
described: curatedPageDescription(camoxApp, result.data.page, located.locale),
|
|
711
|
-
|
|
712
|
-
draftId: result.draftId
|
|
806
|
+
localeReads: localeReads(result.draftId)
|
|
713
807
|
});
|
|
714
808
|
const dehydratedState = dehydrate(queryClient);
|
|
715
809
|
const { project } = result;
|
|
@@ -982,8 +1076,10 @@ async function createDerivedResponse(options, pathname, located, request, { data
|
|
|
982
1076
|
titles: recordValues?.titles
|
|
983
1077
|
})
|
|
984
1078
|
},
|
|
985
|
-
|
|
986
|
-
|
|
1079
|
+
localeReads: requestLocaleReads(options, {
|
|
1080
|
+
authCookieHeader,
|
|
1081
|
+
draftId: read.draftId
|
|
1082
|
+
})
|
|
987
1083
|
});
|
|
988
1084
|
const analytics = dataOnly ? void 0 : visitorAnalytics(options, {
|
|
989
1085
|
studio,
|
|
@@ -1022,7 +1118,7 @@ async function createDerivedResponse(options, pathname, located, request, { data
|
|
|
1022
1118
|
const document = await options.getDocument?.() ?? {};
|
|
1023
1119
|
await preparePreviewDocument(input, options, document, !studio && !dataOnly ? "" : void 0);
|
|
1024
1120
|
if (dataOnly) return Response.json(input, { headers: {
|
|
1025
|
-
"Cache-Control": "private, no-store",
|
|
1121
|
+
...isPrivateRender(request, options) ? { "Cache-Control": "private, no-store" } : {},
|
|
1026
1122
|
Vary: "Cookie",
|
|
1027
1123
|
...clearAuthCookie ? { "Set-Cookie": buildClearServerAuthCookieHeader() } : {}
|
|
1028
1124
|
} });
|
|
@@ -1136,30 +1232,26 @@ async function createCanvasOnlyResponse({ options, pathname, located, request, d
|
|
|
1136
1232
|
...headers
|
|
1137
1233
|
} });
|
|
1138
1234
|
}
|
|
1139
|
-
async function createPageDataResponse({ options, pathname, located, request }) {
|
|
1235
|
+
async function createPageDataResponse({ options, pathname, located, request, pageRead }) {
|
|
1140
1236
|
if (!options.apiUrl || !options.projectUuid) return Response.json({ message: "Camox page loader is not configured." }, { status: 500 });
|
|
1141
1237
|
const singleton = await createDerivedResponse(options, pathname, located, request, {
|
|
1142
1238
|
dataOnly: true,
|
|
1143
1239
|
singletonOnly: true
|
|
1144
1240
|
});
|
|
1145
1241
|
if (singleton) return singleton;
|
|
1146
|
-
const queryClient =
|
|
1242
|
+
const { queryClient, result: reading } = pageRead ?? readCuratedPage({
|
|
1243
|
+
...options,
|
|
1244
|
+
apiUrl: options.apiUrl,
|
|
1245
|
+
projectUuid: options.projectUuid
|
|
1246
|
+
}, request, located);
|
|
1247
|
+
const localeReads = curatedPageLocaleReads(options, request, located, queryClient);
|
|
1147
1248
|
try {
|
|
1148
|
-
const result = await
|
|
1149
|
-
apiUrl: options.apiUrl,
|
|
1150
|
-
authCookieHeader: getServerAuthCookieHeader(request.headers),
|
|
1151
|
-
draftId: getRequestDraftId(request, options),
|
|
1152
|
-
locale: located.locale,
|
|
1153
|
-
origin: new URL(request.url).origin,
|
|
1154
|
-
pathname: located.path,
|
|
1155
|
-
projectUuid: options.projectUuid,
|
|
1156
|
-
queryClient
|
|
1157
|
-
});
|
|
1249
|
+
const result = await reading;
|
|
1158
1250
|
const headers = new Headers({
|
|
1159
1251
|
"Content-Type": "application/json; charset=utf-8",
|
|
1160
|
-
"Cache-Control": "private, no-store",
|
|
1161
1252
|
Vary: "Cookie"
|
|
1162
1253
|
});
|
|
1254
|
+
if (isPrivateRender(request, options)) headers.set("Cache-Control", "private, no-store");
|
|
1163
1255
|
if (result.shouldClearAuthCookie) headers.append("Set-Cookie", buildClearServerAuthCookieHeader());
|
|
1164
1256
|
const camoxApp = await options.getCamoxApp?.();
|
|
1165
1257
|
const { alternates, head } = await readAlternates(request, options, {
|
|
@@ -1170,8 +1262,7 @@ async function createPageDataResponse({ options, pathname, located, request }) {
|
|
|
1170
1262
|
},
|
|
1171
1263
|
head: camoxApp ? buildCamoxPageHead(camoxApp, result.data, located.locale) : null,
|
|
1172
1264
|
described: curatedPageDescription(camoxApp, result.data.page, located.locale),
|
|
1173
|
-
|
|
1174
|
-
draftId: result.draftId
|
|
1265
|
+
localeReads: localeReads(result.draftId)
|
|
1175
1266
|
});
|
|
1176
1267
|
const dehydratedState = dehydrate(queryClient);
|
|
1177
1268
|
const input = {
|
|
@@ -1239,19 +1330,17 @@ async function handleCamoxRequest(request, options = {}) {
|
|
|
1239
1330
|
const { apiUrl, projectUuid } = options;
|
|
1240
1331
|
const markdownPathname = parseMarkdownPath(pathname);
|
|
1241
1332
|
if (apiUrl && projectUuid && (markdownPathname || prefersMarkdown(request.headers.get("Accept") ?? ""))) {
|
|
1242
|
-
const
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
if (markdownPathname) return new Response("Page not found", { status: 404 });
|
|
1254
|
-
}
|
|
1333
|
+
const markdownResponse = await createPageMarkdownResponse({
|
|
1334
|
+
...options,
|
|
1335
|
+
apiUrl,
|
|
1336
|
+
projectUuid
|
|
1337
|
+
}, {
|
|
1338
|
+
pathname: markdownPathname ?? pathname,
|
|
1339
|
+
request,
|
|
1340
|
+
pagesOnly: !markdownPathname
|
|
1341
|
+
});
|
|
1342
|
+
if (markdownResponse) return markdownResponse;
|
|
1343
|
+
if (markdownPathname) return new Response("Page not found", { status: 404 });
|
|
1255
1344
|
}
|
|
1256
1345
|
return respondInLocale(options, request, pathname);
|
|
1257
1346
|
}
|
|
@@ -1260,7 +1349,7 @@ async function handleCamoxRequest(request, options = {}) {
|
|
|
1260
1349
|
* With `dataOnly`, as the JSON client navigation loads.
|
|
1261
1350
|
*/
|
|
1262
1351
|
async function respondInLocale(options, request, pathname, { dataOnly = false } = {}) {
|
|
1263
|
-
const located = await locateRequest(request, options, pathname);
|
|
1352
|
+
const [early, located] = await Promise.all([readDefaultLocalePageEarly(options, request, pathname), locateRequest(request, options, pathname)]);
|
|
1264
1353
|
const localized = matchLocalizedRoute(located.path);
|
|
1265
1354
|
if (!localized) return new Response("Not found", { status: 404 });
|
|
1266
1355
|
const target = {
|
|
@@ -1278,13 +1367,29 @@ async function respondInLocale(options, request, pathname, { dataOnly = false }
|
|
|
1278
1367
|
match: localized,
|
|
1279
1368
|
dataOnly
|
|
1280
1369
|
});
|
|
1281
|
-
|
|
1370
|
+
const page = {
|
|
1371
|
+
...target,
|
|
1372
|
+
pageRead: located.locale.isDefault ? early : void 0
|
|
1373
|
+
};
|
|
1374
|
+
return dataOnly ? createPageDataResponse(page) : createPageHtmlResponse(page);
|
|
1282
1375
|
}
|
|
1283
1376
|
/**
|
|
1284
1377
|
* A page's Markdown Representation in its locale, with the same precedence as its HTML:
|
|
1285
|
-
* singleton, curated page, then derived layout. Null when the page has none.
|
|
1378
|
+
* singleton, curated page, then derived layout. Null when the page has none. Markdown is a public
|
|
1379
|
+
* output: it reads Live, whoever asks, and one read finds the path's locale and its curated page.
|
|
1286
1380
|
*/
|
|
1287
|
-
async function createPageMarkdownResponse(options, {
|
|
1381
|
+
async function createPageMarkdownResponse(options, { pathname, request, pagesOnly }) {
|
|
1382
|
+
let read;
|
|
1383
|
+
try {
|
|
1384
|
+
read = await readPageMarkdown({
|
|
1385
|
+
...options,
|
|
1386
|
+
path: pathname
|
|
1387
|
+
});
|
|
1388
|
+
} catch {
|
|
1389
|
+
return null;
|
|
1390
|
+
}
|
|
1391
|
+
const { locale, path } = read;
|
|
1392
|
+
if (pagesOnly && matchLocalizedRoute(path)?.kind !== "page") return null;
|
|
1288
1393
|
const page = {
|
|
1289
1394
|
pathname,
|
|
1290
1395
|
url: absoluteUrl(request, options)(pathname)
|
|
@@ -1292,20 +1397,12 @@ async function createPageMarkdownResponse(options, { locale, path }, { pathname,
|
|
|
1292
1397
|
const app = await options.getCamoxApp?.();
|
|
1293
1398
|
const layoutMatch = app && matchDerivedLayout(app.getLayouts(), path);
|
|
1294
1399
|
if (layoutMatch?.layout._internal.kind === "singleton") return createLayoutMarkdownResponse(options, layoutMatch, locale, page);
|
|
1295
|
-
return
|
|
1296
|
-
|
|
1297
|
-
camoxApp: app,
|
|
1298
|
-
locale,
|
|
1299
|
-
pathname: path,
|
|
1300
|
-
projectUuid: options.projectUuid,
|
|
1301
|
-
request,
|
|
1302
|
-
url: page.url,
|
|
1303
|
-
onNotFound: async () => layoutMatch ? createLayoutMarkdownResponse(options, layoutMatch, locale, page) : null
|
|
1304
|
-
});
|
|
1400
|
+
if (read.page) return curatedMarkdownResponse(app, read, page.url);
|
|
1401
|
+
return layoutMatch ? createLayoutMarkdownResponse(options, layoutMatch, locale, page) : null;
|
|
1305
1402
|
}
|
|
1306
1403
|
/** Whether a route is one of the studio's own, rather than a page or the Canvas. */
|
|
1307
1404
|
function isStudioRoute(match) {
|
|
1308
|
-
return
|
|
1405
|
+
return STUDIO_ROUTE_KINDS.includes(match.kind);
|
|
1309
1406
|
}
|
|
1310
1407
|
/**
|
|
1311
1408
|
* The studio or page route a locale-relative path names, or null for a runtime-owned route
|
|
@@ -1317,19 +1414,16 @@ function matchLocalizedRoute(path) {
|
|
|
1317
1414
|
}
|
|
1318
1415
|
/**
|
|
1319
1416
|
* The locale a request's path names, among the locales of the scope it reads: the URL's draft's
|
|
1320
|
-
* for a session naming one, else Live's.
|
|
1321
|
-
*
|
|
1322
|
-
* render.
|
|
1417
|
+
* for a session naming one, else Live's. Without an API there are none to read: every path is the
|
|
1418
|
+
* default locale's, which then says it can't render.
|
|
1323
1419
|
*/
|
|
1324
|
-
async function locateRequest(request, options, pathname
|
|
1420
|
+
async function locateRequest(request, options, pathname) {
|
|
1325
1421
|
const { apiUrl, projectUuid } = options;
|
|
1326
1422
|
const locales = apiUrl && projectUuid ? await readScopeLocales({
|
|
1327
1423
|
apiUrl,
|
|
1328
1424
|
projectUuid,
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
draftId: getRequestDraftId(request, options)
|
|
1332
|
-
}
|
|
1425
|
+
authCookieHeader: getServerAuthCookieHeader(request.headers),
|
|
1426
|
+
draftId: getRequestDraftId(request, options)
|
|
1333
1427
|
}) : [UNNAMED_DEFAULT_LOCALE];
|
|
1334
1428
|
return {
|
|
1335
1429
|
...parseLocalePath(pathname, locales),
|
|
@@ -1338,4 +1432,4 @@ async function locateRequest(request, options, pathname, { live = false } = {})
|
|
|
1338
1432
|
}
|
|
1339
1433
|
|
|
1340
1434
|
//#endregion
|
|
1341
|
-
export { createPageHeadInput, getRuntimePathname, handleCamoxRequest, matchRuntimeRoute };
|
|
1435
|
+
export { createPageHeadInput, getRuntimePathname, handleCamoxRequest, isPublicRequest, matchRuntimeRoute };
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
//#region src/features/runtime/worker.d.ts
|
|
2
|
+
/** What the Worker gets with each request: only `waitUntil` matters here. */
|
|
3
|
+
interface WorkerContext {
|
|
4
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
5
|
+
}
|
|
2
6
|
declare const _default: {
|
|
3
|
-
fetch(request: Request): Promise<Response>;
|
|
7
|
+
fetch(request: Request, _env?: unknown, context?: WorkerContext): Promise<Response>;
|
|
4
8
|
};
|
|
5
9
|
//#endregion
|
|
6
10
|
export { _default as default };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { fetchLocalhostOnLoopback } from "./loopbackFetch.js";
|
|
2
|
+
import { readLiveCacheKey, respondThroughPublicCache } from "./publicCache.js";
|
|
2
3
|
import { shouldHandleRequest, withDevHtml } from "./workerRequest.js";
|
|
3
|
-
import { devHtml, handleCamoxRequest, runtimeBasePath } from "virtual:camox/server";
|
|
4
|
+
import { devHtml, handleCamoxRequest, isPublicRequest, publicCacheSource, runtimeBasePath } from "virtual:camox/server";
|
|
4
5
|
|
|
5
6
|
//#region src/features/runtime/worker.ts
|
|
6
7
|
/**
|
|
@@ -12,9 +13,25 @@ if (devHtml) globalThis.fetch = fetchLocalhostOnLoopback(globalThis.fetch.bind(g
|
|
|
12
13
|
function notFound() {
|
|
13
14
|
return new Response("Not found", { status: 404 });
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Answers a request, from the edge cache when every visitor sees the same. Dev renders the
|
|
18
|
+
* developer's development draft, which nothing shares, and a Worker calling this one without
|
|
19
|
+
* its context can't keep anything past the response.
|
|
20
|
+
*/
|
|
21
|
+
function respond(request, context) {
|
|
22
|
+
const cache = globalThis.caches?.default;
|
|
23
|
+
if (devHtml || !context || !cache || !isPublicRequest(request)) return handleCamoxRequest(request);
|
|
24
|
+
const { apiUrl, projectUuid, build } = publicCacheSource;
|
|
25
|
+
return respondThroughPublicCache(request, {
|
|
26
|
+
cache,
|
|
27
|
+
liveCacheKey: () => readLiveCacheKey(apiUrl, projectUuid),
|
|
28
|
+
build,
|
|
29
|
+
waitUntil: (promise) => context.waitUntil(promise)
|
|
30
|
+
}, () => handleCamoxRequest(request));
|
|
31
|
+
}
|
|
32
|
+
var worker_default = { async fetch(request, _env, context) {
|
|
16
33
|
if (!shouldHandleRequest(request, runtimeBasePath)) return notFound();
|
|
17
|
-
const response = await
|
|
34
|
+
const response = await respond(request, context);
|
|
18
35
|
if (!response) return notFound();
|
|
19
36
|
return withDevHtml(response, devHtml);
|
|
20
37
|
} };
|