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 | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 350 | /* we have to use load, because header images can push the layout vertically */ |
| 351 | window.addEventListener("load", function() { |
Andy Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 352 | line_range_highlight(1); |
Andy Green | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 353 | }, false); |
| 354 | |
Andy Green | 9198409 | 2018-06-24 09:08:40 +0800 | [diff] [blame] | 355 | document.addEventListener("DOMContentLoaded", function() { |
| 356 | /* event listener cannot override default #URL browser processing, |
| 357 | * requires onclick */ |
| 358 | var e = document.getElementById("linenumbers"); |
| 359 | if (e) |
| 360 | e.onclick = line_range_click; |
| 361 | }, false); |
| 362 | |
Andy Green | 1915feb | 2018-06-23 06:07:45 +0800 | [diff] [blame] | 363 | window.addEventListener("hashchange", function() { |
Andy Green | e96af4a | 2018-06-24 10:31:13 +0800 | [diff] [blame] | 364 | line_range_highlight(1); |
Andy Green | 1915feb | 2018-06-23 06:07:45 +0800 | [diff] [blame] | 365 | }, false); |
| 366 | |
Andy Green | fe54918 | 2018-06-21 16:56:36 +0800 | [diff] [blame] | 367 | })(); |