diff --git a/bun.lockb b/bun.lockb
index 27d31fe..5c0e788 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index 839d3b5..f088837 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,7 @@
"@tiptap/extension-table-row": "^3.14.0",
"@tiptap/extension-task-item": "^3.14.0",
"@tiptap/extension-task-list": "^3.14.0",
+ "@tiptap/extension-text-align": "^3.14.0",
"@tiptap/extension-text-style": "^3.14.0",
"@tiptap/pm": "^3.14.0",
"@tiptap/starter-kit": "^3.14.0",
@@ -43,6 +44,7 @@
"google-auth-library": "^10.5.0",
"highlight.js": "^11.11.1",
"jose": "^6.1.3",
+ "mermaid": "^11.12.2",
"motion": "^12.23.26",
"solid-js": "^1.9.5",
"solid-tiptap": "^0.8.0",
diff --git a/src/app.css b/src/app.css
index fa91ccd..8fc463b 100644
--- a/src/app.css
+++ b/src/app.css
@@ -969,3 +969,257 @@ details[open] div[data-type="details-content"] {
transform: translateY(0);
}
}
+
+/* Mermaid diagram styles */
+.mermaid-diagram,
+pre[data-type="mermaid"] {
+ margin: 2rem 0;
+ padding: 1rem;
+ background-color: var(--color-surface0);
+ border-radius: 0.5rem;
+ border: 1px solid var(--color-surface2);
+ overflow: visible;
+}
+
+.mermaid-diagram code,
+pre[data-type="mermaid"] code {
+ display: block;
+ white-space: pre;
+ font-family: "JetBrainsMono", monospace;
+ color: var(--color-text);
+ background: transparent;
+}
+
+/* Rendered mermaid SVG container */
+.mermaid-rendered {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-height: 100px;
+}
+
+.mermaid-rendered svg {
+ max-width: 100%;
+ height: auto;
+}
+
+/* Mermaid theme adjustments */
+.mermaid .node rect,
+.mermaid .node circle,
+.mermaid .node polygon,
+.mermaid .node ellipse,
+.mermaid .node path {
+ fill: var(--color-surface1) !important;
+ stroke: var(--color-blue) !important;
+ stroke-width: 2px;
+}
+
+.mermaid .node .label,
+.mermaid .nodeLabel {
+ color: var(--color-text) !important;
+ fill: var(--color-text) !important;
+}
+
+.mermaid .edgePath .path,
+.mermaid .flowchart-link {
+ stroke: var(--color-blue) !important;
+ stroke-width: 2px;
+}
+
+.mermaid .edgeLabel,
+.mermaid .edgeLabel rect {
+ background-color: var(--color-surface0) !important;
+ fill: var(--color-surface0) !important;
+}
+
+.mermaid .edgeLabel span {
+ color: var(--color-text) !important;
+}
+
+.mermaid .cluster rect {
+ fill: var(--color-surface0) !important;
+ stroke: var(--color-surface2) !important;
+}
+
+.mermaid .cluster-label {
+ fill: var(--color-text) !important;
+}
+
+/* Class diagram styles */
+.mermaid .classGroup rect,
+.mermaid .classGroup line {
+ stroke: var(--color-blue) !important;
+ fill: var(--color-surface1) !important;
+}
+
+.mermaid .classLabel {
+ fill: var(--color-text) !important;
+}
+
+/* State diagram styles */
+.mermaid .statediagram-state rect {
+ fill: var(--color-surface1) !important;
+ stroke: var(--color-blue) !important;
+}
+
+.mermaid .statediagram-state text {
+ fill: var(--color-text) !important;
+}
+
+/* Sequence diagram styles */
+.mermaid .actor {
+ fill: var(--color-surface1) !important;
+ stroke: var(--color-blue) !important;
+}
+
+.mermaid .actor text,
+.mermaid .messageText {
+ fill: var(--color-text) !important;
+ stroke: none !important;
+}
+
+.mermaid .activation0,
+.mermaid .activation1,
+.mermaid .activation2 {
+ fill: var(--color-surface2) !important;
+ stroke: var(--color-blue) !important;
+}
+
+/* ER diagram styles */
+.mermaid .er.entityBox {
+ fill: var(--color-surface1) !important;
+ stroke: var(--color-blue) !important;
+}
+
+.mermaid .er.entityLabel {
+ fill: var(--color-text) !important;
+}
+
+.mermaid .er.relationshipLabel {
+ fill: var(--color-text) !important;
+}
+
+/* Gantt chart styles */
+.mermaid .grid .tick line {
+ stroke: var(--color-surface2) !important;
+}
+
+.mermaid .grid .tick text {
+ fill: var(--color-text) !important;
+}
+
+.mermaid .task {
+ fill: var(--color-blue) !important;
+ stroke: var(--color-blue) !important;
+}
+
+.mermaid .taskText {
+ fill: var(--color-text) !important;
+}
+
+.mermaid .taskTextOutsideRight,
+.mermaid .taskTextOutsideLeft {
+ fill: var(--color-text) !important;
+}
+
+/* Pie chart styles */
+.mermaid .pieCircle {
+ stroke: var(--color-surface2) !important;
+}
+
+.mermaid .pieTitleText {
+ fill: var(--color-text) !important;
+}
+
+.mermaid .slice {
+ stroke-width: 2px;
+ stroke: var(--color-surface0) !important;
+}
+
+.mermaid .legend rect {
+ fill: var(--color-blue) !important;
+ stroke: var(--color-blue) !important;
+}
+
+/* Override all text elements in mermaid SVG for high contrast */
+.mermaid-rendered svg text,
+.mermaid svg text,
+svg.mermaid text {
+ fill: #ffffff !important;
+ color: #ffffff !important;
+ stroke: #000000 !important;
+ stroke-width: 0.25px !important;
+}
+
+/* Ensure percentage labels in pie charts are visible */
+.mermaid text.slice,
+.mermaid .slice {
+ fill: #ffffff !important;
+ font-weight: bold !important;
+ font-size: 14px !important;
+ stroke: #000000 !important;
+ stroke-width: 0.75px !important;
+ paint-order: stroke fill !important;
+}
+
+/* Text alignment styles */
+.ProseMirror [style*="text-align: left"],
+.ProseMirror p[style*="text-align: left"],
+.ProseMirror h1[style*="text-align: left"],
+.ProseMirror h2[style*="text-align: left"],
+.ProseMirror h3[style*="text-align: left"],
+.ProseMirror h4[style*="text-align: left"],
+.ProseMirror h5[style*="text-align: left"],
+.ProseMirror h6[style*="text-align: left"] {
+ text-align: left;
+}
+
+.ProseMirror [style*="text-align: center"],
+.ProseMirror p[style*="text-align: center"],
+.ProseMirror h1[style*="text-align: center"],
+.ProseMirror h2[style*="text-align: center"],
+.ProseMirror h3[style*="text-align: center"],
+.ProseMirror h4[style*="text-align: center"],
+.ProseMirror h5[style*="text-align: center"],
+.ProseMirror h6[style*="text-align: center"] {
+ text-align: center;
+}
+
+.ProseMirror [style*="text-align: right"],
+.ProseMirror p[style*="text-align: right"],
+.ProseMirror h1[style*="text-align: right"],
+.ProseMirror h2[style*="text-align: right"],
+.ProseMirror h3[style*="text-align: right"],
+.ProseMirror h4[style*="text-align: right"],
+.ProseMirror h5[style*="text-align: right"],
+.ProseMirror h6[style*="text-align: right"] {
+ text-align: right;
+}
+
+.ProseMirror [style*="text-align: justify"],
+.ProseMirror p[style*="text-align: justify"],
+.ProseMirror h1[style*="text-align: justify"],
+.ProseMirror h2[style*="text-align: justify"],
+.ProseMirror h3[style*="text-align: justify"],
+.ProseMirror h4[style*="text-align: justify"],
+.ProseMirror h5[style*="text-align: justify"],
+.ProseMirror h6[style*="text-align: justify"] {
+ text-align: justify;
+}
+
+/* Image alignment */
+.ProseMirror img[style*="text-align: center"] {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.ProseMirror img[style*="text-align: right"] {
+ display: block;
+ margin-left: auto;
+}
+
+.ProseMirror img[style*="text-align: left"] {
+ display: block;
+ margin-right: auto;
+}
diff --git a/src/components/blog/MermaidRenderer.tsx b/src/components/blog/MermaidRenderer.tsx
new file mode 100644
index 0000000..8530abb
--- /dev/null
+++ b/src/components/blog/MermaidRenderer.tsx
@@ -0,0 +1,51 @@
+import { onMount } from "solid-js";
+import mermaid from "mermaid";
+
+// Initialize mermaid once
+mermaid.initialize({
+ startOnLoad: false,
+ theme: "dark",
+ securityLevel: "loose",
+ fontFamily: "monospace",
+ themeVariables: {
+ darkMode: true,
+ primaryColor: "#2c2f40",
+ primaryTextColor: "#b5c1f1",
+ primaryBorderColor: "#739df2",
+ lineColor: "#739df2",
+ secondaryColor: "#3e4255",
+ tertiaryColor: "#505469"
+ }
+});
+
+export default function MermaidRenderer() {
+ onMount(() => {
+ // Find all mermaid diagrams and render them
+ const mermaidPres = document.querySelectorAll('pre[data-type="mermaid"]');
+
+ mermaidPres.forEach(async (pre, index) => {
+ const code = pre.querySelector("code");
+ if (!code) return;
+
+ const content = code.textContent || "";
+ if (!content.trim()) return;
+
+ try {
+ const id = `mermaid-${index}-${Math.random().toString(36).substr(2, 9)}`;
+ const { svg } = await mermaid.render(id, content);
+
+ // Replace the pre/code with rendered SVG
+ const wrapper = document.createElement("div");
+ wrapper.className = "mermaid-rendered";
+ wrapper.innerHTML = svg;
+ pre.replaceWith(wrapper);
+ } catch (err) {
+ console.error("Failed to render mermaid diagram:", err);
+ // Keep the original code block if rendering fails
+ pre.classList.add("mermaid-error");
+ }
+ });
+ });
+
+ return null;
+}
diff --git a/src/components/blog/PostBodyClient.tsx b/src/components/blog/PostBodyClient.tsx
index 4aab076..5c2933b 100644
--- a/src/components/blog/PostBodyClient.tsx
+++ b/src/components/blog/PostBodyClient.tsx
@@ -1,6 +1,7 @@
import { createEffect } from "solid-js";
import { createSignal } from "solid-js";
import type { HLJSApi } from "highlight.js";
+import MermaidRenderer from "./MermaidRenderer";
export interface PostBodyClientProps {
body: string;
@@ -121,6 +122,7 @@ export default function PostBodyClient(props: PostBodyClientProps) {
class="text-text prose dark:prose-invert max-w-none"
innerHTML={props.body}
/>
+
Hello! World
`, @@ -638,6 +808,44 @@ export default function TextEditor(props: TextEditorProps) { } }); + // Close mermaid menu on outside click + createEffect(() => { + if (showMermaidTemplates()) { + const handleClickOutside = (e: MouseEvent) => { + const target = e.target as HTMLElement; + if ( + !target.closest(".mermaid-menu") && + !target.closest("[data-mermaid-trigger]") + ) { + setShowMermaidTemplates(false); + } + }; + + setTimeout(() => { + document.addEventListener("click", handleClickOutside); + }, 0); + + return () => document.removeEventListener("click", handleClickOutside); + } + }); + + const showMermaidSelector = (e: MouseEvent) => { + const buttonRect = (e.currentTarget as HTMLElement).getBoundingClientRect(); + setMermaidMenuPosition({ + top: buttonRect.bottom + 5, + left: buttonRect.left + }); + setShowMermaidTemplates(!showMermaidTemplates()); + }; + + const insertMermaidDiagram = (template: (typeof MERMAID_TEMPLATES)[0]) => { + const instance = editor(); + if (!instance) return; + + instance.chain().focus().setMermaid(template.code).run(); + setShowMermaidTemplates(false); + }; + // Toggle fullscreen mode const toggleFullscreen = () => { setIsFullscreen(!isFullscreen()); @@ -1005,6 +1213,34 @@ export default function TextEditor(props: TextEditorProps) { + {/* Mermaid Template Selector */} +