Greasy Fork is available in English.

记录位置

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

// ==UserScript==
// @name         记录位置
// @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();
})();