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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/LICENSE +1 -1
  3. package/README.md +206 -54
  4. package/app/app.config.ts +42 -55
  5. package/app/assets/docs.css +555 -0
  6. package/app/components/DocsBanner.vue +29 -0
  7. package/app/components/DocsExample.vue +43 -0
  8. package/app/components/DocsFooter.vue +19 -0
  9. package/app/components/DocsHeader.vue +112 -0
  10. package/app/components/DocsLanding.vue +55 -0
  11. package/app/components/DocsLanguage.vue +29 -0
  12. package/app/components/DocsNavTree.vue +38 -0
  13. package/app/components/DocsPage.vue +84 -0
  14. package/app/components/DocsPageActions.vue +47 -0
  15. package/app/components/DocsPost.vue +58 -0
  16. package/app/components/DocsSearch.vue +134 -0
  17. package/app/components/DocsShell.vue +20 -0
  18. package/app/components/DocsSidebar.vue +18 -0
  19. package/app/components/DocsTable.vue +11 -0
  20. package/app/components/DocsThemeToggle.vue +45 -0
  21. package/app/components/DocsToc.vue +73 -0
  22. package/app/composables/useDocs.ts +52 -0
  23. package/app/composables/useDocsBlog.ts +32 -0
  24. package/app/composables/useDocsCopy.ts +17 -0
  25. package/app/composables/useDocsPopover.ts +36 -0
  26. package/app/composables/useDocsSite.ts +10 -0
  27. package/app/docs-pages/blog.vue +47 -0
  28. package/app/docs-pages/changelog.vue +27 -0
  29. package/app/docs-pages/doc.vue +14 -0
  30. package/app/docs-pages/docs.vue +9 -0
  31. package/app/docs-pages/index.vue +12 -0
  32. package/app/docs-pages/post.vue +8 -0
  33. package/app/error.vue +23 -115
  34. package/app/layouts/default.vue +25 -7
  35. package/app/lib/blogTypes.ts +8 -0
  36. package/app/lib/docsFirst.ts +11 -0
  37. package/app/lib/docsInit.ts +18 -0
  38. package/app/lib/docsNav.ts +36 -0
  39. package/app/lib/docsPage.ts +44 -0
  40. package/app/lib/markdownComponents.ts +16 -0
  41. package/app/lib/previewToc.ts +18 -0
  42. package/dist/config.d.mts +232 -0
  43. package/dist/config.d.ts +232 -0
  44. package/dist/config.mjs +114 -0
  45. package/dist/pages.d.mts +5 -0
  46. package/dist/pages.d.ts +5 -0
  47. package/dist/pages.mjs +36 -0
  48. package/dist/shared/ginko-docs.BwbF7JBR.mjs +4 -0
  49. package/i18n/de.json +1 -0
  50. package/i18n/en.json +1 -0
  51. package/icons/README.md +2 -0
  52. package/icons/bluesky.svg +1 -0
  53. package/icons/claude.svg +1 -0
  54. package/icons/discord.svg +1 -0
  55. package/icons/github.svg +1 -0
  56. package/icons/linkedin.svg +1 -0
  57. package/icons/mastodon.svg +1 -0
  58. package/icons/openai.svg +1 -0
  59. package/icons/x.svg +1 -0
  60. package/icons/youtube.svg +1 -0
  61. package/nuxt.config.ts +34 -209
  62. package/package.json +43 -76
  63. package/public/_docs/fonts/LICENSE +188 -0
  64. package/public/_docs/fonts/inter-latin-wght-normal.woff2 +0 -0
  65. package/public/_docs/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  66. package/public/_docs/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
  67. package/server/feeds/blog-locale.get.ts +9 -0
  68. package/server/feeds/blog.get.ts +4 -0
  69. package/server/lib/blogFeed.ts +35 -0
  70. package/server/lib/rss.ts +57 -0
  71. package/server/mcp/handler.ts +108 -0
  72. package/server/routes/__ginko/docs-mcp.ts +35 -0
  73. package/shared/paths.ts +4 -0
  74. package/app/app.vue +0 -98
  75. package/app/assets/css/prose.css +0 -3240
  76. package/app/assets/css/tailwind.css +0 -394
  77. package/app/assets/css/theme-palettes.css +0 -309
  78. package/app/assets/css/theme-presets.css +0 -141
  79. package/app/components/OgImage/GinkoDocs.satori.vue +0 -238
  80. package/app/components/content/DocumentPageShell.vue +0 -55
  81. package/app/components/content/Feedback.vue +0 -102
  82. package/app/components/content/ImageZoomDialog.vue +0 -84
  83. package/app/components/content/PageMarkdownCopy.vue +0 -104
  84. package/app/components/content/imageZoom.ts +0 -25
  85. package/app/components/mdc/MdcAccordion.vue +0 -67
  86. package/app/components/mdc/MdcAccordionItem.vue +0 -42
  87. package/app/components/mdc/MdcApi.vue +0 -159
  88. package/app/components/mdc/MdcAside.vue +0 -23
  89. package/app/components/mdc/MdcCallout.vue +0 -61
  90. package/app/components/mdc/MdcCard.vue +0 -175
  91. package/app/components/mdc/MdcCards.vue +0 -30
  92. package/app/components/mdc/MdcCenter.vue +0 -71
  93. package/app/components/mdc/MdcCodeGroup.vue +0 -197
  94. package/app/components/mdc/MdcCodeTree.vue +0 -234
  95. package/app/components/mdc/MdcCollapse.vue +0 -61
  96. package/app/components/mdc/MdcColumn.vue +0 -70
  97. package/app/components/mdc/MdcDropcap.vue +0 -84
  98. package/app/components/mdc/MdcError.vue +0 -23
  99. package/app/components/mdc/MdcExcerpt.vue +0 -26
  100. package/app/components/mdc/MdcFigure.vue +0 -85
  101. package/app/components/mdc/MdcFiles.vue +0 -120
  102. package/app/components/mdc/MdcIdea.vue +0 -17
  103. package/app/components/mdc/MdcInfo.vue +0 -17
  104. package/app/components/mdc/MdcInlineToc.vue +0 -51
  105. package/app/components/mdc/MdcKbd.vue +0 -10
  106. package/app/components/mdc/MdcLayout.vue +0 -57
  107. package/app/components/mdc/MdcNote.vue +0 -17
  108. package/app/components/mdc/MdcQuiz.vue +0 -241
  109. package/app/components/mdc/MdcQuizQuestion.vue +0 -221
  110. package/app/components/mdc/MdcReadMore.vue +0 -70
  111. package/app/components/mdc/MdcSteps.vue +0 -59
  112. package/app/components/mdc/MdcSuccess.vue +0 -23
  113. package/app/components/mdc/MdcTab.vue +0 -17
  114. package/app/components/mdc/MdcTabs.vue +0 -150
  115. package/app/components/mdc/MdcTimeline.vue +0 -17
  116. package/app/components/mdc/MdcTimelineItem.vue +0 -40
  117. package/app/components/mdc/MdcWarning.vue +0 -23
  118. package/app/components/mdc/api.utils.ts +0 -132
  119. package/app/components/mdc/code-tree.utils.ts +0 -22
  120. package/app/components/mdc/icons.ts +0 -10
  121. package/app/components/mdc/quiz.utils.ts +0 -38
  122. package/app/components/prose/ProseA.vue +0 -27
  123. package/app/components/prose/ProseBlockquote.vue +0 -30
  124. package/app/components/prose/ProseCode.vue +0 -14
  125. package/app/components/prose/ProseEm.vue +0 -14
  126. package/app/components/prose/ProseH1.vue +0 -18
  127. package/app/components/prose/ProseH2.vue +0 -29
  128. package/app/components/prose/ProseH3.vue +0 -29
  129. package/app/components/prose/ProseH4.vue +0 -29
  130. package/app/components/prose/ProseH5.vue +0 -15
  131. package/app/components/prose/ProseH6.vue +0 -15
  132. package/app/components/prose/ProseHr.vue +0 -12
  133. package/app/components/prose/ProseImg.vue +0 -55
  134. package/app/components/prose/ProseLi.vue +0 -14
  135. package/app/components/prose/ProseOl.vue +0 -14
  136. package/app/components/prose/ProseP.vue +0 -14
  137. package/app/components/prose/ProsePre.vue +0 -180
  138. package/app/components/prose/ProseStrong.vue +0 -14
  139. package/app/components/prose/ProseTable.vue +0 -18
  140. package/app/components/prose/ProseTbody.vue +0 -14
  141. package/app/components/prose/ProseTd.vue +0 -14
  142. package/app/components/prose/ProseTh.vue +0 -14
  143. package/app/components/prose/ProseThead.vue +0 -14
  144. package/app/components/prose/ProseTr.vue +0 -14
  145. package/app/components/prose/ProseUl.vue +0 -14
  146. package/app/components/site/ModeToggle.vue +0 -81
  147. package/app/components/site/SiteBanner.vue +0 -97
  148. package/app/components/site/SiteFooter.vue +0 -90
  149. package/app/components/site/SiteHeader.vue +0 -371
  150. package/app/components/site/SiteHeroCode.vue +0 -173
  151. package/app/components/site/SiteInteractionLayer.vue +0 -42
  152. package/app/components/site/SiteLocaleSwitcher.vue +0 -190
  153. package/app/components/site/SiteLogoMark.vue +0 -12
  154. package/app/components/site/SiteSkipLink.vue +0 -14
  155. package/app/components/site/SiteSocialLinks.vue +0 -33
  156. package/app/components/site/header-utils.ts +0 -3
  157. package/app/components/site/mode-toggle.utils.ts +0 -18
  158. package/app/components/ui/badge/Badge.vue +0 -28
  159. package/app/components/ui/badge/index.ts +0 -24
  160. package/app/components/ui/button/Button.vue +0 -31
  161. package/app/components/ui/button/index.ts +0 -35
  162. package/app/components/ui/collapsible/Collapsible.vue +0 -15
  163. package/app/components/ui/collapsible/CollapsibleContent.vue +0 -25
  164. package/app/components/ui/collapsible/CollapsibleTrigger.vue +0 -12
  165. package/app/components/ui/collapsible/index.ts +0 -3
  166. package/app/components/ui/dropdown-menu/DropdownMenu.vue +0 -15
  167. package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +0 -50
  168. package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +0 -41
  169. package/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +0 -15
  170. package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +0 -37
  171. package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +0 -23
  172. package/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue +0 -14
  173. package/app/components/ui/dropdown-menu/index.ts +0 -7
  174. package/app/components/ui/kbd/Kbd.vue +0 -23
  175. package/app/components/ui/kbd/index.ts +0 -21
  176. package/app/components/ui/scroll-area/ScrollArea.vue +0 -39
  177. package/app/components/ui/scroll-area/ScrollBar.vue +0 -33
  178. package/app/components/ui/scroll-area/index.ts +0 -2
  179. package/app/components/ui/separator/Separator.vue +0 -27
  180. package/app/components/ui/separator/index.ts +0 -1
  181. package/app/components/ui/sheet/Sheet.vue +0 -15
  182. package/app/components/ui/sheet/SheetContent.vue +0 -59
  183. package/app/components/ui/sheet/SheetDescription.vue +0 -21
  184. package/app/components/ui/sheet/SheetOverlay.vue +0 -26
  185. package/app/components/ui/sheet/SheetTitle.vue +0 -21
  186. package/app/components/ui/sheet/SheetTrigger.vue +0 -12
  187. package/app/components/ui/sheet/index.ts +0 -5
  188. package/app/components/ui/tabs/Tabs.vue +0 -24
  189. package/app/components/ui/tabs/TabsList.vue +0 -26
  190. package/app/components/ui/tabs/TabsTrigger.vue +0 -28
  191. package/app/components/ui/tabs/index.ts +0 -3
  192. package/app/composables/site-navigation.utils.ts +0 -133
  193. package/app/composables/useCanonicalUrl.ts +0 -9
  194. package/app/composables/useContentRouteAlternates.ts +0 -35
  195. package/app/composables/useGinkoAnalytics.ts +0 -52
  196. package/app/composables/useGinkoDocsConfig.ts +0 -6
  197. package/app/composables/useGinkoOgImage.ts +0 -20
  198. package/app/composables/useLocalizedPath.ts +0 -12
  199. package/app/composables/useLocalizedRouteSwitch.ts +0 -31
  200. package/app/composables/useMetaKey.ts +0 -18
  201. package/app/composables/usePageMarkdownActions.ts +0 -79
  202. package/app/composables/useProseAppearance.ts +0 -24
  203. package/app/composables/useSchemaJsonLd.ts +0 -25
  204. package/app/composables/useSiteNavigation.ts +0 -64
  205. package/app/config/site.utils.ts +0 -36
  206. package/app/features/docs/components/DocsBreadcrumb.vue +0 -37
  207. package/app/features/docs/components/DocsContributeLinks.vue +0 -75
  208. package/app/features/docs/components/DocsMobileToc.vue +0 -108
  209. package/app/features/docs/components/DocsPageContent.vue +0 -234
  210. package/app/features/docs/components/DocsPageNav.vue +0 -57
  211. package/app/features/docs/components/DocsSidebar.vue +0 -145
  212. package/app/features/docs/components/DocsSidebarDropdown.vue +0 -70
  213. package/app/features/docs/components/DocsSidebarItem.vue +0 -122
  214. package/app/features/docs/components/DocsSidebarList.vue +0 -35
  215. package/app/features/docs/components/DocsSidebarRow.vue +0 -27
  216. package/app/features/docs/components/DocsSidebarTabs.vue +0 -59
  217. package/app/features/docs/components/DocsToc.vue +0 -106
  218. package/app/features/docs/composables/useDocsNavigation.ts +0 -29
  219. package/app/features/docs/composables/useDocsNavigationData.ts +0 -18
  220. package/app/features/docs/composables/useRevealActive.ts +0 -58
  221. package/app/features/docs/composables/useScrollspy.ts +0 -135
  222. package/app/features/docs/docs-navigation.ts +0 -157
  223. package/app/features/docs/toc-context.ts +0 -6
  224. package/app/features/search/command-center.ts +0 -110
  225. package/app/features/search/components/SiteCommandCenter.vue +0 -359
  226. package/app/features/search/components/SiteSearchPageHighlight.vue +0 -126
  227. package/app/features/search/useCommandCenter.ts +0 -292
  228. package/app/layouts/blog.vue +0 -15
  229. package/app/layouts/docs.vue +0 -20
  230. package/app/lib/errors.ts +0 -37
  231. package/app/lib/schema-org.ts +0 -38
  232. package/app/pages/blog/[slug].vue +0 -228
  233. package/app/pages/blog/index.vue +0 -130
  234. package/app/pages/docs/[...slug].vue +0 -10
  235. package/app/pages/docs/index.vue +0 -19
  236. package/app/pages/index.vue +0 -278
  237. package/app/plugins/ginko-docs-theme.ts +0 -14
  238. package/app/plugins/ssr-width.ts +0 -6
  239. package/app/utils/content.ts +0 -67
  240. package/app/utils/file-icons.ts +0 -90
  241. package/app/utils/index.ts +0 -7
  242. package/app/utils/repository.ts +0 -20
  243. package/components.ts +0 -9
  244. package/content-collections.ts +0 -89
  245. package/content.js +0 -200
  246. package/content.ts +0 -104
  247. package/i18n/i18n.config.ts +0 -10
  248. package/i18n/locales.ts +0 -81
  249. package/i18n/messages/global/banner.ts +0 -8
  250. package/i18n/messages/global/blog.ts +0 -18
  251. package/i18n/messages/global/command.ts +0 -49
  252. package/i18n/messages/global/docs.ts +0 -36
  253. package/i18n/messages/global/errors.ts +0 -36
  254. package/i18n/messages/global/feedback.ts +0 -11
  255. package/i18n/messages/global/index.ts +0 -21
  256. package/i18n/messages/global/nav.ts +0 -26
  257. package/i18n/messages/global/site.ts +0 -4
  258. package/i18n/messages/global/theme.ts +0 -9
  259. package/i18n/messages/index.ts +0 -20
  260. package/i18n/messages/localize.ts +0 -35
  261. package/i18n/routes.ts +0 -33
  262. package/icon-bundle.ts +0 -116
  263. package/index.d.ts +0 -37
  264. package/modules/analytics-boundary.ts +0 -23
  265. package/modules/feature-routing.ts +0 -68
  266. package/modules/syntax-highlighting.ts +0 -120
  267. package/public/fonts/public-sans/LICENSE +0 -93
  268. package/public/fonts/public-sans/README.md +0 -12
  269. package/public/fonts/public-sans/public-sans-400-normal-latin.woff +0 -0
  270. package/public/fonts/public-sans/public-sans-500-normal-latin.woff +0 -0
  271. package/public/fonts/public-sans/public-sans-600-normal-latin.woff +0 -0
  272. package/public/fonts/public-sans/public-sans-700-normal-latin.woff +0 -0
  273. package/runtime/generate-favicons.mjs +0 -124
  274. package/runtime/server/agent-markdown.ts +0 -19
  275. package/server/api/_ginko-docs/redirects.get.ts +0 -28
  276. package/server/mcp/tools/get-page.ts +0 -14
  277. package/server/mcp/tools/list-pages.ts +0 -10
  278. package/server/middleware/redirects.ts +0 -22
  279. package/server/plugins/agent-markdown.ts +0 -6
  280. package/server/plugins/sitemap-policy.ts +0 -12
  281. package/server/routes/.well-known/ai-catalog.json.get.ts +0 -10
  282. package/server/routes/[locale]/blog/rss.xml.get.ts +0 -13
  283. package/server/routes/blog/rss.xml.get.ts +0 -5
  284. package/server/routes/mcp/server-card.get.ts +0 -14
  285. package/server/utils/blog-feed.ts +0 -51
  286. package/server/utils/feed.ts +0 -70
  287. package/server/utils/mcp-server-card.ts +0 -45
  288. package/server/utils/redirects.ts +0 -38
  289. package/server/utils/redirects.utils.ts +0 -58
  290. package/server/utils/sitemap.ts +0 -45
  291. package/shared/route-slugs.ts +0 -5
  292. package/shared/theme-palettes.ts +0 -37
  293. package/shared/theme-presets.ts +0 -3
  294. package/shared/types/app-config.ts +0 -172
  295. package/shared/types/nuxt-config.ts +0 -14
  296. package/tags.ts +0 -192
