@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,194 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Where the page came from, under the table of contents.
|
|
4
|
+
*
|
|
5
|
+
* Not at the foot of the ARTICLE, which is where it sat first and where it read
|
|
6
|
+
* as an afterthought below the prev/next links. "Edit this page", when it last
|
|
7
|
+
* changed and who wrote it are one question — provenance — and the right-hand
|
|
8
|
+
* column is already the one answering what a page IS rather than what it says.
|
|
9
|
+
*
|
|
10
|
+
* Everything here is derived. The `sources` entry carries the repository, the
|
|
11
|
+
* ref and the folder; the page carries its own file name and, where git could
|
|
12
|
+
* be asked, its history.
|
|
13
|
+
*/
|
|
14
|
+
const props = defineProps<{
|
|
15
|
+
/**
|
|
16
|
+
* Lay it out as a ROW rather than a stack.
|
|
17
|
+
*
|
|
18
|
+
* The column form is the one this was written for; the row is what it needs
|
|
19
|
+
* where there is no column to sit in — under the article of a page whose
|
|
20
|
+
* layout fills the right-hand side itself, where a stack of three lines
|
|
21
|
+
* hanging off the left edge under a full-width rule reads as leftovers.
|
|
22
|
+
*/
|
|
23
|
+
row?: boolean;
|
|
24
|
+
page:
|
|
25
|
+
| {
|
|
26
|
+
stem?: string;
|
|
27
|
+
/**
|
|
28
|
+
* The day a dated page names — a release, and nothing else so far.
|
|
29
|
+
* It REPLACES the last commit rather than joining it: every release of
|
|
30
|
+
* a changelog shares one file, so their last commit is the same date on
|
|
31
|
+
* all of them and says nothing about any.
|
|
32
|
+
*/
|
|
33
|
+
date?: string;
|
|
34
|
+
/** The diff a release was cut from, where its heading linked one. */
|
|
35
|
+
compare?: string;
|
|
36
|
+
lastUpdated?: string;
|
|
37
|
+
contributors?: { name: string; commits: number; username?: string }[];
|
|
38
|
+
}
|
|
39
|
+
| null
|
|
40
|
+
| undefined;
|
|
41
|
+
}>();
|
|
42
|
+
|
|
43
|
+
const page = computed(() => props.page);
|
|
44
|
+
|
|
45
|
+
const { source } = useDuxtCollection();
|
|
46
|
+
const { locale, t } = useI18n();
|
|
47
|
+
|
|
48
|
+
const file = computed(() => {
|
|
49
|
+
// A GENERATED SECTION has no file per page: every page in it was split out of
|
|
50
|
+
// one artefact, and `path` is that artefact. Sending the reader to a file
|
|
51
|
+
// named after the URL would be a 404 with an edit form on it.
|
|
52
|
+
if (source.value?.generated) return source.value.path;
|
|
53
|
+
|
|
54
|
+
return page.value?.stem
|
|
55
|
+
? sourceFilePath(
|
|
56
|
+
page.value.stem,
|
|
57
|
+
source.value?.prefix ?? '',
|
|
58
|
+
source.value?.path ?? 'docs'
|
|
59
|
+
)
|
|
60
|
+
: undefined;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const link = computed(() =>
|
|
64
|
+
file.value
|
|
65
|
+
? sourceLink(
|
|
66
|
+
source.value?.repositoryUrl,
|
|
67
|
+
source.value?.ref,
|
|
68
|
+
source.value?.refKind,
|
|
69
|
+
file.value
|
|
70
|
+
)
|
|
71
|
+
: undefined
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The day this page names, where it names one.
|
|
76
|
+
*
|
|
77
|
+
* `timeZone: 'UTC'`, unlike the commit date below it: a release date is a
|
|
78
|
+
* calendar day and not a moment, so read as local time it is the day before for
|
|
79
|
+
* every reader west of Greenwich — and a different day on the server than in
|
|
80
|
+
* the browser, which is a hydration mismatch as well as a wrong date. The
|
|
81
|
+
* commit date IS a moment and is left alone.
|
|
82
|
+
*/
|
|
83
|
+
const released = computed(() => changelogDate(page.value?.date, locale.value));
|
|
84
|
+
|
|
85
|
+
const updated = computed(() => {
|
|
86
|
+
// The page's own date wins. Both lines would be true and only one is useful:
|
|
87
|
+
// a changelog's releases all live in one file, so the commit that last
|
|
88
|
+
// touched it dates every release the same day, which is at best the newest
|
|
89
|
+
// one's and at worst years off.
|
|
90
|
+
if (page.value?.date) return undefined;
|
|
91
|
+
|
|
92
|
+
const value = page.value?.lastUpdated;
|
|
93
|
+
if (!value) return undefined;
|
|
94
|
+
|
|
95
|
+
const date = new Date(value);
|
|
96
|
+
if (Number.isNaN(date.getTime())) return undefined;
|
|
97
|
+
|
|
98
|
+
return new Intl.DateTimeFormat(locale.value, { dateStyle: 'medium' }).format(
|
|
99
|
+
date
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const contributors = computed(() => page.value?.contributors ?? []);
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* What this page lets a reader DO with where it came from.
|
|
107
|
+
*
|
|
108
|
+
* The compare link goes above the edit link, because the two are the same kind
|
|
109
|
+
* of thing pointing at the same repository — where the page came FROM, rather
|
|
110
|
+
* than what it says — and it is the one a reader of a release wants first: the
|
|
111
|
+
* notes are the summary, the diff is what actually shipped.
|
|
112
|
+
*/
|
|
113
|
+
const actions = computed(() => {
|
|
114
|
+
const entries: { icon: string; label: string; to: string; external: true }[] =
|
|
115
|
+
[];
|
|
116
|
+
|
|
117
|
+
// The diff a release links, where its heading linked an absolute one — see
|
|
118
|
+
// the parser. `git-compare` rather than the crossed arrows: at 14 px beside a
|
|
119
|
+
// pencil the arrows read as a knot rather than as two branches.
|
|
120
|
+
if (page.value?.compare) {
|
|
121
|
+
entries.push({
|
|
122
|
+
icon: 'lucide:git-compare',
|
|
123
|
+
label: t('duxt.page.compare'),
|
|
124
|
+
to: page.value.compare,
|
|
125
|
+
external: true
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// A tag has no edit form — see `sourceLink`. The label follows the kind
|
|
130
|
+
// rather than promising a form that answers with a 404.
|
|
131
|
+
if (link.value) {
|
|
132
|
+
entries.push({
|
|
133
|
+
icon: link.value.kind === 'edit' ? 'lucide:pencil' : 'lucide:file-code-2',
|
|
134
|
+
label: t(
|
|
135
|
+
link.value.kind === 'edit' ? 'duxt.page.edit' : 'duxt.page.view'
|
|
136
|
+
),
|
|
137
|
+
to: link.value.url,
|
|
138
|
+
external: true
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return entries;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
/** The dated facts, in the order they are read. */
|
|
146
|
+
const notes = computed(() => {
|
|
147
|
+
const lines: string[] = [];
|
|
148
|
+
|
|
149
|
+
if (released.value) {
|
|
150
|
+
lines.push(t('duxt.page.released', { date: released.value }));
|
|
151
|
+
} else if (updated.value) {
|
|
152
|
+
lines.push(t('duxt.page.lastUpdated', { date: updated.value }));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return lines;
|
|
156
|
+
});
|
|
157
|
+
</script>
|
|
158
|
+
|
|
159
|
+
<template>
|
|
160
|
+
<!-- One block, one spacing rule — see `DuxtMetaList`. The contributors are
|
|
161
|
+
the one part that is neither an action nor a line of text, so they go in
|
|
162
|
+
the slot and are spaced as one more item. -->
|
|
163
|
+
<DuxtMetaList :row="row" :actions="actions" :notes="notes">
|
|
164
|
+
<div v-if="contributors.length" :class="row ? '' : 'space-y-1.5'">
|
|
165
|
+
<p>{{ $t('duxt.page.contributors') }}</p>
|
|
166
|
+
|
|
167
|
+
<ul class="flex flex-wrap items-center gap-x-2 gap-y-1.5">
|
|
168
|
+
<li
|
|
169
|
+
v-for="person in contributors"
|
|
170
|
+
:key="person.name"
|
|
171
|
+
class="flex items-center gap-1.5"
|
|
172
|
+
>
|
|
173
|
+
<img
|
|
174
|
+
v-if="person.username"
|
|
175
|
+
:src="`https://github.com/${person.username}.png?size=40`"
|
|
176
|
+
alt=""
|
|
177
|
+
width="18"
|
|
178
|
+
height="18"
|
|
179
|
+
loading="lazy"
|
|
180
|
+
class="size-[18px] rounded-full border"
|
|
181
|
+
/>
|
|
182
|
+
<span
|
|
183
|
+
v-else
|
|
184
|
+
aria-hidden="true"
|
|
185
|
+
class="flex size-[18px] items-center justify-center rounded-full border bg-muted text-[9px] font-medium"
|
|
186
|
+
>
|
|
187
|
+
{{ person.name.slice(0, 1).toUpperCase() }}
|
|
188
|
+
</span>
|
|
189
|
+
<span>{{ person.name }}</span>
|
|
190
|
+
</li>
|
|
191
|
+
</ul>
|
|
192
|
+
</div>
|
|
193
|
+
</DuxtMetaList>
|
|
194
|
+
</template>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ContentNavigationItem } from '@nuxt/content';
|
|
3
|
+
// Previous and next within the current section only. Content's own
|
|
4
|
+
// surroundings query walks the whole collection in one flat order, so the last
|
|
5
|
+
// page of one section would offer a "next" that lands in another — crossing a
|
|
6
|
+
// section is the section row's job, not a link reading "the next page".
|
|
7
|
+
const props = defineProps<{ path: string }>();
|
|
8
|
+
|
|
9
|
+
const { data: navigation } = await useDuxtNavigation();
|
|
10
|
+
|
|
11
|
+
const { items } = useDuxtSection(navigation);
|
|
12
|
+
const localeLink = useDuxtLink();
|
|
13
|
+
|
|
14
|
+
/** The section's pages in reading order, groups flattened into their children. */
|
|
15
|
+
const pages = computed(() => {
|
|
16
|
+
const flat: ContentNavigationItem[] = [];
|
|
17
|
+
|
|
18
|
+
const walk = (entries: ContentNavigationItem[]) => {
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
if (entry.page !== false) flat.push(entry);
|
|
21
|
+
if (entry.children?.length) walk(entry.children);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
walk(items.value);
|
|
26
|
+
return flat;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const index = computed(() =>
|
|
30
|
+
pages.value.findIndex((page) => page.path === props.path)
|
|
31
|
+
);
|
|
32
|
+
const previous = computed(() =>
|
|
33
|
+
index.value > 0 ? pages.value[index.value - 1] : undefined
|
|
34
|
+
);
|
|
35
|
+
const next = computed(() =>
|
|
36
|
+
index.value >= 0 ? pages.value[index.value + 1] : undefined
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* `[` and `]` move through the section the same way the two links do.
|
|
41
|
+
*
|
|
42
|
+
* Bound here rather than in the layout because this component already knows
|
|
43
|
+
* which pages are adjacent — a second computation of that would be a second
|
|
44
|
+
* chance to disagree with what the links say.
|
|
45
|
+
*/
|
|
46
|
+
const router = useRouter();
|
|
47
|
+
|
|
48
|
+
onDuxtShortcut(
|
|
49
|
+
(event) =>
|
|
50
|
+
(event.key === '[' || event.key === ']') &&
|
|
51
|
+
!event.metaKey &&
|
|
52
|
+
!event.ctrlKey,
|
|
53
|
+
(event) => {
|
|
54
|
+
const target = event.key === '[' ? previous.value : next.value;
|
|
55
|
+
if (target?.path) void router.push(localeLink(target.path)!);
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<template>
|
|
61
|
+
<nav
|
|
62
|
+
v-if="previous || next"
|
|
63
|
+
class="mt-16 grid gap-4 border-t pt-8 sm:grid-cols-2"
|
|
64
|
+
:aria-label="$t('duxt.nav.pagination')"
|
|
65
|
+
>
|
|
66
|
+
<NuxtLink
|
|
67
|
+
v-if="previous"
|
|
68
|
+
:to="localeLink(previous.path)"
|
|
69
|
+
class="flex flex-col gap-1 rounded-lg border p-4 transition-colors hover:bg-accent"
|
|
70
|
+
>
|
|
71
|
+
<span class="flex items-center gap-1 text-xs text-muted-foreground">
|
|
72
|
+
<Icon name="lucide:arrow-left" class="size-3.5" />
|
|
73
|
+
{{ $t('duxt.nav.previous') }}
|
|
74
|
+
</span>
|
|
75
|
+
<span class="font-medium">{{ previous.title }}</span>
|
|
76
|
+
</NuxtLink>
|
|
77
|
+
<span v-else class="hidden sm:block" />
|
|
78
|
+
|
|
79
|
+
<NuxtLink
|
|
80
|
+
v-if="next"
|
|
81
|
+
:to="localeLink(next.path)"
|
|
82
|
+
class="flex flex-col gap-1 rounded-lg border p-4 text-right transition-colors hover:bg-accent"
|
|
83
|
+
>
|
|
84
|
+
<span
|
|
85
|
+
class="flex items-center justify-end gap-1 text-xs text-muted-foreground"
|
|
86
|
+
>
|
|
87
|
+
{{ $t('duxt.nav.next') }}
|
|
88
|
+
<Icon name="lucide:arrow-right" class="size-3.5" />
|
|
89
|
+
</span>
|
|
90
|
+
<span class="font-medium">{{ next.title }}</span>
|
|
91
|
+
</NuxtLink>
|
|
92
|
+
</nav>
|
|
93
|
+
</template>
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The SHAPE of a documentation page, for the moment before the real one is up.
|
|
4
|
+
*
|
|
5
|
+
* The landing page's window frames a second copy of the application, and
|
|
6
|
+
* between the band arriving and that copy booting there was an empty box the
|
|
7
|
+
* height of a page. A spinner would have said "wait"; this says what is coming.
|
|
8
|
+
*
|
|
9
|
+
* DRAWN RATHER THAN PHOTOGRAPHED, and that is the whole point of it. A
|
|
10
|
+
* screenshot has to exist, has to be produced twice for the two themes, and is
|
|
11
|
+
* wrong the day the theme changes — none of which a layer can ask of every site
|
|
12
|
+
* that extends it. This is the theme's own tokens in the theme's own measures,
|
|
13
|
+
* so it is correct in light and dark by construction and needs no asset. A site
|
|
14
|
+
* that would rather show a real picture sets `duxt.landing.preview.src`.
|
|
15
|
+
*
|
|
16
|
+
* SIZED BY ITS BOX, NOT BY THE WINDOW. Every breakpoint here is a container
|
|
17
|
+
* query — `@2xl` where the page says `sm`, `@5xl` where it says `lg`, `@7xl`
|
|
18
|
+
* where it says `xl`, all at the same widths. It has to be: this poster is
|
|
19
|
+
* drawn in the LANDING PAGE's document, so a viewport breakpoint asks how wide
|
|
20
|
+
* the browser is when the question is how wide the window is. Half-width, in a
|
|
21
|
+
* showcase band on a wide screen, that drew the full desktop page — two
|
|
22
|
+
* sidebars, a section row — squeezed into a 30 rem box. The frame underneath
|
|
23
|
+
* has no such problem: an iframe carries a viewport of its own.
|
|
24
|
+
*
|
|
25
|
+
* THE MEASURES ARE THE REAL ONES, not a sketch: `h-14` for the header, `w-56`
|
|
26
|
+
* for each of the two columns, the section row at `lg` where `DuxtSections`
|
|
27
|
+
* appears, the same `gap-8` and `max-w-[90rem]` the docs layout sets. A poster
|
|
28
|
+
* whose columns sit somewhere else than the page's is a poster the reader
|
|
29
|
+
* watches jump when the frame takes over — which is the one thing it exists to
|
|
30
|
+
* prevent.
|
|
31
|
+
*
|
|
32
|
+
* Nothing here is content: no words, no links, no headings. It is `aria-hidden`
|
|
33
|
+
* where it stands, and every bar is a rounded rectangle.
|
|
34
|
+
*
|
|
35
|
+
* THREE SHAPES, because the window points at three kinds of page and one shape
|
|
36
|
+
* fitted only the first. A poster whose layout is not the layout that arrives
|
|
37
|
+
* is worse than a plain box: it says a page is coming and then a different one
|
|
38
|
+
* does, which reads as the frame having loaded the wrong thing.
|
|
39
|
+
*/
|
|
40
|
+
withDefaults(
|
|
41
|
+
defineProps<{
|
|
42
|
+
/**
|
|
43
|
+
* `docs` — a Markdown page: two sidebars, prose, a code card.
|
|
44
|
+
* `api` — an operation list: a run of method-and-path rows.
|
|
45
|
+
* `text` — a plain-text artefact such as `llms.txt`: no chrome at all,
|
|
46
|
+
* because the browser renders one in a bare monospace document.
|
|
47
|
+
*/
|
|
48
|
+
variant?: DuxtSkeletonVariant;
|
|
49
|
+
}>(),
|
|
50
|
+
{ variant: 'docs' }
|
|
51
|
+
);
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<template>
|
|
55
|
+
<!-- A PLAIN-TEXT ARTEFACT. No header, no sidebars, no article measures: the
|
|
56
|
+
browser renders `text/plain` as a bare monospace document, and drawing a
|
|
57
|
+
documentation page in front of one is a promise the frame then breaks.
|
|
58
|
+
Ragged line lengths, because that is what a file looks like. -->
|
|
59
|
+
<div
|
|
60
|
+
v-if="variant === 'text'"
|
|
61
|
+
class="flex h-full w-full flex-col gap-2.5 overflow-hidden bg-background p-4 font-mono"
|
|
62
|
+
>
|
|
63
|
+
<div
|
|
64
|
+
v-for="(width, index) in [
|
|
65
|
+
22, 0, 74, 68, 0, 30, 0, 88, 82, 90, 76, 84, 70, 86, 62, 0, 26, 0, 80,
|
|
66
|
+
72, 88, 66, 84, 78, 90, 58
|
|
67
|
+
]"
|
|
68
|
+
:key="index"
|
|
69
|
+
class="h-2.5 shrink-0 rounded"
|
|
70
|
+
:class="width ? 'bg-muted/50' : ''"
|
|
71
|
+
:style="width ? { width: `${width}%` } : { height: '0.25rem' }"
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div v-else class="flex h-full w-full flex-col bg-background">
|
|
76
|
+
<!-- The header: the wordmark, the navbar row, the version pill and search.
|
|
77
|
+
`h-14` plus its border, as `DuxtHeader` sets it. -->
|
|
78
|
+
<div class="flex h-14 shrink-0 items-center gap-4 border-b px-4 @5xl:px-8">
|
|
79
|
+
<div class="h-4 w-16 rounded bg-muted" />
|
|
80
|
+
|
|
81
|
+
<!-- The navbar row, then a clear gap, then the version pill, the search
|
|
82
|
+
box at its real `w-56` and the three icons. Measured off the page
|
|
83
|
+
this stands in for, not sketched. -->
|
|
84
|
+
<div class="ml-auto hidden items-center gap-6 @2xl:flex">
|
|
85
|
+
<div
|
|
86
|
+
v-for="width in [32, 62, 50, 42]"
|
|
87
|
+
:key="width"
|
|
88
|
+
class="h-3 rounded bg-muted/60"
|
|
89
|
+
:style="{ width: `${width}px` }"
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div class="ml-6 flex items-center gap-6">
|
|
94
|
+
<div class="h-5 w-12 rounded-full bg-muted/60" />
|
|
95
|
+
<div class="h-8 w-56 rounded-md border bg-muted/30 @max-2xl:w-24" />
|
|
96
|
+
<div class="hidden gap-6 @2xl:flex">
|
|
97
|
+
<div
|
|
98
|
+
v-for="icon in 3"
|
|
99
|
+
:key="icon"
|
|
100
|
+
class="size-4 rounded bg-muted/60"
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<!-- The section row. Hidden below `lg`, exactly where the real one is. -->
|
|
107
|
+
<div
|
|
108
|
+
class="hidden h-11 shrink-0 items-center gap-1 border-b px-4 @5xl:flex @5xl:px-8"
|
|
109
|
+
>
|
|
110
|
+
<div
|
|
111
|
+
v-for="(width, index) in [58, 54, 40, 58, 72, 108, 24]"
|
|
112
|
+
:key="width"
|
|
113
|
+
class="flex items-center gap-2 rounded-md px-3 py-1.5"
|
|
114
|
+
:class="index === 0 ? 'bg-accent' : ''"
|
|
115
|
+
>
|
|
116
|
+
<div class="size-3.5 rounded bg-muted/60" />
|
|
117
|
+
<div class="h-3 rounded bg-muted/60" :style="{ width: `${width}px` }" />
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<div
|
|
122
|
+
class="mx-auto flex min-h-0 w-full max-w-[90rem] flex-1 gap-8 px-4 @5xl:px-8"
|
|
123
|
+
>
|
|
124
|
+
<!-- The section's own pages, one line each — a docs sidebar lists a
|
|
125
|
+
branch, not a table of contents. -->
|
|
126
|
+
<div class="hidden w-56 shrink-0 flex-col gap-1 py-8 @5xl:flex">
|
|
127
|
+
<div
|
|
128
|
+
v-for="(width, index) in [64, 56, 76]"
|
|
129
|
+
:key="width"
|
|
130
|
+
class="flex items-center gap-2 rounded-md px-2 py-1.5"
|
|
131
|
+
:class="index === 0 ? 'bg-accent' : ''"
|
|
132
|
+
>
|
|
133
|
+
<div class="size-3.5 rounded bg-muted/60" />
|
|
134
|
+
<div
|
|
135
|
+
class="h-3 rounded bg-muted/60"
|
|
136
|
+
:style="{ width: `${width}px` }"
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<!-- The article: breadcrumb, title with the copy control beside it, the
|
|
142
|
+
lead, the rule under the header, then prose and a code card. -->
|
|
143
|
+
<div class="flex min-w-0 max-w-3xl flex-1 flex-col py-8">
|
|
144
|
+
<div class="h-2.5 w-16 rounded bg-muted/60" />
|
|
145
|
+
|
|
146
|
+
<div class="mt-3 flex items-start justify-between gap-4">
|
|
147
|
+
<div class="h-9 w-56 rounded bg-muted" />
|
|
148
|
+
<div class="h-8 w-36 rounded-md border bg-muted/30" />
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<div class="mt-4 h-3.5 w-4/5 rounded bg-muted/60" />
|
|
152
|
+
|
|
153
|
+
<div class="my-8 h-px w-full bg-border" />
|
|
154
|
+
|
|
155
|
+
<!-- AN OPERATION LIST, for a tag page built from an OpenAPI document:
|
|
156
|
+
one lead line and then a run of bordered rows, each a method pill,
|
|
157
|
+
a path and a summary pushed to the end. Nothing like the prose and
|
|
158
|
+
the code card below it, which is exactly why it is drawn
|
|
159
|
+
separately — the two pages look nothing alike. -->
|
|
160
|
+
<template v-if="variant === 'api'">
|
|
161
|
+
<div class="h-2.5 w-4/5 rounded bg-muted/60" />
|
|
162
|
+
|
|
163
|
+
<div class="mt-6 flex flex-col gap-2">
|
|
164
|
+
<div
|
|
165
|
+
v-for="(row, index) in [
|
|
166
|
+
{ method: 34, path: 52, summary: 62 },
|
|
167
|
+
{ method: 38, path: 52, summary: 68 },
|
|
168
|
+
{ method: 34, path: 88, summary: 60 },
|
|
169
|
+
{ method: 44, path: 88, summary: 66 },
|
|
170
|
+
{ method: 46, path: 88, summary: 64 }
|
|
171
|
+
]"
|
|
172
|
+
:key="index"
|
|
173
|
+
class="flex items-center gap-3 rounded-lg border px-3 py-2.5"
|
|
174
|
+
>
|
|
175
|
+
<div
|
|
176
|
+
class="h-5 shrink-0 rounded bg-muted"
|
|
177
|
+
:style="{ width: `${row.method}px` }"
|
|
178
|
+
/>
|
|
179
|
+
<div
|
|
180
|
+
class="h-2.5 rounded bg-muted/60"
|
|
181
|
+
:style="{ width: `${row.path}px` }"
|
|
182
|
+
/>
|
|
183
|
+
<div
|
|
184
|
+
class="ml-auto h-2.5 rounded bg-muted/40"
|
|
185
|
+
:style="{ width: `${row.summary}px` }"
|
|
186
|
+
/>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</template>
|
|
190
|
+
|
|
191
|
+
<template v-else>
|
|
192
|
+
<div class="flex flex-col gap-2.5">
|
|
193
|
+
<div
|
|
194
|
+
v-for="line in ['100%', '88%']"
|
|
195
|
+
:key="line"
|
|
196
|
+
class="h-2.5 rounded bg-muted/60"
|
|
197
|
+
:style="{ width: line }"
|
|
198
|
+
/>
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
<div class="my-6 overflow-hidden rounded-lg border">
|
|
202
|
+
<div class="flex h-11 items-center gap-2 border-b bg-muted/40 px-3">
|
|
203
|
+
<div class="size-3.5 rounded bg-muted/60" />
|
|
204
|
+
<div class="h-2.5 w-20 rounded bg-muted/60" />
|
|
205
|
+
</div>
|
|
206
|
+
<div class="flex flex-col gap-2 p-4">
|
|
207
|
+
<div
|
|
208
|
+
v-for="line in ['58%', '44%', '20%']"
|
|
209
|
+
:key="line"
|
|
210
|
+
class="h-2.5 rounded bg-muted/40"
|
|
211
|
+
:style="{ width: line }"
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
<div class="h-2.5 w-3/5 rounded bg-muted/60" />
|
|
217
|
+
<div class="mt-8 h-5 w-40 rounded bg-muted" />
|
|
218
|
+
</template>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<!-- The contents column: the page's own outline, then the aside's fixed
|
|
222
|
+
links, then the provenance under a rule. -->
|
|
223
|
+
<div class="hidden w-56 shrink-0 flex-col gap-6 py-8 @7xl:flex">
|
|
224
|
+
<div class="flex flex-col gap-2">
|
|
225
|
+
<div class="h-3 w-24 rounded bg-muted" />
|
|
226
|
+
<div class="h-2.5 w-20 rounded bg-muted/60" />
|
|
227
|
+
<div class="h-2.5 w-28 rounded bg-muted/60" />
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<div class="flex flex-col gap-2">
|
|
231
|
+
<div class="h-3 w-20 rounded bg-muted" />
|
|
232
|
+
<div
|
|
233
|
+
v-for="width in [76, 68, 88, 72]"
|
|
234
|
+
:key="width"
|
|
235
|
+
class="flex items-center gap-2"
|
|
236
|
+
>
|
|
237
|
+
<div class="size-3 rounded bg-muted/60" />
|
|
238
|
+
<div
|
|
239
|
+
class="h-2.5 rounded bg-muted/60"
|
|
240
|
+
:style="{ width: `${width}px` }"
|
|
241
|
+
/>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
<!-- Provenance under its rule: the edit link, when it last changed,
|
|
246
|
+
and who wrote it beside their avatar. -->
|
|
247
|
+
<div class="flex flex-col gap-3 border-t pt-4">
|
|
248
|
+
<div class="flex items-center gap-2">
|
|
249
|
+
<div class="size-3 rounded bg-muted/60" />
|
|
250
|
+
<div class="h-2.5 w-20 rounded bg-muted/60" />
|
|
251
|
+
</div>
|
|
252
|
+
<div class="h-2.5 w-32 rounded bg-muted/40" />
|
|
253
|
+
<div class="h-2.5 w-16 rounded bg-muted/40" />
|
|
254
|
+
<div class="flex items-center gap-2">
|
|
255
|
+
<div class="size-4 rounded-full bg-muted/60" />
|
|
256
|
+
<div class="h-2.5 w-16 rounded bg-muted/40" />
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
</template>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* How far down the page the reader is.
|
|
4
|
+
*
|
|
5
|
+
* A two-pixel rule under the header rather than a number: on a long reference
|
|
6
|
+
* page the useful question is "am I near the end", and a percentage answers it
|
|
7
|
+
* worse than a line does. `aria-hidden`, because it says nothing a screen
|
|
8
|
+
* reader cannot already ask the document.
|
|
9
|
+
*
|
|
10
|
+
* Measured against the document, not against a scroll container, so it is
|
|
11
|
+
* correct on the docs layout and on the landing page alike. Passive listener:
|
|
12
|
+
* this must never be the thing that makes scrolling stutter.
|
|
13
|
+
*/
|
|
14
|
+
const progress = ref(0);
|
|
15
|
+
|
|
16
|
+
function measure() {
|
|
17
|
+
const doc = document.documentElement;
|
|
18
|
+
const scrollable = doc.scrollHeight - doc.clientHeight;
|
|
19
|
+
|
|
20
|
+
progress.value = scrollable > 0 ? (doc.scrollTop / scrollable) * 100 : 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
onMounted(() => {
|
|
24
|
+
measure();
|
|
25
|
+
window.addEventListener('scroll', measure, { passive: true });
|
|
26
|
+
window.addEventListener('resize', measure, { passive: true });
|
|
27
|
+
|
|
28
|
+
onBeforeUnmount(() => {
|
|
29
|
+
window.removeEventListener('scroll', measure);
|
|
30
|
+
window.removeEventListener('resize', measure);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<template>
|
|
36
|
+
<div
|
|
37
|
+
aria-hidden="true"
|
|
38
|
+
class="pointer-events-none fixed inset-x-0 top-0 z-50 h-0.5"
|
|
39
|
+
>
|
|
40
|
+
<div
|
|
41
|
+
class="h-full bg-primary transition-[width] duration-75"
|
|
42
|
+
:style="{ width: `${progress}%` }"
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
</template>
|