This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Sunday, 09 November 2025
ملاحظة: هذا جزء من تجاربي مع AI/ طريقة لإنفاق 1000 دولار كرمز ائتمانات الويب. لقد أطعمت هذه مجموعة من الأوراق، فهمي، أسئلة كان علي توليد هذه المقالة. إنها ممتعة وتملأ فجوة لم أرها ملأها في أي مكان آخر.
ويستند هذا المقال إلى مواد سابقة: إذا لم تكن قد فعلت بالفعل، تحقق من إضافة حوريّة. js مع, مُدِّر المواضيع للمُحْرَمَة، و ، ، ، ، ، ، ، ، ، ، ، تعزيز حورية الحورية مع البان/الزوم والتصديرهذا الغوص العميق يفسّر الداخليات وراء تلك التنفيذات.
حورية البحر. js رائعة حقاً. اكتب نصاً بسيطاً ، احصل على رسوم بيانية جميلة. لا مزيد من التعثر مع Visio أو رسم.io ، فقدان ملفات المصدر ، أو الحفاظ على ملفات صور منفصلة. كل شيء يعيش في علامة ، تحكم النسخة جنباً إلى جنب مع رمزك.
ولكن أردت أن أعرف كيف في الواقع يعمل تحت الغطاء. كيف يفعل هذا:
graph LR
A[Text] --> B[Magic?]
B --> C[Beautiful Diagram]
و الأهم من ذلك، كيف يمكنك ربطه به لإضافة سمات مثل بان/زوم، التبديل المواضيعي، و خاصية التصدير التي بنيتها لهذا الموقع (متاحة الآن كما يلي: @medorlymedlyluccid/memed-enhancements)?
بعد الكثير من البحث من خلال شفرة مصدر حورية البحر وبناء الامتدادات الحقيقية، هنا كل شيء تعلمته عن كيفية عمل حورية البحر داخليا وكيفية توسيعه بشكل صحيح.
تقوم حورية البحر بتحويل تعاريف النص إلى رسوم بيانية. فكر في "ماركداون للرسميات."
الطريقة القديمة:
1. 2. 3. كصادرات 4. مُخْطَطَة في الطُّقق 5. ابحث المصدر ملفّ حرّر إعادة تصدير استبدل صورة...
طريقة الحورية:
1. 2. م أنجز
تحديث الإيطالية فقط حرّر نص النسخة تحكم هو فقط نص يعمل بوصة هدف? نعم!
تدعم الحوريات عدداً سخيفاً من أنواع الرسوم البيانية:
graph LR
A[Flowcharts] --> B[Sequence Diagrams]
B --> C[Class Diagrams]
C --> D[State Diagrams]
D --> E[ER Diagrams]
E --> F[Gantt Charts]
F --> G[Pie Charts]
G --> H[Git Graphs]
H --> I[User Journeys]
I --> J[And many more...]
انظر S انظر « فآ » م للقائمة الكاملة.
هذا ما يحدث عندما تقوم حورية البحر بتقديم رسم بياني:
graph TD
A[Text Definition] --> B[Lexer/Tokenizer]
B --> C[Parser]
C --> D[AST Built]
D --> E[Diagram Type Detected]
E --> F[Type-Specific Renderer]
F --> G[SVG Generated]
G --> H[Inserted into DOM]
H --> I[Your Enhancements Run]
style A stroke:#059669,stroke-width:3px,color:#10b981
style D stroke:#2563eb,stroke-width:3px,color:#3b82f6
style G stroke:#7c3aed,stroke-width:3px,color:#8b5cf6
style I stroke:#d97706,stroke-width:3px,color:#f59e0b
دعونا نكسر كل خطوة.
يبدأ كل شيء بالنص. تكتب الرسوم البيانية في DSL (اللغة الخاصة بالمجال):
// Flowchart
const diagram = `
graph TD
A[Start] --> B{Is it working?}
B -->|Yes| C[Great!]
B -->|No| D[Debug time]
`;
الـ elder يكسر نص بوصة رمزيّات. على سبيل المثال، هذا سطر:
A[Start] --> B{Decision}
مثل:
[
{ type: 'NODE_ID', value: 'A' },
{ type: 'NODE_TEXT', value: 'Start' },
{ type: 'ARROW', value: '-->' },
{ type: 'NODE_ID', value: 'B' },
{ type: 'NODE_TEXT', value: 'Decision' },
{ type: 'NODE_SHAPE', value: 'diamond' } // from { }
]
يستهلك المحلل الرسومات ويبني شجرة لصياغة الملخصات (AST):
// Simplified AST structure
{
type: 'flowchart',
direction: 'TD',
nodes: [
{ id: 'A', text: 'Start', shape: 'rect' },
{ id: 'B', text: 'Decision', shape: 'diamond' }
],
edges: [
{ from: 'A', to: 'B', type: 'arrow' }
]
}
تستخدم حورية الحورية حوريات مختلفة لـ كل نوع تخطيطي. هذه غالباً ما يتم توليدها من الملفات التي تستخدم J (مثل Yacc/Bison for JavaScript).
تقوم الحورية بكشف نوع الرسم البياني من السطر الأول:
// Simplified detection logic
if (text.match(/^\s*graph/)) return 'flowchart';
if (text.match(/^\s*sequenceDiagram/)) return 'sequence';
if (text.match(/^\s*classDiagram/)) return 'class';
// ... etc
ولكل نوع من أنواع الرسوم البيانية مُصَنِّع خاص به. يأخذ المُعَدِّل AST ويولِّد عناصر SVG.
فيما يتعلق بتدفقات الصور التجارية، تستخدم حَرِمَة الحَرْمَمَة لمكتبة تخطيط الرسوم البيانية. بالنسبة للآخرين، تستخدم خوارزميات مخصصة أو مكتبات مثل Cytoscape.
// Simplified flowchart renderer
export const draw = function (text, id, version, diagObj) {
const graph = diagObj.db; // The AST
const svg = d3.select(`#${id}`);
// Render nodes
graph.getVertices().forEach(vertex => {
drawNode(svg, vertex);
});
// Render edges
graph.getEdges().forEach(edge => {
drawEdge(svg, edge);
});
// Apply layout algorithm
dagre.layout(graph);
};
المُجَرِّر يُنتِج رَمْز SVG:
<svg xmlns="http://www.w3.org/2000/svg">
<g class="node">
<rect x="0" y="0" width="100" height="50"/>
<text x="50" y="25">Start</text>
</g>
<g class="edge">
<path d="M 100 25 L 200 25" stroke="#333"/>
</g>
</svg>
المُحْوَلَة تُفِد كُل .mermaid ويستبدلها بـ SVG translated SVG:
// From mermaid.ts
export const init = async function (config, nodes) {
const nodesToProcess = nodes || document.querySelectorAll('.mermaid');
for (const node of nodesToProcess) {
const id = `mermaid-${Date.now()}-${Math.random()}`;
const txt = node.textContent;
const { svg } = await render(id, txt);
node.innerHTML = svg;
}
};
بعد إدخال الحوريّة SVG، يمكنك تعزيزها. هنا حيث كلّ مؤازراتي في:
المزيد حول هذا أدناه.
الآن بما أننا نعرف كيف تعمل حورية البحر، دعونا نستكشف كيف نوسعها.
وأهم عملية تمديد أساسية هي التشكيلة التالية:
import mermaid from 'mermaid';
mermaid.initialize({
startOnLoad: true,
theme: 'dark',
securityLevel: 'loose',
flowchart: {
curve: 'basis',
padding: 15
}
});
لقد غطيت هذا بشكل مكثف في مُدِّر المواضيع للمُحْرَمَةلكن هذا هو التنفيذ الرئيسي:
و لا يمكنك تغييره بعد ذلك. ولكن إذا أردت إعادة إدخال الرسوم التخطيطية مع سمة جديدة، تحتاج إلى مصدر الرسم الأصلي - أي حورية لا يخزن في DOM.
يخزن المحتوى الأصلي قبل التصريف، ثم يسترجع ويعاد الاسترجاع عند التبديل
// From my theme-switcher implementation
const originalData = new Map();
// Save original content before first render
const saveOriginalData = async () => {
const elements = document.querySelectorAll('.mermaid');
elements.forEach(element => {
const id = element.id || `mermaid-${Date.now()}`;
element.id = id;
// Store the original diagram source
if (!originalData.has(id)) {
originalData.set(id, element.textContent?.trim());
}
});
};
// When theme changes, restore and re-render
const loadMermaid = async (theme) => {
mermaid.initialize({
startOnLoad: false,
theme: theme
});
const elements = document.querySelectorAll('.mermaid');
for (const element of elements) {
const source = originalData.get(element.id);
if (source) {
element.innerHTML = ''; // Clear
element.removeAttribute('data-processed');
const { svg } = await mermaid.render(
`mermaid-svg-${element.id}`,
source
);
element.innerHTML = svg;
}
}
};
طرق الكشف عن مواضيع متعددة (المواقع التي تعالج فيها المواضيع بشكل مختلف):
function detectTheme() {
// Check various sources
if (typeof window.__themeState !== 'undefined') {
return window.__themeState;
}
if (localStorage.theme) {
return localStorage.theme;
}
if (document.documentElement.classList.contains('dark')) {
return 'dark';
}
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
}
انظر S انظر كود مُزِّر الموضوع الكامل للتفاصيل.
هنا حيث يحدث السحر الحقيقي. بعد أن تقوم حورية البحر بترجمة، يمكنك إضافة خصائص تفاعلية.
لقد غطيت هذا بشكل مكثف في تعزيز حورية الحورية مع البان/الزوم والتصديرلذا سأسلط الضوء على التقنيات الرئيسية هنا
أنشئ حاوية غلاف للضوابط:
function wrapDiagram(element) {
if (element.closest('.mermaid-wrapper')) {
return element.closest('.mermaid-wrapper');
}
const wrapper = document.createElement('div');
wrapper.className = 'mermaid-wrapper';
wrapper.id = `wrapper-${element.id}`;
element.parentNode.insertBefore(wrapper, element);
wrapper.appendChild(element);
return wrapper;
}
& بوصة Svg-pan- zoom:
import svgPanZoom from 'svg-pan-zoom';
const panZoomInstances = new Map();
function initPanZoom(svgElement, diagramId) {
// Clean up existing instance
if (panZoomInstances.has(diagramId)) {
panZoomInstances.get(diagramId).destroy();
panZoomInstances.delete(diagramId);
}
const instance = svgPanZoom(svgElement, {
zoomEnabled: true,
controlIconsEnabled: false,
fit: true,
center: true,
minZoom: 0.1,
maxZoom: 10
});
panZoomInstances.set(diagramId, instance);
return instance;
}
لا يمكن إنشاء هذا النظام:
function createControlButtons(container, diagramId) {
const controlsDiv = document.createElement('div');
controlsDiv.className = 'mermaid-controls';
const buttons = [
{ icon: 'bx-fullscreen', title: 'Fullscreen', action: 'fullscreen' },
{ icon: 'bx-zoom-in', title: 'Zoom In', action: 'zoomIn' },
{ icon: 'bx-zoom-out', title: 'Zoom Out', action: 'zoomOut' },
{ icon: 'bx-reset', title: 'Reset', action: 'reset' },
{ icon: 'bx-move', title: 'Pan', action: 'pan' },
{ icon: 'bx-image', title: 'Export PNG', action: 'exportPng' },
{ icon: 'bx-code-alt', title: 'Export SVG', action: 'exportSvg' }
];
buttons.forEach(btn => {
const button = document.createElement('button');
button.className = `mermaid-control-btn bx ${btn.icon}`;
button.setAttribute('data-action', btn.action);
button.setAttribute('data-diagram-id', diagramId);
controlsDiv.appendChild(button);
});
container.appendChild(controlsDiv);
}
لا ترفق المستمعين على كل زر. استخدم مصدر الحدث:
document.addEventListener('click', (e) => {
const target = e.target;
if (!target.classList.contains('mermaid-control-btn')) return;
const action = target.getAttribute('data-action');
const diagramId = target.getAttribute('data-diagram-id');
const panZoom = panZoomInstances.get(diagramId);
switch (action) {
case 'zoomIn': panZoom?.zoomIn(); break;
case 'zoomOut': panZoom?.zoomOut(); break;
case 'reset': panZoom?.reset(); break;
// ... etc
}
});
التحدي: عناصر SVG لها خصائص ديناميكية، تحويلات شاملة/زومية، وأساليب موروثة. لكي يتم التصدير بشكل صحيح، تحتاج إلى:
& بوصة html إلى صورة:
import { toPng, toSvg } from 'html-to-image';
async function exportDiagram(container, format, diagramId) {
const svgElement = container.querySelector('svg');
if (!svgElement) return;
// Clone to avoid modifying original
const clonedSvg = svgElement.cloneNode(true);
// Get or calculate viewBox
let viewBox = clonedSvg.getAttribute('viewBox');
if (!viewBox) {
const bbox = svgElement.getBBox();
viewBox = `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`;
clonedSvg.setAttribute('viewBox', viewBox);
}
// Set explicit dimensions
const [, , width, height] = viewBox.split(' ').map(Number);
clonedSvg.setAttribute('width', width);
clonedSvg.setAttribute('height', height);
// Remove pan-zoom transforms
clonedSvg.removeAttribute('style');
// Create off-screen container
const temp = document.createElement('div');
temp.style.position = 'absolute';
temp.style.left = '-9999px';
temp.appendChild(clonedSvg);
document.body.appendChild(temp);
// Export
const dataUrl = format === 'png'
? await toPng(clonedSvg, { pixelRatio: 2 })
: await toSvg(clonedSvg);
downloadFile(dataUrl, `diagram-${Date.now()}.${format}`);
// Cleanup
document.body.removeChild(temp);
}
التفاصيل الجوهرية:
انظر S انظر (رمز التصدير الكامل) لمزيد من التفاصيل.
لقد حزمت كل هذه التعزيزات @medorlymedlyluccid/memed-enhancementsانظر: إدخالات مُحسنات مُنْشِشِشِر كحزمة Nnpm لتفاصيل كاملة.
هذه هي الطريقة التي تنسجم بها مع بعضها البعض:
graph TB
A[User Initializes] --> B[init Function]
B --> C[initMermaid]
B --> D[enhanceMermaidDiagrams]
C --> E[Theme Detection]
C --> F[Event Listeners]
C --> G[Mermaid Rendering]
E --> E1[Global State]
E --> E2[LocalStorage]
E --> E3[DOM Class]
E --> E4[OS Preference]
F --> F1[Custom Events]
F --> F2[Media Query]
G --> H[Apply Enhancements]
D --> H
H --> I[Wrap Diagrams]
H --> J[Init Pan/Zoom]
H --> K[Add Controls]
I --> L[Interactive Diagram]
J --> L
K --> L
L --> M[User Interactions]
M --> M1[Zoom In/Out]
M --> M2[Pan]
M --> M3[Fullscreen]
M --> M4[Export PNG/SVG]
style A stroke:#059669,stroke-width:3px,color:#10b981
style L stroke:#2563eb,stroke-width:3px,color:#3b82f6
style M stroke:#7c3aed,stroke-width:3px,color:#8b5cf6
npm install @mostlylucid/mermaid-enhancements
import { init } from '@mostlylucid/mermaid-enhancements';
import '@mostlylucid/mermaid-enhancements/styles.css';
await init();
هذه هي! رسومات حورية البحر الخاصة بك الآن:
كما أنا مغطى في إضافة حوريّة. js مععليك إعادة تأريخ الحوريّة بعد مبادلة المحتوى HTMX:
// On page load
document.addEventListener('DOMContentLoaded', function () {
mermaid.initialize({ startOnLoad: true });
});
// After HTMX swaps content
document.body.addEventListener('htmx:afterSwap', function(evt) {
mermaid.run();
});
مع حزمة التعزيزات:
import { init, enhanceMermaidDiagrams } from '@mostlylucid/mermaid-enhancements';
// Initial load
await init();
// After HTMX swap
document.body.addEventListener('htmx:afterSwap', async function() {
await init(); // Re-init Mermaid with current theme
enhanceMermaidDiagrams(); // Re-apply enhancements
});
بعد بناء هذه الأشياء وتصحيح حالات الحافة الغريبة، وهنا ما يعمل:
الحوريّة لا تحافظ على مصدر الرسم التخطيطيّ الأصليّ بعد التخلّص منه، يجب أن تخزنه بنفسك:
const originalData = new Map();
// Before first render
element.setAttribute('data-original-code', element.textContent);
originalData.set(element.id, element.textContent);
// When re-rendering
element.innerHTML = originalData.get(element.id);
تسربات الذاكرة حقيقية. تدمير الحالات قبل إنشاء حالات جديدة:
if (panZoomInstances.has(id)) {
try {
panZoomInstances.get(id).destroy();
} catch (e) {
console.warn('Failed to destroy:', e);
}
panZoomInstances.delete(id);
}
لا ترفق مستمعات إلى فرادى الأزرار:
// ❌ Don't do this
buttons.forEach(btn => {
btn.addEventListener('click', handler);
});
// ✅ Do this
document.addEventListener('click', (e) => {
if (e.target.matches('.mermaid-control-btn')) {
handleClick(e.target);
}
});
المُحمّل الصاروخي يُؤخّر جافاScript تنفيذ. انتظر للمُتَوَقّعات:
function waitForDependencies(maxAttempts = 50) {
return new Promise((resolve) => {
let attempts = 0;
const check = () => {
if (window.mermaid && window.htmx && window.Alpine) {
resolve();
} else if (attempts >= maxAttempts) {
resolve(); // Give up
} else {
attempts++;
setTimeout(check, Math.min(50 * Math.pow(1.2, attempts), 500));
}
};
check();
});
}
واستبعد نصك الرئيسي من تحميل Rocket:
<script src="main.js" data-cfasync="false"></script>
requestAnimationFrame أفضل توقيتاً من التعسف setTimeout:
// After Mermaid renders
await mermaid.run();
// Wait for paint before enhancing
await new Promise(resolve => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
enhanceMermaidDiagrams();
resolve();
});
});
});
الـ SVGs يمكن أن تكون غريبة.
const svgElement = container.querySelector('svg');
if (!svgElement) {
console.warn('No SVG found');
return;
}
// Clone before modifying
const cloned = svgElement.cloneNode(true);
// Ensure viewBox exists
let viewBox = cloned.getAttribute('viewBox');
if (!viewBox) {
const bbox = svgElement.getBBox();
viewBox = `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`;
}
مع التمهيد المناسب، يجب أن ترى:
Saving original data
Loading mermaid with theme: dark
Mermaid initialized
Enhanced 3 diagrams
بعد التنفيذ:
الـمُشَكِّل:
-
window.mermaid)لا يعمل بان/زوم لا يعمل:
pointer-events: none)المصدرات فقط زاوية:
السمة ليس التبديل:
data-processedلا تُحمِل التعزيزات قبل الحاجة:
let enhancementsLoaded = false;
async function loadEnhancements() {
if (enhancementsLoaded) return;
const { enhanceMermaidDiagrams } = await import('./enhancements.js');
enhanceMermaidDiagrams();
enhancementsLoaded = true;
}
// Load on first interaction
document.addEventListener('click', (e) => {
if (e.target.closest('.mermaid')) {
loadEnhancements();
}
}, { once: true });
مُسندة مُسندة إلى مُنتَزَع:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
renderDiagram(entry.target);
observer.unobserve(entry.target);
}
});
}, { rootMargin: '100px' });
document.querySelectorAll('.mermaid').forEach(el => {
observer.observe(el);
});
عند إعادة تحجيم أو تغيير الموضوع:
let timeout;
window.addEventListener('resize', () => {
clearTimeout(timeout);
timeout = setTimeout(() => {
panZoomInstances.forEach(instance => {
instance.resize();
instance.fit();
});
}, 250);
});
لكن فهم كيفية عملها داخلياً يسمح لك ببناء بعض التعزيزات الرائعة حقاً. الرؤى الرئيسية:
requestAnimationFrameكل التقنيات التي غطيتها هنا تستخدم في الإنتاج في هذا الموقع @medorlymedlyluccid/memed-enhancementsالمصدر الكامل متاح في الناويـب اللـكـس غالبيـاً منهـا/معظمهـا من العارضات.
جرب الضوابط على الرسوم البيانية في الأعلى!
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.