@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.
Files changed (401) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +151 -0
  3. package/app/app.config.ts +13 -0
  4. package/app/app.vue +122 -0
  5. package/app/assets/css/duxt.css +555 -0
  6. package/app/assets/css/typeset.css +498 -0
  7. package/app/components/DuxtBrand.vue +53 -0
  8. package/app/components/DuxtBreadcrumb.vue +37 -0
  9. package/app/components/DuxtCodeBlock.vue +143 -0
  10. package/app/components/DuxtCopyPage.vue +117 -0
  11. package/app/components/DuxtFooter.vue +86 -0
  12. package/app/components/DuxtHeader.vue +496 -0
  13. package/app/components/DuxtJsonEditor.vue +359 -0
  14. package/app/components/DuxtLandingProse.vue +90 -0
  15. package/app/components/DuxtLandingShowcase.vue +295 -0
  16. package/app/components/DuxtLiveWindow.vue +343 -0
  17. package/app/components/DuxtLocale.vue +86 -0
  18. package/app/components/DuxtMetaList.vue +76 -0
  19. package/app/components/DuxtNavigation.vue +94 -0
  20. package/app/components/DuxtNavigationLink.vue +41 -0
  21. package/app/components/DuxtOpenApiCallbacks.vue +92 -0
  22. package/app/components/DuxtOpenApiClient.vue +1339 -0
  23. package/app/components/DuxtOpenApiMedia.vue +108 -0
  24. package/app/components/DuxtOpenApiMethod.vue +59 -0
  25. package/app/components/DuxtOpenApiParameters.vue +114 -0
  26. package/app/components/DuxtOpenApiResponses.vue +109 -0
  27. package/app/components/DuxtOpenApiSchema.vue +365 -0
  28. package/app/components/DuxtOpenApiSecurity.vue +115 -0
  29. package/app/components/DuxtPageFeedback.vue +56 -0
  30. package/app/components/DuxtPageInfo.vue +194 -0
  31. package/app/components/DuxtPageNav.vue +93 -0
  32. package/app/components/DuxtPreviewSkeleton.vue +262 -0
  33. package/app/components/DuxtProgress.vue +45 -0
  34. package/app/components/DuxtSearch.vue +271 -0
  35. package/app/components/DuxtSections.vue +65 -0
  36. package/app/components/DuxtShortcuts.vue +46 -0
  37. package/app/components/DuxtSkipLink.vue +34 -0
  38. package/app/components/DuxtToc.vue +86 -0
  39. package/app/components/DuxtTranslationBanner.vue +64 -0
  40. package/app/components/DuxtVersion.vue +168 -0
  41. package/app/components/DuxtVersionBanner.vue +109 -0
  42. package/app/components/OgImage/Duxt.satori.vue +82 -0
  43. package/app/components/content/Accordion.vue +37 -0
  44. package/app/components/content/AccordionItem.vue +46 -0
  45. package/app/components/content/Callout.vue +58 -0
  46. package/app/components/content/ChangelogGroup.vue +91 -0
  47. package/app/components/content/ChangelogReleases.vue +520 -0
  48. package/app/components/content/CodeGroup.vue +183 -0
  49. package/app/components/content/DevtoolsPanel.vue +107 -0
  50. package/app/components/content/Field.vue +50 -0
  51. package/app/components/content/FieldGroup.vue +8 -0
  52. package/app/components/content/FileTree.vue +92 -0
  53. package/app/components/content/Mermaid.vue +70 -0
  54. package/app/components/content/OpenApiOperation.vue +185 -0
  55. package/app/components/content/OpenApiOperations.vue +72 -0
  56. package/app/components/content/OpenApiOverview.vue +302 -0
  57. package/app/components/content/PackageManagers.vue +187 -0
  58. package/app/components/content/PageCards.vue +48 -0
  59. package/app/components/content/Partial.vue +65 -0
  60. package/app/components/content/Preview.vue +173 -0
  61. package/app/components/content/ProseA.vue +55 -0
  62. package/app/components/content/ProseH2.vue +53 -0
  63. package/app/components/content/ProseH3.vue +53 -0
  64. package/app/components/content/ProseH4.vue +53 -0
  65. package/app/components/content/ProseImg.vue +87 -0
  66. package/app/components/content/ProsePre.vue +58 -0
  67. package/app/components/content/ProseTable.vue +11 -0
  68. package/app/components/content/Since.vue +21 -0
  69. package/app/components/content/Steps.vue +20 -0
  70. package/app/components/content/Tab.vue +23 -0
  71. package/app/components/content/TabGroup.vue +58 -0
  72. package/app/components/ui/alert/Alert.vue +21 -0
  73. package/app/components/ui/alert/AlertDescription.vue +22 -0
  74. package/app/components/ui/alert/AlertTitle.vue +22 -0
  75. package/app/components/ui/alert/index.ts +24 -0
  76. package/app/components/ui/badge/Badge.vue +28 -0
  77. package/app/components/ui/badge/index.ts +52 -0
  78. package/app/components/ui/breadcrumb/Breadcrumb.vue +13 -0
  79. package/app/components/ui/breadcrumb/BreadcrumbEllipsis.vue +22 -0
  80. package/app/components/ui/breadcrumb/BreadcrumbItem.vue +17 -0
  81. package/app/components/ui/breadcrumb/BreadcrumbLink.vue +24 -0
  82. package/app/components/ui/breadcrumb/BreadcrumbList.vue +22 -0
  83. package/app/components/ui/breadcrumb/BreadcrumbPage.vue +20 -0
  84. package/app/components/ui/breadcrumb/BreadcrumbSeparator.vue +21 -0
  85. package/app/components/ui/breadcrumb/index.ts +7 -0
  86. package/app/components/ui/button/Button.vue +31 -0
  87. package/app/components/ui/button/index.ts +39 -0
  88. package/app/components/ui/card/Card.vue +22 -0
  89. package/app/components/ui/card/CardAction.vue +22 -0
  90. package/app/components/ui/card/CardContent.vue +14 -0
  91. package/app/components/ui/card/CardDescription.vue +17 -0
  92. package/app/components/ui/card/CardFooter.vue +17 -0
  93. package/app/components/ui/card/CardHeader.vue +22 -0
  94. package/app/components/ui/card/CardTitle.vue +17 -0
  95. package/app/components/ui/card/index.ts +7 -0
  96. package/app/components/ui/collapsible/Collapsible.vue +19 -0
  97. package/app/components/ui/collapsible/CollapsibleContent.vue +12 -0
  98. package/app/components/ui/collapsible/CollapsibleTrigger.vue +12 -0
  99. package/app/components/ui/collapsible/index.ts +3 -0
  100. package/app/components/ui/command/Command.vue +98 -0
  101. package/app/components/ui/command/CommandDialog.vue +42 -0
  102. package/app/components/ui/command/CommandEmpty.vue +31 -0
  103. package/app/components/ui/command/CommandGroup.vue +52 -0
  104. package/app/components/ui/command/CommandInput.vue +45 -0
  105. package/app/components/ui/command/CommandItem.vue +85 -0
  106. package/app/components/ui/command/CommandList.vue +32 -0
  107. package/app/components/ui/command/CommandSeparator.vue +23 -0
  108. package/app/components/ui/command/CommandShortcut.vue +19 -0
  109. package/app/components/ui/command/index.ts +29 -0
  110. package/app/components/ui/dialog/Dialog.vue +15 -0
  111. package/app/components/ui/dialog/DialogClose.vue +12 -0
  112. package/app/components/ui/dialog/DialogContent.vue +61 -0
  113. package/app/components/ui/dialog/DialogDescription.vue +25 -0
  114. package/app/components/ui/dialog/DialogFooter.vue +30 -0
  115. package/app/components/ui/dialog/DialogHeader.vue +17 -0
  116. package/app/components/ui/dialog/DialogOverlay.vue +28 -0
  117. package/app/components/ui/dialog/DialogScrollContent.vue +65 -0
  118. package/app/components/ui/dialog/DialogTitle.vue +25 -0
  119. package/app/components/ui/dialog/DialogTrigger.vue +12 -0
  120. package/app/components/ui/dialog/index.ts +10 -0
  121. package/app/components/ui/dropdown-menu/DropdownMenu.vue +19 -0
  122. package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +47 -0
  123. package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +47 -0
  124. package/app/components/ui/dropdown-menu/DropdownMenuGroup.vue +12 -0
  125. package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +41 -0
  126. package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +27 -0
  127. package/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +21 -0
  128. package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +48 -0
  129. package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
  130. package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +19 -0
  131. package/app/components/ui/dropdown-menu/DropdownMenuSub.vue +19 -0
  132. package/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue +34 -0
  133. package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +34 -0
  134. package/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue +17 -0
  135. package/app/components/ui/dropdown-menu/index.ts +16 -0
  136. package/app/components/ui/input/Input.vue +35 -0
  137. package/app/components/ui/input/index.ts +1 -0
  138. package/app/components/ui/navigation-menu/NavigationMenu.vue +42 -0
  139. package/app/components/ui/navigation-menu/NavigationMenuContent.vue +35 -0
  140. package/app/components/ui/navigation-menu/NavigationMenuIndicator.vue +32 -0
  141. package/app/components/ui/navigation-menu/NavigationMenuItem.vue +23 -0
  142. package/app/components/ui/navigation-menu/NavigationMenuLink.vue +30 -0
  143. package/app/components/ui/navigation-menu/NavigationMenuList.vue +30 -0
  144. package/app/components/ui/navigation-menu/NavigationMenuTrigger.vue +31 -0
  145. package/app/components/ui/navigation-menu/NavigationMenuViewport.vue +30 -0
  146. package/app/components/ui/navigation-menu/index.ts +14 -0
  147. package/app/components/ui/scroll-area/ScrollArea.vue +31 -0
  148. package/app/components/ui/scroll-area/ScrollBar.vue +38 -0
  149. package/app/components/ui/scroll-area/index.ts +2 -0
  150. package/app/components/ui/select/Select.vue +15 -0
  151. package/app/components/ui/select/SelectContent.vue +59 -0
  152. package/app/components/ui/select/SelectGroup.vue +19 -0
  153. package/app/components/ui/select/SelectItem.vue +42 -0
  154. package/app/components/ui/select/SelectItemText.vue +12 -0
  155. package/app/components/ui/select/SelectLabel.vue +16 -0
  156. package/app/components/ui/select/SelectScrollDownButton.vue +28 -0
  157. package/app/components/ui/select/SelectScrollUpButton.vue +28 -0
  158. package/app/components/ui/select/SelectSeparator.vue +20 -0
  159. package/app/components/ui/select/SelectTrigger.vue +32 -0
  160. package/app/components/ui/select/SelectValue.vue +12 -0
  161. package/app/components/ui/select/index.ts +11 -0
  162. package/app/components/ui/separator/Separator.vue +30 -0
  163. package/app/components/ui/separator/index.ts +1 -0
  164. package/app/components/ui/sheet/Sheet.vue +15 -0
  165. package/app/components/ui/sheet/SheetClose.vue +12 -0
  166. package/app/components/ui/sheet/SheetContent.vue +68 -0
  167. package/app/components/ui/sheet/SheetDescription.vue +23 -0
  168. package/app/components/ui/sheet/SheetFooter.vue +15 -0
  169. package/app/components/ui/sheet/SheetHeader.vue +15 -0
  170. package/app/components/ui/sheet/SheetOverlay.vue +28 -0
  171. package/app/components/ui/sheet/SheetTitle.vue +23 -0
  172. package/app/components/ui/sheet/SheetTrigger.vue +12 -0
  173. package/app/components/ui/sheet/index.ts +8 -0
  174. package/app/components/ui/sidebar/Sidebar.vue +110 -0
  175. package/app/components/ui/sidebar/SidebarContent.vue +23 -0
  176. package/app/components/ui/sidebar/SidebarFooter.vue +18 -0
  177. package/app/components/ui/sidebar/SidebarGroup.vue +18 -0
  178. package/app/components/ui/sidebar/SidebarGroupAction.vue +31 -0
  179. package/app/components/ui/sidebar/SidebarGroupContent.vue +18 -0
  180. package/app/components/ui/sidebar/SidebarGroupLabel.vue +30 -0
  181. package/app/components/ui/sidebar/SidebarHeader.vue +18 -0
  182. package/app/components/ui/sidebar/SidebarInput.vue +19 -0
  183. package/app/components/ui/sidebar/SidebarInset.vue +23 -0
  184. package/app/components/ui/sidebar/SidebarMenu.vue +18 -0
  185. package/app/components/ui/sidebar/SidebarMenuAction.vue +42 -0
  186. package/app/components/ui/sidebar/SidebarMenuBadge.vue +28 -0
  187. package/app/components/ui/sidebar/SidebarMenuButton.vue +60 -0
  188. package/app/components/ui/sidebar/SidebarMenuButtonChild.vue +36 -0
  189. package/app/components/ui/sidebar/SidebarMenuItem.vue +18 -0
  190. package/app/components/ui/sidebar/SidebarMenuSkeleton.vue +35 -0
  191. package/app/components/ui/sidebar/SidebarMenuSub.vue +24 -0
  192. package/app/components/ui/sidebar/SidebarMenuSubButton.vue +43 -0
  193. package/app/components/ui/sidebar/SidebarMenuSubItem.vue +18 -0
  194. package/app/components/ui/sidebar/SidebarProvider.vue +109 -0
  195. package/app/components/ui/sidebar/SidebarRail.vue +35 -0
  196. package/app/components/ui/sidebar/SidebarSeparator.vue +19 -0
  197. package/app/components/ui/sidebar/SidebarTrigger.vue +26 -0
  198. package/app/components/ui/sidebar/index.ts +62 -0
  199. package/app/components/ui/sidebar/utils.ts +19 -0
  200. package/app/components/ui/skeleton/Skeleton.vue +17 -0
  201. package/app/components/ui/skeleton/index.ts +1 -0
  202. package/app/components/ui/sonner/Sonner.vue +67 -0
  203. package/app/components/ui/sonner/index.ts +1 -0
  204. package/app/components/ui/tabs/Tabs.vue +26 -0
  205. package/app/components/ui/tabs/TabsContent.vue +23 -0
  206. package/app/components/ui/tabs/TabsList.vue +28 -0
  207. package/app/components/ui/tabs/TabsTrigger.vue +30 -0
  208. package/app/components/ui/tabs/index.ts +4 -0
  209. package/app/components/ui/textarea/Textarea.vue +32 -0
  210. package/app/components/ui/textarea/index.ts +1 -0
  211. package/app/components/ui/tooltip/Tooltip.vue +15 -0
  212. package/app/components/ui/tooltip/TooltipContent.vue +49 -0
  213. package/app/components/ui/tooltip/TooltipProvider.vue +14 -0
  214. package/app/components/ui/tooltip/TooltipTrigger.vue +12 -0
  215. package/app/components/ui/tooltip/index.ts +4 -0
  216. package/app/composables/useActiveHeading.ts +90 -0
  217. package/app/composables/useDuxtAnimatedHeight.ts +155 -0
  218. package/app/composables/useDuxtBreadcrumb.ts +60 -0
  219. package/app/composables/useDuxtChoice.ts +28 -0
  220. package/app/composables/useDuxtCollection.ts +89 -0
  221. package/app/composables/useDuxtConfig.ts +47 -0
  222. package/app/composables/useDuxtNavigation.ts +87 -0
  223. package/app/composables/useDuxtPageFocus.ts +30 -0
  224. package/app/composables/useDuxtPath.ts +42 -0
  225. package/app/composables/useDuxtSearch.ts +208 -0
  226. package/app/composables/useDuxtSection.ts +44 -0
  227. package/app/composables/useDuxtSectionRow.ts +49 -0
  228. package/app/composables/useDuxtShortcuts.ts +58 -0
  229. package/app/composables/useDuxtSiteUrl.ts +27 -0
  230. package/app/composables/useDuxtToast.ts +25 -0
  231. package/app/composables/useDuxtVersion.ts +138 -0
  232. package/app/composables/useFuzzySearch.ts +83 -0
  233. package/app/composables/usePackageManager.ts +4 -0
  234. package/app/composables/useRecentPages.ts +52 -0
  235. package/app/error.vue +133 -0
  236. package/app/layouts/changelog.vue +85 -0
  237. package/app/layouts/default.vue +23 -0
  238. package/app/layouts/docs.vue +62 -0
  239. package/app/layouts/reference.vue +85 -0
  240. package/app/lib/utils.ts +7 -0
  241. package/app/middleware/duxt-section-layout.global.ts +57 -0
  242. package/app/pages/[...slug].vue +441 -0
  243. package/app/pages/index.vue +479 -0
  244. package/app/types/duxt.d.ts +955 -0
  245. package/app/utils/aside.ts +16 -0
  246. package/app/utils/changelog.ts +182 -0
  247. package/app/utils/code-highlight.ts +116 -0
  248. package/app/utils/duxt-config.ts +185 -0
  249. package/app/utils/duxt-text.ts +135 -0
  250. package/app/utils/file-icons.ts +62 -0
  251. package/app/utils/generated-sections.ts +190 -0
  252. package/app/utils/generated-toc.ts +216 -0
  253. package/app/utils/heading-link.ts +32 -0
  254. package/app/utils/locale-path.ts +45 -0
  255. package/app/utils/navigation-tree.ts +206 -0
  256. package/app/utils/nearest-page.ts +72 -0
  257. package/app/utils/openapi.ts +1028 -0
  258. package/app/utils/package-command.ts +162 -0
  259. package/app/utils/request-samples.ts +635 -0
  260. package/app/utils/section-scope.ts +97 -0
  261. package/app/utils/sheet-navigation.ts +100 -0
  262. package/app/utils/source-file.ts +84 -0
  263. package/app/utils/version-choices.ts +68 -0
  264. package/app/utils/version-paths.ts +65 -0
  265. package/bin/duxt-report.mjs +45 -0
  266. package/components.json +19 -0
  267. package/content-cache.ts +193 -0
  268. package/content.config.ts +38 -0
  269. package/duxt-app-config.ts +124 -0
  270. package/highlight-langs.ts +68 -0
  271. package/i18n/i18n.config.ts +16 -0
  272. package/i18n/locales/de/duxt/changelog.json +17 -0
  273. package/i18n/locales/de/duxt/code.json +13 -0
  274. package/i18n/locales/de/duxt/defaults.json +22 -0
  275. package/i18n/locales/de/duxt/devtools.json +7 -0
  276. package/i18n/locales/de/duxt/error.json +10 -0
  277. package/i18n/locales/de/duxt/footer.json +7 -0
  278. package/i18n/locales/de/duxt/locale.json +7 -0
  279. package/i18n/locales/de/duxt/nav.json +17 -0
  280. package/i18n/locales/de/duxt/openapi.json +89 -0
  281. package/i18n/locales/de/duxt/page.json +34 -0
  282. package/i18n/locales/de/duxt/search.json +12 -0
  283. package/i18n/locales/de/duxt/shortcuts.json +11 -0
  284. package/i18n/locales/de/duxt/theme.json +7 -0
  285. package/i18n/locales/de/duxt/toc.json +7 -0
  286. package/i18n/locales/de/duxt/version.json +25 -0
  287. package/i18n/locales/en/duxt/changelog.json +17 -0
  288. package/i18n/locales/en/duxt/code.json +13 -0
  289. package/i18n/locales/en/duxt/defaults.json +22 -0
  290. package/i18n/locales/en/duxt/devtools.json +7 -0
  291. package/i18n/locales/en/duxt/error.json +10 -0
  292. package/i18n/locales/en/duxt/footer.json +7 -0
  293. package/i18n/locales/en/duxt/locale.json +7 -0
  294. package/i18n/locales/en/duxt/nav.json +17 -0
  295. package/i18n/locales/en/duxt/openapi.json +89 -0
  296. package/i18n/locales/en/duxt/page.json +34 -0
  297. package/i18n/locales/en/duxt/search.json +12 -0
  298. package/i18n/locales/en/duxt/shortcuts.json +11 -0
  299. package/i18n/locales/en/duxt/theme.json +7 -0
  300. package/i18n/locales/en/duxt/toc.json +7 -0
  301. package/i18n/locales/en/duxt/version.json +25 -0
  302. package/i18n/locales/es/duxt/changelog.json +17 -0
  303. package/i18n/locales/es/duxt/code.json +13 -0
  304. package/i18n/locales/es/duxt/defaults.json +22 -0
  305. package/i18n/locales/es/duxt/devtools.json +7 -0
  306. package/i18n/locales/es/duxt/error.json +10 -0
  307. package/i18n/locales/es/duxt/footer.json +7 -0
  308. package/i18n/locales/es/duxt/locale.json +7 -0
  309. package/i18n/locales/es/duxt/nav.json +17 -0
  310. package/i18n/locales/es/duxt/openapi.json +89 -0
  311. package/i18n/locales/es/duxt/page.json +34 -0
  312. package/i18n/locales/es/duxt/search.json +12 -0
  313. package/i18n/locales/es/duxt/shortcuts.json +11 -0
  314. package/i18n/locales/es/duxt/theme.json +7 -0
  315. package/i18n/locales/es/duxt/toc.json +7 -0
  316. package/i18n/locales/es/duxt/version.json +25 -0
  317. package/i18n/locales/fr/duxt/changelog.json +17 -0
  318. package/i18n/locales/fr/duxt/code.json +13 -0
  319. package/i18n/locales/fr/duxt/defaults.json +22 -0
  320. package/i18n/locales/fr/duxt/devtools.json +7 -0
  321. package/i18n/locales/fr/duxt/error.json +10 -0
  322. package/i18n/locales/fr/duxt/footer.json +7 -0
  323. package/i18n/locales/fr/duxt/locale.json +7 -0
  324. package/i18n/locales/fr/duxt/nav.json +17 -0
  325. package/i18n/locales/fr/duxt/openapi.json +89 -0
  326. package/i18n/locales/fr/duxt/page.json +34 -0
  327. package/i18n/locales/fr/duxt/search.json +12 -0
  328. package/i18n/locales/fr/duxt/shortcuts.json +11 -0
  329. package/i18n/locales/fr/duxt/theme.json +7 -0
  330. package/i18n/locales/fr/duxt/toc.json +7 -0
  331. package/i18n/locales/fr/duxt/version.json +25 -0
  332. package/i18n/locales/pt/duxt/changelog.json +17 -0
  333. package/i18n/locales/pt/duxt/code.json +13 -0
  334. package/i18n/locales/pt/duxt/defaults.json +22 -0
  335. package/i18n/locales/pt/duxt/devtools.json +7 -0
  336. package/i18n/locales/pt/duxt/error.json +10 -0
  337. package/i18n/locales/pt/duxt/footer.json +7 -0
  338. package/i18n/locales/pt/duxt/locale.json +7 -0
  339. package/i18n/locales/pt/duxt/nav.json +17 -0
  340. package/i18n/locales/pt/duxt/openapi.json +89 -0
  341. package/i18n/locales/pt/duxt/page.json +34 -0
  342. package/i18n/locales/pt/duxt/search.json +12 -0
  343. package/i18n/locales/pt/duxt/shortcuts.json +11 -0
  344. package/i18n/locales/pt/duxt/theme.json +7 -0
  345. package/i18n/locales/pt/duxt/toc.json +7 -0
  346. package/i18n/locales/pt/duxt/version.json +25 -0
  347. package/i18n/locales/pt-BR/duxt/error.json +8 -0
  348. package/i18n/locales/pt-BR/duxt/nav.json +10 -0
  349. package/i18n/locales/pt-BR/duxt/page.json +10 -0
  350. package/i18n/locales/pt-BR/duxt/search.json +11 -0
  351. package/i18n/locales/pt-BR/duxt/version.json +13 -0
  352. package/mdc.config.ts +28 -0
  353. package/modules/config.ts +440 -0
  354. package/modules/devtools.ts +126 -0
  355. package/modules/git-meta.ts +349 -0
  356. package/modules/redirects.ts +152 -0
  357. package/modules/validate.ts +137 -0
  358. package/nuxt.config.ts +530 -0
  359. package/openapi-model.ts +358 -0
  360. package/openapi-parse.ts +1052 -0
  361. package/package.json +161 -0
  362. package/public/devtools/cache.html +318 -0
  363. package/public/devtools/checks.html +309 -0
  364. package/public/devtools/config.html +461 -0
  365. package/public/devtools/i18n.html +309 -0
  366. package/public/devtools/pages.html +348 -0
  367. package/public/devtools/paths.html +315 -0
  368. package/public/devtools/redirects.html +309 -0
  369. package/public/devtools/search.html +324 -0
  370. package/public/devtools/sources.html +309 -0
  371. package/public/devtools/versions.html +318 -0
  372. package/report.ts +303 -0
  373. package/repository-root.ts +26 -0
  374. package/section-reports.ts +55 -0
  375. package/sections-changelog.ts +644 -0
  376. package/sections-openapi.ts +450 -0
  377. package/sections-resolve.ts +711 -0
  378. package/sections.ts +157 -0
  379. package/server/devtools/content.ts +120 -0
  380. package/server/devtools/context.ts +38 -0
  381. package/server/devtools/entry-path.ts +30 -0
  382. package/server/devtools/handler.ts +104 -0
  383. package/server/devtools/preview.ts +461 -0
  384. package/server/devtools/render/content.ts +233 -0
  385. package/server/devtools/render/sources.ts +414 -0
  386. package/server/devtools/render/system.ts +352 -0
  387. package/server/devtools/shell.ts +611 -0
  388. package/server/devtools/sources.ts +122 -0
  389. package/server/devtools/system.ts +178 -0
  390. package/server/mcp/tools/list-pages.ts +52 -0
  391. package/server/mcp/tools/read-page.ts +57 -0
  392. package/server/mcp/tools/search-docs.ts +74 -0
  393. package/server/middleware/raw-markdown.ts +86 -0
  394. package/server/routes/llms-full.txt.get.ts +75 -0
  395. package/server/routes/llms.txt.get.ts +63 -0
  396. package/server/routes/rss.xml.get.ts +125 -0
  397. package/server/utils/duxt-server-text.ts +46 -0
  398. package/sources-git.ts +113 -0
  399. package/sources-resolve.ts +908 -0
  400. package/sources.ts +287 -0
  401. package/validate-report.ts +494 -0
