@lupinum/ginko-docs 0.2.2 → 0.2.5

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 (206) hide show
  1. package/README.md +130 -73
  2. package/app/app.config.ts +15 -13
  3. package/app/app.vue +4 -2
  4. package/app/assets/css/prose.css +2203 -149
  5. package/app/assets/css/tailwind.css +60 -38
  6. package/app/assets/css/theme-palettes.css +309 -0
  7. package/app/components/content/DocumentPageShell.vue +1 -1
  8. package/app/components/content/Feedback.vue +12 -11
  9. package/app/components/content/ImageZoomDialog.vue +84 -0
  10. package/app/components/content/PageMarkdownCopy.vue +2 -0
  11. package/app/components/content/imageZoom.ts +25 -0
  12. package/app/components/mdc/MdcAccordion.vue +6 -3
  13. package/app/components/mdc/MdcAccordionItem.vue +7 -16
  14. package/app/components/mdc/MdcApi.vue +159 -0
  15. package/app/components/mdc/MdcAside.vue +6 -10
  16. package/app/components/mdc/MdcCallout.vue +9 -10
  17. package/app/components/mdc/MdcCard.vue +36 -47
  18. package/app/components/mdc/MdcCards.vue +30 -0
  19. package/app/components/mdc/MdcCodeGroup.vue +4 -0
  20. package/app/components/mdc/MdcCodeTree.vue +234 -0
  21. package/app/components/mdc/MdcCollapse.vue +61 -0
  22. package/app/components/mdc/MdcError.vue +8 -1
  23. package/app/components/mdc/MdcExcerpt.vue +26 -0
  24. package/app/components/mdc/MdcFigure.vue +54 -20
  25. package/app/components/mdc/MdcFiles.vue +120 -0
  26. package/app/components/mdc/MdcIdea.vue +2 -1
  27. package/app/components/mdc/MdcInfo.vue +2 -1
  28. package/app/components/mdc/MdcInlineToc.vue +51 -0
  29. package/app/components/{ui/dialog/DialogHeader.vue → mdc/MdcKbd.vue} +2 -4
  30. package/app/components/mdc/MdcNote.vue +2 -1
  31. package/app/components/mdc/MdcQuiz.vue +43 -83
  32. package/app/components/mdc/MdcQuizQuestion.vue +104 -109
  33. package/app/components/mdc/MdcReadMore.vue +57 -77
  34. package/app/components/mdc/MdcSteps.vue +45 -11
  35. package/app/components/mdc/MdcSuccess.vue +8 -1
  36. package/app/components/mdc/MdcTabs.vue +44 -26
  37. package/app/components/mdc/MdcTimeline.vue +4 -1
  38. package/app/components/mdc/MdcTimelineItem.vue +18 -27
  39. package/app/components/mdc/MdcWarning.vue +8 -1
  40. package/app/components/mdc/api.utils.ts +132 -0
  41. package/app/components/mdc/code-tree.utils.ts +22 -0
  42. package/app/components/mdc/quiz.utils.ts +38 -0
  43. package/app/components/prose/ProseBlockquote.vue +17 -1
  44. package/app/components/prose/ProseH1.vue +1 -1
  45. package/app/components/prose/ProseH2.vue +7 -2
  46. package/app/components/prose/ProseH3.vue +7 -2
  47. package/app/components/prose/ProseH4.vue +17 -2
  48. package/app/components/{ui/kbd/KbdGroup.vue → prose/ProseH5.vue} +4 -3
  49. package/app/components/{ui/card/CardTitle.vue → prose/ProseH6.vue} +4 -3
  50. package/app/components/prose/ProseImg.vue +34 -99
  51. package/app/components/prose/ProsePre.vue +63 -100
  52. package/app/components/prose/ProseTable.vue +5 -3
  53. package/app/components/site/ModeToggle.vue +12 -3
  54. package/app/components/site/SiteBanner.vue +43 -2
  55. package/app/components/site/SiteFooter.vue +3 -5
  56. package/app/components/site/SiteHeader.vue +266 -132
  57. package/app/components/site/SiteHeroCode.vue +164 -0
  58. package/app/components/site/SiteLocaleSwitcher.vue +25 -9
  59. package/app/components/site/SiteSocialLinks.vue +31 -0
  60. package/app/components/ui/badge/Badge.vue +1 -1
  61. package/app/components/ui/button/Button.vue +1 -1
  62. package/app/components/ui/collapsible/CollapsibleContent.vue +1 -1
  63. package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +14 -4
  64. package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +1 -1
  65. package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +1 -1
  66. package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +1 -1
  67. package/app/components/ui/dropdown-menu/index.ts +0 -9
  68. package/app/components/ui/kbd/Kbd.vue +1 -1
  69. package/app/components/ui/kbd/index.ts +0 -1
  70. package/app/components/ui/scroll-area/ScrollArea.vue +1 -1
  71. package/app/components/ui/scroll-area/ScrollBar.vue +1 -1
  72. package/app/components/ui/separator/Separator.vue +1 -1
  73. package/app/components/ui/sheet/SheetContent.vue +1 -1
  74. package/app/components/ui/sheet/SheetDescription.vue +1 -1
  75. package/app/components/ui/sheet/SheetOverlay.vue +1 -1
  76. package/app/components/ui/sheet/SheetTitle.vue +1 -1
  77. package/app/components/ui/sheet/index.ts +0 -3
  78. package/app/components/ui/tabs/Tabs.vue +1 -1
  79. package/app/components/ui/tabs/TabsList.vue +1 -1
  80. package/app/components/ui/tabs/TabsTrigger.vue +1 -1
  81. package/app/components/ui/tabs/index.ts +0 -1
  82. package/app/composables/site-navigation.utils.ts +69 -12
  83. package/app/composables/useContentRouteAlternates.ts +13 -21
  84. package/app/composables/useLocalizedRouteSwitch.ts +1 -1
  85. package/app/composables/useMetaKey.ts +18 -0
  86. package/app/composables/useProseAppearance.ts +24 -0
  87. package/app/composables/useSiteNavigation.ts +10 -40
  88. package/app/config/site.utils.ts +1 -1
  89. package/app/error.vue +1 -5
  90. package/app/features/docs/components/DocsBreadcrumb.vue +37 -0
  91. package/app/features/docs/components/DocsContributeLinks.vue +28 -18
  92. package/app/features/docs/components/DocsMobileToc.vue +12 -8
  93. package/app/features/docs/components/DocsPageContent.vue +93 -47
  94. package/app/features/docs/components/DocsPageNav.vue +1 -1
  95. package/app/features/docs/components/DocsSidebar.vue +37 -21
  96. package/app/features/docs/components/DocsSidebarItem.vue +53 -100
  97. package/app/features/docs/components/DocsSidebarRow.vue +27 -0
  98. package/app/features/docs/components/DocsSidebarTabs.vue +46 -22
  99. package/app/features/docs/components/DocsToc.vue +80 -34
  100. package/app/features/docs/composables/useDocsNavigation.ts +2 -2
  101. package/app/features/docs/composables/useDocsNavigationData.ts +4 -1
  102. package/app/features/docs/composables/useRevealActive.ts +58 -0
  103. package/app/features/docs/composables/useScrollspy.ts +87 -19
  104. package/app/features/docs/docs-navigation.ts +17 -30
  105. package/app/features/docs/toc-context.ts +6 -0
  106. package/app/features/search/command-center.ts +11 -90
  107. package/app/features/search/components/SiteCommandCenter.vue +58 -38
  108. package/app/features/search/useCommandCenter.ts +48 -48
  109. package/app/layouts/docs.vue +1 -1
  110. package/app/lib/errors.ts +2 -49
  111. package/app/pages/blog/[slug].vue +26 -9
  112. package/app/pages/blog/index.vue +38 -14
  113. package/app/pages/docs/index.vue +10 -14
  114. package/app/pages/index.vue +180 -61
  115. package/app/plugins/ginko-docs-theme.ts +12 -0
  116. package/app/utils/content.ts +11 -0
  117. package/app/utils/file-icons.ts +90 -0
  118. package/app/utils/index.ts +7 -1
  119. package/app/utils/repository.ts +20 -0
  120. package/content.js +68 -50
  121. package/content.ts +51 -32
  122. package/i18n/messages/global/blog.ts +4 -0
  123. package/i18n/messages/global/command.ts +6 -2
  124. package/i18n/messages/global/docs.ts +4 -0
  125. package/i18n/messages/global/nav.ts +6 -3
  126. package/i18n/messages/index.ts +3 -3
  127. package/icon-bundle.ts +9 -56
  128. package/index.d.ts +7 -1
  129. package/modules/feature-routing.ts +15 -0
  130. package/nuxt.config.ts +21 -7
  131. package/package.json +29 -26
  132. package/runtime/server/agent-markdown.ts +0 -2
  133. package/server/api/_ginko-docs/redirects.get.ts +28 -0
  134. package/server/middleware/redirects.ts +22 -0
  135. package/server/routes/[locale]/blog/rss.xml.get.ts +13 -0
  136. package/server/routes/blog/rss.xml.get.ts +5 -0
  137. package/server/utils/blog-feed.ts +49 -0
  138. package/server/utils/feed.ts +70 -0
  139. package/server/utils/redirects.ts +38 -0
  140. package/server/utils/redirects.utils.ts +58 -0
  141. package/shared/theme-palettes.ts +37 -0
  142. package/shared/types/app-config.ts +75 -10
  143. package/tags.ts +47 -59
  144. package/app/components/mdc/MdcAlert.vue +0 -34
  145. package/app/components/mdc/MdcCardGroup.vue +0 -47
  146. package/app/components/mdc/MdcDocImg.vue +0 -25
  147. package/app/components/mdc/MdcField.vue +0 -62
  148. package/app/components/mdc/MdcFieldGroup.vue +0 -18
  149. package/app/components/mdc/MdcPassage.vue +0 -32
  150. package/app/components/mdc/MdcQuizOption.vue +0 -91
  151. package/app/components/mdc/MdcReadMoreGroup.vue +0 -18
  152. package/app/components/mdc/MdcShortcut.vue +0 -46
  153. package/app/components/mdc/MdcStep.vue +0 -42
  154. package/app/components/ui/accordion/Accordion.vue +0 -15
  155. package/app/components/ui/accordion/AccordionContent.vue +0 -23
  156. package/app/components/ui/accordion/AccordionItem.vue +0 -24
  157. package/app/components/ui/accordion/AccordionTrigger.vue +0 -35
  158. package/app/components/ui/accordion/index.ts +0 -4
  159. package/app/components/ui/alert/Alert.vue +0 -21
  160. package/app/components/ui/alert/AlertDescription.vue +0 -22
  161. package/app/components/ui/alert/AlertTitle.vue +0 -22
  162. package/app/components/ui/alert/index.ts +0 -34
  163. package/app/components/ui/card/Card.vue +0 -16
  164. package/app/components/ui/card/CardAction.vue +0 -17
  165. package/app/components/ui/card/CardContent.vue +0 -14
  166. package/app/components/ui/card/CardDescription.vue +0 -14
  167. package/app/components/ui/card/CardFooter.vue +0 -14
  168. package/app/components/ui/card/CardHeader.vue +0 -22
  169. package/app/components/ui/card/card-variants.ts +0 -15
  170. package/app/components/ui/card/index.ts +0 -8
  171. package/app/components/ui/checkbox/Checkbox.vue +0 -35
  172. package/app/components/ui/checkbox/index.ts +0 -1
  173. package/app/components/ui/dialog/Dialog.vue +0 -15
  174. package/app/components/ui/dialog/DialogClose.vue +0 -12
  175. package/app/components/ui/dialog/DialogContent.vue +0 -69
  176. package/app/components/ui/dialog/DialogDescription.vue +0 -21
  177. package/app/components/ui/dialog/DialogFooter.vue +0 -15
  178. package/app/components/ui/dialog/DialogOverlay.vue +0 -26
  179. package/app/components/ui/dialog/DialogPortal.vue +0 -12
  180. package/app/components/ui/dialog/DialogTitle.vue +0 -21
  181. package/app/components/ui/dialog/DialogTrigger.vue +0 -12
  182. package/app/components/ui/dialog/index.ts +0 -10
  183. package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +0 -36
  184. package/app/components/ui/dropdown-menu/DropdownMenuGroup.vue +0 -12
  185. package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +0 -25
  186. package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +0 -17
  187. package/app/components/ui/dropdown-menu/DropdownMenuSub.vue +0 -15
  188. package/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue +0 -29
  189. package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +0 -31
  190. package/app/components/ui/input/Input.vue +0 -23
  191. package/app/components/ui/input/index.ts +0 -1
  192. package/app/components/ui/label/Label.vue +0 -14
  193. package/app/components/ui/label/index.ts +0 -1
  194. package/app/components/ui/sheet/SheetClose.vue +0 -12
  195. package/app/components/ui/sheet/SheetFooter.vue +0 -12
  196. package/app/components/ui/sheet/SheetHeader.vue +0 -12
  197. package/app/components/ui/switch/Switch.vue +0 -39
  198. package/app/components/ui/switch/index.ts +0 -1
  199. package/app/components/ui/tabs/TabsContent.vue +0 -21
  200. package/app/components/ui/textarea/Textarea.vue +0 -31
  201. package/app/components/ui/textarea/index.ts +0 -1
  202. package/app/features/docs/composables/useDocsEntryPath.ts +0 -13
  203. package/app/lib/utils.ts +0 -7
  204. package/i18n/messages/source.ts +0 -5
  205. package/runtime/markdown/inline-code-highlight.ts +0 -155
  206. package/runtime/utils.ts +0 -7
