Scroll to Nearest Paragraph (← → keys) - No Skips

Scroll to nearest paragraph using ← and → keys, never skips paragraphs

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Scroll to Nearest Paragraph (← → keys) - No Skips
// @namespace    http://tampermonkey.net/
// @version      1.8
// @description  Scroll to nearest paragraph using ← and → keys, never skips paragraphs
// @author       Işık Barış Fidaner + ChatGPT Fix
// @match        *://zizekanalysis.wordpress.com/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    let paragraphs = [];
    let current = 0;
    let initialized = false;
    const offset = 50;

    function init() {
        paragraphs = Array.from(document.querySelectorAll('p'))
            .filter(p => p.offsetHeight > 0 && p.offsetParent !== null);
        setInitialIndex();
        initialized = true;
    }

    function setInitialIndex() {
        // Find the paragraph closest (but not below) current scroll position
        const viewportTop = window.scrollY;
        let found = false;
        for (let i = 0; i < paragraphs.length; i++) {
            const rect = paragraphs[i].getBoundingClientRect();
            const paragraphTop = window.scrollY + rect.top - offset;
            if (paragraphTop >= viewportTop - 1) {
                current = i;
                found = true;
                break;
            }
        }
        if (!found) current = paragraphs.length - 1;
    }

    function scrollToParagraph(index) {
        if (index >= 0 && index < paragraphs.length) {
            const rect = paragraphs[index].getBoundingClientRect();
            const scrollY = window.scrollY + rect.top - offset;
            window.scrollTo({
                top: Math.max(scrollY, 0),
                behavior: 'smooth'
            });
            current = index;
        }
    }

    // Only update index after a manual scroll, *not* after script scroll
    let manualScroll = true;
    let scrollTimeout;
    window.addEventListener('scroll', () => {
        if (!initialized) init();
        if (manualScroll) {
            clearTimeout(scrollTimeout);
            scrollTimeout = setTimeout(() => {
                setInitialIndex();
            }, 100);
        }
        manualScroll = true;
    });

    document.addEventListener('keydown', (e) => {
        // Ignore keypresses in input fields or editable areas
        const target = e.target;
        if (
            target.tagName === 'INPUT' ||
            target.tagName === 'TEXTAREA' ||
            target.isContentEditable
        ) {
            return;
        }

        if (!initialized) init();

        if (e.key === 'ArrowRight') {
            e.preventDefault();
            if (current < paragraphs.length - 1) {
                manualScroll = false;
                scrollToParagraph(current + 1);
            }
        } else if (e.key === 'ArrowLeft') {
            e.preventDefault();
            if (current > 0) {
                manualScroll = false;
                scrollToParagraph(current - 1);
            }
        } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
            setTimeout(() => setInitialIndex(), 0);
        }
    });
})();