Skip to content

Commit e631e29

Browse files
committed
feat: show play button isntead
1 parent bf057e6 commit e631e29

File tree

4 files changed

+176
-51
lines changed

4 files changed

+176
-51
lines changed

example/src/index.css

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,7 +1349,8 @@
13491349

13501350
.markdown-body .code-block-copy,
13511351
.markdown-body .code-block-collapse,
1352-
.markdown-body .code-block-maximize {
1352+
.markdown-body .code-block-maximize,
1353+
.markdown-body .code-block-play {
13531354
display: flex;
13541355
align-items: center;
13551356
justify-content: center;
@@ -1365,23 +1366,42 @@
13651366

13661367
.markdown-body .code-block-copy:hover,
13671368
.markdown-body .code-block-collapse:hover,
1368-
.markdown-body .code-block-maximize:hover {
1369+
.markdown-body .code-block-maximize:hover,
1370+
.markdown-body .code-block-play:hover {
13691371
color: var(--fgColor-default, #f0f6fc);
13701372
background-color: var(--bgColor-neutral-muted, #656c7633);
13711373
border-color: var(--borderColor-default, #3d444d);
13721374
}
13731375

13741376
.markdown-body .code-block-copy:active,
1375-
.markdown-body .code-block-collapse:active {
1377+
.markdown-body .code-block-collapse:active,
1378+
.markdown-body .code-block-play:active {
13761379
transform: scale(0.95);
13771380
}
13781381

13791382
.markdown-body .code-block-copy svg,
1380-
.markdown-body .code-block-collapse svg {
1383+
.markdown-body .code-block-collapse svg,
1384+
.markdown-body .code-block-maximize svg,
1385+
.markdown-body .code-block-play svg {
13811386
width: 1rem;
13821387
height: 1rem;
13831388
}
13841389

1390+
/* Visibility logic for Mermaid buttons */
1391+
.markdown-body
1392+
.code-block-wrapper[data-mermaid-status="unrendered"]
1393+
.code-block-maximize,
1394+
.markdown-body
1395+
.code-block-wrapper[data-mermaid-status="code"]
1396+
.code-block-maximize {
1397+
display: none;
1398+
}
1399+
1400+
/* Hide play button for non-mermaid blocks (which don't have data-mermaid-status) */
1401+
.markdown-body .code-block-wrapper:not([data-mermaid-status]) .code-block-play {
1402+
display: none;
1403+
}
1404+
13851405
/* Hide the language data span (it's only for JS to read) */
13861406
.markdown-body .code-lang-data {
13871407
display: none;
@@ -1416,14 +1436,16 @@
14161436

14171437
.markdown-body .code-block-copy,
14181438
.markdown-body .code-block-collapse,
1419-
.markdown-body .code-block-maximize {
1439+
.markdown-body .code-block-maximize,
1440+
.markdown-body .code-block-play {
14201441
color: var(--fgColor-muted, #59636e);
14211442
border-color: var(--borderColor-muted, #d1d9e0b3);
14221443
}
14231444

14241445
.markdown-body .code-block-copy:hover,
14251446
.markdown-body .code-block-collapse:hover,
1426-
.markdown-body .code-block-maximize:hover {
1447+
.markdown-body .code-block-maximize:hover,
1448+
.markdown-body .code-block-play:hover {
14271449
color: var(--fgColor-default, #1f2328);
14281450
background-color: var(--bgColor-neutral-muted, #818b981f);
14291451
border-color: var(--borderColor-default, #d1d9e0);
@@ -1582,8 +1604,7 @@
15821604
}
15831605

15841606
/* Mermaid Diagrams */
1585-
.markdown-body pre:has(code.language-mermaid),
1586-
.markdown-body pre[data-mermaid-processed="true"] {
1607+
.markdown-body pre[data-mermaid-status="rendered"] {
15871608
display: flex;
15881609
justify-content: center;
15891610
align-items: center;
@@ -1594,35 +1615,13 @@
15941615
overflow-x: auto;
15951616
}
15961617

1597-
.markdown-body pre:has(code.language-mermaid) svg,
1598-
.markdown-body pre[data-mermaid-processed="true"] svg {
1618+
.markdown-body pre[data-mermaid-status="rendered"] svg {
15991619
display: block;
16001620
margin: 0 auto;
16011621
max-width: 100%;
16021622
height: auto;
16031623
}
16041624

1605-
.mermaid-render-btn {
1606-
padding: 0.5rem 1rem;
1607-
background: var(--haxiom-accent-color);
1608-
color: var(--haxiom-fg-color);
1609-
border: none;
1610-
border-radius: 4px;
1611-
cursor: pointer;
1612-
font-size: 0.875rem;
1613-
font-weight: 500;
1614-
transition: background 0.2s;
1615-
}
1616-
1617-
.mermaid-render-btn:hover {
1618-
background: var(--haxiom-accent-color-hover);
1619-
}
1620-
1621-
.mermaid-render-btn:disabled {
1622-
background: var(--haxiom-accent-color-disabled);
1623-
cursor: not-allowed;
1624-
}
1625-
16261625
.mermaid-loading {
16271626
padding: 1rem;
16281627
color: var(--fgColor-muted, #6b7280);

src/components/MarkdownRenderer.tsx

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import {
22
Check,
33
ChevronsDownUp,
44
ChevronsUpDown,
5+
Code,
56
Copy,
67
Maximize,
8+
Play,
79
X,
810
} from "lucide-solid";
911
import init, { render_md, type Themes } from "markdown-renderer";
@@ -194,12 +196,40 @@ export const MarkdownRenderer: Component<MarkdownRendererProps> = (props) => {
194196
?.toLowerCase() === "mermaid";
195197

196198
if (isMermaid) {
199+
// Set initial status on wrapper if not already set
200+
const initialStatus = props.immediateRenderMermaid
201+
? "rendered"
202+
: "unrendered";
203+
const wrapperHtml = wrapper as HTMLElement;
204+
if (wrapperHtml && !wrapperHtml.dataset.mermaidStatus) {
205+
wrapperHtml.dataset.mermaidStatus = initialStatus;
206+
}
207+
197208
const maxBtn = document.createElement("button");
198209
maxBtn.className = "code-block-maximize";
199210
maxBtn.setAttribute("aria-label", "Show preview");
200211
const maxDispose = render(() => <Maximize size={ICON_SIZE} />, maxBtn);
201212
iconDisposeCallbacks.push(maxDispose);
202213
buttonContainer.appendChild(maxBtn);
214+
215+
// Create play/code toggle button
216+
const playBtn = document.createElement("button");
217+
playBtn.className = "code-block-play";
218+
playBtn.setAttribute(
219+
"aria-label",
220+
initialStatus === "rendered" ? "Show code" : "Render diagram",
221+
);
222+
const playDispose = render(
223+
() =>
224+
initialStatus === "rendered" ? (
225+
<Code size={ICON_SIZE} />
226+
) : (
227+
<Play size={ICON_SIZE} />
228+
),
229+
playBtn,
230+
);
231+
iconDisposeCallbacks.push(playDispose);
232+
buttonContainer.appendChild(playBtn);
203233
}
204234

205235
// Create collapse/expand button
@@ -264,6 +294,75 @@ export const MarkdownRenderer: Component<MarkdownRendererProps> = (props) => {
264294
return;
265295
}
266296

297+
// Handle play button (toggle between code and diagram)
298+
const playBtn = target.closest(
299+
".code-block-play",
300+
) as HTMLButtonElement | null;
301+
if (playBtn) {
302+
e.stopPropagation();
303+
const wrapper = playBtn.closest(".code-block-wrapper") as HTMLElement;
304+
if (!wrapper) return;
305+
306+
const mermaidPre = wrapper.querySelector("pre") as HTMLElement;
307+
const code =
308+
mermaidPre?.dataset.mermaidSource ||
309+
mermaidPre?.querySelector("code")?.textContent?.trim() ||
310+
"";
311+
312+
if (code && mermaidPre) {
313+
const currentStatus = wrapper.dataset.mermaidStatus;
314+
315+
if (currentStatus === "rendered") {
316+
// Toggle to show code
317+
mermaidPre.innerHTML = `<code>${code}</code>`;
318+
wrapper.dataset.mermaidStatus = "code";
319+
mermaidPre.dataset.mermaidStatus = "code";
320+
321+
// Update icon to Play
322+
playBtn.textContent = "";
323+
const playDispose = render(() => <Play size={ICON_SIZE} />, playBtn);
324+
iconDisposeCallbacks.push(playDispose);
325+
playBtn.setAttribute("aria-label", "Render diagram");
326+
playBtn.dataset.iconSet = "play";
327+
} else {
328+
// Render or toggle back to diagram
329+
playBtn.disabled = true;
330+
// Show loading state
331+
const originalContent = mermaidPre.innerHTML;
332+
mermaidPre.innerHTML =
333+
'<div class="mermaid-loading">⏳ Rendering diagram...</div>';
334+
335+
renderMermaid(code)
336+
.then((svg) => {
337+
mermaidPre.innerHTML = svg;
338+
wrapper.dataset.mermaidStatus = "rendered";
339+
mermaidPre.dataset.mermaidStatus = "rendered";
340+
mermaidPre.dataset.mermaidProcessed = "true";
341+
if (!mermaidPre.dataset.mermaidSource) {
342+
mermaidPre.dataset.mermaidSource = code;
343+
}
344+
345+
// Update icon to Code
346+
playBtn.textContent = "";
347+
const codeDispose = render(
348+
() => <Code size={ICON_SIZE} />,
349+
playBtn,
350+
);
351+
iconDisposeCallbacks.push(codeDispose);
352+
playBtn.setAttribute("aria-label", "Show code");
353+
playBtn.dataset.iconSet = "code";
354+
playBtn.disabled = false;
355+
})
356+
.catch((err) => {
357+
console.error("Manual Mermaid render error:", err);
358+
mermaidPre.innerHTML = originalContent;
359+
playBtn.disabled = false;
360+
});
361+
}
362+
}
363+
return;
364+
}
365+
267366
// Handle maximize button
268367
const maxBtn = target.closest(
269368
".code-block-maximize",
@@ -506,7 +605,38 @@ export const MarkdownRenderer: Component<MarkdownRendererProps> = (props) => {
506605
contentRef,
507606
props.immediateRenderMermaid ?? false,
508607
props.mermaidConfig,
509-
);
608+
).then(() => {
609+
// Sync wrapper status with pre status after block processing
610+
if (contentRef) {
611+
const pres = contentRef.querySelectorAll(
612+
"pre[data-mermaid-status]",
613+
);
614+
for (const pre of pres) {
615+
const wrapper = pre.closest(".code-block-wrapper") as HTMLElement;
616+
if (wrapper) {
617+
const status = (pre as HTMLElement).dataset.mermaidStatus;
618+
wrapper.dataset.mermaidStatus = status;
619+
620+
// Update toggle button icon if it exists
621+
if (status === "rendered") {
622+
const toggleBtn = wrapper.querySelector(
623+
".code-block-play",
624+
) as HTMLButtonElement;
625+
if (toggleBtn && toggleBtn.dataset.iconSet !== "code") {
626+
toggleBtn.textContent = "";
627+
const codeDispose = render(
628+
() => <Code size={ICON_SIZE} />,
629+
toggleBtn,
630+
);
631+
iconDisposeCallbacks.push(codeDispose);
632+
toggleBtn.setAttribute("aria-label", "Show code");
633+
toggleBtn.dataset.iconSet = "code";
634+
}
635+
}
636+
}
637+
}
638+
}
639+
});
510640

511641
// Observe content for size changes
512642
if (resizeObserver && contentRef) {

src/utils/useMermaidRenderer.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -231,24 +231,9 @@ export function useMermaidRenderer(
231231
const shouldRender = currentImmediateRender || expandedHashes.has(hash);
232232

233233
if (!shouldRender) {
234-
// Create render button for lazy loading
235-
const button = document.createElement("button");
236-
button.className = "mermaid-render-btn";
237-
button.textContent = "📊 Render Mermaid Diagram";
238-
button.onclick = async () => {
239-
button.disabled = true;
240-
button.textContent = "⏳ Rendering...";
241-
242-
// Record that the user manually expanded this diagram
243-
expandedHashes.add(hash);
244-
245-
const svg = await renderMermaid(code, currentConfigFn);
246-
container.innerHTML = svg;
247-
};
248-
249-
container.innerHTML = "";
250-
container.appendChild(button);
234+
container.dataset.mermaidStatus = "unrendered";
251235
} else {
236+
container.dataset.mermaidStatus = "rendered";
252237
// Render immediately
253238
container.innerHTML =
254239
'<div class="mermaid-loading">⏳ Rendering diagram...</div>';

vite.config.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { exec } from "node:child_process";
2+
import { existsSync } from "node:fs";
23
import { resolve } from "node:path";
34
import { defineConfig } from "vite";
45
import dts from "vite-plugin-dts";
@@ -15,7 +16,17 @@ export default defineConfig({
1516
{
1617
name: "cargo-build",
1718
buildStart: () => {
18-
return new Promise((resolve, reject) => {
19+
const wasmPath = resolve(
20+
__dirname,
21+
"markdown-renderer/pkg/markdown_renderer_bg.wasm",
22+
);
23+
if (existsSync(wasmPath)) {
24+
console.log("✓ WASM already built, skipping cargo build.");
25+
return;
26+
}
27+
28+
return new Promise((resolvePromise, reject) => {
29+
console.log("Building WASM...");
1930
exec(
2031
"cd markdown-renderer && wasm-pack build --target web --release --features sanitize && cd .. && bun process_wasm_pkg.js",
2132
(err, stdout, stderr) => {
@@ -25,7 +36,7 @@ export default defineConfig({
2536
reject(err);
2637
} else {
2738
console.log(stdout);
28-
resolve();
39+
resolvePromise();
2940
}
3041
},
3142
);

0 commit comments

Comments
 (0)