@@ -0,0 +1,49 @@
1
+ import type { H3Event } from "h3";
2
+ import { createError, setHeader } from "h3";
3
+ import { many } from "@lupinum/ginko-content/server";
4
+ import { useAppConfig, useRuntimeConfig } from "#imports";
5
+ import { blog } from "../../i18n/messages/global/blog";
6
+ import { locales, localizedPath, type LocaleCode } from "../../i18n/locales";
7
+ import { routeSlugs } from "../../shared/route-slugs";
8
+ import { getLocalizedSiteText } from "../../app/config/site.utils";
9
+ import { buildRssFeed } from "./feed";
10
+
11
+ export const MAX_FEED_POSTS = 50;
12
+
13
+ export function blogFeedPath(locale: LocaleCode): string {
14
+ return `${localizedPath(locale, routeSlugs.blog[locale])}/rss.xml`;
15
+ }
16
+
17
+ export async function serveBlogFeed(event: H3Event, locale: LocaleCode) {
18
+ const contentRuntime = useRuntimeConfig(event).public.content as
19
+ | { collections?: Record<string, unknown> }
20
+ | undefined;
21
+ if (!contentRuntime?.collections?.blog) {
22
+ throw createError({ statusCode: 404, statusMessage: "Blog is not enabled" });
23
+ }
24
+
25
+ const site = useAppConfig().ginkoDocs.site;
26
+ const posts = await many(event, "blog", {
27
+ locale,
28
+ fallback: true,
29
+ populate: { author: "authors" },
30
+ sort: { date: "desc" },
31
+ limit: MAX_FEED_POSTS,
32
+ });
33
+
34
+ setHeader(event, "content-type", "application/rss+xml; charset=utf-8");
35
+ return buildRssFeed({
36
+ title: `${blog.title[locale]} - ${getLocalizedSiteText(site.name, locale)}`,
37
+ description: blog.description[locale],
38
+ siteUrl: site.url,
39
+ feedPath: blogFeedPath(locale),
40
+ language: locales.find((entry) => entry.code === locale)?.language ?? locale,
41
+ items: posts.map((post) => ({
42
+ title: post.title,
43
+ path: post.route.resolvedPath,
44
+ description: post.description,
45
+ date: post.date,
46
+ authorName: post.author?.name,
47
+ })),
48
+ });
49
+ }
@@ -0,0 +1,70 @@
1
+ export interface RssFeedItem {
2
+ title: string;
3
+ /** Site-relative path of the post, e.g. "/blog/my-post". */
4
+ path: string;
5
+ description: string;
6
+ /** Authored publication date, YYYY-MM-DD. */
7
+ date: string;
8
+ authorName?: string;
9
+ }
10
+
11
+ export interface RssFeedInput {
12
+ title: string;
13
+ description: string;
14
+ /** Absolute site origin, e.g. "https://docs.example.com". */
15
+ siteUrl: string;
16
+ /** Site-relative path of the feed itself, e.g. "/blog/rss.xml". */
17
+ feedPath: string;
18
+ language: string;
19
+ items: RssFeedItem[];
20
+ }
21
+
22
+ function escapeXml(value: string): string {
23
+ return value
24
+ .replaceAll("&", "&amp;")
25
+ .replaceAll("<", "&lt;")
26
+ .replaceAll(">", "&gt;")
27
+ .replaceAll('"', "&quot;")
28
+ .replaceAll("'", "&apos;");
29
+ }
30
+
31
+ function toRfc822(date: string): string {
32
+ return new Date(`${date}T00:00:00Z`).toUTCString();
33
+ }
34
+
35
+ export function buildRssFeed(feed: RssFeedInput): string {
36
+ const base = feed.siteUrl.replace(/\/$/, "");
37
+ const newestDate = feed.items
38
+ .map((item) => item.date)
39
+ .sort()
40
+ .at(-1);
41
+
42
+ const items = feed.items
43
+ .map((item) => {
44
+ const link = `${base}${item.path}`;
45
+ const author = item.authorName
46
+ ? `\n <dc:creator>${escapeXml(item.authorName)}</dc:creator>`
47
+ : "";
48
+ return ` <item>
49
+ <title>${escapeXml(item.title)}</title>
50
+ <link>${escapeXml(link)}</link>
51
+ <guid>${escapeXml(link)}</guid>
52
+ <pubDate>${toRfc822(item.date)}</pubDate>
53
+ <description>${escapeXml(item.description)}</description>${author}
54
+ </item>`;
55
+ })
56
+ .join("\n");
57
+
58
+ return `<?xml version="1.0" encoding="UTF-8"?>
59
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
60
+ <channel>
61
+ <title>${escapeXml(feed.title)}</title>
62
+ <link>${escapeXml(`${base}${feed.feedPath.replace(/rss\.xml$/, "")}`)}</link>
63
+ <atom:link href="${escapeXml(`${base}${feed.feedPath}`)}" rel="self" type="application/rss+xml"/>
64
+ <description>${escapeXml(feed.description)}</description>
65
+ <language>${escapeXml(feed.language)}</language>${newestDate ? `\n <lastBuildDate>${toRfc822(newestDate)}</lastBuildDate>` : ""}
66
+ ${items}
67
+ </channel>
68
+ </rss>
69
+ `;
70
+ }
@@ -0,0 +1,38 @@
1
+ import type { H3Event } from "h3";
2
+ import { many } from "@lupinum/ginko-content/server";
3
+ import { useRuntimeConfig } from "#imports";
4
+ import { localeCodes } from "../../i18n/locales";
5
+ import { buildRedirectMap, type RedirectSourceDocument } from "./redirects.utils";
6
+
7
+ async function queryRedirectDocuments(event: H3Event): Promise<RedirectSourceDocument[]> {
8
+ const contentRuntime = useRuntimeConfig(event).public.content as
9
+ | { collections?: Record<string, unknown> }
10
+ | undefined;
11
+ const collections = ["docs", ...(contentRuntime?.collections?.blog ? ["blog"] : [])];
12
+
13
+ const results = await Promise.all(
14
+ collections.flatMap((collection) =>
15
+ localeCodes.map((locale) =>
16
+ // fallback: false — an English page resolved into a German route would
17
+ // otherwise register its redirectFrom entries twice.
18
+ many(event, collection, { locale, fallback: false }),
19
+ ),
20
+ ),
21
+ );
22
+ return results.flat() as RedirectSourceDocument[];
23
+ }
24
+
25
+ let cachedMap: Promise<Map<string, string>> | undefined;
26
+
27
+ export function loadRedirectMap(event: H3Event): Promise<Map<string, string>> {
28
+ if (import.meta.dev) {
29
+ return queryRedirectDocuments(event).then(buildRedirectMap);
30
+ }
31
+ cachedMap ??= queryRedirectDocuments(event)
32
+ .then(buildRedirectMap)
33
+ .catch((error) => {
34
+ cachedMap = undefined;
35
+ throw error;
36
+ });
37
+ return cachedMap;
38
+ }
@@ -0,0 +1,58 @@
1
+ import { localeCodes, localizedPath } from "../../i18n/locales";
2
+ import { routeSlugs } from "../../shared/route-slugs";
3
+
4
+ export interface RedirectSourceDocument {
5
+ route: { resolvedPath: string };
6
+ redirectFrom?: string[];
7
+ }
8
+
9
+ export function normalizeRedirectPath(path: string): string {
10
+ const trimmed = path.split("?")[0] ?? path;
11
+ if (trimmed === "/") return "/";
12
+ return trimmed.replace(/\/+$/, "");
13
+ }
14
+
15
+ /** Routes the theme itself owns; a redirect must never shadow one of them. */
16
+ export function themeStaticRoutes(): string[] {
17
+ return localeCodes.flatMap((locale) => [
18
+ localizedPath(locale, routeSlugs.home[locale]),
19
+ localizedPath(locale, routeSlugs.docs[locale]),
20
+ localizedPath(locale, routeSlugs.blog[locale]),
21
+ ]);
22
+ }
23
+
24
+ /**
25
+ * Builds old-path → live-path pairs from every document's `redirectFrom`.
26
+ * Conflicts throw so `failOnError` stops the build instead of shipping a
27
+ * redirect that shadows a live page.
28
+ */
29
+ export function buildRedirectMap(documents: RedirectSourceDocument[]): Map<string, string> {
30
+ const livePaths = new Set(
31
+ documents.map((document) => normalizeRedirectPath(document.route.resolvedPath)),
32
+ );
33
+ const reserved = new Set(themeStaticRoutes().map(normalizeRedirectPath));
34
+ const map = new Map<string, string>();
35
+ const problems: string[] = [];
36
+
37
+ for (const document of documents) {
38
+ const target = normalizeRedirectPath(document.route.resolvedPath);
39
+ for (const source of document.redirectFrom ?? []) {
40
+ const from = normalizeRedirectPath(source);
41
+ if (livePaths.has(from)) {
42
+ problems.push(`"${from}" redirects to "${target}" but is also a live page`);
43
+ } else if (reserved.has(from)) {
44
+ problems.push(`"${from}" redirects to "${target}" but is a theme route`);
45
+ } else if (map.has(from) && map.get(from) !== target) {
46
+ problems.push(`"${from}" is claimed by both "${map.get(from)}" and "${target}"`);
47
+ } else {
48
+ map.set(from, target);
49
+ }
50
+ }
51
+ }
52
+
53
+ if (problems.length) {
54
+ throw new Error(`Invalid redirectFrom entries:\n${problems.map((p) => `- ${p}`).join("\n")}`);
55
+ }
56
+
57
+ return map;
58
+ }
@@ -0,0 +1,37 @@
1
+ export const GINKO_DOCS_NEUTRAL_PALETTES = [
2
+ "slate",
3
+ "gray",
4
+ "zinc",
5
+ "neutral",
6
+ "stone",
7
+ "taupe",
8
+ "mauve",
9
+ "mist",
10
+ "olive",
11
+ "custom",
12
+ ] as const;
13
+
14
+ export const GINKO_DOCS_PRIMARY_PALETTES = [
15
+ "neutral",
16
+ "red",
17
+ "orange",
18
+ "amber",
19
+ "yellow",
20
+ "lime",
21
+ "green",
22
+ "emerald",
23
+ "teal",
24
+ "cyan",
25
+ "sky",
26
+ "blue",
27
+ "indigo",
28
+ "violet",
29
+ "purple",
30
+ "fuchsia",
31
+ "pink",
32
+ "rose",
33
+ "custom",
34
+ ] as const;
35
+
36
+ export type GinkoDocsNeutralPalette = (typeof GINKO_DOCS_NEUTRAL_PALETTES)[number];
37
+ export type GinkoDocsPrimaryPalette = (typeof GINKO_DOCS_PRIMARY_PALETTES)[number];
@@ -1,13 +1,62 @@
1
- export type GinkoDocsLocalizedText = { en: string; de?: string };
1
+ export type { GinkoDocsNeutralPalette, GinkoDocsPrimaryPalette } from "../theme-palettes";
2
+ import type { GinkoDocsNeutralPalette, GinkoDocsPrimaryPalette } from "../theme-palettes";
3
+
4
+ export type GinkoDocsLocalizedText = string | { en: string; de?: string };
5
+
6
+ export type GinkoDocsProseAppearance = "quiet" | "tint";
7
+ export type GinkoDocsProseFamily =
8
+ | "callout"
9
+ | "aside"
10
+ | "excerpt"
11
+ | "cards"
12
+ | "readMore"
13
+ | "accordion"
14
+ | "tabs"
15
+ | "code"
16
+ | "files"
17
+ | "api"
18
+ | "figure"
19
+ | "quiz"
20
+ | "steps"
21
+ | "timeline";
22
+
23
+ export interface GinkoDocsProseConfig {
24
+ appearance: GinkoDocsProseAppearance;
25
+ components?: Partial<Record<GinkoDocsProseFamily, GinkoDocsProseAppearance>>;
26
+ }
2
27
 
