@lupinum/ginko-docs 0.4.0-rc.9 → 0.5.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 +141 -0
- package/LICENSE +1 -1
- package/README.md +300 -53
- package/app/app.config.ts +44 -55
- package/app/assets/docs.css +619 -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 +113 -0
- package/app/components/DocsIcon.vue +12 -0
- package/app/components/DocsLanding.vue +58 -0
- package/app/components/DocsLanguage.vue +40 -0
- package/app/components/DocsNavSheet.vue +37 -0
- package/app/components/DocsNavTree.vue +38 -0
- package/app/components/DocsPage.vue +88 -0
- package/app/components/DocsPageActions.vue +47 -0
- package/app/components/DocsPageEnd.ts +12 -0
- package/app/components/DocsPost.vue +58 -0
- package/app/components/DocsSearch.vue +136 -0
- package/app/components/DocsShell.vue +36 -0
- package/app/components/DocsSidebar.vue +19 -0
- package/app/components/DocsSidebarEnd.ts +4 -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 +53 -0
- package/app/composables/useDocsBlog.ts +33 -0
- package/app/composables/useDocsCopy.ts +17 -0
- package/app/composables/useDocsFrame.ts +11 -0
- package/app/composables/useDocsPopover.ts +36 -0
- package/app/composables/useDocsSite.ts +31 -0
- package/app/docs-pages/blog.vue +55 -0
- package/app/docs-pages/changelog.vue +35 -0
- package/app/docs-pages/doc.vue +16 -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 +38 -115
- package/app/layouts/default.vue +29 -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 +84 -0
- package/app/lib/markdownComponents.ts +15 -0
- package/app/lib/previewToc.ts +18 -0
- package/dist/config.d.mts +252 -0
- package/dist/config.d.ts +252 -0
- package/dist/config.mjs +83 -0
- package/dist/icons.d.mts +38 -0
- package/dist/icons.d.ts +38 -0
- package/dist/icons.mjs +175 -0
- package/dist/pages.d.mts +5 -0
- package/dist/pages.d.ts +5 -0
- package/dist/pages.mjs +68 -0
- package/i18n/de.json +1 -0
- package/i18n/en.json +1 -0
- package/i18n/fr.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 +22 -212
- package/package.json +44 -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/mcp/handler.ts +108 -0
- package/server/routes/__ginko/docs-mcp.ts +35 -0
- package/shared/layerRoutes.ts +66 -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
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { toFirstDoc } from '../lib/docsFirst'
|
|
3
|
+
// The start page: the site's `home` brick (docsHome) when it has one, otherwise the first docs page.
|
|
4
|
+
import schema from '#ginko/schema'
|
|
5
|
+
|
|
6
|
+
const hasHome = Object.hasOwn(schema.bricks, 'home')
|
|
7
|
+
if (!hasHome) await toFirstDoc()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<DocsLanding v-if="hasHome" />
|
|
12
|
+
</template>
|
package/app/error.vue
CHANGED
|
@@ -1,121 +1,44 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
const copy = computed(() => {
|
|
28
|
-
const catalog = messages[locale.value].errors;
|
|
29
|
-
return catalog[normalized.value.kind] ?? catalog.unavailable;
|
|
30
|
-
});
|
|
31
|
-
const homePath = computed(() => localizedPath(locale.value, localizedRoutes[locale.value].home));
|
|
32
|
-
const docsPath = computed(() => localizedPath(locale.value, localizedRoutes[locale.value].docs));
|
|
33
|
-
const title = computed(() => `${normalized.value.statusCode} - ${copy.value.title}`);
|
|
34
|
-
// Keep the full site chrome for content-level errors (404 and friends); hard
|
|
35
|
-
// server failures fall back to the bare page so the error screen cannot crash.
|
|
36
|
-
const showChrome = computed(() => normalized.value.kind !== "server");
|
|
37
|
-
|
|
38
|
-
useHead(() => ({
|
|
39
|
-
title: title.value,
|
|
40
|
-
meta: [{ name: "robots", content: "noindex, nofollow" }],
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
|
-
const handleError = () => clearError({ redirect: homePath.value });
|
|
2
|
+
// Not found and other errors, inside the site's frame (the layer's layout, or the site's own). A site with its own
|
|
3
|
+
// error.vue replaces this one.
|
|
4
|
+
import type { NuxtError } from '#app'
|
|
5
|
+
import { docsMainLanguagePath } from './lib/docsPage'
|
|
6
|
+
|
|
7
|
+
const props = defineProps<{ error: NuxtError }>()
|
|
8
|
+
const { t, localePath, locales, locale } = useI18n()
|
|
9
|
+
const docs = useDocs()
|
|
10
|
+
const route = useRoute()
|
|
11
|
+
const { mainLocale } = useGinkoSite()
|
|
12
|
+
const notFound = computed(() => props.error.statusCode === 404)
|
|
13
|
+
useGinkoSeo(() => ({ title: notFound.value ? t('docs.notFound.title') : t('docs.error.title'), noindex: true }))
|
|
14
|
+
// A 404 under another language's docs address offers the same address in the main language, named in itself. It is a
|
|
15
|
+
// guess (the slug may differ there); the language menu covers the normal way.
|
|
16
|
+
const mainPage = computed(() => {
|
|
17
|
+
const path = notFound.value ? docsMainLanguagePath(route.path, locales.map((l) => l.code), mainLocale, docs.mainBase, (p) => localePath(p, mainLocale)) : undefined
|
|
18
|
+
if (!path) return undefined
|
|
19
|
+
// The language's name in the reader's language; the locale's own name if the runtime does not know the code.
|
|
20
|
+
const fallback = locales.find((l) => l.code === mainLocale)?.name ?? mainLocale
|
|
21
|
+
let language = fallback
|
|
22
|
+
try { language = new Intl.DisplayNames([locale.value], { type: 'language' }).of(mainLocale) ?? fallback } catch { /* keep the fallback */ }
|
|
23
|
+
return { path, language }
|
|
24
|
+
})
|
|
25
|
+
// Links work without JavaScript; with it, clearError leaves the error state and navigates.
|
|
26
|
+
const leave = (to: string) => clearError({ redirect: to })
|
|
44
27
|
</script>
|
|
45
28
|
|
|
46
29
|
<template>
|
|
47
|
-
<
|
|
48
|
-
<
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
class="
|
|
56
|
-
|
|
57
|
-
>
|
|
58
|
-
{{ normalized.statusCode }}
|
|
59
|
-
</p>
|
|
60
|
-
|
|
61
|
-
<h1 class="mb-4 text-3xl font-semibold tracking-tight text-foreground sm:text-4xl">
|
|
62
|
-
{{ copy.title }}
|
|
63
|
-
</h1>
|
|
64
|
-
<p class="mx-auto mb-8 max-w-md leading-7 text-muted-foreground">
|
|
65
|
-
{{ copy.description }}
|
|
66
|
-
</p>
|
|
67
|
-
|
|
68
|
-
<div class="flex flex-col items-center justify-center gap-3 sm:flex-row">
|
|
69
|
-
<button class="error-action error-action-primary" type="button" @click="handleError">
|
|
70
|
-
{{ messages[locale].errors.actions.home }}
|
|
71
|
-
</button>
|
|
72
|
-
<a class="error-action error-action-secondary" :href="docsPath">
|
|
73
|
-
{{ messages[locale].errors.actions.docs }}
|
|
74
|
-
</a>
|
|
30
|
+
<NuxtLayout>
|
|
31
|
+
<div class="docs-error">
|
|
32
|
+
<div>
|
|
33
|
+
<p class="docs-error-code">{{ error.statusCode }}</p>
|
|
34
|
+
<h1>{{ notFound ? t('docs.notFound.title') : t('docs.error.title') }}</h1>
|
|
35
|
+
<p>{{ notFound ? t('docs.notFound.text') : t('docs.error.text') }}</p>
|
|
36
|
+
<div class="docs-error-actions">
|
|
37
|
+
<a :href="localePath('/')" class="docs-button is-large is-primary" @click.prevent="leave(localePath('/'))">{{ t('docs.notFound.home') }}</a>
|
|
38
|
+
<a v-if="docs.first.value" :href="docs.first.value" class="docs-button is-large is-soft" @click.prevent="leave(docs.first.value)">{{ t('docs.docs') }}</a>
|
|
39
|
+
<a v-if="mainPage" :href="mainPage.path" class="docs-button is-large is-soft" @click.prevent="leave(mainPage.path)">{{ t('docs.notFound.mainLanguage', { language: mainPage.language }) }}</a>
|
|
75
40
|
</div>
|
|
76
|
-
</
|
|
77
|
-
</
|
|
78
|
-
|
|
79
|
-
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</NuxtLayout>
|
|
80
44
|
</template>
|
|
81
|
-
|
|
82
|
-
<style scoped>
|
|
83
|
-
.error-action {
|
|
84
|
-
align-items: center;
|
|
85
|
-
border-radius: 0.375rem;
|
|
86
|
-
display: inline-flex;
|
|
87
|
-
font-size: 0.875rem;
|
|
88
|
-
font-weight: 500;
|
|
89
|
-
height: 2.5rem;
|
|
90
|
-
justify-content: center;
|
|
91
|
-
padding-inline: 1rem;
|
|
92
|
-
transition:
|
|
93
|
-
background-color 150ms,
|
|
94
|
-
color 150ms,
|
|
95
|
-
border-color 150ms;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
.error-action:focus-visible {
|
|
99
|
-
outline: 2px solid var(--ring);
|
|
100
|
-
outline-offset: 2px;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.error-action-primary {
|
|
104
|
-
background: var(--primary);
|
|
105
|
-
color: var(--primary-foreground);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
.error-action-primary:hover {
|
|
109
|
-
opacity: 0.9;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.error-action-secondary {
|
|
113
|
-
background: var(--background);
|
|
114
|
-
border: 1px solid var(--input);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
.error-action-secondary:hover {
|
|
118
|
-
background: var(--accent);
|
|
119
|
-
color: var(--accent-foreground);
|
|
120
|
-
}
|
|
121
|
-
</style>
|
package/app/layouts/default.vue
CHANGED
|
@@ -1,12 +1,34 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// The standalone frame: banner, header, page, footer. A site with its own `layouts/default.vue` replaces it, and
|
|
3
|
+
// the docs pages then sit inside the site's frame (the docs body, DocsShell, works either way).
|
|
4
|
+
const { t } = useI18n()
|
|
5
|
+
const { theme, banner } = useAppConfig().docs ?? {}
|
|
6
|
+
provideDocsStandalone()
|
|
7
|
+
useHead({
|
|
8
|
+
// `docs-standalone` turns on the page background, font and color-scheme (docs.css); embedded, the site owns those.
|
|
9
|
+
htmlAttrs: { class: 'docs-standalone', 'data-docs-theme': theme && theme !== 'default' ? theme : undefined },
|
|
10
|
+
// Inter comes with the page instead of after the stylesheet: text set in the fallback re-wraps when the font
|
|
11
|
+
// arrives late and pushes the page down (layout shift).
|
|
12
|
+
link: [{ rel: 'preload', as: 'font', type: 'font/woff2', href: '/_docs/fonts/inter-latin-wght-normal.woff2', crossorigin: '' }],
|
|
13
|
+
})
|
|
14
|
+
// "Skip to content" goes past the docs navigation to the page itself when the page has one (DocsShell), else to main.
|
|
15
|
+
function skip(event: MouseEvent) {
|
|
16
|
+
const target = document.getElementById('docs-content') ?? document.getElementById('docs-main')
|
|
17
|
+
if (!target) return
|
|
18
|
+
event.preventDefault()
|
|
19
|
+
target.focus()
|
|
20
|
+
target.scrollIntoView()
|
|
21
|
+
}
|
|
22
|
+
</script>
|
|
23
|
+
|
|
1
24
|
<template>
|
|
2
|
-
<div class="
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
<
|
|
6
|
-
<main id="main
|
|
25
|
+
<div class="docs-frame">
|
|
26
|
+
<a class="docs-skip" href="#docs-main" @click="skip">{{ t('docs.skip') }}</a>
|
|
27
|
+
<DocsBanner v-if="banner" :id="banner.id" :href="banner.href" />
|
|
28
|
+
<DocsHeader />
|
|
29
|
+
<main id="docs-main" class="docs-frame-main" tabindex="-1">
|
|
7
30
|
<slot />
|
|
8
31
|
</main>
|
|
9
|
-
<
|
|
10
|
-
<SiteInteractionLayer />
|
|
32
|
+
<DocsFooter />
|
|
11
33
|
</div>
|
|
12
34
|
</template>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Types of the blog and changelog entries. Kept out of `composables/` so that they are not auto-imported into the host app.
|
|
2
|
+
import type { ItemOf } from '@lupinum/ginko'
|
|
3
|
+
import type { blogCollections, changelogCollection } from '@lupinum/ginko-docs/config'
|
|
4
|
+
|
|
5
|
+
type Blog = ReturnType<typeof blogCollections>
|
|
6
|
+
export type Post = ItemOf<Blog['posts']['fields']> & { url?: string }
|
|
7
|
+
export type Author = ItemOf<Blog['authors']['fields']>
|
|
8
|
+
export type ChangelogEntry = ItemOf<ReturnType<typeof changelogCollection>['fields']>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useDocs } from '../composables/useDocs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Leads to the first docs page of the language. A site without any docs page yet gets a 404 instead: a redirect to
|
|
5
|
+
* `/` would loop, because `/` leads here when the site has no start page.
|
|
6
|
+
*/
|
|
7
|
+
export function toFirstDoc() {
|
|
8
|
+
const first = useDocs().first.value
|
|
9
|
+
if (!first) throw createError({ statusCode: 404, statusMessage: 'No docs page yet: add a Markdown file to content/docs/.' })
|
|
10
|
+
return navigateTo(first, { redirectCode: 302 })
|
|
11
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Storage keys and the head scripts of the color-mode switch and the banner. Each component adds its own script
|
|
2
|
+
// (useHead with a fixed key, so two toggles on one page add it once), so they also work inside a site's own layout.
|
|
3
|
+
export const COLOR_KEY = 'docs-color-mode'
|
|
4
|
+
export const bannerKey = (id: string) => `docs-banner:${id}`
|
|
5
|
+
|
|
6
|
+
// Runs inline in <head>, before first paint. localStorage can throw (private windows, blocked site data); then the
|
|
7
|
+
// page simply follows the system and shows the banner.
|
|
8
|
+
const guarded = (body: string) => `(function(){try{var d=document.documentElement;${body}}catch(e){}})()`
|
|
9
|
+
|
|
10
|
+
/** Applies the stored light/dark choice (html[data-theme]) before first paint. */
|
|
11
|
+
export function colorInitScript(): string {
|
|
12
|
+
return guarded(`var m=localStorage.getItem(${JSON.stringify(COLOR_KEY)});if(m==='light'||m==='dark')d.setAttribute('data-theme',m);`)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Marks a dismissed banner (html[data-docs-banner-off]) before first paint, so it does not flash. */
|
|
16
|
+
export function bannerInitScript(id: string): string {
|
|
17
|
+
return guarded(`if(localStorage.getItem(${JSON.stringify(bannerKey(id))})==='1')d.setAttribute('data-docs-banner-off','');`)
|
|
18
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Tabs and sections of the side navigation, from the entries' fields. Pure, so the rules have a test.
|
|
2
|
+
import type { NavNode } from '@lupinum/ginko'
|
|
3
|
+
|
|
4
|
+
export interface DocNode extends NavNode {
|
|
5
|
+
section?: string
|
|
6
|
+
badge?: string
|
|
7
|
+
icon?: string
|
|
8
|
+
tab?: string
|
|
9
|
+
children: DocNode[]
|
|
10
|
+
}
|
|
11
|
+
export interface DocTab { title: string; path: string }
|
|
12
|
+
|
|
13
|
+
export interface DocFields { tab?: string; section?: string; badge?: string; icon?: string }
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A root without a tab follows the one before it; a new tab starts without a section; a section runs until the
|
|
17
|
+
* next one. Children always follow their parent's tab and carry no section.
|
|
18
|
+
*/
|
|
19
|
+
export function enrichNav(tree: NavNode[], fieldsOf: (id: string) => DocFields | undefined): DocNode[] {
|
|
20
|
+
let running: string | undefined
|
|
21
|
+
let section: string | undefined
|
|
22
|
+
const enrich = (node: NavNode, tab: string | undefined, root: boolean): DocNode => {
|
|
23
|
+
const data = fieldsOf(node.id ?? '')
|
|
24
|
+
if (root) {
|
|
25
|
+
if (data?.tab && data.tab !== running) section = undefined
|
|
26
|
+
running = data?.tab ?? running
|
|
27
|
+
section = data?.section ?? section
|
|
28
|
+
}
|
|
29
|
+
const own = root ? running : tab
|
|
30
|
+
return {
|
|
31
|
+
...node, tab: own, section: root ? section : undefined, badge: data?.badge, icon: data?.icon,
|
|
32
|
+
children: node.children.map((c) => enrich(c, own, false)),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return tree.map((n) => enrich(n, undefined, true))
|
|
36
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Small pure helpers of the docs page (edit link, Markdown twin, "open in" links), kept apart for tests.
|
|
2
|
+
|
|
3
|
+
/** Edit link of a page's file. Template placeholders: `{url}`, `{branch}`, `{source}`; default is the GitHub form. */
|
|
4
|
+
export function docsEditUrl(repo: { url: string, branch?: string, editUrl?: string } | undefined, source: string | undefined): string | undefined {
|
|
5
|
+
if (!repo?.url || !source) return undefined
|
|
6
|
+
const values: Record<string, string> = { url: repo.url.replace(/\/+$/, ''), branch: repo.branch ?? 'main', source }
|
|
7
|
+
return (repo.editUrl ?? '{url}/edit/{branch}/{source}').replace(/\{(url|branch|source)\}/g, (_, k: string) => values[k]!)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Edit link of a page stored on the Ginko platform: its form in the Studio, `<platform>/s/<site>/all/<brick>/<id>`.
|
|
12
|
+
* No platform address (a production build without NUXT_PUBLIC_GINKO_PLATFORM): no link.
|
|
13
|
+
*/
|
|
14
|
+
export function docsStudioUrl(platform: string | undefined, site: string, brick: string, id: string): string | undefined {
|
|
15
|
+
if (!platform) return undefined
|
|
16
|
+
return `${platform.replace(/\/+$/, '')}/s/${[site, 'all', brick, id].map(encodeURIComponent).join('/')}`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* "Report an issue" for a page. Placeholders: `{url}`, `{title}`, `{page}` (the page's address), URL-encoded except
|
|
21
|
+
* `{url}`. GitHub repositories get the GitHub form by default; other hosts need `issueUrl`.
|
|
22
|
+
*/
|
|
23
|
+
export function docsIssueUrl(repo: { url: string, issueUrl?: string } | undefined, title: string, page: string): string | undefined {
|
|
24
|
+
if (!repo?.url) return undefined
|
|
25
|
+
const url = repo.url.replace(/\/+$/, '')
|
|
26
|
+
const template = repo.issueUrl ?? (/^https:\/\/github\.com\//.test(url) ? '{url}/issues/new?title={title}&body={page}' : undefined)
|
|
27
|
+
const values: Record<string, string> = { url, title: encodeURIComponent(title), page: encodeURIComponent(page) }
|
|
28
|
+
return template?.replace(/\{(url|title|page)\}/g, (_, k: string) => values[k]!)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Path of a page's Markdown twin (`/en/docs/x` -> `/en/docs/x.md`, `/` -> `/index.md`). */
|
|
32
|
+
export function docsMarkdownPath(path: string): string {
|
|
33
|
+
const clean = path.replace(/\/+$/, '')
|
|
34
|
+
return `${clean || '/index'}.md`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A chat the page menu can open the page in (`markdownActions.openIn`). */
|
|
38
|
+
export type DocsAssistant = 'chatgpt' | 'claude'
|
|
39
|
+
|
|
40
|
+
/** Links that open a chat with a prompt pointing at the page's Markdown. */
|
|
41
|
+
export function docsAskLinks(prompt: string): Record<DocsAssistant, string> {
|
|
42
|
+
const q = encodeURIComponent(prompt)
|
|
43
|
+
return { chatgpt: `https://chatgpt.com/?q=${q}`, claude: `https://claude.ai/new?q=${q}` }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The main-language address of the docs page `ref`, from the brick's route (`/docs/[slug]`). For a page the current
|
|
48
|
+
* language lacks: the reader's bundle does not hold it, so this is how its link still leads to the main-language page,
|
|
49
|
+
* as in Markdown (core's `routeTarget`). Undefined when the route has no `[slug]`. Without the main-language entries
|
|
50
|
+
* this takes `ref` as the main-language slug: right for links by file name while the main file keeps the slug made
|
|
51
|
+
* from it; a main file with its own `slug:` or a typo gives a 404 here, where core would report the link.
|
|
52
|
+
*/
|
|
53
|
+
export function docsMainPath(route: Partial<Record<string, string>> | undefined, mainLocale: string, ref: string): string | undefined {
|
|
54
|
+
const pattern = route?.[mainLocale]
|
|
55
|
+
return pattern?.includes('[slug]') ? pattern.replace('[slug]', ref) : undefined
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A route link outside Markdown (the start page's links and cards, `nav`, `footer.links`, `banner.href`):
|
|
60
|
+
* `$docs.<page>` names a docs page by its file name or slug, `$page.<key>` a page of `pages` in ginko.config.ts; an
|
|
61
|
+
* `#anchor` is kept. The same notation as in Markdown, so the link follows the reader's language and survives a
|
|
62
|
+
* changed slug. Undefined when `raw` is no route link or names nothing.
|
|
63
|
+
*/
|
|
64
|
+
export function docsRouteLink(raw: string, find: { doc: (ref: string) => string | undefined, page: (key: string) => string | undefined }): string | undefined {
|
|
65
|
+
const match = /^\$(docs|page)\.([^#]+)(#.*)?$/.exec(raw)
|
|
66
|
+
if (!match) return undefined
|
|
67
|
+
const path = match[1] === 'docs' ? find.doc(match[2]!) : find.page(match[2]!)
|
|
68
|
+
return path === undefined ? undefined : path + (match[3] ?? '')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The main-language address for a docs address in another language (`/de/docs/x` gives `/docs/x`), or undefined when
|
|
73
|
+
* `path` is not under a non-main locale prefix and the main docs base. `resolve` turns the stripped path into the
|
|
74
|
+
* main language's real address (the site's i18n strategy decides whether it carries a prefix). A guess: the slug may be German and then
|
|
75
|
+
* 404 again, like `docsMainPath`; the bundle holds no main-language data to check it.
|
|
76
|
+
*/
|
|
77
|
+
export function docsMainLanguagePath(path: string, locales: readonly string[], mainLocale: string, mainBase: string, resolve: (path: string) => string = (p) => p): string | undefined {
|
|
78
|
+
for (const code of locales) {
|
|
79
|
+
if (code === mainLocale || !path.startsWith(`/${code}/`)) continue
|
|
80
|
+
const rest = path.slice(code.length + 1)
|
|
81
|
+
return rest.startsWith(`${mainBase}/`) ? resolve(rest) : undefined
|
|
82
|
+
}
|
|
83
|
+
return undefined
|
|
84
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Math, mermaid and ```md preview examples for the docs layer. GinkoMarkdown shows their source by default (core stays free of KaTeX);
|
|
2
|
+
// a `components` map wins. Both load lazily, so a page without a formula or a diagram never loads KaTeX or mermaid.
|
|
3
|
+
// Math renders on the server (the HTML holds `.katex`); mermaid renders in the browser.
|
|
4
|
+
import { defineAsyncComponent } from 'vue'
|
|
5
|
+
import DocsTable from '../components/DocsTable.vue'
|
|
6
|
+
|
|
7
|
+
export const markdownComponents = {
|
|
8
|
+
math: defineAsyncComponent(async () => {
|
|
9
|
+
const [, { Math }] = await Promise.all([import('katex/dist/katex.min.css'), import('@comark/vue/plugins/math')])
|
|
10
|
+
return Math
|
|
11
|
+
}),
|
|
12
|
+
mermaid: defineAsyncComponent(() => import('@comark/vue/plugins/mermaid').then((m) => m.Mermaid)),
|
|
13
|
+
example: defineAsyncComponent(() => import('../components/DocsExample.vue')),
|
|
14
|
+
table: DocsTable,
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// The table of contents of a ```md preview: ginko-content keeps only the preview's nodes, so an `::inline-toc`
|
|
2
|
+
// inside a preview gets its links from here. Same shape as the page toc: h2 links with their h3 as children.
|
|
3
|
+
import type { MarkdownToc, TocLink } from '@lupinum/ginko'
|
|
4
|
+
|
|
5
|
+
type Node = string | [string, Record<string, unknown>, ...unknown[]]
|
|
6
|
+
const textOf = (node: Node): string => (typeof node === 'string' ? node : (node.slice(2) as Node[]).map(textOf).join(''))
|
|
7
|
+
|
|
8
|
+
export function previewToc(nodes: readonly unknown[]): MarkdownToc {
|
|
9
|
+
const links: TocLink[] = []
|
|
10
|
+
for (const node of nodes as Node[]) {
|
|
11
|
+
if (typeof node === 'string' || (node[0] !== 'h2' && node[0] !== 'h3') || typeof node[1].id !== 'string') continue
|
|
12
|
+
const link: TocLink = { id: node[1].id, depth: node[0] === 'h2' ? 2 : 3, text: textOf(node).trim() }
|
|
13
|
+
const parent = links.at(-1)
|
|
14
|
+
if (link.depth === 3 && parent?.depth === 2) (parent.children ??= []).push(link)
|
|
15
|
+
else links.push(link)
|
|
16
|
+
}
|
|
17
|
+
return { links }
|
|
18
|
+
}
|