tfrere HF Staff commited on
Commit
bb01efa
·
1 Parent(s): d04d17b

add url changer, fix base model perf, add mermaid zoom

Browse files
app/src/components/HtmlEmbed.astro CHANGED
@@ -172,7 +172,7 @@ const htmlWithId =
172
  }
173
  .html-embed__card.is-frameless {
174
  background: transparent;
175
- border-color: transparent;
176
  padding: 0;
177
  }
178
  .html-embed__desc {
 
172
  }
173
  .html-embed__card.is-frameless {
174
  background: transparent;
175
+ border: none;
176
  padding: 0;
177
  }
178
  .html-embed__desc {
app/src/components/TableOfContents.astro CHANGED
@@ -221,6 +221,7 @@ const { tableOfContentAutoCollapse = false } = Astro.props as Props;
221
 
222
  let prevActiveIdx = -1;
223
  let prevActiveElements = new Set();
 
224
 
225
  const setCollapsedState = (activeIdx) => {
226
  if (!autoCollapse) return;
@@ -502,9 +503,40 @@ const { tableOfContentAutoCollapse = false } = Astro.props as Props;
502
  let lastRequestedIdx = -1;
503
  let isProcessing = false;
504
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  const onScroll = () => {
506
  // active link highlight
507
  let activeIdx = -1;
 
 
508
  for (let i = headingsArr.length - 1; i >= 0; i--) {
509
  const top = headingsArr[i].getBoundingClientRect().top;
510
  if (top - 60 <= 0) {
@@ -516,10 +548,17 @@ const { tableOfContentAutoCollapse = false } = Astro.props as Props;
516
  actives.forEach((a) => a.classList.add("active"));
517
  // Utiliser l'index du heading actif (n'importe quel niveau)
518
  activeIdx = i;
 
519
  break;
520
  }
521
  }
522
 
 
 
 
 
 
 
523
  if (activeIdx === prevActiveIdx) return;
524
 
525
  // Sauvegarder la dernière demande
@@ -551,9 +590,53 @@ const { tableOfContentAutoCollapse = false } = Astro.props as Props;
551
  if (autoCollapse) setCollapsedState(0);
552
 
553
  window.addEventListener("scroll", onScroll);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
554
  // Initialize state
555
  onScroll();
556
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
  // Close mobile accordion when a link inside it is clicked
558
  if (holderMobile) {
559
  const details = holderMobile.closest("details");
 
221
 
222
  let prevActiveIdx = -1;
223
  let prevActiveElements = new Set();
224
+ let prevActiveHeadingId = null;
225
 
226
  const setCollapsedState = (activeIdx) => {
227
  if (!autoCollapse) return;
 
503
  let lastRequestedIdx = -1;
504
  let isProcessing = false;
505
 
506
+ // Fonction pour mettre à jour l'URL avec l'ancre actuelle
507
+ const updateURL = (headingId) => {
508
+ if (!headingId) return;
509
+
510
+ const newUrl = `${window.location.pathname}${window.location.search}#${headingId}`;
511
+
512
+ // Mettre à jour l'URL sans recharger la page
513
+ if (window.location.href !== newUrl) {
514
+ history.pushState(null, null, newUrl);
515
+
516
+ // Communiquer avec l'iframe parent (pour les Spaces HF)
517
+ if (window.parent !== window) {
518
+ try {
519
+ window.parent.postMessage(
520
+ {
521
+ type: "urlChange",
522
+ url: newUrl,
523
+ hash: headingId,
524
+ },
525
+ "*",
526
+ );
527
+ } catch (e) {
528
+ // Ignorer les erreurs de communication avec le parent
529
+ console.debug("Could not communicate with parent window:", e);
530
+ }
531
+ }
532
+ }
533
+ };
534
+
535
  const onScroll = () => {
536
  // active link highlight
537
  let activeIdx = -1;
538
+ let activeHeadingId = null;
539
+
540
  for (let i = headingsArr.length - 1; i >= 0; i--) {
541
  const top = headingsArr[i].getBoundingClientRect().top;
542
  if (top - 60 <= 0) {
 
548
  actives.forEach((a) => a.classList.add("active"));
549
  // Utiliser l'index du heading actif (n'importe quel niveau)
550
  activeIdx = i;
551
+ activeHeadingId = headingsArr[i].id;
552
  break;
553
  }
554
  }
555
 
556
+ // Mettre à jour l'URL si la section active a changé
557
+ if (activeHeadingId && activeHeadingId !== prevActiveHeadingId) {
558
+ updateURL(activeHeadingId);
559
+ prevActiveHeadingId = activeHeadingId;
560
+ }
561
+
562
  if (activeIdx === prevActiveIdx) return;
563
 
564
  // Sauvegarder la dernière demande
 
590
  if (autoCollapse) setCollapsedState(0);
591
 
592
  window.addEventListener("scroll", onScroll);
593
+
594
+ // Gérer la navigation par ancres au chargement de la page
595
+ const handleInitialNavigation = () => {
596
+ const hash = window.location.hash;
597
+ if (hash) {
598
+ const targetElement = document.querySelector(hash);
599
+ if (targetElement) {
600
+ // Attendre que le DOM soit prêt puis faire défiler vers l'élément
601
+ setTimeout(() => {
602
+ targetElement.scrollIntoView({
603
+ behavior: "smooth",
604
+ block: "start",
605
+ });
606
+ // Mettre à jour l'URL après le scroll
607
+ setTimeout(() => {
608
+ updateURL(hash.substring(1)); // Enlever le # du hash
609
+ }, 100);
610
+ }, 100);
611
+ }
612
+ } else {
613
+ // Si pas d'ancre, initialiser avec la première section
614
+ if (headingsArr.length > 0) {
615
+ updateURL(headingsArr[0].id);
616
+ }
617
+ }
618
+ };
619
+
620
  // Initialize state
621
  onScroll();
622
 
623
+ // Gérer la navigation initiale
624
+ handleInitialNavigation();
625
+
626
+ // Gérer les événements de navigation du navigateur (boutons précédent/suivant)
627
+ window.addEventListener("popstate", (event) => {
628
+ const hash = window.location.hash;
629
+ if (hash) {
630
+ const targetElement = document.querySelector(hash);
631
+ if (targetElement) {
632
+ targetElement.scrollIntoView({ behavior: "smooth", block: "start" });
633
+ }
634
+ } else {
635
+ // Si pas d'ancre, aller au début de la page
636
+ window.scrollTo({ top: 0, behavior: "smooth" });
637
+ }
638
+ });
639
+
640
  // Close mobile accordion when a link inside it is clicked
641
  if (holderMobile) {
642
  const details = holderMobile.closest("details");
app/src/content/embeds/base-model-performance.html CHANGED
@@ -78,8 +78,7 @@
78
  }
79
 
80
  .base-model-performance .model-circle {
81
- stroke: white;
82
- stroke-width: 3;
83
  }
84
 
85
  .base-model-performance .model-icon {
@@ -326,39 +325,22 @@
326
  // X-axis label
327
  const xLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
328
  xLabel.setAttribute('x', margin.left + width / 2);
329
- xLabel.setAttribute('y', height + margin.top + 60);
330
  xLabel.setAttribute('text-anchor', 'middle');
331
  xLabel.setAttribute('class', 'axis-label');
332
- xLabel.textContent = 'Model Size';
333
  svg.appendChild(xLabel);
334
 
335
- const xSubLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
336
- xSubLabel.setAttribute('x', margin.left + width / 2);
337
- xSubLabel.setAttribute('y', height + margin.top + 75);
338
- xSubLabel.setAttribute('text-anchor', 'middle');
339
- xSubLabel.setAttribute('class', 'axis-sublabel');
340
- xSubLabel.textContent = 'Billion parameters';
341
- svg.appendChild(xSubLabel);
342
-
343
  // Y-axis label
344
  const yLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
345
  yLabel.setAttribute('x', -(height / 2 + margin.top));
346
- yLabel.setAttribute('y', 50);
347
  yLabel.setAttribute('text-anchor', 'middle');
348
  yLabel.setAttribute('transform', 'rotate(-90)');
349
  yLabel.setAttribute('class', 'axis-label');
350
- yLabel.textContent = 'Win rate (%)';
351
  svg.appendChild(yLabel);
352
 
353
- const ySubLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
354
- ySubLabel.setAttribute('x', -(height / 2 + margin.top));
355
- ySubLabel.setAttribute('y', 65);
356
- ySubLabel.setAttribute('text-anchor', 'middle');
357
- ySubLabel.setAttribute('transform', 'rotate(-90)');
358
- ySubLabel.setAttribute('class', 'axis-sublabel');
359
- ySubLabel.textContent = 'Measured on 12 popular LLM Benchmarks';
360
- svg.appendChild(ySubLabel);
361
-
362
  // Draw arrows
363
  // "faster/cheaper" arrow (shorter, aligned to left)
364
  const arrow1StartX = margin.left + 80;
@@ -419,6 +401,9 @@
419
  if (isHighlight) {
420
  circle.setAttribute('stroke', model.color);
421
  circle.setAttribute('stroke-width', '4');
 
 
 
422
  }
423
  g.appendChild(circle);
424
 
@@ -438,7 +423,7 @@
438
  } else {
439
  logoSize = 36;
440
  }
441
- foreignObject.innerHTML = `<div xmlns="http://www.w3.org/1999/xhtml" style="width: ${isSmolLM3 ? 54 : 44}px; height: ${isSmolLM3 ? 54 : 44}px; display: flex; align-items: center; justify-content: center;"><img src="/data/${model.logo}-logo.svg" style="width: ${logoSize}px; height: ${logoSize}px;" /></div>`;
442
  g.appendChild(foreignObject);
443
 
444
  // Label below
@@ -483,11 +468,14 @@
483
  </div>
484
  `;
485
  tooltip.classList.add('visible');
 
486
 
487
- const rect = container.getBoundingClientRect();
488
- const svgRect = svg.getBoundingClientRect();
489
- tooltip.style.left = `${x * svgRect.width / 900 + 20}px`;
490
- tooltip.style.top = `${y * svgRect.height / 600 - 50}px`;
 
 
491
  });
492
 
493
  g.addEventListener('mouseleave', () => {
 
78
  }
79
 
80
  .base-model-performance .model-circle {
81
+ /* stroke is set dynamically in JavaScript */
 
82
  }
83
 
84
  .base-model-performance .model-icon {
 
325
  // X-axis label
326
  const xLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
327
  xLabel.setAttribute('x', margin.left + width / 2);
328
+ xLabel.setAttribute('y', height + margin.top + 65);
329
  xLabel.setAttribute('text-anchor', 'middle');
330
  xLabel.setAttribute('class', 'axis-label');
331
+ xLabel.innerHTML = '<tspan font-weight="700">Model Size</tspan><tspan font-weight="400"> (Billion parameters)</tspan>';
332
  svg.appendChild(xLabel);
333
 
 
 
 
 
 
 
 
 
334
  // Y-axis label
335
  const yLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
336
  yLabel.setAttribute('x', -(height / 2 + margin.top));
337
+ yLabel.setAttribute('y', 55);
338
  yLabel.setAttribute('text-anchor', 'middle');
339
  yLabel.setAttribute('transform', 'rotate(-90)');
340
  yLabel.setAttribute('class', 'axis-label');
341
+ yLabel.innerHTML = '<tspan font-weight="700">Win Rate (%)</tspan><tspan font-weight="400"> - 12 popular LLM Benchmarks</tspan>';
342
  svg.appendChild(yLabel);
343
 
 
 
 
 
 
 
 
 
 
344
  // Draw arrows
345
  // "faster/cheaper" arrow (shorter, aligned to left)
346
  const arrow1StartX = margin.left + 80;
 
401
  if (isHighlight) {
402
  circle.setAttribute('stroke', model.color);
403
  circle.setAttribute('stroke-width', '4');
404
+ } else {
405
+ circle.setAttribute('stroke', 'rgba(0, 0, 0, 0.25)');
406
+ circle.setAttribute('stroke-width', '2');
407
  }
408
  g.appendChild(circle);
409
 
 
423
  } else {
424
  logoSize = 36;
425
  }
426
+ foreignObject.innerHTML = `<div xmlns="http://www.w3.org/1999/xhtml" style="width: ${isSmolLM3 ? 54 : 44}px; height: ${isSmolLM3 ? 54 : 44}px; display: flex; align-items: center; justify-content: center;"><img src="/data/${model.logo}-logo.svg" style="width: ${logoSize}px; height: auto; max-height: ${logoSize}px;" /></div>`;
427
  g.appendChild(foreignObject);
428
 
429
  // Label below
 
468
  </div>
469
  `;
470
  tooltip.classList.add('visible');
471
+ });
472
 
473
+ g.addEventListener('mousemove', (e) => {
474
+ if (tooltip.classList.contains('visible')) {
475
+ const rect = container.getBoundingClientRect();
476
+ tooltip.style.left = `${e.clientX - rect.left + 10}px`;
477
+ tooltip.style.top = `${e.clientY - rect.top - 10}px`;
478
+ }
479
  });
480
 
481
  g.addEventListener('mouseleave', () => {
app/src/pages/index.astro CHANGED
@@ -206,6 +206,7 @@ const licence =
206
  <script
207
  src="https://cdn.jsdelivr.net/npm/medium-zoom@1.1.0/dist/medium-zoom.min.js"
208
  ></script>
 
209
  <script>
210
  // Debug and global zoom initialization
211
 
 
206
  <script
207
  src="https://cdn.jsdelivr.net/npm/medium-zoom@1.1.0/dist/medium-zoom.min.js"
208
  ></script>
209
+ <script src="/src/scripts/mermaid-zoom.js"></script>
210
  <script>
211
  // Debug and global zoom initialization
212
 
app/src/scripts/README.md ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Mermaid Zoom Integration
2
+
3
+ Ce module fournit une intégration du zoom medium-zoom pour les diagrammes Mermaid dans le projet Astro.
4
+
5
+ ## Fonctionnalités
6
+
7
+ - ✅ **Zoom medium-zoom identique aux images** : Utilise la vraie bibliothèque medium-zoom
8
+ - ✅ **Conversion SVG → Image** : Convertit les diagrammes Mermaid en images pour le zoom
9
+ - ✅ **Adaptation au thème** : Reconversion automatique lors des changements de thème
10
+ - ✅ **Fallback robuste** : Zoom custom si la conversion échoue
11
+ - ✅ **Auto-initialisation** : Détection automatique des nouveaux diagrammes
12
+
13
+ ## Architecture
14
+
15
+ ### Fichiers
16
+
17
+ - `src/scripts/mermaid-zoom.js` : Module principal avec toute la logique
18
+ - `src/pages/index.astro` : Import du module (ligne 209)
19
+ - `src/styles/components/_mermaid.css` : Styles CSS pour les wrappers
20
+
21
+ ### Fonctions principales
22
+
23
+ - `convertSvgToImageForMediumZoom()` : Convertit SVG en image pour medium-zoom
24
+ - `setupMermaidZoom()` : Wrappe les diagrammes et initialise le zoom
25
+ - `initMermaidZoom()` : Fonction principale d'initialisation
26
+ - `openMermaidZoom()` / `closeMermaidZoom()` : Zoom custom de fallback
27
+
28
+ ### API globale
29
+
30
+ ```javascript
31
+ window.MermaidZoom = {
32
+ init: initMermaidZoom, // Initialiser le zoom
33
+ setup: setupMermaidZoom, // Wrapper et conversion
34
+ cleanup: cleanupMermaidObservers, // Nettoyer les observers
35
+ convertSvgToImage: convertSvgToImageForMediumZoom,
36
+ openZoom: openMermaidZoom, // Ouvrir zoom custom
37
+ closeZoom: closeMermaidZoom // Fermer zoom custom
38
+ };
39
+ ```
40
+
41
+ ## Comment ça marche
42
+
43
+ 1. **Détection** : Le script détecte tous les éléments `.mermaid`
44
+ 2. **Wrapper** : Crée un `div.mermaid-zoom-wrapper` autour de chaque diagramme
45
+ 3. **Conversion** : Convertit le SVG en image `<img>` avec `data-zoomable="1"`
46
+ 4. **Medium-zoom** : Initialise `window.mediumZoom()` sur l'image générée
47
+ 5. **Thème** : Observer pour reconvertir lors des changements de thème
48
+
49
+ ## Avantages de cette approche
50
+
51
+ - **Vrai medium-zoom** : Comportement identique aux images existantes
52
+ - **Pas d'erreur canvas** : Évite l'erreur "Tainted canvases" en utilisant des URLs blob
53
+ - **Réactivité au thème** : Les diagrammes s'adaptent automatiquement
54
+ - **Code modulaire** : Facile à maintenir et déboguer
55
+ - **Performance** : Une seule conversion par diagramme
56
+
57
+ ## Debug
58
+
59
+ Le module inclut des logs détaillés :
60
+ - `🚀 Mermaid Zoom Script loaded`
61
+ - `🔍 Found X Mermaid elements`
62
+ - `📦 Wrapping Mermaid element X`
63
+ - `🖼️ SVG converted to img element`
64
+ - `🎉 REAL medium-zoom initialized on Mermaid!`
65
+ - `🎨 Theme changed, reconverting Mermaid image`
app/src/scripts/mermaid-zoom.js ADDED
@@ -0,0 +1,789 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Mermaid Zoom Integration
3
+ * Provides medium-zoom functionality for Mermaid diagrams
4
+ */
5
+
6
+ // Fonction pour appliquer les styles Mermaid au SVG selon le thème
7
+ function applyMermaidStylesToSvg(svgElement) {
8
+ try {
9
+ const isDark = document.documentElement.getAttribute("data-theme") === "dark";
10
+ console.log(`🎨 Applying Mermaid styles for theme: ${isDark ? 'dark' : 'light'}`);
11
+
12
+ // Couleurs selon le thème
13
+ const colors = isDark ? {
14
+ nodeFill: '#1a1a1a',
15
+ nodeStroke: '#ffffff',
16
+ nodeStrokeWidth: '1',
17
+ clusterFill: '#2a2a2a',
18
+ clusterStroke: '#ffffff',
19
+ clusterStrokeWidth: '1',
20
+ pathStroke: '#ffffff',
21
+ pathStrokeWidth: '1',
22
+ textColor: '#ffffff'
23
+ } : {
24
+ nodeFill: '#ffffff',
25
+ nodeStroke: '#333333',
26
+ nodeStrokeWidth: '1',
27
+ clusterFill: '#f9f9f9',
28
+ clusterStroke: '#333333',
29
+ clusterStrokeWidth: '1',
30
+ pathStroke: '#333333',
31
+ pathStrokeWidth: '1',
32
+ textColor: '#333333'
33
+ };
34
+
35
+ // Appliquer border-radius aux rectangles
36
+ const rects = svgElement.querySelectorAll('rect:not(.flowchart-link), .node rect, .nodeLabel rect');
37
+ rects.forEach(rect => {
38
+ rect.setAttribute('rx', '8');
39
+ rect.setAttribute('ry', '8');
40
+ rect.setAttribute('fill', colors.nodeFill);
41
+ rect.setAttribute('stroke', colors.nodeStroke);
42
+ rect.setAttribute('stroke-width', colors.nodeStrokeWidth);
43
+ });
44
+
45
+ // Appliquer border-radius et couleurs aux clusters
46
+ const clusterRects = svgElement.querySelectorAll('.cluster rect');
47
+ clusterRects.forEach(rect => {
48
+ rect.setAttribute('rx', '8');
49
+ rect.setAttribute('ry', '8');
50
+ rect.setAttribute('fill', colors.clusterFill);
51
+ rect.setAttribute('stroke', colors.clusterStroke);
52
+ rect.setAttribute('stroke-width', colors.clusterStrokeWidth);
53
+ });
54
+
55
+ // Appliquer les couleurs aux nœuds
56
+ const nodes = svgElement.querySelectorAll('.node');
57
+ nodes.forEach(node => {
58
+ const rect = node.querySelector('rect');
59
+ if (rect) {
60
+ rect.setAttribute('fill', colors.nodeFill);
61
+ rect.setAttribute('stroke', colors.nodeStroke);
62
+ rect.setAttribute('stroke-width', colors.nodeStrokeWidth);
63
+ }
64
+ });
65
+
66
+ // Appliquer les couleurs aux clusters
67
+ const clusters = svgElement.querySelectorAll('.cluster');
68
+ clusters.forEach(cluster => {
69
+ const rect = cluster.querySelector('rect');
70
+ if (rect) {
71
+ rect.setAttribute('fill', colors.clusterFill);
72
+ rect.setAttribute('stroke', colors.clusterStroke);
73
+ rect.setAttribute('stroke-width', colors.clusterStrokeWidth);
74
+ }
75
+ });
76
+
77
+ // Appliquer les couleurs aux chemins
78
+ const paths = svgElement.querySelectorAll('.edgePath');
79
+ paths.forEach(path => {
80
+ path.setAttribute('stroke', colors.pathStroke);
81
+ path.setAttribute('stroke-width', colors.pathStrokeWidth);
82
+ });
83
+
84
+ // Appliquer les couleurs au texte
85
+ const textElements = svgElement.querySelectorAll('text, .nodeLabel text, .edgeLabel text');
86
+ textElements.forEach(text => {
87
+ text.setAttribute('fill', colors.textColor);
88
+ });
89
+
90
+ console.log(`🎨 Applied Mermaid styles: ${rects.length} rects, ${clusters.length} clusters, ${paths.length} paths, ${textElements.length} text elements`);
91
+
92
+ } catch (error) {
93
+ console.error('❌ Error applying styles to SVG:', error);
94
+ }
95
+ }
96
+
97
+ // Fonction pour extraire les styles CSS appliqués au diagramme Mermaid
98
+ function getComputedStylesForMermaid(mermaidElement) {
99
+ try {
100
+ // Styles CSS essentiels pour Mermaid (hardcodés pour éviter les problèmes CORS)
101
+ const essentialStyles = `
102
+ .mermaid rect:not(.flowchart-link),
103
+ .mermaid .node rect,
104
+ .mermaid .nodeLabel rect {
105
+ rx: 8px !important;
106
+ ry: 8px !important;
107
+ }
108
+
109
+ .mermaid .cluster rect {
110
+ rx: 8px !important;
111
+ ry: 8px !important;
112
+ }
113
+
114
+ .mermaid .nodeLabel p {
115
+ color: black !important;
116
+ }
117
+
118
+ .mermaid .edgeLabel {
119
+ color: black !important;
120
+ }
121
+
122
+ .mermaid .edgePath {
123
+ stroke: #333 !important;
124
+ }
125
+
126
+ .mermaid .node {
127
+ fill: #fff !important;
128
+ stroke: #333 !important;
129
+ }
130
+
131
+ .mermaid .cluster {
132
+ fill: #f9f9f9 !important;
133
+ stroke: #333 !important;
134
+ }
135
+ `;
136
+
137
+ console.log(`🎨 Using essential CSS styles for Mermaid`);
138
+ return essentialStyles;
139
+
140
+ } catch (error) {
141
+ console.error('❌ Error getting CSS styles:', error);
142
+ return null;
143
+ }
144
+ }
145
+
146
+ // Fonction pour forcer Mermaid à se re-rendre avec le bon thème
147
+ function forceMermaidThemeUpdate(mermaidElement) {
148
+ try {
149
+ // Obtenir le thème actuel
150
+ const currentTheme = document.documentElement.getAttribute("data-theme");
151
+ console.log(`🎨 Forcing Mermaid theme update to: ${currentTheme}`);
152
+
153
+ // Trouver le diagramme Mermaid original
154
+ const mermaidCode = mermaidElement.textContent || mermaidElement.innerText;
155
+ if (!mermaidCode) {
156
+ console.log(`❌ No Mermaid code found to re-render`);
157
+ return false;
158
+ }
159
+
160
+ // Forcer le re-rendu de Mermaid avec le nouveau thème
161
+ if (window.mermaid) {
162
+ // Utiliser l'API Mermaid pour re-rendre avec le bon thème
163
+ const config = {
164
+ theme: currentTheme === 'dark' ? 'dark' : 'neutral',
165
+ autoTheme: true
166
+ };
167
+
168
+ console.log(`🔄 Re-rendering Mermaid with config:`, config);
169
+
170
+ // Re-rendre le diagramme
171
+ mermaid.init(undefined, mermaidElement);
172
+
173
+ return true;
174
+ } else {
175
+ console.log(`❌ Mermaid API not available`);
176
+ return false;
177
+ }
178
+ } catch (error) {
179
+ console.error(`❌ Error forcing Mermaid theme update:`, error);
180
+ return false;
181
+ }
182
+ }
183
+
184
+ // Fonction pour convertir SVG en image en préservant EXACTEMENT les dimensions
185
+ function convertSvgToImagePreservingDimensions(svgElement, wrapper, originalMermaid) {
186
+ console.log(`🔄 Converting SVG to image with EXACT dimension preservation`);
187
+
188
+ try {
189
+ // Obtenir les dimensions EXACTES du wrapper actuel
190
+ const wrapperRect = wrapper.getBoundingClientRect();
191
+ const wrapperWidth = Math.round(wrapperRect.width);
192
+ const wrapperHeight = Math.round(wrapperRect.height);
193
+
194
+ console.log(`📏 Wrapper dimensions:`, { width: wrapperWidth, height: wrapperHeight });
195
+
196
+ // Cloner le SVG
197
+ const clonedSvg = svgElement.cloneNode(true);
198
+
199
+ // Appliquer les styles Mermaid
200
+ applyMermaidStylesToSvg(clonedSvg);
201
+
202
+ // Forcer les dimensions exactes du wrapper sur le SVG
203
+ clonedSvg.setAttribute('width', wrapperWidth);
204
+ clonedSvg.setAttribute('height', wrapperHeight);
205
+ clonedSvg.style.width = `${wrapperWidth}px`;
206
+ clonedSvg.style.height = `${wrapperHeight}px`;
207
+
208
+ // Créer une URL data pour le SVG
209
+ const svgData = new XMLSerializer().serializeToString(clonedSvg);
210
+ const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
211
+ const svgUrl = URL.createObjectURL(svgBlob);
212
+
213
+ // Créer un élément img avec les MÊMES dimensions que le wrapper
214
+ const imgElement = document.createElement('img');
215
+ imgElement.src = svgUrl;
216
+ imgElement.style.width = `${wrapperWidth}px`;
217
+ imgElement.style.height = `${wrapperHeight}px`;
218
+ imgElement.style.display = 'block';
219
+ imgElement.setAttribute('data-zoomable', '1');
220
+ imgElement.classList.add('mermaid-zoom-image'); // Classe spécifique pour Mermaid
221
+
222
+ console.log(`🖼️ Image created with exact dimensions:`, { width: wrapperWidth, height: wrapperHeight });
223
+
224
+ // Remplacer le contenu du wrapper par l'image
225
+ wrapper.innerHTML = '';
226
+ wrapper.appendChild(imgElement);
227
+
228
+ // Retirer la classe "converting" pour restaurer l'opacité normale
229
+ wrapper.classList.remove("converting");
230
+
231
+ // Attendre que l'image soit chargée puis initialiser medium-zoom
232
+ imgElement.onload = () => {
233
+ console.log(`✅ Image loaded, initializing REAL medium-zoom`);
234
+
235
+ const isDark = document.documentElement.getAttribute("data-theme") === "dark";
236
+ const background = isDark ? "rgba(0,0,0,.9)" : "rgba(0,0,0,.85)";
237
+
238
+ // Utiliser VRAI medium-zoom
239
+ const zoomInstance = window.mediumZoom(imgElement, {
240
+ background,
241
+ margin: 24,
242
+ scrollOffset: 0,
243
+ });
244
+
245
+ console.log(`🎉 REAL medium-zoom initialized with exact dimensions!`);
246
+
247
+ // Forcer les bonnes couleurs en JavaScript (contournement du CSS global)
248
+ const forceCorrectColors = () => {
249
+ // Trouver l'image zoomée dans le DOM
250
+ const zoomedImage = document.querySelector('.medium-zoom-image--opened');
251
+ if (zoomedImage && zoomedImage.classList.contains('mermaid-zoom-image')) {
252
+ console.log(`🎨 Forcing correct colors for Mermaid zoom image`);
253
+ zoomedImage.style.filter = 'none';
254
+ zoomedImage.style.setProperty('filter', 'none', 'important');
255
+ }
256
+
257
+ // Forcer les z-index élevés
258
+ const overlay = document.querySelector('.medium-zoom-overlay');
259
+ if (overlay) {
260
+ console.log(`🔝 Forcing high z-index for overlay`);
261
+ overlay.style.zIndex = '9999999';
262
+ overlay.style.setProperty('z-index', '9999999', 'important');
263
+ }
264
+
265
+ if (zoomedImage) {
266
+ console.log(`🔝 Forcing high z-index for zoomed image`);
267
+ zoomedImage.style.zIndex = '10000000';
268
+ zoomedImage.style.setProperty('z-index', '10000000', 'important');
269
+ }
270
+ };
271
+
272
+ // Écouter les événements de zoom pour appliquer les bonnes couleurs
273
+ imgElement.addEventListener('zoom:open', forceCorrectColors);
274
+ imgElement.addEventListener('zoom:opened', forceCorrectColors);
275
+
276
+ // Observer pour les changements de thème
277
+ const themeObserver = new MutationObserver(() => {
278
+ console.log(`🎨 Theme changed, forcing Mermaid re-render with new theme`);
279
+
280
+ // Forcer Mermaid à se re-rendre avec le nouveau thème
281
+ const currentTheme = document.documentElement.getAttribute("data-theme");
282
+ console.log(`🎨 Current theme: ${currentTheme}`);
283
+
284
+ // Attendre un peu pour que Mermaid détecte le changement de thème
285
+ setTimeout(() => {
286
+ // Forcer Mermaid à se re-rendre avec le nouveau thème
287
+ const themeUpdated = forceMermaidThemeUpdate(originalMermaid);
288
+
289
+ if (themeUpdated) {
290
+ // Attendre que le re-rendu soit terminé
291
+ setTimeout(() => {
292
+ // Re-obtenir le SVG mis à jour
293
+ const updatedSvgElement = originalMermaid.querySelector("svg");
294
+ if (updatedSvgElement) {
295
+ console.log(`🔄 Re-converting with updated SVG for theme: ${currentTheme}`);
296
+ convertSvgToImagePreservingDimensions(updatedSvgElement, wrapper, originalMermaid);
297
+ } else {
298
+ console.log(`❌ No updated SVG found after theme change`);
299
+ }
300
+ }, 200); // Délai pour laisser Mermaid finir le re-rendu
301
+ } else {
302
+ console.log(`❌ Failed to update Mermaid theme, using current SVG`);
303
+ // Fallback: utiliser le SVG actuel même s'il n'est pas mis à jour
304
+ const currentSvgElement = originalMermaid.querySelector("svg");
305
+ if (currentSvgElement) {
306
+ convertSvgToImagePreservingDimensions(currentSvgElement, wrapper, originalMermaid);
307
+ }
308
+ }
309
+ }, 100); // Petit délai pour laisser Mermaid se mettre à jour
310
+ });
311
+
312
+ themeObserver.observe(document.documentElement, {
313
+ attributes: true,
314
+ attributeFilter: ["data-theme"],
315
+ });
316
+
317
+ wrapper._themeObserver = themeObserver;
318
+ };
319
+
320
+ imgElement.onerror = (error) => {
321
+ console.error(`❌ Error loading SVG as image:`, error);
322
+ // Fallback: utiliser le zoom custom
323
+ wrapper.innerHTML = '';
324
+ wrapper.appendChild(originalMermaid);
325
+ wrapper.classList.remove("converting");
326
+ wrapper.addEventListener("click", (e) => {
327
+ e.preventDefault();
328
+ e.stopPropagation();
329
+ openMermaidZoom(wrapper, originalMermaid);
330
+ });
331
+ };
332
+
333
+ } catch (error) {
334
+ console.error("❌ Error converting SVG to image:", error);
335
+ // Fallback: utiliser le zoom custom
336
+ wrapper.classList.remove("converting");
337
+ wrapper.addEventListener("click", (e) => {
338
+ e.preventDefault();
339
+ e.stopPropagation();
340
+ openMermaidZoom(wrapper, originalMermaid);
341
+ });
342
+ }
343
+ }
344
+
345
+ // Fonction pour initialiser medium-zoom directement sur le SVG
346
+ function initializeMediumZoomOnSvg(svgElement, wrapper, originalMermaid) {
347
+ try {
348
+ // S'assurer que le SVG a des dimensions fixes pour éviter le flicker
349
+ const bbox = svgElement.getBBox();
350
+ const width = bbox.width || svgElement.clientWidth || 800;
351
+ const height = bbox.height || svgElement.clientHeight || 600;
352
+
353
+ // Fixer les dimensions du SVG pour éviter les changements
354
+ svgElement.setAttribute('width', width);
355
+ svgElement.setAttribute('height', height);
356
+ svgElement.style.width = '100%';
357
+ svgElement.style.height = 'auto';
358
+ svgElement.style.display = 'block';
359
+
360
+ // Retirer la classe "converting" pour restaurer l'opacité normale
361
+ wrapper.classList.remove("converting");
362
+
363
+ console.log(`📏 SVG dimensions fixed:`, { width, height });
364
+
365
+ // Initialiser medium-zoom directement sur le wrapper SVG
366
+ const isDark = document.documentElement.getAttribute("data-theme") === "dark";
367
+ const background = isDark ? "rgba(0,0,0,.9)" : "rgba(0,0,0,.85)";
368
+
369
+ // Utiliser medium-zoom sur le wrapper qui contient le SVG
370
+ window.mediumZoom(wrapper, {
371
+ background,
372
+ margin: 24,
373
+ scrollOffset: 0,
374
+ });
375
+
376
+ console.log(`🎉 Medium-zoom initialized directly on SVG wrapper!`);
377
+
378
+ // Observer pour les changements de thème
379
+ const themeObserver = new MutationObserver(() => {
380
+ console.log(`🎨 Theme changed, updating medium-zoom background`);
381
+ // Re-initialiser medium-zoom avec le nouveau thème
382
+ initializeMediumZoomOnSvg(svgElement, wrapper, originalMermaid);
383
+ });
384
+
385
+ themeObserver.observe(document.documentElement, {
386
+ attributes: true,
387
+ attributeFilter: ["data-theme"],
388
+ });
389
+
390
+ // Stocker l'observer pour le nettoyer plus tard
391
+ wrapper._themeObserver = themeObserver;
392
+
393
+ } catch (error) {
394
+ console.error("❌ Error initializing medium-zoom on SVG:", error);
395
+ // Fallback: utiliser le zoom custom
396
+ wrapper.classList.remove("converting");
397
+ wrapper.addEventListener("click", (e) => {
398
+ e.preventDefault();
399
+ e.stopPropagation();
400
+ openMermaidZoom(wrapper, originalMermaid);
401
+ });
402
+ }
403
+ }
404
+
405
+ // Fonction pour attendre que Mermaid soit stable (évite le flicker)
406
+ function waitForMermaidStable(mermaidEl, wrapper, index) {
407
+ // Approche simplifiée : attendre un délai fixe puis convertir
408
+ console.log(`⏳ Waiting for Mermaid ${index} to stabilize...`);
409
+
410
+ setTimeout(() => {
411
+ const svgElement = mermaidEl.querySelector("svg");
412
+ if (svgElement) {
413
+ console.log(`🎯 Converting Mermaid ${index} to image`);
414
+ convertSvgToImageForMediumZoom(svgElement, wrapper, mermaidEl);
415
+ } else {
416
+ console.log(`❌ No SVG found in Mermaid element ${index}`);
417
+ }
418
+ }, 2000); // Attendre 2 secondes puis convertir
419
+ }
420
+
421
+ // Fonction pour convertir SVG en image SANS canvas (pour éviter l'erreur de sécurité)
422
+ function convertSvgToImageForMediumZoom(svgElement, wrapper, originalMermaid) {
423
+ console.log(`🔄 Converting SVG to image for REAL medium-zoom`);
424
+
425
+ try {
426
+ // Cloner le SVG
427
+ const clonedSvg = svgElement.cloneNode(true);
428
+
429
+ // Appliquer directement les styles SVG pour préserver l'apparence
430
+ applyMermaidStylesToSvg(clonedSvg);
431
+
432
+ // S'assurer que le SVG a des dimensions
433
+ const bbox = clonedSvg.getBBox();
434
+ const width = bbox.width || clonedSvg.clientWidth || 800;
435
+ const height = bbox.height || clonedSvg.clientHeight || 600;
436
+
437
+ console.log(`📏 SVG dimensions:`, { width, height });
438
+
439
+ // Créer une image directement à partir du SVG (sans canvas)
440
+ const svgData = new XMLSerializer().serializeToString(clonedSvg);
441
+
442
+ // Ajouter les dimensions au SVG
443
+ clonedSvg.setAttribute("width", width);
444
+ clonedSvg.setAttribute("height", height);
445
+ const svgWithDimensions = new XMLSerializer().serializeToString(clonedSvg);
446
+
447
+ // Créer une URL data pour le SVG
448
+ const svgBlob = new Blob([svgWithDimensions], {
449
+ type: "image/svg+xml;charset=utf-8",
450
+ });
451
+ const svgUrl = URL.createObjectURL(svgBlob);
452
+
453
+ // Créer un élément img avec le SVG
454
+ const imgElement = document.createElement("img");
455
+ imgElement.src = svgUrl;
456
+ imgElement.style.width = "100%";
457
+ imgElement.style.height = "auto";
458
+ imgElement.style.display = "block";
459
+ imgElement.setAttribute("data-zoomable", "1");
460
+
461
+ // Remplacer le contenu du wrapper par l'image
462
+ wrapper.innerHTML = "";
463
+ wrapper.appendChild(imgElement);
464
+
465
+ // Retirer la classe "converting" pour restaurer l'opacité normale
466
+ wrapper.classList.remove("converting");
467
+
468
+ console.log(`🖼️ SVG converted to img element`);
469
+
470
+ // Attendre que l'image soit chargée puis initialiser medium-zoom
471
+ imgElement.onload = () => {
472
+ console.log(`✅ Image loaded, initializing REAL medium-zoom`);
473
+
474
+ const isDark = document.documentElement.getAttribute("data-theme") === "dark";
475
+ const background = isDark ? "rgba(0,0,0,.9)" : "rgba(0,0,0,.85)";
476
+
477
+ // Utiliser VRAI medium-zoom
478
+ window.mediumZoom(imgElement, {
479
+ background,
480
+ margin: 24,
481
+ scrollOffset: 0,
482
+ });
483
+
484
+ console.log(`🎉 REAL medium-zoom initialized on Mermaid!`);
485
+
486
+ // Observer pour les changements de thème - reconvertir l'image
487
+ const themeObserver = new MutationObserver(() => {
488
+ console.log(`🎨 Theme changed, reconverting Mermaid image`);
489
+ // Reconverter l'image avec le nouveau thème
490
+ convertSvgToImageForMediumZoom(svgElement, wrapper, originalMermaid);
491
+ });
492
+
493
+ themeObserver.observe(document.documentElement, {
494
+ attributes: true,
495
+ attributeFilter: ["data-theme"],
496
+ });
497
+
498
+ // Stocker l'observer pour le nettoyer plus tard
499
+ wrapper._themeObserver = themeObserver;
500
+ };
501
+
502
+ imgElement.onerror = (error) => {
503
+ console.error(`❌ Error loading SVG as image:`, error);
504
+ // Fallback: utiliser le zoom custom
505
+ wrapper.innerHTML = "";
506
+ wrapper.appendChild(originalMermaid);
507
+ wrapper.addEventListener("click", (e) => {
508
+ e.preventDefault();
509
+ e.stopPropagation();
510
+ openMermaidZoom(wrapper, originalMermaid);
511
+ });
512
+ };
513
+
514
+ } catch (error) {
515
+ console.error("❌ Error converting SVG to image:", error);
516
+ // Fallback: utiliser le zoom custom
517
+ wrapper.addEventListener("click", (e) => {
518
+ e.preventDefault();
519
+ e.stopPropagation();
520
+ openMermaidZoom(wrapper, originalMermaid);
521
+ });
522
+ }
523
+ }
524
+
525
+ // Fonction pour ouvrir le zoom custom des diagrammes Mermaid (comme medium-zoom)
526
+ function openMermaidZoom(wrapper, mermaidElement) {
527
+ // Créer l'overlay (comme medium-zoom)
528
+ const overlay = document.createElement("div");
529
+ overlay.className = "medium-zoom-overlay";
530
+ overlay.style.cssText = `
531
+ position: fixed;
532
+ top: 0;
533
+ left: 0;
534
+ width: 100%;
535
+ height: 100%;
536
+ background: rgba(0, 0, 0, 0.85);
537
+ z-index: 9999;
538
+ display: flex;
539
+ align-items: center;
540
+ justify-content: center;
541
+ cursor: zoom-out;
542
+ opacity: 0;
543
+ transition: opacity 0.3s ease;
544
+ `;
545
+
546
+ // Créer l'image zoomée (comme medium-zoom)
547
+ const zoomImage = document.createElement("div");
548
+ zoomImage.className = "medium-zoom-image";
549
+ zoomImage.style.cssText = `
550
+ max-width: 90%;
551
+ max-height: 90%;
552
+ background: white;
553
+ border-radius: 8px;
554
+ padding: 20px;
555
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
556
+ transform: scale(0.8);
557
+ transition: transform 0.3s ease;
558
+ overflow: auto;
559
+ `;
560
+
561
+ // Cloner le diagramme Mermaid
562
+ const clonedMermaid = mermaidElement.cloneNode(true);
563
+ clonedMermaid.style.cssText = `
564
+ width: 100%;
565
+ height: auto;
566
+ max-width: none;
567
+ display: block;
568
+ `;
569
+
570
+ zoomImage.appendChild(clonedMermaid);
571
+ overlay.appendChild(zoomImage);
572
+
573
+ // Ajouter l'overlay au body
574
+ document.body.appendChild(overlay);
575
+
576
+ // Animation d'ouverture (comme medium-zoom)
577
+ requestAnimationFrame(() => {
578
+ overlay.style.opacity = "1";
579
+ zoomImage.style.transform = "scale(1)";
580
+ });
581
+
582
+ // Fermer au clic sur l'overlay
583
+ overlay.addEventListener("click", (e) => {
584
+ if (e.target === overlay) {
585
+ closeMermaidZoom(overlay);
586
+ }
587
+ });
588
+
589
+ // Fermer avec Escape
590
+ const handleEscape = (e) => {
591
+ if (e.key === "Escape") {
592
+ closeMermaidZoom(overlay);
593
+ document.removeEventListener("keydown", handleEscape);
594
+ }
595
+ };
596
+ document.addEventListener("keydown", handleEscape);
597
+
598
+ // Fermer au scroll (comme medium-zoom)
599
+ const handleScroll = () => {
600
+ closeMermaidZoom(overlay);
601
+ };
602
+ window.addEventListener("wheel", handleScroll, { passive: true });
603
+ window.addEventListener("touchmove", handleScroll, { passive: true });
604
+ window.addEventListener("scroll", handleScroll, { passive: true });
605
+
606
+ // Stocker les handlers pour les nettoyer
607
+ overlay._handlers = { handleEscape, handleScroll };
608
+ }
609
+
610
+ function closeMermaidZoom(overlay) {
611
+ // Animation de fermeture
612
+ overlay.style.opacity = "0";
613
+ const zoomImage = overlay.querySelector(".medium-zoom-image");
614
+ if (zoomImage) {
615
+ zoomImage.style.transform = "scale(0.8)";
616
+ }
617
+
618
+ // Nettoyer les event listeners
619
+ if (overlay._handlers) {
620
+ document.removeEventListener("keydown", overlay._handlers.handleEscape);
621
+ window.removeEventListener("wheel", overlay._handlers.handleScroll);
622
+ window.removeEventListener("touchmove", overlay._handlers.handleScroll);
623
+ window.removeEventListener("scroll", overlay._handlers.handleScroll);
624
+ }
625
+
626
+ // Supprimer l'overlay après l'animation
627
+ setTimeout(() => {
628
+ if (document.body.contains(overlay)) {
629
+ document.body.removeChild(overlay);
630
+ }
631
+ }, 300);
632
+ }
633
+
634
+ // Fonction pour nettoyer les observers
635
+ function cleanupMermaidObservers(wrapper) {
636
+ if (wrapper._themeObserver) {
637
+ wrapper._themeObserver.disconnect();
638
+ wrapper._themeObserver = null;
639
+ }
640
+ }
641
+
642
+ // Fonction principale pour initialiser le zoom Mermaid
643
+ function setupMermaidZoom() {
644
+ console.log(`🎯 setupMermaidZoom called`);
645
+ const mermaidElements = document.querySelectorAll(".mermaid");
646
+ console.log(`🔍 Found ${mermaidElements.length} Mermaid elements`);
647
+
648
+ mermaidElements.forEach((mermaidEl, index) => {
649
+ // Vérifier si déjà wrappé
650
+ if (
651
+ mermaidEl.parentElement &&
652
+ mermaidEl.parentElement.classList.contains("mermaid-zoom-wrapper")
653
+ ) {
654
+ console.log(`📦 Mermaid ${index} already wrapped`);
655
+ return;
656
+ }
657
+
658
+ console.log(`📦 Wrapping Mermaid element ${index}`);
659
+
660
+ // Créer le wrapper
661
+ const wrapper = document.createElement("div");
662
+ wrapper.className = "mermaid-zoom-wrapper";
663
+ wrapper.setAttribute("data-zoomable", "1");
664
+
665
+ // Insérer le wrapper avant l'élément Mermaid
666
+ mermaidEl.parentNode.insertBefore(wrapper, mermaidEl);
667
+
668
+ // Déplacer l'élément Mermaid dans le wrapper
669
+ wrapper.appendChild(mermaidEl);
670
+
671
+ // S'assurer que le wrapper a des dimensions
672
+ wrapper.style.display = "block";
673
+ wrapper.style.width = "100%";
674
+ wrapper.style.maxWidth = "100%";
675
+
676
+ // Ajouter la classe "converting" pour masquer le flicker
677
+ wrapper.classList.add("converting");
678
+
679
+ // Convertir SVG en image pour utiliser VRAI medium-zoom
680
+ console.log(`✅ Converting Mermaid to image for REAL medium-zoom`);
681
+
682
+ // Utiliser le VRAI medium-zoom avec conversion SVG → image SANS flicker
683
+ console.log(`✅ Setting up REAL medium-zoom for Mermaid ${index}`);
684
+
685
+ // Attendre que Mermaid soit stable puis convertir avec préservation des dimensions
686
+ setTimeout(() => {
687
+ const svgElement = mermaidEl.querySelector("svg");
688
+ if (svgElement) {
689
+ console.log(`🎯 Converting SVG to image with dimension preservation`);
690
+ convertSvgToImagePreservingDimensions(svgElement, wrapper, mermaidEl);
691
+ } else {
692
+ console.log(`❌ No SVG found in Mermaid element ${index}`);
693
+ }
694
+ }, 2000);
695
+ });
696
+ }
697
+
698
+ // Observer global pour forcer les bonnes couleurs et z-index sur les images Mermaid zoomées
699
+ function setupGlobalMermaidColorFix() {
700
+ const observer = new MutationObserver((mutations) => {
701
+ mutations.forEach((mutation) => {
702
+ if (mutation.type === 'childList') {
703
+ // Vérifier si une image Mermaid est zoomée
704
+ const zoomedMermaidImage = document.querySelector('.medium-zoom-image--opened.mermaid-zoom-image');
705
+ if (zoomedMermaidImage) {
706
+ console.log(`🎨 Global fix: Found zoomed Mermaid image, forcing correct colors and z-index`);
707
+ zoomedMermaidImage.style.filter = 'none';
708
+ zoomedMermaidImage.style.setProperty('filter', 'none', 'important');
709
+ zoomedMermaidImage.style.zIndex = '10000000';
710
+ zoomedMermaidImage.style.setProperty('z-index', '10000000', 'important');
711
+ }
712
+
713
+ // Vérifier si l'overlay medium-zoom existe
714
+ const overlay = document.querySelector('.medium-zoom-overlay');
715
+ if (overlay) {
716
+ console.log(`🔝 Global fix: Found medium-zoom overlay, forcing high z-index`);
717
+ overlay.style.zIndex = '9999999';
718
+ overlay.style.setProperty('z-index', '9999999', 'important');
719
+ }
720
+ }
721
+ });
722
+ });
723
+
724
+ observer.observe(document.body, {
725
+ childList: true,
726
+ subtree: true
727
+ });
728
+
729
+ console.log(`🎨 Global Mermaid color and z-index fix observer started`);
730
+ }
731
+
732
+ // Fonction pour initialiser le zoom Mermaid avec retry
733
+ function initMermaidZoom() {
734
+ console.log("🎯 initMermaidZoom called");
735
+
736
+ // Attendre que mediumZoom soit disponible
737
+ const checkMediumZoom = () => {
738
+ if (window.mediumZoom) {
739
+ console.log("✅ mediumZoom found, initializing Mermaid zoom");
740
+ setupMermaidZoom();
741
+ } else {
742
+ console.log("⏳ Waiting for mediumZoom...");
743
+ setTimeout(checkMediumZoom, 100);
744
+ }
745
+ };
746
+
747
+ checkMediumZoom();
748
+ }
749
+
750
+ // Export des fonctions pour utilisation globale
751
+ window.MermaidZoom = {
752
+ init: initMermaidZoom,
753
+ setup: setupMermaidZoom,
754
+ cleanup: cleanupMermaidObservers,
755
+ convertSvgToImage: convertSvgToImageForMediumZoom,
756
+ openZoom: openMermaidZoom,
757
+ closeZoom: closeMermaidZoom
758
+ };
759
+
760
+ // Auto-initialisation si le DOM est déjà chargé
761
+ if (document.readyState === "loading") {
762
+ document.addEventListener("DOMContentLoaded", () => {
763
+ initMermaidZoom();
764
+ setupGlobalMermaidColorFix();
765
+ });
766
+ } else {
767
+ initMermaidZoom();
768
+ setupGlobalMermaidColorFix();
769
+ }
770
+
771
+ // Aussi après le chargement complet
772
+ window.addEventListener("load", () => {
773
+ setTimeout(() => {
774
+ initMermaidZoom();
775
+ setupGlobalMermaidColorFix();
776
+ }, 1000);
777
+ });
778
+
779
+ // Observer simple pour les nouveaux diagrammes Mermaid
780
+ const observer = new MutationObserver(() => {
781
+ setTimeout(initMermaidZoom, 500);
782
+ });
783
+
784
+ observer.observe(document.body, {
785
+ childList: true,
786
+ subtree: true,
787
+ });
788
+
789
+ console.log("🚀 Mermaid Zoom Script v14.0 loaded - REAL medium-zoom with JavaScript z-index fix");
app/src/styles/components/_mermaid.css CHANGED
@@ -2,6 +2,53 @@
2
  color: black !important;
3
  }
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  /* Styles pour les nœuds avec bords arrondis */
6
  .mermaid rect:not(.flowchart-link),
7
  .mermaid .node rect,
@@ -11,6 +58,88 @@
11
  ry: 8px !important;
12
  }
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  /* Styles spécifiques pour différents types de diagrammes */
15
 
16
  /* Flowchart/Graph - styles généraux */
 
2
  color: black !important;
3
  }
4
 
5
+ /* Masquer le flicker pendant la conversion */
6
+ .mermaid-zoom-wrapper.converting {
7
+ opacity: 0.7;
8
+ transition: opacity 0.2s ease;
9
+ }
10
+
11
+ .mermaid-zoom-wrapper.converting .mermaid {
12
+ opacity: 0.7;
13
+ transition: opacity 0.2s ease;
14
+ }
15
+
16
+ /* Assurer que les diagrammes Mermaid prennent toute la largeur */
17
+ .mermaid {
18
+ width: 100% !important;
19
+ max-width: 100% !important;
20
+ display: block !important;
21
+ }
22
+
23
+ /* Styles pour les SVG Mermaid */
24
+ .mermaid svg {
25
+ width: 100% !important;
26
+ max-width: 100% !important;
27
+ height: auto !important;
28
+ }
29
+
30
+ /* Styles pour les wrappers de zoom Mermaid */
31
+ .mermaid-zoom-wrapper {
32
+ display: block !important;
33
+ width: 100% !important;
34
+ max-width: 100% !important;
35
+ cursor: zoom-in;
36
+ position: relative;
37
+ }
38
+
39
+ .mermaid-zoom-wrapper .mermaid {
40
+ width: 100% !important;
41
+ max-width: 100% !important;
42
+ display: block !important;
43
+ }
44
+
45
+ .mermaid-zoom-wrapper .mermaid svg {
46
+ width: 100% !important;
47
+ max-width: 100% !important;
48
+ height: auto !important;
49
+ display: block !important;
50
+ }
51
+
52
  /* Styles pour les nœuds avec bords arrondis */
53
  .mermaid rect:not(.flowchart-link),
54
  .mermaid .node rect,
 
58
  ry: 8px !important;
59
  }
60
 
61
+ /* Styles pour les diagrammes Mermaid zoomables */
62
+ .mermaid-zoom-wrapper:hover {
63
+ opacity: 0.95;
64
+ transition: opacity 0.2s ease;
65
+ }
66
+
67
+ /* Appliquer le même style que les images zoomables */
68
+ .mermaid-zoom-wrapper[data-zoomable="1"] {
69
+ cursor: zoom-in;
70
+ }
71
+
72
+ /* Styles pour le mode zoom ouvert */
73
+ .medium-zoom--opened .mermaid-zoom-wrapper {
74
+ cursor: zoom-out;
75
+ }
76
+
77
+ /* Exclure les images Mermaid du filtre d'inversion en mode sombre */
78
+ /* Le filtre global s'applique à .medium-zoom-image--opened */
79
+ /* Nous devons être plus spécifique pour les images Mermaid */
80
+
81
+ /* Exception pour les images Mermaid - pas de filtre d'inversion */
82
+ :global([data-theme="dark"]) .mermaid-zoom-wrapper img:global(.medium-zoom-image--opened) {
83
+ filter: none !important;
84
+ }
85
+
86
+ /* Alternative: cibler directement l'image dans le wrapper Mermaid */
87
+ :global([data-theme="dark"]) .mermaid-zoom-wrapper :global(.medium-zoom-image--opened) {
88
+ filter: none !important;
89
+ }
90
+
91
+ /* Encore plus spécifique: cibler l'image générée par notre script */
92
+ :global([data-theme="dark"]) .mermaid-zoom-wrapper img[data-zoomable="1"]:global(.medium-zoom-image--opened) {
93
+ filter: none !important;
94
+ }
95
+
96
+ /* Forcer un z-index très élevé pour medium-zoom (au-dessus de tout) */
97
+ :global(.medium-zoom-overlay) {
98
+ z-index: 9999999 !important;
99
+ }
100
+
101
+ :global(.medium-zoom-image--opened) {
102
+ z-index: 10000000 !important;
103
+ }
104
+
105
+ /* Cibler la classe spécifique mermaid-zoom-image */
106
+ :global([data-theme="dark"]) .mermaid-zoom-image:global(.medium-zoom-image--opened) {
107
+ filter: none !important;
108
+ }
109
+
110
+ /* Overlay medium-zoom avec z-index très élevé pour Mermaid */
111
+ :global(.medium-zoom-overlay):has(.mermaid-zoom-wrapper) {
112
+ z-index: 9999999 !important;
113
+ }
114
+
115
+ /* Z-index très élevé pour les images Mermaid */
116
+ :global(.mermaid-zoom-image):global(.medium-zoom-image--opened) {
117
+ z-index: 10000000 !important;
118
+ }
119
+
120
+ :global(.mermaid-zoom-wrapper):has(:global(.medium-zoom-image--opened)) {
121
+ z-index: 10000000 !important;
122
+ }
123
+
124
+ /* Masquer les autres diagrammes quand un est zoomé */
125
+ :global(.medium-zoom--opened) .mermaid-zoom-wrapper {
126
+ opacity: 0;
127
+ z-index: calc(var(--z-base) - 1);
128
+ transition: opacity 0.3s ease;
129
+ }
130
+
131
+ /* Le diagramme zoomé reste visible */
132
+ :global(.medium-zoom--opened) .mermaid-zoom-wrapper:has(.medium-zoom--opened) {
133
+ opacity: 1;
134
+ z-index: var(--z-overlay);
135
+ }
136
+
137
+ /* Fallback pour les navigateurs sans support :has() */
138
+ :global(.medium-zoom--opened) .mermaid-zoom-wrapper.zoom-active {
139
+ opacity: 1 !important;
140
+ z-index: var(--z-overlay) !important;
141
+ }
142
+
143
  /* Styles spécifiques pour différents types de diagrammes */
144
 
145
  /* Flowchart/Graph - styles généraux */