3
28
  export interface GinkoDocsLink {
4
29
  label: GinkoDocsLocalizedText;
5
30
  to: GinkoDocsLocalizedText;
31
+ /** Iconify icon shown where nav items render with icons (e.g. mobile menu). */
32
+ icon?: string;
33
+ /** One-line subtitle shown under the label in the mobile menu. */
34
+ description?: GinkoDocsLocalizedText;
35
+ }
36
+
37
+ export type GinkoDocsSocialPlatform = "github" | "discord" | "linkedin";
38
+
39
+ /**
40
+ * A URL uses the platform's built-in label and icon. Pass an object to override
41
+ * either — `icon` accepts any Iconify name the consuming app has registered, so
42
+ * sites that ship brand marks are not limited to the layer's bundled icons.
43
+ */
44
+ export type GinkoDocsSocialEntry = string | { href: string; label?: string; icon?: string };
45
+
46
+ export interface GinkoDocsHeroCodeTab {
47
+ label: GinkoDocsLocalizedText;
48
+ /** Iconify icon shown in the tab. */
49
+ icon?: string;
50
+ filename?: string;
51
+ /** Shiki language id; derived from the filename extension when unset. */
52
+ language?: string;
53
+ code: string;
6
54
  }
7
55
 
8
56
  export type GinkoDocsHeroMedia =
