tfrere HF Staff commited on
Commit
0a99381
·
1 Parent(s): b0d1810

update sidenote, properly remove mermaid zoom

Browse files
app/src/components/Sidenote.astro CHANGED
@@ -86,11 +86,43 @@
86
  });
87
  };
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  // Fonction pour démarrer l'initialisation avec observer si nécessaire
90
  const startInit = () => {
91
  // Essayer immédiatement
92
  initSidenotes();
93
 
 
 
 
 
 
94
  // Si on ne trouve pas de sidenotes, utiliser MutationObserver pour attendre
95
  // que le contenu MDX soit rendu (important pour les Spaces Hugging Face)
96
  const containers = document.querySelectorAll(".sidenote-container");
@@ -102,10 +134,14 @@
102
  if (newContainers.length > 0) {
103
  // Des sidenotes sont apparus, initialiser
104
  initSidenotes();
 
 
 
 
105
  // Arrêter l'observation après un délai pour éviter les boucles infinies
106
  setTimeout(() => {
107
  obs.disconnect();
108
- }, 5000); // 5 secondes max
109
  }
110
  });
111
 
@@ -125,16 +161,66 @@
125
  }
126
  };
127
 
128
- // Exécuter immédiatement si le DOM est déjà chargé, sinon attendre DOMContentLoaded
129
- if (document.readyState === "loading") {
130
- document.addEventListener("DOMContentLoaded", startInit, {
131
- once: true,
132
- });
133
- } else {
134
- // Le DOM est déjà chargé, exécuter immédiatement
135
- // Mais utiliser un petit délai pour s'assurer que tous les composants sont rendus
136
- setTimeout(startInit, 100);
137
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  </script>
139
 
140
  <style is:global>
 
86
  });
87
  };
88
 
89
+ // Vérifier que les styles sont bien appliqués
90
+ const verifyStylesApplied = () => {
91
+ const wrappers = document.querySelectorAll(".sidenote-wrapper");
92
+ let allApplied = true;
93
+
94
+ wrappers.forEach((wrapper) => {
95
+ const sidenote = wrapper.querySelector(
96
+ ".sidenote-container",
97
+ ) as HTMLElement;
98
+ if (sidenote) {
99
+ const computed = window.getComputedStyle(sidenote);
100
+ // Vérifier que position absolute est bien appliquée
101
+ if (computed.position !== "absolute" && window.innerWidth > 768) {
102
+ allApplied = false;
103
+ // Réappliquer les styles si nécessaire
104
+ sidenote.style.position = "absolute";
105
+ sidenote.style.top = "0";
106
+ sidenote.style.right = "-292px";
107
+ sidenote.style.width = "260px";
108
+ sidenote.style.display = "block";
109
+ }
110
+ }
111
+ });
112
+
113
+ return allApplied;
114
+ };
115
+
116
  // Fonction pour démarrer l'initialisation avec observer si nécessaire
117
  const startInit = () => {
118
  // Essayer immédiatement
119
  initSidenotes();
120
 
121
+ // Vérifier et réappliquer les styles si nécessaire
122
+ setTimeout(() => {
123
+ verifyStylesApplied();
124
+ }, 50);
125
+
126
  // Si on ne trouve pas de sidenotes, utiliser MutationObserver pour attendre
127
  // que le contenu MDX soit rendu (important pour les Spaces Hugging Face)
128
  const containers = document.querySelectorAll(".sidenote-container");
 
134
  if (newContainers.length > 0) {
135
  // Des sidenotes sont apparus, initialiser
136
  initSidenotes();
137
+ // Vérifier les styles après initialisation
138
+ setTimeout(() => {
139
+ verifyStylesApplied();
140
+ }, 50);
141
  // Arrêter l'observation après un délai pour éviter les boucles infinies
142
  setTimeout(() => {
143
  obs.disconnect();
144
+ }, 5000);
145
  }
146
  });
147
 
 
161
  }
162
  };
163
 
164
+ // Attendre que toutes les ressources soient chargées (images, etc.)
165
+ const waitForFullLoad = () => {
166
+ // Helper pour utiliser requestIdleCallback si disponible
167
+ const idleOrTimeout = (callback: () => void, timeout: number) => {
168
+ if (typeof (window as any).requestIdleCallback === "function") {
169
+ (window as any).requestIdleCallback(callback, { timeout });
170
+ } else {
171
+ setTimeout(callback, timeout);
172
+ }
173
+ };
174
+
175
+ if (document.readyState === "loading") {
176
+ // Attendre DOMContentLoaded puis window.load
177
+ document.addEventListener(
178
+ "DOMContentLoaded",
179
+ () => {
180
+ idleOrTimeout(startInit, 300);
181
+ },
182
+ { once: true },
183
+ );
184
+
185
+ // Aussi attendre window.load pour les ressources
186
+ window.addEventListener(
187
+ "load",
188
+ () => {
189
+ setTimeout(() => {
190
+ initSidenotes();
191
+ verifyStylesApplied();
192
+ }, 100);
193
+ },
194
+ { once: true },
195
+ );
196
+ } else if (document.readyState === "interactive") {
197
+ // DOM est chargé mais pas toutes les ressources
198
+ idleOrTimeout(startInit, 300);
199
+
200
+ // Aussi attendre window.load
201
+ window.addEventListener(
202
+ "load",
203
+ () => {
204
+ setTimeout(() => {
205
+ initSidenotes();
206
+ verifyStylesApplied();
207
+ }, 100);
208
+ },
209
+ { once: true },
210
+ );
211
+ } else {
212
+ // Tout est déjà chargé
213
+ idleOrTimeout(startInit, 300);
214
+
215
+ // Vérifier une dernière fois après un délai pour s'assurer que tout est bien rendu
216
+ setTimeout(() => {
217
+ initSidenotes();
218
+ verifyStylesApplied();
219
+ }, 500);
220
+ }
221
+ };
222
+
223
+ waitForFullLoad();
224
  </script>
225
 
226
  <style is:global>
app/src/scripts/mermaid-zoom.js CHANGED
@@ -19,122 +19,122 @@ if (!MERMAID_ZOOM_ENABLED) {
19
  openZoom: () => { },
20
  closeZoom: () => { }
21
  };
