@lupinum/ginko-docs 0.2.2 → 0.2.3
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.
- package/README.md +130 -73
- package/app/app.config.ts +14 -12
- package/app/app.vue +2 -2
- package/app/assets/css/prose.css +2203 -149
- package/app/assets/css/tailwind.css +60 -38
- package/app/assets/css/theme-palettes.css +309 -0
- package/app/components/content/DocumentPageShell.vue +1 -1
- package/app/components/content/Feedback.vue +12 -11
- package/app/components/content/ImageZoomDialog.vue +84 -0
- package/app/components/content/PageMarkdownCopy.vue +2 -0
- package/app/components/content/imageZoom.ts +25 -0
- package/app/components/mdc/MdcAccordion.vue +6 -3
- package/app/components/mdc/MdcAccordionItem.vue +7 -16
- package/app/components/mdc/MdcApi.vue +159 -0
- package/app/components/mdc/MdcAside.vue +6 -10
- package/app/components/mdc/MdcCallout.vue +9 -10
- package/app/components/mdc/MdcCard.vue +36 -47
- package/app/components/mdc/MdcCards.vue +30 -0
- package/app/components/mdc/MdcCodeGroup.vue +4 -0
- package/app/components/mdc/MdcCodeTree.vue +234 -0
- package/app/components/mdc/MdcCollapse.vue +61 -0
- package/app/components/mdc/MdcError.vue +8 -1
- package/app/components/mdc/MdcExcerpt.vue +26 -0
- package/app/components/mdc/MdcFigure.vue +54 -20
- package/app/components/mdc/MdcFiles.vue +120 -0
- package/app/components/mdc/MdcIdea.vue +2 -1
- package/app/components/mdc/MdcInfo.vue +2 -1
- package/app/components/mdc/MdcInlineToc.vue +51 -0
- package/app/components/{ui/dialog/DialogHeader.vue → mdc/MdcKbd.vue} +2 -4
- package/app/components/mdc/MdcNote.vue +2 -1
- package/app/components/mdc/MdcQuiz.vue +43 -83
- package/app/components/mdc/MdcQuizQuestion.vue +104 -109
- package/app/components/mdc/MdcReadMore.vue +57 -77
- package/app/components/mdc/MdcSteps.vue +45 -11
- package/app/components/mdc/MdcSuccess.vue +8 -1
- package/app/components/mdc/MdcTabs.vue +44 -26
- package/app/components/mdc/MdcTimeline.vue +4 -1
- package/app/components/mdc/MdcTimelineItem.vue +18 -27
- package/app/components/mdc/MdcWarning.vue +8 -1
- package/app/components/mdc/api.utils.ts +132 -0
- package/app/components/mdc/code-tree.utils.ts +22 -0
- package/app/components/mdc/quiz.utils.ts +38 -0
- package/app/components/prose/ProseBlockquote.vue +17 -1
- package/app/components/prose/ProseH1.vue +1 -1
- package/app/components/prose/ProseH2.vue +7 -2
- package/app/components/prose/ProseH3.vue +7 -2
- package/app/components/prose/ProseH4.vue +17 -2
- package/app/components/{ui/kbd/KbdGroup.vue → prose/ProseH5.vue} +4 -3
- package/app/components/{ui/card/CardTitle.vue → prose/ProseH6.vue} +4 -3
- package/app/components/prose/ProseImg.vue +19 -112
- package/app/components/prose/ProsePre.vue +63 -100
- package/app/components/prose/ProseTable.vue +5 -3
- package/app/components/site/ModeToggle.vue +12 -3
- package/app/components/site/SiteBanner.vue +43 -2
- package/app/components/site/SiteFooter.vue +3 -5
- package/app/components/site/SiteHeader.vue +255 -132
- package/app/components/site/SiteHeroCode.vue +164 -0
- package/app/components/site/SiteLocaleSwitcher.vue +22 -4
- package/app/components/ui/badge/Badge.vue +1 -1
- package/app/components/ui/button/Button.vue +1 -1
- package/app/components/ui/collapsible/CollapsibleContent.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +14 -4
- package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +1 -1
- package/app/components/ui/dropdown-menu/index.ts +0 -9
- package/app/components/ui/kbd/Kbd.vue +1 -1
- package/app/components/ui/kbd/index.ts +0 -1
- package/app/components/ui/scroll-area/ScrollArea.vue +1 -1
- package/app/components/ui/scroll-area/ScrollBar.vue +1 -1
- package/app/components/ui/separator/Separator.vue +1 -1
- package/app/components/ui/sheet/SheetContent.vue +1 -1
- package/app/components/ui/sheet/SheetDescription.vue +1 -1
- package/app/components/ui/sheet/SheetOverlay.vue +1 -1
- package/app/components/ui/sheet/SheetTitle.vue +1 -1
- package/app/components/ui/sheet/index.ts +0 -3
- package/app/components/ui/tabs/Tabs.vue +1 -1
- package/app/components/ui/tabs/TabsList.vue +1 -1
- package/app/components/ui/tabs/TabsTrigger.vue +1 -1
- package/app/components/ui/tabs/index.ts +0 -1
- package/app/composables/site-navigation.utils.ts +29 -11
- package/app/composables/useContentRouteAlternates.ts +13 -21
- package/app/composables/useLocalizedRouteSwitch.ts +1 -1
- package/app/composables/useMetaKey.ts +18 -0
- package/app/composables/useProseAppearance.ts +24 -0
- package/app/composables/useSiteNavigation.ts +3 -18
- package/app/config/site.utils.ts +1 -1
- package/app/error.vue +1 -5
- package/app/features/docs/components/DocsBreadcrumb.vue +37 -0
- package/app/features/docs/components/DocsContributeLinks.vue +28 -18
- package/app/features/docs/components/DocsMobileToc.vue +12 -8
- package/app/features/docs/components/DocsPageContent.vue +84 -47
- package/app/features/docs/components/DocsPageNav.vue +1 -1
- package/app/features/docs/components/DocsSidebar.vue +30 -20
- package/app/features/docs/components/DocsSidebarItem.vue +53 -100
- package/app/features/docs/components/DocsSidebarRow.vue +27 -0
- package/app/features/docs/components/DocsSidebarTabs.vue +46 -22
- package/app/features/docs/components/DocsToc.vue +80 -34
- package/app/features/docs/composables/useDocsNavigation.ts +2 -2
- package/app/features/docs/composables/useDocsNavigationData.ts +4 -1
- package/app/features/docs/composables/useRevealActive.ts +58 -0
- package/app/features/docs/composables/useScrollspy.ts +87 -19
- package/app/features/docs/docs-navigation.ts +17 -30
- package/app/features/docs/toc-context.ts +6 -0
- package/app/features/search/command-center.ts +11 -90
- package/app/features/search/components/SiteCommandCenter.vue +58 -38
- package/app/features/search/useCommandCenter.ts +46 -46
- package/app/layouts/docs.vue +1 -1
- package/app/lib/errors.ts +2 -49
- package/app/pages/blog/[slug].vue +16 -8
- package/app/pages/blog/index.vue +26 -13
- package/app/pages/docs/index.vue +10 -14
- package/app/pages/index.vue +174 -59
- package/app/plugins/ginko-docs-theme.ts +12 -0
- package/app/utils/content.ts +11 -0
- package/app/utils/file-icons.ts +90 -0
- package/app/utils/index.ts +7 -1
- package/app/utils/repository.ts +20 -0
- package/content.js +23 -26
- package/content.ts +30 -28
- package/i18n/messages/global/blog.ts +4 -0
- package/i18n/messages/global/command.ts +6 -2
- package/i18n/messages/global/docs.ts +3 -0
- package/i18n/messages/global/nav.ts +6 -2
- package/i18n/messages/index.ts +3 -3
- package/icon-bundle.ts +5 -0
- package/index.d.ts +7 -1
- package/nuxt.config.ts +2 -2
- package/package.json +9 -6
- package/runtime/server/agent-markdown.ts +0 -2
- package/shared/theme-palettes.ts +37 -0
- package/shared/types/app-config.ts +63 -6
- package/tags.ts +47 -59
- package/app/components/mdc/MdcAlert.vue +0 -34
- package/app/components/mdc/MdcCardGroup.vue +0 -47
- package/app/components/mdc/MdcDocImg.vue +0 -25
- package/app/components/mdc/MdcField.vue +0 -62
- package/app/components/mdc/MdcFieldGroup.vue +0 -18
- package/app/components/mdc/MdcPassage.vue +0 -32
- package/app/components/mdc/MdcQuizOption.vue +0 -91
- package/app/components/mdc/MdcReadMoreGroup.vue +0 -18
- package/app/components/mdc/MdcShortcut.vue +0 -46
- package/app/components/mdc/MdcStep.vue +0 -42
- package/app/components/ui/accordion/Accordion.vue +0 -15
- package/app/components/ui/accordion/AccordionContent.vue +0 -23
- package/app/components/ui/accordion/AccordionItem.vue +0 -24
- package/app/components/ui/accordion/AccordionTrigger.vue +0 -35
- package/app/components/ui/accordion/index.ts +0 -4
- package/app/components/ui/alert/Alert.vue +0 -21
- package/app/components/ui/alert/AlertDescription.vue +0 -22
- package/app/components/ui/alert/AlertTitle.vue +0 -22
- package/app/components/ui/alert/index.ts +0 -34
- package/app/components/ui/card/Card.vue +0 -16
- package/app/components/ui/card/CardAction.vue +0 -17
- package/app/components/ui/card/CardContent.vue +0 -14
- package/app/components/ui/card/CardDescription.vue +0 -14
- package/app/components/ui/card/CardFooter.vue +0 -14
- package/app/components/ui/card/CardHeader.vue +0 -22
- package/app/components/ui/card/card-variants.ts +0 -15
- package/app/components/ui/card/index.ts +0 -8
- package/app/components/ui/checkbox/Checkbox.vue +0 -35
- package/app/components/ui/checkbox/index.ts +0 -1
- package/app/components/ui/dialog/Dialog.vue +0 -15
- package/app/components/ui/dialog/DialogClose.vue +0 -12
- package/app/components/ui/dialog/DialogContent.vue +0 -69
- package/app/components/ui/dialog/DialogDescription.vue +0 -21
- package/app/components/ui/dialog/DialogFooter.vue +0 -15
- package/app/components/ui/dialog/DialogOverlay.vue +0 -26
- package/app/components/ui/dialog/DialogPortal.vue +0 -12
- package/app/components/ui/dialog/DialogTitle.vue +0 -21
- package/app/components/ui/dialog/DialogTrigger.vue +0 -12
- package/app/components/ui/dialog/index.ts +0 -10
- package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +0 -36
- package/app/components/ui/dropdown-menu/DropdownMenuGroup.vue +0 -12
- package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +0 -25
- package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +0 -17
- package/app/components/ui/dropdown-menu/DropdownMenuSub.vue +0 -15
- package/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue +0 -29
- package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +0 -31
- package/app/components/ui/input/Input.vue +0 -23
- package/app/components/ui/input/index.ts +0 -1
- package/app/components/ui/label/Label.vue +0 -14
- package/app/components/ui/label/index.ts +0 -1
- package/app/components/ui/sheet/SheetClose.vue +0 -12
- package/app/components/ui/sheet/SheetFooter.vue +0 -12
- package/app/components/ui/sheet/SheetHeader.vue +0 -12
- package/app/components/ui/switch/Switch.vue +0 -39
- package/app/components/ui/switch/index.ts +0 -1
- package/app/components/ui/tabs/TabsContent.vue +0 -21
- package/app/components/ui/textarea/Textarea.vue +0 -31
- package/app/components/ui/textarea/index.ts +0 -1
- package/app/features/docs/composables/useDocsEntryPath.ts +0 -13
- package/app/lib/utils.ts +0 -7
- package/i18n/messages/source.ts +0 -5
- package/runtime/markdown/inline-code-highlight.ts +0 -155
- package/runtime/utils.ts +0 -7
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { VNode } from "vue";
|
|
3
|
-
import { h, provide, reactive, ref, computed, useSlots } from "vue";
|
|
3
|
+
import { cloneVNode, h, provide, reactive, ref, computed, useSlots } from "vue";
|
|
4
4
|
import { Icon } from "#components";
|
|
5
5
|
import { useI18n } from "#imports";
|
|
6
6
|
import { cn } from "../../utils";
|
|
7
|
+
import { useProseAppearance } from "../../composables/useProseAppearance";
|
|
7
8
|
|
|
8
9
|
const props = defineProps<{
|
|
9
10
|
title?: string;
|
|
@@ -16,7 +17,9 @@ const props = defineProps<{
|
|
|
16
17
|
perfectLabel?: string;
|
|
17
18
|
retryPrompt?: string;
|
|
18
19
|
resetLabel?: string;
|
|
20
|
+
appearance?: "quiet" | "tint";
|
|
19
21
|
}>();
|
|
22
|
+
const appearance = useProseAppearance("quiz", () => props.appearance);
|
|
20
23
|
|
|
21
24
|
const MDC_QUIZ = Symbol.for("mdc.quiz");
|
|
22
25
|
|
|
@@ -38,15 +41,6 @@ function registerQuestion(): number {
|
|
|
38
41
|
|
|
39
42
|
function markAnswered(index: number, correct: boolean) {
|
|
40
43
|
state.answers.set(index, correct);
|
|
41
|
-
setTimeout(() => {
|
|
42
|
-
if (showResults.value) return;
|
|
43
|
-
for (let i = index + 1; i < state.totalQuestions; i++) {
|
|
44
|
-
if (!state.answers.has(i)) {
|
|
45
|
-
activeIndex.value = i;
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}, 1200);
|
|
50
44
|
}
|
|
51
45
|
|
|
52
46
|
function reset() {
|
|
@@ -60,6 +54,7 @@ provide(MDC_QUIZ, { registerQuestion, markAnswered, resetKey, inQuiz: true });
|
|
|
60
54
|
|
|
61
55
|
const correctCount = computed(() => [...state.answers.values()].filter(Boolean).length);
|
|
62
56
|
const currentAnswered = computed(() => state.answers.has(activeIndex.value));
|
|
57
|
+
const allAnswered = computed(() => state.answers.size === state.totalQuestions);
|
|
63
58
|
const { locale } = useI18n();
|
|
64
59
|
|
|
65
60
|
const text = computed(() => {
|
|
@@ -68,7 +63,7 @@ const text = computed(() => {
|
|
|
68
63
|
return {
|
|
69
64
|
question: props.questionLabel ?? (isGerman ? "Frage" : "Question"),
|
|
70
65
|
of: isGerman ? "von" : "of",
|
|
71
|
-
back: props.backLabel ?? (isGerman ? "
|
|
66
|
+
back: props.backLabel ?? (isGerman ? "Zurück" : "Back"),
|
|
72
67
|
next: props.nextLabel ?? (isGerman ? "Weiter" : "Next"),
|
|
73
68
|
results: props.resultsLabel ?? (isGerman ? "Ergebnis anzeigen" : "See Results"),
|
|
74
69
|
correct: props.correctSummaryLabel ?? (isGerman ? "richtig" : "correct"),
|
|
@@ -97,35 +92,19 @@ function renderButton(
|
|
|
97
92
|
{
|
|
98
93
|
type: "button",
|
|
99
94
|
disabled: options.disabled,
|
|
100
|
-
class: cn(
|
|
101
|
-
"inline-flex h-8 items-center justify-center gap-1.5 rounded-md px-3 text-sm font-medium transition-colors focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4",
|
|
102
|
-
options.variant === "ghost"
|
|
103
|
-
? "hover:bg-accent hover:text-accent-foreground"
|
|
104
|
-
: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
|
|
105
|
-
),
|
|
95
|
+
class: cn("content-quiz-button", options.variant === "ghost" && "content-quiz-button-ghost"),
|
|
106
96
|
onClick: options.onClick,
|
|
107
97
|
},
|
|
108
|
-
|
|
98
|
+
children,
|
|
109
99
|
);
|
|
110
100
|
}
|
|
111
101
|
|
|
112
102
|
function renderHeader() {
|
|
113
|
-
return h("div", { class: "
|
|
114
|
-
h(
|
|
115
|
-
|
|
116
|
-
{
|
|
117
|
-
|
|
118
|
-
"flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary",
|
|
119
|
-
},
|
|
120
|
-
[h(Icon, { name: "lucide:circle-help", class: "size-4", ariaHidden: true })],
|
|
121
|
-
),
|
|
122
|
-
h("div", { class: "min-w-0 flex-1" }, [
|
|
123
|
-
props.title
|
|
124
|
-
? h("div", { class: "font-semibold leading-none tracking-tight" }, () => props.title)
|
|
125
|
-
: null,
|
|
126
|
-
props.description
|
|
127
|
-
? h("div", { class: "text-sm text-muted-foreground" }, () => props.description)
|
|
128
|
-
: null,
|
|
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,
|
|
129
108
|
]),
|
|
130
109
|
]);
|
|
131
110
|
}
|
|
@@ -133,18 +112,15 @@ function renderHeader() {
|
|
|
133
112
|
function renderDots() {
|
|
134
113
|
return h(
|
|
135
114
|
"div",
|
|
136
|
-
{ class: "
|
|
115
|
+
{ class: "content-quiz-dots" },
|
|
137
116
|
Array.from({ length: state.totalQuestions }, (_, i) =>
|
|
138
117
|
h("button", {
|
|
139
118
|
type: "button",
|
|
140
|
-
class:
|
|
141
|
-
|
|
142
|
-
i === activeIndex.value
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
? "bg-primary"
|
|
146
|
-
: "bg-muted-foreground/30",
|
|
147
|
-
),
|
|
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,
|
|
148
124
|
onClick: () => {
|
|
149
125
|
activeIndex.value = i;
|
|
150
126
|
},
|
|
@@ -156,38 +132,29 @@ function renderDots() {
|
|
|
156
132
|
function renderFooter() {
|
|
157
133
|
const isLast = activeIndex.value === state.totalQuestions - 1;
|
|
158
134
|
|
|
159
|
-
return h("div", { class: "
|
|
135
|
+
return h("div", { class: "content-quiz-footer" }, [
|
|
160
136
|
h(
|
|
161
137
|
"span",
|
|
162
|
-
{ class: "
|
|
138
|
+
{ class: "content-quiz-progress" },
|
|
163
139
|
`${text.value.question} ${activeIndex.value + 1} ${text.value.of} ${state.totalQuestions}`,
|
|
164
140
|
),
|
|
165
141
|
renderDots(),
|
|
166
|
-
h("div", { class: "
|
|
167
|
-
renderButton(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
],
|
|
172
|
-
{
|
|
173
|
-
disabled: activeIndex.value === 0,
|
|
174
|
-
onClick: () => {
|
|
175
|
-
activeIndex.value--;
|
|
176
|
-
},
|
|
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--;
|
|
177
147
|
},
|
|
178
|
-
),
|
|
148
|
+
}),
|
|
179
149
|
isLast
|
|
180
150
|
? renderButton(text.value.results, {
|
|
181
|
-
disabled: !
|
|
151
|
+
disabled: !allAnswered.value,
|
|
182
152
|
onClick: () => {
|
|
183
153
|
showResults.value = true;
|
|
184
154
|
},
|
|
185
155
|
})
|
|
186
156
|
: renderButton(
|
|
187
|
-
[
|
|
188
|
-
text.value.next,
|
|
189
|
-
h(Icon, { name: "lucide:chevron-right", class: "size-4", ariaHidden: true }),
|
|
190
|
-
],
|
|
157
|
+
[text.value.next, h(Icon, { name: "lucide:chevron-right", ariaHidden: true })],
|
|
191
158
|
{
|
|
192
159
|
disabled: !currentAnswered.value,
|
|
193
160
|
onClick: () => {
|
|
@@ -202,32 +169,24 @@ function renderFooter() {
|
|
|
202
169
|
function renderResults() {
|
|
203
170
|
const perfect = correctCount.value === state.totalQuestions;
|
|
204
171
|
|
|
205
|
-
return h("div", { class: "
|
|
172
|
+
return h("div", { class: "content-quiz-results" }, [
|
|
206
173
|
h(
|
|
207
174
|
"div",
|
|
208
175
|
{
|
|
209
|
-
class:
|
|
210
|
-
|
|
211
|
-
perfect ? "bg-success/10" : "bg-muted",
|
|
212
|
-
),
|
|
176
|
+
class: "content-quiz-results-icon",
|
|
177
|
+
"data-state": perfect ? "perfect" : undefined,
|
|
213
178
|
},
|
|
214
|
-
[
|
|
215
|
-
h(Icon, {
|
|
216
|
-
name: "lucide:trophy",
|
|
217
|
-
class: cn("size-6", perfect ? "text-success" : "text-muted-foreground"),
|
|
218
|
-
ariaHidden: true,
|
|
219
|
-
}),
|
|
220
|
-
],
|
|
179
|
+
[h(Icon, { name: "lucide:trophy", ariaHidden: true })],
|
|
221
180
|
),
|
|
222
181
|
h("div", {}, [
|
|
223
182
|
h(
|
|
224
183
|
"p",
|
|
225
|
-
{ class: "
|
|
184
|
+
{ class: "content-quiz-results-score" },
|
|
226
185
|
`${correctCount.value} ${text.value.of} ${state.totalQuestions} ${text.value.correct}`,
|
|
227
186
|
),
|
|
228
187
|
h(
|
|
229
188
|
"p",
|
|
230
|
-
{ class: "
|
|
189
|
+
{ class: "content-quiz-results-note" },
|
|
231
190
|
perfect ? text.value.perfect : text.value.retryPrompt,
|
|
232
191
|
),
|
|
233
192
|
]),
|
|
@@ -243,10 +202,12 @@ function renderQuiz() {
|
|
|
243
202
|
const children: VNode[] = [renderHeader()];
|
|
244
203
|
|
|
245
204
|
if (showResults.value) {
|
|
246
|
-
children.push(renderResults());
|
|
205
|
+
children.push(h("div", { class: "content-quiz-body" }, [renderResults()]));
|
|
247
206
|
} else {
|
|
248
207
|
children.push(
|
|
249
|
-
h(
|
|
208
|
+
h(
|
|
209
|
+
"div",
|
|
210
|
+
{ class: "content-quiz-body" },
|
|
250
211
|
items.map((node, i) =>
|
|
251
212
|
h(
|
|
252
213
|
"div",
|
|
@@ -254,7 +215,7 @@ function renderQuiz() {
|
|
|
254
215
|
key: i,
|
|
255
216
|
style: i === activeIndex.value ? undefined : { display: "none" },
|
|
256
217
|
},
|
|
257
|
-
[
|
|
218
|
+
[cloneVNode(node)],
|
|
258
219
|
),
|
|
259
220
|
),
|
|
260
221
|
),
|
|
@@ -267,11 +228,10 @@ function renderQuiz() {
|
|
|
267
228
|
return h(
|
|
268
229
|
"div",
|
|
269
230
|
{
|
|
270
|
-
"data-
|
|
271
|
-
class:
|
|
272
|
-
"not-prose my-8 flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-xs",
|
|
231
|
+
"data-appearance": appearance.value,
|
|
232
|
+
class: "content-quiz not-prose",
|
|
273
233
|
},
|
|
274
|
-
|
|
234
|
+
children,
|
|
275
235
|
);
|
|
276
236
|
}
|
|
277
237
|
</script>
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { Ref
|
|
3
|
-
import { h, inject, ref, computed, watch, useSlots
|
|
2
|
+
import type { Ref } from "vue";
|
|
3
|
+
import { h, inject, ref, computed, watch, useSlots } from "vue";
|
|
4
4
|
import { cn } from "../../utils";
|
|
5
5
|
import { Icon } from "#components";
|
|
6
6
|
import { useI18n } from "#imports";
|
|
7
|
-
|
|
8
|
-
type YamlOption = { text: string; correct?: boolean } | string;
|
|
7
|
+
import { parseQuizOptions, type QuizOption, type QuizType } from "./quiz.utils";
|
|
9
8
|
|
|
10
9
|
const props = withDefaults(
|
|
11
10
|
defineProps<{
|
|
12
11
|
question: string;
|
|
13
|
-
type?:
|
|
12
|
+
type?: QuizType;
|
|
14
13
|
explanation?: string;
|
|
15
|
-
options
|
|
14
|
+
options: QuizOption[];
|
|
16
15
|
checkLabel?: string;
|
|
17
16
|
correctLabel?: string;
|
|
18
17
|
incorrectLabel?: string;
|
|
@@ -56,34 +55,17 @@ const text = computed(() => {
|
|
|
56
55
|
reset: props.resetLabel ?? (isGerman ? "Erneut versuchen" : "Try Again"),
|
|
57
56
|
multipleChoice:
|
|
58
57
|
props.multipleChoiceLabel ??
|
|
59
|
-
(isGerman ? "(mehrere Antworten
|
|
58
|
+
(isGerman ? "(mehrere Antworten möglich)" : "(select all that apply)"),
|
|
60
59
|
};
|
|
61
60
|
});
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return { text: opt.text, correct: opt.correct ?? false };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const useYaml = computed(() => Array.isArray(props.options) && props.options.length > 0);
|
|
69
|
-
|
|
70
|
-
function getOptionNodes(): VNode[] {
|
|
71
|
-
return (slots.default?.() ?? []).filter((node): node is VNode => Boolean(node?.type));
|
|
72
|
-
}
|
|
62
|
+
const options = computed(() => parseQuizOptions(props.options, props.type));
|
|
63
|
+
const useYaml = computed(() => options.value.length > 0);
|
|
73
64
|
|
|
74
65
|
const isCorrect = computed(() => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return correct === selectedOptions.value.has(i);
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
const nodes = getOptionNodes();
|
|
82
|
-
return nodes.every((node, i) => {
|
|
83
|
-
const correct = node.props?.correct !== undefined && node.props?.correct !== false;
|
|
84
|
-
const selected = selectedOptions.value.has(i);
|
|
85
|
-
return correct === selected;
|
|
86
|
-
});
|
|
66
|
+
return options.value.every(
|
|
67
|
+
(option, index) => option.correct === selectedOptions.value.has(index),
|
|
68
|
+
);
|
|
87
69
|
});
|
|
88
70
|
|
|
89
71
|
function handleSelect(index: number) {
|
|
@@ -124,100 +106,113 @@ function renderButton(
|
|
|
124
106
|
{
|
|
125
107
|
type: "button",
|
|
126
108
|
disabled: options.disabled,
|
|
127
|
-
class: cn(
|
|
128
|
-
"inline-flex h-8 items-center justify-center gap-1.5 rounded-md px-3 text-sm font-medium transition-colors focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50",
|
|
129
|
-
options.variant === "ghost"
|
|
130
|
-
? "hover:bg-accent hover:text-accent-foreground"
|
|
131
|
-
: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
|
|
132
|
-
),
|
|
109
|
+
class: cn("content-quiz-button", options.variant === "ghost" && "content-quiz-button-ghost"),
|
|
133
110
|
onClick: options.onClick,
|
|
134
111
|
},
|
|
135
|
-
|
|
112
|
+
label,
|
|
136
113
|
);
|
|
137
114
|
}
|
|
138
115
|
|
|
139
116
|
function renderOptions() {
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
+
{
|
|
145
137
|
key: i,
|
|
146
|
-
|
|
147
|
-
correct,
|
|
148
|
-
label: text,
|
|
149
|
-
selected: selectedOptions.value.has(i),
|
|
138
|
+
type: "button",
|
|
150
139
|
disabled: isSubmitted.value,
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
+
});
|
|
166
157
|
}
|
|
167
158
|
|
|
168
159
|
function renderQuestion() {
|
|
169
|
-
return h(
|
|
170
|
-
|
|
171
|
-
|
|
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)
|
|
172
197
|
? h(
|
|
173
|
-
"
|
|
174
|
-
{
|
|
175
|
-
|
|
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
|
+
],
|
|
176
212
|
)
|
|
177
213
|
: null,
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
h("div", { class: "space-y-2" }, renderOptions()),
|
|
181
|
-
h("div", { class: "flex items-center gap-3" }, [
|
|
182
|
-
!isSubmitted.value
|
|
183
|
-
? renderButton(text.value.check, {
|
|
184
|
-
disabled: selectedOptions.value.size === 0,
|
|
185
|
-
onClick: handleSubmit,
|
|
186
|
-
})
|
|
187
|
-
: h("div", { class: "flex items-center gap-3" }, [
|
|
188
|
-
h(
|
|
189
|
-
"span",
|
|
190
|
-
{
|
|
191
|
-
class: cn(
|
|
192
|
-
"text-sm font-medium",
|
|
193
|
-
isCorrect.value ? "text-success" : "text-destructive",
|
|
194
|
-
),
|
|
195
|
-
},
|
|
196
|
-
isCorrect.value ? text.value.correct : text.value.incorrect,
|
|
197
|
-
),
|
|
198
|
-
!quizContext?.inQuiz
|
|
199
|
-
? renderButton(text.value.reset, { variant: "ghost", onClick: handleReset })
|
|
200
|
-
: null,
|
|
201
|
-
]),
|
|
202
|
-
]),
|
|
203
|
-
isSubmitted.value && props.explanation
|
|
204
|
-
? h(
|
|
205
|
-
"div",
|
|
206
|
-
{
|
|
207
|
-
class:
|
|
208
|
-
"flex gap-2 rounded-lg border border-info/30 bg-info/10 px-4 py-3 text-sm text-foreground",
|
|
209
|
-
},
|
|
210
|
-
[
|
|
211
|
-
h(Icon, {
|
|
212
|
-
name: "lucide:info",
|
|
213
|
-
class: "mt-0.5 size-4 shrink-0 text-info",
|
|
214
|
-
ariaHidden: true,
|
|
215
|
-
}),
|
|
216
|
-
h("span", props.explanation),
|
|
217
|
-
],
|
|
218
|
-
)
|
|
219
|
-
: null,
|
|
220
|
-
]);
|
|
214
|
+
],
|
|
215
|
+
);
|
|
221
216
|
}
|
|
222
217
|
</script>
|
|
223
218
|
|
|
@@ -1,90 +1,70 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { NuxtLink } from "#components";
|
|
3
|
-
import { computed
|
|
4
|
-
import {
|
|
3
|
+
import { computed } from "vue";
|
|
4
|
+
import { useProseAppearance } from "../../composables/useProseAppearance";
|
|
5
5
|
import { isExternalLink, resolveIconifyIcon } from "./icons";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
type ReadMoreLink = {
|
|
8
|
+
title: string;
|
|
9
|
+
to: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
target?: string;
|
|
12
|
+
icon?: string;
|
|
13
|
+
};
|
|
9
14
|
|
|
10
|
-
const props =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
icon?: string;
|
|
16
|
-
}>(),
|
|
17
|
-
{
|
|
18
|
-
icon: "lucide:bookmark",
|
|
19
|
-
},
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
const label = computed(() => {
|
|
23
|
-
if (props.title) return props.title;
|
|
24
|
-
return props.to;
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const iconName = computed(() => resolveIconifyIcon(props.icon));
|
|
28
|
-
|
|
29
|
-
const linkProps = computed(() => {
|
|
30
|
-
if (isExternalLink(props.to)) {
|
|
31
|
-
return {
|
|
32
|
-
href: props.to,
|
|
33
|
-
target: props.target,
|
|
34
|
-
rel: "noreferrer noopener" as const,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
to: props.to,
|
|
40
|
-
target: props.target,
|
|
41
|
-
};
|
|
42
|
-
});
|
|
15
|
+
const props = defineProps<{
|
|
16
|
+
title?: string;
|
|
17
|
+
links?: unknown;
|
|
18
|
+
appearance?: "quiet" | "tint";
|
|
19
|
+
}>();
|
|
43
20
|
|
|
44
|
-
const
|
|
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
|
+
);
|
|
45
33
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
? "
|
|
50
|
-
:
|
|
51
|
-
"relative gap-0 rounded-xl border bg-card py-4 text-card-foreground shadow-xs transition-colors",
|
|
52
|
-
"group-hover:border-foreground/20 group-hover:bg-accent/50",
|
|
53
|
-
),
|
|
54
|
-
}));
|
|
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 };
|
|
55
39
|
</script>
|
|
56
40
|
|
|
57
41
|
<template>
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
:
|
|
62
|
-
|
|
63
|
-
'group not-prose relative block overflow-hidden text-sm text-card-foreground transition-colors duration-200 ease-out',
|
|
64
|
-
'outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
|
|
65
|
-
inReadMoreGroup
|
|
66
|
-
? 'my-0 rounded-none bg-transparent px-6 py-4 shadow-none hover:bg-accent/50'
|
|
67
|
-
: 'my-6 cursor-pointer rounded-xl',
|
|
68
|
-
)
|
|
69
|
-
"
|
|
42
|
+
<nav
|
|
43
|
+
v-if="items.length"
|
|
44
|
+
class="content-read-more not-prose"
|
|
45
|
+
:data-appearance="appearance"
|
|
46
|
+
:aria-label="title"
|
|
70
47
|
>
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<
|
|
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>
|
|
86
65
|
</span>
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
66
|
+
<Icon name="lucide:arrow-up-right" class="content-read-more-arrow" aria-hidden="true" />
|
|
67
|
+
</component>
|
|
68
|
+
</div>
|
|
69
|
+
</nav>
|
|
90
70
|
</template>
|