camox 0.44.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/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 +53 -53
- package/dist/core/components/lexical/MaxLengthPlugin.js +59 -0
- package/dist/core/components/lexical/SelectionBroadcaster.js +53 -54
- package/dist/core/components/lexical/SidebarLexicalEditor.js +322 -229
- package/dist/core/components/lexical/SummaryToggleGuard.js +42 -0
- package/dist/core/components/lexical/TextLinkPopover.js +169 -180
- package/dist/core/createApp.d.ts +241 -312
- package/dist/core/createApp.js +123 -29
- package/dist/core/createBlock.d.ts +5 -5
- package/dist/core/createBlock.js +385 -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 +754 -633
- package/dist/core/editing/createEditableBlock.js +2113 -1066
- 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 +302 -106
- package/dist/core/lib/contentType.js +201 -173
- package/dist/core/lib/fieldTypes.js +3 -0
- package/dist/core/lib/iconTypes.d.ts +3 -3
- package/dist/core/lib/icons.d.ts +4 -4
- package/dist/core/lib/icons.js +40 -44
- 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 +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 +5 -7
- package/dist/core/lib/textStyles.js +2 -2
- 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 +80 -84
- 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 +816 -537
- 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 +181 -134
- 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 +21 -19
- package/dist/features/preview/components/DrillRow.js +150 -0
- package/dist/features/preview/components/FieldOverlayStyles.js +11 -8
- package/dist/features/preview/components/FieldToolbar.js +160 -110
- package/dist/features/preview/components/Frame.js +108 -20
- package/dist/features/preview/components/IconFieldEditor.js +62 -66
- package/dist/features/preview/components/ItemFieldsEditor.js +231 -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 +9 -41
- 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 +128 -130
- 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 +105 -333
- 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 +17 -44
- 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 +32 -63
- 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 +66 -50
- package/dist/features/provider/LocalhostPreviewProvider.js +78 -114
- package/dist/features/provider/PreviewSignInPage.js +54 -0
- package/dist/features/provider/components/CamoxAppContext.js +11 -11
- package/dist/features/provider/components/CommandPalette.js +75 -82
- 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 +229 -158
- 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 +6 -2
- package/dist/features/runtime/pageViewport.js +7 -0
- package/dist/features/runtime/runtime.d.ts +59 -24
- package/dist/features/runtime/runtime.js +779 -191
- 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/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 +1 -1
- 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 +51 -31
- package/dist/features/vite/vite.js +224 -127
- 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 +117 -61
- 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 +2 -2
- package/dist/lib/queries.d.ts +11 -0
- package/dist/lib/queries.js +70 -141
- 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 -50
- 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 -151
- package/dist/studio.css +2 -2
- package/package.json +70 -73
- package/skills/camox/SKILL.md +16 -11
- package/skills/camox/references/artifacts.md +179 -0
- package/skills/camox/references/block-definitions.md +75 -59
- package/skills/camox/references/cli-common.md +9 -11
- package/skills/camox/references/cli-content.md +92 -69
- 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 +4 -4
- 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 +29 -7
- 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 -331
- 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 -167
- 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/components/SendFeedbackDialog.js +0 -83
- package/dist/features/preview/previewCommentsStore.js +0 -135
- package/dist/features/preview/publication.js +0 -81
- package/dist/features/preview/usePageComments.js +0 -39
- package/dist/features/runtime/SharedChromeContext.js +0 -7
- package/dist/features/studio/components/EnvironmentMenu.js +0 -425
- 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,88 +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
|
|
212
|
-
if (requestUrl.searchParams.has("camox-preview") && requestUrl.searchParams.has("ott")) {
|
|
213
|
-
const input = {
|
|
214
|
-
previewHandoff: true,
|
|
215
|
-
presentation: "public",
|
|
216
|
-
source: "live",
|
|
217
|
-
apiUrl: options.apiUrl,
|
|
218
|
-
authenticationUrl: options.authenticationUrl,
|
|
219
|
-
environmentName: options.environmentName,
|
|
220
|
-
projectSlug: options.projectSlug,
|
|
221
|
-
href: requestUrl.href,
|
|
222
|
-
pathname,
|
|
223
|
-
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath),
|
|
224
|
-
head: {},
|
|
225
|
-
loaderData: null,
|
|
226
|
-
layoutIdentity: null,
|
|
227
|
-
dehydratedState: dehydrate(new QueryClient())
|
|
228
|
-
};
|
|
229
|
-
const html = await options.renderPage(input);
|
|
230
|
-
return new Response(`<!doctype html><html><head><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: {
|
|
231
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
232
|
-
"Cache-Control": "private, no-store",
|
|
233
|
-
"Referrer-Policy": "no-referrer"
|
|
234
|
-
} });
|
|
235
|
-
}
|
|
236
|
-
const singleton = await createDerivedResponse(options, pathname, request, false, true);
|
|
488
|
+
const singleton = await createDerivedResponse(options, pathname, located, request, { singletonOnly: true });
|
|
237
489
|
if (singleton) return singleton;
|
|
238
490
|
const queryClient = new QueryClient();
|
|
239
491
|
try {
|
|
240
492
|
const result = await loadCamoxPageForRequest({
|
|
241
493
|
apiUrl: options.apiUrl,
|
|
242
494
|
authCookieHeader: getServerAuthCookieHeader(request.headers),
|
|
243
|
-
|
|
495
|
+
draftId: getRequestDraftId(request, options),
|
|
496
|
+
locale: located.locale,
|
|
244
497
|
origin: new URL(request.url).origin,
|
|
245
|
-
pathname,
|
|
246
|
-
|
|
498
|
+
pathname: located.path,
|
|
499
|
+
projectUuid: options.projectUuid,
|
|
247
500
|
queryClient
|
|
248
501
|
});
|
|
249
502
|
const headers = new Headers({
|
|
250
503
|
"Content-Type": "text/html; charset=utf-8",
|
|
251
|
-
Vary: "Cookie"
|
|
504
|
+
Vary: "Cookie",
|
|
505
|
+
Link: llmsTxtLinkHeader(options)
|
|
252
506
|
});
|
|
253
|
-
if (
|
|
507
|
+
if (isPrivateRender(request, options)) headers.set("Cache-Control", "private, no-store");
|
|
254
508
|
if (result.shouldClearAuthCookie) headers.append("Set-Cookie", buildClearServerAuthCookieHeader());
|
|
255
509
|
const camoxApp = await options.getCamoxApp?.();
|
|
256
510
|
const document = await options.getDocument?.() ?? {};
|
|
257
|
-
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
|
+
});
|
|
258
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);
|
|
259
528
|
const pageRenderInput = {
|
|
260
|
-
|
|
261
|
-
|
|
529
|
+
alternates,
|
|
530
|
+
project,
|
|
531
|
+
presentation: studio ? "studio" : "public",
|
|
532
|
+
...analytics && { analytics },
|
|
262
533
|
apiUrl: options.apiUrl,
|
|
263
534
|
authenticationUrl: options.authenticationUrl,
|
|
264
535
|
dehydratedState,
|
|
265
|
-
|
|
536
|
+
draftId: result.draftId,
|
|
537
|
+
refusedDraft: result.refusedDraft,
|
|
266
538
|
head,
|
|
267
|
-
href:
|
|
268
|
-
layoutIdentity: getLayoutIdentity(result.data.page.layout
|
|
539
|
+
href: renderedHref(request.url, result),
|
|
540
|
+
layoutIdentity: getLayoutIdentity(result.data.page.layout),
|
|
269
541
|
loaderData: result.data,
|
|
270
542
|
pathname,
|
|
271
|
-
|
|
543
|
+
projectUuid: options.projectUuid,
|
|
272
544
|
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath),
|
|
273
|
-
|
|
545
|
+
locale: located.locale,
|
|
546
|
+
locales: located.locales
|
|
274
547
|
};
|
|
275
|
-
await preparePreviewDocument(pageRenderInput, options, document,
|
|
548
|
+
await preparePreviewDocument(pageRenderInput, options, document, studio ? void 0 : "");
|
|
276
549
|
const appHtml = await options.renderPage(pageRenderInput);
|
|
277
|
-
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);
|
|
278
551
|
return new Response(`<!doctype html>
|
|
279
552
|
<html${renderedHead.htmlAttrs}>
|
|
280
553
|
<head>
|
|
281
|
-
${renderStudioThemeScript(
|
|
554
|
+
${renderStudioThemeScript(studio)}
|
|
282
555
|
${renderedHead.headTags}
|
|
283
|
-
|
|
556
|
+
${renderAnalyticsScripts(analytics)}
|
|
557
|
+
${renderBlockPreloads(options, dehydratedState)}
|
|
558
|
+
<script id="__CAMOX_DATA__" type="application/json">${serializeJsonForHtml(hydrationInputFor(pageRenderInput, appHtml))}<\/script>
|
|
284
559
|
</head>
|
|
285
560
|
<body${renderedHead.bodyAttrs}>
|
|
286
561
|
${renderedHead.bodyTagsOpen}
|
|
@@ -291,71 +566,252 @@ ${renderClientEntryScript(options.pageClientEntryUrl)}
|
|
|
291
566
|
</html>`, { headers });
|
|
292
567
|
} catch (error) {
|
|
293
568
|
if (isNotFoundError(error)) {
|
|
294
|
-
const derived = await createDerivedResponse(options, pathname, request);
|
|
569
|
+
const derived = await createDerivedResponse(options, pathname, located, request);
|
|
295
570
|
if (derived) return derived;
|
|
296
571
|
return new Response("No Camox page on this path", { status: 404 });
|
|
297
572
|
}
|
|
298
573
|
throw error;
|
|
299
574
|
}
|
|
300
575
|
}
|
|
301
|
-
|
|
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 });
|
|
638
|
+
const app = await options.getCamoxApp?.();
|
|
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 } = {}) {
|
|
302
707
|
const app = await options.getCamoxApp?.();
|
|
303
|
-
const match = app && matchDerivedLayout(app.getLayouts(),
|
|
708
|
+
const match = app && matchDerivedLayout(app.getLayouts(), located.path);
|
|
304
709
|
if (!match || !dataOnly && !options.renderPage) return null;
|
|
305
710
|
if (singletonOnly && match.layout._internal.kind !== "singleton") return null;
|
|
306
711
|
try {
|
|
307
|
-
const data = await match.layout._internal.loader?.({ params: match.params });
|
|
308
|
-
const serializedData = data === void 0 ? void 0 : JSON.parse(JSON.stringify(data));
|
|
309
712
|
const queryClient = new QueryClient();
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
|
317
735
|
});
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
|
323
771
|
});
|
|
324
|
-
|
|
772
|
+
const analytics = dataOnly ? void 0 : visitorAnalytics(options, {
|
|
773
|
+
studio,
|
|
774
|
+
draftId: read.draftId
|
|
775
|
+
}, shared.project);
|
|
325
776
|
const input = {
|
|
326
|
-
|
|
327
|
-
|
|
777
|
+
alternates,
|
|
778
|
+
project,
|
|
779
|
+
presentation: studio ? "studio" : "public",
|
|
780
|
+
...analytics && { analytics },
|
|
328
781
|
apiUrl: options.apiUrl,
|
|
329
782
|
authenticationUrl: options.authenticationUrl,
|
|
330
|
-
|
|
331
|
-
environmentName: options.environmentName,
|
|
783
|
+
projectUuid: options.projectUuid,
|
|
332
784
|
dehydratedState: dehydrate(queryClient),
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
785
|
+
draftId: read.draftId,
|
|
786
|
+
refusedDraft: read.refusedDraft,
|
|
787
|
+
head: pageHead,
|
|
788
|
+
href: renderedHref(request.url, read),
|
|
789
|
+
layoutIdentity: getLayoutIdentity(shared.layout),
|
|
336
790
|
loaderData: null,
|
|
337
791
|
pathname,
|
|
338
792
|
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath),
|
|
339
|
-
|
|
793
|
+
locale: located.locale,
|
|
794
|
+
locales: located.locales,
|
|
340
795
|
derived: {
|
|
341
796
|
layoutId: match.layout._internal.id,
|
|
342
|
-
|
|
797
|
+
result: serializedResult,
|
|
343
798
|
layout: shared.layout
|
|
344
799
|
}
|
|
345
800
|
};
|
|
346
801
|
const document = await options.getDocument?.() ?? {};
|
|
347
|
-
await preparePreviewDocument(input, options, document,
|
|
802
|
+
await preparePreviewDocument(input, options, document, !studio && !dataOnly ? "" : void 0);
|
|
348
803
|
if (dataOnly) return Response.json(input, { headers: {
|
|
349
804
|
"Cache-Control": "private, no-store",
|
|
350
805
|
Vary: "Cookie",
|
|
351
806
|
...clearAuthCookie ? { "Set-Cookie": buildClearServerAuthCookieHeader() } : {}
|
|
352
807
|
} });
|
|
353
808
|
const html = await options.renderPage(input);
|
|
354
|
-
const head = renderHead(
|
|
355
|
-
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: {
|
|
356
811
|
"Content-Type": "text/html; charset=utf-8",
|
|
357
812
|
Vary: "Cookie",
|
|
358
|
-
|
|
813
|
+
Link: llmsTxtLinkHeader(options),
|
|
814
|
+
...isPrivateRender(request, options) ? { "Cache-Control": "private, no-store" } : {},
|
|
359
815
|
...clearAuthCookie ? { "Set-Cookie": buildClearServerAuthCookieHeader() } : {}
|
|
360
816
|
} });
|
|
361
817
|
} catch (error) {
|
|
@@ -363,26 +819,16 @@ async function createDerivedResponse(options, pathname, request, dataOnly = fals
|
|
|
363
819
|
throw error;
|
|
364
820
|
}
|
|
365
821
|
}
|
|
366
|
-
async function createStudioHtmlResponse({ match, options, request, dataOnly = false }) {
|
|
367
|
-
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({
|
|
368
824
|
message: "Camox Studio renderer is not configured.",
|
|
369
825
|
...match
|
|
370
826
|
}, { status: 500 });
|
|
371
827
|
const queryClient = new QueryClient();
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
let presentation = "public";
|
|
375
|
-
let clearAuthCookie = false;
|
|
376
|
-
if (authCookieHeader) try {
|
|
377
|
-
project = await createServerApiClient(options.apiUrl, options.environmentName, { authCookieHeader }).projects.getBySlug({ slug: options.projectSlug });
|
|
378
|
-
presentation = "studio";
|
|
379
|
-
} catch (error) {
|
|
380
|
-
if (!isAuthSessionError(error)) throw error;
|
|
381
|
-
clearAuthCookie = true;
|
|
382
|
-
}
|
|
828
|
+
const { project, clearAuthCookie } = await resolveRequestMember(request, options);
|
|
829
|
+
const presentation = project ? "studio" : "public";
|
|
383
830
|
const studioRenderInput = {
|
|
384
831
|
project,
|
|
385
|
-
source: "live",
|
|
386
832
|
head: { meta: [{ title: "Camox Studio" }] },
|
|
387
833
|
layoutIdentity: null,
|
|
388
834
|
loaderData: null,
|
|
@@ -390,12 +836,13 @@ async function createStudioHtmlResponse({ match, options, request, dataOnly = fa
|
|
|
390
836
|
apiUrl: options.apiUrl,
|
|
391
837
|
authenticationUrl: options.authenticationUrl,
|
|
392
838
|
dehydratedState: dehydrate(queryClient),
|
|
393
|
-
environmentName: options.environmentName,
|
|
394
839
|
href: new URL(request.url).href,
|
|
395
|
-
pathname
|
|
396
|
-
|
|
840
|
+
pathname,
|
|
841
|
+
projectUuid: options.projectUuid,
|
|
397
842
|
routeKind: match.kind,
|
|
398
|
-
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath)
|
|
843
|
+
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath),
|
|
844
|
+
locale: located.locale,
|
|
845
|
+
locales: located.locales
|
|
399
846
|
};
|
|
400
847
|
if (dataOnly) return Response.json(studioRenderInput, { headers: {
|
|
401
848
|
"Cache-Control": "private, no-store",
|
|
@@ -424,19 +871,80 @@ ${renderClientEntryScript(options.studioClientEntryUrl)}
|
|
|
424
871
|
...clearAuthCookie ? { "Set-Cookie": buildClearServerAuthCookieHeader() } : {}
|
|
425
872
|
} });
|
|
426
873
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
+
});
|
|
430
937
|
if (singleton) return singleton;
|
|
431
938
|
const queryClient = new QueryClient();
|
|
432
939
|
try {
|
|
433
940
|
const result = await loadCamoxPageForRequest({
|
|
434
941
|
apiUrl: options.apiUrl,
|
|
435
942
|
authCookieHeader: getServerAuthCookieHeader(request.headers),
|
|
436
|
-
|
|
943
|
+
draftId: getRequestDraftId(request, options),
|
|
944
|
+
locale: located.locale,
|
|
437
945
|
origin: new URL(request.url).origin,
|
|
438
|
-
pathname,
|
|
439
|
-
|
|
946
|
+
pathname: located.path,
|
|
947
|
+
projectUuid: options.projectUuid,
|
|
440
948
|
queryClient
|
|
441
949
|
});
|
|
442
950
|
const headers = new Headers({
|
|
@@ -446,21 +954,32 @@ async function createPageDataResponse({ options, pathname, request }) {
|
|
|
446
954
|
});
|
|
447
955
|
if (result.shouldClearAuthCookie) headers.append("Set-Cookie", buildClearServerAuthCookieHeader());
|
|
448
956
|
const camoxApp = await options.getCamoxApp?.();
|
|
449
|
-
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
|
+
});
|
|
450
967
|
const dehydratedState = dehydrate(queryClient);
|
|
451
|
-
const project = result.source === "draft" ? await createServerApiClient(options.apiUrl, options.environmentName, { authCookieHeader: getServerAuthCookieHeader(request.headers) }).projects.getBySlug({ slug: options.projectSlug }) : void 0;
|
|
452
968
|
const input = {
|
|
969
|
+
alternates,
|
|
453
970
|
apiUrl: options.apiUrl,
|
|
454
971
|
authenticationUrl: options.authenticationUrl ?? "",
|
|
455
|
-
|
|
456
|
-
projectSlug: options.projectSlug,
|
|
972
|
+
projectUuid: options.projectUuid,
|
|
457
973
|
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath),
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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",
|
|
461
980
|
pathname,
|
|
462
|
-
layoutIdentity: getLayoutIdentity(result.data.page.layout
|
|
463
|
-
project,
|
|
981
|
+
layoutIdentity: getLayoutIdentity(result.data.page.layout),
|
|
982
|
+
project: result.project,
|
|
464
983
|
loaderData: result.data,
|
|
465
984
|
head,
|
|
466
985
|
dehydratedState
|
|
@@ -469,7 +988,7 @@ async function createPageDataResponse({ options, pathname, request }) {
|
|
|
469
988
|
return Response.json(input, { headers });
|
|
470
989
|
} catch (error) {
|
|
471
990
|
if (isNotFoundError(error)) {
|
|
472
|
-
const derived = await createDerivedResponse(options, pathname, request, true);
|
|
991
|
+
const derived = await createDerivedResponse(options, pathname, located, request, { dataOnly: true });
|
|
473
992
|
if (derived) return derived;
|
|
474
993
|
return Response.json({
|
|
475
994
|
kind: "page",
|
|
@@ -495,48 +1014,117 @@ async function handleCamoxRequest(request, options = {}) {
|
|
|
495
1014
|
}
|
|
496
1015
|
if (match.kind === "og") return createOgResponse(request, options);
|
|
497
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);
|
|
498
1019
|
if (match.kind === "data") {
|
|
499
1020
|
const targetPathname = normalizePagePath(new URL(request.url).searchParams.get("path"));
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
match: targetMatch,
|
|
503
|
-
options,
|
|
504
|
-
request,
|
|
505
|
-
dataOnly: true
|
|
506
|
-
});
|
|
507
|
-
if (targetMatch.kind !== "page") return new Response("Not found", { status: 404 });
|
|
508
|
-
return createPageDataResponse({
|
|
509
|
-
options,
|
|
510
|
-
pathname: targetPathname,
|
|
511
|
-
request
|
|
512
|
-
});
|
|
1021
|
+
if (matchRuntimeOwnedRoute(targetPathname)) return new Response("Not found", { status: 404 });
|
|
1022
|
+
return respondInLocale(options, request, targetPathname, { dataOnly: true });
|
|
513
1023
|
}
|
|
514
|
-
if (
|
|
515
|
-
match,
|
|
1024
|
+
if (isPreviewHandoff(request, options) && match.kind === "page") return createPreviewHandoffResponse({
|
|
516
1025
|
options,
|
|
1026
|
+
pathname,
|
|
517
1027
|
request
|
|
518
1028
|
});
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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,
|
|
526
1040
|
request
|
|
527
1041
|
});
|
|
528
1042
|
if (markdownResponse) return markdownResponse;
|
|
1043
|
+
if (markdownPathname) return new Response("Page not found", { status: 404 });
|
|
529
1044
|
}
|
|
530
|
-
return createPageHtmlResponse({
|
|
531
|
-
options,
|
|
532
|
-
pathname: match.pathname,
|
|
533
|
-
request
|
|
534
|
-
});
|
|
535
1045
|
}
|
|
536
|
-
return
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
+
};
|
|
540
1128
|
}
|
|
541
1129
|
|
|
542
1130
|
//#endregion
|