@lupinum/ginko-docs 0.4.0-rc.8 → 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,234 +0,0 @@
1
- <script setup lang="ts">
2
- import {
3
- filterTocByDepth,
4
- flattenTocLinks,
5
- formatContentDate,
6
- getMarkdownTocLinks,
7
- } from "#ginko-docs/utils/content";
8
- import ContentFeedback from "#ginko-docs/components/content/Feedback.vue";
9
- import DocsBreadcrumb from "./DocsBreadcrumb.vue";
10
- import DocsMobileToc from "./DocsMobileToc.vue";
11
- import DocsPageNav from "./DocsPageNav.vue";
12
- import DocsToc from "./DocsToc.vue";
13
- import DocsContributeLinks from "./DocsContributeLinks.vue";
14
- import { computed, provide, ref, watch } from "vue";
15
- import { docsTocKey } from "#ginko-docs/features/docs/toc-context";
16
- import {
17
- createError,
18
- useAppConfig,
19
- useContentPage,
20
- useHead,
21
- useI18n,
22
- useRoute,
23
- useSeoMeta,
24
- } from "#imports";
25
- import { useCanonicalUrl } from "#ginko-docs/composables/useCanonicalUrl";
26
- import { useGinkoOgImage } from "#ginko-docs/composables/useGinkoOgImage";
27
- import { useScrollspy } from "#ginko-docs/features/docs/composables/useScrollspy";
28
- import { useRevealActive } from "#ginko-docs/features/docs/composables/useRevealActive";
29
- import PageMarkdownCopy from "#ginko-docs/components/content/PageMarkdownCopy.vue";
30
- import { Separator } from "#ginko-docs/components/ui/separator";
31
- import { getLocalizedSiteText } from "#ginko-docs/config/site.utils";
32
- import { useSchemaJsonLd } from "#ginko-docs/composables/useSchemaJsonLd";
33
- import { useLocalizedPath } from "#ginko-docs/composables/useLocalizedPath";
34
- import { useDocsNavigation } from "#ginko-docs/features/docs/composables/useDocsNavigation";
35
- import { useContentRouteAlternates } from "#ginko-docs/composables/useContentRouteAlternates";
36
-
37
- type DocsNavLink = {
38
- path: string;
39
- title: string;
40
- };
41
-
42
- function toDocsNavLink(
43
- item: { path?: string; title?: string } | null | undefined,
44
- ): DocsNavLink | null {
45
- return item?.path && item?.title ? { path: item.path, title: item.title } : null;
46
- }
47
-
48
- const { locale, t } = useI18n();
49
- const config = useAppConfig().ginkoDocs;
50
- const route = useRoute();
51
- const localizedPath = useLocalizedPath();
52
- const canonicalUrl = useCanonicalUrl();
53
- const { sync: syncContentRouteAlternates } = useContentRouteAlternates();
54
- const contentPageResult = useContentPage("docs", {
55
- fallback: true,
56
- surround: true,
57
- });
58
- const docsNavigationResult = useDocsNavigation();
59
- const [{ page, previous, next: nextContent, error }, { trail }] = await Promise.all([
60
- contentPageResult,
61
- docsNavigationResult,
62
- ]);
63
- if (error.value) throw error.value;
64
- if (!page.value) throw createError({ statusCode: 404, statusMessage: "Not Found" });
65
- syncContentRouteAlternates(page);
66
-
67
- const siteName = computed(() => getLocalizedSiteText(config.site.name, locale.value));
68
- const pageTitle = computed(() => page.value?.title ?? t("docs.fallbackTitle"));
69
- const pageDescription = computed(() => page.value?.description ?? t("docs.fallbackDescription"));
70
- const tocItems = computed(() =>
71
- filterTocByDepth(flattenTocLinks(getMarkdownTocLinks(page.value?.body)), config.toc?.depth ?? 3),
72
- );
73
- const prev = computed(() => toDocsNavLink(previous.value));
74
- const next = computed(() => toDocsNavLink(nextContent.value));
75
- const visibleTrail = computed(() => trail.value.slice(0, -1));
76
- const schemaBreadcrumbs = computed(() => [
77
- { name: siteName.value, path: localizedPath("home") },
78
- ...trail.value
79
- .filter((item) => item.path)
80
- .map((item) => ({ name: item.title, path: item.path! })),
81
- ]);
82
- provide(docsTocKey, tocItems);
83
- const { activeIds, refresh } = useScrollspy(computed(() => tocItems.value.map((item) => item.id)));
84
-
85
- watch(tocItems, () => {
86
- void refresh();
87
- });
88
-
89
- // Keep the active TOC region visible when a long TOC overflows the sticky
90
- // aside.
91
- const tocAside = ref<HTMLElement | null>(null);
92
- const { reveal: revealActiveTocLink } = useRevealActive(tocAside, "[data-toc-active]", {
93
- topPad: 16,
94
- bottomPad: 44,
95
- });
96
- watch(activeIds, () => {
97
- requestAnimationFrame(revealActiveTocLink);
98
- });
99
-
100
- useSeoMeta({
101
- title: computed(() => `${pageTitle.value} - ${siteName.value}`),
102
- description: pageDescription,
103
- ogTitle: computed(() => `${pageTitle.value} - ${siteName.value}`),
104
- ogDescription: pageDescription,
105
- ogUrl: canonicalUrl,
106
- twitterCard: "summary_large_image",
107
- twitterTitle: computed(() => `${pageTitle.value} - ${siteName.value}`),
108
- twitterDescription: pageDescription,
109
- });
110
-
111
- // Social card with the raw page title (no site-name suffix baked in).
112
- useGinkoOgImage({
113
- title: pageTitle.value,
114
- description: pageDescription.value,
115
- locale: locale.value,
116
- });
117
-
118
- useSchemaJsonLd(() => [
119
- {
120
- "@type": "TechArticle",
121
- headline: pageTitle.value,
122
- description: pageDescription.value,
123
- url: canonicalUrl.value,
124
- inLanguage: locale.value,
125
- dateModified: page.value?.updated,
126
- isPartOf: { "@type": "WebSite", name: siteName.value, url: config.site.url },
127
- publisher: { "@type": "Organization", name: siteName.value, url: config.site.url },
128
- },
129
- {
130
- "@type": "BreadcrumbList",
131
- itemListElement: schemaBreadcrumbs.value.map((item, index) => ({
132
- "@type": "ListItem",
133
- position: index + 1,
134
- name: item.name,
135
- item: new URL(item.path, config.site.url).toString(),
136
- })),
137
- },
138
- ]);
139
-
140
- useHead(() => ({
141
- link: [{ key: "canonical", rel: "canonical", href: canonicalUrl.value }],
142
- meta: [{ property: "og:type", content: "article" }],
143
- }));
144
-
145
- function scrollToTop() {
146
- const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
147
- window.scrollTo({ top: 0, behavior: reducedMotion ? "auto" : "smooth" });
148
- }
149
- </script>
150
-
151
- <template>
152
- <div class="flex min-w-0 flex-1 flex-col xl:flex-row">
153
- <div class="flex min-w-0 flex-1 flex-col">
154
- <DocsMobileToc
155
- class="order-1"
156
- :items="tocItems"
157
- :active-ids="activeIds"
158
- :page-title="pageTitle"
159
- />
160
-
161
- <main
162
- id="main-content"
163
- class="order-2 mx-auto w-full max-w-[72ch] flex-1 px-4 py-8 md:px-6 xl:py-14"
164
- >
165
- <article v-if="page">
166
- <div class="flex items-start justify-between gap-4 sm:gap-6">
167
- <div class="min-w-0">
168
- <DocsBreadcrumb class="mb-2" :items="visibleTrail" />
169
- <h1
170
- class="font-heading text-[27px] leading-[1.15] font-semibold tracking-[-0.02em] text-balance text-foreground"
171
- >
172
- {{ page.title }}
173
- </h1>
174
- <p
175
- v-if="page.description"
176
- class="mt-2 text-base leading-[1.55] text-muted-foreground"
177
- >
178
- {{ page.description }}
179
- </p>
180
- </div>
181
- <PageMarkdownCopy />
182
- </div>
183
- <Separator class="my-8" />
184
-
185
- <div class="content-prose content-prose-docs">
186
- <ContentRenderer :value="page" />
187
- </div>
188
-
189
- <footer class="mt-12 border-t border-border pt-6">
190
- <p v-if="page.updated" class="mb-4 text-sm text-muted-foreground">
191
- {{ t("docs.lastUpdated") }}:
192
- <time :datetime="page.updated">{{ formatContentDate(page.updated, locale) }}</time>
193
- </p>
194
- <div class="flex flex-wrap items-center justify-between gap-x-6 gap-y-4">
195
- <ContentFeedback :label="t('feedback.label')" />
196
- <DocsContributeLinks
197
- class="xl:hidden"
198
- :stem="page.stem"
199
- :extension="page.extension"
200
- :title="pageTitle"
201
- />
202
- </div>
203
- <DocsPageNav class="mt-8" :prev="prev" :next="next" />
204
- </footer>
205
- </article>
206
- </main>
207
- </div>
208
-
209
- <aside
210
- ref="tocAside"
211
- class="sticky top-[var(--site-header-height)] hidden h-[calc(100dvh-var(--site-header-height)-var(--site-banner-height,0px))] w-[var(--docs-toc-width)] shrink-0 flex-col overflow-y-auto pt-10 pr-4 pb-6 xl:flex"
212
- :aria-label="t('docs.toc')"
213
- >
214
- <DocsToc class="mb-6" :items="tocItems" :active-ids="activeIds" />
215
-
216
- <div class="mt-auto flex flex-col gap-2.5 border-t border-border/70 pt-5">
217
- <DocsContributeLinks
218
- variant="rail"
219
- :stem="page?.stem"
220
- :extension="page?.extension"
221
- :title="pageTitle"
222
- />
223
- <button
224
- type="button"
225
- class="inline-flex items-center gap-1.5 self-start text-sm font-medium text-muted-foreground transition-colors hover:text-foreground focus-visible:rounded-sm focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
226
- @click="scrollToTop"
227
- >
228
- <Icon name="lucide:arrow-up" class="size-4" aria-hidden="true" />
229
- {{ t("docs.backToTop") }}
230
- </button>
231
- </div>
232
- </aside>
233
- </div>
234
- </template>
@@ -1,57 +0,0 @@
1
- <script setup lang="ts">
2
- import { useI18n } from "#imports";
3
-
4
- defineProps<{
5
- prev?: { path?: string; title?: string } | null;
6
- next?: { path?: string; title?: string } | null;
7
- }>();
8
- const { t } = useI18n();
9
- </script>
10
-
11
- <template>
12
- <nav
13
- v-if="prev || next"
14
- class="grid grid-cols-1 gap-3 sm:grid-cols-2"
15
- :aria-label="t('docs.pageNavigation')"
16
- >
17
- <!-- Previous -->
18
- <NuxtLink
19
- v-if="prev"
20
- :to="prev.path"
21
- class="group flex min-h-20 flex-col gap-1.5 rounded-xl border border-border bg-card px-5 py-4 transition-[border-color,background-color] duration-200 hover:border-foreground/20 hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring sm:col-start-1"
22
- >
23
- <span
24
- class="flex items-center gap-1 text-xs font-medium text-muted-foreground transition-colors group-hover:text-foreground"
25
- >
26
- <Icon
27
- name="lucide:chevron-left"
28
- class="size-3.5 transition-transform group-hover:-translate-x-0.5"
29
- aria-hidden="true"
30
- />
31
- {{ t("docs.previous") }}
32
- </span>
33
- <span class="text-sm leading-snug font-semibold text-foreground">{{ prev.title }}</span>
34
- </NuxtLink>
35
- <div v-else class="hidden sm:block" />
36
-
37
- <!-- Next -->
38
- <NuxtLink
39
- v-if="next"
40
- :to="next.path"
41
- class="group flex min-h-20 flex-col items-end gap-1.5 rounded-xl border border-border bg-card px-5 py-4 text-right transition-[border-color,background-color] duration-200 hover:border-foreground/20 hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring sm:col-start-2"
42
- >
43
- <span
44
- class="flex items-center gap-1 text-xs font-medium text-muted-foreground transition-colors group-hover:text-foreground"
45
- >
46
- {{ t("docs.next") }}
47
- <Icon
48
- name="lucide:chevron-right"
49
- class="size-3.5 transition-transform group-hover:translate-x-0.5"
50
- aria-hidden="true"
51
- />
52
- </span>
53
- <span class="text-sm leading-snug font-semibold text-foreground">{{ next.title }}</span>
54
- </NuxtLink>
55
- <div v-else class="hidden sm:block" />
56
- </nav>
57
- </template>
@@ -1,145 +0,0 @@
1
- <script setup lang="ts">
2
- import { cn } from "#ginko-docs/utils";
3
- import { useDocsNavigation } from "#ginko-docs/features/docs/composables/useDocsNavigation";
4
- import {
5
- docsNavigationSectionContainsPath,
6
- getDocsNavigationGroups,
7
- resolveDocsSectionTargetPath,
8
- type DocsNavigationSection,
9
- } from "#ginko-docs/features/docs/docs-navigation";
10
- import { ScrollArea } from "#ginko-docs/components/ui/scroll-area";
11
- import { useRevealActive } from "#ginko-docs/features/docs/composables/useRevealActive";
12
- import DocsSidebarItem from "./DocsSidebarItem.vue";
13
- import DocsSidebarDropdown from "./DocsSidebarDropdown.vue";
14
- import DocsSidebarList from "./DocsSidebarList.vue";
15
- import DocsSidebarTabs from "./DocsSidebarTabs.vue";
16
- import { computed, nextTick, onMounted, ref, watch } from "vue";
17
- import { navigateTo, useAppConfig, useI18n, useRoute } from "#imports";
18
-
19
- const props = withDefaults(
20
- defineProps<{
21
- variant?: "desktop" | "drawer";
22
- }>(),
23
- {
24
- variant: "desktop",
25
- },
26
- );
27
-
28
- const { t } = useI18n();
29
- const route = useRoute();
30
- const sidebarSwitcher = useAppConfig().ginkoDocs.site.docsSidebarSwitcher;
31
- const { sections } = await useDocsNavigation();
32
- const switcherSections = computed(() =>
33
- sections.value.filter((section): section is DocsNavigationSection & { title: string } =>
34
- Boolean(section.title),
35
- ),
36
- );
37
- const selectedSectionId = ref("");
38
- const routeSection = computed(() =>
39
- sections.value.find((section) => docsNavigationSectionContainsPath(section, route.path)),
40
- );
41
- const activeSectionId = computed(
42
- () => selectedSectionId.value || routeSection.value?.id || sections.value[0]?.id || "",
43
- );
44
- const activeSection = computed(
45
- () => sections.value.find((section) => section.id === activeSectionId.value) ?? sections.value[0],
46
- );
47
- const groups = computed(() =>
48
- activeSection.value ? getDocsNavigationGroups(activeSection.value) : [],
49
- );
50
-
51
- watch(
52
- () => route.path,
53
- () => {
54
- selectedSectionId.value = "";
55
- },
56
- );
57
-
58
- function setActiveSection(id: string) {
59
- // Re-selecting the active section must not navigate away from the current page.
60
- if (id === activeSectionId.value) return;
61
- selectedSectionId.value = id;
62
- const section = sections.value.find((entry) => entry.id === id);
63
- const path = section ? resolveDocsSectionTargetPath(section) : undefined;
64
- if (path) void navigateTo(path);
65
- }
66
-
67
- // The reduced top padding only applies under the section switcher; without it
68
- // the scroll mask would fade into the first group label.
69
- const scrollViewportClass = computed(() =>
70
- cn(
71
- "size-full rounded-[inherit] p-4 overscroll-contain [mask-image:linear-gradient(to_bottom,transparent,white_12px,white_calc(100%-12px),transparent)]",
72
- switcherSections.value.length > 1 && "pt-2",
73
- ),
74
- );
75
-
76
- // Deep links can land on an item far outside the visible band — center it
77
- // once on mount and on section switches.
78
- const scrollArea = ref<{ $el?: HTMLElement } | null>(null);
79
-
80
- const { reveal: revealActiveItem } = useRevealActive(
81
- () => scrollArea.value?.$el?.querySelector<HTMLElement>("[data-slot='scroll-area-viewport']"),
82
- "[data-active='true']",
83
- );
84
-
85
- onMounted(() => {
86
- void nextTick(revealActiveItem);
87
- });
88
- watch(activeSectionId, () => {
89
- void nextTick(revealActiveItem);
90
- });
91
-
92
- const asideClass = computed(() =>
93
- cn(
94
- "flex h-full w-full flex-col text-sm transition-[border-color] duration-[250ms] ease-out *:w-full",
95
- props.variant === "desktop"
96
- ? "absolute inset-y-0 start-0 items-end border-e border-border"
97
- : "relative min-h-0 items-stretch border-e-0",
98
- ),
99
- );
100
- </script>
101
-
102
- <template>
103
- <aside :data-variant="variant" :aria-label="t('docs.label')" :class="asideClass">
104
- <div v-if="switcherSections.length > 1" class="flex flex-col gap-3 p-4 pb-2">
105
- <DocsSidebarTabs
106
- v-if="sidebarSwitcher === 'tabs'"
107
- :sections="switcherSections"
108
- :active-id="activeSectionId"
109
- @update:active-id="setActiveSection"
110
- />
111
- <DocsSidebarDropdown
112
- v-else-if="sidebarSwitcher === 'dropdown'"
113
- :sections="switcherSections"
114
- :active-id="activeSectionId"
115
- @update:active-id="setActiveSection"
116
- />
117
- <DocsSidebarList
118
- v-else
119
- :sections="switcherSections"
120
- :active-id="activeSectionId"
121
- @update:active-id="setActiveSection"
122
- />
123
- </div>
124
-
125
- <ScrollArea ref="scrollArea" class="min-h-0 flex-1" :viewport-class="scrollViewportClass">
126
- <div class="flex min-w-full flex-col gap-0.5">
127
- <template v-for="group in groups" :key="group.id">
128
- <p
129
- v-if="group.title"
130
- data-slot="docs-sidebar-group-title"
131
- class="mt-5 mb-1.5 inline-flex items-center gap-2 px-2 text-[11px] font-semibold tracking-wider text-muted-foreground uppercase first:mt-0"
132
- >
133
- {{ group.title }}
134
- </p>
135
- <DocsSidebarItem
136
- v-for="item in group.items"
137
- :key="item.path ?? item.title"
138
- :item="item"
139
- :depth="0"
140
- />
141
- </template>
142
- </div>
143
- </ScrollArea>
144
- </aside>
145
- </template>
@@ -1,70 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from "vue";
3
- import type { DocsNavigationSection } from "#ginko-docs/features/docs/docs-navigation";
4
- import {
5
- DropdownMenu,
6
- DropdownMenuContent,
7
- DropdownMenuRadioGroup,
8
- DropdownMenuRadioItem,
9
- DropdownMenuTrigger,
10
- } from "#ginko-docs/components/ui/dropdown-menu";
11
-
12
- const defaultIcon = "lucide:book-open";
13
-
14
- const props = defineProps<{
15
- sections: Array<DocsNavigationSection & { title: string }>;
16
- activeId: string;
17
- }>();
18
-
19
- const emit = defineEmits<{
20
- "update:activeId": [id: string];
21
- }>();
22
-
23
- const activeSection = computed(
24
- () => props.sections.find((section) => section.id === props.activeId) ?? props.sections[0],
25
- );
26
-
27
- function selectSection(value: unknown) {
28
- if (typeof value === "string") emit("update:activeId", value);
29
- }
30
- </script>
31
-
32
- <template>
33
- <DropdownMenu v-if="activeSection">
34
- <DropdownMenuTrigger as-child>
35
- <button
36
- type="button"
37
- class="flex h-10 w-full items-center gap-2 rounded-lg border border-border bg-background px-3 text-start text-foreground transition-colors outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring"
38
- data-slot="docs-sidebar-dropdown"
39
- >
40
- <Icon
41
- :name="activeSection.icon ?? defaultIcon"
42
- class="size-4 shrink-0"
43
- aria-hidden="true"
44
- />
45
- <span class="min-w-0 flex-1 truncate text-sm font-medium">{{ activeSection.title }}</span>
46
- <Icon
47
- name="lucide:chevrons-up-down"
48
- class="size-4 shrink-0 text-muted-foreground"
49
- aria-hidden="true"
50
- />
51
- </button>
52
- </DropdownMenuTrigger>
53
- <DropdownMenuContent align="start" class="w-(--reka-dropdown-menu-trigger-width)">
54
- <DropdownMenuRadioGroup :model-value="activeId" @update:model-value="selectSection">
55
- <DropdownMenuRadioItem
56
- v-for="section in sections"
57
- :key="section.id"
58
- :value="section.id"
59
- class="gap-2"
60
- >
61
- <template #indicator-icon>
62
- <Icon name="lucide:check" class="size-4" aria-hidden="true" />
63
- </template>
64
- <Icon :name="section.icon ?? defaultIcon" class="size-4 shrink-0" aria-hidden="true" />
65
- <span class="truncate">{{ section.title }}</span>
66
- </DropdownMenuRadioItem>
67
- </DropdownMenuRadioGroup>
68
- </DropdownMenuContent>
69
- </DropdownMenu>
70
- </template>
@@ -1,122 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed, ref, watch } from "vue";
3
- import { useRoute } from "vue-router";
4
- import {
5
- navigationItemContainsPath,
6
- normalizeNavigationPath,
7
- } from "@lupinum/ginko-content/navigation";
8
- import type { DocsNavigationItem } from "#ginko-docs/features/docs/docs-navigation";
9
- import { Button } from "#ginko-docs/components/ui/button";
10
- import DocsSidebarRow from "./DocsSidebarRow.vue";
11
- import {
12
- Collapsible,
13
- CollapsibleContent,
14
- CollapsibleTrigger,
15
- } from "#ginko-docs/components/ui/collapsible";
16
- import { cn } from "#ginko-docs/utils";
17
-
18
- defineOptions({
19
- name: "DocsSidebarItem",
20
- });
21
-
22
- const props = defineProps<{
23
- item: DocsNavigationItem;
24
- depth: number;
25
- }>();
26
-
27
- const route = useRoute();
28
-
29
- function itemIsExactActive(path: string, item: DocsNavigationItem): boolean {
30
- return Boolean(item.path && normalizeNavigationPath(path) === normalizeNavigationPath(item.path));
31
- }
32
-
33
- function folderShouldBeOpen(path: string, item: DocsNavigationItem): boolean {
34
- return item.children.length > 0 && navigationItemContainsPath(item, path);
35
- }
36
-
37
- const isExactActive = computed(() => itemIsExactActive(route.path, props.item));
38
-
39
- const paddingClass = computed(() => {
40
- const steps = ["ps-[13px]", "ps-[25px]", "ps-[37px]"] as const;
41
- return steps[Math.min(Math.max(props.depth - 1, 0), steps.length - 1)]!;
42
- });
43
-
44
- // Depth 0: compact pill rows. Depth > 0: each row carries the rail as its own
45
- // start border so the active segment recolors it — aligned by construction.
46
- const linkRowClass = computed(() =>
47
- cn(
48
- "group relative flex w-full flex-row items-center gap-2 text-start [overflow-wrap:anywhere] text-muted-foreground transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring",
49
- "[&_svg]:size-4 [&_svg]:shrink-0",
50
- "hover:bg-accent/50 hover:text-accent-foreground/80 hover:transition-none",
51
- "data-[active=true]:bg-muted data-[active=true]:text-foreground data-[active=true]:font-medium data-[active=true]:hover:bg-muted data-[active=true]:hover:transition-colors",
52
- props.depth === 0
53
- ? "rounded-[7px] px-2 py-[5.5px] text-[13px]"
54
- : cn(
55
- "rounded-e-[7px] border-s border-border py-[5px] pe-2 text-[12.5px]",
56
- "hover:border-s-muted-foreground/40",
57
- "data-[active=true]:border-s-brand",
58
- paddingClass.value,
59
- ),
60
- ),
61
- );
62
-
63
- const folderOpen = ref(folderShouldBeOpen(route.path, props.item));
64
-
65
- watch(
66
- () => route.path,
67
- () => {
68
- if (folderShouldBeOpen(route.path, props.item)) {
69
- folderOpen.value = true;
70
- }
71
- },
72
- );
73
-
74
- // No gap between child rows: their start borders stack into one continuous
75
- // rail. Deeper levels indent via padding only, sharing the same rail.
76
- const contentRailClass = computed(() =>
77
- props.depth === 0 ? "ms-[15.5px] mt-0.5 mb-1 flex flex-col" : "flex flex-col",
78
- );
79
- </script>
80
-
81
- <template>
82
- <Collapsible v-if="item.children.length > 0" v-model:open="folderOpen" :unmount-on-hide="false">
83
- <CollapsibleTrigger v-if="item.path" as-child>
84
- <NuxtLink
85
- :to="item.path"
86
- :class="linkRowClass"
87
- :aria-current="isExactActive ? 'page' : undefined"
88
- :data-active="isExactActive ? 'true' : 'false'"
89
- >
90
- <DocsSidebarRow :item="item" show-chevron />
91
- </NuxtLink>
92
- </CollapsibleTrigger>
93
- <CollapsibleTrigger v-else as-child>
94
- <Button
95
- variant="ghost"
96
- :class="linkRowClass"
97
- class="h-auto justify-start"
98
- :data-active="isExactActive ? 'true' : 'false'"
99
- >
100
- <DocsSidebarRow :item="item" show-chevron />
101
- </Button>
102
- </CollapsibleTrigger>
103
-
104
- <CollapsibleContent :class="contentRailClass">
105
- <DocsSidebarItem
106
- v-for="child in item.children"
107
- :key="child.path ?? child.title"
108
- :item="child"
109
- :depth="depth + 1"
110
- />
111
- </CollapsibleContent>
112
- </Collapsible>
113
- <NuxtLink
114
- v-else-if="item.path"
115
- :to="item.path"
116
- :aria-current="isExactActive ? 'page' : undefined"
117
- :class="linkRowClass"
118
- :data-active="isExactActive ? 'true' : 'false'"
119
- >
120
- <DocsSidebarRow :item="item" />
121
- </NuxtLink>
122
- </template>
@@ -1,35 +0,0 @@
1
- <script setup lang="ts">
2
- import type { DocsNavigationSection } from "#ginko-docs/features/docs/docs-navigation";
3
-
4
- const defaultIcon = "lucide:book-open";
5
-
6
- defineProps<{
7
- sections: Array<DocsNavigationSection & { title: string }>;
8
- activeId: string;
9
- }>();
10
-
11
- const emit = defineEmits<{
12
- "update:activeId": [id: string];
13
- }>();
14
- </script>
15
-
16
- <template>
17
- <ul class="flex flex-col gap-0.5" data-slot="docs-sidebar-list">
18
- <li v-for="section in sections" :key="section.id">
19
- <button
20
- type="button"
21
- class="flex min-h-9 w-full items-center gap-2 rounded-lg px-2 text-start text-sm transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring"
22
- :class="
23
- activeId === section.id
24
- ? 'bg-muted font-medium text-foreground'
25
- : 'text-muted-foreground hover:bg-accent/50 hover:text-foreground'
26
- "
27
- :aria-pressed="activeId === section.id"
28
- @click="emit('update:activeId', section.id)"
29
- >
30
- <Icon :name="section.icon ?? defaultIcon" class="size-4 shrink-0" aria-hidden="true" />
31
- <span class="truncate">{{ section.title }}</span>
32
- </button>
33
- </li>
34
- </ul>
35
- </template>