@lupinum/ginko-docs 0.4.0 → 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 +63 -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 +28 -8
- package/dist/config.d.ts +28 -8
- package/dist/config.mjs +16 -47
- 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,9 +62,12 @@ 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;
|
|
63
73
|
};
|
|
@@ -140,10 +150,13 @@ declare function docsHome(): _lupinum_ginko.SingleBrick<{
|
|
|
140
150
|
* /blog/<slug> (main locale unprefixed, `/<l>/blog/<slug>` otherwise) and come out newest first.
|
|
141
151
|
* Authors have no page; a post names them in `authors`. Files: `content/posts/<n>.<name>[.<locale>].md`
|
|
142
152
|
* and `content/authors/<name>[.<locale>].yml`. Add `blog: blogPages({ locales })` to `pages` as well.
|
|
153
|
+
* The bricks are `posts()` and `authors()` of `@lupinum/ginko/basics` with the English labels docs sites store.
|
|
154
|
+
* `source` (both bricks): see `DocsSource`.
|
|
143
155
|
*/
|
|
144
156
|
declare function blogCollections(opts: {
|
|
145
157
|
locales: Locales;
|
|
146
158
|
max?: number;
|
|
159
|
+
source?: DocsSource;
|
|
147
160
|
}): {
|
|
148
161
|
posts: _lupinum_ginko.CollectionBrick<{
|
|
149
162
|
readonly title: _lupinum_ginko_internal.TextField & Omit<_lupinum_ginko_internal.TextField, "type" | "label" | "pos" | "retired" | "default"> & {
|
|
@@ -158,7 +171,7 @@ declare function blogCollections(opts: {
|
|
|
158
171
|
readonly authors: _lupinum_ginko_internal.RefField & {
|
|
159
172
|
multiple: true;
|
|
160
173
|
} & {
|
|
161
|
-
readonly to:
|
|
174
|
+
readonly to: string;
|
|
162
175
|
readonly optional: true;
|
|
163
176
|
};
|
|
164
177
|
readonly image: _lupinum_ginko_internal.ImageField & {
|
|
@@ -169,7 +182,7 @@ declare function blogCollections(opts: {
|
|
|
169
182
|
readonly max: 40;
|
|
170
183
|
};
|
|
171
184
|
}> & Omit<{
|
|
172
|
-
readonly itemLabel:
|
|
185
|
+
readonly itemLabel: Record<string, string>;
|
|
173
186
|
readonly fields: {
|
|
174
187
|
readonly tag: _lupinum_ginko_internal.TextField & {
|
|
175
188
|
readonly max: 40;
|
|
@@ -195,7 +208,6 @@ declare function blogCollections(opts: {
|
|
|
195
208
|
readonly avatar: _lupinum_ginko_internal.ImageField & {
|
|
196
209
|
readonly optional: true;
|
|
197
210
|
};
|
|
198
|
-
/** Shown as a link on the author's name. */
|
|
199
211
|
readonly url: _lupinum_ginko_internal.TextField & {
|
|
200
212
|
readonly format: "url";
|
|
201
213
|
readonly optional: true;
|
|
@@ -206,9 +218,12 @@ declare function blogCollections(opts: {
|
|
|
206
218
|
* The optional changelog: one page at /changelog with one entry per release, newest first. Entries have no
|
|
207
219
|
* page of their own, the page links to each by anchor. Files: `content/changelog/<name>[.<locale>].md`
|
|
208
220
|
* 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.
|
|
221
|
+
* The brick is `changelog()` of `@lupinum/ginko/basics` with the English labels docs sites store. `source`: see
|
|
222
|
+
* `DocsSource`.
|
|
209
223
|
*/
|
|
210
224
|
declare function changelogCollection(opts?: {
|
|
211
225
|
max?: number;
|
|
226
|
+
source?: DocsSource;
|
|
212
227
|
}): _lupinum_ginko.CollectionBrick<{
|
|
213
228
|
readonly version: _lupinum_ginko_internal.TextField & {
|
|
214
229
|
readonly max: 40;
|
|
@@ -221,6 +236,10 @@ declare function changelogCollection(opts?: {
|
|
|
221
236
|
readonly profile: "page";
|
|
222
237
|
};
|
|
223
238
|
}, undefined>;
|
|
239
|
+
/** The start page by locale, for `pages` in ginko.config.ts: the main locale at `/`, the others at `/<locale>`. */
|
|
240
|
+
declare const homePages: (opts: {
|
|
241
|
+
locales: Locales;
|
|
242
|
+
}) => Record<string, "/">;
|
|
224
243
|
/** The list pages by locale, for `pages` in ginko.config.ts: the sitemap and the static build know them from there. */
|
|
225
244
|
declare const blogPages: (opts: {
|
|
226
245
|
locales: Locales;
|
|
@@ -229,4 +248,5 @@ declare const changelogPages: (opts: {
|
|
|
229
248
|
locales: Locales;
|
|
230
249
|
}) => Record<string, "/changelog">;
|
|
231
250
|
|
|
232
|
-
export { blogCollections, blogPages, changelogCollection, changelogPages, docsCollection, docsHome };
|
|
251
|
+
export { blogCollections, blogPages, changelogCollection, changelogPages, docsCollection, docsHome, homePages };
|
|
252
|
+
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,9 +62,12 @@ 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;
|
|
63
73
|
};
|
|
@@ -140,10 +150,13 @@ declare function docsHome(): _lupinum_ginko.SingleBrick<{
|
|
|
140
150
|
* /blog/<slug> (main locale unprefixed, `/<l>/blog/<slug>` otherwise) and come out newest first.
|
|
141
151
|
* Authors have no page; a post names them in `authors`. Files: `content/posts/<n>.<name>[.<locale>].md`
|
|
142
152
|
* and `content/authors/<name>[.<locale>].yml`. Add `blog: blogPages({ locales })` to `pages` as well.
|
|
153
|
+
* The bricks are `posts()` and `authors()` of `@lupinum/ginko/basics` with the English labels docs sites store.
|
|
154
|
+
* `source` (both bricks): see `DocsSource`.
|
|
143
155
|
*/
|
|
144
156
|
declare function blogCollections(opts: {
|
|
145
157
|
locales: Locales;
|
|
146
158
|
max?: number;
|
|
159
|
+
source?: DocsSource;
|
|
147
160
|
}): {
|
|
148
161
|
posts: _lupinum_ginko.CollectionBrick<{
|
|
149
162
|
readonly title: _lupinum_ginko_internal.TextField & Omit<_lupinum_ginko_internal.TextField, "type" | "label" | "pos" | "retired" | "default"> & {
|
|
@@ -158,7 +171,7 @@ declare function blogCollections(opts: {
|
|
|
158
171
|
readonly authors: _lupinum_ginko_internal.RefField & {
|
|
159
172
|
multiple: true;
|
|
160
173
|
} & {
|
|
161
|
-
readonly to:
|
|
174
|
+
readonly to: string;
|
|
162
175
|
readonly optional: true;
|
|
163
176
|
};
|
|
164
177
|
readonly image: _lupinum_ginko_internal.ImageField & {
|
|
@@ -169,7 +182,7 @@ declare function blogCollections(opts: {
|
|
|
169
182
|
readonly max: 40;
|
|
170
183
|
};
|
|
171
184
|
}> & Omit<{
|
|
172
|
-
readonly itemLabel:
|
|
185
|
+
readonly itemLabel: Record<string, string>;
|
|
173
186
|
readonly fields: {
|
|
174
187
|
readonly tag: _lupinum_ginko_internal.TextField & {
|
|
175
188
|
readonly max: 40;
|
|
@@ -195,7 +208,6 @@ declare function blogCollections(opts: {
|
|
|
195
208
|
readonly avatar: _lupinum_ginko_internal.ImageField & {
|
|
196
209
|
readonly optional: true;
|
|
197
210
|
};
|
|
198
|
-
/** Shown as a link on the author's name. */
|
|
199
211
|
readonly url: _lupinum_ginko_internal.TextField & {
|
|
200
212
|
readonly format: "url";
|
|
201
213
|
readonly optional: true;
|
|
@@ -206,9 +218,12 @@ declare function blogCollections(opts: {
|
|
|
206
218
|
* The optional changelog: one page at /changelog with one entry per release, newest first. Entries have no
|
|
207
219
|
* page of their own, the page links to each by anchor. Files: `content/changelog/<name>[.<locale>].md`
|
|
208
220
|
* 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.
|
|
221
|
+
* The brick is `changelog()` of `@lupinum/ginko/basics` with the English labels docs sites store. `source`: see
|
|
222
|
+
* `DocsSource`.
|
|
209
223
|
*/
|
|
210
224
|
declare function changelogCollection(opts?: {
|
|
211
225
|
max?: number;
|
|
226
|
+
source?: DocsSource;
|
|
212
227
|
}): _lupinum_ginko.CollectionBrick<{
|
|
213
228
|
readonly version: _lupinum_ginko_internal.TextField & {
|
|
214
229
|
readonly max: 40;
|
|
@@ -221,6 +236,10 @@ declare function changelogCollection(opts?: {
|
|
|
221
236
|
readonly profile: "page";
|
|
222
237
|
};
|
|
223
238
|
}, undefined>;
|
|
239
|
+
/** The start page by locale, for `pages` in ginko.config.ts: the main locale at `/`, the others at `/<locale>`. */
|
|
240
|
+
declare const homePages: (opts: {
|
|
241
|
+
locales: Locales;
|
|
242
|
+
}) => Record<string, "/">;
|
|
224
243
|
/** The list pages by locale, for `pages` in ginko.config.ts: the sitemap and the static build know them from there. */
|
|
225
244
|
declare const blogPages: (opts: {
|
|
226
245
|
locales: Locales;
|
|
@@ -229,4 +248,5 @@ declare const changelogPages: (opts: {
|
|
|
229
248
|
locales: Locales;
|
|
230
249
|
}) => Record<string, "/changelog">;
|
|
231
250
|
|
|
232
|
-
export { blogCollections, blogPages, changelogCollection, changelogPages, docsCollection, docsHome };
|
|
251
|
+
export { blogCollections, blogPages, changelogCollection, changelogPages, docsCollection, docsHome, homePages };
|
|
252
|
+
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,10 +40,10 @@ 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
48
|
title: f.text("Title", { max: 120 }),
|
|
43
49
|
description: f.longtext("Description", { optional: true, max: 400 }),
|
|
@@ -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 };
|