blob: d89fdf7763320b549527e56aa211e73b87bdd549 [file] [log] [blame]
Andy Greenfe549182018-06-21 16:56:36 +08001/* cgit.css: javacript functions for cgit
2 *
3 * Copyright (C) 2006-2018 cgit Development Team <cgit@lists.zx2c4.com>
4 *
5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text)
7 */
8
9(function () {
10
Andy Greene96af4a2018-06-24 10:31:13 +080011var burger, menu_popup;
12
Andy Greenfe549182018-06-21 16:56:36 +080013function collect_offsetTop(e1)
14{
15 var t = 0;
16
17 while (e1) {
18 if (e1.offsetTop)
19 t += e1.offsetTop;
20 e1 = e1.offsetParent;
21 }
22
23 return t;
24}
25
26function find_parent_of_type(e, type)
27{
28 while (e.tagName.toLowerCase() != type)
29 e = e.parentNode;
30
31 return e;
32}
33
Andy Greencbec7752018-06-29 09:05:46 +080034function parse_hashurl_start(h)
35{
36 return parseInt(h.substring(2));
37}
38
39function parse_hashurl_end(h, start)
40{
41 var t = h.indexOf("-"), e = start;
42
43 if (t >= 1)
44 e = parseInt(h.substring(t + 1));
45
46 if (e < start)
47 e = start;
48
49 return e;
50}
51
52
Andy Greene96af4a2018-06-24 10:31:13 +080053/*
54 * This creates an absolute div as a child of the content table.
55 * It's horizontally and vertically aligned and sized according
56 * to the #URL information like #n123-456
57 *
58 * If the highlight div already exists, it's removed and remade.
59 */
60
61function line_range_highlight(do_burger)
Andy Greenfe549182018-06-21 16:56:36 +080062{
63 var h = window.location.hash, l1 = 0, l2 = 0, e, t;
64
Andy Green1915feb2018-06-23 06:07:45 +080065 e = document.getElementById("cgit-line-range");
66 if (e) {
67 l1 = e.l1;
68 while (l1 <= e.l2) {
69 var e1;
70 e1 = document.getElementById('n' + l1++);
Andy Greene96af4a2018-06-24 10:31:13 +080071 e1.classList.remove(
72 'selected-line-link-highlight');
Andy Green1915feb2018-06-23 06:07:45 +080073 }
74
75 e.remove();
76 }
77
Andy Greencbec7752018-06-29 09:05:46 +080078 l1 = parse_hashurl_start(h);
Andy Greenfe549182018-06-21 16:56:36 +080079 if (!l1)
80 return;
81
Andy Greencbec7752018-06-29 09:05:46 +080082 l2 = parse_hashurl_end(h, l1);
Andy Greenfe549182018-06-21 16:56:36 +080083
Andy Green0fcc4502018-06-23 06:41:47 +080084 var lh, etable, etr, de, n, hl, v;
Andy Greenfe549182018-06-21 16:56:36 +080085
86 e = document.getElementById('n' + l1);
87 if (!e)
88 return;
89
Andy Greene96af4a2018-06-24 10:31:13 +080090 if (do_burger)
91 burger_create(e);
92
Andy Greenfe549182018-06-21 16:56:36 +080093 de = document.createElement("DIV");
94
95 de.className = "selected-lines";
96 de.style.bottom = e.style.bottom;
97 de.style.top = collect_offsetTop(e) + 'px';
Andy Green1915feb2018-06-23 06:07:45 +080098 de.id = "cgit-line-range";
Andy Greenfe549182018-06-21 16:56:36 +080099 de.l1 = l1;
100 de.l2 = l2;
101
102 /* we will tack the highlight div at the parent tr */
103 etr = find_parent_of_type(e, "tr");
104
105 de.style.width = etr.offsetWidth + 'px';
106
Andy Greene96af4a2018-06-24 10:31:13 +0800107 /* the table is offset from the left, the highlight needs to follow it */
Andy Greenfe549182018-06-21 16:56:36 +0800108 etable = find_parent_of_type(etr, "table");
Andy Greenfe549182018-06-21 16:56:36 +0800109 de.style.left = etable.offsetLeft + 'px';
110 de.style.height = ((l2 - l1 + 1) * e.offsetHeight) + 'px';
111
112 etr.insertBefore(de, etr.firstChild);
113
114 setTimeout(function() {
115 de.style.backgroundColor = "rgba(255, 255, 0, 0.2)";
116 }, 1);
117
118 n = l1;
119 while (n <= l2)
Andy Greene96af4a2018-06-24 10:31:13 +0800120 document.getElementById('n' + n++).classList.add(
121 'selected-line-link-highlight');
Andy Greenfe549182018-06-21 16:56:36 +0800122
Andy Green0fcc4502018-06-23 06:41:47 +0800123 hl = (window.innerHeight / (e.offsetHeight + 1));
124 v = (l1 + ((l2 - l1) / 2)) - (hl / 2);
125 if (v > l1)
126 v = l1;
127 if (v < 1)
128 v = 1;
129
130 t = document.getElementById('n' + Math.round(v));
131 if (!t)
132 t = e;
133
134 t.scrollIntoView(true);
Andy Greenfe549182018-06-21 16:56:36 +0800135}
136
Andy Greene96af4a2018-06-24 10:31:13 +0800137function copy_clipboard(value)
138{
139 var inp = document.createElement("textarea");
140 var e = document.getElementById("linenumbers");
Andy Green91984092018-06-24 09:08:40 +0800141
Andy Greene96af4a2018-06-24 10:31:13 +0800142 inp.type = "text";
143 inp.value = value;
144 /* hidden style stops it working for clipboard */
145 inp.setAttribute('readonly', '');
146 inp.style.position = "absolute";
147 inp.style.left = "-1000px";
148
149 e.appendChild(inp);
150
151 inp.select();
152
153 document.execCommand("copy");
154
155 inp.remove();
156}
157
Andy Greencbec7752018-06-29 09:05:46 +0800158/* we want to copy plain text for a line range */
159
160function copy_text(elem, l1, l2)
161{
162 var tc = elem.textContent.split('\n'), s = "";
163
164 l1 --;
165
166 while (l1 < l2)
167 s += tc[l1++] + '\n';
168
169 copy_clipboard(s);
170}
171
172
Andy Greene96af4a2018-06-24 10:31:13 +0800173/*
174 * An element in the popup menu was clicked, perform the appropriate action
175 */
176function mi_click(e) {
Andy Greencbec7752018-06-29 09:05:46 +0800177 var u, n, l1, l2, el;
Andy Greene96af4a2018-06-24 10:31:13 +0800178
Andy Green91984092018-06-24 09:08:40 +0800179 e.stopPropagation();
180 e.preventDefault();
181
Andy Greene96af4a2018-06-24 10:31:13 +0800182 switch (e.target.id) {
183 case "mi-c-line":
Andy Greencbec7752018-06-29 09:05:46 +0800184 l1 = parse_hashurl_start(window.location.hash);
185 l2 = parse_hashurl_end(window.location.hash, l1);
186 el = document.getElementsByClassName("highlight")[0].firstChild;
187 copy_text(el, l1, l2);
Andy Greene96af4a2018-06-24 10:31:13 +0800188 break;
189 case "mi-c-link":
190 copy_clipboard(window.location.href);
191 break;
192 case "mi-c-blame":
193 u = window.location.href;
194 t = u.indexOf("/tree/");
195 if (t)
196 window.location = u.substring(0, t) + "/blame/" +
197 u.substring(t + 6);
198 break;
199 case "mi-c-tree":
200 u = window.location.href;
201 t = u.indexOf("/blame/");
202 if (t)
203 window.location = u.substring(0, t) + "/tree/" +
204 u.substring(t + 7);
205 break;
206 }
207
208 if (!menu_popup)
209 return;
210
211 menu_popup.remove();
212 menu_popup = null;
213}
214
215/* We got a click on the (***) burger menu */
216
217function burger_click(e) {
218 var e1 = e, etable, d = new Date, s = "", n, is_blame,
219 ar = new Array("mi-c-line", "mi-c-link", "mi-c-blame", "mi-c-tree"),
220 an = new Array("Copy Lines", "Copy Link",
221 "View Blame", /* 2: shown in /tree/ */
222 "Remove Blame" /* 3: shown in /blame/ */);
223
224 e.preventDefault();
225
226 if (menu_popup) {
227 menu_popup.remove();
228 menu_popup = null;
229
230 return;
231 }
232
233 /*
234 * Create the popup menu
235 */
236
237 is_blame = !!document.getElementsByClassName("hashes").length;
238
239 menu_popup = document.createElement("DIV");
240 menu_popup.className = "popup-menu";
241 menu_popup.style.top = collect_offsetTop(e1) + e.offsetHeight + "px";
242
243 s = "<ul id='menu-ul'>";
244 for (n = 0; n < an.length; n++)
245 if (n < 2 || is_blame == (n == 3))
246 s += "<li id='" + ar[n] + "' tabindex='" + n + "'>" +
247 an[n] + "</li>";
248
249 menu_popup.innerHTML = s;
250
251 burger.insertBefore(menu_popup, null);
252
253 document.getElementById(ar[0]).focus();
254 for (n = 0; n < an.length; n++)
255 if (n < 2 || is_blame == (n == 3))
256 document.getElementById(ar[n]).
257 addEventListener("click", mi_click);
258
259 setTimeout(function() {
260 menu_popup.style.opacity = "1";
261 }, 1);
262
263 /* detect loss of focus for popup menu */
264 menu_popup.addEventListener("focusout", function(e) {
265 /* if focus went to a child (menu item), ignore */
266 if (e.relatedTarget &&
267 e.relatedTarget.parentNode.id == "menu-ul")
268 return;
269
270 menu_popup.remove();
271 menu_popup = null;
272 });
273}
274
275function burger_create(e)
276{
277 var e1 = e, etable, d = new Date;
278
279 if (burger)
280 burger.remove();
281
282 burger = document.createElement("DIV");
283 burger.className = "selected-lines-popup";
284 burger.style.top = collect_offsetTop(e1) + "px";
285
286 /* event listener cannot override default browser #URL behaviour */
287 burger.onclick = burger_click;
288
289 etable = find_parent_of_type(e, "table");
290 etable.insertBefore(burger, etable.firstChild);
291 burger_time = d.getTime();
292
293 setTimeout(function() {
294 burger.style.opacity = "1";
295 }, 1);
296}
297
298/*
299 * We got a click on a line number #url
300 *
301 * Create the "burger" menu there.
302 *
303 * Redraw the line range highlight accordingly.
304 */
305
306function line_range_click(e) {
307 var t, elem, m, n = window.location.href.length -
308 window.location.hash.length;
309
310 /* disable passthru to stop scrolling by browser #URL handler */
311 e.stopPropagation();
312 e.preventDefault();
313
314 if (!e.target.id)
315 return;
316
317 if (menu_popup) {
318 menu_popup.remove();
319 menu_popup = null;
320
321 return;
322 }
323
324 elem = document.getElementById(e.target.id);
325 if (!elem)
326 return;
327
328 burger_create(elem);
329
Andy Green91984092018-06-24 09:08:40 +0800330 if (!window.location.hash ||
Andy Greene96af4a2018-06-24 10:31:13 +0800331 window.location.hash.indexOf("-") >= 0 ||
332 e.target.id.substring(1) == window.location.href.substring(n + 2))
Andy Green91984092018-06-24 09:08:40 +0800333 t = window.location.href.substring(0, n) +
334 '#n' + e.target.id.substring(1);
335 else {
336 if (parseInt(window.location.hash.substring(2)) <
337 parseInt(e.target.id.substring(1))) /* forwards */
338 t = window.location + '-' + e.target.id.substring(1);
339 else
340 t = window.location.href.substring(0, n) +
341 '#n' + e.target.id.substring(1) + '-' +
342 window.location.href.substring(n + 2);
343 }
344
345 window.history.replaceState(null, null, t);
346
Andy Greene96af4a2018-06-24 10:31:13 +0800347 line_range_highlight(0);
Andy Green91984092018-06-24 09:08:40 +0800348}
349
Andy Green6e1401a2018-06-24 15:05:20 +0800350/* this follows the logic and suffixes used in ui-shared.c */
351
352var age_classes = [ "age-mins", "age-hours", "age-days", "age-weeks", "age-months", "age-years" ];
353var age_suffix = [ "min.", "hours", "days", "weeks", "months", "years", "years" ];
354var age_next = [ 60, 3600, 24 * 3600, 7 * 24 * 3600, 30 * 24 * 3600, 365 * 24 * 3600, 365 * 24 * 3600 ];
355var age_limit = [ 7200, 24 * 7200, 7 * 24 * 7200, 30 * 24 * 7200, 365 * 25 * 7200, 365 * 25 * 7200 ];
356var update_next = [ 10, 5 * 60, 1800, 24 * 3600, 24 * 3600, 24 * 3600, 24 * 3600 ];
357
358function render_age(e, age)
359{
360 var t, n;
361
362 for (n = 0; n < age_classes.length; n++)
363 if (age < age_limit[n])
364 break;
365
366 t = Math.round(age / age_next[n]) + " " + age_suffix[n];
367
368 if (e.textContent != t) {
369 e.textContent = t;
370 if (n == age_classes.length)
371 n--;
372 if (e.className != age_classes[n])
373 e.className = age_classes[n];
374 }
375}
376
377function aging()
378{
379 var n, next = 24 * 3600,
380 now_ut = Math.round((new Date().getTime() / 1000));
381
382 for (n = 0; n < age_classes.length; n++) {
383 var m, elems = document.getElementsByClassName(age_classes[n]);
384
385 if (elems.length && update_next[n] < next)
386 next = update_next[n];
387
388 for (m = 0; m < elems.length; m++) {
389 var age = now_ut - elems[m].getAttribute("ut");
390
391 render_age(elems[m], age);
392 }
393 }
394
395 /*
396 * We only need to come back when the age might have changed.
397 * Eg, if everything is counted in hours already, once per
398 * 5 minutes is accurate enough.
399 */
400
401 window.setTimeout(aging, next * 1000);
402}
403
404
Andy Greenfe549182018-06-21 16:56:36 +0800405/* we have to use load, because header images can push the layout vertically */
406window.addEventListener("load", function() {
Andy Greene96af4a2018-06-24 10:31:13 +0800407 line_range_highlight(1);
Andy Greenfe549182018-06-21 16:56:36 +0800408}, false);
409
Andy Green91984092018-06-24 09:08:40 +0800410document.addEventListener("DOMContentLoaded", function() {
411 /* event listener cannot override default #URL browser processing,
412 * requires onclick */
413 var e = document.getElementById("linenumbers");
414 if (e)
415 e.onclick = line_range_click;
Andy Green6e1401a2018-06-24 15:05:20 +0800416
417 /* we can do the aging on DOM content load since no layout dependency */
418 aging();
Andy Green91984092018-06-24 09:08:40 +0800419}, false);
420
Andy Green1915feb2018-06-23 06:07:45 +0800421window.addEventListener("hashchange", function() {
Andy Greene96af4a2018-06-24 10:31:13 +0800422 line_range_highlight(1);
Andy Green1915feb2018-06-23 06:07:45 +0800423}, false);
424
Andy Greenfe549182018-06-21 16:56:36 +0800425})();