|
4 | 4 | <meta charset="UTF-8"> |
5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
6 | 6 | <title>Mermaid Parser</title> |
7 | | - <style> |
8 | | - * { |
9 | | - box-sizing: border-box; |
10 | | - } |
11 | | - |
12 | | - body { |
13 | | - margin: 0; |
14 | | - padding: 20px; |
15 | | - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; |
16 | | - background: #f5f5f5; |
17 | | - min-height: 100vh; |
18 | | - } |
19 | | - |
20 | | - .container { |
21 | | - max-width: 900px; |
22 | | - margin: 0 auto; |
23 | | - } |
24 | | - |
25 | | - .input-section { |
26 | | - margin-bottom: 20px; |
27 | | - } |
28 | | - |
29 | | - .textarea-wrapper { |
30 | | - display: flex; |
31 | | - gap: 10px; |
32 | | - align-items: flex-start; |
33 | | - } |
34 | | - |
35 | | - textarea { |
36 | | - flex: 1; |
37 | | - min-height: 150px; |
38 | | - padding: 12px; |
39 | | - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; |
40 | | - font-size: 14px; |
41 | | - border: 1px solid #ddd; |
42 | | - border-radius: 6px; |
43 | | - resize: vertical; |
44 | | - } |
45 | | - |
46 | | - textarea:focus { |
47 | | - outline: none; |
48 | | - border-color: #4a90d9; |
49 | | - } |
50 | | - |
51 | | - .preview-section { |
52 | | - background: white; |
53 | | - border: 1px solid #ddd; |
54 | | - border-radius: 6px; |
55 | | - min-height: 200px; |
56 | | - padding: 20px; |
57 | | - display: flex; |
58 | | - justify-content: center; |
59 | | - align-items: center; |
60 | | - position: relative; |
61 | | - } |
62 | | - |
63 | | - .preview-section .mermaid { |
64 | | - background: white; |
65 | | - } |
66 | | - |
67 | | - .preview-section.error { |
68 | | - color: #d32f2f; |
69 | | - font-size: 14px; |
70 | | - } |
71 | | - |
72 | | - .btn { |
73 | | - padding: 10px 20px; |
74 | | - font-size: 14px; |
75 | | - border: none; |
76 | | - border-radius: 6px; |
77 | | - cursor: pointer; |
78 | | - transition: background 0.2s; |
79 | | - } |
80 | | - |
81 | | - .btn-primary { |
82 | | - background: #4a90d9; |
83 | | - color: white; |
84 | | - } |
85 | | - |
86 | | - .btn-primary:hover { |
87 | | - background: #3a7bc8; |
88 | | - } |
89 | | - |
90 | | - .btn-secondary { |
91 | | - background: #6c757d; |
92 | | - color: white; |
93 | | - } |
94 | | - |
95 | | - .btn-secondary:hover { |
96 | | - background: #5a6268; |
97 | | - } |
98 | | - |
99 | | - .btn:disabled { |
100 | | - opacity: 0.5; |
101 | | - cursor: not-allowed; |
102 | | - } |
103 | | - |
104 | | - .actions { |
105 | | - display: flex; |
106 | | - justify-content: flex-end; |
107 | | - margin-top: 10px; |
108 | | - } |
109 | | - |
110 | | - .placeholder-text { |
111 | | - color: #999; |
112 | | - text-align: center; |
113 | | - } |
114 | | - </style> |
115 | 7 | </head> |
116 | 8 | <body> |
117 | 9 | <div class="container"> |
118 | 10 | <div class="input-section"> |
119 | 11 | <div class="textarea-wrapper"> |
120 | 12 | <textarea id="input" placeholder="Paste your Mermaid syntax here..."></textarea> |
121 | | - <button class="btn btn-primary" id="copyText">Copy Text</button> |
| 13 | + <button class="btn btn-primary" id="copyText"> |
| 14 | + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 15 | + <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> |
| 16 | + <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> |
| 17 | + </svg> |
| 18 | + Copy Text |
| 19 | + </button> |
122 | 20 | </div> |
123 | 21 | </div> |
124 | 22 |
|
125 | | - <div class="preview-section" id="preview"> |
126 | | - <div id="diagram" class="mermaid"></div> |
127 | | - <span class="placeholder-text" id="placeholder">Diagram preview will appear here</span> |
| 23 | + <div class="preview-wrapper"> |
| 24 | + <div class="resize-handle" id="resizeHandle" title="Drag to resize"></div> |
| 25 | + <div class="preview-section" id="preview"> |
| 26 | + <div id="diagram"></div> |
| 27 | + <span class="placeholder-text" id="placeholder">Diagram preview will appear here</span> |
| 28 | + </div> |
| 29 | + </div> |
| 30 | + |
| 31 | + <div id="errorPanel" class="error-panel"> |
| 32 | + <div class="error-title"> |
| 33 | + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 34 | + <circle cx="12" cy="12" r="10"></circle> |
| 35 | + <line x1="12" y1="8" x2="12" y2="12"></line> |
| 36 | + <line x1="12" y1="16" x2="12.01" y2="16"></line> |
| 37 | + </svg> |
| 38 | + Syntax Error |
| 39 | + </div> |
| 40 | + <pre class="error-content" id="errorContent"></pre> |
| 41 | + <div class="error-location" id="errorLocation"></div> |
128 | 42 | </div> |
129 | 43 |
|
130 | 44 | <div class="actions"> |
131 | | - <button class="btn btn-secondary" id="copyImage">Copy Image</button> |
| 45 | + <button class="btn btn-secondary" id="copyImage"> |
| 46 | + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 47 | + <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect> |
| 48 | + <circle cx="8.5" cy="8.5" r="1.5"></circle> |
| 49 | + <polyline points="21 15 16 10 5 21"></polyline> |
| 50 | + </svg> |
| 51 | + Copy Image (300 DPI) |
| 52 | + </button> |
132 | 53 | </div> |
133 | 54 | </div> |
134 | 55 |
|
135 | | - <script src="https://cdn.jsdelivr.net/npm/mermaid@11.14.0/dist/mermaid.min.js"></script> |
136 | | - <script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script> |
137 | | - |
138 | | - <script> |
139 | | - mermaid.initialize({ |
140 | | - startOnLoad: false, |
141 | | - securityLevel: 'loose', |
142 | | - theme: 'default' |
143 | | - }); |
144 | | - |
145 | | - const input = document.getElementById('input'); |
146 | | - const preview = document.getElementById('preview'); |
147 | | - const diagram = document.getElementById('diagram'); |
148 | | - const placeholder = document.getElementById('placeholder'); |
149 | | - const copyTextBtn = document.getElementById('copyText'); |
150 | | - const copyImageBtn = document.getElementById('copyImage'); |
151 | | - |
152 | | - let debounceTimer; |
153 | | - |
154 | | - async function render() { |
155 | | - const code = input.value.trim(); |
156 | | - |
157 | | - if (!code) { |
158 | | - diagram.innerHTML = ''; |
159 | | - diagram.classList.remove('mermaid'); |
160 | | - placeholder.style.display = 'block'; |
161 | | - preview.classList.remove('error'); |
162 | | - return; |
163 | | - } |
164 | | - |
165 | | - placeholder.style.display = 'none'; |
166 | | - diagram.classList.add('mermaid'); |
167 | | - |
168 | | - try { |
169 | | - const id = 'mermaid-' + Date.now(); |
170 | | - const { svg } = await mermaid.render(id, code); |
171 | | - diagram.innerHTML = svg; |
172 | | - preview.classList.remove('error'); |
173 | | - } catch (error) { |
174 | | - diagram.innerHTML = ''; |
175 | | - diagram.classList.remove('mermaid'); |
176 | | - preview.classList.add('error'); |
177 | | - placeholder.style.display = 'block'; |
178 | | - placeholder.textContent = 'Syntax error: ' + error.message; |
179 | | - } |
180 | | - } |
181 | | - |
182 | | - input.addEventListener('input', () => { |
183 | | - clearTimeout(debounceTimer); |
184 | | - debounceTimer = setTimeout(render, 300); |
185 | | - }); |
186 | | - |
187 | | - copyTextBtn.addEventListener('click', async () => { |
188 | | - const text = input.value; |
189 | | - if (!text) return; |
190 | | - |
191 | | - try { |
192 | | - await navigator.clipboard.writeText(text); |
193 | | - copyTextBtn.textContent = 'Copied!'; |
194 | | - setTimeout(() => { |
195 | | - copyTextBtn.textContent = 'Copy Text'; |
196 | | - }, 1500); |
197 | | - } catch (err) { |
198 | | - console.error('Failed to copy text:', err); |
199 | | - } |
200 | | - }); |
201 | | - |
202 | | - copyImageBtn.addEventListener('click', async () => { |
203 | | - const svgElement = diagram.querySelector('svg'); |
204 | | - if (!svgElement) { |
205 | | - alert('No diagram to copy. Please render a diagram first.'); |
206 | | - return; |
207 | | - } |
208 | | - |
209 | | - copyImageBtn.disabled = true; |
210 | | - copyImageBtn.textContent = 'Processing...'; |
211 | | - |
212 | | - try { |
213 | | - const canvas = await html2canvas(diagram, { |
214 | | - backgroundColor: '#ffffff', |
215 | | - scale: 2 |
216 | | - }); |
217 | | - |
218 | | - canvas.toBlob(async (blob) => { |
219 | | - if (blob) { |
220 | | - try { |
221 | | - await navigator.clipboard.write([ |
222 | | - new ClipboardItem({ 'image/png': blob }) |
223 | | - ]); |
224 | | - copyImageBtn.textContent = 'Copied!'; |
225 | | - } catch (err) { |
226 | | - const url = URL.createObjectURL(blob); |
227 | | - const a = document.createElement('a'); |
228 | | - a.href = url; |
229 | | - a.download = 'diagram.png'; |
230 | | - a.click(); |
231 | | - URL.revokeObjectURL(url); |
232 | | - copyImageBtn.textContent = 'Downloaded!'; |
233 | | - } |
234 | | - } |
235 | | - setTimeout(() => { |
236 | | - copyImageBtn.textContent = 'Copy Image'; |
237 | | - copyImageBtn.disabled = false; |
238 | | - }, 1500); |
239 | | - }, 'image/png'); |
240 | | - } catch (err) { |
241 | | - console.error('Failed to copy image:', err); |
242 | | - copyImageBtn.textContent = 'Copy Image'; |
243 | | - copyImageBtn.disabled = false; |
244 | | - } |
245 | | - }); |
| 56 | + <div id="toast" class="toast"></div> |
246 | 57 |
|
247 | | - render(); |
248 | | - </script> |
| 58 | + <script type="module" src="/src/main.ts"></script> |
249 | 59 | </body> |
250 | 60 | </html> |
0 commit comments