@affanhamid/markdown-renderer 2.0.1 → 2.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.
package/dist/index.cjs CHANGED
@@ -820,6 +820,13 @@ function renderMarkdownToHtml(markdown, options) {
820
820
  const isExecutable = options?.executableLanguages && language && options.executableLanguages.includes(language.toLowerCase());
821
821
  const currentIndex = codeBlockIndex;
822
822
  codeBlockIndex++;
823
+ if (language === "mermaid") {
824
+ parts.push(
825
+ `<div class="md-mermaid" data-mermaid-code="${escapeHtml(codeContent)}"><pre style="overflow-x:auto;background:#f7f7f7;padding:0.75rem;border-radius:0.375rem;font-size:0.8rem;color:#666"><code>${escapedCode}</code></pre></div>`
826
+ );
827
+ i++;
828
+ break;
829
+ }
823
830
  if (isExecutable) {
824
831
  parts.push(
825
832
  `<div class="md-code-block" data-language="${escapedLang}" data-code-index="${currentIndex}" data-executable="true"><div class="md-code-block-header" style="display:flex;align-items:center;justify-content:space-between;padding:0.25rem 0.75rem;background:#f0f0f0;border-radius:0.375rem 0.375rem 0 0;border:1px solid #e0e0e0;border-bottom:none"><span style="font-size:0.75rem;color:#666;font-family:monospace">${escapedLang}</span><button class="md-run-btn" data-code-index="${currentIndex}" style="padding:0.2rem 0.6rem;font-size:0.75rem;border-radius:0.25rem;border:1px solid #ccc;background:#fff;cursor:pointer;font-family:inherit">Run</button></div><pre style="overflow-x:auto;border-radius:0 0 0.375rem 0.375rem;background:#f7f7f7;color:#1f2937;padding:0.75rem;font-size:0.875rem;margin:0;border:1px solid #e0e0e0;border-top:none"><code class="language-${escapedLang}" data-executable="true">${escapedCode}</code></pre><div class="md-code-output" data-output-for="${currentIndex}" style="display:none"></div></div>`
@@ -847,6 +854,28 @@ function renderMarkdownToHtml(markdown, options) {
847
854
  i++;
848
855
  continue;
849
856
  }
857
+ const calloutMatch = trimmed.match(/^\\begin\{callout\}\{(\w+)\}$/);
858
+ if (calloutMatch && calloutMatch[1]) {
859
+ const color = calloutMatch[1];
860
+ const contentLines = [];
861
+ i++;
862
+ while (i < lines.length) {
863
+ const calloutLine = (lines[i] || "").trim();
864
+ if (calloutLine === "\\end{callout}") {
865
+ i++;
866
+ break;
867
+ }
868
+ contentLines.push(lines[i] || "");
869
+ i++;
870
+ }
871
+ const innerHtml = renderMarkdownToHtml(contentLines.join("\n"), options);
872
+ const innerMatch = innerHtml.match(/<div class="prose[^"]*">(.*)<\/div>/s);
873
+ const innerContent = innerMatch?.[1] ?? escapeHtml(contentLines.join("\n"));
874
+ parts.push(
875
+ `<div class="md-callout border-${color}-200 bg-${color}-50 text-${color}-900 dark:border-${color}-700/40 dark:bg-${color}-900/10 dark:text-${color}-200 my-4 rounded-lg border px-4 py-3 text-sm leading-relaxed [&>p]:mb-0 [&>p:last-child]:mb-0">${innerContent}</div>`
876
+ );
877
+ continue;
878
+ }
850
879
  const imageMatch = trimmed.match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
851
880
  if (imageMatch && imageMatch[2]) {
852
881
  const alt = escapeHtml(imageMatch[1] ?? "");
@@ -1038,6 +1067,42 @@ var MarkdownRenderer = ({
1038
1067
  );
1039
1068
  };
1040
1069
  }, [html, handleRun]);
1070
+ (0, import_react.useEffect)(() => {
1071
+ const container = containerRef.current;
1072
+ if (!container) return;
1073
+ const mermaidBlocks = container.querySelectorAll(".md-mermaid");
1074
+ if (mermaidBlocks.length === 0) return;
1075
+ let alive = true;
1076
+ void (async () => {
1077
+ try {
1078
+ const mermaid = (await import("mermaid")).default;
1079
+ mermaid.initialize({
1080
+ startOnLoad: false,
1081
+ securityLevel: "antiscript",
1082
+ theme: "neutral",
1083
+ fontFamily: "ui-sans-serif, system-ui, sans-serif",
1084
+ fontSize: 13,
1085
+ htmlLabels: false,
1086
+ flowchart: { useMaxWidth: true, htmlLabels: false },
1087
+ sequence: { useMaxWidth: true }
1088
+ });
1089
+ for (const block of Array.from(mermaidBlocks)) {
1090
+ if (!alive) break;
1091
+ const code = block.getAttribute("data-mermaid-code") || "";
1092
+ const id = `mermaid-${Math.random().toString(36).slice(2, 9)}`;
1093
+ try {
1094
+ const { svg } = await mermaid.render(id, code.trim());
1095
+ block.innerHTML = `<div style="display:flex;justify-content:center;overflow:auto;padding:1rem">${svg}</div>`;
1096
+ } catch {
1097
+ }
1098
+ }
1099
+ } catch {
1100
+ }
1101
+ })();
1102
+ return () => {
1103
+ alive = false;
1104
+ };
1105
+ }, [html]);
1041
1106
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: containerRef, dangerouslySetInnerHTML: { __html: html } });
1042
1107
  };
