@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,62 @@
|
|
|
1
|
+
import type { VariantProps } from 'class-variance-authority';
|
|
2
|
+
import type { HTMLAttributes } from 'vue';
|
|
3
|
+
import { cva } from 'class-variance-authority';
|
|
4
|
+
|
|
5
|
+
export interface SidebarProps {
|
|
6
|
+
side?: 'left' | 'right';
|
|
7
|
+
variant?: 'sidebar' | 'floating' | 'inset';
|
|
8
|
+
collapsible?: 'offcanvas' | 'icon' | 'none';
|
|
9
|
+
class?: HTMLAttributes['class'];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { default as Sidebar } from './Sidebar.vue';
|
|
13
|
+
export { default as SidebarContent } from './SidebarContent.vue';
|
|
14
|
+
export { default as SidebarFooter } from './SidebarFooter.vue';
|
|
15
|
+
export { default as SidebarGroup } from './SidebarGroup.vue';
|
|
16
|
+
export { default as SidebarGroupAction } from './SidebarGroupAction.vue';
|
|
17
|
+
export { default as SidebarGroupContent } from './SidebarGroupContent.vue';
|
|
18
|
+
export { default as SidebarGroupLabel } from './SidebarGroupLabel.vue';
|
|
19
|
+
export { default as SidebarHeader } from './SidebarHeader.vue';
|
|
20
|
+
export { default as SidebarInput } from './SidebarInput.vue';
|
|
21
|
+
export { default as SidebarInset } from './SidebarInset.vue';
|
|
22
|
+
export { default as SidebarMenu } from './SidebarMenu.vue';
|
|
23
|
+
export { default as SidebarMenuAction } from './SidebarMenuAction.vue';
|
|
24
|
+
export { default as SidebarMenuBadge } from './SidebarMenuBadge.vue';
|
|
25
|
+
export { default as SidebarMenuButton } from './SidebarMenuButton.vue';
|
|
26
|
+
export { default as SidebarMenuItem } from './SidebarMenuItem.vue';
|
|
27
|
+
export { default as SidebarMenuSkeleton } from './SidebarMenuSkeleton.vue';
|
|
28
|
+
export { default as SidebarMenuSub } from './SidebarMenuSub.vue';
|
|
29
|
+
export { default as SidebarMenuSubButton } from './SidebarMenuSubButton.vue';
|
|
30
|
+
export { default as SidebarMenuSubItem } from './SidebarMenuSubItem.vue';
|
|
31
|
+
export { default as SidebarProvider } from './SidebarProvider.vue';
|
|
32
|
+
export { default as SidebarRail } from './SidebarRail.vue';
|
|
33
|
+
export { default as SidebarSeparator } from './SidebarSeparator.vue';
|
|
34
|
+
export { default as SidebarTrigger } from './SidebarTrigger.vue';
|
|
35
|
+
|
|
36
|
+
export { useSidebar } from './utils';
|
|
37
|
+
|
|
38
|
+
export const sidebarMenuButtonVariants = cva(
|
|
39
|
+
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
|
|
40
|
+
{
|
|
41
|
+
variants: {
|
|
42
|
+
variant: {
|
|
43
|
+
default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
|
|
44
|
+
outline:
|
|
45
|
+
'bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]'
|
|
46
|
+
},
|
|
47
|
+
size: {
|
|
48
|
+
default: 'h-8 text-sm',
|
|
49
|
+
sm: 'h-7 text-xs',
|
|
50
|
+
lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!'
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
defaultVariants: {
|
|
54
|
+
variant: 'default',
|
|
55
|
+
size: 'default'
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
export type SidebarMenuButtonVariants = VariantProps<
|
|
61
|
+
typeof sidebarMenuButtonVariants
|
|
62
|
+
>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ComputedRef, Ref } from 'vue';
|
|
2
|
+
import { createContext } from 'reka-ui';
|
|
3
|
+
|
|
4
|
+
export const SIDEBAR_COOKIE_NAME = 'sidebar_state';
|
|
5
|
+
export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
|
6
|
+
export const SIDEBAR_WIDTH = '16rem';
|
|
7
|
+
export const SIDEBAR_WIDTH_MOBILE = '18rem';
|
|
8
|
+
export const SIDEBAR_WIDTH_ICON = '3rem';
|
|
9
|
+
export const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
|
|
10
|
+
|
|
11
|
+
export const [useSidebar, provideSidebarContext] = createContext<{
|
|
12
|
+
state: ComputedRef<'expanded' | 'collapsed'>;
|
|
13
|
+
open: Ref<boolean>;
|
|
14
|
+
setOpen: (value: boolean) => void;
|
|
15
|
+
isMobile: Ref<boolean>;
|
|
16
|
+
openMobile: Ref<boolean>;
|
|
17
|
+
setOpenMobile: (value: boolean) => void;
|
|
18
|
+
toggleSidebar: () => void;
|
|
19
|
+
}>('Sidebar');
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue';
|
|
3
|
+
import { cn } from '@duxt/lib/utils';
|
|
4
|
+
|
|
5
|
+
interface SkeletonProps {
|
|
6
|
+
class?: HTMLAttributes['class'];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const props = defineProps<SkeletonProps>();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<div
|
|
14
|
+
data-slot="skeleton"
|
|
15
|
+
:class="cn('animate-pulse rounded-md bg-primary/10', props.class)"
|
|
16
|
+
/>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Skeleton } from './Skeleton.vue';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { ToasterProps } from 'vue-sonner';
|
|
3
|
+
import { Toaster as Sonner } from 'vue-sonner';
|
|
4
|
+
|
|
5
|
+
// Sonner needs to be told which theme it is in — it does not read the class
|
|
6
|
+
// itself. @nuxtjs/color-mode already tracks that, so the two stay in step.
|
|
7
|
+
const colorMode = useColorMode();
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
theme: _theme,
|
|
11
|
+
closeButton = true,
|
|
12
|
+
duration = 4000,
|
|
13
|
+
position = 'bottom-right',
|
|
14
|
+
...forwarded
|
|
15
|
+
} = defineProps<ToasterProps>();
|
|
16
|
+
|
|
17
|
+
const theme = computed(() => (colorMode.value === 'dark' ? 'dark' : 'light'));
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<Sonner
|
|
22
|
+
class="toaster group"
|
|
23
|
+
:theme="theme"
|
|
24
|
+
:close-button="closeButton"
|
|
25
|
+
:duration="duration"
|
|
26
|
+
:position="position"
|
|
27
|
+
v-bind="forwarded"
|
|
28
|
+
:toast-options="{
|
|
29
|
+
classes: {
|
|
30
|
+
toast:
|
|
31
|
+
'group toast group-[.toaster]:bg-popover group-[.toaster]:text-popover-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg group-[.toaster]:rounded-lg group-[.toaster]:px-4 group-[.toaster]:py-3 group-[.toaster]:gap-3',
|
|
32
|
+
icon: 'self-start mt-0.5',
|
|
33
|
+
content: 'grid gap-0.5',
|
|
34
|
+
title: 'font-medium leading-tight text-foreground',
|
|
35
|
+
description:
|
|
36
|
+
'text-sm leading-relaxed group-[.toast]:text-muted-foreground',
|
|
37
|
+
closeButton:
|
|
38
|
+
'group-[.toast]:border-border group-[.toast]:bg-popover group-[.toast]:text-muted-foreground hover:group-[.toast]:text-foreground',
|
|
39
|
+
actionButton:
|
|
40
|
+
'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
|
|
41
|
+
cancelButton:
|
|
42
|
+
'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground'
|
|
43
|
+
}
|
|
44
|
+
}"
|
|
45
|
+
>
|
|
46
|
+
<!-- Icons come from the layer's own icon component and take the semantic
|
|
47
|
+
status tokens, so they follow the theme instead of a fixed palette. -->
|
|
48
|
+
<template #success-icon>
|
|
49
|
+
<Icon name="lucide:circle-check" class="size-4 text-success" />
|
|
50
|
+
</template>
|
|
51
|
+
<template #info-icon>
|
|
52
|
+
<Icon name="lucide:info" class="size-4 text-info" />
|
|
53
|
+
</template>
|
|
54
|
+
<template #warning-icon>
|
|
55
|
+
<Icon name="lucide:triangle-alert" class="size-4 text-warning" />
|
|
56
|
+
</template>
|
|
57
|
+
<template #error-icon>
|
|
58
|
+
<Icon name="lucide:circle-x" class="size-4 text-destructive" />
|
|
59
|
+
</template>
|
|
60
|
+
<template #loading-icon>
|
|
61
|
+
<Icon
|
|
62
|
+
name="lucide:loader-circle"
|
|
63
|
+
class="size-4 animate-spin text-muted-foreground"
|
|
64
|
+
/>
|
|
65
|
+
</template>
|
|
66
|
+
</Sonner>
|
|
67
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Toaster } from './Sonner.vue';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { TabsRootEmits, TabsRootProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { TabsRoot, useForwardPropsEmits } from 'reka-ui';
|
|
6
|
+
import { cn } from '@duxt/lib/utils';
|
|
7
|
+
|
|
8
|
+
const props = defineProps<
|
|
9
|
+
TabsRootProps & { class?: HTMLAttributes['class'] }
|
|
10
|
+
>();
|
|
11
|
+
const emits = defineEmits<TabsRootEmits>();
|
|
12
|
+
|
|
13
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
14
|
+
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<TabsRoot
|
|
19
|
+
v-slot="slotProps"
|
|
20
|
+
data-slot="tabs"
|
|
21
|
+
v-bind="forwarded"
|
|
22
|
+
:class="cn('flex flex-col gap-2', props.class)"
|
|
23
|
+
>
|
|
24
|
+
<slot v-bind="slotProps" />
|
|
25
|
+
</TabsRoot>
|
|
26
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { TabsContentProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { TabsContent } from 'reka-ui';
|
|
6
|
+
import { cn } from '@duxt/lib/utils';
|
|
7
|
+
|
|
8
|
+
const props = defineProps<
|
|
9
|
+
TabsContentProps & { class?: HTMLAttributes['class'] }
|
|
10
|
+
>();
|
|
11
|
+
|
|
12
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<TabsContent
|
|
17
|
+
data-slot="tabs-content"
|
|
18
|
+
:class="cn('flex-1 outline-none', props.class)"
|
|
19
|
+
v-bind="delegatedProps"
|
|
20
|
+
>
|
|
21
|
+
<slot />
|
|
22
|
+
</TabsContent>
|
|
23
|
+
</template>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { TabsListProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { TabsList } from 'reka-ui';
|
|
6
|
+
import { cn } from '@duxt/lib/utils';
|
|
7
|
+
|
|
8
|
+
const props = defineProps<
|
|
9
|
+
TabsListProps & { class?: HTMLAttributes['class'] }
|
|
10
|
+
>();
|
|
11
|
+
|
|
12
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<TabsList
|
|
17
|
+
data-slot="tabs-list"
|
|
18
|
+
v-bind="delegatedProps"
|
|
19
|
+
:class="
|
|
20
|
+
cn(
|
|
21
|
+
'bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-0.75',
|
|
22
|
+
props.class
|
|
23
|
+
)
|
|
24
|
+
"
|
|
25
|
+
>
|
|
26
|
+
<slot />
|
|
27
|
+
</TabsList>
|
|
28
|
+
</template>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { TabsTriggerProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import { TabsTrigger, useForwardProps } from 'reka-ui';
|
|
6
|
+
import { cn } from '@duxt/lib/utils';
|
|
7
|
+
|
|
8
|
+
const props = defineProps<
|
|
9
|
+
TabsTriggerProps & { class?: HTMLAttributes['class'] }
|
|
10
|
+
>();
|
|
11
|
+
|
|
12
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
13
|
+
|
|
14
|
+
const forwardedProps = useForwardProps(delegatedProps);
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<TabsTrigger
|
|
19
|
+
data-slot="tabs-trigger"
|
|
20
|
+
:class="
|
|
21
|
+
cn(
|
|
22
|
+
`data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-3 focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4`,
|
|
23
|
+
props.class
|
|
24
|
+
)
|
|
25
|
+
"
|
|
26
|
+
v-bind="forwardedProps"
|
|
27
|
+
>
|
|
28
|
+
<slot />
|
|
29
|
+
</TabsTrigger>
|
|
30
|
+
</template>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue';
|
|
3
|
+
import { useVModel } from '@vueuse/core';
|
|
4
|
+
import { cn } from '@duxt/lib/utils';
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
class?: HTMLAttributes['class'];
|
|
8
|
+
defaultValue?: string | number;
|
|
9
|
+
modelValue?: string | number;
|
|
10
|
+
}>();
|
|
11
|
+
|
|
12
|
+
const emits = defineEmits<{
|
|
13
|
+
(e: 'update:modelValue', payload: string | number): void;
|
|
14
|
+
}>();
|
|
15
|
+
|
|
16
|
+
const modelValue = useVModel(props, 'modelValue', emits, {
|
|
17
|
+
passive: true,
|
|
18
|
+
defaultValue: props.defaultValue
|
|
19
|
+
});
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<textarea
|
|
24
|
+
v-model="modelValue"
|
|
25
|
+
:class="
|
|
26
|
+
cn(
|
|
27
|
+
'flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
|
28
|
+
props.class
|
|
29
|
+
)
|
|
30
|
+
"
|
|
31
|
+
/>
|
|
32
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Textarea } from './Textarea.vue';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { TooltipRootEmits, TooltipRootProps } from 'reka-ui';
|
|
3
|
+
import { TooltipRoot, useForwardPropsEmits } from 'reka-ui';
|
|
4
|
+
|
|
5
|
+
const props = defineProps<TooltipRootProps>();
|
|
6
|
+
const emits = defineEmits<TooltipRootEmits>();
|
|
7
|
+
|
|
8
|
+
const forwarded = useForwardPropsEmits(props, emits);
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<TooltipRoot v-slot="slotProps" data-slot="tooltip" v-bind="forwarded">
|
|
13
|
+
<slot v-bind="slotProps" />
|
|
14
|
+
</TooltipRoot>
|
|
15
|
+
</template>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { TooltipContentEmits, TooltipContentProps } from 'reka-ui';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { reactiveOmit } from '@vueuse/core';
|
|
5
|
+
import {
|
|
6
|
+
TooltipArrow,
|
|
7
|
+
TooltipContent,
|
|
8
|
+
TooltipPortal,
|
|
9
|
+
useForwardPropsEmits
|
|
10
|
+
} from 'reka-ui';
|
|
11
|
+
import { cn } from '@duxt/lib/utils';
|
|
12
|
+
|
|
13
|
+
defineOptions({
|
|
14
|
+
inheritAttrs: false
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(
|
|
18
|
+
defineProps<TooltipContentProps & { class?: HTMLAttributes['class'] }>(),
|
|
19
|
+
{
|
|
20
|
+
sideOffset: 4
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const emits = defineEmits<TooltipContentEmits>();
|
|
25
|
+
|
|
26
|
+
const delegatedProps = reactiveOmit(props, 'class');
|
|
27
|
+
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<TooltipPortal>
|
|
32
|
+
<TooltipContent
|
|
33
|
+
data-slot="tooltip-content"
|
|
34
|
+
v-bind="{ ...forwarded, ...$attrs }"
|
|
35
|
+
:class="
|
|
36
|
+
cn(
|
|
37
|
+
'bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit rounded-md px-3 py-1.5 text-xs text-balance',
|
|
38
|
+
props.class
|
|
39
|
+
)
|
|
40
|
+
"
|
|
41
|
+
>
|
|
42
|
+
<slot />
|
|
43
|
+
|
|
44
|
+
<TooltipArrow
|
|
45
|
+
class="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-xs"
|
|
46
|
+
/>
|
|
47
|
+
</TooltipContent>
|
|
48
|
+
</TooltipPortal>
|
|
49
|
+
</template>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { TooltipProviderProps } from 'reka-ui';
|
|
3
|
+
import { TooltipProvider } from 'reka-ui';
|
|
4
|
+
|
|
5
|
+
const props = withDefaults(defineProps<TooltipProviderProps>(), {
|
|
6
|
+
delayDuration: 0
|
|
7
|
+
});
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<TooltipProvider v-bind="props">
|
|
12
|
+
<slot />
|
|
13
|
+
</TooltipProvider>
|
|
14
|
+
</template>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { TooltipTriggerProps } from 'reka-ui';
|
|
3
|
+
import { TooltipTrigger } from 'reka-ui';
|
|
4
|
+
|
|
5
|
+
const props = defineProps<TooltipTriggerProps>();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<TooltipTrigger data-slot="tooltip-trigger" v-bind="props">
|
|
10
|
+
<slot />
|
|
11
|
+
</TooltipTrigger>
|
|
12
|
+
</template>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The heading currently in view, for marking the table of contents.
|
|
3
|
+
*
|
|
4
|
+
* WHY NOT AN INTERSECTIONOBSERVER, which is the obvious tool and was the first
|
|
5
|
+
* implementation here: it reports crossings, not state, so it can only answer
|
|
6
|
+
* "which heading last entered the band". Three cases have no crossing to
|
|
7
|
+
* report, and in all three the marker stuck to the wrong entry:
|
|
8
|
+
*
|
|
9
|
+
* - the last section on the page, whose heading can never reach a trigger
|
|
10
|
+
* line 30% down the viewport because the document runs out of scroll first;
|
|
11
|
+
* - a section longer than the viewport, where the heading has left the band
|
|
12
|
+
* above and the next has not arrived, so nothing fires for a whole screen
|
|
13
|
+
* of reading;
|
|
14
|
+
* - a jump — a hash on load, a click in this very list — which can skip a
|
|
15
|
+
* heading past the band entirely without ever intersecting it.
|
|
16
|
+
*
|
|
17
|
+
* Reading positions instead answers the question directly: the active heading
|
|
18
|
+
* is the LAST one whose top has passed the trigger line. That is true at every
|
|
19
|
+
* scroll offset rather than only at the moments something crossed, so all three
|
|
20
|
+
* cases fall out of it. The bottom of the document is the one place the rule
|
|
21
|
+
* still needs help — see below.
|
|
22
|
+
*
|
|
23
|
+
* The cost is running on scroll rather than on crossings. It is one
|
|
24
|
+
* `getBoundingClientRect` per heading, coalesced into an animation frame, so at
|
|
25
|
+
* most one pass per painted frame however fast the wheel turns.
|
|
26
|
+
*/
|
|
27
|
+
export function useActiveHeading(ids: Ref<string[]>) {
|
|
28
|
+
const active = ref<string>();
|
|
29
|
+
|
|
30
|
+
/** Where a heading counts as reached: reading position, not the top edge. */
|
|
31
|
+
const TRIGGER = 96;
|
|
32
|
+
|
|
33
|
+
let frame = 0;
|
|
34
|
+
|
|
35
|
+
function measure() {
|
|
36
|
+
frame = 0;
|
|
37
|
+
|
|
38
|
+
const headings = ids.value
|
|
39
|
+
.map((id) => document.getElementById(id))
|
|
40
|
+
.filter((element): element is HTMLElement => Boolean(element));
|
|
41
|
+
|
|
42
|
+
if (!headings.length) {
|
|
43
|
+
active.value = undefined;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// At the end of the document no further scrolling is possible, so the
|
|
48
|
+
// trigger line can no longer reach what is left. Everything still below it
|
|
49
|
+
// would stay unreachable forever; the last heading is the honest answer.
|
|
50
|
+
const atBottom =
|
|
51
|
+
window.innerHeight + window.scrollY >=
|
|
52
|
+
document.documentElement.scrollHeight - 2;
|
|
53
|
+
|
|
54
|
+
if (atBottom) {
|
|
55
|
+
active.value = headings.at(-1)!.id;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let current = headings[0]!;
|
|
60
|
+
for (const heading of headings) {
|
|
61
|
+
if (heading.getBoundingClientRect().top > TRIGGER) break;
|
|
62
|
+
current = heading;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
active.value = current.id;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function schedule() {
|
|
69
|
+
if (frame) return;
|
|
70
|
+
frame = requestAnimationFrame(measure);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
onMounted(() => {
|
|
74
|
+
measure();
|
|
75
|
+
window.addEventListener('scroll', schedule, { passive: true });
|
|
76
|
+
window.addEventListener('resize', schedule, { passive: true });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// A new page's headings exist only after it renders, and the ids change
|
|
80
|
+
// before the DOM does.
|
|
81
|
+
watch(ids, () => nextTick(measure));
|
|
82
|
+
|
|
83
|
+
onBeforeUnmount(() => {
|
|
84
|
+
if (frame) cancelAnimationFrame(frame);
|
|
85
|
+
window.removeEventListener('scroll', schedule);
|
|
86
|
+
window.removeEventListener('resize', schedule);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return active;
|
|
90
|
+
}
|