mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
feat(docs): add interactive Mermaid diagram modal
This commit is contained in:
@@ -3,8 +3,107 @@ import { useRoute } from 'vitepress';
|
|||||||
import { nextTick, onMounted, watch } from 'vue';
|
import { nextTick, onMounted, watch } from 'vue';
|
||||||
import mermaid from 'mermaid';
|
import mermaid from 'mermaid';
|
||||||
import '@catppuccin/vitepress/theme/macchiato/mauve.css';
|
import '@catppuccin/vitepress/theme/macchiato/mauve.css';
|
||||||
|
import './mermaid-modal.css';
|
||||||
|
|
||||||
let mermaidLoader: Promise<any> | null = null;
|
let mermaidLoader: Promise<any> | null = null;
|
||||||
|
const MERMAID_MODAL_ID = 'mermaid-diagram-modal';
|
||||||
|
|
||||||
|
function closeMermaidModal() {
|
||||||
|
if (typeof document === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = document.getElementById(MERMAID_MODAL_ID);
|
||||||
|
if (!modal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.classList.remove('is-open');
|
||||||
|
document.body.classList.remove('mermaid-modal-open');
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureMermaidModal(): HTMLDivElement {
|
||||||
|
const existing = document.getElementById(MERMAID_MODAL_ID);
|
||||||
|
if (existing) {
|
||||||
|
return existing as HTMLDivElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = document.createElement('div');
|
||||||
|
modal.id = MERMAID_MODAL_ID;
|
||||||
|
modal.className = 'mermaid-modal';
|
||||||
|
modal.innerHTML = `
|
||||||
|
<div class="mermaid-modal__backdrop" data-mermaid-close="true"></div>
|
||||||
|
<div class="mermaid-modal__dialog" role="dialog" aria-modal="true" aria-label="Expanded Mermaid diagram">
|
||||||
|
<button class="mermaid-modal__close" type="button" aria-label="Close Mermaid diagram">Close</button>
|
||||||
|
<div class="mermaid-modal__content"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
modal.addEventListener('click', (event) => {
|
||||||
|
const target = event.target as HTMLElement | null;
|
||||||
|
if (!target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.closest('[data-mermaid-close="true"]') || target.closest('.mermaid-modal__close')) {
|
||||||
|
closeMermaidModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key === 'Escape' && modal.classList.contains('is-open')) {
|
||||||
|
closeMermaidModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
return modal;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openMermaidModal(sourceNode: HTMLElement) {
|
||||||
|
if (typeof document === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = ensureMermaidModal();
|
||||||
|
const content = modal.querySelector<HTMLDivElement>('.mermaid-modal__content');
|
||||||
|
if (!content) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
content.replaceChildren(sourceNode.cloneNode(true));
|
||||||
|
modal.classList.add('is-open');
|
||||||
|
document.body.classList.add('mermaid-modal-open');
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachMermaidInteractions(nodes: HTMLElement[]) {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.dataset.mermaidInteractive === 'true') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const svg = node.querySelector<HTMLElement>('svg');
|
||||||
|
if (!svg) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.classList.add('mermaid-interactive');
|
||||||
|
node.setAttribute('role', 'button');
|
||||||
|
node.setAttribute('tabindex', '0');
|
||||||
|
node.setAttribute('aria-label', 'Open Mermaid diagram in full view');
|
||||||
|
|
||||||
|
const open = () => openMermaidModal(svg);
|
||||||
|
node.addEventListener('click', open);
|
||||||
|
node.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key === 'Enter' || event.key === ' ') {
|
||||||
|
event.preventDefault();
|
||||||
|
open();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
node.dataset.mermaidInteractive = 'true';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getMermaid() {
|
async function getMermaid() {
|
||||||
if (!mermaidLoader) {
|
if (!mermaidLoader) {
|
||||||
@@ -54,6 +153,7 @@ async function renderMermaidBlocks() {
|
|||||||
|
|
||||||
if (nodes.length > 0) {
|
if (nodes.length > 0) {
|
||||||
await mermaid.run({ nodes });
|
await mermaid.run({ nodes });
|
||||||
|
attachMermaidInteractions(nodes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
69
docs/.vitepress/theme/mermaid-modal.css
Normal file
69
docs/.vitepress/theme/mermaid-modal.css
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
.mermaid-interactive {
|
||||||
|
cursor: zoom-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-interactive:focus-visible {
|
||||||
|
outline: 2px solid var(--vp-c-brand-1);
|
||||||
|
outline-offset: 4px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-modal {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 200;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-modal.is-open {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-modal__backdrop {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-modal__dialog {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
margin: 4vh auto;
|
||||||
|
width: min(96vw, 1800px);
|
||||||
|
max-height: 92vh;
|
||||||
|
border: 1px solid var(--vp-c-border);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--vp-c-bg);
|
||||||
|
box-shadow: var(--vp-shadow-4);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-modal__close {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 16px;
|
||||||
|
margin-top: 12px;
|
||||||
|
border: 1px solid var(--vp-c-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
background: var(--vp-c-bg-soft);
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-modal__content {
|
||||||
|
overflow: auto;
|
||||||
|
max-height: calc(92vh - 56px);
|
||||||
|
padding: 8px 16px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-modal__content svg {
|
||||||
|
max-width: none;
|
||||||
|
width: max-content;
|
||||||
|
height: auto;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mermaid-modal-open {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user