NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Bitbucket Collapse Markdown // @version 0.1.0 // @description A userscript that collapses markdown headers // @license https://creativecommons.org/licenses/by-sa/4.0/ // @author Rob Garrison // @namespace https://github.com/Mottie // @include https://bitbucket.org/* // @run-at document-idle // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @icon https://bitbucket.org/mottie/bitbucket-userscripts/raw/HEAD/images/bitbucket.svg // @updateURL https://bitbucket.org/mottie/bitbucket-userscripts/raw/HEAD/bitbucket-collapse-markdown.user.js // @downloadURL https://bitbucket.org/mottie/bitbucket-userscripts/raw/HEAD/bitbucket-collapse-markdown.user.js // ==/UserScript== (() => { "use strict"; const defaultColors = [ // palette generated by http://tools.medialab.sciences-po.fr/iwanthue/ // (colorblind friendly, soft) "#6778d0", "#ac9c3d", "#b94a73", "#56ae6c", "#9750a1", "#ba543d" ], headers = "H1 H2 H3 H4 H5 H6".split(" "), collapsed = "bbcm-collapsed", arrowColors = document.createElement("style"); let startCollapsed = GM_getValue("bbcm-collapsed", false), colors = GM_getValue("bbcm-colors", defaultColors); GM_addStyle(` .wiki-content h1, .wiki-content h2, .wiki-content h3, .wiki-content h4, .wiki-content h5, .wiki-content h6 { position:relative; padding-right:.8em; cursor:pointer; } .wiki-content h1:after, .wiki-content h2:after, .wiki-content h3:after, .wiki-content h4:after, .wiki-content h5:after, .wiki-content h6:after { display:inline-block; position:absolute; right:0; top:calc(50% - .5em); font-size:.8em; content:"\u25bc"; } .wiki-content .${collapsed}:after { transform: rotate(90deg); } .bbcm-hidden { display:none !important; } `); function addColors() { arrowColors.textContent = ` .wiki-content h1:after { color:${colors[0]} } .wiki-content h2:after { color:${colors[1]} } .wiki-content h3:after { color:${colors[2]} } .wiki-content h4:after { color:${colors[3]} } .wiki-content h5:after { color:${colors[4]} } .wiki-content h6:after { color:${colors[5]} } `; } function toggle(el, shifted) { if (el) { el.classList.toggle(collapsed); let els; const name = el.nodeName || "", level = parseInt(name.replace(/[^\d]/, ""), 10), isCollapsed = el.classList.contains(collapsed); if (shifted) { // collapse all same level anchors els = $$(`.wiki-content ${name}`); for (el of els) { nextHeader(el, level, isCollapsed); } } else { nextHeader(el, level, isCollapsed); } removeSelection(); } } function nextHeader(el, level, isCollapsed) { el.classList.toggle(collapsed, isCollapsed); const selector = headers.slice(0, level).join(","), name = [collapsed, "bbcm-hidden"], els = []; el = el.nextElementSibling; while (el && !el.matches(selector)) { els[els.length] = el; el = el.nextElementSibling; } if (els.length) { if (isCollapsed) { els.forEach(el => { el.classList.add("bbcm-hidden"); }); } else { els.forEach(el => { el.classList.remove(...name); }); } } } function removeSelection() { // remove text selection - https://stackoverflow.com/a/3171348/145346 const sel = window.getSelection ? window.getSelection() : document.selection; if (sel) { if (sel.removeAllRanges) { sel.removeAllRanges(); } else if (sel.empty) { sel.empty(); } } } function addBinding() { document.addEventListener("click", event => { const target = event.target; if (target && headers.indexOf(target.nodeName || "") > -1) { // make sure the header is inside of markdown if (closest(".wiki-content", target)) { toggle(target, event.shiftKey); } } }); } function checkColors() { if (!colors || colors.length !== 6) { colors = [].concat(defaultColors); } } function init() { document.querySelector("head").appendChild(arrowColors); checkColors(); addColors(); addBinding(); } function $$(selectors, el) { return Array.from((el || document).querySelectorAll(selectors)); } function closest(selector, el) { while (el && el.nodeType === 1) { if (el.matches(selector)) { return el; } el = el.parentNode; } return null; } // Add GM options GM_registerMenuCommand("Set Bitbucket collapse markdown state", () => { const val = prompt( "Set initial state to (c)ollapsed or (e)xpanded (first letter only):", startCollapsed ? "collapsed" : "expanded" ); if (val !== null) { startCollapsed = /^c/i.test(val); GM_setValue("bbcm-collapsed", startCollapsed); console.log( `Bitbucket Collapse Markdown: Headers will ` + `${startCollapsed ? "be" : "not be"} initially collapsed` ); } }); GM_registerMenuCommand("Set Bitbucket collapse markdown colors", () => { let val = prompt("Set header arrow colors:", JSON.stringify(colors)); if (val !== null) { // allow pasting in a JSON format try { val = JSON.parse(val); if (val && val.length === 6) { colors = val; GM_setValue("bbcm-colors", colors); console.log("Bitbucket Collapse Markdown: colors set to", colors); addColors(); return; } console.error( "Bitbucket Collapse Markdown: invalid color definition (6 colors)", val ); // reset colors to default (in case colors variable is corrupted) checkColors(); } catch (err) { console.error("Bitbucket Collapse Markdown: invalid JSON"); } } }); init(); })();