@lupinum/ginko-docs 0.4.0-rc.9 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/LICENSE +1 -1
  3. package/README.md +206 -54
  4. package/app/app.config.ts +42 -55
  5. package/app/assets/docs.css +555 -0
  6. package/app/components/DocsBanner.vue +29 -0
  7. package/app/components/DocsExample.vue +43 -0
  8. package/app/components/DocsFooter.vue +19 -0
  9. package/app/components/DocsHeader.vue +112 -0
  10. package/app/components/DocsLanding.vue +55 -0
  11. package/app/components/DocsLanguage.vue +29 -0
  12. package/app/components/DocsNavTree.vue +38 -0
  13. package/app/components/DocsPage.vue +84 -0
  14. package/app/components/DocsPageActions.vue +47 -0
  15. package/app/components/DocsPost.vue +58 -0
  16. package/app/components/DocsSearch.vue +134 -0
  17. package/app/components/DocsShell.vue +20 -0
  18. package/app/components/DocsSidebar.vue +18 -0
  19. package/app/components/DocsTable.vue +11 -0
  20. package/app/components/DocsThemeToggle.vue +45 -0
  21. package/app/components/DocsToc.vue +73 -0
  22. package/app/composables/useDocs.ts +52 -0
  23. package/app/composables/useDocsBlog.ts +32 -0
  24. package/app/composables/useDocsCopy.ts +17 -0
  25. package/app/composables/useDocsPopover.ts +36 -0
  26. package/app/composables/useDocsSite.ts +10 -0
  27. package/app/docs-pages/blog.vue +47 -0
  28. package/app/docs-pages/changelog.vue +27 -0
  29. package/app/docs-pages/doc.vue +14 -0
  30. package/app/docs-pages/docs.vue +9 -0
  31. package/app/docs-pages/index.vue +12 -0
  32. package/app/docs-pages/post.vue +8 -0
  33. package/app/error.vue +23 -115
  34. package/app/layouts/default.vue +25 -7
  35. package/app/lib/blogTypes.ts +8 -0
  36. package/app/lib/docsFirst.ts +11 -0
  37. package/app/lib/docsInit.ts +18 -0
  38. package/app/lib/docsNav.ts +36 -0
  39. package/app/lib/docsPage.ts +44 -0
  40. package/app/lib/markdownComponents.ts +16 -0
  41. package/app/lib/previewToc.ts +18 -0
  42. package/dist/config.d.mts +232 -0
  43. package/dist/config.d.ts +232 -0
  44. package/dist/config.mjs +114 -0
  45. package/dist/pages.d.mts +5 -0
  46. package/dist/pages.d.ts +5 -0
  47. package/dist/pages.mjs +36 -0
  48. package/dist/shared/ginko-docs.BwbF7JBR.mjs +4 -0
  49. package/i18n/de.json +1 -0
  50. package/i18n/en.json +1 -0
  51. package/icons/README.md +2 -0
  52. package/icons/bluesky.svg +1 -0
  53. package/icons/claude.svg +1 -0
  54. package/icons/discord.svg +1 -0
  55. package/icons/github.svg +1 -0
  56. package/icons/linkedin.svg +1 -0
  57. package/icons/mastodon.svg +1 -0
  58. package/icons/openai.svg +1 -0
  59. package/icons/x.svg +1 -0
  60. package/icons/youtube.svg +1 -0
  61. package/nuxt.config.ts +34 -209
  62. package/package.json +43 -76
  63. package/public/_docs/fonts/LICENSE +188 -0
  64. package/public/_docs/fonts/inter-latin-wght-normal.woff2 +0 -0
  65. package/public/_docs/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  66. package/public/_docs/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
  67. package/server/feeds/blog-locale.get.ts +9 -0
  68. package/server/feeds/blog.get.ts +4 -0
  69. package/server/lib/blogFeed.ts +35 -0
  70. package/server/lib/rss.ts +57 -0
  71. package/server/mcp/handler.ts +108 -0
  72. package/server/routes/__ginko/docs-mcp.ts +35 -0
  73. package/shared/paths.ts +4 -0
  74. package/app/app.vue +0 -98
  75. package/app/assets/css/prose.css +0 -3240
  76. package/app/assets/css/tailwind.css +0 -394
  77. package/app/assets/css/theme-palettes.css +0 -309
  78. package/app/assets/css/theme-presets.css +0 -141
  79. package/app/components/OgImage/GinkoDocs.satori.vue +0 -238
  80. package/app/components/content/DocumentPageShell.vue +0 -55
  81. package/app/components/content/Feedback.vue +0 -102
  82. package/app/components/content/ImageZoomDialog.vue +0 -84
  83. package/app/components/content/PageMarkdownCopy.vue +0 -104
  84. package/app/components/content/imageZoom.ts +0 -25
  85. package/app/components/mdc/MdcAccordion.vue +0 -67
  86. package/app/components/mdc/MdcAccordionItem.vue +0 -42
  87. package/app/components/mdc/MdcApi.vue +0 -159
  88. package/app/components/mdc/MdcAside.vue +0 -23
  89. package/app/components/mdc/MdcCallout.vue +0 -61
  90. package/app/components/mdc/MdcCard.vue +0 -175
  91. package/app/components/mdc/MdcCards.vue +0 -30
  92. package/app/components/mdc/MdcCenter.vue +0 -71
  93. package/app/components/mdc/MdcCodeGroup.vue +0 -197
  94. package/app/components/mdc/MdcCodeTree.vue +0 -234
  95. package/app/components/mdc/MdcCollapse.vue +0 -61
  96. package/app/components/mdc/MdcColumn.vue +0 -70
  97. package/app/components/mdc/MdcDropcap.vue +0 -84
  98. package/app/components/mdc/MdcError.vue +0 -23
  99. package/app/components/mdc/MdcExcerpt.vue +0 -26
  100. package/app/components/mdc/MdcFigure.vue +0 -85
  101. package/app/components/mdc/MdcFiles.vue +0 -120
  102. package/app/components/mdc/MdcIdea.vue +0 -17
  103. package/app/components/mdc/MdcInfo.vue +0 -17
  104. package/app/components/mdc/MdcInlineToc.vue +0 -51
  105. package/app/components/mdc/MdcKbd.vue +0 -10
  106. package/app/components/mdc/MdcLayout.vue +0 -57
  107. package/app/components/mdc/MdcNote.vue +0 -17
  108. package/app/components/mdc/MdcQuiz.vue +0 -241
  109. package/app/components/mdc/MdcQuizQuestion.vue +0 -221
  110. package/app/components/mdc/MdcReadMore.vue +0 -70
  111. package/app/components/mdc/MdcSteps.vue +0 -59
  112. package/app/components/mdc/MdcSuccess.vue +0 -23
  113. package/app/components/mdc/MdcTab.vue +0 -17
  114. package/app/components/mdc/MdcTabs.vue +0 -150
  115. package/app/components/mdc/MdcTimeline.vue +0 -17
  116. package/app/components/mdc/MdcTimelineItem.vue +0 -40
  117. package/app/components/mdc/MdcWarning.vue +0 -23
  118. package/app/components/mdc/api.utils.ts +0 -132
  119. package/app/components/mdc/code-tree.utils.ts +0 -22
  120. package/app/components/mdc/icons.ts +0 -10
  121. package/app/components/mdc/quiz.utils.ts +0 -38
  122. package/app/components/prose/ProseA.vue +0 -27
  123. package/app/components/prose/ProseBlockquote.vue +0 -30
  124. package/app/components/prose/ProseCode.vue +0 -14
  125. package/app/components/prose/ProseEm.vue +0 -14
  126. package/app/components/prose/ProseH1.vue +0 -18
  127. package/app/components/prose/ProseH2.vue +0 -29
  128. package/app/components/prose/ProseH3.vue +0 -29
  129. package/app/components/prose/ProseH4.vue +0 -29
  130. package/app/components/prose/ProseH5.vue +0 -15
  131. package/app/components/prose/ProseH6.vue +0 -15
  132. package/app/components/prose/ProseHr.vue +0 -12
  133. package/app/components/prose/ProseImg.vue +0 -55
  134. package/app/components/prose/ProseLi.vue +0 -14
  135. package/app/components/prose/ProseOl.vue +0 -14
  136. package/app/components/prose/ProseP.vue +0 -14
  137. package/app/components/prose/ProsePre.vue +0 -180
  138. package/app/components/prose/ProseStrong.vue +0 -14
  139. package/app/components/prose/ProseTable.vue +0 -18
  140. package/app/components/prose/ProseTbody.vue +0 -14
  141. package/app/components/prose/ProseTd.vue +0 -14
  142. package/app/components/prose/ProseTh.vue +0 -14
  143. package/app/components/prose/ProseThead.vue +0 -14
  144. package/app/components/prose/ProseTr.vue +0 -14
  145. package/app/components/prose/ProseUl.vue +0 -14
  146. package/app/components/site/ModeToggle.vue +0 -81
  147. package/app/components/site/SiteBanner.vue +0 -97
  148. package/app/components/site/SiteFooter.vue +0 -90
  149. package/app/components/site/SiteHeader.vue +0 -371
  150. package/app/components/site/SiteHeroCode.vue +0 -173
  151. package/app/components/site/SiteInteractionLayer.vue +0 -42
  152. package/app/components/site/SiteLocaleSwitcher.vue +0 -190
  153. package/app/components/site/SiteLogoMark.vue +0 -12
  154. package/app/components/site/SiteSkipLink.vue +0 -14
  155. package/app/components/site/SiteSocialLinks.vue +0 -33
  156. package/app/components/site/header-utils.ts +0 -3
  157. package/app/components/site/mode-toggle.utils.ts +0 -18
  158. package/app/components/ui/badge/Badge.vue +0 -28
  159. package/app/components/ui/badge/index.ts +0 -24
  160. package/app/components/ui/button/Button.vue +0 -31
  161. package/app/components/ui/button/index.ts +0 -35
  162. package/app/components/ui/collapsible/Collapsible.vue +0 -15
  163. package/app/components/ui/collapsible/CollapsibleContent.vue +0 -25
  164. package/app/components/ui/collapsible/CollapsibleTrigger.vue +0 -12
  165. package/app/components/ui/collapsible/index.ts +0 -3
  166. package/app/components/ui/dropdown-menu/DropdownMenu.vue +0 -15
  167. package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +0 -50
  168. package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +0 -41
  169. package/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +0 -15
  170. package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +0 -37
  171. package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +0 -23
  172. package/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue +0 -14
  173. package/app/components/ui/dropdown-menu/index.ts +0 -7
  174. package/app/components/ui/kbd/Kbd.vue +0 -23
  175. package/app/components/ui/kbd/index.ts +0 -21
  176. package/app/components/ui/scroll-area/ScrollArea.vue +0 -39
  177. package/app/components/ui/scroll-area/ScrollBar.vue +0 -33
  178. package/app/components/ui/scroll-area/index.ts +0 -2
  179. package/app/components/ui/separator/Separator.vue +0 -27
  180. package/app/components/ui/separator/index.ts +0 -1
  181. package/app/components/ui/sheet/Sheet.vue +0 -15
  182. package/app/components/ui/sheet/SheetContent.vue +0 -59
  183. package/app/components/ui/sheet/SheetDescription.vue +0 -21
  184. package/app/components/ui/sheet/SheetOverlay.vue +0 -26
  185. package/app/components/ui/sheet/SheetTitle.vue +0 -21
  186. package/app/components/ui/sheet/SheetTrigger.vue +0 -12
  187. package/app/components/ui/sheet/index.ts +0 -5
  188. package/app/components/ui/tabs/Tabs.vue +0 -24
  189. package/app/components/ui/tabs/TabsList.vue +0 -26
  190. package/app/components/ui/tabs/TabsTrigger.vue +0 -28
  191. package/app/components/ui/tabs/index.ts +0 -3
  192. package/app/composables/site-navigation.utils.ts +0 -133
  193. package/app/composables/useCanonicalUrl.ts +0 -9
  194. package/app/composables/useContentRouteAlternates.ts +0 -35
  195. package/app/composables/useGinkoAnalytics.ts +0 -52
  196. package/app/composables/useGinkoDocsConfig.ts +0 -6
  197. package/app/composables/useGinkoOgImage.ts +0 -20
  198. package/app/composables/useLocalizedPath.ts +0 -12
  199. package/app/composables/useLocalizedRouteSwitch.ts +0 -31
  200. package/app/composables/useMetaKey.ts +0 -18
  201. package/app/composables/usePageMarkdownActions.ts +0 -79
  202. package/app/composables/useProseAppearance.ts +0 -24
  203. package/app/composables/useSchemaJsonLd.ts +0 -25
  204. package/app/composables/useSiteNavigation.ts +0 -64
  205. package/app/config/site.utils.ts +0 -36
  206. package/app/features/docs/components/DocsBreadcrumb.vue +0 -37
  207. package/app/features/docs/components/DocsContributeLinks.vue +0 -75
  208. package/app/features/docs/components/DocsMobileToc.vue +0 -108
  209. package/app/features/docs/components/DocsPageContent.vue +0 -234
  210. package/app/features/docs/components/DocsPageNav.vue +0 -57
  211. package/app/features/docs/components/DocsSidebar.vue +0 -145
  212. package/app/features/docs/components/DocsSidebarDropdown.vue +0 -70
  213. package/app/features/docs/components/DocsSidebarItem.vue +0 -122
  214. package/app/features/docs/components/DocsSidebarList.vue +0 -35
  215. package/app/features/docs/components/DocsSidebarRow.vue +0 -27
  216. package/app/features/docs/components/DocsSidebarTabs.vue +0 -59
  217. package/app/features/docs/components/DocsToc.vue +0 -106
  218. package/app/features/docs/composables/useDocsNavigation.ts +0 -29
  219. package/app/features/docs/composables/useDocsNavigationData.ts +0 -18
  220. package/app/features/docs/composables/useRevealActive.ts +0 -58
  221. package/app/features/docs/composables/useScrollspy.ts +0 -135
  222. package/app/features/docs/docs-navigation.ts +0 -157
  223. package/app/features/docs/toc-context.ts +0 -6
  224. package/app/features/search/command-center.ts +0 -110
  225. package/app/features/search/components/SiteCommandCenter.vue +0 -359
  226. package/app/features/search/components/SiteSearchPageHighlight.vue +0 -126
  227. package/app/features/search/useCommandCenter.ts +0 -292
  228. package/app/layouts/blog.vue +0 -15
  229. package/app/layouts/docs.vue +0 -20
  230. package/app/lib/errors.ts +0 -37
  231. package/app/lib/schema-org.ts +0 -38
  232. package/app/pages/blog/[slug].vue +0 -228
  233. package/app/pages/blog/index.vue +0 -130
  234. package/app/pages/docs/[...slug].vue +0 -10
  235. package/app/pages/docs/index.vue +0 -19
  236. package/app/pages/index.vue +0 -278
  237. package/app/plugins/ginko-docs-theme.ts +0 -14
  238. package/app/plugins/ssr-width.ts +0 -6
  239. package/app/utils/content.ts +0 -67
  240. package/app/utils/file-icons.ts +0 -90
  241. package/app/utils/index.ts +0 -7
  242. package/app/utils/repository.ts +0 -20
  243. package/components.ts +0 -9
  244. package/content-collections.ts +0 -89
  245. package/content.js +0 -200
  246. package/content.ts +0 -104
  247. package/i18n/i18n.config.ts +0 -10
  248. package/i18n/locales.ts +0 -81
  249. package/i18n/messages/global/banner.ts +0 -8
  250. package/i18n/messages/global/blog.ts +0 -18
  251. package/i18n/messages/global/command.ts +0 -49
  252. package/i18n/messages/global/docs.ts +0 -36
  253. package/i18n/messages/global/errors.ts +0 -36
  254. package/i18n/messages/global/feedback.ts +0 -11
  255. package/i18n/messages/global/index.ts +0 -21
  256. package/i18n/messages/global/nav.ts +0 -26
  257. package/i18n/messages/global/site.ts +0 -4
  258. package/i18n/messages/global/theme.ts +0 -9
  259. package/i18n/messages/index.ts +0 -20
  260. package/i18n/messages/localize.ts +0 -35
  261. package/i18n/routes.ts +0 -33
  262. package/icon-bundle.ts +0 -116
  263. package/index.d.ts +0 -37
  264. package/modules/analytics-boundary.ts +0 -23
  265. package/modules/feature-routing.ts +0 -68
  266. package/modules/syntax-highlighting.ts +0 -120
  267. package/public/fonts/public-sans/LICENSE +0 -93
  268. package/public/fonts/public-sans/README.md +0 -12
  269. package/public/fonts/public-sans/public-sans-400-normal-latin.woff +0 -0
  270. package/public/fonts/public-sans/public-sans-500-normal-latin.woff +0 -0
  271. package/public/fonts/public-sans/public-sans-600-normal-latin.woff +0 -0
  272. package/public/fonts/public-sans/public-sans-700-normal-latin.woff +0 -0
  273. package/runtime/generate-favicons.mjs +0 -124
  274. package/runtime/server/agent-markdown.ts +0 -19
  275. package/server/api/_ginko-docs/redirects.get.ts +0 -28
  276. package/server/mcp/tools/get-page.ts +0 -14
  277. package/server/mcp/tools/list-pages.ts +0 -10
  278. package/server/middleware/redirects.ts +0 -22
  279. package/server/plugins/agent-markdown.ts +0 -6
  280. package/server/plugins/sitemap-policy.ts +0 -12
  281. package/server/routes/.well-known/ai-catalog.json.get.ts +0 -10
  282. package/server/routes/[locale]/blog/rss.xml.get.ts +0 -13
  283. package/server/routes/blog/rss.xml.get.ts +0 -5
  284. package/server/routes/mcp/server-card.get.ts +0 -14
  285. package/server/utils/blog-feed.ts +0 -51
  286. package/server/utils/feed.ts +0 -70
  287. package/server/utils/mcp-server-card.ts +0 -45
  288. package/server/utils/redirects.ts +0 -38
  289. package/server/utils/redirects.utils.ts +0 -58
  290. package/server/utils/sitemap.ts +0 -45
  291. package/shared/route-slugs.ts +0 -5
  292. package/shared/theme-palettes.ts +0 -37
  293. package/shared/theme-presets.ts +0 -3
  294. package/shared/types/app-config.ts +0 -172
  295. package/shared/types/nuxt-config.ts +0 -14
  296. package/tags.ts +0 -192
