@kirchdev/duxt 0.1.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/LICENSE +21 -0
- package/README.md +151 -0
- package/app/app.config.ts +13 -0
- package/app/app.vue +122 -0
- package/app/assets/css/duxt.css +555 -0
- package/app/assets/css/typeset.css +498 -0
- package/app/components/DuxtBrand.vue +53 -0
- package/app/components/DuxtBreadcrumb.vue +37 -0
- package/app/components/DuxtCodeBlock.vue +143 -0
- package/app/components/DuxtCopyPage.vue +117 -0
- package/app/components/DuxtFooter.vue +86 -0
- package/app/components/DuxtHeader.vue +496 -0
- package/app/components/DuxtJsonEditor.vue +359 -0
- package/app/components/DuxtLandingProse.vue +90 -0
- package/app/components/DuxtLandingShowcase.vue +295 -0
- package/app/components/DuxtLiveWindow.vue +343 -0
- package/app/components/DuxtLocale.vue +86 -0
- package/app/components/DuxtMetaList.vue +76 -0
- package/app/components/DuxtNavigation.vue +94 -0
- package/app/components/DuxtNavigationLink.vue +41 -0
- package/app/components/DuxtOpenApiCallbacks.vue +92 -0
- package/app/components/DuxtOpenApiClient.vue +1339 -0
- package/app/components/DuxtOpenApiMedia.vue +108 -0
- package/app/components/DuxtOpenApiMethod.vue +59 -0
- package/app/components/DuxtOpenApiParameters.vue +114 -0
- package/app/components/DuxtOpenApiResponses.vue +109 -0
- package/app/components/DuxtOpenApiSchema.vue +365 -0
- package/app/components/DuxtOpenApiSecurity.vue +115 -0
- package/app/components/DuxtPageFeedback.vue +56 -0
- package/app/components/DuxtPageInfo.vue +194 -0
- package/app/components/DuxtPageNav.vue +93 -0
- package/app/components/DuxtPreviewSkeleton.vue +262 -0
- package/app/components/DuxtProgress.vue +45 -0
- package/app/components/DuxtSearch.vue +271 -0
- package/app/components/DuxtSections.vue +65 -0
- package/app/components/DuxtShortcuts.vue +46 -0
- package/app/components/DuxtSkipLink.vue +34 -0
- package/app/components/DuxtToc.vue +86 -0
- package/app/components/DuxtTranslationBanner.vue +64 -0
- package/app/components/DuxtVersion.vue +168 -0
- package/app/components/DuxtVersionBanner.vue +109 -0
- package/app/components/OgImage/Duxt.satori.vue +82 -0
- package/app/components/content/Accordion.vue +37 -0
- package/app/components/content/AccordionItem.vue +46 -0
- package/app/components/content/Callout.vue +58 -0
- package/app/components/content/ChangelogGroup.vue +91 -0
- package/app/components/content/ChangelogReleases.vue +520 -0
- package/app/components/content/CodeGroup.vue +183 -0
- package/app/components/content/DevtoolsPanel.vue +107 -0
- package/app/components/content/Field.vue +50 -0
- package/app/components/content/FieldGroup.vue +8 -0
- package/app/components/content/FileTree.vue +92 -0
- package/app/components/content/Mermaid.vue +70 -0
- package/app/components/content/OpenApiOperation.vue +185 -0
- package/app/components/content/OpenApiOperations.vue +72 -0
- package/app/components/content/OpenApiOverview.vue +302 -0
- package/app/components/content/PackageManagers.vue +187 -0
- package/app/components/content/PageCards.vue +48 -0
- package/app/components/content/Partial.vue +65 -0
- package/app/components/content/Preview.vue +173 -0
- package/app/components/content/ProseA.vue +55 -0
- package/app/components/content/ProseH2.vue +53 -0
- package/app/components/content/ProseH3.vue +53 -0
- package/app/components/content/ProseH4.vue +53 -0
- package/app/components/content/ProseImg.vue +87 -0
- package/app/components/content/ProsePre.vue +58 -0
- package/app/components/content/ProseTable.vue +11 -0
- package/app/components/content/Since.vue +21 -0
- package/app/components/content/Steps.vue +20 -0
- package/app/components/content/Tab.vue +23 -0
- package/app/components/content/TabGroup.vue +58 -0
- package/app/components/ui/alert/Alert.vue +21 -0
- package/app/components/ui/alert/AlertDescription.vue +22 -0
- package/app/components/ui/alert/AlertTitle.vue +22 -0
- package/app/components/ui/alert/index.ts +24 -0
- package/app/components/ui/badge/Badge.vue +28 -0
- package/app/components/ui/badge/index.ts +52 -0
- package/app/components/ui/breadcrumb/Breadcrumb.vue +13 -0
- package/app/components/ui/breadcrumb/BreadcrumbEllipsis.vue +22 -0
- package/app/components/ui/breadcrumb/BreadcrumbItem.vue +17 -0
- package/app/components/ui/breadcrumb/BreadcrumbLink.vue +24 -0
- package/app/components/ui/breadcrumb/BreadcrumbList.vue +22 -0
- package/app/components/ui/breadcrumb/BreadcrumbPage.vue +20 -0
- package/app/components/ui/breadcrumb/BreadcrumbSeparator.vue +21 -0
- package/app/components/ui/breadcrumb/index.ts +7 -0
- package/app/components/ui/button/Button.vue +31 -0
- package/app/components/ui/button/index.ts +39 -0
- package/app/components/ui/card/Card.vue +22 -0
- package/app/components/ui/card/CardAction.vue +22 -0
- package/app/components/ui/card/CardContent.vue +14 -0
- package/app/components/ui/card/CardDescription.vue +17 -0
- package/app/components/ui/card/CardFooter.vue +17 -0
- package/app/components/ui/card/CardHeader.vue +22 -0
- package/app/components/ui/card/CardTitle.vue +17 -0
- package/app/components/ui/card/index.ts +7 -0
- package/app/components/ui/collapsible/Collapsible.vue +19 -0
- package/app/components/ui/collapsible/CollapsibleContent.vue +12 -0
- package/app/components/ui/collapsible/CollapsibleTrigger.vue +12 -0
- package/app/components/ui/collapsible/index.ts +3 -0
- package/app/components/ui/command/Command.vue +98 -0
- package/app/components/ui/command/CommandDialog.vue +42 -0
- package/app/components/ui/command/CommandEmpty.vue +31 -0
- package/app/components/ui/command/CommandGroup.vue +52 -0
- package/app/components/ui/command/CommandInput.vue +45 -0
- package/app/components/ui/command/CommandItem.vue +85 -0
- package/app/components/ui/command/CommandList.vue +32 -0
- package/app/components/ui/command/CommandSeparator.vue +23 -0
- package/app/components/ui/command/CommandShortcut.vue +19 -0
- package/app/components/ui/command/index.ts +29 -0
- package/app/components/ui/dialog/Dialog.vue +15 -0
- package/app/components/ui/dialog/DialogClose.vue +12 -0
- package/app/components/ui/dialog/DialogContent.vue +61 -0
- package/app/components/ui/dialog/DialogDescription.vue +25 -0
- package/app/components/ui/dialog/DialogFooter.vue +30 -0
- package/app/components/ui/dialog/DialogHeader.vue +17 -0
- package/app/components/ui/dialog/DialogOverlay.vue +28 -0
- package/app/components/ui/dialog/DialogScrollContent.vue +65 -0
- package/app/components/ui/dialog/DialogTitle.vue +25 -0
- package/app/components/ui/dialog/DialogTrigger.vue +12 -0
- package/app/components/ui/dialog/index.ts +10 -0
- package/app/components/ui/dropdown-menu/DropdownMenu.vue +19 -0
- package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +47 -0
- package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +47 -0
- package/app/components/ui/dropdown-menu/DropdownMenuGroup.vue +12 -0
- package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +41 -0
- package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +27 -0
- package/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +21 -0
- package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +48 -0
- package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
- package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +19 -0
- package/app/components/ui/dropdown-menu/DropdownMenuSub.vue +19 -0
- package/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue +34 -0
- package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +34 -0
- package/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue +17 -0
- package/app/components/ui/dropdown-menu/index.ts +16 -0
- package/app/components/ui/input/Input.vue +35 -0
- package/app/components/ui/input/index.ts +1 -0
- package/app/components/ui/navigation-menu/NavigationMenu.vue +42 -0
- package/app/components/ui/navigation-menu/NavigationMenuContent.vue +35 -0
- package/app/components/ui/navigation-menu/NavigationMenuIndicator.vue +32 -0
- package/app/components/ui/navigation-menu/NavigationMenuItem.vue +23 -0
- package/app/components/ui/navigation-menu/NavigationMenuLink.vue +30 -0
- package/app/components/ui/navigation-menu/NavigationMenuList.vue +30 -0
- package/app/components/ui/navigation-menu/NavigationMenuTrigger.vue +31 -0
- package/app/components/ui/navigation-menu/NavigationMenuViewport.vue +30 -0
- package/app/components/ui/navigation-menu/index.ts +14 -0
- package/app/components/ui/scroll-area/ScrollArea.vue +31 -0
- package/app/components/ui/scroll-area/ScrollBar.vue +38 -0
- package/app/components/ui/scroll-area/index.ts +2 -0
- package/app/components/ui/select/Select.vue +15 -0
- package/app/components/ui/select/SelectContent.vue +59 -0
- package/app/components/ui/select/SelectGroup.vue +19 -0
- package/app/components/ui/select/SelectItem.vue +42 -0
- package/app/components/ui/select/SelectItemText.vue +12 -0
- package/app/components/ui/select/SelectLabel.vue +16 -0
- package/app/components/ui/select/SelectScrollDownButton.vue +28 -0
- package/app/components/ui/select/SelectScrollUpButton.vue +28 -0
- package/app/components/ui/select/SelectSeparator.vue +20 -0
- package/app/components/ui/select/SelectTrigger.vue +32 -0
- package/app/components/ui/select/SelectValue.vue +12 -0
- package/app/components/ui/select/index.ts +11 -0
- package/app/components/ui/separator/Separator.vue +30 -0
- package/app/components/ui/separator/index.ts +1 -0
- package/app/components/ui/sheet/Sheet.vue +15 -0
- package/app/components/ui/sheet/SheetClose.vue +12 -0
- package/app/components/ui/sheet/SheetContent.vue +68 -0
- package/app/components/ui/sheet/SheetDescription.vue +23 -0
- package/app/components/ui/sheet/SheetFooter.vue +15 -0
- package/app/components/ui/sheet/SheetHeader.vue +15 -0
- package/app/components/ui/sheet/SheetOverlay.vue +28 -0
- package/app/components/ui/sheet/SheetTitle.vue +23 -0
- package/app/components/ui/sheet/SheetTrigger.vue +12 -0
- package/app/components/ui/sheet/index.ts +8 -0
- package/app/components/ui/sidebar/Sidebar.vue +110 -0
- package/app/components/ui/sidebar/SidebarContent.vue +23 -0
- package/app/components/ui/sidebar/SidebarFooter.vue +18 -0
- package/app/components/ui/sidebar/SidebarGroup.vue +18 -0
- package/app/components/ui/sidebar/SidebarGroupAction.vue +31 -0
- package/app/components/ui/sidebar/SidebarGroupContent.vue +18 -0
- package/app/components/ui/sidebar/SidebarGroupLabel.vue +30 -0
- package/app/components/ui/sidebar/SidebarHeader.vue +18 -0
- package/app/components/ui/sidebar/SidebarInput.vue +19 -0
- package/app/components/ui/sidebar/SidebarInset.vue +23 -0
- package/app/components/ui/sidebar/SidebarMenu.vue +18 -0
- package/app/components/ui/sidebar/SidebarMenuAction.vue +42 -0
- package/app/components/ui/sidebar/SidebarMenuBadge.vue +28 -0
- package/app/components/ui/sidebar/SidebarMenuButton.vue +60 -0
- package/app/components/ui/sidebar/SidebarMenuButtonChild.vue +36 -0
- package/app/components/ui/sidebar/SidebarMenuItem.vue +18 -0
- package/app/components/ui/sidebar/SidebarMenuSkeleton.vue +35 -0
- package/app/components/ui/sidebar/SidebarMenuSub.vue +24 -0
- package/app/components/ui/sidebar/SidebarMenuSubButton.vue +43 -0
- package/app/components/ui/sidebar/SidebarMenuSubItem.vue +18 -0
- package/app/components/ui/sidebar/SidebarProvider.vue +109 -0
- package/app/components/ui/sidebar/SidebarRail.vue +35 -0
- package/app/components/ui/sidebar/SidebarSeparator.vue +19 -0
- package/app/components/ui/sidebar/SidebarTrigger.vue +26 -0
- package/app/components/ui/sidebar/index.ts +62 -0
- package/app/components/ui/sidebar/utils.ts +19 -0
- package/app/components/ui/skeleton/Skeleton.vue +17 -0
- package/app/components/ui/skeleton/index.ts +1 -0
- package/app/components/ui/sonner/Sonner.vue +67 -0
- package/app/components/ui/sonner/index.ts +1 -0
- package/app/components/ui/tabs/Tabs.vue +26 -0
- package/app/components/ui/tabs/TabsContent.vue +23 -0
- package/app/components/ui/tabs/TabsList.vue +28 -0
- package/app/components/ui/tabs/TabsTrigger.vue +30 -0
- package/app/components/ui/tabs/index.ts +4 -0
- package/app/components/ui/textarea/Textarea.vue +32 -0
- package/app/components/ui/textarea/index.ts +1 -0
- package/app/components/ui/tooltip/Tooltip.vue +15 -0
- package/app/components/ui/tooltip/TooltipContent.vue +49 -0
- package/app/components/ui/tooltip/TooltipProvider.vue +14 -0
- package/app/components/ui/tooltip/TooltipTrigger.vue +12 -0
- package/app/components/ui/tooltip/index.ts +4 -0
- package/app/composables/useActiveHeading.ts +90 -0
- package/app/composables/useDuxtAnimatedHeight.ts +155 -0
- package/app/composables/useDuxtBreadcrumb.ts +60 -0
- package/app/composables/useDuxtChoice.ts +28 -0
- package/app/composables/useDuxtCollection.ts +89 -0
- package/app/composables/useDuxtConfig.ts +47 -0
- package/app/composables/useDuxtNavigation.ts +87 -0
- package/app/composables/useDuxtPageFocus.ts +30 -0
- package/app/composables/useDuxtPath.ts +42 -0
- package/app/composables/useDuxtSearch.ts +208 -0
- package/app/composables/useDuxtSection.ts +44 -0
- package/app/composables/useDuxtSectionRow.ts +49 -0
- package/app/composables/useDuxtShortcuts.ts +58 -0
- package/app/composables/useDuxtSiteUrl.ts +27 -0
- package/app/composables/useDuxtToast.ts +25 -0
- package/app/composables/useDuxtVersion.ts +138 -0
- package/app/composables/useFuzzySearch.ts +83 -0
- package/app/composables/usePackageManager.ts +4 -0
- package/app/composables/useRecentPages.ts +52 -0
- package/app/error.vue +133 -0
- package/app/layouts/changelog.vue +85 -0
- package/app/layouts/default.vue +23 -0
- package/app/layouts/docs.vue +62 -0
- package/app/layouts/reference.vue +85 -0
- package/app/lib/utils.ts +7 -0
- package/app/middleware/duxt-section-layout.global.ts +57 -0
- package/app/pages/[...slug].vue +441 -0
- package/app/pages/index.vue +479 -0
- package/app/types/duxt.d.ts +955 -0
- package/app/utils/aside.ts +16 -0
- package/app/utils/changelog.ts +182 -0
- package/app/utils/code-highlight.ts +116 -0
- package/app/utils/duxt-config.ts +185 -0
- package/app/utils/duxt-text.ts +135 -0
- package/app/utils/file-icons.ts +62 -0
- package/app/utils/generated-sections.ts +190 -0
- package/app/utils/generated-toc.ts +216 -0
- package/app/utils/heading-link.ts +32 -0
- package/app/utils/locale-path.ts +45 -0
- package/app/utils/navigation-tree.ts +206 -0
- package/app/utils/nearest-page.ts +72 -0
- package/app/utils/openapi.ts +1028 -0
- package/app/utils/package-command.ts +162 -0
- package/app/utils/request-samples.ts +635 -0
- package/app/utils/section-scope.ts +97 -0
- package/app/utils/sheet-navigation.ts +100 -0
- package/app/utils/source-file.ts +84 -0
- package/app/utils/version-choices.ts +68 -0
- package/app/utils/version-paths.ts +65 -0
- package/bin/duxt-report.mjs +45 -0
- package/components.json +19 -0
- package/content-cache.ts +193 -0
- package/content.config.ts +38 -0
- package/duxt-app-config.ts +124 -0
- package/highlight-langs.ts +68 -0
- package/i18n/i18n.config.ts +16 -0
- package/i18n/locales/de/duxt/changelog.json +17 -0
- package/i18n/locales/de/duxt/code.json +13 -0
- package/i18n/locales/de/duxt/defaults.json +22 -0
- package/i18n/locales/de/duxt/devtools.json +7 -0
- package/i18n/locales/de/duxt/error.json +10 -0
- package/i18n/locales/de/duxt/footer.json +7 -0
- package/i18n/locales/de/duxt/locale.json +7 -0
- package/i18n/locales/de/duxt/nav.json +17 -0
- package/i18n/locales/de/duxt/openapi.json +89 -0
- package/i18n/locales/de/duxt/page.json +34 -0
- package/i18n/locales/de/duxt/search.json +12 -0
- package/i18n/locales/de/duxt/shortcuts.json +11 -0
- package/i18n/locales/de/duxt/theme.json +7 -0
- package/i18n/locales/de/duxt/toc.json +7 -0
- package/i18n/locales/de/duxt/version.json +25 -0
- package/i18n/locales/en/duxt/changelog.json +17 -0
- package/i18n/locales/en/duxt/code.json +13 -0
- package/i18n/locales/en/duxt/defaults.json +22 -0
- package/i18n/locales/en/duxt/devtools.json +7 -0
- package/i18n/locales/en/duxt/error.json +10 -0
- package/i18n/locales/en/duxt/footer.json +7 -0
- package/i18n/locales/en/duxt/locale.json +7 -0
- package/i18n/locales/en/duxt/nav.json +17 -0
- package/i18n/locales/en/duxt/openapi.json +89 -0
- package/i18n/locales/en/duxt/page.json +34 -0
- package/i18n/locales/en/duxt/search.json +12 -0
- package/i18n/locales/en/duxt/shortcuts.json +11 -0
- package/i18n/locales/en/duxt/theme.json +7 -0
- package/i18n/locales/en/duxt/toc.json +7 -0
- package/i18n/locales/en/duxt/version.json +25 -0
- package/i18n/locales/es/duxt/changelog.json +17 -0
- package/i18n/locales/es/duxt/code.json +13 -0
- package/i18n/locales/es/duxt/defaults.json +22 -0
- package/i18n/locales/es/duxt/devtools.json +7 -0
- package/i18n/locales/es/duxt/error.json +10 -0
- package/i18n/locales/es/duxt/footer.json +7 -0
- package/i18n/locales/es/duxt/locale.json +7 -0
- package/i18n/locales/es/duxt/nav.json +17 -0
- package/i18n/locales/es/duxt/openapi.json +89 -0
- package/i18n/locales/es/duxt/page.json +34 -0
- package/i18n/locales/es/duxt/search.json +12 -0
- package/i18n/locales/es/duxt/shortcuts.json +11 -0
- package/i18n/locales/es/duxt/theme.json +7 -0
- package/i18n/locales/es/duxt/toc.json +7 -0
- package/i18n/locales/es/duxt/version.json +25 -0
- package/i18n/locales/fr/duxt/changelog.json +17 -0
- package/i18n/locales/fr/duxt/code.json +13 -0
- package/i18n/locales/fr/duxt/defaults.json +22 -0
- package/i18n/locales/fr/duxt/devtools.json +7 -0
- package/i18n/locales/fr/duxt/error.json +10 -0
- package/i18n/locales/fr/duxt/footer.json +7 -0
- package/i18n/locales/fr/duxt/locale.json +7 -0
- package/i18n/locales/fr/duxt/nav.json +17 -0
- package/i18n/locales/fr/duxt/openapi.json +89 -0
- package/i18n/locales/fr/duxt/page.json +34 -0
- package/i18n/locales/fr/duxt/search.json +12 -0
- package/i18n/locales/fr/duxt/shortcuts.json +11 -0
- package/i18n/locales/fr/duxt/theme.json +7 -0
- package/i18n/locales/fr/duxt/toc.json +7 -0
- package/i18n/locales/fr/duxt/version.json +25 -0
- package/i18n/locales/pt/duxt/changelog.json +17 -0
- package/i18n/locales/pt/duxt/code.json +13 -0
- package/i18n/locales/pt/duxt/defaults.json +22 -0
- package/i18n/locales/pt/duxt/devtools.json +7 -0
- package/i18n/locales/pt/duxt/error.json +10 -0
- package/i18n/locales/pt/duxt/footer.json +7 -0
- package/i18n/locales/pt/duxt/locale.json +7 -0
- package/i18n/locales/pt/duxt/nav.json +17 -0
- package/i18n/locales/pt/duxt/openapi.json +89 -0
- package/i18n/locales/pt/duxt/page.json +34 -0
- package/i18n/locales/pt/duxt/search.json +12 -0
- package/i18n/locales/pt/duxt/shortcuts.json +11 -0
- package/i18n/locales/pt/duxt/theme.json +7 -0
- package/i18n/locales/pt/duxt/toc.json +7 -0
- package/i18n/locales/pt/duxt/version.json +25 -0
- package/i18n/locales/pt-BR/duxt/error.json +8 -0
- package/i18n/locales/pt-BR/duxt/nav.json +10 -0
- package/i18n/locales/pt-BR/duxt/page.json +10 -0
- package/i18n/locales/pt-BR/duxt/search.json +11 -0
- package/i18n/locales/pt-BR/duxt/version.json +13 -0
- package/mdc.config.ts +28 -0
- package/modules/config.ts +440 -0
- package/modules/devtools.ts +126 -0
- package/modules/git-meta.ts +349 -0
- package/modules/redirects.ts +152 -0
- package/modules/validate.ts +137 -0
- package/nuxt.config.ts +530 -0
- package/openapi-model.ts +358 -0
- package/openapi-parse.ts +1052 -0
- package/package.json +161 -0
- package/public/devtools/cache.html +318 -0
- package/public/devtools/checks.html +309 -0
- package/public/devtools/config.html +461 -0
- package/public/devtools/i18n.html +309 -0
- package/public/devtools/pages.html +348 -0
- package/public/devtools/paths.html +315 -0
- package/public/devtools/redirects.html +309 -0
- package/public/devtools/search.html +324 -0
- package/public/devtools/sources.html +309 -0
- package/public/devtools/versions.html +318 -0
- package/report.ts +303 -0
- package/repository-root.ts +26 -0
- package/section-reports.ts +55 -0
- package/sections-changelog.ts +644 -0
- package/sections-openapi.ts +450 -0
- package/sections-resolve.ts +711 -0
- package/sections.ts +157 -0
- package/server/devtools/content.ts +120 -0
- package/server/devtools/context.ts +38 -0
- package/server/devtools/entry-path.ts +30 -0
- package/server/devtools/handler.ts +104 -0
- package/server/devtools/preview.ts +461 -0
- package/server/devtools/render/content.ts +233 -0
- package/server/devtools/render/sources.ts +414 -0
- package/server/devtools/render/system.ts +352 -0
- package/server/devtools/shell.ts +611 -0
- package/server/devtools/sources.ts +122 -0
- package/server/devtools/system.ts +178 -0
- package/server/mcp/tools/list-pages.ts +52 -0
- package/server/mcp/tools/read-page.ts +57 -0
- package/server/mcp/tools/search-docs.ts +74 -0
- package/server/middleware/raw-markdown.ts +86 -0
- package/server/routes/llms-full.txt.get.ts +75 -0
- package/server/routes/llms.txt.get.ts +63 -0
- package/server/routes/rss.xml.get.ts +125 -0
- package/server/utils/duxt-server-text.ts +46 -0
- package/sources-git.ts +113 -0
- package/sources-resolve.ts +908 -0
- package/sources.ts +287 -0
- package/validate-report.ts +494 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Where the list is kept. Namespaced so a consumer's own keys cannot collide. */
|
|
2
|
+
const STORAGE_KEY = 'duxt:recent-pages';
|
|
3
|
+
const LIMIT = 5;
|
|
4
|
+
|
|
5
|
+
export interface RecentPage {
|
|
6
|
+
path: string;
|
|
7
|
+
title: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The pages this reader visited last, for the search dialog's empty state.
|
|
12
|
+
*
|
|
13
|
+
* localStorage, not a cookie: unlike the package-manager choice this is never
|
|
14
|
+
* needed during server rendering, so it has no business travelling with every
|
|
15
|
+
* request. It stays in the browser, identifies nobody, and clearing site data
|
|
16
|
+
* forgets it.
|
|
17
|
+
*/
|
|
18
|
+
export function useRecentPages() {
|
|
19
|
+
const recent = useState<RecentPage[]>('duxt-recent-pages', () => []);
|
|
20
|
+
|
|
21
|
+
function read(): RecentPage[] {
|
|
22
|
+
try {
|
|
23
|
+
const stored = window.localStorage.getItem(STORAGE_KEY);
|
|
24
|
+
return stored ? (JSON.parse(stored) as RecentPage[]) : [];
|
|
25
|
+
} catch {
|
|
26
|
+
// Blocked storage, or a value someone else wrote — either way, no history.
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function remember(page: RecentPage) {
|
|
32
|
+
if (!import.meta.client || !page.path || !page.title) return;
|
|
33
|
+
|
|
34
|
+
const next = [
|
|
35
|
+
page,
|
|
36
|
+
...read().filter((entry) => entry.path !== page.path)
|
|
37
|
+
].slice(0, LIMIT);
|
|
38
|
+
recent.value = next;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
|
|
42
|
+
} catch {
|
|
43
|
+
// A convenience not worth an error.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function load() {
|
|
48
|
+
if (import.meta.client) recent.value = read();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { recent, remember, load };
|
|
52
|
+
}
|
package/app/error.vue
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { NuxtError } from '#app';
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{ error: NuxtError }>();
|
|
5
|
+
|
|
6
|
+
const localeLink = useDuxtLink();
|
|
7
|
+
const path = useDuxtPath();
|
|
8
|
+
const duxt = useDuxtConfig();
|
|
9
|
+
const { locale } = useI18n();
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The error page replaces `app.vue`, so it inherits nothing from it — not the
|
|
13
|
+
* `lang` attribute and not the title template. Both have to be set again here,
|
|
14
|
+
* or the one page a reader reaches by accident is the one page with an
|
|
15
|
+
* untitled, language-less document.
|
|
16
|
+
*/
|
|
17
|
+
useHead(() => ({
|
|
18
|
+
htmlAttrs: { lang: locale.value },
|
|
19
|
+
title: `${props.error.statusCode} · ${duxt.title}`
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* An error page is not a page of the site.
|
|
24
|
+
*
|
|
25
|
+
* Nothing stopped a crawler indexing this one, and a 404 that gets indexed
|
|
26
|
+
* competes in a search result with the page the reader was actually looking
|
|
27
|
+
* for. `follow` on purpose: the suggestions below are real pages and a crawler
|
|
28
|
+
* that arrived here by a broken link should still reach them.
|
|
29
|
+
*/
|
|
30
|
+
useSeoMeta({ robots: 'noindex, follow' });
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The nearest real pages, from the navigation the layout already fetched.
|
|
34
|
+
*
|
|
35
|
+
* A 404 in versioned documentation is usually a URL that is nearly right, and
|
|
36
|
+
* an empty "not found" tells that reader nothing. Only for a 404 — a 500 has
|
|
37
|
+
* no near miss to offer, and suggesting pages after a server error reads as if
|
|
38
|
+
* the site had decided the reader was mistaken.
|
|
39
|
+
*/
|
|
40
|
+
const { data: navigation } = await useDuxtNavigation();
|
|
41
|
+
|
|
42
|
+
const suggestions = computed(() => {
|
|
43
|
+
if (props.error.statusCode !== 404) return [];
|
|
44
|
+
|
|
45
|
+
const pages: { path: string; title?: string }[] = [];
|
|
46
|
+
|
|
47
|
+
const walk = (
|
|
48
|
+
items: { path?: string; title?: string; children?: unknown[] }[]
|
|
49
|
+
) => {
|
|
50
|
+
for (const item of items) {
|
|
51
|
+
if (item.path) pages.push({ path: item.path, title: item.title });
|
|
52
|
+
if (Array.isArray(item.children)) {
|
|
53
|
+
walk(item.children as { path?: string; title?: string }[]);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
walk((navigation.value ?? []) as { path?: string; title?: string }[]);
|
|
59
|
+
|
|
60
|
+
return nearestPages(path.value, pages);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// A page missing from one version but present in another is the interesting
|
|
64
|
+
// case: the reader asked for something real, just not here.
|
|
65
|
+
const elsewhere = computed(
|
|
66
|
+
() =>
|
|
67
|
+
(
|
|
68
|
+
props.error.data as {
|
|
69
|
+
elsewhere?: { version: { label: string }; path: string }[];
|
|
70
|
+
}
|
|
71
|
+
)?.elsewhere ?? []
|
|
72
|
+
);
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<template>
|
|
76
|
+
<NuxtLayout>
|
|
77
|
+
<div
|
|
78
|
+
class="mx-auto flex max-w-2xl flex-col items-center px-4 py-32 text-center"
|
|
79
|
+
>
|
|
80
|
+
<p class="font-mono text-sm text-muted-foreground">
|
|
81
|
+
{{ error.statusCode }}
|
|
82
|
+
</p>
|
|
83
|
+
<h1 class="mt-3 text-3xl font-semibold tracking-tight text-balance">
|
|
84
|
+
{{ error.statusMessage ?? $t('duxt.error.title') }}
|
|
85
|
+
</h1>
|
|
86
|
+
|
|
87
|
+
<div v-if="elsewhere.length" class="mt-8 w-full">
|
|
88
|
+
<p class="mb-3 text-sm text-muted-foreground">
|
|
89
|
+
{{ $t('duxt.error.elsewhere') }}
|
|
90
|
+
</p>
|
|
91
|
+
<div class="flex flex-wrap justify-center gap-2">
|
|
92
|
+
<UiButton
|
|
93
|
+
v-for="entry in elsewhere"
|
|
94
|
+
:key="entry.path"
|
|
95
|
+
as-child
|
|
96
|
+
variant="outline"
|
|
97
|
+
size="sm"
|
|
98
|
+
>
|
|
99
|
+
<NuxtLink :to="localeLink(entry.path)" class="font-mono text-xs">
|
|
100
|
+
{{ entry.version.label }}
|
|
101
|
+
</NuxtLink>
|
|
102
|
+
</UiButton>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div v-if="suggestions.length" class="mt-8 w-full">
|
|
107
|
+
<p class="mb-3 text-sm text-muted-foreground">
|
|
108
|
+
{{ $t('duxt.error.nearest') }}
|
|
109
|
+
</p>
|
|
110
|
+
<ul class="flex flex-col gap-1 text-sm">
|
|
111
|
+
<li v-for="page in suggestions" :key="page.path">
|
|
112
|
+
<NuxtLink
|
|
113
|
+
:to="localeLink(page.path)"
|
|
114
|
+
class="flex items-center justify-center gap-2 rounded-md px-3 py-1.5 transition-colors hover:bg-accent"
|
|
115
|
+
>
|
|
116
|
+
<span class="font-medium">{{ page.title ?? page.path }}</span>
|
|
117
|
+
<span class="font-mono text-xs text-muted-foreground">{{
|
|
118
|
+
page.path
|
|
119
|
+
}}</span>
|
|
120
|
+
</NuxtLink>
|
|
121
|
+
</li>
|
|
122
|
+
</ul>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<UiButton as-child class="mt-10">
|
|
126
|
+
<NuxtLink :to="localeLink('/')">
|
|
127
|
+
<Icon name="lucide:arrow-left" class="size-4" />
|
|
128
|
+
{{ $t('duxt.error.back') }}
|
|
129
|
+
</NuxtLink>
|
|
130
|
+
</UiButton>
|
|
131
|
+
</div>
|
|
132
|
+
</NuxtLayout>
|
|
133
|
+
</template>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The shell a release history gets: the releases beside it, and a reading
|
|
4
|
+
* column for the entries.
|
|
5
|
+
*
|
|
6
|
+
* THE SECOND NAME THROUGH THE SHARED SLOT (`DuxtSectionType.layout`), and the
|
|
7
|
+
* shape a changelog needs — which is not the shape the API reference needed.
|
|
8
|
+
* `reference` hands its page the whole column it has, because parameter tables
|
|
9
|
+
* and a request client want it; a release note is prose, and prose set across
|
|
10
|
+
* that column is unreadable. So the column is capped at a measure and the
|
|
11
|
+
* releases take the sidebar. Both shells stop at the same 90rem the header and
|
|
12
|
+
* the footer do — the difference is what each gives its page inside that.
|
|
13
|
+
*
|
|
14
|
+
* Two differences from `docs`, and both follow from what the pages ARE. The
|
|
15
|
+
* sidebar is the release list rather than a prose tree — same navigation, same
|
|
16
|
+
* collection, because a generated section is an ordinary collection and its
|
|
17
|
+
* pages are the releases. And the reading column is capped inside a window that
|
|
18
|
+
* still leaves room for the contents on the right, which is why `main` carries
|
|
19
|
+
* a width rather than the page: a layout owns how wide its prose is set, and
|
|
20
|
+
* `pages/[...slug].vue` draws the same column for every generated section.
|
|
21
|
+
*
|
|
22
|
+
* The contents column IS there, and its links are not the ones Content found:
|
|
23
|
+
* a release page's headings are drawn by `ChangelogGroup` from a prop, so the
|
|
24
|
+
* outline is read back off the page's own AST — see `generated-toc.ts`.
|
|
25
|
+
*
|
|
26
|
+
* Bound only by the SPLIT granularity. A changelog asked for as the one file it
|
|
27
|
+
* was written as is an ordinary docs page and renders in the docs shell — see
|
|
28
|
+
* `changelogSectionType.layout`.
|
|
29
|
+
*
|
|
30
|
+
* A page rendered here draws its own header: see the `owned` computed in
|
|
31
|
+
* `pages/[...slug].vue`.
|
|
32
|
+
*/
|
|
33
|
+
const { data: navigation } = await useDuxtNavigation();
|
|
34
|
+
|
|
35
|
+
const { items } = useDuxtSection(navigation);
|
|
36
|
+
|
|
37
|
+
// The header the sticky columns hang under — `6.5rem` with the section row,
|
|
38
|
+
// less without it. Handed down as a variable so the columns here and the
|
|
39
|
+
// contents column `pages/[...slug].vue` draws all read one value.
|
|
40
|
+
const { headerOffset } = useDuxtSectionRow();
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<template>
|
|
44
|
+
<div
|
|
45
|
+
class="flex min-h-[100dvh] flex-col bg-background text-foreground"
|
|
46
|
+
:style="{ '--duxt-header-offset': headerOffset }"
|
|
47
|
+
>
|
|
48
|
+
<DuxtSkipLink />
|
|
49
|
+
|
|
50
|
+
<DuxtProgress />
|
|
51
|
+
<DuxtShortcuts />
|
|
52
|
+
|
|
53
|
+
<DuxtHeader />
|
|
54
|
+
<DuxtSections />
|
|
55
|
+
|
|
56
|
+
<div class="mx-auto flex w-full max-w-[90rem] flex-1 gap-8 px-4 lg:px-8">
|
|
57
|
+
<!-- A section with one page has nothing to navigate, exactly as in the
|
|
58
|
+
docs layout: the column would list the page the reader is on. That is
|
|
59
|
+
the whole overview of a changelog with a single release. -->
|
|
60
|
+
<div v-if="items.length > 1" class="hidden w-56 shrink-0 lg:block">
|
|
61
|
+
<div
|
|
62
|
+
class="sticky top-[var(--duxt-header-offset)] max-h-[calc(100vh-var(--duxt-header-offset)-1.5rem)] overflow-y-auto py-8 pr-2"
|
|
63
|
+
>
|
|
64
|
+
<DuxtNavigation
|
|
65
|
+
:items="items"
|
|
66
|
+
:label="$t('duxt.changelog.releases')"
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<main
|
|
72
|
+
id="duxt-main"
|
|
73
|
+
tabindex="-1"
|
|
74
|
+
class="mx-auto flex w-full min-w-0 max-w-5xl flex-1 outline-none"
|
|
75
|
+
>
|
|
76
|
+
<slot />
|
|
77
|
+
</main>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<DuxtFooter />
|
|
81
|
+
|
|
82
|
+
<!-- One toaster per layout: toast() needs a mounted target to draw into. -->
|
|
83
|
+
<UiToaster position="bottom-right" />
|
|
84
|
+
</div>
|
|
85
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// The landing layout: full width, no sidebar. Docs pages opt into `docs`.
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<div class="flex min-h-[100dvh] flex-col bg-background text-foreground">
|
|
7
|
+
<DuxtSkipLink />
|
|
8
|
+
|
|
9
|
+
<!-- How far down the page the reader is, and the `?` sheet listing every
|
|
10
|
+
key the theme binds. -->
|
|
11
|
+
<DuxtProgress />
|
|
12
|
+
<DuxtShortcuts />
|
|
13
|
+
|
|
14
|
+
<DuxtHeader />
|
|
15
|
+
<main id="duxt-main" tabindex="-1" class="flex-1 outline-none">
|
|
16
|
+
<slot />
|
|
17
|
+
</main>
|
|
18
|
+
<DuxtFooter />
|
|
19
|
+
|
|
20
|
+
<!-- One toaster per layout: toast() needs a mounted target to draw into. -->
|
|
21
|
+
<UiToaster position="bottom-right" />
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const { data: navigation } = await useDuxtNavigation();
|
|
3
|
+
|
|
4
|
+
const { items } = useDuxtSection(navigation);
|
|
5
|
+
|
|
6
|
+
// The header the sticky columns hang under — `6.5rem` with the section row,
|
|
7
|
+
// less without it. Handed down as a variable so the columns here and the
|
|
8
|
+
// contents column `pages/[...slug].vue` draws all read one value.
|
|
9
|
+
const { headerOffset } = useDuxtSectionRow();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<div
|
|
14
|
+
class="flex min-h-[100dvh] flex-col bg-background text-foreground"
|
|
15
|
+
:style="{ '--duxt-header-offset': headerOffset }"
|
|
16
|
+
>
|
|
17
|
+
<DuxtSkipLink />
|
|
18
|
+
|
|
19
|
+
<!-- How far down the page the reader is, and the `?` sheet listing every
|
|
20
|
+
key the theme binds. -->
|
|
21
|
+
<DuxtProgress />
|
|
22
|
+
<DuxtShortcuts />
|
|
23
|
+
|
|
24
|
+
<DuxtHeader />
|
|
25
|
+
|
|
26
|
+
<!-- The section row sits with the docs, not in the global header: the
|
|
27
|
+
landing page has no sections to show. -->
|
|
28
|
+
<DuxtSections />
|
|
29
|
+
|
|
30
|
+
<!-- Three columns inside one centred container, not a sidebar pinned to the
|
|
31
|
+
window edge: on a wide screen the docs stay a readable block instead of
|
|
32
|
+
drifting apart. -->
|
|
33
|
+
<div class="mx-auto flex w-full max-w-[90rem] flex-1 gap-8 px-4 lg:px-8">
|
|
34
|
+
<!-- A div, not an aside. The <nav> inside is the landmark and carries
|
|
35
|
+
the label; a second, unnamed `complementary` around it only makes a
|
|
36
|
+
screen reader's landmark list longer. -->
|
|
37
|
+
<!-- A section with one page has nothing to navigate: the sidebar would be
|
|
38
|
+
a column listing the page the reader is already on. It drops out, and
|
|
39
|
+
the content takes the width. -->
|
|
40
|
+
<div v-if="items.length > 1" class="hidden w-56 shrink-0 lg:block">
|
|
41
|
+
<div
|
|
42
|
+
class="sticky top-[var(--duxt-header-offset)] max-h-[calc(100vh-var(--duxt-header-offset)-1.5rem)] overflow-y-auto py-8 pr-2"
|
|
43
|
+
>
|
|
44
|
+
<DuxtNavigation :items="items" :label="$t('duxt.nav.docs')" />
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<main
|
|
49
|
+
id="duxt-main"
|
|
50
|
+
tabindex="-1"
|
|
51
|
+
class="flex min-w-0 flex-1 outline-none"
|
|
52
|
+
>
|
|
53
|
+
<slot />
|
|
54
|
+
</main>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<DuxtFooter />
|
|
58
|
+
|
|
59
|
+
<!-- One toaster per layout: toast() needs a mounted target to draw into. -->
|
|
60
|
+
<UiToaster position="bottom-right" />
|
|
61
|
+
</div>
|
|
62
|
+
</template>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The layout a generated section's type binds when the docs shell will not do.
|
|
4
|
+
*
|
|
5
|
+
* THE FIRST NAME THROUGH THE SHARED SLOT (`DuxtSectionType.layout`), and the
|
|
6
|
+
* shape the API reference needs: the operations on the left, and the whole
|
|
7
|
+
* remaining width for the page — which then splits itself into the description
|
|
8
|
+
* and the client, because those two are properties of the operation and a
|
|
9
|
+
* layout cannot reach them.
|
|
10
|
+
*
|
|
11
|
+
* ONE difference from `docs`, and it is the sidebar: the endpoint list rather
|
|
12
|
+
* than a prose tree — the same navigation, from the same collection, because a
|
|
13
|
+
* generated section is an ordinary collection and its pages ARE the endpoints.
|
|
14
|
+
*
|
|
15
|
+
* The WIDTH is not a difference, and it was: this shell ran to 100rem where
|
|
16
|
+
* every other one on the site stops at 90rem, so on a wide screen the reference
|
|
17
|
+
* stood ten rem past the header above it and the footer below it. The argument
|
|
18
|
+
* for it — parameter tables and a request client want room — was an argument
|
|
19
|
+
* about the OPERATION, which splits itself and can be given that room there;
|
|
20
|
+
* it was never an argument for the page hanging over the site's own edge.
|
|
21
|
+
* Alignment with the header is worth more than ten rem nobody asked for.
|
|
22
|
+
*
|
|
23
|
+
* A page rendered here draws its own header: see the `owned` computed in
|
|
24
|
+
* `pages/[...slug].vue`.
|
|
25
|
+
*
|
|
26
|
+
* NO CONTENTS COLUMN, and it is this layout that has to say so — the page
|
|
27
|
+
* cannot know. An operation already fills its right-hand side with the request
|
|
28
|
+
* client, and a second sticky column beside that one is two columns of
|
|
29
|
+
* furniture around a table of parameters. `provide` before any await, or the
|
|
30
|
+
* value never reaches the page.
|
|
31
|
+
*/
|
|
32
|
+
provide(DUXT_ASIDE, false);
|
|
33
|
+
|
|
34
|
+
const { data: navigation } = await useDuxtNavigation();
|
|
35
|
+
|
|
36
|
+
const { items } = useDuxtSection(navigation);
|
|
37
|
+
|
|
38
|
+
// The header the sticky columns hang under — `6.5rem` with the section row,
|
|
39
|
+
// less without it. Handed down as a variable so the columns here and the
|
|
40
|
+
// contents column `pages/[...slug].vue` draws all read one value.
|
|
41
|
+
const { headerOffset } = useDuxtSectionRow();
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<div
|
|
46
|
+
class="flex min-h-[100dvh] flex-col bg-background text-foreground"
|
|
47
|
+
:style="{ '--duxt-header-offset': headerOffset }"
|
|
48
|
+
>
|
|
49
|
+
<DuxtSkipLink />
|
|
50
|
+
|
|
51
|
+
<DuxtProgress />
|
|
52
|
+
<DuxtShortcuts />
|
|
53
|
+
|
|
54
|
+
<DuxtHeader />
|
|
55
|
+
<DuxtSections />
|
|
56
|
+
|
|
57
|
+
<div class="mx-auto flex w-full max-w-[90rem] flex-1 gap-8 px-4 lg:px-8">
|
|
58
|
+
<!-- A section with one page has nothing to navigate, exactly as in the
|
|
59
|
+
docs layout: the column would list the page the reader is on. -->
|
|
60
|
+
<div v-if="items.length > 1" class="hidden w-64 shrink-0 lg:block">
|
|
61
|
+
<div
|
|
62
|
+
class="sticky top-[var(--duxt-header-offset)] max-h-[calc(100vh-var(--duxt-header-offset)-1.5rem)] overflow-y-auto py-8 pr-2"
|
|
63
|
+
>
|
|
64
|
+
<DuxtNavigation
|
|
65
|
+
:items="items"
|
|
66
|
+
:label="$t('duxt.openapi.endpoints')"
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<main
|
|
72
|
+
id="duxt-main"
|
|
73
|
+
tabindex="-1"
|
|
74
|
+
class="flex min-w-0 flex-1 outline-none"
|
|
75
|
+
>
|
|
76
|
+
<slot />
|
|
77
|
+
</main>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<DuxtFooter />
|
|
81
|
+
|
|
82
|
+
<!-- One toaster per layout: toast() needs a mounted target to draw into. -->
|
|
83
|
+
<UiToaster position="bottom-right" />
|
|
84
|
+
</div>
|
|
85
|
+
</template>
|
package/app/lib/utils.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The layout slot a generated section's type renders through.
|
|
3
|
+
*
|
|
4
|
+
* A ROUTE MIDDLEWARE, and deliberately not `<NuxtLayout>` inside the page: a
|
|
5
|
+
* layout rendered from within a page is remounted on every navigation, which
|
|
6
|
+
* would rebuild the header, the section row and the sidebar — losing their
|
|
7
|
+
* scroll and their open groups — on every click of a docs site. `setPageLayout`
|
|
8
|
+
* from a middleware is the one place the choice can be made before the route
|
|
9
|
+
* resolves, so the layout stays where it is and only its name changes.
|
|
10
|
+
*
|
|
11
|
+
* Cheap where no type names a layout: `generatedLayout` filters to the sections
|
|
12
|
+
* that do and answers before matching a single prefix. The path it is handed is
|
|
13
|
+
* still computed either way — arguments are evaluated first — which is one
|
|
14
|
+
* `stripLocalePrefix` per navigation and nothing else.
|
|
15
|
+
*/
|
|
16
|
+
export default defineNuxtRouteMiddleware((to) => {
|
|
17
|
+
const sources =
|
|
18
|
+
(useAppConfig().duxt as DuxtConfig | undefined)?.resolvedSources ?? [];
|
|
19
|
+
|
|
20
|
+
const layout = generatedLayout(
|
|
21
|
+
// The DOCUMENTATION path, as every other lookup in the theme means it: the
|
|
22
|
+
// locale translates the interface, not the content tree, so the manifest's
|
|
23
|
+
// prefixes have no locale segment in front of them.
|
|
24
|
+
stripLocalePrefix(to.path, localeCodes()),
|
|
25
|
+
sources
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// Cast for the reason `DuxtCollectionName` is a plain string: the name is
|
|
29
|
+
// DATA. Nuxt types `setPageLayout` by the layouts it found in this app, and a
|
|
30
|
+
// type's layout is named in a config the layer cannot see at build time.
|
|
31
|
+
//
|
|
32
|
+
// The FUNCTION is cast, not the argument. `setPageLayout` is generic over
|
|
33
|
+
// `keyof NuxtLayouts`, so `Parameters<typeof setPageLayout>[0]` resolves
|
|
34
|
+
// through a conditional on the type parameter and lands back on `string` —
|
|
35
|
+
// which is then not assignable to the parameter it was derived from. Casting
|
|
36
|
+
// the call signature says the one thing that is actually true: this name is
|
|
37
|
+
// checked at run time, against a registry the compiler cannot see.
|
|
38
|
+
if (layout) (setPageLayout as (name: string) => void)(layout);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The locales the site serves, read off the plugin rather than `useI18n()`.
|
|
43
|
+
*
|
|
44
|
+
* `useI18n()` wants a component instance; a middleware has none, and
|
|
45
|
+
* `nuxtApp.$i18n` is what @nuxtjs/i18n exposes for exactly this. Absent — a
|
|
46
|
+
* consumer who removed the module — the path is used as it stands, which is
|
|
47
|
+
* correct for a site with no locale prefixes to strip.
|
|
48
|
+
*/
|
|
49
|
+
function localeCodes(): string[] {
|
|
50
|
+
const i18n = useNuxtApp().$i18n as
|
|
51
|
+
| { locales?: { value?: (string | { code: string })[] } }
|
|
52
|
+
| undefined;
|
|
53
|
+
|
|
54
|
+
return (i18n?.locales?.value ?? []).map((entry) =>
|
|
55
|
+
typeof entry === 'string' ? entry : entry.code
|
|
56
|
+
);
|
|
57
|
+
}
|