lildocs 0.1.16 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -12,7 +12,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
12
12
  import markedShiki from "marked-shiki";
13
13
  import { bundledThemes, codeToHtml } from "shiki";
14
14
  import { decodeHTML } from "entities";
15
- import { renderMermaidSVG } from "beautiful-mermaid";
15
+ import { fromShikiTheme, renderMermaidSVG } from "beautiful-mermaid";
16
16
  import os from "node:os";
17
17
  import { generateMarkdownForModule } from "exports-md";
18
18
  import { clampGamut, converter, formatHex, parse, wcagContrast } from "culori";
@@ -914,10 +914,15 @@ function escapeHtml$1(value) {
914
914
  //#endregion
915
915
  //#region src/core/mermaid.ts
916
916
  async function createMermaidRenderer(options) {
917
+ const lightColors = await loadShikiColors(options.themeConfig.light);
918
+ const darkColors = options.themeConfig.dark ? await loadShikiColors(options.themeConfig.dark) : void 0;
919
+ const colorKeys = sharedColorKeys(lightColors, darkColors);
920
+ const renderOptions = toRenderOptions(colorKeys, options.themeConfig.fontFamily);
917
921
  return {
922
+ css: toThemeCss(lightColors, darkColors, colorKeys),
918
923
  async render(source, idHint) {
919
924
  try {
920
- const svg = addImageRole(renderMermaidSVG(source, toRenderOptions(options.themeConfig)));
925
+ const svg = addImageRole(renderMermaidSVG(source, renderOptions));
921
926
  return `<figure class="mermaidDiagram" id="${escapeHtml(idHint)}">${svg}</figure>`;
922
927
  } catch (error) {
923
928
  throw new LildocsError(`Failed to render Mermaid diagram ${idHint}: ${errorMessage$1(error)}`);
@@ -926,17 +931,51 @@ async function createMermaidRenderer(options) {
926
931
  async close() {}
927
932
  };
928
933
  }
929
- function toRenderOptions(themeConfig) {
934
+ const optionalColorKeys = [
935
+ "line",
936
+ "accent",
937
+ "muted",
938
+ "surface",
939
+ "border"
940
+ ];
941
+ async function loadShikiColors(themeName) {
942
+ const themeLoader = bundledThemes[themeName];
943
+ if (!themeLoader) throw new LildocsError(`Unknown bundled Shiki theme: ${themeName}`);
944
+ const themeModule = await themeLoader();
945
+ return fromShikiTheme("default" in themeModule ? themeModule.default : themeModule);
946
+ }
947
+ function sharedColorKeys(light, dark) {
948
+ return optionalColorKeys.filter((key) => light[key] !== void 0 && (!dark || dark[key] !== void 0));
949
+ }
950
+ function toRenderOptions(colorKeys, fontFamily) {
930
951
  return {
931
- bg: themeConfig.bg,
932
- fg: themeConfig.fg,
933
- accent: themeConfig.accent,
934
- muted: themeConfig.muted,
935
- surface: themeConfig.surface,
936
- border: themeConfig.border,
937
- font: themeConfig.fontFamily
952
+ bg: "var(--ld-mermaid-bg)",
953
+ fg: "var(--ld-mermaid-fg)",
954
+ ...Object.fromEntries(colorKeys.map((key) => [key, `var(--ld-mermaid-${key})`])),
955
+ font: fontFamily
938
956
  };
939
957
  }
958
+ function toThemeCss(light, dark, colorKeys) {
959
+ const lightVariables = toCssVariables(light, colorKeys, " ");
960
+ const darkVariables = dark ? toCssVariables(dark, colorKeys, " ") : void 0;
961
+ return `.mermaidDiagram {
962
+ ${lightVariables}
963
+ }${darkVariables ? `
964
+
965
+ @media (prefers-color-scheme: dark) {
966
+ .mermaidDiagram {
967
+ ${darkVariables}
968
+ }
969
+ }` : ""}
970
+ `;
971
+ }
972
+ function toCssVariables(colors, colorKeys, indent) {
973
+ return [
974
+ `${indent}--ld-mermaid-bg: ${colors.bg};`,
975
+ `${indent}--ld-mermaid-fg: ${colors.fg};`,
976
+ ...colorKeys.map((key) => `${indent}--ld-mermaid-${key}: ${colors[key]};`)
977
+ ].join("\n");
978
+ }
940
979
  function addImageRole(svg) {
941
980
  return svg.replace("<svg", "<svg role=\"img\"");
942
981
  }
@@ -1336,12 +1375,8 @@ function resolveBackgroundOptions(options) {
1336
1375
  function themeToMermaidConfig(theme, fontOverrides = {}) {
1337
1376
  const fonts = resolveThemeFonts(theme.light, fontOverrides);
1338
1377
  return {
1339
- bg: theme.light.color.background,
1340
- fg: theme.light.color.text,
1341
- accent: theme.light.color.link,
1342
- muted: theme.light.color.mutedText,
1343
- surface: theme.light.color.codeBackground,
1344
- border: theme.light.color.border,
1378
+ light: theme.light.shiki?.theme ?? "github-light",
1379
+ dark: theme.dark?.shiki?.theme,
1345
1380
  fontFamily: fonts.body
1346
1381
  };
1347
1382
  }
@@ -1668,13 +1703,13 @@ async function buildSite(options) {
1668
1703
  defaultText: packageName,
1669
1704
  favicon: configOptions.favicon
1670
1705
  });
1671
- const css = `${resolveThemeFontImports(theme, configOptions.fonts)}${fontResolution.css}${themeToCssVariables(theme, fontResolution.themeFonts, configOptions.link, configOptions.navigation)}\n${backgroundResolution.css}${logoResolution.css}${baseCss}`;
1706
+ const mermaid = await createMermaidRenderer({ themeConfig: themeToMermaidConfig(theme, fontResolution.themeFonts) });
1707
+ const css = `${resolveThemeFontImports(theme, configOptions.fonts)}${fontResolution.css}${themeToCssVariables(theme, fontResolution.themeFonts, configOptions.link, configOptions.navigation)}\n${mermaid.css}${backgroundResolution.css}${logoResolution.css}${baseCss}`;
1672
1708
  const assets = [
1673
1709
  ...fontResolution.assets,
1674
1710
  ...backgroundResolution.assets,
1675
1711
  ...logoResolution.assets
1676
1712
  ];
1677
- const mermaid = await createMermaidRenderer({ themeConfig: themeToMermaidConfig(theme, fontResolution.themeFonts) });
1678
1713
  let frontendRenderer;
1679
1714
  try {
1680
1715
  await rm(outDir, {
@@ -271,6 +271,10 @@ html.is-animating .transition-scale {
271
271
  margin-block: 1em;
272
272
  }
273
273
 
274
+ .content article > div > ul {
275
+ margin-bottom: 2em;
276
+ }
277
+
274
278
  .content :first-child {
275
279
  margin-top: 0;
276
280
  }
@@ -524,17 +528,20 @@ html.is-animating .transition-scale {
524
528
  border-collapse: collapse;
525
529
  }
526
530
 
527
- .tableFrame {
531
+ .tableFrame,
532
+ .mermaidFrame {
528
533
  margin-block: 1em;
529
534
  }
530
535
 
531
- .tableToolbar {
536
+ .tableToolbar,
537
+ .mermaidToolbar {
532
538
  display: flex;
533
539
  justify-content: flex-end;
534
540
  margin-top: 8px;
535
541
  }
536
542
 
537
- .tableViewport {
543
+ .tableViewport,
544
+ .mermaidViewport {
538
545
  max-width: 100%;
539
546
  overflow-x: auto;
540
547
  }
@@ -544,7 +551,12 @@ html.is-animating .transition-scale {
544
551
  margin: 0;
545
552
  }
546
553
 
554
+ .content .mermaidViewport .mermaidDiagram {
555
+ margin: 0;
556
+ }
557
+
547
558
  .tableExpandButton,
559
+ .mermaidExpandButton,
548
560
  .tableFullscreenClose {
549
561
  display: inline-flex;
550
562
  align-items: center;
@@ -562,19 +574,23 @@ html.is-animating .transition-scale {
562
574
  }
563
575
 
564
576
  .tableExpandButton .ti,
577
+ .mermaidExpandButton .ti,
565
578
  .tableFullscreenClose .ti {
566
579
  font-size: 18px;
567
580
  }
568
581
 
569
582
  .tableExpandButton:hover,
570
583
  .tableExpandButton:focus-visible,
584
+ .mermaidExpandButton:hover,
585
+ .mermaidExpandButton:focus-visible,
571
586
  .tableFullscreenClose:hover,
572
587
  .tableFullscreenClose:focus-visible {
573
588
  border-color: var(--ld-color-link);
574
589
  color: var(--ld-color-link);
575
590
  }
576
591
 
577
- .tableFullscreenDialog {
592
+ .tableFullscreenDialog,
593
+ .mermaidFullscreenDialog {
578
594
  width: 100vw;
579
595
  max-width: none;
580
596
  height: 100vh;
@@ -587,7 +603,8 @@ html.is-animating .transition-scale {
587
603
  font-family: var(--ld-font-body);
588
604
  }
589
605
 
590
- .tableFullscreenDialog::backdrop {
606
+ .tableFullscreenDialog::backdrop,
607
+ .mermaidFullscreenDialog::backdrop {
591
608
  background: color-mix(in srgb, black 35%, transparent);
592
609
  }
593
610
 
@@ -613,6 +630,31 @@ html.is-animating .transition-scale {
613
630
  font-size: 105%;
614
631
  }
615
632
 
633
+ .mermaidFullscreenViewport {
634
+ display: grid;
635
+ min-width: 0;
636
+ min-height: 0;
637
+ overflow: auto;
638
+ padding: 20px;
639
+ place-items: center;
640
+ }
641
+
642
+ .mermaidFullscreenViewport .mermaidDiagram {
643
+ display: flex;
644
+ width: 100%;
645
+ height: 100%;
646
+ margin: 0;
647
+ align-items: center;
648
+ justify-content: center;
649
+ }
650
+
651
+ .mermaidFullscreenViewport .mermaidDiagram svg {
652
+ width: 100%;
653
+ height: 100%;
654
+ max-width: 100%;
655
+ max-height: 100%;
656
+ }
657
+
616
658
  .tableFullscreenViewport table {
617
659
  display: table;
618
660
  width: max-content;
@@ -621,7 +663,8 @@ html.is-animating .transition-scale {
621
663
  border-collapse: collapse;
622
664
  }
623
665
 
624
- html.tableFullscreenOpen {
666
+ html.tableFullscreenOpen,
667
+ html.mermaidFullscreenOpen {
625
668
  overflow: hidden;
626
669
  }
627
670
 
@@ -2,9 +2,11 @@ let activeDialog: HTMLDialogElement | undefined;
2
2
 
3
3
  export function initTableViewer() {
4
4
  enhanceTables();
5
+ enhanceMermaidDiagrams();
5
6
  document.addEventListener("lildocs:page-view", () => {
6
7
  activeDialog?.close();
7
8
  enhanceTables();
9
+ enhanceMermaidDiagrams();
8
10
  });
9
11
  }
10
12
 
@@ -31,7 +33,16 @@ function enhanceTables() {
31
33
  const viewport = document.createElement("div");
32
34
  viewport.className = "tableViewport";
33
35
 
34
- button.addEventListener("click", () => openTableDialog(table, button));
36
+ button.addEventListener("click", () =>
37
+ openFullscreenDialog(table, button, {
38
+ dialogClass: "tableFullscreenDialog",
39
+ dialogLabel: "Full screen table",
40
+ title: table.querySelector("caption")?.textContent?.trim() || "Table",
41
+ closeLabel: "Minimize table",
42
+ viewportClass: "tableFullscreenViewport content",
43
+ openClass: "tableFullscreenOpen",
44
+ }),
45
+ );
35
46
  table.before(frame);
36
47
  toolbar.append(button);
37
48
  viewport.append(table);
@@ -39,29 +50,81 @@ function enhanceTables() {
39
50
  }
40
51
  }
41
52
 
42
- function openTableDialog(table: HTMLTableElement, opener: HTMLButtonElement) {
53
+ function enhanceMermaidDiagrams() {
54
+ const diagrams = document.querySelectorAll<HTMLElement>(
55
+ ".content article .mermaidDiagram",
56
+ );
57
+ for (const diagram of Array.from(diagrams)) {
58
+ if (diagram.closest(".mermaidFrame")) {
59
+ continue;
60
+ }
61
+
62
+ const frame = document.createElement("div");
63
+ frame.className = "mermaidFrame";
64
+ const toolbar = document.createElement("div");
65
+ toolbar.className = "mermaidToolbar";
66
+ const button = document.createElement("button");
67
+ button.type = "button";
68
+ button.className = "mermaidExpandButton";
69
+ button.title = "Expand diagram";
70
+ button.setAttribute("aria-label", "View diagram in full screen");
71
+ button.innerHTML =
72
+ '<span class="ti ti-arrows-maximize" aria-hidden="true"></span>';
73
+ const viewport = document.createElement("div");
74
+ viewport.className = "mermaidViewport";
75
+
76
+ button.addEventListener("click", () =>
77
+ openFullscreenDialog(diagram, button, {
78
+ dialogClass: "mermaidFullscreenDialog",
79
+ dialogLabel: "Full screen diagram",
80
+ title: "Diagram",
81
+ closeLabel: "Minimize diagram",
82
+ viewportClass: "mermaidFullscreenViewport content",
83
+ openClass: "mermaidFullscreenOpen",
84
+ }),
85
+ );
86
+ diagram.before(frame);
87
+ toolbar.append(button);
88
+ viewport.append(diagram);
89
+ frame.append(viewport, toolbar);
90
+ }
91
+ }
92
+
93
+ type FullscreenDialogOptions = {
94
+ dialogClass: string;
95
+ dialogLabel: string;
96
+ title: string;
97
+ closeLabel: string;
98
+ viewportClass: string;
99
+ openClass: string;
100
+ };
101
+
102
+ function openFullscreenDialog(
103
+ source: HTMLElement,
104
+ opener: HTMLButtonElement,
105
+ options: FullscreenDialogOptions,
106
+ ) {
43
107
  activeDialog?.close();
44
108
 
45
109
  const dialog = document.createElement("dialog");
46
- dialog.className = "tableFullscreenDialog";
47
- dialog.setAttribute("aria-label", "Full screen table");
110
+ dialog.className = options.dialogClass;
111
+ dialog.setAttribute("aria-label", options.dialogLabel);
48
112
  const content = document.createElement("div");
49
113
  content.className = "tableFullscreenContent";
50
114
  const header = document.createElement("header");
51
115
  header.className = "tableFullscreenHeader";
52
116
  const title = document.createElement("strong");
53
- title.textContent =
54
- table.querySelector("caption")?.textContent?.trim() || "Table";
117
+ title.textContent = options.title;
55
118
  const closeButton = document.createElement("button");
56
119
  closeButton.type = "button";
57
120
  closeButton.className = "tableFullscreenClose";
58
- closeButton.title = "Minimize table";
59
- closeButton.setAttribute("aria-label", "Minimize table");
121
+ closeButton.title = options.closeLabel;
122
+ closeButton.setAttribute("aria-label", options.closeLabel);
60
123
  closeButton.innerHTML =
61
124
  '<span class="ti ti-arrows-minimize" aria-hidden="true"></span>';
62
125
  const viewport = document.createElement("div");
63
- viewport.className = "tableFullscreenViewport content";
64
- viewport.append(table.cloneNode(true));
126
+ viewport.className = options.viewportClass;
127
+ viewport.append(source.cloneNode(true));
65
128
 
66
129
  closeButton.addEventListener("click", () => dialog.close());
67
130
  dialog.addEventListener("cancel", (event) => {
@@ -76,7 +139,7 @@ function openTableDialog(table: HTMLTableElement, opener: HTMLButtonElement) {
76
139
  dialog.addEventListener(
77
140
  "close",
78
141
  () => {
79
- document.documentElement.classList.remove("tableFullscreenOpen");
142
+ document.documentElement.classList.remove(options.openClass);
80
143
  dialog.remove();
81
144
  if (activeDialog === dialog) {
82
145
  activeDialog = undefined;
@@ -92,7 +155,7 @@ function openTableDialog(table: HTMLTableElement, opener: HTMLButtonElement) {
92
155
  content.append(header, viewport);
93
156
  dialog.append(content);
94
157
  document.body.append(dialog);
95
- document.documentElement.classList.add("tableFullscreenOpen");
158
+ document.documentElement.classList.add(options.openClass);
96
159
  activeDialog = dialog;
97
160
  dialog.showModal();
98
161
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lildocs",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "A lightweight CLI that turns Markdown docs into a static searchable documentation site.",
5
5
  "homepage": "https://aleclarson.github.io/lildocs/",
6
6
  "repository": {