@minidocs/astro 0.1.0 → 0.2.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/README.md +15 -1
- package/dist/index.d.mts +0 -2
- package/dist/index.mjs +22 -9
- package/dist/{links-UTLyoo_o.mjs → links-Dp7d5R-O.mjs} +98 -33
- package/dist/loaders.d.mts +10 -5
- package/dist/loaders.mjs +35 -23
- package/dist/routing-D28b92w8.mjs +60 -0
- package/dist/runtime.d.mts +53 -11
- package/dist/runtime.mjs +59 -42
- package/package.json +2 -2
- package/src/styles/base.css +2 -1
- package/src/styles/pager.css +13 -8
- package/src/styles/search.css +5 -0
- package/dist/routing-TMqRCY2Q.mjs +0 -48
package/README.md
CHANGED
|
@@ -16,10 +16,24 @@ import { defineConfig } from 'astro/config';
|
|
|
16
16
|
import minidocs from '@minidocs/astro';
|
|
17
17
|
|
|
18
18
|
export default defineConfig({
|
|
19
|
-
integrations: [mdx(), minidocs(
|
|
19
|
+
integrations: [mdx(), minidocs()],
|
|
20
20
|
});
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
```ts
|
|
24
|
+
// src/content.config.ts
|
|
25
|
+
import { defineCollection } from 'astro:content';
|
|
26
|
+
import { z } from 'astro/zod';
|
|
27
|
+
import { glob } from '@minidocs/astro/loaders';
|
|
28
|
+
|
|
29
|
+
const docs = defineCollection({
|
|
30
|
+
loader: glob({ base: './src/content/docs', pattern: '**/*.{md,mdx}', prefix: 'docs' }),
|
|
31
|
+
schema: z.object({ title: z.string() }),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const collections = { docs };
|
|
35
|
+
```
|
|
36
|
+
|
|
23
37
|
Requires Astro 7 and Node.js 22.22 or later. See [Getting Started](https://github.com/Ivan-1F/minidocs/blob/main/docs/src/content/docs/en/getting-started.mdx) for the rest of the setup.
|
|
24
38
|
|
|
25
39
|
## License
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { AstroIntegration } from "astro";
|
|
2
2
|
//#region src/settings.d.ts
|
|
3
3
|
interface MinidocsOptions {
|
|
4
|
-
/** Path segment the docs live under, e.g. `docs` for `/docs/...`. Defaults to no prefix. */
|
|
5
|
-
prefix?: string;
|
|
6
4
|
/** Locales to try, in order, when a page is not translated. Locales without an entry never fall back. Requires `i18n`. */
|
|
7
5
|
fallback?: Record<string, string | string[]>;
|
|
8
6
|
/** Markdown syntax minidocs adds on top of the configured processor. */
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as contentOptions } from "./routing-
|
|
2
|
-
import { a as renderConfigTypes, i as
|
|
1
|
+
import { t as contentOptions } from "./routing-D28b92w8.mjs";
|
|
2
|
+
import { a as registry, c as renderConfigTypes, i as namedCollections, n as remarkContentLinks, o as findUnbuiltPages, r as satteriContentLinks } from "./links-Dp7d5R-O.mjs";
|
|
3
3
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { dirname } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -175,7 +175,6 @@ function extendProcessor(processor, callouts) {
|
|
|
175
175
|
const trim = (path) => path.replace(/^\/+|\/+$/g, "");
|
|
176
176
|
function resolveSettings(config, options) {
|
|
177
177
|
return {
|
|
178
|
-
prefix: trim(options.prefix ?? ""),
|
|
179
178
|
base: trim(config.base),
|
|
180
179
|
trailingSlash: config.trailingSlash === "always" || config.trailingSlash === "ignore" && config.build.format === "directory",
|
|
181
180
|
i18n: resolveI18n(config.i18n, options),
|
|
@@ -208,6 +207,7 @@ const CONFIG_ID = "virtual:minidocs/config";
|
|
|
208
207
|
const RENDERERS_ID = "virtual:minidocs/renderers";
|
|
209
208
|
function minidocs(options = {}) {
|
|
210
209
|
let hasMdx = false;
|
|
210
|
+
let isStatic = false;
|
|
211
211
|
return {
|
|
212
212
|
name: "minidocs",
|
|
213
213
|
hooks: {
|
|
@@ -223,13 +223,16 @@ function minidocs(options = {}) {
|
|
|
223
223
|
resolveId: (id) => id === CONFIG_ID ? `\0${CONFIG_ID}` : void 0,
|
|
224
224
|
load: (id) => id === `\0${CONFIG_ID}` ? `export default ${JSON.stringify({
|
|
225
225
|
...registry.settings,
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
collections: Object.fromEntries(Object.entries(namedCollections()).map(([name, collection]) => [name, {
|
|
227
|
+
base: collection.base,
|
|
228
|
+
prefix: collection.prefix,
|
|
229
|
+
pageIds: pageIds(collection)
|
|
230
|
+
}]))
|
|
228
231
|
})};` : void 0
|
|
229
232
|
}, {
|
|
230
233
|
name: "minidocs:renderers",
|
|
231
234
|
resolveId: (id) => id === RENDERERS_ID ? `\0${RENDERERS_ID}` : void 0,
|
|
232
|
-
load: (id) => id === `\0${RENDERERS_ID}` ? hasMdx ? `import { getContainerRenderer } from '@astrojs/mdx';\nexport default [getContainerRenderer()];` : "export default [];" : void 0
|
|
235
|
+
load: (id) => id === `\0${RENDERERS_ID}` ? hasMdx ? `import { getContainerRenderer } from '@astrojs/mdx/container-renderer';\nexport default [getContainerRenderer()];` : "export default [];" : void 0
|
|
233
236
|
}],
|
|
234
237
|
ssr: { noExternal: ["@minidocs/astro", "@minidocs/core"] }
|
|
235
238
|
} });
|
|
@@ -238,6 +241,7 @@ function minidocs(options = {}) {
|
|
|
238
241
|
registry.settings = resolveSettings(config, options);
|
|
239
242
|
registry.root = fileURLToPath(config.root);
|
|
240
243
|
hasMdx = config.integrations.some(({ name }) => name === "@astrojs/mdx");
|
|
244
|
+
isStatic = config.output === "static" && !config.adapter;
|
|
241
245
|
const injected = injectTypes({
|
|
242
246
|
filename: "types.d.ts",
|
|
243
247
|
content: renderConfigTypes(registry.settings)
|
|
@@ -248,15 +252,24 @@ function minidocs(options = {}) {
|
|
|
248
252
|
mkdirSync(dirname(path), { recursive: true });
|
|
249
253
|
writeFileSync(path, "export {};\n");
|
|
250
254
|
}
|
|
255
|
+
},
|
|
256
|
+
"astro:build:done"({ pages }) {
|
|
257
|
+
if (!isStatic || !registry.settings) return;
|
|
258
|
+
const built = new Set(pages.map(({ pathname }) => pathname.replace(/^\/+|\/+$/g, "")));
|
|
259
|
+
const missing = findUnbuiltPages(registry.settings, namedCollections(), built);
|
|
260
|
+
if (missing.length === 0) return;
|
|
261
|
+
const shown = missing.slice(0, 10).map((page) => ` ${page}`);
|
|
262
|
+
if (missing.length > shown.length) shown.push(` and ${missing.length - shown.length} more`);
|
|
263
|
+
throw new Error(`[minidocs] No route built these pages, so links to them are broken. Check that each collection's route file sits in the folder its prefix names.\n${shown.join("\n")}`);
|
|
251
264
|
}
|
|
252
265
|
}
|
|
253
266
|
};
|
|
254
267
|
}
|
|
255
268
|
/** Ids of every page in a collection, in any locale. */
|
|
256
|
-
function pageIds(
|
|
269
|
+
function pageIds(collection) {
|
|
257
270
|
if (!registry.settings) return [];
|
|
258
|
-
const { locales, localeDirs } = contentOptions(registry.settings, base);
|
|
259
|
-
return [...new Set(filePaths.map((filePath) => parseFilePath(filePath, base, locales, localeDirs).id))];
|
|
271
|
+
const { locales, localeDirs } = contentOptions(registry.settings, collection.base);
|
|
272
|
+
return [...new Set(collection.filePaths.map((filePath) => parseFilePath(filePath, collection.base, locales, localeDirs).id))];
|
|
260
273
|
}
|
|
261
274
|
//#endregion
|
|
262
275
|
export { minidocs as default };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as href, t as contentOptions } from "./routing-
|
|
1
|
+
import { i as markdownHref, n as href, o as routePath, t as contentOptions } from "./routing-D28b92w8.mjs";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { dirname, posix, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -8,6 +8,14 @@ const union = (values) => {
|
|
|
8
8
|
const list = [...new Set(values)].sort().map((value) => JSON.stringify(value));
|
|
9
9
|
return list.length > 0 ? list.join(" | ") : "never";
|
|
10
10
|
};
|
|
11
|
+
const byName = (collections) => Object.entries(collections).sort(([a], [b]) => a < b ? -1 : 1);
|
|
12
|
+
/** Every page a collection needs a route for, with the locale its URL is built with. */
|
|
13
|
+
function pagesOf(settings, collection) {
|
|
14
|
+
return listPages(collection.filePaths.map((filePath) => ({ filePath })), contentOptions(settings, collection.base)).map((page) => ({
|
|
15
|
+
...page,
|
|
16
|
+
urlLocale: settings.i18n ? page.locale : void 0
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
11
19
|
/** Renders the site-wide types, known as soon as the config is resolved. */
|
|
12
20
|
function renderConfigTypes(settings) {
|
|
13
21
|
return [
|
|
@@ -27,14 +35,14 @@ function renderTypes(settings, collections) {
|
|
|
27
35
|
"declare module '@minidocs/astro/runtime' {",
|
|
28
36
|
" interface Collections {"
|
|
29
37
|
];
|
|
30
|
-
for (const [name,
|
|
31
|
-
const options = contentOptions(settings, base);
|
|
32
|
-
const pages =
|
|
38
|
+
for (const [name, collection] of byName(collections)) {
|
|
39
|
+
const options = contentOptions(settings, collection.base);
|
|
40
|
+
const pages = pagesOf(settings, collection);
|
|
33
41
|
const idsAndDirs = (locale) => {
|
|
34
42
|
const inLocale = pages.filter((page) => page.locale === locale);
|
|
35
43
|
const dirs = /* @__PURE__ */ new Set([""]);
|
|
36
44
|
for (const page of inLocale) {
|
|
37
|
-
const { dir } = parseFilePath(page.entry.filePath, base, options.locales, options.localeDirs);
|
|
45
|
+
const { dir } = parseFilePath(page.entry.filePath, collection.base, options.locales, options.localeDirs);
|
|
38
46
|
const segments = dir.split("/").filter(Boolean);
|
|
39
47
|
for (let i = 1; i <= segments.length; i++) dirs.add(segments.slice(0, i).join("/"));
|
|
40
48
|
}
|
|
@@ -45,7 +53,7 @@ function renderTypes(settings, collections) {
|
|
|
45
53
|
};
|
|
46
54
|
lines.push(`\t\t${JSON.stringify(name)}: {`);
|
|
47
55
|
if (settings.i18n) {
|
|
48
|
-
const perLocale =
|
|
56
|
+
const perLocale = options.locales.map((locale) => [JSON.stringify(locale), idsAndDirs(locale)]);
|
|
49
57
|
lines.push(" pages: {", ...perLocale.map(([locale, { ids }]) => `\t\t\t\t${locale}: ${ids};`), " };");
|
|
50
58
|
lines.push(" dirs: {", ...perLocale.map(([locale, { dirs }]) => `\t\t\t\t${locale}: ${dirs};`), " };");
|
|
51
59
|
} else {
|
|
@@ -57,25 +65,66 @@ function renderTypes(settings, collections) {
|
|
|
57
65
|
lines.push(" }", "}", "export {};", "");
|
|
58
66
|
return lines.join("\n");
|
|
59
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Finds pages that would be served at the same URL, across all collections, for HTML pages and Markdown versions alike.
|
|
70
|
+
* Returns one message per pair of files.
|
|
71
|
+
*/
|
|
72
|
+
function findUrlConflicts(settings, collections) {
|
|
73
|
+
const owners = /* @__PURE__ */ new Map();
|
|
74
|
+
const conflicts = /* @__PURE__ */ new Map();
|
|
75
|
+
for (const [name, collection] of byName(collections)) for (const page of pagesOf(settings, collection)) {
|
|
76
|
+
const owner = `${page.entry.filePath} (${name})`;
|
|
77
|
+
const urls = [href(settings, collection, page.id, page.urlLocale), markdownHref(settings, collection, page.id, page.urlLocale)];
|
|
78
|
+
for (const url of urls) {
|
|
79
|
+
const other = owners.get(url);
|
|
80
|
+
if (other === void 0) owners.set(url, owner);
|
|
81
|
+
else if (!conflicts.has(`${other}\n${owner}`)) conflicts.set(`${other}\n${owner}`, `"${url}" is the URL of two pages:\n ${other}\n ${owner}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return [...conflicts.values()];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Finds pages that no route generated, given the paths of built pages below `base`.
|
|
88
|
+
* A route file outside the folder that `prefix` names builds its pages elsewhere, and every link to them breaks.
|
|
89
|
+
*/
|
|
90
|
+
function findUnbuiltPages(settings, collections, built) {
|
|
91
|
+
const missing = [];
|
|
92
|
+
for (const [name, collection] of byName(collections)) for (const page of pagesOf(settings, collection)) {
|
|
93
|
+
const path = routePath(settings, collection, page.id, page.urlLocale) ?? "";
|
|
94
|
+
if (!built.has(path)) missing.push(`/${path} for ${page.entry.filePath} (${name}, prefix "${collection.prefix}")`);
|
|
95
|
+
}
|
|
96
|
+
return missing;
|
|
97
|
+
}
|
|
60
98
|
//#endregion
|
|
61
99
|
//#region src/registry.ts
|
|
62
100
|
const key = Symbol.for("minidocs.registry");
|
|
63
101
|
const scope = globalThis;
|
|
64
|
-
scope[key] ??= {
|
|
102
|
+
scope[key] ??= {
|
|
103
|
+
collections: {},
|
|
104
|
+
pendingLinks: []
|
|
105
|
+
};
|
|
65
106
|
const registry = scope[key];
|
|
107
|
+
/** Collections whose loader has run, keyed by collection name. */
|
|
108
|
+
function namedCollections() {
|
|
109
|
+
return Object.fromEntries(Object.values(registry.collections).filter((state) => state.name !== void 0).map((state) => [state.name, state]));
|
|
110
|
+
}
|
|
66
111
|
//#endregion
|
|
67
112
|
//#region src/links.ts
|
|
68
113
|
/** Link to another page from a page in the same locale, relative so it stays in whatever locale the page is shown in. */
|
|
69
|
-
function relativeHref(settings,
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const fromDir =
|
|
73
|
-
if (
|
|
74
|
-
return posix.join(posix.relative(fromDir, posix.dirname(
|
|
114
|
+
function relativeHref(settings, from, to, locale) {
|
|
115
|
+
const fromHref = href(settings, from.collection, from.id, locale);
|
|
116
|
+
const toHref = href(settings, to.collection, to.id, locale);
|
|
117
|
+
const fromDir = fromHref.slice(0, fromHref.lastIndexOf("/") + 1);
|
|
118
|
+
if (toHref.endsWith("/")) return `${posix.relative(fromDir, toHref) || "."}/`;
|
|
119
|
+
return posix.join(posix.relative(fromDir, posix.dirname(toHref)), posix.basename(toHref));
|
|
120
|
+
}
|
|
121
|
+
/** The collection whose root holds a project-relative file, the innermost one if roots are nested. */
|
|
122
|
+
function collectionOf(collections, file) {
|
|
123
|
+
return collections.filter(({ base }) => file.startsWith(`${base}/`)).reduce((best, collection) => best && best.base.length > collection.base.length ? best : collection, void 0);
|
|
75
124
|
}
|
|
76
125
|
const EXCLUDED = "points to a page that the collection's filter excludes.";
|
|
77
126
|
/**
|
|
78
|
-
* Rewrites a relative link to a content file into the URL of its page.
|
|
127
|
+
* Rewrites a relative link to a content file, in any collection, into the URL of its page.
|
|
79
128
|
* Links within one locale become relative URLs, so pages shown through fallback keep linking within the shown locale.
|
|
80
129
|
* Returns `undefined` for links that are not to `.md` or `.mdx` files. Throws when the target does not exist.
|
|
81
130
|
*/
|
|
@@ -85,13 +134,23 @@ function resolveContentLink(settings, source, url, root) {
|
|
|
85
134
|
const [, path, hash = ""] = match;
|
|
86
135
|
const absolute = resolve(root, dirname(source.file), decodeURI(path));
|
|
87
136
|
const target = relative(root, absolute).split(sep).join("/");
|
|
88
|
-
|
|
137
|
+
const collection = collectionOf(source.collections, target);
|
|
138
|
+
if (!collection) throw new Error(`"${url}" points outside every collection loaded with minidocs' glob, so it has no page.`);
|
|
89
139
|
if (!existsSync(absolute)) throw new Error(`"${url}" points to a file that does not exist.`);
|
|
90
|
-
if (source.isExcluded?.(target)) throw new Error(`"${url}" ${EXCLUDED}`);
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
140
|
+
if (source.isExcluded?.(target, collection)) throw new Error(`"${url}" ${EXCLUDED}`);
|
|
141
|
+
const parse = (file, of) => {
|
|
142
|
+
const { locales, localeDirs } = contentOptions(settings, of.base);
|
|
143
|
+
return parseFilePath(file, of.base, locales, localeDirs);
|
|
144
|
+
};
|
|
145
|
+
const from = parse(source.file, source.collection);
|
|
146
|
+
const to = parse(target, collection);
|
|
147
|
+
return (from.locale === to.locale ? relativeHref(settings, {
|
|
148
|
+
collection: source.collection,
|
|
149
|
+
id: from.id
|
|
150
|
+
}, {
|
|
151
|
+
collection,
|
|
152
|
+
id: to.id
|
|
153
|
+
}, settings.i18n ? from.locale : void 0) : href(settings, collection, to.id, to.locale)) + hash;
|
|
95
154
|
}
|
|
96
155
|
/**
|
|
97
156
|
* Resolves a link found in a Markdown file, or returns `undefined` to leave it alone.
|
|
@@ -101,30 +160,36 @@ function rewrite(filePath, url) {
|
|
|
101
160
|
const { settings, root } = registry;
|
|
102
161
|
if (!settings || !root) return void 0;
|
|
103
162
|
const file = relative(root, filePath).split(sep).join("/");
|
|
104
|
-
const
|
|
163
|
+
const collections = Object.values(registry.collections);
|
|
164
|
+
const collection = collectionOf(collections, file);
|
|
105
165
|
if (!collection) return void 0;
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
166
|
+
const ready = (state) => state.loaded && !state.loading;
|
|
167
|
+
const isExcluded = (target, to) => {
|
|
168
|
+
if (!ready(collection) || !ready(to)) {
|
|
169
|
+
registry.pendingLinks.push({
|
|
109
170
|
file,
|
|
110
171
|
url,
|
|
111
|
-
target
|
|
172
|
+
target,
|
|
173
|
+
source: collection,
|
|
174
|
+
to
|
|
112
175
|
});
|
|
113
176
|
return false;
|
|
114
177
|
}
|
|
115
|
-
return
|
|
178
|
+
return to.excluded.has(target);
|
|
116
179
|
};
|
|
117
180
|
return resolveContentLink(settings, {
|
|
118
181
|
file,
|
|
119
|
-
|
|
182
|
+
collection,
|
|
183
|
+
collections,
|
|
120
184
|
isExcluded
|
|
121
185
|
}, url, root);
|
|
122
186
|
}
|
|
123
|
-
/** Checks links
|
|
124
|
-
function checkPendingLinks(
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
187
|
+
/** Checks links rendered while a collection involved was loading, once both collections have loaded. */
|
|
188
|
+
function checkPendingLinks() {
|
|
189
|
+
const ready = (state) => state.loaded && !state.loading;
|
|
190
|
+
const due = registry.pendingLinks.filter(({ source, to }) => ready(source) && ready(to));
|
|
191
|
+
registry.pendingLinks = registry.pendingLinks.filter((link) => !due.includes(link));
|
|
192
|
+
const broken = due.filter(({ file, target, source, to }) => !source.excluded.has(file) && to.excluded.has(target));
|
|
128
193
|
if (broken.length === 0) return;
|
|
129
194
|
const root = registry.root ?? "";
|
|
130
195
|
throw new Error(broken.map(({ file, url }) => failure(resolve(root, file), url, /* @__PURE__ */ new Error(`"${url}" ${EXCLUDED}`)).message).join("\n"));
|
|
@@ -174,4 +239,4 @@ function remarkContentLinks() {
|
|
|
174
239
|
};
|
|
175
240
|
}
|
|
176
241
|
//#endregion
|
|
177
|
-
export {
|
|
242
|
+
export { registry as a, renderConfigTypes as c, namedCollections as i, renderTypes as l, remarkContentLinks as n, findUnbuiltPages as o, satteriContentLinks as r, findUrlConflicts as s, checkPendingLinks as t };
|
package/dist/loaders.d.mts
CHANGED
|
@@ -7,14 +7,19 @@ export interface FilterEntry {
|
|
|
7
7
|
data: Record<string, unknown>;
|
|
8
8
|
filePath?: string | undefined;
|
|
9
9
|
}
|
|
10
|
+
export interface MinidocsGlobOptions {
|
|
11
|
+
/** Path segment the pages live under, e.g. `docs` for `/docs/...`. Defaults to the site root. */
|
|
12
|
+
prefix?: string;
|
|
13
|
+
/** Entries for which it returns `false` never reach the collection. */
|
|
14
|
+
filter?: (entry: FilterEntry) => boolean;
|
|
15
|
+
}
|
|
10
16
|
/**
|
|
11
|
-
* Astro's `glob()` loader, plus recording of the collection root so minidocs can
|
|
12
|
-
* derive locales
|
|
17
|
+
* Astro's `glob()` loader, plus recording of the collection root and URL prefix so minidocs can
|
|
18
|
+
* derive locales, page ids and URLs, and generate types for them.
|
|
13
19
|
* Entries rejected by `filter` never reach the collection, so types, links and pages all agree on what exists.
|
|
14
20
|
*/
|
|
15
|
-
export declare function glob({ filter, ...options }: GlobOptions & {
|
|
21
|
+
export declare function glob({ prefix, filter, ...options }: GlobOptions & {
|
|
16
22
|
base: string | URL;
|
|
17
|
-
|
|
18
|
-
}): Loader;
|
|
23
|
+
} & MinidocsGlobOptions): Loader;
|
|
19
24
|
//#endregion
|
|
20
25
|
export {};
|
package/dist/loaders.mjs
CHANGED
|
@@ -1,36 +1,43 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as registry, i as namedCollections, l as renderTypes, s as findUrlConflicts, t as checkPendingLinks } from "./links-Dp7d5R-O.mjs";
|
|
2
2
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { dirname, relative } from "node:path";
|
|
3
|
+
import { dirname, relative, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { glob as glob$1 } from "astro/loaders";
|
|
6
6
|
//#region src/loaders.ts
|
|
7
|
+
/** Collection root relative to the project, as Astro's glob resolves `base`. */
|
|
8
|
+
const relativeBase = (base, root) => relative(root, base instanceof URL ? fileURLToPath(base) : resolve(root, base)).replaceAll("\\", "/");
|
|
9
|
+
function register(base, declared) {
|
|
10
|
+
registry.collections[base] ??= {
|
|
11
|
+
base,
|
|
12
|
+
...declared,
|
|
13
|
+
filePaths: [],
|
|
14
|
+
excluded: /* @__PURE__ */ new Set(),
|
|
15
|
+
loading: false,
|
|
16
|
+
loaded: false
|
|
17
|
+
};
|
|
18
|
+
return Object.assign(registry.collections[base], declared);
|
|
19
|
+
}
|
|
7
20
|
/**
|
|
8
|
-
* Astro's `glob()` loader, plus recording of the collection root so minidocs can
|
|
9
|
-
* derive locales
|
|
21
|
+
* Astro's `glob()` loader, plus recording of the collection root and URL prefix so minidocs can
|
|
22
|
+
* derive locales, page ids and URLs, and generate types for them.
|
|
10
23
|
* Entries rejected by `filter` never reach the collection, so types, links and pages all agree on what exists.
|
|
11
24
|
*/
|
|
12
|
-
function glob({ filter, ...options }) {
|
|
25
|
+
function glob({ prefix = "", filter, ...options }) {
|
|
13
26
|
const inner = glob$1(options);
|
|
27
|
+
const declared = { prefix: prefix.replace(/^\/+|\/+$/g, "") };
|
|
28
|
+
if (registry.root) register(relativeBase(options.base, registry.root), declared);
|
|
14
29
|
return {
|
|
15
30
|
...inner,
|
|
16
31
|
name: "minidocs-glob",
|
|
17
32
|
async load(context) {
|
|
18
|
-
const
|
|
19
|
-
|
|
33
|
+
const state = register(relativeBase(options.base, fileURLToPath(context.config.root)), declared);
|
|
34
|
+
if (state.name !== void 0 && state.name !== context.collection) throw new Error(`[minidocs] Collections "${state.name}" and "${context.collection}" are both loaded from ${state.base}.`);
|
|
35
|
+
state.name = context.collection;
|
|
20
36
|
let scheduled;
|
|
21
37
|
const update = () => {
|
|
22
38
|
clearTimeout(scheduled);
|
|
23
|
-
scheduled = setTimeout(() => writeTypes(context), 50);
|
|
24
|
-
};
|
|
25
|
-
registry.collections[context.collection] ??= {
|
|
26
|
-
base,
|
|
27
|
-
filePaths: [],
|
|
28
|
-
excluded: /* @__PURE__ */ new Set(),
|
|
29
|
-
loading: false,
|
|
30
|
-
pendingLinks: []
|
|
39
|
+
scheduled = setTimeout(() => writeTypes(context, state, (message) => context.logger.error(message)), 50);
|
|
31
40
|
};
|
|
32
|
-
const state = registry.collections[context.collection];
|
|
33
|
-
state.base = base;
|
|
34
41
|
const store = new Proxy(context.store, { get(target, property, receiver) {
|
|
35
42
|
const value = Reflect.get(target, property, receiver);
|
|
36
43
|
if (property !== "set" && property !== "delete" && property !== "clear") return value;
|
|
@@ -57,20 +64,25 @@ function glob({ filter, ...options }) {
|
|
|
57
64
|
} finally {
|
|
58
65
|
state.loading = false;
|
|
59
66
|
}
|
|
67
|
+
state.loaded = true;
|
|
60
68
|
clearTimeout(scheduled);
|
|
61
|
-
writeTypes(context)
|
|
62
|
-
|
|
69
|
+
writeTypes(context, state, (message) => {
|
|
70
|
+
throw new Error(message);
|
|
71
|
+
});
|
|
72
|
+
checkPendingLinks();
|
|
63
73
|
}
|
|
64
74
|
};
|
|
65
75
|
}
|
|
66
|
-
function writeTypes(context) {
|
|
76
|
+
function writeTypes(context, state, report) {
|
|
67
77
|
const { settings, typesFile } = registry;
|
|
68
78
|
if (!settings || !typesFile) throw new Error("[minidocs] Add the minidocs() integration to your Astro config to use glob from '@minidocs/astro/loaders'.");
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
state.filePaths = context.store.values().map((entry) => entry.filePath).filter((filePath) => Boolean(filePath));
|
|
80
|
+
const collections = namedCollections();
|
|
71
81
|
const path = fileURLToPath(typesFile);
|
|
72
82
|
mkdirSync(dirname(path), { recursive: true });
|
|
73
|
-
writeFileSync(path, renderTypes(settings,
|
|
83
|
+
writeFileSync(path, renderTypes(settings, collections));
|
|
84
|
+
const conflicts = findUrlConflicts(settings, collections);
|
|
85
|
+
if (conflicts.length > 0) report(conflicts.map((conflict) => `[minidocs] ${conflict}`).join("\n"));
|
|
74
86
|
}
|
|
75
87
|
//#endregion
|
|
76
88
|
export { glob };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//#region src/routing.ts
|
|
2
|
+
/** The locale every page of a single-language site belongs to inside core. Never exposed to users. */
|
|
3
|
+
const SINGLE_LOCALE = "default";
|
|
4
|
+
/** Options for core: locale directories for multilingual sites, one implicit locale otherwise. */
|
|
5
|
+
function contentOptions(settings, base) {
|
|
6
|
+
return settings.i18n ? {
|
|
7
|
+
base,
|
|
8
|
+
locales: settings.i18n.locales,
|
|
9
|
+
fallback: settings.i18n.fallback
|
|
10
|
+
} : {
|
|
11
|
+
base,
|
|
12
|
+
locales: [SINGLE_LOCALE],
|
|
13
|
+
localeDirs: false
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** The URL segment of a page id: the root index page has none. */
|
|
17
|
+
function slugOf(id) {
|
|
18
|
+
return id === "index" ? "" : id;
|
|
19
|
+
}
|
|
20
|
+
const normalizeLocale = (locale) => locale.replaceAll("_", "-").toLowerCase();
|
|
21
|
+
/** The URL segment of a locale, `undefined` for the default locale when it is not prefixed, as rest parameters expect. */
|
|
22
|
+
function localeParam(settings, locale) {
|
|
23
|
+
const { i18n } = settings;
|
|
24
|
+
if (!i18n || locale === void 0 || locale === i18n.defaultLocale && !i18n.prefixDefaultLocale) return void 0;
|
|
25
|
+
return normalizeLocale(locale);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Path of a page below `base`, e.g. `en/docs/getting-started`, without slashes around it.
|
|
29
|
+
* `undefined` stands for the site root.
|
|
30
|
+
*/
|
|
31
|
+
function routePath(settings, collection, id, locale) {
|
|
32
|
+
return [
|
|
33
|
+
localeParam(settings, locale),
|
|
34
|
+
collection.prefix,
|
|
35
|
+
slugOf(id)
|
|
36
|
+
].filter(Boolean).join("/") || void 0;
|
|
37
|
+
}
|
|
38
|
+
/** Link to a page, including `base` and the trailing slash Astro would use. */
|
|
39
|
+
function href(settings, collection, id, locale) {
|
|
40
|
+
const path = [settings.base, routePath(settings, collection, id, locale)].filter(Boolean).join("/");
|
|
41
|
+
if (!path) return "/";
|
|
42
|
+
return `/${path}${settings.trailingSlash ? "/" : ""}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Path of the Markdown version of a page below the locale segment, e.g. `docs/getting-started`.
|
|
46
|
+
* The root of a site without a prefix has no segment of its own, so it becomes `index`.
|
|
47
|
+
*/
|
|
48
|
+
function markdownPath(collection, id) {
|
|
49
|
+
return [collection.prefix, slugOf(id)].filter(Boolean).join("/") || "index";
|
|
50
|
+
}
|
|
51
|
+
/** Link to the Markdown version of a page, including `base`. */
|
|
52
|
+
function markdownHref(settings, collection, id, locale) {
|
|
53
|
+
return `/${[
|
|
54
|
+
settings.base,
|
|
55
|
+
localeParam(settings, locale),
|
|
56
|
+
markdownPath(collection, id)
|
|
57
|
+
].filter(Boolean).join("/")}.md`;
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { markdownPath as a, markdownHref as i, href as n, routePath as o, localeParam as r, slugOf as s, contentOptions as t };
|
package/dist/runtime.d.mts
CHANGED
|
@@ -20,6 +20,8 @@ type IsI18n = Config extends {
|
|
|
20
20
|
export type Locale = Config extends {
|
|
21
21
|
locale: infer L extends string;
|
|
22
22
|
} ? L : string;
|
|
23
|
+
/** Names of the collections loaded with minidocs' glob, or any string before their types are generated. */
|
|
24
|
+
export type CollectionName = [keyof Collections] extends [never] ? string : Extract<keyof Collections, string>;
|
|
23
25
|
type Known<C extends string> = C extends keyof Collections ? Collections[C] : never;
|
|
24
26
|
type Lookup<T, L> = T extends string ? T : L extends keyof T ? Extract<T[L], string> : never;
|
|
25
27
|
type Field<C extends string, K extends string, L> = [Known<C>] extends [never] ? string : Known<C> extends Record<K, infer T> ? Lookup<T, L> : string;
|
|
@@ -73,15 +75,29 @@ export type DocTree<E extends DocEntry> = IsI18n extends true ? Record<Locale, D
|
|
|
73
75
|
interface PageInfo<E extends DocEntry> {
|
|
74
76
|
id: Id<E>;
|
|
75
77
|
entry: E;
|
|
76
|
-
/** Route
|
|
77
|
-
|
|
78
|
+
/** Route parameters for `src/pages/docs/[...slug].astro`. `slug` is `undefined` for the root page of the collection. */
|
|
79
|
+
params: {
|
|
80
|
+
slug: string | undefined;
|
|
81
|
+
};
|
|
78
82
|
href: string;
|
|
79
|
-
/** Route
|
|
80
|
-
|
|
83
|
+
/** Route parameters for `src/pages/[...path].md.ts`, which serves the Markdown versions of every collection. */
|
|
84
|
+
markdownParams: {
|
|
85
|
+
path: string;
|
|
86
|
+
};
|
|
81
87
|
/** Link to the Markdown version of the page. */
|
|
82
88
|
markdownHref: string;
|
|
83
89
|
}
|
|
84
90
|
interface LocalizedPageInfo<E extends DocEntry> extends PageInfo<E> {
|
|
91
|
+
/** Route parameters for `src/pages/[...locale]/docs/[...slug].astro`. `locale` is `undefined` for the default locale when it is not prefixed. */
|
|
92
|
+
params: {
|
|
93
|
+
locale: string | undefined;
|
|
94
|
+
slug: string | undefined;
|
|
95
|
+
};
|
|
96
|
+
/** Route parameters for `src/pages/[...locale]/[...path].md.ts`, which serves the Markdown versions of every collection. */
|
|
97
|
+
markdownParams: {
|
|
98
|
+
locale: string | undefined;
|
|
99
|
+
path: string;
|
|
100
|
+
};
|
|
85
101
|
/** Locale the page is shown in. */
|
|
86
102
|
locale: Locale;
|
|
87
103
|
/** Locale the content comes from. Differs from `locale` only for fallback pages. */
|
|
@@ -97,10 +113,13 @@ export type DocPage<E extends DocEntry> = IsI18n extends true ? LocalizedPageInf
|
|
|
97
113
|
type LocaleArgs = IsI18n extends true ? [locale: Locale] : [];
|
|
98
114
|
/** Builds the navigation tree. Multilingual sites get one tree per locale, built by running `build` once for each. */
|
|
99
115
|
export declare function defineTree<E extends DocEntry>(entries: readonly E[], build: (context: BuildContext<E>) => DocNode<E>[] | Promise<DocNode<E>[]>): Promise<DocTree<E>>;
|
|
100
|
-
/** Link to a page, including `base` and the trailing slash Astro would use. */
|
|
101
|
-
export declare function toHref(
|
|
102
|
-
/**
|
|
103
|
-
|
|
116
|
+
/** Link to a page, including `base` and the trailing slash Astro would use. Multilingual sites take a locale. */
|
|
117
|
+
export declare function toHref<C extends CollectionName>(collection: C, id: PageIdOf<C>, ...locale: LocaleArgs): string;
|
|
118
|
+
/**
|
|
119
|
+
* The `[...locale]` route parameter of a locale, for the site's own pages such as a home page or a 404 page.
|
|
120
|
+
* `undefined` for the default locale when it is not prefixed.
|
|
121
|
+
*/
|
|
122
|
+
export declare function toLocaleParam(locale: Locale): string | undefined;
|
|
104
123
|
/** Whether a component renders for the Markdown version of a page, where it should output plain HTML that converts cleanly. */
|
|
105
124
|
export declare function isMarkdown(astro: {
|
|
106
125
|
locals: object;
|
|
@@ -122,23 +141,46 @@ export type MarkdownPage<E extends DocEntry> = IsI18n extends true ? {
|
|
|
122
141
|
* Renders the content of a page and converts it into Markdown for agents, without a title.
|
|
123
142
|
* Components can check `isMarkdown(Astro)` to render a form that converts cleanly.
|
|
124
143
|
*/
|
|
125
|
-
export declare function toMarkdown
|
|
144
|
+
export declare function toMarkdown(page: MarkdownPage<DocEntry>, options?: MarkdownOptions): Promise<string>;
|
|
145
|
+
/** A node of a tree from any collection, so trees of several collections can be joined into one file. */
|
|
146
|
+
interface TreeNode {
|
|
147
|
+
label: string;
|
|
148
|
+
page?: {
|
|
149
|
+
id: string;
|
|
150
|
+
entry: DocEntry;
|
|
151
|
+
} | undefined;
|
|
152
|
+
href?: string | undefined;
|
|
153
|
+
children: readonly TreeNode[];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Entries of every collection found in a tree, which may join trees of several collections.
|
|
157
|
+
* A tree node is structurally the same as a group wrapping it, so the walk stops at a fixed depth instead.
|
|
158
|
+
*/
|
|
159
|
+
type EntryOf<N, Depth extends unknown[] = []> = Depth['length'] extends 4 ? never : N extends {
|
|
160
|
+
page?: infer P;
|
|
161
|
+
children: readonly (infer C)[];
|
|
162
|
+
} ? (P extends {
|
|
163
|
+
entry: infer E;
|
|
164
|
+
} ? E : never) | EntryOf<C, [...Depth, unknown]> : never;
|
|
126
165
|
/**
|
|
127
166
|
* Renders a tree as llms.txt. Top-level pages come first, every top-level node with children becomes a section,
|
|
128
167
|
* and links point to the Markdown versions of pages. The `index` page's description becomes the summary.
|
|
129
168
|
*/
|
|
130
|
-
export declare function toLlmsTxt<
|
|
169
|
+
export declare function toLlmsTxt<N extends TreeNode>(nodes: readonly N[], options: LlmsTxtOptions<EntryOf<N>>): string;
|
|
131
170
|
/**
|
|
132
171
|
* Every page of a tree in reading order, as Markdown under its title and source URL, for llms-full.txt.
|
|
133
172
|
* Like llms.txt, links and images are full URLs when `site` is set.
|
|
134
173
|
*/
|
|
135
|
-
export declare function toLlmsFullTxt<
|
|
174
|
+
export declare function toLlmsFullTxt<N extends TreeNode>(nodes: readonly N[], options?: MarkdownOptions): Promise<string>;
|
|
136
175
|
/** Every page that needs a route. Multilingual sites include every locale and untranslated pages served through fallback. */
|
|
137
176
|
export declare function listPages<E extends DocEntry>(entries: readonly E[]): DocPage<E>[];
|
|
138
177
|
interface NavNode {
|
|
139
178
|
page?: {
|
|
140
179
|
id: string;
|
|
141
180
|
locale?: string;
|
|
181
|
+
entry: {
|
|
182
|
+
collection: string;
|
|
183
|
+
};
|
|
142
184
|
} | undefined;
|
|
143
185
|
href?: string | undefined;
|
|
144
186
|
children: NavNode[];
|
package/dist/runtime.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as markdownPath, i as markdownHref, n as href, r as localeParam, s as slugOf, t as contentOptions } from "./routing-D28b92w8.mjs";
|
|
2
2
|
import { MissingPageError, createTree, findPath, listPages as listPages$1, navState as navState$1, neighbors, tocTree } from "@minidocs/core";
|
|
3
3
|
import config from "virtual:minidocs/config";
|
|
4
4
|
import { fromHtml } from "hast-util-from-html";
|
|
@@ -8,11 +8,12 @@ import { toMarkdown as toMarkdown$1 } from "mdast-util-to-markdown";
|
|
|
8
8
|
//#region src/llms.ts
|
|
9
9
|
const escapeLabel = (text) => text.replace(/[[\]\\]/g, "\\$&");
|
|
10
10
|
const oneLine = (text) => text.replace(/\s+/g, " ").trim();
|
|
11
|
-
/** Pages of a tree in reading order, each page once. */
|
|
11
|
+
/** Pages of a tree in reading order, each page once. Trees of several collections can be joined, so a page is its collection and id. */
|
|
12
12
|
function pagesInOrder(nodes) {
|
|
13
13
|
const pages = /* @__PURE__ */ new Map();
|
|
14
14
|
const visit = (node) => {
|
|
15
|
-
|
|
15
|
+
const key = node.page && `${node.page.entry.collection}\0${node.page.id}`;
|
|
16
|
+
if (key && !pages.has(key)) pages.set(key, node.page);
|
|
16
17
|
node.children.forEach(visit);
|
|
17
18
|
};
|
|
18
19
|
nodes.forEach(visit);
|
|
@@ -149,13 +150,16 @@ function htmlToMarkdown(html, context) {
|
|
|
149
150
|
}
|
|
150
151
|
//#endregion
|
|
151
152
|
//#region src/runtime.ts
|
|
152
|
-
function
|
|
153
|
-
const collection =
|
|
154
|
-
if (!collection) throw new Error(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
153
|
+
function collectionNamed(name) {
|
|
154
|
+
const collection = config.collections[name];
|
|
155
|
+
if (!collection) throw new Error(`[minidocs] Collection "${name}" must be loaded with glob from '@minidocs/astro/loaders'.`);
|
|
156
|
+
return collection;
|
|
157
|
+
}
|
|
158
|
+
function collectionOf(entries) {
|
|
159
|
+
const name = entries[0]?.collection;
|
|
160
|
+
if (!name) throw new Error("[minidocs] Expected at least one entry.");
|
|
161
|
+
if (entries.some((entry) => entry.collection !== name)) throw new Error("[minidocs] All entries must come from the same collection.");
|
|
162
|
+
return collectionNamed(name);
|
|
159
163
|
}
|
|
160
164
|
/** Drops locale details from nodes of single-language sites, where they carry no meaning. */
|
|
161
165
|
function withoutLocale(nodes) {
|
|
@@ -170,7 +174,8 @@ function withoutLocale(nodes) {
|
|
|
170
174
|
}
|
|
171
175
|
/** Builds the navigation tree. Multilingual sites get one tree per locale, built by running `build` once for each. */
|
|
172
176
|
async function defineTree(entries, build) {
|
|
173
|
-
const
|
|
177
|
+
const collection = collectionOf(entries);
|
|
178
|
+
const options = contentOptions(config, collection.base);
|
|
174
179
|
const run = build;
|
|
175
180
|
let tree;
|
|
176
181
|
try {
|
|
@@ -179,18 +184,22 @@ async function defineTree(entries, build) {
|
|
|
179
184
|
label: (entry) => entry.data.title
|
|
180
185
|
}, config.i18n ? run : ({ locale: _, ...helpers }) => run(helpers));
|
|
181
186
|
} catch (error) {
|
|
182
|
-
if (error instanceof MissingPageError &&
|
|
187
|
+
if (error instanceof MissingPageError && collection.pageIds.includes(error.id)) throw new Error(`[minidocs] Page "${error.id}" is in the collection but not among the entries passed to defineTree. To leave pages out, use the filter option of glob from '@minidocs/astro/loaders' instead of filtering the entries.`, { cause: error });
|
|
183
188
|
throw error;
|
|
184
189
|
}
|
|
185
190
|
return config.i18n ? tree : withoutLocale(tree[options.locales[0]]);
|
|
186
191
|
}
|
|
187
|
-
/** Link to a page, including `base` and the trailing slash Astro would use. */
|
|
188
|
-
function toHref(id, ...locale) {
|
|
189
|
-
return href(config, id, locale[0]);
|
|
192
|
+
/** Link to a page, including `base` and the trailing slash Astro would use. Multilingual sites take a locale. */
|
|
193
|
+
function toHref(collection, id, ...locale) {
|
|
194
|
+
return href(config, collectionNamed(collection), id, locale[0]);
|
|
190
195
|
}
|
|
191
|
-
/**
|
|
192
|
-
|
|
193
|
-
|
|
196
|
+
/**
|
|
197
|
+
* The `[...locale]` route parameter of a locale, for the site's own pages such as a home page or a 404 page.
|
|
198
|
+
* `undefined` for the default locale when it is not prefixed.
|
|
199
|
+
*/
|
|
200
|
+
function toLocaleParam(locale) {
|
|
201
|
+
if (!config.i18n) throw new Error("[minidocs] toLocaleParam requires `i18n` in your Astro config.");
|
|
202
|
+
return localeParam(config, locale);
|
|
194
203
|
}
|
|
195
204
|
const MARKDOWN = "minidocs:markdown";
|
|
196
205
|
/** Whether a component renders for the Markdown version of a page, where it should output plain HTML that converts cleanly. */
|
|
@@ -212,16 +221,17 @@ async function containerFor(extra) {
|
|
|
212
221
|
}
|
|
213
222
|
return container;
|
|
214
223
|
}
|
|
215
|
-
|
|
216
|
-
/** Markdown versions of every page
|
|
217
|
-
function
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
224
|
+
let markdownPages;
|
|
225
|
+
/** Markdown versions of every page on the site, keyed by the URL path of the HTML page, so links into any collection convert. */
|
|
226
|
+
function markdownPagesOnSite() {
|
|
227
|
+
if (!markdownPages) {
|
|
228
|
+
markdownPages = /* @__PURE__ */ new Map();
|
|
229
|
+
for (const collection of Object.values(config.collections)) {
|
|
230
|
+
const locales = config.i18n?.locales ?? [void 0];
|
|
231
|
+
for (const id of collection.pageIds) for (const locale of locales) markdownPages.set(pathKey(href(config, collection, id, locale)), markdownHref(config, collection, id, locale));
|
|
232
|
+
}
|
|
223
233
|
}
|
|
224
|
-
return
|
|
234
|
+
return markdownPages;
|
|
225
235
|
}
|
|
226
236
|
/** Full URL of a path when `site` is set, for text that is often pasted where paths do not resolve. */
|
|
227
237
|
const fullUrl = (path) => config.site ? new URL(path, config.site).href : path;
|
|
@@ -246,12 +256,12 @@ async function renderMarkdown(page, options, resolve) {
|
|
|
246
256
|
throw error;
|
|
247
257
|
}
|
|
248
258
|
return htmlToMarkdown(html, {
|
|
249
|
-
pageHref: href(config, id, locale),
|
|
250
|
-
markdownOf: (path) =>
|
|
259
|
+
pageHref: href(config, collectionNamed(entry.collection), id, locale),
|
|
260
|
+
markdownOf: (path) => markdownPagesOnSite().get(path),
|
|
251
261
|
resolve
|
|
252
262
|
});
|
|
253
263
|
}
|
|
254
|
-
const markdownUrl = ({ id, locale }) => fullUrl(markdownHref(config, id, locale));
|
|
264
|
+
const markdownUrl = ({ id, locale, entry }) => fullUrl(markdownHref(config, collectionNamed(entry.collection), id, locale));
|
|
255
265
|
/**
|
|
256
266
|
* Renders a tree as llms.txt. Top-level pages come first, every top-level node with children becomes a section,
|
|
257
267
|
* and links point to the Markdown versions of pages. The `index` page's description becomes the summary.
|
|
@@ -269,30 +279,37 @@ async function toLlmsFullTxt(nodes, options = {}) {
|
|
|
269
279
|
}
|
|
270
280
|
/** Every page that needs a route. Multilingual sites include every locale and untranslated pages served through fallback. */
|
|
271
281
|
function listPages(entries) {
|
|
272
|
-
const
|
|
282
|
+
const collection = collectionOf(entries);
|
|
283
|
+
const pages = listPages$1(entries, contentOptions(config, collection.base));
|
|
273
284
|
if (!config.i18n) return pages.map(({ id, entry }) => ({
|
|
274
285
|
id,
|
|
275
286
|
entry,
|
|
276
|
-
|
|
277
|
-
href: href(config, id),
|
|
278
|
-
|
|
279
|
-
markdownHref: markdownHref(config, id)
|
|
287
|
+
params: { slug: slugOf(id) || void 0 },
|
|
288
|
+
href: href(config, collection, id),
|
|
289
|
+
markdownParams: { path: markdownPath(collection, id) },
|
|
290
|
+
markdownHref: markdownHref(config, collection, id)
|
|
280
291
|
}));
|
|
281
292
|
const alternates = /* @__PURE__ */ new Map();
|
|
282
293
|
for (const page of pages) {
|
|
283
294
|
const list = alternates.get(page.id) ?? [];
|
|
284
295
|
list.push({
|
|
285
296
|
locale: page.locale,
|
|
286
|
-
href: href(config, page.id, page.locale)
|
|
297
|
+
href: href(config, collection, page.id, page.locale)
|
|
287
298
|
});
|
|
288
299
|
alternates.set(page.id, list);
|
|
289
300
|
}
|
|
290
301
|
return pages.map((page) => ({
|
|
291
302
|
...page,
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
303
|
+
params: {
|
|
304
|
+
locale: localeParam(config, page.locale),
|
|
305
|
+
slug: slugOf(page.id) || void 0
|
|
306
|
+
},
|
|
307
|
+
href: href(config, collection, page.id, page.locale),
|
|
308
|
+
markdownParams: {
|
|
309
|
+
locale: localeParam(config, page.locale),
|
|
310
|
+
path: markdownPath(collection, page.id)
|
|
311
|
+
},
|
|
312
|
+
markdownHref: markdownHref(config, collection, page.id, page.locale),
|
|
296
313
|
alternates: alternates.get(page.id)
|
|
297
314
|
}));
|
|
298
315
|
}
|
|
@@ -301,10 +318,10 @@ function navState(nodes, id) {
|
|
|
301
318
|
const state = navState$1(nodes, id);
|
|
302
319
|
return { of(node) {
|
|
303
320
|
return {
|
|
304
|
-
href: node.page ? href(config, node.page.id, node.page.locale) : node.href,
|
|
321
|
+
href: node.page ? href(config, collectionNamed(node.page.entry.collection), node.page.id, node.page.locale) : node.href,
|
|
305
322
|
...state.of(node)
|
|
306
323
|
};
|
|
307
324
|
} };
|
|
308
325
|
}
|
|
309
326
|
//#endregion
|
|
310
|
-
export { defineTree, findPath, isMarkdown, listPages, navState, neighbors, toHref, toLlmsFullTxt, toLlmsTxt,
|
|
327
|
+
export { defineTree, findPath, isMarkdown, listPages, navState, neighbors, toHref, toLlmsFullTxt, toLlmsTxt, toLocaleParam, toMarkdown, tocTree };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minidocs/astro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Astro integration for building documentation sites from composable primitives",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"mdast-util-gfm": "^3.1.0",
|
|
59
59
|
"mdast-util-to-markdown": "^2.2.0",
|
|
60
60
|
"remark-directive": "^4.0.0",
|
|
61
|
-
"@minidocs/core": "
|
|
61
|
+
"@minidocs/core": "0.2.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@types/hast": "^3.0.5",
|
package/src/styles/base.css
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
--md-color-text: light-dark(#3f3f46, #d4d4d8);
|
|
8
8
|
--md-color-muted: light-dark(#71717a, #a1a1aa);
|
|
9
9
|
--md-color-border: light-dark(#e4e4e7, #2e2e33);
|
|
10
|
-
|
|
10
|
+
/* Neutral by default, so a site brings its own color by setting this one variable */
|
|
11
|
+
--md-color-accent: var(--md-color-heading);
|
|
11
12
|
--md-color-hover: color-mix(in srgb, var(--md-color-heading) 5%, transparent);
|
|
12
13
|
--md-color-current: color-mix(in srgb, var(--md-color-accent) 9%, transparent);
|
|
13
14
|
--md-color-code-bg: light-dark(#f6f6f7, #202024);
|
package/src/styles/pager.css
CHANGED
|
@@ -9,24 +9,34 @@
|
|
|
9
9
|
border-block-start: 1px solid var(--md-color-border);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
/* Padding gives hover a background like the sidebar's, and margins on the outer side keep text aligned */
|
|
12
13
|
.md-pager-link {
|
|
13
14
|
display: flex;
|
|
14
15
|
flex-direction: column;
|
|
15
16
|
gap: 0.125rem;
|
|
17
|
+
margin-block: -0.5rem;
|
|
18
|
+
margin-inline: -0.75rem 0;
|
|
19
|
+
padding: 0.5rem 0.75rem;
|
|
20
|
+
border-radius: var(--md-radius);
|
|
16
21
|
color: var(--md-color-heading);
|
|
17
22
|
text-decoration: none;
|
|
23
|
+
transition: background-color 0.15s;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.md-pager-link:hover {
|
|
27
|
+
background: var(--md-color-hover);
|
|
18
28
|
}
|
|
19
29
|
|
|
20
30
|
/* The next page stays on the end side even when there is no previous one */
|
|
21
31
|
.md-pager-link[rel="next"] {
|
|
22
32
|
grid-column: 2;
|
|
33
|
+
margin-inline: 0 -0.75rem;
|
|
23
34
|
text-align: end;
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
.md-pager-link:focus-visible {
|
|
27
|
-
border-radius: 2px;
|
|
28
38
|
outline: var(--md-focus-ring);
|
|
29
|
-
outline-offset:
|
|
39
|
+
outline-offset: -2px;
|
|
30
40
|
}
|
|
31
41
|
|
|
32
42
|
.md-pager-label {
|
|
@@ -36,15 +46,10 @@
|
|
|
36
46
|
|
|
37
47
|
.md-pager-title {
|
|
38
48
|
font-weight: 500;
|
|
39
|
-
transition: color 0.15s;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.md-pager-link:hover .md-pager-title {
|
|
43
|
-
color: var(--md-color-accent);
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
@media (prefers-reduced-motion: reduce) {
|
|
47
|
-
.md-pager-
|
|
52
|
+
.md-pager-link {
|
|
48
53
|
transition: none;
|
|
49
54
|
}
|
|
50
55
|
}
|
package/src/styles/search.css
CHANGED
|
@@ -151,9 +151,14 @@
|
|
|
151
151
|
background: var(--md-color-hover);
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
/* The underline keeps matches visible even when the accent is the heading color */
|
|
154
155
|
.md-search-results mark {
|
|
155
156
|
background: none;
|
|
156
157
|
color: var(--md-color-accent);
|
|
158
|
+
text-decoration: underline;
|
|
159
|
+
text-decoration-color: color-mix(in srgb, var(--md-color-accent) 50%, transparent);
|
|
160
|
+
text-decoration-thickness: 1px;
|
|
161
|
+
text-underline-offset: 0.2em;
|
|
157
162
|
}
|
|
158
163
|
|
|
159
164
|
.md-search-idle,
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
//#region src/routing.ts
|
|
2
|
-
/** The locale every page of a single-language site belongs to inside core. Never exposed to users. */
|
|
3
|
-
const SINGLE_LOCALE = "default";
|
|
4
|
-
/** Options for core: locale directories for multilingual sites, one implicit locale otherwise. */
|
|
5
|
-
function contentOptions(settings, base) {
|
|
6
|
-
return settings.i18n ? {
|
|
7
|
-
base,
|
|
8
|
-
locales: settings.i18n.locales,
|
|
9
|
-
fallback: settings.i18n.fallback
|
|
10
|
-
} : {
|
|
11
|
-
base,
|
|
12
|
-
locales: [SINGLE_LOCALE],
|
|
13
|
-
localeDirs: false
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
/** The URL segment of a page id: the root index page has none. */
|
|
17
|
-
function slugOf(id) {
|
|
18
|
-
return id === "index" ? "" : id;
|
|
19
|
-
}
|
|
20
|
-
const normalizeLocale = (locale) => locale.replaceAll("_", "-").toLowerCase();
|
|
21
|
-
/**
|
|
22
|
-
* Route parameter for a page, e.g. `en/docs/getting-started`, without `base` or slashes around it.
|
|
23
|
-
* `undefined` stands for the site root, as Astro expects for rest parameters.
|
|
24
|
-
*/
|
|
25
|
-
function routePath(settings, id, locale) {
|
|
26
|
-
const { i18n } = settings;
|
|
27
|
-
return [
|
|
28
|
-
!i18n || locale === void 0 || locale === i18n.defaultLocale && !i18n.prefixDefaultLocale ? "" : normalizeLocale(locale),
|
|
29
|
-
settings.prefix,
|
|
30
|
-
slugOf(id)
|
|
31
|
-
].filter(Boolean).join("/") || void 0;
|
|
32
|
-
}
|
|
33
|
-
/** Link to a page, including `base` and the trailing slash Astro would use. */
|
|
34
|
-
function href(settings, id, locale) {
|
|
35
|
-
const path = [settings.base, routePath(settings, id, locale)].filter(Boolean).join("/");
|
|
36
|
-
if (!path) return "/";
|
|
37
|
-
return `/${path}${settings.trailingSlash ? "/" : ""}`;
|
|
38
|
-
}
|
|
39
|
-
/** Route parameter for a page in `src/pages/[...path].md.ts`. The site root has no segment of its own, so it becomes `index`. */
|
|
40
|
-
function markdownPath(settings, id, locale) {
|
|
41
|
-
return routePath(settings, id, locale) ?? "index";
|
|
42
|
-
}
|
|
43
|
-
/** Link to the Markdown version of a page, including `base`. */
|
|
44
|
-
function markdownHref(settings, id, locale) {
|
|
45
|
-
return `/${[settings.base, markdownPath(settings, id, locale)].filter(Boolean).join("/")}.md`;
|
|
46
|
-
}
|
|
47
|
-
//#endregion
|
|
48
|
-
export { routePath as a, markdownPath as i, href as n, markdownHref as r, contentOptions as t };
|