Last active
April 25, 2026 05:43
-
-
Save milo-minderbinder/2f8f084e3a6ab3adfbf91f0d5b79b2c4 to your computer and use it in GitHub Desktop.
Tampermonkey user script that adds collapsable links for each heading, subheading, and tag (e.g. API methods, subtopics, etc.) in the right-hand navigation section.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name neovim-docs-helper | |
| // @namespace https://gist.github.com/milo-minderbinder/ | |
| // @version 0.2 | |
| // @description adds collapsable links for each heading, subheading, and tag (e.g. API methods, subtopics, etc.) in neovim user doc pages. | |
| // @author Chris Passarello | |
| // @match https://neovim.io/doc/user/* | |
| // @icon https://www.google.com/s2/favicons?sz=64&domain=neovim.io | |
| // @downloadURL https://gist.github.com/milo-minderbinder/2f8f084e3a6ab3adfbf91f0d5b79b2c4/raw/neovim-docs-helper.user.js | |
| // @updateURL https://gist.github.com/milo-minderbinder/2f8f084e3a6ab3adfbf91f0d5b79b2c4/raw/neovim-docs-helper.user.js | |
| // @run-at document-idle | |
| // @grant none | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| console.log('neovim-docs-helper'); | |
| function waitForElement(selector, f, delay = 500) { | |
| console.log(`waiting ${delay}ms for element with selector: ${selector}`); | |
| setTimeout(() => { | |
| const e = document.querySelector(selector); | |
| if (e != null) { | |
| console.log(`got element with selector "${selector}": ${e}`); | |
| return f(e); | |
| } | |
| waitForElement(selector, f, delay); | |
| }, delay); | |
| } | |
| function showHide(show, querySelectorRoot, selectors) { | |
| querySelectorRoot.querySelectorAll(selectors).forEach((e) => { | |
| if (show === true) { | |
| e.removeAttribute('hidden'); | |
| } else { | |
| e.setAttribute('hidden', true); | |
| } | |
| }); | |
| } | |
| /** | |
| * Create & append buttons to container that will toggle the 'hidden' attribute for matching Nodes. | |
| * | |
| * @param container {Element} to append the buttons to | |
| * @param querySelectorRoot {Element|Document} to match the selectors in | |
| * @param selectors {string} the CSS selectors to match the nodes which will be toggled by the buttons | |
| * @returns {Element} | |
| */ | |
| function createShowHideButtons(container, querySelectorRoot = document, selectors = 'ul.collapsable') { | |
| if (container == null) { | |
| container = document.createElement('div'); | |
| } | |
| for (const v of ['+', '-']) { | |
| const btn = document.createElement('a'); | |
| const id = v === '+' ? 'expandAll' : 'collapseAll'; | |
| // btn.setAttribute('id', id); | |
| btn.setAttribute('name', id); | |
| // btn.setAttribute('href', `#${btn.id}`); | |
| btn.setAttribute('href', document.location.href); | |
| btn.text = v; | |
| btn.addEventListener('click', (event) => { | |
| btn.setAttribute('href', document.location.href); | |
| // btn.text = btn.text + v; | |
| showHide(v === '+', querySelectorRoot, selectors); | |
| }); | |
| container.appendChild(btn); | |
| } | |
| return container; | |
| } | |
| function run() { | |
| const runSelector = 'div.col-narrow.toc'; | |
| waitForElement(runSelector, (_) => { | |
| const navDiv = document.querySelector(runSelector); | |
| const expandCollapseDiv = createShowHideButtons(null); | |
| navDiv.firstElementChild.before(expandCollapseDiv); | |
| const docLinks = Object.create(null); | |
| let headingList; | |
| let subheadingList; | |
| document.querySelectorAll( | |
| 'div.help-para>h2.help-heading[id], div.help-para>h3.help-heading[id], div.help-para .help-tag-right') | |
| .forEach((e) => { | |
| if (e.className === 'help-heading' && e.tagName === 'H2') { | |
| subheadingList = null; | |
| headingList = document.createElement('ul'); | |
| headingList.classList.add('collapsable'); | |
| docLinks[e.id] = headingList; | |
| //console.log(`${e.id}`); | |
| } else { | |
| const a = document.createElement('a'); | |
| a.href = `#${e.id}`; | |
| a.text = decodeURIComponent(e.id); | |
| const li = document.createElement('li'); | |
| li.appendChild(a); | |
| if (e.className === 'help-heading' && e.tagName === 'H3') { | |
| a.text = e.innerText; | |
| subheadingList = document.createElement('ul'); | |
| subheadingList.classList.add('collapsable'); | |
| li.appendChild(subheadingList); | |
| headingList.appendChild(li); | |
| //console.log(`\t${e.id}`); | |
| } else if (subheadingList != null) { | |
| subheadingList.appendChild(li); | |
| } else { | |
| headingList.appendChild(li); | |
| //console.log(`\t${e.id}`); | |
| } | |
| } | |
| }); | |
| document.querySelectorAll('div.help-toc-h1>a').forEach((e) => { | |
| const k = decodeURIComponent(e.hash).slice(1); | |
| if (docLinks[k] == null) { | |
| console.log(k, 'no docLinks for heading:', e); | |
| } else { | |
| e.after(docLinks[k]); | |
| e.parentElement.querySelectorAll('ul.collapsable').forEach((ul) => { | |
| const listToggles = createShowHideButtons(document.createElement('span'), ul.parentElement); | |
| ul.before(listToggles); | |
| listToggles.before(' '); | |
| }); | |
| } | |
| }); | |
| }); | |
| } | |
| run(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment