@lupinum/ginko-docs 0.4.0-rc.9 → 0.4.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 (296) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/LICENSE +1 -1
  3. package/README.md +206 -54
  4. package/app/app.config.ts +42 -55
  5. package/app/assets/docs.css +555 -0
  6. package/app/components/DocsBanner.vue +29 -0
  7. package/app/components/DocsExample.vue +43 -0
  8. package/app/components/DocsFooter.vue +19 -0
  9. package/app/components/DocsHeader.vue +112 -0
  10. package/app/components/DocsLanding.vue +55 -0
  11. package/app/components/DocsLanguage.vue +29 -0
  12. package/app/components/DocsNavTree.vue +38 -0
  13. package/app/components/DocsPage.vue +84 -0
  14. package/app/components/DocsPageActions.vue +47 -0
  15. package/app/components/DocsPost.vue +58 -0
  16. package/app/components/DocsSearch.vue +134 -0
  17. package/app/components/DocsShell.vue +20 -0
  18. package/app/components/DocsSidebar.vue +18 -0
  19. package/app/components/DocsTable.vue +11 -0
  20. package/app/components/DocsThemeToggle.vue +45 -0
  21. package/app/components/DocsToc.vue +73 -0
  22. package/app/composables/useDocs.ts +52 -0
  23. package/app/composables/useDocsBlog.ts +32 -0
  24. package/app/composables/useDocsCopy.ts +17 -0
  25. package/app/composables/useDocsPopover.ts +36 -0
  26. package/app/composables/useDocsSite.ts +10 -0
  27. package/app/docs-pages/blog.vue +47 -0
  28. package/app/docs-pages/changelog.vue +27 -0
  29. package/app/docs-pages/doc.vue +14 -0
  30. package/app/docs-pages/docs.vue +9 -0
  31. package/app/docs-pages/index.vue +12 -0
  32. package/app/docs-pages/post.vue +8 -0
  33. package/app/error.vue +23 -115
  34. package/app/layouts/default.vue +25 -7
  35. package/app/lib/blogTypes.ts +8 -0
  36. package/app/lib/docsFirst.ts +11 -0
  37. package/app/lib/docsInit.ts +18 -0
  38. package/app/lib/docsNav.ts +36 -0
  39. package/app/lib/docsPage.ts +44 -0
  40. package/app/lib/markdownComponents.ts +16 -0
  41. package/app/lib/previewToc.ts +18 -0
  42. package/dist/config.d.mts +232 -0
  43. package/dist/config.d.ts +232 -0
  44. package/dist/config.mjs +114 -0
  45. package/dist/pages.d.mts +5 -0
  46. package/dist/pages.d.ts +5 -0
  47. package/dist/pages.mjs +36 -0
  48. package/dist/shared/ginko-docs.BwbF7JBR.mjs +4 -0
  49. package/i18n/de.json +1 -0
  50. package/i18n/en.json +1 -0
  51. package/icons/README.md +2 -0
  52. package/icons/bluesky.svg +1 -0
  53. package/icons/claude.svg +1 -0
  54. package/icons/discord.svg +1 -0
  55. package/icons/github.svg +1 -0
  56. package/icons/linkedin.svg +1 -0
  57. package/icons/mastodon.svg +1 -0
  58. package/icons/openai.svg +1 -0
  59. package/icons/x.svg +1 -0
  60. package/icons/youtube.svg +1 -0
  61. package/nuxt.config.ts +34 -209
  62. package/package.json +43 -76
  63. package/public/_docs/fonts/LICENSE +188 -0
  64. package/public/_docs/fonts/inter-latin-wght-normal.woff2 +0 -0
  65. package/public/_docs/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  66. package/public/_docs/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
  67. package/server/feeds/blog-locale.get.ts +9 -0
  68. package/server/feeds/blog.get.ts +4 -0
  69. package/server/lib/blogFeed.ts +35 -0
  70. package/server/lib/rss.ts +57 -0
  71. package/server/mcp/handler.ts +108 -0
  72. package/server/routes/__ginko/docs-mcp.ts +35 -0
  73. package/shared/paths.ts +4 -0
  74. package/app/app.vue +0 -98
  75. package/app/assets/css/prose.css +0 -3240
  76. package/app/assets/css/tailwind.css +0 -394
  77. package/app/assets/css/theme-palettes.css +0 -309
  78. package/app/assets/css/theme-presets.css +0 -141
  79. package/app/components/OgImage/GinkoDocs.satori.vue +0 -238
  80. package/app/components/content/DocumentPageShell.vue +0 -55
  81. package/app/components/content/Feedback.vue +0 -102
  82. package/app/components/content/ImageZoomDialog.vue +0 -84
  83. package/app/components/content/PageMarkdownCopy.vue +0 -104
  84. package/app/components/content/imageZoom.ts +0 -25
  85. package/app/components/mdc/MdcAccordion.vue +0 -67
  86. package/app/components/mdc/MdcAccordionItem.vue +0 -42
  87. package/app/components/mdc/MdcApi.vue +0 -159
  88. package/app/components/mdc/MdcAside.vue +0 -23
  89. package/app/components/mdc/MdcCallout.vue +0 -61
  90. package/app/components/mdc/MdcCard.vue +0 -175
  91. package/app/components/mdc/MdcCards.vue +0 -30
  92. package/app/components/mdc/MdcCenter.vue +0 -71
  93. package/app/components/mdc/MdcCodeGroup.vue +0 -197
  94. package/app/components/mdc/MdcCodeTree.vue +0 -234
  95. package/app/components/mdc/MdcCollapse.vue +0 -61
  96. package/app/components/mdc/MdcColumn.vue +0 -70
  97. package/app/components/mdc/MdcDropcap.vue +0 -84
  98. package/app/components/mdc/MdcError.vue +0 -23
  99. package/app/components/mdc/MdcExcerpt.vue +0 -26
  100. package/app/components/mdc/MdcFigure.vue +0 -85
  101. package/app/components/mdc/MdcFiles.vue +0 -120
  102. package/app/components/mdc/MdcIdea.vue +0 -17
  103. package/app/components/mdc/MdcInfo.vue +0 -17
  104. package/app/components/mdc/MdcInlineToc.vue +0 -51
  105. package/app/components/mdc/MdcKbd.vue +0 -10
  106. package/app/components/mdc/MdcLayout.vue +0 -57
  107. package/app/components/mdc/MdcNote.vue +0 -17
  108. package/app/components/mdc/MdcQuiz.vue +0 -241
  109. package/app/components/mdc/MdcQuizQuestion.vue +0 -221
  110. package/app/components/mdc/MdcReadMore.vue +0 -70
  111. package/app/components/mdc/MdcSteps.vue +0 -59
  112. package/app/components/mdc/MdcSuccess.vue +0 -23
  113. package/app/components/mdc/MdcTab.vue +0 -17
  114. package/app/components/mdc/MdcTabs.vue +0 -150
  115. package/app/components/mdc/MdcTimeline.vue +0 -17
  116. package/app/components/mdc/MdcTimelineItem.vue +0 -40
  117. package/app/components/mdc/MdcWarning.vue +0 -23
  118. package/app/components/mdc/api.utils.ts +0 -132
  119. package/app/components/mdc/code-tree.utils.ts +0 -22
  120. package/app/components/mdc/icons.ts +0 -10
  121. package/app/components/mdc/quiz.utils.ts +0 -38
  122. package/app/components/prose/ProseA.vue +0 -27
  123. package/app/components/prose/ProseBlockquote.vue +0 -30
  124. package/app/components/prose/ProseCode.vue +0 -14
  125. package/app/components/prose/ProseEm.vue +0 -14
  126. package/app/components/prose/ProseH1.vue +0 -18
  127. package/app/components/prose/ProseH2.vue +0 -29
  128. package/app/components/prose/ProseH3.vue +0 -29
  129. package/app/components/prose/ProseH4.vue +0 -29
  130. package/app/components/prose/ProseH5.vue +0 -15
  131. package/app/components/prose/ProseH6.vue +0 -15
  132. package/app/components/prose/ProseHr.vue +0 -12
  133. package/app/components/prose/ProseImg.vue +0 -55
  134. package/app/components/prose/ProseLi.vue +0 -14
  135. package/app/components/prose/ProseOl.vue +0 -14
  136. package/app/components/prose/ProseP.vue +0 -14
  137. package/app/components/prose/ProsePre.vue +0 -180
  138. package/app/components/prose/ProseStrong.vue +0 -14
  139. package/app/components/prose/ProseTable.vue +0 -18
  140. package/app/components/prose/ProseTbody.vue +0 -14
  141. package/app/components/prose/ProseTd.vue +0 -14
  142. package/app/components/prose/ProseTh.vue +0 -14
  143. package/app/components/prose/ProseThead.vue +0 -14
  144. package/app/components/prose/ProseTr.vue +0 -14
  145. package/app/components/prose/ProseUl.vue +0 -14
  146. package/app/components/site/ModeToggle.vue +0 -81
  147. package/app/components/site/SiteBanner.vue +0 -97
  148. package/app/components/site/SiteFooter.vue +0 -90
  149. package/app/components/site/SiteHeader.vue +0 -371
  150. package/app/components/site/SiteHeroCode.vue +0 -173
  151. package/app/components/site/SiteInteractionLayer.vue +0 -42
  152. package/app/components/site/SiteLocaleSwitcher.vue +0 -190
  153. package/app/components/site/SiteLogoMark.vue +0 -12
  154. package/app/components/site/SiteSkipLink.vue +0 -14
  155. package/app/components/site/SiteSocialLinks.vue +0 -33
  156. package/app/components/site/header-utils.ts +0 -3
  157. package/app/components/site/mode-toggle.utils.ts +0 -18
  158. package/app/components/ui/badge/Badge.vue +0 -28
  159. package/app/components/ui/badge/index.ts +0 -24
  160. package/app/components/ui/button/Button.vue +0 -31
  161. package/app/components/ui/button/index.ts +0 -35
  162. package/app/components/ui/collapsible/Collapsible.vue +0 -15
  163. package/app/components/ui/collapsible/CollapsibleContent.vue +0 -25
  164. package/app/components/ui/collapsible/CollapsibleTrigger.vue +0 -12
  165. package/app/components/ui/collapsible/index.ts +0 -3
  166. package/app/components/ui/dropdown-menu/DropdownMenu.vue +0 -15
  167. package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +0 -50
  168. package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +0 -41
  169. package/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +0 -15
  170. package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +0 -37
  171. package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +0 -23
  172. package/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue +0 -14
  173. package/app/components/ui/dropdown-menu/index.ts +0 -7
  174. package/app/components/ui/kbd/Kbd.vue +0 -23
  175. package/app/components/ui/kbd/index.ts +0 -21
  176. package/app/components/ui/scroll-area/ScrollArea.vue +0 -39
  177. package/app/components/ui/scroll-area/ScrollBar.vue +0 -33
  178. package/app/components/ui/scroll-area/index.ts +0 -2
  179. package/app/components/ui/separator/Separator.vue +0 -27
  180. package/app/components/ui/separator/index.ts +0 -1
  181. package/app/components/ui/sheet/Sheet.vue +0 -15
  182. package/app/components/ui/sheet/SheetContent.vue +0 -59
  183. package/app/components/ui/sheet/SheetDescription.vue +0 -21
  184. package/app/components/ui/sheet/SheetOverlay.vue +0 -26
  185. package/app/components/ui/sheet/SheetTitle.vue +0 -21
  186. package/app/components/ui/sheet/SheetTrigger.vue +0 -12
  187. package/app/components/ui/sheet/index.ts +0 -5
  188. package/app/components/ui/tabs/Tabs.vue +0 -24
  189. package/app/components/ui/tabs/TabsList.vue +0 -26
  190. package/app/components/ui/tabs/TabsTrigger.vue +0 -28
  191. package/app/components/ui/tabs/index.ts +0 -3
  192. package/app/composables/site-navigation.utils.ts +0 -133
  193. package/app/composables/useCanonicalUrl.ts +0 -9
  194. package/app/composables/useContentRouteAlternates.ts +0 -35
  195. package/app/composables/useGinkoAnalytics.ts +0 -52
  196. package/app/composables/useGinkoDocsConfig.ts +0 -6
  197. package/app/composables/useGinkoOgImage.ts +0 -20
  198. package/app/composables/useLocalizedPath.ts +0 -12
  199. package/app/composables/useLocalizedRouteSwitch.ts +0 -31
  200. package/app/composables/useMetaKey.ts +0 -18
  201. package/app/composables/usePageMarkdownActions.ts +0 -79
  202. package/app/composables/useProseAppearance.ts +0 -24
  203. package/app/composables/useSchemaJsonLd.ts +0 -25
  204. package/app/composables/useSiteNavigation.ts +0 -64
  205. package/app/config/site.utils.ts +0 -36
  206. package/app/features/docs/components/DocsBreadcrumb.vue +0 -37
  207. package/app/features/docs/components/DocsContributeLinks.vue +0 -75
  208. package/app/features/docs/components/DocsMobileToc.vue +0 -108
  209. package/app/features/docs/components/DocsPageContent.vue +0 -234
  210. package/app/features/docs/components/DocsPageNav.vue +0 -57
  211. package/app/features/docs/components/DocsSidebar.vue +0 -145
  212. package/app/features/docs/components/DocsSidebarDropdown.vue +0 -70
  213. package/app/features/docs/components/DocsSidebarItem.vue +0 -122
  214. package/app/features/docs/components/DocsSidebarList.vue +0 -35
  215. package/app/features/docs/components/DocsSidebarRow.vue +0 -27
  216. package/app/features/docs/components/DocsSidebarTabs.vue +0 -59
  217. package/app/features/docs/components/DocsToc.vue +0 -106
  218. package/app/features/docs/composables/useDocsNavigation.ts +0 -29
  219. package/app/features/docs/composables/useDocsNavigationData.ts +0 -18
  220. package/app/features/docs/composables/useRevealActive.ts +0 -58
  221. package/app/features/docs/composables/useScrollspy.ts +0 -135
  222. package/app/features/docs/docs-navigation.ts +0 -157
  223. package/app/features/docs/toc-context.ts +0 -6
  224. package/app/features/search/command-center.ts +0 -110
  225. package/app/features/search/components/SiteCommandCenter.vue +0 -359
  226. package/app/features/search/components/SiteSearchPageHighlight.vue +0 -126
  227. package/app/features/search/useCommandCenter.ts +0 -292
  228. package/app/layouts/blog.vue +0 -15
  229. package/app/layouts/docs.vue +0 -20
  230. package/app/lib/errors.ts +0 -37
  231. package/app/lib/schema-org.ts +0 -38
  232. package/app/pages/blog/[slug].vue +0 -228
  233. package/app/pages/blog/index.vue +0 -130
  234. package/app/pages/docs/[...slug].vue +0 -10
  235. package/app/pages/docs/index.vue +0 -19
  236. package/app/pages/index.vue +0 -278
  237. package/app/plugins/ginko-docs-theme.ts +0 -14
  238. package/app/plugins/ssr-width.ts +0 -6
  239. package/app/utils/content.ts +0 -67
  240. package/app/utils/file-icons.ts +0 -90
  241. package/app/utils/index.ts +0 -7
  242. package/app/utils/repository.ts +0 -20
  243. package/components.ts +0 -9
  244. package/content-collections.ts +0 -89
  245. package/content.js +0 -200
  246. package/content.ts +0 -104
  247. package/i18n/i18n.config.ts +0 -10
  248. package/i18n/locales.ts +0 -81
  249. package/i18n/messages/global/banner.ts +0 -8
  250. package/i18n/messages/global/blog.ts +0 -18
  251. package/i18n/messages/global/command.ts +0 -49
  252. package/i18n/messages/global/docs.ts +0 -36
  253. package/i18n/messages/global/errors.ts +0 -36
  254. package/i18n/messages/global/feedback.ts +0 -11
  255. package/i18n/messages/global/index.ts +0 -21
  256. package/i18n/messages/global/nav.ts +0 -26
  257. package/i18n/messages/global/site.ts +0 -4
  258. package/i18n/messages/global/theme.ts +0 -9
  259. package/i18n/messages/index.ts +0 -20
  260. package/i18n/messages/localize.ts +0 -35
  261. package/i18n/routes.ts +0 -33
  262. package/icon-bundle.ts +0 -116
  263. package/index.d.ts +0 -37
  264. package/modules/analytics-boundary.ts +0 -23
  265. package/modules/feature-routing.ts +0 -68
  266. package/modules/syntax-highlighting.ts +0 -120
  267. package/public/fonts/public-sans/LICENSE +0 -93
  268. package/public/fonts/public-sans/README.md +0 -12
  269. package/public/fonts/public-sans/public-sans-400-normal-latin.woff +0 -0
  270. package/public/fonts/public-sans/public-sans-500-normal-latin.woff +0 -0
  271. package/public/fonts/public-sans/public-sans-600-normal-latin.woff +0 -0
  272. package/public/fonts/public-sans/public-sans-700-normal-latin.woff +0 -0
  273. package/runtime/generate-favicons.mjs +0 -124
  274. package/runtime/server/agent-markdown.ts +0 -19
  275. package/server/api/_ginko-docs/redirects.get.ts +0 -28
  276. package/server/mcp/tools/get-page.ts +0 -14
  277. package/server/mcp/tools/list-pages.ts +0 -10
  278. package/server/middleware/redirects.ts +0 -22
  279. package/server/plugins/agent-markdown.ts +0 -6
  280. package/server/plugins/sitemap-policy.ts +0 -12
  281. package/server/routes/.well-known/ai-catalog.json.get.ts +0 -10
  282. package/server/routes/[locale]/blog/rss.xml.get.ts +0 -13
  283. package/server/routes/blog/rss.xml.get.ts +0 -5
  284. package/server/routes/mcp/server-card.get.ts +0 -14
  285. package/server/utils/blog-feed.ts +0 -51
  286. package/server/utils/feed.ts +0 -70
  287. package/server/utils/mcp-server-card.ts +0 -45
  288. package/server/utils/redirects.ts +0 -38
  289. package/server/utils/redirects.utils.ts +0 -58
  290. package/server/utils/sitemap.ts +0 -45
  291. package/shared/route-slugs.ts +0 -5
  292. package/shared/theme-palettes.ts +0 -37
  293. package/shared/theme-presets.ts +0 -3
  294. package/shared/types/app-config.ts +0 -172
  295. package/shared/types/nuxt-config.ts +0 -14
  296. package/tags.ts +0 -192
