@minidocs/astro 0.0.0-stage → 0.1.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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -2
  3. package/dist/behaviors.d.mts +114 -0
  4. package/dist/behaviors.mjs +635 -0
  5. package/dist/index.d.mts +25 -0
  6. package/dist/index.mjs +262 -0
  7. package/dist/links-UTLyoo_o.mjs +177 -0
  8. package/dist/loaders.d.mts +20 -0
  9. package/dist/loaders.mjs +76 -0
  10. package/dist/routing-TMqRCY2Q.mjs +48 -0
  11. package/dist/runtime.d.mts +160 -0
  12. package/dist/runtime.mjs +310 -0
  13. package/package.json +80 -3
  14. package/src/components/Prose.astro +9 -0
  15. package/src/components/callout/Root.astro +14 -0
  16. package/src/components/callout/Title.astro +9 -0
  17. package/src/components/callout/index.ts +2 -0
  18. package/src/components/index.ts +7 -0
  19. package/src/components/layout/Aside.astro +29 -0
  20. package/src/components/layout/Header.astro +18 -0
  21. package/src/components/layout/Main.astro +9 -0
  22. package/src/components/layout/Root.astro +15 -0
  23. package/src/components/layout/Sidebar.astro +9 -0
  24. package/src/components/layout/Title.astro +10 -0
  25. package/src/components/layout/index.ts +6 -0
  26. package/src/components/pager/Label.astro +9 -0
  27. package/src/components/pager/Link.astro +12 -0
  28. package/src/components/pager/Root.astro +12 -0
  29. package/src/components/pager/Title.astro +9 -0
  30. package/src/components/pager/index.ts +4 -0
  31. package/src/components/search/Count.astro +10 -0
  32. package/src/components/search/Dialog.astro +14 -0
  33. package/src/components/search/Empty.astro +10 -0
  34. package/src/components/search/Error.astro +10 -0
  35. package/src/components/search/Found.astro +10 -0
  36. package/src/components/search/Idle.astro +10 -0
  37. package/src/components/search/Input.astro +26 -0
  38. package/src/components/search/Loading.astro +10 -0
  39. package/src/components/search/Query.astro +10 -0
  40. package/src/components/search/Results.astro +20 -0
  41. package/src/components/search/Root.astro +15 -0
  42. package/src/components/search/Shortcut.astro +14 -0
  43. package/src/components/search/Trigger.astro +28 -0
  44. package/src/components/search/index.ts +13 -0
  45. package/src/components/sidebar/Group.astro +45 -0
  46. package/src/components/sidebar/Item.astro +9 -0
  47. package/src/components/sidebar/Label.astro +9 -0
  48. package/src/components/sidebar/Link.astro +23 -0
  49. package/src/components/sidebar/List.astro +11 -0
  50. package/src/components/sidebar/Root.astro +24 -0
  51. package/src/components/sidebar/Trigger.astro +29 -0
  52. package/src/components/sidebar/index.ts +7 -0
  53. package/src/components/toc/Item.astro +9 -0
  54. package/src/components/toc/Link.astro +12 -0
  55. package/src/components/toc/List.astro +11 -0
  56. package/src/components/toc/Root.astro +20 -0
  57. package/src/components/toc/index.ts +4 -0
  58. package/src/styles/base.css +41 -0
  59. package/src/styles/callout.css +58 -0
  60. package/src/styles/layout.css +406 -0
  61. package/src/styles/pager.css +51 -0
  62. package/src/styles/prose.css +218 -0
  63. package/src/styles/search.css +202 -0
  64. package/src/styles/sidebar.css +162 -0
  65. package/src/styles/toc.css +79 -0
  66. package/src/styles.css +9 -0
