mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-18 19:30:27 +08:00
feat(markdown): mermaid diagram support
This commit is contained in:
Generated
+1150
-3
File diff suppressed because it is too large
Load Diff
@@ -78,6 +78,7 @@
|
||||
"marked-linkify-it": "^3.1.15",
|
||||
"md5": "^2.3.0",
|
||||
"svelte": "^5.55.4",
|
||||
"mermaid": "^11.14.0",
|
||||
"twemoji": "^14.0.2",
|
||||
"zod": "^4.3.6"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import matter from "@radicle/gray-matter";
|
||||
import { afterUpdate } from "svelte";
|
||||
import { toDom } from "hast-util-to-dom";
|
||||
import mermaid from "mermaid";
|
||||
|
||||
import * as router from "@app/lib/router";
|
||||
import * as modal from "@app/lib/modal";
|
||||
@@ -100,6 +101,12 @@
|
||||
}
|
||||
|
||||
afterUpdate(async () => {
|
||||
// Initialize mermaid with error handling configuration
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
securityLevel: 'loose',
|
||||
});
|
||||
|
||||
for (const e of container.querySelectorAll("a")) {
|
||||
try {
|
||||
const url = new URL(e.href);
|
||||
@@ -210,7 +217,7 @@
|
||||
|
||||
// Replaces code blocks in the background with highlighted code.
|
||||
const prefix = "language-";
|
||||
const nodes = Array.from(document.body.querySelectorAll("pre code"));
|
||||
const nodes = Array.from(container.querySelectorAll("pre code"));
|
||||
|
||||
const treeChanges: Promise<void>[] = [];
|
||||
|
||||
@@ -231,8 +238,36 @@
|
||||
);
|
||||
if (!className) continue;
|
||||
|
||||
const language = className.slice(prefix.length);
|
||||
|
||||
// Handle mermaid diagrams
|
||||
if (language === 'mermaid') {
|
||||
const mermaidCode = node.textContent || '';
|
||||
try {
|
||||
// First, validate the syntax without rendering
|
||||
const isValid = await mermaid.parse(mermaidCode, { suppressErrors: true });
|
||||
|
||||
if (isValid) {
|
||||
// Generate a unique id for this diagram
|
||||
const diagramId = `mermaid-${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
// Try to render the mermaid diagram
|
||||
const { svg } = await mermaid.render(diagramId, mermaidCode);
|
||||
|
||||
// Replace the code block with the rendered diagram
|
||||
const mermaidDiv = document.createElement('div');
|
||||
mermaidDiv.innerHTML = svg;
|
||||
mermaidDiv.classList.add('mermaid-diagram');
|
||||
preWrapper.replaceWith(mermaidDiv);
|
||||
}
|
||||
} catch (error) {
|
||||
// On any error, keep the raw text visible (do nothing, let it render as code)
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
treeChanges.push(
|
||||
highlight(node.textContent ?? "", className.slice(prefix.length))
|
||||
highlight(node.textContent ?? "", language)
|
||||
.then(tree => {
|
||||
if (tree) {
|
||||
node.replaceChildren(toDom(tree, { fragment: true }));
|
||||
@@ -500,6 +535,16 @@
|
||||
.markdown :global(dl dd) {
|
||||
margin: 0 0 0 2rem;
|
||||
}
|
||||
|
||||
.markdown :global(.mermaid-diagram) {
|
||||
margin: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.markdown :global(.mermaid-diagram svg) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
{#if frontMatter && frontMatter.length > 0}
|
||||
|
||||
BIN
Binary file not shown.
@@ -125,6 +125,23 @@ test("math", async ({ page }) => {
|
||||
await expect(page).toHaveScreenshot({ fullPage: true });
|
||||
});
|
||||
|
||||
test("mermaid diagram", async ({ page }) => {
|
||||
await page.goto(`${markdownUrl}/tree/main/mermaid.md`, {
|
||||
waitUntil: "networkidle",
|
||||
});
|
||||
await expect(page.locator(".mermaid-diagram svg")).toBeVisible();
|
||||
await expect(page).toHaveScreenshot({ fullPage: true });
|
||||
});
|
||||
|
||||
test("broken mermaid falls back to code block", async ({ page }) => {
|
||||
await page.goto(`${markdownUrl}/tree/main/mermaid-broken.md`, {
|
||||
waitUntil: "networkidle",
|
||||
});
|
||||
await expect(page.locator("pre code.language-mermaid")).toBeVisible();
|
||||
await expect(page.locator(".mermaid-diagram")).toHaveCount(0);
|
||||
await expect(page).toHaveScreenshot({ fullPage: true });
|
||||
});
|
||||
|
||||
test("relative image not able to being loaded", async ({ page }) => {
|
||||
await page.goto(`${markdownUrl}/tree/main/loading-image.md`, {
|
||||
waitUntil: "networkidle",
|
||||
|
||||
Reference in New Issue
Block a user