blob: d89fdf7763320b549527e56aa211e73b87bdd549 [file] [log] [blame]
/* cgit.css: javacript functions for cgit
*
* Copyright (C) 2006-2018 cgit Development Team <cgit@lists.zx2c4.com>
*
* Licensed under GNU General Public License v2
* (see COPYING for full license text)
*/
(function () {
var burger, menu_popup;
function collect_offsetTop(e1)
{
var t = 0;
while (e1) {
if (e1.offsetTop)
t += e1.offsetTop;
e1 = e1.offsetParent;
}
return t;
}
function find_parent_of_type(e, type)
{
while (e.tagName.toLowerCase() != type)
e = e.parentNode;
return e;
}
function parse_hashurl_start(h)
{
return parseInt(h.substring(2));
}
function parse_hashurl_end(h, start)
{
var t = h.indexOf("-"), e = start;
if (t >= 1)
e = parseInt(h.substring(t + 1));
if (e < start)
e = start;
return e;
}
/*
* This creates an absolute div as a child of the content table.
* It's horizontally and vertically aligned and sized according
* to the #URL information like #n123-456
*
* If the highlight div already exists, it's removed and remade.
*/
function line_range_highlight(do_burger)
{
var h = window.location.hash, l1 = 0, l2 = 0, e, t;
e = document.getElementById("cgit-line-range");
if (e) {
l1 = e.l1;
while (l1 <= e.l2) {
var e1;
e1 = document.getElementById('n' + l1++);
e1.classList.remove(
'selected-line-link-highlight');
}
e.remove();
}
l1 = parse_hashurl_start(h);
if (!l1)
return;
l2 = parse_hashurl_end(h, l1);
var lh, etable, etr, de, n, hl, v;
e = document.getElementById('n' + l1);
if (!e)
return;
if (do_burger)
burger_create(e);
de = document.createElement("DIV");
de.className = "selected-lines";
de.style.bottom = e.style.bottom;
de.style.top = collect_offsetTop(e) + 'px';
de.id = "cgit-line-range";
de.l1 = l1;
de.l2 = l2;
/* we will tack the highlight div at the parent tr */
etr = find_parent_of_type(e, "tr");
de.style.width = etr.offsetWidth + 'px';
/* the table is offset from the left, the highlight needs to follow it */
etable = find_parent_of_type(etr, "table");
de.style.left = etable.offsetLeft + 'px';
de.style.height = ((l2 - l1 + 1) * e.offsetHeight) + 'px';
etr.insertBefore(de, etr.firstChild);
setTimeout(function() {
de.style.backgroundColor = "rgba(255, 255, 0, 0.2)";
}, 1);
n = l1;
while (n <= l2)
document.getElementById('n' + n++).classList.add(
'selected-line-link-highlight');
hl = (window.innerHeight / (e.offsetHeight + 1));
v = (l1 + ((l2 - l1) / 2)) - (hl / 2);
if (v > l1)
v = l1;
if (v < 1)
v = 1;
t = document.getElementById('n' + Math.round(v));
if (!t)
t = e;
t.scrollIntoView(true);
}
function copy_clipboard(value)
{
var inp = document.createElement("textarea");
var e = document.getElementById("linenumbers");
inp.type = "text";
inp.value = value;
/* hidden style stops it working for clipboard */
inp.setAttribute('readonly', '');
inp.style.position = "absolute";
inp.style.left = "-1000px";
e.appendChild(inp);
inp.select();
document.execCommand("copy");
inp.remove();
}
/* we want to copy plain text for a line range */
function copy_text(elem, l1, l2)
{
var tc = elem.textContent.split('\n'), s = "";
l1 --;
while (l1 < l2)
s += tc[l1++] + '\n';
copy_clipboard(s);
}
/*
* An element in the popup menu was clicked, perform the appropriate action
*/
function mi_click(e) {
var u, n, l1, l2, el;
e.stopPropagation();
e.preventDefault();
switch (e.target.id) {
case "mi-c-line":
l1 = parse_hashurl_start(window.location.hash);
l2 = parse_hashurl_end(window.location.hash, l1);
el = document.getElementsByClassName("highlight")[0].firstChild;
copy_text(el, l1, l2);
break;
case "mi-c-link":
copy_clipboard(window.location.href);
break;
case "mi-c-blame":
u = window.location.href;
t = u.indexOf("/tree/");
if (t)
window.location = u.substring(0, t) + "/blame/" +
u.substring(t + 6);
break;
case "mi-c-tree":
u = window.location.href;
t = u.indexOf("/blame/");
if (t)
window.location = u.substring(0, t) + "/tree/" +
u.substring(t + 7);
break;
}
if (!menu_popup)
return;
menu_popup.remove();
menu_popup = null;
}
/* We got a click on the (***) burger menu */
function burger_click(e) {
var e1 = e, etable, d = new Date, s = "", n, is_blame,
ar = new Array("mi-c-line", "mi-c-link", "mi-c-blame", "mi-c-tree"),
an = new Array("Copy Lines", "Copy Link",
"View Blame", /* 2: shown in /tree/ */
"Remove Blame" /* 3: shown in /blame/ */);
e.preventDefault();
if (menu_popup) {
menu_popup.remove();
menu_popup = null;
return;
}
/*
* Create the popup menu
*/
is_blame = !!document.getElementsByClassName("hashes").length;
menu_popup = document.createElement("DIV");
menu_popup.className = "popup-menu";
menu_popup.style.top = collect_offsetTop(e1) + e.offsetHeight + "px";
s = "<ul id='menu-ul'>";
for (n = 0; n < an.length; n++)
if (n < 2 || is_blame == (n == 3))
s += "<li id='" + ar[n] + "' tabindex='" + n + "'>" +
an[n] + "</li>";
menu_popup.innerHTML = s;
burger.insertBefore(menu_popup, null);
document.getElementById(ar[0]).focus();
for (n = 0; n < an.length; n++)
if (n < 2 || is_blame == (n == 3))
document.getElementById(ar[n]).
addEventListener("click", mi_click);
setTimeout(function() {
menu_popup.style.opacity = "1";
}, 1);
/* detect loss of focus for popup menu */
menu_popup.addEventListener("focusout", function(e) {
/* if focus went to a child (menu item), ignore */
if (e.relatedTarget &&
e.relatedTarget.parentNode.id == "menu-ul")
return;
menu_popup.remove();
menu_popup = null;
});
}
function burger_create(e)
{
var e1 = e, etable, d = new Date;
if (burger)
burger.remove();
burger = document.createElement("DIV");
burger.className = "selected-lines-popup";
burger.style.top = collect_offsetTop(e1) + "px";
/* event listener cannot override default browser #URL behaviour */
burger.onclick = burger_click;
etable = find_parent_of_type(e, "table");
etable.insertBefore(burger, etable.firstChild);
burger_time = d.getTime();
setTimeout(function() {
burger.style.opacity = "1";
}, 1);
}
/*
* We got a click on a line number #url
*
* Create the "burger" menu there.
*
* Redraw the line range highlight accordingly.
*/
function line_range_click(e) {
var t, elem, m, n = window.location.href.length -
window.location.hash.length;
/* disable passthru to stop scrolling by browser #URL handler */
e.stopPropagation();
e.preventDefault();
if (!e.target.id)
return;
if (menu_popup) {
menu_popup.remove();
menu_popup = null;
return;
}
elem = document.getElementById(e.target.id);
if (!elem)
return;
burger_create(elem);
if (!window.location.hash ||
window.location.hash.indexOf("-") >= 0 ||
e.target.id.substring(1) == window.location.href.substring(n + 2))
t = window.location.href.substring(0, n) +
'#n' + e.target.id.substring(1);
else {
if (parseInt(window.location.hash.substring(2)) <
parseInt(e.target.id.substring(1))) /* forwards */
t = window.location + '-' + e.target.id.substring(1);
else
t = window.location.href.substring(0, n) +
'#n' + e.target.id.substring(1) + '-' +
window.location.href.substring(n + 2);
}
window.history.replaceState(null, null, t);
line_range_highlight(0);
}
/* 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("ut");
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);
}
/* we have to use load, because header images can push the layout vertically */
window.addEventListener("load", function() {
line_range_highlight(1);
}, false);
document.addEventListener("DOMContentLoaded", function() {
/* event listener cannot override default #URL browser processing,
* requires onclick */
var e = document.getElementById("linenumbers");
if (e)
e.onclick = line_range_click;
/* we can do the aging on DOM content load since no layout dependency */
aging();
}, false);
window.addEventListener("hashchange", function() {
line_range_highlight(1);
}, false);
})();