@@ -0,0 +1,160 @@
1
+ import { TocNode, TocOptions, findPath, neighbors, tocTree } from "@minidocs/core";
2
+ import { AstroRenderer } from "astro";
3
+ //#region src/llms.d.ts
4
+ interface LlmsTxtOptions<E> {
5
+ /** Name of the site, the heading of the file. */
6
+ title: string;
7
+ /** Description of a page, shown after its link. The description of the `index` page also becomes the summary of the file. */
8
+ describe?: ((entry: E) => string | undefined) | undefined;
9
+ }
10
+ //#endregion
11
+ //#region src/runtime.d.ts
12
+ /** Site-wide types generated from the Astro config: `{ i18n: true; locale: ... }`, or `{ i18n: false }` for single-language sites. */
13
+ export interface Config {}
14
+ /** Per-collection types generated by `glob` from '@minidocs/astro/loaders'. Without them, ids fall back to `string`. */
15
+ export interface Collections {}
16
+ type IsI18n = Config extends {
17
+ i18n: true;
18
+ } ? true : false;
19
+ /** Locales of the site. */
20
+ export type Locale = Config extends {
21
+ locale: infer L extends string;
22
+ } ? L : string;
23
+ type Known<C extends string> = C extends keyof Collections ? Collections[C] : never;
24
+ type Lookup<T, L> = T extends string ? T : L extends keyof T ? Extract<T[L], string> : never;
25
+ 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;
26
+ /** Page ids that can be referenced in a locale: its own pages plus pages reachable through fallback. */
27
+ export type PageIdOf<C extends string, L = Locale> = Field<C, 'pages', L>;
28
+ /** Directories that contain pages in a locale. */
29
+ export type DirOf<C extends string, L = Locale> = Field<C, 'dirs', L>;
30
+ /** The minimal shape of a content collection entry minidocs works with. */
31
+ export interface DocEntry {
32
+ collection: string;
33
+ filePath?: string | undefined;
34
+ data: {
35
+ title: string;
36
+ };
37
+ }
38
+ type Id<E extends DocEntry> = PageIdOf<E['collection']>;
39
+ interface LocalizedPageRef<E extends DocEntry> {
40
+ id: Id<E>;
41
+ /** Locale of the tree the node belongs to. */
42
+ locale: Locale;
43
+ /** Locale `entry` comes from. Differs from `locale` only for fallback pages. */
44
+ contentLocale: Locale;
45
+ entry: E;
46
+ isFallback: boolean;
47
+ }
48
+ export interface DocNode<E extends DocEntry> {
49
+ label: string;
50
+ page?: IsI18n extends true ? LocalizedPageRef<E> : {
51
+ id: Id<E>;
52
+ entry: E;
53
+ };
54
+ /** External link target, never set together with `page`. */
55
+ href?: string;
56
+ children: DocNode<E>[];
57
+ }
58
+ interface TreeHelpers<E extends DocEntry, L> {
59
+ page: (id: PageIdOf<E['collection'], L>, children?: DocNode<E>[]) => DocNode<E>;
60
+ group: (label: string, children: DocNode<E>[]) => DocNode<E>;
61
+ /** Returns the contents of a directory, excluding the directory's own index page. */
62
+ dir: (path: DirOf<E['collection'], L>, options?: {
63
+ compare?: (a: DocNode<E>, b: DocNode<E>) => number;
64
+ }) => DocNode<E>[];
65
+ link: (label: string, href: string) => DocNode<E>;
66
+ }
67
+ /** For multilingual sites, one context per locale, so checking `locale` narrows which pages `page()` accepts. */
68
+ export type BuildContext<E extends DocEntry> = IsI18n extends true ? { [L in Locale]: TreeHelpers<E, L> & {
69
+ locale: L;
70
+ }; }[Locale] : TreeHelpers<E, Locale>;
71
+ /** One tree per locale for multilingual sites, a single tree otherwise. */
72
+ export type DocTree<E extends DocEntry> = IsI18n extends true ? Record<Locale, DocNode<E>[]> : DocNode<E>[];
73
+ interface PageInfo<E extends DocEntry> {
74
+ id: Id<E>;
75
+ entry: E;
76
+ /** Route parameter for `src/pages/[...path].astro`. `undefined` is the site root. */
77
+ path: string | undefined;
78
+ href: string;
79
+ /** Route parameter for `src/pages/[...path].md.ts`. */
80
+ markdownPath: string;
81
+ /** Link to the Markdown version of the page. */
82
+ markdownHref: string;
83
+ }
84
+ interface LocalizedPageInfo<E extends DocEntry> extends PageInfo<E> {
85
+ /** Locale the page is shown in. */
86
+ locale: Locale;
87
+ /** Locale the content comes from. Differs from `locale` only for fallback pages. */
88
+ contentLocale: Locale;
89
+ isFallback: boolean;
90
+ /** Every locale this page exists in, translated or through fallback, including the current one. */
91
+ alternates: {
92
+ locale: Locale;
93
+ href: string;
94
+ }[];
95
+ }
96
+ export type DocPage<E extends DocEntry> = IsI18n extends true ? LocalizedPageInfo<E> : PageInfo<E>;
97
+ type LocaleArgs = IsI18n extends true ? [locale: Locale] : [];
98
+ /** Builds the navigation tree. Multilingual sites get one tree per locale, built by running `build` once for each. */
99
+ 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(id: string, ...locale: LocaleArgs): string;
102
+ /** Route parameter for a page in `src/pages/[...path].astro`. `undefined` is the site root. */
103
+ export declare function toPath(id: string, ...locale: LocaleArgs): string | undefined;
104
+ /** Whether a component renders for the Markdown version of a page, where it should output plain HTML that converts cleanly. */
105
+ export declare function isMarkdown(astro: {
106
+ locals: object;
107
+ }): boolean;
108
+ export interface MarkdownOptions {
109
+ /** Container renderers for UI framework components in the content, e.g. `getContainerRenderer()` from `@astrojs/react`. MDX is added automatically. */
110
+ renderers?: AstroRenderer[];
111
+ }
112
+ /** A page to convert: one from `listPages`, or the `page` of a tree node. */
113
+ export type MarkdownPage<E extends DocEntry> = IsI18n extends true ? {
114
+ id: Id<E>;
115
+ entry: E;
116
+ locale: Locale;
117
+ } : {
118
+ id: Id<E>;
119
+ entry: E;
120
+ };
121
+ /**
122
+ * Renders the content of a page and converts it into Markdown for agents, without a title.
123
+ * Components can check `isMarkdown(Astro)` to render a form that converts cleanly.
124
+ */
125
+ export declare function toMarkdown<E extends DocEntry>(page: MarkdownPage<E>, options?: MarkdownOptions): Promise<string>;
126
+ /**
127
+ * Renders a tree as llms.txt. Top-level pages come first, every top-level node with children becomes a section,
128
+ * and links point to the Markdown versions of pages. The `index` page's description becomes the summary.
129
+ */
130
+ export declare function toLlmsTxt<E extends DocEntry>(nodes: DocNode<E>[], options: LlmsTxtOptions<E>): string;
131
+ /**
132
+ * Every page of a tree in reading order, as Markdown under its title and source URL, for llms-full.txt.
133
+ * Like llms.txt, links and images are full URLs when `site` is set.
134
+ */
135
+ export declare function toLlmsFullTxt<E extends DocEntry>(nodes: DocNode<E>[], options?: MarkdownOptions): Promise<string>;
136
+ /** Every page that needs a route. Multilingual sites include every locale and untranslated pages served through fallback. */
137
+ export declare function listPages<E extends DocEntry>(entries: readonly E[]): DocPage<E>[];
138
+ interface NavNode {
139
+ page?: {
140
+ id: string;
141
+ locale?: string;
142
+ } | undefined;
143
+ href?: string | undefined;
144
+ children: NavNode[];
145
+ }
146
+ /** Named after the props of navigation components, so it can be spread onto them. */
147
+ export interface NodeState {
148
+ /** Link of a page or an external link, `undefined` for plain groups. */
149
+ href: string | undefined;
150
+ /** Whether the node is the current page. Only the first occurrence of a duplicated page counts. */
151
+ current: boolean;
152
+ /** Whether the current page is the node itself or below it. */
153
+ open: boolean;
154
+ }
155
+ /** Tracks where the current page sits in a tree. `of(node)` returns what navigation components need to render a node. */
156
+ export declare function navState<N extends NavNode>(nodes: N[], id: string): {
157
+ of(node: N): NodeState;
158
+ };
159
+ //#endregion
160
+ export { type LlmsTxtOptions, type TocNode, type TocOptions, findPath, neighbors, tocTree };
@@ -0,0 +1,310 @@
1
+ import { a as routePath, i as markdownPath, n as href, r as markdownHref, t as contentOptions } from "./routing-TMqRCY2Q.mjs";
2
+ import { MissingPageError, createTree, findPath, listPages as listPages$1, navState as navState$1, neighbors, tocTree } from "@minidocs/core";
3
+ import config from "virtual:minidocs/config";
4
+ import { fromHtml } from "hast-util-from-html";
5
+ import { defaultHandlers, toMdast } from "hast-util-to-mdast";
6
+ import { gfmToMarkdown } from "mdast-util-gfm";
7
+ import { toMarkdown as toMarkdown$1 } from "mdast-util-to-markdown";
8
+ //#region src/llms.ts
9
+ const escapeLabel = (text) => text.replace(/[[\]\\]/g, "\\$&");
10
+ const oneLine = (text) => text.replace(/\s+/g, " ").trim();
11
+ /** Pages of a tree in reading order, each page once. */
12
+ function pagesInOrder(nodes) {
13
+ const pages = /* @__PURE__ */ new Map();
14
+ const visit = (node) => {
15
+ if (node.page && !pages.has(node.page.id)) pages.set(node.page.id, node.page);
16
+ node.children.forEach(visit);
17
+ };
18
+ nodes.forEach(visit);
19
+ return [...pages.values()];
20
+ }
21
+ /**
22
+ * Renders a tree as llms.txt: the title, the `index` page's description as the summary,
23
+ * top-level pages as a list, then a section for every top-level node with children.
24
+ * `url` links a page to its Markdown version.
25
+ */
26
+ function renderLlmsTxt(nodes, options, url) {
27
+ const item = (node, depth, withChildren = true) => {
28
+ const description = node.page && options.describe?.(node.page.entry);
29
+ const line = node.page ? `[${escapeLabel(node.label)}](${url(node.page)})${description ? `: ${oneLine(description)}` : ""}` : node.href ? `[${escapeLabel(node.label)}](${node.href})` : node.label;
30
+ const children = withChildren ? node.children.flatMap((child) => item(child, depth + 1)) : [];
31
+ return [`${" ".repeat(depth)}- ${line}`, ...children];
32
+ };
33
+ const blocks = [`# ${options.title}`];
34
+ const index = pagesInOrder(nodes).find((page) => page.id === "index");
35
+ const summary = index && options.describe?.(index.entry);
36
+ if (summary) blocks.push(`> ${oneLine(summary)}`);
37
+ const loose = nodes.filter((node) => node.children.length === 0);
38
+ if (loose.length > 0) blocks.push(loose.flatMap((node) => item(node, 0)).join("\n"));
39
+ for (const node of nodes.filter((node) => node.children.length > 0)) {
40
+ const lines = [...node.page ? item(node, 0, false) : [], ...node.children.flatMap((child) => item(child, 0))];
41
+ blocks.push(`## ${node.label}\n\n${lines.join("\n")}`);
42
+ }
43
+ return `${blocks.join("\n\n")}\n`;
44
+ }
45
+ //#endregion
46
+ //#region src/serialize.ts
47
+ const ORIGIN = "http://minidocs.invalid";
48
+ /** URL path of a link, in the form `pathKey` compares with. */
49
+ function pathKey(href) {
50
+ return new URL(href, ORIGIN).pathname.replace(/\/+$/, "");
51
+ }
52
+ /**
53
+ * Turns a URL on the site into a path, pointing pages at their Markdown versions.
54
+ * Relative URLs resolve against the HTML page, since the Markdown file sits one level up.
55
+ * Returns `undefined` for URLs to other sites and links within the page.
56
+ */
57
+ function rewriteUrl(url, context) {
58
+ if (/^([a-z][a-z\d+.-]*:|\/\/|#)/i.test(url)) return void 0;
59
+ const resolved = new URL(url, ORIGIN + context.pageHref);
60
+ const target = context.markdownOf(pathKey(resolved.href));
61
+ const path = target ? target + resolved.hash : resolved.pathname + resolved.search + resolved.hash;
62
+ return context.resolve ? context.resolve(path) : path;
63
+ }
64
+ /** Hands an element to its default handler with one URL property rewritten. */
65
+ const withUrl = (property, handle, context) => (state, node, parent) => {
66
+ const url = node.properties[property];
67
+ const rewritten = typeof url === "string" ? rewriteUrl(url, context) : void 0;
68
+ return handle(state, rewritten ? {
69
+ ...node,
70
+ properties: {
71
+ ...node.properties,
72
+ [property]: rewritten
73
+ }
74
+ } : node, parent);
75
+ };
76
+ const isElement = (node, tagName) => node?.type === "element" && node.tagName === tagName;
77
+ const hasClass = (node, name) => [node.properties.className].flat().includes(name);
78
+ /** Callouts, from the Markdown syntax or the `Callout` component, become GitHub alerts with the title in bold. */
79
+ const callout = (state, node) => {
80
+ if (!hasClass(node, "md-callout") || typeof node.properties.dataType !== "string") return defaultHandlers.div(state, node);
81
+ const elements = node.children.filter((child) => child.type !== "text" || child.value.trim());
82
+ const title = isElement(elements[0], "p") && hasClass(elements[0], "md-callout-title") ? elements[0] : void 0;
83
+ const marker = {
84
+ type: "html",
85
+ value: `[!${node.properties.dataType.toUpperCase()}]`
86
+ };
87
+ const body = state.toFlow(state.all({
88
+ ...node,
89
+ children: title ? elements.slice(1) : elements
90
+ }));
91
+ const [first, ...rest] = body;
92
+ const head = title ? {
93
+ type: "paragraph",
94
+ children: [
95
+ marker,
96
+ {
97
+ type: "text",
98
+ value: "\n"
99
+ },
100
+ {
101
+ type: "strong",
102
+ children: state.all(title)
103
+ }
104
+ ]
105
+ } : first?.type === "paragraph" ? {
106
+ ...first,
107
+ children: [
108
+ marker,
109
+ {
110
+ type: "text",
111
+ value: "\n"
112
+ },
113
+ ...first.children
114
+ ]
115
+ } : void 0;
116
+ const quote = {
117
+ type: "blockquote",
118
+ children: head ? [head, ...title ? body : rest] : [{
119
+ type: "paragraph",
120
+ children: [marker]
121
+ }, ...body]
122
+ };
123
+ state.patch(node, quote);
124
+ return quote;
125
+ };
126
+ /** Shiki puts the language on `<pre>`, but the converter only reads it from a `language-*` class on `<code>`. */
127
+ const pre = (state, node) => {
128
+ const language = node.properties.dataLanguage;
129
+ const code = node.children.find((child) => isElement(child, "code"));
130
+ if (typeof language === "string" && language !== "plaintext" && code) code.properties.className = [`language-${language}`];
131
+ return defaultHandlers.pre(state, node);
132
+ };
133
+ /** Converts a page rendered by Astro into Markdown for agents. */
134
+ function htmlToMarkdown(html, context) {
135
+ const handlers = {
136
+ a: withUrl("href", defaultHandlers.a, context),
137
+ img: withUrl("src", defaultHandlers.img, context),
138
+ div: callout,
139
+ pre
140
+ };
141
+ const mdast = toMdast(fromHtml(html, { fragment: true }), { handlers });
142
+ return toMarkdown$1(mdast, {
143
+ extensions: [gfmToMarkdown()],
144
+ bullet: "-",
145
+ rule: "-",
146
+ fences: true,
147
+ listItemIndent: "one"
148
+ });
149
+ }
150
+ //#endregion
151
+ //#region src/runtime.ts
152
+ function optionsFor(entries) {
153
+ const collection = entries[0]?.collection;
154
+ if (!collection) throw new Error("[minidocs] Expected at least one entry.");
155
+ const base = config.bases[collection];
156
+ if (base === void 0) throw new Error(`[minidocs] Collection "${collection}" must be loaded with glob from '@minidocs/astro/loaders'.`);
157
+ if (entries.some((entry) => entry.collection !== collection)) throw new Error("[minidocs] All entries must come from the same collection.");
158
+ return contentOptions(config, base);
159
+ }
160
+ /** Drops locale details from nodes of single-language sites, where they carry no meaning. */
161
+ function withoutLocale(nodes) {
162
+ return nodes.map(({ page, children, ...node }) => ({
163
+ ...node,
164
+ ...page && { page: {
165
+ id: page.id,
166
+ entry: page.entry
167
+ } },
168
+ children: withoutLocale(children)
169
+ }));
170
+ }
171
+ /** Builds the navigation tree. Multilingual sites get one tree per locale, built by running `build` once for each. */
172
+ async function defineTree(entries, build) {
173
+ const options = optionsFor(entries);
174
+ const run = build;
175
+ let tree;
176
+ try {
177
+ tree = await createTree(entries, {
178
+ ...options,
179
+ label: (entry) => entry.data.title
180
+ }, config.i18n ? run : ({ locale: _, ...helpers }) => run(helpers));
181
+ } catch (error) {
182
+ if (error instanceof MissingPageError && config.pageIds[entries[0].collection]?.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
+ throw error;
184
+ }
185
+ return config.i18n ? tree : withoutLocale(tree[options.locales[0]]);
186
+ }
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]);
190
+ }
191
+ /** Route parameter for a page in `src/pages/[...path].astro`. `undefined` is the site root. */
192
+ function toPath(id, ...locale) {
193
+ return routePath(config, id, locale[0]);
194
+ }
195
+ const MARKDOWN = "minidocs:markdown";
196
+ /** Whether a component renders for the Markdown version of a page, where it should output plain HTML that converts cleanly. */
197
+ function isMarkdown(astro) {
198
+ return astro.locals[MARKDOWN] === true;
199
+ }
200
+ const containers = /* @__PURE__ */ new Map();
201
+ async function containerFor(extra) {
202
+ const { default: builtin } = await import("virtual:minidocs/renderers");
203
+ const renderers = [...builtin, ...extra];
204
+ const key = renderers.map(({ name }) => name).join(",");
205
+ let container = containers.get(key);
206
+ if (!container) {
207
+ container = (async () => {
208
+ const [{ experimental_AstroContainer }, { loadRenderers }] = await Promise.all([import("astro/container"), import("astro:container")]);
209
+ return experimental_AstroContainer.create({ renderers: await loadRenderers(renderers) });
210
+ })();
211
+ containers.set(key, container);
212
+ }
213
+ return container;
214
+ }
215
+ const markdownPages = /* @__PURE__ */ new Map();
216
+ /** Markdown versions of every page in a collection, keyed by the URL path of the HTML page. */
217
+ function markdownPagesOf(collection) {
218
+ let pages = markdownPages.get(collection);
219
+ if (!pages) {
220
+ pages = /* @__PURE__ */ new Map();
221
+ for (const id of config.pageIds[collection] ?? []) for (const locale of config.i18n?.locales ?? [void 0]) pages.set(pathKey(href(config, id, locale)), markdownHref(config, id, locale));
222
+ markdownPages.set(collection, pages);
223
+ }
224
+ return pages;
225
+ }
226
+ /** Full URL of a path when `site` is set, for text that is often pasted where paths do not resolve. */
227
+ const fullUrl = (path) => config.site ? new URL(path, config.site).href : path;
228
+ /**
229
+ * Renders the content of a page and converts it into Markdown for agents, without a title.
230
+ * Components can check `isMarkdown(Astro)` to render a form that converts cleanly.
231
+ */
232
+ function toMarkdown(page, options = {}) {
233
+ return renderMarkdown(page, options);
234
+ }
235
+ /** `toMarkdown`, with every URL on the site passed through `resolve`. */
236
+ async function renderMarkdown(page, options, resolve) {
237
+ const { id, entry } = page;
238
+ const locale = page.locale;
239
+ const [{ render }, container] = await Promise.all([import("astro:content"), containerFor(options.renderers ?? [])]);
240
+ const { Content } = await render(entry);
241
+ let html;
242
+ try {
243
+ html = await container.renderToString(Content, { locals: { [MARKDOWN]: true } });
244
+ } catch (error) {
245
+ if (error.name === "NoMatchingRenderer") throw new Error(`[minidocs] Page "${id}" uses a component the Markdown version cannot render. Pass its container renderer to toMarkdown, e.g. \`toMarkdown(page, { renderers: [getContainerRenderer()] })\` with getContainerRenderer from the framework's integration.`, { cause: error });
246
+ throw error;
247
+ }
248
+ return htmlToMarkdown(html, {
249
+ pageHref: href(config, id, locale),
250
+ markdownOf: (path) => markdownPagesOf(entry.collection).get(path),
251
+ resolve
252
+ });
253
+ }
254
+ const markdownUrl = ({ id, locale }) => fullUrl(markdownHref(config, id, locale));
255
+ /**
256
+ * Renders a tree as llms.txt. Top-level pages come first, every top-level node with children becomes a section,
257
+ * and links point to the Markdown versions of pages. The `index` page's description becomes the summary.
258
+ */
259
+ function toLlmsTxt(nodes, options) {
260
+ return renderLlmsTxt(nodes, options, markdownUrl);
261
+ }
262
+ /**
263
+ * Every page of a tree in reading order, as Markdown under its title and source URL, for llms-full.txt.
264
+ * Like llms.txt, links and images are full URLs when `site` is set.
265
+ */
266
+ async function toLlmsFullTxt(nodes, options = {}) {
267
+ const pages = pagesInOrder(nodes);
268
+ return (await Promise.all(pages.map(async (page) => `# ${page.entry.data.title}\n\nSource: ${markdownUrl(page)}\n\n${await renderMarkdown(page, options, fullUrl)}`))).join("\n");
269
+ }
270
+ /** Every page that needs a route. Multilingual sites include every locale and untranslated pages served through fallback. */
271
+ function listPages(entries) {
272
+ const pages = listPages$1(entries, optionsFor(entries));
273
+ if (!config.i18n) return pages.map(({ id, entry }) => ({
274
+ id,
275
+ entry,
276
+ path: routePath(config, id),
277
+ href: href(config, id),
278
+ markdownPath: markdownPath(config, id),
279
+ markdownHref: markdownHref(config, id)
280
+ }));
281
+ const alternates = /* @__PURE__ */ new Map();
282
+ for (const page of pages) {
283
+ const list = alternates.get(page.id) ?? [];
284
+ list.push({
285
+ locale: page.locale,
286
+ href: href(config, page.id, page.locale)
287
+ });
288
+ alternates.set(page.id, list);
289
+ }
290
+ return pages.map((page) => ({
291
+ ...page,
292
+ path: routePath(config, page.id, page.locale),
293
+ href: href(config, page.id, page.locale),
294
+ markdownPath: markdownPath(config, page.id, page.locale),
295
+ markdownHref: markdownHref(config, page.id, page.locale),
296
+ alternates: alternates.get(page.id)
297
+ }));
298
+ }
299
+ /** Tracks where the current page sits in a tree. `of(node)` returns what navigation components need to render a node. */
300
+ function navState(nodes, id) {
301
+ const state = navState$1(nodes, id);
302
+ return { of(node) {
303
+ return {
304
+ href: node.page ? href(config, node.page.id, node.page.locale) : node.href,
305
+ ...state.of(node)
306
+ };
307
+ } };
308
+ }
309
+ //#endregion
310
+ export { defineTree, findPath, isMarkdown, listPages, navState, neighbors, toHref, toLlmsFullTxt, toLlmsTxt, toMarkdown, toPath, tocTree };
package/package.json CHANGED
@@ -1,6 +1,83 @@
1
1
  {
2
2
  "name": "@minidocs/astro",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Astro integration for building documentation sites from composable primitives",
5
+ "keywords": [
6
+ "astro",
7
+ "astro-integration",
8
+ "withastro",
9
+ "documentation",
10
+ "docs"
11
+ ],
12
+ "homepage": "https://github.com/Ivan-1F/minidocs#readme",
13
+ "bugs": "https://github.com/Ivan-1F/minidocs/issues",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/Ivan-1F/minidocs.git",
17
+ "directory": "packages/astro"
18
+ },
19
+ "license": "MIT",
20
+ "type": "module",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.mts",
24
+ "default": "./dist/index.mjs"
25
+ },
26
+ "./loaders": {
27
+ "types": "./dist/loaders.d.mts",
28
+ "default": "./dist/loaders.mjs"
29
+ },
30
+ "./runtime": {
31
+ "types": "./dist/runtime.d.mts",
32
+ "default": "./dist/runtime.mjs"
33
+ },
34
+ "./behaviors": {
35
+ "types": "./dist/behaviors.d.mts",
36
+ "default": "./dist/behaviors.mjs"
37
+ },
38
+ "./components": "./src/components/index.ts",
39
+ "./styles.css": "./src/styles.css",
40
+ "./styles/*": "./src/styles/*"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "files": [
46
+ "dist",
47
+ "src/components/**/*.{astro,ts}",
48
+ "src/styles.css",
49
+ "src/styles",
50
+ "!**/*.test.ts"
51
+ ],
52
+ "peerDependencies": {
53
+ "astro": "^7.0.0"
54
+ },
55
+ "dependencies": {
56
+ "hast-util-from-html": "^2.0.3",
57
+ "hast-util-to-mdast": "^10.1.2",
58
+ "mdast-util-gfm": "^3.1.0",
59
+ "mdast-util-to-markdown": "^2.2.0",
60
+ "remark-directive": "^4.0.0",
61
+ "@minidocs/core": "^0.1.0"
62
+ },
63
+ "devDependencies": {
64
+ "@types/hast": "^3.0.5",
65
+ "@types/mdast": "^4.0.4",
66
+ "@types/node": "^24",
67
+ "astro": "^7.4.0",
68
+ "happy-dom": "^21.0.4",
69
+ "rehype-stringify": "^10.0.1",
70
+ "remark-parse": "^11.0.0",
71
+ "remark-rehype": "^11.1.2",
72
+ "satteri": "0.10.5",
73
+ "tsdown": "^0.23.1",
74
+ "typescript": "^7.0.2",
75
+ "unified": "^11.0.5",
76
+ "vitest": "^5.0.5"
77
+ },
78
+ "scripts": {
79
+ "build": "tsdown",
80
+ "test": "vitest run --passWithNoTests",
81
+ "typecheck": "tsc --noEmit && tsc --noEmit -p test-d/i18n && tsc --noEmit -p test-d/single"
82
+ }
6
83
  }
