Andy Green | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 1 | /* 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 Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 11 | var burger, menu_popup; |
| 12 | |
Andy Green | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 13 | function 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 | |
| 26 | function find_parent_of_type(e, type) |
| 27 | { |
| 28 | while (e.tagName.toLowerCase() != type) |
| 29 | e = e.parentNode; |
| 30 | |
| 31 | return e; |
| 32 | } |
| 33 | |
Andy Green | cbec775 | 2018-06-29 09:05:46 +0800 | [diff] [blame] | 34 | function parse_hashurl_start(h) |
| 35 | { |
| 36 | return parseInt(h.substring(2)); |
| 37 | } |
| 38 | |
| 39 | function 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 Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 53 | /* |
| 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 | |
| 61 | function line_range_highlight(do_burger) |
Andy Green | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 62 | { |
| 63 | var h = window.location.hash, l1 = 0, l2 = 0, e, t; |
| 64 | |
Andy Green | 1915feb | 2018-06-23 06:07:45 +0800 | [diff] [blame] | 65 | 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 Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 71 | e1.classList.remove( |
| 72 | 'selected-line-link-highlight'); |
Andy Green | 1915feb | 2018-06-23 06:07:45 +0800 | [diff] [blame] | 73 | } |
| 74 | |
| 75 | e.remove(); |
| 76 | } |
| 77 | |
Andy Green | cbec775 | 2018-06-29 09:05:46 +0800 | [diff] [blame] | 78 | l1 = parse_hashurl_start(h); |
Andy Green | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 79 | if (!l1) |
| 80 | return; |
| 81 | |
Andy Green | cbec775 | 2018-06-29 09:05:46 +0800 | [diff] [blame] | 82 | l2 = parse_hashurl_end(h, l1); |
Andy Green | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 83 | |
Andy Green | 0fcc450 | 2018-06-23 06:41:47 +0800 | [diff] [blame] | 84 | var lh, etable, etr, de, n, hl, v; |
Andy Green | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 85 | |
| 86 | e = document.getElementById('n' + l1); |
| 87 | if (!e) |
| 88 | return; |
| 89 | |
Andy Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 90 | if (do_burger) |
| 91 | burger_create(e); |
| 92 | |
Andy Green | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 93 | 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 Green | 1915feb | 2018-06-23 06:07:45 +0800 | [diff] [blame] | 98 | de.id = "cgit-line-range"; |
Andy Green | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 99 | 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 Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 107 | /* the table is offset from the left, the highlight needs to follow it */ |
Andy Green | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 108 | etable = find_parent_of_type(etr, "table"); |
Andy Green | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 109 | 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 Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 120 | document.getElementById('n' + n++).classList.add( |
| 121 | 'selected-line-link-highlight'); |
Andy Green | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 122 | |
Andy Green | 0fcc450 | 2018-06-23 06:41:47 +0800 | [diff] [blame] | 123 | 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 Green | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 135 | } |
| 136 | |
Andy Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 137 | function copy_clipboard(value) |
| 138 | { |
| 139 | var inp = document.createElement("textarea"); |
| 140 | var e = document.getElementById("linenumbers"); |
Andy Green | 9198409 | 2018-06-24 09:08:40 +0800 | [diff] [blame] | 141 | |
Andy Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 142 | 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 Green | cbec775 | 2018-06-29 09:05:46 +0800 | [diff] [blame] | 158 | /* we want to copy plain text for a line range */ |
| 159 | |
| 160 | function 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 Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 173 | /* |
| 174 | * An element in the popup menu was clicked, perform the appropriate action |
| 175 | */ |
| 176 | function mi_click(e) { |
Andy Green | cbec775 | 2018-06-29 09:05:46 +0800 | [diff] [blame] | 177 | var u, n, l1, l2, el; |
Andy Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 178 | |
Andy Green | 9198409 | 2018-06-24 09:08:40 +0800 | [diff] [blame] | 179 | e.stopPropagation(); |
| 180 | e.preventDefault(); |
| 181 | |
Andy Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 182 | switch (e.target.id) { |
| 183 | case "mi-c-line": |
Andy Green | cbec775 | 2018-06-29 09:05:46 +0800 | [diff] [blame] | 184 | 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 Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 188 | 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 | |
| 217 | function 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 | |
| 275 | function 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 | |
| 306 | function 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 Green | 9198409 | 2018-06-24 09:08:40 +0800 | [diff] [blame] | 330 | if (!window.location.hash || |
Andy Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 331 | window.location.hash.indexOf("-") >= 0 || |
| 332 | e.target.id.substring(1) == window.location.href.substring(n + 2)) |
Andy Green | 9198409 | 2018-06-24 09:08:40 +0800 | [diff] [blame] | 333 | 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 Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 347 | line_range_highlight(0); |
Andy Green | 9198409 | 2018-06-24 09:08:40 +0800 | [diff] [blame] | 348 | } |
| 349 | |
Andy Green | 6e1401a | 2018-06-24 15:05:20 +0800 | [diff] [blame] | 350 | /* this follows the logic and suffixes used in ui-shared.c */ |
| 351 | |
| 352 | var age_classes = [ "age-mins", "age-hours", "age-days", "age-weeks", "age-months", "age-years" ]; |
| 353 | var age_suffix = [ "min.", "hours", "days", "weeks", "months", "years", "years" ]; |
| 354 | var age_next = [ 60, 3600, 24 * 3600, 7 * 24 * 3600, 30 * 24 * 3600, 365 * 24 * 3600, 365 * 24 * 3600 ]; |
| 355 | var age_limit = [ 7200, 24 * 7200, 7 * 24 * 7200, 30 * 24 * 7200, 365 * 25 * 7200, 365 * 25 * 7200 ]; |
| 356 | var update_next = [ 10, 5 * 60, 1800, 24 * 3600, 24 * 3600, 24 * 3600, 24 * 3600 ]; |
| 357 | |
| 358 | function 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 | |
| 377 | function 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 Green | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 405 | /* we have to use load, because header images can push the layout vertically */ |
| 406 | window.addEventListener("load", function() { |
Andy Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 407 | line_range_highlight(1); |
Andy Green | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 408 | }, false); |
| 409 | |
Andy Green | 9198409 | 2018-06-24 09:08:40 +0800 | [diff] [blame] | 410 | document.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 Green | 6e1401a | 2018-06-24 15:05:20 +0800 | [diff] [blame] | 416 | |
| 417 | /* we can do the aging on DOM content load since no layout dependency */ |
| 418 | aging(); |
Andy Green | 9198409 | 2018-06-24 09:08:40 +0800 | [diff] [blame] | 419 | }, false); |
| 420 | |
Andy Green | 1915feb | 2018-06-23 06:07:45 +0800 | [diff] [blame] | 421 | window.addEventListener("hashchange", function() { |
Andy Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 422 | line_range_highlight(1); |
Andy Green | 1915feb | 2018-06-23 06:07:45 +0800 | [diff] [blame] | 423 | }, false); |
| 424 | |
Andy Green | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 425 | })(); |