1043
1108
  var markdown_renderer_default = MarkdownRenderer;
package/dist/index.js CHANGED
@@ -781,6 +781,13 @@ function renderMarkdownToHtml(markdown, options) {
781
781
  const isExecutable = options?.executableLanguages && language && options.executableLanguages.includes(language.toLowerCase());
782
782
  const currentIndex = codeBlockIndex;
783
783
  codeBlockIndex++;
784
+ if (language === "mermaid") {
785
+ parts.push(
786
+ `<div class="md-mermaid" data-mermaid-code="${escapeHtml(codeContent)}"><pre style="overflow-x:auto;background:#f7f7f7;padding:0.75rem;border-radius:0.375rem;font-size:0.8rem;color:#666"><code>${escapedCode}</code></pre></div>`
787
+ );
788
+ i++;
789
+ break;
790
+ }
784
791
  if (isExecutable) {
785
792
  parts.push(
786
793
  `<div class="md-code-block" data-language="${escapedLang}" data-code-index="${currentIndex}" data-executable="true"><div class="md-code-block-header" style="display:flex;align-items:center;justify-content:space-between;padding:0.25rem 0.75rem;background:#f0f0f0;border-radius:0.375rem 0.375rem 0 0;border:1px solid #e0e0e0;border-bottom:none"><span style="font-size:0.75rem;color:#666;font-family:monospace">${escapedLang}</span><button class="md-run-btn" data-code-index="${currentIndex}" style="padding:0.2rem 0.6rem;font-size:0.75rem;border-radius:0.25rem;border:1px solid #ccc;background:#fff;cursor:pointer;font-family:inherit">Run</button></div><pre style="overflow-x:auto;border-radius:0 0 0.375rem 0.375rem;background:#f7f7f7;color:#1f2937;padding:0.75rem;font-size:0.875rem;margin:0;border:1px solid #e0e0e0;border-top:none"><code class="language-${escapedLang}" data-executable="true">${escapedCode}</code></pre><div class="md-code-output" data-output-for="${currentIndex}" style="display:none"></div></div>`
@@ -808,6 +815,28 @@ function renderMarkdownToHtml(markdown, options) {
808
815
  i++;
809
816
  continue;
810
817
  }
818
+ const calloutMatch = trimmed.match(/^\\begin\{callout\}\{(\w+)\}$/);
819
+ if (calloutMatch && calloutMatch[1]) {
820
+ const color = calloutMatch[1];
821
+ const contentLines = [];
822
+ i++;
823
+ while (i < lines.length) {
824
+ const calloutLine = (lines[i] || "").trim();
825
+ if (calloutLine === "\\end{callout}") {
826
+ i++;
827
+ break;
828
+ }
829
+ contentLines.push(lines[i] || "");
830
+ i++;
831
+ }
832
+ const innerHtml = renderMarkdownToHtml(contentLines.join("\n"), options);
833
+ const innerMatch = innerHtml.match(/<div class="prose[^"]*">(.*)<\/div>/s);
834
+ const innerContent = innerMatch?.[1] ?? escapeHtml(contentLines.join("\n"));
835
+ parts.push(
836
+ `<div class="md-callout border-${color}-200 bg-${color}-50 text-${color}-900 dark:border-${color}-700/40 dark:bg-${color}-900/10 dark:text-${color}-200 my-4 rounded-lg border px-4 py-3 text-sm leading-relaxed [&>p]:mb-0 [&>p:last-child]:mb-0">${innerContent}</div>`
837
+ );
838
+ continue;
839
+ }
811
840
  const imageMatch = trimmed.match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
812
841
  if (imageMatch && imageMatch[2]) {
813
842
  const alt = escapeHtml(imageMatch[1] ?? "");
@@ -999,6 +1028,42 @@ var MarkdownRenderer = ({
999
1028
  );
1000
1029
  };
1001
1030
  }, [html, handleRun]);
1031
+ useEffect(() => {
1032
+ const container = containerRef.current;
1033
+ if (!container) return;
1034
+ const mermaidBlocks = container.querySelectorAll(".md-mermaid");
1035
+ if (mermaidBlocks.length === 0) return;
1036
+ let alive = true;
1037
+ void (async () => {
1038
+ try {
1039
+ const mermaid = (await import("mermaid")).default;
1040
+ mermaid.initialize({
1041
+ startOnLoad: false,
1042
+ securityLevel: "antiscript",
1043
+ theme: "neutral",
1044
+ fontFamily: "ui-sans-serif, system-ui, sans-serif",
1045
+ fontSize: 13,
1046
+ htmlLabels: false,
1047
+ flowchart: { useMaxWidth: true, htmlLabels: false },
1048
+ sequence: { useMaxWidth: true }
1049
+ });
1050
+ for (const block of Array.from(mermaidBlocks)) {
1051
+ if (!alive) break;
1052
+ const code = block.getAttribute("data-mermaid-code") || "";
1053
+ const id = `mermaid-${Math.random().toString(36).slice(2, 9)}`;
1054
+ try {
1055
+ const { svg } = await mermaid.render(id, code.trim());
1056
+ block.innerHTML = `<div style="display:flex;justify-content:center;overflow:auto;padding:1rem">${svg}</div>`;
1057
+ } catch {
1058
+ }
1059
+ }
1060
+ } catch {
1061
+ }
1062
+ })();
1063
+ return () => {
1064
+ alive = false;
1065
+ };
1066
+ }, [html]);
1002
1067
  return /* @__PURE__ */ jsx("div", { ref: containerRef, dangerouslySetInnerHTML: { __html: html } });
1003
1068
  };
1004
1069
  var markdown_renderer_default = MarkdownRenderer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@affanhamid/markdown-renderer",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Custom markdown renderer with KaTeX support",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -14,7 +14,7 @@
14
14
  }
15
15
  },
16
16
  "scripts": {
17
- "build": "tsup src/index.ts --format cjs,esm --dts --external react",
17
+ "build": "tsup src/index.ts --format cjs,esm --dts --external react --external mermaid",
18
18
  "test": "jest"
19
19
  },
20
20
  "dependencies": {
@@ -22,13 +22,20 @@
22
22
  "shiki": "^4.0.1"
23
23
  },
24
24
  "peerDependencies": {
25
- "react": ">=18"
25
+ "react": ">=18",
26
+ "mermaid": ">=10"
27
+ },
28
+ "peerDependenciesMeta": {
29
+ "mermaid": {
30
+ "optional": true
31
+ }
26
32
  },
27
33
  "devDependencies": {
28
34
  "@types/jest": "^30.0.0",
29
35
  "@types/katex": "^0.16.7",
30
36
  "@types/react": "^19.0.0",
31
37
  "jest": "^30.2.0",
38
+ "mermaid": "^11.12.3",
32
39
  "ts-jest": "^29.4.6",
33
40
  "tsup": "^8.0.0",
34
41
  "typescript": "^5.8.0"