OXYGENHUB
# c40c2d3edb51 oxygenhub / assets / hub.js
6.0 KiB · JavaScript · last changed Theme switch, auto theme, and a highlighting fix 1 month ago source blame history raw
1
/* Progressive enhancement only: every page works without this file. */
2
(function () {
3
  "use strict";
4
  /* The band's active tab: the longest link that prefixes the path. */
5
  var p = location.pathname, best = null;
6
  document.querySelectorAll(".band a").forEach(function (a) {
7
    var h = a.getAttribute("href");
8
    if (h !== "/" && (p === h || p.indexOf(h + "/") === 0) && (!best || h.length > best.getAttribute("href").length)) best = a;
9
  });
10
  if (best) best.classList.add("on");
11
12
  /* Off-the-shelf editors, loaded only where a page asks for them:
13
     CodeMirror 6 over textarea.code, EasyMDE over textarea.md. */
14
  function load(src, css) {
15
    return new Promise(function (ok) {
16
      if (css) { var l = document.createElement("link"); l.rel = "stylesheet"; l.href = css; document.head.appendChild(l); }
17
      var s = document.createElement("script"); s.src = src; s.onload = ok; document.head.appendChild(s);
18
    });
19
  }
20
  if (document.querySelector("textarea.code")) load("/static/vendor/editor.js");
21
  var mds = document.querySelectorAll("textarea.md");
22
  if (mds.length) load("/static/vendor/easymde.min.js", "/static/vendor/easymde.min.css").then(function () {
23
    var E = window.EasyMDE;
24
    function b(name, action, text, title) { return { name: name, action: action, text: text, title: title, className: "mde-btn" }; }
25
    var toolbar = [b("bold", E.toggleBold, "B", "Bold"), b("italic", E.toggleItalic, "I", "Italic"), b("heading", E.toggleHeadingSmaller, "H", "Heading"), "|",
26
      b("code", E.toggleCodeBlock, "</>", "Code"), b("quote", E.toggleBlockquote, "“", "Quote"), b("ul", E.toggleUnorderedList, "•", "List"), b("ol", E.toggleOrderedList, "1.", "Numbered list"), "|",
27
      b("link", E.drawLink, "link", "Link"), "|", b("preview", E.togglePreview, "preview", "Preview")];
28
    mds.forEach(function (t) { new E({ element: t, spellChecker: false, status: false, forceSync: true, autoDownloadFontAwesome: false, toolbar: toolbar }); });
29
  });
30
31
  /* Theme switch in the header: dark, light, auto (follows the system); kept in the theme cookie. */
32
  var toggle = document.querySelector("[data-theme-toggle]");
33
  if (toggle) {
34
    var root = document.documentElement, order = ["dark", "light", "auto"], glyph = { dark: "◐", light: "☀", auto: "◑" };
35
    var show = function () {
36
      var p = root.dataset.themePref || "dark";
37
      toggle.title = "Theme: " + p + " (click to change)";
38
      toggle.setAttribute("aria-label", toggle.title);
39
      toggle.firstElementChild.textContent = glyph[p];
40
    };
41
    show();
42
    toggle.addEventListener("click", function () {
43
      var p = order[(order.indexOf(root.dataset.themePref || "dark") + 1) % order.length];
44
      document.cookie = "theme=" + p + "; path=/; max-age=31536000; samesite=lax";
45
      root.dataset.themePref = p;
46
      root.dataset.theme = p === "auto" ? (window.matchMedia && matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
47
      show();
48
    });
49
  }
50
51
  /* Copy buttons. */
52
  document.querySelectorAll("[data-copy]").forEach(function (b) {
53
    b.addEventListener("click", function () {
54
      navigator.clipboard.writeText(b.getAttribute("data-copy")).then(function () {
55
        var t = b.textContent; b.textContent = "copied"; setTimeout(function () { b.textContent = t; }, 1200);
56
      });
57
    });
58
  });
59
60
  /* Tree rows: the last change per entry, fetched after the page paints. */
61
  var tree = document.querySelector("table.tree[data-last]");
62
  if (tree) {
63
    var paths = [].map.call(tree.querySelectorAll("tr[data-path]"), function (r) { return r.getAttribute("data-path"); });
64
    if (paths.length) {
65
      fetch(tree.getAttribute("data-last") + "?paths=" + encodeURIComponent(paths.join(",")), { credentials: "same-origin" })
66
        .then(function (r) { return r.json(); })
67
        .then(function (rows) {
68
          rows.forEach(function (c) {
69
            var tr = tree.querySelector('tr[data-path="' + CSS.escape(c.path) + '"]');
70
            if (!tr) return;
71
            var a = tr.querySelector("td.last a"), t = tr.querySelector("td.age");
72
            if (a) { a.textContent = c.title; a.href = tree.getAttribute("data-change") + c.hash; }
73
            if (t) { t.textContent = c.ago; t.title = c.when; }
74
          });
75
        }).catch(function () {});
76
    }
77
  }
78
79
  /* Line anchors: #L12 or #L12-L20 highlight a range and clicking a number selects. */
80
  function mark() {
81
    var m = location.hash.match(/^#L(\d+)(?:-L(\d+))?$/);
82
    document.querySelectorAll("tr.hl").forEach(function (r) { r.classList.remove("hl"); });
83
    if (!m) return;
84
    var a = +m[1], b = +(m[2] || m[1]);
85
    for (var i = Math.min(a, b); i <= Math.max(a, b); i++) {
86
      var row = document.getElementById("L" + i);
87
      if (row) row.classList.add("hl");
88
    }
89
    var first = document.getElementById("L" + Math.min(a, b));
90
    if (first && !mark.done) { first.scrollIntoView({ block: "center" }); mark.done = true; }
91
  }
92
  window.addEventListener("hashchange", mark);
93
  mark();
94
  document.querySelectorAll("table.code td.n").forEach(function (td) {
95
    td.addEventListener("click", function (e) {
96
      var n = td.parentNode.id;
97
      if (e.shiftKey && location.hash.match(/^#L(\d+)/)) location.hash = location.hash.replace(/^#(L\d+).*/, "$1-" + n);
98
      else location.hash = n;
99
    });
100
  });
101
102
  /* Keyboard: / focuses search; g c / g p / g i jump; ? shows the sheet. */
103
  var pending = null;
104
  document.addEventListener("keydown", function (e) {
105
    if (e.target && /^(INPUT|TEXTAREA|SELECT)$/.test(e.target.tagName)) return;
106
    if (e.key === "/") { var s = document.querySelector("input[name=q]"); if (s) { e.preventDefault(); s.focus(); } return; }
107
    if (e.key === "?") { var k = document.getElementById("keys"); if (k) k.hidden = !k.hidden; return; }
108
    var repo = document.body.getAttribute("data-repo");
109
    if (pending === "g" && repo) {
110
      var to = { c: "", p: "/proposals", i: "/issues", h: "/changes/" + (document.body.getAttribute("data-ref") || "main"), b: "/bookmarks", t: "/trust" }[e.key];
111
      if (to !== undefined) location.href = repo + to;
112
      pending = null; return;
113
    }
114
    pending = e.key === "g" ? "g" : null;
115
  });
116
})();