@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,187 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// `::package-managers{command="add -D @kirchdev/duxt"}` — one command, every
|
|
3
|
+
// manager, in a single box: tabs and copy button sit in the code block's own
|
|
4
|
+
// header rather than floating above a separate card.
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
command: string;
|
|
7
|
+
managers?: DuxtPackageManager[];
|
|
8
|
+
}>();
|
|
9
|
+
|
|
10
|
+
const duxt = useDuxtConfig();
|
|
11
|
+
|
|
12
|
+
// Per block, then the site's configured order, then all four.
|
|
13
|
+
const offered = computed(
|
|
14
|
+
() => props.managers ?? duxt.packageManagers ?? duxtPackageManagers
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A manager that cannot express this command gets NO TAB.
|
|
19
|
+
*
|
|
20
|
+
* Berry has no `outdated` and npm no `patch`, and the nearest relative in each
|
|
21
|
+
* case does a different job — so a tab there would be a control that lies.
|
|
22
|
+
* Dropping it reuses what the block could already do: it may list fewer
|
|
23
|
+
* managers than the reader's cookie covers, and falls back to its own first tab.
|
|
24
|
+
* `modules/validate.ts` reports which page asked, so the omission is not silent.
|
|
25
|
+
*/
|
|
26
|
+
const managers = computed(() =>
|
|
27
|
+
offered.value.filter(
|
|
28
|
+
(manager) => packageCommand(manager, props.command) !== undefined
|
|
29
|
+
)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The mark each manager is drawn with.
|
|
34
|
+
*
|
|
35
|
+
* vscode-icons, because it carries the real colours in the file itself — the
|
|
36
|
+
* same collection every file name and every fence language on this site is
|
|
37
|
+
* drawn from. What stood here before was simple-icons, which is monochrome by
|
|
38
|
+
* design, plus eight hand-set hex values: two per manager, because one cannot
|
|
39
|
+
* serve both themes. That table is gone with it.
|
|
40
|
+
*/
|
|
41
|
+
const managerIcons: Record<DuxtPackageManager, string> = {
|
|
42
|
+
npm: 'vscode-icons:file-type-npm',
|
|
43
|
+
pnpm: 'vscode-icons:file-type-pnpm',
|
|
44
|
+
yarn: 'vscode-icons:file-type-yarn',
|
|
45
|
+
bun: 'vscode-icons:file-type-bun'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* pnpm, and pnpm alone, needs a second spelling.
|
|
50
|
+
*
|
|
51
|
+
* Its mark is orange squares AND WHITE ONES, so half the grid disappears on a
|
|
52
|
+
* white page. vscode-icons ships a `light` variant for exactly that — the white
|
|
53
|
+
* cells drawn `#4e4e4e` instead — and it ships one for no other manager here,
|
|
54
|
+
* which is the collection reaching the same conclusion. Two elements swapped by
|
|
55
|
+
* the theme class, the way the preview poster serves its two screenshots.
|
|
56
|
+
*/
|
|
57
|
+
const managerIconsLight: Partial<Record<DuxtPackageManager, string>> = {
|
|
58
|
+
pnpm: 'vscode-icons:file-type-light-pnpm'
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const commands = computed(() =>
|
|
62
|
+
Object.fromEntries(
|
|
63
|
+
managers.value.map((manager) => [
|
|
64
|
+
manager,
|
|
65
|
+
packageCommand(manager, props.command)!
|
|
66
|
+
])
|
|
67
|
+
)
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const { data: highlighted } = await useAsyncData(
|
|
71
|
+
`package-managers-${props.command}`,
|
|
72
|
+
async () => {
|
|
73
|
+
const entries = await Promise.all(
|
|
74
|
+
Object.entries(commands.value).map(
|
|
75
|
+
async ([manager, command]) =>
|
|
76
|
+
[manager, await highlightCode(command, 'bash')] as const
|
|
77
|
+
)
|
|
78
|
+
);
|
|
79
|
+
return Object.fromEntries(entries);
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Shared across every block on the page and remembered between pages, so a
|
|
84
|
+
// reader picks their manager once for the whole site. The value arrives with
|
|
85
|
+
// the request, so the server renders the right tab and nothing jumps.
|
|
86
|
+
const stored = usePackageManager();
|
|
87
|
+
|
|
88
|
+
// A block can list fewer managers than the reader's choice covers; fall back to
|
|
89
|
+
// its first rather than showing nothing.
|
|
90
|
+
const active = computed<DuxtPackageManager>({
|
|
91
|
+
get: () =>
|
|
92
|
+
managers.value.find((manager) => manager === stored.value) ??
|
|
93
|
+
managers.value[0]!,
|
|
94
|
+
set: (value) => {
|
|
95
|
+
stored.value = value;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
const copied = ref(false);
|
|
99
|
+
const notify = useDuxtToast();
|
|
100
|
+
const { t } = useI18n();
|
|
101
|
+
|
|
102
|
+
async function copy() {
|
|
103
|
+
try {
|
|
104
|
+
await navigator.clipboard.writeText(commands.value[active.value]!);
|
|
105
|
+
copied.value = true;
|
|
106
|
+
notify.success(t('duxt.code.copiedToast'));
|
|
107
|
+
setTimeout(() => (copied.value = false), 2000);
|
|
108
|
+
} catch {
|
|
109
|
+
notify.error(
|
|
110
|
+
'Could not copy',
|
|
111
|
+
'The clipboard is unavailable in this context.'
|
|
112
|
+
);
|
|
113
|
+
// Clipboard is unavailable over plain HTTP; a failed copy stays silent.
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
</script>
|
|
117
|
+
|
|
118
|
+
<template>
|
|
119
|
+
<!-- Nothing to show where NO manager can express the command: `active` has
|
|
120
|
+
no first tab to fall back on, and a card with an empty header reads as a
|
|
121
|
+
broken component rather than as a command nobody can run. The build
|
|
122
|
+
validator names the page. -->
|
|
123
|
+
<div
|
|
124
|
+
v-if="managers.length"
|
|
125
|
+
class="duxt-code my-6 overflow-hidden rounded-lg border bg-card"
|
|
126
|
+
>
|
|
127
|
+
<div
|
|
128
|
+
class="flex min-h-11 items-center gap-1 border-b bg-muted/40 px-2 py-1.5"
|
|
129
|
+
>
|
|
130
|
+
<button
|
|
131
|
+
v-for="manager in managers"
|
|
132
|
+
:key="manager"
|
|
133
|
+
type="button"
|
|
134
|
+
class="flex cursor-pointer items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition-colors"
|
|
135
|
+
:class="
|
|
136
|
+
active === manager
|
|
137
|
+
? 'bg-background text-foreground shadow-sm'
|
|
138
|
+
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
|
139
|
+
"
|
|
140
|
+
@click="active = manager"
|
|
141
|
+
>
|
|
142
|
+
<Icon
|
|
143
|
+
:name="
|
|
144
|
+
managerIconsLight[manager] ??
|
|
145
|
+
managerIcons[manager] ??
|
|
146
|
+
'lucide:terminal'
|
|
147
|
+
"
|
|
148
|
+
class="size-3.5"
|
|
149
|
+
:class="managerIconsLight[manager] ? 'dark:hidden' : ''"
|
|
150
|
+
/>
|
|
151
|
+
<Icon
|
|
152
|
+
v-if="managerIconsLight[manager]"
|
|
153
|
+
:name="managerIcons[manager]"
|
|
154
|
+
class="hidden size-3.5 dark:block"
|
|
155
|
+
/>
|
|
156
|
+
{{ manager }}
|
|
157
|
+
</button>
|
|
158
|
+
|
|
159
|
+
<UiButton
|
|
160
|
+
variant="ghost"
|
|
161
|
+
size="icon"
|
|
162
|
+
class="ml-auto size-7"
|
|
163
|
+
:aria-label="
|
|
164
|
+
copied ? $t('duxt.code.copied') : $t('duxt.code.copyCommand')
|
|
165
|
+
"
|
|
166
|
+
@click="copy"
|
|
167
|
+
>
|
|
168
|
+
<Icon
|
|
169
|
+
:name="copied ? 'lucide:check' : 'lucide:copy'"
|
|
170
|
+
class="size-3.5"
|
|
171
|
+
/>
|
|
172
|
+
</UiButton>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<!-- eslint-disable-next-line vue/no-v-html -- Shiki output, built on the
|
|
176
|
+
server from this component's own prop, never from page content. -->
|
|
177
|
+
<div
|
|
178
|
+
v-if="highlighted?.[active]"
|
|
179
|
+
class="duxt-shell overflow-x-auto px-4 py-3 font-mono text-sm"
|
|
180
|
+
v-html="highlighted[active]"
|
|
181
|
+
/>
|
|
182
|
+
<pre
|
|
183
|
+
v-else
|
|
184
|
+
class="overflow-x-auto px-4 py-3 font-mono text-sm"
|
|
185
|
+
><code>{{ commands[active] }}</code></pre>
|
|
186
|
+
</div>
|
|
187
|
+
</template>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ContentNavigationItem } from '@nuxt/content';
|
|
3
|
+
// `::page-cards` on a section's index page: one card per child, taken from the
|
|
4
|
+
// navigation, so a new page appears here without the index being edited.
|
|
5
|
+
const props = defineProps<{ path?: string }>();
|
|
6
|
+
|
|
7
|
+
const path = useDuxtPath();
|
|
8
|
+
const localeLink = useDuxtLink();
|
|
9
|
+
|
|
10
|
+
const { data: navigation } = await useDuxtNavigation();
|
|
11
|
+
|
|
12
|
+
const base = computed(() => props.path ?? path.value);
|
|
13
|
+
|
|
14
|
+
const items = computed(() => {
|
|
15
|
+
const branch = navigation.value?.find((item) => item.path === base.value);
|
|
16
|
+
return branch?.children?.filter((child) => child.path !== base.value) ?? [];
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/** `icon` comes from frontmatter, which Content types as unknown. */
|
|
20
|
+
const iconOf = (item: ContentNavigationItem) =>
|
|
21
|
+
typeof item.icon === 'string' ? item.icon : undefined;
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<div v-if="items.length" class="not-typeset my-8 grid gap-4 sm:grid-cols-2">
|
|
26
|
+
<NuxtLink
|
|
27
|
+
v-for="item in items"
|
|
28
|
+
:key="item.path"
|
|
29
|
+
:to="localeLink(item.path)"
|
|
30
|
+
>
|
|
31
|
+
<UiCard
|
|
32
|
+
class="h-full transition-colors hover:border-foreground/20 hover:bg-accent/30"
|
|
33
|
+
>
|
|
34
|
+
<UiCardHeader>
|
|
35
|
+
<Icon
|
|
36
|
+
v-if="iconOf(item)"
|
|
37
|
+
:name="iconOf(item)!"
|
|
38
|
+
class="size-5 text-muted-foreground"
|
|
39
|
+
/>
|
|
40
|
+
<UiCardTitle class="text-base">{{ item.title }}</UiCardTitle>
|
|
41
|
+
<UiCardDescription v-if="item.description">{{
|
|
42
|
+
item.description
|
|
43
|
+
}}</UiCardDescription>
|
|
44
|
+
</UiCardHeader>
|
|
45
|
+
</UiCard>
|
|
46
|
+
</NuxtLink>
|
|
47
|
+
</div>
|
|
48
|
+
</template>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { partialsCollection } from '../../../sources-resolve';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `:partial{name="install"}` — a block written once and rendered in every
|
|
6
|
+
* source that needs it.
|
|
7
|
+
*
|
|
8
|
+
* Content ships no include directive, and across repositories that is a gap
|
|
9
|
+
* with no workaround: an install note that must read the same in three projects
|
|
10
|
+
* is copied into three projects and drifts. The partials collection is built
|
|
11
|
+
* from every source's `_partials/` folder, so a block defined in one repository
|
|
12
|
+
* is available to the pages of another.
|
|
13
|
+
*
|
|
14
|
+
* TRANSLATED like the pages around it, and through the same chain: a partials
|
|
15
|
+
* collection per language, tried best first, falling back to the untranslated
|
|
16
|
+
* original. Without that a German page including `:partial{name="install"}`
|
|
17
|
+
* renders an English block in the middle of German prose — which is worse than
|
|
18
|
+
* an untranslated page, because nothing on the page says it happened.
|
|
19
|
+
*
|
|
20
|
+
* A missing partial renders nothing rather than an error. A page whose install
|
|
21
|
+
* note failed to resolve is still a readable page; the build says so instead —
|
|
22
|
+
* see `modules/validate.ts`.
|
|
23
|
+
*/
|
|
24
|
+
const props = defineProps<{ name?: string }>();
|
|
25
|
+
|
|
26
|
+
const { source, fallbacks } = useDuxtCollection();
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The collections to try, best first — the page's own chain, mapped onto the
|
|
30
|
+
* partials collections. The same order, deliberately: a page and the blocks it
|
|
31
|
+
* includes falling back to different languages is how a page ends up half
|
|
32
|
+
* translated.
|
|
33
|
+
*/
|
|
34
|
+
const chain = computed(() =>
|
|
35
|
+
[
|
|
36
|
+
...new Set(
|
|
37
|
+
[source.value, ...fallbacks.value]
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.map((entry) => partialsCollection(entry!))
|
|
40
|
+
)
|
|
41
|
+
].map((name) => name as DuxtCollectionArg)
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const { data: partial } = await useAsyncData(
|
|
45
|
+
() => `duxt-partial-${props.name}`,
|
|
46
|
+
async () => {
|
|
47
|
+
if (!props.name) return null;
|
|
48
|
+
|
|
49
|
+
for (const collection of chain.value) {
|
|
50
|
+
const found = await queryCollection(collection)
|
|
51
|
+
.where('stem', 'LIKE', `%_partials/${props.name}`)
|
|
52
|
+
.first();
|
|
53
|
+
|
|
54
|
+
if (found) return found;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return null;
|
|
58
|
+
},
|
|
59
|
+
{ watch: [() => props.name, chain] }
|
|
60
|
+
);
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<ContentRenderer v-if="partial" :value="partial" />
|
|
65
|
+
</template>
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { TabsContent, TabsList, TabsRoot, TabsTrigger } from 'reka-ui';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `::preview` — a component rendered as the page renders it, with the MDC that
|
|
6
|
+
* produced it one tab away.
|
|
7
|
+
*
|
|
8
|
+
* The documentation of a theme has one problem no prose solves: a page can
|
|
9
|
+
* describe `::callout{type="warning"}`, and the reader still has to build the
|
|
10
|
+
* site to find out what it looks like. This block renders the real component in
|
|
11
|
+
* the real theme — same tokens, same dark mode, same page — so the example is
|
|
12
|
+
* the thing itself rather than a picture of it.
|
|
13
|
+
*
|
|
14
|
+
* ::preview
|
|
15
|
+
* :::callout{type="tip"}
|
|
16
|
+
* Rendered by the page you are reading.
|
|
17
|
+
* :::
|
|
18
|
+
*
|
|
19
|
+
* #code
|
|
20
|
+
* ```mdc
|
|
21
|
+
* ::callout{type="tip"}
|
|
22
|
+
* Rendered by the page you are reading.
|
|
23
|
+
* ::
|
|
24
|
+
* ```
|
|
25
|
+
* ::
|
|
26
|
+
*
|
|
27
|
+
* The source is WRITTEN OUT in the `#code` slot rather than derived from the
|
|
28
|
+
* default slot, and that is a deliberate limit, not a shortcut: by the time this
|
|
29
|
+
* component exists its children are rendered VNodes, and the Markdown that
|
|
30
|
+
* produced them is gone — reconstructing it would mean re-serialising an AST
|
|
31
|
+
* into a syntax it never came from, and getting it subtly wrong on every block
|
|
32
|
+
* that nests. Two tabs and no `#code` is a valid block: the example renders and
|
|
33
|
+
* no source tab appears.
|
|
34
|
+
*/
|
|
35
|
+
const slots = useSlots();
|
|
36
|
+
|
|
37
|
+
const hasCode = computed(() => Boolean(slots.code));
|
|
38
|
+
const active = ref('preview');
|
|
39
|
+
|
|
40
|
+
const { t } = useI18n();
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The fence in `#code`, read off its VNode — the same trick `::code-group`
|
|
44
|
+
* uses on its children. What it yields is the filename or the language, which
|
|
45
|
+
* is what the tab should say and which icon it should carry: a reader picking
|
|
46
|
+
* between an example and its source is picking between a rendered thing and a
|
|
47
|
+
* `.vue`, a `.md` or an `.mdc` file, and the icon says which without a word.
|
|
48
|
+
*/
|
|
49
|
+
const codeMeta = computed(() => {
|
|
50
|
+
const nodes = (slots.code?.() ?? []).flatMap((node) =>
|
|
51
|
+
Array.isArray(node.children) && typeof node.type === 'symbol'
|
|
52
|
+
? (node.children as VNode[])
|
|
53
|
+
: [node]
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const props = (nodes.find((node) => typeof node.type === 'object')?.props ??
|
|
57
|
+
{}) as { filename?: string; language?: string; code?: string };
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
label: props.filename ?? props.language ?? t('duxt.code.source'),
|
|
61
|
+
icon: fileIcon(props.filename ?? props.language ?? 'md'),
|
|
62
|
+
code: props.code
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The copy button lives up here rather than in the fence's own header, because
|
|
68
|
+
* the fence's header is hidden: it would say `mdc` directly under a tab that
|
|
69
|
+
* already says `mdc`, and one card does not need naming twice.
|
|
70
|
+
*/
|
|
71
|
+
const notify = useDuxtToast();
|
|
72
|
+
const copied = ref(false);
|
|
73
|
+
|
|
74
|
+
async function copy() {
|
|
75
|
+
const code = codeMeta.value.code;
|
|
76
|
+
if (!code) return;
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await navigator.clipboard.writeText(code);
|
|
80
|
+
copied.value = true;
|
|
81
|
+
notify.success(t('duxt.code.copiedToast'));
|
|
82
|
+
setTimeout(() => (copied.value = false), 2000);
|
|
83
|
+
} catch {
|
|
84
|
+
notify.error(t('duxt.page.copyFailed'));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const tabs = computed(() => [
|
|
89
|
+
{ value: 'preview', label: t('duxt.code.preview'), icon: 'lucide:eye' },
|
|
90
|
+
{ value: 'code', label: codeMeta.value.label, icon: codeMeta.value.icon }
|
|
91
|
+
]);
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<template>
|
|
95
|
+
<!-- No tabs at all when there is no source: a tab strip with one tab is a
|
|
96
|
+
control that cannot be operated, and reka would still put it in the
|
|
97
|
+
accessibility tree as one. -->
|
|
98
|
+
<div v-if="!hasCode" class="my-6">
|
|
99
|
+
<slot />
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<!-- One card, built like the package-manager block and the code block beside
|
|
103
|
+
it: a header of pills, then the panel. Three blocks on a page that all
|
|
104
|
+
switch between things should not switch in three different shapes.
|
|
105
|
+
|
|
106
|
+
One surface for the whole card, example and source alike: switching
|
|
107
|
+
tabs should change what is in the box, not what the box is made of. -->
|
|
108
|
+
<TabsRoot
|
|
109
|
+
v-else
|
|
110
|
+
v-model="active"
|
|
111
|
+
class="my-6 overflow-hidden rounded-lg border bg-card"
|
|
112
|
+
>
|
|
113
|
+
<!-- The header row is the flex container, and the strip is one child of
|
|
114
|
+
it: a `tablist` may hold tabs and nothing else, and a button inside one
|
|
115
|
+
is an `aria-required-children` failure — latent here only because it
|
|
116
|
+
renders on the source tab, which is not the tab a page loads on. -->
|
|
117
|
+
<div
|
|
118
|
+
class="flex min-h-11 items-center gap-1 border-b bg-muted/40 px-2 py-1.5"
|
|
119
|
+
>
|
|
120
|
+
<TabsList
|
|
121
|
+
class="flex items-center gap-1"
|
|
122
|
+
:aria-label="$t('duxt.code.previewTabs') as string"
|
|
123
|
+
>
|
|
124
|
+
<TabsTrigger
|
|
125
|
+
v-for="tab in tabs"
|
|
126
|
+
:key="tab.value"
|
|
127
|
+
:value="tab.value"
|
|
128
|
+
class="flex cursor-pointer items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm"
|
|
129
|
+
>
|
|
130
|
+
<Icon :name="tab.icon" class="size-3.5" />
|
|
131
|
+
{{ tab.label }}
|
|
132
|
+
</TabsTrigger>
|
|
133
|
+
</TabsList>
|
|
134
|
+
|
|
135
|
+
<!-- Right of the tabs, where every other card on the site puts it. Only
|
|
136
|
+
on the source tab: a copy button beside a rendered example copies
|
|
137
|
+
something the reader cannot see. -->
|
|
138
|
+
<UiButton
|
|
139
|
+
v-if="active === 'code' && codeMeta.code"
|
|
140
|
+
variant="ghost"
|
|
141
|
+
size="icon"
|
|
142
|
+
class="ml-auto size-7"
|
|
143
|
+
:aria-label="copied ? $t('duxt.code.copied') : $t('duxt.code.copy')"
|
|
144
|
+
@click="copy"
|
|
145
|
+
>
|
|
146
|
+
<Icon
|
|
147
|
+
:name="copied ? 'lucide:check' : 'lucide:copy'"
|
|
148
|
+
class="size-3.5"
|
|
149
|
+
/>
|
|
150
|
+
</UiButton>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<TabsContent value="preview" class="outline-none">
|
|
154
|
+
<!-- The example on the card's own surface, its outer margins taken off:
|
|
155
|
+
a callout carries `my-6`, and inside a padded panel that margin is
|
|
156
|
+
the padding a second time. -->
|
|
157
|
+
<div class="px-4 py-6 [&>*:first-child]:mt-0 [&>*:last-child]:mb-0">
|
|
158
|
+
<slot />
|
|
159
|
+
</div>
|
|
160
|
+
</TabsContent>
|
|
161
|
+
|
|
162
|
+
<!-- The fence brought its own card into a card, so its frame, its rounding
|
|
163
|
+
and its own header come off — the tab above already names the language,
|
|
164
|
+
and the copy button moved up there with it. Its surface stays, and it is
|
|
165
|
+
the card's own — so the panel does not change colour between tabs. -->
|
|
166
|
+
<TabsContent
|
|
167
|
+
value="code"
|
|
168
|
+
class="outline-none [&_.duxt-code]:my-0 [&_.duxt-code]:rounded-none [&_.duxt-code]:border-0 [&_.duxt-code-header]:hidden [&_.duxt-code-copy]:hidden"
|
|
169
|
+
>
|
|
170
|
+
<slot name="code" />
|
|
171
|
+
</TabsContent>
|
|
172
|
+
</TabsRoot>
|
|
173
|
+
</template>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* A link inside a page, resolved against the source it was written in.
|
|
4
|
+
*
|
|
5
|
+
* Markdown has no idea it is being served under a prefix. `/getting-started`
|
|
6
|
+
* written in this repository's `docs/` is correct on a site with one source and
|
|
7
|
+
* a 404 on a site with two, where the same page lives at
|
|
8
|
+
* `/duxt/getting-started` — and the author of the Markdown, who may be another
|
|
9
|
+
* repository entirely, cannot know which. Worse across versions: a link written
|
|
10
|
+
* in v0.7.0 must stay inside v0.7.0, or the reader silently changes version
|
|
11
|
+
* mid-sentence.
|
|
12
|
+
*
|
|
13
|
+
* So an absolute internal path is read as relative TO ITS OWN SOURCE, and the
|
|
14
|
+
* prefix is put back here. A path that already carries the prefix is left
|
|
15
|
+
* alone, so a link written the long way round still works.
|
|
16
|
+
*/
|
|
17
|
+
const props = defineProps<{
|
|
18
|
+
href?: string;
|
|
19
|
+
target?: string;
|
|
20
|
+
}>();
|
|
21
|
+
|
|
22
|
+
const { source } = useDuxtCollection();
|
|
23
|
+
const localeLink = useDuxtLink();
|
|
24
|
+
|
|
25
|
+
const external = computed(() =>
|
|
26
|
+
Boolean(props.href && /^(?:[a-z]+:|\/\/)/i.test(props.href))
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const to = computed(() => {
|
|
30
|
+
const href = props.href ?? '';
|
|
31
|
+
if (external.value || !href.startsWith('/')) return href;
|
|
32
|
+
|
|
33
|
+
const prefix = source.value?.prefix ?? '';
|
|
34
|
+
const [path, hash] = href.split('#');
|
|
35
|
+
const resolved =
|
|
36
|
+
prefix && !isInside(path!, prefix) ? `${prefix}${path}` : path!;
|
|
37
|
+
|
|
38
|
+
return `${localeLink(resolved)}${hash ? `#${hash}` : ''}`;
|
|
39
|
+
});
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<a
|
|
44
|
+
v-if="external || !href?.startsWith('/')"
|
|
45
|
+
:href="href"
|
|
46
|
+
:target="target ?? (external ? '_blank' : undefined)"
|
|
47
|
+
:rel="external ? 'noopener' : undefined"
|
|
48
|
+
>
|
|
49
|
+
<slot />
|
|
50
|
+
</a>
|
|
51
|
+
|
|
52
|
+
<NuxtLink v-else :to="to" :target="target">
|
|
53
|
+
<slot />
|
|
54
|
+
</NuxtLink>
|
|
55
|
+
</template>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* THE WHOLE HEADING IS THE LINK, not just the marker beside it.
|
|
4
|
+
*
|
|
5
|
+
* A '#' that appears at -left-5 on hover is a target most readers never find,
|
|
6
|
+
* and no touch device can hover onto it at all. The words are what someone aims
|
|
7
|
+
* at when they want to link to a section.
|
|
8
|
+
*
|
|
9
|
+
* The marker stays, now decorative: it says the heading is anchored. It is
|
|
10
|
+
* aria-hidden so the link is announced by the heading text itself, which also
|
|
11
|
+
* drops the generic "anchor" label a screen reader used to read in its place.
|
|
12
|
+
*
|
|
13
|
+
* `scroll-mt-28` clears the whole sticky stack — the 57px header AND the
|
|
14
|
+
* section row under it — because 20 (80px) only cleared the header and left an
|
|
15
|
+
* anchored heading hiding behind the sections.
|
|
16
|
+
*
|
|
17
|
+
* The explanation sits here rather than in the template because a Prose heading
|
|
18
|
+
* is instantiated once per heading: Vue keeps template comments in the rendered
|
|
19
|
+
* HTML, so a comment there ships to the browser as many times as the page has
|
|
20
|
+
* headings.
|
|
21
|
+
*/
|
|
22
|
+
defineProps<{ id?: string }>();
|
|
23
|
+
|
|
24
|
+
const slots = useSlots();
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A heading that IS a link cannot be wrapped in one — see `headingHasLink`.
|
|
28
|
+
* The marker beside it becomes the anchor instead, and stops being decorative:
|
|
29
|
+
* it is the only thing left to announce, so it takes a name.
|
|
30
|
+
*/
|
|
31
|
+
const nested = computed(() => headingHasLink(slots.default?.()));
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<h2 :id="id" class="group scroll-mt-28">
|
|
36
|
+
<a v-if="id && !nested" :href="`#${id}`" class="duxt-heading-link">
|
|
37
|
+
<span class="duxt-anchor" aria-hidden="true">#</span>
|
|
38
|
+
<slot />
|
|
39
|
+
</a>
|
|
40
|
+
|
|
41
|
+
<template v-else-if="id">
|
|
42
|
+
<a
|
|
43
|
+
:href="`#${id}`"
|
|
44
|
+
class="duxt-anchor duxt-anchor-only"
|
|
45
|
+
:aria-label="$t('duxt.page.anchor')"
|
|
46
|
+
>#</a
|
|
47
|
+
>
|
|
48
|
+
<slot />
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<slot v-else />
|
|
52
|
+
</h2>
|
|
53
|
+
</template>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* THE WHOLE HEADING IS THE LINK, not just the marker beside it.
|
|
4
|
+
*
|
|
5
|
+
* A '#' that appears at -left-5 on hover is a target most readers never find,
|
|
6
|
+
* and no touch device can hover onto it at all. The words are what someone aims
|
|
7
|
+
* at when they want to link to a section.
|
|
8
|
+
*
|
|
9
|
+
* The marker stays, now decorative: it says the heading is anchored. It is
|
|
10
|
+
* aria-hidden so the link is announced by the heading text itself, which also
|
|
11
|
+
* drops the generic "anchor" label a screen reader used to read in its place.
|
|
12
|
+
*
|
|
13
|
+
* `scroll-mt-28` clears the whole sticky stack — the 57px header AND the
|
|
14
|
+
* section row under it — because 20 (80px) only cleared the header and left an
|
|
15
|
+
* anchored heading hiding behind the sections.
|
|
16
|
+
*
|
|
17
|
+
* The explanation sits here rather than in the template because a Prose heading
|
|
18
|
+
* is instantiated once per heading: Vue keeps template comments in the rendered
|
|
19
|
+
* HTML, so a comment there ships to the browser as many times as the page has
|
|
20
|
+
* headings.
|
|
21
|
+
*/
|
|
22
|
+
defineProps<{ id?: string }>();
|
|
23
|
+
|
|
24
|
+
const slots = useSlots();
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A heading that IS a link cannot be wrapped in one — see `headingHasLink`.
|
|
28
|
+
* The marker beside it becomes the anchor instead, and stops being decorative:
|
|
29
|
+
* it is the only thing left to announce, so it takes a name.
|
|
30
|
+
*/
|
|
31
|
+
const nested = computed(() => headingHasLink(slots.default?.()));
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<h3 :id="id" class="group scroll-mt-28">
|
|
36
|
+
<a v-if="id && !nested" :href="`#${id}`" class="duxt-heading-link">
|
|
37
|
+
<span class="duxt-anchor" aria-hidden="true">#</span>
|
|
38
|
+
<slot />
|
|
39
|
+
</a>
|
|
40
|
+
|
|
41
|
+
<template v-else-if="id">
|
|
42
|
+
<a
|
|
43
|
+
:href="`#${id}`"
|
|
44
|
+
class="duxt-anchor duxt-anchor-only"
|
|
45
|
+
:aria-label="$t('duxt.page.anchor')"
|
|
46
|
+
>#</a
|
|
47
|
+
>
|
|
48
|
+
<slot />
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<slot v-else />
|
|
52
|
+
</h3>
|
|
53
|
+
</template>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* THE WHOLE HEADING IS THE LINK, not just the marker beside it.
|
|
4
|
+
*
|
|
5
|
+
* A '#' that appears at -left-5 on hover is a target most readers never find,
|
|
6
|
+
* and no touch device can hover onto it at all. The words are what someone aims
|
|
7
|
+
* at when they want to link to a section.
|
|
8
|
+
*
|
|
9
|
+
* The marker stays, now decorative: it says the heading is anchored. It is
|
|
10
|
+
* aria-hidden so the link is announced by the heading text itself, which also
|
|
11
|
+
* drops the generic "anchor" label a screen reader used to read in its place.
|
|
12
|
+
*
|
|
13
|
+
* `scroll-mt-28` clears the whole sticky stack — the 57px header AND the
|
|
14
|
+
* section row under it — because 20 (80px) only cleared the header and left an
|
|
15
|
+
* anchored heading hiding behind the sections.
|
|
16
|
+
*
|
|
17
|
+
* The explanation sits here rather than in the template because a Prose heading
|
|
18
|
+
* is instantiated once per heading: Vue keeps template comments in the rendered
|
|
19
|
+
* HTML, so a comment there ships to the browser as many times as the page has
|
|
20
|
+
* headings.
|
|
21
|
+
*/
|
|
22
|
+
defineProps<{ id?: string }>();
|
|
23
|
+
|
|
24
|
+
const slots = useSlots();
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A heading that IS a link cannot be wrapped in one — see `headingHasLink`.
|
|
28
|
+
* The marker beside it becomes the anchor instead, and stops being decorative:
|
|
29
|
+
* it is the only thing left to announce, so it takes a name.
|
|
30
|
+
*/
|
|
31
|
+
const nested = computed(() => headingHasLink(slots.default?.()));
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<h4 :id="id" class="group scroll-mt-28">
|
|
36
|
+
<a v-if="id && !nested" :href="`#${id}`" class="duxt-heading-link">
|
|
37
|
+
<span class="duxt-anchor" aria-hidden="true">#</span>
|
|
38
|
+
<slot />
|
|
39
|
+
</a>
|
|
40
|
+
|
|
41
|
+
<template v-else-if="id">
|
|
42
|
+
<a
|
|
43
|
+
:href="`#${id}`"
|
|
44
|
+
class="duxt-anchor duxt-anchor-only"
|
|
45
|
+
:aria-label="$t('duxt.page.anchor')"
|
|
46
|
+
>#</a
|
|
47
|
+
>
|
|
48
|
+
<slot />
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<slot v-else />
|
|
52
|
+
</h4>
|
|
53
|
+
</template>
|