@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,441 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const { collection, source, fallbacks, translates } = useDuxtCollection();
|
|
3
|
+
|
|
4
|
+
definePageMeta({ layout: 'docs' });
|
|
5
|
+
|
|
6
|
+
const path = useDuxtPath();
|
|
7
|
+
const duxt = useDuxtConfig();
|
|
8
|
+
const localeLink = useDuxtLink();
|
|
9
|
+
const { absolute } = useDuxtSiteUrl();
|
|
10
|
+
const { locale } = useI18n();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The page, in the best language this site has it in.
|
|
14
|
+
*
|
|
15
|
+
* A translated tree is rarely complete, and a reader who followed a link into
|
|
16
|
+
* one should not be punished for the gap with a 404 — Starlight is the only
|
|
17
|
+
* one of the comparable generators that says so out loud, and it is right.
|
|
18
|
+
* `fallbacks` is empty on a site without translations, so this is one query
|
|
19
|
+
* there, exactly as before.
|
|
20
|
+
*/
|
|
21
|
+
const { data: found } = await useAsyncData(
|
|
22
|
+
() => `docs-${locale.value}-${path.value}`,
|
|
23
|
+
async () => {
|
|
24
|
+
// Source OBJECTS, not names: the language each one carries is what the
|
|
25
|
+
// banner compares against, and the first entry has one too.
|
|
26
|
+
const chain = [source.value, ...fallbacks.value].filter(Boolean);
|
|
27
|
+
|
|
28
|
+
// The manifest can be absent for a tick after a hot reload, and a page that
|
|
29
|
+
// exists must not 404 over that: the collection name carries its own
|
|
30
|
+
// fallback, so query that rather than nothing at all.
|
|
31
|
+
const entries = chain.length
|
|
32
|
+
? chain
|
|
33
|
+
: [{ collection: collection.value, locale: undefined }];
|
|
34
|
+
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
const hit = await queryCollection(entry!.collection as DuxtCollectionArg)
|
|
37
|
+
.path(path.value)
|
|
38
|
+
.first();
|
|
39
|
+
|
|
40
|
+
// The language actually delivered, which is what the banner compares
|
|
41
|
+
// against — NOT the position in the chain. A locale the sources do not
|
|
42
|
+
// translate at all has the original as its first entry, and a reader
|
|
43
|
+
// asking for Spanish and getting English has to be told so even though
|
|
44
|
+
// nothing fell back.
|
|
45
|
+
//
|
|
46
|
+
// `asked` travels WITH the hit. `locale` changes the instant the reader
|
|
47
|
+
// picks a language, while this query — and therefore `from` — is still
|
|
48
|
+
// the previous language's result, so comparing the delivered language
|
|
49
|
+
// against the LIVE locale reports every switch as untranslated for the
|
|
50
|
+
// tick between the two. Freezing the language that was asked for into
|
|
51
|
+
// the same object keeps the pair consistent at every moment.
|
|
52
|
+
if (hit) return { page: hit, from: entry!.locale, asked: locale.value };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return undefined;
|
|
56
|
+
},
|
|
57
|
+
{ watch: [collection, path] }
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const page = computed(() => found.value?.page);
|
|
61
|
+
|
|
62
|
+
/** True when the reader is being shown a language they did not ask for. */
|
|
63
|
+
const untranslated = computed(() => {
|
|
64
|
+
if (!translates.value || !found.value) return false;
|
|
65
|
+
|
|
66
|
+
// Compared by LANGUAGE, not by code: a `de/` folder serves `de-DE`, and a
|
|
67
|
+
// reader there is not looking at a foreign language just because the folder
|
|
68
|
+
// names no region. Same rule as the locale files, and as `localeChain`.
|
|
69
|
+
const delivered = found.value.from;
|
|
70
|
+
if (!delivered) return true;
|
|
71
|
+
|
|
72
|
+
return delivered.split('-')[0] !== found.value.asked.split('-')[0];
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!page.value) {
|
|
76
|
+
throw createError({
|
|
77
|
+
statusCode: 404,
|
|
78
|
+
statusMessage: 'Page not found',
|
|
79
|
+
fatal: true
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Feeds the search dialog's empty state.
|
|
84
|
+
const { remember } = useRecentPages();
|
|
85
|
+
onMounted(() =>
|
|
86
|
+
remember({ path: path.value, title: page.value?.title ?? path.value })
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// After a client-side navigation the focus is still on whatever link was
|
|
90
|
+
// clicked. Move it to the heading of the page that arrived.
|
|
91
|
+
const heading = useDuxtPageFocus();
|
|
92
|
+
|
|
93
|
+
const { current, shouldIndex, preferredPath } = useDuxtVersion();
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Does the page draw its own header?
|
|
97
|
+
*
|
|
98
|
+
* A generated section whose TYPE NAMES A LAYOUT has replaced the docs shell
|
|
99
|
+
* around this page, and the two halves cannot both draw a header: an API
|
|
100
|
+
* reference wants its title beside a method chip and its right-hand column
|
|
101
|
+
* filled with a request client, not with a table of contents. So the rule is
|
|
102
|
+
* one line — a type that names a layout owns its page — and everything below
|
|
103
|
+
* that belongs to the docs shell drops out: the breadcrumb, the title block,
|
|
104
|
+
* the article's reading width and the prev/next pair. The contents column is
|
|
105
|
+
* the one part that stayed, because it is the one part a generated page still
|
|
106
|
+
* has an outline for — see `generated` below.
|
|
107
|
+
*
|
|
108
|
+
* The BANNERS stay. "You are reading an old version" and "this page is not
|
|
109
|
+
* translated" are true of a generated page exactly as they are of a written
|
|
110
|
+
* one, and a type cannot be expected to redraw them.
|
|
111
|
+
*
|
|
112
|
+
* `generatedLayout` is the same lookup `middleware/duxt-section-layout.global`
|
|
113
|
+
* makes to choose the layout, so the two cannot disagree about which pages are
|
|
114
|
+
* in one.
|
|
115
|
+
*/
|
|
116
|
+
const owned = computed(() =>
|
|
117
|
+
Boolean(generatedLayout(path.value, duxt?.resolvedSources ?? []))
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The contents of a page whose headings a component draws.
|
|
122
|
+
*
|
|
123
|
+
* `body.toc` is Content's outline of the MARKDOWN it parsed, and a generated
|
|
124
|
+
* page's headings are not in it: a release page's are the group names
|
|
125
|
+
* `ChangelogGroup` renders from a prop, so Content saw a component call and
|
|
126
|
+
* found nothing to list. Reading them back off the AST the page ships anyway is
|
|
127
|
+
* what gives a generated section the same right-hand column every written page
|
|
128
|
+
* has — and it stays empty, so the column drops out, for a type whose pages
|
|
129
|
+
* genuinely have no outline.
|
|
130
|
+
*/
|
|
131
|
+
const generated = computed(() =>
|
|
132
|
+
owned.value ? generatedToc(page.value?.body) : []
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* A layout can refuse the column outright — see `DUXT_ASIDE`. The reference
|
|
137
|
+
* does, because an operation fills that side with its request client.
|
|
138
|
+
*/
|
|
139
|
+
const allowed = inject(DUXT_ASIDE, true);
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Is there a right-hand column to draw at all?
|
|
143
|
+
*
|
|
144
|
+
* The outline is not the only thing in it: `DuxtToc` also carries the aside's
|
|
145
|
+
* fixed links, and `DuxtPageInfo` sits underneath. So the release OVERVIEW —
|
|
146
|
+
* which deliberately contributes no outline, because its versions are the
|
|
147
|
+
* visible page — still gets the column every other page has, rather than
|
|
148
|
+
* losing the community links and the provenance with it.
|
|
149
|
+
*/
|
|
150
|
+
/**
|
|
151
|
+
* Does the type draw its own title, or does the shell?
|
|
152
|
+
*
|
|
153
|
+
* See `generatedTitle`. A changelog writes no `<h1>`, so its pages take the
|
|
154
|
+
* same header every written page has; an API reference writes one, and keeps
|
|
155
|
+
* the compact row — the breadcrumb and the copy control, which are facts about
|
|
156
|
+
* the URL and the source rather than about the type.
|
|
157
|
+
*/
|
|
158
|
+
const titled = computed(() => owned.value && generatedTitle(page.value?.body));
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Does the shell draw the description, or has the body already said it?
|
|
162
|
+
*
|
|
163
|
+
* See `generatedLeadsWith`.
|
|
164
|
+
*/
|
|
165
|
+
const described = computed(() => {
|
|
166
|
+
const description = page.value?.description;
|
|
167
|
+
if (!owned.value || !description) return false;
|
|
168
|
+
|
|
169
|
+
return !generatedLeadsWith(description, page.value?.body);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const aside = computed(
|
|
173
|
+
() =>
|
|
174
|
+
allowed &&
|
|
175
|
+
(generated.value.length > 0 || (duxt.aside?.links?.length ?? 0) > 0)
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// The social card. Rendered from the layer's own template unless the consumer
|
|
179
|
+
// ships a component of the same name — see `OgImage/Duxt.satori.vue`.
|
|
180
|
+
//
|
|
181
|
+
// `defineOgImage`, not `defineOgImageComponent`: the latter is deprecated in
|
|
182
|
+
// nuxt-og-image v6 and warns once per render. Same arguments, same behaviour.
|
|
183
|
+
defineOgImage('Duxt', {
|
|
184
|
+
title: page.value.title,
|
|
185
|
+
description: page.value.description,
|
|
186
|
+
site: duxt.title,
|
|
187
|
+
version: current.value?.version ?? ''
|
|
188
|
+
});
|
|
189
|
+
const trail = await useDuxtBreadcrumb(() => path.value);
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The half nuxt-seo-utils cannot infer.
|
|
193
|
+
*
|
|
194
|
+
* It derives `og:title`, `og:description` and the Twitter pair from the title
|
|
195
|
+
* and description below, so those are gone from here — what is left is what
|
|
196
|
+
* only this page knows. `og:url` is THIS page; the canonical below is the page
|
|
197
|
+
* a crawler should keep, and on an old version those are deliberately not the
|
|
198
|
+
* same URL.
|
|
199
|
+
*
|
|
200
|
+
* `robots` is the version half: an older or dead version of a page asks not to
|
|
201
|
+
* be indexed, so a search engine stops offering v0.7.0 where the reader wanted
|
|
202
|
+
* today's docs. An untranslated page is the SAME page under another URL, and
|
|
203
|
+
* indexing it once per locale is duplicate content.
|
|
204
|
+
*/
|
|
205
|
+
useSeoMeta({
|
|
206
|
+
title: () => page.value?.title,
|
|
207
|
+
description: () => page.value?.description,
|
|
208
|
+
ogType: 'article',
|
|
209
|
+
ogUrl: () => absolute(localeLink(path.value) ?? path.value),
|
|
210
|
+
robots: () =>
|
|
211
|
+
shouldIndex.value && !untranslated.value ? undefined : 'noindex, follow'
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* THE CANONICAL IS OURS, not the module's.
|
|
216
|
+
*
|
|
217
|
+
* nuxt-seo-utils writes a canonical pointing at the page being rendered, which
|
|
218
|
+
* is right for every site that has one version of a page and wrong for this
|
|
219
|
+
* one: an old version has to point at the current one, or a search engine keeps
|
|
220
|
+
* serving v0.7.0. unhead deduplicates `link[rel=canonical]`, and this call runs
|
|
221
|
+
* after the module's, so this is the tag that ships — asserted over the built
|
|
222
|
+
* HTML by `scripts/check-seo.ts`, because it is a rule that would break
|
|
223
|
+
* silently.
|
|
224
|
+
*/
|
|
225
|
+
useHead(() => ({
|
|
226
|
+
link: [
|
|
227
|
+
{
|
|
228
|
+
rel: 'canonical',
|
|
229
|
+
href: absolute(localeLink(preferredPath.value) ?? preferredPath.value)
|
|
230
|
+
}
|
|
231
|
+
]
|
|
232
|
+
}));
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The page, as schema.org — a `TechArticle` and the trail that reaches it.
|
|
236
|
+
*
|
|
237
|
+
* This used to be a hand-written `@graph` in a script tag. nuxt-schema-org
|
|
238
|
+
* keeps one graph per document and resolves the references inside it, so the
|
|
239
|
+
* `WebSite` and `Organization` declared once in `app.vue` are what these nodes
|
|
240
|
+
* hang off, rather than a second copy of the site inlined per page.
|
|
241
|
+
*
|
|
242
|
+
* The trail comes from the same composable the breadcrumb draws, so the two
|
|
243
|
+
* cannot disagree.
|
|
244
|
+
*/
|
|
245
|
+
useSchemaOrg([
|
|
246
|
+
defineArticle({
|
|
247
|
+
'@type': 'TechArticle',
|
|
248
|
+
headline: () => page.value?.title,
|
|
249
|
+
description: () => page.value?.description,
|
|
250
|
+
inLanguage: () => locale.value
|
|
251
|
+
}),
|
|
252
|
+
/**
|
|
253
|
+
* The WHOLE NODE is the computed, not the field inside it.
|
|
254
|
+
*
|
|
255
|
+
* `DeepResolvableProperties` makes a string field resolvable but maps an
|
|
256
|
+
* array by its own keys, so `itemListElement` takes a plain array and neither
|
|
257
|
+
* a ref nor a getter. Wrapping the definer instead keeps the trail reactive
|
|
258
|
+
* across a client-side navigation, which is the only reason it has to be.
|
|
259
|
+
*/
|
|
260
|
+
computed(() =>
|
|
261
|
+
defineBreadcrumb({
|
|
262
|
+
itemListElement: trail.value.map((item) => ({
|
|
263
|
+
name: item.title,
|
|
264
|
+
item: absolute(localeLink(item.path ?? '/') ?? '/')
|
|
265
|
+
}))
|
|
266
|
+
})
|
|
267
|
+
)
|
|
268
|
+
]);
|
|
269
|
+
</script>
|
|
270
|
+
|
|
271
|
+
<template>
|
|
272
|
+
<!-- The type's own layout owns the page: no reading width and no header of
|
|
273
|
+
ours — see `owned`. The CONTENTS COLUMN is drawn here rather than left to
|
|
274
|
+
the layout, because only the page holds the body it is read from. -->
|
|
275
|
+
<div v-if="owned" class="flex min-w-0 flex-1 gap-10">
|
|
276
|
+
<div class="min-w-0 flex-1 py-8">
|
|
277
|
+
<DuxtVersionBanner />
|
|
278
|
+
|
|
279
|
+
<DuxtTranslationBanner v-if="untranslated" :from="found?.from" />
|
|
280
|
+
|
|
281
|
+
<!-- THE SAME HEADER A WRITTEN PAGE GETS, for a type that draws no title
|
|
282
|
+
of its own. A release page has a title, a description and a trail
|
|
283
|
+
like any other, and the only reason it ever drew its own heading was
|
|
284
|
+
that the layout drew none — which left the copy control floating in a
|
|
285
|
+
row above a title it belongs beside. -->
|
|
286
|
+
<!-- As wide as the page below it. The reading measure is the LAYOUT's
|
|
287
|
+
to set — `changelog` caps its own column, `reference` deliberately
|
|
288
|
+
does not — and a header narrower than the parameter tables under it
|
|
289
|
+
draws a rule that stops halfway across the page. -->
|
|
290
|
+
<header v-if="!titled" class="mb-8 border-b pb-8">
|
|
291
|
+
<DuxtBreadcrumb
|
|
292
|
+
v-if="duxt?.breadcrumb !== false"
|
|
293
|
+
:path="path"
|
|
294
|
+
class="mb-3"
|
|
295
|
+
/>
|
|
296
|
+
|
|
297
|
+
<div class="flex items-start justify-between gap-4">
|
|
298
|
+
<h1
|
|
299
|
+
ref="heading"
|
|
300
|
+
tabindex="-1"
|
|
301
|
+
class="text-4xl font-semibold tracking-tight text-balance outline-none"
|
|
302
|
+
>
|
|
303
|
+
{{ page?.title }}
|
|
304
|
+
</h1>
|
|
305
|
+
|
|
306
|
+
<DuxtCopyPage
|
|
307
|
+
class="mt-1"
|
|
308
|
+
:path="path"
|
|
309
|
+
:title="page?.title"
|
|
310
|
+
:rawbody="(page as { rawbody?: string })?.rawbody"
|
|
311
|
+
/>
|
|
312
|
+
</div>
|
|
313
|
+
|
|
314
|
+
<!-- THE DESCRIPTION, unless the body already opens with it — see
|
|
315
|
+
`generatedLead`. A page whose description Content derived from its
|
|
316
|
+
own first paragraph would print that sentence twice, three lines
|
|
317
|
+
apart; one whose type WROTE a description (an API document's
|
|
318
|
+
`summary`, which appears nowhere in its prose) reads like every
|
|
319
|
+
written page instead, with the lead under the title rather than
|
|
320
|
+
below the rule. -->
|
|
321
|
+
<p
|
|
322
|
+
v-if="described"
|
|
323
|
+
class="mt-3 text-lg text-muted-foreground text-pretty"
|
|
324
|
+
>
|
|
325
|
+
{{ page?.description }}
|
|
326
|
+
</p>
|
|
327
|
+
</header>
|
|
328
|
+
|
|
329
|
+
<!-- A type that DOES draw its own title keeps the compact row: where the
|
|
330
|
+
reader is, and what they can do with the page. -->
|
|
331
|
+
<div v-else class="mb-6 flex items-start justify-between gap-4">
|
|
332
|
+
<DuxtBreadcrumb v-if="duxt?.breadcrumb !== false" :path="path" />
|
|
333
|
+
|
|
334
|
+
<DuxtCopyPage
|
|
335
|
+
class="-mt-1 ml-auto"
|
|
336
|
+
:path="path"
|
|
337
|
+
:title="page?.title"
|
|
338
|
+
:rawbody="(page as { rawbody?: string })?.rawbody"
|
|
339
|
+
/>
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<div class="typeset typeset-docs">
|
|
343
|
+
<ContentRenderer v-if="page" :value="page" />
|
|
344
|
+
</div>
|
|
345
|
+
|
|
346
|
+
<!-- The SECTION's own order, which is what a generated section has: the
|
|
347
|
+
releases newest to oldest, the endpoints as the artefact listed them.
|
|
348
|
+
`DuxtPageNav` walks the same navigation the sidebar draws and stops
|
|
349
|
+
at the section's edge, so the links can only be siblings — and a
|
|
350
|
+
release page that ended in whitespace now ends in the release before
|
|
351
|
+
it, which is how a history is read. -->
|
|
352
|
+
<DuxtPageNav :path="path" />
|
|
353
|
+
|
|
354
|
+
<!-- Provenance survives the header, because it is the one part of it that
|
|
355
|
+
is still true: every page of a generated section came out of one
|
|
356
|
+
artefact, and `DuxtPageInfo` already links at that artefact rather
|
|
357
|
+
than at a file named after the URL. It moves into the column beside
|
|
358
|
+
the contents wherever there is one, exactly as in the docs shell. -->
|
|
359
|
+
<!-- As a row, and as wide as the page: with no column to sit in it is a
|
|
360
|
+
footer, and a rule that stopped at the reading measure under a page
|
|
361
|
+
set wider than that ended halfway across. -->
|
|
362
|
+
<DuxtPageInfo v-if="!aside" row :page="page" />
|
|
363
|
+
</div>
|
|
364
|
+
|
|
365
|
+
<div v-if="aside" class="hidden w-56 shrink-0 xl:block">
|
|
366
|
+
<div
|
|
367
|
+
class="sticky top-[var(--duxt-header-offset)] max-h-[calc(100vh-var(--duxt-header-offset)-1.5rem)] overflow-y-auto py-8"
|
|
368
|
+
>
|
|
369
|
+
<DuxtToc :links="generated" />
|
|
370
|
+
|
|
371
|
+
<DuxtPageInfo :page="page" />
|
|
372
|
+
</div>
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
|
|
376
|
+
<div v-else class="flex min-w-0 flex-1 justify-center gap-10">
|
|
377
|
+
<article class="min-w-0 max-w-3xl flex-1 py-8">
|
|
378
|
+
<DuxtVersionBanner />
|
|
379
|
+
|
|
380
|
+
<DuxtTranslationBanner v-if="untranslated" :from="found?.from" />
|
|
381
|
+
|
|
382
|
+
<header class="mb-8 border-b pb-8">
|
|
383
|
+
<DuxtBreadcrumb
|
|
384
|
+
v-if="duxt?.breadcrumb !== false"
|
|
385
|
+
:path="path"
|
|
386
|
+
class="mb-3"
|
|
387
|
+
/>
|
|
388
|
+
<!-- The copy action sits with the title, not under the article: it is
|
|
389
|
+
what a reader does with the page BEFORE reading it, and a control
|
|
390
|
+
for that at the bottom is a control nobody finds. -->
|
|
391
|
+
<div class="flex items-start justify-between gap-4">
|
|
392
|
+
<h1
|
|
393
|
+
ref="heading"
|
|
394
|
+
tabindex="-1"
|
|
395
|
+
class="text-4xl font-semibold tracking-tight text-balance outline-none"
|
|
396
|
+
>
|
|
397
|
+
{{ page?.title }}
|
|
398
|
+
</h1>
|
|
399
|
+
|
|
400
|
+
<DuxtCopyPage
|
|
401
|
+
class="mt-1"
|
|
402
|
+
:path="path"
|
|
403
|
+
:title="page?.title"
|
|
404
|
+
:rawbody="(page as { rawbody?: string })?.rawbody"
|
|
405
|
+
/>
|
|
406
|
+
</div>
|
|
407
|
+
<p
|
|
408
|
+
v-if="page?.description"
|
|
409
|
+
class="mt-3 text-lg text-muted-foreground text-pretty"
|
|
410
|
+
>
|
|
411
|
+
{{ page.description }}
|
|
412
|
+
</p>
|
|
413
|
+
</header>
|
|
414
|
+
|
|
415
|
+
<div class="typeset typeset-docs">
|
|
416
|
+
<ContentRenderer v-if="page" :value="page" />
|
|
417
|
+
</div>
|
|
418
|
+
|
|
419
|
+
<DuxtPageNav :path="path" />
|
|
420
|
+
|
|
421
|
+
<DuxtPageFeedback />
|
|
422
|
+
</article>
|
|
423
|
+
|
|
424
|
+
<!-- A div for the same reason as the sidebar's: DuxtToc's two <nav>s are
|
|
425
|
+
the landmarks, and both are labelled. -->
|
|
426
|
+
<div class="hidden w-56 shrink-0 xl:block">
|
|
427
|
+
<div
|
|
428
|
+
class="sticky top-[var(--duxt-header-offset)] max-h-[calc(100vh-var(--duxt-header-offset)-1.5rem)] overflow-y-auto py-8"
|
|
429
|
+
>
|
|
430
|
+
<DuxtToc :links="page?.body?.toc?.links ?? []" />
|
|
431
|
+
|
|
432
|
+
<!-- Provenance under the contents: where this page came from, when it
|
|
433
|
+
last changed and who wrote it. On the right rather than under the
|
|
434
|
+
article, where it read as an afterthought below the prev/next
|
|
435
|
+
links — this column is already the one answering what a page IS
|
|
436
|
+
rather than what it says. -->
|
|
437
|
+
<DuxtPageInfo :page="page" />
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
</template>
|