@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
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { VNode } from "vue";
|
|
3
|
+
import { Comment, cloneVNode, h, ref, resolveComponent, useSlots } from "vue";
|
|
4
|
+
import { cn } from "../../utils";
|
|
5
|
+
import { resolveFileIcon } from "../../utils/file-icons";
|
|
6
|
+
import { useProseAppearance } from "../../composables/useProseAppearance";
|
|
7
|
+
import { validateCodeTreePaths } from "./code-tree.utils";
|
|
8
|
+
|
|
9
|
+
type CodeBlockProps = {
|
|
10
|
+
code?: string;
|
|
11
|
+
filename?: string | null;
|
|
12
|
+
language?: string | null;
|
|
13
|
+
meta?: string | null;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type TreeLeaf = {
|
|
17
|
+
path: string;
|
|
18
|
+
node: VNode;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type TreeNode = {
|
|
22
|
+
label: string;
|
|
23
|
+
path: string;
|
|
24
|
+
children?: TreeNode[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const props = defineProps<{
|
|
28
|
+
/** Path of the file to show initially, e.g. "app/app.config.ts". */
|
|
29
|
+
defaultValue?: string;
|
|
30
|
+
expandAll?: boolean;
|
|
31
|
+
appearance?: "quiet" | "tint";
|
|
32
|
+
}>();
|
|
33
|
+
const appearance = useProseAppearance("code", () => props.appearance);
|
|
34
|
+
|
|
35
|
+
const slots = useSlots();
|
|
36
|
+
|
|
37
|
+
function isRenderableNode(node: VNode) {
|
|
38
|
+
return node.type !== Comment;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function cleanLabel(input: string | null | undefined): string {
|
|
42
|
+
return (
|
|
43
|
+
input
|
|
44
|
+
?.trim()
|
|
45
|
+
.replace(/^\[(.*)]$/, "$1")
|
|
46
|
+
.trim() || ""
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function labelFromMeta(meta: string | null | undefined): string {
|
|
51
|
+
const match = meta?.match(/\[([^\]]+)]/);
|
|
52
|
+
return cleanLabel(match?.[1]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolveLeaves(): TreeLeaf[] {
|
|
56
|
+
const leaves = (slots.default?.() || []).filter(isRenderableNode).map((node) => {
|
|
57
|
+
const nodeProps = (node.props || {}) as CodeBlockProps;
|
|
58
|
+
const path = cleanLabel(nodeProps.filename) || labelFromMeta(nodeProps.meta);
|
|
59
|
+
if (!path) throw new TypeError("Every code tree block requires a filename label");
|
|
60
|
+
return { path, node };
|
|
61
|
+
});
|
|
62
|
+
validateCodeTreePaths(
|
|
63
|
+
leaves.map((leaf) => leaf.path),
|
|
64
|
+
props.defaultValue,
|
|
65
|
+
);
|
|
66
|
+
return leaves;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function buildTree(paths: string[]): TreeNode[] {
|
|
70
|
+
const map = new Map<string, TreeNode>();
|
|
71
|
+
const root: TreeNode[] = [];
|
|
72
|
+
|
|
73
|
+
for (const fullPath of paths) {
|
|
74
|
+
const parts = fullPath.split("/");
|
|
75
|
+
let path = "";
|
|
76
|
+
|
|
77
|
+
parts.forEach((part, i) => {
|
|
78
|
+
path = path ? `${path}/${part}` : part;
|
|
79
|
+
if (map.has(path)) return;
|
|
80
|
+
|
|
81
|
+
const node: TreeNode = { label: part, path, ...(i < parts.length - 1 && { children: [] }) };
|
|
82
|
+
map.set(path, node);
|
|
83
|
+
if (i === 0) root.push(node);
|
|
84
|
+
else map.get(parts.slice(0, i).join("/"))?.children?.push(node);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const sort = (nodes: TreeNode[]): TreeNode[] =>
|
|
89
|
+
nodes
|
|
90
|
+
.sort((a, b) =>
|
|
91
|
+
!!a.children === !!b.children ? a.label.localeCompare(b.label) : b.children ? 1 : -1,
|
|
92
|
+
)
|
|
93
|
+
.map((node) => ({ ...node, children: node.children && sort(node.children) }));
|
|
94
|
+
|
|
95
|
+
return sort(root);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function ancestorsOf(path: string): string[] {
|
|
99
|
+
const parts = path.split("/");
|
|
100
|
+
return parts.slice(0, -1).map((_, i) => parts.slice(0, i + 1).join("/"));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const selectedPath = ref(props.defaultValue ?? "");
|
|
104
|
+
const expanded = ref(new Set<string>());
|
|
105
|
+
const expansionChanged = ref(false);
|
|
106
|
+
|
|
107
|
+
function toggleDir(path: string, initial: Set<string>) {
|
|
108
|
+
if (!expansionChanged.value) {
|
|
109
|
+
expanded.value = new Set(initial);
|
|
110
|
+
expansionChanged.value = true;
|
|
111
|
+
}
|
|
112
|
+
if (expanded.value.has(path)) expanded.value.delete(path);
|
|
113
|
+
else expanded.value.add(path);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const rowClass = "content-code-tree-row";
|
|
117
|
+
|
|
118
|
+
function renderRows(
|
|
119
|
+
nodes: TreeNode[],
|
|
120
|
+
openPaths: Set<string>,
|
|
121
|
+
activePath: string,
|
|
122
|
+
): (VNode | null)[] {
|
|
123
|
+
const Icon = resolveComponent("Icon");
|
|
124
|
+
|
|
125
|
+
return nodes.map((node) => {
|
|
126
|
+
if (node.children) {
|
|
127
|
+
const isOpen = openPaths.has(node.path);
|
|
128
|
+
return h("li", { key: node.path, role: "none" }, [
|
|
129
|
+
h(
|
|
130
|
+
"button",
|
|
131
|
+
{
|
|
132
|
+
type: "button",
|
|
133
|
+
class: rowClass,
|
|
134
|
+
"aria-expanded": isOpen ? "true" : "false",
|
|
135
|
+
onClick: () => toggleDir(node.path, openPaths),
|
|
136
|
+
},
|
|
137
|
+
[
|
|
138
|
+
h(Icon, {
|
|
139
|
+
name: "lucide:chevron-right",
|
|
140
|
+
class: "content-code-tree-chevron",
|
|
141
|
+
"data-open": isOpen ? "true" : undefined,
|
|
142
|
+
"aria-hidden": "true",
|
|
143
|
+
}),
|
|
144
|
+
h(Icon, {
|
|
145
|
+
name: isOpen ? "lucide:folder-open" : "lucide:folder",
|
|
146
|
+
class: "content-code-tree-icon",
|
|
147
|
+
"aria-hidden": "true",
|
|
148
|
+
}),
|
|
149
|
+
h("span", { class: "content-code-tree-label" }, node.label),
|
|
150
|
+
],
|
|
151
|
+
),
|
|
152
|
+
isOpen
|
|
153
|
+
? h(
|
|
154
|
+
"ul",
|
|
155
|
+
{ role: "group", class: "content-code-tree-branch" },
|
|
156
|
+
renderRows(node.children, openPaths, activePath),
|
|
157
|
+
)
|
|
158
|
+
: null,
|
|
159
|
+
]);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const isSelected = activePath === node.path;
|
|
163
|
+
return h("li", { key: node.path, role: "none" }, [
|
|
164
|
+
h(
|
|
165
|
+
"button",
|
|
166
|
+
{
|
|
167
|
+
type: "button",
|
|
168
|
+
class: cn(rowClass, isSelected && "content-code-tree-row-active"),
|
|
169
|
+
"aria-current": isSelected ? "true" : undefined,
|
|
170
|
+
onClick: () => (selectedPath.value = node.path),
|
|
171
|
+
},
|
|
172
|
+
[
|
|
173
|
+
h("span", { class: "content-code-tree-spacer", "aria-hidden": "true" }),
|
|
174
|
+
h(resolveComponent("Icon"), {
|
|
175
|
+
name: resolveFileIcon(node.label),
|
|
176
|
+
class: "content-code-tree-icon",
|
|
177
|
+
"aria-hidden": "true",
|
|
178
|
+
}),
|
|
179
|
+
h("span", { class: "content-code-tree-label" }, node.label),
|
|
180
|
+
],
|
|
181
|
+
),
|
|
182
|
+
]);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function renderCodeTree() {
|
|
187
|
+
const leaves = resolveLeaves();
|
|
188
|
+
if (!leaves.length) return null;
|
|
189
|
+
const activePath = leaves.some((leaf) => leaf.path === selectedPath.value)
|
|
190
|
+
? selectedPath.value
|
|
191
|
+
: leaves[0]!.path;
|
|
192
|
+
const initialOpenPaths = new Set(
|
|
193
|
+
props.expandAll ? leaves.flatMap((leaf) => ancestorsOf(leaf.path)) : ancestorsOf(activePath),
|
|
194
|
+
);
|
|
195
|
+
const openPaths = expansionChanged.value ? expanded.value : initialOpenPaths;
|
|
196
|
+
const tree = buildTree(leaves.map((leaf) => leaf.path));
|
|
197
|
+
|
|
198
|
+
return h(
|
|
199
|
+
"div",
|
|
200
|
+
{
|
|
201
|
+
class: "content-code-tree not-prose",
|
|
202
|
+
"data-appearance": appearance.value,
|
|
203
|
+
},
|
|
204
|
+
[
|
|
205
|
+
h(
|
|
206
|
+
"ul",
|
|
207
|
+
{
|
|
208
|
+
class: "content-code-tree-list",
|
|
209
|
+
},
|
|
210
|
+
renderRows(tree, openPaths, activePath),
|
|
211
|
+
),
|
|
212
|
+
h(
|
|
213
|
+
"div",
|
|
214
|
+
{ class: "content-code-tree-pane" },
|
|
215
|
+
leaves.map((leaf) =>
|
|
216
|
+
h(
|
|
217
|
+
"div",
|
|
218
|
+
{
|
|
219
|
+
key: leaf.path,
|
|
220
|
+
class: "content-code-tree-panel",
|
|
221
|
+
style: activePath === leaf.path ? undefined : { display: "none" },
|
|
222
|
+
},
|
|
223
|
+
[cloneVNode(leaf.node)],
|
|
224
|
+
),
|
|
225
|
+
),
|
|
226
|
+
),
|
|
227
|
+
],
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
</script>
|
|
231
|
+
|
|
232
|
+
<template>
|
|
233
|
+
<component :is="renderCodeTree" />
|
|
234
|
+
</template>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onMounted, provide, ref, useId } from "vue";
|
|
3
|
+
import { useResizeObserver } from "@vueuse/core";
|
|
4
|
+
import { useI18n } from "#imports";
|
|
5
|
+
import { useProseAppearance } from "../../composables/useProseAppearance";
|
|
6
|
+
|
|
7
|
+
const props = defineProps<{ appearance?: "quiet" | "tint" }>();
|
|
8
|
+
const appearance = useProseAppearance("code", () => props.appearance);
|
|
9
|
+
// The clipped code block must render on the surface the collapse declares.
|
|
10
|
+
provide("contentCodeAppearance", appearance);
|
|
11
|
+
|
|
12
|
+
// Clips tall authored content (usually a long code block) at a fixed height
|
|
13
|
+
// until the reader expands it. Expanding removes the cap entirely, so the page
|
|
14
|
+
// owns scrolling instead of a nested scroll area.
|
|
15
|
+
const { t } = useI18n();
|
|
16
|
+
const regionId = useId();
|
|
17
|
+
const region = ref<HTMLElement | null>(null);
|
|
18
|
+
const expanded = ref(false);
|
|
19
|
+
const overflows = ref(false);
|
|
20
|
+
const lineCount = ref(0);
|
|
21
|
+
|
|
22
|
+
function measure() {
|
|
23
|
+
const el = region.value;
|
|
24
|
+
if (!el) return;
|
|
25
|
+
overflows.value = el.scrollHeight - el.clientHeight > 2;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
onMounted(() => {
|
|
29
|
+
lineCount.value = region.value?.querySelectorAll("pre .line").length ?? 0;
|
|
30
|
+
measure();
|
|
31
|
+
});
|
|
32
|
+
useResizeObserver(region, measure);
|
|
33
|
+
|
|
34
|
+
const label = computed(() =>
|
|
35
|
+
lineCount.value ? t("docs.showAllLines", { count: lineCount.value }) : t("docs.showMore"),
|
|
36
|
+
);
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
40
|
+
<div class="content-collapse" :data-appearance="appearance">
|
|
41
|
+
<div
|
|
42
|
+
:id="regionId"
|
|
43
|
+
ref="region"
|
|
44
|
+
class="content-collapse-region"
|
|
45
|
+
:data-collapsed="!expanded || undefined"
|
|
46
|
+
>
|
|
47
|
+
<slot />
|
|
48
|
+
</div>
|
|
49
|
+
<div v-if="!expanded && overflows" class="content-collapse-fade">
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
class="content-collapse-button"
|
|
53
|
+
:aria-controls="regionId"
|
|
54
|
+
aria-expanded="false"
|
|
55
|
+
@click="expanded = true"
|
|
56
|
+
>
|
|
57
|
+
{{ label }}
|
|
58
|
+
</button>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
@@ -5,12 +5,19 @@ import MdcCallout from "./MdcCallout.vue";
|
|
|
5
5
|
const props = defineProps<{
|
|
6
6
|
title?: string;
|
|
7
7
|
icon?: string;
|
|
8
|
+
appearance?: "quiet" | "tint";
|
|
8
9
|
class?: HTMLAttributes["class"];
|
|
9
10
|
}>();
|
|
10
11
|
</script>
|
|
11
12
|
|
|
12
13
|
<template>
|
|
13
|
-
<MdcCallout
|
|
14
|
+
<MdcCallout
|
|
15
|
+
:title="title"
|
|
16
|
+
:icon="icon"
|
|
17
|
+
type="error"
|
|
18
|
+
:appearance="appearance"
|
|
19
|
+
:class="props.class"
|
|
20
|
+
>
|
|
14
21
|
<slot />
|
|
15
22
|
</MdcCallout>
|
|
16
23
|
</template>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "vue";
|
|
3
|
+
import { useProseAppearance } from "../../composables/useProseAppearance";
|
|
4
|
+
import { cn } from "../../utils";
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
label?: string;
|
|
8
|
+
source?: string;
|
|
9
|
+
appearance?: "quiet" | "tint";
|
|
10
|
+
class?: HTMLAttributes["class"];
|
|
11
|
+
}>();
|
|
12
|
+
|
|
13
|
+
const appearance = useProseAppearance("excerpt", () => props.appearance);
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<figure :class="cn('content-excerpt not-prose', props.class)" :data-appearance="appearance">
|
|
18
|
+
<p v-if="label" class="content-excerpt-label">
|
|
19
|
+
{{ label }}
|
|
20
|
+
</p>
|
|
21
|
+
<blockquote class="content-excerpt-body content-prose content-prose-trim">
|
|
22
|
+
<slot />
|
|
23
|
+
</blockquote>
|
|
24
|
+
<figcaption v-if="source" class="content-excerpt-source">{{ source }}</figcaption>
|
|
25
|
+
</figure>
|
|
26
|
+
</template>
|
|
@@ -1,20 +1,41 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { HTMLAttributes } from "vue";
|
|
3
|
+
import { Motion } from "motion-v";
|
|
3
4
|
import { computed } from "vue";
|
|
5
|
+
import { useI18n } from "#imports";
|
|
6
|
+
import ImageZoomDialog from "#ginko-docs/components/content/ImageZoomDialog.vue";
|
|
7
|
+
import { useGinkoDocsConfig } from "#ginko-docs/composables/useGinkoDocsConfig";
|
|
4
8
|
import { cn } from "../../utils";
|
|
9
|
+
import { useProseAppearance } from "../../composables/useProseAppearance";
|
|
5
10
|
|
|
6
|
-
const props =
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
const props = withDefaults(
|
|
12
|
+
defineProps<{
|
|
13
|
+
src?: string;
|
|
14
|
+
alt?: string;
|
|
15
|
+
caption?: string;
|
|
16
|
+
width?: string | number;
|
|
17
|
+
height?: string | number;
|
|
18
|
+
bleed?: boolean | string;
|
|
19
|
+
aspect?: "auto" | "video" | "wide" | "square" | "portrait";
|
|
20
|
+
fit?: "cover" | "contain";
|
|
21
|
+
zoom?: boolean | string;
|
|
22
|
+
class?: HTMLAttributes["class"];
|
|
23
|
+
appearance?: "quiet" | "tint";
|
|
24
|
+
}>(),
|
|
25
|
+
{
|
|
26
|
+
// Vue casts an absent Boolean-typed prop to false; "auto" preserves the
|
|
27
|
+
// site-wide images.zoom default unless the author overrides it.
|
|
28
|
+
zoom: "auto",
|
|
29
|
+
},
|
|
30
|
+
);
|
|
31
|
+
const appearance = useProseAppearance("figure", () => props.appearance);
|
|
17
32
|
|
|
33
|
+
const { t } = useI18n();
|
|
34
|
+
const config = useGinkoDocsConfig();
|
|
35
|
+
const zoomEnabled = computed(() => {
|
|
36
|
+
if (props.zoom === "auto") return config.images?.zoom !== false;
|
|
37
|
+
return props.zoom === true || props.zoom === "true";
|
|
38
|
+
});
|
|
18
39
|
const shouldBleed = computed(
|
|
19
40
|
() => props.bleed === true || props.bleed === "true" || props.bleed === "outside",
|
|
20
41
|
);
|
|
@@ -28,23 +49,36 @@ const aspectClass = computed(() => {
|
|
|
28
49
|
portrait: "aspect-[4/5]",
|
|
29
50
|
}[props.aspect ?? "auto"];
|
|
30
51
|
});
|
|
52
|
+
|
|
53
|
+
const imageClass = computed(() =>
|
|
54
|
+
cn("w-full", aspectClass.value, props.fit === "contain" ? "object-contain" : "object-cover"),
|
|
55
|
+
);
|
|
31
56
|
</script>
|
|
32
57
|
|
|
33
58
|
<template>
|
|
34
59
|
<figure
|
|
35
60
|
:class="cn('content-media not-prose', props.class)"
|
|
36
61
|
:data-bleed="shouldBleed ? 'true' : undefined"
|
|
62
|
+
:data-appearance="appearance"
|
|
37
63
|
>
|
|
38
|
-
<
|
|
39
|
-
v-if="src"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
64
|
+
<template v-if="src">
|
|
65
|
+
<ImageZoomDialog v-if="zoomEnabled" :src="src" :alt="alt" :label="caption">
|
|
66
|
+
<template #trigger="{ layoutId, transition }">
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
class="block w-full cursor-zoom-in rounded-[inherit] focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
|
|
70
|
+
:aria-label="`${t('docs.zoomImage')}: ${alt ?? ''}`"
|
|
71
|
+
>
|
|
72
|
+
<Motion as-child :layout-id="layoutId" :transition="transition">
|
|
73
|
+
<img :src="src" :alt="alt" :width="width" :height="height" :class="imageClass" />
|
|
74
|
+
</Motion>
|
|
75
|
+
</button>
|
|
76
|
+
</template>
|
|
77
|
+
</ImageZoomDialog>
|
|
78
|
+
<img v-else :src="src" :alt="alt" :width="width" :height="height" :class="imageClass" />
|
|
79
|
+
</template>
|
|
46
80
|
<slot />
|
|
47
|
-
<figcaption v-if="caption || alt"
|
|
81
|
+
<figcaption v-if="caption || alt">
|
|
48
82
|
{{ caption || alt }}
|
|
49
83
|
</figcaption>
|
|
50
84
|
</figure>
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { VNode } from "vue";
|
|
3
|
+
import { h, resolveComponent, useSlots } from "vue";
|
|
4
|
+
import { useProseAppearance } from "../../composables/useProseAppearance";
|
|
5
|
+
import { resolveFileIcon } from "../../utils/file-icons";
|
|
6
|
+
|
|
7
|
+
type FileEntry = {
|
|
8
|
+
name: string;
|
|
9
|
+
path: string;
|
|
10
|
+
children?: FileEntry[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const props = defineProps<{
|
|
14
|
+
active?: string;
|
|
15
|
+
annotations?: Record<string, string>;
|
|
16
|
+
appearance?: "quiet" | "tint";
|
|
17
|
+
}>();
|
|
18
|
+
|
|
19
|
+
const slots = useSlots();
|
|
20
|
+
const appearance = useProseAppearance("files", () => props.appearance);
|
|
21
|
+
|
|
22
|
+
function childrenOf(node: VNode): VNode[] {
|
|
23
|
+
if (Array.isArray(node.children)) return node.children.filter(isVNode);
|
|
24
|
+
if (node.children && typeof node.children === "object") {
|
|
25
|
+
const slot = (node.children as { default?: () => unknown }).default;
|
|
26
|
+
const children = typeof slot === "function" ? slot() : [];
|
|
27
|
+
if (Array.isArray(children)) return children.filter(isVNode);
|
|
28
|
+
return isVNode(children) ? [children] : [];
|
|
29
|
+
}
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isVNode(value: unknown): value is VNode {
|
|
34
|
+
return value !== null && typeof value === "object" && "type" in value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function textOf(value: unknown): string {
|
|
38
|
+
if (typeof value === "string") return value;
|
|
39
|
+
if (Array.isArray(value)) return value.map(textOf).join("");
|
|
40
|
+
if (!isVNode(value)) return "";
|
|
41
|
+
if (typeof value.children === "string") return value.children;
|
|
42
|
+
if (value.children && typeof value.children === "object") {
|
|
43
|
+
const slot = (value.children as { default?: () => unknown }).default;
|
|
44
|
+
if (typeof slot === "function") return textOf(slot());
|
|
45
|
+
}
|
|
46
|
+
return childrenOf(value).map(textOf).join("");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseList(list: VNode, parent = ""): FileEntry[] {
|
|
50
|
+
return childrenOf(list)
|
|
51
|
+
.map((item) => {
|
|
52
|
+
const children = childrenOf(item);
|
|
53
|
+
const nested = children.length > 1 ? children.at(-1) : undefined;
|
|
54
|
+
const name = children
|
|
55
|
+
.filter((node) => node !== nested)
|
|
56
|
+
.map(textOf)
|
|
57
|
+
.join("")
|
|
58
|
+
.trim();
|
|
59
|
+
const path = parent ? `${parent}/${name}` : name;
|
|
60
|
+
return {
|
|
61
|
+
name,
|
|
62
|
+
path,
|
|
63
|
+
...(nested ? { children: parseList(nested, path) } : {}),
|
|
64
|
+
};
|
|
65
|
+
})
|
|
66
|
+
.filter((entry) => entry.name.length > 0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function renderEntries(items: FileEntry[]): VNode {
|
|
70
|
+
const Icon = resolveComponent("Icon");
|
|
71
|
+
return h(
|
|
72
|
+
"ul",
|
|
73
|
+
{ class: "content-files-list" },
|
|
74
|
+
items.map((entry) => {
|
|
75
|
+
const folder = Boolean(entry.children);
|
|
76
|
+
const active = props.active === entry.path;
|
|
77
|
+
return h("li", { key: entry.path, class: "content-files-item" }, [
|
|
78
|
+
h(
|
|
79
|
+
"div",
|
|
80
|
+
{
|
|
81
|
+
class: "content-files-row",
|
|
82
|
+
"data-active": active || undefined,
|
|
83
|
+
"aria-current": active ? "true" : undefined,
|
|
84
|
+
},
|
|
85
|
+
[
|
|
86
|
+
h(Icon, {
|
|
87
|
+
name: folder ? "lucide:folder-open" : resolveFileIcon(entry.name),
|
|
88
|
+
class: "content-files-icon",
|
|
89
|
+
"aria-hidden": "true",
|
|
90
|
+
}),
|
|
91
|
+
h("span", { class: "content-files-name" }, entry.name),
|
|
92
|
+
props.annotations?.[entry.path]
|
|
93
|
+
? h("span", { class: "content-files-annotation" }, props.annotations[entry.path])
|
|
94
|
+
: null,
|
|
95
|
+
],
|
|
96
|
+
),
|
|
97
|
+
entry.children?.length ? renderEntries(entry.children) : null,
|
|
98
|
+
]);
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function renderFiles() {
|
|
104
|
+
const list = slots.default?.()[0];
|
|
105
|
+
const entries = list ? parseList(list) : [];
|
|
106
|
+
if (!entries.length) return null;
|
|
107
|
+
return h(
|
|
108
|
+
"div",
|
|
109
|
+
{
|
|
110
|
+
class: "content-files not-prose",
|
|
111
|
+
"data-appearance": appearance.value,
|
|
112
|
+
},
|
|
113
|
+
[renderEntries(entries)],
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
</script>
|
|
117
|
+
|
|
118
|
+
<template>
|
|
119
|
+
<component :is="renderFiles" />
|
|
120
|
+
</template>
|
|
@@ -5,12 +5,13 @@ import MdcCallout from "./MdcCallout.vue";
|
|
|
5
5
|
const props = defineProps<{
|
|
6
6
|
title?: string;
|
|
7
7
|
icon?: string;
|
|
8
|
+
appearance?: "quiet" | "tint";
|
|
8
9
|
class?: HTMLAttributes["class"];
|
|
9
10
|
}>();
|
|
10
11
|
</script>
|
|
11
12
|
|
|
12
13
|
<template>
|
|
13
|
-
<MdcCallout :title="title" :icon="icon" type="idea" :class="props.class">
|
|
14
|
+
<MdcCallout :title="title" :icon="icon" type="idea" :appearance="appearance" :class="props.class">
|
|
14
15
|
<slot />
|
|
15
16
|
</MdcCallout>
|
|
16
17
|
</template>
|
|
@@ -5,12 +5,13 @@ import MdcCallout from "./MdcCallout.vue";
|
|
|
5
5
|
const props = defineProps<{
|
|
6
6
|
title?: string;
|
|
7
7
|
icon?: string;
|
|
8
|
+
appearance?: "quiet" | "tint";
|
|
8
9
|
class?: HTMLAttributes["class"];
|
|
9
10
|
}>();
|
|
10
11
|
</script>
|
|
11
12
|
|
|
12
13
|
<template>
|
|
13
|
-
<MdcCallout :title="title" :icon="icon" type="info" :class="props.class">
|
|
14
|
+
<MdcCallout :title="title" :icon="icon" type="info" :appearance="appearance" :class="props.class">
|
|
14
15
|
<slot />
|
|
15
16
|
</MdcCallout>
|
|
16
17
|
</template>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "vue";
|
|
3
|
+
import { computed, inject } from "vue";
|
|
4
|
+
import {
|
|
5
|
+
Collapsible,
|
|
6
|
+
CollapsibleContent,
|
|
7
|
+
CollapsibleTrigger,
|
|
8
|
+
} from "#ginko-docs/components/ui/collapsible";
|
|
9
|
+
import DocsToc from "#ginko-docs/features/docs/components/DocsToc.vue";
|
|
10
|
+
import { docsTocKey } from "#ginko-docs/features/docs/toc-context";
|
|
11
|
+
import { filterTocByDepth } from "#ginko-docs/utils/content";
|
|
12
|
+
import { useI18n } from "#imports";
|
|
13
|
+
import { cn } from "../../utils";
|
|
14
|
+
|
|
15
|
+
const props = defineProps<{
|
|
16
|
+
title?: string;
|
|
17
|
+
/** Restrict to headings up to this depth (within the page TOC depth). */
|
|
18
|
+
depth?: number;
|
|
19
|
+
open?: boolean | string;
|
|
20
|
+
class?: HTMLAttributes["class"];
|
|
21
|
+
}>();
|
|
22
|
+
|
|
23
|
+
const { t } = useI18n();
|
|
24
|
+
const pageToc = inject(docsTocKey, undefined);
|
|
25
|
+
|
|
26
|
+
const items = computed(() => {
|
|
27
|
+
const all = pageToc?.value ?? [];
|
|
28
|
+
return props.depth ? filterTocByDepth(all, props.depth) : all;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const defaultOpen = computed(
|
|
32
|
+
() => props.open !== undefined && props.open !== false && props.open !== "false",
|
|
33
|
+
);
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<Collapsible
|
|
38
|
+
v-if="items.length"
|
|
39
|
+
:default-open="defaultOpen"
|
|
40
|
+
:class="cn('content-inline-toc not-prose', props.class)"
|
|
41
|
+
>
|
|
42
|
+
<CollapsibleTrigger class="content-inline-toc-trigger">
|
|
43
|
+
<Icon name="lucide:list" class="content-inline-toc-icon" aria-hidden="true" />
|
|
44
|
+
<span class="content-inline-toc-title">{{ title ?? t("docs.toc") }}</span>
|
|
45
|
+
<Icon name="lucide:chevron-down" class="content-inline-toc-chevron" aria-hidden="true" />
|
|
46
|
+
</CollapsibleTrigger>
|
|
47
|
+
<CollapsibleContent class="content-inline-toc-content">
|
|
48
|
+
<DocsToc :items="items" :show-title="false" />
|
|
49
|
+
</CollapsibleContent>
|
|
50
|
+
</Collapsible>
|
|
51
|
+
</template>
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { HTMLAttributes } from "vue";
|
|
3
|
-
import { cn } from "
|
|
3
|
+
import { cn } from "../../utils";
|
|
4
4
|
|
|
5
5
|
const props = defineProps<{ class?: HTMLAttributes["class"] }>();
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<template>
|
|
9
|
-
<
|
|
10
|
-
<slot />
|
|
11
|
-
</div>
|
|
9
|
+
<kbd :class="cn('content-kbd', props.class)"><slot /></kbd>
|
|
12
10
|
</template>
|
|
@@ -5,12 +5,13 @@ import MdcCallout from "./MdcCallout.vue";
|
|
|
5
5
|
const props = defineProps<{
|
|
6
6
|
title?: string;
|
|
7
7
|
icon?: string;
|
|
8
|
+
appearance?: "quiet" | "tint";
|
|
8
9
|
class?: HTMLAttributes["class"];
|
|
9
10
|
}>();
|
|
10
11
|
</script>
|
|
11
12
|
|
|
12
13
|
<template>
|
|
13
|
-
<MdcCallout :title="title" :icon="icon" type="note" :class="props.class">
|
|
14
|
+
<MdcCallout :title="title" :icon="icon" type="note" :appearance="appearance" :class="props.class">
|
|
14
15
|
<slot />
|
|
15
16
|
</MdcCallout>
|
|
16
17
|
</template>
|