@lupinum/ginko-docs 0.4.0 → 0.6.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 +74 -0
- package/README.md +111 -16
- package/app/app.config.ts +2 -0
- package/app/assets/docs.css +79 -15
- package/app/components/DocsBanner.vue +1 -1
- package/app/components/DocsExample.vue +1 -1
- package/app/components/DocsHeader.vue +7 -6
- package/app/components/DocsIcon.vue +12 -0
- package/app/components/DocsLanding.vue +8 -5
- package/app/components/DocsLanguage.vue +18 -7
- package/app/components/DocsNavSheet.vue +37 -0
- package/app/components/DocsNavTree.vue +2 -2
- package/app/components/DocsPage.vue +16 -12
- package/app/components/DocsPageActions.vue +5 -5
- package/app/components/DocsPageEnd.ts +12 -0
- package/app/components/DocsPost.vue +11 -11
- package/app/components/DocsSearch.vue +7 -5
- package/app/components/DocsShell.vue +19 -3
- package/app/components/DocsSidebar.vue +2 -1
- package/app/components/DocsSidebarEnd.ts +4 -0
- package/app/components/DocsThemeToggle.vue +3 -3
- package/app/components/DocsToc.vue +2 -2
- package/app/composables/useDocs.ts +3 -2
- package/app/composables/useDocsBlog.ts +8 -7
- package/app/composables/useDocsFrame.ts +11 -0
- package/app/composables/useDocsSite.ts +24 -3
- package/app/docs-pages/blog.vue +12 -4
- package/app/docs-pages/changelog.vue +13 -5
- package/app/docs-pages/doc.vue +3 -1
- package/app/error.vue +16 -1
- package/app/layouts/default.vue +4 -0
- package/app/lib/docsPage.ts +40 -0
- package/app/lib/markdownComponents.ts +1 -2
- package/dist/config.d.mts +29 -8
- package/dist/config.d.ts +29 -8
- package/dist/config.mjs +17 -48
- package/dist/icons.d.mts +38 -0
- package/dist/icons.d.ts +38 -0
- package/dist/icons.mjs +175 -0
- package/dist/pages.mjs +56 -24
- package/i18n/de.json +1 -1
- package/i18n/en.json +1 -1
- package/i18n/fr.json +1 -0
- package/nuxt.config.ts +2 -17
- package/package.json +11 -10
- package/server/lib/blogFeed.ts +1 -1
- package/shared/layerRoutes.ts +66 -0
- package/dist/shared/ginko-docs.BwbF7JBR.mjs +0 -4
- package/server/lib/rss.ts +0 -57
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { releaseAnchor as anchor } from '@lupinum/ginko/basics'
|
|
2
3
|
import { markdownComponents } from '../lib/markdownComponents'
|
|
3
4
|
// The changelog: one page, one section per release, newest first (the collection's order). Each version has an anchor.
|
|
4
|
-
const { t } = useI18n()
|
|
5
|
+
const { t, language } = useI18n()
|
|
5
6
|
const blog = useDocsBlog()
|
|
7
|
+
const langOf = useGinkoLang()
|
|
6
8
|
const entries = blog.changelog()
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
9
|
+
// A list page: CollectionPage JSON-LD, so search engines read it as the index of its entries.
|
|
10
|
+
const site = useGinkoSite()
|
|
11
|
+
const route = useRoute()
|
|
12
|
+
useGinkoSeo(() => ({
|
|
13
|
+
title: t('docs.changelog'),
|
|
14
|
+
description: t('docs.changelogLead'),
|
|
15
|
+
jsonLd: [{ '@context': 'https://schema.org', '@type': 'CollectionPage', name: t('docs.changelog'), url: new URL(route.path, site.url).href, inLanguage: language.value }],
|
|
16
|
+
}))
|
|
10
17
|
</script>
|
|
11
18
|
|
|
12
19
|
<template>
|
|
@@ -15,12 +22,13 @@ const anchor = (version: string) => `v${version.toLowerCase().replace(/[^a-z0-9]
|
|
|
15
22
|
<header class="docs-blog-head">
|
|
16
23
|
<h1>{{ t('docs.changelog') }}</h1>
|
|
17
24
|
</header>
|
|
25
|
+
<p v-if="!entries.items.length" class="docs-meta">{{ t('docs.changelogEmpty') }}</p>
|
|
18
26
|
<section v-for="entry in entries.items" :id="anchor(entry.data.version)" :key="entry.id" class="docs-release">
|
|
19
27
|
<div class="docs-release-meta">
|
|
20
28
|
<h2><a :href="`#${anchor(entry.data.version)}`">{{ entry.data.version }}</a></h2>
|
|
21
29
|
<p class="docs-meta"><time :datetime="entry.data.date">{{ blog.formatDate(entry.data.date) }}</time></p>
|
|
22
30
|
</div>
|
|
23
|
-
<GinkoMarkdown :value="entry.data.body" :components="markdownComponents" class="docs-prose" />
|
|
31
|
+
<GinkoMarkdown :value="entry.data.body" :components="markdownComponents" class="docs-prose" :lang="langOf(entry, 'body')" />
|
|
24
32
|
</section>
|
|
25
33
|
</div>
|
|
26
34
|
</div>
|
package/app/docs-pages/doc.vue
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
const route = useRoute()
|
|
3
3
|
const doc = useGinkoEntry('docs', () => route.params.slug)
|
|
4
|
-
|
|
4
|
+
// The toc comes with the text (page state holds only its ref); the same request as the page's <GinkoMarkdown>.
|
|
5
|
+
const tree = await useGinkoMarkdown(() => doc.value.data.body)
|
|
6
|
+
const toc = computed(() => tree.value?.toc?.links ?? [])
|
|
5
7
|
</script>
|
|
6
8
|
|
|
7
9
|
<template>
|
package/app/error.vue
CHANGED
|
@@ -2,12 +2,26 @@
|
|
|
2
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
3
|
// error.vue replaces this one.
|
|
4
4
|
import type { NuxtError } from '#app'
|
|
5
|
+
import { docsMainLanguagePath } from './lib/docsPage'
|
|
5
6
|
|
|
6
7
|
const props = defineProps<{ error: NuxtError }>()
|
|
7
|
-
const { t, localePath } = useI18n()
|
|
8
|
+
const { t, localePath, locales, locale } = useI18n()
|
|
8
9
|
const docs = useDocs()
|
|
10
|
+
const route = useRoute()
|
|
11
|
+
const { mainLocale } = useGinkoSite()
|
|
9
12
|
const notFound = computed(() => props.error.statusCode === 404)
|
|
10
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
|
+
})
|
|
11
25
|
// Links work without JavaScript; with it, clearError leaves the error state and navigates.
|
|
12
26
|
const leave = (to: string) => clearError({ redirect: to })
|
|
13
27
|
</script>
|
|
@@ -22,6 +36,7 @@ const leave = (to: string) => clearError({ redirect: to })
|
|
|
22
36
|
<div class="docs-error-actions">
|
|
23
37
|
<a :href="localePath('/')" class="docs-button is-large is-primary" @click.prevent="leave(localePath('/'))">{{ t('docs.notFound.home') }}</a>
|
|
24
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>
|
|
25
40
|
</div>
|
|
26
41
|
</div>
|
|
27
42
|
</div>
|
package/app/layouts/default.vue
CHANGED
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
// the docs pages then sit inside the site's frame (the docs body, DocsShell, works either way).
|
|
4
4
|
const { t } = useI18n()
|
|
5
5
|
const { theme, banner } = useAppConfig().docs ?? {}
|
|
6
|
+
provideDocsStandalone()
|
|
6
7
|
useHead({
|
|
7
8
|
// `docs-standalone` turns on the page background, font and color-scheme (docs.css); embedded, the site owns those.
|
|
8
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: '' }],
|
|
9
13
|
})
|
|
10
14
|
// "Skip to content" goes past the docs navigation to the page itself when the page has one (DocsShell), else to main.
|
|
11
15
|
function skip(event: MouseEvent) {
|
package/app/lib/docsPage.ts
CHANGED
|
@@ -42,3 +42,43 @@ export function docsAskLinks(prompt: string): Record<DocsAssistant, string> {
|
|
|
42
42
|
const q = encodeURIComponent(prompt)
|
|
43
43
|
return { chatgpt: `https://chatgpt.com/?q=${q}`, claude: `https://claude.ai/new?q=${q}` }
|
|
44
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
|
+
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// a `components` map wins. Both load lazily, so a page without a formula or a diagram never loads KaTeX or mermaid.
|
|
3
3
|
// Math renders on the server (the HTML holds `.katex`); mermaid renders in the browser.
|
|
4
4
|
import { defineAsyncComponent } from 'vue'
|
|
5
|
-
import DocsExample from '../components/DocsExample.vue'
|
|
6
5
|
import DocsTable from '../components/DocsTable.vue'
|
|
7
6
|
|
|
8
7
|
export const markdownComponents = {
|
|
@@ -11,6 +10,6 @@ export const markdownComponents = {
|
|
|
11
10
|
return Math
|
|
12
11
|
}),
|
|
13
12
|
mermaid: defineAsyncComponent(() => import('@comark/vue/plugins/mermaid').then((m) => m.Mermaid)),
|
|
14
|
-
example: DocsExample,
|
|
13
|
+
example: defineAsyncComponent(() => import('../components/DocsExample.vue')),
|
|
15
14
|
table: DocsTable,
|
|
16
15
|
}
|
package/dist/config.d.mts
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import * as _lupinum_ginko from '@lupinum/ginko';
|
|
2
|
-
import { Locale } from '@lupinum/ginko';
|
|
2
|
+
import { BrickSource, Locale } from '@lupinum/ginko';
|
|
3
3
|
import * as _lupinum_ginko_internal from '@lupinum/ginko/internal';
|
|
4
4
|
export { components } from '@lupinum/ginko-components/registry';
|
|
5
5
|
|
|
6
6
|
/** The site's locales, main locale first. The same list as `locales` in `defineSite`, as a plain array or a tuple. */
|
|
7
7
|
type Locales = readonly Locale[];
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Where a brick's content comes from: `'files'` (the default) reads Markdown from `content/`, `'platform'` keeps it on
|
|
10
|
+
* the Ginko platform, where people edit it in Ginko Studio. Unlike the bricks of `@lupinum/ginko/basics`, docs bricks
|
|
11
|
+
* read files unless they are told otherwise: docs usually live in Git.
|
|
12
|
+
*/
|
|
13
|
+
type DocsSource = BrickSource | 'platform';
|
|
14
|
+
/** `base`: where the docs live, default `/docs` (e.g. `/handbook` inside a larger site). `source`: see `DocsSource`. */
|
|
9
15
|
declare function docsCollection(opts: {
|
|
10
16
|
locales: Locales;
|
|
11
17
|
label?: string;
|
|
12
18
|
max?: number;
|
|
13
19
|
base?: `/${string}`;
|
|
20
|
+
source?: DocsSource;
|
|
14
21
|
}): _lupinum_ginko.CollectionBrick<{
|
|
15
22
|
readonly title: _lupinum_ginko_internal.TextField & Omit<_lupinum_ginko_internal.TextField, "type" | "label" | "pos" | "retired" | "default"> & {
|
|
16
23
|
default?: (string | Partial<Record<string, string>>) | undefined;
|
|
@@ -55,11 +62,15 @@ declare function docsCollection(opts: {
|
|
|
55
62
|
/**
|
|
56
63
|
* The optional start page: one Markdown file `content/home[.<locale>].md`. The frontmatter fills the hero (title,
|
|
57
64
|
* description, install command, up to two links) and the feature grid; the text below the frontmatter follows them and
|
|
58
|
-
* may use every component. Without it, `/` opens the first docs page. Add `home: {
|
|
65
|
+
* may use every component. Without it, `/` opens the first docs page. Add `home: homePages({ locales })` to `pages`.
|
|
66
|
+
* `source`: see `DocsSource`.
|
|
59
67
|
*/
|
|
60
|
-
declare function docsHome(
|
|
68
|
+
declare function docsHome(opts?: {
|
|
69
|
+
source?: DocsSource;
|
|
70
|
+
}): _lupinum_ginko.SingleBrick<{
|
|
61
71
|
readonly title: _lupinum_ginko_internal.TextField & {
|
|
62
72
|
readonly max: 120;
|
|
73
|
+
readonly default: "Documentation";
|
|
63
74
|
};
|
|
64
75
|
readonly description: _lupinum_ginko_internal.LongTextField & {
|
|
65
76
|
readonly optional: true;
|
|
@@ -140,10 +151,13 @@ declare function docsHome(): _lupinum_ginko.SingleBrick<{
|
|
|
140
151
|
* /blog/<slug> (main locale unprefixed, `/<l>/blog/<slug>` otherwise) and come out newest first.
|
|
141
152
|
* Authors have no page; a post names them in `authors`. Files: `content/posts/<n>.<name>[.<locale>].md`
|
|
142
153
|
* and `content/authors/<name>[.<locale>].yml`. Add `blog: blogPages({ locales })` to `pages` as well.
|
|
154
|
+
* The bricks are `posts()` and `authors()` of `@lupinum/ginko/basics` with the English labels docs sites store.
|
|
155
|
+
* `source` (both bricks): see `DocsSource`.
|
|
143
156
|
*/
|
|
144
157
|
declare function blogCollections(opts: {
|
|
145
158
|
locales: Locales;
|
|
146
159
|
max?: number;
|
|
160
|
+
source?: DocsSource;
|
|
147
161
|
}): {
|
|
148
162
|
posts: _lupinum_ginko.CollectionBrick<{
|
|
149
163
|
readonly title: _lupinum_ginko_internal.TextField & Omit<_lupinum_ginko_internal.TextField, "type" | "label" | "pos" | "retired" | "default"> & {
|
|
@@ -158,7 +172,7 @@ declare function blogCollections(opts: {
|
|
|
158
172
|
readonly authors: _lupinum_ginko_internal.RefField & {
|
|
159
173
|
multiple: true;
|
|
160
174
|
} & {
|
|
161
|
-
readonly to:
|
|
175
|
+
readonly to: string;
|
|
162
176
|
readonly optional: true;
|
|
163
177
|
};
|
|
164
178
|
readonly image: _lupinum_ginko_internal.ImageField & {
|
|
@@ -169,7 +183,7 @@ declare function blogCollections(opts: {
|
|
|
169
183
|
readonly max: 40;
|
|
170
184
|
};
|
|
171
185
|
}> & Omit<{
|
|
172
|
-
readonly itemLabel:
|
|
186
|
+
readonly itemLabel: Record<string, string>;
|
|
173
187
|
readonly fields: {
|
|
174
188
|
readonly tag: _lupinum_ginko_internal.TextField & {
|
|
175
189
|
readonly max: 40;
|
|
@@ -195,7 +209,6 @@ declare function blogCollections(opts: {
|
|
|
195
209
|
readonly avatar: _lupinum_ginko_internal.ImageField & {
|
|
196
210
|
readonly optional: true;
|
|
197
211
|
};
|
|
198
|
-
/** Shown as a link on the author's name. */
|
|
199
212
|
readonly url: _lupinum_ginko_internal.TextField & {
|
|
200
213
|
readonly format: "url";
|
|
201
214
|
readonly optional: true;
|
|
@@ -206,9 +219,12 @@ declare function blogCollections(opts: {
|
|
|
206
219
|
* The optional changelog: one page at /changelog with one entry per release, newest first. Entries have no
|
|
207
220
|
* page of their own, the page links to each by anchor. Files: `content/changelog/<name>[.<locale>].md`
|
|
208
221
|
* with `version` and `date` in the frontmatter (a dot in the name would read as a language: use `v1-2-0.md`). Add `changelog: changelogPages({ locales })` to `pages` as well.
|
|
222
|
+
* The brick is `changelog()` of `@lupinum/ginko/basics` with the English labels docs sites store. `source`: see
|
|
223
|
+
* `DocsSource`.
|
|
209
224
|
*/
|
|
210
225
|
declare function changelogCollection(opts?: {
|
|
211
226
|
max?: number;
|
|
227
|
+
source?: DocsSource;
|
|
212
228
|
}): _lupinum_ginko.CollectionBrick<{
|
|
213
229
|
readonly version: _lupinum_ginko_internal.TextField & {
|
|
214
230
|
readonly max: 40;
|
|
@@ -221,6 +237,10 @@ declare function changelogCollection(opts?: {
|
|
|
221
237
|
readonly profile: "page";
|
|
222
238
|
};
|
|
223
239
|
}, undefined>;
|
|
240
|
+
/** The start page by locale, for `pages` in ginko.config.ts: the main locale at `/`, the others at `/<locale>`. */
|
|
241
|
+
declare const homePages: (opts: {
|
|
242
|
+
locales: Locales;
|
|
243
|
+
}) => Record<string, "/">;
|
|
224
244
|
/** The list pages by locale, for `pages` in ginko.config.ts: the sitemap and the static build know them from there. */
|
|
225
245
|
declare const blogPages: (opts: {
|
|
226
246
|
locales: Locales;
|
|
@@ -229,4 +249,5 @@ declare const changelogPages: (opts: {
|
|
|
229
249
|
locales: Locales;
|
|
230
250
|
}) => Record<string, "/changelog">;
|
|
231
251
|
|
|
232
|
-
export { blogCollections, blogPages, changelogCollection, changelogPages, docsCollection, docsHome };
|
|
252
|
+
export { blogCollections, blogPages, changelogCollection, changelogPages, docsCollection, docsHome, homePages };
|
|
253
|
+
export type { DocsSource };
|
package/dist/config.d.ts
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import * as _lupinum_ginko from '@lupinum/ginko';
|
|
2
|
-
import { Locale } from '@lupinum/ginko';
|
|
2
|
+
import { BrickSource, Locale } from '@lupinum/ginko';
|
|
3
3
|
import * as _lupinum_ginko_internal from '@lupinum/ginko/internal';
|
|
4
4
|
export { components } from '@lupinum/ginko-components/registry';
|
|
5
5
|
|
|
6
6
|
/** The site's locales, main locale first. The same list as `locales` in `defineSite`, as a plain array or a tuple. */
|
|
7
7
|
type Locales = readonly Locale[];
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Where a brick's content comes from: `'files'` (the default) reads Markdown from `content/`, `'platform'` keeps it on
|
|
10
|
+
* the Ginko platform, where people edit it in Ginko Studio. Unlike the bricks of `@lupinum/ginko/basics`, docs bricks
|
|
11
|
+
* read files unless they are told otherwise: docs usually live in Git.
|
|
12
|
+
*/
|
|
13
|
+
type DocsSource = BrickSource | 'platform';
|
|
14
|
+
/** `base`: where the docs live, default `/docs` (e.g. `/handbook` inside a larger site). `source`: see `DocsSource`. */
|
|
9
15
|
declare function docsCollection(opts: {
|
|
10
16
|
locales: Locales;
|
|
11
17
|
label?: string;
|
|
12
18
|
max?: number;
|
|
13
19
|
base?: `/${string}`;
|
|
20
|
+
source?: DocsSource;
|
|
14
21
|
}): _lupinum_ginko.CollectionBrick<{
|
|
15
22
|
readonly title: _lupinum_ginko_internal.TextField & Omit<_lupinum_ginko_internal.TextField, "type" | "label" | "pos" | "retired" | "default"> & {
|
|
16
23
|
default?: (string | Partial<Record<string, string>>) | undefined;
|
|
@@ -55,11 +62,15 @@ declare function docsCollection(opts: {
|
|
|
55
62
|
/**
|
|
56
63
|
* The optional start page: one Markdown file `content/home[.<locale>].md`. The frontmatter fills the hero (title,
|
|
57
64
|
* description, install command, up to two links) and the feature grid; the text below the frontmatter follows them and
|
|
58
|
-
* may use every component. Without it, `/` opens the first docs page. Add `home: {
|
|
65
|
+
* may use every component. Without it, `/` opens the first docs page. Add `home: homePages({ locales })` to `pages`.
|
|
66
|
+
* `source`: see `DocsSource`.
|
|
59
67
|
*/
|
|
60
|
-
declare function docsHome(
|
|
68
|
+
declare function docsHome(opts?: {
|
|
69
|
+
source?: DocsSource;
|
|
70
|
+
}): _lupinum_ginko.SingleBrick<{
|
|
61
71
|
readonly title: _lupinum_ginko_internal.TextField & {
|
|
62
72
|
readonly max: 120;
|
|
73
|
+
readonly default: "Documentation";
|
|
63
74
|
};
|
|
64
75
|
readonly description: _lupinum_ginko_internal.LongTextField & {
|
|
65
76
|
readonly optional: true;
|
|
@@ -140,10 +151,13 @@ declare function docsHome(): _lupinum_ginko.SingleBrick<{
|
|
|
140
151
|
* /blog/<slug> (main locale unprefixed, `/<l>/blog/<slug>` otherwise) and come out newest first.
|
|
141
152
|
* Authors have no page; a post names them in `authors`. Files: `content/posts/<n>.<name>[.<locale>].md`
|
|
142
153
|
* and `content/authors/<name>[.<locale>].yml`. Add `blog: blogPages({ locales })` to `pages` as well.
|
|
154
|
+
* The bricks are `posts()` and `authors()` of `@lupinum/ginko/basics` with the English labels docs sites store.
|
|
155
|
+
* `source` (both bricks): see `DocsSource`.
|
|
143
156
|
*/
|
|
144
157
|
declare function blogCollections(opts: {
|
|
145
158
|
locales: Locales;
|
|
146
159
|
max?: number;
|
|
160
|
+
source?: DocsSource;
|
|
147
161
|
}): {
|
|
148
162
|
posts: _lupinum_ginko.CollectionBrick<{
|
|
149
163
|
readonly title: _lupinum_ginko_internal.TextField & Omit<_lupinum_ginko_internal.TextField, "type" | "label" | "pos" | "retired" | "default"> & {
|
|
@@ -158,7 +172,7 @@ declare function blogCollections(opts: {
|
|
|
158
172
|
readonly authors: _lupinum_ginko_internal.RefField & {
|
|
159
173
|
multiple: true;
|
|
160
174
|
} & {
|
|
161
|
-
readonly to:
|
|
175
|
+
readonly to: string;
|
|
162
176
|
readonly optional: true;
|
|
163
177
|
};
|
|
164
178
|
readonly image: _lupinum_ginko_internal.ImageField & {
|
|
@@ -169,7 +183,7 @@ declare function blogCollections(opts: {
|
|
|
169
183
|
readonly max: 40;
|
|
170
184
|
};
|
|
171
185
|
}> & Omit<{
|
|
172
|
-
readonly itemLabel:
|
|
186
|
+
readonly itemLabel: Record<string, string>;
|
|
173
187
|
readonly fields: {
|
|
174
188
|
readonly tag: _lupinum_ginko_internal.TextField & {
|
|
175
189
|
readonly max: 40;
|
|
@@ -195,7 +209,6 @@ declare function blogCollections(opts: {
|
|
|
195
209
|
readonly avatar: _lupinum_ginko_internal.ImageField & {
|
|
196
210
|
readonly optional: true;
|
|
197
211
|
};
|
|
198
|
-
/** Shown as a link on the author's name. */
|
|
199
212
|
readonly url: _lupinum_ginko_internal.TextField & {
|
|
200
213
|
readonly format: "url";
|
|
201
214
|
readonly optional: true;
|
|
@@ -206,9 +219,12 @@ declare function blogCollections(opts: {
|
|
|
206
219
|
* The optional changelog: one page at /changelog with one entry per release, newest first. Entries have no
|
|
207
220
|
* page of their own, the page links to each by anchor. Files: `content/changelog/<name>[.<locale>].md`
|
|
208
221
|
* with `version` and `date` in the frontmatter (a dot in the name would read as a language: use `v1-2-0.md`). Add `changelog: changelogPages({ locales })` to `pages` as well.
|
|
222
|
+
* The brick is `changelog()` of `@lupinum/ginko/basics` with the English labels docs sites store. `source`: see
|
|
223
|
+
* `DocsSource`.
|
|
209
224
|
*/
|
|
210
225
|
declare function changelogCollection(opts?: {
|
|
211
226
|
max?: number;
|
|
227
|
+
source?: DocsSource;
|
|
212
228
|
}): _lupinum_ginko.CollectionBrick<{
|
|
213
229
|
readonly version: _lupinum_ginko_internal.TextField & {
|
|
214
230
|
readonly max: 40;
|
|
@@ -221,6 +237,10 @@ declare function changelogCollection(opts?: {
|
|
|
221
237
|
readonly profile: "page";
|
|
222
238
|
};
|
|
223
239
|
}, undefined>;
|
|
240
|
+
/** The start page by locale, for `pages` in ginko.config.ts: the main locale at `/`, the others at `/<locale>`. */
|
|
241
|
+
declare const homePages: (opts: {
|
|
242
|
+
locales: Locales;
|
|
243
|
+
}) => Record<string, "/">;
|
|
224
244
|
/** The list pages by locale, for `pages` in ginko.config.ts: the sitemap and the static build know them from there. */
|
|
225
245
|
declare const blogPages: (opts: {
|
|
226
246
|
locales: Locales;
|
|
@@ -229,4 +249,5 @@ declare const changelogPages: (opts: {
|
|
|
229
249
|
locales: Locales;
|
|
230
250
|
}) => Record<string, "/changelog">;
|
|
231
251
|
|
|
232
|
-
export { blogCollections, blogPages, changelogCollection, changelogPages, docsCollection, docsHome };
|
|
252
|
+
export { blogCollections, blogPages, changelogCollection, changelogPages, docsCollection, docsHome, homePages };
|
|
253
|
+
export type { DocsSource };
|
package/dist/config.mjs
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import { collection, f, single } from '@lupinum/ginko';
|
|
2
|
-
import {
|
|
2
|
+
import { authors, posts, changelog } from '@lupinum/ginko/basics';
|
|
3
|
+
import { labelsIn } from '@lupinum/ginko/internal';
|
|
3
4
|
export { components } from '@lupinum/ginko-components/registry';
|
|
4
5
|
|
|
6
|
+
const BLOG_PATH = "/blog";
|
|
7
|
+
const CHANGELOG_PATH = "/changelog";
|
|
8
|
+
|
|
9
|
+
const sourceOf = (source = "files") => source === "files" ? { source } : {};
|
|
5
10
|
function localized(locales, path) {
|
|
6
11
|
const [main] = locales;
|
|
7
12
|
if (main === void 0) throw new Error("[ginko-docs] `locales` is empty. Fix: pass the same list as `locales` in defineSite.");
|
|
8
|
-
|
|
13
|
+
const address = path;
|
|
14
|
+
return Object.fromEntries(locales.map((l) => [l, l === main ? path : address === "/" ? `/${l}` : `/${l}${path}`]));
|
|
9
15
|
}
|
|
10
16
|
function docsCollection(opts) {
|
|
11
17
|
const { locales } = opts;
|
|
12
18
|
const base = opts.base ?? "/docs";
|
|
13
19
|
return collection({
|
|
14
20
|
label: opts.label ?? "Docs",
|
|
15
|
-
source
|
|
21
|
+
...sourceOf(opts.source),
|
|
16
22
|
max: opts.max ?? 500,
|
|
17
23
|
order: "manual",
|
|
18
24
|
route: localized(locales, `${base}/[slug]`),
|
|
@@ -34,12 +40,12 @@ function docsCollection(opts) {
|
|
|
34
40
|
}
|
|
35
41
|
});
|
|
36
42
|
}
|
|
37
|
-
function docsHome() {
|
|
43
|
+
function docsHome(opts = {}) {
|
|
38
44
|
return single({
|
|
39
45
|
label: "Start page",
|
|
40
|
-
source
|
|
46
|
+
...sourceOf(opts.source),
|
|
41
47
|
fields: {
|
|
42
|
-
title: f.text("Title", { max: 120 }),
|
|
48
|
+
title: f.text("Title", { max: 120, default: "Documentation" }),
|
|
43
49
|
description: f.longtext("Description", { optional: true, max: 400 }),
|
|
44
50
|
/** A command shown with a copy button, e.g. `pnpm add @lupinum/ginko`. */
|
|
45
51
|
install: f.text("Install command", { optional: true, max: 200, translate: false }),
|
|
@@ -63,52 +69,15 @@ function docsHome() {
|
|
|
63
69
|
function blogCollections(opts) {
|
|
64
70
|
const { locales } = opts;
|
|
65
71
|
return {
|
|
66
|
-
posts:
|
|
67
|
-
|
|
68
|
-
source: "files",
|
|
69
|
-
max: opts.max ?? 500,
|
|
70
|
-
order: { field: "date", dir: "desc" },
|
|
71
|
-
route: localized(locales, `${BLOG_PATH}/[slug]`),
|
|
72
|
-
fields: {
|
|
73
|
-
title: f.text("Title"),
|
|
74
|
-
description: f.longtext("Description", { optional: true }),
|
|
75
|
-
date: f.date("Date"),
|
|
76
|
-
authors: f.refs("Authors", { to: "authors", optional: true }),
|
|
77
|
-
image: f.image("Image", { optional: true }),
|
|
78
|
-
tags: f.list("Tags", { itemLabel: "Tag", fields: { tag: f.text("Tag", { max: 40 }) }, max: 10, optional: true }),
|
|
79
|
-
slug: f.slug("Address", { from: "title" }),
|
|
80
|
-
body: f.markdown("Text", { profile: "page" })
|
|
81
|
-
}
|
|
82
|
-
}),
|
|
83
|
-
authors: collection({
|
|
84
|
-
label: "Authors",
|
|
85
|
-
source: "files",
|
|
86
|
-
max: 100,
|
|
87
|
-
order: "manual",
|
|
88
|
-
fields: {
|
|
89
|
-
name: f.text("Name"),
|
|
90
|
-
role: f.text("Role", { optional: true }),
|
|
91
|
-
avatar: f.image("Image", { optional: true }),
|
|
92
|
-
/** Shown as a link on the author's name. */
|
|
93
|
-
url: f.text("Website", { format: "url", optional: true })
|
|
94
|
-
}
|
|
95
|
-
})
|
|
72
|
+
posts: labelsIn(posts({ ...sourceOf(opts.source), max: opts.max, route: localized(locales, `${BLOG_PATH}/[slug]`) }), "en"),
|
|
73
|
+
authors: labelsIn(authors(sourceOf(opts.source)), "en")
|
|
96
74
|
};
|
|
97
75
|
}
|
|
98
76
|
function changelogCollection(opts = {}) {
|
|
99
|
-
return
|
|
100
|
-
label: "Changelog",
|
|
101
|
-
source: "files",
|
|
102
|
-
max: opts.max ?? 500,
|
|
103
|
-
order: { field: "date", dir: "desc" },
|
|
104
|
-
fields: {
|
|
105
|
-
version: f.text("Version", { max: 40, translate: false }),
|
|
106
|
-
date: f.date("Date"),
|
|
107
|
-
body: f.markdown("Text", { profile: "page" })
|
|
108
|
-
}
|
|
109
|
-
});
|
|
77
|
+
return labelsIn(changelog({ ...sourceOf(opts.source), max: opts.max }), "en");
|
|
110
78
|
}
|
|
79
|
+
const homePages = (opts) => localized(opts.locales, "/");
|
|
111
80
|
const blogPages = (opts) => localized(opts.locales, BLOG_PATH);
|
|
112
81
|
const changelogPages = (opts) => localized(opts.locales, CHANGELOG_PATH);
|
|
113
82
|
|
|
114
|
-
export { blogCollections, blogPages, changelogCollection, changelogPages, docsCollection, docsHome };
|
|
83
|
+
export { blogCollections, blogPages, changelogCollection, changelogPages, docsCollection, docsHome, homePages };
|
package/dist/icons.d.mts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getIconData } from '@iconify/utils';
|
|
2
|
+
import { NuxtModule } from 'nuxt/schema';
|
|
3
|
+
|
|
4
|
+
type IconSet = Parameters<typeof getIconData>[0];
|
|
5
|
+
type Entry = [body: string, viewBox: string];
|
|
6
|
+
/**
|
|
7
|
+
* Code without its comments, so a commented-out icon is not bundled. `//` counts only at a line start or after a space
|
|
8
|
+
* (not in `https://`). In a .vue file the template keeps its text; only `<!-- -->` and the comments inside `<script>` go.
|
|
9
|
+
*/
|
|
10
|
+
declare const stripComments: (source: string, file?: string) => string;
|
|
11
|
+
/** The icon names in app.config.ts (`icon: 'x'`) or a .vue file (`<Icon name="x">`, `icon="x"`). Comments are ignored. */
|
|
12
|
+
declare const codeIconNames: (source: string, file: string) => string[];
|
|
13
|
+
/**
|
|
14
|
+
* The icon names a content file names. YAML, JSON and front matter are read whole. In Markdown only the front matter,
|
|
15
|
+
* the attributes of a component (`:card{icon="x"}`) and the `---` block inside a `::component` count; prose and code are skipped.
|
|
16
|
+
*/
|
|
17
|
+
declare const contentIconNames: (source: string, file: string) => string[];
|
|
18
|
+
/** One icon as [svg body, viewBox], read from the `docs` collection folder or an installed @iconify-json set (looked up from `roots`). */
|
|
19
|
+
declare const readIcon: (name: string, roots: readonly string[], customDir: string, sets?: Map<string, IconSet | undefined>) => Entry | undefined;
|
|
20
|
+
interface CollectOptions {
|
|
21
|
+
/** The layer's own folder (its `app/` is scanned for literal names, its `icons/` holds the `docs:` logos). */
|
|
22
|
+
layerRoot: string;
|
|
23
|
+
/** Root folders of every layer, the site first. Their `content/`, `app.config.ts` and (for the site's own layers) `app/` are scanned. */
|
|
24
|
+
roots: readonly string[];
|
|
25
|
+
/** Where to resolve `@iconify-json/<set>` from. */
|
|
26
|
+
resolveFrom: readonly string[];
|
|
27
|
+
/** Paths in warnings are shown relative to this folder. */
|
|
28
|
+
relativeTo: string;
|
|
29
|
+
}
|
|
30
|
+
/** Every icon the layer and the site name, read from the installed sets, plus one warning per name that is not installed. */
|
|
31
|
+
declare const collectIcons: (o: CollectOptions) => {
|
|
32
|
+
icons: Record<string, Entry>;
|
|
33
|
+
warnings: string[];
|
|
34
|
+
};
|
|
35
|
+
declare const docsIcons: NuxtModule;
|
|
36
|
+
|
|
37
|
+
export { codeIconNames, collectIcons, contentIconNames, docsIcons as default, readIcon, stripComments };
|
|
38
|
+
export type { CollectOptions };
|
package/dist/icons.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getIconData } from '@iconify/utils';
|
|
2
|
+
import { NuxtModule } from 'nuxt/schema';
|
|
3
|
+
|
|
4
|
+
type IconSet = Parameters<typeof getIconData>[0];
|
|
5
|
+
type Entry = [body: string, viewBox: string];
|
|
6
|
+
/**
|
|
7
|
+
* Code without its comments, so a commented-out icon is not bundled. `//` counts only at a line start or after a space
|
|
8
|
+
* (not in `https://`). In a .vue file the template keeps its text; only `<!-- -->` and the comments inside `<script>` go.
|
|
9
|
+
*/
|
|
10
|
+
declare const stripComments: (source: string, file?: string) => string;
|
|
11
|
+
/** The icon names in app.config.ts (`icon: 'x'`) or a .vue file (`<Icon name="x">`, `icon="x"`). Comments are ignored. */
|
|
12
|
+
declare const codeIconNames: (source: string, file: string) => string[];
|
|
13
|
+
/**
|
|
14
|
+
* The icon names a content file names. YAML, JSON and front matter are read whole. In Markdown only the front matter,
|
|
15
|
+
* the attributes of a component (`:card{icon="x"}`) and the `---` block inside a `::component` count; prose and code are skipped.
|
|
16
|
+
*/
|
|
17
|
+
declare const contentIconNames: (source: string, file: string) => string[];
|
|
18
|
+
/** One icon as [svg body, viewBox], read from the `docs` collection folder or an installed @iconify-json set (looked up from `roots`). */
|
|
19
|
+
declare const readIcon: (name: string, roots: readonly string[], customDir: string, sets?: Map<string, IconSet | undefined>) => Entry | undefined;
|
|
20
|
+
interface CollectOptions {
|
|
21
|
+
/** The layer's own folder (its `app/` is scanned for literal names, its `icons/` holds the `docs:` logos). */
|
|
22
|
+
layerRoot: string;
|
|
23
|
+
/** Root folders of every layer, the site first. Their `content/`, `app.config.ts` and (for the site's own layers) `app/` are scanned. */
|
|
24
|
+
roots: readonly string[];
|
|
25
|
+
/** Where to resolve `@iconify-json/<set>` from. */
|
|
26
|
+
resolveFrom: readonly string[];
|
|
27
|
+
/** Paths in warnings are shown relative to this folder. */
|
|
28
|
+
relativeTo: string;
|
|
29
|
+
}
|
|
30
|
+
/** Every icon the layer and the site name, read from the installed sets, plus one warning per name that is not installed. */
|
|
31
|
+
declare const collectIcons: (o: CollectOptions) => {
|
|
32
|
+
icons: Record<string, Entry>;
|
|
33
|
+
warnings: string[];
|
|
34
|
+
};
|
|
35
|
+
declare const docsIcons: NuxtModule;
|
|
36
|
+
|
|
37
|
+
export { codeIconNames, collectIcons, contentIconNames, docsIcons as default, readIcon, stripComments };
|
|
38
|
+
export type { CollectOptions };
|