Skip to content

Commit 0087b76

Browse files
committed
TOC: Make container resizable & code cleanup. Closes Mottie#132
1 parent b879f7c commit 0087b76

File tree

1 file changed

+63
-65
lines changed

1 file changed

+63
-65
lines changed

github-toc.user.js

Lines changed: 63 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// ==UserScript==
22
// @name GitHub Table of Contents
3-
// @version 2.0.6
3+
// @version 2.1.0
44
// @description A userscript that adds a table of contents to readme & wiki pages
55
// @license MIT
66
// @author Rob Garrison
@@ -15,13 +15,14 @@
1515
// @grant GM.setValue
1616
// @grant GM_addStyle
1717
// @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
1920
// @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js?updated=20180103
2021
// @icon https://github.githubassets.com/pinned-octocat.svg
2122
// @updateURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-toc.user.js
2223
// @downloadURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-toc.user.js
2324
// ==/UserScript==
24-
/* global GM */
25+
/* global $ $$ on addClass removeClass */
2526
(async () => {
2627
"use strict";
2728

@@ -40,8 +41,8 @@
4041

4142
GM.addStyle(`
4243
/* 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; }
4546
.ghus-toc h3 { cursor:move; }
4647
.ghus-toc-title { padding-left:20px; }
4748
/* icon toggles TOC container & subgroups */
@@ -52,34 +53,36 @@
5253
.ghus-toc .ghus-toc-docs { float:right; }
5354
/* move collapsed TOC to top right corner */
5455
.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;
5759
}
5860
.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; }
6062
.ghus-toc:not(.ghus-toc-hidden).collapsed + .Header { padding-right: ${defaults.headerPad} !important; }
6163
/* move header text out-of-view when collapsed */
6264
.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; }
6668
.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; }
6870
.ghus-toc .ghus-toc-h1 { padding-left:15px; }
6971
.ghus-toc .ghus-toc-h2 { padding-left:30px; }
7072
.ghus-toc .ghus-toc-h3 { padding-left:45px; }
7173
.ghus-toc .ghus-toc-h4 { padding-left:60px; }
7274
.ghus-toc .ghus-toc-h5 { padding-left:75px; }
7375
.ghus-toc .ghus-toc-h6 { padding-left:90px; }
7476
/* 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;
7779
background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSdvY3RpY29uJyBoZWlnaHQ9JzE0JyB2aWV3Qm94PScwIDAgMTIgMTYnPjxwYXRoIGQ9J00wIDVsNiA2IDYtNkgweic+PC9wYXRoPjwvc3ZnPg==) left center no-repeat;
7880
}
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); }
8183
.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; }
8386
/* prevent google translate from breaking links */
8487
.ghus-toc li a font { pointer-events:none; }
8588
`);
@@ -90,7 +93,6 @@
9093
let title = await GM.getValue("github-toc-title", defaults.title);
9194

9295
const container = document.createElement("div");
93-
const useClient = !!document.all;
9496

9597
// keyboard shortcuts
9698
const keyboard = {
@@ -107,6 +109,16 @@
107109
unsel: null
108110
};
109111

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+
110122
const stopPropag = event => {
111123
event.preventDefault();
112124
event.stopPropagation();
@@ -115,11 +127,9 @@
115127
// drag code adapted from http://jsfiddle.net/tovic/Xcb8d/light/
116128
function dragInit(event) {
117129
if (!container.classList.contains("collapsed")) {
118-
const x = useClient ? window.event.clientX : event.pageX;
119-
const y = useClient ? window.event.clientY : event.pageY;
120130
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;
123133
selectionToggle(true);
124134
} else {
125135
drag.el = null;
@@ -129,20 +139,26 @@
129139

130140
function dragMove(event) {
131141
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`;
136144
drag.el.style.right = "auto";
137145
}
138146
}
139147

140-
function dragStop() {
148+
async function dragStop(event) {
141149
if (drag.el !== null) {
142150
dragSave();
143151
selectionToggle();
144152
}
145153
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+
}
146162
}
147163

148164
async function dragSave(restore) {
@@ -180,6 +196,12 @@
180196
container.style.top = top;
181197
}
182198

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+
183205
// stop text selection while dragging
184206
function selectionToggle(disable) {
185207
const body = $("body");
@@ -194,7 +216,7 @@
194216
body.setAttribute("unselectable", drag.unsel);
195217
}
196218
body.classList.remove("ghus-toc-no-selection");
197-
body.removeEventListener("onselectstart", stopPropag);
219+
off(body, "onselectstart", stopPropag);
198220
}
199221
removeSelection();
200222
}
@@ -255,7 +277,10 @@
255277
if (anchor.parentElement) {
256278
header = anchor.parentElement;
257279
// 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, "”");
259284
content += `
260285
<li class="ghus-toc-${header.nodeName.toLowerCase()}">
261286
<span class="ghus-toc-icon octicon ghd-invert"></span>
@@ -279,16 +304,15 @@
279304
let indx, el, next, count, num, group;
280305
const els = $$("li", container);
281306
const len = els.length;
282-
const regex = /\d/;
283307
for (indx = 0; indx < len; indx++) {
284308
count = 0;
285309
group = [];
286310
el = els[indx];
287-
next = el && el.nextElementSibling;
311+
next = el?.nextElementSibling;
288312
if (next) {
289-
num = el.className.match(regex)[0];
313+
num = el.className.match(regex.number)[0];
290314
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) {
292316
count++;
293317
group[group.length] = next;
294318
}
@@ -313,7 +337,7 @@
313337
const collapse = el.classList.contains("collapsed");
314338
if (event.target.classList.contains("ghus-toc-icon")) {
315339
if (event.shiftKey) {
316-
name = el.className.match(/ghus-toc-h\d/);
340+
name = el.className.match(regex.header);
317341
els = name ? $$("." + name, container) : [];
318342
indx = els.length;
319343
while (indx--) {
@@ -328,7 +352,7 @@
328352
}
329353

330354
function collapseChildren(el, collapse) {
331-
const name = el && el.className.match(/collapsible-(\d+)/);
355+
const name = el?.className.match(regex.collapsible);
332356
const children = name ? $$(".ghus-toc-childof-" + name[1], container) : null;
333357
if (children) {
334358
if (collapse) {
@@ -358,7 +382,7 @@
358382
return;
359383
}
360384
// prevent opening panel while typing in comments
361-
if (/(input|textarea)/i.test(document.activeElement.nodeName)) {
385+
if (regex.ignore.test(document.activeElement.nodeName)) {
362386
return;
363387
}
364388
// toggle TOC (g+t)
@@ -372,6 +396,7 @@
372396
// reset TOC window position (g+r)
373397
if (keyboard.lastKey === tocReset[0] && key === tocReset[1]) {
374398
container.setAttribute("style", "");
399+
setSize();
375400
dragSave(true);
376401
}
377402
keyboard.lastKey = key;
@@ -393,12 +418,13 @@
393418

394419
// TOC saved state
395420
const hidden = await GM.getValue("github-toc-hidden", false);
421+
setSize();
396422
container.className = "ghus-toc boxed-group wiki-pages-box readability-sidebar" + (hidden ? " collapsed" : "");
397423
container.setAttribute("role", "navigation");
398424
container.setAttribute("unselectable", "on");
399425
container.setAttribute("index", "0");
400426
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, " ")}">
402428
<span class="ghus-toc-toggle ghus-toc-icon">
403429
<svg class="octicon" height="14" width="14" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 16 12">
404430
<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,34 +462,6 @@
436462
tocAdd();
437463
}
438464

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-
467465
// Add GM options
468466
GM.registerMenuCommand("Set Table of Contents Title", async () => {
469467
title = prompt("Table of Content Title:", title);

0 commit comments

Comments
 (0)