proxmox-helper-script/mermaidjs.html
2026-04-17 23:15:19 +08:00

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>