reallysimpledocs 1.0.0-beta.4 → 1.0.0-beta.5

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 (49) hide show
  1. package/README.md +35 -35
  2. package/package.json +10 -13
  3. package/src/astro/index.js +46 -98
  4. package/src/astro/preview-source.js +90 -0
  5. package/src/components/index.js +1 -0
  6. package/src/css/custom.css +252 -33
  7. package/src/css/styles/luma.css +11 -0
  8. package/src/css/styles/lyra.css +11 -0
  9. package/src/css/styles/maia.css +11 -0
  10. package/src/css/styles/mira.css +11 -0
  11. package/src/css/styles/nova.css +11 -0
  12. package/src/css/styles/rhea.css +11 -0
  13. package/src/css/styles/sera.css +11 -0
  14. package/src/css/styles/vega.css +11 -0
  15. package/src/runtime/components/BaseLayout.astro +20 -27
  16. package/src/runtime/components/CommandDialog.astro +30 -32
  17. package/src/runtime/components/DefaultContentHeader.astro +0 -17
  18. package/src/runtime/components/DefaultHead.astro +120 -0
  19. package/src/runtime/components/DefaultHeadContent.astro +2 -0
  20. package/src/runtime/components/DefaultSidebarHeader.astro +1 -1
  21. package/src/runtime/components/DocsLayout.astro +114 -165
  22. package/src/runtime/components/DropdownMenu.astro +1 -1
  23. package/src/runtime/components/ThemeToggle.astro +21 -0
  24. package/src/runtime/components/mdx/Callout.astro +2 -32
  25. package/src/runtime/components/mdx/Card.astro +3 -8
  26. package/src/runtime/components/mdx/CardGrid.astro +1 -1
  27. package/src/runtime/components/mdx/Code.astro +2 -16
  28. package/src/runtime/components/mdx/CodeGroup.astro +7 -11
  29. package/src/runtime/components/mdx/Preview.astro +7 -13
  30. package/src/runtime/components/mdx/Steps.astro +1 -5
  31. package/src/runtime/components/mdx/Tab.astro +6 -0
  32. package/src/runtime/components/mdx/Tabs.astro +7 -7
  33. package/src/runtime/lib/docs.js +130 -102
  34. package/src/runtime/lib/highlight.js +1 -14
  35. package/src/runtime/lib/macros.js +10 -5
  36. package/src/runtime/lib/markdown-export.js +134 -171
  37. package/src/runtime/pages/doc.astro +1 -3
  38. package/src/runtime/pages/llms-full.txt.ts +2 -2
  39. package/src/runtime/pages/llms.txt.ts +2 -2
  40. package/src/runtime/pages/markdown.ts +2 -2
  41. package/src/runtime/pages/search-index.ts +12 -3
  42. package/CHANGELOG.md +0 -35
  43. package/src/css/overrides.css +0 -1
  44. package/src/css/sources.css +0 -1
  45. package/src/runtime/lib/markdown-exporters/index.js +0 -144
  46. package/src/runtime/lib/mdx-source.js +0 -19
  47. package/src/runtime/lib/preview-source-plugin.js +0 -33
  48. package/src/runtime/scripts/copy-code.js +0 -130
  49. package/src/site/ContentHeader.astro +0 -33
