|
1 | 1 | // ==UserScript==
|
2 | 2 | // @name GitHub Table of Contents
|
3 |
| -// @version 2.0.6 |
| 3 | +// @version 2.1.0 |
4 | 4 | // @description A userscript that adds a table of contents to readme & wiki pages
|
5 | 5 | // @license MIT
|
6 | 6 | // @author Rob Garrison
|
|
15 | 15 | // @grant GM.setValue
|
16 | 16 | // @grant GM_addStyle
|
17 | 17 | // @grant GM.addStyle
|
18 |
| -// @require https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=882023 |
| 18 | +// @require https://greasyfork.org/scripts/398877-utils-js/code/utilsjs.js?version=895926 |
| 19 | +// @require https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=666427 |
19 | 20 | // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js?updated=20180103
|
20 | 21 | // @icon https://github.githubassets.com/pinned-octocat.svg
|
21 | 22 | // @updateURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-toc.user.js
|
22 | 23 | // @downloadURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-toc.user.js
|
23 | 24 | // ==/UserScript==
|
24 |
| -/* global GM */ |
| 25 | +/* global $ $$ on addClass removeClass */ |
25 | 26 | (async () => {
|
26 | 27 | "use strict";
|
27 | 28 |
|
|
40 | 41 |
|
41 | 42 | GM.addStyle(`
|
42 | 43 | /* z-index > 1000 to be above the */
|
43 |
| - .ghus-toc { position:fixed; z-index:1001; min-width:200px; top:${defaults.top}; |
44 |
| - right:${defaults.right}; } |
| 44 | + .ghus-toc { position:fixed; z-index:1001; min-width:200px; min-height:100px; top:${defaults.top}; |
| 45 | + right:${defaults.right}; resize:both; overflow:hidden; padding: 0 3px 38px 0; margin:0; } |
45 | 46 | .ghus-toc h3 { cursor:move; }
|
46 | 47 | .ghus-toc-title { padding-left:20px; }
|
47 | 48 | /* icon toggles TOC container & subgroups */
|
|
52 | 53 | .ghus-toc .ghus-toc-docs { float:right; }
|
53 | 54 | /* move collapsed TOC to top right corner */
|
54 | 55 | .ghus-toc.collapsed {
|
55 |
| - width:30px; height:30px; min-width:auto; overflow:hidden; top:16px !important; left:auto !important; |
56 |
| - right:10px !important; border:1px solid rgba(128, 128, 128, 0.5); border-radius:3px; |
| 56 | + width:30px !important; height:34px !important; min-width:auto; min-height:auto; overflow:hidden; |
| 57 | + top:16px !important; left:auto !important; right:10px !important; margin:0; padding:0; |
| 58 | + border:1px solid rgba(128, 128, 128, 0.5); border-radius:3px; resize:none; |
57 | 59 | }
|
58 | 60 | .ghus-toc.collapsed > h3 { cursor:pointer; padding-top:5px; border:none; background:#222; color:#ddd; }
|
59 |
| - .ghus-toc.collapsed .ghus-toc-docs { display:none; } |
| 61 | + .ghus-toc.collapsed .ghus-toc-docs, .ghus-toc.collapsed .ghus-toc-title { display:none; } |
60 | 62 | .ghus-toc:not(.ghus-toc-hidden).collapsed + .Header { padding-right: ${defaults.headerPad} !important; }
|
61 | 63 | /* move header text out-of-view when collapsed */
|
62 | 64 | .ghus-toc.collapsed > h3 svg { margin-top:6px; }
|
63 |
| - .ghus-toc-hidden, .ghus-toc.collapsed .boxed-group-inner, |
64 |
| - .ghus-toc li:not(.collapsible) .ghus-toc-icon { display:none; } |
65 |
| - .ghus-toc .boxed-group-inner { max-width:250px; max-height:400px; overflow-y:auto; overflow-x:hidden; } |
| 65 | + .ghus-toc-hidden, .ghus-toc.collapsed .boxed-group-inner { display:none; } |
| 66 | + .ghus-toc .boxed-group-inner { width:100%; height:100%; overflow-x:hidden; overflow-y:auto; |
| 67 | + margin-bottom:50px; } |
66 | 68 | .ghus-toc ul { list-style:none; }
|
67 |
| - .ghus-toc li { max-width:230px; white-space:nowrap; overflow-x:hidden; text-overflow:ellipsis; } |
| 69 | + .ghus-toc li { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; position:relative; } |
68 | 70 | .ghus-toc .ghus-toc-h1 { padding-left:15px; }
|
69 | 71 | .ghus-toc .ghus-toc-h2 { padding-left:30px; }
|
70 | 72 | .ghus-toc .ghus-toc-h3 { padding-left:45px; }
|
71 | 73 | .ghus-toc .ghus-toc-h4 { padding-left:60px; }
|
72 | 74 | .ghus-toc .ghus-toc-h5 { padding-left:75px; }
|
73 | 75 | .ghus-toc .ghus-toc-h6 { padding-left:90px; }
|
74 | 76 | /* anchor collapsible icon */
|
75 |
| - .ghus-toc li.collapsible .ghus-toc-icon { |
76 |
| - width:16px; height:10px; display:inline-block; margin-left:-16px; |
| 77 | + .ghus-toc li.collapsible .ghus-toc-icon:before { |
| 78 | + content:' '; position:absolute; width:10px; height:10px; display:inline-block; left:-12px; top:-10px; |
77 | 79 | background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSdvY3RpY29uJyBoZWlnaHQ9JzE0JyB2aWV3Qm94PScwIDAgMTIgMTYnPjxwYXRoIGQ9J00wIDVsNiA2IDYtNkgweic+PC9wYXRoPjwvc3ZnPg==) left center no-repeat;
|
78 | 80 | }
|
79 |
| - /* on rotate, height becomes width, so this is keeping things lined up */ |
80 |
| - .ghus-toc li.collapsible.collapsed .ghus-toc-icon { -webkit-transform:rotate(-90deg); transform:rotate(-90deg); height:10px; width:12px; margin-right:2px; } |
| 81 | + .ghus-toc li.collapsible.collapsed .ghus-toc-icon:before { -webkit-transform:rotate(-90deg); |
| 82 | + transform:rotate(-90deg); } |
81 | 83 | .ghus-toc-icon svg, .ghus-toc-docs svg { pointer-events:none; }
|
82 |
| - .ghus-toc-no-selection { -webkit-user-select:none !important; -moz-user-select:none !important; user-select:none !important; } |
| 84 | + .ghus-toc-no-selection { -webkit-user-select:none !important; -moz-user-select:none !important; |
| 85 | + user-select:none !important; } |
83 | 86 | /* prevent google translate from breaking links */
|
84 | 87 | .ghus-toc li a font { pointer-events:none; }
|
85 | 88 | `);
|
|
90 | 93 | let title = await GM.getValue("github-toc-title", defaults.title);
|
91 | 94 |
|
92 | 95 | const container = document.createElement("div");
|
93 |
| - const useClient = !!document.all; |
94 | 96 |
|
95 | 97 | // keyboard shortcuts
|
96 | 98 | const keyboard = {
|
|
107 | 109 | unsel: null
|
108 | 110 | };
|
109 | 111 |
|
| 112 | + const regex = { |
| 113 | + quote: /'/g, |
| 114 | + doubleQuote: /"/g, |
| 115 | + number: /\d/, |
| 116 | + toggle: /\+/g, |
| 117 | + header: /ghus-toc-h\d/, |
| 118 | + collapsible: /collapsible-(\d+)/, |
| 119 | + ignore: /(input|textarea)/i, |
| 120 | + }; |
| 121 | + |
110 | 122 | const stopPropag = event => {
|
111 | 123 | event.preventDefault();
|
112 | 124 | event.stopPropagation();
|
|
115 | 127 | // drag code adapted from http://jsfiddle.net/tovic/Xcb8d/light/
|
116 | 128 | function dragInit(event) {
|
117 | 129 | if (!container.classList.contains("collapsed")) {
|
118 |
| - const x = useClient ? window.event.clientX : event.pageX; |
119 |
| - const y = useClient ? window.event.clientY : event.pageY; |
120 | 130 | drag.el = container;
|
121 |
| - drag.elmX = x - drag.el.offsetLeft; |
122 |
| - drag.elmY = y - drag.el.offsetTop; |
| 131 | + drag.elmX = event.pageX - drag.el.offsetLeft; |
| 132 | + drag.elmY = event.pageY - drag.el.offsetTop; |
123 | 133 | selectionToggle(true);
|
124 | 134 | } else {
|
125 | 135 | drag.el = null;
|
|
129 | 139 |
|
130 | 140 | function dragMove(event) {
|
131 | 141 | if (drag.el !== null) {
|
132 |
| - const x = useClient ? window.event.clientX : event.pageX; |
133 |
| - const y = useClient ? window.event.clientY : event.pageY; |
134 |
| - drag.el.style.left = (x - drag.elmX) + "px"; |
135 |
| - drag.el.style.top = (y - drag.elmY) + "px"; |
| 142 | + drag.el.style.left = `${event.pageX - drag.elmX}px`; |
| 143 | + drag.el.style.top = `${event.pageY - drag.elmY}px`; |
136 | 144 | drag.el.style.right = "auto";
|
137 | 145 | }
|
138 | 146 | }
|
139 | 147 |
|
140 |
| - function dragStop() { |
| 148 | + async function dragStop(event) { |
141 | 149 | if (drag.el !== null) {
|
142 | 150 | dragSave();
|
143 | 151 | selectionToggle();
|
144 | 152 | }
|
145 | 153 | drag.el = null;
|
| 154 | + |
| 155 | + if (event.target === container) { |
| 156 | + // save container size on mouseup |
| 157 | + await GM.setValue( |
| 158 | + "github-toc-size", |
| 159 | + [container.clientWidth, container.clientHeight] |
| 160 | + ); |
| 161 | + } |
146 | 162 | }
|
147 | 163 |
|
148 | 164 | async function dragSave(restore) {
|
|
180 | 196 | container.style.top = top;
|
181 | 197 | }
|
182 | 198 |
|
| 199 | + async function setSize() { |
| 200 | + const size = await GM.getValue("github-toc-size", [250, 250]); |
| 201 | + container.style.width = `${size[0]}px`; |
| 202 | + container.style.height = `${size[1]}px`; |
| 203 | + } |
| 204 | + |
183 | 205 | // stop text selection while dragging
|
184 | 206 | function selectionToggle(disable) {
|
185 | 207 | const body = $("body");
|
|
194 | 216 | body.setAttribute("unselectable", drag.unsel);
|
195 | 217 | }
|
196 | 218 | body.classList.remove("ghus-toc-no-selection");
|
197 |
| - body.removeEventListener("onselectstart", stopPropag); |
| 219 | + off(body, "onselectstart", stopPropag); |
198 | 220 | }
|
199 | 221 | removeSelection();
|
200 | 222 | }
|
|
255 | 277 | if (anchor.parentElement) {
|
256 | 278 | header = anchor.parentElement;
|
257 | 279 | // replace single & double quotes with right angled quotes
|
258 |
| - txt = header.textContent.trim().replace(/'/g, "’").replace(/"/g, "”"); |
| 280 | + txt = header.textContent |
| 281 | + .trim() |
| 282 | + .replace(regex.quote, "’") |
| 283 | + .replace(regex.doubleQuote, "”"); |
259 | 284 | content += `
|
260 | 285 | <li class="ghus-toc-${header.nodeName.toLowerCase()}">
|
261 | 286 | <span class="ghus-toc-icon octicon ghd-invert"></span>
|
|
279 | 304 | let indx, el, next, count, num, group;
|
280 | 305 | const els = $$("li", container);
|
281 | 306 | const len = els.length;
|
282 |
| - const regex = /\d/; |
283 | 307 | for (indx = 0; indx < len; indx++) {
|
284 | 308 | count = 0;
|
285 | 309 | group = [];
|
286 | 310 | el = els[indx];
|
287 |
| - next = el && el.nextElementSibling; |
| 311 | + next = el?.nextElementSibling; |
288 | 312 | if (next) {
|
289 |
| - num = el.className.match(regex)[0]; |
| 313 | + num = el.className.match(regex.number)[0]; |
290 | 314 | while (next && !next.classList.contains("ghus-toc-h" + num)) {
|
291 |
| - if (next.className.match(regex)[0] > num) { |
| 315 | + if (next.className.match(regex.number)[0] > num) { |
292 | 316 | count++;
|
293 | 317 | group[group.length] = next;
|
294 | 318 | }
|
|
313 | 337 | const collapse = el.classList.contains("collapsed");
|
314 | 338 | if (event.target.classList.contains("ghus-toc-icon")) {
|
315 | 339 | if (event.shiftKey) {
|
316 |
| - name = el.className.match(/ghus-toc-h\d/); |
| 340 | + name = el.className.match(regex.header); |
317 | 341 | els = name ? $$("." + name, container) : [];
|
318 | 342 | indx = els.length;
|
319 | 343 | while (indx--) {
|
|
328 | 352 | }
|
329 | 353 |
|
330 | 354 | function collapseChildren(el, collapse) {
|
331 |
| - const name = el && el.className.match(/collapsible-(\d+)/); |
| 355 | + const name = el?.className.match(regex.collapsible); |
332 | 356 | const children = name ? $$(".ghus-toc-childof-" + name[1], container) : null;
|
333 | 357 | if (children) {
|
334 | 358 | if (collapse) {
|
|
358 | 382 | return;
|
359 | 383 | }
|
360 | 384 | // prevent opening panel while typing in comments
|
361 |
| - if (/(input|textarea)/i.test(document.activeElement.nodeName)) { |
| 385 | + if (regex.ignore.test(document.activeElement.nodeName)) { |
362 | 386 | return;
|
363 | 387 | }
|
364 | 388 | // toggle TOC (g+t)
|
|
372 | 396 | // reset TOC window position (g+r)
|
373 | 397 | if (keyboard.lastKey === tocReset[0] && key === tocReset[1]) {
|
374 | 398 | container.setAttribute("style", "");
|
| 399 | + setSize(); |
375 | 400 | dragSave(true);
|
376 | 401 | }
|
377 | 402 | keyboard.lastKey = key;
|
|
393 | 418 |
|
394 | 419 | // TOC saved state
|
395 | 420 | const hidden = await GM.getValue("github-toc-hidden", false);
|
| 421 | + setSize(); |
396 | 422 | container.className = "ghus-toc boxed-group wiki-pages-box readability-sidebar" + (hidden ? " collapsed" : "");
|
397 | 423 | container.setAttribute("role", "navigation");
|
398 | 424 | container.setAttribute("unselectable", "on");
|
399 | 425 | container.setAttribute("index", "0");
|
400 | 426 | container.innerHTML = `
|
401 |
| - <h3 class="js-wiki-toggle-collapse wiki-auxiliary-content" data-hotkey="${defaults.toggle.replace(/\+/g, " ")}"> |
| 427 | + <h3 class="js-wiki-toggle-collapse wiki-auxiliary-content" data-hotkey="${defaults.toggle.replace(regex.toggle, " ")}"> |
402 | 428 | <span class="ghus-toc-toggle ghus-toc-icon">
|
403 | 429 | <svg class="octicon" height="14" width="14" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 16 12">
|
404 | 430 | <path d="M2 13c0 .6 0 1-.6 1H.6c-.6 0-.6-.4-.6-1s0-1 .6-1h.8c.6 0 .6.4.6 1zm2.6-9h6.8c.6 0 .6-.4.6-1s0-1-.6-1H4.6C4 2 4 2.4 4 3s0 1 .6 1zM1.4 7H.6C0 7 0 7.4 0 8s0 1 .6 1h.8C2 9 2 8.6 2 8s0-1-.6-1zm0-5H.6C0 2 0 2.4 0 3s0 1 .6 1h.8C2 4 2 3.6 2 3s0-1-.6-1zm10 5H4.6C4 7 4 7.4 4 8s0 1 .6 1h6.8c.6 0 .6-.4.6-1s0-1-.6-1zm0 5H4.6c-.6 0-.6.4-.6 1s0 1 .6 1h6.8c.6 0 .6-.4.6-1s0-1-.6-1z"/>
|
|
436 | 462 | tocAdd();
|
437 | 463 | }
|
438 | 464 |
|
439 |
| - function $(str, el) { |
440 |
| - return (el || document).querySelector(str); |
441 |
| - } |
442 |
| - |
443 |
| - function $$(str, el) { |
444 |
| - return Array.from((el || document).querySelectorAll(str)); |
445 |
| - } |
446 |
| - |
447 |
| - function on(el, name, handler) { |
448 |
| - el.addEventListener(name, handler); |
449 |
| - } |
450 |
| - |
451 |
| - function addClass(els, name) { |
452 |
| - let indx; |
453 |
| - const len = els.length; |
454 |
| - for (indx = 0; indx < len; indx++) { |
455 |
| - els[indx].classList.add(name); |
456 |
| - } |
457 |
| - } |
458 |
| - |
459 |
| - function removeClass(els, name) { |
460 |
| - let indx; |
461 |
| - const len = els.length; |
462 |
| - for (indx = 0; indx < len; indx++) { |
463 |
| - els[indx].classList.remove(name); |
464 |
| - } |
465 |
| - } |
466 |
| - |
467 | 465 | // Add GM options
|
468 | 466 | GM.registerMenuCommand("Set Table of Contents Title", async () => {
|
469 | 467 | title = prompt("Table of Content Title:", title);
|
|
0 commit comments