reallysimpledocs 1.0.0-beta.3 → 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 (43) hide show
  1. package/README.md +33 -31
  2. package/package.json +10 -5
  3. package/src/astro/index.js +46 -43
  4. package/src/astro/preview-source.js +90 -0
  5. package/src/components/index.js +1 -0
  6. package/src/css/custom.css +259 -29
  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 +29 -14
  17. package/src/runtime/components/DefaultContentHeader.astro +2 -0
  18. package/src/runtime/components/DefaultHead.astro +120 -0
  19. package/src/runtime/components/DefaultHeadContent.astro +2 -0
  20. package/src/runtime/components/DefaultSidebarFooter.astro +2 -0
  21. package/src/runtime/components/DefaultSidebarHeader.astro +1 -1
  22. package/src/runtime/components/DocsLayout.astro +90 -122
  23. package/src/runtime/components/DropdownMenu.astro +1 -1
  24. package/src/runtime/components/Sidebar.astro +4 -0
  25. package/src/runtime/components/ThemeToggle.astro +21 -0
  26. package/src/runtime/components/mdx/Callout.astro +2 -9
  27. package/src/runtime/components/mdx/Card.astro +3 -8
  28. package/src/runtime/components/mdx/CardGrid.astro +1 -1
  29. package/src/runtime/components/mdx/Code.astro +2 -16
  30. package/src/runtime/components/mdx/CodeGroup.astro +7 -11
  31. package/src/runtime/components/mdx/Preview.astro +7 -12
  32. package/src/runtime/components/mdx/Steps.astro +1 -5
  33. package/src/runtime/components/mdx/Tab.astro +6 -0
  34. package/src/runtime/components/mdx/Tabs.astro +7 -7
  35. package/src/runtime/lib/docs.js +122 -16
  36. package/src/runtime/lib/highlight.js +1 -14
  37. package/src/runtime/lib/macros.js +11 -2
  38. package/src/runtime/lib/markdown-export.js +163 -0
  39. package/src/runtime/lib/site.js +0 -1
  40. package/src/runtime/pages/search-index.ts +10 -1
  41. package/src/css/overrides.css +0 -1
  42. package/src/css/sources.css +0 -1
  43. package/src/runtime/scripts/copy-code.js +0 -109
@@ -1,18 +1,14 @@
1
1
  ---
2
- const { defaultTab, class: className = "" } = Astro.props;
2
+ const { class: className = "" } = Astro.props;
3
3
  const id = `code-group-${Math.random().toString(36).slice(2)}`;
4
4
  const slotHtml = Astro.slots.has("default") ? await Astro.slots.render("default") : "";
5
5
  const normalizedTabs = parseCodeTabs(slotHtml);
6
- const defaultValue = defaultTab || normalizedTabs[0]?.value;
7
- const classes = [
8
- "relative my-6 w-full max-w-full overflow-hidden rounded-lg [&>[role=tabpanel]]:border-t-0 [&_[data-code-block]]:my-0 [&_[data-code-block]]:min-w-0 [&_[data-code-block]]:rounded-none [&_[data-code-block]>header]:hidden",
9
- className,
10
- ];
6
+ const defaultValue = normalizedTabs[0]?.value;
11
7
 
12
8
  function parseCodeTabs(html) {
13
9
  const blocks = [];
14
10
  const pattern =
15
- /<div\b(?=[^>]*\bdata-code-block\b)(?=[^>]*\bdata-code-title="([^"]*)")[^>]*>([\s\S]*?)<\/div>/g;
11
+ /<div\b(?=[^>]*\bclass="[^"]*\bcode-block\b[^"]*")(?=[^>]*\bdata-code-title="([^"]*)")[^>]*>([\s\S]*?)<\/div>/g;
16
12
  let match;