@@ -1,200 +1,163 @@
1
- import { gfmToMarkdown } from "mdast-util-gfm";
2
- import { toMarkdown } from "mdast-util-to-markdown";
3
- import remarkGfm from "remark-gfm";
4
- import remarkMdx from "remark-mdx";
5
- import remarkParse from "remark-parse";
6
- import { unified } from "unified";
7
- import { serializeComponent } from "./markdown-exporters/index.js";
8
- import { getMdxChildrenSource, trimCode } from "./mdx-source.js";
9
-
10
- const processor = unified().use(remarkParse).use(remarkGfm).use(remarkMdx);
11
-
12
- const customSerializersCache = new Map();
13
-
14
- export async function mdxToMarkdown(source, options = {}) {
15
- const tree = processor.parse(source);
16
- const state = {
17
- constants: collectExportedStrings(tree),
18
- customSerializers: await loadCustomSerializers(options.markdownSerializers),
19
- source: String(source || ""),
20
- };
21
- const children = await transformBlocks(tree.children || [], state);
22
- return `${stringify({ type: "root", children }).trim()}\n`;
23
- }
24
-
25
- async function loadCustomSerializers(entries = {}) {
26
- const cacheKey = JSON.stringify(entries || {});
27
- if (customSerializersCache.has(cacheKey)) return customSerializersCache.get(cacheKey);
28
-
29
- const serializers = {};
30
- for (const [name, moduleId] of Object.entries(entries || {})) {
31
- if (!moduleId) continue;
32
- const module = await import(/* @vite-ignore */ moduleId);
33
- const serializer = module.default || module.serialize;
34
- if (typeof serializer !== "function") {
35
- throw new Error(`Markdown serializer for "${name}" must export a function as default or serialize.`);
36
- }
37
- serializers[name] = serializer;
1
+ export function normalizeDocMarkdown(markdown, context = new Map()) {
2
+ return transformMarkdownTextSegments(markdown, (content) => {
3
+ return normalizeMdxComponents(stripMdxModuleSyntax(content), context);
4
+ })
5
+ .replace(/[ \t]+\n/g, "\n")
6
+ .replace(/\n{3,}/g, "\n\n")
7
+ .trim();
8
+ }
9
+
10
+ function stripMdxModuleSyntax(content) {
11
+ return String(content || "")
12
+ .replace(/^\s*import\s+[^;\n]+;?\s*$/gm, "")
13
+ .replace(/^\s*export\s+(?:const|let|var|function)\s+[\s\S]*?(?=\n(?:#|[A-Z][A-Za-z]*\(|<[A-Z]|\w|\s*$))/gm, "");
14
+ }
15
+
16
+ function transformMarkdownTextSegments(markdown, transform) {
17
+ return String(markdown || "")
18
+ .split(/(```[\s\S]*?```)/g)
19
+ .map((part) => (part.startsWith("```") ? part : transform(part)))
20
+ .join("");
21
+ }
22
+
23
+ const serializers = {
24
+ Code: serializeCode,
25
+ CodeGroup: serializeChildren,
26
+ Tabs: serializeChildren,
27
+ Tab: serializeChildren,
28
+ CardGrid: serializeChildren,
29
+ Preview: serializePreview,
30
+ Callout: serializeCallout,
31
+ Steps: serializeSteps,
32
+ Card: serializeCard,
33
+ };
34
+
35
+ export function normalizeMdxComponents(content, context = new Map()) {
36
+ let out = String(content || "");
37
+ const previewBlocks = [];
38
+
39
+ out = replacePairedComponent(out, "Preview", (attrs, children) => {
40
+ const token = `\u0000RSD_PREVIEW_${previewBlocks.length}\u0000`;
41
+ previewBlocks.push(serializers.Preview(attrs, children, context));
42
+ return token;
43
+ });
44
+
45
+ for (const name of ["CodeGroup", "Tabs", "Tab", "CardGrid", "Callout", "Steps", "Code"]) {
46
+ out = replacePairedComponent(out, name, (attrs, children) => serializers[name](attrs, children, context));
38
47
  }
39
48
 
40
- customSerializersCache.set(cacheKey, serializers);
41
- return serializers;
49
+ out = out.replace(/<Code\b([^>]*)\/>/g, (_, attrs) => serializeCode(attrs, getCodeAttr(attrs, context), context));
50
+ out = out.replace(/<Card\b([^>]*)\/>/g, (_, attrs) => serializeCard(attrs));
51
+ previewBlocks.forEach((block, index) => {
52
+ out = out.replaceAll(`\u0000RSD_PREVIEW_${index}\u0000`, block);
53
+ });
54
+
55
+ return out;
42
56
  }
43
57
 
44
- async function transformBlocks(nodes, state) {
45
- const blocks = [];
46
- for (const node of nodes) {
47
- blocks.push(...(await transformBlock(node, state)));
48
- }
49
- return blocks.filter(Boolean);
58
+ function replacePairedComponent(content, name, serialize) {
59
+ return content.replace(new RegExp(`<${name}\\b([^>]*)>([\\s\\S]*?)<\\/${name}>`, "g"), (_, attrs, children) =>
60
+ serialize(attrs, children),
61
+ );
50
62
  }
51
63
 
52
- async function transformBlock(node, state) {
53
- if (!node) return [];
64
+ function serializeChildren(_attrs, children, context = new Map()) {
65
+ return `\n\n${normalizeMdxComponents(children, context).trim()}\n\n`;
66
+ }
54
67
 
55
- if (node.type === "mdxjsEsm") return [];
68
+ function serializePreview(attrs, children, context = new Map()) {
69
+ const lang = getStringAttr(attrs, "lang") || "mdx";
70
+ const code = getExpressionAttr(attrs, "code");
71
+ const source = stripCommonIndent(code && context.has(code) ? context.get(code) : children).trim();
72
+ return `\n\n${fencedCode(lang, source)}\n\n`;
73
+ }
56
74
 
57
- if (isMdxElement(node)) {
58
- return await transformComponent(node, state);
59
- }
75
+ function serializeCallout(attrs, children, context = new Map()) {
76
+ const title = getStringAttr(attrs, "title") || calloutTitle(getStringAttr(attrs, "type"));
77
+ const body = normalizeMdxComponents(children, context)
78
+ .trim()
79
+ .split("\n")
80
+ .map((line) => `> ${line}`)
81
+ .join("\n");
82
+ return `\n\n> **${title}**\n${body ? `${body}\n` : ""}\n`;
83
+ }
60
84
 
61
- if (node.type === "paragraph") {
62
- const children = significantChildren(node.children || []);
63
- if (children.length === 1 && isMdxElement(children[0])) {
64
- return await transformComponent(children[0], state);
65
- }
66
- }
85
+ function serializeSteps(_attrs, children, context = new Map()) {
86
+ let index = 0;
87
+ const steps = children.replace(/<Step\b([^>]*)>([\s\S]*?)<\/Step>/g, (_step, attrs, body) => {
88
+ index += 1;
89
+ const title = getStringAttr(attrs, "title");
90
+ const text = normalizeMdxComponents(body, context).trim();
91
+ return `${index}. ${title ? `**${title}**` : ""}${text ? `\n\n${indentMarkdown(text, " ")}` : ""}`;
92
+ });
93
+ return `\n\n${steps}\n\n`;
94
+ }
67
95
 
68
- const clone = { ...node };
69
- if (Array.isArray(clone.children)) {
70
- if (isBlockContainer(clone.type)) {
71
- clone.children = await transformBlocks(clone.children, state);
72
- } else {
73
- clone.children = await transformInline(clone.children, state);
74
- }
75
- }
76
- return [clone];
77
- }
78
-
79
- async function transformComponent(node, state) {
80
- return (
81
- (await serializeComponent(
82
- node,
83
- {
84
- transformBlocks: (nodes) => transformBlocks(nodes, state),
85
- getAttr: (entry, name) => getAttr(entry, name, state.constants),
86
- getCodeValue: (entry) => getCodeValue(entry, state.constants),
87
- getChildrenSource: (entry) => getMdxChildrenSource(entry, state.source),
88
- isNamedElement,
89
- significantChildren,
90
- },
91
- state.customSerializers,
92
- )) || (await transformBlocks(node.children || [], state))
93
- );
96
+ function serializeCode(attrs, children, context = new Map()) {
97
+ const lang = getStringAttr(attrs, "lang") || "text";
98
+ const title = getStringAttr(attrs, "title");
99
+ const label = title ? ` ${title}` : "";
100
+ const code = unwrapMdxString(children, context).trim();
101
+ return `\n\n${fencedCode(`${lang}${label}`, code)}\n\n`;
94
102
  }
95
103
 
96
- async function transformInline(nodes, state) {
97
- const inline = [];
98
- for (const node of nodes) {
99
- if (isMdxElement(node)) {
100
- const text = stringify({ type: "root", children: await transformComponent(node, state) }).trim();
101
- if (text) inline.push({ type: "text", value: text });
102
- continue;
103
- }
104
- if (node.type === "mdxTextExpression" || node.type === "mdxFlowExpression") {
105
- const value = expressionValue(node, state.constants);
106
- if (value) inline.push({ type: "text", value });
107
- continue;
108
- }
109
- const clone = { ...node };
110
- if (Array.isArray(clone.children)) clone.children = await transformInline(clone.children, state);
111
- inline.push(clone);
112
- }
113
- return inline;
104
+ function serializeCard(attrs) {
105
+ const title = getStringAttr(attrs, "title") || "Card";
106
+ const description = getStringAttr(attrs, "description");
107
+ const href = getStringAttr(attrs, "href");
108
+ const label = description ? `**${title}**: ${description}` : `**${title}**`;
109
+ return href ? `\n\n- [${label}](${href})\n` : `\n\n- ${label}\n`;
114
110
  }
115
111
 
116
- function getCodeValue(node, constants) {
117
- const parts = (node.children || []).map((child) => {
118
- if (child.type === "text") return child.value || "";
119
- if (child.type === "mdxFlowExpression" || child.type === "mdxTextExpression") {
120
- return expressionValue(child, constants);
121
- }
122
- return "";
123
- });
124
- return trimCode(parts.join("") || String(getAttr(node, "code", constants) || ""));
125
- }
126
-
127
- function getAttr(node, name, constants) {
128
- const attr = (node.attributes || []).find((entry) => entry.type === "mdxJsxAttribute" && entry.name === name);
129
- if (!attr) return "";
130
- if (attr.value == null) return "true";
131
- if (typeof attr.value === "string") return attr.value;
132
- return expressionValue(attr.value, constants);
133
- }
134
-
135
- function collectExportedStrings(tree) {
136
- const constants = new Map();
137
- for (const node of tree.children || []) {
138
- if (node.type !== "mdxjsEsm") continue;
139
- for (const statement of node.data?.estree?.body || []) {
140
- if (statement.type !== "ExportNamedDeclaration") continue;
141
- if (statement.declaration?.type !== "VariableDeclaration") continue;
142
- for (const declaration of statement.declaration.declarations || []) {
143
- if (declaration.id?.type !== "Identifier") continue;
144
- const value = staticValue(declaration.init, constants);
145
- if (value) constants.set(declaration.id.name, value);
146
- }
147
- }
148
- }
149
- return constants;
112
+ function getStringAttr(attrs, name) {
113
+ return String(attrs || "").match(new RegExp(`${name}=["']([^"']*)["']`))?.[1] || "";
150
114
  }
151
115
 
152
- function expressionValue(node, constants) {
153
- const expression = node.data?.estree?.body?.[0]?.expression || node.data?.estree?.body?.[0];
154
- return staticValue(expression, constants) || "";
116
+ function getExpressionAttr(attrs, name) {
117
+ return String(attrs || "").match(new RegExp(`${name}=\\{([A-Za-z_$][\\w$]*)\\}`))?.[1] || "";
155
118
  }
156
119
 
157
- function staticValue(node, constants) {
158
- if (!node) return "";
159
- if (node.type === "Literal") return node.value ?? "";
160
- if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
161
- return node.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw ?? "").join("");
162
- }
163
- if (node.type === "Identifier") return constants.get(node.name) || "";
164
- if (node.type === "ObjectExpression") {
165
- return Object.fromEntries(
166
- node.properties
167
- .filter((property) => property.type === "Property")
168
- .map((property) => [property.key.name || property.key.value, staticValue(property.value, constants)]),
169
- );
170
- }
171
- if (node.type === "ArrayExpression") {
172
- return node.elements.map((element) => staticValue(element, constants));
173
- }
174
- return "";
120
+ function getCodeAttr(attrs, context = new Map()) {
121
+ const direct = String(attrs || "").match(/\bcode=\{`([\s\S]*?)`\}/)?.[1] || "";
122
+ if (direct) return direct.replace(/\\n/g, "\n").replace(/\\`/g, "`");
123
+ const ref = getExpressionAttr(attrs, "code");
124
+ return ref && context.has(ref) ? context.get(ref) : "";
175
125
  }
176
126
 
177
- function stringify(tree) {
178
- return toMarkdown(tree, {
179
- bullet: "-",
180
- fences: true,
181
- rule: "-",
182
- extensions: [gfmToMarkdown()],
183
- });
127
+ function unwrapMdxString(value, context = new Map()) {
128
+ const text = String(value || "").trim();
129
+ const ref = text.match(/^\{([A-Za-z_$][\w$]*)\}$/)?.[1];
130
+ if (ref && context.has(ref)) return context.get(ref);
131
+ const template = text.match(/^\{`([\s\S]*)`\}$/);
132
+ if (template) return template[1].replace(/\\n/g, "\n").replace(/\\`/g, "`");
133
+ return text.replace(/^\{["']([\s\S]*)["']\}$/, "$1");
134
+ }
135
+
136
+ function calloutTitle(type) {
137
+ if (type === "warning") return "Warning";
138
+ if (type === "destructive") return "Danger";
139
+ return "Note";
184
140
  }
185
141
 
186
- function isMdxElement(node) {
187
- return node?.type === "mdxJsxFlowElement" || node?.type === "mdxJsxTextElement";
142
+ function indentMarkdown(markdown, prefix) {
143
+ return String(markdown || "")
144
+ .split("\n")
145
+ .map((line) => `${prefix}${line}`)
146
+ .join("\n");
188
147
  }
189
148
 
190
- function isNamedElement(node, name) {
191
- return isMdxElement(node) && node.name === name;
149
+ function fencedCode(info, code) {
150
+ const fence = longestBacktickRun(code) >= 3 ? "`".repeat(longestBacktickRun(code) + 1) : "```";
151
+ return `${fence}${info}\n${code}\n${fence}`;
192
152
  }
193
153
 
194
- function significantChildren(children) {
195
- return children.filter((child) => child.type !== "text" || child.value.trim());
154
+ function longestBacktickRun(value) {
155
+ return Math.max(0, ...String(value || "").matchAll(/`+/g).map((match) => match[0].length));
196
156
  }
197
157
 
198
- function isBlockContainer(type) {
199
- return type === "root" || type === "blockquote" || type === "list" || type === "listItem";
158
+ function stripCommonIndent(value) {
159
+ const lines = String(value || "").replace(/^\n+|\n+$/g, "").split("\n");
160
+ const indents = lines.filter((line) => line.trim()).map((line) => line.match(/^[ \t]*/)[0].length);
161
+ const indent = indents.length ? Math.min(...indents) : 0;
162
+ return indent ? lines.map((line) => line.slice(Math.min(indent, line.match(/^[ \t]*/)[0].length))).join("\n") : lines.join("\n");
200
163
  }
@@ -1,6 +1,5 @@
1
1
  ---
2
2
  import config from "virtual:reallysimpledocs/config";
3
- import UserMdxComponents from "virtual:reallysimpledocs/mdx-components";
4
3
  import { getMdxPage } from "virtual:reallysimpledocs/mdx-pages";
5
4
  import * as RsdComponents from "../../components/index.js";
6
5
  import DocsLayout from "../components/DocsLayout.astro";
@@ -19,9 +18,8 @@ const isMdx = page.extension === ".mdx";
19
18
  const mdxModule = isMdx ? await getMdxPage(page.slug) : null;
20
19
  const MdxContent = mdxModule?.Content || mdxModule?.default;
21
20
  const doc = isMdx ? { page, headings: getDocHeadings(config, page), html: "" } : await renderDoc(config, page);
22
- const mdxComponents = { ...RsdComponents, ...UserMdxComponents };
23
21
  ---
24
22
 
25
23
  <DocsLayout page={page} headings={doc.headings}>
26
- {MdxContent ? <MdxContent components={mdxComponents} /> : <Fragment set:html={doc.html} />}
24
+ {MdxContent ? <MdxContent components={RsdComponents} /> : <Fragment set:html={doc.html} />}
27
25
  </DocsLayout>
@@ -2,12 +2,12 @@ import config from "virtual:reallysimpledocs/config";
2
2
  import { getLlmDocs } from "../lib/docs.js";
3
3
  import { getSite } from "../lib/site.js";
4
4
 
5
- export async function GET({ site }) {
5
+ export function GET({ site }) {
6
6
  const siteData = getSite(config);
7
7
  const siteUrl = site?.toString().replace(/\/$/, "") || siteData.url || "";
8
8
  const lines = [];
9
9
 
10
- (await getLlmDocs(config)).forEach((doc) => {
10
+ getLlmDocs(config).forEach((doc) => {
11
11
  lines.push(`# ${doc.title}`, `Source: ${siteUrl}${doc.path}`, "");
12
12
  lines.push(doc.content, "", "---", "");
13
13
  });
@@ -2,9 +2,9 @@ import config from "virtual:reallysimpledocs/config";
2
2
  import { flattenMenuSlugs, getLlmDocs, getManifest } from "../lib/docs.js";
3
3
  import { getSite } from "../lib/site.js";
4
4
 
5
- export async function GET({ site }) {
5
+ export function GET({ site }) {
6
6
  const manifest = getManifest(config);
7
- const docs = await getLlmDocs(config);
7
+ const docs = getLlmDocs(config);
8
8
  const bySlug = new Map(docs.map((doc) => [doc.slug, doc]));
9
9
  const siteData = getSite(config);
10
10
  const siteUrl = site?.toString().replace(/\/$/, "") || siteData.url || "";
@@ -8,8 +8,8 @@ export function getStaticPaths() {
8
8
  }));
9
9
  }
10
10
 
11
- export async function GET({ props }) {
12
- return new Response(await getMarkdownExport(config, props.page), {
11
+ export function GET({ props }) {
12
+ return new Response(getMarkdownExport(config, props.page), {
13
13
  headers: {
14
14
  "content-type": "text/markdown; charset=utf-8",
15
15
  },
@@ -2,12 +2,14 @@ import lunr from "lunr";
2
2
  import config from "virtual:reallysimpledocs/config";
3
3
  import { getSearchDocs } from "../lib/docs.js";
4
4
 
5
- export async function GET() {
6
- const documents = await getSearchDocs(config);
5
+ export function GET() {
6
+ const documents = getSearchDocs(config);
7
7
  const index = lunr(function () {
8
8
  this.ref("slug");
9
9
  this.field("title", { boost: 10 });
10
+ this.field("breadcrumb", { boost: 8 });
10
11
  this.field("body");
12
+ this.field("code", { boost: 0.2 });
11
13
  this.metadataWhitelist = ["position"];
12
14
 
13
15
  documents.forEach((doc) => this.add(doc));
@@ -16,7 +18,14 @@ export async function GET() {
16
18
  return new Response(
17
19
  JSON.stringify({
18
20
  index,
19
- documents: documents.map(({ slug, title, path, body }) => ({ slug, title, path, body })),
21
+ documents: documents.map(({ slug, title, breadcrumb, path, body, code }) => ({
22
+ slug,
23
+ title,
24
+ breadcrumb,
25
+ path,
26
+ body,
27
+ code,
28
+ })),
20
29
  }),
21
30
  {
22
31
  headers: {
package/CHANGELOG.md DELETED
@@ -1,35 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
-
7
- ## [1.0.0-beta.4] - 2026-06-16
8
-
9
- ### Added
10
-
11
- - Added MDX documentation components for callouts, code blocks, code groups, previews, tabs, steps, and cards.
12
- - Added automatic MDX component availability in docs pages, so common ReallySimpleDocs components do not need page-level imports.
13
- - Added custom `mdxComponents` registration for project-specific MDX components.
14
- - Added custom `markdownSerializers` registration so project-specific MDX components can control Markdown, search, and LLM export output.
15
- - Added Markdown export serializers for ReallySimpleDocs components so generated Markdown, search content, and `llms-full.txt` avoid raw MDX component noise.
16
- - Added customizable sidebar header, sidebar footer, and content header component overrides.
17
- - Added breadcrumb rendering from the docs navigation tree.
18
- - Added Lunr-powered command search with result previews and highlighted matches.
19
- - Added Basecoat style selection through the ReallySimpleDocs integration config.
20
-
21
- ### Changed
22
-
23
- - Ported ReallySimpleDocs to an Astro integration with generated docs routes, assets, Markdown exports, search index, and Basecoat styling.
24
- - Updated docs content to use Markdown by default and MDX only for richer component examples.
25
- - Updated code examples to use inline component children instead of passing source code as props.
26
- - Changed empty table-of-contents behavior so the right panel is hidden instead of rendering an empty “On this page” section.
27
- - Changed breadcrumbs to render in the sticky content header for nested pages.
28
- - Updated the docs overview and component pages to explain component usage, Markdown output, and Basecoat interoperability.
29
-
30
- ### Fixed
31
-
32
- - Fixed Shiki code block colors across light and dark mode.
33
- - Fixed copy-code button layout for regular code blocks and tabbed code blocks.
34
- - Fixed callout rendering in Markdown and MDX pages.
35
- - Fixed sidebar and header markup to follow Basecoat component patterns.
@@ -1 +0,0 @@
1
- /* Custom CSS overrides (loaded last). */
@@ -1 +0,0 @@
1
- @source "../runtime/**/*.{astro,js,ts}";
@@ -1,144 +0,0 @@
1
- const serializers = {
2
- Callout: renderCallout,
3
- Code: renderCode,
4
- CodeGroup: renderCodeGroup,
5
- Preview: renderPreview,
6
- Steps: renderSteps,
7
- Tabs: renderChildren,
8
- Card: renderCard,
9
- CardGrid: renderCardGrid,
10
- };
11
-
12
- export async function serializeComponent(node, context, customSerializers = {}) {
13
- if (customSerializers[node.name]) return await customSerializers[node.name](node, context);
14
- return serializers[node.name]?.(node, context) || null;
15
- }
16
-
17
- async function renderChildren(node, context) {
18
- return await context.transformBlocks(node.children || []);
19
- }
20
-
21
- async function renderCallout(node, context) {
22
- const title = context.getAttr(node, "title");
23
- const action = context.getAttr(node, "action");
24
- const children = await context.transformBlocks(node.children || []);
25
- const quoteChildren = [];
26
- if (title) quoteChildren.push(strongParagraph(title));
27
- quoteChildren.push(...children);
28
- if (action?.label) {
29
- quoteChildren.push({
30
- type: "paragraph",
31
- children: action.href
32
- ? [{ type: "link", url: action.href, children: [{ type: "text", value: action.label }] }]
33
- : [{ type: "strong", children: [{ type: "text", value: action.label }] }],
34
- });
35
- }
36
- return [{ type: "blockquote", children: quoteChildren }];
37
- }
38
-
39
- function renderCode(node, context) {
40
- const title = context.getAttr(node, "title") || context.getAttr(node, "slot");
41
- const lang = context.getAttr(node, "lang") || "text";
42
- const value = context.getCodeValue(node);
43
- const blocks = [];
44
- if (title) blocks.push(strongParagraph(title));
45
- blocks.push({ type: "code", lang, value });
46
- return blocks;
47
- }
48
-
49
- function renderCodeGroup(node, context) {
50
- const codeNodes = collectCodeChildren(node, context);
51
- if (!codeNodes.length) return renderChildren(node, context);
52
- return codeNodes.flatMap((child, index) => {
53
- const blocks = renderCode(child, context);
54
- return index === 0 ? blocks : [{ type: "thematicBreak" }, ...blocks];
55
- });
56
- }
57
-
58
- async function renderPreview(node, context) {
59
- const value = context.getAttr(node, "code") || context.getChildrenSource(node);
60
- if (value) {
61
- return [{ type: "code", lang: context.getAttr(node, "lang") || "mdx", value }];
62
- }
63
- return await renderChildren(node, context);
64
- }
65
-
66
- async function renderSteps(node, context) {
67
- const steps = (node.children || []).filter((child) => context.isNamedElement(child, "Step"));
68
- if (!steps.length) return await renderChildren(node, context);
69
- const children = [];
70
- for (const step of steps) {
71
- children.push({
72
- type: "listItem",
73
- spread: true,
74
- children: await stepToBlocks(step, context),
75
- });
76
- }
77
- return [
78
- {
79
- type: "list",
80
- ordered: true,
81
- spread: true,
82
- children,
83
- },
84
- ];
85
- }
86
-
87
- function renderCard(node, context) {
88
- return [cardList([node], context)];
89
- }
90
-
91
- function renderCardGrid(node, context) {
92
- return [cardList((node.children || []).filter((child) => context.isNamedElement(child, "Card")), context)];
93
- }
94
-
95
- async function stepToBlocks(node, context) {
96
- const title = context.getAttr(node, "title");
97
- const children = await context.transformBlocks(node.children || []);
98
- if (!title) return children;
99
- return [strongParagraph(title), ...children];
100
- }
101
-
102
- function cardList(cards, context) {
103
- return {
104
- type: "list",
105
- ordered: false,
106
- spread: true,
107
- children: cards.map((card) => ({
108
- type: "listItem",
109
- spread: false,
110
- children: [cardParagraph(card, context)],
111
- })),
112
- };
113
- }
114
-
115
- function cardParagraph(node, context) {
116
- const title = context.getAttr(node, "title") || "Card";
117
- const description = context.getAttr(node, "description");
118
- const href = context.getAttr(node, "href");
119
- const titleNode =
120
- href && href !== "#"
121
- ? { type: "link", url: href, children: [{ type: "text", value: title }] }
122
- : { type: "strong", children: [{ type: "text", value: title }] };
123
- return {
124
- type: "paragraph",
125
- children: description ? [titleNode, { type: "text", value: `: ${description}` }] : [titleNode],
126
- };
127
- }
128
-
129
- function collectCodeChildren(node, context) {
130
- return (node.children || []).flatMap((child) => {
131
- if (context.isNamedElement(child, "Code")) return [child];
132
- if (child.type === "paragraph") {
133
- return context.significantChildren(child.children || []).filter((entry) => context.isNamedElement(entry, "Code"));
134
- }
135
- return [];
136
- });
137
- }
138
-
139
- function strongParagraph(value) {
140
- return {
141
- type: "paragraph",
142
- children: [{ type: "strong", children: [{ type: "text", value }] }],
143
- };
144
- }
@@ -1,19 +0,0 @@
1
- export function getMdxChildrenSource(node, source) {
2
- const children = node?.children || [];
3
- const first = children[0];
4
- const last = children.at(-1);
5
- const start = first?.position?.start?.offset;
6
- const end = last?.position?.end?.offset;
7
- if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) return "";
8
- return trimCode(String(source || "").slice(start, end));
9
- }
10
-
11
- export function trimCode(value) {
12
- const lines = String(value || "").replace(/\r\n/g, "\n").split("\n");
13
- while (lines.length && !lines[0].trim()) lines.shift();
14
- while (lines.length && !lines.at(-1).trim()) lines.pop();
15
- const indent = lines
16
- .filter((line) => line.trim())
17
- .reduce((min, line) => Math.min(min, line.match(/^\s*/)[0].length), Infinity);
18
- return lines.map((line) => line.slice(Number.isFinite(indent) ? indent : 0)).join("\n");
19
- }