mermaid-edit/index copy.php
DJP c4c3e02b41 Initial commit: Interactive Mermaid Editor
Features implemented:
- Visual node/edge editing with property panels
- Drag and drop node positioning
- Color customization for nodes and edges
- Text editing with real-time updates
- Bidirectional Mermaid code synchronization
- Subgraph/grouping support for layout control
- Export functionality to JSON with position data
- Layout lock system (experimental)
- Edge label editing and styling
- Comprehensive Mermaid syntax parser

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-11 09:14:26 -04:00

376 lines
No EOL
14 KiB
PHP

<?php
require_once 'config.php';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo SITE_TITLE; ?></title>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--primary-btn-color: #333;
--primary-btn-hover-color: #555;
--background-color: #fff;
--text-color: #333;
--border-color: #ccc;
--header-footer-bg: #333;
--header-footer-text: #fff;
--textarea-bg: #fff;
--textarea-text: #333;
--output-bg: #fff;
}
body {
font-family: 'Montserrat', sans-serif;
line-height: 1.6;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
min-height: 100vh;
background-color: var(--background-color);
color: var(--text-color);
transition: background-color 0.3s ease, color 0.3s ease;
}
header {
background-color: var(--header-footer-bg);
color: var(--header-footer-text);
text-align: center;
padding: 0.5rem;
}
main {
flex-grow: 1;
padding: 2rem;
display: flex;
flex-direction: column;
}
.container {
display: flex;
gap: 2rem;
flex-grow: 1;
}
.input-area, .output-area {
flex: 1;
display: flex;
flex-direction: column;
}
textarea {
width: 100%;
height: 300px;
margin-bottom: 1rem;
font-family: 'Montserrat', sans-serif;
background-color: var(--textarea-bg);
color: var(--textarea-text);
border: 1px solid var(--border-color);
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
#mermaidOutput {
border: 1px solid var(--border-color);
padding: 1rem;
flex-grow: 1;
overflow: auto;
background-color: #fff; /* Always keep diagram background white */
color: #333; /* Always keep diagram text dark */
transition: border-color 0.3s ease;
}
.button-container {
margin-top: 1rem;
}
button {
padding: 0.5rem 1rem;
background-color: var(--primary-btn-color);
color: #fff;
border: none;
cursor: pointer;
font-family: 'Montserrat', sans-serif;
margin-right: 0.5rem;
margin-bottom: 0.5rem;
transition: background-color 0.3s ease;
}
button:hover {
background-color: var(--primary-btn-hover-color);
}
footer {
background-color: var(--header-footer-bg);
color: var(--header-footer-text);
text-align: center;
padding: 1rem;
}
/* Dark Mode Toggle Button */
.dark-mode-toggle {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: var(--primary-btn-color);
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
color: #fff;
}
/* Dark Mode Styles */
.dark-mode {
--background-color: #1e1e1e;
--text-color: #f5f5f5;
--border-color: #444;
--header-footer-bg: #000;
--header-footer-text: #f5f5f5;
--textarea-bg: #333;
--textarea-text: #f5f5f5;
--output-bg: #333;
--primary-btn-color: #555;
--primary-btn-hover-color: #777;
}
/* Special styling for the output area heading in dark mode */
.dark-mode .output-area h2 {
background-color: #333;
padding: 8px;
border-radius: 4px;
margin-bottom: 12px;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
</head>
<body>
<header>
<h1><?php echo SITE_TITLE; ?></h1>
</header>
<!-- Dark Mode Toggle Button -->
<button id="darkModeToggle" class="dark-mode-toggle" title="Toggle Dark Mode">
<span id="lightModeIcon">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<!-- Sun icon path -->
<path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>
</svg>
</span>
<span id="darkModeIcon" style="display: none;">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<!-- Moon icon path -->
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/>
</svg>
</span>
</button>
<main>
<div class="container">
<div class="input-area">
<h2>Input Mermaid Diagram Code</h2>
<textarea id="mermaidInput">graph TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Android]
C -->|Two| E[Nuka Cola]
C -->|Three| F[Cold fusion generator]</textarea>
<div class="button-container">
<button id="renderBtn">Render Diagram</button>
<button id="exportMmdBtn">Download as .mmd</button>
<button id="importMmdBtn">Import .mmd</button>
<input type="file" id="fileInput" accept=".mmd" style="display: none;">
</div>
</div>
<div class="output-area">
<h2>Rendered Diagram</h2>
<div id="mermaidOutput"></div>
<div class="button-container">
<button id="exportBtn">Download as PNG</button>
</div>
</div>
</div>
</main>
<footer>
<p>&copy; <?php echo date('Y'); ?> <?php echo SITE_TITLE; ?></p>
</footer>
<script>
// Initial configuration will be set based on dark mode preference
let initialConfig = {
startOnLoad: false,
theme: 'base',
themeVariables: {
primaryColor: '#ffc406',
primaryTextColor: '#000000',
primaryBorderColor: '#000000',
lineColor: '#000000',
secondaryColor: '#B8B9B9',
tertiaryColor: '#B8B9B9'
},
flowchart: {
useMaxWidth: false,
htmlLabels: true,
curve: 'basis'
},
securityLevel: 'loose',
fontFamily: 'Montserrat, sans-serif',
fontSize: 14
};
// Always keep the Mermaid diagram in light mode for consistency
// Dark mode only applies to the UI, not the diagram itself
// Initialize with the appropriate theme
mermaid.initialize(initialConfig);
function renderMermaidDiagram() {
const input = document.getElementById('mermaidInput').value;
const output = document.getElementById('mermaidOutput');
output.innerHTML = ''; // Clear previous content
mermaid.render('mermaid-svg', input).then(result => {
output.innerHTML = result.svg;
}).catch(error => {
output.innerHTML = `<p style="color: red;">Error rendering diagram: ${error.message}</p>`;
});
}
// Initial render
document.addEventListener('DOMContentLoaded', renderMermaidDiagram);
document.getElementById('renderBtn').addEventListener('click', renderMermaidDiagram);
document.getElementById('exportBtn').addEventListener('click', function() {
const svg = document.querySelector('#mermaidOutput svg');
if (!svg) {
alert('No diagram to export. Please render a diagram first.');
return;
}
const svgData = new XMLSerializer().serializeToString(svg);
// Create a high-resolution canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const scale = 4; // Increase this for higher resolution
canvas.width = svg.viewBox.baseVal.width * scale;
canvas.height = svg.viewBox.baseVal.height * scale;
const img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const pngFile = canvas.toDataURL('image/png');
const downloadLink = document.createElement('a');
downloadLink.download = 'mermaid_diagram_high_res.png';
downloadLink.href = pngFile;
downloadLink.click();
};
img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgData)));
});
// Export as .mmd file
document.getElementById('exportMmdBtn').addEventListener('click', function() {
const mermaidCode = document.getElementById('mermaidInput').value;
if (!mermaidCode.trim()) {
alert('No diagram code to export.');
return;
}
// Create a blob with the mermaid code
const blob = new Blob([mermaidCode], {type: 'text/plain'});
const url = URL.createObjectURL(blob);
// Create download link
const downloadLink = document.createElement('a');
downloadLink.download = 'mermaid_diagram.mmd';
downloadLink.href = url;
downloadLink.click();
// Clean up
URL.revokeObjectURL(url);
});
// Import .mmd file
document.getElementById('importMmdBtn').addEventListener('click', function() {
document.getElementById('fileInput').click();
});
document.getElementById('fileInput').addEventListener('change', function(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
const contents = e.target.result;
document.getElementById('mermaidInput').value = contents;
renderMermaidDiagram();
};
reader.readAsText(file);
// Reset file input
event.target.value = '';
});
// Dark mode toggle functionality
document.addEventListener('DOMContentLoaded', function() {
// Check for saved dark mode preference
const darkModeEnabled = localStorage.getItem('darkMode') === 'enabled';
if (darkModeEnabled) {
document.body.classList.add('dark-mode');
document.getElementById('lightModeIcon').style.display = 'none';
document.getElementById('darkModeIcon').style.display = 'block';
// Update Mermaid theme for dark mode
updateMermaidTheme(true);
}
// Toggle dark mode when button is clicked
document.getElementById('darkModeToggle').addEventListener('click', function() {
document.body.classList.toggle('dark-mode');
const isDarkMode = document.body.classList.contains('dark-mode');
// Save preference and toggle icons
if (isDarkMode) {
localStorage.setItem('darkMode', 'enabled');
document.getElementById('lightModeIcon').style.display = 'none';
document.getElementById('darkModeIcon').style.display = 'block';
} else {
localStorage.setItem('darkMode', 'disabled');
document.getElementById('lightModeIcon').style.display = 'block';
document.getElementById('darkModeIcon').style.display = 'none';
}
// Update Mermaid theme based on dark mode
updateMermaidTheme(isDarkMode);
// Re-render the diagram with new theme
renderMermaidDiagram();
});
});
// Function to update Mermaid theme based on dark mode
function updateMermaidTheme(isDarkMode) {
// Always use light theme for diagrams regardless of site theme
// This ensures exported diagrams look good and consistent
mermaid.initialize({
startOnLoad: false,
theme: 'base',
themeVariables: {
primaryColor: '#ffc406',
primaryTextColor: '#000000',
primaryBorderColor: '#000000',
lineColor: '#000000',
secondaryColor: '#B8B9B9',
tertiaryColor: '#B8B9B9'
},
flowchart: {
useMaxWidth: false,
htmlLabels: true,
curve: 'basis'
},
securityLevel: 'loose',
fontFamily: 'Montserrat, sans-serif',
fontSize: 14
});
}
</script>
</body>
</html>