9
57
  | { type: "image"; src: string; alt: string }
10
- | { type: "code"; code: string; language?: string; filename?: string };
58
+ | { type: "code"; code: string; language?: string; filename?: string }
59
+ | { type: "code-tabs"; tabs: GinkoDocsHeroCodeTab[] };
11
60
 
12
61
  export type GinkoDocsPlausibleExtension =
13
62
  | "hash"
@@ -21,22 +70,27 @@ export type GinkoDocsPlausibleExtension =
21
70
  | "manual";
22
71
 
23
72
  export interface GinkoDocsAppConfig {
73
+ theme: {
74
+ neutral: GinkoDocsNeutralPalette;
75
+ primary: GinkoDocsPrimaryPalette;
76
+ };
77
+ prose: GinkoDocsProseConfig;
24
78
  site: {
25
79
  name: GinkoDocsLocalizedText;
26
80
  description: GinkoDocsLocalizedText;
27
81
  url: string;
28
82
  logo: { light: string; dark: string };
29
- localeSwitcher: "dropdown" | "segmented";
30
83
  docsSidebarSwitcher: "dropdown" | "list" | "tabs";
31
84
  lupinumAttribution: boolean;
32
85
  };
33
86
  nav: {
34
87
  /** "auto" derives Docs (+ Blog when blog routes exist); an array overrides entirely. */
35
88
  links: "auto" | GinkoDocsLink[];
89
+ /** Render configured social links as icon buttons in the header instead of labelled rows. */
90
+ socialIcons: boolean;
36
91
  };
37
92
  banner: {
38
- /** "auto" shows the banner when blog routes exist (legacy behavior). */
39
- enabled: boolean | "auto";
93
+ enabled: boolean;
40
94
  /** Dismissal storage key — change it to re-show the banner after an update. */
41
95
  id: string;
42
96
  text?: GinkoDocsLocalizedText;
@@ -53,10 +107,7 @@ export interface GinkoDocsAppConfig {
53
107
  extensions?: GinkoDocsPlausibleExtension[];
54
108
  };
55
109
  };
56
- social: {
57
- github?: string;
58
- linkedin?: string;
59
- };
110
+ social: Partial<Record<GinkoDocsSocialPlatform, GinkoDocsSocialEntry>>;
60
111
  feedback: {
61
112
  enabled: boolean;
62
113
  };
@@ -83,7 +134,6 @@ export interface GinkoDocsAppConfig {
83
134
  contentDirectory?: string;
84
135
  };
85
136
  landing: {
86
- eyebrow?: GinkoDocsLocalizedText;
87
137
  title: GinkoDocsLocalizedText;
88
138
  description: GinkoDocsLocalizedText;
89
139
  primary: GinkoDocsLink;
@@ -91,10 +141,25 @@ export interface GinkoDocsAppConfig {
91
141
  hero?: {
92
142
  media?: GinkoDocsHeroMedia;
93
143
  };
144
+ /** Copyable install command shown under the hero CTAs. */
145
+ install?: { command: string };
94
146
  features: Array<{
95
147
  title: GinkoDocsLocalizedText;
96
148
  description: GinkoDocsLocalizedText;
97
149
  icon?: string;
98
150
  }>;
151
+ /** Dark full-width band highlighting agent readiness. */
152
+ agent?: {
153
+ title: GinkoDocsLocalizedText;
154
+ description: GinkoDocsLocalizedText;
155
+ /** Terminal transcript; "$"-prefixed lines render as commands, "#" as comments. */
156
+ code: string;
157
+ };
158
+ /** Closing call to action; primary falls back to landing.primary. */
159
+ cta?: {
160
+ title: GinkoDocsLocalizedText;
161
+ primary?: GinkoDocsLink;
162
+ secondary?: GinkoDocsLink;
163
+ };
99
164
  };
100
165
  }
package/tags.ts CHANGED
@@ -3,42 +3,36 @@ import type { PortableComponentPolicyV1 } from "@lupinum/ginko-content/cms-contr
3
3
  export const contentComponentTags = {
4
4
  accordion: "MdcAccordion",
5
5
  "accordion-item": "MdcAccordionItem",
6
- alert: "MdcAlert",
6
+ api: "MdcApi",
7
7
  aside: "MdcAside",
8
- callout: "MdcCallout",
9
8
  card: "MdcCard",
10
- "card-group": "MdcCardGroup",
9
+ cards: "MdcCards",
11
10
  center: "MdcCenter",
12
11
  "code-group": "MdcCodeGroup",
12
+ "code-tree": "MdcCodeTree",
13
+ collapse: "MdcCollapse",
13
14
  column: "MdcColumn",
14
- danger: "MdcError",
15
- "doc-img": "MdcDocImg",
16
15
  dropcap: "MdcDropcap",
17
16
  error: "MdcError",
18
- field: "MdcField",
19
- "field-group": "MdcFieldGroup",
17
+ excerpt: "MdcExcerpt",
20
18
  figure: "MdcFigure",
19
+ files: "MdcFiles",
21
20
  idea: "MdcIdea",
22
21
  img: "ProseImg",
23
22
  info: "MdcInfo",
24
- kbd: "MdcShortcut",
23
+ kbd: "MdcKbd",
25
24
  layout: "MdcLayout",
26
25
  note: "MdcNote",
27
- passage: "MdcPassage",
28
26
  quiz: "MdcQuiz",
29
- "quiz-option": "MdcQuizOption",
30
27
  "quiz-question": "MdcQuizQuestion",
31
28
  "read-more": "MdcReadMore",
32
- "read-more-group": "MdcReadMoreGroup",
33
- shortcut: "MdcShortcut",
34
- step: "MdcStep",
35
29
  steps: "MdcSteps",
36
30
  success: "MdcSuccess",
37
31
  tab: "MdcTab",
38
32
  tabs: "MdcTabs",
39
33
  timeline: "MdcTimeline",
40
34
  "timeline-item": "MdcTimelineItem",
41
- warn: "MdcWarning",
35
+ toc: "MdcInlineToc",
42
36
  warning: "MdcWarning",
43
37
  } as const;
44
38
 
@@ -59,12 +53,13 @@ const inline = (props: ComponentProps = {}): ComponentDefinition => ({
59
53
  media: null,
60
54
  });
61
55
 
62
- const notice = block({ title: optional("string"), icon: optional("string") });
63
- const shortcut = inline({ value: optional("string"), size: optional("string") });
56
+ const appearance = { appearance: optional("string") };
57
+ const notice = block({ title: optional("string"), icon: optional("string"), ...appearance });
64
58
 
65
59
  export const contentComponentPolicy = {
66
60
  components: {
67
61
  accordion: block({
62
+ ...appearance,
68
63
  type: optional("string"),
69
64
  collapsible: optional("boolean"),
70
65
  defaultValue: optional("json"),
@@ -77,17 +72,15 @@ export const contentComponentPolicy = {
77
72
  },
78
73
  ["default", "title", "content"],
79
74
  ),
80
- alert: block({
75
+ api: block({
76
+ ...appearance,
81
77
  title: optional("string"),
82
78
  icon: optional("string"),
83
- type: optional("string"),
84
- }),
85
- aside: block({ label: optional("string") }),
86
- callout: block({
87
- title: optional("string"),
88
- type: optional("string"),
89
- icon: optional("string"),
79
+ method: optional("string"),
80
+ path: optional("string"),
81
+ groups: optional("json"),
90
82
  }),
83
+ aside: block({ label: optional("string"), ...appearance }),
91
84
  card: block(
92
85
  {
93
86
  title: optional("string"),
@@ -100,32 +93,31 @@ export const contentComponentPolicy = {
100
93
  img: optional("asset"),
101
94
  showLinkIcon: optional("boolean"),
102
95
  horizontal: optional("boolean"),
96
+ ...appearance,
103
97
  },
104
98
  ["default", "title", "description", "footer"],
105
99
  { sourceProp: "img", altProp: null, titleProp: null, filenameProp: null },
106
100
  ),
107
- "card-group": block({ cols: optional("string") }),
101
+ cards: block({ cols: optional("string"), ...appearance }),
108
102
  center: block({
109
103
  size: optional("string"),
110
104
  max: optional("string"),
111
105
  type: optional("string"),
112
106
  }),
113
- "code-group": block(),
107
+ "code-group": block(appearance),
108
+ "code-tree": block({
109
+ ...appearance,
110
+ defaultValue: optional("string"),
111
+ expandAll: optional("boolean"),
112
+ }),
113
+ collapse: block(appearance),
114
114
  column: block({ size: optional("string") }),
115
- danger: notice,
116
- "doc-img": block({ bleed: optional("string") }),
117
115
  dropcap: block({ lines: optional("json") }),
118
116
  error: notice,
119
- field: block({
120
- name: required("string"),
121
- type: optional("string"),
122
- required: optional("boolean"),
123
- description: optional("string"),
124
- defaultValue: optional("string"),
125
- }),
126
- "field-group": block(),
117
+ excerpt: block({ label: optional("string"), source: optional("string"), ...appearance }),
127
118
  figure: block(
128
119
  {
120
+ ...appearance,
129
121
  src: required("asset"),
130
122
  alt: required("string"),
131
123
  caption: optional("string"),
@@ -134,17 +126,19 @@ export const contentComponentPolicy = {
134
126
  bleed: optional("string"),
135
127
  aspect: optional("string"),
136
128
  fit: optional("string"),
129
+ zoom: optional("json"),
137
130
  },
138
131
  ["default"],
139
132
  { sourceProp: "src", altProp: "alt", titleProp: null, filenameProp: null },
140
133
  ),
134
+ files: block({ active: optional("string"), annotations: optional("json"), ...appearance }),
141
135
  idea: notice,
142
136
  info: notice,
143
- kbd: shortcut,
137
+ kbd: inline(),
144
138
  layout: block({ type: optional("string") }),
145
139
  note: notice,
146
- passage: block({ label: optional("string") }),
147
140
  quiz: block({
141
+ ...appearance,
148
142
  title: optional("string"),
149
143
  description: optional("string"),
150
144
  questionLabel: optional("string"),
@@ -156,44 +150,38 @@ export const contentComponentPolicy = {
156
150
  retryPrompt: optional("string"),
157
151
  resetLabel: optional("string"),
158
152
  }),
159
- "quiz-option": block({
160
- correct: optional("boolean"),
161
- label: optional("string"),
162
- }),
163
153
  "quiz-question": block({
164
154
  question: required("string"),
165
155
  type: optional("string"),
166
156
  explanation: optional("string"),
167
- options: optional("json"),
157
+ options: required("json"),
168
158
  checkLabel: optional("string"),
169
159
  correctLabel: optional("string"),
170
160
  incorrectLabel: optional("string"),
171
161
  resetLabel: optional("string"),
172
162
  multipleChoiceLabel: optional("string"),
173
163
  }),
174
- "read-more": block(
175
- {
176
- title: optional("string"),
177
- to: required("string"),
178
- target: optional("string"),
179
- icon: optional("string"),
180
- },
181
- [],
182
- ),
183
- "read-more-group": block(),
184
- shortcut,
185
- step: block({ title: optional("string"), icon: optional("string") }),
186
- steps: block({ variant: optional("string") }),
164
+ "read-more": block({ title: optional("string"), links: required("json"), ...appearance }, []),
165
+ steps: block({ mode: optional("string"), ...appearance }),
187
166
  success: notice,
188
167
  tab: block({ icon: optional("string"), label: optional("string") }),
189
- tabs: block({ variant: optional("string"), padded: optional("boolean") }),
190
- timeline: block(),
168
+ tabs: block({ layout: optional("string"), padded: optional("boolean"), ...appearance }),
169
+ timeline: block(appearance),
191
170
  "timeline-item": block({
192
171
  date: optional("string"),
193
172
  label: optional("string"),
194
173
  title: optional("string"),
174
+ icon: optional("string"),
175
+ active: optional("boolean"),
195
176
  }),
196
- warn: notice,
177
+ toc: block(
178
+ {
179
+ title: optional("string"),
180
+ depth: optional("json"),
181
+ open: optional("json"),
182
+ },
183
+ [],
184
+ ),
197
185
  warning: notice,
198
186
  },
199
187
  } satisfies PortableComponentPolicyV1;
@@ -1,34 +0,0 @@
1
- <script setup lang="ts">
2
- import type { HTMLAttributes } from "vue";
3
- import { computed } from "vue";
4
- import MdcCallout from "./MdcCallout.vue";
5
-
6
- type AlertType = "default" | "info" | "warning" | "success" | "danger" | "secondary";
7
- type CalloutType = "info" | "warning" | "error" | "success" | "idea";
8
-
9
- const props = withDefaults(
10
- defineProps<{
11
- title?: string;
12
- icon?: string;
13
- type?: AlertType;
14
- class?: HTMLAttributes["class"];
15
- }>(),
16
- {
17
- type: "warning",
18
- },
19
- );
20
-
21
- const calloutType = computed<CalloutType>(() => {
22
- if (props.type === "danger") return "error";
23
- if (props.type === "success") return "success";
24
- if (props.type === "info" || props.type === "secondary") return "info";
25
-
26
- return "warning";
27
- });
28
- </script>
29
-
30
- <template>
31
- <MdcCallout :title="title" :icon="icon" :type="calloutType" :class="props.class">
32
- <slot />
33
- </MdcCallout>
34
- </template>