From 8edd12469116c9c5c3c96450affd54dfa879fb8a Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Wed, 31 Dec 2025 00:51:54 -0500 Subject: [PATCH] mobile support --- src/components/blog/extensions/Mermaid.ts | 78 +++++++++++++++++++++-- 1 file changed, 71 insertions(+), 7 deletions(-) diff --git a/src/components/blog/extensions/Mermaid.ts b/src/components/blog/extensions/Mermaid.ts index 7888bc9..80563ce 100644 --- a/src/components/blog/extensions/Mermaid.ts +++ b/src/components/blog/extensions/Mermaid.ts @@ -211,23 +211,80 @@ export const Mermaid = Node.create({ // Run validation validateSyntax(); - // Edit button overlay + // Edit button overlay - visible on mobile tap/selection, hover on desktop const editBtn = document.createElement("button"); editBtn.className = - "absolute top-2 right-2 bg-blue text-white px-3 py-1 rounded text-sm opacity-0 group-hover:opacity-100 transition-opacity duration-200 z-10"; + "absolute top-2 right-2 bg-blue text-white px-3 py-1 rounded text-sm opacity-0 group-hover:opacity-100 transition-opacity duration-200 z-10 touch-manipulation"; editBtn.textContent = "Edit Diagram"; editBtn.contentEditable = "false"; - editBtn.addEventListener("click", (e) => { - e.preventDefault(); - e.stopPropagation(); - - // Emit custom event to open modal + const openEditor = () => { const pos = typeof getPos === "function" ? getPos() : 0; const event = new CustomEvent("edit-mermaid", { detail: { content: node.attrs.content, pos } }); editor.view.dom.dispatchEvent(event); + }; + + editBtn.addEventListener("click", (e) => { + e.preventDefault(); + e.stopPropagation(); + openEditor(); + }); + + // Mobile support: Show button when node is selected (tapped) + let isSelected = false; + const updateButtonVisibility = () => { + if (typeof getPos !== "function") return; + + const pos = getPos(); + const { from, to } = editor.state.selection; + + // Check if this node is selected + const nodeIsSelected = from === pos && to === pos + node.nodeSize; + + if (nodeIsSelected !== isSelected) { + isSelected = nodeIsSelected; + if (isSelected) { + // Show button when selected (for mobile) + editBtn.style.opacity = "1"; + } else { + // Hide button when not selected (reset to CSS control) + editBtn.style.opacity = ""; + } + } + }; + + // Listen for selection changes + const plugin = editor.view.state.plugins.find( + (p: any) => p.spec?.key === "mermaidSelection" + ); + + // Use intersection observer to trigger update when visible + let updateInterval: ReturnType | null = null; + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + // Check selection periodically when visible + updateInterval = setInterval(updateButtonVisibility, 100); + } else { + // Stop checking when not visible + if (updateInterval) { + clearInterval(updateInterval); + updateInterval = null; + } + } + }); + }, + { threshold: 0.01 } + ); + + observer.observe(dom); + + // Also check on touch + dom.addEventListener("touchstart", () => { + setTimeout(updateButtonVisibility, 50); }); dom.appendChild(pre); @@ -244,7 +301,14 @@ export const Mermaid = Node.create({ code.textContent = updatedNode.attrs.content || ""; // Re-validate on update validateSyntax(); + updateButtonVisibility(); return true; + }, + destroy: () => { + if (updateInterval) { + clearInterval(updateInterval); + } + observer.disconnect(); } }; };