MediaWiki:Timeless.js

来自「荏苒之境」
Sicusa留言 | 贡献2025年8月21日 (四) 17:33的版本

注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的更改的影响。

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5Ctrl-R(Mac为⌘-R
  • Google Chrome:Ctrl-Shift-R(Mac为⌘-Shift-R
  • Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5
/* 将为Timeless皮肤的用户加载此处的所有JavaScript  */

class HeadingTracker {
    constructor(rootElement) {
    	this.rootElement = rootElement
        this.headings = [];
        this.cacheExpired = true;
        this.init();
    }

    init() {
        this.collectHeadings();
        window.addEventListener('resize', () => this.cacheExpired = true);
        window.addEventListener('DOMContentLoaded', () => this.cacheExpired = true);
    }

    collectHeadings() {
        const rawHeadings = Array.from(this.rootElement.querySelectorAll('h1, h2, h3, h4, h5, h6'));
        this.headings = rawHeadings
            .map(heading => ({
                element: heading,
                top: heading.getBoundingClientRect().top + window.scrollY
            }))
            .sort((a, b) => a.top - b.top);
        this.cacheExpired = false;
    }

    getNearestHeadingAboveViewport() {
        if (this.cacheExpired) this.collectHeadings();

        const scrollY = window.scrollY + 60;
        let left = 0, right = this.headings.length - 1;
        let result = null;

        while (left <= right) {
            const mid = Math.floor((left + right) / 2);
            if (this.headings[mid].top <= scrollY) {
                result = this.headings[mid].element;
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return result;
    }
}

let toc = document.getElementById('toc');
let siteNavigation = document.getElementById('mw-site-navigation');

if (toc && siteNavigation) {
    toc.parentNode.removeChild(toc);
    siteNavigation.appendChild(toc);
    
    var ignoreScrollUpdate
    var lastHeading
    var setCurrentHeading
    
    let tocTextList = Array.from(toc.querySelectorAll(".toctext"))
    let tocTexts = new Map(tocTextList.map(t => {
    	let textContent = t.textContent
    	t.parentElement.onclick = () => {
    		ignoreScrollUpdate = true;
    		setCurrentHeading(textContent);
    	};
    	return [textContent, t];
    }));
    
    setCurrentHeading = heading => {
    	if (heading == lastHeading) {
    		return;
    	}
    	if (lastHeading) {
    		let text = tocTexts.get(lastHeading)
    		if (text) text.classList.remove('current-heading');
    	}
    	lastHeading = heading;
    	if (heading) {
    		let text = tocTexts.get(heading)
    		if (text) text.classList.add('current-heading');
    	}
    };
    
	let tracker = new HeadingTracker(
		document.getElementById('mw-content-text'));
	
	window.addEventListener('scroll', () => {
		if (window.innerWidth < 1100) {
			return;
		}
		if (ignoreScrollUpdate) {
			ignoreScrollUpdate = false;
			return;
		}
	    let heading = tracker.getNearestHeadingAboveViewport();
	    setCurrentHeading(heading ? heading.textContent : null);
	});
}