@lupinum/ginko-docs 0.3.0-rc.1 → 0.3.0-rc.2
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/README.md +1 -1
- package/content-collections.ts +85 -0
- package/content.js +42 -38
- package/content.ts +7 -88
- package/nuxt.config.ts +1 -1
- package/package.json +2 -1
- package/server/utils/blog-feed.ts +4 -2
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Ginko Docs is a Nuxt layer for focused documentation sites. It combines Ginko Co
|
|
|
15
15
|
Install the layer and its Ginko Content peer:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
pnpm add -D @lupinum/ginko-docs@0.3.0-rc.
|
|
18
|
+
pnpm add -D @lupinum/ginko-docs@0.3.0-rc.2 @lupinum/ginko-content@0.4.0-rc.1
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
Keep the public identity in one shared value:
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { defineCollection, defineContentConfig, reference } from "@lupinum/ginko-content/config";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { routeSlugs } from "./shared/route-slugs";
|
|
4
|
+
|
|
5
|
+
const isoDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Expected an ISO date (YYYY-MM-DD)");
|
|
6
|
+
const nonEmptyString = z.string().trim().min(1);
|
|
7
|
+
const redirectFrom = z
|
|
8
|
+
.array(nonEmptyString.regex(/^\//, "redirectFrom entries must be absolute site paths"))
|
|
9
|
+
.optional();
|
|
10
|
+
|
|
11
|
+
const withSitemapLastmod = <T extends object>(data: T, lastmod: string | undefined) =>
|
|
12
|
+
lastmod ? { ...data, sitemap: { lastmod: `${lastmod}T00:00:00.000Z` } } : data;
|
|
13
|
+
|
|
14
|
+
const docsSchema = z.object({
|
|
15
|
+
title: z.string(),
|
|
16
|
+
description: z.string(),
|
|
17
|
+
icon: z.string().optional(),
|
|
18
|
+
badge: z.string().optional(),
|
|
19
|
+
updated: isoDate.optional(),
|
|
20
|
+
redirectFrom,
|
|
21
|
+
sidebar: z.enum(["section", "group"]).optional(),
|
|
22
|
+
navigation: z
|
|
23
|
+
.object({
|
|
24
|
+
title: z.string().optional(),
|
|
25
|
+
icon: z.string().optional(),
|
|
26
|
+
badge: z.string().optional(),
|
|
27
|
+
sidebar: z.enum(["section", "group"]).optional(),
|
|
28
|
+
})
|
|
29
|
+
.optional(),
|
|
30
|
+
});
|
|
31
|
+
const docsSchemaWithLastmod = docsSchema.transform((data) =>
|
|
32
|
+
withSitemapLastmod(data, data.updated),
|
|
33
|
+
);
|
|
34
|
+
const blogSchema = z.object({
|
|
35
|
+
title: z.string(),
|
|
36
|
+
description: z.string(),
|
|
37
|
+
badge: z.string().optional(),
|
|
38
|
+
date: isoDate,
|
|
39
|
+
readingTime: nonEmptyString,
|
|
40
|
+
author: reference("authors"),
|
|
41
|
+
image: z.string().optional(),
|
|
42
|
+
redirectFrom,
|
|
43
|
+
});
|
|
44
|
+
const blogSchemaWithLastmod = blogSchema.transform((data) => withSitemapLastmod(data, data.date));
|
|
45
|
+
const authorsSchema = z.object({
|
|
46
|
+
slug: z.string(),
|
|
47
|
+
name: z.string(),
|
|
48
|
+
role: z.string(),
|
|
49
|
+
bio: z.string(),
|
|
50
|
+
avatar: z.string(),
|
|
51
|
+
links: z.array(z.object({ label: z.string(), href: z.string() })).optional(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export function createGinkoDocsCollections(i18n: boolean) {
|
|
55
|
+
return defineContentConfig({
|
|
56
|
+
collections: {
|
|
57
|
+
docs: defineCollection({
|
|
58
|
+
type: "page",
|
|
59
|
+
source: i18n ? "{1.docs,1.dokumentation}/**/*.md" : "docs/**/*.md",
|
|
60
|
+
i18n: i18n ? true : undefined,
|
|
61
|
+
route: i18n ? routeSlugs.docs : routeSlugs.docs.en,
|
|
62
|
+
agent: { section: "optional", markdown: true },
|
|
63
|
+
strict: true,
|
|
64
|
+
schema: docsSchemaWithLastmod,
|
|
65
|
+
}),
|
|
66
|
+
blog: defineCollection({
|
|
67
|
+
type: "page",
|
|
68
|
+
source: "2.blog/*.md",
|
|
69
|
+
i18n: i18n ? true : undefined,
|
|
70
|
+
route: i18n ? routeSlugs.blog : routeSlugs.blog.en,
|
|
71
|
+
agent: { section: "blog", markdown: true },
|
|
72
|
+
strict: true,
|
|
73
|
+
schema: blogSchemaWithLastmod,
|
|
74
|
+
}),
|
|
75
|
+
authors: defineCollection({
|
|
76
|
+
type: "data",
|
|
77
|
+
source: "authors/**/*.json",
|
|
78
|
+
i18n: i18n ? true : undefined,
|
|
79
|
+
strict: true,
|
|
80
|
+
sitemap: false,
|
|
81
|
+
schema: authorsSchema,
|
|
82
|
+
}),
|
|
83
|
+
},
|
|
84
|
+
}).collections;
|
|
85
|
+
}
|
package/content.js
CHANGED
|
@@ -22,17 +22,12 @@ const routeSlugs = {
|
|
|
22
22
|
},
|
|
23
23
|
};
|
|
24
24
|
//#endregion
|
|
25
|
-
//#region layer/content.ts
|
|
25
|
+
//#region layer/content-collections.ts
|
|
26
26
|
const isoDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Expected an ISO date (YYYY-MM-DD)");
|
|
27
27
|
const nonEmptyString = z.string().trim().min(1);
|
|
28
|
-
/** Former public URLs of a page, as served: locale prefix and translated slugs included. */
|
|
29
28
|
const redirectFrom = z
|
|
30
29
|
.array(nonEmptyString.regex(/^\//, "redirectFrom entries must be absolute site paths"))
|
|
31
30
|
.optional();
|
|
32
|
-
/**
|
|
33
|
-
* Derives the sitemap lastmod from the authored date so it has one source of
|
|
34
|
-
* truth. Route records require normalized UTC ISO values.
|
|
35
|
-
*/
|
|
36
31
|
const withSitemapLastmod = (data, lastmod) =>
|
|
37
32
|
lastmod
|
|
38
33
|
? {
|
|
@@ -86,6 +81,46 @@ const authorsSchema = z.object({
|
|
|
86
81
|
)
|
|
87
82
|
.optional(),
|
|
88
83
|
});
|
|
84
|
+
function createGinkoDocsCollections(i18n) {
|
|
85
|
+
return defineContentConfig({
|
|
86
|
+
collections: {
|
|
87
|
+
docs: defineCollection({
|
|
88
|
+
type: "page",
|
|
89
|
+
source: i18n ? "{1.docs,1.dokumentation}/**/*.md" : "docs/**/*.md",
|
|
90
|
+
i18n: i18n ? true : void 0,
|
|
91
|
+
route: i18n ? routeSlugs.docs : routeSlugs.docs.en,
|
|
92
|
+
agent: {
|
|
93
|
+
section: "optional",
|
|
94
|
+
markdown: true,
|
|
95
|
+
},
|
|
96
|
+
strict: true,
|
|
97
|
+
schema: docsSchemaWithLastmod,
|
|
98
|
+
}),
|
|
99
|
+
blog: defineCollection({
|
|
100
|
+
type: "page",
|
|
101
|
+
source: "2.blog/*.md",
|
|
102
|
+
i18n: i18n ? true : void 0,
|
|
103
|
+
route: i18n ? routeSlugs.blog : routeSlugs.blog.en,
|
|
104
|
+
agent: {
|
|
105
|
+
section: "blog",
|
|
106
|
+
markdown: true,
|
|
107
|
+
},
|
|
108
|
+
strict: true,
|
|
109
|
+
schema: blogSchemaWithLastmod,
|
|
110
|
+
}),
|
|
111
|
+
authors: defineCollection({
|
|
112
|
+
type: "data",
|
|
113
|
+
source: "authors/**/*.json",
|
|
114
|
+
i18n: i18n ? true : void 0,
|
|
115
|
+
strict: true,
|
|
116
|
+
sitemap: false,
|
|
117
|
+
schema: authorsSchema,
|
|
118
|
+
}),
|
|
119
|
+
},
|
|
120
|
+
}).collections;
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region layer/content.ts
|
|
89
124
|
function defineGinkoDocsConfig(options) {
|
|
90
125
|
const locales = options.locales ?? ["en"];
|
|
91
126
|
if (
|
|
@@ -106,38 +141,7 @@ function defineGinkoDocsConfig(options) {
|
|
|
106
141
|
"source",
|
|
107
142
|
"updated",
|
|
108
143
|
]);
|
|
109
|
-
const docs =
|
|
110
|
-
type: "page",
|
|
111
|
-
source: i18n ? "{1.docs,1.dokumentation}/**/*.md" : "docs/**/*.md",
|
|
112
|
-
i18n: i18n ? true : void 0,
|
|
113
|
-
route: i18n ? routeSlugs.docs : routeSlugs.docs.en,
|
|
114
|
-
agent: {
|
|
115
|
-
section: "optional",
|
|
116
|
-
markdown: true,
|
|
117
|
-
},
|
|
118
|
-
strict: true,
|
|
119
|
-
schema: docsSchemaWithLastmod,
|
|
120
|
-
});
|
|
121
|
-
const blog = defineCollection({
|
|
122
|
-
type: "page",
|
|
123
|
-
source: "2.blog/*.md",
|
|
124
|
-
i18n: i18n ? true : void 0,
|
|
125
|
-
route: i18n ? routeSlugs.blog : routeSlugs.blog.en,
|
|
126
|
-
agent: {
|
|
127
|
-
section: "blog",
|
|
128
|
-
markdown: true,
|
|
129
|
-
},
|
|
130
|
-
strict: true,
|
|
131
|
-
schema: blogSchemaWithLastmod,
|
|
132
|
-
});
|
|
133
|
-
const authors = defineCollection({
|
|
134
|
-
type: "data",
|
|
135
|
-
source: "authors/**/*.json",
|
|
136
|
-
i18n: i18n ? true : void 0,
|
|
137
|
-
strict: true,
|
|
138
|
-
sitemap: false,
|
|
139
|
-
schema: authorsSchema,
|
|
140
|
-
});
|
|
144
|
+
const { docs, blog, authors } = createGinkoDocsCollections(i18n);
|
|
141
145
|
const config = {
|
|
142
146
|
agent: {
|
|
143
147
|
site: {
|
package/content.ts
CHANGED
|
@@ -1,27 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defineAgentMetadataFields,
|
|
3
3
|
defineAgentSection,
|
|
4
|
-
defineCollection,
|
|
5
4
|
defineContentConfig,
|
|
6
|
-
reference,
|
|
7
5
|
} from "@lupinum/ginko-content/config";
|
|
8
|
-
import type {
|
|
9
|
-
import {
|
|
10
|
-
import { routeSlugs } from "./shared/route-slugs";
|
|
11
|
-
|
|
12
|
-
const isoDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Expected an ISO date (YYYY-MM-DD)");
|
|
13
|
-
const nonEmptyString = z.string().trim().min(1);
|
|
14
|
-
/** Former public URLs of a page, as served: locale prefix and translated slugs included. */
|
|
15
|
-
const redirectFrom = z
|
|
16
|
-
.array(nonEmptyString.regex(/^\//, "redirectFrom entries must be absolute site paths"))
|
|
17
|
-
.optional();
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Derives the sitemap lastmod from the authored date so it has one source of
|
|
21
|
-
* truth. Route records require normalized UTC ISO values.
|
|
22
|
-
*/
|
|
23
|
-
const withSitemapLastmod = <T extends object>(data: T, lastmod: string | undefined) =>
|
|
24
|
-
lastmod ? { ...data, sitemap: { lastmod: `${lastmod}T00:00:00.000Z` } } : data;
|
|
6
|
+
import type { ContentConfig } from "@lupinum/ginko-content/config";
|
|
7
|
+
import { createGinkoDocsCollections } from "./content-collections";
|
|
25
8
|
|
|
26
9
|
export interface GinkoDocsContentOptions {
|
|
27
10
|
site: {
|
|
@@ -33,49 +16,10 @@ export interface GinkoDocsContentOptions {
|
|
|
33
16
|
blog?: boolean;
|
|
34
17
|
}
|
|
35
18
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
badge: z.string().optional(),
|
|
41
|
-
updated: isoDate.optional(),
|
|
42
|
-
redirectFrom,
|
|
43
|
-
sidebar: z.enum(["section", "group"]).optional(),
|
|
44
|
-
navigation: z
|
|
45
|
-
.object({
|
|
46
|
-
title: z.string().optional(),
|
|
47
|
-
icon: z.string().optional(),
|
|
48
|
-
badge: z.string().optional(),
|
|
49
|
-
sidebar: z.enum(["section", "group"]).optional(),
|
|
50
|
-
})
|
|
51
|
-
.optional(),
|
|
52
|
-
});
|
|
53
|
-
const docsSchemaWithLastmod = docsSchema.transform((data) =>
|
|
54
|
-
withSitemapLastmod(data, data.updated),
|
|
55
|
-
);
|
|
56
|
-
const blogSchema = z.object({
|
|
57
|
-
title: z.string(),
|
|
58
|
-
description: z.string(),
|
|
59
|
-
badge: z.string().optional(),
|
|
60
|
-
date: isoDate,
|
|
61
|
-
readingTime: nonEmptyString,
|
|
62
|
-
author: reference("authors"),
|
|
63
|
-
image: z.string().optional(),
|
|
64
|
-
redirectFrom,
|
|
65
|
-
});
|
|
66
|
-
const blogSchemaWithLastmod = blogSchema.transform((data) => withSitemapLastmod(data, data.date));
|
|
67
|
-
const authorsSchema = z.object({
|
|
68
|
-
slug: z.string(),
|
|
69
|
-
name: z.string(),
|
|
70
|
-
role: z.string(),
|
|
71
|
-
bio: z.string(),
|
|
72
|
-
avatar: z.string(),
|
|
73
|
-
links: z.array(z.object({ label: z.string(), href: z.string() })).optional(),
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
type DocsCollection = ContentCollectionConfig<typeof docsSchemaWithLastmod>;
|
|
77
|
-
type BlogCollection = ContentCollectionConfig<typeof blogSchemaWithLastmod>;
|
|
78
|
-
type AuthorsCollection = ContentCollectionConfig<typeof authorsSchema>;
|
|
19
|
+
type Collections = ReturnType<typeof createGinkoDocsCollections>;
|
|
20
|
+
type DocsCollection = Collections["docs"];
|
|
21
|
+
type BlogCollection = Collections["blog"];
|
|
22
|
+
type AuthorsCollection = Collections["authors"];
|
|
79
23
|
type DocsContentConfig = ContentConfig<{ docs: DocsCollection }>;
|
|
80
24
|
type DocsBlogContentConfig = ContentConfig<{
|
|
81
25
|
docs: DocsCollection;
|
|
@@ -110,32 +54,7 @@ export function defineGinkoDocsConfig(
|
|
|
110
54
|
"source",
|
|
111
55
|
"updated",
|
|
112
56
|
]);
|
|
113
|
-
const docs =
|
|
114
|
-
type: "page",
|
|
115
|
-
source: i18n ? "{1.docs,1.dokumentation}/**/*.md" : "docs/**/*.md",
|
|
116
|
-
i18n: i18n ? true : undefined,
|
|
117
|
-
route: i18n ? routeSlugs.docs : routeSlugs.docs.en,
|
|
118
|
-
agent: { section: "optional", markdown: true },
|
|
119
|
-
strict: true,
|
|
120
|
-
schema: docsSchemaWithLastmod,
|
|
121
|
-
});
|
|
122
|
-
const blog = defineCollection({
|
|
123
|
-
type: "page",
|
|
124
|
-
source: "2.blog/*.md",
|
|
125
|
-
i18n: i18n ? true : undefined,
|
|
126
|
-
route: i18n ? routeSlugs.blog : routeSlugs.blog.en,
|
|
127
|
-
agent: { section: "blog", markdown: true },
|
|
128
|
-
strict: true,
|
|
129
|
-
schema: blogSchemaWithLastmod,
|
|
130
|
-
});
|
|
131
|
-
const authors = defineCollection({
|
|
132
|
-
type: "data",
|
|
133
|
-
source: "authors/**/*.json",
|
|
134
|
-
i18n: i18n ? true : undefined,
|
|
135
|
-
strict: true,
|
|
136
|
-
sitemap: false,
|
|
137
|
-
schema: authorsSchema,
|
|
138
|
-
});
|
|
57
|
+
const { docs, blog, authors } = createGinkoDocsCollections(i18n);
|
|
139
58
|
const config = {
|
|
140
59
|
agent: {
|
|
141
60
|
site: {
|
package/nuxt.config.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lupinum/ginko-docs",
|
|
3
|
-
"version": "0.3.0-rc.
|
|
3
|
+
"version": "0.3.0-rc.2",
|
|
4
4
|
"description": "A Nuxt documentation layer powered by Ginko Content.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"content",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"runtime",
|
|
27
27
|
"server",
|
|
28
28
|
"shared",
|
|
29
|
+
"content-collections.ts",
|
|
29
30
|
"content.js",
|
|
30
31
|
"content.ts",
|
|
31
32
|
"components.ts",
|
|
@@ -6,9 +6,11 @@ import { blog } from "../../i18n/messages/global/blog";
|
|
|
6
6
|
import { locales, localizedPath, type LocaleCode } from "../../i18n/locales";
|
|
7
7
|
import { routeSlugs } from "../../shared/route-slugs";
|
|
8
8
|
import { getLocalizedSiteText } from "../../app/config/site.utils";
|
|
9
|
+
import { createGinkoDocsCollections } from "../../content-collections";
|
|
9
10
|
import { buildRssFeed } from "./feed";
|
|
10
11
|
|
|
11
12
|
export const MAX_FEED_POSTS = 50;
|
|
13
|
+
const { blog: blogCollection, authors: authorsCollection } = createGinkoDocsCollections(true);
|
|
12
14
|
|
|
13
15
|
export function blogFeedPath(locale: LocaleCode): string {
|
|
14
16
|
return `${localizedPath(locale, routeSlugs.blog[locale])}/rss.xml`;
|
|
@@ -23,10 +25,10 @@ export async function serveBlogFeed(event: H3Event, locale: LocaleCode) {
|
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
const site = useAppConfig().ginkoDocs.site;
|
|
26
|
-
const posts = await many(event,
|
|
28
|
+
const posts = await many(event, blogCollection, {
|
|
27
29
|
locale,
|
|
28
30
|
fallback: true,
|
|
29
|
-
populate: { author:
|
|
31
|
+
populate: { author: authorsCollection },
|
|
30
32
|
sort: { date: "desc" },
|
|
31
33
|
limit: MAX_FEED_POSTS,
|
|
32
34
|
});
|