✨ 网页时光穿梭器 Pro

允许用户自定义快捷键记录滚动位置,记录快捷键默认 Ctrl+O,恢复位置快捷键默认 Ctrl+Shift+H。提供保存位置、恢复位置、一键清理记录的菜单选项。恢复时在记录中找到匹配的完整网址记录,跳转到最近的匹配网址并平滑滚动恢复位置。进入网页时检查是否有记录,如果有则自动跳转到保存位置,主页面不自动恢复位置。

当前为 2024-08-29 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         ✨ 网页时光穿梭器 Pro
// @version      3.1
// @description  允许用户自定义快捷键记录滚动位置,记录快捷键默认 Ctrl+O,恢复位置快捷键默认 Ctrl+Shift+H。提供保存位置、恢复位置、一键清理记录的菜单选项。恢复时在记录中找到匹配的完整网址记录,跳转到最近的匹配网址并平滑滚动恢复位置。进入网页时检查是否有记录,如果有则自动跳转到保存位置,主页面不自动恢复位置。
// @author       hiisme
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_notification
// @namespace https://greasyfork.org/users/217852
// ==/UserScript==

(async () => {
    const MAX_STORED_PAGES = 20;
    const DEFAULT_SAVE_SHORTCUT = 'Ctrl+O';
    const DEFAULT_RESTORE_SHORTCUT = 'Ctrl+Shift+H';

    let saveShortcut = await GM_getValue('saveShortcut', DEFAULT_SAVE_SHORTCUT);
    let restoreShortcut = await GM_getValue('restoreShortcut', DEFAULT_RESTORE_SHORTCUT);

    const getStorageKey = () => `scrollPosition_${window.location.href}`;

    const saveScrollPosition = async () => {
        const storedData = await GM_getValue('scrollPositions', {});
        storedData[getStorageKey()] = Math.round(window.scrollY);
        await GM_setValue('scrollPositions', storedData);
        manageStoredKeys(storedData);
        showNotification('滚动位置已保存');
    };

    const smoothScrollTo = (position) => {
        window.scrollTo({
            top: position,
            behavior: 'smooth'
        });
    };

    const restoreScrollPosition = async () => {
        const storedData = await GM_getValue('scrollPositions', {});
        const domain = new URL(window.location.href).origin;
        const keys = Object.keys(storedData);
        const closestMatch = keys
            .filter(key => key.includes(domain))
            .sort()
            .pop();

        if (closestMatch) {
            const targetUrl = closestMatch.split('_')[1];
            if (targetUrl !== window.location.href) {
                await GM_setValue('scrollPositionsToRestore', {
                    url: targetUrl,
                    position: storedData[closestMatch]
                });
                window.location.href = targetUrl;
            } else {
                smoothScrollTo(storedData[closestMatch]);
                showNotification('滚动位置已恢复');
            }
        } else {
            showNotification('未找到保存的位置');
        }
    };

    const clearAllPositions = async () => {
        await GM_setValue('scrollPositions', {});
        showNotification('所有滚动位置记录已清除');
    };

    const manageStoredKeys = (storedData) => {
        const keys = Object.keys(storedData);
        if (keys.length > MAX_STORED_PAGES) {
            keys
                .sort((a, b) => storedData[a] - storedData[b])
                .slice(0, keys.length - MAX_STORED_PAGES)
                .forEach(key => delete storedData[key]);
            GM_setValue('scrollPositions', storedData);
        }
    };

    const showNotification = (message) => {
        GM_notification({
            text: message,
            title: '脚本通知',
            timeout: 3000
        });
    };

    const isValidShortcut = (shortcut) => /^((Ctrl|Shift|Alt)\+)*[A-Z]$/.test(shortcut);

    const handleKeyboardEvent = (event) => {
        const modifierKeys = `${event.ctrlKey ? 'Ctrl+' : ''}${event.shiftKey ? 'Shift+' : ''}${event.altKey ? 'Alt+' : ''}${event.key.toUpperCase()}`;
        if (modifierKeys === saveShortcut) {
            saveScrollPosition();
            event.preventDefault();
        } else if (modifierKeys === restoreShortcut) {
            restoreScrollPosition();
            event.preventDefault();
        }
    };

    const setupEventListeners = () => {
        window.addEventListener('keydown', handleKeyboardEvent);
    };

    GM_registerMenuCommand('保存当前位置', saveScrollPosition);
    GM_registerMenuCommand('恢复保存的位置', restoreScrollPosition);
    GM_registerMenuCommand('清除所有位置记录', clearAllPositions);

    GM_registerMenuCommand('设置记录位置快捷键', async () => {
        const newSaveShortcut = prompt('请输入新的记录位置快捷键 (例如 Ctrl+O)', saveShortcut);
        if (newSaveShortcut && isValidShortcut(newSaveShortcut)) {
            await GM_setValue('saveShortcut', newSaveShortcut);
            saveShortcut = newSaveShortcut;
            alert(`记录位置快捷键已设置为: ${newSaveShortcut}`);
        } else {
            alert('快捷键格式无效,请使用 Ctrl、Shift、Alt 组合键加单个字母。');
        }
    });

    GM_registerMenuCommand('设置恢复位置快捷键', async () => {
        const newRestoreShortcut = prompt('请输入新的恢复位置快捷键 (例如 Ctrl+Shift+H)', restoreShortcut);
        if (newRestoreShortcut && isValidShortcut(newRestoreShortcut)) {
            await GM_setValue('restoreShortcut', newRestoreShortcut);
            restoreShortcut = newRestoreShortcut;
            alert(`恢复位置快捷键已设置为: ${newRestoreShortcut}`);
        } else {
            alert('快捷键格式无效,请使用 Ctrl、Shift、Alt 组合键加单个字母。');
        }
    });

    const checkForPendingRestore = async () => {
        const pendingData = await GM_getValue('scrollPositionsToRestore', null);
        if (pendingData) {
            await GM_setValue('scrollPositionsToRestore', null);
            window.addEventListener('load', () => {
                smoothScrollTo(pendingData.position);
                showNotification('滚动位置已恢复');
            }, { once: true });
            window.location.href = pendingData.url;
        }
    };

    const initialize = async () => {
        setupEventListeners();
        await checkForPendingRestore();
        window.addEventListener('load', async () => {
            const storedData = await GM_getValue('scrollPositions', {});
            const currentKey = getStorageKey();
            if (storedData[currentKey]) {
                smoothScrollTo(storedData[currentKey]);
                showNotification('滚动位置已恢复');
            }
        });
    };

    initialize();
})();