@@ -0,0 +1,185 @@
1
+ <script setup lang="ts">
2
+ import type {
3
+ DuxtOpenApiOperation,
4
+ DuxtOpenApiSecurity,
5
+ DuxtOpenApiSecurityScheme,
6
+ DuxtOpenApiServer
7
+ } from '../../../openapi-model';
8
+
9
+ /**
10
+ * One operation, as the two right-hand columns of the reference.
11
+ *
12
+ * MDC: the `openapi` section type writes `::open-api-operation` into every
13
+ * operation page, with the structure as props and the operation's own
14
+ * description in the slot. The description is Markdown because the
15
+ * specification says it is CommonMark — rendering it as plain text is the
16
+ * commonest thing a reference gets wrong, and it costs the author every link
17
+ * and every code span they wrote.
18
+ *
19
+ * THE COLUMNS. The `reference` layout gives the operation list on the left and
20
+ * the full width; this splits what is left into the description in the middle
21
+ * and the client on the right, which is the arrangement the issue settled on.
22
+ * It is one grid rather than two page regions on purpose: the right column is a
23
+ * property of the OPERATION — its servers, its parameters, its security — so it
24
+ * has to be rendered by whatever renders the operation, and a layout cannot
25
+ * reach it.
26
+ *
27
+ * The structural blocks carry `not-typeset`; the slot does not. A page's prose
28
+ * wants the typeset preset and a parameter table does not, and they are
29
+ * siblings here so each gets what it needs.
30
+ */
31
+ const props = defineProps<{
32
+ operation: DuxtOpenApiOperation;
33
+ servers?: DuxtOpenApiServer[];
34
+ security?: DuxtOpenApiSecurity;
35
+ securitySchemes?: DuxtOpenApiSecurityScheme[];
36
+ }>();
37
+
38
+ /**
39
+ * A fragment that lands on the client rather than at the top of the page.
40
+ *
41
+ * The panel sits beside the endpoint at `xl` and BELOW the whole description
42
+ * everywhere narrower — so on a phone, and in any frame narrower than 80rem, a
43
+ * link to the operation shows the prose and hides the one control the page is
44
+ * about. `#…-try-it` is how a reader, a release note or the landing page's own
45
+ * window points at it.
46
+ *
47
+ * Keyed by the operation rather than a bare `try-it`, because
48
+ * `OpenApiOperations` renders a run of these on one page and a duplicated id is
49
+ * an anchor that resolves to whichever came first.
50
+ */
51
+ const clientAnchor = computed(() =>
52
+ `${props.operation.operationId ?? `${props.operation.method}-${props.operation.path}`}-try-it`
53
+ .toLowerCase()
54
+ .replace(/[^a-z0-9]+/g, '-')
55
+ .replace(/^-|-$/g, '')
56
+ );
57
+ </script>
58
+
59
+ <template>
60
+ <!-- Wider on a wide screen: a `curl` line with a couple of headers wraps at
61
+ 23rem however much room the window has, and the column that shows it is
62
+ why `reference` hands its page the whole column rather than capping it at
63
+ a reading measure the way `docs` and `changelog` do.
64
+
65
+ 30rem AT `2xl`, not the 34 it was. That number was chosen while the shell
66
+ ran to 100rem, where it left the description the larger half; inside
67
+ 90rem the same 34 split the row down the middle, and the description —
68
+ which is what the page is FOR — became the narrower side of a client. -->
69
+ <div
70
+ class="grid gap-8 xl:grid-cols-[minmax(0,1fr)_23rem] 2xl:grid-cols-[minmax(0,1fr)_30rem]"
71
+ >
72
+ <div class="min-w-0">
73
+ <div class="not-typeset flex flex-wrap items-center gap-2">
74
+ <DuxtOpenApiMethod :method="operation.method" class="h-6 px-2" />
75
+
76
+ <code class="font-mono text-sm break-all">{{ operation.path }}</code>
77
+
78
+ <!-- A webhook is the same object pointing the other way, and a reader
79
+ who does not know that will try to call it. -->
80
+ <UiBadge v-if="operation.kind === 'webhook'" variant="secondary">
81
+ {{ $t('duxt.openapi.webhook') }}
82
+ </UiBadge>
83
+
84
+ <UiBadge v-if="operation.deprecated" variant="destructive">
85
+ {{ $t('duxt.openapi.deprecated') }}
86
+ </UiBadge>
87
+ </div>
88
+
89
+ <p
90
+ v-if="operation.operationId"
91
+ class="not-typeset mt-2 font-mono text-xs text-muted-foreground"
92
+ >
93
+ {{ operation.operationId }}
94
+ </p>
95
+
96
+ <div class="mt-4">
97
+ <slot />
98
+ </div>
99
+
100
+ <div class="not-typeset">
101
+ <DuxtOpenApiSecurity :security="security" />
102
+
103
+ <DuxtOpenApiParameters :parameters="operation.parameters" />
104
+
105
+ <section v-if="operation.requestBody?.content?.length" class="mt-8">
106
+ <h2 class="duxt-label">
107
+ {{ $t('duxt.openapi.requestBody') }}
108
+ <span
109
+ v-if="operation.requestBody.required"
110
+ class="ml-1 text-xs font-medium text-destructive"
111
+ >
112
+ {{ $t('duxt.openapi.required') }}
113
+ </span>
114
+ </h2>
115
+
116
+ <p
117
+ v-if="operation.requestBody.description"
118
+ class="mt-2 text-sm text-muted-foreground"
119
+ >
120
+ {{ operation.requestBody.description }}
121
+ </p>
122
+
123
+ <div class="mt-3 border-t border-border/60 pt-3">
124
+ <DuxtOpenApiMedia
125
+ :content="operation.requestBody.content"
126
+ direction="request"
127
+ />
128
+ </div>
129
+ </section>
130
+
131
+ <DuxtOpenApiResponses :responses="operation.responses" />
132
+
133
+ <DuxtOpenApiCallbacks :callbacks="operation.callbacks" />
134
+
135
+ <p v-if="operation.externalDocs" class="mt-8 text-sm">
136
+ <a
137
+ :href="operation.externalDocs.url"
138
+ rel="noopener noreferrer"
139
+ target="_blank"
140
+ class="text-primary underline underline-offset-4"
141
+ >
142
+ {{
143
+ operation.externalDocs.description ?? $t('duxt.openapi.moreInfo')
144
+ }}
145
+ </a>
146
+ </p>
147
+ </div>
148
+ </div>
149
+
150
+ <!-- Sticky only where there is a column to be sticky in: below `xl` the
151
+ client sits after the description, which is the order a phone reads.
152
+
153
+ AND IT SCROLLS ITSELF, the way the sidebar and the table of contents
154
+ already do — same expression, `100vh` less the chrome above it. Sticky
155
+ alone pinned a panel taller than the viewport, so the request samples
156
+ under the send button were cut off and only came into view once the
157
+ reader had scrolled the whole page to its end: the panel is beside the
158
+ endpoint precisely so it does not have to be chased there.
159
+
160
+ Bounded and scrollable only at `xl`, because below it the client is in
161
+ normal flow and a second scrollbar inside the page would be one nobody
162
+ asked for.
163
+
164
+ AND IT KEEPS A REM OFF THE CHROME. Pinned at exactly `--duxt-header-offset`
165
+ the card's top border sat against the navbar's bottom border, so the
166
+ two read as one thick line and the panel looked attached to the header
167
+ rather than floating under it. The `max-h` gives the same rem back at
168
+ the bottom, so the panel still ends clear of the viewport edge. -->
169
+ <div
170
+ :id="clientAnchor"
171
+ class="not-typeset min-w-0 scroll-mt-[var(--duxt-header-offset)]"
172
+ >
173
+ <div
174
+ class="xl:sticky xl:top-[calc(var(--duxt-header-offset)+1rem)] xl:max-h-[calc(100vh-var(--duxt-header-offset)-2.5rem)] xl:overflow-y-auto xl:pr-1"
175
+ >
176
+ <DuxtOpenApiClient
177
+ :operation="operation"
178
+ :servers="servers"
179
+ :security="security"
180
+ :security-schemes="securitySchemes"
181
+ />
182
+ </div>
183
+ </div>
184
+ </div>
185
+ </template>
@@ -0,0 +1,72 @@
1
+ <script setup lang="ts">
2
+ import type { DuxtOpenApiExternalDocs } from '../../../openapi-model';
3
+
4
+ /**
5
+ * The endpoints under one tag, as the list a reader scans.
6
+ *
7
+ * MDC: `::open-api-operations` on a tag's index page. The method is a coloured
8
+ * chip and the path is the link text, because "find this endpoint" is the
9
+ * search a reader actually performs — a list of summaries alone makes them read
10
+ * every line to find `DELETE /pets/{id}`.
11
+ */
12
+ defineProps<{
13
+ operations?: {
14
+ method: string;
15
+ path: string;
16
+ kind?: string;
17
+ summary?: string;
18
+ deprecated?: boolean;
19
+ to: string;
20
+ }[];
21
+ externalDocs?: DuxtOpenApiExternalDocs;
22
+ }>();
23
+
24
+ const localeLink = useDuxtLink();
25
+ </script>
26
+
27
+ <template>
28
+ <div>
29
+ <!-- Outside `not-typeset`: the tag's own description is Markdown and wants
30
+ the typeset preset; the list below emphatically does not. -->
31
+ <slot />
32
+
33
+ <ul v-if="operations?.length" class="not-typeset mt-6 -mx-3">
34
+ <li
35
+ v-for="entry in operations"
36
+ :key="`${entry.method}-${entry.to}`"
37
+ class="border-t border-border/60 first:border-t-0"
38
+ >
39
+ <NuxtLink
40
+ :to="localeLink(entry.to) ?? entry.to"
41
+ class="flex flex-wrap items-center gap-3 rounded-lg px-3 py-3 transition-colors hover:bg-muted/40"
42
+ >
43
+ <DuxtOpenApiMethod :method="entry.method" class="w-16" />
44
+
45
+ <code class="font-mono text-sm break-all">{{ entry.path }}</code>
46
+
47
+ <span
48
+ v-if="entry.summary"
49
+ class="w-full text-sm text-muted-foreground sm:ml-auto sm:w-auto sm:text-right"
50
+ >
51
+ {{ entry.summary }}
52
+ </span>
53
+
54
+ <UiBadge v-if="entry.deprecated" variant="destructive">
55
+ {{ $t('duxt.openapi.deprecated') }}
56
+ </UiBadge>
57
+ </NuxtLink>
58
+ </li>
59
+ </ul>
60
+
61
+ <p v-if="externalDocs" class="not-typeset mt-6 text-sm">
62
+ <a
63
+ :href="externalDocs.url"
64
+ rel="noopener noreferrer"
65
+ target="_blank"
66
+ class="text-primary underline underline-offset-4"
67
+ >
68
+ {{ externalDocs.description ?? $t('duxt.openapi.moreInfo') }}
69
+ </a>
70
+ </p>
71
+ </div>
72
+ </template>
@@ -0,0 +1,302 @@
1
+ <script setup lang="ts">
2
+ import type {
3
+ DuxtOpenApiExternalDocs,
4
+ DuxtOpenApiSecurity,
5
+ DuxtOpenApiSecurityScheme,
6
+ DuxtOpenApiServer
7
+ } from '../../../openapi-model';
8
+
9
+ /**
10
+ * The reference's front page: what the API is, where it lives, how to get in.
11
+ *
12
+ * MDC: `::open-api-overview` on the section's index page, with the document's
13
+ * own `info.description` in the slot. It is the page a reader lands on from the
14
+ * navbar entry, and the three questions it answers — base URL, authentication,
15
+ * which groups of endpoints exist — are the three a reference is opened for
16
+ * before any single endpoint is.
17
+ */
18
+ const props = defineProps<{
19
+ version?: string;
20
+ servers?: DuxtOpenApiServer[];
21
+ securitySchemes?: DuxtOpenApiSecurityScheme[];
22
+ security?: DuxtOpenApiSecurity;
23
+ contact?: { name?: string; url?: string; email?: string };
24
+ license?: { name?: string; identifier?: string; url?: string };
25
+ termsOfService?: string;
26
+ externalDocs?: DuxtOpenApiExternalDocs;
27
+ groups?: {
28
+ name: string;
29
+ description?: string;
30
+ webhooks?: boolean;
31
+ operations?: number;
32
+ to: string;
33
+ }[];
34
+ }>();
35
+
36
+ const localeLink = useDuxtLink();
37
+
38
+ /** Every operation the document describes, counted over its groups. */
39
+ const operations = computed(() =>
40
+ (props.groups ?? []).reduce((all, group) => all + (group.operations ?? 0), 0)
41
+ );
42
+ </script>
43
+
44
+ <template>
45
+ <div>
46
+ <!-- NO SUMMARY LINE. `info.summary` is this page's description, and the
47
+ shell draws a description under the title like it does on every written
48
+ page — printing it here as well put the lead below the rule, where the
49
+ body starts, and said the same sentence twice. -->
50
+ <!-- The document's own description, as the Markdown it is written in. -->
51
+ <slot />
52
+
53
+ <!-- WHAT THE DOCUMENT IS, in figures — the same row the release history
54
+ opens with, and the reason the version is no longer a badge floating
55
+ over the page: a version is a fact about the document, and a fact
56
+ belongs where the other facts are. On its own above the title it read
57
+ as a label for the page, which it never was. -->
58
+ <dl
59
+ v-if="version || groups?.length"
60
+ class="not-typeset mt-8 grid grid-cols-2 gap-x-6 gap-y-7 border-b pb-5 sm:grid-cols-4"
61
+ >
62
+ <div v-if="version" class="flex flex-col gap-1">
63
+ <dd
64
+ class="font-mono text-2xl font-semibold tracking-tight tabular-nums sm:text-3xl"
65
+ >
66
+ {{ version }}
67
+ </dd>
68
+ <dt class="flex items-center gap-1.5 text-xs font-medium text-primary">
69
+ <Icon name="lucide:tag" class="size-3.5" />
70
+ {{ $t('duxt.openapi.version') }}
71
+ </dt>
72
+ </div>
73
+
74
+ <div v-if="groups?.length" class="flex flex-col gap-1">
75
+ <dd
76
+ class="text-2xl font-semibold tracking-tight tabular-nums sm:text-3xl"
77
+ >
78
+ {{ operations }}
79
+ </dd>
80
+ <dt class="flex items-center gap-1.5 text-xs font-medium text-primary">
81
+ <Icon name="lucide:arrow-left-right" class="size-3.5" />
82
+ {{ $t('duxt.openapi.operations') }}
83
+ </dt>
84
+ </div>
85
+
86
+ <div v-if="groups?.length" class="flex flex-col gap-1">
87
+ <dd
88
+ class="text-2xl font-semibold tracking-tight tabular-nums sm:text-3xl"
89
+ >
90
+ {{ groups.length }}
91
+ </dd>
92
+ <dt class="flex items-center gap-1.5 text-xs font-medium text-primary">
93
+ <Icon name="lucide:folder-tree" class="size-3.5" />
94
+ {{ $t('duxt.openapi.groups') }}
95
+ </dt>
96
+ </div>
97
+
98
+ <div v-if="servers?.length" class="flex flex-col gap-1">
99
+ <dd
100
+ class="text-2xl font-semibold tracking-tight tabular-nums sm:text-3xl"
101
+ >
102
+ {{ servers.length }}
103
+ </dd>
104
+ <dt class="flex items-center gap-1.5 text-xs font-medium text-primary">
105
+ <Icon name="lucide:server" class="size-3.5" />
106
+ {{ $t('duxt.openapi.serverCount') }}
107
+ </dt>
108
+ </div>
109
+ </dl>
110
+
111
+ <div class="not-typeset">
112
+ <!-- THE GROUPS FIRST, and that is the whole of this reordering: they
113
+ are what a reader opened the reference for, and they stood third —
114
+ under the base URL and the authentication, which are details you
115
+ come back to once you know which endpoint you want. -->
116
+ <section v-if="groups?.length" class="mt-8">
117
+ <h2 class="duxt-label">
118
+ {{ $t('duxt.openapi.endpoints') }}
119
+ </h2>
120
+
121
+ <!-- The groups as rows, with the arrow every other list of links on
122
+ this site steps forward under the pointer. A grid of cards claimed
123
+ they are alternatives to choose between; they are a table of
124
+ contents. -->
125
+ <ul class="mt-3 -mx-3">
126
+ <li
127
+ v-for="group in groups"
128
+ :key="group.to"
129
+ class="border-t border-border/60 first:border-t-0"
130
+ >
131
+ <NuxtLink
132
+ :to="localeLink(group.to) ?? group.to"
133
+ class="group flex items-start gap-3 rounded-lg px-3 py-3 transition-colors hover:bg-muted/40"
134
+ >
135
+ <span class="min-w-0 flex-1">
136
+ <span class="flex flex-wrap items-baseline gap-x-3">
137
+ <span class="font-medium">{{ group.name }}</span>
138
+ <span class="text-xs text-muted-foreground tabular-nums">
139
+ {{ group.operations }}
140
+ </span>
141
+ </span>
142
+
143
+ <span
144
+ v-if="group.description"
145
+ class="mt-1 line-clamp-2 block text-sm text-muted-foreground"
146
+ >
147
+ {{ group.description }}
148
+ </span>
149
+ </span>
150
+
151
+ <Icon
152
+ name="lucide:arrow-right"
153
+ aria-hidden="true"
154
+ class="mt-1 size-3.5 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5"
155
+ />
156
+ </NuxtLink>
157
+ </li>
158
+ </ul>
159
+ </section>
160
+
161
+ <section v-if="servers?.length" class="mt-8">
162
+ <h2 class="duxt-label">
163
+ {{ $t('duxt.openapi.servers') }}
164
+ </h2>
165
+
166
+ <!-- Rows a hairline apart rather than a bordered card: the card said
167
+ "one object" about a list of several, and three of them down a page
168
+ put a frame around every fact the page states. -->
169
+ <ul class="mt-3 divide-y divide-border/60 border-t border-border/60">
170
+ <li v-for="server in servers" :key="server.url" class="py-3">
171
+ <code class="font-mono text-sm break-all">{{ server.url }}</code>
172
+
173
+ <p
174
+ v-if="server.description"
175
+ class="mt-1 text-sm text-muted-foreground"
176
+ >
177
+ {{ server.description }}
178
+ </p>
179
+
180
+ <!-- A `{variable}` in the URL is not decoration: without its
181
+ default and its allowed values the base URL is unusable. -->
182
+ <dl
183
+ v-if="server.variables?.length"
184
+ class="mt-2 space-y-1 text-xs text-muted-foreground"
185
+ >
186
+ <div
187
+ v-for="variable in server.variables"
188
+ :key="variable.name"
189
+ class="flex flex-wrap gap-x-2"
190
+ >
191
+ <dt class="font-mono">{{ variable.name }}</dt>
192
+ <dd class="font-mono">{{ variable.default }}</dd>
193
+ <dd v-if="variable.enum?.length" class="font-mono">
194
+ {{ variable.enum.join(' · ') }}
195
+ </dd>
196
+ </div>
197
+ </dl>
198
+ </li>
199
+ </ul>
200
+ </section>
201
+
202
+ <section v-if="securitySchemes?.length" class="mt-8">
203
+ <h2 class="duxt-label">
204
+ {{ $t('duxt.openapi.authentication') }}
205
+ </h2>
206
+
207
+ <ul class="mt-3 divide-y divide-border/60 border-t border-border/60">
208
+ <li v-for="scheme in securitySchemes" :key="scheme.key" class="py-3">
209
+ <div class="flex flex-wrap items-baseline gap-2">
210
+ <code class="font-mono text-sm font-medium">{{
211
+ scheme.key
212
+ }}</code>
213
+ <span class="text-xs text-muted-foreground">
214
+ {{ scheme.type }}
215
+ <template v-if="scheme.scheme">· {{ scheme.scheme }}</template>
216
+ <template v-if="scheme.name">· {{ scheme.name }}</template>
217
+ <template v-if="scheme.in">
218
+ · {{ $t(`duxt.openapi.in.${scheme.in}`) }}
219
+ </template>
220
+ </span>
221
+ </div>
222
+
223
+ <p
224
+ v-if="scheme.description"
225
+ class="mt-1 text-sm text-muted-foreground"
226
+ >
227
+ {{ scheme.description }}
228
+ </p>
229
+ </li>
230
+ </ul>
231
+ </section>
232
+
233
+ <!-- ONE ROW, not four labelled lines. Contact, licence and terms are
234
+ what a reader looks up once and never reads: as a definition list
235
+ they took four lines and the visual weight of a section, under an
236
+ actual section that answers what the API does. -->
237
+ <p
238
+ v-if="contact ?? license ?? termsOfService ?? externalDocs"
239
+ class="mt-10 flex flex-wrap items-center gap-x-5 gap-y-2 border-t pt-5 text-xs text-muted-foreground"
240
+ >
241
+ <a
242
+ v-if="contact"
243
+ :href="contact.url ?? `mailto:${contact.email}`"
244
+ rel="noopener noreferrer"
245
+ class="inline-flex items-center gap-1.5 transition-colors hover:text-foreground"
246
+ >
247
+ <Icon name="lucide:mail" class="size-3.5" />
248
+ <!-- The words the label used to print, kept for whoever cannot see
249
+ the icon that replaced them: an envelope beside "Support" says
250
+ "contact" only to a reader who can see the envelope. -->
251
+ <span class="sr-only">{{ $t('duxt.openapi.contact') }}:</span>
252
+ {{ contact.name ?? contact.url ?? contact.email }}
253
+ </a>
254
+
255
+ <a
256
+ v-if="license"
257
+ :href="license.url"
258
+ rel="noopener noreferrer"
259
+ class="inline-flex items-center gap-1.5 transition-colors hover:text-foreground"
260
+ >
261
+ <Icon name="lucide:scale" class="size-3.5" />
262
+ <span class="sr-only">{{ $t('duxt.openapi.license') }}:</span>
263
+ {{ license.name ?? license.identifier }}
264
+ </a>
265
+
266
+ <a
267
+ v-if="termsOfService"
268
+ :href="termsOfService"
269
+ rel="noopener noreferrer"
270
+ class="inline-flex items-center gap-1.5 transition-colors hover:text-foreground"
271
+ >
272
+ <Icon name="lucide:file-text" class="size-3.5" />
273
+ {{ $t('duxt.openapi.terms') }}
274
+ </a>
275
+
276
+ <!-- The one item here whose text the DOCUMENT wrote. `description` is
277
+ prose in OpenAPI's own words, so it may be a label ("Guide") or a
278
+ whole sentence — and a sentence in a row of two-word chips reads as
279
+ a paragraph that lost its paragraph. It is clamped rather than
280
+ shortened: the full text stays in the link, which is what a screen
281
+ reader announces and what the title shows on hover. -->
282
+ <a
283
+ v-if="externalDocs"
284
+ :href="externalDocs.url"
285
+ :title="externalDocs.description"
286
+ rel="noopener noreferrer"
287
+ target="_blank"
288
+ class="inline-flex min-w-0 items-center gap-1.5 transition-colors hover:text-foreground"
289
+ >
290
+ <Icon name="lucide:book-open" class="size-3.5 shrink-0" />
291
+ <span class="max-w-64 truncate">
292
+ {{ externalDocs.description ?? $t('duxt.openapi.moreInfo') }}
293
+ </span>
294
+ <Icon
295
+ name="lucide:arrow-up-right"
296
+ class="size-3 shrink-0 opacity-60"
297
+ />
298
+ </a>
299
+ </p>
300
+ </div>
301
+ </div>
302
+ </template>