148 lines
4.5 KiB
HTML
148 lines
4.5 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Self-hosted Mermaid Editor</title>
|
|
<script src="/mermaid.min.js"></script>
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
height: 100vh;
|
|
background: #f0f0f0;
|
|
}
|
|
textarea {
|
|
width: 100%;
|
|
height: 100%;
|
|
padding: 1rem;
|
|
font-family: 'JetBrains Mono', Consolas, monospace;
|
|
font-size: 14px;
|
|
border: none;
|
|
resize: none;
|
|
background: #2d2d2d;
|
|
color: #f8f8f2;
|
|
}
|
|
#preview {
|
|
padding: 1rem;
|
|
overflow: auto;
|
|
border-left: 1px solid #ccc;
|
|
background: #fff;
|
|
}
|
|
.mermaid { max-width: 90%; margin: 0 auto; }
|
|
</style>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
mermaid.initialize({startOnLoad: false, securityLevel: 'loose'});
|
|
|
|
const editor = document.querySelector('textarea');
|
|
const preview = document.getElementById('preview');
|
|
|
|
if (!editor || !preview) {
|
|
console.error('DOM elements not found');
|
|
return;
|
|
}
|
|
|
|
editor.addEventListener('input', () => {
|
|
const code = editor.value.trim();
|
|
if (code) {
|
|
try {
|
|
mermaid.render('diagram', code).then(result => {
|
|
preview.innerHTML = result.svg;
|
|
}).catch(err => {
|
|
preview.innerHTML = `<p style="color:red;">Error: ${err.message}</p>`;
|
|
});
|
|
} catch (e) {
|
|
preview.innerHTML = `<p style="color:red;">Parse error: ${e.message}</p>`;
|
|
}
|
|
} else {
|
|
preview.innerHTML = '<p style="color:#999;">Type Mermaid code here...</p>';
|
|
}
|
|
});
|
|
|
|
// Export functions
|
|
window.exportSyntax = function() {
|
|
const code = editor.value.trim();
|
|
if (!code) {
|
|
alert('Diagram is empty');
|
|
return;
|
|
}
|
|
const blob = new Blob([code], { type: 'text/plain' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'diagram.mmd';
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
};
|
|
|
|
window.exportSVG = function() {
|
|
const svg = document.querySelector('#preview svg');
|
|
if (!svg) {
|
|
alert('Render diagram first');
|
|
return;
|
|
}
|
|
const svgData = new XMLSerializer().serializeToString(svg);
|
|
const blob = new Blob([svgData], { type: 'image/svg+xml' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'diagram.svg';
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
};
|
|
|
|
window.exportPNG = function() {
|
|
const svg = document.querySelector('#preview svg');
|
|
if (!svg) {
|
|
alert('Render diagram first');
|
|
return;
|
|
}
|
|
|
|
const canvas = document.createElement('canvas');
|
|
const ctx = canvas.getContext('2d');
|
|
const svgData = new XMLSerializer().serializeToString(svg);
|
|
const img = new Image();
|
|
|
|
img.onload = function() {
|
|
canvas.width = img.width;
|
|
canvas.height = img.height;
|
|
ctx.fillStyle = 'white';
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
ctx.drawImage(img, 0, 0);
|
|
|
|
canvas.toBlob(function(blob) {
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'diagram.png';
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
});
|
|
};
|
|
|
|
img.src = 'data:image/svg+xml;base64,' + btoa(svgData);
|
|
};
|
|
|
|
// Initial render
|
|
editor.value = `graph TD
|
|
A[Start] --> B{Decision?}
|
|
B -->|Yes| C[Do A]
|
|
B -->|No| D[Do B]
|
|
C --> E[End]
|
|
D --> E`;
|
|
editor.dispatchEvent(new Event('input'));
|
|
});
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<textarea spellcheck="false"></textarea>
|
|
<div id="preview"></div>
|
|
<div style="position: fixed; bottom: 1rem; right: 1rem; display: flex; gap: 0.5rem;">
|
|
<button onclick="exportSyntax()" style="padding: 0.5rem 1rem; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">📥 .mmd</button>
|
|
<button onclick="exportSVG()" style="padding: 0.5rem 1rem; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer;">📥 SVG</button>
|
|
<button onclick="exportPNG()" style="padding: 0.5rem 1rem; background: #FF9800; color: white; border: none; border-radius: 4px; cursor: pointer;">📥 PNG</button>
|
|
</div>
|
|
</body>
|
|
</html> |