@lupinum/ginko-docs 0.2.2

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 (240) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -0
  3. package/app/app.config.ts +49 -0
  4. package/app/app.vue +71 -0
  5. package/app/assets/css/prose.css +1154 -0
  6. package/app/assets/css/tailwind.css +365 -0
  7. package/app/components/OgImage/GinkoDocs.satori.vue +117 -0
  8. package/app/components/content/DocumentPageShell.vue +55 -0
  9. package/app/components/content/Feedback.vue +97 -0
  10. package/app/components/content/PageMarkdownCopy.vue +102 -0
  11. package/app/components/mdc/MdcAccordion.vue +64 -0
  12. package/app/components/mdc/MdcAccordionItem.vue +51 -0
  13. package/app/components/mdc/MdcAlert.vue +34 -0
  14. package/app/components/mdc/MdcAside.vue +27 -0
  15. package/app/components/mdc/MdcCallout.vue +62 -0
  16. package/app/components/mdc/MdcCard.vue +186 -0
  17. package/app/components/mdc/MdcCardGroup.vue +47 -0
  18. package/app/components/mdc/MdcCenter.vue +71 -0
  19. package/app/components/mdc/MdcCodeGroup.vue +193 -0
  20. package/app/components/mdc/MdcColumn.vue +70 -0
  21. package/app/components/mdc/MdcDocImg.vue +25 -0
  22. package/app/components/mdc/MdcDropcap.vue +84 -0
  23. package/app/components/mdc/MdcError.vue +16 -0
  24. package/app/components/mdc/MdcField.vue +62 -0
  25. package/app/components/mdc/MdcFieldGroup.vue +18 -0
  26. package/app/components/mdc/MdcFigure.vue +51 -0
  27. package/app/components/mdc/MdcIdea.vue +16 -0
  28. package/app/components/mdc/MdcInfo.vue +16 -0
  29. package/app/components/mdc/MdcLayout.vue +57 -0
  30. package/app/components/mdc/MdcNote.vue +16 -0
  31. package/app/components/mdc/MdcPassage.vue +32 -0
  32. package/app/components/mdc/MdcQuiz.vue +281 -0
  33. package/app/components/mdc/MdcQuizOption.vue +91 -0
  34. package/app/components/mdc/MdcQuizQuestion.vue +226 -0
  35. package/app/components/mdc/MdcReadMore.vue +90 -0
  36. package/app/components/mdc/MdcReadMoreGroup.vue +18 -0
  37. package/app/components/mdc/MdcShortcut.vue +46 -0
  38. package/app/components/mdc/MdcStep.vue +42 -0
  39. package/app/components/mdc/MdcSteps.vue +25 -0
  40. package/app/components/mdc/MdcSuccess.vue +16 -0
  41. package/app/components/mdc/MdcTab.vue +17 -0
  42. package/app/components/mdc/MdcTabs.vue +132 -0
  43. package/app/components/mdc/MdcTimeline.vue +14 -0
  44. package/app/components/mdc/MdcTimelineItem.vue +49 -0
  45. package/app/components/mdc/MdcWarning.vue +16 -0
  46. package/app/components/mdc/icons.ts +10 -0
  47. package/app/components/prose/ProseA.vue +27 -0
  48. package/app/components/prose/ProseBlockquote.vue +14 -0
  49. package/app/components/prose/ProseCode.vue +14 -0
  50. package/app/components/prose/ProseEm.vue +14 -0
  51. package/app/components/prose/ProseH1.vue +18 -0
  52. package/app/components/prose/ProseH2.vue +24 -0
  53. package/app/components/prose/ProseH3.vue +24 -0
  54. package/app/components/prose/ProseH4.vue +14 -0
  55. package/app/components/prose/ProseHr.vue +12 -0
  56. package/app/components/prose/ProseImg.vue +120 -0
  57. package/app/components/prose/ProseLi.vue +14 -0
  58. package/app/components/prose/ProseOl.vue +14 -0
  59. package/app/components/prose/ProseP.vue +14 -0
  60. package/app/components/prose/ProsePre.vue +217 -0
  61. package/app/components/prose/ProseStrong.vue +14 -0
  62. package/app/components/prose/ProseTable.vue +16 -0
  63. package/app/components/prose/ProseTbody.vue +14 -0
  64. package/app/components/prose/ProseTd.vue +14 -0
  65. package/app/components/prose/ProseTh.vue +14 -0
  66. package/app/components/prose/ProseThead.vue +14 -0
  67. package/app/components/prose/ProseTr.vue +14 -0
  68. package/app/components/prose/ProseUl.vue +14 -0
  69. package/app/components/site/ModeToggle.vue +123 -0
  70. package/app/components/site/SiteBanner.vue +56 -0
  71. package/app/components/site/SiteFooter.vue +80 -0
  72. package/app/components/site/SiteHeader.vue +250 -0
  73. package/app/components/site/SiteInteractionLayer.vue +35 -0
  74. package/app/components/site/SiteLocaleSwitcher.vue +268 -0
  75. package/app/components/site/SiteLogoMark.vue +12 -0
  76. package/app/components/site/SiteSkipLink.vue +14 -0
  77. package/app/components/ui/accordion/Accordion.vue +15 -0
  78. package/app/components/ui/accordion/AccordionContent.vue +23 -0
  79. package/app/components/ui/accordion/AccordionItem.vue +24 -0
  80. package/app/components/ui/accordion/AccordionTrigger.vue +35 -0
  81. package/app/components/ui/accordion/index.ts +4 -0
  82. package/app/components/ui/alert/Alert.vue +21 -0
  83. package/app/components/ui/alert/AlertDescription.vue +22 -0
  84. package/app/components/ui/alert/AlertTitle.vue +22 -0
  85. package/app/components/ui/alert/index.ts +34 -0
  86. package/app/components/ui/badge/Badge.vue +28 -0
  87. package/app/components/ui/badge/index.ts +24 -0
  88. package/app/components/ui/button/Button.vue +31 -0
  89. package/app/components/ui/button/index.ts +35 -0
  90. package/app/components/ui/card/Card.vue +16 -0
  91. package/app/components/ui/card/CardAction.vue +17 -0
  92. package/app/components/ui/card/CardContent.vue +14 -0
  93. package/app/components/ui/card/CardDescription.vue +14 -0
  94. package/app/components/ui/card/CardFooter.vue +14 -0
  95. package/app/components/ui/card/CardHeader.vue +22 -0
  96. package/app/components/ui/card/CardTitle.vue +14 -0
  97. package/app/components/ui/card/card-variants.ts +15 -0
  98. package/app/components/ui/card/index.ts +8 -0
  99. package/app/components/ui/checkbox/Checkbox.vue +35 -0
  100. package/app/components/ui/checkbox/index.ts +1 -0
  101. package/app/components/ui/collapsible/Collapsible.vue +15 -0
  102. package/app/components/ui/collapsible/CollapsibleContent.vue +25 -0
  103. package/app/components/ui/collapsible/CollapsibleTrigger.vue +12 -0
  104. package/app/components/ui/collapsible/index.ts +3 -0
  105. package/app/components/ui/dialog/Dialog.vue +15 -0
  106. package/app/components/ui/dialog/DialogClose.vue +12 -0
  107. package/app/components/ui/dialog/DialogContent.vue +69 -0
  108. package/app/components/ui/dialog/DialogDescription.vue +21 -0
  109. package/app/components/ui/dialog/DialogFooter.vue +15 -0
  110. package/app/components/ui/dialog/DialogHeader.vue +12 -0
  111. package/app/components/ui/dialog/DialogOverlay.vue +26 -0
  112. package/app/components/ui/dialog/DialogPortal.vue +12 -0
  113. package/app/components/ui/dialog/DialogTitle.vue +21 -0
  114. package/app/components/ui/dialog/DialogTrigger.vue +12 -0
  115. package/app/components/ui/dialog/index.ts +10 -0
  116. package/app/components/ui/dropdown-menu/DropdownMenu.vue +15 -0
  117. package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +36 -0
  118. package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +40 -0
  119. package/app/components/ui/dropdown-menu/DropdownMenuGroup.vue +12 -0
  120. package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +41 -0
  121. package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +25 -0
  122. package/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +15 -0
  123. package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +37 -0
  124. package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
  125. package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +17 -0
  126. package/app/components/ui/dropdown-menu/DropdownMenuSub.vue +15 -0
  127. package/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue +29 -0
  128. package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +31 -0
  129. package/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue +14 -0
  130. package/app/components/ui/dropdown-menu/index.ts +16 -0
  131. package/app/components/ui/input/Input.vue +23 -0
  132. package/app/components/ui/input/index.ts +1 -0
  133. package/app/components/ui/kbd/Kbd.vue +23 -0
  134. package/app/components/ui/kbd/KbdGroup.vue +14 -0
  135. package/app/components/ui/kbd/index.ts +22 -0
  136. package/app/components/ui/label/Label.vue +14 -0
  137. package/app/components/ui/label/index.ts +1 -0
  138. package/app/components/ui/scroll-area/ScrollArea.vue +39 -0
  139. package/app/components/ui/scroll-area/ScrollBar.vue +33 -0
  140. package/app/components/ui/scroll-area/index.ts +2 -0
  141. package/app/components/ui/separator/Separator.vue +27 -0
  142. package/app/components/ui/separator/index.ts +1 -0
  143. package/app/components/ui/sheet/Sheet.vue +15 -0
  144. package/app/components/ui/sheet/SheetClose.vue +12 -0
  145. package/app/components/ui/sheet/SheetContent.vue +59 -0
  146. package/app/components/ui/sheet/SheetDescription.vue +21 -0
  147. package/app/components/ui/sheet/SheetFooter.vue +12 -0
  148. package/app/components/ui/sheet/SheetHeader.vue +12 -0
  149. package/app/components/ui/sheet/SheetOverlay.vue +26 -0
  150. package/app/components/ui/sheet/SheetTitle.vue +21 -0
  151. package/app/components/ui/sheet/SheetTrigger.vue +12 -0
  152. package/app/components/ui/sheet/index.ts +8 -0
  153. package/app/components/ui/switch/Switch.vue +39 -0
  154. package/app/components/ui/switch/index.ts +1 -0
  155. package/app/components/ui/tabs/Tabs.vue +24 -0
  156. package/app/components/ui/tabs/TabsContent.vue +21 -0
  157. package/app/components/ui/tabs/TabsList.vue +26 -0
  158. package/app/components/ui/tabs/TabsTrigger.vue +28 -0
  159. package/app/components/ui/tabs/index.ts +4 -0
  160. package/app/components/ui/textarea/Textarea.vue +31 -0
  161. package/app/components/ui/textarea/index.ts +1 -0
  162. package/app/composables/site-navigation.utils.ts +80 -0
  163. package/app/composables/useCanonicalUrl.ts +9 -0
  164. package/app/composables/useContentRouteAlternates.ts +43 -0
  165. package/app/composables/useGinkoAnalytics.ts +37 -0
  166. package/app/composables/useGinkoDocsConfig.ts +6 -0
  167. package/app/composables/useGinkoOgImage.ts +20 -0
  168. package/app/composables/useLocalizedPath.ts +12 -0
  169. package/app/composables/useLocalizedRouteSwitch.ts +31 -0
  170. package/app/composables/usePageMarkdownActions.ts +79 -0
  171. package/app/composables/useSchemaJsonLd.ts +25 -0
  172. package/app/composables/useSiteNavigation.ts +86 -0
  173. package/app/config/site.utils.ts +36 -0
  174. package/app/error.vue +125 -0
  175. package/app/features/docs/components/DocsContributeLinks.vue +65 -0
  176. package/app/features/docs/components/DocsMobileToc.vue +104 -0
  177. package/app/features/docs/components/DocsPageContent.vue +188 -0
  178. package/app/features/docs/components/DocsPageNav.vue +57 -0
  179. package/app/features/docs/components/DocsSidebar.vue +129 -0
  180. package/app/features/docs/components/DocsSidebarDropdown.vue +70 -0
  181. package/app/features/docs/components/DocsSidebarItem.vue +169 -0
  182. package/app/features/docs/components/DocsSidebarList.vue +35 -0
  183. package/app/features/docs/components/DocsSidebarTabs.vue +35 -0
  184. package/app/features/docs/components/DocsToc.vue +60 -0
  185. package/app/features/docs/composables/useDocsEntryPath.ts +13 -0
  186. package/app/features/docs/composables/useDocsNavigation.ts +29 -0
  187. package/app/features/docs/composables/useDocsNavigationData.ts +15 -0
  188. package/app/features/docs/composables/useScrollspy.ts +67 -0
  189. package/app/features/docs/docs-navigation.ts +170 -0
  190. package/app/features/search/command-center.ts +156 -0
  191. package/app/features/search/components/SiteCommandCenter.vue +323 -0
  192. package/app/features/search/components/SiteSearchPageHighlight.vue +124 -0
  193. package/app/features/search/useCommandCenter.ts +300 -0
  194. package/app/layouts/blog.vue +15 -0
  195. package/app/layouts/default.vue +12 -0
  196. package/app/layouts/docs.vue +20 -0
  197. package/app/lib/errors.ts +84 -0
  198. package/app/lib/schema-org.ts +38 -0
  199. package/app/lib/utils.ts +7 -0
  200. package/app/pages/blog/[slug].vue +211 -0
  201. package/app/pages/blog/index.vue +106 -0
  202. package/app/pages/docs/[...slug].vue +10 -0
  203. package/app/pages/docs/index.vue +23 -0
  204. package/app/pages/index.vue +140 -0
  205. package/app/plugins/ssr-width.ts +6 -0
  206. package/app/utils/content.ts +56 -0
  207. package/app/utils/index.ts +1 -0
  208. package/components.ts +9 -0
  209. package/content.js +175 -0
  210. package/content.ts +148 -0
  211. package/i18n/i18n.config.ts +10 -0
  212. package/i18n/locales.ts +81 -0
  213. package/i18n/messages/global/banner.ts +8 -0
  214. package/i18n/messages/global/blog.ts +14 -0
  215. package/i18n/messages/global/command.ts +44 -0
  216. package/i18n/messages/global/docs.ts +32 -0
  217. package/i18n/messages/global/errors.ts +36 -0
  218. package/i18n/messages/global/feedback.ts +11 -0
  219. package/i18n/messages/global/index.ts +21 -0
  220. package/i18n/messages/global/nav.ts +18 -0
  221. package/i18n/messages/global/site.ts +4 -0
  222. package/i18n/messages/global/theme.ts +7 -0
  223. package/i18n/messages/index.ts +20 -0
  224. package/i18n/messages/localize.ts +35 -0
  225. package/i18n/messages/source.ts +5 -0
  226. package/i18n/routes.ts +33 -0
  227. package/icon-bundle.ts +141 -0
  228. package/index.d.ts +13 -0
  229. package/modules/feature-routing.ts +32 -0
  230. package/nuxt.config.ts +194 -0
  231. package/package.json +91 -0
  232. package/runtime/markdown/inline-code-highlight.ts +155 -0
  233. package/runtime/server/agent-markdown.ts +21 -0
  234. package/runtime/utils.ts +7 -0
  235. package/server/mcp/tools/get-page.ts +14 -0
  236. package/server/mcp/tools/list-pages.ts +10 -0
  237. package/server/plugins/agent-markdown.ts +6 -0
  238. package/shared/route-slugs.ts +5 -0
  239. package/shared/types/app-config.ts +100 -0
  240. package/tags.ts +204 -0
