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.