File size: 8,289 Bytes
6afedde
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
---

---

<div class="sidenote-container">
  <aside class="sidenote">
    <slot />
  </aside>
</div>

<script>
  const initSidenotes = () => {
    const containers = document.querySelectorAll(".sidenote-container");

    containers.forEach((container) => {
      // Vérifier si on est déjà dans un wrapper (éviter les doublons)
      if (container.closest(".sidenote-wrapper")) {
        return; // Déjà traité
      }

      // Trouver l'élément précédent (sibling juste avant)
      let previousElement =
        container.previousElementSibling as HTMLElement | null;

      // Si pas d'élément précédent direct, essayer de trouver via le parent
      if (!previousElement && container.parentNode) {
        const parent = container.parentNode as HTMLElement;
        // Si le parent n'a qu'un seul enfant (le container), chercher le sibling du parent
        if (parent.children.length === 1 && parent.previousElementSibling) {
          previousElement = parent.previousElementSibling as HTMLElement;
        }
      }

      if (
        previousElement &&
        previousElement.parentNode &&
        container.parentNode
      ) {
        // Créer un wrapper div qui contiendra l'élément précédent et le sidenote
        const wrapper = document.createElement("div");
        wrapper.className = "sidenote-wrapper";

        const parent = container.parentNode;

        // Cas normal : même parent
        if (previousElement.parentNode === parent) {
          parent.insertBefore(wrapper, previousElement);
          wrapper.appendChild(previousElement);
          wrapper.appendChild(container);
        } else {
          // Cas où ils sont dans des parents différents
          const prevParent = previousElement.parentNode;
          prevParent.insertBefore(wrapper, previousElement);
          wrapper.appendChild(previousElement);
          parent.removeChild(container);
          wrapper.appendChild(container);
        }

        // Style le wrapper
        wrapper.style.position = "relative";
        wrapper.style.display = "block";

        // Style le sidenote container
        const sidenoteContainer = container as HTMLElement;
        sidenoteContainer.style.position = "absolute";
        sidenoteContainer.style.top = "0";
        sidenoteContainer.style.right = "-292px"; // 260px width + 32px gap
        sidenoteContainer.style.width = "260px";

        // Afficher le container avec un fade-in
        sidenoteContainer.style.display = "block";
        sidenoteContainer.style.opacity = "0";

        // Fade-in avec requestAnimationFrame (plus fiable que setTimeout)
        requestAnimationFrame(() => {
          requestAnimationFrame(() => {
            sidenoteContainer.style.opacity = "1";
          });
        });
      } else {
        // Fallback : si pas d'élément précédent, afficher juste le sidenote normalement
        const sidenoteContainer = container as HTMLElement;
        sidenoteContainer.style.display = "block";
        sidenoteContainer.style.opacity = "1";
      }
    });
  };

  // Vérifier que les styles sont bien appliqués
  const verifyStylesApplied = () => {
    const wrappers = document.querySelectorAll(".sidenote-wrapper");
    let allApplied = true;

    wrappers.forEach((wrapper) => {
      const sidenote = wrapper.querySelector(
        ".sidenote-container",
      ) as HTMLElement;
      if (sidenote) {
        const computed = window.getComputedStyle(sidenote);
        // Vérifier que position absolute est bien appliquée
        if (computed.position !== "absolute" && window.innerWidth > 768) {
          allApplied = false;
          // Réappliquer les styles si nécessaire
          sidenote.style.position = "absolute";
          sidenote.style.top = "0";
          sidenote.style.right = "-292px";
          sidenote.style.width = "260px";
          sidenote.style.display = "block";
        }
      }
    });

    return allApplied;
  };

  // Fonction pour démarrer l'initialisation avec observer si nécessaire
  const startInit = () => {
    // Essayer immédiatement
    initSidenotes();

    // Vérifier et réappliquer les styles si nécessaire
    setTimeout(() => {
      verifyStylesApplied();
    }, 50);

    // Si on ne trouve pas de sidenotes, utiliser MutationObserver pour attendre
    // que le contenu MDX soit rendu (important pour les Spaces Hugging Face)
    const containers = document.querySelectorAll(".sidenote-container");

    if (containers.length === 0) {
      // Aucun sidenote trouvé, observer les changements du DOM
      const observer = new MutationObserver((mutations, obs) => {
        const newContainers = document.querySelectorAll(".sidenote-container");
        if (newContainers.length > 0) {
          // Des sidenotes sont apparus, initialiser
          initSidenotes();
          // Vérifier les styles après initialisation
          setTimeout(() => {
            verifyStylesApplied();
          }, 50);
          // Arrêter l'observation après un délai pour éviter les boucles infinies
          setTimeout(() => {
            obs.disconnect();
          }, 5000);
        }
      });

      // Observer les changements dans le body (ou main si disponible)
      const targetNode = document.querySelector("main") || document.body;
      if (targetNode) {
        observer.observe(targetNode, {
          childList: true,
          subtree: true,
        });
      }

      // Arrêter l'observation après 10 secondes max (sécurité)
      setTimeout(() => {
        observer.disconnect();
      }, 10000);
    }
  };

  // Attendre que toutes les ressources soient chargées (images, etc.)
  const waitForFullLoad = () => {
    // Helper pour utiliser requestIdleCallback si disponible
    const idleOrTimeout = (callback: () => void, timeout: number) => {
      if (typeof (window as any).requestIdleCallback === "function") {
        (window as any).requestIdleCallback(callback, { timeout });
      } else {
        setTimeout(callback, timeout);
      }
    };

    if (document.readyState === "loading") {
      // Attendre DOMContentLoaded puis window.load
      document.addEventListener(
        "DOMContentLoaded",
        () => {
          idleOrTimeout(startInit, 300);
        },
        { once: true },
      );

      // Aussi attendre window.load pour les ressources
      window.addEventListener(
        "load",
        () => {
          setTimeout(() => {
            initSidenotes();
            verifyStylesApplied();
          }, 100);
        },
        { once: true },
      );
    } else if (document.readyState === "interactive") {
      // DOM est chargé mais pas toutes les ressources
      idleOrTimeout(startInit, 300);

      // Aussi attendre window.load
      window.addEventListener(
        "load",
        () => {
          setTimeout(() => {
            initSidenotes();
            verifyStylesApplied();
          }, 100);
        },
        { once: true },
      );
    } else {
      // Tout est déjà chargé
      idleOrTimeout(startInit, 300);

      // Vérifier une dernière fois après un délai pour s'assurer que tout est bien rendu
      setTimeout(() => {
        initSidenotes();
        verifyStylesApplied();
      }, 500);
    }
  };

  waitForFullLoad();
</script>

<style is:global>
  .sidenote-wrapper {
    /* Le wrapper contient l'élément original et le sidenote */
    position: relative;
    display: block;
  }

  .sidenote-container {
    /* Caché par défaut, sera affiché par JS */
    display: none;
    margin: 0;
    /* Transition for fade-in */
    transition: opacity 0.3s ease-in-out;
  }

  .sidenote {
    border-radius: 8px;
    padding: 0 30px;
    font-size: 0.9rem;
    color: var(--muted-color);
    margin: 0;
  }

  @media (--bp-content-collapse) {
    .sidenote-wrapper {
      /* Sur mobile, le wrapper n'a pas besoin de position relative */
      position: static !important;
    }

    .sidenote-container {
      position: static !important;
      width: auto !important;
      right: auto !important;
      top: auto !important;
      margin-top: 8px;
      /* Affichage normal sur mobile */
      display: block !important;
      opacity: 1 !important;
    }

    .sidenote {
      padding: 0;
    }
  }
</style>