@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,292 +0,0 @@
1
- import { useLocalStorage } from "@vueuse/core";
2
- import { useContentSearch } from "@lupinum/ginko-content/client";
3
- import { computed, watch } from "vue";
4
- import { navigateTo, useI18n, useState } from "#imports";
5
- import { useLocalizedPath } from "#ginko-docs/composables/useLocalizedPath";
6
- import { useSiteNavigation } from "#ginko-docs/composables/useSiteNavigation";
7
- import {
8
- getDocsNavigationGroups,
9
- type DocsNavigationItem,
10
- } from "#ginko-docs/features/docs/docs-navigation";
11
- import { useDocsNavigation } from "#ginko-docs/features/docs/composables/useDocsNavigation";
12
- import {
13
- dedupeCommandCenterItems,
14
- groupCommandCenterItems,
15
- PAGE_HIGHLIGHT_STORAGE_KEY,
16
- rememberRecentItem,
17
- resolveRecentItems,
18
- type CommandCenterItem,
19
- type StoredRecentItem,
20
- } from "#ginko-docs/features/search/command-center";
21
-
22
- const MAX_DEFAULT_PAGE_ITEMS = 6;
23
- const MAX_DEFAULT_DOC_ITEMS = 3;
24
-
25
- export function useCommandCenterState() {
26
- const open = useState<boolean>("site-command-center-open", () => false);
27
- const query = useState<string>("site-command-center-query", () => "");
28
-
29
- function closeCommandCenter() {
30
- open.value = false;
31
- query.value = "";
32
- }
33
-
34
- function openCommandCenter(initialQuery = "") {
35
- query.value = initialQuery;
36
- open.value = true;
37
- }
38
-
39
- return {
40
- open,
41
- query,
42
- openCommandCenter,
43
- closeCommandCenter,
44
- };
45
- }
46
-
47
- function flattenNavItems(items: DocsNavigationItem[]): DocsNavigationItem[] {
48
- const output: DocsNavigationItem[] = [];
49
- for (const item of items) {
50
- output.push(item);
51
- if (item.children.length > 0) {
52
- output.push(...flattenNavItems(item.children));
53
- }
54
- }
55
- return output;
56
- }
57
-
58
- export async function useCommandCenter() {
59
- const { locale, t } = useI18n();
60
- const localizedPath = useLocalizedPath();
61
- const {
62
- open,
63
- query,
64
- openCommandCenter,
65
- closeCommandCenter: closeState,
66
- } = useCommandCenterState();
67
- const recentSelections = useLocalStorage<StoredRecentItem[]>("site-command-center-recent", []);
68
-
69
- const { mainNav, socialLinks } = useSiteNavigation();
70
- // Start every context-bound composable before the first await. Calling a
71
- // Nuxt/i18n composable after an awaited operation loses the setup context.
72
- const docsNavigationResult = useDocsNavigation();
73
- const contentSearchResult = useContentSearch({
74
- limit: 8,
75
- locale: () => locale.value,
76
- });
77
- const [
78
- { sections },
79
- {
80
- query: searchQuery,
81
- results: searchResults,
82
- pending: searchPending,
83
- error: searchError,
84
- isEmpty: searchIsEmpty,
85
- },
86
- ] = await Promise.all([docsNavigationResult, contentSearchResult]);
87
-
88
- const contentSearchItems = computed<CommandCenterItem[]>(() =>
89
- searchResults.value.map((hit) => {
90
- const href = hit.anchor ? `${hit.path}#${hit.anchor}` : hit.path;
91
-
92
- return {
93
- id: `search-${href}`,
94
- title: hit.title,
95
- subtitle: hit.excerpt,
96
- href,
97
- group: "search",
98
- icon: hit.collection === "blog" ? "lucide:newspaper" : "lucide:book-open",
99
- badge: hit.collection === "blog" ? t("nav.blog") : t("docs.label"),
100
- };
101
- }),
102
- );
103
-
104
- const pageItems = computed<CommandCenterItem[]>(() => {
105
- const featured: CommandCenterItem[] = [
106
- {
107
- id: "page-home",
108
- title: t("command.pages.home.title"),
109
- subtitle: t("command.pages.home.description"),
110
- href: localizedPath("home"),
111
- group: "pages",
112
- icon: "lucide:home",
113
- keywords: ["landing", "overview", "hero"],
114
- },
115
- ];
116
-
117
- const fromMainNav = mainNav.value.map<CommandCenterItem>((item) => ({
118
- id: `page-main-${item.href}`,
119
- title: item.label,
120
- subtitle: t("command.pages.primary"),
121
- href: item.href,
122
- group: "pages",
123
- icon: item.href === localizedPath("blog") ? "lucide:newspaper" : "lucide:file-text",
124
- keywords: ["navigation", item.label],
125
- }));
126
-
127
- const fromFooter = [
128
- { label: t("nav.documentation"), href: localizedPath("docs") },
129
- ...socialLinks.value,
130
- ]
131
- .filter((item) => !(item as { external?: boolean }).external)
132
- .map<CommandCenterItem>((item) => ({
133
- id: `page-footer-${item.href}`,
134
- title: item.label,
135
- subtitle: t("command.pages.secondary"),
136
- href: item.href,
137
- group: "pages",
138
- icon: "lucide:file-text",
139
- keywords: ["footer", item.label],
140
- }));
141
-
142
- return dedupeCommandCenterItems([...featured, ...fromMainNav, ...fromFooter]);
143
- });
144
-
145
- const docsItems = computed<CommandCenterItem[]>(() => {
146
- const navigationEntries = sections.value.flatMap((section) =>
147
- getDocsNavigationGroups(section).flatMap((group) =>
148
- flattenNavItems(group.items)
149
- .filter((item) => item.path)
150
- .map<CommandCenterItem>((item) => ({
151
- id: `doc-${item.path}`,
152
- title: item.title,
153
- subtitle: group.title ?? section.title ?? t("docs.label"),
154
- href: item.path,
155
- group: "docs_nav",
156
- icon: item.icon ?? "lucide:book-open",
157
- keywords: [section.title ?? "", group.title ?? "", item.badge ?? "", item.title],
158
- badge: item.badge,
159
- })),
160
- ),
161
- );
162
-
163
- const sectionIndexEntries = sections.value.flatMap<CommandCenterItem>((section) =>
164
- section.path && section.title
165
- ? [
166
- {
167
- id: `doc-section-${section.path}`,
168
- title: section.title,
169
- subtitle: t("command.pages.documentation"),
170
- href: section.path,
171
- group: "docs_nav",
172
- icon: section.icon ?? "lucide:book-open",
173
- keywords: [section.title],
174
- },
175
- ]
176
- : [],
177
- );
178
-
179
- return dedupeCommandCenterItems([...sectionIndexEntries, ...navigationEntries]);
180
- });
181
-
182
- const actionItems = computed<CommandCenterItem[]>(() => {
183
- const github = socialLinks.value.find((item) => item.platform === "github");
184
- return github
185
- ? [
186
- {
187
- id: "action-github",
188
- title: t("command.actions.github.title"),
189
- subtitle: t("command.actions.github.description"),
190
- href: github.href,
191
- external: true,
192
- group: "actions" as const,
193
- icon: github.icon ?? "ginko-social:github",
194
- keywords: ["repo", "source", "code"],
195
- },
196
- ]
197
- : [];
198
- });
199
-
200
- const canonicalItems = computed(() =>
201
- dedupeCommandCenterItems([...pageItems.value, ...docsItems.value, ...actionItems.value]),
202
- );
203
-
204
- const recentItems = computed<CommandCenterItem[]>(() => {
205
- if (query.value) return [];
206
-
207
- return resolveRecentItems(recentSelections.value, canonicalItems.value);
208
- });
209
-
210
- const defaultItems = computed<CommandCenterItem[]>(() => {
211
- const recentSourceIds = new Set(recentItems.value.map((item) => item.sourceId));
212
- const docSectionItems = docsItems.value.slice(0, MAX_DEFAULT_DOC_ITEMS);
213
-
214
- return dedupeCommandCenterItems([
215
- ...pageItems.value.slice(0, MAX_DEFAULT_PAGE_ITEMS),
216
- ...docSectionItems,
217
- ...actionItems.value,
218
- ]).filter((item) => !recentSourceIds.has(item.id));
219
- });
220
-
221
- const groupedItems = computed(() => {
222
- if (query.value.trim()) {
223
- return contentSearchItems.value.length
224
- ? [
225
- {
226
- id: "search" as const,
227
- title: t("command.groups.search"),
228
- items: contentSearchItems.value,
229
- },
230
- ]
231
- : [];
232
- }
233
- return groupCommandCenterItems([...recentItems.value, ...defaultItems.value], (group) =>
234
- t(`command.groups.${group}`),
235
- );
236
- });
237
-
238
- function rememberSelection(item: CommandCenterItem) {
239
- const canonicalItem =
240
- item.group === "search"
241
- ? canonicalItems.value.find((candidate) => candidate.href === item.href)
242
- : item;
243
- if (canonicalItem) {
244
- recentSelections.value = rememberRecentItem(canonicalItem, recentSelections.value);
245
- }
246
- }
247
-
248
- function closeCommandCenter() {
249
- closeState();
250
- searchQuery.value = "";
251
- }
252
-
253
- async function selectItem(item: CommandCenterItem) {
254
- const submittedQuery = query.value.trim();
255
- rememberSelection(item);
256
- closeCommandCenter();
257
-
258
- if (!item.href) return;
259
-
260
- if (item.external) {
261
- if (import.meta.client) {
262
- window.open(item.href, "_blank", "noopener,noreferrer");
263
- }
264
- return;
265
- }
266
-
267
- if (submittedQuery && import.meta.client) {
268
- sessionStorage.setItem(
269
- PAGE_HIGHLIGHT_STORAGE_KEY,
270
- JSON.stringify({ href: item.href, query: submittedQuery }),
271
- );
272
- }
273
-
274
- await navigateTo(item.href);
275
- }
276
-
277
- watch(query, (value) => {
278
- searchQuery.value = value;
279
- });
280
-
281
- return {
282
- open,
283
- query,
284
- groupedItems,
285
- searchPending,
286
- searchError,
287
- searchIsEmpty,
288
- openCommandCenter,
289
- closeCommandCenter,
290
- selectItem,
291
- };
292
- }
@@ -1,15 +0,0 @@
1
- <template>
2
- <div class="flex min-h-dvh flex-col bg-background text-foreground selection:bg-primary/15">
3
- <SiteSkipLink />
4
- <SiteBanner />
5
- <SiteHeader />
6
- <main
7
- id="main-content"
8
- class="mx-auto w-full max-w-screen-2xl flex-1 px-4 pt-6 pb-12 md:px-6 md:pt-8 md:pb-16"
9
- >
10
- <slot />
11
- </main>
12
- <SiteFooter />
13
- <SiteInteractionLayer />
14
- </div>
15
- </template>
@@ -1,20 +0,0 @@
1
- <template>
2
- <div class="flex min-h-dvh flex-col bg-background text-foreground selection:bg-primary/15">
3
- <SiteSkipLink />
4
- <SiteBanner />
5
- <SiteHeader />
6
-
7
- <div class="mx-auto flex w-full max-w-screen-2xl min-w-0 flex-1">
8
- <div
9
- class="pointer-events-none sticky top-[var(--site-header-height)] z-20 hidden h-[calc(100dvh-var(--site-header-height)-var(--site-banner-height,0px))] w-[var(--docs-sidebar-width)] shrink-0 overflow-hidden *:pointer-events-auto md:block"
10
- >
11
- <DocsSidebar />
12
- </div>
13
-
14
- <slot />
15
- </div>
16
-
17
- <SiteFooter />
18
- <SiteInteractionLayer />
19
- </div>
20
- </template>
package/app/lib/errors.ts DELETED
@@ -1,37 +0,0 @@
1
- export type ErrorKind = "accessDenied" | "notFound" | "server" | "unavailable";
2
-
3
- export interface NormalizedAppError {
4
- kind: ErrorKind;
5
- statusCode: number;
6
- }
7
-
8
- type ErrorLike = {
9
- status?: number;
10
- statusCode?: number;
11
- };
12
-
13
- function isObject(value: unknown): value is Record<string, unknown> {
14
- return Boolean(value && typeof value === "object");
15
- }
16
-
17
- function toErrorLike(error: unknown): ErrorLike {
18
- return isObject(error) ? error : {};
19
- }
20
-
21
- function toStatusCode(error: ErrorLike): number {
22
- const status = error.statusCode ?? error.status;
23
- return typeof status === "number" && Number.isInteger(status) ? status : 500;
24
- }
25
-
26
- function errorKind(statusCode: number): ErrorKind {
27
- if (statusCode === 404) return "notFound";
28
- if (statusCode === 401 || statusCode === 403) return "accessDenied";
29
- if (statusCode >= 500) return "server";
30
- return "unavailable";
31
- }
32
-
33
- export function normalizeAppError(error: unknown): NormalizedAppError {
34
- const source = toErrorLike(error);
35
- const statusCode = toStatusCode(source);
36
- return { kind: errorKind(statusCode), statusCode };
37
- }
@@ -1,38 +0,0 @@
1
- import type { LocaleCode } from "../../i18n/locales";
2
-
3
- function absoluteUrl(path: string, siteUrl: string) {
4
- return new URL(path, siteUrl).toString();
5
- }
6
-
7
- export function createBreadcrumbSchema(
8
- items: Array<{ name: string; path: string }>,
9
- siteUrl: string,
10
- ) {
11
- return {
12
- "@type": "BreadcrumbList",
13
- itemListElement: items.map((item, index) => ({
14
- "@type": "ListItem",
15
- position: index + 1,
16
- name: item.name,
17
- item: absoluteUrl(item.path, siteUrl),
18
- })),
19
- };
20
- }
21
-
22
- export function createArticleSchema(
23
- article: { date: string; description: string; title: string },
24
- url: string,
25
- authorName: string,
26
- locale: LocaleCode,
27
- ) {
28
- return {
29
- "@type": "TechArticle",
30
- headline: article.title,
31
- description: article.description,
32
- datePublished: article.date,
33
- dateModified: article.date,
34
- url,
35
- inLanguage: locale,
36
- author: authorName ? { name: authorName } : undefined,
37
- };
38
- }
@@ -1,228 +0,0 @@
1
- <script setup lang="ts">
2
- import { Badge } from "#ginko-docs/components/ui/badge";
3
- import ContentFeedback from "#ginko-docs/components/content/Feedback.vue";
4
- import { Button } from "#ginko-docs/components/ui/button";
5
- import { createArticleSchema, createBreadcrumbSchema } from "#ginko-docs/lib/schema-org";
6
- import { getLocalizedSiteText } from "#ginko-docs/config/site.utils";
7
- import {
8
- filterTocByDepth,
9
- flattenTocLinks,
10
- formatContentDate,
11
- getMarkdownTocLinks,
12
- } from "#ginko-docs/utils/content";
13
- import { computed, watch } from "vue";
14
- import {
15
- definePageMeta,
16
- createError,
17
- useAppConfig,
18
- useContentPage,
19
- useHead,
20
- useI18n,
21
- useRoute,
22
- useSeoMeta,
23
- } from "#imports";
24
- import { useLocalizedPath } from "#ginko-docs/composables/useLocalizedPath";
25
- import { useCanonicalUrl } from "#ginko-docs/composables/useCanonicalUrl";
26
- import { useGinkoOgImage } from "#ginko-docs/composables/useGinkoOgImage";
27
- import { useSchemaJsonLd } from "#ginko-docs/composables/useSchemaJsonLd";
28
- import DocumentPageShell from "#ginko-docs/components/content/DocumentPageShell.vue";
29
- import PageMarkdownCopy from "#ginko-docs/components/content/PageMarkdownCopy.vue";
30
- import { useContentRouteAlternates } from "#ginko-docs/composables/useContentRouteAlternates";
31
-
32
- definePageMeta({ layout: "blog" });
33
-
34
- const { locale, t } = useI18n();
35
- const config = useAppConfig().ginkoDocs;
36
- const route = useRoute();
37
- const localizedPath = useLocalizedPath();
38
- const canonicalUrl = useCanonicalUrl();
39
- const { sync: syncContentRouteAlternates } = useContentRouteAlternates();
40
- const {
41
- page: post,
42
- previous,
43
- next,
44
- error,
45
- } = await useContentPage("blog", {
46
- fallback: true,
47
- populate: { author: "authors" },
48
- surround: true,
49
- });
50
- if (error.value) throw error.value;
51
- if (!post.value) throw createError({ statusCode: 404, statusMessage: "Not Found" });
52
- syncContentRouteAlternates(post);
53
-
54
- const pageTitle = computed(() => post.value?.title ?? t("blog.fallbackTitle"));
55
- const pageDescription = computed(() => post.value?.description ?? t("blog.fallbackDescription"));
56
- const tocItems = computed(() =>
57
- filterTocByDepth(flattenTocLinks(getMarkdownTocLinks(post.value?.body)), config.toc?.depth ?? 3),
58
- );
59
- const formattedDate = computed(() => formatContentDate(post.value?.date, locale.value));
60
- const siteName = computed(() => getLocalizedSiteText(config.site.name, locale.value));
61
- const articleAuthor = computed(() => post.value?.author?.name ?? siteName.value);
62
- const suggestions = computed(() => [previous.value, next.value].filter((entry) => entry !== null));
63
-
64
- useSeoMeta({
65
- title: computed(() => `${pageTitle.value} - ${siteName.value}`),
66
- description: pageDescription,
67
- ogTitle: computed(() => `${pageTitle.value} - ${siteName.value}`),
68
- ogDescription: pageDescription,
69
- ogUrl: canonicalUrl,
70
- twitterCard: "summary_large_image",
71
- twitterTitle: computed(() => `${pageTitle.value} - ${siteName.value}`),
72
- twitterDescription: pageDescription,
73
- });
74
-
75
- // Social card with the raw post title (no site-name suffix baked in).
76
- useGinkoOgImage({
77
- title: pageTitle.value,
78
- description: pageDescription.value,
79
- locale: locale.value,
80
- });
81
-
82
- useHead(() => ({
83
- link: [
84
- { key: "canonical", rel: "canonical", href: canonicalUrl.value },
85
- {
86
- key: "rss",
87
- rel: "alternate",
88
- type: "application/rss+xml",
89
- title: `${t("blog.title")} - ${siteName.value}`,
90
- href: `${localizedPath("blog")}/rss.xml`,
91
- },
92
- ],
93
- meta: [{ property: "og:type", content: "article" }],
94
- }));
95
-
96
- useSchemaJsonLd(() =>
97
- post.value
98
- ? [
99
- createBreadcrumbSchema(
100
- [
101
- { name: t("blog.title"), path: localizedPath("blog") },
102
- { name: post.value.title, path: post.value.route.resolvedPath },
103
- ],
104
- config.site.url,
105
- ),
106
- createArticleSchema(
107
- {
108
- date: post.value.date,
109
- description: post.value.description,
110
- title: post.value.title,
111
- },
112
- canonicalUrl.value,
113
- articleAuthor.value,
114
- locale.value,
115
- ),
116
- ]
117
- : [],
118
- );
119
- </script>
120
-
121
- <template>
122
- <DocumentPageShell :padded="false">
123
- <Button as-child variant="ghost" class="mb-8 -ml-2 text-muted-foreground">
124
- <NuxtLink :to="localizedPath('blog')">
125
- <Icon name="lucide:arrow-left" class="size-4" aria-hidden="true" />
126
- {{ t("blog.back") }}
127
- </NuxtLink>
128
- </Button>
129
-
130
- <div v-if="post" class="mb-12">
131
- <div class="mb-8 flex items-center justify-between gap-4">
132
- <div class="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2">
133
- <Badge v-if="post.badge" variant="secondary">{{ post.badge }}</Badge>
134
- <span class="text-sm text-muted-foreground"
135
- >{{ formattedDate
136
- }}<template v-if="post.readingTime"> · {{ post.readingTime }}</template></span
137
- >
138
- <span class="hidden text-sm text-muted-foreground sm:inline" aria-hidden="true"> · </span>
139
- <span class="inline-flex items-center gap-2 text-sm text-muted-foreground">
140
- <img
141
- v-if="post.author?.avatar"
142
- :src="post.author.avatar"
143
- :alt="articleAuthor"
144
- class="size-5 shrink-0 rounded-full border border-border object-cover"
145
- loading="lazy"
146
- />
147
- {{ articleAuthor }}
148
- </span>
149
- </div>
150
- <PageMarkdownCopy />
151
- </div>
152
- <h1
153
- class="font-serif text-3xl leading-tight font-semibold tracking-tight text-foreground sm:text-4xl md:text-5xl"
154
- >
155
- {{ post.title }}
156
- </h1>
157
- <p class="mt-6 text-lg leading-8 text-muted-foreground sm:text-xl">{{ post.description }}</p>
158
- </div>
159
-
160
- <nav
161
- v-if="tocItems.length"
162
- :aria-label="t('blog.toc')"
163
- class="not-prose mx-auto mb-12 max-w-[44rem] border-y border-border py-7"
164
- >
165
- <p class="mb-6 text-sm font-semibold text-foreground">{{ t("blog.toc") }}</p>
166
- <ol class="grid gap-x-12 gap-y-2 sm:grid-cols-2" role="list">
167
- <li v-for="(item, index) in tocItems" :key="item.id">
168
- <a
169
- :href="`#${item.id}`"
170
- class="group grid grid-cols-[2rem_minmax(0,1fr)] gap-3 rounded-md py-1.5 transition-colors hover:bg-accent/45 sm:-mx-2 sm:px-2"
171
- >
172
- <span
173
- class="pt-0.5 font-mono text-xs leading-6 text-muted-foreground tabular-nums transition-colors group-hover:text-foreground/70"
174
- >
175
- {{ String(index + 1).padStart(2, "0") }}
176
- </span>
177
- <span
178
- class="text-sm leading-6 text-foreground/80 transition-colors group-hover:text-foreground"
179
- :class="
180
- item.depth === 3
181
- ? 'pl-3 text-muted-foreground'
182
- : (item.depth ?? 2) >= 4
183
- ? 'pl-6 text-muted-foreground'
184
- : ''
185
- "
186
- >
187
- {{ item.label }}
188
- </span>
189
- </a>
190
- </li>
191
- </ol>
192
- </nav>
193
-
194
- <div v-if="post" class="content-prose mx-auto max-w-[42rem]">
195
- <ContentRenderer :value="post" />
196
- </div>
197
-
198
- <ContentFeedback
199
- class="mt-14 border-y border-border py-5"
200
- :label="t('feedback.articleLabel')"
201
- />
202
-
203
- <div v-if="suggestions.length" class="mt-14">
204
- <h2 class="mb-6 text-sm font-semibold tracking-wider text-muted-foreground uppercase">
205
- {{ t("blog.moreArticles") }}
206
- </h2>
207
- <div class="divide-y divide-border border-y border-border">
208
- <NuxtLink
209
- v-for="p in suggestions"
210
- :key="p.path"
211
- :to="p.path"
212
- class="group grid grid-cols-[minmax(0,1fr)_1.5rem] items-center gap-4 py-6 transition-colors hover:bg-accent/35 sm:px-2"
213
- >
214
- <h3
215
- class="font-serif text-lg leading-snug font-semibold text-foreground transition-colors group-hover:text-primary"
216
- >
217
- {{ p.title }}
218
- </h3>
219
- <Icon
220
- name="lucide:arrow-right"
221
- class="hidden size-4 self-center text-muted-foreground transition-all group-hover:translate-x-0.5 group-hover:text-primary sm:block"
222
- aria-hidden="true"
223
- />
224
- </NuxtLink>
225
- </div>
226
- </div>
227
- </DocumentPageShell>
228
- </template>