package/nuxt.config.ts ADDED
@@ -0,0 +1,194 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { dirname, join } from "node:path";
3
+ import tailwindcss from "@tailwindcss/vite";
4
+ import { transformerNotationDiff, transformerNotationHighlight } from "@shikijs/transformers";
5
+ import darkPlus from "shiki/dist/themes/dark-plus.mjs";
6
+ import lightPlus from "shiki/dist/themes/light-plus.mjs";
7
+ import { contentComponentPolicy, contentComponentTags } from "./tags";
8
+ import { i18nPages } from "./i18n/routes";
9
+ import { includeIconNames, layerIconCollections, layerIconNames } from "./icon-bundle";
10
+
11
+ const root = dirname(fileURLToPath(import.meta.url));
12
+ const app = join(root, "app");
13
+
14
+ export default defineNuxtConfig({
15
+ build: {
16
+ transpile: ["@lupinum/ginko-docs"],
17
+ },
18
+ $meta: { name: "ginko-docs" },
19
+ compatibilityDate: "2025-07-15",
20
+ alias: { "#ginko-docs": app },
21
+ css: [join(app, "assets/css/tailwind.css"), join(app, "assets/css/prose.css")],
22
+ modules: [
23
+ join(root, "modules/feature-routing"),
24
+ "@nuxt/icon",
25
+ "@nuxt/fonts",
26
+ "@nuxtjs/i18n",
27
+ "@lupinum/ginko-content",
28
+ "@nuxt/image",
29
+ "@nuxt/scripts",
30
+ "nuxt-og-image",
31
+ "@nuxtjs/sitemap",
32
+ "@nuxtjs/robots",
33
+ "@nuxtjs/color-mode",
34
+ "@nuxtjs/mcp-toolkit",
35
+ ],
36
+ ogImage: {
37
+ zeroRuntime: true,
38
+ // Satori renders queue up behind page prerendering on modest build
39
+ // machines; the default 15s budget is too tight under that contention.
40
+ security: { renderTimeout: 120_000 },
41
+ },
42
+ mcp: {
43
+ name: "Ginko Docs",
44
+ version: "0.2.2",
45
+ },
46
+ components: {
47
+ dirs: [
48
+ {
49
+ path: join(app, "components/site"),
50
+ pathPrefix: false,
51
+ global: true,
52
+ pattern: [
53
+ "SiteBanner.vue",
54
+ "SiteFooter.vue",
55
+ "SiteHeader.vue",
56
+ "SiteInteractionLayer.vue",
57
+ "SiteLocaleSwitcher.vue",
58
+ "SiteLogoMark.vue",
59
+ "SiteSkipLink.vue",
60
+ ],
61
+ },
62
+ {
63
+ path: join(app, "features/docs/components"),
64
+ pathPrefix: false,
65
+ global: true,
66
+ pattern: "DocsSidebar.vue",
67
+ },
68
+ { path: join(app, "components/mdc"), pathPrefix: false, global: true },
69
+ { path: join(app, "components/prose"), pathPrefix: false, global: true },
70
+ ],
71
+ },
72
+ imports: { autoImport: false },
73
+ colorMode: { classSuffix: "" },
74
+ icon: {
75
+ provider: "none",
76
+ fallbackToApi: false,
77
+ customCollections: layerIconCollections,
78
+ clientBundle: {
79
+ icons: [...layerIconNames],
80
+ includeCustomCollections: false,
81
+ scan: {
82
+ globInclude: [
83
+ "**/*.{vue,js,mjs,cjs,ts,jsx,tsx,md,mdc,mdx,yml,yaml}",
84
+ "**/.navigation.{yml,yaml}",
85
+ ],
86
+ globExclude: [
87
+ ".git",
88
+ ".nuxt",
89
+ ".output",
90
+ ".cache",
91
+ "node_modules",
92
+ "dist",
93
+ "build",
94
+ "coverage",
95
+ "test",
96
+ "tests",
97
+ ],
98
+ },
99
+ },
100
+ },
101
+ fonts: {
102
+ defaults: {
103
+ weights: [400, 500, 600, 700],
104
+ styles: ["normal"],
105
+ subsets: ["latin", "latin-ext"],
106
+ },
107
+ families: [{ name: "Public Sans", provider: "google", global: true }],
108
+ },
109
+ i18n: {
110
+ customRoutes: "config",
111
+ defaultLocale: "en",
112
+ detectBrowserLanguage: false,
113
+ pages: i18nPages,
114
+ strategy: "prefix_except_default",
115
+ vueI18n: join(root, "i18n/i18n.config.ts"),
116
+ },
117
+ content: {
118
+ componentPolicy: contentComponentPolicy,
119
+ i18n: {
120
+ translatedSlugs: true,
121
+ },
122
+ markdown: {
123
+ plugins: [
124
+ [
125
+ "highlight",
126
+ {
127
+ preStyles: false,
128
+ transformers: [transformerNotationDiff(), transformerNotationHighlight()],
129
+ themes: { light: lightPlus, dark: darkPlus },
130
+ },
131
+ ],
132
+ ["toc", { depth: 4, searchDepth: 4 }],
133
+ "summary",
134
+ ["footnotes", { label: "" }],
135
+ ],
136
+ tags: contentComponentTags,
137
+ anchorLinks: { depth: 4, exclude: [1] },
138
+ },
139
+ search: { engine: "minisearch" },
140
+ sitemap: true,
141
+ agent: { routes: true, linkHeaders: true, markdownNegotiation: true, prerender: true },
142
+ },
143
+ sitemap: {
144
+ excludeAppSources: ["nuxt:prerender"],
145
+ },
146
+ app: {
147
+ head: {
148
+ charset: "utf-8",
149
+ htmlAttrs: { lang: "en" },
150
+ viewport: "width=device-width, initial-scale=1",
151
+ meta: [{ name: "color-scheme", content: "light dark" }],
152
+ },
153
+ },
154
+ hooks: {
155
+ "icon:clientBundleIcons"(icons) {
156
+ includeIconNames(icons);
157
+ },
158
+ "components:dirs"(dirs) {
159
+ const defaultComponentsDir = join(app, "components").replaceAll("\\", "/");
160
+ const filtered = dirs.filter((entry) => {
161
+ const path = (typeof entry === "string" ? entry : entry.path).replaceAll("\\", "/");
162
+ return path !== defaultComponentsDir;
163
+ });
164
+ dirs.splice(0, dirs.length, ...filtered);
165
+ },
166
+ },
167
+ nitro: {
168
+ prerender: {
169
+ // OG images render through a Resvg worker. Parallel prerenders can
170
+ // terminate that worker under load and leave otherwise valid pages with
171
+ // 408/500 social-image routes, so package builds must stay serialized.
172
+ concurrency: 1,
173
+ crawlLinks: true,
174
+ failOnError: true,
175
+ routes: ["/llms.txt", "/llms-full.txt", "/sitemap.xml", "/robots.txt"],
176
+ },
177
+ },
178
+ vite: {
179
+ optimizeDeps: {
180
+ include: [
181
+ "@vueuse/core",
182
+ "class-variance-authority",
183
+ "clsx",
184
+ "reka-ui",
185
+ "tailwind-merge",
186
+ "zod",
187
+ ],
188
+ },
189
+ resolve: {
190
+ dedupe: ["@lupinum/ginko-content", "vue", "vue-router"],
191
+ },
192
+ plugins: [tailwindcss()],
193
+ },
194
+ });
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "@lupinum/ginko-docs",
3
+ "version": "0.2.2",
4
+ "description": "A polished Nuxt documentation layer powered by Ginko Content.",
5
+ "keywords": [
6
+ "content",
7
+ "documentation",
8
+ "i18n",
9
+ "markdown",
10
+ "nuxt"
11
+ ],
12
+ "homepage": "https://github.com/Mat4m0/lupinum-docs-shadcn#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/Mat4m0/lupinum-docs-shadcn/issues"
15
+ },
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/Mat4m0/lupinum-docs-shadcn.git",
20
+ "directory": "layer"
21
+ },
22
+ "files": [
23
+ "app",
24
+ "i18n",
25
+ "modules",
26
+ "runtime",
27
+ "server",
28
+ "shared",
29
+ "content.js",
30
+ "content.ts",
31
+ "components.ts",
32
+ "icon-bundle.ts",
33
+ "index.d.ts",
34
+ "nuxt.config.ts",
35
+ "tags.ts",
36
+ "!**/*.test.ts"
37
+ ],
38
+ "type": "module",
39
+ "main": "./nuxt.config.ts",
40
+ "exports": {
41
+ ".": "./nuxt.config.ts",
42
+ "./content": {
43
+ "types": "./content.ts",
44
+ "import": "./content.js",
45
+ "default": "./content.js"
46
+ },
47
+ "./app-config": "./shared/types/app-config.ts",
48
+ "./components": "./components.ts"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ },
53
+ "dependencies": {
54
+ "@iconify-json/circle-flags": "^1.2.10",
55
+ "@iconify-json/logos": "^1.2.11",
56
+ "@iconify-json/lucide": "^1.2.111",
57
+ "@nuxt/fonts": "^0.14.0",
58
+ "@nuxt/icon": "^2.2.3",
59
+ "@nuxt/image": "^2.0.0",
60
+ "@nuxt/kit": "^4.4.6",
61
+ "@nuxt/scripts": "^0.13.2",
62
+ "@nuxtjs/color-mode": "^4.0.0",
63
+ "@nuxtjs/i18n": "^10.4.0",
64
+ "@nuxtjs/mcp-toolkit": "0.17.2",
65
+ "@nuxtjs/robots": "^6.1.2",
66
+ "@nuxtjs/sitemap": "^8.2.2",
67
+ "@resvg/resvg-js": "^2.6.0",
68
+ "@shikijs/transformers": "^4.2.0",
69
+ "@tailwindcss/vite": "^4.3.0",
70
+ "@vueuse/core": "^14.2.1",
71
+ "class-variance-authority": "^0.7.1",
72
+ "clsx": "^2.1.1",
73
+ "h3": "^1.15.4",
74
+ "nitropack": "^2.13.4",
75
+ "nuxt-og-image": "^6.7.2",
76
+ "reka-ui": "^2.9.2",
77
+ "satori": "^0.19.2",
78
+ "shiki": "^4.0.2",
79
+ "tailwind-merge": "^3.5.0",
80
+ "tailwindcss": "^4.2.2",
81
+ "tw-animate-css": "^1.4.0",
82
+ "zod": "^4.4.3"
83
+ },
84
+ "peerDependencies": {
85
+ "@lupinum/ginko-content": ">=0.3.0-rc.2 <0.4.0",
86
+ "nuxt": "^4.4.6",
87
+ "vue": "^3.5.35",
88
+ "vue-router": "^5.1.0"
89
+ },
90
+ "scripts": {}
91
+ }
@@ -0,0 +1,155 @@
1
+ import type { ComarkElement, ComarkNode, ComarkPluginFactory } from "comark";
2
+ import { getHighlighter, type HighlightOptions } from "comark/plugins/highlight";
3
+ import { codeToTokens, getTokenStyleObject, stringifyTokenStyle } from "shiki/core";
4
+
5
+ type CodeAttributes = {
6
+ class?: string;
7
+ lang?: string;
8
+ language?: string;
9
+ };
10
+
11
+ type ThemeRegistration = NonNullable<NonNullable<HighlightOptions["themes"]>["light"]>;
12
+
13
+ type CodeThemes = {
14
+ light: ThemeRegistration;
15
+ dark: ThemeRegistration;
16
+ };
17
+
18
+ function cloneTheme<T extends ThemeRegistration>(theme: T): T {
19
+ return structuredClone(theme);
20
+ }
21
+
22
+ async function resolveCodeThemes(themes?: HighlightOptions["themes"]): Promise<CodeThemes> {
23
+ if (themes?.light || themes?.dark) {
24
+ const light = themes.light || themes.dark;
25
+ const dark = themes.dark || themes.light;
26
+
27
+ return {
28
+ light: cloneTheme(light as ThemeRegistration),
29
+ dark: cloneTheme(dark as ThemeRegistration),
30
+ };
31
+ }
32
+
33
+ const [light, dark] = await Promise.all([
34
+ import("shiki/dist/themes/material-theme-lighter.mjs"),
35
+ import("shiki/dist/themes/material-theme-palenight.mjs"),
36
+ ]);
37
+
38
+ return {
39
+ light: cloneTheme(light.default),
40
+ dark: cloneTheme(dark.default),
41
+ };
42
+ }
43
+
44
+ function getThemeName(theme: ThemeRegistration, fallback: string): string {
45
+ return typeof theme === "object" &&
46
+ theme !== null &&
47
+ "name" in theme &&
48
+ typeof theme.name === "string"
49
+ ? theme.name
50
+ : fallback;
51
+ }
52
+
53
+ function isElement(node: ComarkNode): node is ComarkElement {
54
+ return Array.isArray(node) && typeof node[0] === "string";
55
+ }
56
+
57
+ function getCodeLanguage(attrs: CodeAttributes): string | undefined {
58
+ const language = attrs.language ?? attrs.lang;
59
+
60
+ return language?.trim() || undefined;
61
+ }
62
+
63
+ function appendClass(attrs: CodeAttributes, className: string): CodeAttributes {
64
+ return {
65
+ ...attrs,
66
+ class: [attrs.class, className].filter(Boolean).join(" "),
67
+ };
68
+ }
69
+
70
+ async function highlightInlineCodeNode(
71
+ node: ComarkElement,
72
+ options: HighlightOptions,
73
+ ): Promise<ComarkElement> {
74
+ const attrs = (node[1] ?? {}) as CodeAttributes;
75
+ const language = getCodeLanguage(attrs);
76
+ const code = node
77
+ .slice(2)
78
+ .filter((child): child is string => typeof child === "string")
79
+ .join("");
80
+
81
+ if (!language || !code) {
82
+ return node;
83
+ }
84
+
85
+ const themes = await resolveCodeThemes(options.themes);
86
+ const lightThemeName = getThemeName(themes.light, "material-theme-lighter");
87
+ const darkThemeName = getThemeName(themes.dark, "material-theme-palenight");
88
+ const highlighter = await getHighlighter({
89
+ ...options,
90
+ registerDefaultThemes: false,
91
+ themes,
92
+ });
93
+ const result = codeToTokens(highlighter, code, {
94
+ lang: language,
95
+ themes: {
96
+ light: lightThemeName,
97
+ dark: lightThemeName !== darkThemeName ? darkThemeName : undefined,
98
+ },
99
+ });
100
+ const children: ComarkNode[] = [];
101
+
102
+ for (let lineIndex = 0; lineIndex < result.tokens.length; lineIndex += 1) {
103
+ const line = result.tokens[lineIndex] ?? [];
104
+
105
+ for (const token of line) {
106
+ const style = stringifyTokenStyle(token.htmlStyle || getTokenStyleObject(token));
107
+ children.push(style ? ["span", { style }, token.content] : ["span", {}, token.content]);
108
+ }
109
+
110
+ if (lineIndex < result.tokens.length - 1) {
111
+ children.push("\n");
112
+ }
113
+ }
114
+
115
+ return [
116
+ "code",
117
+ appendClass(attrs, `shiki ${result.themeName || ""} language-${language}`.trim()),
118
+ ...children,
119
+ ];
120
+ }
121
+
122
+ async function highlightInlineCodeChildren(
123
+ parent: ComarkElement,
124
+ options: HighlightOptions,
125
+ ): Promise<void> {
126
+ const insidePre = parent[0] === "pre";
127
+
128
+ for (let index = 2; index < parent.length; index += 1) {
129
+ const child = parent[index] as ComarkNode;
130
+
131
+ if (!isElement(child)) {
132
+ continue;
133
+ }
134
+
135
+ if (!insidePre && child[0] === "code") {
136
+ parent[index] = await highlightInlineCodeNode(child, options);
137
+ continue;
138
+ }
139
+
140
+ await highlightInlineCodeChildren(child, options);
141
+ }
142
+ }
143
+
144
+ const inlineCodeHighlightPlugin: ComarkPluginFactory<HighlightOptions> = (options = {}) => ({
145
+ name: "inline-code-highlight",
146
+ async post(state) {
147
+ for (const node of state.tree.nodes) {
148
+ if (isElement(node)) {
149
+ await highlightInlineCodeChildren(node, options);
150
+ }
151
+ }
152
+ },
153
+ });
154
+
155
+ export default inlineCodeHighlightPlugin;
@@ -0,0 +1,21 @@
1
+ import {
2
+ registerAgentMarkdownSerializers,
3
+ type AgentMarkdownSerializer,
4
+ type AgentMarkdownSerializerMap,
5
+ } from "@lupinum/ginko-content/agent-registry";
6
+ import { contentComponentTags } from "../../tags";
7
+
8
+ const renderXmlComponent: AgentMarkdownSerializer = (node, ctx) =>
9
+ ctx.xmlComponent(node.tag || "component", ctx.cleanProps(node), ctx.renderChildren(node));
10
+
11
+ export const contentComponentAgentMarkdownTags = Object.keys(contentComponentTags);
12
+
13
+ export function registerContentComponentAgentMarkdownSerializers() {
14
+ const serializers: AgentMarkdownSerializerMap = {};
15
+ for (const [tag, component] of Object.entries(contentComponentTags)) {
16
+ serializers[tag] = renderXmlComponent;
17
+ serializers[component] = renderXmlComponent;
18
+ }
19
+
20
+ registerAgentMarkdownSerializers(serializers);
21
+ }
@@ -0,0 +1,7 @@
1
+ import type { ClassValue } from "clsx";
2
+ import { clsx } from "clsx";
3
+ import { twMerge } from "tailwind-merge";
4
+
5
+ export function cn(...inputs: ClassValue[]) {
6
+ return twMerge(clsx(inputs));
7
+ }
@@ -0,0 +1,14 @@
1
+ import { defineMcpTool } from "@nuxtjs/mcp-toolkit/server";
2
+ import { agentRawPathForRoute } from "@lupinum/ginko-content/agent-paths";
3
+ import { z } from "zod";
4
+
5
+ export default defineMcpTool({
6
+ description: "Read a documentation page as Markdown.",
7
+ annotations: { readOnlyHint: true },
8
+ inputSchema: {
9
+ path: z.string().startsWith("/").describe("Documentation path, for example /docs/introduction"),
10
+ },
11
+ handler: async ({ path }) => {
12
+ return await $fetch<string>(agentRawPathForRoute(path), { responseType: "text" });
13
+ },
14
+ });
@@ -0,0 +1,10 @@
1
+ import { defineMcpTool } from "@nuxtjs/mcp-toolkit/server";
2
+
3
+ export default defineMcpTool({
4
+ description: "List the documentation pages available to agents.",
5
+ annotations: { readOnlyHint: true },
6
+ handler: async () => {
7
+ const index = await $fetch<string>("/llms.txt", { responseType: "text" });
8
+ return index;
9
+ },
10
+ });
@@ -0,0 +1,6 @@
1
+ import { defineNitroPlugin } from "nitropack/runtime/plugin";
2
+ import { registerContentComponentAgentMarkdownSerializers } from "../../runtime/server/agent-markdown";
3
+
4
+ export default defineNitroPlugin(() => {
5
+ registerContentComponentAgentMarkdownSerializers();
6
+ });
@@ -0,0 +1,5 @@
1
+ export const routeSlugs = {
2
+ home: { en: "/", de: "/" },
3
+ docs: { en: "/docs", de: "/dokumentation" },
4
+ blog: { en: "/blog", de: "/blog" },
5
+ } as const;
@@ -0,0 +1,100 @@
1
+ export type GinkoDocsLocalizedText = { en: string; de?: string };
2
+
3
+ export interface GinkoDocsLink {
4
+ label: GinkoDocsLocalizedText;
5
+ to: GinkoDocsLocalizedText;
6
+ }
7
+
8
+ export type GinkoDocsHeroMedia =
9
+ | { type: "image"; src: string; alt: string }
10
+ | { type: "code"; code: string; language?: string; filename?: string };
11
+
12
+ export type GinkoDocsPlausibleExtension =
13
+ | "hash"
14
+ | "outbound-links"
15
+ | "file-downloads"
16
+ | "tagged-events"
17
+ | "revenue"
18
+ | "pageview-props"
19
+ | "compat"
20
+ | "local"
21
+ | "manual";
22
+
23
+ export interface GinkoDocsAppConfig {
24
+ site: {
25
+ name: GinkoDocsLocalizedText;
26
+ description: GinkoDocsLocalizedText;
27
+ url: string;
28
+ logo: { light: string; dark: string };
29
+ localeSwitcher: "dropdown" | "segmented";
30
+ docsSidebarSwitcher: "dropdown" | "list" | "tabs";
31
+ lupinumAttribution: boolean;
32
+ };
33
+ nav: {
34
+ /** "auto" derives Docs (+ Blog when blog routes exist); an array overrides entirely. */
35
+ links: "auto" | GinkoDocsLink[];
36
+ };
37
+ banner: {
38
+ /** "auto" shows the banner when blog routes exist (legacy behavior). */
39
+ enabled: boolean | "auto";
40
+ /** Dismissal storage key — change it to re-show the banner after an update. */
41
+ id: string;
42
+ text?: GinkoDocsLocalizedText;
43
+ link?: { label?: GinkoDocsLocalizedText; to?: GinkoDocsLocalizedText };
44
+ showOnLanding: boolean;
45
+ };
46
+ analytics?: {
47
+ plausible?: {
48
+ /** Leave unset to keep analytics fully disabled. */
49
+ domain?: string;
50
+ /** Self-hosted Plausible script URL override. */
51
+ scriptSrc?: string;
52
+ /** Plausible script extensions, e.g. ["outbound-links", "file-downloads"]. */
53
+ extensions?: GinkoDocsPlausibleExtension[];
54
+ };
55
+ };
56
+ social: {
57
+ github?: string;
58
+ linkedin?: string;
59
+ };
60
+ feedback: {
61
+ enabled: boolean;
62
+ };
63
+ ogImage: {
64
+ enabled: boolean;
65
+ /** OgImage template component name (file in app/components/OgImage/). */
66
+ component: string;
67
+ };
68
+ markdownActions: {
69
+ chatGpt: boolean;
70
+ claude: boolean;
71
+ mcp: boolean;
72
+ };
73
+ images: {
74
+ /** Click-to-zoom lightbox for prose images. */
75
+ zoom: boolean;
76
+ };
77
+ toc: {
78
+ depth: 2 | 3 | 4;
79
+ };
80
+ repository?: {
81
+ url: string;
82
+ branch?: string;
83
+ contentDirectory?: string;
84
+ };
85
+ landing: {
86
+ eyebrow?: GinkoDocsLocalizedText;
87
+ title: GinkoDocsLocalizedText;
88
+ description: GinkoDocsLocalizedText;
89
+ primary: GinkoDocsLink;
90
+ secondary?: GinkoDocsLink;
91
+ hero?: {
92
+ media?: GinkoDocsHeroMedia;
93
+ };
94
+ features: Array<{
95
+ title: GinkoDocsLocalizedText;
96
+ description: GinkoDocsLocalizedText;
97
+ icon?: string;
98
+ }>;
99
+ };
100
+ }