@baybreezy/docd 0.0.1
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/app/app.config.ts +239 -0
- package/app/app.vue +19 -0
- package/app/assets/css/tailwind.css +335 -0
- package/app/assets/css/theme.css +1171 -0
- package/app/components/OgImage/Docs.takumi.vue +69 -0
- package/app/components/OgImage/Landing.takumi.vue +64 -0
- package/app/components/OgImage/Site.takumi.vue +32 -0
- package/app/components/Ui/Avatar/Avatar.vue +75 -0
- package/app/components/Ui/Avatar/Badge.vue +34 -0
- package/app/components/Ui/Avatar/Fallback.vue +30 -0
- package/app/components/Ui/Avatar/Group.vue +29 -0
- package/app/components/Ui/Avatar/GroupCount.vue +29 -0
- package/app/components/Ui/Avatar/Image.vue +29 -0
- package/app/components/Ui/Badge.vue +86 -0
- package/app/components/Ui/BadgeGroup.vue +314 -0
- package/app/components/Ui/BgPattern/Grid.vue +137 -0
- package/app/components/Ui/BorderBeam.vue +97 -0
- package/app/components/Ui/Breadcrumbs.vue +103 -0
- package/app/components/Ui/Button.vue +169 -0
- package/app/components/Ui/ButtonGroup/ButtonGroup.vue +54 -0
- package/app/components/Ui/ButtonGroup/Separator.vue +38 -0
- package/app/components/Ui/ButtonGroup/Text.vue +33 -0
- package/app/components/Ui/Card/Action.vue +31 -0
- package/app/components/Ui/Card/Card.vue +58 -0
- package/app/components/Ui/Card/Content.vue +34 -0
- package/app/components/Ui/Card/Description.vue +36 -0
- package/app/components/Ui/Card/Footer.vue +31 -0
- package/app/components/Ui/Card/Header.vue +31 -0
- package/app/components/Ui/Card/Title.vue +37 -0
- package/app/components/Ui/Collapsible/Collapsible.vue +16 -0
- package/app/components/Ui/Collapsible/Content.vue +27 -0
- package/app/components/Ui/Collapsible/Trigger.vue +13 -0
- package/app/components/Ui/Command/Command.vue +115 -0
- package/app/components/Ui/Command/Dialog.vue +65 -0
- package/app/components/Ui/Command/Empty.vue +32 -0
- package/app/components/Ui/Command/Footer.vue +40 -0
- package/app/components/Ui/Command/Group.vue +58 -0
- package/app/components/Ui/Command/Input.vue +57 -0
- package/app/components/Ui/Command/Item.vue +102 -0
- package/app/components/Ui/Command/List.vue +33 -0
- package/app/components/Ui/Command/Separator.vue +30 -0
- package/app/components/Ui/Command/Shortcut.vue +28 -0
- package/app/components/Ui/Container.vue +46 -0
- package/app/components/Ui/ContextMenu/Arrow.vue +43 -0
- package/app/components/Ui/ContextMenu/CheckboxItem.vue +42 -0
- package/app/components/Ui/ContextMenu/Content.vue +53 -0
- package/app/components/Ui/ContextMenu/ContextMenu.vue +14 -0
- package/app/components/Ui/ContextMenu/Group.vue +12 -0
- package/app/components/Ui/ContextMenu/Item.vue +65 -0
- package/app/components/Ui/ContextMenu/ItemIndicator.vue +30 -0
- package/app/components/Ui/ContextMenu/Label.vue +35 -0
- package/app/components/Ui/ContextMenu/Portal.vue +12 -0
- package/app/components/Ui/ContextMenu/RadioGroup.vue +16 -0
- package/app/components/Ui/ContextMenu/RadioItem.vue +40 -0
- package/app/components/Ui/ContextMenu/Separator.vue +25 -0
- package/app/components/Ui/ContextMenu/Shortcut.vue +30 -0
- package/app/components/Ui/ContextMenu/Sub.vue +15 -0
- package/app/components/Ui/ContextMenu/SubContent.vue +55 -0
- package/app/components/Ui/ContextMenu/SubTrigger.vue +42 -0
- package/app/components/Ui/ContextMenu/Trigger.vue +12 -0
- package/app/components/Ui/Dialog/Close.vue +12 -0
- package/app/components/Ui/Dialog/Content.vue +79 -0
- package/app/components/Ui/Dialog/Description.vue +29 -0
- package/app/components/Ui/Dialog/Dialog.vue +14 -0
- package/app/components/Ui/Dialog/Footer.vue +32 -0
- package/app/components/Ui/Dialog/Header.vue +32 -0
- package/app/components/Ui/Dialog/Overlay.vue +25 -0
- package/app/components/Ui/Dialog/Portal.vue +12 -0
- package/app/components/Ui/Dialog/Title.vue +29 -0
- package/app/components/Ui/Dialog/Trigger.vue +12 -0
- package/app/components/Ui/Divider.vue +102 -0
- package/app/components/Ui/Drawer/Close.vue +12 -0
- package/app/components/Ui/Drawer/Content.vue +67 -0
- package/app/components/Ui/Drawer/Description.vue +33 -0
- package/app/components/Ui/Drawer/Drawer.vue +17 -0
- package/app/components/Ui/Drawer/Footer.vue +35 -0
- package/app/components/Ui/Drawer/Header.vue +34 -0
- package/app/components/Ui/Drawer/Overlay.vue +20 -0
- package/app/components/Ui/Drawer/Portal.vue +12 -0
- package/app/components/Ui/Drawer/Title.vue +35 -0
- package/app/components/Ui/Drawer/Trigger.vue +12 -0
- package/app/components/Ui/DropdownMenu/Arrow.vue +43 -0
- package/app/components/Ui/DropdownMenu/CheckboxItem.vue +41 -0
- package/app/components/Ui/DropdownMenu/Content.vue +56 -0
- package/app/components/Ui/DropdownMenu/DropdownMenu.vue +15 -0
- package/app/components/Ui/DropdownMenu/Group.vue +12 -0
- package/app/components/Ui/DropdownMenu/Item.vue +64 -0
- package/app/components/Ui/DropdownMenu/ItemIndicator.vue +32 -0
- package/app/components/Ui/DropdownMenu/Label.vue +34 -0
- package/app/components/Ui/DropdownMenu/Portal.vue +12 -0
- package/app/components/Ui/DropdownMenu/RadioGroup.vue +16 -0
- package/app/components/Ui/DropdownMenu/RadioItem.vue +40 -0
- package/app/components/Ui/DropdownMenu/Separator.vue +25 -0
- package/app/components/Ui/DropdownMenu/Shortcut.vue +44 -0
- package/app/components/Ui/DropdownMenu/Sub.vue +14 -0
- package/app/components/Ui/DropdownMenu/SubContent.vue +56 -0
- package/app/components/Ui/DropdownMenu/SubTrigger.vue +47 -0
- package/app/components/Ui/DropdownMenu/Trigger.vue +12 -0
- package/app/components/Ui/FancyIcon.vue +243 -0
- package/app/components/Ui/GradientDivider.vue +26 -0
- package/app/components/Ui/IframeLazy.vue +97 -0
- package/app/components/Ui/Input.vue +97 -0
- package/app/components/Ui/InputGroup/Addon.vue +66 -0
- package/app/components/Ui/InputGroup/Button.vue +50 -0
- package/app/components/Ui/InputGroup/Input.vue +25 -0
- package/app/components/Ui/InputGroup/InputGroup.vue +44 -0
- package/app/components/Ui/InputGroup/Text.vue +33 -0
- package/app/components/Ui/InputGroup/Textarea.vue +25 -0
- package/app/components/Ui/Kbd/Group.vue +32 -0
- package/app/components/Ui/Kbd/Kbd.vue +62 -0
- package/app/components/Ui/Label.vue +40 -0
- package/app/components/Ui/Popover/Anchor.vue +13 -0
- package/app/components/Ui/Popover/Arrow.vue +42 -0
- package/app/components/Ui/Popover/Close.vue +13 -0
- package/app/components/Ui/Popover/Content.vue +55 -0
- package/app/components/Ui/Popover/Popover.vue +15 -0
- package/app/components/Ui/Popover/Portal.vue +13 -0
- package/app/components/Ui/Popover/Trigger.vue +12 -0
- package/app/components/Ui/Popover/X.vue +38 -0
- package/app/components/Ui/ScrollArea/Corner.vue +12 -0
- package/app/components/Ui/ScrollArea/ScrollArea.vue +38 -0
- package/app/components/Ui/ScrollArea/Scrollbar.vue +39 -0
- package/app/components/Ui/ScrollArea/Thumb.vue +39 -0
- package/app/components/Ui/ScrollArea/Viewport.vue +27 -0
- package/app/components/Ui/Scrollspy.vue +61 -0
- package/app/components/Ui/Separator.vue +40 -0
- package/app/components/Ui/Skeleton.vue +39 -0
- package/app/components/Ui/Sonner.vue +25 -0
- package/app/components/Ui/Tabs/Content.vue +27 -0
- package/app/components/Ui/Tabs/Indicator.vue +37 -0
- package/app/components/Ui/Tabs/List.vue +37 -0
- package/app/components/Ui/Tabs/Tabs.vue +25 -0
- package/app/components/Ui/Tabs/Trigger.vue +41 -0
- package/app/components/Ui/Textarea.vue +81 -0
- package/app/components/Ui/Tooltip/Arrow.vue +30 -0
- package/app/components/Ui/Tooltip/Content.vue +50 -0
- package/app/components/Ui/Tooltip/Portal.vue +12 -0
- package/app/components/Ui/Tooltip/Provider.vue +14 -0
- package/app/components/Ui/Tooltip/Tooltip.vue +25 -0
- package/app/components/Ui/Tooltip/Trigger.vue +12 -0
- package/app/components/Ui/Tree/Item.vue +15 -0
- package/app/components/Ui/Tree/Tree.vue +15 -0
- package/app/components/Ui/Tree/Virtualizer.vue +13 -0
- package/app/components/component-api/ComponentApiSection.vue +135 -0
- package/app/components/content/docs/Callout/DocsCallout.vue +99 -0
- package/app/components/content/prose/ProseA.global.vue +31 -0
- package/app/components/content/prose/ProseBlockquote.global.vue +28 -0
- package/app/components/content/prose/ProseCallout.global.vue +306 -0
- package/app/components/content/prose/ProseCard.global.vue +100 -0
- package/app/components/content/prose/ProseCodeCollapse.global.vue +125 -0
- package/app/components/content/prose/ProseCodeCopy.global.vue +67 -0
- package/app/components/content/prose/ProseCodeGroup.global.vue +104 -0
- package/app/components/content/prose/ProseCodeTree.global.vue +277 -0
- package/app/components/content/prose/ProseCollapsible.global.vue +64 -0
- package/app/components/content/prose/ProseColorModeImage.global.vue +58 -0
- package/app/components/content/prose/ProseComponentApi.global.vue +104 -0
- package/app/components/content/prose/ProseComponentEvents.global.vue +27 -0
- package/app/components/content/prose/ProseComponentExposed.global.vue +27 -0
- package/app/components/content/prose/ProseComponentProps.global.vue +27 -0
- package/app/components/content/prose/ProseComponentSlots.global.vue +27 -0
- package/app/components/content/prose/ProseEm.global.vue +5 -0
- package/app/components/content/prose/ProseField.global.vue +118 -0
- package/app/components/content/prose/ProseFieldGroup.global.vue +62 -0
- package/app/components/content/prose/ProseH1.global.vue +58 -0
- package/app/components/content/prose/ProseH2.global.vue +58 -0
- package/app/components/content/prose/ProseH3.global.vue +58 -0
- package/app/components/content/prose/ProseH4.global.vue +58 -0
- package/app/components/content/prose/ProseH5.global.vue +47 -0
- package/app/components/content/prose/ProseH6.global.vue +47 -0
- package/app/components/content/prose/ProseHr.global.vue +33 -0
- package/app/components/content/prose/ProseIconList.global.vue +113 -0
- package/app/components/content/prose/ProseImg.global.vue +119 -0
- package/app/components/content/prose/ProseLead.global.vue +28 -0
- package/app/components/content/prose/ProseLi.global.vue +25 -0
- package/app/components/content/prose/ProseMermaid.global.vue +127 -0
- package/app/components/content/prose/ProseOl.global.vue +25 -0
- package/app/components/content/prose/ProseP.global.vue +25 -0
- package/app/components/content/prose/ProsePmInstall.global.vue +60 -0
- package/app/components/content/prose/ProsePmRun.global.vue +51 -0
- package/app/components/content/prose/ProsePmX.global.vue +56 -0
- package/app/components/content/prose/ProsePre.global.vue +181 -0
- package/app/components/content/prose/ProseShowCase.global.vue +56 -0
- package/app/components/content/prose/ProseSmartIcon.global.vue +54 -0
- package/app/components/content/prose/ProseStep.global.vue +22 -0
- package/app/components/content/prose/ProseSteps.global.vue +22 -0
- package/app/components/content/prose/ProseStrong.global.vue +34 -0
- package/app/components/content/prose/ProseTable.global.vue +34 -0
- package/app/components/content/prose/ProseTabs.global.vue +104 -0
- package/app/components/content/prose/ProseTabsInner.global.vue +274 -0
- package/app/components/content/prose/ProseTbody.global.vue +5 -0
- package/app/components/content/prose/ProseTd.global.vue +25 -0
- package/app/components/content/prose/ProseTh.global.vue +25 -0
- package/app/components/content/prose/ProseThead.global.vue +9 -0
- package/app/components/content/prose/ProseTr.global.vue +35 -0
- package/app/components/content/prose/ProseUl.global.vue +25 -0
- package/app/components/docs/DocsExtraStuff.vue +39 -0
- package/app/components/docs/DocsGithubLink.vue +11 -0
- package/app/components/docs/DocsHeader.vue +3 -0
- package/app/components/docs/DocsLogo.vue +74 -0
- package/app/components/docs/DocsMobileBar.vue +26 -0
- package/app/components/docs/DocsMobileNav.vue +49 -0
- package/app/components/docs/DocsNav.vue +161 -0
- package/app/components/docs/DocsPageTransition.vue +32 -0
- package/app/components/docs/DocsSearch.vue +220 -0
- package/app/components/docs/DocsSearchButton.vue +20 -0
- package/app/components/docs/DocsThemeCustomizer.vue +229 -0
- package/app/components/docs/DocsThemeToggler.vue +98 -0
- package/app/components/docs/DocsTocRail.vue +162 -0
- package/app/components/docs/DocsToclink.vue +52 -0
- package/app/components/docs/DocsZigZagRail.vue +70 -0
- package/app/components/landing/Hero.vue +222 -0
- package/app/components/page/PageFooter.vue +49 -0
- package/app/components/page/PageHeader.vue +68 -0
- package/app/components/page/PageHeaderLinks.vue +138 -0
- package/app/components/page/PageMobileToc.vue +162 -0
- package/app/components/page/PageToc.vue +33 -0
- package/app/composables/useDefaultSlotItems.ts +90 -0
- package/app/composables/useDocPage.ts +66 -0
- package/app/composables/useDocSearch.ts +23 -0
- package/app/composables/useDocd.ts +107 -0
- package/app/composables/useLogoAssets.ts +319 -0
- package/app/composables/usePm.ts +73 -0
- package/app/composables/useProseComponentMeta.ts +30 -0
- package/app/composables/useScrollspy.ts +327 -0
- package/app/composables/useSeo.ts +240 -0
- package/app/composables/useUIConfig.ts +36 -0
- package/app/layouts/default.vue +5 -0
- package/app/layouts/docs.vue +64 -0
- package/app/middleware/colorMode.global.ts +8 -0
- package/app/middleware/doc-page-layout.ts +11 -0
- package/app/middleware/landing-page-layout.ts +11 -0
- package/app/pages/[...slug].vue +63 -0
- package/app/plugins/mermaid.client.ts +35 -0
- package/app/plugins/navigation-direction.client.ts +15 -0
- package/app/plugins/theme-restore.client.ts +7 -0
- package/app/templates/landing.vue +40 -0
- package/app/transitions/presets.ts +71 -0
- package/app/types/prose-component-meta.d.ts +4 -0
- package/app/utils/og.ts +26 -0
- package/app/utils/prerender.ts +9 -0
- package/app/utils/translucent.ts +56 -0
- package/app/utils/tw-helper.ts +4 -0
- package/content.config.ts +81 -0
- package/index.d.ts +25 -0
- package/modules/config.ts +89 -0
- package/modules/custom-icons.ts +17 -0
- package/modules/markdown-rewrite.ts +96 -0
- package/modules/prose-component-meta.ts +150 -0
- package/modules/routing.ts +22 -0
- package/modules/skills/index.ts +167 -0
- package/modules/skills/runtime/server/routes/skills-files.ts +49 -0
- package/modules/skills/runtime/server/routes/skills-index.ts +8 -0
- package/nuxt.config.ts +96 -0
- package/nuxt.schema.ts +288 -0
- package/package.json +95 -0
- package/server/mcp/tools/get-page.ts +66 -0
- package/server/mcp/tools/list-pages.ts +61 -0
- package/server/routes/sitemap.xml.ts +73 -0
- package/server/utils/content.ts +3 -0
- package/tailwind.config.cjs +55 -0
- package/utils/component-api-content.ts +467 -0
- package/utils/component-api-discovery.ts +165 -0
- package/utils/component-api-schema.ts +28 -0
- package/utils/component-api.ts +243 -0
- package/utils/generate-prose-component-meta.ts +303 -0
- package/utils/git.ts +123 -0
- package/utils/meta.ts +40 -0
- package/utils/pages.ts +21 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
type TargetLike =
|
|
2
|
+
| HTMLElement
|
|
3
|
+
| Window
|
|
4
|
+
| Document
|
|
5
|
+
| null
|
|
6
|
+
| undefined
|
|
7
|
+
| MaybeRefOrGetter<HTMLElement | Window | Document | null | undefined>;
|
|
8
|
+
|
|
9
|
+
type AnchorRecord = {
|
|
10
|
+
anchor: HTMLElement;
|
|
11
|
+
section: HTMLElement;
|
|
12
|
+
sectionId: string;
|
|
13
|
+
offset: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export interface UseScrollspyOptions {
|
|
17
|
+
/** Container that actually scrolls (ref or element). Omit to use `window`. */
|
|
18
|
+
target?: TargetLike;
|
|
19
|
+
/** Element containing the ANCHORS (ref or element). If omitted, use the returned `root` ref. */
|
|
20
|
+
root?: HTMLElement | null | Ref<HTMLElement | null | undefined>;
|
|
21
|
+
/** Base data-* name -> anchors should have `data-[name]-anchor="sectionId"`. */
|
|
22
|
+
dataAttribute?: string;
|
|
23
|
+
/** Default offset from top (px) when computing active section / scrolling. */
|
|
24
|
+
offset?: number;
|
|
25
|
+
/** Smooth scroll on click. */
|
|
26
|
+
smooth?: boolean;
|
|
27
|
+
/** Update URL hash as active section changes. */
|
|
28
|
+
history?: boolean;
|
|
29
|
+
/** Throttle time (ms) for scroll handler. */
|
|
30
|
+
throttleTime?: number;
|
|
31
|
+
/** Whether to mark only the current section or every visible section as active. */
|
|
32
|
+
mode?: "single" | "multiple";
|
|
33
|
+
/** Optional callback when active id changes (you can also watch `activeId`). */
|
|
34
|
+
onChange?: (id: string) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function useScrollspy(options: UseScrollspyOptions = {}) {
|
|
38
|
+
const {
|
|
39
|
+
target,
|
|
40
|
+
root: rootOpt,
|
|
41
|
+
dataAttribute = "scrollspy",
|
|
42
|
+
offset = 0,
|
|
43
|
+
smooth = true,
|
|
44
|
+
history = true,
|
|
45
|
+
throttleTime = 50,
|
|
46
|
+
mode = "single",
|
|
47
|
+
onChange,
|
|
48
|
+
} = options;
|
|
49
|
+
|
|
50
|
+
// Anchors live inside this container; use returned `root` if none passed
|
|
51
|
+
const root = (rootOpt ?? ref<HTMLElement | null>(null)) as Ref<HTMLElement | null>;
|
|
52
|
+
const anchors = shallowRef<HTMLElement[]>([]);
|
|
53
|
+
const activeId = ref<string | null>(null);
|
|
54
|
+
const activeIds = ref<string[]>([]);
|
|
55
|
+
const prevId = ref<string | null>(null);
|
|
56
|
+
|
|
57
|
+
// --- Resolve the scroller (window by default) ---
|
|
58
|
+
const scroller = shallowRef<Window | HTMLElement | null>(null);
|
|
59
|
+
|
|
60
|
+
function sectionTopWithinScroller(sectionEl: HTMLElement): number {
|
|
61
|
+
const se = scroller.value;
|
|
62
|
+
const rect = sectionEl.getBoundingClientRect();
|
|
63
|
+
if (!se || se === window) {
|
|
64
|
+
return rect.top + (window.scrollY || document.documentElement.scrollTop);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const scRect = se.getBoundingClientRect();
|
|
68
|
+
return rect.top - scRect.top + se.scrollTop;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function resolveScroller() {
|
|
72
|
+
const t = unrefElement(target as any);
|
|
73
|
+
if (!t || t === window || t === document) {
|
|
74
|
+
scroller.value = window;
|
|
75
|
+
} else if (t instanceof HTMLElement) {
|
|
76
|
+
scroller.value = t;
|
|
77
|
+
} else {
|
|
78
|
+
scroller.value = window;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
onMounted(resolveScroller);
|
|
83
|
+
watch(() => unrefElement(target as any), resolveScroller, { immediate: true });
|
|
84
|
+
|
|
85
|
+
// --- Discover anchors inside `root` and keep updated on DOM changes ---
|
|
86
|
+
function queryAnchors() {
|
|
87
|
+
anchors.value = root.value
|
|
88
|
+
? Array.from(root.value.querySelectorAll<HTMLElement>(`[data-${dataAttribute}-anchor]`))
|
|
89
|
+
: [];
|
|
90
|
+
// After any DOM change that updates anchors (e.g. route-driven re-render),
|
|
91
|
+
// recompute the active section without waiting for a scroll event.
|
|
92
|
+
void nextTick(() => handleScroll());
|
|
93
|
+
}
|
|
94
|
+
onMounted(queryAnchors);
|
|
95
|
+
useMutationObserver(root, queryAnchors, { childList: true, subtree: true });
|
|
96
|
+
|
|
97
|
+
// Helpers for scroll metrics
|
|
98
|
+
function getScrollTop(se: Window | HTMLElement | null) {
|
|
99
|
+
if (!se || se === window) {
|
|
100
|
+
return window.scrollY || document.documentElement.scrollTop;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return se.scrollTop;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getScrollHeight(se: Window | HTMLElement | null) {
|
|
107
|
+
if (!se || se === window) {
|
|
108
|
+
return document.documentElement.scrollHeight;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return se.scrollHeight;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getClientHeight(se: Window | HTMLElement | null) {
|
|
115
|
+
if (!se || se === window) {
|
|
116
|
+
return window.innerHeight;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return se.clientHeight;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function getViewportBounds(customOffset: number) {
|
|
123
|
+
const se = scroller.value;
|
|
124
|
+
if (!se || se === window) {
|
|
125
|
+
return {
|
|
126
|
+
top: customOffset,
|
|
127
|
+
bottom: window.innerHeight,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const rect = se.getBoundingClientRect();
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
top: rect.top + customOffset,
|
|
135
|
+
bottom: rect.bottom,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function getAnchorRecords() {
|
|
140
|
+
return anchors.value.reduce<AnchorRecord[]>((records, anchor) => {
|
|
141
|
+
const sectionId =
|
|
142
|
+
anchor.getAttribute(`data-${dataAttribute}-anchor`)?.replace("#", "") || null;
|
|
143
|
+
if (!sectionId) return records;
|
|
144
|
+
|
|
145
|
+
const section = document.getElementById(sectionId);
|
|
146
|
+
if (!section) return records;
|
|
147
|
+
|
|
148
|
+
let customOffset = offset;
|
|
149
|
+
const dataOffset = anchor.getAttribute(`data-${dataAttribute}-offset`);
|
|
150
|
+
if (dataOffset) customOffset = parseInt(dataOffset, 10);
|
|
151
|
+
|
|
152
|
+
records.push({
|
|
153
|
+
anchor,
|
|
154
|
+
section,
|
|
155
|
+
sectionId,
|
|
156
|
+
offset: customOffset,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
return records;
|
|
160
|
+
}, []);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// --- Core: compute active anchor on scroll ---
|
|
164
|
+
function setActive(nextActiveIds: string[], sectionId: string | null, force = false) {
|
|
165
|
+
const activeIdSet = new Set(nextActiveIds);
|
|
166
|
+
|
|
167
|
+
anchors.value.forEach((anchor) => {
|
|
168
|
+
const id = anchor.getAttribute(`data-${dataAttribute}-anchor`)?.replace("#", "") || null;
|
|
169
|
+
if (id && activeIdSet.has(id)) anchor.setAttribute("data-active", "true");
|
|
170
|
+
else anchor.removeAttribute("data-active");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
activeIds.value = nextActiveIds;
|
|
174
|
+
activeId.value = sectionId;
|
|
175
|
+
|
|
176
|
+
if (sectionId && history && (force || prevId.value !== sectionId)) {
|
|
177
|
+
window.history.replaceState({}, "", `#${sectionId}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (sectionId && prevId.value !== sectionId) {
|
|
181
|
+
onChange?.(sectionId);
|
|
182
|
+
prevId.value = sectionId;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function handleScroll() {
|
|
187
|
+
if (!anchors.value.length) {
|
|
188
|
+
setActive([], null);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const se = scroller.value;
|
|
193
|
+
const scrollTop = getScrollTop(se);
|
|
194
|
+
const records = getAnchorRecords();
|
|
195
|
+
if (!records.length) {
|
|
196
|
+
setActive([], null);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let activeIdx = 0;
|
|
201
|
+
let minDelta = Infinity;
|
|
202
|
+
|
|
203
|
+
records.forEach((record, idx) => {
|
|
204
|
+
const top = sectionTopWithinScroller(record.section) - record.offset;
|
|
205
|
+
const delta = Math.abs(top - scrollTop);
|
|
206
|
+
|
|
207
|
+
if (top <= scrollTop && delta < minDelta) {
|
|
208
|
+
minDelta = delta;
|
|
209
|
+
activeIdx = idx;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// At bottom → force last anchor active
|
|
214
|
+
const atBottom = scrollTop + getClientHeight(se) >= getScrollHeight(se) - 2;
|
|
215
|
+
if (atBottom) activeIdx = records.length - 1;
|
|
216
|
+
|
|
217
|
+
const currentId = records[activeIdx]?.sectionId || null;
|
|
218
|
+
const nextActiveIds =
|
|
219
|
+
mode === "multiple"
|
|
220
|
+
? records
|
|
221
|
+
.filter((record) => {
|
|
222
|
+
const rect = record.section.getBoundingClientRect();
|
|
223
|
+
const viewport = getViewportBounds(record.offset);
|
|
224
|
+
return rect.bottom > viewport.top && rect.top < viewport.bottom;
|
|
225
|
+
})
|
|
226
|
+
.map((record) => record.sectionId)
|
|
227
|
+
: currentId
|
|
228
|
+
? [currentId]
|
|
229
|
+
: [];
|
|
230
|
+
|
|
231
|
+
setActive(nextActiveIds, currentId);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const onScroll = throttleTime
|
|
235
|
+
? useThrottleFn(handleScroll, throttleTime, true, false)
|
|
236
|
+
: handleScroll;
|
|
237
|
+
|
|
238
|
+
// Rebind when scroller changes
|
|
239
|
+
useEventListener(scroller, "scroll", onScroll, { passive: true });
|
|
240
|
+
|
|
241
|
+
// --- Click to scroll ---
|
|
242
|
+
function scrollTo(
|
|
243
|
+
idOrEl: string | HTMLElement,
|
|
244
|
+
behavior: ScrollBehavior = smooth ? "smooth" : "auto"
|
|
245
|
+
) {
|
|
246
|
+
let anchorEl: HTMLElement | null = null;
|
|
247
|
+
let sectionId: string | null = null;
|
|
248
|
+
|
|
249
|
+
if (typeof idOrEl === "string") {
|
|
250
|
+
sectionId = idOrEl;
|
|
251
|
+
anchorEl =
|
|
252
|
+
anchors.value.find((a) => a.getAttribute(`data-${dataAttribute}-anchor`) === idOrEl) ||
|
|
253
|
+
anchors.value.find(
|
|
254
|
+
(a) => a.getAttribute(`data-${dataAttribute}-anchor`) === `#${idOrEl}`
|
|
255
|
+
) ||
|
|
256
|
+
null;
|
|
257
|
+
} else {
|
|
258
|
+
anchorEl = idOrEl;
|
|
259
|
+
sectionId = anchorEl.getAttribute(`data-${dataAttribute}-anchor`)?.replace("#", "") || null;
|
|
260
|
+
}
|
|
261
|
+
if (!sectionId) return;
|
|
262
|
+
|
|
263
|
+
const sectionEl = document.getElementById(sectionId);
|
|
264
|
+
if (!sectionEl) return;
|
|
265
|
+
|
|
266
|
+
let customOffset = offset;
|
|
267
|
+
const dataOffset = anchorEl?.getAttribute?.(`data-${dataAttribute}-offset`);
|
|
268
|
+
if (dataOffset) customOffset = parseInt(dataOffset, 10);
|
|
269
|
+
|
|
270
|
+
const top = sectionTopWithinScroller(sectionEl) - customOffset;
|
|
271
|
+
const se = scroller.value ?? window;
|
|
272
|
+
|
|
273
|
+
if ("scrollTo" in se) {
|
|
274
|
+
(se as Window | HTMLElement).scrollTo({ top, left: 0, behavior });
|
|
275
|
+
} else {
|
|
276
|
+
window.scrollTo({ top, left: 0, behavior });
|
|
277
|
+
}
|
|
278
|
+
setActive([sectionId], sectionId, true);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Bind click listeners to anchors and auto-clean when the list changes
|
|
282
|
+
watch(
|
|
283
|
+
anchors,
|
|
284
|
+
(list, _old, onCleanup) => {
|
|
285
|
+
const stops: Array<() => void> = [];
|
|
286
|
+
list.forEach((el) => {
|
|
287
|
+
stops.push(
|
|
288
|
+
useEventListener(el, "click", (e) => {
|
|
289
|
+
e.preventDefault();
|
|
290
|
+
scrollTo(el as HTMLElement);
|
|
291
|
+
})
|
|
292
|
+
);
|
|
293
|
+
});
|
|
294
|
+
onCleanup(() => stops.forEach((s) => s()));
|
|
295
|
+
},
|
|
296
|
+
{ immediate: true }
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
// Initial sync after mount
|
|
300
|
+
onMounted(() => {
|
|
301
|
+
// If URL has a hash, scroll there
|
|
302
|
+
const raw = window.location.hash.replace("#", "");
|
|
303
|
+
if (raw) {
|
|
304
|
+
const safe = window.CSS && "escape" in window.CSS ? (window.CSS as any).escape(raw) : raw;
|
|
305
|
+
const targetAnchor = root.value?.querySelector(
|
|
306
|
+
`[data-${dataAttribute}-anchor="${safe}"], [data-${dataAttribute}-anchor="#${safe}"]`
|
|
307
|
+
) as HTMLElement | null;
|
|
308
|
+
if (targetAnchor) scrollTo(targetAnchor, "auto");
|
|
309
|
+
}
|
|
310
|
+
// Compute initial active anchor after layout has settled (double-RAF is more
|
|
311
|
+
// reliable than a fixed timeout because it waits for two full paint frames).
|
|
312
|
+
requestAnimationFrame(() => requestAnimationFrame(() => handleScroll()));
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
/** Container that holds the anchors (use this if you didn't pass `root`) */
|
|
317
|
+
root,
|
|
318
|
+
/** Currently active section id */
|
|
319
|
+
activeId,
|
|
320
|
+
/** Currently active section ids */
|
|
321
|
+
activeIds,
|
|
322
|
+
/** Programmatically scroll to a section id or anchor element */
|
|
323
|
+
scrollTo,
|
|
324
|
+
/** Force recompute (e.g., after dynamic layout changes) */
|
|
325
|
+
recompute: () => handleScroll(),
|
|
326
|
+
};
|
|
327
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import type { ContentNavigationItem } from "@nuxt/content";
|
|
2
|
+
import { joinURL, withoutTrailingSlash } from "ufo";
|
|
3
|
+
import type { MaybeRefOrGetter } from "vue";
|
|
4
|
+
|
|
5
|
+
export interface BreadcrumbItem {
|
|
6
|
+
title: string;
|
|
7
|
+
path: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Find breadcrumb path to a page in the navigation tree
|
|
12
|
+
*/
|
|
13
|
+
export function findPageBreadcrumbs(
|
|
14
|
+
navigation: ContentNavigationItem[] | undefined,
|
|
15
|
+
path: string,
|
|
16
|
+
currentPath: BreadcrumbItem[] = []
|
|
17
|
+
): BreadcrumbItem[] | undefined {
|
|
18
|
+
if (!navigation) return undefined;
|
|
19
|
+
|
|
20
|
+
for (const item of navigation) {
|
|
21
|
+
const itemPath = [...currentPath, { title: item.title, path: item.path }];
|
|
22
|
+
|
|
23
|
+
if (item.path === path) {
|
|
24
|
+
return itemPath;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (item.children) {
|
|
28
|
+
const found = findPageBreadcrumbs(item.children, path, itemPath);
|
|
29
|
+
if (found) return found;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface UseSeoOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Page title
|
|
39
|
+
*/
|
|
40
|
+
title: MaybeRefOrGetter<string | undefined>;
|
|
41
|
+
/**
|
|
42
|
+
* Page description
|
|
43
|
+
*/
|
|
44
|
+
description: MaybeRefOrGetter<string | undefined>;
|
|
45
|
+
/**
|
|
46
|
+
* Page type for og:type (default: 'article' for docs, 'website' for landing)
|
|
47
|
+
*/
|
|
48
|
+
type?: MaybeRefOrGetter<"website" | "article">;
|
|
49
|
+
/**
|
|
50
|
+
* Custom OG image URL (absolute)
|
|
51
|
+
*/
|
|
52
|
+
ogImage?: MaybeRefOrGetter<string | undefined>;
|
|
53
|
+
/**
|
|
54
|
+
* Published date for article schema
|
|
55
|
+
*/
|
|
56
|
+
publishedAt?: MaybeRefOrGetter<string | undefined>;
|
|
57
|
+
/**
|
|
58
|
+
* Modified date for article schema
|
|
59
|
+
*/
|
|
60
|
+
modifiedAt?: MaybeRefOrGetter<string | undefined>;
|
|
61
|
+
/**
|
|
62
|
+
* Breadcrumb items for BreadcrumbList schema
|
|
63
|
+
*/
|
|
64
|
+
breadcrumbs?: MaybeRefOrGetter<BreadcrumbItem[] | undefined>;
|
|
65
|
+
/**
|
|
66
|
+
* Language for the page
|
|
67
|
+
*/
|
|
68
|
+
lang?: MaybeRefOrGetter<string | undefined>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Composable for comprehensive SEO setup including:
|
|
73
|
+
* - Meta tags (title, description, og:*, twitter:*)
|
|
74
|
+
* - Canonical URLs
|
|
75
|
+
* - Hreflang tags for i18n
|
|
76
|
+
* - JSON-LD structured data
|
|
77
|
+
*/
|
|
78
|
+
export function useSeo(options: UseSeoOptions) {
|
|
79
|
+
const route = useRoute();
|
|
80
|
+
const site = useSiteConfig();
|
|
81
|
+
const { logo } = useDocd();
|
|
82
|
+
const title = computed(() => toValue(options.title));
|
|
83
|
+
const description = computed(() => toValue(options.description));
|
|
84
|
+
const type = computed(() => toValue(options.type) || "article");
|
|
85
|
+
const ogImage = computed(() => toValue(options.ogImage));
|
|
86
|
+
const publishedAt = computed(() => toValue(options.publishedAt));
|
|
87
|
+
const modifiedAt = computed(() => toValue(options.modifiedAt));
|
|
88
|
+
const breadcrumbs = computed(() => toValue(options.breadcrumbs));
|
|
89
|
+
const lang = computed(() => toValue(options.lang) || site.language || "en");
|
|
90
|
+
|
|
91
|
+
// Build canonical URL
|
|
92
|
+
const canonicalUrl = computed(() => {
|
|
93
|
+
if (!site.url) return undefined;
|
|
94
|
+
return joinURL(site.url, route.path);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Base URL for building other URLs
|
|
98
|
+
const baseUrl = computed(() => (site.url ? withoutTrailingSlash(site.url) : ""));
|
|
99
|
+
|
|
100
|
+
// Set meta tags
|
|
101
|
+
useSeoMeta({
|
|
102
|
+
title,
|
|
103
|
+
description,
|
|
104
|
+
ogTitle: title,
|
|
105
|
+
ogDescription: description,
|
|
106
|
+
ogType: type,
|
|
107
|
+
ogUrl: canonicalUrl,
|
|
108
|
+
twitterTitle: title,
|
|
109
|
+
twitterDescription: description,
|
|
110
|
+
twitterCard: "summary_large_image",
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Set canonical link
|
|
114
|
+
useHead({
|
|
115
|
+
link: computed(() => {
|
|
116
|
+
const links: Array<{ rel: string; href?: string; hreflang?: string }> = [];
|
|
117
|
+
|
|
118
|
+
// Canonical URL
|
|
119
|
+
if (canonicalUrl.value) {
|
|
120
|
+
links.push({
|
|
121
|
+
rel: "canonical",
|
|
122
|
+
href: canonicalUrl.value,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Logo favicon as icon
|
|
127
|
+
if (logo.value?.favicon) {
|
|
128
|
+
links.push({
|
|
129
|
+
rel: "icon",
|
|
130
|
+
href: logo.value.favicon,
|
|
131
|
+
});
|
|
132
|
+
} else if (logo.value?.light) {
|
|
133
|
+
links.push({
|
|
134
|
+
rel: "icon",
|
|
135
|
+
href: logo.value.light,
|
|
136
|
+
});
|
|
137
|
+
} else if (logo.value?.dark) {
|
|
138
|
+
links.push({
|
|
139
|
+
rel: "icon",
|
|
140
|
+
href: logo.value.dark,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return links;
|
|
145
|
+
}),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Custom OG image handling
|
|
149
|
+
if (ogImage.value) {
|
|
150
|
+
useSeoMeta({
|
|
151
|
+
ogImage: ogImage.value,
|
|
152
|
+
twitterImage: ogImage.value,
|
|
153
|
+
twitterCard: "summary_large_image",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// JSON-LD structured data
|
|
158
|
+
useHead({
|
|
159
|
+
htmlAttrs: { lang },
|
|
160
|
+
script: computed(() => {
|
|
161
|
+
const scripts: Array<{ type: string; innerHTML: string }> = [];
|
|
162
|
+
|
|
163
|
+
if (!baseUrl.value || !title.value) return scripts;
|
|
164
|
+
|
|
165
|
+
const pageUrl = joinURL(baseUrl.value, route.path);
|
|
166
|
+
|
|
167
|
+
// Article schema for documentation pages
|
|
168
|
+
if (type.value === "article") {
|
|
169
|
+
const articleSchema: Record<string, unknown> = {
|
|
170
|
+
"@context": "https://schema.org",
|
|
171
|
+
"@type": "Article",
|
|
172
|
+
headline: title.value,
|
|
173
|
+
description: description.value,
|
|
174
|
+
url: pageUrl,
|
|
175
|
+
mainEntityOfPage: {
|
|
176
|
+
"@type": "WebPage",
|
|
177
|
+
"@id": pageUrl,
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
if (publishedAt.value) {
|
|
182
|
+
articleSchema.datePublished = publishedAt.value;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (modifiedAt.value) {
|
|
186
|
+
articleSchema.dateModified = modifiedAt.value;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (site.name) {
|
|
190
|
+
articleSchema.publisher = {
|
|
191
|
+
"@type": "Organization",
|
|
192
|
+
name: site.name,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
scripts.push({
|
|
197
|
+
type: "application/ld+json",
|
|
198
|
+
innerHTML: JSON.stringify(articleSchema),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// WebSite schema for landing pages
|
|
203
|
+
if (type.value === "website") {
|
|
204
|
+
const websiteSchema: Record<string, unknown> = {
|
|
205
|
+
"@context": "https://schema.org",
|
|
206
|
+
"@type": "WebSite",
|
|
207
|
+
name: site.name || title.value,
|
|
208
|
+
description: description.value,
|
|
209
|
+
url: baseUrl.value,
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
scripts.push({
|
|
213
|
+
type: "application/ld+json",
|
|
214
|
+
innerHTML: JSON.stringify(websiteSchema),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// BreadcrumbList schema for navigation
|
|
219
|
+
if (breadcrumbs.value && breadcrumbs.value.length > 0) {
|
|
220
|
+
const breadcrumbSchema = {
|
|
221
|
+
"@context": "https://schema.org",
|
|
222
|
+
"@type": "BreadcrumbList",
|
|
223
|
+
itemListElement: breadcrumbs.value.map((item, index) => ({
|
|
224
|
+
"@type": "ListItem",
|
|
225
|
+
position: index + 1,
|
|
226
|
+
name: item.title,
|
|
227
|
+
item: joinURL(baseUrl.value, item.path),
|
|
228
|
+
})),
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
scripts.push({
|
|
232
|
+
type: "application/ld+json",
|
|
233
|
+
innerHTML: JSON.stringify(breadcrumbSchema),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return scripts;
|
|
238
|
+
}),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { BorderType, DocdExtraLink, DocdTransitionConfig, DocLogoConfig } from "../app.config";
|
|
2
|
+
|
|
3
|
+
interface DocdUIMap {
|
|
4
|
+
header: {
|
|
5
|
+
title?: string;
|
|
6
|
+
hideSearch?: boolean;
|
|
7
|
+
logo?: DocLogoConfig;
|
|
8
|
+
};
|
|
9
|
+
footer?: {
|
|
10
|
+
hideThemeCustomizer?: boolean;
|
|
11
|
+
hideLightDarkToggle?: boolean;
|
|
12
|
+
};
|
|
13
|
+
toc: {
|
|
14
|
+
title?: string;
|
|
15
|
+
icon?: string;
|
|
16
|
+
};
|
|
17
|
+
borderType: BorderType;
|
|
18
|
+
transition: DocdTransitionConfig;
|
|
19
|
+
extraLinks: DocdExtraLink[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useUIConfig<K extends keyof DocdUIMap>(section: K): ComputedRef<DocdUIMap[K]> {
|
|
23
|
+
const appConfig = useAppConfig();
|
|
24
|
+
return computed(() => {
|
|
25
|
+
const ui = (appConfig.docd as { ui?: Partial<DocdUIMap> } | undefined)?.ui;
|
|
26
|
+
return (ui?.[section] ?? defaultValues[section]) as DocdUIMap[K];
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const defaultValues: DocdUIMap = {
|
|
31
|
+
header: {},
|
|
32
|
+
toc: { title: "On this page", icon: "lucide:list" },
|
|
33
|
+
borderType: "dashed",
|
|
34
|
+
transition: { name: "fade", duration: 0.35, easing: "easeOut" },
|
|
35
|
+
extraLinks: [],
|
|
36
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="grid min-h-dvh w-full grid-cols-1 tablet:grid-cols-[var(--nav-width)_minmax(0,1fr)]">
|
|
3
|
+
<aside
|
|
4
|
+
class="sticky top-0 z-20 hidden h-dvh flex-col border-r text-card-foreground tablet:flex"
|
|
5
|
+
:class="isDashed ? 'border-dashed' : ''"
|
|
6
|
+
>
|
|
7
|
+
<div
|
|
8
|
+
class="sticky top-0 mb-4 border-b bg-background/80 backdrop-blur-lg"
|
|
9
|
+
:class="isDashed ? 'border-dashed' : ''"
|
|
10
|
+
>
|
|
11
|
+
<div class="flex h-(--header-height) items-center justify-between px-4">
|
|
12
|
+
<DocsLogo />
|
|
13
|
+
<DocsSearchButton v-if="!hideSearch" />
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<UiScrollArea
|
|
17
|
+
class="h-[calc(100dvh-calc(var(--header-height)*2))] flex-1 mask-[linear-gradient(to_bottom,transparent,white_12px,white_calc(100%-12px),transparent)] px-4"
|
|
18
|
+
>
|
|
19
|
+
<DocsNav v-if="navigation" :items="navigation" />
|
|
20
|
+
</UiScrollArea>
|
|
21
|
+
<div
|
|
22
|
+
class="mt-auto flex h-(--header-height) items-center justify-between gap-3 border-t px-2"
|
|
23
|
+
:class="isDashed ? 'border-dashed' : ''"
|
|
24
|
+
>
|
|
25
|
+
<template v-if="github">
|
|
26
|
+
<DocsGithubLink />
|
|
27
|
+
</template>
|
|
28
|
+
<div class="ml-auto flex items-center gap-1.5">
|
|
29
|
+
<template v-if="github && (!hideThemeCustomizer || !hideLightDarkToggle)">
|
|
30
|
+
<UiDivider
|
|
31
|
+
:type="isDashed ? 'dashed' : 'solid'"
|
|
32
|
+
orientation="vertical"
|
|
33
|
+
class="h-[calc(var(--header-height)-2rem)]"
|
|
34
|
+
/>
|
|
35
|
+
</template>
|
|
36
|
+
<DocsThemeCustomizer v-if="!hideThemeCustomizer" />
|
|
37
|
+
<DocsThemeToggler v-if="!hideLightDarkToggle" class="dark:text-muted-foreground" />
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</aside>
|
|
41
|
+
<main class="flex min-h-dvh flex-col">
|
|
42
|
+
<header
|
|
43
|
+
class="sticky top-0 z-50 shrink-0 bg-background/80 backdrop-blur-lg"
|
|
44
|
+
:class="isDashed ? 'border-dashed' : ''"
|
|
45
|
+
>
|
|
46
|
+
<DocsMobileBar />
|
|
47
|
+
<div class="border-b" :class="isDashed ? 'border-dashed' : ''">
|
|
48
|
+
<UiContainer class="flex h-(--header-height) items-center">
|
|
49
|
+
<DocsHeader />
|
|
50
|
+
</UiContainer>
|
|
51
|
+
</div>
|
|
52
|
+
</header>
|
|
53
|
+
<PageMobileToc />
|
|
54
|
+
<div class="grow">
|
|
55
|
+
<slot />
|
|
56
|
+
</div>
|
|
57
|
+
</main>
|
|
58
|
+
</div>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<script lang="ts" setup>
|
|
62
|
+
const { navigation } = await useDocPage();
|
|
63
|
+
const { isDashed, github, hideSearch, hideLightDarkToggle, hideThemeCustomizer } = useDocd();
|
|
64
|
+
</script>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface DocPageLayout {
|
|
2
|
+
layout?: string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export default defineNuxtRouteMiddleware(async (to) => {
|
|
6
|
+
const page = (await queryCollection("docs" as never)
|
|
7
|
+
.path(to.path)
|
|
8
|
+
.first()) as DocPageLayout | null;
|
|
9
|
+
|
|
10
|
+
setPageLayout(page?.layout || "docs");
|
|
11
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface LandingPageLayout {
|
|
2
|
+
layout?: string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export default defineNuxtRouteMiddleware(async (to) => {
|
|
6
|
+
const page = (await queryCollection("landing" as never)
|
|
7
|
+
.path(to.path)
|
|
8
|
+
.first()) as LandingPageLayout | null;
|
|
9
|
+
|
|
10
|
+
setPageLayout(page?.layout || "default");
|
|
11
|
+
});
|