17
13
  while ((match = pattern.exec(html))) {
18
14
  const label = decodeHtml(match[1]).trim();
@@ -36,10 +32,10 @@ function decodeHtml(value) {
36
32
  }
37
33
  ---
38
34
 
39
- <div class:list={classes} data-code-group>
40
- <header class="flex h-10 items-center gap-2 border-b border-border/50 bg-muted/50 px-3 text-sm font-medium">
41
- <div class="tabs my-0 min-w-0 max-w-full gap-0">
42
- <nav class="h-full justify-start rounded-none bg-transparent p-0" role="tablist" aria-orientation="horizontal" data-variant="line">
35
+ <div class:list={["code-group", className]}>
36
+ <header>
37
+ <div class="tabs">
38
+ <nav role="tablist" aria-orientation="horizontal" data-variant="line">
43
39
  {
44
40
  normalizedTabs.map((tab, index) => {
45
41
  const selected = tab.value === defaultValue;
@@ -4,15 +4,13 @@ import { highlightCode } from "../../lib/highlight.js";
4
4
  const { code = "", lang = "html", title = "Preview", class: className = "" } = Astro.props;
5
5
  const id = `preview-${Math.random().toString(36).slice(2)}`;
6
6
  const html = await highlightCode(code, lang);
7
+ const previewClassName = className || "w-fit";
7
8
  ---
8
9
 
9
- <section
10
- class="relative my-6 max-w-full overflow-hidden rounded-lg border [&>[role=tabpanel]]:border-t-0 [&>[data-code-panel]_[data-code-block]]:my-0 [&>[data-code-panel]_[data-code-block]]:min-w-0 [&>[data-code-panel]_[data-code-block]]:rounded-none [&>[data-preview-panel]>div]:flex [&>[data-preview-panel]>div]:min-h-64 [&>[data-preview-panel]>div]:w-full [&>[data-preview-panel]>div]:items-center [&>[data-preview-panel]>div]:justify-center [&>[data-preview-panel]>div]:p-6"
11
- data-preview
12
- >
13
- <header class="flex h-10 items-center gap-2 border-b border-border/50 bg-muted/50 px-3 text-sm font-medium">
14
- <div class="tabs my-0 min-w-0 max-w-full gap-0">
15
- <nav class="h-full justify-start rounded-none bg-transparent p-0" role="tablist" aria-orientation="horizontal" data-variant="line">
10
+ <section class="preview">
11
+ <header>
12
+ <div class="tabs">
13
+ <nav role="tablist" aria-orientation="horizontal" data-variant="line">
16
14
  <button
17
15
  type="button"
18
16
  role="tab"
@@ -44,7 +42,7 @@ const html = await highlightCode(code, lang);
44
42
  tabindex="-1"
45
43
  aria-selected="true"
46
44
  >
47
- <div class={className}>
45
+ <div class={previewClassName}>
48
46
  <slot />
49
47
  </div>
50
48
  </section>
@@ -57,10 +55,7 @@ const html = await highlightCode(code, lang);
57
55
  aria-selected="false"
58
56
  hidden
59
57
  >
60
- <div
61
- class="relative my-6 w-full max-w-full overflow-hidden rounded-lg"
62
- data-code-block
63
- >
58
+ <div class="code-block">
64
59
  <Fragment set:html={html} />
65
60
  </div>
66
61
  </section>
@@ -1,11 +1,7 @@
1
1
  ---
2
2
  const { class: className = "" } = Astro.props;
3
- const classes = [
4
- "mb-12 mt-6 [counter-reset:step] md:ml-4 md:border-l md:pl-8 [&>section]:mb-10 [&>section]:[counter-increment:step] [&>section:last-child]:mb-0 [&>section>*+*]:mt-6 [&>section>*]:leading-relaxed [&>section>*:first-child]:mt-0 [&>section>*:last-child]:mb-0 [&>section>h3+*]:mt-4 [&>section>h3]:relative [&>section>h3]:scroll-m-28 [&>section>h3]:text-lg [&>section>h3]:font-medium [&>section>h3]:tracking-tight [&>section>h3]:before:mr-4 [&>section>h3]:before:inline-flex [&>section>h3]:before:size-7 [&>section>h3]:before:items-center [&>section>h3]:before:justify-center [&>section>h3]:before:rounded-full [&>section>h3]:before:bg-muted [&>section>h3]:before:text-sm [&>section>h3]:before:font-medium [&>section>h3]:before:text-muted-foreground [&>section>h3]:before:content-[counter(step)] md:[&>section>h3]:before:absolute md:[&>section>h3]:before:left-0 md:[&>section>h3]:before:top-0 md:[&>section>h3]:before:mr-0 md:[&>section>h3]:before:-translate-x-[2.875rem]",
5
- className,
6
- ];
7
3
  ---
8
4
 
9
- <div role="list" class:list={classes}>
5
+ <div role="list" class:list={["steps", className]}>
10
6
  <slot />
11
7
  </div>
@@ -0,0 +1,6 @@
1
+ ---
2
+ const { title } = Astro.props;
3
+ void title;
4
+ ---
5
+
6
+ <slot />
@@ -1,16 +1,17 @@
1
1
  ---
2
- const { tabs = [], defaultTab, orientation = "horizontal", variant, class: className = "" } = Astro.props;
2
+ const { tabs = [], variant, class: className = "" } = Astro.props;
3
3
  const id = `tabs-${Math.random().toString(36).slice(2)}`;
4
- const normalizedTabs = tabs.map((tab, index) => ({
4
+ const configuredTabs = typeof tabs === "string" ? JSON.parse(tabs) : tabs;
5
+ const normalizedTabs = configuredTabs.map((tab, index) => ({
5
6
  ...tab,
6
7
  slot: tab.slot || String(index),
7
8
  value: tab.value || tab.slot || String(index),
8
9
  }));
9
- const defaultValue = defaultTab || normalizedTabs.find((tab) => !tab.disabled)?.value || normalizedTabs[0]?.value;
10
+ const defaultValue = normalizedTabs[0]?.value;
10
11
  ---
11
12
 
12
- <div class:list={["tabs my-6", className]}>
13
- <nav role="tablist" aria-orientation={orientation} data-variant={variant}>
13
+ <div class:list={["tabs", className]}>
14
+ <nav role="tablist" aria-orientation="horizontal" data-variant={variant}>
14
15
  {
15
16
  normalizedTabs.map((tab, index) => {
16
17
  const selected = tab.value === defaultValue;
@@ -21,8 +22,7 @@ const defaultValue = defaultTab || normalizedTabs.find((tab) => !tab.disabled)?.
21
22
  id={`${id}-tab-${index + 1}`}
22
23
  aria-controls={`${id}-panel-${index + 1}`}
23
24
  aria-selected={selected ? "true" : "false"}
24
- tabindex={selected && !tab.disabled ? "0" : "-1"}
25
- disabled={tab.disabled}
25
+ tabindex={selected ? "0" : "-1"}
26
26
  >
27
27
  {tab.label}
28
28
  </button>
@@ -2,8 +2,9 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { marked } from "marked";
4
4
  import GithubSlugger from "github-slugger";
5
+ import { codeToHtml } from "shiki";
5
6
  import * as lucideIcons from "lucide-static";
6
- import { highlightCode as renderHighlightedCode } from "./highlight.js";
7
+ import { normalizeDocMarkdown } from "./markdown-export.js";
7
8
  import { markdownPath, routePath } from "./paths.js";
8
9
 
9
10
  export function getManifest(config) {
@@ -83,7 +84,7 @@ export function getDocMarkdown(config, page) {
83
84
  }
84
85
 
85
86
  export function getMarkdownExport(config, page) {
86
- return `${getDocMarkdown(config, page).trim()}\n`;
87
+ return `${getNormalizedDocMarkdown(config, page).trim()}\n`;
87
88
  }
88
89
 
89
90
  export async function renderDoc(config, page) {
@@ -117,7 +118,7 @@ export async function renderDoc(config, page) {
117
118
  };
118
119
 
119
120
  const tokens = marked.lexer(content);
120
- await highlightMarkdownCode(tokens);
121
+ await highlightCode(tokens);
121
122
  const html = marked.parser(tokens, { renderer });
122
123
  return { page, html, headings };
123
124
  }
@@ -182,13 +183,26 @@ export function buildMenus(config, currentSlug) {
182
183
  icon,
183
184
  url: page.path,
184
185
  label: item.label || page.title,
186
+ badge: item.badge,
185
187
  current: page.slug === currentSlug,
186
188
  keywords: page.title,
189
+ attrs: item.attrs,
187
190
  };
188
191
  };
189
192
 
190
193
  const processItem = (item) => {
191
194
  if (typeof item === "string") return processSlug(item);
195
+ if (item?.url && item?.label) {
196
+ return {
197
+ type: "item",
198
+ icon: resolveIcon(item.icon),
199
+ url: item.url,
200
+ label: item.label,
201
+ badge: item.badge,
202
+ external: /^https?:\/\//.test(item.url),
203
+ attrs: item.attrs,
204
+ };
205
+ }
192
206
  const slug = getPageItemSlug(item);
193
207
  if (slug) return processSlug(slug, item);
194
208
  if (item?.type === "submenu") {
@@ -221,6 +235,7 @@ export function buildMenus(config, currentSlug) {
221
235
  url: item.url,
222
236
  label: item.label,
223
237
  keywords: item.keywords,
238
+ attrs: item.attrs,
224
239
  })),
225
240
  }));
226
241
 
@@ -230,22 +245,81 @@ export function buildMenus(config, currentSlug) {
230
245
  export function getLlmDocs(config) {
231
246
  return getPages(config).map((page) => ({
232
247
  ...page,
233
- content: parseDocMarkdown(getDocMarkdown(config, page), page.title).body.trim(),
248
+ content: getNormalizedDocMarkdown(config, page, { includeTitle: false }).trim(),
234
249
  }));
235
250
  }
236
251
 
237
252
  export function getSearchDocs(config) {
238
- return getPages(config).map((page) => {
239
- const parsed = parseDocMarkdown(getDocMarkdown(config, page), page.title);
253
+ const pages = getPages(config);
254
+ const trails = getPageTrails(config, pages);
255
+
256
+ return pages.map((page) => {
257
+ const markdown = getNormalizedDocMarkdown(config, page, { includeTitle: false });
258
+ const { prose, code } = splitFencedMarkdown(markdown);
259
+ const trail = trails.get(page.slug) || [page.title];
260
+
240
261
  return {
241
262
  slug: page.slug,
242
263
  title: page.title,
264
+ breadcrumb: trail.length > 1 ? trail.join(" › ") : "",
243
265
  path: page.path,
244
- body: markdownPlainText(parsed.body),
266
+ body: markdownPlainText(prose),
267
+ code: markdownPlainText(code),
245
268
  };
246
269
  });
247
270
  }
248
271
 
272
+ function getPageTrails(config, pages) {
273
+ const manifest = getManifest(config);
274
+ const pagesBySlug = new Map(pages.map((page) => [page.slug, page]));
275
+ const trails = new Map();
276
+
277
+ const pageLabel = (item, slug) => {
278
+ if (typeof item === "object" && item?.label) return item.label;
279
+ return pagesBySlug.get(slug)?.title || fallbackLabelFromSlug(slug);
280
+ };
281
+
282
+ const walk = (items = [], parents = []) => {
283
+ items.forEach((item) => {
284
+ if (typeof item === "string") {
285
+ trails.set(item, [...parents, pageLabel(item, item)]);
286
+ return;
287
+ }
288
+
289
+ if (item?.type === "submenu") {
290
+ walk(item.items || [], [...parents, item.label].filter(Boolean));
291
+ return;
292
+ }
293
+
294
+ const slug = getPageItemSlug(item);
295
+ if (slug) trails.set(slug, [...parents, pageLabel(item, slug)]);
296
+ });
297
+ };
298
+
299
+ (manifest.menu || []).forEach((group) => {
300
+ if (group?.type === "group") walk(group.items || []);
301
+ });
302
+
303
+ return trails;
304
+ }
305
+
306
+ function splitFencedMarkdown(markdown) {
307
+ const code = [];
308
+ const prose = String(markdown || "").replace(/```[^\n]*\n([\s\S]*?)```/g, (_, block) => {
309
+ code.push(block);
310
+ return "\n";
311
+ });
312
+
313
+ return { prose, code: code.join("\n\n") };
314
+ }
315
+
316
+ function getNormalizedDocMarkdown(config, page, { includeTitle = true } = {}) {
317
+ const source = getDocMarkdown(config, page);
318
+ const parsed = parseDocMarkdown(source, page.title);
319
+ const body = normalizeDocMarkdown(renderNunjucksCompat(parsed.body), collectMdxExports(source));
320
+ return includeTitle ? `# ${parsed.title}\n\n${body}` : body;
321
+ }
322
+
249
323
  function flattenSidebarItems(items = []) {
250
324
  return items.flatMap((item) => (item.type === "submenu" ? flattenSidebarItems(item.items) : [item]));
251
325
  }
@@ -277,6 +351,15 @@ function markdownPlainText(markdown) {
277
351
  return tokensText(marked.lexer(renderNunjucksCompat(normalizeDocSource(markdown))));
278
352
  }
279
353
 
354
+ function collectMdxExports(source) {
355
+ const values = new Map();
356
+ const pattern = /export\s+const\s+([A-Za-z_$][\w$]*)\s*=\s*`([\s\S]*?)`;/g;
357
+ for (const match of String(source || "").matchAll(pattern)) {
358
+ values.set(match[1], match[2].replace(/\\n/g, "\n").replace(/\\`/g, "`"));
359
+ }
360
+ return values;
361
+ }
362
+
280
363
  function normalizeDocSource(source) {
281
364
  const withoutFrontmatter = String(source || "")
282
365
  .trimStart()
@@ -288,7 +371,7 @@ function normalizeDocSource(source) {
288
371
  .trimStart();
289
372
  }
290
373
 
291
- async function highlightMarkdownCode(tokens) {
374
+ async function highlightCode(tokens) {
292
375
  for (const token of tokens) {
293
376
  if (token.type === "code") {
294
377
  const html = await highlightToken(token);
@@ -302,24 +385,33 @@ async function highlightMarkdownCode(tokens) {
302
385
  }
303
386
 
304
387
  for (const childTokens of nestedTokenLists(token)) {
305
- await highlightMarkdownCode(childTokens);
388
+ await highlightCode(childTokens);
306
389
  }
307
390
  }
308
391
  }
309
392
 
310
393
  async function highlightToken(token) {
311
- const lang = normalizeLanguage(token.lang);
312
394
  try {
313
- return wrapCodeBlock(await renderHighlightedCode(token.text, lang));
395
+ return await codeToHtml(token.text, {
396
+ lang: normalizeLanguage(token.lang),
397
+ themes: {
398
+ light: "github-light",
399
+ dark: "github-dark",
400
+ },
401
+ defaultColor: false,
402
+ });
314
403
  } catch {
315
- return wrapCodeBlock(await renderHighlightedCode(token.text, "text"));
404
+ return codeToHtml(token.text, {
405
+ lang: "text",
406
+ themes: {
407
+ light: "github-light",
408
+ dark: "github-dark",
409
+ },
410
+ defaultColor: false,
411
+ });
316
412
  }
317
413
  }
318
414
 
319
- function wrapCodeBlock(html) {
320
- return `<div class="relative my-6 w-full max-w-full overflow-hidden rounded-lg" data-code-block>${html}</div>`;
321
- }
322
-
323
415
  function normalizeLanguage(lang) {
324
416
  const value = lang?.trim().split(/\s+/)[0].toLowerCase() || "text";
325
417
  if (value === "njk") return "html";
@@ -349,6 +441,7 @@ function plainText(tokens) {
349
441
  function tokensText(tokens) {
350
442
  return tokens
351
443
  .map((token) => {
444
+ if (token.type === "html") return htmlText(token.raw || token.text || "");
352
445
  const childTokens = [];
353
446
  if (token.tokens) childTokens.push(token.tokens);
354
447
  if (token.items) childTokens.push(...token.items.flatMap((item) => (item.tokens ? [item.tokens] : [])));
@@ -360,3 +453,16 @@ function tokensText(tokens) {
360
453
  .replace(/\s+/g, " ")
361
454
  .trim();
362
455
  }
456
+
457
+ function htmlText(html) {
458
+ return String(html || "")
459
+ .replace(/<script\b[\s\S]*?<\/script>/gi, " ")
460
+ .replace(/<style\b[\s\S]*?<\/style>/gi, " ")
461
+ .replace(/<[^>]+>/g, " ")
462
+ .replace(/&nbsp;/g, " ")
463
+ .replace(/&amp;/g, "&")
464
+ .replace(/&lt;/g, "<")
465
+ .replace(/&gt;/g, ">")
466
+ .replace(/&quot;/g, '"')
467
+ .replace(/&#39;/g, "'");
468
+ }
@@ -3,7 +3,7 @@ import { codeToHtml } from "shiki";
3
3
  export async function highlightCode(code, lang = "text") {
4
4
  const value = String(code || "");
5
5
  const language = String(lang || "text").toLowerCase() === "mdx" ? "tsx" : String(lang || "text");
6
- const html = await codeToHtml(value, {
6
+ return codeToHtml(value, {
7
7
  lang: language,
8
8
  themes: {
9
9
  light: "github-light",
@@ -11,17 +11,4 @@ export async function highlightCode(code, lang = "text") {
11
11
  },
12
12
  defaultColor: false,
13
13
  });
14
- return addCodeLayoutClasses(html);
15
- }
16
-
17
- function addCodeLayoutClasses(html) {
18
- return String(html)
19
- .replace(
20
- /<pre class="/,
21
- '<pre class="!my-0 max-w-full overflow-x-auto !rounded-none !border-0 ',
22
- )
23
- .replace(
24
- /<code>/,
25
- '<code class="block min-w-full w-max px-4 py-3 text-[13px]">',
26
- );
27
14
  }
@@ -1,5 +1,6 @@
1
1
  import Search from "lucide-static/icons/search.svg?raw";
2
- import { attrs, escapeHtml } from "./html.js";
2
+ import ArrowUpRight from "lucide-static/icons/arrow-up-right.svg?raw";
3
+ import { addSvgClass, attrs, escapeHtml } from "./html.js";
3
4
 
4
5
  export function renderSidebar({
5
6
  id = "sidebar",
@@ -58,16 +59,24 @@ export function renderSidebarContent(items = [], parentIdPrefix = "content") {
58
59
  <a${attrs({ href: item.url, "aria-current": item.current ? "page" : undefined, ...without(item.attrs, ["href", "aria-current"]) })}>
59
60
  ${item.icon || ""}
60
61
  <span>${escapeHtml(item.label)}</span>
62
+ ${renderSidebarMeta(item)}
61
63
  </a>
62
64
  </li>`;
63
65
  })
64
66
  .join("");
65
67
  }
66
68
 
69
+ function renderSidebarMeta(item) {
70
+ const badge = item.badge ? `<span class="badge" data-variant="outline">${escapeHtml(item.badge)}</span>` : "";
71
+ const external = item.external ? addSvgClass(ArrowUpRight, "size-3 opacity-50") : "";
72
+ if (!badge && !external) return "";
73
+ return `<span class="ml-auto inline-flex items-center gap-1">${badge}${external}</span>`;
74
+ }
75
+
67
76
  export function renderCommandDialog({
68
77
  id = "command-search",
69
78
  items = [],
70
- placeholder = "Search the docs...",
79
+ placeholder = "Search...",
71
80
  emptyText = "No results found.",
72
81
  open = false,
73
82
  commandAttrs = {},
@@ -0,0 +1,163 @@
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));
47
+ }
48
+
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;
56
+ }
57
+
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
+ );
62
+ }
63
+
64
+ function serializeChildren(_attrs, children, context = new Map()) {
65
+ return `\n\n${normalizeMdxComponents(children, context).trim()}\n\n`;
66
+ }
67
+
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
+ }
74
+
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
+ }
84
+
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
+ }
95
+
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`;
102
+ }
103
+
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`;
110
+ }
111
+
112
+ function getStringAttr(attrs, name) {
113
+ return String(attrs || "").match(new RegExp(`${name}=["']([^"']*)["']`))?.[1] || "";
114
+ }
115
+
116
+ function getExpressionAttr(attrs, name) {
117
+ return String(attrs || "").match(new RegExp(`${name}=\\{([A-Za-z_$][\\w$]*)\\}`))?.[1] || "";
118
+ }
119
+
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) : "";
125
+ }
126
+
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";
140
+ }
141
+
142
+ function indentMarkdown(markdown, prefix) {
143
+ return String(markdown || "")
144
+ .split("\n")
145
+ .map((line) => `${prefix}${line}`)
146
+ .join("\n");
147
+ }
148
+
149
+ function fencedCode(info, code) {
150
+ const fence = longestBacktickRun(code) >= 3 ? "`".repeat(longestBacktickRun(code) + 1) : "```";
151
+ return `${fence}${info}\n${code}\n${fence}`;
152
+ }
153
+
154
+ function longestBacktickRun(value) {
155
+ return Math.max(0, ...String(value || "").matchAll(/`+/g).map((match) => match[0].length));
156
+ }
157
+
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");
163
+ }
@@ -4,7 +4,6 @@ export const defaultSite = {
4
4
  title: "ReallySimpleDocs",
5
5
  description: "A really simple documentation site.",
6
6
  url: "",
7
- links: [],
8
7
  };
9
8
 
10
9
  export function getSite(config) {
@@ -7,7 +7,9 @@ export function GET() {
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 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: {
@@ -1 +0,0 @@
1
- /* Custom CSS overrides (loaded last). */
@@ -1 +0,0 @@
1
- @source "../runtime/**/*.{astro,js,ts}";