Skip to content

Commit 59098aa

Browse files
committed
Add support for Mermaid diagrams rendering
This PR introduces the possibility to add mermaid diagrams directly as code in the antora docs. These diagrams are then rendered client-side in real time. They support zooming and panning, making them really easy to view and navigate. Support for mermaid will allow for easy integration of high level conforma diagrams into our docs, that are easily versionable (as opposed to embedding pre-rendered images generated by other tools) Assisted by: Claude Code Ref: https://issues.redhat.com/browse/EC-1270
1 parent 945e700 commit 59098aa

File tree

8 files changed

+1669
-47
lines changed

8 files changed

+1669
-47
lines changed

antora/antora-playbook.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,4 @@ antora:
5151
asciidoc:
5252
extensions:
5353
- '@asciidoctor/tabs'
54+
- './extensions/mermaid.js'

antora/extensions/mermaid.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
function createMermaidExtension() {
2+
return function () {
3+
this.block('mermaid', function () {
4+
const self = this
5+
self.named('mermaid')
6+
self.onContext('literal')
7+
self.process(function (parent, reader) {
8+
const source = reader.getLines().join('\n')
9+
const html = `<div class="mermaid">
10+
${source}
11+
</div>`
12+
return self.createBlock(parent, 'pass', html)
13+
})
14+
})
15+
}
16+
}
17+
18+
module.exports = createMermaidExtension()

antora/package-lock.json

Lines changed: 1393 additions & 45 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

