NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name MediaWiki Enhanced Reading // @namespace https://github.com/tomviner/mediawiki-userscripts/ // @version 0.3 // @description Improves MediaWiki reading experience with fixed TOC and better text width // @author Tom Viner // @license MIT // @homepageURL https://github.com/tomviner/mediawiki-userscripts/ // @match *://wiki.sohonet.co.uk/wiki/* // @run-at document-end // @noframes // ==/UserScript== /* jshint esversion: 6 */ (function() { 'use strict'; const style = document.createElement('style'); style.textContent = ` #bodyContent { max-width: 800px; } #toc { position: fixed; right: 0; top: 7em; z-index: 10000; background-color: rgba(249, 249, 249, 0.9); width: 300px; } #toc > ul { max-height: 80vh; overflow: auto; overscroll-behavior: contain; /* Prevents scroll chaining */ } .toctoggle { float: right; } /* Active section highlight */ li.toc-active > a { background-color: #e8f2ff; box-shadow: -3px 0 0 #3366cc; } `; document.head.appendChild(style); function updateActiveTocSection() { const headlines = Array.from(document.querySelectorAll('.mw-headline')); const toc = document.getElementById('toc'); if (!headlines.length || !toc) return; // Find which headline is most visible const viewportMiddle = window.innerHeight / 400; let closestHeadline = null; let minDistance = Infinity; headlines.forEach(headline => { const rect = headline.getBoundingClientRect(); const distance = Math.abs(rect.top - viewportMiddle); if (distance < minDistance) { minDistance = distance; closestHeadline = headline; } }); if (!closestHeadline) return; // Update active section document.querySelectorAll('#toc li.toc-active').forEach(item => { item.classList.remove('toc-active'); }); const activeLink = document.querySelector(`#toc a[href="#${closestHeadline.id}"]`); if (activeLink) { const activeLi = activeLink.closest('li'); if (activeLi) { activeLi.classList.add('toc-active'); activeLi.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } } } // Add scroll event listener with debouncing let ticking = false; window.addEventListener('scroll', () => { if (!ticking) { window.requestAnimationFrame(() => { updateActiveTocSection(); ticking = false; }); ticking = true; } }); // Initialize setTimeout(updateActiveTocSection, 1000); })();