@@ -0,0 +1,9 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+
4
+ type Props = HTMLAttributes<'article'>;
5
+
6
+ const { class: className, ...attrs } = Astro.props;
7
+ ---
8
+
9
+ <article class:list={['md-prose', className]} {...attrs}><slot /></article>
@@ -0,0 +1,14 @@
1
+ ---
2
+ import type { CalloutType } from '@minidocs/astro';
3
+ import type { HTMLAttributes } from 'astro/types';
4
+
5
+ interface Props extends HTMLAttributes<'div'> {
6
+ /** Decides the color in the default styles. */
7
+ type?: CalloutType;
8
+ }
9
+
10
+ // Same markup as the :::type and > [!TYPE] syntax renders, so both share the default styles
11
+ const { type = 'note', class: className, ...attrs } = Astro.props;
12
+ ---
13
+
14
+ <div class:list={['md-callout', className]} data-type={type} role="note" {...attrs}><slot /></div>
@@ -0,0 +1,9 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+
4
+ type Props = HTMLAttributes<'p'>;
5
+
6
+ const { class: className, ...attrs } = Astro.props;
7
+ ---
8
+
9
+ <p class:list={['md-callout-title', className]} {...attrs}><slot /></p>
@@ -0,0 +1,2 @@
1
+ export { default as Root } from './Root.astro';
2
+ export { default as Title } from './Title.astro';
@@ -0,0 +1,7 @@
1
+ export * as Callout from './callout';
2
+ export * as Layout from './layout';
3
+ export { default as Prose } from './Prose.astro';
4
+ export * as Pager from './pager';
5
+ export * as Search from './search';
6
+ export * as Sidebar from './sidebar';
7
+ export * as Toc from './toc';
@@ -0,0 +1,29 @@
1
+ ---
2
+ import { restoreAside } from '@minidocs/astro/behaviors';
3
+ import type { HTMLAttributes } from 'astro/types';
4
+
5
+ interface Props extends HTMLAttributes<'details'> {
6
+ /** Heading of the column, and the toggle when the aside collapses above the content. */
7
+ title: string;
8
+ }
9
+
10
+ const { title, class: className, ...attrs } = Astro.props;
11
+ // The empty span is filled by attachAside with the heading being read, from md:toc-change events
12
+ // Runs before the first paint, so a collapsible aside starts closed without a flash of its contents
13
+ const restore = `(${restoreAside})(document.currentScript.parentElement)`;
14
+ ---
15
+
16
+ <details class:list={['md-layout-aside', className]} open {...attrs}>
17
+ <summary class="md-layout-aside-title">
18
+ <span class="md-layout-aside-label">{title}</span>
19
+ <span class="md-layout-aside-current" data-md-aside-current></span>
20
+ </summary>
21
+ <slot />
22
+ <script is:inline set:html={restore} />
23
+ </details>
24
+
25
+ <script>
26
+ import { attachAside } from '@minidocs/astro/behaviors';
27
+
28
+ for (const aside of document.querySelectorAll<HTMLDetailsElement>('.md-layout-aside')) attachAside(aside);
29
+ </script>
@@ -0,0 +1,18 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+
4
+ type Props = HTMLAttributes<'header'>;
5
+
6
+ const { class: className, ...attrs } = Astro.props;
7
+ ---
8
+
9
+ <header class:list={['md-layout-header', className]} {...attrs}>
10
+ <div class="md-layout-header-inner">
11
+ <slot />
12
+ {Astro.slots.has('end') && (
13
+ <div class="md-layout-header-end">
14
+ <slot name="end" />
15
+ </div>
16
+ )}
17
+ </div>
18
+ </header>
@@ -0,0 +1,9 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+
4
+ type Props = HTMLAttributes<'main'>;
5
+
6
+ const { class: className, ...attrs } = Astro.props;
7
+ ---
8
+
9
+ <main class:list={['md-layout-main', className]} {...attrs}><slot /></main>