camox 0.43.0 → 0.45.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/bin/camox.mjs +9 -1
- package/dist/components/AuthGate.js +0 -6
- package/dist/components/DeviceSelector.js +96 -0
- package/dist/components/PlatformLabel.js +20 -24
- package/dist/core/components/lexical/HighlightNode.js +1 -1
- package/dist/core/components/lexical/InlineContentEditable.js +15 -19
- package/dist/core/components/lexical/InlineLexicalEditor.js +54 -74
- package/dist/core/components/lexical/InlineStylesPlugin.js +57 -41
- package/dist/core/components/lexical/MaxLengthPlugin.js +59 -0
- package/dist/core/components/lexical/SelectionBroadcaster.js +55 -56
- package/dist/core/components/lexical/SidebarLexicalEditor.js +327 -234
- package/dist/core/components/lexical/SummaryToggleGuard.js +42 -0
- package/dist/core/components/lexical/TextLinkPopover.js +169 -180
- package/dist/core/createApp.d.ts +254 -299
- package/dist/core/createApp.js +123 -29
- package/dist/core/createBlock.d.ts +5 -5
- package/dist/core/createBlock.js +399 -152
- package/dist/core/createCollection.d.ts +55 -0
- package/dist/core/createCollection.js +93 -0
- package/dist/core/createLayout.d.ts +197 -61
- package/dist/core/createLayout.js +114 -56
- package/dist/core/defineDocument.d.ts +3 -5
- package/dist/core/derivedRoutes.js +49 -1
- package/dist/core/dom.d.ts +13 -4
- package/dist/core/dom.js +44 -1
- package/dist/core/editing/BlockEditingRuntime.d.ts +16 -0
- package/dist/core/editing/BlockEditingRuntime.js +6 -15
- package/dist/core/editing/CompleteBlockEditingRuntime.js +28 -23
- package/dist/core/editing/createEditableBlock.d.ts +820 -565
- package/dist/core/editing/createEditableBlock.js +2153 -971
- package/dist/core/editing/referenceWrites.js +51 -0
- package/dist/core/editing/useLatestRecord.js +90 -0
- package/dist/core/hooks/useArtifactHost.js +139 -0
- package/dist/core/hooks/useFieldSelection.js +43 -18
- package/dist/core/hooks/useIsEditable.js +12 -10
- package/dist/core/hooks/useOverlayMessage.js +15 -19
- package/dist/core/lib/artifact.d.ts +21 -0
- package/dist/core/lib/artifact.js +154 -0
- package/dist/core/lib/artifactColors.js +123 -0
- package/dist/core/lib/artifactData.d.ts +1 -0
- package/dist/core/lib/artifactData.js +131 -0
- package/dist/core/lib/artifactHost.js +205 -0
- package/dist/core/lib/artifactRuntime.js +36 -0
- package/dist/core/lib/artifactTheme.js +57 -0
- package/dist/core/lib/contentType.d.ts +304 -103
- package/dist/core/lib/contentType.js +207 -168
- package/dist/core/lib/fieldTypes.js +4 -0
- package/dist/core/lib/iconTypes.d.ts +13 -0
- package/dist/core/lib/icons.d.ts +6 -0
- package/dist/core/lib/icons.js +126 -0
- package/dist/core/lib/imagePlaceholder.js +177 -0
- package/dist/core/lib/imageTransform.js +1 -1
- package/dist/core/lib/lexicalReact.d.ts +3 -1
- package/dist/core/lib/lexicalState.js +7 -7
- package/dist/core/lib/modifierFormats.js +1 -1
- package/dist/core/lib/previewPaused.js +9 -0
- package/dist/core/lib/recordScope.js +316 -0
- package/dist/core/lib/reference.d.ts +81 -0
- package/dist/core/lib/reference.js +29 -0
- package/dist/core/lib/textLinks.js +7 -5
- package/dist/core/lib/textStyles.d.ts +6 -8
- package/dist/core/lib/textStyles.js +5 -5
- package/dist/core/notFound.d.ts +7 -0
- package/dist/core/notFound.js +11 -0
- package/dist/core/pageDestinations.js +6 -3
- package/dist/core/recordItem.js +34 -0
- package/dist/core/requestContext.d.ts +26 -0
- package/dist/core/requestContext.js +20 -0
- package/dist/features/canvas/CamoxCanvas.js +1734 -0
- package/dist/features/canvas/CanvasBlockFrame.js +353 -0
- package/dist/features/canvas/CanvasBlockHeader.js +55 -0
- package/dist/features/canvas/CanvasLoadingScreen.js +35 -0
- package/dist/features/canvas/CanvasOverlays.js +254 -0
- package/dist/features/canvas/CanvasPageEdge.js +87 -0
- package/dist/features/canvas/CanvasPageFrame.js +335 -0
- package/dist/features/canvas/CanvasPageHeader.js +125 -0
- package/dist/features/canvas/CanvasPreviewPageSlot.js +103 -0
- package/dist/features/canvas/CanvasShelfMarker.js +255 -0
- package/dist/features/canvas/DerivedPagePathInput.js +238 -0
- package/dist/features/canvas/canvasCamera.js +229 -0
- package/dist/features/canvas/canvasFlight.js +104 -0
- package/dist/features/canvas/canvasFrameCapture.js +364 -0
- package/dist/features/canvas/canvasFrameDocument.js +317 -0
- package/dist/features/canvas/canvasFrameMode.js +53 -0
- package/dist/features/canvas/canvasInsertionSeams.js +44 -0
- package/dist/features/canvas/canvasItems.js +120 -0
- package/dist/features/canvas/canvasOverlayGeometry.js +240 -0
- package/dist/features/canvas/canvasPages.js +79 -0
- package/dist/features/canvas/canvasScreenSpace.js +26 -0
- package/dist/features/canvas/canvasSelection.js +18 -0
- package/dist/features/canvas/canvasStore.js +149 -0
- package/dist/features/canvas/canvasThumbnails.js +304 -0
- package/dist/features/canvas/canvasZoom.js +10 -0
- package/dist/features/canvas/sidebar/CanvasInspector.js +202 -0
- package/dist/features/canvas/sidebar/CanvasNavigator.js +226 -0
- package/dist/features/canvas/sidebar/CanvasSidebar.js +60 -0
- package/dist/features/canvas/sidebar/CanvasSidebarFrame.js +48 -0
- package/dist/features/canvas/sidebar/CanvasSidebarSkeleton.js +39 -0
- package/dist/features/canvas/sidebar/CatalogBlockInspector.js +189 -0
- package/dist/features/canvas/sidebar/CuratedPageInspector.js +69 -0
- package/dist/features/canvas/sidebar/DerivedPageInspector.js +37 -0
- package/dist/features/canvas/sidebar/LayoutPageBlocks.js +78 -0
- package/dist/features/canvas/sidebar/LayoutPageOverview.js +71 -0
- package/dist/features/canvas/sidebar/PageInspector.js +157 -0
- package/dist/features/canvas/sidebar/PageInspectorSkeleton.js +90 -0
- package/dist/features/canvas/sidebar/PageOverview.js +27 -0
- package/dist/features/canvas/sidebar/SingletonPageInspector.js +31 -0
- package/dist/features/canvas/useCanvasCamera.js +803 -0
- package/dist/features/canvas/useCanvasFrame.js +149 -0
- package/dist/features/canvas/useCanvasFrameInput.js +97 -0
- package/dist/features/canvas/useCanvasNearness.js +57 -0
- package/dist/features/canvas/useDiscoveredPreviewPaths.js +62 -0
- package/dist/features/content/CamoxContent.d.ts +2 -5
- package/dist/features/content/CamoxContent.js +174 -271
- package/dist/features/content/CollectionItemSheetContext.js +106 -0
- package/dist/features/content/ContentAssets.js +315 -0
- package/dist/features/content/ContentCollection.js +663 -0
- package/dist/features/content/collection-form.js +58 -0
- package/dist/features/content/components/AssetCard.js +101 -69
- package/dist/features/content/components/AssetCardSkeleton.js +4 -8
- package/dist/features/content/components/ContentSidebar.js +129 -18
- package/dist/features/content/components/ContentSidebarGroup.js +37 -0
- package/dist/features/content/components/DeleteCollectionItemButton.js +202 -0
- package/dist/features/content/components/MissingFromLocale.js +282 -0
- package/dist/features/content/components/UploadDropZone.js +27 -31
- package/dist/features/content/components/UploadProgressDrawer.js +90 -98
- package/dist/features/navigation/navigation.d.ts +66 -20
- package/dist/features/navigation/navigation.js +257 -90
- package/dist/features/og/defaultFonts.js +8 -8
- package/dist/features/og/geist-variable-ttf-base64.js +1 -1
- package/dist/features/og/imageResponse.d.ts +8 -0
- package/dist/features/og/imageResponse.js +26 -0
- package/dist/features/page/CuratedPageContent.js +167 -0
- package/dist/features/page/DerivedPageContent.js +26 -30
- package/dist/features/page/SharedLayoutContent.js +25 -27
- package/dist/features/preview/CamoxPreview.d.ts +23 -61
- package/dist/features/preview/CamoxPreview.js +811 -507
- package/dist/features/preview/EditablePageContent.js +1 -1
- package/dist/features/preview/EditablePageExperience.js +16 -22
- package/dist/features/preview/components/AddBlockDialog.js +270 -0
- package/dist/features/preview/components/AssetFieldEditor.js +173 -154
- package/dist/features/preview/components/AssetLightbox.js +194 -136
- package/dist/features/preview/components/AssetPickerModal.js +153 -170
- package/dist/features/preview/components/BlockActionsPopover.js +150 -149
- package/dist/features/preview/components/BlockErrorBoundary.js +8 -12
- package/dist/features/preview/components/BlockThumbnail.js +176 -100
- package/dist/features/preview/components/CanvasPreview.js +243 -0
- package/dist/features/preview/components/CreatePageModal.js +128 -137
- package/dist/features/preview/components/DebouncedFieldEditor.js +29 -33
- package/dist/features/preview/components/DerivedLayoutDialog.js +46 -29
- package/dist/features/preview/components/DrillRow.js +150 -0
- package/dist/features/preview/components/FieldOverlayStyles.js +33 -10
- package/dist/features/preview/components/FieldToolbar.js +166 -116
- package/dist/features/preview/components/Frame.js +108 -20
- package/dist/features/preview/components/IconFieldEditor.js +227 -0
- package/dist/features/preview/components/ItemFieldsEditor.js +238 -205
- package/dist/features/preview/components/LinkFieldEditor.js +135 -139
- package/dist/features/preview/components/Metadata.js +22 -30
- package/dist/features/preview/components/MobilePreviewDrawer.js +89 -108
- package/dist/features/preview/components/MultipleAssetFieldEditor.js +334 -246
- package/dist/features/preview/components/OverlayTracker.js +10 -42
- package/dist/features/preview/components/Overlays.js +81 -69
- package/dist/features/preview/components/PageInfo.js +1748 -0
- package/dist/features/preview/components/PageInfoSkeleton.js +51 -0
- package/dist/features/preview/components/PageLocationFieldset.js +112 -116
- package/dist/features/preview/components/PageNavigatorHeader.js +24 -0
- package/dist/features/preview/components/PageNicknameField.js +22 -26
- package/dist/features/preview/components/PagePicker.js +145 -147
- package/dist/features/preview/components/PageTree.js +525 -532
- package/dist/features/preview/components/PreviewActivation.js +28 -32
- package/dist/features/preview/components/PreviewPanel.js +620 -319
- package/dist/features/preview/components/PreviewToolbar.js +111 -340
- package/dist/features/preview/components/RecordCard.js +80 -0
- package/dist/features/preview/components/RecordCombobox.js +178 -0
- package/dist/features/preview/components/ReferenceFieldEditor.js +149 -0
- package/dist/features/preview/components/ReferenceListFieldEditor.js +432 -0
- package/dist/features/preview/components/RemovedBlockPlaceholder.js +95 -0
- package/dist/features/preview/components/RepeatableItemsList.js +125 -138
- package/dist/features/preview/components/SelectionEditor.js +993 -0
- package/dist/features/preview/components/ShikiMarkdown.js +14 -18
- package/dist/features/preview/components/SidebarSection.js +52 -64
- package/dist/features/preview/components/StudioEditingFallback.js +56 -0
- package/dist/features/preview/components/UnlinkAssetButton.js +104 -107
- package/dist/features/preview/components/blockGridNavigation.js +15 -0
- package/dist/features/preview/components/contentFieldSchema.js +8 -0
- package/dist/features/preview/components/useFieldWriter.js +200 -0
- package/dist/features/preview/components/useRecordView.js +383 -0
- package/dist/features/preview/components/useRepeatableItemActions.js +35 -37
- package/dist/features/preview/components/useUpdateBlockPosition.js +37 -37
- package/dist/features/preview/editingFrame.js +197 -0
- package/dist/features/preview/overlayHighlights.js +27 -0
- package/dist/features/preview/overlayMessages.js +72 -1
- package/dist/features/preview/pageRecord.js +189 -0
- package/dist/features/preview/previewConstants.js +6 -2
- package/dist/features/preview/previewPageFrame.js +95 -0
- package/dist/features/preview/previewPreparation.js +7 -0
- package/dist/features/preview/previewSelection.js +33 -59
- package/dist/features/preview/previewStore.js +225 -123
- package/dist/features/preview/referencePickerFocus.js +29 -0
- package/dist/features/preview/repeatableItemTargets.js +56 -0
- package/dist/features/preview/selectionReveal.js +21 -0
- package/dist/features/preview/usePreviewViewportActions.js +114 -0
- package/dist/features/preview/viewportContinuity.js +109 -0
- package/dist/features/provider/AuthenticatedCamoxProvider.js +69 -44
- package/dist/features/provider/CamoxProvider.d.ts +3 -6
- package/dist/features/provider/CamoxProvider.js +38 -50
- package/dist/features/provider/CoreCamoxProvider.js +71 -23
- package/dist/features/provider/LocalhostPreviewProvider.js +73 -73
- package/dist/features/provider/PreviewSignInPage.js +54 -0
- package/dist/features/provider/components/CamoxAppContext.js +11 -11
- package/dist/features/provider/components/CommandPalette.js +87 -143
- package/dist/features/provider/useAdminShortcuts.js +7 -11
- package/dist/features/routes/pageRuntime.d.ts +44 -4
- package/dist/features/routes/pageRuntime.js +244 -137
- package/dist/features/runtime/RuntimeChrome.js +117 -115
- package/dist/features/runtime/alternates.js +64 -0
- package/dist/features/runtime/analytics.d.ts +7 -0
- package/dist/features/runtime/analytics.js +66 -0
- package/dist/features/runtime/artifactSources.js +87 -0
- package/dist/features/runtime/editingFallback.js +20 -0
- package/dist/features/runtime/linkPrefetch.js +97 -0
- package/dist/features/runtime/livePages.js +62 -0
- package/dist/features/runtime/llmsTxt.js +40 -0
- package/dist/features/runtime/loopbackFetch.js +18 -0
- package/dist/features/runtime/navigationAccessibility.js +51 -0
- package/dist/features/runtime/navigationTarget.js +23 -2
- package/dist/features/runtime/pageApp.js +216 -141
- package/dist/features/runtime/pageBlockTypes.js +20 -0
- package/dist/features/runtime/pageClient.d.ts +2 -4
- package/dist/features/runtime/pageClient.js +18 -3
- package/dist/features/runtime/pageNavigation.js +254 -32
- package/dist/features/runtime/pageServer.d.ts +2 -4
- package/dist/features/runtime/pageServer.js +8 -1
- package/dist/features/runtime/pageViewport.js +7 -0
- package/dist/features/runtime/runtime.d.ts +60 -23
- package/dist/features/runtime/runtime.js +779 -166
- package/dist/features/runtime/runtimeChromeLoader.js +58 -0
- package/dist/features/runtime/runtimeRoutes.d.ts +19 -0
- package/dist/features/runtime/runtimeRoutes.js +25 -0
- package/dist/features/runtime/scrollRestoration.js +133 -0
- package/dist/features/runtime/studioApp.d.ts +4 -3
- package/dist/features/runtime/studioApp.js +5 -9
- package/dist/features/runtime/studioClient.d.ts +2 -4
- package/dist/features/runtime/studioClient.js +12 -3
- package/dist/features/runtime/studioServer.d.ts +2 -4
- package/dist/features/runtime/studioServer.js +6 -1
- package/dist/features/runtime/worker.d.ts +6 -0
- package/dist/features/runtime/worker.js +23 -0
- package/dist/features/runtime/workerRequest.js +44 -0
- package/dist/features/studio/CamoxStudio.d.ts +2 -6
- package/dist/features/studio/CamoxStudio.js +34 -49
- package/dist/features/studio/FollowAgentProvider.js +338 -0
- package/dist/features/studio/components/ChatSidebar.js +356 -0
- package/dist/features/studio/components/DraftsControl.js +676 -0
- package/dist/features/studio/components/FaviconDialog.js +318 -0
- package/dist/features/studio/components/LocalePicker.js +785 -0
- package/dist/features/studio/components/Navbar.js +123 -103
- package/dist/features/studio/components/ProjectMenu.js +239 -260
- package/dist/features/studio/components/RenameDraftDialog.js +206 -0
- package/dist/features/studio/components/ReviewDraftSheet.js +1472 -0
- package/dist/features/studio/components/ThreadComposer.js +157 -0
- package/dist/features/studio/components/ThreadMarkdown.js +113 -0
- package/dist/features/studio/components/ThreadMessages.js +270 -0
- package/dist/features/studio/components/UserButton.js +34 -23
- package/dist/features/studio/draftLabel.js +33 -0
- package/dist/features/studio/draftNotice.js +13 -0
- package/dist/features/studio/followAgent.js +250 -0
- package/dist/features/studio/followAgentContext.js +7 -0
- package/dist/features/studio/localeOptions.js +118 -0
- package/dist/features/studio/localeSwitch.js +19 -0
- package/dist/features/studio/routes.js +48 -1
- package/dist/features/studio/studioStore.js +64 -1
- package/dist/features/studio/useTheme.js +24 -27
- package/dist/features/vite/artifactRuntimeMinification.js +36 -0
- package/dist/features/vite/auth.js +1 -50
- package/dist/features/vite/blockBoilerplate.js +6 -5
- package/dist/features/vite/blockChunks.js +31 -0
- package/dist/features/vite/bundlerLogs.js +17 -0
- package/dist/features/vite/definitionsSync.js +158 -226
- package/dist/features/vite/devAuthentication.js +4 -16
- package/dist/features/vite/devSignIn.js +87 -0
- package/dist/features/vite/iconGeneration.js +16 -0
- package/dist/features/vite/layoutGeneration.js +11 -1
- package/dist/features/vite/publishSeededContent.js +30 -0
- package/dist/features/vite/routeGeneration.js +6 -7
- package/dist/features/vite/runtimeDev.js +80 -99
- package/dist/features/vite/vite.d.ts +53 -31
- package/dist/features/vite/vite.js +230 -116
- package/dist/features/vite/workerBuild.js +39 -0
- package/dist/hooks/use-debounced-field.js +116 -47
- package/dist/hooks/use-file-upload.js +114 -67
- package/dist/hooks/use-marquee-selection.js +32 -36
- package/dist/hooks/use-page-destinations.js +69 -37
- package/dist/lib/api-client-server.d.ts +5 -0
- package/dist/lib/api-client-server.js +10 -4
- package/dist/lib/api-client.d.ts +6 -0
- package/dist/lib/api-client.js +51 -16
- package/dist/lib/api-socket.js +17 -0
- package/dist/lib/auth-page-styles.js +39 -0
- package/dist/lib/auth.js +139 -60
- package/dist/lib/collection-cache.js +23 -0
- package/dist/lib/draft-scope.d.ts +4 -0
- package/dist/lib/draft-scope.js +222 -0
- package/dist/lib/html.js +12 -0
- package/dist/lib/link-pages.js +154 -0
- package/dist/lib/locale-path.d.ts +22 -0
- package/dist/lib/locale-path.js +57 -0
- package/dist/lib/normalized-data.js +252 -152
- package/dist/lib/preview-handoff.js +13 -0
- package/dist/lib/queries.d.ts +11 -0
- package/dist/lib/queries.js +71 -131
- package/dist/lib/studio-reads.d.ts +14 -0
- package/dist/lib/studio-reads.js +574 -0
- package/dist/lib/use-project-room.js +50 -47
- package/dist/lib/use-thread.js +310 -0
- package/dist/lib/utils.js +25 -29
- package/dist/lib/view-locale-context.js +22 -0
- package/dist/lib/view-locale.js +46 -0
- package/dist/package.js +5 -0
- package/dist/studio-overlays.css +41 -147
- package/dist/studio.css +2 -2
- package/package.json +70 -72
- package/skills/camox/SKILL.md +16 -5
- package/skills/camox/references/artifacts.md +179 -0
- package/skills/camox/references/block-definitions.md +98 -55
- package/skills/camox/references/cli-common.md +18 -6
- package/skills/camox/references/cli-content.md +92 -58
- package/skills/camox/references/cli-publishing.md +26 -0
- package/skills/camox/references/dom-integrations.md +27 -0
- package/skills/camox/references/field-styling.md +7 -7
- package/skills/camox/references/layout-definitions.md +16 -14
- package/skills/camox/references/localization.md +56 -0
- package/skills/camox/references/routed-layouts.md +133 -15
- package/skills/camox-expand/SKILL.md +69 -0
- package/skills/camox-expand/references/jobs.md +39 -0
- package/skills/camoxify/SKILL.md +38 -2
- package/dist/core/components/AddBlockControlBar.js +0 -78
- package/dist/core/hooks/useRequireDraftSource.js +0 -23
- package/dist/features/metadata/sitemap.d.ts +0 -8
- package/dist/features/metadata/sitemap.js +0 -17
- package/dist/features/og/imageResponse.node.d.ts +0 -12
- package/dist/features/og/imageResponse.node.js +0 -49
- package/dist/features/og/imageResponse.workerd.d.ts +0 -12
- package/dist/features/og/imageResponse.workerd.js +0 -40
- package/dist/features/page/PublishedPageContent.js +0 -205
- package/dist/features/page/PublishedPageExperience.js +0 -23
- package/dist/features/preview/DerivedPreview.js +0 -134
- package/dist/features/preview/commentsEnabled.js +0 -8
- package/dist/features/preview/components/AddBlockSidebar.js +0 -381
- package/dist/features/preview/components/AttachedComments.js +0 -194
- package/dist/features/preview/components/CommentHeader.js +0 -122
- package/dist/features/preview/components/CommentSidebar.js +0 -29
- package/dist/features/preview/components/CommentTargetQuote.js +0 -166
- package/dist/features/preview/components/DerivedLayoutSidebar.d.ts +0 -5
- package/dist/features/preview/components/DerivedLayoutSidebar.js +0 -179
- package/dist/features/preview/components/DerivedPageInfoSidebar.js +0 -69
- package/dist/features/preview/components/LeftSidebar.js +0 -68
- package/dist/features/preview/components/PageEditorSidebar.js +0 -563
- package/dist/features/preview/components/PageInfoSidebar.js +0 -1551
- package/dist/features/preview/components/PageNavigatorSidebar.js +0 -78
- package/dist/features/preview/components/PageStatusBadge.js +0 -97
- package/dist/features/preview/components/PeekedBlock.js +0 -217
- package/dist/features/preview/components/PreviewComments.js +0 -56
- package/dist/features/preview/components/PreviewToolbarSpacer.js +0 -43
- package/dist/features/preview/components/PublicationRow.js +0 -422
- package/dist/features/preview/components/PublishDialog.js +0 -194
- package/dist/features/preview/components/RightSidebar.js +0 -59
- package/dist/features/preview/previewCommentsStore.js +0 -115
- package/dist/features/preview/publication.js +0 -81
- package/dist/features/runtime/SharedChromeContext.js +0 -7
- package/dist/features/studio/components/EnvironmentMenu.js +0 -413
- package/dist/features/vite/appGeneration.js +0 -89
- package/dist/lib/view-queries.js +0 -28
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/Matches.d.ts +0 -114
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/RouterProvider.d.ts +0 -25
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/config.d.ts +0 -1
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/defer.d.ts +0 -1
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/fileRoute.d.ts +0 -20
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/global.d.ts +0 -8
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/hash-scroll.d.ts +0 -1
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/index.d.ts +0 -18
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/link.d.ts +0 -161
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/load-matches.d.ts +0 -6
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/location.d.ts +0 -54
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/lru-cache.d.ts +0 -8
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/manifest.d.ts +0 -32
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/new-process-route-tree.d.ts +0 -115
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/not-found.d.ts +0 -1
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/redirect.d.ts +0 -49
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/rewrite.d.ts +0 -1
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/root.d.ts +0 -6
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/route.d.ts +0 -343
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/routeInfo.d.ts +0 -48
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/router.d.ts +0 -695
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/scroll-restoration.d.ts +0 -1
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/searchMiddleware.d.ts +0 -1
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/searchParams.d.ts +0 -5
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/ssr/serializer/RawStream.d.ts +0 -38
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/ssr/serializer/seroval-plugins.d.ts +0 -1
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/ssr/serializer/transformer.d.ts +0 -65
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/stores.d.ts +0 -69
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/structuralSharing.d.ts +0 -1
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/typePrimitives.d.ts +0 -1
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/useLoaderData.d.ts +0 -1
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/useLoaderDeps.d.ts +0 -1
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/useNavigate.d.ts +0 -10
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/useParams.d.ts +0 -1
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/useRouteContext.d.ts +0 -1
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/useSearch.d.ts +0 -1
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/utils.d.ts +0 -28
- package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/validators.d.ts +0 -49
- package/skills/camox/references/cli-environments.md +0 -37
|
@@ -1,16 +1,28 @@
|
|
|
1
|
+
import { serializeJsonForHtml } from "../../lib/html.js";
|
|
2
|
+
import { createRequestContext } from "../../core/requestContext.js";
|
|
1
3
|
import { buildClearServerAuthCookieHeader, getServerAuthCookieHeader } from "../../lib/server-auth-cookie.js";
|
|
2
|
-
import {
|
|
4
|
+
import { UNNAMED_DEFAULT_LOCALE, localizedPath, parseLocalePath } from "../../lib/locale-path.js";
|
|
5
|
+
import { draftNamedBy } from "../../lib/draft-scope.js";
|
|
6
|
+
import { RUNTIME_ROUTE_PATHS, matchRuntimeOwnedRoute } from "./runtimeRoutes.js";
|
|
7
|
+
import { reads } from "../../lib/studio-reads.js";
|
|
8
|
+
import { asCollectionItem, recordMarkdownTemplate, recordPlainText } from "../../core/recordItem.js";
|
|
3
9
|
import { PLATFORM_SCRIPT } from "../../lib/platform.js";
|
|
10
|
+
import { STUDIO_ASSETS_PATH, STUDIO_CONTENT_PATH, matchCanvasOnlyPath, matchCollectionContentPath } from "../studio/routes.js";
|
|
4
11
|
import { STUDIO_THEME_SCRIPT } from "../studio/studioTheme.js";
|
|
5
|
-
import { matchDerivedLayout } from "../../core/derivedRoutes.js";
|
|
6
|
-
import { buildCamoxPageHead, createMarkdownResponse, createServerApiClient,
|
|
12
|
+
import { discoverLayoutPages, matchDerivedLayout } from "../../core/derivedRoutes.js";
|
|
13
|
+
import { buildCamoxPageHead, buildFaviconUrl, buildPageHead, buildPageMeta, createMarkdownResponse, createServerApiClient, createServerStudioCache, isNotFoundError, loadCamoxPageForRequest, parseMarkdownPath, prefersMarkdown, readDraftOrLive, readScopeLocales, renderedHref, resolveMemberProject, withFrontMatter } from "../routes/pageRuntime.js";
|
|
14
|
+
import { alternatesByPage, findAlternates, hreflangLinks } from "./alternates.js";
|
|
15
|
+
import { configuredProviders, renderAnalyticsScripts } from "./analytics.js";
|
|
16
|
+
import { omitArtifactSources } from "./artifactSources.js";
|
|
17
|
+
import { RouteDiscoveryError, listLivePages } from "./livePages.js";
|
|
18
|
+
import { formatLlmsTxt } from "./llmsTxt.js";
|
|
19
|
+
import { getPageBlockTypes } from "./pageBlockTypes.js";
|
|
20
|
+
import { DEVELOPMENT_DRAFT, LOCALE_HEADER } from "@camox/api-contract/drafts";
|
|
7
21
|
import { QueryClient, dehydrate } from "@tanstack/react-query";
|
|
8
22
|
import { createHead, renderSSRHead } from "@unhead/react/server";
|
|
9
23
|
|
|
10
24
|
//#region src/features/runtime/runtime.ts
|
|
11
25
|
const DEFAULT_RUNTIME_BASE_PATH = "";
|
|
12
|
-
const RUNTIME_HEALTH_PATH = "/_camox/health";
|
|
13
|
-
const RUNTIME_REGISTRY_PATH = "/_camox/registry";
|
|
14
26
|
function normalizeRuntimeBasePath(basePath) {
|
|
15
27
|
if (basePath === void 0) return DEFAULT_RUNTIME_BASE_PATH;
|
|
16
28
|
if (!basePath || basePath === "/") return "";
|
|
@@ -24,6 +36,21 @@ function getRuntimePathname(url, runtimeBasePath = DEFAULT_RUNTIME_BASE_PATH) {
|
|
|
24
36
|
if (!pathname.startsWith(`${basePath}/`)) return null;
|
|
25
37
|
return pathname.slice(basePath.length) || "/";
|
|
26
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* The draft the request's URL names, if any, or in dev mode the development draft. Sitemaps and
|
|
41
|
+
* Markdown ignore it: they read Live.
|
|
42
|
+
*/
|
|
43
|
+
function getRequestDraftId(request, options) {
|
|
44
|
+
if (options.developmentDraft) return DEVELOPMENT_DRAFT;
|
|
45
|
+
return draftNamedBy(request.url) ?? void 0;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Whether a response may show what only this visitor sees: a session, or a draft named in the
|
|
49
|
+
* URL. Shared caches must never keep it.
|
|
50
|
+
*/
|
|
51
|
+
function isPrivateRender(request, options) {
|
|
52
|
+
return !!getServerAuthCookieHeader(request.headers) || getRequestDraftId(request, options) !== void 0;
|
|
53
|
+
}
|
|
27
54
|
function escapeXml(value) {
|
|
28
55
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
29
56
|
}
|
|
@@ -31,34 +58,29 @@ function normalizePagePath(path) {
|
|
|
31
58
|
if (!path) return "/";
|
|
32
59
|
return path.startsWith("/") ? path : `/${path}`;
|
|
33
60
|
}
|
|
34
|
-
function getLayoutIdentity(layout
|
|
61
|
+
function getLayoutIdentity(layout) {
|
|
35
62
|
if (!layout) return null;
|
|
36
|
-
const version = source === "live" ? String(layout.livePublishedCheckpointId ?? "none") : String(layout.contentUpdatedAt ?? layout.updatedAt);
|
|
37
63
|
return {
|
|
38
64
|
blockIds: [...layout.beforeBlockIds, ...layout.afterBlockIds],
|
|
39
|
-
id: layout.id,
|
|
40
65
|
layoutId: layout.layoutId,
|
|
41
|
-
version
|
|
66
|
+
version: String(layout.contentUpdatedAt ?? layout.updatedAt)
|
|
42
67
|
};
|
|
43
68
|
}
|
|
44
69
|
function matchRuntimeRoute(pathname) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
};
|
|
49
|
-
if (pathname === RUNTIME_REGISTRY_PATH) return {
|
|
50
|
-
kind: "registry",
|
|
51
|
-
pathname
|
|
52
|
-
};
|
|
53
|
-
if (pathname === "/_camox/data") return {
|
|
54
|
-
kind: "data",
|
|
70
|
+
const ownedRoute = matchRuntimeOwnedRoute(pathname);
|
|
71
|
+
if (ownedRoute) return {
|
|
72
|
+
kind: ownedRoute,
|
|
55
73
|
pathname
|
|
56
74
|
};
|
|
57
75
|
if (pathname === "/camox") return {
|
|
58
76
|
kind: "studio",
|
|
59
77
|
pathname
|
|
60
78
|
};
|
|
61
|
-
if (pathname
|
|
79
|
+
if (matchCanvasOnlyPath(pathname)) return {
|
|
80
|
+
kind: "canvas",
|
|
81
|
+
pathname
|
|
82
|
+
};
|
|
83
|
+
if (pathname === "/camox/content" || pathname === `${"/camox/content"}/` || pathname === "/camox/content/assets" || matchCollectionContentPath(pathname)) return {
|
|
62
84
|
kind: "studio-content",
|
|
63
85
|
pathname
|
|
64
86
|
};
|
|
@@ -66,14 +88,6 @@ function matchRuntimeRoute(pathname) {
|
|
|
66
88
|
kind: "studio-nested",
|
|
67
89
|
pathname
|
|
68
90
|
};
|
|
69
|
-
if (pathname === "/og") return {
|
|
70
|
-
kind: "og",
|
|
71
|
-
pathname
|
|
72
|
-
};
|
|
73
|
-
if (pathname === "/sitemap.xml") return {
|
|
74
|
-
kind: "sitemap",
|
|
75
|
-
pathname
|
|
76
|
-
};
|
|
77
91
|
return {
|
|
78
92
|
kind: "page",
|
|
79
93
|
pathname
|
|
@@ -85,24 +99,156 @@ function withRuntimeBasePath(pathname, runtimeBasePath) {
|
|
|
85
99
|
if (pathname === "/") return `${basePath}/`;
|
|
86
100
|
return `${basePath}${pathname}`;
|
|
87
101
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Answers with an index of the website's Live pages, or a 500 saying why they can't be listed.
|
|
104
|
+
* Sitemaps and llms.txt read Live whoever asks: crawlers only ever see published content. With
|
|
105
|
+
* `everyLocale`, it reads the pages of each of Live's locales. Any read failing, whether Live's
|
|
106
|
+
* locales, a locale's pages or route discovery, fails the whole index: an index silently missing
|
|
107
|
+
* pages is worse than one that visibly fails.
|
|
108
|
+
*/
|
|
109
|
+
async function respondWithLiveWebsite(options, name, respond, { everyLocale = false } = {}) {
|
|
110
|
+
const { apiUrl, projectUuid } = options;
|
|
111
|
+
if (!apiUrl || !projectUuid) return Response.json({ message: `Camox ${name} is not configured.` }, { status: 500 });
|
|
112
|
+
const app = await options.getCamoxApp?.();
|
|
113
|
+
const readLocale = async (locale) => {
|
|
114
|
+
const { projectName, pages } = await createServerApiClient(apiUrl, { locale }).pages.listBySlug({ projectUuid });
|
|
115
|
+
const records = createRecordLookup(options, "live", { locale });
|
|
116
|
+
return {
|
|
117
|
+
locale,
|
|
118
|
+
projectName,
|
|
119
|
+
pages: await listLivePages({
|
|
120
|
+
pages,
|
|
121
|
+
app,
|
|
122
|
+
records
|
|
123
|
+
})
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
let locales;
|
|
127
|
+
let localizations;
|
|
128
|
+
try {
|
|
129
|
+
locales = everyLocale ? [...await readScopeLocales({
|
|
130
|
+
apiUrl,
|
|
131
|
+
projectUuid
|
|
132
|
+
})].sort((a, b) => Number(b.isDefault) - Number(a.isDefault)) : [UNNAMED_DEFAULT_LOCALE];
|
|
133
|
+
localizations = await Promise.all(locales.map(readLocale));
|
|
134
|
+
} catch (error) {
|
|
135
|
+
const discovery = error instanceof RouteDiscoveryError;
|
|
136
|
+
const cause = discovery ? error.cause : error;
|
|
137
|
+
console.error(`[camox] ${discovery ? "Route discovery" : "Reading Live"} failed while building the ${name}.`, cause);
|
|
138
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
139
|
+
return Response.json({ message: `Camox ${name} failed: ${reason}` }, { status: 500 });
|
|
140
|
+
}
|
|
141
|
+
const [defaultLocale, ...otherLocales] = localizations;
|
|
142
|
+
return respond({
|
|
143
|
+
app,
|
|
144
|
+
projectName: defaultLocale.projectName,
|
|
145
|
+
localizations: [defaultLocale, ...otherLocales],
|
|
146
|
+
locales
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/** A path's absolute URL, with the runtime base path. */
|
|
150
|
+
function absoluteUrl(request, options) {
|
|
151
|
+
const { origin } = new URL(request.url);
|
|
152
|
+
return (path) => `${origin}${withRuntimeBasePath(path, options.runtimeBasePath)}`;
|
|
153
|
+
}
|
|
154
|
+
/** `body` with an ETag derived from it, or a 304 when the request's `If-None-Match` names it. */
|
|
155
|
+
async function withEtag(request, body, headers) {
|
|
156
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(body));
|
|
157
|
+
const etag = `"${[...new Uint8Array(digest)].slice(0, 16).map((byte) => byte.toString(16).padStart(2, "0")).join("")}"`;
|
|
158
|
+
const taggedHeaders = {
|
|
159
|
+
...headers,
|
|
160
|
+
ETag: etag
|
|
161
|
+
};
|
|
162
|
+
if ((request.headers.get("If-None-Match") ?? "").split(",").map((tag) => tag.trim().replace(/^W\//, "")).some((tag) => tag === etag || tag === "*")) return new Response(null, {
|
|
163
|
+
status: 304,
|
|
164
|
+
headers: taggedHeaders
|
|
165
|
+
});
|
|
166
|
+
return new Response(body, { headers: taggedHeaders });
|
|
167
|
+
}
|
|
168
|
+
function createLlmsTxtResponse(request, options) {
|
|
169
|
+
return respondWithLiveWebsite(options, "llms.txt", (website) => withEtag(request, formatLlmsTxt({
|
|
170
|
+
projectName: website.projectName,
|
|
171
|
+
pages: website.localizations[0].pages,
|
|
172
|
+
layouts: (website.app?.getLayouts() ?? []).map(({ _internal }) => _internal),
|
|
173
|
+
url: absoluteUrl(request, options)
|
|
174
|
+
}), {
|
|
175
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
176
|
+
"Cache-Control": "public, max-age=300, stale-while-revalidate=3600"
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
/** Tells LLM tools fetching a page's HTML where the website's llms.txt is. */
|
|
180
|
+
function llmsTxtLinkHeader(options) {
|
|
181
|
+
return `<${withRuntimeBasePath(RUNTIME_ROUTE_PATHS["llms-txt"], options.runtimeBasePath)}>; rel="llms-txt"`;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* One sitemap for every locale: each localization of a page once, with the page's alternates as
|
|
185
|
+
* `xhtml:link`s, as its `hreflang` links list them.
|
|
186
|
+
*/
|
|
187
|
+
function createSitemapResponse(request, options) {
|
|
188
|
+
return respondWithLiveWebsite(options, "sitemap", (website) => {
|
|
189
|
+
const url = absoluteUrl(request, options);
|
|
190
|
+
const alternates = alternatesByPage(website.localizations);
|
|
191
|
+
const entries = website.localizations.flatMap(({ locale, pages }) => pages.map(({ key, fullPath, updatedAt }) => {
|
|
192
|
+
const lastmod = updatedAt == null ? "" : `\n <lastmod>${escapeXml(new Date(updatedAt).toISOString())}</lastmod>`;
|
|
193
|
+
const links = hreflangLinks(alternates.get(key) ?? [], website.locales, url).map(({ hreflang, href }) => `\n <xhtml:link rel="alternate" hreflang="${escapeXml(hreflang)}" href="${escapeXml(href)}"/>`).join("");
|
|
194
|
+
return ` <url>
|
|
195
|
+
<loc>${escapeXml(url(localizedPath(fullPath, locale)))}</loc>${lastmod}${links}
|
|
100
196
|
</url>`;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
197
|
+
})).join("\n");
|
|
198
|
+
return new Response(`<?xml version="1.0" encoding="UTF-8"?>
|
|
199
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
|
104
200
|
${entries}
|
|
105
201
|
</urlset>`, { headers: { "Content-Type": "application/xml; charset=utf-8" } });
|
|
202
|
+
}, { everyLocale: true });
|
|
203
|
+
}
|
|
204
|
+
/** The request's member project, if its session is valid. Without an API, nobody is a member. */
|
|
205
|
+
async function resolveRequestMember(request, options) {
|
|
206
|
+
if (!options.apiUrl || !options.projectUuid) return { clearAuthCookie: false };
|
|
207
|
+
return resolveMemberProject({
|
|
208
|
+
apiUrl: options.apiUrl,
|
|
209
|
+
authCookieHeader: getServerAuthCookieHeader(request.headers),
|
|
210
|
+
projectUuid: options.projectUuid
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* The first discovered page of a derived layout, which the Canvas previews by default. With
|
|
215
|
+
* `recordId`, the page rendering that record, which the Studio follows when its slug changes.
|
|
216
|
+
* Records are found by their values in the locale `LOCALE_HEADER` names, the default locale
|
|
217
|
+
* without it, and the path is that locale's, without its prefix: route patterns aren't localized.
|
|
218
|
+
*/
|
|
219
|
+
async function createRoutesResponse(request, options) {
|
|
220
|
+
const camoxApp = await options.getCamoxApp?.();
|
|
221
|
+
if (!camoxApp) return Response.json({ message: "Camox app registry is not available." }, { status: 500 });
|
|
222
|
+
const { searchParams } = new URL(request.url);
|
|
223
|
+
const layoutId = searchParams.get("layoutId") ?? "";
|
|
224
|
+
const recordId = searchParams.get("recordId");
|
|
225
|
+
const layout = camoxApp.getLayoutById(layoutId);
|
|
226
|
+
if (layout?._internal.kind !== "derived") return new Response("Not found", { status: 404 });
|
|
227
|
+
const { project, clearAuthCookie } = await resolveRequestMember(request, options);
|
|
228
|
+
const headers = {
|
|
229
|
+
"Cache-Control": "private, no-store",
|
|
230
|
+
Vary: `Cookie, ${LOCALE_HEADER}`,
|
|
231
|
+
...clearAuthCookie ? { "Set-Cookie": buildClearServerAuthCookieHeader() } : {}
|
|
232
|
+
};
|
|
233
|
+
const tag = request.headers.get(LOCALE_HEADER);
|
|
234
|
+
try {
|
|
235
|
+
const pages = await discoverLayoutPages(layout, createLayoutRequestContext(options, project ? "draft" : "live", {
|
|
236
|
+
authCookieHeader: getServerAuthCookieHeader(request.headers),
|
|
237
|
+
locale: tag ? {
|
|
238
|
+
tag,
|
|
239
|
+
isDefault: false
|
|
240
|
+
} : void 0
|
|
241
|
+
}));
|
|
242
|
+
const page = recordId ? pages.find((entry) => entry.recordId === recordId) : pages[0];
|
|
243
|
+
return Response.json({ path: page?.path ?? null }, { headers });
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.error(`[camox] Route discovery failed for layout "${layoutId}".`, error);
|
|
246
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
247
|
+
return Response.json({ message }, {
|
|
248
|
+
status: 500,
|
|
249
|
+
headers
|
|
250
|
+
});
|
|
251
|
+
}
|
|
106
252
|
}
|
|
107
253
|
async function createOgResponse(request, options) {
|
|
108
254
|
const camoxApp = await options.getCamoxApp?.();
|
|
@@ -156,9 +302,19 @@ function createPageHeadInput(head) {
|
|
|
156
302
|
}
|
|
157
303
|
const pageLinks = [];
|
|
158
304
|
for (const entry of links) {
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
305
|
+
if (!entry.href) continue;
|
|
306
|
+
if (entry.rel === "icon") {
|
|
307
|
+
pageLinks.push({
|
|
308
|
+
rel: "icon",
|
|
309
|
+
href: entry.href,
|
|
310
|
+
"data-camox-page-head": "true"
|
|
311
|
+
});
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (entry.rel === "alternate" && (entry.hreflang || entry.type)) pageLinks.push({
|
|
315
|
+
rel: "alternate",
|
|
316
|
+
...entry.hreflang && { hreflang: entry.hreflang },
|
|
317
|
+
...entry.type && { type: entry.type },
|
|
162
318
|
href: entry.href,
|
|
163
319
|
"data-camox-page-head": "true"
|
|
164
320
|
});
|
|
@@ -169,10 +325,11 @@ function createPageHeadInput(head) {
|
|
|
169
325
|
link: pageLinks
|
|
170
326
|
};
|
|
171
327
|
}
|
|
172
|
-
function renderHead(document, pageHead, stylesheetUrl, studioStylesheetUrl) {
|
|
328
|
+
function renderHead(document, pageHead, stylesheetUrl, studioStylesheetUrl, locale) {
|
|
173
329
|
const head = createHead();
|
|
174
330
|
head.push(createDefaultHeadInput(stylesheetUrl));
|
|
175
331
|
head.push(document);
|
|
332
|
+
if (locale) head.push({ htmlAttrs: { lang: locale.tag } });
|
|
176
333
|
if (studioStylesheetUrl) head.push({ link: [{
|
|
177
334
|
rel: "stylesheet",
|
|
178
335
|
href: studioStylesheetUrl,
|
|
@@ -181,15 +338,28 @@ function renderHead(document, pageHead, stylesheetUrl, studioStylesheetUrl) {
|
|
|
181
338
|
head.push(pageHead);
|
|
182
339
|
return renderSSRHead(head);
|
|
183
340
|
}
|
|
184
|
-
|
|
185
|
-
|
|
341
|
+
/**
|
|
342
|
+
* The browser loads a page's blocks before hydrating it. Preloading them alongside the entry
|
|
343
|
+
* script avoids a request waterfall.
|
|
344
|
+
*/
|
|
345
|
+
function renderBlockPreloads(options, dehydratedState) {
|
|
346
|
+
const preloads = options.blockPreloads;
|
|
347
|
+
if (!preloads) return "";
|
|
348
|
+
const urls = /* @__PURE__ */ new Set();
|
|
349
|
+
for (const type of getPageBlockTypes(dehydratedState)) {
|
|
350
|
+
const modulePath = preloads.modulePaths[type];
|
|
351
|
+
if (!modulePath) continue;
|
|
352
|
+
if (!preloads.chunks) urls.add(modulePath);
|
|
353
|
+
for (const chunk of preloads.chunks?.[modulePath] ?? []) urls.add(`/${chunk}`);
|
|
354
|
+
}
|
|
355
|
+
return [...urls].map((url) => `<link rel="modulepreload" href="${escapeXml(url)}">`).join("");
|
|
186
356
|
}
|
|
187
357
|
function renderClientEntryScript(src) {
|
|
188
358
|
if (!src) return "";
|
|
189
359
|
return `<script type="module" src="${escapeXml(src)}"><\/script>`;
|
|
190
360
|
}
|
|
191
361
|
async function preparePreviewDocument(input, options, document, content) {
|
|
192
|
-
const siteHead = renderHead(document, createPageHeadInput(input.head ?? {}), options.stylesheetUrl);
|
|
362
|
+
const siteHead = renderHead(document, createPageHeadInput(input.head ?? {}), options.stylesheetUrl, void 0, input.locale);
|
|
193
363
|
const href = new URL(withRuntimeBasePath(input.pathname, options.runtimeBasePath), input.href).href;
|
|
194
364
|
const siteHtml = content ?? (options.renderPage ? await options.renderPage({
|
|
195
365
|
...input,
|
|
@@ -199,63 +369,193 @@ async function preparePreviewDocument(input, options, document, content) {
|
|
|
199
369
|
}) : "");
|
|
200
370
|
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>`;
|
|
201
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* The render input a page's HTML embeds for hydration. Visitors load public pages, whose HTML
|
|
374
|
+
* already holds each rendered artifact source in its frame, so their copy leaves those out.
|
|
375
|
+
*/
|
|
376
|
+
function hydrationInputFor(input, html) {
|
|
377
|
+
if (input.presentation === "studio") return input;
|
|
378
|
+
return {
|
|
379
|
+
...input,
|
|
380
|
+
dehydratedState: omitArtifactSources(input.dehydratedState, html)
|
|
381
|
+
};
|
|
382
|
+
}
|
|
202
383
|
function renderStudioThemeScript(enabled) {
|
|
203
384
|
return enabled ? `<script data-camox-studio>${STUDIO_THEME_SCRIPT}${PLATFORM_SCRIPT}<\/script>` : "";
|
|
204
385
|
}
|
|
205
|
-
|
|
206
|
-
|
|
386
|
+
/**
|
|
387
|
+
* The analytics providers a render counts its reader with: only a visitor, meaning someone the
|
|
388
|
+
* API didn't confirm as a member, on a Live page. A development draft never counts anyone.
|
|
389
|
+
*/
|
|
390
|
+
function visitorAnalytics(options, render, project) {
|
|
391
|
+
if (render.studio || render.draftId || options.developmentDraft) return void 0;
|
|
392
|
+
return configuredProviders(project.analytics);
|
|
393
|
+
}
|
|
394
|
+
/** Whether a request is a preview link's token handoff, which signs in before reading anything. */
|
|
395
|
+
function isPreviewHandoff(request, options) {
|
|
396
|
+
const { searchParams } = new URL(request.url);
|
|
397
|
+
return searchParams.has("camox-preview") && searchParams.has("ott") && !!options.apiUrl && !!options.authenticationUrl && !!options.projectUuid && !!options.renderPage;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* The alternates of the page a request renders, where it exists in each of the scope's locales,
|
|
401
|
+
* and its head with their `hreflang` links. Without a head, there is none to add them to.
|
|
402
|
+
*/
|
|
403
|
+
async function readAlternates(request, options, { located, identity, head, ...reading }) {
|
|
404
|
+
const alternates = await findRequestAlternates(options, located, identity, reading);
|
|
405
|
+
const links = hreflangLinks(alternates, located.locales, absoluteUrl(request, options));
|
|
406
|
+
return {
|
|
407
|
+
alternates,
|
|
408
|
+
head: head && withHreflangs(head, links)
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
/** Where the page a request renders exists in each of the scope's locales: itself alone in one. */
|
|
412
|
+
async function findRequestAlternates(options, located, identity, { authCookieHeader, draftId }) {
|
|
413
|
+
const { apiUrl, projectUuid } = options;
|
|
414
|
+
const here = [{
|
|
415
|
+
locale: located.locale.tag,
|
|
416
|
+
path: localizedPath(located.path, located.locale)
|
|
417
|
+
}];
|
|
418
|
+
if (!apiUrl || !projectUuid || located.locales.length < 2) return here;
|
|
419
|
+
const app = await options.getCamoxApp?.();
|
|
420
|
+
const reading = {
|
|
421
|
+
authCookieHeader: draftId ? authCookieHeader : void 0,
|
|
422
|
+
draftId
|
|
423
|
+
};
|
|
424
|
+
return findAlternates({
|
|
425
|
+
identity,
|
|
426
|
+
located,
|
|
427
|
+
layouts: app?.getLayouts() ?? [],
|
|
428
|
+
reads: {
|
|
429
|
+
pages: async (locale) => (await createServerApiClient(apiUrl, {
|
|
430
|
+
...reading,
|
|
431
|
+
locale
|
|
432
|
+
}).pages.listBySlug({ projectUuid })).pages,
|
|
433
|
+
context: (locale) => createLayoutRequestContext(options, draftId ? "draft" : "live", {
|
|
434
|
+
...reading,
|
|
435
|
+
locale
|
|
436
|
+
})
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
/** A page's head with its alternates' `hreflang` links. */
|
|
441
|
+
function withHreflangs(head, hreflangs) {
|
|
442
|
+
if (!hreflangs.length) return head;
|
|
443
|
+
const links = hreflangs.map((link) => ({
|
|
444
|
+
rel: "alternate",
|
|
445
|
+
...link
|
|
446
|
+
}));
|
|
447
|
+
return {
|
|
448
|
+
...head,
|
|
449
|
+
links: [...head.links ?? [], ...links]
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* The sign-in page a preview link opens on. No page, project or locale lookup yet: the browser
|
|
454
|
+
* authenticates first, then loads the page again.
|
|
455
|
+
*/
|
|
456
|
+
async function createPreviewHandoffResponse({ options, pathname, request }) {
|
|
457
|
+
const locale = UNNAMED_DEFAULT_LOCALE;
|
|
458
|
+
const input = {
|
|
459
|
+
previewHandoff: true,
|
|
460
|
+
presentation: "public",
|
|
461
|
+
apiUrl: options.apiUrl,
|
|
462
|
+
authenticationUrl: options.authenticationUrl,
|
|
463
|
+
projectUuid: options.projectUuid,
|
|
464
|
+
href: request.url,
|
|
465
|
+
pathname,
|
|
466
|
+
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath),
|
|
467
|
+
locale,
|
|
468
|
+
locales: [locale],
|
|
469
|
+
head: {},
|
|
470
|
+
loaderData: null,
|
|
471
|
+
layoutIdentity: null,
|
|
472
|
+
dehydratedState: dehydrate(new QueryClient())
|
|
473
|
+
};
|
|
474
|
+
const html = await options.renderPage(input);
|
|
475
|
+
return new Response(`<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="referrer" content="no-referrer"><title>Camox draft preview</title><script id="__CAMOX_DATA__" type="application/json">${serializeJsonForHtml(input)}<\/script></head><body><div id="root">${html}</div>${renderClientEntryScript(options.pageClientEntryUrl)}</body></html>`, { headers: {
|
|
476
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
477
|
+
"Cache-Control": "private, no-store",
|
|
478
|
+
"Referrer-Policy": "no-referrer"
|
|
479
|
+
} });
|
|
480
|
+
}
|
|
481
|
+
async function createPageHtmlResponse({ options, pathname, located, request }) {
|
|
482
|
+
if (!options.apiUrl || !options.authenticationUrl || !options.projectUuid || !options.renderPage) return createPageDataResponse({
|
|
207
483
|
options,
|
|
208
484
|
pathname,
|
|
485
|
+
located,
|
|
209
486
|
request
|
|
210
487
|
});
|
|
211
|
-
const singleton = await createDerivedResponse(options, pathname,
|
|
488
|
+
const singleton = await createDerivedResponse(options, pathname, located, request, { singletonOnly: true });
|
|
212
489
|
if (singleton) return singleton;
|
|
213
490
|
const queryClient = new QueryClient();
|
|
214
491
|
try {
|
|
215
492
|
const result = await loadCamoxPageForRequest({
|
|
216
493
|
apiUrl: options.apiUrl,
|
|
217
494
|
authCookieHeader: getServerAuthCookieHeader(request.headers),
|
|
218
|
-
|
|
495
|
+
draftId: getRequestDraftId(request, options),
|
|
496
|
+
locale: located.locale,
|
|
219
497
|
origin: new URL(request.url).origin,
|
|
220
|
-
pathname,
|
|
221
|
-
|
|
498
|
+
pathname: located.path,
|
|
499
|
+
projectUuid: options.projectUuid,
|
|
222
500
|
queryClient
|
|
223
501
|
});
|
|
224
502
|
const headers = new Headers({
|
|
225
503
|
"Content-Type": "text/html; charset=utf-8",
|
|
226
|
-
Vary: "Cookie"
|
|
504
|
+
Vary: "Cookie",
|
|
505
|
+
Link: llmsTxtLinkHeader(options)
|
|
227
506
|
});
|
|
228
|
-
if (
|
|
507
|
+
if (isPrivateRender(request, options)) headers.set("Cache-Control", "private, no-store");
|
|
229
508
|
if (result.shouldClearAuthCookie) headers.append("Set-Cookie", buildClearServerAuthCookieHeader());
|
|
230
509
|
const camoxApp = await options.getCamoxApp?.();
|
|
231
510
|
const document = await options.getDocument?.() ?? {};
|
|
232
|
-
const head =
|
|
511
|
+
const { alternates, head } = await readAlternates(request, options, {
|
|
512
|
+
located,
|
|
513
|
+
identity: {
|
|
514
|
+
kind: "curated",
|
|
515
|
+
pageId: result.data.page.page.id
|
|
516
|
+
},
|
|
517
|
+
head: camoxApp ? buildCamoxPageHead(camoxApp, result.data, located.locale) : {},
|
|
518
|
+
authCookieHeader: getServerAuthCookieHeader(request.headers),
|
|
519
|
+
draftId: result.draftId
|
|
520
|
+
});
|
|
233
521
|
const dehydratedState = dehydrate(queryClient);
|
|
522
|
+
const { project } = result;
|
|
523
|
+
const studio = project !== void 0;
|
|
524
|
+
const analytics = visitorAnalytics(options, {
|
|
525
|
+
studio,
|
|
526
|
+
draftId: result.draftId
|
|
527
|
+
}, result.data.page.project);
|
|
234
528
|
const pageRenderInput = {
|
|
235
|
-
|
|
236
|
-
|
|
529
|
+
alternates,
|
|
530
|
+
project,
|
|
531
|
+
presentation: studio ? "studio" : "public",
|
|
532
|
+
...analytics && { analytics },
|
|
237
533
|
apiUrl: options.apiUrl,
|
|
238
534
|
authenticationUrl: options.authenticationUrl,
|
|
239
535
|
dehydratedState,
|
|
240
|
-
|
|
536
|
+
draftId: result.draftId,
|
|
537
|
+
refusedDraft: result.refusedDraft,
|
|
241
538
|
head,
|
|
242
|
-
href:
|
|
243
|
-
layoutIdentity: getLayoutIdentity(result.data.page.layout
|
|
539
|
+
href: renderedHref(request.url, result),
|
|
540
|
+
layoutIdentity: getLayoutIdentity(result.data.page.layout),
|
|
244
541
|
loaderData: result.data,
|
|
245
542
|
pathname,
|
|
246
|
-
|
|
543
|
+
projectUuid: options.projectUuid,
|
|
247
544
|
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath),
|
|
248
|
-
|
|
545
|
+
locale: located.locale,
|
|
546
|
+
locales: located.locales
|
|
249
547
|
};
|
|
250
|
-
await preparePreviewDocument(pageRenderInput, options, document,
|
|
548
|
+
await preparePreviewDocument(pageRenderInput, options, document, studio ? void 0 : "");
|
|
251
549
|
const appHtml = await options.renderPage(pageRenderInput);
|
|
252
|
-
const renderedHead = renderHead(
|
|
550
|
+
const renderedHead = renderHead(studio ? {} : document, createPageHeadInput(head), studio ? void 0 : options.stylesheetUrl, studio ? options.studioStylesheetUrl : void 0, studio ? void 0 : located.locale);
|
|
253
551
|
return new Response(`<!doctype html>
|
|
254
552
|
<html${renderedHead.htmlAttrs}>
|
|
255
553
|
<head>
|
|
256
|
-
${renderStudioThemeScript(
|
|
554
|
+
${renderStudioThemeScript(studio)}
|
|
257
555
|
${renderedHead.headTags}
|
|
258
|
-
|
|
556
|
+
${renderAnalyticsScripts(analytics)}
|
|
557
|
+
${renderBlockPreloads(options, dehydratedState)}
|
|
558
|
+
<script id="__CAMOX_DATA__" type="application/json">${serializeJsonForHtml(hydrationInputFor(pageRenderInput, appHtml))}<\/script>
|
|
259
559
|
</head>
|
|
260
560
|
<body${renderedHead.bodyAttrs}>
|
|
261
561
|
${renderedHead.bodyTagsOpen}
|
|
@@ -266,71 +566,252 @@ ${renderClientEntryScript(options.pageClientEntryUrl)}
|
|
|
266
566
|
</html>`, { headers });
|
|
267
567
|
} catch (error) {
|
|
268
568
|
if (isNotFoundError(error)) {
|
|
269
|
-
const derived = await createDerivedResponse(options, pathname, request);
|
|
569
|
+
const derived = await createDerivedResponse(options, pathname, located, request);
|
|
270
570
|
if (derived) return derived;
|
|
271
571
|
return new Response("No Camox page on this path", { status: 404 });
|
|
272
572
|
}
|
|
273
573
|
throw error;
|
|
274
574
|
}
|
|
275
575
|
}
|
|
276
|
-
|
|
576
|
+
/**
|
|
577
|
+
* The context `loader` and `routes` get. Its record lookup reads each record's current content;
|
|
578
|
+
* only draft reads carry the editor's session, and `draftId` names the draft they read.
|
|
579
|
+
*/
|
|
580
|
+
function createLayoutRequestContext(options, source, reading) {
|
|
581
|
+
return createRequestContext({ records: createRecordLookup(options, source, reading) });
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Reads the records `loader` and `routes` ask for, in `locale` or the default locale; none
|
|
585
|
+
* without an API.
|
|
586
|
+
*/
|
|
587
|
+
function createRecordLookup(options, source, { authCookieHeader, draftId, locale } = {}) {
|
|
588
|
+
const { apiUrl, projectUuid } = options;
|
|
589
|
+
if (!apiUrl || !projectUuid) return void 0;
|
|
590
|
+
const api = createServerApiClient(apiUrl, {
|
|
591
|
+
authCookieHeader: source === "draft" ? authCookieHeader : void 0,
|
|
592
|
+
draftId,
|
|
593
|
+
locale
|
|
594
|
+
});
|
|
595
|
+
return {
|
|
596
|
+
findByField: (input) => api.collectionDefinitions.findRecordsByField({
|
|
597
|
+
...input,
|
|
598
|
+
projectUuid
|
|
599
|
+
}),
|
|
600
|
+
listPageValues: (input) => api.collectionDefinitions.listRecordPageValues({
|
|
601
|
+
...input,
|
|
602
|
+
projectUuid
|
|
603
|
+
})
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
const RECORD_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
607
|
+
/** An envelope names its record; it doesn't authorize anything, the API does. */
|
|
608
|
+
function isValidCollectionItem(app, result) {
|
|
609
|
+
const data = result.data;
|
|
610
|
+
return typeof result.collectionId === "string" && !!app?.getCollectionById(result.collectionId) && typeof data?.id === "string" && RECORD_ID.test(data.id) && typeof data.content === "object" && data.content !== null;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Runs a code-owned page's loader in the page's locale. The envelope survives SSR, navigation and
|
|
614
|
+
* hydration, and its data renders as returned: Camox never translates it.
|
|
615
|
+
*/
|
|
616
|
+
async function loadLayoutResult(app, match, context, locale) {
|
|
617
|
+
const { loader, id } = match.layout._internal;
|
|
618
|
+
if (!loader) return void 0;
|
|
619
|
+
const result = await loader({
|
|
620
|
+
params: match.params,
|
|
621
|
+
context,
|
|
622
|
+
locale: locale.tag
|
|
623
|
+
});
|
|
624
|
+
const serializedResult = result === void 0 ? void 0 : JSON.parse(JSON.stringify(result));
|
|
625
|
+
const record = asCollectionItem(serializedResult);
|
|
626
|
+
if (!(record ? isValidCollectionItem(app, record) : serializedResult?.kind === "data" && Object.hasOwn(serializedResult, "data"))) throw new Error(`Layout "${id}" loader must return { kind: "data", data } with JSON-serializable data, or a collection-item envelope naming a registered collection and a record id`);
|
|
627
|
+
return serializedResult;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* The Markdown of a singleton or derived page: front matter, its before blocks, the layout's
|
|
631
|
+
* toMarkdown output, then its after blocks. Without toMarkdown, the page is HTML only.
|
|
632
|
+
*/
|
|
633
|
+
async function createLayoutMarkdownResponse(options, match, locale, page) {
|
|
634
|
+
const { toMarkdown, id } = match.layout._internal;
|
|
635
|
+
const { apiUrl, projectUuid } = options;
|
|
636
|
+
if (!toMarkdown || !apiUrl || !projectUuid) return null;
|
|
637
|
+
const api = createServerApiClient(apiUrl, { locale });
|
|
277
638
|
const app = await options.getCamoxApp?.();
|
|
278
|
-
|
|
639
|
+
/** The layout's own Markdown, between its before and after blocks, and what built it. */
|
|
640
|
+
const layoutMarkdown = async () => {
|
|
641
|
+
const result = await loadLayoutResult(app, match, createLayoutRequestContext(options, "live", { locale }), locale);
|
|
642
|
+
const record = asCollectionItem(result);
|
|
643
|
+
const collection = record && app?.getCollectionById(record.collectionId);
|
|
644
|
+
if (!record || !collection) return {
|
|
645
|
+
body: toMarkdown({
|
|
646
|
+
data: result?.data,
|
|
647
|
+
params: match.params
|
|
648
|
+
}),
|
|
649
|
+
data: result?.data
|
|
650
|
+
};
|
|
651
|
+
const { data } = record;
|
|
652
|
+
const template = recordMarkdownTemplate(collection, (item) => toMarkdown({
|
|
653
|
+
data,
|
|
654
|
+
params: match.params,
|
|
655
|
+
item
|
|
656
|
+
}));
|
|
657
|
+
return {
|
|
658
|
+
body: [await api.collectionDefinitions.recordMarkdown({
|
|
659
|
+
projectUuid,
|
|
660
|
+
collectionId: record.collectionId,
|
|
661
|
+
id: data.id,
|
|
662
|
+
toMarkdown: template
|
|
663
|
+
})],
|
|
664
|
+
data,
|
|
665
|
+
item: recordPlainText(collection, data.content)
|
|
666
|
+
};
|
|
667
|
+
};
|
|
668
|
+
let rendered;
|
|
669
|
+
try {
|
|
670
|
+
rendered = await layoutMarkdown();
|
|
671
|
+
} catch (error) {
|
|
672
|
+
if (isNotFoundError(error)) return new Response("Page not found", { status: 404 });
|
|
673
|
+
throw error;
|
|
674
|
+
}
|
|
675
|
+
const slots = await api.blocks.getLayoutMarkdown({
|
|
676
|
+
projectUuid,
|
|
677
|
+
layoutId: id
|
|
678
|
+
}).catch((error) => {
|
|
679
|
+
if (isNotFoundError(error)) return {
|
|
680
|
+
before: "",
|
|
681
|
+
after: "",
|
|
682
|
+
projectName: ""
|
|
683
|
+
};
|
|
684
|
+
throw error;
|
|
685
|
+
});
|
|
686
|
+
const meta = buildPageMeta({
|
|
687
|
+
layout: match.layout._internal,
|
|
688
|
+
pageMetaTitle: match.layout._internal.title,
|
|
689
|
+
pageMetaDescription: void 0,
|
|
690
|
+
data: rendered.data,
|
|
691
|
+
item: rendered.item,
|
|
692
|
+
params: match.params,
|
|
693
|
+
projectName: slots.projectName,
|
|
694
|
+
pageFullPath: page.pathname
|
|
695
|
+
});
|
|
696
|
+
const markdown = [
|
|
697
|
+
slots.before,
|
|
698
|
+
...rendered.body,
|
|
699
|
+
slots.after
|
|
700
|
+
].filter(Boolean).join("\n\n");
|
|
701
|
+
return new Response(withFrontMatter(markdown, {
|
|
702
|
+
...meta,
|
|
703
|
+
url: page.url
|
|
704
|
+
}), { headers: { "Content-Type": "text/markdown; charset=utf-8" } });
|
|
705
|
+
}
|
|
706
|
+
async function createDerivedResponse(options, pathname, located, request, { dataOnly = false, singletonOnly = false } = {}) {
|
|
707
|
+
const app = await options.getCamoxApp?.();
|
|
708
|
+
const match = app && matchDerivedLayout(app.getLayouts(), located.path);
|
|
279
709
|
if (!match || !dataOnly && !options.renderPage) return null;
|
|
280
710
|
if (singletonOnly && match.layout._internal.kind !== "singleton") return null;
|
|
281
711
|
try {
|
|
282
|
-
const data = await match.layout._internal.loader?.({ params: match.params });
|
|
283
|
-
const serializedData = data === void 0 ? void 0 : JSON.parse(JSON.stringify(data));
|
|
284
712
|
const queryClient = new QueryClient();
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
713
|
+
const { project, clearAuthCookie } = await resolveRequestMember(request, options);
|
|
714
|
+
const studio = project !== void 0;
|
|
715
|
+
const authCookieHeader = studio ? getServerAuthCookieHeader(request.headers) : void 0;
|
|
716
|
+
const requestedDraft = getRequestDraftId(request, options);
|
|
717
|
+
const readLayout = () => readDraftOrLive(requestedDraft, studio, (draftId) => createServerApiClient(options.apiUrl, {
|
|
718
|
+
authCookieHeader,
|
|
719
|
+
draftId,
|
|
720
|
+
locale: located.locale
|
|
721
|
+
}).layouts.get({
|
|
722
|
+
projectUuid: options.projectUuid,
|
|
723
|
+
layoutId: match.layout._internal.id
|
|
724
|
+
}));
|
|
725
|
+
const runLoader = (draftId) => loadLayoutResult(app, match, createLayoutRequestContext(options, studio ? "draft" : "live", {
|
|
726
|
+
authCookieHeader,
|
|
727
|
+
draftId,
|
|
728
|
+
locale: located.locale
|
|
729
|
+
}), located.locale);
|
|
730
|
+
const [serializedResult, { value: shared, ...read }] = studio && requestedDraft ? await readLayout().then(async (read) => [await runLoader(read.draftId), read]) : await Promise.all([runLoader(), readLayout()]);
|
|
731
|
+
const studioCache = createServerStudioCache(queryClient, {
|
|
732
|
+
apiUrl: options.apiUrl,
|
|
733
|
+
authCookieHeader,
|
|
734
|
+
locale: located.locale
|
|
292
735
|
});
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
736
|
+
const projectUuid = options.projectUuid;
|
|
737
|
+
const shownIn = { in: read.draftId ?? null };
|
|
738
|
+
studioCache.land(reads.layoutStructure({
|
|
739
|
+
projectUuid,
|
|
740
|
+
layoutId: match.layout._internal.id
|
|
741
|
+
}), shared, shownIn);
|
|
742
|
+
studioCache.set(reads.derivedPageData({
|
|
743
|
+
projectUuid,
|
|
744
|
+
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath),
|
|
745
|
+
pathname
|
|
746
|
+
}), serializedResult ?? null, shownIn);
|
|
747
|
+
const record = asCollectionItem(serializedResult);
|
|
748
|
+
const recordCollection = record && app.getCollectionById(record.collectionId);
|
|
749
|
+
const { alternates, head: pageHead } = await readAlternates(request, options, {
|
|
750
|
+
located,
|
|
751
|
+
identity: record && match.layout._internal.kind === "derived" ? {
|
|
752
|
+
kind: "record",
|
|
753
|
+
layout: match.layout,
|
|
754
|
+
recordId: record.data.id
|
|
755
|
+
} : { kind: "code-owned" },
|
|
756
|
+
head: buildPageHead({
|
|
757
|
+
layout: match.layout._internal,
|
|
758
|
+
pageMetaTitle: match.layout._internal.title,
|
|
759
|
+
pageMetaDescription: void 0,
|
|
760
|
+
data: serializedResult?.data,
|
|
761
|
+
item: record && recordCollection ? recordPlainText(recordCollection, record.data.content) : void 0,
|
|
762
|
+
params: match.params,
|
|
763
|
+
projectName: shared.projectName,
|
|
764
|
+
pageFullPath: pathname,
|
|
765
|
+
origin: new URL(request.url).origin,
|
|
766
|
+
faviconUrl: buildFaviconUrl(options.apiUrl, shared.project),
|
|
767
|
+
hasMarkdown: match.layout._internal.toMarkdown !== void 0
|
|
768
|
+
}),
|
|
769
|
+
authCookieHeader,
|
|
770
|
+
draftId: read.draftId
|
|
298
771
|
});
|
|
299
|
-
|
|
772
|
+
const analytics = dataOnly ? void 0 : visitorAnalytics(options, {
|
|
773
|
+
studio,
|
|
774
|
+
draftId: read.draftId
|
|
775
|
+
}, shared.project);
|
|
300
776
|
const input = {
|
|
301
|
-
|
|
302
|
-
|
|
777
|
+
alternates,
|
|
778
|
+
project,
|
|
779
|
+
presentation: studio ? "studio" : "public",
|
|
780
|
+
...analytics && { analytics },
|
|
303
781
|
apiUrl: options.apiUrl,
|
|
304
782
|
authenticationUrl: options.authenticationUrl,
|
|
305
|
-
|
|
306
|
-
environmentName: options.environmentName,
|
|
783
|
+
projectUuid: options.projectUuid,
|
|
307
784
|
dehydratedState: dehydrate(queryClient),
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
785
|
+
draftId: read.draftId,
|
|
786
|
+
refusedDraft: read.refusedDraft,
|
|
787
|
+
head: pageHead,
|
|
788
|
+
href: renderedHref(request.url, read),
|
|
789
|
+
layoutIdentity: getLayoutIdentity(shared.layout),
|
|
311
790
|
loaderData: null,
|
|
312
791
|
pathname,
|
|
313
792
|
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath),
|
|
314
|
-
|
|
793
|
+
locale: located.locale,
|
|
794
|
+
locales: located.locales,
|
|
315
795
|
derived: {
|
|
316
796
|
layoutId: match.layout._internal.id,
|
|
317
|
-
|
|
797
|
+
result: serializedResult,
|
|
318
798
|
layout: shared.layout
|
|
319
799
|
}
|
|
320
800
|
};
|
|
321
801
|
const document = await options.getDocument?.() ?? {};
|
|
322
|
-
await preparePreviewDocument(input, options, document,
|
|
802
|
+
await preparePreviewDocument(input, options, document, !studio && !dataOnly ? "" : void 0);
|
|
323
803
|
if (dataOnly) return Response.json(input, { headers: {
|
|
324
804
|
"Cache-Control": "private, no-store",
|
|
325
805
|
Vary: "Cookie",
|
|
326
806
|
...clearAuthCookie ? { "Set-Cookie": buildClearServerAuthCookieHeader() } : {}
|
|
327
807
|
} });
|
|
328
808
|
const html = await options.renderPage(input);
|
|
329
|
-
const head = renderHead(
|
|
330
|
-
return new Response(`<!doctype html><html${head.htmlAttrs}><head>${renderStudioThemeScript(
|
|
809
|
+
const head = renderHead(studio ? {} : document, createPageHeadInput(pageHead), studio ? void 0 : options.stylesheetUrl, studio ? options.studioStylesheetUrl : void 0, studio ? void 0 : located.locale);
|
|
810
|
+
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: {
|
|
331
811
|
"Content-Type": "text/html; charset=utf-8",
|
|
332
812
|
Vary: "Cookie",
|
|
333
|
-
|
|
813
|
+
Link: llmsTxtLinkHeader(options),
|
|
814
|
+
...isPrivateRender(request, options) ? { "Cache-Control": "private, no-store" } : {},
|
|
334
815
|
...clearAuthCookie ? { "Set-Cookie": buildClearServerAuthCookieHeader() } : {}
|
|
335
816
|
} });
|
|
336
817
|
} catch (error) {
|
|
@@ -338,26 +819,16 @@ async function createDerivedResponse(options, pathname, request, dataOnly = fals
|
|
|
338
819
|
throw error;
|
|
339
820
|
}
|
|
340
821
|
}
|
|
341
|
-
async function createStudioHtmlResponse({ match, options, request, dataOnly = false }) {
|
|
342
|
-
if (!options.apiUrl || !options.authenticationUrl || !options.
|
|
822
|
+
async function createStudioHtmlResponse({ match, options, pathname, located, request, dataOnly = false }) {
|
|
823
|
+
if (!options.apiUrl || !options.authenticationUrl || !options.projectUuid || !dataOnly && !options.renderStudio) return Response.json({
|
|
343
824
|
message: "Camox Studio renderer is not configured.",
|
|
344
825
|
...match
|
|
345
826
|
}, { status: 500 });
|
|
346
827
|
const queryClient = new QueryClient();
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
let presentation = "public";
|
|
350
|
-
let clearAuthCookie = false;
|
|
351
|
-
if (authCookieHeader) try {
|
|
352
|
-
project = await createServerApiClient(options.apiUrl, options.environmentName, { authCookieHeader }).projects.getBySlug({ slug: options.projectSlug });
|
|
353
|
-
presentation = "studio";
|
|
354
|
-
} catch (error) {
|
|
355
|
-
if (!isAuthSessionError(error)) throw error;
|
|
356
|
-
clearAuthCookie = true;
|
|
357
|
-
}
|
|
828
|
+
const { project, clearAuthCookie } = await resolveRequestMember(request, options);
|
|
829
|
+
const presentation = project ? "studio" : "public";
|
|
358
830
|
const studioRenderInput = {
|
|
359
831
|
project,
|
|
360
|
-
source: "live",
|
|
361
832
|
head: { meta: [{ title: "Camox Studio" }] },
|
|
362
833
|
layoutIdentity: null,
|
|
363
834
|
loaderData: null,
|
|
@@ -365,12 +836,13 @@ async function createStudioHtmlResponse({ match, options, request, dataOnly = fa
|
|
|
365
836
|
apiUrl: options.apiUrl,
|
|
366
837
|
authenticationUrl: options.authenticationUrl,
|
|
367
838
|
dehydratedState: dehydrate(queryClient),
|
|
368
|
-
environmentName: options.environmentName,
|
|
369
839
|
href: new URL(request.url).href,
|
|
370
|
-
pathname
|
|
371
|
-
|
|
840
|
+
pathname,
|
|
841
|
+
projectUuid: options.projectUuid,
|
|
372
842
|
routeKind: match.kind,
|
|
373
|
-
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath)
|
|
843
|
+
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath),
|
|
844
|
+
locale: located.locale,
|
|
845
|
+
locales: located.locales
|
|
374
846
|
};
|
|
375
847
|
if (dataOnly) return Response.json(studioRenderInput, { headers: {
|
|
376
848
|
"Cache-Control": "private, no-store",
|
|
@@ -399,19 +871,80 @@ ${renderClientEntryScript(options.studioClientEntryUrl)}
|
|
|
399
871
|
...clearAuthCookie ? { "Set-Cookie": buildClearServerAuthCookieHeader() } : {}
|
|
400
872
|
} });
|
|
401
873
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
874
|
+
/**
|
|
875
|
+
* The Canvas with nothing selected, or a block of the Block Catalog, renders the
|
|
876
|
+
* page app, like a Page, so selecting it keeps the Canvas mounted. A block path
|
|
877
|
+
* without a page-content block of that id redirects home.
|
|
878
|
+
*/
|
|
879
|
+
async function createCanvasOnlyResponse({ options, pathname, located, request, dataOnly = false }) {
|
|
880
|
+
const { blockId } = matchCanvasOnlyPath(located.path);
|
|
881
|
+
const app = blockId ? await options.getCamoxApp?.() : void 0;
|
|
882
|
+
const block = blockId ? app?.getBlockById(blockId) : void 0;
|
|
883
|
+
if (blockId && (!block || block._internal.layoutOnly)) {
|
|
884
|
+
if (dataOnly) return Response.json({
|
|
885
|
+
kind: "canvas",
|
|
886
|
+
message: "No block of the Block Catalog on this path",
|
|
887
|
+
pathname
|
|
888
|
+
}, { status: 404 });
|
|
889
|
+
return Response.redirect(new URL(withRuntimeBasePath(localizedPath("/", located.locale), options.runtimeBasePath), request.url), 302);
|
|
890
|
+
}
|
|
891
|
+
if (!options.apiUrl || !options.authenticationUrl || !options.projectUuid || !dataOnly && !options.renderPage) return Response.json({
|
|
892
|
+
message: "Camox page renderer is not configured.",
|
|
893
|
+
kind: "canvas",
|
|
894
|
+
pathname
|
|
895
|
+
}, { status: 500 });
|
|
896
|
+
const { project, clearAuthCookie } = await resolveRequestMember(request, options);
|
|
897
|
+
const studio = project !== void 0;
|
|
898
|
+
const title = block?._internal.title ?? "Canvas";
|
|
899
|
+
const input = {
|
|
900
|
+
project,
|
|
901
|
+
presentation: studio ? "studio" : "public",
|
|
902
|
+
apiUrl: options.apiUrl,
|
|
903
|
+
authenticationUrl: options.authenticationUrl,
|
|
904
|
+
projectUuid: options.projectUuid,
|
|
905
|
+
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath),
|
|
906
|
+
locale: located.locale,
|
|
907
|
+
locales: located.locales,
|
|
908
|
+
href: request.url,
|
|
909
|
+
pathname,
|
|
910
|
+
head: { meta: [{ title }] },
|
|
911
|
+
layoutIdentity: null,
|
|
912
|
+
loaderData: null,
|
|
913
|
+
dehydratedState: dehydrate(new QueryClient()),
|
|
914
|
+
canvasOnly: blockId ? { blockId } : {},
|
|
915
|
+
draftId: studio ? getRequestDraftId(request, options) : void 0
|
|
916
|
+
};
|
|
917
|
+
await preparePreviewDocument(input, options, await options.getDocument?.() ?? {}, "");
|
|
918
|
+
const headers = {
|
|
919
|
+
"Cache-Control": "private, no-store",
|
|
920
|
+
Vary: "Cookie",
|
|
921
|
+
...clearAuthCookie ? { "Set-Cookie": buildClearServerAuthCookieHeader() } : {}
|
|
922
|
+
};
|
|
923
|
+
if (dataOnly) return Response.json(input, { headers });
|
|
924
|
+
const html = await options.renderPage(input);
|
|
925
|
+
const head = renderHead({}, { title }, void 0, studio ? options.studioStylesheetUrl : void 0);
|
|
926
|
+
return new Response(`<!doctype html><html${head.htmlAttrs}><head>${renderStudioThemeScript(studio)}${head.headTags}<script id="__CAMOX_DATA__" type="application/json">${serializeJsonForHtml(input)}<\/script></head><body${head.bodyAttrs}>${head.bodyTagsOpen}<div id="root">${html}</div>${head.bodyTags}${renderClientEntryScript(options.pageClientEntryUrl)}</body></html>`, { headers: {
|
|
927
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
928
|
+
...headers
|
|
929
|
+
} });
|
|
930
|
+
}
|
|
931
|
+
async function createPageDataResponse({ options, pathname, located, request }) {
|
|
932
|
+
if (!options.apiUrl || !options.projectUuid) return Response.json({ message: "Camox page loader is not configured." }, { status: 500 });
|
|
933
|
+
const singleton = await createDerivedResponse(options, pathname, located, request, {
|
|
934
|
+
dataOnly: true,
|
|
935
|
+
singletonOnly: true
|
|
936
|
+
});
|
|
405
937
|
if (singleton) return singleton;
|
|
406
938
|
const queryClient = new QueryClient();
|
|
407
939
|
try {
|
|
408
940
|
const result = await loadCamoxPageForRequest({
|
|
409
941
|
apiUrl: options.apiUrl,
|
|
410
942
|
authCookieHeader: getServerAuthCookieHeader(request.headers),
|
|
411
|
-
|
|
943
|
+
draftId: getRequestDraftId(request, options),
|
|
944
|
+
locale: located.locale,
|
|
412
945
|
origin: new URL(request.url).origin,
|
|
413
|
-
pathname,
|
|
414
|
-
|
|
946
|
+
pathname: located.path,
|
|
947
|
+
projectUuid: options.projectUuid,
|
|
415
948
|
queryClient
|
|
416
949
|
});
|
|
417
950
|
const headers = new Headers({
|
|
@@ -421,21 +954,32 @@ async function createPageDataResponse({ options, pathname, request }) {
|
|
|
421
954
|
});
|
|
422
955
|
if (result.shouldClearAuthCookie) headers.append("Set-Cookie", buildClearServerAuthCookieHeader());
|
|
423
956
|
const camoxApp = await options.getCamoxApp?.();
|
|
424
|
-
const head =
|
|
957
|
+
const { alternates, head } = await readAlternates(request, options, {
|
|
958
|
+
located,
|
|
959
|
+
identity: {
|
|
960
|
+
kind: "curated",
|
|
961
|
+
pageId: result.data.page.page.id
|
|
962
|
+
},
|
|
963
|
+
head: camoxApp ? buildCamoxPageHead(camoxApp, result.data, located.locale) : null,
|
|
964
|
+
authCookieHeader: getServerAuthCookieHeader(request.headers),
|
|
965
|
+
draftId: result.draftId
|
|
966
|
+
});
|
|
425
967
|
const dehydratedState = dehydrate(queryClient);
|
|
426
|
-
const project = result.source === "draft" ? await createServerApiClient(options.apiUrl, options.environmentName, { authCookieHeader: getServerAuthCookieHeader(request.headers) }).projects.getBySlug({ slug: options.projectSlug }) : void 0;
|
|
427
968
|
const input = {
|
|
969
|
+
alternates,
|
|
428
970
|
apiUrl: options.apiUrl,
|
|
429
971
|
authenticationUrl: options.authenticationUrl ?? "",
|
|
430
|
-
|
|
431
|
-
projectSlug: options.projectSlug,
|
|
972
|
+
projectUuid: options.projectUuid,
|
|
432
973
|
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath),
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
974
|
+
locale: located.locale,
|
|
975
|
+
locales: located.locales,
|
|
976
|
+
href: renderedHref(request.url, result),
|
|
977
|
+
draftId: result.draftId,
|
|
978
|
+
refusedDraft: result.refusedDraft,
|
|
979
|
+
presentation: result.project ? "studio" : "public",
|
|
436
980
|
pathname,
|
|
437
|
-
layoutIdentity: getLayoutIdentity(result.data.page.layout
|
|
438
|
-
project,
|
|
981
|
+
layoutIdentity: getLayoutIdentity(result.data.page.layout),
|
|
982
|
+
project: result.project,
|
|
439
983
|
loaderData: result.data,
|
|
440
984
|
head,
|
|
441
985
|
dehydratedState
|
|
@@ -444,7 +988,7 @@ async function createPageDataResponse({ options, pathname, request }) {
|
|
|
444
988
|
return Response.json(input, { headers });
|
|
445
989
|
} catch (error) {
|
|
446
990
|
if (isNotFoundError(error)) {
|
|
447
|
-
const derived = await createDerivedResponse(options, pathname, request, true);
|
|
991
|
+
const derived = await createDerivedResponse(options, pathname, located, request, { dataOnly: true });
|
|
448
992
|
if (derived) return derived;
|
|
449
993
|
return Response.json({
|
|
450
994
|
kind: "page",
|
|
@@ -470,48 +1014,117 @@ async function handleCamoxRequest(request, options = {}) {
|
|
|
470
1014
|
}
|
|
471
1015
|
if (match.kind === "og") return createOgResponse(request, options);
|
|
472
1016
|
if (match.kind === "sitemap") return createSitemapResponse(request, options);
|
|
1017
|
+
if (match.kind === "llms-txt") return createLlmsTxtResponse(request, options);
|
|
1018
|
+
if (match.kind === "routes") return createRoutesResponse(request, options);
|
|
473
1019
|
if (match.kind === "data") {
|
|
474
1020
|
const targetPathname = normalizePagePath(new URL(request.url).searchParams.get("path"));
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
match: targetMatch,
|
|
478
|
-
options,
|
|
479
|
-
request,
|
|
480
|
-
dataOnly: true
|
|
481
|
-
});
|
|
482
|
-
if (targetMatch.kind !== "page") return new Response("Not found", { status: 404 });
|
|
483
|
-
return createPageDataResponse({
|
|
484
|
-
options,
|
|
485
|
-
pathname: targetPathname,
|
|
486
|
-
request
|
|
487
|
-
});
|
|
1021
|
+
if (matchRuntimeOwnedRoute(targetPathname)) return new Response("Not found", { status: 404 });
|
|
1022
|
+
return respondInLocale(options, request, targetPathname, { dataOnly: true });
|
|
488
1023
|
}
|
|
489
|
-
if (
|
|
490
|
-
match,
|
|
1024
|
+
if (isPreviewHandoff(request, options) && match.kind === "page") return createPreviewHandoffResponse({
|
|
491
1025
|
options,
|
|
1026
|
+
pathname,
|
|
492
1027
|
request
|
|
493
1028
|
});
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
1029
|
+
const { apiUrl, projectUuid } = options;
|
|
1030
|
+
const markdownPathname = parseMarkdownPath(pathname);
|
|
1031
|
+
if (apiUrl && projectUuid && (markdownPathname || prefersMarkdown(request.headers.get("Accept") ?? ""))) {
|
|
1032
|
+
const located = await locateRequest(request, options, markdownPathname ?? pathname, { live: true });
|
|
1033
|
+
if (markdownPathname || matchLocalizedRoute(located.path)?.kind === "page") {
|
|
1034
|
+
const markdownResponse = await createPageMarkdownResponse({
|
|
1035
|
+
...options,
|
|
1036
|
+
apiUrl,
|
|
1037
|
+
projectUuid
|
|
1038
|
+
}, located, {
|
|
1039
|
+
pathname: markdownPathname ?? pathname,
|
|
501
1040
|
request
|
|
502
1041
|
});
|
|
503
1042
|
if (markdownResponse) return markdownResponse;
|
|
1043
|
+
if (markdownPathname) return new Response("Page not found", { status: 404 });
|
|
504
1044
|
}
|
|
505
|
-
return createPageHtmlResponse({
|
|
506
|
-
options,
|
|
507
|
-
pathname: match.pathname,
|
|
508
|
-
request
|
|
509
|
-
});
|
|
510
1045
|
}
|
|
511
|
-
return
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
1046
|
+
return respondInLocale(options, request, pathname);
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Answers a path under its locale's prefix: the Canvas, a studio route or a page, in that locale.
|
|
1050
|
+
* With `dataOnly`, as the JSON client navigation loads.
|
|
1051
|
+
*/
|
|
1052
|
+
async function respondInLocale(options, request, pathname, { dataOnly = false } = {}) {
|
|
1053
|
+
const located = await locateRequest(request, options, pathname);
|
|
1054
|
+
const localized = matchLocalizedRoute(located.path);
|
|
1055
|
+
if (!localized) return new Response("Not found", { status: 404 });
|
|
1056
|
+
const target = {
|
|
1057
|
+
options,
|
|
1058
|
+
pathname,
|
|
1059
|
+
located,
|
|
1060
|
+
request
|
|
1061
|
+
};
|
|
1062
|
+
if (localized.kind === "canvas") return createCanvasOnlyResponse({
|
|
1063
|
+
...target,
|
|
1064
|
+
dataOnly
|
|
1065
|
+
});
|
|
1066
|
+
if (isStudioRoute(localized)) return createStudioHtmlResponse({
|
|
1067
|
+
...target,
|
|
1068
|
+
match: localized,
|
|
1069
|
+
dataOnly
|
|
1070
|
+
});
|
|
1071
|
+
return dataOnly ? createPageDataResponse(target) : createPageHtmlResponse(target);
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* A page's Markdown Representation in its locale, with the same precedence as its HTML:
|
|
1075
|
+
* singleton, curated page, then derived layout. Null when the page has none.
|
|
1076
|
+
*/
|
|
1077
|
+
async function createPageMarkdownResponse(options, { locale, path }, { pathname, request }) {
|
|
1078
|
+
const page = {
|
|
1079
|
+
pathname,
|
|
1080
|
+
url: absoluteUrl(request, options)(pathname)
|
|
1081
|
+
};
|
|
1082
|
+
const app = await options.getCamoxApp?.();
|
|
1083
|
+
const layoutMatch = app && matchDerivedLayout(app.getLayouts(), path);
|
|
1084
|
+
if (layoutMatch?.layout._internal.kind === "singleton") return createLayoutMarkdownResponse(options, layoutMatch, locale, page);
|
|
1085
|
+
return createMarkdownResponse({
|
|
1086
|
+
apiUrl: options.apiUrl,
|
|
1087
|
+
camoxApp: app,
|
|
1088
|
+
locale,
|
|
1089
|
+
pathname: path,
|
|
1090
|
+
projectUuid: options.projectUuid,
|
|
1091
|
+
request,
|
|
1092
|
+
url: page.url,
|
|
1093
|
+
onNotFound: async () => layoutMatch ? createLayoutMarkdownResponse(options, layoutMatch, locale, page) : null
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
/** Whether a route is one of the studio's own, rather than a page or the Canvas. */
|
|
1097
|
+
function isStudioRoute(match) {
|
|
1098
|
+
return match.kind === "studio" || match.kind === "studio-content" || match.kind === "studio-nested";
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* The studio or page route a locale-relative path names, or null for a runtime-owned route
|
|
1102
|
+
* under a locale's prefix (`/de/sitemap.xml`), which names nothing.
|
|
1103
|
+
*/
|
|
1104
|
+
function matchLocalizedRoute(path) {
|
|
1105
|
+
const match = matchRuntimeRoute(path);
|
|
1106
|
+
return match.kind === "page" || match.kind === "canvas" || isStudioRoute(match) ? match : null;
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* The locale a request's path names, among the locales of the scope it reads: the URL's draft's
|
|
1110
|
+
* for a session naming one, else Live's. `live` reads Live's whatever the request names. Without
|
|
1111
|
+
* an API there are none to read: every path is the default locale's, which then says it can't
|
|
1112
|
+
* render.
|
|
1113
|
+
*/
|
|
1114
|
+
async function locateRequest(request, options, pathname, { live = false } = {}) {
|
|
1115
|
+
const { apiUrl, projectUuid } = options;
|
|
1116
|
+
const locales = apiUrl && projectUuid ? await readScopeLocales({
|
|
1117
|
+
apiUrl,
|
|
1118
|
+
projectUuid,
|
|
1119
|
+
...!live && {
|
|
1120
|
+
authCookieHeader: getServerAuthCookieHeader(request.headers),
|
|
1121
|
+
draftId: getRequestDraftId(request, options)
|
|
1122
|
+
}
|
|
1123
|
+
}) : [UNNAMED_DEFAULT_LOCALE];
|
|
1124
|
+
return {
|
|
1125
|
+
...parseLocalePath(pathname, locales),
|
|
1126
|
+
locales
|
|
1127
|
+
};
|
|
515
1128
|
}
|
|
516
1129
|
|
|
517
1130
|
//#endregion
|