ardo 2.7.2 → 3.0.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.
@@ -333,8 +333,8 @@ declare function ThemeToggle(): react_jsx_runtime.JSX.Element;
333
333
  declare function Search(): react_jsx_runtime.JSX.Element;
334
334
 
335
335
  interface CodeBlockProps {
336
- /** The code to display */
337
- code: string;
336
+ /** The code to display (as prop or as children string) */
337
+ code?: string;
338
338
  /** Programming language for syntax highlighting */
339
339
  language?: string;
340
340
  /** Optional title shown above the code */
@@ -343,17 +343,28 @@ interface CodeBlockProps {
343
343
  lineNumbers?: boolean;
344
344
  /** Line numbers to highlight */
345
345
  highlightLines?: number[];
346
- /** Optional custom content to render instead of the code */
346
+ /** Code as children supports template literals with auto-outdent */
347
347
  children?: React.ReactNode;
348
+ /** Pre-rendered Shiki HTML (injected by ardo:codeblock-highlight plugin) */
349
+ __html?: string;
348
350
  }
349
351
  /**
350
352
  * Syntax-highlighted code block with copy button.
353
+ *
354
+ * Code can be provided via the `code` prop or as children:
355
+ * ```tsx
356
+ * <CodeBlock language="typescript">{`
357
+ * const x = 42
358
+ * `}</CodeBlock>
359
+ * ```
360
+ * When children is a string, leading/trailing blank lines and common
361
+ * indentation are stripped automatically.
351
362
  */
352
- declare function CodeBlock({ code, language, title, lineNumbers, highlightLines, children, }: CodeBlockProps): react_jsx_runtime.JSX.Element;
363
+ declare function CodeBlock({ code: codeProp, language, title, lineNumbers, highlightLines, children, __html, }: CodeBlockProps): react_jsx_runtime.JSX.Element;
353
364
  interface CodeGroupProps {
354
365
  /** CodeBlock components to display as tabs */
355
366
  children: React.ReactNode;
356
- /** Comma-separated tab labels (set by remarkContainersMdx from code block meta) */
367
+ /** Comma-separated tab labels */
357
368
  labels?: string;
358
369
  }
359
370
  /**
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-NBRHGTR2.js";
5
5
  import {
6
6
  generateApiDocs
7
- } from "./chunk-CYZLI4AU.js";
7
+ } from "./chunk-PGHUPTGL.js";
8
8
 
9
9
  // src/vite/plugin.ts
10
10
  import { reactRouter } from "@react-router/dev/vite";
@@ -12,7 +12,6 @@ import mdx from "@mdx-js/rollup";
12
12
  import remarkFrontmatter from "remark-frontmatter";
13
13
  import remarkMdxFrontmatter from "remark-mdx-frontmatter";
14
14
  import remarkGfm from "remark-gfm";
15
- import remarkDirective from "remark-directive";
16
15
  import rehypeShiki from "@shikijs/rehype";
17
16
 
18
17
  // src/markdown/shiki.ts
@@ -20,23 +19,59 @@ import {
20
19
  createHighlighter
21
20
  } from "shiki";
22
21
  import { visit } from "unist-util-visit";
22
+ var DEFAULT_THEMES = {
23
+ light: "github-light-default",
24
+ dark: "github-dark-default"
25
+ };
26
+ var cachedHighlighter;
27
+ async function highlightCode(code, language, options) {
28
+ const themeConfig = options?.theme ?? DEFAULT_THEMES;
29
+ if (!cachedHighlighter) {
30
+ cachedHighlighter = await createShikiHighlighter({
31
+ theme: themeConfig,
32
+ lineNumbers: false,
33
+ anchor: false,
34
+ toc: { level: [2, 3] }
35
+ });
36
+ }
37
+ if (typeof themeConfig === "string") {
38
+ return cachedHighlighter.codeToHtml(code, { lang: language, theme: themeConfig });
39
+ }
40
+ return cachedHighlighter.codeToHtml(code, {
41
+ lang: language,
42
+ themes: { light: themeConfig.light, dark: themeConfig.dark },
43
+ defaultColor: false
44
+ });
45
+ }
23
46
  async function createShikiHighlighter(config) {
24
47
  const themeConfig = config.theme;
25
48
  const themes = typeof themeConfig === "string" ? [themeConfig] : [themeConfig.light, themeConfig.dark];
26
49
  const highlighter = await createHighlighter({
27
50
  themes,
28
51
  langs: [
52
+ // Web fundamentals
29
53
  "javascript",
30
54
  "typescript",
31
55
  "jsx",
32
56
  "tsx",
33
- "json",
34
57
  "html",
35
58
  "css",
59
+ "scss",
60
+ // Data & config formats
61
+ "json",
62
+ "jsonc",
63
+ "yaml",
64
+ "toml",
65
+ "xml",
66
+ "graphql",
67
+ // Markdown & docs
36
68
  "markdown",
69
+ "mdx",
70
+ // Shell & DevOps
37
71
  "bash",
38
72
  "shell",
39
- "yaml",
73
+ "dockerfile",
74
+ // General purpose
40
75
  "python",
41
76
  "rust",
42
77
  "go",
@@ -236,172 +271,6 @@ function ardoLineTransformer(options = {}) {
236
271
  };
237
272
  }
238
273
 
239
- // src/markdown/containers.ts
240
- import { visit as visit2 } from "unist-util-visit";
241
- var containerTypes = [
242
- "tip",
243
- "warning",
244
- "danger",
245
- "info",
246
- "note",
247
- "details",
248
- "code-group"
249
- ];
250
- var defaultTitles = {
251
- tip: "TIP",
252
- warning: "WARNING",
253
- danger: "DANGER",
254
- info: "INFO",
255
- note: "NOTE",
256
- details: "Details",
257
- "code-group": ""
258
- };
259
- function remarkContainers() {
260
- return function(tree) {
261
- visit2(tree, "containerDirective", (node) => {
262
- const type = node.name;
263
- if (!containerTypes.includes(type)) {
264
- return;
265
- }
266
- const data = node.data || (node.data = {});
267
- const titleNode = node.children[0];
268
- let customTitle;
269
- if (titleNode && titleNode.type === "paragraph" && titleNode.children[0]?.type === "text" && titleNode.data?.directiveLabel) {
270
- customTitle = titleNode.children[0].value;
271
- node.children.shift();
272
- }
273
- const title = customTitle || defaultTitles[type];
274
- if (type === "code-group") {
275
- data.hName = "div";
276
- data.hProperties = {
277
- className: ["ardo-code-group"]
278
- };
279
- const tabs = [];
280
- for (const child of node.children) {
281
- if (child.type === "code") {
282
- const codeNode = child;
283
- const meta = codeNode.meta || "";
284
- const labelMatch = meta.match(/\[([^\]]+)\]/);
285
- const label = labelMatch ? labelMatch[1] : codeNode.lang || "Code";
286
- tabs.push({ label, content: child });
287
- }
288
- }
289
- const tabsHtml = tabs.map(
290
- (tab, i) => `<button class="ardo-code-group-tab${i === 0 ? " active" : ""}" data-index="${i}">${escapeHtml2(tab.label)}</button>`
291
- ).join("");
292
- node.children = [
293
- {
294
- type: "html",
295
- value: `<div class="ardo-code-group-tabs">${tabsHtml}</div>`
296
- },
297
- {
298
- type: "html",
299
- value: '<div class="ardo-code-group-panels">'
300
- },
301
- ...tabs.map(
302
- (tab, i) => ({
303
- type: "html",
304
- value: `<div class="ardo-code-group-panel${i === 0 ? " active" : ""}" data-index="${i}">`
305
- })
306
- ),
307
- ...node.children.flatMap((child, _i) => [
308
- child,
309
- {
310
- type: "html",
311
- value: "</div>"
312
- }
313
- ]),
314
- {
315
- type: "html",
316
- value: "</div>"
317
- }
318
- ];
319
- return;
320
- }
321
- if (type === "details") {
322
- data.hName = "details";
323
- data.hProperties = {
324
- className: ["ardo-details"]
325
- };
326
- node.children.unshift({
327
- type: "html",
328
- value: `<summary class="ardo-details-summary">${escapeHtml2(title)}</summary>`
329
- });
330
- return;
331
- }
332
- data.hName = "div";
333
- data.hProperties = {
334
- className: ["ardo-container", `ardo-container-${type}`]
335
- };
336
- node.children.unshift({
337
- type: "html",
338
- value: `<p class="ardo-container-title">${escapeHtml2(title)}</p>`
339
- });
340
- });
341
- };
342
- }
343
- function escapeHtml2(text) {
344
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
345
- }
346
- var containerToComponent = {
347
- tip: "Tip",
348
- warning: "Warning",
349
- danger: "Danger",
350
- info: "Info",
351
- note: "Note"
352
- };
353
- function remarkContainersMdx() {
354
- return function(tree) {
355
- visit2(tree, "containerDirective", (node, index, parent) => {
356
- if (!parent || typeof index !== "number") return;
357
- if (node.name === "code-group") {
358
- const labels = node.children.filter((child) => child.type === "code").map((child) => {
359
- const meta = child.meta || "";
360
- const match = meta.match(/\[([^\]]+)\]/);
361
- return match ? match[1] : child.lang || "Code";
362
- });
363
- const jsxNode2 = {
364
- type: "mdxJsxFlowElement",
365
- name: "CodeGroup",
366
- attributes: [
367
- {
368
- type: "mdxJsxAttribute",
369
- name: "labels",
370
- value: labels.join(",")
371
- }
372
- ],
373
- children: node.children
374
- };
375
- parent.children[index] = jsxNode2;
376
- return;
377
- }
378
- const componentName = containerToComponent[node.name];
379
- if (!componentName) return;
380
- const titleNode = node.children[0];
381
- let customTitle;
382
- if (titleNode && titleNode.type === "paragraph" && titleNode.children[0]?.type === "text" && titleNode.data?.directiveLabel) {
383
- customTitle = titleNode.children[0].value;
384
- node.children.shift();
385
- }
386
- const attributes = [];
387
- if (customTitle) {
388
- attributes.push({
389
- type: "mdxJsxAttribute",
390
- name: "title",
391
- value: customTitle
392
- });
393
- }
394
- const jsxNode = {
395
- type: "mdxJsxFlowElement",
396
- name: componentName,
397
- attributes,
398
- children: node.children
399
- };
400
- parent.children[index] = jsxNode;
401
- });
402
- };
403
- }
404
-
405
274
  // src/vite/plugin.ts
406
275
  import fs2 from "fs/promises";
407
276
  import fsSync2 from "fs";
@@ -546,6 +415,168 @@ ${entries.join("\n")}
546
415
  };
547
416
  }
548
417
 
418
+ // src/vite/codeblock-plugin.ts
419
+ function outdent(text) {
420
+ const trimmed = text.replace(/^\n+/, "").replace(/\n\s*$/, "");
421
+ const lines = trimmed.split("\n");
422
+ const indent = lines.reduce((min, line) => {
423
+ if (line.trim().length === 0) return min;
424
+ const match = line.match(/^(\s*)/);
425
+ return match ? Math.min(min, match[1].length) : min;
426
+ }, Infinity);
427
+ if (indent === 0 || indent === Infinity) return trimmed;
428
+ return lines.map((line) => line.slice(indent)).join("\n");
429
+ }
430
+ function findSelfClosingCodeBlocks(source) {
431
+ const results = [];
432
+ const tag = "<CodeBlock";
433
+ let searchFrom = 0;
434
+ while (true) {
435
+ const start = source.indexOf(tag, searchFrom);
436
+ if (start === -1) break;
437
+ const afterTag = start + tag.length;
438
+ if (afterTag >= source.length || !/\s/.test(source[afterTag])) {
439
+ searchFrom = afterTag;
440
+ continue;
441
+ }
442
+ let i = afterTag;
443
+ let depth = 0;
444
+ let inSingle = false;
445
+ let inDouble = false;
446
+ let inTemplate = false;
447
+ let found = false;
448
+ while (i < source.length) {
449
+ const ch = source[i];
450
+ if ((inSingle || inDouble || inTemplate) && ch === "\\") {
451
+ i += 2;
452
+ continue;
453
+ }
454
+ if (inSingle) {
455
+ if (ch === "'") inSingle = false;
456
+ i++;
457
+ continue;
458
+ }
459
+ if (inDouble) {
460
+ if (ch === '"') inDouble = false;
461
+ i++;
462
+ continue;
463
+ }
464
+ if (inTemplate) {
465
+ if (ch === "`") inTemplate = false;
466
+ i++;
467
+ continue;
468
+ }
469
+ if (ch === "'") {
470
+ inSingle = true;
471
+ i++;
472
+ continue;
473
+ }
474
+ if (ch === '"') {
475
+ inDouble = true;
476
+ i++;
477
+ continue;
478
+ }
479
+ if (ch === "`") {
480
+ inTemplate = true;
481
+ i++;
482
+ continue;
483
+ }
484
+ if (ch === "{") {
485
+ depth++;
486
+ i++;
487
+ continue;
488
+ }
489
+ if (ch === "}") {
490
+ depth--;
491
+ i++;
492
+ continue;
493
+ }
494
+ if (depth === 0 && ch === "/" && i + 1 < source.length && source[i + 1] === ">") {
495
+ const fullMatch = source.substring(start, i + 2);
496
+ const propsStr = source.substring(afterTag, i).trim();
497
+ results.push({ fullMatch, propsStr, index: start });
498
+ found = true;
499
+ searchFrom = i + 2;
500
+ break;
501
+ }
502
+ if (depth === 0 && ch === ">") {
503
+ searchFrom = i + 1;
504
+ found = true;
505
+ break;
506
+ }
507
+ i++;
508
+ }
509
+ if (!found) break;
510
+ }
511
+ return results;
512
+ }
513
+ function ardoCodeBlockPlugin(markdownConfig) {
514
+ return {
515
+ name: "ardo:codeblock-highlight",
516
+ enforce: "pre",
517
+ async transform(code, id) {
518
+ if (!/\.[jt]sx$/.test(id)) return;
519
+ if (!code.includes("CodeBlock")) return;
520
+ if (id.includes("node_modules")) return;
521
+ let result = code;
522
+ let offset = 0;
523
+ const propMatches = findSelfClosingCodeBlocks(code);
524
+ for (const match of propMatches) {
525
+ const { fullMatch, propsStr } = match;
526
+ const codeMatch = propsStr.match(/\bcode="((?:[^"\\]|\\.)*)"/s) || propsStr.match(/\bcode=\{\s*"((?:[^"\\]|\\.)*)"\s*\}/s) || propsStr.match(/\bcode=\{\s*'((?:[^'\\]|\\.)*)'\s*\}/s);
527
+ if (!codeMatch) continue;
528
+ const langMatch = propsStr.match(/\blanguage="([^"]*)"/) || propsStr.match(/\blanguage=\{"([^"]*)"\}/) || propsStr.match(/\blanguage=\{'([^']*)'\}/);
529
+ if (!langMatch) continue;
530
+ if (propsStr.includes("__html")) continue;
531
+ const codeContent = codeMatch[1].replace(/\\n/g, "\n").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
532
+ const language = langMatch[1];
533
+ try {
534
+ const html = await highlightCode(codeContent, language, {
535
+ theme: markdownConfig?.theme
536
+ });
537
+ const escapedHtml = JSON.stringify(html);
538
+ const newPropsStr = `__html={${escapedHtml}} ` + propsStr;
539
+ const newFullMatch = fullMatch.replace(propsStr, newPropsStr);
540
+ result = result.slice(0, match.index + offset) + newFullMatch + result.slice(match.index + offset + fullMatch.length);
541
+ offset += newFullMatch.length - fullMatch.length;
542
+ } catch {
543
+ }
544
+ }
545
+ const childrenRegex = /<CodeBlock\s+([^>]*?)>([\s\S]*?)<\/CodeBlock>/g;
546
+ offset = result.length - code.length;
547
+ let regexMatch;
548
+ while ((regexMatch = childrenRegex.exec(code)) !== null) {
549
+ const fullMatch = regexMatch[0];
550
+ const propsStr = regexMatch[1];
551
+ let rawChildren = regexMatch[2];
552
+ const langMatch = propsStr.match(/\blanguage="([^"]*)"/) || propsStr.match(/\blanguage=\{"([^"]*)"\}/) || propsStr.match(/\blanguage=\{'([^']*)'\}/);
553
+ if (!langMatch) continue;
554
+ if (propsStr.includes("__html")) continue;
555
+ const templateMatch = rawChildren.match(/^\s*\{`([\s\S]*)`\}\s*$/);
556
+ if (templateMatch) {
557
+ rawChildren = templateMatch[1];
558
+ }
559
+ const codeContent = outdent(rawChildren);
560
+ const language = langMatch[1];
561
+ try {
562
+ const html = await highlightCode(codeContent, language, {
563
+ theme: markdownConfig?.theme
564
+ });
565
+ const escapedHtml = JSON.stringify(html);
566
+ const escapedCode = JSON.stringify(codeContent);
567
+ const newTag = `<CodeBlock __html={${escapedHtml}} code={${escapedCode}} ${propsStr} />`;
568
+ result = result.slice(0, regexMatch.index + offset) + newTag + result.slice(regexMatch.index + offset + fullMatch.length);
569
+ offset += newTag.length - fullMatch.length;
570
+ } catch {
571
+ }
572
+ }
573
+ if (result !== code) {
574
+ return { code: result, map: null };
575
+ }
576
+ }
577
+ };
578
+ }
579
+
549
580
  // src/vite/plugin.ts
550
581
  function findPackageRoot(cwd) {
551
582
  let dir = path2.resolve(cwd);
@@ -803,6 +834,7 @@ export const meta = () => [${metaEntries.join(", ")}];
803
834
  };
804
835
  plugins.unshift(typedocPlugin);
805
836
  }
837
+ plugins.push(ardoCodeBlockPlugin(pressConfig.markdown));
806
838
  const themeConfig = pressConfig.markdown?.theme ?? defaultMarkdownConfig.theme;
807
839
  const hasThemeObject = themeConfig && typeof themeConfig === "object" && "light" in themeConfig;
808
840
  const lineNumbers = pressConfig.markdown?.lineNumbers || false;
@@ -823,8 +855,6 @@ export const meta = () => [${metaEntries.join(", ")}];
823
855
  remarkFrontmatter,
824
856
  [remarkMdxFrontmatter, { name: "frontmatter" }],
825
857
  remarkGfm,
826
- remarkDirective,
827
- remarkContainersMdx,
828
858
  remarkCodeMeta
829
859
  ],
830
860
  rehypePlugins: [[rehypeShiki, shikiOptions]],
@@ -979,20 +1009,19 @@ import { unified } from "unified";
979
1009
  import remarkParse from "remark-parse";
980
1010
  import remarkGfm2 from "remark-gfm";
981
1011
  import remarkFrontmatter2 from "remark-frontmatter";
982
- import remarkDirective2 from "remark-directive";
983
1012
  import remarkRehype from "remark-rehype";
984
1013
  import rehypeStringify from "rehype-stringify";
985
1014
  import matter2 from "gray-matter";
986
1015
 
987
1016
  // src/markdown/toc.ts
988
- import { visit as visit3 } from "unist-util-visit";
1017
+ import { visit as visit2 } from "unist-util-visit";
989
1018
  function remarkExtractToc(options) {
990
1019
  const { tocExtraction, levels } = options;
991
1020
  const [minLevel, maxLevel] = levels;
992
1021
  return function(tree) {
993
1022
  const headings = [];
994
1023
  let headingIndex = 0;
995
- visit3(tree, "heading", (node) => {
1024
+ visit2(tree, "heading", (node) => {
996
1025
  if (node.depth < minLevel || node.depth > maxLevel) {
997
1026
  return;
998
1027
  }
@@ -1058,7 +1087,7 @@ function buildTocTree(headings, _minLevel) {
1058
1087
  }
1059
1088
 
1060
1089
  // src/markdown/links.ts
1061
- import { visit as visit4 } from "unist-util-visit";
1090
+ import { visit as visit3 } from "unist-util-visit";
1062
1091
  function rehypeLinks(options) {
1063
1092
  const { basePath } = options;
1064
1093
  const normalizedBase = basePath === "/" ? "" : basePath.replace(/\/$/, "");
@@ -1066,7 +1095,7 @@ function rehypeLinks(options) {
1066
1095
  if (!normalizedBase) {
1067
1096
  return;
1068
1097
  }
1069
- visit4(tree, "element", (node) => {
1098
+ visit3(tree, "element", (node) => {
1070
1099
  if (node.tagName === "a") {
1071
1100
  const href = node.properties?.href;
1072
1101
  if (typeof href === "string") {
@@ -1086,7 +1115,7 @@ async function transformMarkdown(content, config, options = {}) {
1086
1115
  const { basePath = "/", highlighter: providedHighlighter } = options;
1087
1116
  const tocExtraction = { toc: [] };
1088
1117
  const highlighter = providedHighlighter ?? await createShikiHighlighter(config);
1089
- const processor = unified().use(remarkParse).use(remarkFrontmatter2, ["yaml"]).use(remarkGfm2).use(remarkDirective2).use(remarkContainers).use(remarkExtractToc, { tocExtraction, levels: config.toc?.level ?? [2, 3] }).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeShikiFromHighlighter, { highlighter, config }).use(rehypeLinks, { basePath }).use(rehypeStringify, { allowDangerousHtml: true });
1118
+ const processor = unified().use(remarkParse).use(remarkFrontmatter2, ["yaml"]).use(remarkGfm2).use(remarkExtractToc, { tocExtraction, levels: config.toc?.level ?? [2, 3] }).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeShikiFromHighlighter, { highlighter, config }).use(rehypeLinks, { basePath }).use(rehypeStringify, { allowDangerousHtml: true });
1090
1119
  if (config.remarkPlugins) {
1091
1120
  for (const plugin of config.remarkPlugins) {
1092
1121
  processor.use(plugin);
@@ -1285,6 +1314,7 @@ function normalizePath(p) {
1285
1314
  }
1286
1315
 
1287
1316
  export {
1317
+ highlightCode,
1288
1318
  createShikiHighlighter,
1289
1319
  ardoRoutesPlugin,
1290
1320
  detectGitHubBasename,
@@ -1297,4 +1327,4 @@ export {
1297
1327
  getPageDataForRoute,
1298
1328
  generateSidebar2 as generateSidebar
1299
1329
  };
1300
- //# sourceMappingURL=chunk-WSEWAHW3.js.map
1330
+ //# sourceMappingURL=chunk-DEHRVW7C.js.map