@andesine/pages 0.0.0-stage → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +103 -0
- package/LICENSE +21 -0
- package/README.md +92 -2
- package/dist/answers.d.ts +6 -0
- package/dist/astro/content.astro +118 -0
- package/dist/astro/middleware.js +39 -0
- package/dist/astro/middleware.js.map +1 -0
- package/dist/astro/routes/answers.js +129 -0
- package/dist/astro/routes/answers.js.map +1 -0
- package/dist/astro/routes/entry-pages.js +22 -0
- package/dist/astro/routes/entry-pages.js.map +1 -0
- package/dist/astro/routes/llms-full.js +12 -0
- package/dist/astro/routes/llms-full.js.map +1 -0
- package/dist/astro/routes/llms.js +12 -0
- package/dist/astro/routes/llms.js.map +1 -0
- package/dist/astro/routes/markdown-alias.js +14 -0
- package/dist/astro/routes/markdown-alias.js.map +1 -0
- package/dist/astro/routes/markdown.js +12 -0
- package/dist/astro/routes/markdown.js.map +1 -0
- package/dist/astro/routes/not-found.astro +12 -0
- package/dist/astro/routes/robots.js +9 -0
- package/dist/astro/routes/robots.js.map +1 -0
- package/dist/astro/routes/search.js +42 -0
- package/dist/astro/routes/search.js.map +1 -0
- package/dist/astro/routes/sitemap.js +12 -0
- package/dist/astro/routes/sitemap.js.map +1 -0
- package/dist/astro/routes/skill.js +13 -0
- package/dist/astro/routes/skill.js.map +1 -0
- package/dist/astro/routes/skills.js +24 -0
- package/dist/astro/routes/skills.js.map +1 -0
- package/dist/astro/routes/social.js +91 -0
- package/dist/astro/routes/social.js.map +1 -0
- package/dist/astro/routes/static.astro +53 -0
- package/dist/astro/routes/well-known-llms.js +2 -0
- package/dist/astro.d.ts +34 -0
- package/dist/astro.js +60 -0
- package/dist/astro.js.map +1 -0
- package/dist/chunks/assets-D6WJmZ05.js +21 -0
- package/dist/chunks/assets-D6WJmZ05.js.map +1 -0
- package/dist/chunks/catalog-DU4BUrpw.js +28 -0
- package/dist/chunks/catalog-DU4BUrpw.js.map +1 -0
- package/dist/chunks/config-CuH5JSI4.js +184 -0
- package/dist/chunks/config-CuH5JSI4.js.map +1 -0
- package/dist/chunks/files-BxqiQzFj.js +62 -0
- package/dist/chunks/files-BxqiQzFj.js.map +1 -0
- package/dist/chunks/hash-D4piIIQ6.js +11 -0
- package/dist/chunks/hash-D4piIIQ6.js.map +1 -0
- package/dist/chunks/index-D8wL2B5s.d.ts +571 -0
- package/dist/chunks/index-vsu3hISM.d.ts +62 -0
- package/dist/chunks/limits-Ppc7BQPG.js +45 -0
- package/dist/chunks/limits-Ppc7BQPG.js.map +1 -0
- package/dist/chunks/markdown-DKJSQxR2.js +42 -0
- package/dist/chunks/markdown-DKJSQxR2.js.map +1 -0
- package/dist/chunks/mdx-components-CUUDTapN.d.ts +9 -0
- package/dist/chunks/normalize-DG5nxOLm.js +361 -0
- package/dist/chunks/normalize-DG5nxOLm.js.map +1 -0
- package/dist/chunks/paths-75QgA3Qs.js +16 -0
- package/dist/chunks/paths-75QgA3Qs.js.map +1 -0
- package/dist/chunks/prepare-lnuw_ycM.js +219 -0
- package/dist/chunks/prepare-lnuw_ycM.js.map +1 -0
- package/dist/chunks/read-DCv1lmcM.js +57 -0
- package/dist/chunks/read-DCv1lmcM.js.map +1 -0
- package/dist/chunks/request-DOuBHGQs.js +92 -0
- package/dist/chunks/request-DOuBHGQs.js.map +1 -0
- package/dist/chunks/response-CtzaKMWH.js +15 -0
- package/dist/chunks/response-CtzaKMWH.js.map +1 -0
- package/dist/chunks/site-DY-uZ0PP.js +419 -0
- package/dist/chunks/site-DY-uZ0PP.js.map +1 -0
- package/dist/chunks/skill-CBTGUHQH.js +85 -0
- package/dist/chunks/skill-CBTGUHQH.js.map +1 -0
- package/dist/chunks/sources-Cm6NdIen.js +236 -0
- package/dist/chunks/sources-Cm6NdIen.js.map +1 -0
- package/dist/chunks/stored-CabomV-C.js +354 -0
- package/dist/chunks/stored-CabomV-C.js.map +1 -0
- package/dist/chunks/stored-CcEfvCQH.js +475 -0
- package/dist/chunks/stored-CcEfvCQH.js.map +1 -0
- package/dist/chunks/types-DBzuXfSe.d.ts +53 -0
- package/dist/chunks/view-C-9ZM1zK.js +745 -0
- package/dist/chunks/view-C-9ZM1zK.js.map +1 -0
- package/dist/chunks/view-Dlf_VRI7.d.ts +151 -0
- package/dist/client.d.ts +73 -0
- package/dist/client.js +295 -0
- package/dist/client.js.map +1 -0
- package/dist/collections.d.ts +7 -0
- package/dist/collections.js +215 -0
- package/dist/collections.js.map +1 -0
- package/dist/entry-pages.d.ts +10 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.d.ts +16 -0
- package/dist/integration.js +435 -0
- package/dist/integration.js.map +1 -0
- package/dist/llms-full.d.ts +5 -0
- package/dist/llms.d.ts +5 -0
- package/dist/markdown-alias.d.ts +7 -0
- package/dist/markdown.d.ts +7 -0
- package/dist/middleware.d.ts +8 -0
- package/dist/robots.d.ts +5 -0
- package/dist/runtime.d.ts +81 -0
- package/dist/runtime.js +56 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scripts/restore-navigation.js +1 -0
- package/dist/search.d.ts +6 -0
- package/dist/sitemap.d.ts +5 -0
- package/dist/skill.d.ts +5 -0
- package/dist/skills.d.ts +10 -0
- package/dist/social.d.ts +7 -0
- package/dist/well-known-llms.d.ts +2 -0
- package/package.json +103 -3
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { n as toSegments, t as toHref } from "./paths-75QgA3Qs.js";
|
|
2
|
+
import { i as toFileMarkdown, t as loadAndesineSource } from "./stored-CabomV-C.js";
|
|
3
|
+
import { n as loadOpenAPISource } from "./stored-CcEfvCQH.js";
|
|
4
|
+
import { r as toLabel$1 } from "./markdown-DKJSQxR2.js";
|
|
5
|
+
import { mdxAdapter } from "@andesine/converters/mdx";
|
|
6
|
+
import { getCollection } from "astro:content";
|
|
7
|
+
//#region src/navigation/tree.ts
|
|
8
|
+
/** Returns the nodes from the root to the node of the page, or an empty list. */
|
|
9
|
+
const findTrail = (nodes, page) => {
|
|
10
|
+
for (const node of nodes) {
|
|
11
|
+
if (node.page === page) return [node];
|
|
12
|
+
const trail = node.children ? findTrail(node.children, page) : [];
|
|
13
|
+
if (trail.length) return [node, ...trail];
|
|
14
|
+
}
|
|
15
|
+
return [];
|
|
16
|
+
};
|
|
17
|
+
/** Returns the nodes with a page, in reading order. */
|
|
18
|
+
const flattenTree = (nodes) => {
|
|
19
|
+
return nodes.flatMap((node) => {
|
|
20
|
+
return [...node.page ? [node] : [], ...node.children ? flattenTree(node.children) : []];
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
const createNavigationItems = (nodes, trail) => {
|
|
24
|
+
return nodes.map((node) => {
|
|
25
|
+
return {
|
|
26
|
+
id: node.id,
|
|
27
|
+
label: node.label,
|
|
28
|
+
href: node.page?.href,
|
|
29
|
+
method: node.method,
|
|
30
|
+
current: node === trail.at(-1),
|
|
31
|
+
active: trail.includes(node),
|
|
32
|
+
children: node.children ? createNavigationItems(node.children, trail) : []
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/navigation/sections.ts
|
|
38
|
+
const createSection = (config, sources, fallbackHref) => {
|
|
39
|
+
const navigation = sources.flatMap((source) => source.navigation);
|
|
40
|
+
const href = flattenTree(navigation)[0]?.page?.href ?? sources[0]?.href ?? fallbackHref;
|
|
41
|
+
return {
|
|
42
|
+
...config,
|
|
43
|
+
href,
|
|
44
|
+
sources,
|
|
45
|
+
navigation
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
/** Without configured sections, one implicit section holds all sources. */
|
|
49
|
+
const createSections = (config, sources) => {
|
|
50
|
+
const base = toSegments(config.base, "Site base");
|
|
51
|
+
const mountHref = (sourceID) => {
|
|
52
|
+
const mount = config.sources.find((source) => source.id === sourceID)?.mount ?? "/";
|
|
53
|
+
return toHref(base, toSegments(mount, "Mount"));
|
|
54
|
+
};
|
|
55
|
+
if (!config.sections.length) return [createSection({
|
|
56
|
+
id: "default",
|
|
57
|
+
label: config.name,
|
|
58
|
+
links: []
|
|
59
|
+
}, sources, mountHref(sources[0]?.id))];
|
|
60
|
+
return config.sections.map((section) => {
|
|
61
|
+
return createSection(section, sources.filter((source) => section.sources.includes(source.id)), mountHref(section.sources[0]));
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/sources/files/load.ts
|
|
66
|
+
const toLabel = (name) => {
|
|
67
|
+
return name.replace(/[-_]+/g, " ").replace(/^./, (letter) => letter.toUpperCase());
|
|
68
|
+
};
|
|
69
|
+
const toMarkdown = (entry, label, pageURL) => {
|
|
70
|
+
try {
|
|
71
|
+
return toFileMarkdown(entry.body, pageURL);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
throw new Error(`${label}: ${error.message}`);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const sortTree = (nodes) => {
|
|
77
|
+
return [...nodes].sort((a, b) => a.order - b.order || a.path.localeCompare(b.path, "en")).map(({ id, label, page, children }) => {
|
|
78
|
+
return {
|
|
79
|
+
id,
|
|
80
|
+
label,
|
|
81
|
+
page,
|
|
82
|
+
children: children && sortTree(children)
|
|
83
|
+
};
|
|
84
|
+
}).filter((node) => node.page || node.children?.length);
|
|
85
|
+
};
|
|
86
|
+
const createFilesSource = (source, options, entries) => {
|
|
87
|
+
const { base } = options;
|
|
88
|
+
const prefix = [...toSegments(base, "Site base"), ...toSegments(source.mount, `Source "${source.id}" mount`)];
|
|
89
|
+
const root = [];
|
|
90
|
+
const folders = /* @__PURE__ */ new Map();
|
|
91
|
+
const pages = [];
|
|
92
|
+
const getFolder = (segments) => {
|
|
93
|
+
if (!segments.length) return root;
|
|
94
|
+
const path = segments.join("/");
|
|
95
|
+
const existing = folders.get(path);
|
|
96
|
+
if (existing) return existing.children;
|
|
97
|
+
const folder = {
|
|
98
|
+
id: `${source.id}:${path}/`,
|
|
99
|
+
label: toLabel(segments.at(-1)),
|
|
100
|
+
path,
|
|
101
|
+
order: 0,
|
|
102
|
+
children: []
|
|
103
|
+
};
|
|
104
|
+
folders.set(path, folder);
|
|
105
|
+
getFolder(segments.slice(0, -1)).push(folder);
|
|
106
|
+
return folder.children;
|
|
107
|
+
};
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
const data = entry.data;
|
|
110
|
+
const label = `Source "${source.id}", file "${entry.id}"`;
|
|
111
|
+
const path = entry.id.replace(/\.mdx?$/, "");
|
|
112
|
+
const segments = toSegments(path, label);
|
|
113
|
+
const folderSegments = segments.slice(0, -1);
|
|
114
|
+
const isIndex = segments.at(-1) === "index";
|
|
115
|
+
const route = data.slug === void 0 ? isIndex ? folderSegments : segments : toSegments(data.slug, `${label}, slug`);
|
|
116
|
+
const href = toHref(prefix, route);
|
|
117
|
+
const page = {
|
|
118
|
+
sourceID: source.id,
|
|
119
|
+
id: entry.id,
|
|
120
|
+
href,
|
|
121
|
+
title: data.title,
|
|
122
|
+
description: data.description,
|
|
123
|
+
toc: data.toc,
|
|
124
|
+
layout: data.layout,
|
|
125
|
+
searchHidden: data.searchHidden,
|
|
126
|
+
markdown: toMarkdown(entry, label, new URL(href, options.site)),
|
|
127
|
+
updatedAt: data.updatedAt?.toISOString(),
|
|
128
|
+
content: {
|
|
129
|
+
type: "file",
|
|
130
|
+
entry
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const node = {
|
|
134
|
+
id: `${source.id}:${entry.id}`,
|
|
135
|
+
label: data.navigationLabel ?? data.title,
|
|
136
|
+
path,
|
|
137
|
+
order: data.order,
|
|
138
|
+
page: data.navigationHidden ? void 0 : page
|
|
139
|
+
};
|
|
140
|
+
pages.push(page);
|
|
141
|
+
if (isIndex && folderSegments.length) {
|
|
142
|
+
getFolder(folderSegments);
|
|
143
|
+
Object.assign(folders.get(folderSegments.join("/")), {
|
|
144
|
+
label: node.label,
|
|
145
|
+
order: node.order,
|
|
146
|
+
page: node.page
|
|
147
|
+
});
|
|
148
|
+
} else getFolder(folderSegments).push(node);
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
id: source.id,
|
|
152
|
+
href: toHref(prefix),
|
|
153
|
+
pages,
|
|
154
|
+
navigation: sortTree(root)
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
const loadFilesSource = async (source, options) => {
|
|
158
|
+
const entries = await getCollection(source.id);
|
|
159
|
+
return createFilesSource(source, options, entries);
|
|
160
|
+
};
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/sources/openapi/links.ts
|
|
163
|
+
/** Reads a string prop, written as `id="…"` or `id={"…"}`. */
|
|
164
|
+
const readAttribute = (node, name) => {
|
|
165
|
+
const value = node.attributes?.find((attribute) => attribute.name === name)?.value;
|
|
166
|
+
if (typeof value === "string") return value;
|
|
167
|
+
try {
|
|
168
|
+
const parsed = value ? JSON.parse(value.value) : void 0;
|
|
169
|
+
return typeof parsed === "string" ? parsed : void 0;
|
|
170
|
+
} catch {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const findOperationTags = (node) => {
|
|
175
|
+
return node.type.startsWith("mdxJsx") && node.name === "Operation" ? [node] : (node.children ?? []).flatMap(findOperationTags);
|
|
176
|
+
};
|
|
177
|
+
/** Parses the page as MDX, like its source, so code and other elements are never changed. */
|
|
178
|
+
const parseOperationTags = async (markdown) => {
|
|
179
|
+
if (!markdown.includes("<Operation")) return [];
|
|
180
|
+
try {
|
|
181
|
+
return findOperationTags(await mdxAdapter.parse(markdown));
|
|
182
|
+
} catch {
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Replaces `<Operation>` tags in Markdown alternatives with links to the operations' Markdown,
|
|
188
|
+
* which agents can follow. Pages change in place, so navigation keeps pointing to them.
|
|
189
|
+
*/
|
|
190
|
+
const linkOperationTags = async (sources, site) => {
|
|
191
|
+
const operations = /* @__PURE__ */ new Map();
|
|
192
|
+
const toLink = (tag) => {
|
|
193
|
+
const target = operations.get(`${readAttribute(tag, "source")}:${readAttribute(tag, "id")}`);
|
|
194
|
+
const operation = target?.content.type === "openapi" ? target.content.operation : void 0;
|
|
195
|
+
if (!target || !operation) return void 0;
|
|
196
|
+
const url = new URL(`${target.href}index.md`, site).href;
|
|
197
|
+
return `[${toLabel$1(target.title)}](${url}): \`${operation.method.toUpperCase()} ${operation.path}\``;
|
|
198
|
+
};
|
|
199
|
+
sources.forEach((source) => {
|
|
200
|
+
source.pages.forEach((page) => {
|
|
201
|
+
if (page.content.type === "openapi" && page.content.operation) operations.set(`${source.id}:${page.content.operation.id}`, page);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
if (!operations.size) return sources;
|
|
205
|
+
await Promise.all(sources.flatMap((source) => source.pages).map(async (page) => {
|
|
206
|
+
(await parseOperationTags(page.markdown)).reverse().forEach((tag) => {
|
|
207
|
+
const start = tag.position?.start.offset;
|
|
208
|
+
const end = tag.position?.end.offset;
|
|
209
|
+
const link = toLink(tag);
|
|
210
|
+
if (start === void 0 || end === void 0 || link === void 0) return;
|
|
211
|
+
page.markdown = `${page.markdown.slice(0, start)}${link}${page.markdown.slice(end)}`;
|
|
212
|
+
});
|
|
213
|
+
}));
|
|
214
|
+
return sources;
|
|
215
|
+
};
|
|
216
|
+
//#endregion
|
|
217
|
+
//#region src/sources/index.ts
|
|
218
|
+
/** Loads the sources from their collections. */
|
|
219
|
+
const loadStaticSources = async (config) => {
|
|
220
|
+
const sources = await Promise.all(config.sources.flatMap((source) => {
|
|
221
|
+
if (source.type === "files") return [loadFilesSource(source, {
|
|
222
|
+
base: config.base,
|
|
223
|
+
site: config.site
|
|
224
|
+
})];
|
|
225
|
+
if (source.type === "openapi") return [loadOpenAPISource(source, {
|
|
226
|
+
base: config.base,
|
|
227
|
+
site: config.site
|
|
228
|
+
})];
|
|
229
|
+
return [loadAndesineSource(source, config.base)];
|
|
230
|
+
}));
|
|
231
|
+
return linkOperationTags(sources, config.site);
|
|
232
|
+
};
|
|
233
|
+
//#endregion
|
|
234
|
+
export { findTrail as a, createNavigationItems as i, loadFilesSource as n, flattenTree as o, createSections as r, loadStaticSources as t };
|
|
235
|
+
|
|
236
|
+
//# sourceMappingURL=sources-Cm6NdIen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sources-Cm6NdIen.js","names":["toLabel"],"sources":["../../src/navigation/tree.ts","../../src/navigation/sections.ts","../../src/sources/files/load.ts","../../src/sources/openapi/links.ts","../../src/sources/index.ts"],"sourcesContent":["import type { NavigationItem } from \"../context\";\nimport type { SourceNode, SourcePage } from \"../sources\";\n\n/** Returns the nodes from the root to the node of the page, or an empty list. */\nconst findTrail = (nodes: SourceNode[], page: SourcePage): SourceNode[] => {\n for (const node of nodes) {\n if (node.page === page) return [node];\n\n const trail = node.children ? findTrail(node.children, page) : [];\n\n if (trail.length) return [node, ...trail];\n }\n\n return [];\n};\n/** Returns the nodes with a page, in reading order. */\nconst flattenTree = (nodes: SourceNode[]): SourceNode[] => {\n return nodes.flatMap((node) => {\n return [...(node.page ? [node] : []), ...(node.children ? flattenTree(node.children) : [])];\n });\n};\nconst createNavigationItems = (nodes: SourceNode[], trail: SourceNode[]): NavigationItem[] => {\n return nodes.map((node) => {\n return {\n id: node.id,\n label: node.label,\n href: node.page?.href,\n method: node.method,\n current: node === trail.at(-1),\n active: trail.includes(node),\n children: node.children ? createNavigationItems(node.children, trail) : []\n };\n });\n};\n\nexport { findTrail, flattenTree, createNavigationItems };\n","import type { IconLinkConfig, PagesConfig } from \"../config\";\nimport { toHref, toSegments } from \"../routing/paths\";\nimport type { SourceData, SourceNode } from \"../sources\";\nimport { flattenTree } from \"./tree\";\n\ninterface Section {\n id: string;\n label: string;\n icon?: string;\n links: IconLinkConfig[];\n /** URL of the first page, or of the first source mount. */\n href: string;\n sources: SourceData[];\n navigation: SourceNode[];\n}\n\nconst createSection = (\n config: Pick<Section, \"id\" | \"label\" | \"icon\" | \"links\">,\n sources: SourceData[],\n /** Used when the sources have no pages. */\n fallbackHref: string\n): Section => {\n const navigation = sources.flatMap((source) => source.navigation);\n const href = flattenTree(navigation)[0]?.page?.href ?? sources[0]?.href ?? fallbackHref;\n\n return { ...config, href, sources, navigation };\n};\n/** Without configured sections, one implicit section holds all sources. */\nconst createSections = (config: PagesConfig, sources: SourceData[]): Section[] => {\n const base = toSegments(config.base, \"Site base\");\n const mountHref = (sourceID?: string): string => {\n const mount = config.sources.find((source) => source.id === sourceID)?.mount ?? \"/\";\n\n return toHref(base, toSegments(mount, \"Mount\"));\n };\n\n if (!config.sections.length) {\n return [\n createSection(\n { id: \"default\", label: config.name, links: [] },\n sources,\n mountHref(sources[0]?.id)\n )\n ];\n }\n\n return config.sections.map((section) => {\n return createSection(\n section,\n sources.filter((source) => section.sources.includes(source.id)),\n mountHref(section.sources[0])\n );\n });\n};\n\nexport { createSections };\nexport type { Section };\n","import { getCollection } from \"astro:content\";\nimport type { FilesSourceConfig } from \"../../config\";\nimport { toFileMarkdown } from \"../../content/markdown\";\nimport { toHref, toSegments } from \"../../routing/paths\";\nimport type { FileEntry, SourceData, SourceNode, SourcePage } from \"../types\";\nimport type { FileMetadata } from \"./schema\";\n\ninterface TreeNode extends SourceNode {\n path: string;\n order: number;\n children?: TreeNode[];\n}\n\ninterface FilesSourceOptions {\n base: string;\n /** The site URL, for absolute links in Markdown alternatives. */\n site: string;\n}\n\nconst toLabel = (name: string): string => {\n return name.replace(/[-_]+/g, \" \").replace(/^./, (letter) => letter.toUpperCase());\n};\nconst toMarkdown = (entry: FileEntry, label: string, pageURL: URL): string => {\n try {\n return toFileMarkdown(entry.body, pageURL);\n } catch (error) {\n throw new Error(`${label}: ${(error as Error).message}`);\n }\n};\nconst sortTree = (nodes: TreeNode[]): SourceNode[] => {\n return [...nodes]\n .sort((a, b) => a.order - b.order || a.path.localeCompare(b.path, \"en\"))\n .map(({ id, label, page, children }) => {\n return {\n id,\n label,\n page,\n children: children && sortTree(children)\n };\n })\n .filter((node) => node.page || node.children?.length);\n};\nconst createFilesSource = (\n source: FilesSourceConfig,\n options: FilesSourceOptions,\n entries: FileEntry[]\n): SourceData => {\n const { base } = options;\n const prefix = [\n ...toSegments(base, \"Site base\"),\n ...toSegments(source.mount, `Source \"${source.id}\" mount`)\n ];\n const root: TreeNode[] = [];\n const folders = new Map<string, TreeNode>();\n const pages: SourcePage[] = [];\n const getFolder = (segments: string[]): TreeNode[] => {\n if (!segments.length) return root;\n\n const path = segments.join(\"/\");\n const existing = folders.get(path);\n\n if (existing) return existing.children!;\n\n const folder: TreeNode = {\n id: `${source.id}:${path}/`,\n label: toLabel(segments.at(-1)!),\n path,\n order: 0,\n children: []\n };\n\n folders.set(path, folder);\n getFolder(segments.slice(0, -1)).push(folder);\n\n return folder.children!;\n };\n\n for (const entry of entries) {\n const data = entry.data as FileMetadata;\n const label = `Source \"${source.id}\", file \"${entry.id}\"`;\n const path = entry.id.replace(/\\.mdx?$/, \"\");\n const segments = toSegments(path, label);\n const folderSegments = segments.slice(0, -1);\n const isIndex = segments.at(-1) === \"index\";\n const route =\n data.slug === undefined\n ? isIndex\n ? folderSegments\n : segments\n : toSegments(data.slug, `${label}, slug`);\n const href = toHref(prefix, route);\n const page: SourcePage = {\n sourceID: source.id,\n id: entry.id,\n href,\n title: data.title,\n description: data.description,\n toc: data.toc,\n layout: data.layout,\n searchHidden: data.searchHidden,\n markdown: toMarkdown(entry, label, new URL(href, options.site)),\n updatedAt: data.updatedAt?.toISOString(),\n content: { type: \"file\", entry }\n };\n const node: TreeNode = {\n id: `${source.id}:${entry.id}`,\n label: data.navigationLabel ?? data.title,\n path,\n order: data.order,\n page: data.navigationHidden ? undefined : page\n };\n\n pages.push(page);\n\n // A folder's index page is the landing page of its group.\n if (isIndex && folderSegments.length) {\n getFolder(folderSegments);\n Object.assign(folders.get(folderSegments.join(\"/\"))!, {\n label: node.label,\n order: node.order,\n page: node.page\n });\n } else {\n getFolder(folderSegments).push(node);\n }\n }\n\n return { id: source.id, href: toHref(prefix), pages, navigation: sortTree(root) };\n};\nconst loadFilesSource = async (\n source: FilesSourceConfig,\n options: FilesSourceOptions\n): Promise<SourceData> => {\n const entries = (await getCollection(source.id)) as FileEntry[];\n\n return createFilesSource(source, options, entries);\n};\n\nexport { createFilesSource, loadFilesSource };\n","import { mdxAdapter } from \"@andesine/converters/mdx\";\nimport { toLabel } from \"../../output/markdown\";\nimport type { SourceData, SourcePage } from \"../types\";\n\ninterface ParsedNode {\n type: string;\n name?: string | null;\n attributes?: ParsedAttribute[];\n children?: ParsedNode[];\n position?: { start: { offset?: number }; end: { offset?: number } };\n}\n\ninterface ParsedAttribute {\n type: string;\n name?: string;\n value?: string | { value: string } | null;\n}\n\n/** Reads a string prop, written as `id=\"…\"` or `id={\"…\"}`. */\nconst readAttribute = (node: ParsedNode, name: string): string | undefined => {\n const value = node.attributes?.find((attribute) => attribute.name === name)?.value;\n\n if (typeof value === \"string\") return value;\n\n try {\n const parsed: unknown = value ? JSON.parse(value.value) : undefined;\n\n return typeof parsed === \"string\" ? parsed : undefined;\n } catch {\n return undefined;\n }\n};\nconst findOperationTags = (node: ParsedNode): ParsedNode[] => {\n const isTag = node.type.startsWith(\"mdxJsx\") && node.name === \"Operation\";\n\n return isTag ? [node] : (node.children ?? []).flatMap(findOperationTags);\n};\n/** Parses the page as MDX, like its source, so code and other elements are never changed. */\nconst parseOperationTags = async (markdown: string): Promise<ParsedNode[]> => {\n if (!markdown.includes(\"<Operation\")) return [];\n\n try {\n return findOperationTags((await mdxAdapter.parse(markdown)) as ParsedNode);\n } catch {\n // Pages that are not MDX, e.g. Markdown files, cannot render the component.\n return [];\n }\n};\n/**\n * Replaces `<Operation>` tags in Markdown alternatives with links to the operations' Markdown,\n * which agents can follow. Pages change in place, so navigation keeps pointing to them.\n */\nconst linkOperationTags = async (sources: SourceData[], site: string): Promise<SourceData[]> => {\n const operations = new Map<string, SourcePage>();\n const toLink = (tag: ParsedNode): string | undefined => {\n const target = operations.get(`${readAttribute(tag, \"source\")}:${readAttribute(tag, \"id\")}`);\n const operation = target?.content.type === \"openapi\" ? target.content.operation : undefined;\n\n if (!target || !operation) return undefined;\n\n const url = new URL(`${target.href}index.md`, site).href;\n\n return `[${toLabel(target.title)}](${url}): \\`${operation.method.toUpperCase()} ${operation.path}\\``;\n };\n\n sources.forEach((source) => {\n source.pages.forEach((page) => {\n if (page.content.type === \"openapi\" && page.content.operation) {\n operations.set(`${source.id}:${page.content.operation.id}`, page);\n }\n });\n });\n\n if (!operations.size) return sources;\n\n await Promise.all(\n sources\n .flatMap((source) => source.pages)\n .map(async (page) => {\n const tags = await parseOperationTags(page.markdown);\n\n // From the end, so the offsets of earlier tags stay valid.\n tags.reverse().forEach((tag) => {\n const start = tag.position?.start.offset;\n const end = tag.position?.end.offset;\n const link = toLink(tag);\n\n if (start === undefined || end === undefined || link === undefined) return;\n\n page.markdown = `${page.markdown.slice(0, start)}${link}${page.markdown.slice(end)}`;\n });\n })\n );\n\n return sources;\n};\n\nexport { linkOperationTags };\n","import type { PagesConfig } from \"../config\";\nimport { loadAndesineSource } from \"./andesine/stored\";\nimport { loadFilesSource } from \"./files/load\";\nimport { linkOperationTags } from \"./openapi/links\";\nimport { loadOpenAPISource } from \"./openapi/stored\";\nimport type { SourceData } from \"./types\";\n\n/** Loads the sources from their collections. */\nconst loadStaticSources = async (config: PagesConfig): Promise<SourceData[]> => {\n const sources = await Promise.all(\n config.sources.flatMap((source) => {\n if (source.type === \"files\") {\n return [loadFilesSource(source, { base: config.base, site: config.site })];\n }\n\n if (source.type === \"openapi\") {\n return [loadOpenAPISource(source, { base: config.base, site: config.site })];\n }\n\n return [loadAndesineSource(source, config.base)];\n })\n );\n\n return linkOperationTags(sources, config.site);\n};\n\nexport { loadStaticSources };\nexport type * from \"./types\";\n"],"mappings":"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"}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { n as toSegments, t as toHref } from "./paths-75QgA3Qs.js";
|
|
2
|
+
import { n as prepareContent } from "./prepare-lnuw_ycM.js";
|
|
3
|
+
import { toStructuredContent } from "@andesine/sdk";
|
|
4
|
+
import { toMDX } from "@andesine/converters/mdx";
|
|
5
|
+
import { getCollection } from "astro:content";
|
|
6
|
+
//#region src/content/fragments.ts
|
|
7
|
+
const slotNames = ["summary", "aside"];
|
|
8
|
+
const contentTypes = /* @__PURE__ */ new Set([
|
|
9
|
+
"image",
|
|
10
|
+
"element",
|
|
11
|
+
"horizontalRule",
|
|
12
|
+
"codeBlock",
|
|
13
|
+
"table"
|
|
14
|
+
]);
|
|
15
|
+
/** Empty fragments have only empty paragraphs. */
|
|
16
|
+
const hasContent = (node) => {
|
|
17
|
+
return Boolean(node.text?.trim()) || contentTypes.has(node.type) || (node.content ?? []).some(hasContent);
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Splits an entry into the page body and the `summary` and `aside` fragments, which the layout
|
|
21
|
+
* shows in their own places. Other named fragments stay in the body.
|
|
22
|
+
*/
|
|
23
|
+
const splitFragments = (entry) => {
|
|
24
|
+
const document = entry.content;
|
|
25
|
+
const slots = slotNames.flatMap((name) => {
|
|
26
|
+
const fragment = entry.fragments[name];
|
|
27
|
+
return fragment ? [{
|
|
28
|
+
name,
|
|
29
|
+
label: fragment.name,
|
|
30
|
+
content: fragment.content
|
|
31
|
+
}] : [];
|
|
32
|
+
});
|
|
33
|
+
const labels = new Set(slots.map((slot) => slot.label));
|
|
34
|
+
const isSlot = (node) => {
|
|
35
|
+
return node.type === "fragment" && labels.has(String(node.attrs?.name));
|
|
36
|
+
};
|
|
37
|
+
const result = { body: {
|
|
38
|
+
...document,
|
|
39
|
+
content: (document.content ?? []).filter((node) => !isSlot(node))
|
|
40
|
+
} };
|
|
41
|
+
for (const slot of slots) if (hasContent(slot.content)) result[slot.name] = slot.content;
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/content/visibility.ts
|
|
46
|
+
const tagPattern = /<Visibility\s+for=(["'])(agents|humans)\1\s*>|<\/Visibility\s*>/g;
|
|
47
|
+
/** Ranges of fenced and inline code, where tags are only text. */
|
|
48
|
+
const findCode = (source) => {
|
|
49
|
+
const ranges = [];
|
|
50
|
+
let fence;
|
|
51
|
+
let offset = 0;
|
|
52
|
+
for (const line of source.split("\n")) {
|
|
53
|
+
const marker = /^\s{0,3}(`{3,}|~{3,})/.exec(line)?.[1];
|
|
54
|
+
const end = offset + line.length;
|
|
55
|
+
if (fence) {
|
|
56
|
+
if (marker?.[0] === fence.marker[0] && marker.length >= fence.marker.length) {
|
|
57
|
+
ranges.push([fence.start, end]);
|
|
58
|
+
fence = void 0;
|
|
59
|
+
}
|
|
60
|
+
} else if (marker) fence = {
|
|
61
|
+
marker,
|
|
62
|
+
start: offset
|
|
63
|
+
};
|
|
64
|
+
else for (const match of line.matchAll(/`+[^`]*`+/g)) ranges.push([offset + match.index, offset + match.index + match[0].length]);
|
|
65
|
+
offset = end + 1;
|
|
66
|
+
}
|
|
67
|
+
if (fence) ranges.push([fence.start, source.length]);
|
|
68
|
+
return ranges;
|
|
69
|
+
};
|
|
70
|
+
/** Removes the indent that all lines share, as JSX children are often indented. */
|
|
71
|
+
const dedent = (text) => {
|
|
72
|
+
const lines = text.split("\n");
|
|
73
|
+
const indents = lines.filter((line) => line.trim()).map((line) => /^[ \t]*/.exec(line)[0].length);
|
|
74
|
+
const indent = Math.min(...indents);
|
|
75
|
+
return indents.length ? lines.map((line) => line.slice(indent)).join("\n") : text;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Keeps the `<Visibility>` blocks of one audience, without their tags, and removes the others.
|
|
79
|
+
* Tags in code stay. Blocks cannot be nested.
|
|
80
|
+
*/
|
|
81
|
+
const filterVisibility = (source, audience) => {
|
|
82
|
+
const code = findCode(source);
|
|
83
|
+
const tags = [...source.matchAll(tagPattern)].map((match) => {
|
|
84
|
+
return {
|
|
85
|
+
start: match.index,
|
|
86
|
+
end: match.index + match[0].length,
|
|
87
|
+
audience: match[2]
|
|
88
|
+
};
|
|
89
|
+
}).filter((tag) => !code.some(([start, end]) => tag.start >= start && tag.end <= end));
|
|
90
|
+
let result = "";
|
|
91
|
+
let position = 0;
|
|
92
|
+
for (let index = 0; index < tags.length; index++) {
|
|
93
|
+
const open = tags[index];
|
|
94
|
+
const close = tags[index + 1];
|
|
95
|
+
if (!(open.audience !== void 0 && close !== void 0 && !close.audience)) throw new Error("Each <Visibility> needs a closing tag, and blocks cannot be nested.");
|
|
96
|
+
result += source.slice(position, open.start);
|
|
97
|
+
result += open.audience === audience ? dedent(source.slice(open.end, close.start)) : "";
|
|
98
|
+
position = close.end;
|
|
99
|
+
index++;
|
|
100
|
+
}
|
|
101
|
+
return `${result}${source.slice(position)}`.replace(/\n{3,}/g, "\n\n");
|
|
102
|
+
};
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/content/markdown.ts
|
|
105
|
+
const rewriteLinks = (node, linkURL) => {
|
|
106
|
+
const props = node.attrs?.props;
|
|
107
|
+
const href = props?.href;
|
|
108
|
+
const isLinkedCard = node.type === "element" && node.attrs?.name === "Card" && typeof href === "string";
|
|
109
|
+
return {
|
|
110
|
+
...node,
|
|
111
|
+
attrs: isLinkedCard ? {
|
|
112
|
+
...node.attrs,
|
|
113
|
+
props: {
|
|
114
|
+
...props,
|
|
115
|
+
href: linkURL(href)
|
|
116
|
+
}
|
|
117
|
+
} : node.attrs,
|
|
118
|
+
marks: node.marks?.map((mark) => {
|
|
119
|
+
return mark.type === "link" && typeof mark.attrs?.href === "string" ? {
|
|
120
|
+
...mark,
|
|
121
|
+
attrs: {
|
|
122
|
+
...mark.attrs,
|
|
123
|
+
href: linkURL(mark.attrs.href)
|
|
124
|
+
}
|
|
125
|
+
} : mark;
|
|
126
|
+
}),
|
|
127
|
+
content: node.content?.map((child) => rewriteLinks(child, linkURL))
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Converts Andesine documents to the page's Markdown alternative. Elements become MDX
|
|
132
|
+
* components, like in MDX files.
|
|
133
|
+
*/
|
|
134
|
+
const toPageMarkdown = async (documents, options) => {
|
|
135
|
+
return (await Promise.all(documents.map((document) => {
|
|
136
|
+
return toMDX(rewriteLinks(document, options.linkURL), {
|
|
137
|
+
title: "omit",
|
|
138
|
+
properties: "omit",
|
|
139
|
+
imageURL: options.imageURL,
|
|
140
|
+
encode: { elements: { Visibility: (node, context) => {
|
|
141
|
+
return (node.attrs?.props)?.for === "agents" ? context.children(node) : null;
|
|
142
|
+
} } }
|
|
143
|
+
});
|
|
144
|
+
}))).map((part) => part.trim()).filter(Boolean).join("\n\n");
|
|
145
|
+
};
|
|
146
|
+
const toAbsolute = (target, base) => {
|
|
147
|
+
return /^[a-z][\w+.-]*:/i.test(target) || target.startsWith("{") ? target : new URL(target, base).href;
|
|
148
|
+
};
|
|
149
|
+
/** Makes link and image targets absolute, outside of inline code. */
|
|
150
|
+
const rewriteLine = (line, base) => {
|
|
151
|
+
return line.split(/(`+[^`]*`+)/).map((part, index) => {
|
|
152
|
+
return index % 2 ? part : part.replace(/(\]\()(<[^>]*>|[^)\s]+)/g, (_, start, target) => {
|
|
153
|
+
const wrapped = target.startsWith("<");
|
|
154
|
+
const url = toAbsolute(wrapped ? target.slice(1, -1) : target, base);
|
|
155
|
+
return `${start}${wrapped ? `<${url}>` : url}`;
|
|
156
|
+
}).replace(/^(\s{0,3}\[[^\]]+\]:\s*)(<[^>]*>|\S+)/, (_, start, target) => {
|
|
157
|
+
const wrapped = target.startsWith("<");
|
|
158
|
+
const url = toAbsolute(wrapped ? target.slice(1, -1) : target, base);
|
|
159
|
+
return `${start}${wrapped ? `<${url}>` : url}`;
|
|
160
|
+
}).replace(/\b(href|src)=(["'])([^"']+)\2/g, (_, name, quote, target) => {
|
|
161
|
+
return `${name}=${quote}${toAbsolute(target, base)}${quote}`;
|
|
162
|
+
});
|
|
163
|
+
}).join("");
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Prepares the source of a file page as its Markdown alternative. It keeps the content for
|
|
167
|
+
* agents, removes the leading MDX `import` and `export` statements, which only work in the
|
|
168
|
+
* build, and makes links absolute.
|
|
169
|
+
*/
|
|
170
|
+
const toFileMarkdown = (body = "", pageURL) => {
|
|
171
|
+
let fence;
|
|
172
|
+
return filterVisibility(body, "agents").replace(/^(?:(?:import|export)\s[\s\S]*?(?:\n\s*\n|$))+/, "").split("\n").map((line) => {
|
|
173
|
+
const marker = /^\s{0,3}(`{3,}|~{3,})/.exec(line)?.[1];
|
|
174
|
+
if (fence) {
|
|
175
|
+
if (marker?.[0] === fence[0] && marker.length >= fence.length) fence = void 0;
|
|
176
|
+
return line;
|
|
177
|
+
}
|
|
178
|
+
if (marker) {
|
|
179
|
+
fence = marker;
|
|
180
|
+
return line;
|
|
181
|
+
}
|
|
182
|
+
return rewriteLine(line, pageURL);
|
|
183
|
+
}).join("\n").trim();
|
|
184
|
+
};
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/sources/andesine/load.ts
|
|
187
|
+
const isIndex = (slugPath) => slugPath.endsWith("/index");
|
|
188
|
+
/** Reads the standard documentation properties. Missing properties use their defaults. */
|
|
189
|
+
const readMetadata = (entry) => {
|
|
190
|
+
const properties = toStructuredContent(entry).properties;
|
|
191
|
+
const text = (key) => {
|
|
192
|
+
const value = properties[key];
|
|
193
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
194
|
+
};
|
|
195
|
+
const flag = (key, fallback) => {
|
|
196
|
+
const value = properties[key];
|
|
197
|
+
return typeof value === "boolean" ? value : fallback;
|
|
198
|
+
};
|
|
199
|
+
return {
|
|
200
|
+
description: text("description"),
|
|
201
|
+
searchHidden: flag("searchHidden", false),
|
|
202
|
+
toc: flag("toc", true),
|
|
203
|
+
layout: text("layout") === "wide" ? "wide" : "docs"
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Returns the site URL of a published slug path, relative to the source's root collection. An
|
|
208
|
+
* `index` entry has its collection's URL, like an `index` file.
|
|
209
|
+
*/
|
|
210
|
+
const createHrefResolver = (source, base, rootSlugPath) => {
|
|
211
|
+
const label = `Source "${source.id}"`;
|
|
212
|
+
const prefix = [...toSegments(base, "Site base"), ...toSegments(source.mount, `${label} mount`)];
|
|
213
|
+
const root = toSegments(rootSlugPath, `${label} root`);
|
|
214
|
+
return (slugPath) => {
|
|
215
|
+
const segments = toSegments(slugPath, label).slice(root.length);
|
|
216
|
+
return toHref(prefix, isIndex(slugPath) ? segments.slice(0, -1) : segments);
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
/** Lists the entries of a collection and its descendants, with their collections. */
|
|
220
|
+
const listTreeEntries = (collection) => {
|
|
221
|
+
return [...collection.entries.map((entry) => ({
|
|
222
|
+
entry,
|
|
223
|
+
collection
|
|
224
|
+
})), ...collection.collections.flatMap(listTreeEntries)];
|
|
225
|
+
};
|
|
226
|
+
/** An `index` entry takes its collection's name, so its page is not titled "Index". */
|
|
227
|
+
const getTitle = ({ entry, collection }) => {
|
|
228
|
+
return isIndex(entry.slugPath) ? collection.name : entry.name;
|
|
229
|
+
};
|
|
230
|
+
/** Prepares the content of published entries as pages. */
|
|
231
|
+
const createAndesinePages = async (source, tree, entries, options) => {
|
|
232
|
+
const label = `Source "${source.id}"`;
|
|
233
|
+
const toPageHref = createHrefResolver(source, options.base, tree.slugPath);
|
|
234
|
+
const treeEntries = new Map(listTreeEntries(tree).map((item) => [item.entry.id, item]));
|
|
235
|
+
const links = new Map([...treeEntries.values()].flatMap(({ entry }) => {
|
|
236
|
+
const href = toPageHref(entry.slugPath);
|
|
237
|
+
return [[entry.path, href], [entry.slugPath, href]];
|
|
238
|
+
}));
|
|
239
|
+
const linkURL = (href) => {
|
|
240
|
+
const [, path = "", suffix = ""] = /^([^?#]*)(.*)$/s.exec(href) ?? [];
|
|
241
|
+
return links.has(path) ? `${links.get(path)}${suffix}` : href;
|
|
242
|
+
};
|
|
243
|
+
return Promise.all(entries.map(async (entry) => {
|
|
244
|
+
const data = readMetadata(entry);
|
|
245
|
+
const treeEntry = treeEntries.get(entry.id);
|
|
246
|
+
const findAsset = (assetID) => {
|
|
247
|
+
const asset = entry.assets.find((item) => item.assetID === assetID && item.variant === "display");
|
|
248
|
+
if (!asset) throw new Error(`${label}, entry ${entry.id}: image ${assetID} is missing.`);
|
|
249
|
+
return asset;
|
|
250
|
+
};
|
|
251
|
+
const href = toPageHref(entry.slugPath);
|
|
252
|
+
const pageURL = new URL(href, options.site);
|
|
253
|
+
const fragments = splitFragments(entry);
|
|
254
|
+
const imageURL = async (image) => {
|
|
255
|
+
return options.assets.resolve(findAsset(image.attrs?.assetID), entry.id);
|
|
256
|
+
};
|
|
257
|
+
const prepare = (document) => {
|
|
258
|
+
return document && prepareContent(document, {
|
|
259
|
+
imageURL,
|
|
260
|
+
linkURL,
|
|
261
|
+
highlight: options.highlight
|
|
262
|
+
});
|
|
263
|
+
};
|
|
264
|
+
const [[body, summary, aside], markdown] = await Promise.all([Promise.all([
|
|
265
|
+
prepare(fragments.body),
|
|
266
|
+
prepare(fragments.summary),
|
|
267
|
+
prepare(fragments.aside)
|
|
268
|
+
]), toPageMarkdown([
|
|
269
|
+
fragments.summary,
|
|
270
|
+
fragments.body,
|
|
271
|
+
fragments.aside
|
|
272
|
+
].filter((item) => item !== void 0), {
|
|
273
|
+
imageURL: async (image) => new URL(await imageURL(image), pageURL).href,
|
|
274
|
+
linkURL: (link) => new URL(linkURL(link), pageURL).href
|
|
275
|
+
})]).catch((error) => {
|
|
276
|
+
throw new Error(`${label}, entry ${entry.id}: ${error.message}`);
|
|
277
|
+
});
|
|
278
|
+
return {
|
|
279
|
+
sourceID: source.id,
|
|
280
|
+
id: entry.id,
|
|
281
|
+
href,
|
|
282
|
+
title: treeEntry ? getTitle(treeEntry) : entry.name,
|
|
283
|
+
description: data.description,
|
|
284
|
+
toc: data.toc,
|
|
285
|
+
layout: data.layout,
|
|
286
|
+
searchHidden: data.searchHidden,
|
|
287
|
+
markdown,
|
|
288
|
+
updatedAt: entry.version.createdAt,
|
|
289
|
+
content: {
|
|
290
|
+
type: "andesine",
|
|
291
|
+
...body,
|
|
292
|
+
iconCSS: [
|
|
293
|
+
body,
|
|
294
|
+
summary,
|
|
295
|
+
aside
|
|
296
|
+
].map((item) => item?.iconCSS ?? "").join(""),
|
|
297
|
+
summary: summary?.nodes,
|
|
298
|
+
aside: aside?.nodes
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}));
|
|
302
|
+
};
|
|
303
|
+
/**
|
|
304
|
+
* Maps a publication tree to navigation, like the app's explorer: collections first, then
|
|
305
|
+
* entries. The pages come from the same snapshot as the tree, so each entry has one.
|
|
306
|
+
*/
|
|
307
|
+
const createAndesineSource = (source, tree, base, prepared) => {
|
|
308
|
+
const toPageHref = createHrefResolver(source, base, tree.slugPath);
|
|
309
|
+
const pagesByID = new Map(prepared.map((page) => [page.id, { ...page }]));
|
|
310
|
+
const pages = listTreeEntries(tree).map((item) => pagesByID.get(item.entry.id));
|
|
311
|
+
const toNode = (page) => {
|
|
312
|
+
return {
|
|
313
|
+
id: `${source.id}:${page.id}`,
|
|
314
|
+
label: page.title,
|
|
315
|
+
page
|
|
316
|
+
};
|
|
317
|
+
};
|
|
318
|
+
const build = (collection) => {
|
|
319
|
+
const groups = collection.collections.flatMap((child) => {
|
|
320
|
+
const landing = child.entries.find((entry) => isIndex(entry.slugPath));
|
|
321
|
+
const children = build(child);
|
|
322
|
+
const node = {
|
|
323
|
+
id: `${source.id}:${child.id}`,
|
|
324
|
+
label: child.name,
|
|
325
|
+
page: landing && pagesByID.get(landing.id),
|
|
326
|
+
children
|
|
327
|
+
};
|
|
328
|
+
return node.page || children.length ? [node] : [];
|
|
329
|
+
});
|
|
330
|
+
const isRoot = collection === tree;
|
|
331
|
+
const leaves = [...collection.entries].reverse().filter((entry) => isRoot || !isIndex(entry.slugPath)).map((entry) => toNode(pagesByID.get(entry.id)));
|
|
332
|
+
return [...groups, ...leaves];
|
|
333
|
+
};
|
|
334
|
+
return {
|
|
335
|
+
id: source.id,
|
|
336
|
+
href: toPageHref(tree.slugPath),
|
|
337
|
+
pages,
|
|
338
|
+
navigation: build(tree)
|
|
339
|
+
};
|
|
340
|
+
};
|
|
341
|
+
//#endregion
|
|
342
|
+
//#region src/sources/andesine/stored.ts
|
|
343
|
+
const recordID = "source";
|
|
344
|
+
/** Reads a publication that the build loaded. */
|
|
345
|
+
const loadAndesineSource = async (source, base) => {
|
|
346
|
+
const [record] = await getCollection(source.id);
|
|
347
|
+
const stored = record?.data;
|
|
348
|
+
if (!stored) throw new Error(`Source "${source.id}": the build did not load the publication.`);
|
|
349
|
+
return createAndesineSource(source, stored.tree, base, stored.pages);
|
|
350
|
+
};
|
|
351
|
+
//#endregion
|
|
352
|
+
export { filterVisibility as a, toFileMarkdown as i, recordID as n, createAndesinePages as r, loadAndesineSource as t };
|
|
353
|
+
|
|
354
|
+
//# sourceMappingURL=stored-CabomV-C.js.map
|