22
- // Arrêter l'exécution ici
23
- throw new Error("Mermaid zoom disabled");
24
- }
25
-
26
- // Fonction pour appliquer les styles Mermaid au SVG selon le thème
27
- function applyMermaidStylesToSvg(svgElement) {
28
- try {
29
- const isDark = document.documentElement.getAttribute("data-theme") === "dark";
30
- console.log(`🎨 Applying Mermaid styles for theme: ${isDark ? 'dark' : 'light'}`);
31
-
32
- // Couleurs selon le thème - avec plus de contraste pour le zoom
33
- const colors = isDark ? {
34
- nodeFill: '#1a1a1a',
35
- nodeStroke: '#ffffff',
36
- nodeStrokeWidth: '2', // Plus épais pour plus de visibilité
37
- clusterFill: '#2a2a2a',
38
- clusterStroke: '#ffffff',
39
- clusterStrokeWidth: '2',
40
- pathStroke: '#ffffff',
41
- pathStrokeWidth: '2', // Plus épais pour les flèches
42
- textColor: '#ffffff',
43
- linkColor: '#ffffff' // Couleur spécifique pour les liens
44
- } : {
45
- nodeFill: '#ffffff',
46
- nodeStroke: '#000000', // Noir pur pour plus de contraste
47
- nodeStrokeWidth: '2',
48
- clusterFill: '#f9f9f9',
49
- clusterStroke: '#000000', // Noir pur
50
- clusterStrokeWidth: '2',
51
- pathStroke: '#000000', // Noir pur pour les flèches
52
- pathStrokeWidth: '2',
53
- textColor: '#000000', // Noir pur
54
- linkColor: '#000000' // Noir pur pour les liens
55
- };
56
-
57
- // Appliquer border-radius aux rectangles
58
- const rects = svgElement.querySelectorAll('rect:not(.flowchart-link), .node rect, .nodeLabel rect');
59
- rects.forEach(rect => {
60
- rect.setAttribute('rx', '8');
61
- rect.setAttribute('ry', '8');
62
- rect.setAttribute('fill', colors.nodeFill);
63
- rect.setAttribute('stroke', colors.nodeStroke);
64
- rect.setAttribute('stroke-width', colors.nodeStrokeWidth);
65
- });
66
 
67
- // Appliquer border-radius et couleurs aux clusters
68
- const clusterRects = svgElement.querySelectorAll('.cluster rect');
69
- clusterRects.forEach(rect => {
70
- rect.setAttribute('rx', '8');
71
- rect.setAttribute('ry', '8');
72
- rect.setAttribute('fill', colors.clusterFill);
73
- rect.setAttribute('stroke', colors.clusterStroke);
74
- rect.setAttribute('stroke-width', colors.clusterStrokeWidth);
75
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
- // Appliquer les couleurs aux nœuds
78
- const nodes = svgElement.querySelectorAll('.node');
79
- nodes.forEach(node => {
80
- const rect = node.querySelector('rect');
81
- if (rect) {
82
  rect.setAttribute('fill', colors.nodeFill);
83
  rect.setAttribute('stroke', colors.nodeStroke);
84
  rect.setAttribute('stroke-width', colors.nodeStrokeWidth);
85
- }
86
- });
87
 
88
- // Appliquer les couleurs aux clusters
89
- const clusters = svgElement.querySelectorAll('.cluster');
90
- clusters.forEach(cluster => {
91
- const rect = cluster.querySelector('rect');
92
- if (rect) {
93
  rect.setAttribute('fill', colors.clusterFill);
94
  rect.setAttribute('stroke', colors.clusterStroke);
95
  rect.setAttribute('stroke-width', colors.clusterStrokeWidth);
96
- }
97
- });
98
 
99
- // Appliquer les couleurs aux chemins et flèches
100
- const paths = svgElement.querySelectorAll('.edgePath, path, .flowchart-link');
101
- paths.forEach(path => {
102
- path.setAttribute('stroke', colors.pathStroke);
103
- path.setAttribute('stroke-width', colors.pathStrokeWidth);
104
- });
 
 
 
 
105
 
106
- // Appliquer les couleurs aux liens spécifiquement
107
- const links = svgElement.querySelectorAll('.flowchart-link, .edgeLabel');
108
- links.forEach(link => {
109
- link.setAttribute('stroke', colors.linkColor);
110
- link.setAttribute('fill', colors.linkColor);
111
- });
 
 
 
 
112
 
113
- // Appliquer les couleurs au texte
114
- const textElements = svgElement.querySelectorAll('text, .nodeLabel text, .edgeLabel text');
115
- textElements.forEach(text => {
116
- text.setAttribute('fill', colors.textColor);
117
- });
 
118
 
119
- // Appliquer les couleurs aux marqueurs de flèches
120
- const markers = svgElement.querySelectorAll('marker, marker path');
121
- markers.forEach(marker => {
122
- marker.setAttribute('fill', colors.pathStroke);
123
- marker.setAttribute('stroke', colors.pathStroke);
124
- });
125
 
126
- console.log(`🎨 Applied Mermaid styles: ${rects.length} rects, ${clusters.length} clusters, ${paths.length} paths, ${textElements.length} text elements`);
 
 
 
 
 
 
 
 
 
 
 
127
 
128
- } catch (error) {
129
- console.error('❌ Error applying styles to SVG:', error);
 
 
 
130
  }
131
- }
132
 
133
- // Fonction pour extraire les styles CSS appliqués au diagramme Mermaid
134
- function getComputedStylesForMermaid(mermaidElement) {
135
- try {
136
- // Styles CSS essentiels pour Mermaid (hardcodés pour éviter les problèmes CORS)
137
- const essentialStyles = `
138
  .mermaid rect:not(.flowchart-link),
139
  .mermaid .node rect,
140
  .mermaid .nodeLabel rect {
@@ -170,194 +170,267 @@ function getComputedStylesForMermaid(mermaidElement) {
170
  }
171
  `;
172
 
173
- console.log(`🎨 Using essential CSS styles for Mermaid`);
174
- return essentialStyles;
175
 
176
- } catch (error) {
177
- console.error('❌ Error getting CSS styles:', error);
178
- return null;
179
- }
180
- }
181
-
182
- // Fonction pour forcer Mermaid à se re-rendre avec le bon thème
183
- function forceMermaidThemeUpdate(mermaidElement) {
184
- try {
185
- // Obtenir le thème actuel
186
- const currentTheme = document.documentElement.getAttribute("data-theme");
187
- console.log(`🎨 Forcing Mermaid theme update to: ${currentTheme}`);
188
-
189
- // Trouver le diagramme Mermaid original
190
- const mermaidCode = mermaidElement.textContent || mermaidElement.innerText;
191
- if (!mermaidCode) {
192
- console.log(`❌ No Mermaid code found to re-render`);
193
- return false;
194
  }
 
195
 
196
- // Forcer le re-rendu de Mermaid avec le nouveau thème
197
- if (window.mermaid) {
198
- // Utiliser l'API Mermaid pour re-rendre avec le bon thème
199
- const config = {
200
- theme: currentTheme === 'dark' ? 'dark' : 'neutral',
201
- autoTheme: true
202
- };
 
 
 
 
 
 
203
 
204
- console.log(`🔄 Re-rendering Mermaid with config:`, config);
 
 
 
 
 
 
205
 
206
- // Re-rendre le diagramme
207
- mermaid.init(undefined, mermaidElement);
208
 
209
- return true;
210
- } else {
211
- console.log(`❌ Mermaid API not available`);
 
 
 
 
 
 
 
212
  return false;
213
  }
214
- } catch (error) {
215
- console.error(`❌ Error forcing Mermaid theme update:`, error);
216
- return false;
217
  }
218
- }
219
 
220
- // Fonction pour convertir SVG en image en préservant EXACTEMENT les dimensions
221
- function convertSvgToImagePreservingDimensions(svgElement, wrapper, originalMermaid) {
222
- console.log(`🔄 Converting SVG to image with EXACT dimension preservation`);
223
 
224
- try {
225
- // Obtenir les dimensions EXACTES du wrapper actuel
226
- const wrapperRect = wrapper.getBoundingClientRect();
227
- const wrapperWidth = Math.round(wrapperRect.width);
228
- const wrapperHeight = Math.round(wrapperRect.height);
229
 
230
- console.log(`📏 Wrapper dimensions:`, { width: wrapperWidth, height: wrapperHeight });
231
 
232
- // Cloner le SVG
233
- const clonedSvg = svgElement.cloneNode(true);
234
 
235
- // Appliquer les styles Mermaid
236
- applyMermaidStylesToSvg(clonedSvg);
237
 
238
- // Créer une image PLUS GRANDE pour permettre un vrai zoom (2x la taille)
239
- const zoomFactor = 2;
240
- const imageWidth = wrapperWidth * zoomFactor;
241
- const imageHeight = wrapperHeight * zoomFactor;
242
 
243
- // Forcer les dimensions plus grandes sur le SVG
244
- clonedSvg.setAttribute('width', imageWidth);
245
- clonedSvg.setAttribute('height', imageHeight);
246
- clonedSvg.style.width = `${imageWidth}px`;
247
- clonedSvg.style.height = `${imageHeight}px`;
248
 
249
- console.log(`🔍 Creating zoomable image:`, {
250
- original: { width: wrapperWidth, height: wrapperHeight },
251
- zoomed: { width: imageWidth, height: imageHeight },
252
- factor: zoomFactor
253
- });
254
 
255
- // Créer une URL data pour le SVG
256
- const svgData = new XMLSerializer().serializeToString(clonedSvg);
257
- const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
258
- const svgUrl = URL.createObjectURL(svgBlob);
259
 
260
- // Créer un élément img avec les dimensions du wrapper (affichage normal)
261
- const imgElement = document.createElement('img');
262
- imgElement.src = svgUrl;
263
- imgElement.style.width = `${wrapperWidth}px`; // Affichage normal
264
- imgElement.style.height = `${wrapperHeight}px`; // Affichage normal
265
- imgElement.style.display = 'block';
266
- imgElement.setAttribute('data-zoomable', '1');
267
- imgElement.classList.add('mermaid-zoom-image'); // Classe spécifique pour Mermaid
268
 
269
- // Ajouter les attributs pour medium-zoom (dimensions réelles de l'image)
270
- imgElement.setAttribute('data-zoom-width', imageWidth);
271
- imgElement.setAttribute('data-zoom-height', imageHeight);
272
 
273
- console.log(`🖼️ Image created with exact dimensions:`, { width: wrapperWidth, height: wrapperHeight });
274
 
275
- // Remplacer le contenu du wrapper par l'image
276
- wrapper.innerHTML = '';
277
- wrapper.appendChild(imgElement);
278
 
279
- // Retirer la classe "converting" pour restaurer l'opacité normale
280
- wrapper.classList.remove("converting");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
 
282
- // Attendre que l'image soit chargée puis initialiser medium-zoom
283
- imgElement.onload = () => {
284
- console.log(` Image loaded, initializing REAL medium-zoom`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
  const isDark = document.documentElement.getAttribute("data-theme") === "dark";
287
  const background = isDark ? "rgba(0,0,0,.9)" : "rgba(0,0,0,.85)";
288
 
289
- // Utiliser VRAI medium-zoom
290
- const zoomInstance = window.mediumZoom(imgElement, {
291
  background,
292
  margin: 24,
293
  scrollOffset: 0,
294
  });
295
 
296
- console.log(`🎉 REAL medium-zoom initialized with exact dimensions!`);
297
-
298
- // Forcer les bonnes couleurs en JavaScript (contournement du CSS global)
299
- const forceCorrectColors = () => {
300
- // Trouver l'image zoomée dans le DOM
301
- const zoomedImage = document.querySelector('.medium-zoom-image--opened');
302
- if (zoomedImage && zoomedImage.classList.contains('mermaid-zoom-image')) {
303
- console.log(`🎨 Forcing correct colors for Mermaid zoom image`);
304
- zoomedImage.style.filter = 'none';
305
- zoomedImage.style.setProperty('filter', 'none', 'important');
306
- }
307
-
308
- // Forcer les z-index élevés
309
- const overlay = document.querySelector('.medium-zoom-overlay');
310
- if (overlay) {
311
- console.log(`🔝 Forcing high z-index for overlay`);
312
- overlay.style.zIndex = '9999999';
313
- overlay.style.setProperty('z-index', '9999999', 'important');
314
- }
315
-
316
- if (zoomedImage) {
317
- console.log(`🔝 Forcing high z-index for zoomed image`);
318
- zoomedImage.style.zIndex = '10000000';
319
- zoomedImage.style.setProperty('z-index', '10000000', 'important');
320
- }
321
- };
322
-
323
- // Écouter les événements de zoom pour appliquer les bonnes couleurs
324
- imgElement.addEventListener('zoom:open', forceCorrectColors);
325
- imgElement.addEventListener('zoom:opened', forceCorrectColors);
326
 
327
  // Observer pour les changements de thème
328
  const themeObserver = new MutationObserver(() => {
329
- console.log(`🎨 Theme changed, forcing Mermaid re-render with new theme`);
330
-
331
- // Forcer Mermaid à se re-rendre avec le nouveau thème
332
- const currentTheme = document.documentElement.getAttribute("data-theme");
333
- console.log(`🎨 Current theme: ${currentTheme}`);
334
-
335
- // Attendre un peu pour que Mermaid détecte le changement de thème
336
- setTimeout(() => {
337
- // Forcer Mermaid à se re-rendre avec le nouveau thème
338
- const themeUpdated = forceMermaidThemeUpdate(originalMermaid);
339
-
340
- if (themeUpdated) {
341
- // Attendre que le re-rendu soit terminé
342
- setTimeout(() => {
343
- // Re-obtenir le SVG mis à jour
344
- const updatedSvgElement = originalMermaid.querySelector("svg");
345
- if (updatedSvgElement) {
346
- console.log(`🔄 Re-converting with updated SVG for theme: ${currentTheme}`);
347
- convertSvgToImagePreservingDimensions(updatedSvgElement, wrapper, originalMermaid);
348
- } else {
349
- console.log(`❌ No updated SVG found after theme change`);
350
- }
351
- }, 200); // Délai pour laisser Mermaid finir le re-rendu
352
- } else {
353
- console.log(`❌ Failed to update Mermaid theme, using current SVG`);
354
- // Fallback: utiliser le SVG actuel même s'il n'est pas mis à jour
355
- const currentSvgElement = originalMermaid.querySelector("svg");
356
- if (currentSvgElement) {
357
- convertSvgToImagePreservingDimensions(currentSvgElement, wrapper, originalMermaid);
358
- }
359
- }
360
- }, 100); // Petit délai pour laisser Mermaid se mettre à jour
361
  });
362
 
363
  themeObserver.observe(document.documentElement, {
@@ -365,220 +438,147 @@ function convertSvgToImagePreservingDimensions(svgElement, wrapper, originalMerm
365
  attributeFilter: ["data-theme"],
366
  });
367
 
 
368
  wrapper._themeObserver = themeObserver;
369
- };
370
 
371
- imgElement.onerror = (error) => {
372
- console.error(`❌ Error loading SVG as image:`, error);
373
  // Fallback: utiliser le zoom custom
374
- wrapper.innerHTML = '';
375
- wrapper.appendChild(originalMermaid);
376
  wrapper.classList.remove("converting");
377
  wrapper.addEventListener("click", (e) => {
378
  e.preventDefault();
379
  e.stopPropagation();
380
  openMermaidZoom(wrapper, originalMermaid);
381
  });
382
- };
383
-
384
- } catch (error) {
385
- console.error("❌ Error converting SVG to image:", error);
386
- // Fallback: utiliser le zoom custom
387
- wrapper.classList.remove("converting");
388
- wrapper.addEventListener("click", (e) => {
389
- e.preventDefault();
390
- e.stopPropagation();
391
- openMermaidZoom(wrapper, originalMermaid);
392
- });
393
  }
394
- }
395
-
396
- // Fonction pour initialiser medium-zoom directement sur le SVG
397
- function initializeMediumZoomOnSvg(svgElement, wrapper, originalMermaid) {
398
- try {
399
- // S'assurer que le SVG a des dimensions fixes pour éviter le flicker
400
- const bbox = svgElement.getBBox();
401
- const width = bbox.width || svgElement.clientWidth || 800;
402
- const height = bbox.height || svgElement.clientHeight || 600;
403
-
404
- // Fixer les dimensions du SVG pour éviter les changements
405
- svgElement.setAttribute('width', width);
406
- svgElement.setAttribute('height', height);
407
- svgElement.style.width = '100%';
408
- svgElement.style.height = 'auto';
409
- svgElement.style.display = 'block';
410
-
411
- // Retirer la classe "converting" pour restaurer l'opacité normale
412
- wrapper.classList.remove("converting");
413
-
414
- console.log(`📏 SVG dimensions fixed:`, { width, height });
415
-
416
- // Initialiser medium-zoom directement sur le wrapper SVG
417
- const isDark = document.documentElement.getAttribute("data-theme") === "dark";
418
- const background = isDark ? "rgba(0,0,0,.9)" : "rgba(0,0,0,.85)";
419
-
420
- // Utiliser medium-zoom sur le wrapper qui contient le SVG
421
- window.mediumZoom(wrapper, {
422
- background,
423
- margin: 24,
424
- scrollOffset: 0,
425
- });
426
-
427
- console.log(`🎉 Medium-zoom initialized directly on SVG wrapper!`);
428
 
429
- // Observer pour les changements de thème
430
- const themeObserver = new MutationObserver(() => {
431
- console.log(`🎨 Theme changed, updating medium-zoom background`);
432
- // Re-initialiser medium-zoom avec le nouveau thème
433
- initializeMediumZoomOnSvg(svgElement, wrapper, originalMermaid);
434
- });
435
-
436
- themeObserver.observe(document.documentElement, {
437
- attributes: true,
438
- attributeFilter: ["data-theme"],
439
- });
440
 
441
- // Stocker l'observer pour le nettoyer plus tard
442
- wrapper._themeObserver = themeObserver;
443
-
444
- } catch (error) {
445
- console.error("❌ Error initializing medium-zoom on SVG:", error);
446
- // Fallback: utiliser le zoom custom
447
- wrapper.classList.remove("converting");
448
- wrapper.addEventListener("click", (e) => {
449
- e.preventDefault();
450
- e.stopPropagation();
451
- openMermaidZoom(wrapper, originalMermaid);
452
- });
453
  }
454
- }
455
-
456
- // Fonction pour attendre que Mermaid soit stable (évite le flicker)
457
- function waitForMermaidStable(mermaidEl, wrapper, index) {
458
- // Approche simplifiée : attendre un délai fixe puis convertir
459
- console.log(`⏳ Waiting for Mermaid ${index} to stabilize...`);
460
-
461
- setTimeout(() => {
462
- const svgElement = mermaidEl.querySelector("svg");
463
- if (svgElement) {
464
- console.log(`🎯 Converting Mermaid ${index} to image`);
465
- convertSvgToImageForMediumZoom(svgElement, wrapper, mermaidEl);
466
- } else {
467
- console.log(`❌ No SVG found in Mermaid element ${index}`);
468
- }
469
- }, 2000); // Attendre 2 secondes puis convertir
470
- }
471
 
472
- // Fonction pour convertir SVG en image SANS canvas (pour éviter l'erreur de sécurité)
473
- function convertSvgToImageForMediumZoom(svgElement, wrapper, originalMermaid) {
474
- console.log(`🔄 Converting SVG to image for REAL medium-zoom`);
475
 
476
- try {
477
- // Cloner le SVG
478
- const clonedSvg = svgElement.cloneNode(true);
479
 
480
- // Appliquer directement les styles SVG pour préserver l'apparence
481
- applyMermaidStylesToSvg(clonedSvg);
482
 
483
- // S'assurer que le SVG a des dimensions
484
- const bbox = clonedSvg.getBBox();
485
- const width = bbox.width || clonedSvg.clientWidth || 800;
486
- const height = bbox.height || clonedSvg.clientHeight || 600;
487
 
488
- console.log(`📏 SVG dimensions:`, { width, height });
489
 
490
- // Créer une image directement à partir du SVG (sans canvas)
491
- const svgData = new XMLSerializer().serializeToString(clonedSvg);
492
 
493
- // Ajouter les dimensions au SVG
494
- clonedSvg.setAttribute("width", width);
495
- clonedSvg.setAttribute("height", height);
496
- const svgWithDimensions = new XMLSerializer().serializeToString(clonedSvg);
497
 
498
- // Créer une URL data pour le SVG
499
- const svgBlob = new Blob([svgWithDimensions], {
500
- type: "image/svg+xml;charset=utf-8",
501
- });
502
- const svgUrl = URL.createObjectURL(svgBlob);
503
 
504
- // Créer un élément img avec le SVG
505
- const imgElement = document.createElement("img");
506
- imgElement.src = svgUrl;
507
- imgElement.style.width = "100%";
508
- imgElement.style.height = "auto";
509
- imgElement.style.display = "block";
510
- imgElement.setAttribute("data-zoomable", "1");
511
 
512
- // Remplacer le contenu du wrapper par l'image
513
- wrapper.innerHTML = "";
514
- wrapper.appendChild(imgElement);
515
 
516
- // Retirer la classe "converting" pour restaurer l'opacité normale
517
- wrapper.classList.remove("converting");
518
 
519
- console.log(`🖼️ SVG converted to img element`);
520
 
521
- // Attendre que l'image soit chargée puis initialiser medium-zoom
522
- imgElement.onload = () => {
523
- console.log(`✅ Image loaded, initializing REAL medium-zoom`);
524
 
525
- const isDark = document.documentElement.getAttribute("data-theme") === "dark";
526
- const background = isDark ? "rgba(0,0,0,.9)" : "rgba(0,0,0,.85)";
527
 
528
- // Utiliser VRAI medium-zoom
529
- window.mediumZoom(imgElement, {
530
- background,
531
- margin: 24,
532
- scrollOffset: 0,
533
- });
534
 
535
- console.log(`🎉 REAL medium-zoom initialized on Mermaid!`);
536
 
537
- // Observer pour les changements de thème - reconvertir l'image
538
- const themeObserver = new MutationObserver(() => {
539
- console.log(`🎨 Theme changed, reconverting Mermaid image`);
540
- // Reconverter l'image avec le nouveau thème
541
- convertSvgToImageForMediumZoom(svgElement, wrapper, originalMermaid);
542
- });
543
 
544
- themeObserver.observe(document.documentElement, {
545
- attributes: true,
546
- attributeFilter: ["data-theme"],
547
- });
548
 
549
- // Stocker l'observer pour le nettoyer plus tard
550
- wrapper._themeObserver = themeObserver;
551
- };
 
 
 
 
 
 
 
 
 
 
 
 
552
 
553
- imgElement.onerror = (error) => {
554
- console.error(`❌ Error loading SVG as image:`, error);
555
  // Fallback: utiliser le zoom custom
556
- wrapper.innerHTML = "";
557
- wrapper.appendChild(originalMermaid);
558
  wrapper.addEventListener("click", (e) => {
559
  e.preventDefault();
560
  e.stopPropagation();
561
  openMermaidZoom(wrapper, originalMermaid);
562
  });
563
- };
564
-
565
- } catch (error) {
566
- console.error("❌ Error converting SVG to image:", error);
567
- // Fallback: utiliser le zoom custom
568
- wrapper.addEventListener("click", (e) => {
569
- e.preventDefault();
570
- e.stopPropagation();
571
- openMermaidZoom(wrapper, originalMermaid);
572
- });
573
  }
574
- }
575
-
576
- // Fonction pour ouvrir le zoom custom des diagrammes Mermaid (comme medium-zoom)
577
- function openMermaidZoom(wrapper, mermaidElement) {
578
- // Créer l'overlay (comme medium-zoom)
579
- const overlay = document.createElement("div");
580
- overlay.className = "medium-zoom-overlay";
581
- overlay.style.cssText = `
582
  position: fixed;
583
  top: 0;
584
  left: 0;
@@ -594,10 +594,10 @@ function openMermaidZoom(wrapper, mermaidElement) {
594
  transition: opacity 0.3s ease;
595
  `;
596
 
597
- // Créer l'image zoomée (comme medium-zoom)
598
- const zoomImage = document.createElement("div");
599
- zoomImage.className = "medium-zoom-image";
600
- zoomImage.style.cssText = `
601
  max-width: 90%;
602
  max-height: 90%;
603
  background: white;
@@ -609,249 +609,250 @@ function openMermaidZoom(wrapper, mermaidElement) {
609
  overflow: auto;
610
  `;
611
 
612
- // Cloner le diagramme Mermaid
613
- const clonedMermaid = mermaidElement.cloneNode(true);
614
- clonedMermaid.style.cssText = `
615
  width: 100%;
616
  height: auto;
617
  max-width: none;
618
  display: block;
619
  `;
620
 
621
- zoomImage.appendChild(clonedMermaid);
622
- overlay.appendChild(zoomImage);
623
 
624
- // Ajouter l'overlay au body
625
- document.body.appendChild(overlay);
626
 
627
- // Animation d'ouverture (comme medium-zoom)
628
- requestAnimationFrame(() => {
629
- overlay.style.opacity = "1";
630
- zoomImage.style.transform = "scale(1)";
631
- });
632
 
633
- // Fermer au clic sur l'overlay
634
- overlay.addEventListener("click", (e) => {
635
- if (e.target === overlay) {
636
- closeMermaidZoom(overlay);
637
- }
638
- });
639
 
640
- // Fermer avec Escape
641
- const handleEscape = (e) => {
642
- if (e.key === "Escape") {
 
 
 
 
 
 
 
 
643
  closeMermaidZoom(overlay);
644
- document.removeEventListener("keydown", handleEscape);
645
- }
646
- };
647
- document.addEventListener("keydown", handleEscape);
648
 
649
- // Fermer au scroll (comme medium-zoom)
650
- const handleScroll = () => {
651
- closeMermaidZoom(overlay);
652
- };
653
- window.addEventListener("wheel", handleScroll, { passive: true });
654
- window.addEventListener("touchmove", handleScroll, { passive: true });
655
- window.addEventListener("scroll", handleScroll, { passive: true });
656
-
657
- // Stocker les handlers pour les nettoyer
658
- overlay._handlers = { handleEscape, handleScroll };
659
- }
660
-
661
- function closeMermaidZoom(overlay) {
662
- // Animation de fermeture
663
- overlay.style.opacity = "0";
664
- const zoomImage = overlay.querySelector(".medium-zoom-image");
665
- if (zoomImage) {
666
- zoomImage.style.transform = "scale(0.8)";
667
  }
668
 
669
- // Nettoyer les event listeners
670
- if (overlay._handlers) {
671
- document.removeEventListener("keydown", overlay._handlers.handleEscape);
672
- window.removeEventListener("wheel", overlay._handlers.handleScroll);
673
- window.removeEventListener("touchmove", overlay._handlers.handleScroll);
674
- window.removeEventListener("scroll", overlay._handlers.handleScroll);
675
- }
676
 
677
- // Supprimer l'overlay après l'animation
678
- setTimeout(() => {
679
- if (document.body.contains(overlay)) {
680
- document.body.removeChild(overlay);
 
 
681
  }
682
- }, 300);
683
- }
684
-
685
- // Fonction pour nettoyer les observers
686
- function cleanupMermaidObservers(wrapper) {
687
- if (wrapper._themeObserver) {
688
- wrapper._themeObserver.disconnect();
689
- wrapper._themeObserver = null;
690
  }
691
- }
692
-
693
- // Fonction principale pour initialiser le zoom Mermaid
694
- function setupMermaidZoom() {
695
- console.log(`🎯 setupMermaidZoom called`);
696
- const mermaidElements = document.querySelectorAll(".mermaid");
697
- console.log(`🔍 Found ${mermaidElements.length} Mermaid elements`);
698
-
699
- let processedCount = 0;
700
- mermaidElements.forEach((mermaidEl, index) => {
701
- // Vérifier si déjà wrappé
702
- if (
703
- mermaidEl.parentElement &&
704
- mermaidEl.parentElement.classList.contains("mermaid-zoom-wrapper")
705
- ) {
706
- console.log(`📦 Mermaid ${index} already wrapped`);
707
- processedCount++;
708
- return;
709
  }
 
710
 
711
- console.log(`📦 Wrapping Mermaid element ${index}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
712
 
713
- // Créer le wrapper
714
- const wrapper = document.createElement("div");
715
- wrapper.className = "mermaid-zoom-wrapper";
716
- wrapper.setAttribute("data-zoomable", "1");
717
 
718
- // Insérer le wrapper avant l'élément Mermaid
719
- mermaidEl.parentNode.insertBefore(wrapper, mermaidEl);
 
 
720
 
721
- // Déplacer l'élément Mermaid dans le wrapper
722
- wrapper.appendChild(mermaidEl);
723
 
724
- // S'assurer que le wrapper a des dimensions
725
- wrapper.style.display = "block";
726
- wrapper.style.width = "100%";
727
- wrapper.style.maxWidth = "100%";
728
 
729
- // Ajouter la classe "converting" pour masquer le flicker
730
- wrapper.classList.add("converting");
 
 
731
 
732
- // Convertir SVG en image pour utiliser VRAI medium-zoom
733
- console.log(`✅ Converting Mermaid to image for REAL medium-zoom`);
734
 
735
- // Utiliser le VRAI medium-zoom avec conversion SVG image SANS flicker
736
- console.log(`✅ Setting up REAL medium-zoom for Mermaid ${index}`);
737
 
738
- // Attendre que Mermaid soit stable puis convertir avec préservation des dimensions
739
- setTimeout(() => {
740
- const svgElement = mermaidEl.querySelector("svg");
741
- if (svgElement) {
742
- console.log(`🎯 Converting SVG to image with dimension preservation`);
743
- convertSvgToImagePreservingDimensions(svgElement, wrapper, mermaidEl);
744
- } else {
745
- console.log(`❌ No SVG found in Mermaid element ${index}`);
746
- }
747
- }, 1000); // Réduit de 2000ms à 1000ms pour être plus rapide
748
- });
749
 
750
- console.log(`✅ Processed ${processedCount} already wrapped, ${mermaidElements.length - processedCount} new diagrams`);
751
- }
752
-
753
- // Observer global pour forcer les bonnes couleurs et z-index sur les images Mermaid zoomées
754
- function setupGlobalMermaidColorFix() {
755
- const observer = new MutationObserver((mutations) => {
756
- mutations.forEach((mutation) => {
757
- if (mutation.type === 'childList') {
758
- // Vérifier si une image Mermaid est zoomée
759
- const zoomedMermaidImage = document.querySelector('.medium-zoom-image--opened.mermaid-zoom-image');
760
- if (zoomedMermaidImage) {
761
- console.log(`🎨 Global fix: Found zoomed Mermaid image, forcing correct colors and z-index`);
762
- zoomedMermaidImage.style.filter = 'none';
763
- zoomedMermaidImage.style.setProperty('filter', 'none', 'important');
764
- zoomedMermaidImage.style.zIndex = '10000000';
765
- zoomedMermaidImage.style.setProperty('z-index', '10000000', 'important');
766
  }
 
 
767
 
768
- // Vérifier si l'overlay medium-zoom existe
769
- const overlay = document.querySelector('.medium-zoom-overlay');
770
- if (overlay) {
771
- console.log(`🔝 Global fix: Found medium-zoom overlay, forcing high z-index`);
772
- overlay.style.zIndex = '9999999';
773
- overlay.style.setProperty('z-index', '9999999', 'important');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
774
  }
775
- }
776
  });
777
- });
778
 
779
- observer.observe(document.body, {
780
- childList: true,
781
- subtree: true
782
- });
783
 
784
- console.log(`🎨 Global Mermaid color and z-index fix observer started`);
785
- }
786
-
787
- // Fonction pour initialiser le zoom Mermaid avec retry
788
- function initMermaidZoom() {
789
- console.log("🎯 initMermaidZoom called");
790
-
791
- // Attendre que mediumZoom soit disponible
792
- const checkMediumZoom = () => {
793
- if (window.mediumZoom) {
794
- console.log("✅ mediumZoom found, initializing Mermaid zoom");
795
- setupMermaidZoom();
796
- } else {
797
- console.log("⏳ Waiting for mediumZoom...");
798
- setTimeout(checkMediumZoom, 100);
799
- }
800
- };
801
 
802
- checkMediumZoom();
803
- }
804
-
805
- // Export des fonctions pour utilisation globale
806
- window.MermaidZoom = {
807
- init: initMermaidZoom,
808
- setup: setupMermaidZoom,
809
- cleanup: cleanupMermaidObservers,
810
- convertSvgToImage: convertSvgToImageForMediumZoom,
811
- openZoom: openMermaidZoom,
812
- closeZoom: closeMermaidZoom
813
- };
814
-
815
- // Auto-initialisation si le DOM est déjà chargé
816
- if (document.readyState === "loading") {
817
- document.addEventListener("DOMContentLoaded", () => {
818
- initMermaidZoom();
819
- setupGlobalMermaidColorFix();
820
- });
821
- } else {
822
- initMermaidZoom();
823
- setupGlobalMermaidColorFix();
824
- }
825
 
826
- // Aussi après le chargement complet
827
- window.addEventListener("load", () => {
828
- setTimeout(() => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
829
  initMermaidZoom();
830
  setupGlobalMermaidColorFix();
831
- }, 1000);
832
- });
833
-
834
- // Observer simple pour les nouveaux diagrammes Mermaid (avec debounce)
835
- let resizeTimeout;
836
- const observer = new MutationObserver(() => {
837
- // Debounce pour éviter les appels multiples
838
- clearTimeout(resizeTimeout);
839
- resizeTimeout = setTimeout(() => {
840
- // Vérifier s'il y a vraiment de nouveaux diagrammes
841
- const mermaidElements = document.querySelectorAll(".mermaid");
842
- const wrappedElements = document.querySelectorAll(".mermaid-zoom-wrapper");
843
 
844
- // Seulement si il y a plus de diagrammes que de wrappers
845
- if (mermaidElements.length > wrappedElements.length) {
846
- console.log(`🔄 New Mermaid diagrams detected: ${mermaidElements.length} total, ${wrappedElements.length} wrapped`);
847
  initMermaidZoom();
848
- }
849
- }, 500); // Délai plus long pour éviter les appels fréquents
850
- });
851
 
852
- observer.observe(document.body, {
853
- childList: true,
854
- subtree: true,
855
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
856
 
857
- console.log("🚀 Mermaid Zoom Script v19.0 loaded - DEBOUNCED observer to prevent resize loops");
 
 
19
  openZoom: () => { },
20
  closeZoom: () => { }
21
  };
22
+ // Arrêter l'exécution ici sans lancer d'erreur
23
+ // Le reste du code ne s'exécutera pas car tout est dans le bloc if ci-dessous
24
+ } else {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
+ // Fonction pour appliquer les styles Mermaid au SVG selon le thème
27
+ function applyMermaidStylesToSvg(svgElement) {
28
+ try {
29
+ const isDark = document.documentElement.getAttribute("data-theme") === "dark";
30
+ console.log(`🎨 Applying Mermaid styles for theme: ${isDark ? 'dark' : 'light'}`);
31
+
32
+ // Couleurs selon le thème - avec plus de contraste pour le zoom
33
+ const colors = isDark ? {
34
+ nodeFill: '#1a1a1a',
35
+ nodeStroke: '#ffffff',
36
+ nodeStrokeWidth: '2', // Plus épais pour plus de visibilité
37
+ clusterFill: '#2a2a2a',
38
+ clusterStroke: '#ffffff',
39
+ clusterStrokeWidth: '2',
40
+ pathStroke: '#ffffff',
41
+ pathStrokeWidth: '2', // Plus épais pour les flèches
42
+ textColor: '#ffffff',
43
+ linkColor: '#ffffff' // Couleur spécifique pour les liens
44
+ } : {
45
+ nodeFill: '#ffffff',
46
+ nodeStroke: '#000000', // Noir pur pour plus de contraste
47
+ nodeStrokeWidth: '2',
48
+ clusterFill: '#f9f9f9',
49
+ clusterStroke: '#000000', // Noir pur
50
+ clusterStrokeWidth: '2',
51
+ pathStroke: '#000000', // Noir pur pour les flèches
52
+ pathStrokeWidth: '2',
53
+ textColor: '#000000', // Noir pur
54
+ linkColor: '#000000' // Noir pur pour les liens
55
+ };
56
 
57
+ // Appliquer border-radius aux rectangles
58
+ const rects = svgElement.querySelectorAll('rect:not(.flowchart-link), .node rect, .nodeLabel rect');
59
+ rects.forEach(rect => {
60
+ rect.setAttribute('rx', '8');
61
+ rect.setAttribute('ry', '8');
62
  rect.setAttribute('fill', colors.nodeFill);
63
  rect.setAttribute('stroke', colors.nodeStroke);
64
  rect.setAttribute('stroke-width', colors.nodeStrokeWidth);
65
+ });
 
66
 
67
+ // Appliquer border-radius et couleurs aux clusters
68
+ const clusterRects = svgElement.querySelectorAll('.cluster rect');
69
+ clusterRects.forEach(rect => {
70
+ rect.setAttribute('rx', '8');
71
+ rect.setAttribute('ry', '8');
72
  rect.setAttribute('fill', colors.clusterFill);
73
  rect.setAttribute('stroke', colors.clusterStroke);
74
  rect.setAttribute('stroke-width', colors.clusterStrokeWidth);
75
+ });
 
76
 
77
+ // Appliquer les couleurs aux nœuds
78
+ const nodes = svgElement.querySelectorAll('.node');
79
+ nodes.forEach(node => {
80
+ const rect = node.querySelector('rect');
81
+ if (rect) {
82
+ rect.setAttribute('fill', colors.nodeFill);
83
+ rect.setAttribute('stroke', colors.nodeStroke);
84
+ rect.setAttribute('stroke-width', colors.nodeStrokeWidth);
85
+ }
86
+ });
87
 
88
+ // Appliquer les couleurs aux clusters
89
+ const clusters = svgElement.querySelectorAll('.cluster');
90
+ clusters.forEach(cluster => {
91
+ const rect = cluster.querySelector('rect');
92
+ if (rect) {
93
+ rect.setAttribute('fill', colors.clusterFill);
94
+ rect.setAttribute('stroke', colors.clusterStroke);
95
+ rect.setAttribute('stroke-width', colors.clusterStrokeWidth);
96
+ }
97
+ });
98
 
99
+ // Appliquer les couleurs aux chemins et flèches
100
+ const paths = svgElement.querySelectorAll('.edgePath, path, .flowchart-link');
101
+ paths.forEach(path => {
102
+ path.setAttribute('stroke', colors.pathStroke);
103
+ path.setAttribute('stroke-width', colors.pathStrokeWidth);
104
+ });
105
 
106
+ // Appliquer les couleurs aux liens spécifiquement
107
+ const links = svgElement.querySelectorAll('.flowchart-link, .edgeLabel');
108
+ links.forEach(link => {
109
+ link.setAttribute('stroke', colors.linkColor);
110
+ link.setAttribute('fill', colors.linkColor);
111
+ });
112
 
113
+ // Appliquer les couleurs au texte
114
+ const textElements = svgElement.querySelectorAll('text, .nodeLabel text, .edgeLabel text');
115
+ textElements.forEach(text => {
116
+ text.setAttribute('fill', colors.textColor);
117
+ });
118
+
119
+ // Appliquer les couleurs aux marqueurs de flèches
120
+ const markers = svgElement.querySelectorAll('marker, marker path');
121
+ markers.forEach(marker => {
122
+ marker.setAttribute('fill', colors.pathStroke);
123
+ marker.setAttribute('stroke', colors.pathStroke);
124
+ });
125
 
126
+ console.log(`🎨 Applied Mermaid styles: ${rects.length} rects, ${clusters.length} clusters, ${paths.length} paths, ${textElements.length} text elements`);
127
+
128
+ } catch (error) {
129
+ console.error('❌ Error applying styles to SVG:', error);
130
+ }
131
  }
 
132
 
133
+ // Fonction pour extraire les styles CSS appliqués au diagramme Mermaid
134
+ function getComputedStylesForMermaid(mermaidElement) {
135
+ try {
136
+ // Styles CSS essentiels pour Mermaid (hardcodés pour éviter les problèmes CORS)
137
+ const essentialStyles = `
138
  .mermaid rect:not(.flowchart-link),
139
  .mermaid .node rect,
140
  .mermaid .nodeLabel rect {
 
170
  }
171
  `;
172
 
173
+ console.log(`🎨 Using essential CSS styles for Mermaid`);
174
+ return essentialStyles;
175
 
176
+ } catch (error) {
177
+ console.error('❌ Error getting CSS styles:', error);
178
+ return null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  }
180
+ }
181
 
182
+ // Fonction pour forcer Mermaid à se re-rendre avec le bon thème
183
+ function forceMermaidThemeUpdate(mermaidElement) {
184
+ try {
185
+ // Obtenir le thème actuel
186
+ const currentTheme = document.documentElement.getAttribute("data-theme");
187
+ console.log(`🎨 Forcing Mermaid theme update to: ${currentTheme}`);
188
+
189
+ // Trouver le diagramme Mermaid original
190
+ const mermaidCode = mermaidElement.textContent || mermaidElement.innerText;
191
+ if (!mermaidCode) {
192
+ console.log(`❌ No Mermaid code found to re-render`);
193
+ return false;
194
+ }
195
 
196
+ // Forcer le re-rendu de Mermaid avec le nouveau thème
197
+ if (window.mermaid) {
198
+ // Utiliser l'API Mermaid pour re-rendre avec le bon thème
199
+ const config = {
200
+ theme: currentTheme === 'dark' ? 'dark' : 'neutral',
201
+ autoTheme: true
202
+ };
203
 
204
+ console.log(`🔄 Re-rendering Mermaid with config:`, config);
 
205
 
206
+ // Re-rendre le diagramme
207
+ mermaid.init(undefined, mermaidElement);
208
+
209
+ return true;
210
+ } else {
211
+ console.log(`❌ Mermaid API not available`);
212
+ return false;
213
+ }
214
+ } catch (error) {
215
+ console.error(`❌ Error forcing Mermaid theme update:`, error);
216
  return false;
217
  }
 
 
 
218
  }
 
219
 
220
+ // Fonction pour convertir SVG en image en préservant EXACTEMENT les dimensions
221
+ function convertSvgToImagePreservingDimensions(svgElement, wrapper, originalMermaid) {
222
+ console.log(`🔄 Converting SVG to image with EXACT dimension preservation`);
223
 
224
+ try {
225
+ // Obtenir les dimensions EXACTES du wrapper actuel
226
+ const wrapperRect = wrapper.getBoundingClientRect();
227
+ const wrapperWidth = Math.round(wrapperRect.width);
228
+ const wrapperHeight = Math.round(wrapperRect.height);
229
 
230
+ console.log(`📏 Wrapper dimensions:`, { width: wrapperWidth, height: wrapperHeight });
231
 
232
+ // Cloner le SVG
233
+ const clonedSvg = svgElement.cloneNode(true);
234
 
235
+ // Appliquer les styles Mermaid
236
+ applyMermaidStylesToSvg(clonedSvg);
237
 
238
+ // Créer une image PLUS GRANDE pour permettre un vrai zoom (2x la taille)
239
+ const zoomFactor = 2;
240
+ const imageWidth = wrapperWidth * zoomFactor;
241
+ const imageHeight = wrapperHeight * zoomFactor;
242
 
243
+ // Forcer les dimensions plus grandes sur le SVG
244
+ clonedSvg.setAttribute('width', imageWidth);
245
+ clonedSvg.setAttribute('height', imageHeight);
246
+ clonedSvg.style.width = `${imageWidth}px`;
247
+ clonedSvg.style.height = `${imageHeight}px`;
248
 
249
+ console.log(`🔍 Creating zoomable image:`, {
250
+ original: { width: wrapperWidth, height: wrapperHeight },
251
+ zoomed: { width: imageWidth, height: imageHeight },
252
+ factor: zoomFactor
253
+ });
254
 
255
+ // Créer une URL data pour le SVG
256
+ const svgData = new XMLSerializer().serializeToString(clonedSvg);
257
+ const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
258
+ const svgUrl = URL.createObjectURL(svgBlob);
259
 
260
+ // Créer un élément img avec les dimensions du wrapper (affichage normal)
261
+ const imgElement = document.createElement('img');
262
+ imgElement.src = svgUrl;
263
+ imgElement.style.width = `${wrapperWidth}px`; // Affichage normal
264
+ imgElement.style.height = `${wrapperHeight}px`; // Affichage normal
265
+ imgElement.style.display = 'block';
266
+ imgElement.setAttribute('data-zoomable', '1');
267
+ imgElement.classList.add('mermaid-zoom-image'); // Classe spécifique pour Mermaid
268
 
269
+ // Ajouter les attributs pour medium-zoom (dimensions réelles de l'image)
270
+ imgElement.setAttribute('data-zoom-width', imageWidth);
271
+ imgElement.setAttribute('data-zoom-height', imageHeight);
272
 
273
+ console.log(`🖼️ Image created with exact dimensions:`, { width: wrapperWidth, height: wrapperHeight });
274
 
275
+ // Remplacer le contenu du wrapper par l'image
276
+ wrapper.innerHTML = '';
277
+ wrapper.appendChild(imgElement);
278
 
279
+ // Retirer la classe "converting" pour restaurer l'opacité normale
280
+ wrapper.classList.remove("converting");
281
+
282
+ // Attendre que l'image soit chargée puis initialiser medium-zoom
283
+ imgElement.onload = () => {
284
+ console.log(`✅ Image loaded, initializing REAL medium-zoom`);
285
+
286
+ const isDark = document.documentElement.getAttribute("data-theme") === "dark";
287
+ const background = isDark ? "rgba(0,0,0,.9)" : "rgba(0,0,0,.85)";
288
+
289
+ // Utiliser VRAI medium-zoom
290
+ const zoomInstance = window.mediumZoom(imgElement, {
291
+ background,
292
+ margin: 24,
293
+ scrollOffset: 0,
294
+ });
295
+
296
+ console.log(`🎉 REAL medium-zoom initialized with exact dimensions!`);
297
+
298
+ // Forcer les bonnes couleurs en JavaScript (contournement du CSS global)
299
+ const forceCorrectColors = () => {
300
+ // Trouver l'image zoomée dans le DOM
301
+ const zoomedImage = document.querySelector('.medium-zoom-image--opened');
302
+ if (zoomedImage && zoomedImage.classList.contains('mermaid-zoom-image')) {
303
+ console.log(`🎨 Forcing correct colors for Mermaid zoom image`);
304
+ zoomedImage.style.filter = 'none';
305
+ zoomedImage.style.setProperty('filter', 'none', 'important');
306
+ }
307
+
308
+ // Forcer les z-index élevés
309
+ const overlay = document.querySelector('.medium-zoom-overlay');
310
+ if (overlay) {
311
+ console.log(`🔝 Forcing high z-index for overlay`);
312
+ overlay.style.zIndex = '9999999';
313
+ overlay.style.setProperty('z-index', '9999999', 'important');
314
+ }
315
+
316
+ if (zoomedImage) {
317
+ console.log(`🔝 Forcing high z-index for zoomed image`);
318
+ zoomedImage.style.zIndex = '10000000';
319
+ zoomedImage.style.setProperty('z-index', '10000000', 'important');
320
+ }
321
+ };
322
+
323
+ // Écouter les événements de zoom pour appliquer les bonnes couleurs
324
+ imgElement.addEventListener('zoom:open', forceCorrectColors);
325
+ imgElement.addEventListener('zoom:opened', forceCorrectColors);
326
+
327
+ // Observer pour les changements de thème
328
+ const themeObserver = new MutationObserver(() => {
329
+ console.log(`🎨 Theme changed, forcing Mermaid re-render with new theme`);
330
 
331
+ // Forcer Mermaid à se re-rendre avec le nouveau thème
332
+ const currentTheme = document.documentElement.getAttribute("data-theme");
333
+ console.log(`🎨 Current theme: ${currentTheme}`);
334
+
335
+ // Attendre un peu pour que Mermaid détecte le changement de thème
336
+ setTimeout(() => {
337
+ // Forcer Mermaid à se re-rendre avec le nouveau thème
338
+ const themeUpdated = forceMermaidThemeUpdate(originalMermaid);
339
+
340
+ if (themeUpdated) {
341
+ // Attendre que le re-rendu soit terminé
342
+ setTimeout(() => {
343
+ // Re-obtenir le SVG mis à jour
344
+ const updatedSvgElement = originalMermaid.querySelector("svg");
345
+ if (updatedSvgElement) {
346
+ console.log(`🔄 Re-converting with updated SVG for theme: ${currentTheme}`);
347
+ convertSvgToImagePreservingDimensions(updatedSvgElement, wrapper, originalMermaid);
348
+ } else {
349
+ console.log(`❌ No updated SVG found after theme change`);
350
+ }
351
+ }, 200); // Délai pour laisser Mermaid finir le re-rendu
352
+ } else {
353
+ console.log(`❌ Failed to update Mermaid theme, using current SVG`);
354
+ // Fallback: utiliser le SVG actuel même s'il n'est pas mis à jour
355
+ const currentSvgElement = originalMermaid.querySelector("svg");
356
+ if (currentSvgElement) {
357
+ convertSvgToImagePreservingDimensions(currentSvgElement, wrapper, originalMermaid);
358
+ }
359
+ }
360
+ }, 100); // Petit délai pour laisser Mermaid se mettre à jour
361
+ });
362
 
363
+ themeObserver.observe(document.documentElement, {
364
+ attributes: true,
365
+ attributeFilter: ["data-theme"],
366
+ });
367
+
368
+ wrapper._themeObserver = themeObserver;
369
+ };
370
+
371
+ imgElement.onerror = (error) => {
372
+ console.error(`❌ Error loading SVG as image:`, error);
373
+ // Fallback: utiliser le zoom custom
374
+ wrapper.innerHTML = '';
375
+ wrapper.appendChild(originalMermaid);
376
+ wrapper.classList.remove("converting");
377
+ wrapper.addEventListener("click", (e) => {
378
+ e.preventDefault();
379
+ e.stopPropagation();
380
+ openMermaidZoom(wrapper, originalMermaid);
381
+ });
382
+ };
383
+
384
+ } catch (error) {
385
+ console.error("❌ Error converting SVG to image:", error);
386
+ // Fallback: utiliser le zoom custom
387
+ wrapper.classList.remove("converting");
388
+ wrapper.addEventListener("click", (e) => {
389
+ e.preventDefault();
390
+ e.stopPropagation();
391
+ openMermaidZoom(wrapper, originalMermaid);
392
+ });
393
+ }
394
+ }
395
+
396
+ // Fonction pour initialiser medium-zoom directement sur le SVG
397
+ function initializeMediumZoomOnSvg(svgElement, wrapper, originalMermaid) {
398
+ try {
399
+ // S'assurer que le SVG a des dimensions fixes pour éviter le flicker
400
+ const bbox = svgElement.getBBox();
401
+ const width = bbox.width || svgElement.clientWidth || 800;
402
+ const height = bbox.height || svgElement.clientHeight || 600;
403
+
404
+ // Fixer les dimensions du SVG pour éviter les changements
405
+ svgElement.setAttribute('width', width);
406
+ svgElement.setAttribute('height', height);
407
+ svgElement.style.width = '100%';
408
+ svgElement.style.height = 'auto';
409
+ svgElement.style.display = 'block';
410
+
411
+ // Retirer la classe "converting" pour restaurer l'opacité normale
412
+ wrapper.classList.remove("converting");
413
+
414
+ console.log(`📏 SVG dimensions fixed:`, { width, height });
415
+
416
+ // Initialiser medium-zoom directement sur le wrapper SVG
417
  const isDark = document.documentElement.getAttribute("data-theme") === "dark";
418
  const background = isDark ? "rgba(0,0,0,.9)" : "rgba(0,0,0,.85)";
419
 
420
+ // Utiliser medium-zoom sur le wrapper qui contient le SVG
421
+ window.mediumZoom(wrapper, {
422
  background,
423
  margin: 24,
424
  scrollOffset: 0,
425
  });
426
 
427
+ console.log(`🎉 Medium-zoom initialized directly on SVG wrapper!`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
 
429
  // Observer pour les changements de thème
430
  const themeObserver = new MutationObserver(() => {
431
+ console.log(`🎨 Theme changed, updating medium-zoom background`);
432
+ // Re-initialiser medium-zoom avec le nouveau thème
433
+ initializeMediumZoomOnSvg(svgElement, wrapper, originalMermaid);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  });
435
 
436
  themeObserver.observe(document.documentElement, {
 
438
  attributeFilter: ["data-theme"],
439
  });
440
 
441
+ // Stocker l'observer pour le nettoyer plus tard
442
  wrapper._themeObserver = themeObserver;
 
443
 
444
+ } catch (error) {
445
+ console.error("❌ Error initializing medium-zoom on SVG:", error);
446
  // Fallback: utiliser le zoom custom
 
 
447
  wrapper.classList.remove("converting");
448
  wrapper.addEventListener("click", (e) => {
449
  e.preventDefault();
450
  e.stopPropagation();
451
  openMermaidZoom(wrapper, originalMermaid);
452
  });
453
+ }
 
 
 
 
 
 
 
 
 
 
454
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
455
 
456
+ // Fonction pour attendre que Mermaid soit stable (évite le flicker)
457
+ function waitForMermaidStable(mermaidEl, wrapper, index) {
458
+ // Approche simplifiée : attendre un délai fixe puis convertir
459
+ console.log(`⏳ Waiting for Mermaid ${index} to stabilize...`);
 
 
 
 
 
 
 
460
 
461
+ setTimeout(() => {
462
+ const svgElement = mermaidEl.querySelector("svg");
463
+ if (svgElement) {
464
+ console.log(`🎯 Converting Mermaid ${index} to image`);
465
+ convertSvgToImageForMediumZoom(svgElement, wrapper, mermaidEl);
466
+ } else {
467
+ console.log(`❌ No SVG found in Mermaid element ${index}`);
468
+ }
469
+ }, 2000); // Attendre 2 secondes puis convertir
 
 
 
470
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
471
 
472
+ // Fonction pour convertir SVG en image SANS canvas (pour éviter l'erreur de sécurité)
473
+ function convertSvgToImageForMediumZoom(svgElement, wrapper, originalMermaid) {
474
+ console.log(`🔄 Converting SVG to image for REAL medium-zoom`);
475
 
476
+ try {
477
+ // Cloner le SVG
478
+ const clonedSvg = svgElement.cloneNode(true);
479
 
480
+ // Appliquer directement les styles SVG pour préserver l'apparence
481
+ applyMermaidStylesToSvg(clonedSvg);
482
 
483
+ // S'assurer que le SVG a des dimensions
484
+ const bbox = clonedSvg.getBBox();
485
+ const width = bbox.width || clonedSvg.clientWidth || 800;
486
+ const height = bbox.height || clonedSvg.clientHeight || 600;
487
 
488
+ console.log(`📏 SVG dimensions:`, { width, height });
489
 
490
+ // Créer une image directement à partir du SVG (sans canvas)
491
+ const svgData = new XMLSerializer().serializeToString(clonedSvg);
492
 
493
+ // Ajouter les dimensions au SVG
494
+ clonedSvg.setAttribute("width", width);
495
+ clonedSvg.setAttribute("height", height);
496
+ const svgWithDimensions = new XMLSerializer().serializeToString(clonedSvg);
497
 
498
+ // Créer une URL data pour le SVG
499
+ const svgBlob = new Blob([svgWithDimensions], {
500
+ type: "image/svg+xml;charset=utf-8",
501
+ });
502
+ const svgUrl = URL.createObjectURL(svgBlob);
503
 
504
+ // Créer un élément img avec le SVG
505
+ const imgElement = document.createElement("img");
506
+ imgElement.src = svgUrl;
507
+ imgElement.style.width = "100%";
508
+ imgElement.style.height = "auto";
509
+ imgElement.style.display = "block";
510
+ imgElement.setAttribute("data-zoomable", "1");
511
 
512
+ // Remplacer le contenu du wrapper par l'image
513
+ wrapper.innerHTML = "";
514
+ wrapper.appendChild(imgElement);
515
 
516
+ // Retirer la classe "converting" pour restaurer l'opacité normale
517
+ wrapper.classList.remove("converting");
518
 
519
+ console.log(`🖼️ SVG converted to img element`);
520
 
521
+ // Attendre que l'image soit chargée puis initialiser medium-zoom
522
+ imgElement.onload = () => {
523
+ console.log(`✅ Image loaded, initializing REAL medium-zoom`);
524
 
525
+ const isDark = document.documentElement.getAttribute("data-theme") === "dark";
526
+ const background = isDark ? "rgba(0,0,0,.9)" : "rgba(0,0,0,.85)";
527
 
528
+ // Utiliser VRAI medium-zoom
529
+ window.mediumZoom(imgElement, {
530
+ background,
531
+ margin: 24,
532
+ scrollOffset: 0,
533
+ });
534
 
535
+ console.log(`🎉 REAL medium-zoom initialized on Mermaid!`);
536
 
537
+ // Observer pour les changements de thème - reconvertir l'image
538
+ const themeObserver = new MutationObserver(() => {
539
+ console.log(`🎨 Theme changed, reconverting Mermaid image`);
540
+ // Reconverter l'image avec le nouveau thème
541
+ convertSvgToImageForMediumZoom(svgElement, wrapper, originalMermaid);
542
+ });
543
 
544
+ themeObserver.observe(document.documentElement, {
545
+ attributes: true,
546
+ attributeFilter: ["data-theme"],
547
+ });
548
 
549
+ // Stocker l'observer pour le nettoyer plus tard
550
+ wrapper._themeObserver = themeObserver;
551
+ };
552
+
553
+ imgElement.onerror = (error) => {
554
+ console.error(`❌ Error loading SVG as image:`, error);
555
+ // Fallback: utiliser le zoom custom
556
+ wrapper.innerHTML = "";
557
+ wrapper.appendChild(originalMermaid);
558
+ wrapper.addEventListener("click", (e) => {
559
+ e.preventDefault();
560
+ e.stopPropagation();
561
+ openMermaidZoom(wrapper, originalMermaid);
562
+ });
563
+ };
564
 
565
+ } catch (error) {
566
+ console.error("❌ Error converting SVG to image:", error);
567
  // Fallback: utiliser le zoom custom
 
 
568
  wrapper.addEventListener("click", (e) => {
569
  e.preventDefault();
570
  e.stopPropagation();
571
  openMermaidZoom(wrapper, originalMermaid);
572
  });
573
+ }
 
 
 
 
 
 
 
 
 
574
  }
575
+
576
+ // Fonction pour ouvrir le zoom custom des diagrammes Mermaid (comme medium-zoom)
577
+ function openMermaidZoom(wrapper, mermaidElement) {
578
+ // Créer l'overlay (comme medium-zoom)
579
+ const overlay = document.createElement("div");
580
+ overlay.className = "medium-zoom-overlay";
581
+ overlay.style.cssText = `
 
582
  position: fixed;
583
  top: 0;
584
  left: 0;
 
594
  transition: opacity 0.3s ease;
595
  `;
596
 
597
+ // Créer l'image zoomée (comme medium-zoom)
598
+ const zoomImage = document.createElement("div");
599
+ zoomImage.className = "medium-zoom-image";
600
+ zoomImage.style.cssText = `
601
  max-width: 90%;
602
  max-height: 90%;
603
  background: white;
 
609
  overflow: auto;
610
  `;
611
 
612
+ // Cloner le diagramme Mermaid
613
+ const clonedMermaid = mermaidElement.cloneNode(true);
614
+ clonedMermaid.style.cssText = `
615
  width: 100%;
616
  height: auto;
617
  max-width: none;
618
  display: block;
619
  `;
620
 
621
+ zoomImage.appendChild(clonedMermaid);
622
+ overlay.appendChild(zoomImage);
623
 
624
+ // Ajouter l'overlay au body
625
+ document.body.appendChild(overlay);
626
 
627
+ // Animation d'ouverture (comme medium-zoom)
628
+ requestAnimationFrame(() => {
629
+ overlay.style.opacity = "1";
630
+ zoomImage.style.transform = "scale(1)";
631
+ });
632
 
633
+ // Fermer au clic sur l'overlay
634
+ overlay.addEventListener("click", (e) => {
635
+ if (e.target === overlay) {
636
+ closeMermaidZoom(overlay);
637
+ }
638
+ });
639
 
640
+ // Fermer avec Escape
641
+ const handleEscape = (e) => {
642
+ if (e.key === "Escape") {
643
+ closeMermaidZoom(overlay);
644
+ document.removeEventListener("keydown", handleEscape);
645
+ }
646
+ };
647
+ document.addEventListener("keydown", handleEscape);
648
+
649
+ // Fermer au scroll (comme medium-zoom)
650
+ const handleScroll = () => {
651
  closeMermaidZoom(overlay);
652
+ };
653
+ window.addEventListener("wheel", handleScroll, { passive: true });
654
+ window.addEventListener("touchmove", handleScroll, { passive: true });
655
+ window.addEventListener("scroll", handleScroll, { passive: true });
656
 
657
+ // Stocker les handlers pour les nettoyer
658
+ overlay._handlers = { handleEscape, handleScroll };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
659
  }
660
 
661
+ function closeMermaidZoom(overlay) {
662
+ // Animation de fermeture
663
+ overlay.style.opacity = "0";
664
+ const zoomImage = overlay.querySelector(".medium-zoom-image");
665
+ if (zoomImage) {
666
+ zoomImage.style.transform = "scale(0.8)";
667
+ }
668
 
669
+ // Nettoyer les event listeners
670
+ if (overlay._handlers) {
671
+ document.removeEventListener("keydown", overlay._handlers.handleEscape);
672
+ window.removeEventListener("wheel", overlay._handlers.handleScroll);
673
+ window.removeEventListener("touchmove", overlay._handlers.handleScroll);
674
+ window.removeEventListener("scroll", overlay._handlers.handleScroll);
675
  }
676
+
677
+ // Supprimer l'overlay après l'animation
678
+ setTimeout(() => {
679
+ if (document.body.contains(overlay)) {
680
+ document.body.removeChild(overlay);
681
+ }
682
+ }, 300);
 
683
  }
684
+
685
+ // Fonction pour nettoyer les observers
686
+ function cleanupMermaidObservers(wrapper) {
687
+ if (wrapper._themeObserver) {
688
+ wrapper._themeObserver.disconnect();
689
+ wrapper._themeObserver = null;
 
 
 
 
 
 
 
 
 
 
 
 
690
  }
691
+ }
692
 
693
+ // Fonction principale pour initialiser le zoom Mermaid
694
+ function setupMermaidZoom() {
695
+ console.log(`🎯 setupMermaidZoom called`);
696
+ const mermaidElements = document.querySelectorAll(".mermaid");
697
+ console.log(`🔍 Found ${mermaidElements.length} Mermaid elements`);
698
+
699
+ let processedCount = 0;
700
+ mermaidElements.forEach((mermaidEl, index) => {
701
+ // Vérifier si déjà wrappé
702
+ if (
703
+ mermaidEl.parentElement &&
704
+ mermaidEl.parentElement.classList.contains("mermaid-zoom-wrapper")
705
+ ) {
706
+ console.log(`📦 Mermaid ${index} already wrapped`);
707
+ processedCount++;
708
+ return;
709
+ }
710
 
711
+ console.log(`📦 Wrapping Mermaid element ${index}`);
 
 
 
712
 
713
+ // Créer le wrapper
714
+ const wrapper = document.createElement("div");
715
+ wrapper.className = "mermaid-zoom-wrapper";
716
+ wrapper.setAttribute("data-zoomable", "1");
717
 
718
+ // Insérer le wrapper avant l'élément Mermaid
719
+ mermaidEl.parentNode.insertBefore(wrapper, mermaidEl);
720
 
721
+ // Déplacer l'élément Mermaid dans le wrapper
722
+ wrapper.appendChild(mermaidEl);
 
 
723
 
724
+ // S'assurer que le wrapper a des dimensions
725
+ wrapper.style.display = "block";
726
+ wrapper.style.width = "100%";
727
+ wrapper.style.maxWidth = "100%";
728
 
729
+ // Ajouter la classe "converting" pour masquer le flicker
730
+ wrapper.classList.add("converting");
731
 
732
+ // Convertir SVG en image pour utiliser VRAI medium-zoom
733
+ console.log(`✅ Converting Mermaid to image for REAL medium-zoom`);
734
 
735
+ // Utiliser le VRAI medium-zoom avec conversion SVG image SANS flicker
736
+ console.log(`✅ Setting up REAL medium-zoom for Mermaid ${index}`);
 
 
 
 
 
 
 
 
 
737
 
738
+ // Attendre que Mermaid soit stable puis convertir avec préservation des dimensions
739
+ setTimeout(() => {
740
+ const svgElement = mermaidEl.querySelector("svg");
741
+ if (svgElement) {
742
+ console.log(`🎯 Converting SVG to image with dimension preservation`);
743
+ convertSvgToImagePreservingDimensions(svgElement, wrapper, mermaidEl);
744
+ } else {
745
+ console.log(`❌ No SVG found in Mermaid element ${index}`);
 
 
 
 
 
 
 
 
746
  }
747
+ }, 1000); // Réduit de 2000ms à 1000ms pour être plus rapide
748
+ });
749
 
750
+ console.log(`✅ Processed ${processedCount} already wrapped, ${mermaidElements.length - processedCount} new diagrams`);
751
+ }
752
+
753
+ // Observer global pour forcer les bonnes couleurs et z-index sur les images Mermaid zoomées
754
+ function setupGlobalMermaidColorFix() {
755
+ const observer = new MutationObserver((mutations) => {
756
+ mutations.forEach((mutation) => {
757
+ if (mutation.type === 'childList') {
758
+ // Vérifier si une image Mermaid est zoomée
759
+ const zoomedMermaidImage = document.querySelector('.medium-zoom-image--opened.mermaid-zoom-image');
760
+ if (zoomedMermaidImage) {
761
+ console.log(`🎨 Global fix: Found zoomed Mermaid image, forcing correct colors and z-index`);
762
+ zoomedMermaidImage.style.filter = 'none';
763
+ zoomedMermaidImage.style.setProperty('filter', 'none', 'important');
764
+ zoomedMermaidImage.style.zIndex = '10000000';
765
+ zoomedMermaidImage.style.setProperty('z-index', '10000000', 'important');
766
+ }
767
+
768
+ // Vérifier si l'overlay medium-zoom existe
769
+ const overlay = document.querySelector('.medium-zoom-overlay');
770
+ if (overlay) {
771
+ console.log(`🔝 Global fix: Found medium-zoom overlay, forcing high z-index`);
772
+ overlay.style.zIndex = '9999999';
773
+ overlay.style.setProperty('z-index', '9999999', 'important');
774
+ }
775
  }
776
+ });
777
  });
 
778
 
779
+ observer.observe(document.body, {
780
+ childList: true,
781
+ subtree: true
782
+ });
783
 
784
+ console.log(`🎨 Global Mermaid color and z-index fix observer started`);
785
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
786
 
787
+ // Fonction pour initialiser le zoom Mermaid avec retry
788
+ function initMermaidZoom() {
789
+ console.log("🎯 initMermaidZoom called");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
790
 
791
+ // Attendre que mediumZoom soit disponible
792
+ const checkMediumZoom = () => {
793
+ if (window.mediumZoom) {
794
+ console.log("✅ mediumZoom found, initializing Mermaid zoom");
795
+ setupMermaidZoom();
796
+ } else {
797
+ console.log("⏳ Waiting for mediumZoom...");
798
+ setTimeout(checkMediumZoom, 100);
799
+ }
800
+ };
801
+
802
+ checkMediumZoom();
803
+ }
804
+
805
+ // Export des fonctions pour utilisation globale
806
+ window.MermaidZoom = {
807
+ init: initMermaidZoom,
808
+ setup: setupMermaidZoom,
809
+ cleanup: cleanupMermaidObservers,
810
+ convertSvgToImage: convertSvgToImageForMediumZoom,
811
+ openZoom: openMermaidZoom,
812
+ closeZoom: closeMermaidZoom
813
+ };
814
+
815
+ // Auto-initialisation si le DOM est déjà chargé
816
+ if (document.readyState === "loading") {
817
+ document.addEventListener("DOMContentLoaded", () => {
818
+ initMermaidZoom();
819
+ setupGlobalMermaidColorFix();
820
+ });
821
+ } else {
822
  initMermaidZoom();
823
  setupGlobalMermaidColorFix();
824
+ }
 
 
 
 
 
 
 
 
 
 
 
825
 
826
+ // Aussi après le chargement complet
827
+ window.addEventListener("load", () => {
828
+ setTimeout(() => {
829
  initMermaidZoom();
830
+ setupGlobalMermaidColorFix();
831
+ }, 1000);
832
+ });
833
 
834
+ // Observer simple pour les nouveaux diagrammes Mermaid (avec debounce)
835
+ let resizeTimeout;
836
+ const observer = new MutationObserver(() => {
837
+ // Debounce pour éviter les appels multiples
838
+ clearTimeout(resizeTimeout);
839
+ resizeTimeout = setTimeout(() => {
840
+ // Vérifier s'il y a vraiment de nouveaux diagrammes
841
+ const mermaidElements = document.querySelectorAll(".mermaid");
842
+ const wrappedElements = document.querySelectorAll(".mermaid-zoom-wrapper");
843
+
844
+ // Seulement si il y a plus de diagrammes que de wrappers
845
+ if (mermaidElements.length > wrappedElements.length) {
846
+ console.log(`🔄 New Mermaid diagrams detected: ${mermaidElements.length} total, ${wrappedElements.length} wrapped`);
847
+ initMermaidZoom();
848
+ }
849
+ }, 500); // Délai plus long pour éviter les appels fréquents
850
+ });
851
+
852
+ observer.observe(document.body, {
853
+ childList: true,
854
+ subtree: true,
855
+ });
856
 
857
+ console.log("🚀 Mermaid Zoom Script v19.0 loaded - DEBOUNCED observer to prevent resize loops");
858
+ } // Fin du bloc else (MERMAID_ZOOM_ENABLED)