/* cgit.js: javacript functions for cgit * * Copyright (C) 2006-2018 cgit Development Team * * Licensed under GNU General Public License v2 * (see LICENSE.txt for full license text) */ (function () { /* This follows the logic and suffixes used in ui-shared.c */ var age_classes = [ "age-mins", "age-hours", "age-days", "age-weeks", "age-months", "age-years" ]; var age_suffix = [ "min.", "hours", "days", "weeks", "months", "years", "years" ]; var age_next = [ 60, 3600, 24 * 3600, 7 * 24 * 3600, 30 * 24 * 3600, 365 * 24 * 3600, 365 * 24 * 3600 ]; var age_limit = [ 7200, 24 * 7200, 7 * 24 * 7200, 30 * 24 * 7200, 365 * 25 * 7200, 365 * 25 * 7200 ]; var update_next = [ 10, 5 * 60, 1800, 24 * 3600, 24 * 3600, 24 * 3600, 24 * 3600 ]; function render_age(e, age) { var t, n; for (n = 0; n < age_classes.length; n++) if (age < age_limit[n]) break; t = Math.round(age / age_next[n]) + " " + age_suffix[n]; if (e.textContent != t) { e.textContent = t; if (n == age_classes.length) n--; if (e.className != age_classes[n]) e.className = age_classes[n]; } } function aging() { var n, next = 24 * 3600, now_ut = Math.round((new Date().getTime() / 1000)); for (n = 0; n < age_classes.length; n++) { var m, elems = document.getElementsByClassName(age_classes[n]); if (elems.length && update_next[n] < next) next = update_next[n]; for (m = 0; m < elems.length; m++) { var age = now_ut - elems[m].getAttribute("data-ut"); /* A commit dated ahead of the viewer's clock would * otherwise render as a negative age; ui-shared.c * clamps the same way. */ if (age < 0) age = 0; render_age(elems[m], age); } } /* * We only need to come back when the age might have changed. * Eg, if everything is counted in hours already, once per * 5 minutes is accurate enough. */ window.setTimeout(aging, next * 1000); } document.addEventListener("DOMContentLoaded", function() { /* we can do the aging on DOM content load since no layout dependency */ aging(); }, false); })(); /* Selects marked data-autosubmit reload the page with the new setting. * Wired here instead of inline onchange handlers, which a strict * Content-Security-Policy blocks. Without scripting the forms keep * their noscript reload button. */ (function () { document.addEventListener("DOMContentLoaded", function () { var i, els = document.querySelectorAll("select[data-autosubmit]"); for (i = 0; i < els.length; i++) els[i].addEventListener("change", function () { this.form.submit(); }); }, false); })(); /* Wash a faded highlight over the source line targeted by the URL * fragment, #n231, or over a range like #n5-n12. The line anchors live * in the number gutter, so this measures the anchor and lays a * full-width bar across the code at the same height. Without JS the * CSS :target rule still tints the line number itself. */ (function () { var bar = null; function place() { var m, a, b, table, box, ra, rb, top, bottom; if (bar) { bar.remove(); bar = null; } m = /^#n(\d+)(?:-n?(\d+))?$/.exec(location.hash); if (!m) return; a = document.getElementById("n" + m[1]); if (!a) return; b = (m[2] && document.getElementById("n" + m[2])) || a; table = a.closest("table"); if (!table) return; box = table.getBoundingClientRect(); ra = a.getBoundingClientRect(); rb = b.getBoundingClientRect(); top = Math.min(ra.top, rb.top) - box.top; bottom = Math.max(ra.bottom, rb.bottom) - box.top; bar = document.createElement("div"); bar.className = "line-hl"; bar.style.top = top + "px"; bar.style.height = (bottom - top) + "px"; table.appendChild(bar); /* The browser only scrolls to fragments that name a real id, so * bring ranges into view ourselves. */ if (m[2]) a.scrollIntoView({ block: "center" }); } document.addEventListener("DOMContentLoaded", place, false); window.addEventListener("hashchange", place, false); })(); /* Colour theme toggle: cycles auto -> light -> dark and remembers the * choice. "auto" leaves the page following the system preference via CSS. */ (function () { var KEY = "cgit-theme"; var ORDER = [ "auto", "light", "dark" ]; var ICONS = { auto: '', light: '', dark: '' }; function saved() { try { return localStorage.getItem(KEY); } catch (e) { return null; } } function persist(value) { try { localStorage.setItem(KEY, value); } catch (e) { } } function apply(theme, btn) { var html = document.documentElement; if (theme === "auto") html.removeAttribute("data-theme"); else html.setAttribute("data-theme", theme); /* Keep the standalone page background in sync with the choice. */ html.style.colorScheme = (theme === "auto") ? "" : theme; if (btn) { btn.innerHTML = ICONS[theme]; btn.title = "Colour theme: " + theme; } } var theme = saved(); if (ORDER.indexOf(theme) < 0) theme = "auto"; /* Settled here rather than on DOMContentLoaded. This file is fetched from * and runs before the body is parsed, so the choice lands on * ahead of the first paint and the page never shows the other theme first. * The marker sits on for the same reason, since div#cgit does not * exist yet. cgit-js reserves the toggle's column so revealing the button * below does not shift the header. */ apply(theme, null); document.documentElement.classList.add("cgit-js"); document.addEventListener("DOMContentLoaded", function () { var root = document.getElementById("cgit"); if (!root) return; var btn = root.querySelector(".theme-toggle"); if (!btn) return; apply(theme, btn); btn.hidden = false; btn.addEventListener("click", function () { theme = ORDER[(ORDER.indexOf(theme) + 1) % ORDER.length]; persist(theme); apply(theme, btn); }); }, false); })();