1(function () { 2 "use strict"; 3 4 const cache = new Map(); 5 6 function translations() { 7 const french = document.documentElement.lang.toLowerCase().startsWith("fr"); 8 return french 9 ? { 10 close: "Fermer", 11 loading: "Chargement du journal des modificationsâ¦", 12 error: "Impossible de charger le journal des modifications. Veuillez réessayer plus tard." 13 } 14 : { 15 close: "Close", 16 loading: "Loading changelogâ¦", 17 error: "Unable to load the changelog. Please try again later." 18 }; 19 } 20 21 function createModal(copy) { 22 const wrapper = document.createElement("div"); 23 wrapper.innerHTML = ` 24 <div class="modal fade changelog-modal" id="changelogModal" tabindex="-1" aria-labelledby="changelogModalTitle" aria-hidden="true"> 25 <div class="modal-dialog modal-lg modal-dialog-scrollable"> 26 <div class="modal-content"> 27 <div class="modal-header"> 28 <h2 class="modal-title h5" id="changelogModalTitle"></h2> 29 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${copy.close}"></button> 30 </div> 31 <div class="modal-body" id="changelogModalBody"></div> 32 <div class="modal-footer"> 33 <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">${copy.close}</button> 34 </div> 35 </div> 36 </div> 37 </div>`; 38 const modal = wrapper.firstElementChild; 39 document.body.appendChild(modal); 40 return modal; 41 } 42 43 function loadChangelog(url) { 44 if (!cache.has(url)) { 45 const request = fetch(url, { 46 credentials: "same-origin", 47 redirect: "error" 48 }) 49 .then(function (response) { 50 const contentType = response.headers.get("content-type") || ""; 51 if (!response.ok) { 52 throw new Error(`Changelog request failed with status ${response.status}`); 53 } 54 if (response.redirected || !contentType.toLowerCase().startsWith("text/html")) { 55 throw new Error("Changelog response is redirected or is not HTML"); 56 } 57 return response.text(); 58 }) 59 .then(function (content) { 60 const template = document.createElement("template"); 61 template.innerHTML = content.trim(); 62 const fragment = template.content.firstElementChild; 63 if ( 64 template.content.children.length !== 1 || 65 !fragment || 66 !fragment.matches("article.changelog-content") 67 ) { 68 throw new Error("Changelog response is not a valid fragment"); 69 } 70 return content; 71 }) 72 .catch(function (error) { 73 cache.delete(url); 74 throw error; 75 }); 76 cache.set(url, request); 77 } 78 return cache.get(url); 79 } 80 81 document.addEventListener("DOMContentLoaded", function () { 82 const triggers = document.querySelectorAll("[data-changelog-src]"); 83 if (!triggers.length || typeof bootstrap === "undefined") { 84 return; 85 } 86 87 const copy = translations(); 88 const modalElement = createModal(copy); 89 const modal = bootstrap.Modal.getOrCreateInstance(modalElement); 90 const title = modalElement.querySelector("#changelogModalTitle"); 91 const body = modalElement.querySelector("#changelogModalBody"); 92 let activeTrigger = null; 93 94 function showChangelog(trigger, restoreFocus) { 95 activeTrigger = restoreFocus ? trigger : null; 96 title.textContent = trigger.dataset.changelogTitle; 97 body.innerHTML = `<p class="changelog-loading" role="status">${copy.loading}</p>`; 98 modal.show(trigger); 99 100 loadChangelog(trigger.dataset.changelogSrc) 101 .then(function (content) { 102 body.innerHTML = content; 103 }) 104 .catch(function () { 105 body.innerHTML = `<div class="alert alert-danger" role="alert">${copy.error}</div>`; 106 }); 107 } 108 109 modalElement.addEventListener("hidden.bs.modal", function () { 110 if (activeTrigger) { 111 activeTrigger.focus(); 112 activeTrigger = null; 113 } 114 }); 115
116 triggers.forEach(function (trigger) { 117 trigger.addEventListener("click", function () { 118 showChangelog(trigger, true); 119 }); 120 }); 121 122 const requestedChangelog = new URLSearchParams(window.location.search).get("changelog"); 123 const requestedTrigger = Array.from(triggers).find(function (trigger) { 124 return trigger.dataset.changelogKey === requestedChangelog; 125 }); 126 127 if (requestedTrigger) { 128 showChangelog(requestedTrigger, false); 129 } 130 }); 131})();
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.