@lupinum/ginko-docs 0.4.0-rc.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -0
- package/LICENSE +1 -1
- package/README.md +206 -54
- package/app/app.config.ts +42 -55
- package/app/assets/docs.css +555 -0
- package/app/components/DocsBanner.vue +29 -0
- package/app/components/DocsExample.vue +43 -0
- package/app/components/DocsFooter.vue +19 -0
- package/app/components/DocsHeader.vue +112 -0
- package/app/components/DocsLanding.vue +55 -0
- package/app/components/DocsLanguage.vue +29 -0
- package/app/components/DocsNavTree.vue +38 -0
- package/app/components/DocsPage.vue +84 -0
- package/app/components/DocsPageActions.vue +47 -0
- package/app/components/DocsPost.vue +58 -0
- package/app/components/DocsSearch.vue +134 -0
- package/app/components/DocsShell.vue +20 -0
- package/app/components/DocsSidebar.vue +18 -0
- package/app/components/DocsTable.vue +11 -0
- package/app/components/DocsThemeToggle.vue +45 -0
- package/app/components/DocsToc.vue +73 -0
- package/app/composables/useDocs.ts +52 -0
- package/app/composables/useDocsBlog.ts +32 -0
- package/app/composables/useDocsCopy.ts +17 -0
- package/app/composables/useDocsPopover.ts +36 -0
- package/app/composables/useDocsSite.ts +10 -0
- package/app/docs-pages/blog.vue +47 -0
- package/app/docs-pages/changelog.vue +27 -0
- package/app/docs-pages/doc.vue +14 -0
- package/app/docs-pages/docs.vue +9 -0
- package/app/docs-pages/index.vue +12 -0
- package/app/docs-pages/post.vue +8 -0
- package/app/error.vue +23 -115
- package/app/layouts/default.vue +25 -7
- package/app/lib/blogTypes.ts +8 -0
- package/app/lib/docsFirst.ts +11 -0
- package/app/lib/docsInit.ts +18 -0
- package/app/lib/docsNav.ts +36 -0
- package/app/lib/docsPage.ts +44 -0
- package/app/lib/markdownComponents.ts +16 -0
- package/app/lib/previewToc.ts +18 -0
- package/dist/config.d.mts +232 -0
- package/dist/config.d.ts +232 -0
- package/dist/config.mjs +114 -0
- package/dist/pages.d.mts +5 -0
- package/dist/pages.d.ts +5 -0
- package/dist/pages.mjs +36 -0
- package/dist/shared/ginko-docs.BwbF7JBR.mjs +4 -0
- package/i18n/de.json +1 -0
- package/i18n/en.json +1 -0
- package/icons/README.md +2 -0
- package/icons/bluesky.svg +1 -0
- package/icons/claude.svg +1 -0
- package/icons/discord.svg +1 -0
- package/icons/github.svg +1 -0
- package/icons/linkedin.svg +1 -0
- package/icons/mastodon.svg +1 -0
- package/icons/openai.svg +1 -0
- package/icons/x.svg +1 -0
- package/icons/youtube.svg +1 -0
- package/nuxt.config.ts +34 -209
- package/package.json +43 -76
- package/public/_docs/fonts/LICENSE +188 -0
- package/public/_docs/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/public/_docs/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- package/public/_docs/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
- package/server/feeds/blog-locale.get.ts +9 -0
- package/server/feeds/blog.get.ts +4 -0
- package/server/lib/blogFeed.ts +35 -0
- package/server/lib/rss.ts +57 -0
- package/server/mcp/handler.ts +108 -0
- package/server/routes/__ginko/docs-mcp.ts +35 -0
- package/shared/paths.ts +4 -0
- package/app/app.vue +0 -98
- package/app/assets/css/prose.css +0 -3240
- package/app/assets/css/tailwind.css +0 -394
- package/app/assets/css/theme-palettes.css +0 -309
- package/app/assets/css/theme-presets.css +0 -141
- package/app/components/OgImage/GinkoDocs.satori.vue +0 -238
- package/app/components/content/DocumentPageShell.vue +0 -55
- package/app/components/content/Feedback.vue +0 -102
- package/app/components/content/ImageZoomDialog.vue +0 -84
- package/app/components/content/PageMarkdownCopy.vue +0 -104
- package/app/components/content/imageZoom.ts +0 -25
- package/app/components/mdc/MdcAccordion.vue +0 -67
- package/app/components/mdc/MdcAccordionItem.vue +0 -42
- package/app/components/mdc/MdcApi.vue +0 -159
- package/app/components/mdc/MdcAside.vue +0 -23
- package/app/components/mdc/MdcCallout.vue +0 -61
- package/app/components/mdc/MdcCard.vue +0 -175
- package/app/components/mdc/MdcCards.vue +0 -30
- package/app/components/mdc/MdcCenter.vue +0 -71
- package/app/components/mdc/MdcCodeGroup.vue +0 -197
- package/app/components/mdc/MdcCodeTree.vue +0 -234
- package/app/components/mdc/MdcCollapse.vue +0 -61
- package/app/components/mdc/MdcColumn.vue +0 -70
- package/app/components/mdc/MdcDropcap.vue +0 -84
- package/app/components/mdc/MdcError.vue +0 -23
- package/app/components/mdc/MdcExcerpt.vue +0 -26
- package/app/components/mdc/MdcFigure.vue +0 -85
- package/app/components/mdc/MdcFiles.vue +0 -120
- package/app/components/mdc/MdcIdea.vue +0 -17
- package/app/components/mdc/MdcInfo.vue +0 -17
- package/app/components/mdc/MdcInlineToc.vue +0 -51
- package/app/components/mdc/MdcKbd.vue +0 -10
- package/app/components/mdc/MdcLayout.vue +0 -57
- package/app/components/mdc/MdcNote.vue +0 -17
- package/app/components/mdc/MdcQuiz.vue +0 -241
- package/app/components/mdc/MdcQuizQuestion.vue +0 -221
- package/app/components/mdc/MdcReadMore.vue +0 -70
- package/app/components/mdc/MdcSteps.vue +0 -59
- package/app/components/mdc/MdcSuccess.vue +0 -23
- package/app/components/mdc/MdcTab.vue +0 -17
- package/app/components/mdc/MdcTabs.vue +0 -150
- package/app/components/mdc/MdcTimeline.vue +0 -17
- package/app/components/mdc/MdcTimelineItem.vue +0 -40
- package/app/components/mdc/MdcWarning.vue +0 -23
- package/app/components/mdc/api.utils.ts +0 -132
- package/app/components/mdc/code-tree.utils.ts +0 -22
- package/app/components/mdc/icons.ts +0 -10
- package/app/components/mdc/quiz.utils.ts +0 -38
- package/app/components/prose/ProseA.vue +0 -27
- package/app/components/prose/ProseBlockquote.vue +0 -30
- package/app/components/prose/ProseCode.vue +0 -14
- package/app/components/prose/ProseEm.vue +0 -14
- package/app/components/prose/ProseH1.vue +0 -18
- package/app/components/prose/ProseH2.vue +0 -29
- package/app/components/prose/ProseH3.vue +0 -29
- package/app/components/prose/ProseH4.vue +0 -29
- package/app/components/prose/ProseH5.vue +0 -15
- package/app/components/prose/ProseH6.vue +0 -15
- package/app/components/prose/ProseHr.vue +0 -12
- package/app/components/prose/ProseImg.vue +0 -55
- package/app/components/prose/ProseLi.vue +0 -14
- package/app/components/prose/ProseOl.vue +0 -14
- package/app/components/prose/ProseP.vue +0 -14
- package/app/components/prose/ProsePre.vue +0 -180
- package/app/components/prose/ProseStrong.vue +0 -14
- package/app/components/prose/ProseTable.vue +0 -18
- package/app/components/prose/ProseTbody.vue +0 -14
- package/app/components/prose/ProseTd.vue +0 -14
- package/app/components/prose/ProseTh.vue +0 -14
- package/app/components/prose/ProseThead.vue +0 -14
- package/app/components/prose/ProseTr.vue +0 -14
- package/app/components/prose/ProseUl.vue +0 -14
- package/app/components/site/ModeToggle.vue +0 -81
- package/app/components/site/SiteBanner.vue +0 -97
- package/app/components/site/SiteFooter.vue +0 -90
- package/app/components/site/SiteHeader.vue +0 -371
- package/app/components/site/SiteHeroCode.vue +0 -173
- package/app/components/site/SiteInteractionLayer.vue +0 -42
- package/app/components/site/SiteLocaleSwitcher.vue +0 -190
- package/app/components/site/SiteLogoMark.vue +0 -12
- package/app/components/site/SiteSkipLink.vue +0 -14
- package/app/components/site/SiteSocialLinks.vue +0 -33
- package/app/components/site/header-utils.ts +0 -3
- package/app/components/site/mode-toggle.utils.ts +0 -18
- package/app/components/ui/badge/Badge.vue +0 -28
- package/app/components/ui/badge/index.ts +0 -24
- package/app/components/ui/button/Button.vue +0 -31
- package/app/components/ui/button/index.ts +0 -35
- package/app/components/ui/collapsible/Collapsible.vue +0 -15
- package/app/components/ui/collapsible/CollapsibleContent.vue +0 -25
- package/app/components/ui/collapsible/CollapsibleTrigger.vue +0 -12
- package/app/components/ui/collapsible/index.ts +0 -3
- package/app/components/ui/dropdown-menu/DropdownMenu.vue +0 -15
- package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +0 -50
- package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +0 -41
- package/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +0 -15
- package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +0 -37
- package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +0 -23
- package/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue +0 -14
- package/app/components/ui/dropdown-menu/index.ts +0 -7
- package/app/components/ui/kbd/Kbd.vue +0 -23
- package/app/components/ui/kbd/index.ts +0 -21
- package/app/components/ui/scroll-area/ScrollArea.vue +0 -39
- package/app/components/ui/scroll-area/ScrollBar.vue +0 -33
- package/app/components/ui/scroll-area/index.ts +0 -2
- package/app/components/ui/separator/Separator.vue +0 -27
- package/app/components/ui/separator/index.ts +0 -1
- package/app/components/ui/sheet/Sheet.vue +0 -15
- package/app/components/ui/sheet/SheetContent.vue +0 -59
- package/app/components/ui/sheet/SheetDescription.vue +0 -21
- package/app/components/ui/sheet/SheetOverlay.vue +0 -26
- package/app/components/ui/sheet/SheetTitle.vue +0 -21
- package/app/components/ui/sheet/SheetTrigger.vue +0 -12
- package/app/components/ui/sheet/index.ts +0 -5
- package/app/components/ui/tabs/Tabs.vue +0 -24
- package/app/components/ui/tabs/TabsList.vue +0 -26
- package/app/components/ui/tabs/TabsTrigger.vue +0 -28
- package/app/components/ui/tabs/index.ts +0 -3
- package/app/composables/site-navigation.utils.ts +0 -133
- package/app/composables/useCanonicalUrl.ts +0 -9
- package/app/composables/useContentRouteAlternates.ts +0 -35
- package/app/composables/useGinkoAnalytics.ts +0 -52
- package/app/composables/useGinkoDocsConfig.ts +0 -6
- package/app/composables/useGinkoOgImage.ts +0 -20
- package/app/composables/useLocalizedPath.ts +0 -12
- package/app/composables/useLocalizedRouteSwitch.ts +0 -31
- package/app/composables/useMetaKey.ts +0 -18
- package/app/composables/usePageMarkdownActions.ts +0 -79
- package/app/composables/useProseAppearance.ts +0 -24
- package/app/composables/useSchemaJsonLd.ts +0 -25
- package/app/composables/useSiteNavigation.ts +0 -64
- package/app/config/site.utils.ts +0 -36
- package/app/features/docs/components/DocsBreadcrumb.vue +0 -37
- package/app/features/docs/components/DocsContributeLinks.vue +0 -75
- package/app/features/docs/components/DocsMobileToc.vue +0 -108
- package/app/features/docs/components/DocsPageContent.vue +0 -234
- package/app/features/docs/components/DocsPageNav.vue +0 -57
- package/app/features/docs/components/DocsSidebar.vue +0 -145
- package/app/features/docs/components/DocsSidebarDropdown.vue +0 -70
- package/app/features/docs/components/DocsSidebarItem.vue +0 -122
- package/app/features/docs/components/DocsSidebarList.vue +0 -35
- package/app/features/docs/components/DocsSidebarRow.vue +0 -27
- package/app/features/docs/components/DocsSidebarTabs.vue +0 -59
- package/app/features/docs/components/DocsToc.vue +0 -106
- package/app/features/docs/composables/useDocsNavigation.ts +0 -29
- package/app/features/docs/composables/useDocsNavigationData.ts +0 -18
- package/app/features/docs/composables/useRevealActive.ts +0 -58
- package/app/features/docs/composables/useScrollspy.ts +0 -135
- package/app/features/docs/docs-navigation.ts +0 -157
- package/app/features/docs/toc-context.ts +0 -6
- package/app/features/search/command-center.ts +0 -110
- package/app/features/search/components/SiteCommandCenter.vue +0 -359
- package/app/features/search/components/SiteSearchPageHighlight.vue +0 -126
- package/app/features/search/useCommandCenter.ts +0 -292
- package/app/layouts/blog.vue +0 -15
- package/app/layouts/docs.vue +0 -20
- package/app/lib/errors.ts +0 -37
- package/app/lib/schema-org.ts +0 -38
- package/app/pages/blog/[slug].vue +0 -228
- package/app/pages/blog/index.vue +0 -130
- package/app/pages/docs/[...slug].vue +0 -10
- package/app/pages/docs/index.vue +0 -19
- package/app/pages/index.vue +0 -278
- package/app/plugins/ginko-docs-theme.ts +0 -14
- package/app/plugins/ssr-width.ts +0 -6
- package/app/utils/content.ts +0 -67
- package/app/utils/file-icons.ts +0 -90
- package/app/utils/index.ts +0 -7
- package/app/utils/repository.ts +0 -20
- package/components.ts +0 -9
- package/content-collections.ts +0 -89
- package/content.js +0 -200
- package/content.ts +0 -104
- package/i18n/i18n.config.ts +0 -10
- package/i18n/locales.ts +0 -81
- package/i18n/messages/global/banner.ts +0 -8
- package/i18n/messages/global/blog.ts +0 -18
- package/i18n/messages/global/command.ts +0 -49
- package/i18n/messages/global/docs.ts +0 -36
- package/i18n/messages/global/errors.ts +0 -36
- package/i18n/messages/global/feedback.ts +0 -11
- package/i18n/messages/global/index.ts +0 -21
- package/i18n/messages/global/nav.ts +0 -26
- package/i18n/messages/global/site.ts +0 -4
- package/i18n/messages/global/theme.ts +0 -9
- package/i18n/messages/index.ts +0 -20
- package/i18n/messages/localize.ts +0 -35
- package/i18n/routes.ts +0 -33
- package/icon-bundle.ts +0 -116
- package/index.d.ts +0 -37
- package/modules/analytics-boundary.ts +0 -23
- package/modules/feature-routing.ts +0 -68
- package/modules/syntax-highlighting.ts +0 -120
- package/public/fonts/public-sans/LICENSE +0 -93
- package/public/fonts/public-sans/README.md +0 -12
- package/public/fonts/public-sans/public-sans-400-normal-latin.woff +0 -0
- package/public/fonts/public-sans/public-sans-500-normal-latin.woff +0 -0
- package/public/fonts/public-sans/public-sans-600-normal-latin.woff +0 -0
- package/public/fonts/public-sans/public-sans-700-normal-latin.woff +0 -0
- package/runtime/generate-favicons.mjs +0 -124
- package/runtime/server/agent-markdown.ts +0 -19
- package/server/api/_ginko-docs/redirects.get.ts +0 -28
- package/server/mcp/tools/get-page.ts +0 -14
- package/server/mcp/tools/list-pages.ts +0 -10
- package/server/middleware/redirects.ts +0 -22
- package/server/plugins/agent-markdown.ts +0 -6
- package/server/plugins/sitemap-policy.ts +0 -12
- package/server/routes/.well-known/ai-catalog.json.get.ts +0 -10
- package/server/routes/[locale]/blog/rss.xml.get.ts +0 -13
- package/server/routes/blog/rss.xml.get.ts +0 -5
- package/server/routes/mcp/server-card.get.ts +0 -14
- package/server/utils/blog-feed.ts +0 -51
- package/server/utils/feed.ts +0 -70
- package/server/utils/mcp-server-card.ts +0 -45
- package/server/utils/redirects.ts +0 -38
- package/server/utils/redirects.utils.ts +0 -58
- package/server/utils/sitemap.ts +0 -45
- package/shared/route-slugs.ts +0 -5
- package/shared/theme-palettes.ts +0 -37
- package/shared/theme-presets.ts +0 -3
- package/shared/types/app-config.ts +0 -172
- package/shared/types/nuxt-config.ts +0 -14
- package/tags.ts +0 -192
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { DocsNavigationItem } from "#ginko-docs/features/docs/docs-navigation";
|
|
3
|
-
import { Badge } from "#ginko-docs/components/ui/badge";
|
|
4
|
-
|
|
5
|
-
defineProps<{
|
|
6
|
-
item: DocsNavigationItem;
|
|
7
|
-
showChevron?: boolean;
|
|
8
|
-
}>();
|
|
9
|
-
</script>
|
|
10
|
-
|
|
11
|
-
<template>
|
|
12
|
-
<Icon v-if="item.icon" :name="item.icon" class="size-4 shrink-0" aria-hidden="true" />
|
|
13
|
-
<span class="min-w-0 truncate">{{ item.title }}</span>
|
|
14
|
-
<Badge
|
|
15
|
-
v-if="item.badge"
|
|
16
|
-
variant="secondary"
|
|
17
|
-
class="ms-auto h-4 shrink-0 rounded-full px-1.5 py-0 text-[10px]"
|
|
18
|
-
>
|
|
19
|
-
{{ item.badge }}
|
|
20
|
-
</Badge>
|
|
21
|
-
<Icon
|
|
22
|
-
v-if="showChevron"
|
|
23
|
-
name="lucide:chevron-down"
|
|
24
|
-
class="ms-auto size-3.5 shrink-0 -rotate-90 text-muted-foreground/70 transition-transform duration-200 ease-out group-hover:text-muted-foreground group-data-[state=open]:rotate-0 group-data-[active=true]:text-primary/70 rtl:rotate-90"
|
|
25
|
-
aria-hidden="true"
|
|
26
|
-
/>
|
|
27
|
-
</template>
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed } from "vue";
|
|
3
|
-
import {
|
|
4
|
-
resolveDocsSectionTargetPath,
|
|
5
|
-
type DocsNavigationSection,
|
|
6
|
-
} from "#ginko-docs/features/docs/docs-navigation";
|
|
7
|
-
import { Tabs, TabsList, TabsTrigger } from "#ginko-docs/components/ui/tabs";
|
|
8
|
-
|
|
9
|
-
const props = defineProps<{
|
|
10
|
-
sections: Array<DocsNavigationSection & { title: string }>;
|
|
11
|
-
activeId: string;
|
|
12
|
-
}>();
|
|
13
|
-
|
|
14
|
-
const emit = defineEmits<{
|
|
15
|
-
"update:activeId": [id: string];
|
|
16
|
-
}>();
|
|
17
|
-
|
|
18
|
-
const tabs = computed(() =>
|
|
19
|
-
props.sections.map((section) => ({
|
|
20
|
-
id: section.id,
|
|
21
|
-
title: section.title,
|
|
22
|
-
target: resolveDocsSectionTargetPath(section),
|
|
23
|
-
})),
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
function onTabClick(id: string, event: MouseEvent) {
|
|
27
|
-
// Re-clicking the active section would otherwise navigate away from the
|
|
28
|
-
// current page to the section's first page.
|
|
29
|
-
if (id === props.activeId) event.preventDefault();
|
|
30
|
-
}
|
|
31
|
-
</script>
|
|
32
|
-
|
|
33
|
-
<template>
|
|
34
|
-
<Tabs :model-value="activeId" activation-mode="manual" data-slot="docs-sidebar-tabs">
|
|
35
|
-
<TabsList class="w-full">
|
|
36
|
-
<template v-for="tab in tabs" :key="tab.id">
|
|
37
|
-
<TabsTrigger v-if="tab.target" :value="tab.id" as-child>
|
|
38
|
-
<NuxtLink
|
|
39
|
-
:to="tab.target"
|
|
40
|
-
:title="tab.title"
|
|
41
|
-
class="min-w-0 flex-1 basis-0"
|
|
42
|
-
@click="onTabClick(tab.id, $event)"
|
|
43
|
-
>
|
|
44
|
-
<span class="truncate">{{ tab.title }}</span>
|
|
45
|
-
</NuxtLink>
|
|
46
|
-
</TabsTrigger>
|
|
47
|
-
<TabsTrigger
|
|
48
|
-
v-else
|
|
49
|
-
:value="tab.id"
|
|
50
|
-
:title="tab.title"
|
|
51
|
-
class="min-w-0 flex-1 basis-0"
|
|
52
|
-
@click="emit('update:activeId', tab.id)"
|
|
53
|
-
>
|
|
54
|
-
<span class="truncate">{{ tab.title }}</span>
|
|
55
|
-
</TabsTrigger>
|
|
56
|
-
</template>
|
|
57
|
-
</TabsList>
|
|
58
|
-
</Tabs>
|
|
59
|
-
</template>
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { HTMLAttributes } from "vue";
|
|
3
|
-
import { nextTick, onMounted, ref, watch } from "vue";
|
|
4
|
-
import { useEventListener } from "@vueuse/core";
|
|
5
|
-
import { cn } from "#ginko-docs/utils";
|
|
6
|
-
import type { FlatTocItem } from "#ginko-docs/utils/content";
|
|
7
|
-
import { useI18n } from "#imports";
|
|
8
|
-
|
|
9
|
-
const props = withDefaults(
|
|
10
|
-
defineProps<{
|
|
11
|
-
items: FlatTocItem[];
|
|
12
|
-
activeIds?: string[];
|
|
13
|
-
showTitle?: boolean;
|
|
14
|
-
class?: HTMLAttributes["class"];
|
|
15
|
-
}>(),
|
|
16
|
-
{
|
|
17
|
-
showTitle: true,
|
|
18
|
-
activeIds: () => [],
|
|
19
|
-
},
|
|
20
|
-
);
|
|
21
|
-
const { t } = useI18n();
|
|
22
|
-
|
|
23
|
-
// The sliding indicator spans the active rows. Measure the rendered rows
|
|
24
|
-
// instead of assuming a fixed row height, so wrapped or restyled rows keep
|
|
25
|
-
// the indicator aligned.
|
|
26
|
-
const listRef = ref<HTMLElement | null>(null);
|
|
27
|
-
const indicatorStyle = ref({ opacity: "0", top: "0px", height: "0px" });
|
|
28
|
-
|
|
29
|
-
function measureIndicator() {
|
|
30
|
-
const list = listRef.value;
|
|
31
|
-
if (!list) return;
|
|
32
|
-
const active = list.querySelectorAll<HTMLElement>("[data-toc-active]");
|
|
33
|
-
const first = active[0];
|
|
34
|
-
const last = active[active.length - 1];
|
|
35
|
-
if (!first || !last) {
|
|
36
|
-
indicatorStyle.value = { ...indicatorStyle.value, opacity: "0" };
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
indicatorStyle.value = {
|
|
40
|
-
opacity: "1",
|
|
41
|
-
top: `${first.offsetTop}px`,
|
|
42
|
-
height: `${last.offsetTop + last.offsetHeight - first.offsetTop}px`,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
watch(
|
|
47
|
-
() => [props.activeIds, props.items],
|
|
48
|
-
() => nextTick(measureIndicator),
|
|
49
|
-
{ deep: true },
|
|
50
|
-
);
|
|
51
|
-
onMounted(measureIndicator);
|
|
52
|
-
useEventListener("resize", measureIndicator, { passive: true });
|
|
53
|
-
|
|
54
|
-
function scrollToHeading(id: string) {
|
|
55
|
-
const element = document.getElementById(id);
|
|
56
|
-
if (!element) return;
|
|
57
|
-
// scrollIntoView ignores the global reduced-motion CSS override, so check
|
|
58
|
-
// explicitly. `scroll-margin-top` on headings handles the sticky offset.
|
|
59
|
-
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
60
|
-
element.scrollIntoView({ behavior: reducedMotion ? "auto" : "smooth", block: "start" });
|
|
61
|
-
// Sync the hash without router involvement — router-driven hash navigation
|
|
62
|
-
// would scroll-restore and fight the smooth scroll above.
|
|
63
|
-
history.replaceState(history.state, "", `#${encodeURIComponent(id)}`);
|
|
64
|
-
}
|
|
65
|
-
</script>
|
|
66
|
-
|
|
67
|
-
<template>
|
|
68
|
-
<nav v-if="items.length" :class="cn('w-full', props.class)" :aria-label="t('docs.toc')">
|
|
69
|
-
<h3
|
|
70
|
-
v-if="showTitle"
|
|
71
|
-
class="mb-4 flex items-center gap-2 text-xs font-semibold tracking-wider text-muted-foreground uppercase"
|
|
72
|
-
>
|
|
73
|
-
<Icon name="lucide:list" class="size-3.5 shrink-0" aria-hidden="true" />
|
|
74
|
-
{{ t("docs.toc") }}
|
|
75
|
-
</h3>
|
|
76
|
-
|
|
77
|
-
<div class="relative border-l border-border pl-3">
|
|
78
|
-
<div
|
|
79
|
-
class="pointer-events-none absolute -left-px w-0.5 rounded-full bg-brand transition-all duration-200 ease-out"
|
|
80
|
-
:style="indicatorStyle"
|
|
81
|
-
/>
|
|
82
|
-
|
|
83
|
-
<ul ref="listRef" class="space-y-1">
|
|
84
|
-
<li v-for="item in items" :key="item.id">
|
|
85
|
-
<a
|
|
86
|
-
:href="`#${item.id}`"
|
|
87
|
-
:title="item.label"
|
|
88
|
-
:data-toc-active="activeIds.includes(item.id) ? 'true' : undefined"
|
|
89
|
-
class="block truncate rounded-sm py-0.5 text-[13px] leading-5 transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
|
|
90
|
-
:class="
|
|
91
|
-
cn(
|
|
92
|
-
item.depth === 3 ? 'pl-3' : (item.depth ?? 2) >= 4 ? 'pl-6' : '',
|
|
93
|
-
activeIds.includes(item.id)
|
|
94
|
-
? 'font-medium text-foreground'
|
|
95
|
-
: 'text-muted-foreground hover:text-foreground',
|
|
96
|
-
)
|
|
97
|
-
"
|
|
98
|
-
@click.prevent="scrollToHeading(item.id)"
|
|
99
|
-
>
|
|
100
|
-
{{ item.label }}
|
|
101
|
-
</a>
|
|
102
|
-
</li>
|
|
103
|
-
</ul>
|
|
104
|
-
</div>
|
|
105
|
-
</nav>
|
|
106
|
-
</template>
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { computed } from "vue";
|
|
2
|
-
import { findNavigationTrail } from "@lupinum/ginko-content/navigation";
|
|
3
|
-
import { useRoute } from "#imports";
|
|
4
|
-
import {
|
|
5
|
-
getDocsNavigationSections,
|
|
6
|
-
normalizeDocsNavigationItem,
|
|
7
|
-
type DocsNavigationSection,
|
|
8
|
-
} from "../docs-navigation";
|
|
9
|
-
import { useDocsNavigationData } from "./useDocsNavigationData";
|
|
10
|
-
|
|
11
|
-
export async function useDocsNavigation() {
|
|
12
|
-
const route = useRoute();
|
|
13
|
-
const { data } = await useDocsNavigationData();
|
|
14
|
-
|
|
15
|
-
const roots = computed(() => {
|
|
16
|
-
return (data.value ?? []).map((item, index) => normalizeDocsNavigationItem(item, index));
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
const sections = computed<DocsNavigationSection[]>(() => {
|
|
20
|
-
return getDocsNavigationSections(roots.value);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
const trail = computed(() => findNavigationTrail(roots.value, route.path));
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
sections,
|
|
27
|
-
trail,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { computed } from "vue";
|
|
2
|
-
import { navigation } from "@lupinum/ginko-content/client";
|
|
3
|
-
import { useAsyncData, useI18n } from "#imports";
|
|
4
|
-
|
|
5
|
-
// Sync on purpose: the returned AsyncData is itself awaitable, so async callers
|
|
6
|
-
// still `await` it, while sync callers (e.g. the site header) read `data`
|
|
7
|
-
// reactively without suspending.
|
|
8
|
-
export function useDocsNavigationData() {
|
|
9
|
-
const { locale } = useI18n();
|
|
10
|
-
const navigationKey = computed(() => `docs-navigation:${locale.value}`);
|
|
11
|
-
|
|
12
|
-
return useAsyncData(navigationKey, () =>
|
|
13
|
-
navigation("docs", {
|
|
14
|
-
locale: locale.value,
|
|
15
|
-
select: ["icon", "badge", "sidebar"],
|
|
16
|
-
}),
|
|
17
|
-
);
|
|
18
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import type { Ref } from "vue";
|
|
2
|
-
|
|
3
|
-
export interface RevealActiveOptions {
|
|
4
|
-
/** Band padding in px; the active element is left alone inside the band. */
|
|
5
|
-
topPad?: number;
|
|
6
|
-
bottomPad?: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface RevealMetrics {
|
|
10
|
-
scrollTop: number;
|
|
11
|
-
containerHeight: number;
|
|
12
|
-
/** Active element's offset from the container's visible top edge. */
|
|
13
|
-
activeTop: number;
|
|
14
|
-
activeHeight: number;
|
|
15
|
-
topPad: number;
|
|
16
|
-
bottomPad: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Returns the scrollTop that centers the active element, or null if it is
|
|
20
|
-
* already inside the visible band. */
|
|
21
|
-
export function computeRevealScrollTop(metrics: RevealMetrics): number | null {
|
|
22
|
-
const { scrollTop, containerHeight, activeTop, activeHeight, topPad, bottomPad } = metrics;
|
|
23
|
-
const visible = activeTop >= topPad && activeTop + activeHeight <= containerHeight - bottomPad;
|
|
24
|
-
if (visible) return null;
|
|
25
|
-
return scrollTop + activeTop - containerHeight / 2 + activeHeight / 2;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Keeps the active element of an overflowing container visible by centering
|
|
30
|
-
* it when it drifts outside the visible band. Scrolling is instant on
|
|
31
|
-
* purpose: this is positioning, not motion, and must not compete with the
|
|
32
|
-
* page scroll. Callers decide when to schedule `reveal` (nextTick, rAF, …).
|
|
33
|
-
*/
|
|
34
|
-
export function useRevealActive(
|
|
35
|
-
container: Ref<HTMLElement | null> | (() => HTMLElement | null | undefined),
|
|
36
|
-
selector: string,
|
|
37
|
-
options: RevealActiveOptions = {},
|
|
38
|
-
) {
|
|
39
|
-
function reveal() {
|
|
40
|
-
const el = typeof container === "function" ? container() : container.value;
|
|
41
|
-
if (!el || el.scrollHeight <= el.clientHeight) return;
|
|
42
|
-
const active = el.querySelector<HTMLElement>(selector);
|
|
43
|
-
if (!active) return;
|
|
44
|
-
const containerRect = el.getBoundingClientRect();
|
|
45
|
-
const activeRect = active.getBoundingClientRect();
|
|
46
|
-
const next = computeRevealScrollTop({
|
|
47
|
-
scrollTop: el.scrollTop,
|
|
48
|
-
containerHeight: el.clientHeight,
|
|
49
|
-
activeTop: activeRect.top - containerRect.top,
|
|
50
|
-
activeHeight: activeRect.height,
|
|
51
|
-
topPad: options.topPad ?? 8,
|
|
52
|
-
bottomPad: options.bottomPad ?? 8,
|
|
53
|
-
});
|
|
54
|
-
if (next !== null) el.scrollTop = next;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return { reveal };
|
|
58
|
-
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
nextTick,
|
|
3
|
-
onBeforeUnmount,
|
|
4
|
-
onMounted,
|
|
5
|
-
ref,
|
|
6
|
-
toValue,
|
|
7
|
-
watch,
|
|
8
|
-
type MaybeRefOrGetter,
|
|
9
|
-
} from "vue";
|
|
10
|
-
|
|
11
|
-
export type ScrollspyHeading = {
|
|
12
|
-
id: string;
|
|
13
|
-
/** Viewport-relative top, in document order. */
|
|
14
|
-
top: number;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export type ScrollspyZone = {
|
|
18
|
-
zoneTop: number;
|
|
19
|
-
zoneBottom: number;
|
|
20
|
-
/** Viewport-relative bottom of the document (scrollHeight - scrollY). */
|
|
21
|
-
docEnd: number;
|
|
22
|
-
/** When the page is scrolled to the very bottom, the zone extends to docEnd
|
|
23
|
-
* so a short final section still activates. */
|
|
24
|
-
atBottom: boolean;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// Headings use `scroll-margin-top: 7rem` (= ZONE_TOP), so after an anchor jump
|
|
28
|
-
// the clicked heading sits exactly at the zone top and becomes the first
|
|
29
|
-
// active entry. The tolerance absorbs sub-pixel layout drift.
|
|
30
|
-
const ZONE_TOP = 112;
|
|
31
|
-
const TOLERANCE = 8;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* A heading is active while any part of its section (heading → next heading)
|
|
35
|
-
* intersects the reading zone. Pure so the zone model is unit-testable.
|
|
36
|
-
*/
|
|
37
|
-
export function computeActiveIds(headings: ScrollspyHeading[], zone: ScrollspyZone): string[] {
|
|
38
|
-
if (headings.length === 0) return [];
|
|
39
|
-
|
|
40
|
-
const zoneTop = zone.zoneTop + TOLERANCE;
|
|
41
|
-
const zoneBottom = zone.atBottom ? zone.docEnd : zone.zoneBottom;
|
|
42
|
-
|
|
43
|
-
const active: string[] = [];
|
|
44
|
-
for (let index = 0; index < headings.length; index++) {
|
|
45
|
-
const sectionTop = headings[index]!.top;
|
|
46
|
-
const sectionBottom = index < headings.length - 1 ? headings[index + 1]!.top : zone.docEnd;
|
|
47
|
-
if (sectionBottom > zoneTop && sectionTop < zoneBottom) {
|
|
48
|
-
active.push(headings[index]!.id);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (active.length > 0) return active;
|
|
52
|
-
|
|
53
|
-
// No section in the zone (e.g. a long passage between headings): fall back
|
|
54
|
-
// to the last heading scrolled past the zone top, then to the first heading.
|
|
55
|
-
for (let index = headings.length - 1; index >= 0; index--) {
|
|
56
|
-
if (headings[index]!.top <= zoneTop) return [headings[index]!.id];
|
|
57
|
-
}
|
|
58
|
-
return [headings[0]!.id];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function useScrollspy(ids: MaybeRefOrGetter<string[]>) {
|
|
62
|
-
const activeIds = ref<string[]>([]);
|
|
63
|
-
let observed: HTMLElement[] = [];
|
|
64
|
-
let rafId: number | null = null;
|
|
65
|
-
|
|
66
|
-
function update() {
|
|
67
|
-
const scrollY = window.scrollY;
|
|
68
|
-
const innerHeight = window.innerHeight;
|
|
69
|
-
const scrollHeight = document.documentElement.scrollHeight;
|
|
70
|
-
const next = computeActiveIds(
|
|
71
|
-
observed.map((element) => ({
|
|
72
|
-
id: element.id,
|
|
73
|
-
top: element.getBoundingClientRect().top,
|
|
74
|
-
})),
|
|
75
|
-
{
|
|
76
|
-
zoneTop: ZONE_TOP,
|
|
77
|
-
zoneBottom: innerHeight * 0.8,
|
|
78
|
-
docEnd: scrollHeight - scrollY,
|
|
79
|
-
atBottom: scrollY + innerHeight >= scrollHeight - 2,
|
|
80
|
-
},
|
|
81
|
-
);
|
|
82
|
-
if (
|
|
83
|
-
next.length !== activeIds.value.length ||
|
|
84
|
-
next.some((id, index) => id !== activeIds.value[index])
|
|
85
|
-
) {
|
|
86
|
-
activeIds.value = next;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function scheduleUpdate() {
|
|
91
|
-
if (rafId !== null) return;
|
|
92
|
-
rafId = requestAnimationFrame(() => {
|
|
93
|
-
rafId = null;
|
|
94
|
-
update();
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async function refresh() {
|
|
99
|
-
if (!import.meta.client) return;
|
|
100
|
-
|
|
101
|
-
await nextTick();
|
|
102
|
-
observed = toValue(ids)
|
|
103
|
-
.map((id) => document.getElementById(id))
|
|
104
|
-
.filter((element): element is HTMLElement => Boolean(element));
|
|
105
|
-
|
|
106
|
-
update();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
onMounted(() => {
|
|
110
|
-
void refresh();
|
|
111
|
-
window.addEventListener("scroll", scheduleUpdate, { passive: true });
|
|
112
|
-
window.addEventListener("resize", scheduleUpdate, { passive: true });
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
onBeforeUnmount(() => {
|
|
116
|
-
observed = [];
|
|
117
|
-
if (import.meta.client) {
|
|
118
|
-
window.removeEventListener("scroll", scheduleUpdate);
|
|
119
|
-
window.removeEventListener("resize", scheduleUpdate);
|
|
120
|
-
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
watch(
|
|
125
|
-
() => toValue(ids).join("|"),
|
|
126
|
-
() => {
|
|
127
|
-
void refresh();
|
|
128
|
-
},
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
activeIds,
|
|
133
|
-
refresh,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
findFirstNavigationPage,
|
|
3
|
-
navigationItemContainsPath,
|
|
4
|
-
normalizeNavigationPath,
|
|
5
|
-
type ContentNavigationTreeItem,
|
|
6
|
-
type NavigationSidebar,
|
|
7
|
-
} from "@lupinum/ginko-content/navigation";
|
|
8
|
-
|
|
9
|
-
export type RawDocsTreeItem = Omit<ContentNavigationTreeItem, "children"> & {
|
|
10
|
-
icon?: unknown;
|
|
11
|
-
badge?: unknown;
|
|
12
|
-
children?: RawDocsTreeItem[];
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export type DocsNavigationItem = {
|
|
16
|
-
id: string;
|
|
17
|
-
title: string;
|
|
18
|
-
path?: string;
|
|
19
|
-
icon?: string;
|
|
20
|
-
badge?: string;
|
|
21
|
-
sidebar?: NavigationSidebar;
|
|
22
|
-
children: DocsNavigationItem[];
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export type DocsNavigationSection = {
|
|
26
|
-
id: string;
|
|
27
|
-
title?: string;
|
|
28
|
-
path?: string;
|
|
29
|
-
icon?: string;
|
|
30
|
-
items: DocsNavigationItem[];
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export type DocsNavigationGroup = {
|
|
34
|
-
id: string;
|
|
35
|
-
title?: string;
|
|
36
|
-
icon?: string;
|
|
37
|
-
items: DocsNavigationItem[];
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
function itemId(item: RawDocsTreeItem, position: string): string {
|
|
41
|
-
return item.path ?? `docs:${position}:${item.title}`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function normalizeDocsNavigationItem(
|
|
45
|
-
item: RawDocsTreeItem,
|
|
46
|
-
position: string | number = 0,
|
|
47
|
-
): DocsNavigationItem {
|
|
48
|
-
const positionKey = String(position);
|
|
49
|
-
return {
|
|
50
|
-
id: itemId(item, positionKey),
|
|
51
|
-
title: item.title,
|
|
52
|
-
path: item.path,
|
|
53
|
-
icon: typeof item.icon === "string" ? item.icon : undefined,
|
|
54
|
-
badge: typeof item.badge === "string" ? item.badge : undefined,
|
|
55
|
-
sidebar: item.sidebar,
|
|
56
|
-
children: (item.children ?? []).map((child, index) =>
|
|
57
|
-
normalizeDocsNavigationItem(child, `${positionKey}.${index}`),
|
|
58
|
-
),
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function docsNavigationSectionContainsPath(
|
|
63
|
-
section: DocsNavigationSection,
|
|
64
|
-
path: string,
|
|
65
|
-
): boolean {
|
|
66
|
-
const normalizedPath = normalizeNavigationPath(path);
|
|
67
|
-
return (
|
|
68
|
-
(section.path !== undefined && normalizeNavigationPath(section.path) === normalizedPath) ||
|
|
69
|
-
section.items.some((item) => navigationItemContainsPath(item, normalizedPath))
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Structural sections have no page of their own — they resolve to their first
|
|
74
|
-
// navigable page so switching to them lands somewhere instead of only swapping
|
|
75
|
-
// the sidebar contents.
|
|
76
|
-
export function resolveDocsSectionTargetPath(section: DocsNavigationSection): string | undefined {
|
|
77
|
-
return section.path ?? findFirstNavigationPage(section.items)?.path;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function getDocsNavigationSections(items: DocsNavigationItem[]): DocsNavigationSection[] {
|
|
81
|
-
if (!items.some((item) => item.sidebar === "section")) {
|
|
82
|
-
return [{ id: "docs:main", items: [...items] }];
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const sections: DocsNavigationSection[] = [];
|
|
86
|
-
let current: DocsNavigationSection | undefined;
|
|
87
|
-
|
|
88
|
-
for (const item of items) {
|
|
89
|
-
if (item.sidebar === "section") {
|
|
90
|
-
current = {
|
|
91
|
-
id: item.id,
|
|
92
|
-
title: item.title,
|
|
93
|
-
path: item.path,
|
|
94
|
-
icon: item.icon,
|
|
95
|
-
items: [...item.children],
|
|
96
|
-
};
|
|
97
|
-
sections.push(current);
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (!current) {
|
|
102
|
-
current = { id: "docs:main", items: [] };
|
|
103
|
-
sections.push(current);
|
|
104
|
-
}
|
|
105
|
-
current.items.push(item);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return sections;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function getDocsNavigationGroups(section: DocsNavigationSection): DocsNavigationGroup[] {
|
|
112
|
-
const groups: DocsNavigationGroup[] = [];
|
|
113
|
-
const ungrouped: DocsNavigationItem[] = [];
|
|
114
|
-
|
|
115
|
-
if (section.path) {
|
|
116
|
-
ungrouped.push({
|
|
117
|
-
id: section.id,
|
|
118
|
-
title: section.title ?? "Overview",
|
|
119
|
-
path: section.path,
|
|
120
|
-
icon: section.icon,
|
|
121
|
-
children: [],
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
for (const child of section.items) {
|
|
126
|
-
if (child.sidebar !== "group") {
|
|
127
|
-
ungrouped.push(child);
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const items = child.path
|
|
132
|
-
? [
|
|
133
|
-
{
|
|
134
|
-
...child,
|
|
135
|
-
children: [],
|
|
136
|
-
},
|
|
137
|
-
...child.children,
|
|
138
|
-
]
|
|
139
|
-
: child.children;
|
|
140
|
-
|
|
141
|
-
groups.push({
|
|
142
|
-
id: child.id,
|
|
143
|
-
title: child.title,
|
|
144
|
-
icon: child.icon,
|
|
145
|
-
items,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (ungrouped.length) {
|
|
150
|
-
groups.unshift({
|
|
151
|
-
id: `${section.id}:main`,
|
|
152
|
-
items: ungrouped,
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return groups.filter((group) => group.items.length > 0);
|
|
157
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { ComputedRef, InjectionKey } from "vue";
|
|
2
|
-
import type { FlatTocItem } from "#ginko-docs/utils/content";
|
|
3
|
-
|
|
4
|
-
/** Current page TOC, provided by the docs page shell so in-content components
|
|
5
|
-
* (e.g. the `::toc` inline table of contents) can render it. */
|
|
6
|
-
export const docsTocKey: InjectionKey<ComputedRef<FlatTocItem[]>> = Symbol("ginko-docs-toc");
|