@@ -1,241 +0,0 @@
1
- <script setup lang="ts">
2
- import type { VNode } from "vue";
3
- import { cloneVNode, h, provide, reactive, ref, computed, useSlots } from "vue";
4
- import { Icon } from "#components";
5
- import { useI18n } from "#imports";
6
- import { cn } from "../../utils";
7
- import { useProseAppearance } from "../../composables/useProseAppearance";
8
-
9
- const props = defineProps<{
10
- title?: string;
11
- description?: string;
12
- questionLabel?: string;
13
- backLabel?: string;
14
- nextLabel?: string;
15
- resultsLabel?: string;
16
- correctSummaryLabel?: string;
17
- perfectLabel?: string;
18
- retryPrompt?: string;
19
- resetLabel?: string;
20
- appearance?: "quiet" | "tint";
21
- }>();
22
- const appearance = useProseAppearance("quiz", () => props.appearance);
23
-
24
- const MDC_QUIZ = Symbol.for("mdc.quiz");
25
-
26
- const slots = useSlots();
27
- const activeIndex = ref(0);
28
- const showResults = ref(false);
29
- const resetKey = ref(0);
30
-
31
- const state = reactive({
32
- totalQuestions: 0,
33
- answers: new Map<number, boolean>(),
34
- });
35
-
36
- function registerQuestion(): number {
37
- const index = state.totalQuestions;
38
- state.totalQuestions++;
39
- return index;
40
- }
41
-
42
- function markAnswered(index: number, correct: boolean) {
43
- state.answers.set(index, correct);
44
- }
45
-
46
- function reset() {
47
- state.answers.clear();
48
- activeIndex.value = 0;
49
- showResults.value = false;
50
- resetKey.value++;
51
- }
52
-
53
- provide(MDC_QUIZ, { registerQuestion, markAnswered, resetKey, inQuiz: true });
54
-
55
- const correctCount = computed(() => [...state.answers.values()].filter(Boolean).length);
56
- const currentAnswered = computed(() => state.answers.has(activeIndex.value));
57
- const allAnswered = computed(() => state.answers.size === state.totalQuestions);
58
- const { locale } = useI18n();
59
-
60
- const text = computed(() => {
61
- const isGerman = String(locale.value) === "de";
62
-
63
- return {
64
- question: props.questionLabel ?? (isGerman ? "Frage" : "Question"),
65
- of: isGerman ? "von" : "of",
66
- back: props.backLabel ?? (isGerman ? "Zurück" : "Back"),
67
- next: props.nextLabel ?? (isGerman ? "Weiter" : "Next"),
68
- results: props.resultsLabel ?? (isGerman ? "Ergebnis anzeigen" : "See Results"),
69
- correct: props.correctSummaryLabel ?? (isGerman ? "richtig" : "correct"),
70
- perfect: props.perfectLabel ?? (isGerman ? "Alles richtig." : "Perfect score!"),
71
- retryPrompt:
72
- props.retryPrompt ??
73
- (isGerman ? "Weiterlernen und erneut versuchen." : "Keep learning and try again."),
74
- reset: props.resetLabel ?? (isGerman ? "Erneut versuchen" : "Try Again"),
75
- };
76
- });
77
-
78
- function getQuestionNodes(): VNode[] {
79
- return (slots.default?.() ?? []).filter((node): node is VNode => Boolean(node?.type));
80
- }
81
-
82
- function renderButton(
83
- children: string | Array<string | VNode>,
84
- options: {
85
- disabled?: boolean;
86
- onClick: () => void;
87
- variant?: "outline" | "ghost";
88
- },
89
- ) {
90
- return h(
91
- "button",
92
- {
93
- type: "button",
94
- disabled: options.disabled,
95
- class: cn("content-quiz-button", options.variant === "ghost" && "content-quiz-button-ghost"),
96
- onClick: options.onClick,
97
- },
98
- children,
99
- );
100
- }
101
-
102
- function renderHeader() {
103
- return h("div", { class: "content-quiz-header" }, [
104
- h(Icon, { name: "lucide:circle-help", class: "content-quiz-header-icon", ariaHidden: true }),
105
- h("div", { class: "content-quiz-heading" }, [
106
- props.title ? h("div", { class: "content-quiz-title" }, props.title) : null,
107
- props.description ? h("div", { class: "content-quiz-description" }, props.description) : null,
108
- ]),
109
- ]);
110
- }
111
-
112
- function renderDots() {
113
- return h(
114
- "div",
115
- { class: "content-quiz-dots" },
116
- Array.from({ length: state.totalQuestions }, (_, i) =>
117
- h("button", {
118
- type: "button",
119
- class: "content-quiz-dot",
120
- "data-state":
121
- i === activeIndex.value ? "active" : state.answers.has(i) ? "answered" : undefined,
122
- "aria-label": `${text.value.question} ${i + 1}`,
123
- "aria-current": i === activeIndex.value ? "true" : undefined,
124
- onClick: () => {
125
- activeIndex.value = i;
126
- },
127
- }),
128
- ),
129
- );
130
- }
131
-
132
- function renderFooter() {
133
- const isLast = activeIndex.value === state.totalQuestions - 1;
134
-
135
- return h("div", { class: "content-quiz-footer" }, [
136
- h(
137
- "span",
138
- { class: "content-quiz-progress" },
139
- `${text.value.question} ${activeIndex.value + 1} ${text.value.of} ${state.totalQuestions}`,
140
- ),
141
- renderDots(),
142
- h("div", { class: "content-quiz-nav" }, [
143
- renderButton([h(Icon, { name: "lucide:chevron-left", ariaHidden: true }), text.value.back], {
144
- disabled: activeIndex.value === 0,
145
- onClick: () => {
146
- activeIndex.value--;
147
- },
148
- }),
149
- isLast
150
- ? renderButton(text.value.results, {
151
- disabled: !allAnswered.value,
152
- onClick: () => {
153
- showResults.value = true;
154
- },
155
- })
156
- : renderButton(
157
- [text.value.next, h(Icon, { name: "lucide:chevron-right", ariaHidden: true })],
158
- {
159
- disabled: !currentAnswered.value,
160
- onClick: () => {
161
- activeIndex.value++;
162
- },
163
- },
164
- ),
165
- ]),
166
- ]);
167
- }
168
-
169
- function renderResults() {
170
- const perfect = correctCount.value === state.totalQuestions;
171
-
172
- return h("div", { class: "content-quiz-results" }, [
173
- h(
174
- "div",
175
- {
176
- class: "content-quiz-results-icon",
177
- "data-state": perfect ? "perfect" : undefined,
178
- },
179
- [h(Icon, { name: "lucide:trophy", ariaHidden: true })],
180
- ),
181
- h("div", {}, [
182
- h(
183
- "p",
184
- { class: "content-quiz-results-score" },
185
- `${correctCount.value} ${text.value.of} ${state.totalQuestions} ${text.value.correct}`,
186
- ),
187
- h(
188
- "p",
189
- { class: "content-quiz-results-note" },
190
- perfect ? text.value.perfect : text.value.retryPrompt,
191
- ),
192
- ]),
193
- renderButton(text.value.reset, { onClick: reset }),
194
- ]);
195
- }
196
-
197
- function renderQuiz() {
198
- const items = getQuestionNodes();
199
-
200
- if (!items.length) return null;
201
-
202
- const children: VNode[] = [renderHeader()];
203
-
204
- if (showResults.value) {
205
- children.push(h("div", { class: "content-quiz-body" }, [renderResults()]));
206
- } else {
207
- children.push(
208
- h(
209
- "div",
210
- { class: "content-quiz-body" },
211
- items.map((node, i) =>
212
- h(
213
- "div",
214
- {
215
- key: i,
216
- style: i === activeIndex.value ? undefined : { display: "none" },
217
- },
218
- [cloneVNode(node)],
219
- ),
220
- ),
221
- ),
222
- );
223
- if (state.totalQuestions > 1) {
224
- children.push(renderFooter());
225
- }
226
- }
227
-
228
- return h(
229
- "div",
230
- {
231
- "data-appearance": appearance.value,
232
- class: "content-quiz not-prose",
233
- },
234
- children,
235
- );
236
- }
237
- </script>
238
-
239
- <template>
240
- <component :is="renderQuiz" />
241
- </template>
@@ -1,221 +0,0 @@
1
- <script setup lang="ts">
2
- import type { Ref } from "vue";
3
- import { h, inject, ref, computed, watch, useSlots } from "vue";
4
- import { cn } from "../../utils";
5
- import { Icon } from "#components";
6
- import { useI18n } from "#imports";
7
- import { parseQuizOptions, type QuizOption, type QuizType } from "./quiz.utils";
8
-
9
- const props = withDefaults(
10
- defineProps<{
11
- question: string;
12
- type?: QuizType;
13
- explanation?: string;
14
- options: QuizOption[];
15
- checkLabel?: string;
16
- correctLabel?: string;
17
- incorrectLabel?: string;
18
- resetLabel?: string;
19
- multipleChoiceLabel?: string;
20
- }>(),
21
- {
22
- type: "single",
23
- },
24
- );
25
-
26
- const MDC_QUIZ = Symbol.for("mdc.quiz");
27
- const quizContext = inject<{
28
- registerQuestion: () => number;
29
- markAnswered: (index: number, correct: boolean) => void;
30
- resetKey: Ref<number>;
31
- inQuiz: boolean;
32
- } | null>(MDC_QUIZ, null);
33
-
34
- const questionIndex = quizContext?.registerQuestion() ?? -1;
35
-
36
- if (quizContext?.resetKey) {
37
- watch(quizContext.resetKey, () => {
38
- isSubmitted.value = false;
39
- selectedOptions.value = new Set();
40
- });
41
- }
42
-
43
- const slots = useSlots();
44
- const selectedOptions = ref(new Set<number>());
45
- const isSubmitted = ref(false);
46
- const { locale } = useI18n();
47
-
48
- const text = computed(() => {
49
- const isGerman = String(locale.value) === "de";
50
-
51
- return {
52
- check: props.checkLabel ?? (isGerman ? "Antwort pruefen" : "Check Answer"),
53
- correct: props.correctLabel ?? (isGerman ? "Richtig" : "Correct"),
54
- incorrect: props.incorrectLabel ?? (isGerman ? "Nicht richtig" : "Incorrect"),
55
- reset: props.resetLabel ?? (isGerman ? "Erneut versuchen" : "Try Again"),
56
- multipleChoice:
57
- props.multipleChoiceLabel ??
58
- (isGerman ? "(mehrere Antworten möglich)" : "(select all that apply)"),
59
- };
60
- });
61
-
62
- const options = computed(() => parseQuizOptions(props.options, props.type));
63
- const useYaml = computed(() => options.value.length > 0);
64
-
65
- const isCorrect = computed(() => {
66
- return options.value.every(
67
- (option, index) => option.correct === selectedOptions.value.has(index),
68
- );
69
- });
70
-
71
- function handleSelect(index: number) {
72
- if (isSubmitted.value) return;
73
- if (props.type === "single") {
74
- selectedOptions.value = new Set([index]);
75
- } else {
76
- const next = new Set(selectedOptions.value);
77
- if (next.has(index)) {
78
- next.delete(index);
79
- } else {
80
- next.add(index);
81
- }
82
- selectedOptions.value = next;
83
- }
84
- }
85
-
86
- function handleSubmit() {
87
- isSubmitted.value = true;
88
- quizContext?.markAnswered(questionIndex, isCorrect.value);
89
- }
90
-
91
- function handleReset() {
92
- isSubmitted.value = false;
93
- selectedOptions.value = new Set();
94
- }
95
-
96
- function renderButton(
97
- label: string,
98
- options: {
99
- disabled?: boolean;
100
- onClick: () => void;
101
- variant?: "outline" | "ghost";
102
- },
103
- ) {
104
- return h(
105
- "button",
106
- {
107
- type: "button",
108
- disabled: options.disabled,
109
- class: cn("content-quiz-button", options.variant === "ghost" && "content-quiz-button-ghost"),
110
- onClick: options.onClick,
111
- },
112
- label,
113
- );
114
- }
115
-
116
- function renderOptions() {
117
- return options.value.map(({ text: label, correct }, i) => {
118
- const selected = selectedOptions.value.has(i);
119
- const state = isSubmitted.value
120
- ? correct
121
- ? "correct"
122
- : selected
123
- ? "incorrect"
124
- : "idle"
125
- : selected
126
- ? "selected"
127
- : "idle";
128
- const resultIcon =
129
- state === "correct"
130
- ? "lucide:circle-check"
131
- : state === "incorrect"
132
- ? "lucide:circle-x"
133
- : null;
134
- return h(
135
- "button",
136
- {
137
- key: i,
138
- type: "button",
139
- disabled: isSubmitted.value,
140
- class: "content-quiz-option",
141
- "data-state": state,
142
- onClick: () => handleSelect(i),
143
- },
144
- [
145
- h(
146
- "span",
147
- { class: "content-quiz-option-key", "aria-hidden": "true" },
148
- String.fromCharCode(65 + i),
149
- ),
150
- h("span", { class: "content-quiz-option-label" }, label),
151
- resultIcon
152
- ? h(Icon, { name: resultIcon, class: "content-quiz-option-result", ariaHidden: true })
153
- : null,
154
- ],
155
- );
156
- });
157
- }
158
-
159
- function renderQuestion() {
160
- return h(
161
- "div",
162
- {
163
- class: cn(
164
- "content-quiz-question",
165
- !quizContext?.inQuiz && "content-quiz-question-standalone not-prose",
166
- ),
167
- },
168
- [
169
- h("p", { class: "content-quiz-question-title" }, [
170
- props.question,
171
- props.type === "multiple"
172
- ? h("span", { class: "content-quiz-question-hint" }, text.value.multipleChoice)
173
- : null,
174
- ]),
175
- h("div", { class: "content-quiz-options" }, renderOptions()),
176
- h("div", { class: "content-quiz-actions" }, [
177
- !isSubmitted.value
178
- ? renderButton(text.value.check, {
179
- disabled: selectedOptions.value.size === 0,
180
- onClick: handleSubmit,
181
- })
182
- : h("div", { class: "content-quiz-actions" }, [
183
- h(
184
- "span",
185
- {
186
- class: "content-quiz-result",
187
- "data-state": isCorrect.value ? "correct" : "incorrect",
188
- },
189
- isCorrect.value ? text.value.correct : text.value.incorrect,
190
- ),
191
- !quizContext?.inQuiz
192
- ? renderButton(text.value.reset, { variant: "ghost", onClick: handleReset })
193
- : null,
194
- ]),
195
- ]),
196
- isSubmitted.value && (props.explanation || useYaml.value)
197
- ? h(
198
- "div",
199
- {
200
- class: "content-quiz-explanation",
201
- },
202
- [
203
- h(Icon, {
204
- name: "lucide:info",
205
- class: "content-quiz-explanation-icon",
206
- ariaHidden: true,
207
- }),
208
- props.explanation
209
- ? h("span", props.explanation)
210
- : h("div", { class: "content-prose content-prose-trim" }, slots.default?.()),
211
- ],
212
- )
213
- : null,
214
- ],
215
- );
216
- }
217
- </script>
218
-
219
- <template>
220
- <component :is="renderQuestion" />
221
- </template>
@@ -1,70 +0,0 @@
1
- <script setup lang="ts">
2
- import { NuxtLink } from "#components";
3
- import { computed } from "vue";
4
- import { useProseAppearance } from "../../composables/useProseAppearance";
5
- import { isExternalLink, resolveIconifyIcon } from "./icons";
6
-
7
- type ReadMoreLink = {
8
- title: string;
9
- to: string;
10
- description?: string;
11
- target?: string;
12
- icon?: string;
13
- };
14
-
15
- const props = defineProps<{
16
- title?: string;
17
- links?: unknown;
18
- appearance?: "quiet" | "tint";
19
- }>();
20
-
21
- const appearance = useProseAppearance("readMore", () => props.appearance);
22
- const items = computed<ReadMoreLink[]>(() =>
23
- Array.isArray(props.links)
24
- ? props.links.filter(
25
- (link): link is ReadMoreLink =>
26
- Boolean(link) &&
27
- typeof link === "object" &&
28
- typeof link.title === "string" &&
29
- typeof link.to === "string",
30
- )
31
- : [],
32
- );
33
-
34
- const componentFor = (to: string) => (isExternalLink(to) ? "a" : NuxtLink);
35
- const linkProps = (item: ReadMoreLink) =>
36
- isExternalLink(item.to)
37
- ? { href: item.to, target: item.target, rel: "noreferrer noopener" }
38
- : { to: item.to, target: item.target };
39
- </script>
40
-
41
- <template>
42
- <nav
43
- v-if="items.length"
44
- class="content-read-more not-prose"
45
- :data-appearance="appearance"
46
- :aria-label="title"
47
- >
48
- <p v-if="title" class="content-read-more-title">{{ title }}</p>
49
- <div class="content-read-more-list">
50
- <component
51
- :is="componentFor(item.to)"
52
- v-for="item in items"
53
- :key="item.to"
54
- v-bind="linkProps(item)"
55
- class="content-read-more-link"
56
- >
57
- <span v-if="item.icon" class="content-read-more-icon" aria-hidden="true">
58
- <Icon :name="resolveIconifyIcon(item.icon) ?? item.icon" />
59
- </span>
60
- <span class="content-read-more-copy">
61
- <span class="content-read-more-label">{{ item.title }}</span>
62
- <span v-if="item.description" class="content-read-more-description">
63
- {{ item.description }}
64
- </span>
65
- </span>
66
- <Icon name="lucide:arrow-up-right" class="content-read-more-arrow" aria-hidden="true" />
67
- </component>
68
- </div>
69
- </nav>
70
- </template>
@@ -1,59 +0,0 @@
1
- <script setup lang="ts">
2
- import type { HTMLAttributes, VNode } from "vue";
3
- import { cloneVNode, computed, h, resolveComponent, useSlots } from "vue";
4
- import { useProseAppearance } from "../../composables/useProseAppearance";
5
- import { cn } from "../../utils";
6
-
7
- const props = withDefaults(
8
- defineProps<{
9
- mode?: "icons" | "numbered";
10
- appearance?: "quiet" | "tint";
11
- class?: HTMLAttributes["class"];
12
- }>(),
13
- { mode: "icons" },
14
- );
15
-
16
- const slots = useSlots();
17
- const appearance = useProseAppearance("steps", () => props.appearance);
18
-
19
- const steps = computed(() => {
20
- const result: Array<{ heading: VNode; body: VNode[] }> = [];
21
- for (const node of slots.default?.() ?? []) {
22
- if (typeof node.props?.id === "string") {
23
- result.push({ heading: node, body: [] });
24
- } else if (result.length) {
25
- result.at(-1)!.body.push(node);
26
- }
27
- }
28
- return result;
29
- });
30
-
31
- function renderSteps() {
32
- const Icon = resolveComponent("Icon");
33
- return h(
34
- "div",
35
- {
36
- class: cn("content-steps not-prose", props.class),
37
- "data-mode": props.mode,
38
- "data-appearance": appearance.value,
39
- },
40
- steps.value.map((step, index) =>
41
- h("section", { key: index, class: "content-step" }, [
42
- h("span", { class: "content-step-marker", "aria-hidden": "true" }, [
43
- props.mode === "icons"
44
- ? h(Icon, { name: "lucide:check", class: "content-step-icon" })
45
- : String(index + 1),
46
- ]),
47
- h("div", { class: "content-step-body content-prose content-prose-trim" }, [
48
- cloneVNode(step.heading, { class: "content-step-title" }),
49
- ...step.body,
50
- ]),
51
- ]),
52
- ),
53
- );
54
- }
55
- </script>
56
-
57
- <template>
58
- <component :is="renderSteps" />
59
- </template>
@@ -1,23 +0,0 @@
1
- <script setup lang="ts">
2
- import type { HTMLAttributes } from "vue";
3
- import MdcCallout from "./MdcCallout.vue";
4
-
5
- const props = defineProps<{
6
- title?: string;
7
- icon?: string;
8
- appearance?: "quiet" | "tint";
9
- class?: HTMLAttributes["class"];
10
- }>();
11
- </script>
12
-
13
- <template>
14
- <MdcCallout
15
- :title="title"
16
- :icon="icon"
17
- type="success"
18
- :appearance="appearance"
19
- :class="props.class"
20
- >
21
- <slot />
22
- </MdcCallout>
23
- </template>
@@ -1,17 +0,0 @@
1
- <script setup lang="ts">
2
- defineOptions({
3
- inheritAttrs: false,
4
- });
5
-
6
- defineProps<{
7
- icon?: string;
8
- label?: string;
9
- inGroup?: boolean;
10
- }>();
11
- </script>
12
-
13
- <template>
14
- <div :label="label" :in-group="inGroup">
15
- <slot />
16
- </div>
17
- </template>