PageSourceSearch

https://visiblair.com/js/changelog-modal.js

js visiblair.com collected 2026-10-10 00:34:47 UTC 4,602 bytes, 131 lines download raw bytes

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.