@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,520 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The changelog overview: every release, newest first, filterable by what
|
|
4
|
+
* changed.
|
|
5
|
+
*
|
|
6
|
+
* MDC: `::changelog-releases` on the section's index page, written by
|
|
7
|
+
* `sections-changelog.ts`. What it draws is a TIMELINE rather than a bullet
|
|
8
|
+
* list of links — the two questions a reader arrives at a changelog with are
|
|
9
|
+
* "what is the latest" and "when did X change", and a date and the kinds of
|
|
10
|
+
* change beside each version answer both without opening anything.
|
|
11
|
+
*
|
|
12
|
+
* THE ENTRIES ARE NOT HERE. Each release has a page of its own — that is what
|
|
13
|
+
* the split granularity buys — and repeating its bullets here would put the
|
|
14
|
+
* whole history twice into the search index, `llms-full.txt` and the feed.
|
|
15
|
+
*
|
|
16
|
+
* The FILTER is what the grouping is for. On a project with two hundred
|
|
17
|
+
* releases "show me the ones with breaking changes" is the question, and the
|
|
18
|
+
* chips are built from the names the file itself used, so a changelog written
|
|
19
|
+
* in German filters in German with nothing configured.
|
|
20
|
+
*
|
|
21
|
+
* The whole entry is the link, not the version alone: a version number is a
|
|
22
|
+
* four-character target, and everything beside it — the date, the kinds of
|
|
23
|
+
* change — describes the page that link goes to.
|
|
24
|
+
*/
|
|
25
|
+
const props = withDefaults(
|
|
26
|
+
defineProps<{
|
|
27
|
+
releases?: {
|
|
28
|
+
version: string;
|
|
29
|
+
date?: string;
|
|
30
|
+
to: string;
|
|
31
|
+
groups?: { name: string; count: number }[];
|
|
32
|
+
}[];
|
|
33
|
+
}>(),
|
|
34
|
+
{ releases: () => [] }
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const { locale } = useI18n();
|
|
38
|
+
const localeLink = useDuxtLink();
|
|
39
|
+
|
|
40
|
+
/** Every group name the file used, in the order it first used it. */
|
|
41
|
+
const names = computed(() => {
|
|
42
|
+
const seen: string[] = [];
|
|
43
|
+
|
|
44
|
+
for (const release of props.releases) {
|
|
45
|
+
for (const group of release.groups ?? []) {
|
|
46
|
+
if (!seen.includes(group.name)) seen.push(group.name);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return seen;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/** How many releases carry each name — what the chip counts. */
|
|
54
|
+
const totals = computed(() => {
|
|
55
|
+
const counts: Record<string, number> = {};
|
|
56
|
+
|
|
57
|
+
for (const release of props.releases) {
|
|
58
|
+
for (const name of new Set((release.groups ?? []).map((g) => g.name))) {
|
|
59
|
+
counts[name] = (counts[name] ?? 0) + 1;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return counts;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Nothing selected is EVERYTHING, not nothing.
|
|
68
|
+
*
|
|
69
|
+
* It is also what the server renders: a filter is a reader's choice, so the
|
|
70
|
+
* page has to be complete before anyone makes one — otherwise the crawler, the
|
|
71
|
+
* reader with no JavaScript and the reader whose hydration has not landed yet
|
|
72
|
+
* are each shown a filtered changelog nobody asked for.
|
|
73
|
+
*/
|
|
74
|
+
const selected = ref<string[]>([]);
|
|
75
|
+
|
|
76
|
+
const shown = computed(() =>
|
|
77
|
+
selected.value.length
|
|
78
|
+
? props.releases.filter((release) =>
|
|
79
|
+
(release.groups ?? []).some((group) =>
|
|
80
|
+
selected.value.includes(group.name)
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
: props.releases
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
function toggle(name: string) {
|
|
87
|
+
selected.value = selected.value.includes(name)
|
|
88
|
+
? selected.value.filter((entry) => entry !== name)
|
|
89
|
+
: [...selected.value, name];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The release day, in the reader's language — shared with the meta row a
|
|
94
|
+
* release page draws, so one release cannot be dated two different ways.
|
|
95
|
+
*/
|
|
96
|
+
const formatted = (value?: string) => changelogDate(value, locale.value);
|
|
97
|
+
/** How many groups a row names before it starts counting the rest. */
|
|
98
|
+
const SHOWN_GROUPS = 3;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The whole history in two numbers, over the list rather than in it.
|
|
102
|
+
*
|
|
103
|
+
* The same summary the landing page opens with — a figure set large with a
|
|
104
|
+
* small labelled line under it — because it answers the question the list
|
|
105
|
+
* cannot: how much there IS. A list of six rows is read as six rows whatever
|
|
106
|
+
* they carry, and the sum of what those releases changed is nowhere on the page
|
|
107
|
+
* otherwise.
|
|
108
|
+
*
|
|
109
|
+
* IT COUNTS WHAT IS SHOWN, not what exists, so a filtered page's figures are
|
|
110
|
+
* about the filtered page — with the total kept beside the first one, which is
|
|
111
|
+
* also where the heading's own count used to sit.
|
|
112
|
+
*/
|
|
113
|
+
const stats = computed(() => ({
|
|
114
|
+
releases: shown.value.length,
|
|
115
|
+
total: props.releases.length,
|
|
116
|
+
changes: shown.value.reduce(
|
|
117
|
+
(all, release) =>
|
|
118
|
+
all + (release.groups ?? []).reduce((sum, group) => sum + group.count, 0),
|
|
119
|
+
0
|
|
120
|
+
),
|
|
121
|
+
// What the project actually does, as opposed to how much of it: a history of
|
|
122
|
+
// forty releases that only ever fixes bugs says something a change count
|
|
123
|
+
// cannot. Counted over the names the file used, so it is the file's own
|
|
124
|
+
// vocabulary being counted and not a taxonomy of ours.
|
|
125
|
+
kinds: new Set(
|
|
126
|
+
shown.value.flatMap((release) =>
|
|
127
|
+
(release.groups ?? []).map((group) => group.name)
|
|
128
|
+
)
|
|
129
|
+
).size,
|
|
130
|
+
// The last day something shipped — the one figure here that is not a count,
|
|
131
|
+
// and the one a reader checks first on a project they are considering.
|
|
132
|
+
last: formatted(shown.value[0]?.date)
|
|
133
|
+
}));
|
|
134
|
+
|
|
135
|
+
/** The newest release the file lists — marked wherever the filter puts it. */
|
|
136
|
+
const latest = (release: { to: string }) =>
|
|
137
|
+
release.to === props.releases[0]?.to;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The rows as they are drawn: a release, the groups it NAMES, and how many it
|
|
141
|
+
* leaves.
|
|
142
|
+
*
|
|
143
|
+
* The whole list was the row before this, and on a release that touched
|
|
144
|
+
* everything that was twelve names over two lines — twelve facts on a page
|
|
145
|
+
* whose job is to tell six releases apart. A row that grows with the release it
|
|
146
|
+
* describes also stops the list comparing them: the one release with every
|
|
147
|
+
* section becomes the page.
|
|
148
|
+
*
|
|
149
|
+
* So three, BY COUNT rather than by the order the file wrote them: the
|
|
150
|
+
* shallowest reading of "what was this release" is what it did most of, and
|
|
151
|
+
* release-please's own section order is fixed and means nothing. What is left
|
|
152
|
+
* is counted, never dropped silently.
|
|
153
|
+
*
|
|
154
|
+
* A FILTERED row names what it was filtered by first. Otherwise selecting
|
|
155
|
+
* "Tests" leaves a release matching on a group its row does not mention, which
|
|
156
|
+
* reads as a filter that let the wrong page through.
|
|
157
|
+
*/
|
|
158
|
+
const rows = computed(() =>
|
|
159
|
+
shown.value.map((release) => {
|
|
160
|
+
const groups = [...(release.groups ?? [])].sort((a, b) => {
|
|
161
|
+
const chosen =
|
|
162
|
+
Number(selected.value.includes(b.name)) -
|
|
163
|
+
Number(selected.value.includes(a.name));
|
|
164
|
+
|
|
165
|
+
return chosen || b.count - a.count;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
release,
|
|
170
|
+
named: groups.slice(0, SHOWN_GROUPS),
|
|
171
|
+
rest: Math.max(0, groups.length - SHOWN_GROUPS)
|
|
172
|
+
};
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
</script>
|
|
176
|
+
|
|
177
|
+
<template>
|
|
178
|
+
<div class="not-typeset">
|
|
179
|
+
<!-- The list gets a heading of its own, and the control sits on the same
|
|
180
|
+
line at the other end. Two things follow from that: the timeline stops
|
|
181
|
+
being a slab of versions hanging off the preamble with no name, and the
|
|
182
|
+
filter reads as what acts on the list rather than as something wedged
|
|
183
|
+
between the prose and it.
|
|
184
|
+
|
|
185
|
+
WHAT WAS CHOSEN GOES ON ITS OWN ROW, under the control and above the
|
|
186
|
+
versions. Beside the control it moved the control: every chip added or
|
|
187
|
+
dropped re-laid the row it shared, so the button a reader was aiming at
|
|
188
|
+
slid out from under the pointer between two clicks. On a row of its own
|
|
189
|
+
the header is fixed and only the row that is actually changing moves. -->
|
|
190
|
+
<div class="mt-12 mb-8 space-y-3">
|
|
191
|
+
<div class="flex flex-wrap items-center justify-between gap-x-4 gap-y-3">
|
|
192
|
+
<h2 class="text-lg font-semibold tracking-tight">
|
|
193
|
+
{{ $t('duxt.changelog.history') }}
|
|
194
|
+
</h2>
|
|
195
|
+
|
|
196
|
+
<!-- One kind of change is no choice: the control would offer a single
|
|
197
|
+
item that hides everything else.
|
|
198
|
+
|
|
199
|
+
A MENU rather than a row of chips, and the reason is the fixture: a
|
|
200
|
+
release-please changelog writes twelve section names, and twelve
|
|
201
|
+
coloured chips wrapped over three rows read as noise sitting
|
|
202
|
+
between the prose and the history.
|
|
203
|
+
|
|
204
|
+
THE MENU KEEPS ITS DOTS while the history below sets the name in
|
|
205
|
+
the tone instead, and the split is the rule rather than an
|
|
206
|
+
oversight: a menu item is a control, where a mark beside a label is
|
|
207
|
+
what makes the row hittable and scannable, and the history is
|
|
208
|
+
reading matter, where a mark on every line of every release is
|
|
209
|
+
noise. Same six colours either way. -->
|
|
210
|
+
<div
|
|
211
|
+
v-if="names.length > 1"
|
|
212
|
+
role="group"
|
|
213
|
+
:aria-label="$t('duxt.changelog.filter')"
|
|
214
|
+
>
|
|
215
|
+
<UiDropdownMenu>
|
|
216
|
+
<UiDropdownMenuTrigger as-child>
|
|
217
|
+
<button
|
|
218
|
+
type="button"
|
|
219
|
+
class="inline-flex cursor-pointer items-center gap-2 rounded-md border px-3 py-1.5 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
|
|
220
|
+
>
|
|
221
|
+
<Icon name="lucide:list-filter" class="size-3.5 opacity-60" />
|
|
222
|
+
{{ $t('duxt.changelog.kind') }}
|
|
223
|
+
<span v-if="selected.length" class="tabular-nums text-primary">
|
|
224
|
+
{{ selected.length }}
|
|
225
|
+
</span>
|
|
226
|
+
<Icon name="lucide:chevron-down" class="size-3.5 opacity-60" />
|
|
227
|
+
</button>
|
|
228
|
+
</UiDropdownMenuTrigger>
|
|
229
|
+
|
|
230
|
+
<!-- Capped for the same reason the version menu is: a project with
|
|
231
|
+
twelve kinds is not the ceiling, and reka only stops the list
|
|
232
|
+
at the viewport edge. `end`, because the trigger sits at the
|
|
233
|
+
right edge of the reading column. -->
|
|
234
|
+
<UiDropdownMenuContent
|
|
235
|
+
align="end"
|
|
236
|
+
class="max-h-[min(24rem,var(--reka-dropdown-menu-content-available-height))] w-64 overflow-y-auto"
|
|
237
|
+
>
|
|
238
|
+
<UiDropdownMenuCheckboxItem
|
|
239
|
+
v-for="name in names"
|
|
240
|
+
:key="name"
|
|
241
|
+
:model-value="selected.includes(name)"
|
|
242
|
+
@select="(event: Event) => event.preventDefault()"
|
|
243
|
+
@update:model-value="toggle(name)"
|
|
244
|
+
>
|
|
245
|
+
<span
|
|
246
|
+
aria-hidden="true"
|
|
247
|
+
class="size-1.5 shrink-0 rounded-full"
|
|
248
|
+
:class="changelogTone(name).dot"
|
|
249
|
+
/>
|
|
250
|
+
<span class="truncate">{{ changelogLabel(name) }}</span>
|
|
251
|
+
<span
|
|
252
|
+
class="ml-auto pl-2 text-xs text-muted-foreground tabular-nums"
|
|
253
|
+
>
|
|
254
|
+
{{ totals[name] }}
|
|
255
|
+
</span>
|
|
256
|
+
</UiDropdownMenuCheckboxItem>
|
|
257
|
+
</UiDropdownMenuContent>
|
|
258
|
+
</UiDropdownMenu>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
<!-- Each chip removes its own kind, because "which one did I turn on" is
|
|
263
|
+
answered by the same control that turns it off.
|
|
264
|
+
|
|
265
|
+
ANIMATED FOR THE SAME REASON THE LIST IS: a chip appearing is the
|
|
266
|
+
visible half of a filter being set, and the list below moves at the
|
|
267
|
+
same moment — the two reading as one gesture is the whole point. Same
|
|
268
|
+
timings, same reduced-motion behaviour.
|
|
269
|
+
|
|
270
|
+
NEITHER CHIP CARRIES `transition-colors`, and that is what makes the
|
|
271
|
+
leave visible at all: two transition utilities on one element are two
|
|
272
|
+
declarations of the same property, and which one wins is the order
|
|
273
|
+
Tailwind emitted them in — not the order they are written here. With
|
|
274
|
+
`transition-colors` on the chip the leave ran its 150ms on the colour
|
|
275
|
+
and left opacity and transform untouched, so the chip simply
|
|
276
|
+
vanished. The hover colour is instant now, which on a chip this size
|
|
277
|
+
is not a loss.
|
|
278
|
+
|
|
279
|
+
The row is ALWAYS RENDERED and hidden while empty (`empty:hidden`),
|
|
280
|
+
rather than held behind a `v-if` that would take the leaving chips
|
|
281
|
+
with it before they could leave. During a leave it is no longer
|
|
282
|
+
empty, so it stays laid out until the last chip is gone. -->
|
|
283
|
+
<TransitionGroup
|
|
284
|
+
tag="div"
|
|
285
|
+
role="group"
|
|
286
|
+
:aria-label="$t('duxt.changelog.selected')"
|
|
287
|
+
class="flex flex-wrap items-center gap-2 empty:hidden"
|
|
288
|
+
enter-active-class="transition duration-200 ease-out motion-reduce:transition-none"
|
|
289
|
+
enter-from-class="scale-95 opacity-0"
|
|
290
|
+
leave-active-class="transition duration-150 ease-in motion-reduce:transition-none"
|
|
291
|
+
leave-to-class="scale-95 opacity-0"
|
|
292
|
+
move-class="transition-transform duration-200 ease-out motion-reduce:transition-none"
|
|
293
|
+
>
|
|
294
|
+
<button
|
|
295
|
+
v-for="name in selected"
|
|
296
|
+
:key="name"
|
|
297
|
+
type="button"
|
|
298
|
+
class="inline-flex cursor-pointer items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium ring-1 ring-inset focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
|
|
299
|
+
:class="changelogTone(name).chip"
|
|
300
|
+
@click="toggle(name)"
|
|
301
|
+
>
|
|
302
|
+
<span
|
|
303
|
+
aria-hidden="true"
|
|
304
|
+
class="size-1.5 rounded-full"
|
|
305
|
+
:class="changelogTone(name).dot"
|
|
306
|
+
/>
|
|
307
|
+
{{ changelogLabel(name) }}
|
|
308
|
+
<Icon name="lucide:x" class="size-3 opacity-60" />
|
|
309
|
+
</button>
|
|
310
|
+
|
|
311
|
+
<button
|
|
312
|
+
v-if="selected.length"
|
|
313
|
+
key="reset"
|
|
314
|
+
type="button"
|
|
315
|
+
class="cursor-pointer text-xs text-muted-foreground underline-offset-4 hover:text-foreground hover:underline focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
|
|
316
|
+
@click="selected = []"
|
|
317
|
+
>
|
|
318
|
+
{{ $t('duxt.changelog.reset') }}
|
|
319
|
+
</button>
|
|
320
|
+
</TransitionGroup>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
<!-- WHAT THE LIST CANNOT SAY: how much there is. Two figures set the way
|
|
324
|
+
the landing page sets its own — a number, and a small labelled line
|
|
325
|
+
under it — because six rows are read as six rows whatever they carry.
|
|
326
|
+
|
|
327
|
+
They count what is SHOWN. The total stays beside the first one, which
|
|
328
|
+
is where the heading's own count used to sit.
|
|
329
|
+
|
|
330
|
+
A GRID, NOT A WRAPPING ROW, and it is the one place on this page where
|
|
331
|
+
the column count is written down: four figures over the full width read
|
|
332
|
+
as the header of the list under them, where the same four bunched at
|
|
333
|
+
the left edge read as a caption with a hole beside it. The landing's
|
|
334
|
+
own row wraps and centres instead, for a number of figures a SITE
|
|
335
|
+
chooses; here the four are the layer's and fixed.
|
|
336
|
+
|
|
337
|
+
THE LABEL CARRIES THE ACCENT, the figure does not: `--primary` is the
|
|
338
|
+
one colour a consuming site sets to make the theme its own, and two
|
|
339
|
+
short labelled lines are where it costs nothing and reads as the
|
|
340
|
+
site's. The numbers stay in the text colour — they are read, not
|
|
341
|
+
branded. -->
|
|
342
|
+
<dl
|
|
343
|
+
class="mb-1 grid grid-cols-2 gap-x-6 gap-y-7 border-b pb-5 sm:grid-cols-4"
|
|
344
|
+
>
|
|
345
|
+
<div class="flex flex-col gap-1">
|
|
346
|
+
<dd
|
|
347
|
+
class="text-2xl font-semibold tracking-tight tabular-nums sm:text-3xl"
|
|
348
|
+
>
|
|
349
|
+
{{ stats.releases
|
|
350
|
+
}}<span v-if="selected.length" class="text-muted-foreground">
|
|
351
|
+
 / {{ stats.total }}
|
|
352
|
+
</span>
|
|
353
|
+
</dd>
|
|
354
|
+
<dt class="flex items-center gap-1.5 text-xs font-medium text-primary">
|
|
355
|
+
<Icon name="lucide:tag" class="size-3.5" />
|
|
356
|
+
{{ $t('duxt.changelog.releases') }}
|
|
357
|
+
</dt>
|
|
358
|
+
</div>
|
|
359
|
+
|
|
360
|
+
<div class="flex flex-col gap-1">
|
|
361
|
+
<dd
|
|
362
|
+
class="text-2xl font-semibold tracking-tight tabular-nums sm:text-3xl"
|
|
363
|
+
>
|
|
364
|
+
{{ stats.changes }}
|
|
365
|
+
</dd>
|
|
366
|
+
<dt class="flex items-center gap-1.5 text-xs font-medium text-primary">
|
|
367
|
+
<Icon name="lucide:git-commit-horizontal" class="size-3.5" />
|
|
368
|
+
{{ $t('duxt.changelog.changes') }}
|
|
369
|
+
</dt>
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
<div v-if="stats.kinds" class="flex flex-col gap-1">
|
|
373
|
+
<dd
|
|
374
|
+
class="text-2xl font-semibold tracking-tight tabular-nums sm:text-3xl"
|
|
375
|
+
>
|
|
376
|
+
{{ stats.kinds }}
|
|
377
|
+
</dd>
|
|
378
|
+
<dt class="flex items-center gap-1.5 text-xs font-medium text-primary">
|
|
379
|
+
<Icon name="lucide:list-filter" class="size-3.5" />
|
|
380
|
+
{{ $t('duxt.changelog.kinds') }}
|
|
381
|
+
</dt>
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
<!-- A date rather than a count, and the only one here: it is what a
|
|
385
|
+
reader weighing up a project checks first. Dropped where the file
|
|
386
|
+
dates nothing, rather than printed as a dash. -->
|
|
387
|
+
<div v-if="stats.last" class="flex flex-col gap-1">
|
|
388
|
+
<dd
|
|
389
|
+
class="text-2xl font-semibold tracking-tight tabular-nums sm:text-3xl"
|
|
390
|
+
>
|
|
391
|
+
{{ stats.last }}
|
|
392
|
+
</dd>
|
|
393
|
+
<dt class="flex items-center gap-1.5 text-xs font-medium text-primary">
|
|
394
|
+
<Icon name="lucide:calendar" class="size-3.5" />
|
|
395
|
+
{{ $t('duxt.changelog.last') }}
|
|
396
|
+
</dt>
|
|
397
|
+
</div>
|
|
398
|
+
</dl>
|
|
399
|
+
|
|
400
|
+
<!-- ONE LINE PER RELEASE, in columns: version, date, what it carried. Two
|
|
401
|
+
stacked lines gave every release the height of a paragraph and put the
|
|
402
|
+
versions six rows apart — and a history is read down the left edge, one
|
|
403
|
+
line at a time.
|
|
404
|
+
|
|
405
|
+
A GRID rather than a flex row, because the columns are what makes the
|
|
406
|
+
line readable: every date starts at the same x, so does every summary,
|
|
407
|
+
and the eye compares releases instead of re-finding the fields. The
|
|
408
|
+
badge column stays in the grid even where no release carries one — an
|
|
409
|
+
empty track is what keeps the fifth row's summary under the first's.
|
|
410
|
+
|
|
411
|
+
It COLLAPSES rather than scrolls: under `sm` the wrapper stops being
|
|
412
|
+
`contents`, so version, date and badge sit on one line and the summary
|
|
413
|
+
takes the one below, which is the same row with less room. -->
|
|
414
|
+
<!-- FILTERING MOVES ROWS, so the rows move. A list that jumps from six
|
|
415
|
+
entries to two gives a reader no way to see what happened; the same
|
|
416
|
+
change taken over 200ms is read as a filter narrowing rather than as a
|
|
417
|
+
new page.
|
|
418
|
+
|
|
419
|
+
`TransitionGroup` rather than a motion library: this is a fade, a
|
|
420
|
+
nudge and the FLIP the browser does for `move-class`, and none of it is
|
|
421
|
+
worth a runtime dependency in every site that extends the layer. A
|
|
422
|
+
leaving row is taken out of the flow (`absolute`) so the rows under it
|
|
423
|
+
close the gap while it fades rather than after.
|
|
424
|
+
|
|
425
|
+
`motion-reduce:transition-none` throughout, because a reader who asked
|
|
426
|
+
the system for less motion asked this list too. -->
|
|
427
|
+
<TransitionGroup
|
|
428
|
+
tag="ol"
|
|
429
|
+
class="relative -mx-3"
|
|
430
|
+
enter-active-class="transition duration-200 ease-out motion-reduce:transition-none"
|
|
431
|
+
enter-from-class="translate-y-1 opacity-0"
|
|
432
|
+
leave-active-class="absolute inset-x-0 transition duration-150 ease-in motion-reduce:transition-none"
|
|
433
|
+
leave-to-class="-translate-y-1 opacity-0"
|
|
434
|
+
move-class="transition-transform duration-200 ease-out motion-reduce:transition-none"
|
|
435
|
+
>
|
|
436
|
+
<li
|
|
437
|
+
v-for="{ release, named, rest } in rows"
|
|
438
|
+
:key="release.to"
|
|
439
|
+
class="border-t border-border/60 first:border-t-0"
|
|
440
|
+
>
|
|
441
|
+
<NuxtLink
|
|
442
|
+
:to="localeLink(release.to)"
|
|
443
|
+
class="group grid grid-cols-[1fr_auto] items-baseline gap-x-4 gap-y-1.5 rounded-lg px-3 py-3 transition-colors hover:bg-muted/40 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none sm:grid-cols-[9rem_1fr_auto_auto]"
|
|
444
|
+
>
|
|
445
|
+
<span class="flex items-baseline gap-3 sm:contents">
|
|
446
|
+
<!-- ONE CELL for the version and its badge, so the two stand a
|
|
447
|
+
chip's width apart rather than a column apart: the badge is a
|
|
448
|
+
fact about the version, and a fixed track between them made the
|
|
449
|
+
gap a layout decision instead of a typographic one. The cell
|
|
450
|
+
itself is fixed, which is what still lines the summaries up. -->
|
|
451
|
+
<span
|
|
452
|
+
class="flex items-baseline gap-2 sm:col-start-1 sm:row-start-1"
|
|
453
|
+
>
|
|
454
|
+
<h3
|
|
455
|
+
class="font-semibold tabular-nums transition-colors group-hover:text-primary"
|
|
456
|
+
>
|
|
457
|
+
{{ release.version }}
|
|
458
|
+
</h3>
|
|
459
|
+
|
|
460
|
+
<UiBadge
|
|
461
|
+
v-if="latest(release)"
|
|
462
|
+
class="px-2 py-0 text-[0.6875rem] leading-5"
|
|
463
|
+
>
|
|
464
|
+
{{ $t('duxt.changelog.latest') }}
|
|
465
|
+
</UiBadge>
|
|
466
|
+
</span>
|
|
467
|
+
|
|
468
|
+
<!-- At the far end, beside the arrow. It is the one field a reader
|
|
469
|
+
scans down rather than reads across, and the right edge is
|
|
470
|
+
where a column of dates lines up without competing with the
|
|
471
|
+
version for the left one. -->
|
|
472
|
+
<time
|
|
473
|
+
v-if="release.date"
|
|
474
|
+
:datetime="release.date"
|
|
475
|
+
class="text-xs text-muted-foreground tabular-nums sm:col-start-3 sm:row-start-1"
|
|
476
|
+
>
|
|
477
|
+
{{ formatted(release.date) }}
|
|
478
|
+
</time>
|
|
479
|
+
</span>
|
|
480
|
+
|
|
481
|
+
<p
|
|
482
|
+
class="col-span-2 row-start-2 text-xs text-muted-foreground sm:col-span-1 sm:col-start-2 sm:row-start-1"
|
|
483
|
+
>
|
|
484
|
+
<span v-for="(group, index) in named" :key="group.name">
|
|
485
|
+
<!-- The separator is punctuation, not a word: set fainter than
|
|
486
|
+
either side and given room, so the eye breaks the line into
|
|
487
|
+
three facts instead of reading one run of text. -->
|
|
488
|
+
<span
|
|
489
|
+
v-if="index"
|
|
490
|
+
aria-hidden="true"
|
|
491
|
+
class="px-1 text-muted-foreground/50"
|
|
492
|
+
>
|
|
493
|
+
·
|
|
494
|
+
</span>
|
|
495
|
+
{{ changelogLabel(group.name) }}
|
|
496
|
+
<span class="ml-0.5 font-medium text-foreground tabular-nums">
|
|
497
|
+
{{ group.count }}
|
|
498
|
+
</span>
|
|
499
|
+
</span>
|
|
500
|
+
|
|
501
|
+
<template v-if="rest">
|
|
502
|
+
<span aria-hidden="true" class="px-1 text-muted-foreground/50">
|
|
503
|
+
·
|
|
504
|
+
</span>
|
|
505
|
+
{{ $t('duxt.changelog.more', { count: rest }) }}
|
|
506
|
+
</template>
|
|
507
|
+
</p>
|
|
508
|
+
|
|
509
|
+
<!-- The affordance every card and row of this site uses: the arrow
|
|
510
|
+
that steps forward under the pointer. -->
|
|
511
|
+
<Icon
|
|
512
|
+
name="lucide:arrow-right"
|
|
513
|
+
aria-hidden="true"
|
|
514
|
+
class="col-start-2 row-start-1 size-3.5 shrink-0 self-center text-muted-foreground transition-transform group-hover:translate-x-0.5 sm:col-start-4"
|
|
515
|
+
/>
|
|
516
|
+
</NuxtLink>
|
|
517
|
+
</li>
|
|
518
|
+
</TransitionGroup>
|
|
519
|
+
</div>
|
|
520
|
+
</template>
|