@@ -0,0 +1,11 @@
1
+ import { useDocs } from '../composables/useDocs'
2
+
3
+ /**
4
+ * Leads to the first docs page of the language. A site without any docs page yet gets a 404 instead: a redirect to
5
+ * `/` would loop, because `/` leads here when the site has no start page.
6
+ */
7
+ export function toFirstDoc() {
8
+ const first = useDocs().first.value
9
+ if (!first) throw createError({ statusCode: 404, statusMessage: 'No docs page yet: add a Markdown file to content/docs/.' })
10
+ return navigateTo(first, { redirectCode: 302 })
11
+ }
@@ -0,0 +1,18 @@
1
+ // Storage keys and the head scripts of the color-mode switch and the banner. Each component adds its own script
2
+ // (useHead with a fixed key, so two toggles on one page add it once), so they also work inside a site's own layout.
3
+ export const COLOR_KEY = 'docs-color-mode'
4
+ export const bannerKey = (id: string) => `docs-banner:${id}`
5
+
6
+ // Runs inline in <head>, before first paint. localStorage can throw (private windows, blocked site data); then the
7
+ // page simply follows the system and shows the banner.
8
+ const guarded = (body: string) => `(function(){try{var d=document.documentElement;${body}}catch(e){}})()`
9
+
10
+ /** Applies the stored light/dark choice (html[data-theme]) before first paint. */
11
+ export function colorInitScript(): string {
12
+ return guarded(`var m=localStorage.getItem(${JSON.stringify(COLOR_KEY)});if(m==='light'||m==='dark')d.setAttribute('data-theme',m);`)
13
+ }
14
+
15
+ /** Marks a dismissed banner (html[data-docs-banner-off]) before first paint, so it does not flash. */
16
+ export function bannerInitScript(id: string): string {
17
+ return guarded(`if(localStorage.getItem(${JSON.stringify(bannerKey(id))})==='1')d.setAttribute('data-docs-banner-off','');`)
18
+ }
@@ -0,0 +1,36 @@
1
+ // Tabs and sections of the side navigation, from the entries' fields. Pure, so the rules have a test.
2
+ import type { NavNode } from '@lupinum/ginko'
3
+
4
+ export interface DocNode extends NavNode {
5
+ section?: string
6
+ badge?: string
7
+ icon?: string
8
+ tab?: string
9
+ children: DocNode[]
10
+ }
11
+ export interface DocTab { title: string; path: string }
12
+
13
+ export interface DocFields { tab?: string; section?: string; badge?: string; icon?: string }
14
+
15
+ /**
16
+ * A root without a tab follows the one before it; a new tab starts without a section; a section runs until the
17
+ * next one. Children always follow their parent's tab and carry no section.
18
+ */
19
+ export function enrichNav(tree: NavNode[], fieldsOf: (id: string) => DocFields | undefined): DocNode[] {
20
+ let running: string | undefined
21
+ let section: string | undefined
22
+ const enrich = (node: NavNode, tab: string | undefined, root: boolean): DocNode => {
23
+ const data = fieldsOf(node.id ?? '')
24
+ if (root) {
25
+ if (data?.tab && data.tab !== running) section = undefined
26
+ running = data?.tab ?? running
27
+ section = data?.section ?? section
28
+ }
29
+ const own = root ? running : tab
30
+ return {
31
+ ...node, tab: own, section: root ? section : undefined, badge: data?.badge, icon: data?.icon,
32
+ children: node.children.map((c) => enrich(c, own, false)),
33
+ }
34
+ }
35
+ return tree.map((n) => enrich(n, undefined, true))
36
+ }
@@ -0,0 +1,44 @@
1
+ // Small pure helpers of the docs page (edit link, Markdown twin, "open in" links), kept apart for tests.
2
+
3
+ /** Edit link of a page's file. Template placeholders: `{url}`, `{branch}`, `{source}`; default is the GitHub form. */
4
+ export function docsEditUrl(repo: { url: string, branch?: string, editUrl?: string } | undefined, source: string | undefined): string | undefined {
5
+ if (!repo?.url || !source) return undefined
6
+ const values: Record<string, string> = { url: repo.url.replace(/\/+$/, ''), branch: repo.branch ?? 'main', source }
7
+ return (repo.editUrl ?? '{url}/edit/{branch}/{source}').replace(/\{(url|branch|source)\}/g, (_, k: string) => values[k]!)
8
+ }
9
+
10
+ /**
11
+ * Edit link of a page stored on the Ginko platform: its form in the Studio, `<platform>/s/<site>/all/<brick>/<id>`.
12
+ * No platform address (a production build without NUXT_PUBLIC_GINKO_PLATFORM): no link.
13
+ */
14
+ export function docsStudioUrl(platform: string | undefined, site: string, brick: string, id: string): string | undefined {
15
+ if (!platform) return undefined
16
+ return `${platform.replace(/\/+$/, '')}/s/${[site, 'all', brick, id].map(encodeURIComponent).join('/')}`
17
+ }
18
+
19
+ /**
20
+ * "Report an issue" for a page. Placeholders: `{url}`, `{title}`, `{page}` (the page's address), URL-encoded except
21
+ * `{url}`. GitHub repositories get the GitHub form by default; other hosts need `issueUrl`.
22
+ */
23
+ export function docsIssueUrl(repo: { url: string, issueUrl?: string } | undefined, title: string, page: string): string | undefined {
24
+ if (!repo?.url) return undefined
25
+ const url = repo.url.replace(/\/+$/, '')
26
+ const template = repo.issueUrl ?? (/^https:\/\/github\.com\//.test(url) ? '{url}/issues/new?title={title}&body={page}' : undefined)
27
+ const values: Record<string, string> = { url, title: encodeURIComponent(title), page: encodeURIComponent(page) }
28
+ return template?.replace(/\{(url|title|page)\}/g, (_, k: string) => values[k]!)
29
+ }
30
+
31
+ /** Path of a page's Markdown twin (`/en/docs/x` -> `/en/docs/x.md`, `/` -> `/index.md`). */
32
+ export function docsMarkdownPath(path: string): string {
33
+ const clean = path.replace(/\/+$/, '')
34
+ return `${clean || '/index'}.md`
35
+ }
36
+
37
+ /** A chat the page menu can open the page in (`markdownActions.openIn`). */
38
+ export type DocsAssistant = 'chatgpt' | 'claude'
39
+
40
+ /** Links that open a chat with a prompt pointing at the page's Markdown. */
41
+ export function docsAskLinks(prompt: string): Record<DocsAssistant, string> {
42
+ const q = encodeURIComponent(prompt)
43
+ return { chatgpt: `https://chatgpt.com/?q=${q}`, claude: `https://claude.ai/new?q=${q}` }
44
+ }
@@ -0,0 +1,16 @@
1
+ // Math, mermaid and ```md preview examples for the docs layer. GinkoMarkdown shows their source by default (core stays free of KaTeX);
2
+ // a `components` map wins. Both load lazily, so a page without a formula or a diagram never loads KaTeX or mermaid.
3
+ // Math renders on the server (the HTML holds `.katex`); mermaid renders in the browser.
4
+ import { defineAsyncComponent } from 'vue'
5
+ import DocsExample from '../components/DocsExample.vue'
6
+ import DocsTable from '../components/DocsTable.vue'
7
+
8
+ export const markdownComponents = {
9
+ math: defineAsyncComponent(async () => {
10
+ const [, { Math }] = await Promise.all([import('katex/dist/katex.min.css'), import('@comark/vue/plugins/math')])
11
+ return Math
12
+ }),
13
+ mermaid: defineAsyncComponent(() => import('@comark/vue/plugins/mermaid').then((m) => m.Mermaid)),
14
+ example: DocsExample,
15
+ table: DocsTable,
16
+ }
@@ -0,0 +1,18 @@
1
+ // The table of contents of a ```md preview: ginko-content keeps only the preview's nodes, so an `::inline-toc`
2
+ // inside a preview gets its links from here. Same shape as the page toc: h2 links with their h3 as children.
3
+ import type { MarkdownToc, TocLink } from '@lupinum/ginko'
4
+
5
+ type Node = string | [string, Record<string, unknown>, ...unknown[]]
6
+ const textOf = (node: Node): string => (typeof node === 'string' ? node : (node.slice(2) as Node[]).map(textOf).join(''))
7
+
8
+ export function previewToc(nodes: readonly unknown[]): MarkdownToc {
9
+ const links: TocLink[] = []
10
+ for (const node of nodes as Node[]) {
11
+ if (typeof node === 'string' || (node[0] !== 'h2' && node[0] !== 'h3') || typeof node[1].id !== 'string') continue
12
+ const link: TocLink = { id: node[1].id, depth: node[0] === 'h2' ? 2 : 3, text: textOf(node).trim() }
13
+ const parent = links.at(-1)
14
+ if (link.depth === 3 && parent?.depth === 2) (parent.children ??= []).push(link)
15
+ else links.push(link)
16
+ }
17
+ return { links }
18
+ }
@@ -0,0 +1,232 @@
1
+ import * as _lupinum_ginko from '@lupinum/ginko';
2
+ import { Locale } from '@lupinum/ginko';
3
+ import * as _lupinum_ginko_internal from '@lupinum/ginko/internal';
4
+ export { components } from '@lupinum/ginko-components/registry';
5
+
6
+ /** The site's locales, main locale first. The same list as `locales` in `defineSite`, as a plain array or a tuple. */
7
+ type Locales = readonly Locale[];
8
+ /** `base`: where the docs live, default `/docs` (e.g. `/handbook` inside a larger site). */
9
+ declare function docsCollection(opts: {
10
+ locales: Locales;
11
+ label?: string;
12
+ max?: number;
13
+ base?: `/${string}`;
14
+ }): _lupinum_ginko.CollectionBrick<{
15
+ readonly title: _lupinum_ginko_internal.TextField & Omit<_lupinum_ginko_internal.TextField, "type" | "label" | "pos" | "retired" | "default"> & {
16
+ default?: (string | Partial<Record<string, string>>) | undefined;
17
+ };
18
+ readonly description: _lupinum_ginko_internal.LongTextField & {
19
+ readonly optional: true;
20
+ };
21
+ /** Starts a group in the side navigation under this heading; the pages after it belong to it until the next one. */
22
+ readonly section: _lupinum_ginko_internal.TextField & {
23
+ readonly optional: true;
24
+ };
25
+ /** Nests the entry under another doc in the side navigation and the breadcrumbs. */
26
+ readonly parent: _lupinum_ginko_internal.RefField & {
27
+ multiple?: false;
28
+ } & {
29
+ readonly to: "docs";
30
+ readonly optional: true;
31
+ };
32
+ /** Section tab above the side navigation; a page without one follows the tab of the page before it. Children follow their parent. */
33
+ readonly tab: _lupinum_ginko_internal.TextField & {
34
+ readonly optional: true;
35
+ readonly max: 30;
36
+ };
37
+ /** A short label next to the entry in the side navigation, e.g. "New". */
38
+ readonly badge: _lupinum_ginko_internal.TextField & {
39
+ readonly optional: true;
40
+ readonly max: 20;
41
+ };
42
+ /** An icon name such as `lucide:book-open` (any installed Iconify set), shown before the title in the side navigation. */
43
+ readonly icon: _lupinum_ginko_internal.TextField & {
44
+ readonly optional: true;
45
+ readonly max: 60;
46
+ readonly translate: false;
47
+ };
48
+ readonly slug: _lupinum_ginko_internal.SlugField & {
49
+ readonly from: "title";
50
+ };
51
+ readonly body: _lupinum_ginko_internal.MarkdownField & {
52
+ readonly profile: "page";
53
+ };
54
+ }, Record<string, `/${string}/[slug]`>>;
55
+ /**
56
+ * The optional start page: one Markdown file `content/home[.<locale>].md`. The frontmatter fills the hero (title,
57
+ * description, install command, up to two links) and the feature grid; the text below the frontmatter follows them and
58
+ * may use every component. Without it, `/` opens the first docs page. Add `home: { <main>: '/', <l>: '/<l>' }` to `pages`.
59
+ */
60
+ declare function docsHome(): _lupinum_ginko.SingleBrick<{
61
+ readonly title: _lupinum_ginko_internal.TextField & {
62
+ readonly max: 120;
63
+ };
64
+ readonly description: _lupinum_ginko_internal.LongTextField & {
65
+ readonly optional: true;
66
+ readonly max: 400;
67
+ };
68
+ /** A command shown with a copy button, e.g. `pnpm add @lupinum/ginko`. */
69
+ readonly install: _lupinum_ginko_internal.TextField & {
70
+ readonly optional: true;
71
+ readonly max: 200;
72
+ readonly translate: false;
73
+ };
74
+ /** Buttons under the description; the first one is the main one. */
75
+ readonly links: _lupinum_ginko_internal.ListField<{
76
+ readonly label: _lupinum_ginko_internal.TextField & {
77
+ readonly max: 40;
78
+ };
79
+ readonly href: _lupinum_ginko_internal.TextField & {
80
+ readonly max: 300;
81
+ };
82
+ }> & Omit<{
83
+ readonly itemLabel: "Link";
84
+ readonly fields: {
85
+ readonly label: _lupinum_ginko_internal.TextField & {
86
+ readonly max: 40;
87
+ };
88
+ readonly href: _lupinum_ginko_internal.TextField & {
89
+ readonly max: 300;
90
+ };
91
+ };
92
+ readonly max: 2;
93
+ readonly default: readonly [];
94
+ }, "fields">;
95
+ readonly features: _lupinum_ginko_internal.ListField<{
96
+ readonly icon: _lupinum_ginko_internal.TextField & {
97
+ readonly optional: true;
98
+ readonly max: 60;
99
+ readonly translate: false;
100
+ };
101
+ readonly title: _lupinum_ginko_internal.TextField & {
102
+ readonly max: 80;
103
+ };
104
+ readonly description: _lupinum_ginko_internal.LongTextField & {
105
+ readonly max: 300;
106
+ };
107
+ readonly href: _lupinum_ginko_internal.TextField & {
108
+ readonly optional: true;
109
+ readonly max: 300;
110
+ };
111
+ }> & Omit<{
112
+ readonly itemLabel: "Feature";
113
+ readonly fields: {
114
+ readonly icon: _lupinum_ginko_internal.TextField & {
115
+ readonly optional: true;
116
+ readonly max: 60;
117
+ readonly translate: false;
118
+ };
119
+ readonly title: _lupinum_ginko_internal.TextField & {
120
+ readonly max: 80;
121
+ };
122
+ readonly description: _lupinum_ginko_internal.LongTextField & {
123
+ readonly max: 300;
124
+ };
125
+ readonly href: _lupinum_ginko_internal.TextField & {
126
+ readonly optional: true;
127
+ readonly max: 300;
128
+ };
129
+ };
130
+ readonly max: 12;
131
+ readonly default: readonly [];
132
+ }, "fields">;
133
+ readonly body: _lupinum_ginko_internal.MarkdownField & {
134
+ readonly profile: "page";
135
+ readonly optional: true;
136
+ };
137
+ }>;
138
+ /**
139
+ * The optional blog: `{ posts, authors }`, spread into `bricks` in ginko.config.ts. Posts live at
140
+ * /blog/<slug> (main locale unprefixed, `/<l>/blog/<slug>` otherwise) and come out newest first.
141
+ * Authors have no page; a post names them in `authors`. Files: `content/posts/<n>.<name>[.<locale>].md`
142
+ * and `content/authors/<name>[.<locale>].yml`. Add `blog: blogPages({ locales })` to `pages` as well.
143
+ */
144
+ declare function blogCollections(opts: {
145
+ locales: Locales;
146
+ max?: number;
147
+ }): {
148
+ posts: _lupinum_ginko.CollectionBrick<{
149
+ readonly title: _lupinum_ginko_internal.TextField & Omit<_lupinum_ginko_internal.TextField, "type" | "label" | "pos" | "retired" | "default"> & {
150
+ default?: (string | Partial<Record<string, string>>) | undefined;
151
+ };
152
+ readonly description: _lupinum_ginko_internal.LongTextField & {
153
+ readonly optional: true;
154
+ };
155
+ readonly date: _lupinum_ginko_internal.DateField & Omit<_lupinum_ginko_internal.DateField, "type" | "label" | "pos" | "retired" | "default"> & {
156
+ default?: string | undefined;
157
+ };
158
+ readonly authors: _lupinum_ginko_internal.RefField & {
159
+ multiple: true;
160
+ } & {
161
+ readonly to: "authors";
162
+ readonly optional: true;
163
+ };
164
+ readonly image: _lupinum_ginko_internal.ImageField & {
165
+ readonly optional: true;
166
+ };
167
+ readonly tags: _lupinum_ginko_internal.ListField<{
168
+ readonly tag: _lupinum_ginko_internal.TextField & {
169
+ readonly max: 40;
170
+ };
171
+ }> & Omit<{
172
+ readonly itemLabel: "Tag";
173
+ readonly fields: {
174
+ readonly tag: _lupinum_ginko_internal.TextField & {
175
+ readonly max: 40;
176
+ };
177
+ };
178
+ readonly max: 10;
179
+ readonly optional: true;
180
+ }, "fields">;
181
+ readonly slug: _lupinum_ginko_internal.SlugField & {
182
+ readonly from: "title";
183
+ };
184
+ readonly body: _lupinum_ginko_internal.MarkdownField & {
185
+ readonly profile: "page";
186
+ };
187
+ }, Record<string, "/blog/[slug]">>;
188
+ authors: _lupinum_ginko.CollectionBrick<{
189
+ readonly name: _lupinum_ginko_internal.TextField & Omit<_lupinum_ginko_internal.TextField, "type" | "label" | "pos" | "retired" | "default"> & {
190
+ default?: (string | Partial<Record<string, string>>) | undefined;
191
+ };
192
+ readonly role: _lupinum_ginko_internal.TextField & {
193
+ readonly optional: true;
194
+ };
195
+ readonly avatar: _lupinum_ginko_internal.ImageField & {
196
+ readonly optional: true;
197
+ };
198
+ /** Shown as a link on the author's name. */
199
+ readonly url: _lupinum_ginko_internal.TextField & {
200
+ readonly format: "url";
201
+ readonly optional: true;
202
+ };
203
+ }, undefined>;
204
+ };
205
+ /**
206
+ * The optional changelog: one page at /changelog with one entry per release, newest first. Entries have no
207
+ * page of their own, the page links to each by anchor. Files: `content/changelog/<name>[.<locale>].md`
208
+ * with `version` and `date` in the frontmatter (a dot in the name would read as a language: use `v1-2-0.md`). Add `changelog: changelogPages({ locales })` to `pages` as well.
209
+ */
210
+ declare function changelogCollection(opts?: {
211
+ max?: number;
212
+ }): _lupinum_ginko.CollectionBrick<{
213
+ readonly version: _lupinum_ginko_internal.TextField & {
214
+ readonly max: 40;
215
+ readonly translate: false;
216
+ };
217
+ readonly date: _lupinum_ginko_internal.DateField & Omit<_lupinum_ginko_internal.DateField, "type" | "label" | "pos" | "retired" | "default"> & {
218
+ default?: string | undefined;
219
+ };
220
+ readonly body: _lupinum_ginko_internal.MarkdownField & {
221
+ readonly profile: "page";
222
+ };
223
+ }, undefined>;
224
+ /** The list pages by locale, for `pages` in ginko.config.ts: the sitemap and the static build know them from there. */
225
+ declare const blogPages: (opts: {
226
+ locales: Locales;
227
+ }) => Record<string, "/blog">;
228
+ declare const changelogPages: (opts: {
229
+ locales: Locales;
230
+ }) => Record<string, "/changelog">;
231
+
232
+ export { blogCollections, blogPages, changelogCollection, changelogPages, docsCollection, docsHome };
@@ -0,0 +1,232 @@
1
+ import * as _lupinum_ginko from '@lupinum/ginko';
2
+ import { Locale } from '@lupinum/ginko';
3
+ import * as _lupinum_ginko_internal from '@lupinum/ginko/internal';
4
+ export { components } from '@lupinum/ginko-components/registry';
5
+
6
+ /** The site's locales, main locale first. The same list as `locales` in `defineSite`, as a plain array or a tuple. */
7
+ type Locales = readonly Locale[];
8
+ /** `base`: where the docs live, default `/docs` (e.g. `/handbook` inside a larger site). */
9
+ declare function docsCollection(opts: {
10
+ locales: Locales;
11
+ label?: string;
12
+ max?: number;
13
+ base?: `/${string}`;
14
+ }): _lupinum_ginko.CollectionBrick<{
15
+ readonly title: _lupinum_ginko_internal.TextField & Omit<_lupinum_ginko_internal.TextField, "type" | "label" | "pos" | "retired" | "default"> & {
16
+ default?: (string | Partial<Record<string, string>>) | undefined;
17
+ };
18
+ readonly description: _lupinum_ginko_internal.LongTextField & {
19
+ readonly optional: true;
20
+ };
21
+ /** Starts a group in the side navigation under this heading; the pages after it belong to it until the next one. */
22
+ readonly section: _lupinum_ginko_internal.TextField & {
23
+ readonly optional: true;
24
+ };
25
+ /** Nests the entry under another doc in the side navigation and the breadcrumbs. */
26
+ readonly parent: _lupinum_ginko_internal.RefField & {
27
+ multiple?: false;
28
+ } & {
29
+ readonly to: "docs";
30
+ readonly optional: true;
31
+ };
32
+ /** Section tab above the side navigation; a page without one follows the tab of the page before it. Children follow their parent. */
33
+ readonly tab: _lupinum_ginko_internal.TextField & {
34
+ readonly optional: true;
35
+ readonly max: 30;
36
+ };
37
+ /** A short label next to the entry in the side navigation, e.g. "New". */
38
+ readonly badge: _lupinum_ginko_internal.TextField & {
39
+ readonly optional: true;
40
+ readonly max: 20;
41
+ };
42
+ /** An icon name such as `lucide:book-open` (any installed Iconify set), shown before the title in the side navigation. */
43
+ readonly icon: _lupinum_ginko_internal.TextField & {
44
+ readonly optional: true;
45
+ readonly max: 60;
46
+ readonly translate: false;
47
+ };
48
+ readonly slug: _lupinum_ginko_internal.SlugField & {
49
+ readonly from: "title";
50
+ };
51
+ readonly body: _lupinum_ginko_internal.MarkdownField & {
52
+ readonly profile: "page";
53
+ };
54
+ }, Record<string, `/${string}/[slug]`>>;
55
+ /**
56
+ * The optional start page: one Markdown file `content/home[.<locale>].md`. The frontmatter fills the hero (title,
57
+ * description, install command, up to two links) and the feature grid; the text below the frontmatter follows them and
58
+ * may use every component. Without it, `/` opens the first docs page. Add `home: { <main>: '/', <l>: '/<l>' }` to `pages`.
59
+ */
60
+ declare function docsHome(): _lupinum_ginko.SingleBrick<{
61
+ readonly title: _lupinum_ginko_internal.TextField & {
62
+ readonly max: 120;
63
+ };
64
+ readonly description: _lupinum_ginko_internal.LongTextField & {
65
+ readonly optional: true;
66
+ readonly max: 400;
67
+ };
68
+ /** A command shown with a copy button, e.g. `pnpm add @lupinum/ginko`. */
69
+ readonly install: _lupinum_ginko_internal.TextField & {
70
+ readonly optional: true;
71
+ readonly max: 200;
72
+ readonly translate: false;
73
+ };
74
+ /** Buttons under the description; the first one is the main one. */
75
+ readonly links: _lupinum_ginko_internal.ListField<{
76
+ readonly label: _lupinum_ginko_internal.TextField & {
77
+ readonly max: 40;
78
+ };
79
+ readonly href: _lupinum_ginko_internal.TextField & {
80
+ readonly max: 300;
81
+ };
82
+ }> & Omit<{
83
+ readonly itemLabel: "Link";
84
+ readonly fields: {
85
+ readonly label: _lupinum_ginko_internal.TextField & {
86
+ readonly max: 40;
87
+ };
88
+ readonly href: _lupinum_ginko_internal.TextField & {
89
+ readonly max: 300;
90
+ };
91
+ };
92
+ readonly max: 2;
93
+ readonly default: readonly [];
94
+ }, "fields">;
95
+ readonly features: _lupinum_ginko_internal.ListField<{
96
+ readonly icon: _lupinum_ginko_internal.TextField & {
97
+ readonly optional: true;
98
+ readonly max: 60;
99
+ readonly translate: false;
100
+ };
101
+ readonly title: _lupinum_ginko_internal.TextField & {
102
+ readonly max: 80;
103
+ };
104
+ readonly description: _lupinum_ginko_internal.LongTextField & {
105
+ readonly max: 300;
106
+ };
107
+ readonly href: _lupinum_ginko_internal.TextField & {
108
+ readonly optional: true;
109
+ readonly max: 300;
110
+ };
111
+ }> & Omit<{
112
+ readonly itemLabel: "Feature";
113
+ readonly fields: {
114
+ readonly icon: _lupinum_ginko_internal.TextField & {
115
+ readonly optional: true;
116
+ readonly max: 60;
117
+ readonly translate: false;
118
+ };
119
+ readonly title: _lupinum_ginko_internal.TextField & {
120
+ readonly max: 80;
121
+ };
122
+ readonly description: _lupinum_ginko_internal.LongTextField & {
123
+ readonly max: 300;
124
+ };
125
+ readonly href: _lupinum_ginko_internal.TextField & {
126
+ readonly optional: true;
127
+ readonly max: 300;
128
+ };
129
+ };
130
+ readonly max: 12;
131
+ readonly default: readonly [];
132
+ }, "fields">;
133
+ readonly body: _lupinum_ginko_internal.MarkdownField & {
134
+ readonly profile: "page";
135
+ readonly optional: true;
136
+ };
137
+ }>;
138
+ /**
139
+ * The optional blog: `{ posts, authors }`, spread into `bricks` in ginko.config.ts. Posts live at
140
+ * /blog/<slug> (main locale unprefixed, `/<l>/blog/<slug>` otherwise) and come out newest first.
141
+ * Authors have no page; a post names them in `authors`. Files: `content/posts/<n>.<name>[.<locale>].md`
142
+ * and `content/authors/<name>[.<locale>].yml`. Add `blog: blogPages({ locales })` to `pages` as well.
143
+ */
144
+ declare function blogCollections(opts: {
145
+ locales: Locales;
146
+ max?: number;
147
+ }): {
148
+ posts: _lupinum_ginko.CollectionBrick<{
149
+ readonly title: _lupinum_ginko_internal.TextField & Omit<_lupinum_ginko_internal.TextField, "type" | "label" | "pos" | "retired" | "default"> & {
150
+ default?: (string | Partial<Record<string, string>>) | undefined;
151
+ };
152
+ readonly description: _lupinum_ginko_internal.LongTextField & {
153
+ readonly optional: true;
154
+ };
155
+ readonly date: _lupinum_ginko_internal.DateField & Omit<_lupinum_ginko_internal.DateField, "type" | "label" | "pos" | "retired" | "default"> & {
156
+ default?: string | undefined;
157
+ };
158
+ readonly authors: _lupinum_ginko_internal.RefField & {
159
+ multiple: true;
160
+ } & {
161
+ readonly to: "authors";
162
+ readonly optional: true;
163
+ };
164
+ readonly image: _lupinum_ginko_internal.ImageField & {
165
+ readonly optional: true;
166
+ };
167
+ readonly tags: _lupinum_ginko_internal.ListField<{
168
+ readonly tag: _lupinum_ginko_internal.TextField & {
169
+ readonly max: 40;
170
+ };
171
+ }> & Omit<{
172
+ readonly itemLabel: "Tag";
173
+ readonly fields: {
174
+ readonly tag: _lupinum_ginko_internal.TextField & {
175
+ readonly max: 40;
176
+ };
177
+ };
178
+ readonly max: 10;
179
+ readonly optional: true;
180
+ }, "fields">;
181
+ readonly slug: _lupinum_ginko_internal.SlugField & {
182
+ readonly from: "title";
183
+ };
184
+ readonly body: _lupinum_ginko_internal.MarkdownField & {
185
+ readonly profile: "page";
186
+ };
187
+ }, Record<string, "/blog/[slug]">>;
188
+ authors: _lupinum_ginko.CollectionBrick<{
189
+ readonly name: _lupinum_ginko_internal.TextField & Omit<_lupinum_ginko_internal.TextField, "type" | "label" | "pos" | "retired" | "default"> & {
190
+ default?: (string | Partial<Record<string, string>>) | undefined;
191
+ };
192
+ readonly role: _lupinum_ginko_internal.TextField & {
193
+ readonly optional: true;
194
+ };
195
+ readonly avatar: _lupinum_ginko_internal.ImageField & {
196
+ readonly optional: true;
197
+ };
198
+ /** Shown as a link on the author's name. */
199
+ readonly url: _lupinum_ginko_internal.TextField & {
200
+ readonly format: "url";
201
+ readonly optional: true;
202
+ };
203
+ }, undefined>;
204
+ };
205
+ /**
206
+ * The optional changelog: one page at /changelog with one entry per release, newest first. Entries have no
207
+ * page of their own, the page links to each by anchor. Files: `content/changelog/<name>[.<locale>].md`
208
+ * with `version` and `date` in the frontmatter (a dot in the name would read as a language: use `v1-2-0.md`). Add `changelog: changelogPages({ locales })` to `pages` as well.
209
+ */
210
+ declare function changelogCollection(opts?: {
211
+ max?: number;
212
+ }): _lupinum_ginko.CollectionBrick<{
213
+ readonly version: _lupinum_ginko_internal.TextField & {
214
+ readonly max: 40;
215
+ readonly translate: false;
216
+ };
217
+ readonly date: _lupinum_ginko_internal.DateField & Omit<_lupinum_ginko_internal.DateField, "type" | "label" | "pos" | "retired" | "default"> & {
218
+ default?: string | undefined;
219
+ };
220
+ readonly body: _lupinum_ginko_internal.MarkdownField & {
221
+ readonly profile: "page";
222
+ };
223
+ }, undefined>;
224
+ /** The list pages by locale, for `pages` in ginko.config.ts: the sitemap and the static build know them from there. */
225
+ declare const blogPages: (opts: {
226
+ locales: Locales;
227
+ }) => Record<string, "/blog">;
228
+ declare const changelogPages: (opts: {
229
+ locales: Locales;
230
+ }) => Record<string, "/changelog">;
231
+
232
+ export { blogCollections, blogPages, changelogCollection, changelogPages, docsCollection, docsHome };