antora/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
},
2525
"dependencies": {
2626
"@antora/lunr-extension": "^1.0.0-alpha.10",
27-
"@asciidoctor/tabs": "^1.0.0-beta.6"
27+
"@asciidoctor/tabs": "^1.0.0-beta.6",
28+
"mermaid": "^11.10.1"
2829
}
2930
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* Mermaid diagram styling */
2+
.mermaid {
3+
text-align: center;
4+
padding: 0.5rem;
5+
border: 1px solid #e1e5e9;
6+
border-radius: 6px;
7+
background-color: #ffffff;
8+
overflow-x: auto;
9+
cursor: zoom-in;
10+
transition: box-shadow 0.2s ease;
11+
}
12+
13+
.mermaid:hover {
14+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
15+
}
16+
17+
/* Make sure text in diagrams is readable */
18+
.mermaid span {
19+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
20+
font-size: 18px !important;
21+
}
22+
23+
/* Ensure diagrams don't get cut off */
24+
.mermaid svg {
25+
max-width: 100%;
26+
height: auto;
27+
display: block;
28+
}
29+
30+
/* Zoom overlay styling */
31+
.mermaid-zoom-overlay {
32+
backdrop-filter: blur(4px);
33+
}
34+
35+
.mermaid-zoom-overlay .mermaid {
36+
cursor: zoom-out;
37+
border: none;
38+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
39+
max-width: 95vw;
40+
max-height: 95vh;
41+
overflow: auto;
42+
}
43+
44+
/* Responsive adjustments */
45+
@media (max-width: 768px) {
46+
.mermaid {
47+
padding: 0.5rem;
48+
margin: 0.5rem 0;
49+
}
50+
51+
.mermaid-zoom-overlay .mermaid {
52+
max-width: 98vw;
53+
max-height: 98vh;
54+
padding: 10px;
55+
}
56+
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
document.addEventListener('DOMContentLoaded', function() {
2+
// Load Mermaid from CDN
3+
const script = document.createElement('script');
4+
script.src = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/mermaid.min.js';
5+
script.onload = function() {
6+
// Initialize Mermaid when the script loads
7+
mermaid.initialize({
8+
startOnLoad: true,
9+
theme: 'default',
10+
securityLevel: 'loose',
11+
flowchart: {
12+
useMaxWidth: false,
13+
htmlLabels: true
14+
},
15+
sequence: {
16+
useMaxWidth: false
17+
},
18+
gantt: {
19+
useMaxWidth: false
20+
}
21+
});
22+
23+
// Add zoom functionality to Mermaid diagrams
24+
mermaid.init().then(() => {
25+
document.querySelectorAll('.mermaid').forEach(function(diagram) {
26+
// Make diagrams clickable for zoom
27+
diagram.style.cursor = 'zoom-in';
28+
diagram.style.maxWidth = '100%';
29+
diagram.style.height = 'auto';
30+
31+
// Add click handler for zoom
32+
diagram.addEventListener('click', function() {
33+
// Create zoom overlay
34+
const overlay = document.createElement('div');
35+
overlay.className = 'mermaid-zoom-overlay';
36+
overlay.style.cssText = `
37+
position: fixed;
38+
top: 0;
39+
left: 0;
40+
width: 100%;
41+
height: 100%;
42+
background: rgba(0, 0, 0, 0.8);
43+
display: flex;
44+
justify-content: center;
45+
align-items: center;
46+
z-index: 9999;
47+
cursor: grab;
48+
overflow: hidden;
49+
`;
50+
51+
// Create container for panning
52+
const container = document.createElement('div');
53+
container.style.cssText = `
54+
position: relative;
55+
cursor: grab;
56+
user-select: none;
57+
transform-origin: center center;
58+
`;
59+
60+
// Clone the diagram for the overlay
61+
const clonedDiagram = diagram.cloneNode(true);
62+
clonedDiagram.style.cssText = `
63+
max-width: none;
64+
max-height: none;
65+
background: white;
66+
padding: 20px;
67+
border-radius: 8px;
68+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
69+
cursor: grab;
70+
user-select: none;
71+
transform-origin: center center;
72+
`;
73+
74+
container.appendChild(clonedDiagram);
75+
overlay.appendChild(container);
76+
document.body.appendChild(overlay);
77+
78+
// Zoom and pan functionality
79+
let scale = 1;
80+
let translateX = 0;
81+
let translateY = 0;
82+
let isDragging = false;
83+
let lastX = 0;
84+
let lastY = 0;
85+
86+
function updateTransform() {
87+
container.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
88+
}
89+
90+
// Wheel zoom
91+
overlay.addEventListener('wheel', function(e) {
92+
e.preventDefault();
93+
const delta = e.deltaY > 0 ? 0.9 : 1.1;
94+
const newScale = Math.max(0.5, Math.min(5, scale * delta));
95+
96+
if (newScale !== scale) {
97+
const rect = overlay.getBoundingClientRect();
98+
const centerX = rect.width / 2;
99+
const centerY = rect.height / 2;
100+
const mouseX = e.clientX - rect.left;
101+
const mouseY = e.clientY - rect.top;
102+
103+
const offsetX = mouseX - centerX;
104+
const offsetY = mouseY - centerY;
105+
106+
translateX = translateX - offsetX * (newScale / scale - 1);
107+
translateY = translateY - offsetY * (newScale / scale - 1);
108+
scale = newScale;
109+
110+
updateTransform();
111+
}
112+
});
113+
114+
// Mouse drag
115+
container.addEventListener('mousedown', function(e) {
116+
if (e.button === 0) { // Left mouse button
117+
isDragging = true;
118+
lastX = e.clientX;
119+
lastY = e.clientY;
120+
container.style.cursor = 'grabbing';
121+
overlay.style.cursor = 'grabbing';
122+
e.preventDefault();
123+
}
124+
});
125+
126+
overlay.addEventListener('mousemove', function(e) {
127+
if (isDragging) {
128+
const deltaX = e.clientX - lastX;
129+
const deltaY = e.clientY - lastY;
130+
translateX += deltaX;
131+
translateY += deltaY;
132+
lastX = e.clientX;
133+
lastY = e.clientY;
134+
updateTransform();
135+
}
136+
});
137+
138+
overlay.addEventListener('mouseup', function() {
139+
isDragging = false;
140+
container.style.cursor = 'grab';
141+
overlay.style.cursor = 'grab';
142+
});
143+
144+
// Double-click to reset zoom
145+
container.addEventListener('dblclick', function(e) {
146+
e.stopPropagation();
147+
scale = 1;
148+
translateX = 0;
149+
translateY = 0;
150+
updateTransform();
151+
});
152+
153+
// Close on background click (but not on diagram)
154+
overlay.addEventListener('click', function(e) {
155+
if (e.target === overlay) {
156+
document.body.removeChild(overlay);
157+
}
158+
});
159+
160+
// Close on escape key
161+
const escapeHandler = function(e) {
162+
if (e.key === 'Escape') {
163+
document.body.removeChild(overlay);
164+
document.removeEventListener('keydown', escapeHandler);
165+
}
166+
};
167+
document.addEventListener('keydown', escapeHandler);
168+
169+
// Add instructions
170+
const instructions = document.createElement('div');
171+
instructions.style.cssText = `
172+
position: absolute;
173+
top: 20px;
174+
left: 20px;
175+
background: rgba(0, 0, 0, 0.7);
176+
color: white;
177+
padding: 10px 15px;
178+
border-radius: 5px;
179+
font-size: 14px;
180+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
181+
z-index: 10000;
182+
pointer-events: none;
183+
`;
184+
instructions.innerHTML = `
185+
<div>🔍 Scroll to zoom</div>
186+
<div>🖱️ Drag to pan</div>
187+
<div>📱 Double-click to reset</div>
188+
<div>⌨️ ESC to close</div>
189+
`;
190+
overlay.appendChild(instructions);
191+
});
192+
});
193+
});
194+
};
195+
document.head.appendChild(script);
196+
});

antora/supplemental-ui/partials/footer-scripts.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script id="site-script" src="{{uiRootPath}}/js/site.js" data-ui-root-path="{{{uiRootPath}}}"></script>
22
<script src="{{uiRootPath}}/js/zoom.js"></script>
3+
<script src="{{uiRootPath}}/js/mermaid-init.js"></script>
34
{{#if env.SITE_SEARCH_PROVIDER}}
45
{{> search-scripts}}
56
{{/if}}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
<link rel="stylesheet" href="/css/site.min.css">
2-
<link rel="stylesheet" href="{{{uiRootPath}}}/css/vendor/tabs.css">
2+
<link rel="stylesheet" href="{{{uiRootPath}}}/css/vendor/tabs.css">
3+
<link rel="stylesheet" href="{{{uiRootPath}}}/css/mermaid.css">

0 commit comments

Comments
 (0)