Skip to content

Commit b82827b

Browse files
committed
mermaid 组件增强,修改建站时间
1 parent ad7eb1e commit b82827b

2 files changed

Lines changed: 369 additions & 8 deletions

File tree

packages/pure/components/advanced/MermaidEnhanced.astro

Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,119 @@
8888
transform: scale(1);
8989
}
9090
}
91+
92+
/* Fullscreen modal */
93+
:global(.mermaid-fullscreen-modal) {
94+
position: fixed;
95+
top: 0;
96+
left: 0;
97+
right: 0;
98+
bottom: 0;
99+
z-index: 9999;
100+
background: hsl(var(--background) / 0.95);
101+
backdrop-filter: blur(8px);
102+
display: flex;
103+
flex-direction: column;
104+
opacity: 0;
105+
transition: opacity 0.3s ease;
106+
overflow: hidden;
107+
}
108+
109+
:global(.mermaid-fullscreen-modal.active) {
110+
opacity: 1;
111+
}
112+
113+
/* SVG container with pan and zoom */
114+
:global(.mermaid-fullscreen-content) {
115+
flex: 1;
116+
display: flex;
117+
align-items: center;
118+
justify-content: center;
119+
overflow: hidden;
120+
position: relative;
121+
cursor: grab;
122+
padding: 80px 20px 100px;
123+
}
124+
125+
:global(.mermaid-fullscreen-content.dragging) {
126+
cursor: grabbing;
127+
}
128+
129+
:global(.mermaid-fullscreen-svg-wrapper) {
130+
position: relative;
131+
transition: transform 0.1s ease-out;
132+
transform-origin: center center;
133+
}
134+
135+
:global(.mermaid-fullscreen-svg-wrapper svg) {
136+
display: block;
137+
max-width: 100%;
138+
max-height: 100%;
139+
background: hsl(var(--background));
140+
border-radius: 0.5rem;
141+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
142+
}
143+
144+
/* Bottom toolbar */
145+
:global(.mermaid-fullscreen-toolbar) {
146+
position: absolute;
147+
bottom: 0;
148+
left: 0;
149+
right: 0;
150+
display: flex;
151+
align-items: center;
152+
justify-content: center;
153+
gap: 0.5rem;
154+
padding: 1rem;
155+
background: hsl(var(--background) / 0.95);
156+
backdrop-filter: blur(8px);
157+
border-top: 1px solid hsl(var(--border));
158+
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
159+
}
160+
161+
:global(.mermaid-toolbar-btn) {
162+
display: flex;
163+
align-items: center;
164+
justify-content: center;
165+
width: 40px;
166+
height: 40px;
167+
padding: 0;
168+
border-radius: 0.375rem;
169+
background: hsl(var(--muted));
170+
border: 1px solid hsl(var(--border));
171+
cursor: pointer;
172+
color: hsl(var(--foreground));
173+
transition: all 0.2s ease;
174+
font-size: 14px;
175+
}
176+
177+
:global(.mermaid-toolbar-btn:hover) {
178+
background: hsl(var(--muted) / 0.8);
179+
border-color: hsl(var(--primary));
180+
color: hsl(var(--primary));
181+
}
182+
183+
:global(.mermaid-toolbar-btn:active) {
184+
transform: scale(0.95);
185+
}
186+
187+
:global(.mermaid-toolbar-btn:disabled) {
188+
opacity: 0.5;
189+
cursor: not-allowed;
190+
}
191+
192+
:global(.mermaid-toolbar-btn svg) {
193+
width: 18px;
194+
height: 18px;
195+
}
196+
197+
:global(.mermaid-zoom-info) {
198+
min-width: 80px;
199+
text-align: center;
200+
font-size: 14px;
201+
color: hsl(var(--foreground) / 0.8);
202+
font-variant-numeric: tabular-nums;
203+
}
91204
</style>
92205

