FreshRSS Double-Tap and Auto-Scroll

Double-tap to close active articles and auto-scroll to the active article in FreshRSS

当前为 2025-02-04 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         FreshRSS Double-Tap and Auto-Scroll
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Double-tap to close active articles and auto-scroll to the active article in FreshRSS
// @author       Your Name
// @homepage     https://greasyfork.org/en/scripts/525912
// @match        http://192.168.1.2:1030/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    
    // Debug mode
    const DEBUG = false;
    
    function debugLog(message) {
        if (DEBUG) {
            console.log(`[FreshRSS Script]: ${message}`);
        }
    }
    
    debugLog('Script loaded');

    // Function to scroll to element
    function scrollToElement(element) {
        if (element) {
            const header = document.querySelector('header');
            const headerHeight = header ? header.offsetHeight : 0;
            const elementPosition = element.getBoundingClientRect().top + window.pageYOffset;
            const offsetPosition = elementPosition - headerHeight - 10;

            window.scrollTo({
                top: offsetPosition,
                behavior: 'smooth'
            });
            debugLog('Scrolling to element: ' + element.id);
        }
    }

    // Handle double-tap to close
    document.addEventListener('dblclick', function(event) {
        const interactiveElements = ['A', 'BUTTON', 'INPUT', 'TEXTAREA', 'SELECT', 'LABEL'];
        if (interactiveElements.includes(event.target.tagName)) {
            debugLog('Ignored double-tap on interactive element');
            return;
        }

        const activeElement = event.target.closest('.flux.active');
        if (activeElement) {
            activeElement.classList.remove('active');
            debugLog('Closed article via double-tap');
            // scrollToElement(activeElement);
            activeElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
        }
    });

    // Monitor for new active articles using both click and mutation events
    document.addEventListener('click', function(event) {
        const fluxElement = event.target.closest('.flux');
        if (fluxElement) {
            // Use a shorter delay and check multiple times
            const checkInterval = setInterval(() => {
                if (fluxElement.classList.contains('active')) {
                    debugLog('Article became active via click');
                    scrollToElement(fluxElement);
                    clearInterval(checkInterval);
                }
            }, 100); // Check every 100ms
            
            // Stop checking after 1 second to prevent infinite checking
            setTimeout(() => clearInterval(checkInterval), 1000);
        }
    });

    // Additional mutation observer to catch programmatic changes
    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            if (mutation.target.classList && mutation.target.classList.contains('flux')) {
                if (mutation.target.classList.contains('active')) {
                    debugLog('Article became active via mutation');
                    scrollToElement(mutation.target);
                }
            }
        });
    });

    // Start observing the document with the configured parameters
    observer.observe(document.body, {
        attributes: true,
        attributeFilter: ['class'],
        subtree: true
    });
})();