PageSourceSearch

https://convergencespp.com/wp-content/plugins/code-fixes/assets/js/cco-popups.js

js convergencespp.com collected 2026-10-04 13:30:16 UTC 12,424 bytes, 494 lines download raw bytes

1document.addEventListener("DOMContentLoaded", function () {
2  /**
3   * Durée du cookie "Afficher une seule fois".
4   *
5   * 1 = le popup pourra réapparaître après 24 heures.
6   *
7   * Si tu veux par exemple 30 jours :
8   * const COOKIE_DAYS = 30;
9   */
10  const COOKIE_DAYS = 1;
11
12  /**
13   * Crée / met à jour un cookie.
14   */
15  function setCookie(name, value, days) {
16    if (!name) {
17      return;
18    }
19
20    const expires = new Date();
21
22    expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000);
23
24    let cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + "; path=/" + "; expires=" + expires.toUTCString() + "; SameSite=Lax";
25
26    /*
27     * Secure uniquement si le site est en HTTPS.
28     */
29    if (window.location.protocol === "https:") {
30      cookie += "; Secure";
31    }
32
33    document.cookie = cookie;
34  }
35
36  /**
37   * Lit un cookie.
38   */
39  function getCookie(name) {
40    if (!name) {
41      return null;
42    }
43
44    const encodedName = encodeURIComponent(name) + "=";
45
46    const cookies = document.cookie ? document.cookie.split("; ") : [];
47
48    for (let i = 0; i < cookies.length; i++) {
49      if (cookies[i].indexOf(encodedName) === 0) {
50        return decodeURIComponent(cookies[i].substring(encodedName.length));
51      }
52    }
53
54    return null;
55  }
56
57  /**
58   * Vérifie qu'un sélecteur CSS est valide.
59   *
60   * querySelector() / closest() lèvent une exception
61   * JavaScript si on leur donne un sélecteur invalide.
62   */
63  function isValidSelector(selector) {
64    if (!selector) {
65      return false;
66    }
67
68    try {
69      document.querySelector(selector);
70      return true;
71    } catch (error) {
72      console.warn("CCO Popup: sélecteur CSS invalide :", selector);
73
74      return false;
75    }
76  }
77
78  /**
79   * Cherche un parent correspondant au sélecteur
80   * sans risquer d'interrompre tout le JS.
81   */
82  function safeClosest(target, selector) {
83    if (!selector || !(target instanceof Element)) {
84      return null;
85    }
86
87    try {
88      return target.closest(selector);
89    } catch (error) {
90      return null;
91    }
92  }
93
94  /**
95   * Initialise chaque popup présent dans la page.
96   */
97  document.querySelectorAll(".cco-modal-wrapper").forEach(function (wrapper) {
98    const html = wrapper.dataset.modalHtml || "";
99
100    if (!html) {
101      console.warn("CCO Popup: aucun HTML trouvé dans le wrapper.", wrapper);
102
103      return;
104    }
105
106    /*
107     * OPTIONS
108     */
109    const openOnLoad = wrapper.dataset.openOnLoad === "yes";
110
111    const triggerSel = (wrapper.dataset.triggerSelector || "").trim();
112
113    const closeTriggerSel = (wrapper.dataset.closeTriggerSelector || "").trim();
114
115    const closeOnOverlay = wrapper.dataset.closeOnOverlayClick === "yes";
116
117    const displayCloseBtn = wrapper.dataset.displayCloseButton === "yes";
118
119    /*
120     * On valide les sélecteurs une seule fois.
121     */
122    const triggerSelectorIsValid = triggerSel !== "" && isValidSelector(triggerSel);
123
124    const closeTriggerSelectorIsValid = closeTriggerSel !== "" && isValidSelector(closeTriggerSel);
125
126    /*
127     * CRÉATION DU MODAL
128     */
129    const container = document.createElement("div");
130
131    container.innerHTML = html.trim();
132
133    /*
134     * firstElementChild plutôt que firstChild :
135     * firstChild pourrait théoriquement être un nœud texte.
136     */
137    const modal = container.firstElementChild;
138
139    if (!modal) {
140      console.warn("CCO Popup: impossible de créer le modal.", wrapper);
141
142      return;
143    }
144
145    /*
146     * Injection dans le body.
147     */
148    document.body.insertAdjacentElement("afterbegin", modal);
149
150    wrapper.style.display = "none";
151
152    /*
153     * DATA DU MODAL
154     */
155    const cookieName = modal.dataset.cookieName || "";
156
157    const askOnce = modal.dataset.askOnlyOne === "1";
158
159    const cancelUrl = modal.dataset.cancelUrl || "";
160
161    /*
162     * Éléments internes.
163     */
164    const closeBtn = modal.querySelector(".cco-modal-close");
165
166    const okBtn = modal.querySelector(".cco-modal-ok-button");
167
168    const cancelBtn = modal.querySelector(".cco-modal-cancel-button");
169
170    const modalContent = modal.querySelector(".cco-modal-content");
171    const vimeoPlayers = new WeakMap();
172    /*
173     * Accessibilité basique.
174     */
175    modal.setAttribute("aria-hidden", "true");
176
177    modal.setAttribute("role", "dialog");
178
179    modal.setAttribute("aria-modal", "true");
180
181    if (modalContent) {
182      modalContent.setAttribute("tabindex", "-1");
183    }
184
185    /*
186     * Mémorise l'élément qui avait le focus
187     * avant l'ouverture du popup.
188     */
189    let previouslyFocusedElement = null;
190
191    /**
192     * Met en pause les vidéos.
193     */
194    function pauseIframes() {
195      modal.querySelectorAll("iframe").forEach(function (iframe) {
196        const src = iframe.getAttribute("src") || "";
197
198        const normalizedSrc = src.toLowerCase();
199
200        /*
201         * YouTube classique + youtube-nocookie.
202         */
203        const isYouTube = normalizedSrc.includes("youtube.com") || normalizedSrc.includes("youtu.be") || normalizedSrc.includes("youtube-nocookie.com");
204
205        const isVimeo = normalizedSrc.includes("vimeo.com");
206
207        if (isYouTube) {
208          try {
209            if (iframe.contentWindow) {
210              const origin = new URL(iframe.src, window.location.href).origin;
211
212              iframe.contentWindow.postMessage(
213                JSON.stringify({
214                  event: "command",
215                  func: "pauseVideo",
216                  args: [],
217                }),
218                origin,
219              );
220            }
221          } catch (error) {
222            console.warn("CCO Popup: impossible de mettre YouTube en pause.", error);
223          }
224
225          return;
226        }
227
228        if (isVimeo) {
229          try {
230            if (window.Vimeo && typeof window.Vimeo.Player === "function") {
231              let player = vimeoPlayers.get(iframe);
232
233              if (!player) {
234                player = new window.Vimeo.Player(iframe);
235
236                vimeoPlayers.set(iframe, player);
237              }
238
239              player.pause().catch(function (error) {
240                console.warn("CCO Popup: impossible de mettre Vimeo en pause.", error);
241              });
242            }
243          } catch (error) {
244            console.warn("CCO Popup: erreur Vimeo.", error);
245          }
246
247          return;
248        }
249      });
250    }
251
252    /**
253     * Ouvre le popup.
254     */
255    function openModal() {
256      /*
257       * Le popup a déjà été affiché.
258       */
259      if (askOnce && cookieName && getCookie(cookieName)) {
260        return false;
261      }
262
263      /*
264       * Évite une double ouverture.
265       */
266      if (modal.style.display === "flex") {
267        return true;
268      }
269
270      previouslyFocusedElement = document.activeElement;
271
272      modal.style.display = "flex";
273
274      modal.setAttribute("aria-hidden", "false");
275
276      /*
277       * "Afficher une seule fois" signifie :
278       * dès que le visiteur a réellement vu le modal,
279       * on mémorise l'affichage.
280       *
281       * L'ancien JS ne créait le cookie
282       * qu'à la fermeture.
283       */
284      if (askOnce && cookieName) {
285        setCookie(cookieName, "1", COOKIE_DAYS);
286      }
287
288      /*
289       * Focus clavier.
290       */
291      window.requestAnimationFrame(function () {
292        let focusTarget = null;
293
294        if (displayCloseBtn && closeBtn) {
295          focusTarget = closeBtn;
296        } else if (okBtn) {
297          focusTarget = okBtn;
298        } else if (cancelBtn) {
299          focusTarget = cancelBtn;
300        } else if (modalContent) {
301          focusTarget = modalContent;
302        }
303
304        if (focusTarget && typeof focusTarget.focus === "function") {
305          focusTarget.focus();
306        }
307      });
308
309      return true;
310    }
311
312    /**
313     * Ferme le popup.
314     */
315    function closeModal() {
316      if (modal.style.display === "none") {
317        return;
318      }
319
320      pauseIframes();
321
322      modal.style.display = "none";
323
324      modal.setAttribute("aria-hidden", "true");
325
326      /*
327       * Restitue le focus à l'élément
328       * qui avait ouvert le popup.
329       */
330      if (previouslyFocusedElement && typeof previouslyFocusedElement.focus === "function") {
331        try {
332          previouslyFocusedElement.focus();
333        } catch (error) {
334          // Rien à faire.
335        }
336      }
337
338      previouslyFocusedElement = null;
339    }
340
341    /*
342     * -------------------------------------------------
343     * CLIC SUR L'OVERLAY
344     * -------------------------------------------------
345     */
346    if (closeOnOverlay) {
347      modal.addEventListener("click", function (event) {
348        /*
349         * On ferme uniquement si le clic
350         * est directement sur l'overlay.
351         *
352         * Un clic dans .cco-modal-content
353         * ne ferme donc pas le popup.
354         */
355        if (event.target === modal) {
356          closeModal();
357        }
358      });
359    }
360
361    /*
362     * -------------------------------------------------
363     * BOUTON CROIX
364     * -------------------------------------------------
365     */
366    if (displayCloseBtn && closeBtn) {
367      closeBtn.addEventListener("click", function (event) {
368        event.preventDefault();
369
370        closeModal();
371      });
372    }
373
374    /*
375     * -------------------------------------------------
376     * BOUTON OK
377     * -------------------------------------------------
378     */
379    if (okBtn) {
380      okBtn.addEventListener("click", function (event) {
381        event.preventDefault();
382
383        closeModal();
384      });
385    }
386
387    /*
388     * -------------------------------------------------
389     * BOUTON / LIEN ANNULER
390     * -------------------------------------------------
391     */
392    if (cancelBtn) {
393      cancelBtn.addEventListener("click", function (event) {
394        event.preventDefault();
395
396        closeModal();
397
398        /*
399         * Une URL est définie :
400         * on redirige.
401         */
402        if (cancelUrl) {
403          window.location.href = cancelUrl;
404
405          return;
406        }
407
408        /*
409         * Pas d'URL :
410         * Annuler ferme simplement le popup.
411         *
412         * L'ancien code faisait :
413         *
414         * window.history.back();
415         *
416         * Ce comportement pouvait faire quitter
417         * complètement la page courante.
418         */
419      });
420    }
421
422    /*
423     * -------------------------------------------------
424     * DÉCLENCHEURS OUVERTURE / FERMETURE
425     * -------------------------------------------------
426     *
427     * On utilise la délégation d'événement.
428     *
429     * C'est préférable à :
430     *
431     * document.querySelectorAll(triggerSel)
432     *
433     * car WPBakery, AJAX ou un autre script
434     * peuvent ajouter les boutons après
435     * DOMContentLoaded.
436     */
437    document.addEventListener("click", function (event) {
438      /*
439       * Close trigger prioritaire.
440       */
441      if (closeTriggerSelectorIsValid) {
442        const closeTrigger = safeClosest(event.target, closeTriggerSel);
443
444        if (closeTrigger) {
445          event.preventDefault();
446
447          closeModal();
448
449          return;
450        }
451      }
452
453      /*
454       * Open trigger.
455       */
456      if (triggerSelectorIsValid) {
457        const trigger = safeClosest(event.target, triggerSel);
458
459        if (trigger) {
460          event.preventDefault();
461
462          openModal();
463        }
464      }
465    });
466
467    /*
468     * -------------------------------------------------
469     * TOUCHE ESC
470     * -------------------------------------------------
471     *
472     * Je considère ESC comme une action standard
473     * de fermeture d'un dialog.
474     */
475    document.addEventListener("keydown", function (event) {
476      if (event.key !== "Escape") {
477        return;
478      }
479
480      if (modal.style.display !== "none") {
481        closeModal();
482      }
483    });
484
485    /*
486     * -------------------------------------------------
487     * AUTO OPEN
488     * -------------------------------------------------
489     */
490    if (openOnLoad) {
491      openModal();
492    }
493  });
494});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.