93206
<script is:inline>
@@ -155,9 +268,20 @@
155268
</svg>
156269
`;
157270

271+
// Expand button
272+
const expandBtn = document.createElement('button');
273+
expandBtn.className = 'mermaid-control-btn';
274+
expandBtn.setAttribute('title', 'Expand to fullscreen');
275+
expandBtn.innerHTML = `
276+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
277+
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path>
278+
</svg>
279+
`;
280+
158281
// Add buttons to controls
159282
controls.appendChild(copyBtn);
160283
controls.appendChild(downloadBtn);
284+
controls.appendChild(expandBtn);
161285

162286
// Add to diagram container
163287
diagram.appendChild(controls);
@@ -218,7 +342,242 @@
218342
console.error('Failed to download:', err);
219343
}
220344
});
345+
346+
// Expand to fullscreen functionality
347+
expandBtn.addEventListener('click', (e) => {
348+
e.stopPropagation();
349+
openFullscreenViewer(diagram);
350+
});
351+
});
352+
}
353+
354+
// Fullscreen viewer functionality
355+
function openFullscreenViewer(diagram) {
356+
const svgElement = diagram.querySelector('svg');
357+
if (!svgElement) return;
358+
359+
const originalCode = originalCodeMap.get(diagram) || diagram.getAttribute('data-diagram') || '';
360+
361+
// Create modal
362+
const modal = document.createElement('div');
363+
modal.className = 'mermaid-fullscreen-modal';
364+
365+
// Create content container
366+
const content = document.createElement('div');
367+
content.className = 'mermaid-fullscreen-content';
368+
369+
// Create SVG wrapper
370+
const svgWrapper = document.createElement('div');
371+
svgWrapper.className = 'mermaid-fullscreen-svg-wrapper';
372+
373+
// Clone SVG
374+
const clonedSvg = svgElement.cloneNode(true);
375+
svgWrapper.appendChild(clonedSvg);
376+
content.appendChild(svgWrapper);
377+
378+
// Create toolbar
379+
const toolbar = document.createElement('div');
380+
toolbar.className = 'mermaid-fullscreen-toolbar';
381+
382+
// Zoom state
383+
let scale = 1;
384+
let panX = 0;
385+
let panY = 0;
386+
let isDragging = false;
387+
let startX = 0;
388+
let startY = 0;
389+
let startPanX = 0;
390+
let startPanY = 0;
391+
392+
// Update transform
393+
function updateTransform() {
394+
svgWrapper.style.transform = `translate(${panX}px, ${panY}px) scale(${scale})`;
395+
zoomInfo.textContent = `${Math.round(scale * 100)}%`;
396+
zoomOutBtn.disabled = scale <= 0.25;
397+
zoomInBtn.disabled = scale >= 5;
398+
}
399+
400+
// Zoom in button
401+
const zoomInBtn = document.createElement('button');
402+
zoomInBtn.className = 'mermaid-toolbar-btn';
403+
zoomInBtn.setAttribute('title', 'Zoom in');
404+
zoomInBtn.innerHTML = `
405+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
406+
<circle cx="11" cy="11" r="8"></circle>
407+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
408+
<line x1="11" y1="8" x2="11" y2="14"></line>
409+
<line x1="8" y1="11" x2="14" y2="11"></line>
410+
</svg>
411+
`;
412+
zoomInBtn.addEventListener('click', () => {
413+
scale = Math.min(scale * 1.2, 5);
414+
updateTransform();
415+
});
416+
417+
// Zoom out button
418+
const zoomOutBtn = document.createElement('button');
419+
zoomOutBtn.className = 'mermaid-toolbar-btn';
420+
zoomOutBtn.setAttribute('title', 'Zoom out');
421+
zoomOutBtn.innerHTML = `
422+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
423+
<circle cx="11" cy="11" r="8"></circle>
424+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
425+
<line x1="8" y1="11" x2="14" y2="11"></line>
426+
</svg>
427+
`;
428+
zoomOutBtn.addEventListener('click', () => {
429+
scale = Math.max(scale / 1.2, 0.25);
430+
updateTransform();
431+
});
432+
433+
// Reset button
434+
const resetBtn = document.createElement('button');
435+
resetBtn.className = 'mermaid-toolbar-btn';
436+
resetBtn.setAttribute('title', 'Reset zoom');
437+
resetBtn.innerHTML = `
438+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
439+
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"></path>
440+
<path d="M21 3v5h-5"></path>
441+
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"></path>
442+
<path d="M3 21v-5h5"></path>
443+
</svg>
444+
`;
445+
resetBtn.addEventListener('click', () => {
446+
scale = 1;
447+
panX = 0;
448+
panY = 0;
449+
updateTransform();
450+
});
451+
452+
// Copy code button
453+
const copyCodeBtn = document.createElement('button');
454+
copyCodeBtn.className = 'mermaid-toolbar-btn';
455+
copyCodeBtn.setAttribute('title', 'Copy source code');
456+
copyCodeBtn.innerHTML = `
457+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
458+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
459+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
460+
</svg>
461+
`;
462+
copyCodeBtn.addEventListener('click', async () => {
463+
if (originalCode) {
464+
try {
465+
await navigator.clipboard.writeText(originalCode);
466+
copyCodeBtn.innerHTML = `
467+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
468+
<path d="M20 6L9 17l-5-5"></path>
469+
</svg>
470+
`;
471+
setTimeout(() => {
472+
copyCodeBtn.innerHTML = `
473+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
474+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
475+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
476+
</svg>
477+
`;
478+
}, 2000);
479+
} catch (err) {
480+
console.error('Failed to copy:', err);
481+
}
482+
}
483+
});
484+
485+
// Zoom info
486+
const zoomInfo = document.createElement('div');
487+
zoomInfo.className = 'mermaid-zoom-info';
488+
zoomInfo.textContent = '100%';
489+
490+
// Close button
491+
const closeBtn = document.createElement('button');
492+
closeBtn.className = 'mermaid-toolbar-btn';
493+
closeBtn.setAttribute('title', 'Close');
494+
closeBtn.innerHTML = `
495+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
496+
<line x1="18" y1="6" x2="6" y2="18"></line>
497+
<line x1="6" y1="6" x2="18" y2="18"></line>
498+
</svg>
499+
`;
500+
closeBtn.addEventListener('click', () => {
501+
modal.classList.remove('active');
502+
setTimeout(() => {
503+
document.body.removeChild(modal);
504+
}, 300);
505+
});
506+
507+
// Build toolbar
508+
toolbar.appendChild(zoomOutBtn);
509+
toolbar.appendChild(zoomInfo);
510+
toolbar.appendChild(zoomInBtn);
511+
toolbar.appendChild(resetBtn);
512+
toolbar.appendChild(copyCodeBtn);
513+
toolbar.appendChild(closeBtn);
514+
515+
// Mouse wheel zoom
516+
content.addEventListener('wheel', (e) => {
517+
e.preventDefault();
518+
const delta = e.deltaY > 0 ? 0.9 : 1.1;
519+
const oldScale = scale;
520+
scale = Math.max(0.25, Math.min(5, scale * delta));
521+
522+
// Zoom towards mouse position
523+
const rect = content.getBoundingClientRect();
524+
const mouseX = e.clientX - rect.left;
525+
const mouseY = e.clientY - rect.top;
526+
const scaleDiff = scale - oldScale;
527+
panX -= (mouseX - rect.width / 2 - panX) * scaleDiff / oldScale;
528+
panY -= (mouseY - rect.height / 2 - panY) * scaleDiff / oldScale;
529+
530+
updateTransform();
531+
}, { passive: false });
532+
533+
// Pan with mouse drag
534+
content.addEventListener('mousedown', (e) => {
535+
if (e.button === 0) { // Left mouse button
536+
isDragging = true;
537+
content.classList.add('dragging');
538+
startX = e.clientX;
539+
startY = e.clientY;
540+
startPanX = panX;
541+
startPanY = panY;
542+
}
221543
});
544+
545+
document.addEventListener('mousemove', (e) => {
546+
if (isDragging) {
547+
panX = startPanX + (e.clientX - startX);
548+
panY = startPanY + (e.clientY - startY);
549+
updateTransform();
550+
}
551+
});
552+
553+
document.addEventListener('mouseup', () => {
554+
if (isDragging) {
555+
isDragging = false;
556+
content.classList.remove('dragging');
557+
}
558+
});
559+
560+
// Close on Escape key
561+
const handleEscape = (e) => {
562+
if (e.key === 'Escape') {
563+
closeBtn.click();
564+
document.removeEventListener('keydown', handleEscape);
565+
}
566+
};
567+
document.addEventListener('keydown', handleEscape);
568+
569+
// Assemble modal
570+
modal.appendChild(content);
571+
modal.appendChild(toolbar);
572+
document.body.appendChild(modal);
573+
574+
// Trigger animation
575+
requestAnimationFrame(() => {
576+
modal.classList.add('active');
577+
});
578+
579+
// Initial transform
580+
updateTransform();
222581
}
223582

224583
// Wait for DOM ready and check again

0 commit comments

Comments
 (0)