@@ -1,90 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from "vue";
3
- import { useI18n } from "#imports";
4
- import { useLocalizedPath } from "#ginko-docs/composables/useLocalizedPath";
5
- import { useSiteNavigation } from "#ginko-docs/composables/useSiteNavigation";
6
-
7
- const { site, socialLinks } = useSiteNavigation();
8
- const { t } = useI18n();
9
- const localizedPath = useLocalizedPath();
10
-
11
- const currentYearDate = new Date();
12
- const resources = computed(() => [
13
- { label: t("nav.documentation"), href: localizedPath("docs"), external: false },
14
- ...socialLinks.value,
15
- ]);
16
- </script>
17
-
18
- <template>
19
- <footer class="border-t border-border bg-muted/30" :aria-label="t('nav.company')">
20
- <div class="mx-auto max-w-5xl px-4 py-12 md:px-6 md:py-16">
21
- <div class="flex flex-col gap-8 md:flex-row md:items-start md:justify-between">
22
- <div class="max-w-sm">
23
- <NuxtLink
24
- :to="localizedPath('home')"
25
- :aria-label="site.name"
26
- class="flex items-center gap-2 font-semibold text-foreground"
27
- >
28
- <SiteLogoMark />
29
- </NuxtLink>
30
- <p class="mt-3 text-sm leading-relaxed text-muted-foreground">
31
- {{ site.description }}
32
- </p>
33
- </div>
34
- <div>
35
- <h3 class="mb-3 text-sm font-semibold text-foreground">{{ t("nav.resources") }}</h3>
36
- <ul class="space-y-2.5">
37
- <li v-for="item in resources" :key="item.href">
38
- <NuxtLink
39
- :to="item.href"
40
- :target="item.external ? '_blank' : undefined"
41
- :rel="item.external ? 'noopener noreferrer' : undefined"
42
- class="inline-flex items-center gap-1 text-sm text-muted-foreground transition-colors hover:text-foreground"
43
- >
44
- {{ item.label }}
45
- <Icon
46
- v-if="item.external"
47
- name="lucide:arrow-up-right"
48
- class="size-3 opacity-60"
49
- aria-hidden="true"
50
- />
51
- </NuxtLink>
52
- </li>
53
- </ul>
54
- </div>
55
- </div>
56
-
57
- <div
58
- class="mt-10 flex flex-col items-center justify-between gap-3 border-t border-border pt-6 text-sm text-muted-foreground sm:flex-row"
59
- >
60
- <p>
61
- © <NuxtTime :datetime="currentYearDate" year="numeric" /> {{ site.name }}.
62
- {{ t("site.footer") }}
63
- </p>
64
- <div class="flex flex-wrap items-center justify-center gap-x-4 gap-y-2 sm:justify-end">
65
- <NuxtLink
66
- v-for="link in site.legalLinks"
67
- :key="link.href"
68
- :to="link.href"
69
- :target="link.external ? '_blank' : undefined"
70
- :rel="link.external ? 'noopener noreferrer' : undefined"
71
- class="underline decoration-border underline-offset-4 transition-colors hover:text-foreground hover:decoration-foreground"
72
- >
73
- {{ link.label }}
74
- </NuxtLink>
75
- <p v-if="site.lupinumAttribution">
76
- {{ t("site.openSourceBy") }}
77
- <a
78
- href="https://lupinum.com"
79
- target="_blank"
80
- rel="noopener noreferrer"
81
- class="font-medium text-foreground underline decoration-border underline-offset-4 transition-colors hover:decoration-foreground"
82
- >
83
- Lupinum<span class="sr-only"> ({{ t("nav.externalLink") }})</span> </a
84
- >.
85
- </p>
86
- </div>
87
- </div>
88
- </div>
89
- </footer>
90
- </template>
@@ -1,371 +0,0 @@
1
- <script setup lang="ts">
2
- import { Button } from "#ginko-docs/components/ui/button";
3
- import { Kbd } from "#ginko-docs/components/ui/kbd";
4
- import {
5
- Sheet,
6
- SheetContent,
7
- SheetDescription,
8
- SheetTitle,
9
- SheetTrigger,
10
- } from "#ginko-docs/components/ui/sheet";
11
- import { computed, nextTick, ref, watch } from "vue";
12
- import {
13
- findFirstNavigationPage,
14
- navigationItemContainsPath,
15
- normalizeNavigationPath,
16
- } from "@lupinum/ginko-content/navigation";
17
- import { useI18n, useRoute } from "#imports";
18
- import { useLocalizedPath } from "#ginko-docs/composables/useLocalizedPath";
19
- import { useSiteNavigation } from "#ginko-docs/composables/useSiteNavigation";
20
- import { useCommandCenterState } from "#ginko-docs/features/search/useCommandCenter";
21
- import { useMetaKey } from "#ginko-docs/composables/useMetaKey";
22
- import { useDocsNavigationData } from "#ginko-docs/features/docs/composables/useDocsNavigationData";
23
- import {
24
- getDocsNavigationSections,
25
- normalizeDocsNavigationItem,
26
- } from "#ginko-docs/features/docs/docs-navigation";
27
- import ModeToggle from "#ginko-docs/components/site/ModeToggle.vue";
28
- import { useGinkoDocsConfig } from "#ginko-docs/composables/useGinkoDocsConfig";
29
-
30
- const { mainNav, socialLinks } = useSiteNavigation();
31
- const { openCommandCenter } = useCommandCenterState();
32
- const config = useGinkoDocsConfig();
33
- const { t } = useI18n();
34
- const route = useRoute();
35
- const isMobileMenuOpen = ref(false);
36
- const localizedPath = useLocalizedPath();
37
-
38
- const isActive = (href: string) => route.path === href || route.path.startsWith(href + "/");
39
-
40
- const metaKey = useMetaKey();
41
- const searchShortcut = computed(() => (metaKey.value === "⌘" ? "⌘K" : "Ctrl K"));
42
- const homePath = computed(() => localizedPath("home"));
43
- const docsPath = computed(() => localizedPath("docs"));
44
-
45
- const { data: docsNavData } = useDocsNavigationData();
46
- const docsSections = computed(() =>
47
- getDocsNavigationSections(
48
- (docsNavData.value ?? []).map((item, index) => normalizeDocsNavigationItem(item, index)),
49
- ),
50
- );
51
- const docsMenuSections = computed(() => {
52
- const currentPath = normalizeNavigationPath(route.path);
53
-
54
- return docsSections.value
55
- .map((section) => ({
56
- id: section.id,
57
- title: section.title,
58
- links: section.items
59
- .map((item) => ({
60
- id: item.id,
61
- label: item.title,
62
- href: item.path ?? findFirstNavigationPage(item.children)?.path,
63
- active: navigationItemContainsPath(item, currentPath),
64
- }))
65
- .filter((link): link is typeof link & { href: string } => Boolean(link.href)),
66
- }))
67
- .filter((section) => section.links.length > 0);
68
- });
69
- const hasDocsLevel = computed(() => docsMenuSections.value.length > 0);
70
-
71
- const rootNavItems = computed(() =>
72
- mainNav.value.map((item) => ({
73
- ...item,
74
- drill: hasDocsLevel.value && item.href === docsPath.value,
75
- })),
76
- );
77
- const docsLevelTitle = computed(
78
- () => rootNavItems.value.find((item) => item.drill)?.label ?? t("nav.documentation"),
79
- );
80
-
81
- type MenuLevel = "root" | "docs";
82
- const menuLevel = ref<MenuLevel>("root");
83
- const docsDrillButton = ref<HTMLButtonElement | null>(null);
84
- const docsBackButton = ref<HTMLButtonElement | null>(null);
85
-
86
- function setDrillButtonRef(el: unknown) {
87
- docsDrillButton.value = el instanceof HTMLButtonElement ? el : null;
88
- }
89
-
90
- async function drillIntoDocs() {
91
- menuLevel.value = "docs";
92
- await nextTick();
93
- docsBackButton.value?.focus({ preventScroll: true });
94
- }
95
-
96
- async function backToRoot() {
97
- menuLevel.value = "root";
98
- await nextTick();
99
- docsDrillButton.value?.focus({ preventScroll: true });
100
- }
101
-
102
- function closeMenu() {
103
- isMobileMenuOpen.value = false;
104
- }
105
-
106
- function openSearch() {
107
- isMobileMenuOpen.value = false;
108
- openCommandCenter();
109
- }
110
-
111
- watch(isMobileMenuOpen, (open) => {
112
- if (open) {
113
- menuLevel.value = hasDocsLevel.value && isActive(docsPath.value) ? "docs" : "root";
114
- }
115
- });
116
-
117
- watch(
118
- () => route.path,
119
- () => {
120
- isMobileMenuOpen.value = false;
121
- },
122
- );
123
- </script>
124
-
125
- <template>
126
- <header
127
- class="sticky top-0 z-50 flex h-14 w-full shrink-0 items-center border-b border-border bg-background/90 backdrop-blur-md"
128
- >
129
- <div
130
- class="mx-auto flex w-full max-w-screen-2xl min-w-0 flex-1 items-center gap-3 px-4 min-[691px]:gap-4 min-[691px]:px-6"
131
- >
132
- <NuxtLink
133
- :to="homePath"
134
- class="flex shrink-0 items-center gap-2 font-semibold text-foreground"
135
- :aria-label="t('nav.home')"
136
- >
137
- <SiteLogoMark />
138
- </NuxtLink>
139
-
140
- <nav
141
- class="ml-1 hidden shrink-0 items-center gap-1 min-[691px]:ml-2 min-[691px]:flex"
142
- :aria-label="t('nav.main')"
143
- >
144
- <NuxtLink
145
- v-for="item in mainNav"
146
- :key="item.href"
147
- :to="item.href"
148
- class="rounded-lg px-3 py-1.5 text-sm font-medium transition-colors"
149
- :class="
150
- isActive(item.href)
151
- ? 'bg-muted text-foreground'
152
- : 'text-muted-foreground hover:bg-muted hover:text-foreground'
153
- "
154
- >
155
- {{ item.label }}
156
- </NuxtLink>
157
- </nav>
158
-
159
- <div class="ml-auto flex min-w-0 items-center gap-1 min-[691px]:gap-1.5">
160
- <Button
161
- variant="outline"
162
- class="hidden h-9 w-[22.5rem] min-w-0 max-w-[22.5rem] flex-1 justify-start gap-2 overflow-hidden rounded-lg border-border/70 bg-muted/35 px-3 whitespace-nowrap text-muted-foreground min-[691px]:flex"
163
- :aria-label="t('nav.searchDocumentation')"
164
- type="button"
165
- @click="openSearch"
166
- >
167
- <Icon name="lucide:search" class="size-4 shrink-0" aria-hidden="true" />
168
- <span class="min-w-0 flex-1 truncate text-left" aria-hidden="true">{{
169
- t("nav.searchDocumentation")
170
- }}</span>
171
- <Kbd class="hidden h-5 shrink-0 bg-background text-[10px] min-[900px]:inline-flex">{{
172
- searchShortcut
173
- }}</Kbd>
174
- </Button>
175
-
176
- <div class="flex shrink-0 items-center gap-0.5">
177
- <Button
178
- variant="ghost"
179
- size="icon"
180
- class="size-9 rounded-lg min-[691px]:hidden"
181
- :aria-label="t('nav.searchDocumentation')"
182
- type="button"
183
- @click="openSearch"
184
- >
185
- <Icon name="lucide:search" class="size-[18px]" aria-hidden="true" />
186
- </Button>
187
-
188
- <ClientOnly>
189
- <ModeToggle class="hidden min-[691px]:inline-flex" />
190
-
191
- <template #fallback>
192
- <span
193
- class="hidden size-9 shrink-0 rounded-lg min-[691px]:inline-flex"
194
- aria-hidden="true"
195
- />
196
- </template>
197
- </ClientOnly>
198
-
199
- <SiteLocaleSwitcher class="hidden min-[691px]:flex" />
200
-
201
- <SiteSocialLinks v-if="config.nav.socialIcons" />
202
-
203
- <Sheet v-model:open="isMobileMenuOpen">
204
- <SheetTrigger as-child>
205
- <Button
206
- variant="ghost"
207
- size="icon"
208
- class="size-9 rounded-lg min-[691px]:hidden"
209
- :aria-label="t('nav.openMenu')"
210
- type="button"
211
- >
212
- <Icon name="lucide:menu" class="size-[18px]" aria-hidden="true" />
213
- </Button>
214
- </SheetTrigger>
215
- <SheetContent
216
- side="top"
217
- class="inset-0 h-dvh gap-0 overflow-hidden border-b-0 bg-background/95 p-0 backdrop-blur-md min-[691px]:hidden"
218
- >
219
- <SheetTitle class="sr-only">{{ t("nav.mobile") }}</SheetTitle>
220
- <SheetDescription class="sr-only">{{ t("nav.main") }}</SheetDescription>
221
- <div class="flex h-full min-h-0 flex-col pt-16">
222
- <div
223
- class="flex min-h-0 w-[200%] flex-1 transition-transform duration-300 ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none"
224
- :class="menuLevel === 'docs' ? '-translate-x-1/2' : ''"
225
- >
226
- <nav
227
- class="flex min-h-0 w-1/2 flex-col overflow-x-hidden overflow-y-auto px-6"
228
- :aria-label="t('nav.mobile')"
229
- :inert="menuLevel !== 'root'"
230
- >
231
- <div class="m-auto w-full space-y-8 py-6">
232
- <template v-for="item in rootNavItems" :key="item.href">
233
- <button
234
- v-if="item.drill"
235
- :ref="setDrillButtonRef"
236
- type="button"
237
- class="group block w-full text-start"
238
- @click="drillIntoDocs"
239
- >
240
- <span
241
- class="flex items-center gap-2.5 text-[2.5rem] leading-[1.05] font-semibold tracking-[-0.03em] text-balance"
242
- :class="
243
- isActive(item.href)
244
- ? 'text-foreground'
245
- : 'text-foreground/70 transition-colors group-hover:text-foreground'
246
- "
247
- >
248
- {{ item.label }}
249
- <Icon
250
- name="lucide:chevron-right"
251
- class="size-6 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5"
252
- aria-hidden="true"
253
- />
254
- </span>
255
- <span
256
- v-if="item.description"
257
- class="mt-1.5 block text-sm text-muted-foreground"
258
- >
259
- {{ item.description }}
260
- </span>
261
- </button>
262
- <NuxtLink v-else :to="item.href" class="group block" @click="closeMenu">
263
- <span
264
- class="block text-[2.5rem] leading-[1.05] font-semibold tracking-[-0.03em] text-balance"
265
- :class="
266
- isActive(item.href)
267
- ? 'text-foreground'
268
- : 'text-foreground/70 transition-colors group-hover:text-foreground'
269
- "
270
- >
271
- {{ item.label }}
272
- </span>
273
- <span
274
- v-if="item.description"
275
- class="mt-1.5 block text-sm text-muted-foreground"
276
- >
277
- {{ item.description }}
278
- </span>
279
- </NuxtLink>
280
- </template>
281
- </div>
282
- </nav>
283
-
284
- <nav
285
- v-if="hasDocsLevel"
286
- class="flex min-h-0 w-1/2 flex-col px-6"
287
- :aria-label="docsLevelTitle"
288
- :inert="menuLevel !== 'docs'"
289
- >
290
- <button
291
- ref="docsBackButton"
292
- type="button"
293
- class="flex items-center gap-1 self-start py-2 text-sm font-semibold text-muted-foreground transition-colors hover:text-foreground"
294
- @click="backToRoot"
295
- >
296
- <Icon name="lucide:chevron-left" class="size-4" aria-hidden="true" />
297
- {{ t("nav.menu") }}
298
- </button>
299
- <p
300
- class="mt-1 text-[2rem] leading-[1.1] font-semibold tracking-[-0.03em] text-foreground"
301
- >
302
- {{ docsLevelTitle }}
303
- </p>
304
- <div class="-mx-2 mt-3 min-h-0 flex-1 overflow-x-hidden overflow-y-auto pb-4">
305
- <template v-for="section in docsMenuSections" :key="section.id">
306
- <p
307
- v-if="section.title"
308
- class="mt-6 mb-1 px-2 text-xs font-semibold tracking-[0.08em] text-muted-foreground uppercase first:mt-2"
309
- >
310
- {{ section.title }}
311
- </p>
312
- <NuxtLink
313
- v-for="link in section.links"
314
- :key="link.id"
315
- :to="link.href"
316
- class="flex items-center rounded-md px-2 py-2 text-lg font-semibold transition-colors"
317
- :class="
318
- link.active
319
- ? 'bg-muted text-foreground'
320
- : 'text-foreground hover:bg-muted/60'
321
- "
322
- :aria-current="link.active ? 'true' : undefined"
323
- @click="closeMenu"
324
- >
325
- {{ link.label }}
326
- <span
327
- v-if="link.active"
328
- class="mr-1 ml-auto size-1.5 rounded-full bg-primary"
329
- aria-hidden="true"
330
- />
331
- </NuxtLink>
332
- </template>
333
- </div>
334
- </nav>
335
- </div>
336
-
337
- <div class="shrink-0 space-y-3 border-t border-border px-6 pt-4 pb-8">
338
- <ClientOnly>
339
- <ModeToggle variant="menu-row" />
340
- <SiteLocaleSwitcher variant="menu-row" @navigate="closeMenu" />
341
-
342
- <template #fallback>
343
- <span class="block h-28" aria-hidden="true" />
344
- </template>
345
- </ClientOnly>
346
- <div
347
- v-if="!config.nav.socialIcons && socialLinks.length"
348
- class="flex flex-wrap items-center gap-2"
349
- >
350
- <NuxtLink
351
- v-for="link in socialLinks"
352
- :key="link.href"
353
- :to="link.href"
354
- target="_blank"
355
- rel="noopener noreferrer"
356
- class="inline-flex h-9 items-center gap-2 rounded-full border border-border px-4 text-sm font-semibold text-foreground transition-colors hover:bg-muted"
357
- @click="closeMenu"
358
- >
359
- <Icon v-if="link.icon" :name="link.icon" class="size-4" aria-hidden="true" />
360
- {{ link.label }}
361
- </NuxtLink>
362
- </div>
363
- </div>
364
- </div>
365
- </SheetContent>
366
- </Sheet>
367
- </div>
368
- </div>
369
- </div>
370
- </header>
371
- </template>
@@ -1,173 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed, ref, useId } from "vue";
3
- import { useAsyncData, useI18n, useRuntimeConfig } from "#imports";
4
- import { cn } from "../../utils";
5
- import { filenameExtension, resolveFileIcon } from "../../utils/file-icons";
6
-
7
- export type SiteHeroCodeTab = {
8
- label: string;
9
- icon?: string;
10
- filename?: string;
11
- language?: string;
12
- code: string;
13
- };
14
-
15
- const props = defineProps<{ tabs: SiteHeroCodeTab[] }>();
16
-
17
- const { t } = useI18n();
18
- const runtimeConfig = useRuntimeConfig();
19
- const syntaxThemes = computed(() => {
20
- const themes = runtimeConfig.public.ginkoDocs?.syntaxHighlighting?.themes;
21
- return {
22
- light: themes?.light ?? "light-plus",
23
- dark: themes?.dark ?? "dark-plus",
24
- };
25
- });
26
- const regionId = useId();
27
- const activeIndex = ref(0);
28
-
29
- const activeTab = computed(() => props.tabs[activeIndex.value] ?? props.tabs[0]);
30
-
31
- function tabIcon(tab: SiteHeroCodeTab): string {
32
- return tab.icon ?? resolveFileIcon(tab.filename);
33
- }
34
-
35
- const HTML_ESCAPES: Record<string, string> = { "&": "&amp;", "<": "&lt;", ">": "&gt;" };
36
-
37
- function escapeHtml(input: string): string {
38
- return input.replace(/[&<>]/g, (char) => HTML_ESCAPES[char] ?? char);
39
- }
40
-
41
- function plainLines(code: string): string {
42
- return code
43
- .split("\n")
44
- .map((line) => `<span class="line">${escapeHtml(line)}</span>`)
45
- .join("");
46
- }
47
-
48
- // Config code arrives as raw strings, so it skips the build-time content
49
- // pipeline; highlight here with the configured Shiki theme pair.
50
- const { data: highlighted } = await useAsyncData(`site-hero-code-${regionId}`, async () => {
51
- const { codeToHast, hastToHtml } = await import("shiki");
52
- const themes = syntaxThemes.value;
53
-
54
- return Promise.all(
55
- props.tabs.map(async (tab) => {
56
- const lang = tab.language ?? filenameExtension(tab.filename) ?? "text";
57
-
58
- try {
59
- const root = await codeToHast(tab.code, {
60
- lang,
61
- themes,
62
- });
63
- const pre = root.children.find((node) => node.type === "element");
64
- const code = pre?.children.find((node) => node.type === "element");
65
- if (!code) return plainLines(tab.code);
66
-
67
- return hastToHtml({ type: "root" as const, children: code.children });
68
- } catch {
69
- return plainLines(tab.code);
70
- }
71
- }),
72
- );
73
- });
74
- </script>
75
-
76
- <template>
77
- <div class="site-hero-code content-codegroup not-prose group my-0 shadow-md">
78
- <div class="content-codegroup-header">
79
- <div v-if="tabs.length > 1" class="content-codegroup-tabs" role="tablist">
80
- <button
81
- v-for="(tab, index) in tabs"
82
- :key="tab.label"
83
- type="button"
84
- role="tab"
85
- :aria-selected="activeIndex === index ? 'true' : 'false'"
86
- :class="
87
- cn('content-codegroup-tab', activeIndex === index && 'content-codegroup-tab-active')
88
- "
89
- @click="activeIndex = index"
90
- >
91
- <Icon :name="tabIcon(tab)" class="content-codegroup-tab-icon" aria-hidden="true" />
92
- {{ tab.label }}
93
- </button>
94
- </div>
95
- <div
96
- v-else-if="activeTab"
97
- class="flex min-w-0 items-center gap-2 text-sm font-medium text-muted-foreground"
98
- >
99
- <Icon :name="tabIcon(activeTab)" class="content-codegroup-tab-icon" aria-hidden="true" />
100
- <span class="truncate">{{ activeTab.label }}</span>
101
- </div>
102
- </div>
103
- <div class="content-codegroup-panels grid">
104
- <div
105
- v-for="(tab, index) in tabs"
106
- :key="tab.label"
107
- role="tabpanel"
108
- class="col-start-1 row-start-1 min-w-0"
109
- :class="activeIndex === index ? '' : 'invisible'"
110
- :inert="activeIndex !== index"
111
- >
112
- <figure
113
- dir="ltr"
114
- data-fd-codeblock
115
- class="content-codeblock group shiki not-prose content-codeblock-in-group relative"
116
- >
117
- <div
118
- role="region"
119
- tabindex="0"
120
- :aria-label="`${t('docs.codeSample')}: ${tab.label} (${regionId}-${index})`"
121
- class="fd-scroll-container max-h-[600px] overflow-auto focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-inset"
122
- >
123
- <!-- Shiki output over site-owner config code, never end-user input. -->
124
- <!-- eslint-disable vue/no-v-html -->
125
- <pre
126
- class="w-max min-w-full"
127
- ><code v-html="highlighted?.[index] ?? plainLines(tab.code)" /></pre>
128
- <!-- eslint-enable vue/no-v-html -->
129
- </div>
130
- </figure>
131
- </div>
132
- </div>
133
- </div>
134
- </template>
135
-
136
- <style scoped>
137
- /* Hero scale: same chrome as docs code groups, one step larger. */
138
- .site-hero-code {
139
- padding: 0.25rem 0.375rem 0.375rem;
140
- }
141
-
142
- .site-hero-code .content-codegroup-header {
143
- padding: 0.375rem 0.375rem 0.25rem 0.625rem;
144
- }
145
-
146
- .site-hero-code .content-codegroup-tabs {
147
- gap: 0.375rem;
148
- }
149
-
150
- .site-hero-code .content-codegroup-tab {
151
- height: 2.125rem;
152
- gap: 0.5rem;
153
- padding: 0 0.625rem 0.4375rem;
154
- font-size: 0.9375rem;
155
- }
156
-
157
- .site-hero-code .content-codegroup-tab-icon {
158
- width: 1rem;
159
- height: 1rem;
160
- }
161
-
162
- .site-hero-code .content-codeblock pre {
163
- padding-top: 1.125rem;
164
- padding-bottom: 1.125rem;
165
- font-size: 0.875rem;
166
- line-height: 1.75;
167
- }
168
-
169
- .site-hero-code :deep([data-fd-codeblock] pre code .line) {
170
- padding-right: 1.5rem;
171
- padding-left: 1.5rem;
172
- }
173
- </style>
@@ -1,42 +0,0 @@
1
- <script setup lang="ts">
2
- import { defineAsyncComponent, onBeforeUnmount, onMounted, watch } from "vue";
3
- import { useRoute } from "#imports";
4
- import SiteSearchPageHighlight from "#ginko-docs/features/search/components/SiteSearchPageHighlight.vue";
5
- import { useCommandCenterState } from "#ginko-docs/features/search/useCommandCenter";
6
-
7
- const SiteCommandCenter = defineAsyncComponent(
8
- () => import("#ginko-docs/features/search/components/SiteCommandCenter.vue"),
9
- );
10
- const route = useRoute();
11
- const { open, openCommandCenter, closeCommandCenter } = useCommandCenterState();
12
-
13
- watch(
14
- () => route.fullPath,
15
- () => closeCommandCenter(),
16
- );
17
-
18
- function handleGlobalShortcut(event: KeyboardEvent) {
19
- const element = event.target as HTMLElement | null;
20
- const typing =
21
- element instanceof HTMLElement &&
22
- (element.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(element.tagName));
23
-
24
- if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
25
- event.preventDefault();
26
- openCommandCenter();
27
- } else if (!typing && event.key === "/") {
28
- event.preventDefault();
29
- openCommandCenter();
30
- }
31
- }
32
-
33
- onMounted(() => window.addEventListener("keydown", handleGlobalShortcut));
34
- onBeforeUnmount(() => window.removeEventListener("keydown", handleGlobalShortcut));
35
- </script>
36
-
37
- <template>
38
- <ClientOnly>
39
- <SiteCommandCenter v-if="open" />
40
- <SiteSearchPageHighlight />
41
- </ClientOnly>
42
- </template>