字体与字号调整

🏷️ 支持按网站独立保存设置的字体控制工具

// ==UserScript==
// @name         字体与字号调整
// @namespace    http://tampermonkey.net/
// @version      2.0.3
// @description  🏷️ 支持按网站独立保存设置的字体控制工具
// @author       pcysanji
// @match        *://*/*
// @exclude      *://*.chatgpt.com/*
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

    const CONFIG_KEY_PREFIX = 'SiteFontControl_';
    const DEFAULT_FONTS = ['system-ui', 'Segoe UI', 'Microsoft YaHei', 'Arial', 'sans-serif'];
    
    // 获取当前网站标识
    const getSiteKey = () => {
        try {
            return new URL(window.location.href).hostname;
        } catch {
            return 'global';
        }
    };

    // 加载网站独立配置
    const loadConfig = () => {
        const defaultConfig = {
            baseScale: 1.0,
            step: 0.1,
            fontFamily: 'system-ui',
            dynamicWatch: true,
            intervalSec: 0
        };
        return GM_getValue(CONFIG_KEY_PREFIX + getSiteKey(), defaultConfig);
    };

    // 保存网站独立配置
    const saveConfig = (config) => {
        GM_setValue(CONFIG_KEY_PREFIX + getSiteKey(), config);
    };

    let config = loadConfig();
    let observer = null;
    let intervalTimer = null;
    let menuHandles = {};

    // 核心功能函数
    function saveOriginalStyles(element = document.body) {
        if (!element || element.nodeType !== Node.ELEMENT_NODE) return;

        if (!element.dataset.origFontSize) {
            element.dataset.origFontSize = getComputedStyle(element).fontSize;
        }
        if (!element.dataset.origFontFamily) {
            element.dataset.origFontFamily = getComputedStyle(element).fontFamily;
        }

        const processNodes = node => {
            if (node.shadowRoot) {
                node.shadowRoot.childNodes.forEach(saveOriginalStyles);
            }
            if (node.tagName === 'IFRAME') {
                try {
                    node.contentDocument?.body && saveOriginalStyles(node.contentDocument.body);
                } catch {}
            }
        };

        processNodes(element);
        element.childNodes.forEach(saveOriginalStyles);
    }

    function applySettings(element = document.body) {
        if (!element || element.nodeType !== Node.ELEMENT_NODE) return;

        // 应用字号缩放
        if (element.dataset.origFontSize) {
            const originalSize = element.dataset.origFontSize;
            const unit = originalSize.replace(/[\d.-]/g, '');
            const baseValue = parseFloat(originalSize);
            
            let baseSize = baseValue;
            if (unit === 'rem') {
                baseSize *= parseFloat(getComputedStyle(document.documentElement).fontSize);
            } else if (unit === 'em') {
                baseSize *= parseFloat(getComputedStyle(element.parentElement).fontSize);
            }
            
            element.style.fontSize = `${baseSize * config.baseScale}px`;
        }

        // 应用独立字体设置
        element.style.fontFamily = `${config.fontFamily}, ${element.dataset.origFontFamily}`;

        // 递归处理
        element.childNodes.forEach(child => requestAnimationFrame(() => applySettings(child)));
        if (element.shadowRoot) applySettings(element.shadowRoot);
        if (element.tagName === 'IFRAME') {
            try {
                element.contentDocument?.body && applySettings(element.contentDocument.body);
            } catch {}
        }
    }

    function restoreFontSize() {
        config.baseScale = 1.0;
        document.querySelectorAll('*').forEach(el => {
            el.style.removeProperty('font-size');
        });
        applySettings();
        saveConfig(config);
    }

    function restoreFontFamily() {
        config.fontFamily = 'system-ui';
        document.querySelectorAll('*').forEach(el => {
            el.style.removeProperty('font-family');
        });
        applySettings();
        saveConfig(config);
    }

    // 菜单系统
    function createMenu() {
        Object.values(menuHandles).forEach(id => GM_unregisterMenuCommand(id));
        menuHandles = {};

        // 状态显示
        menuHandles.status = GM_registerMenuCommand(
            `🌐 ${getSiteKey()} | 缩放: ${config.baseScale.toFixed(1)}x | 字体: ${config.fontFamily}`,
            () => {},
            { autoClose: false }
        );

        // 字号控制
        menuHandles.increase = GM_registerMenuCommand("🔠 增大字号 (+0.1)", () => {
            config.baseScale = Math.min(config.baseScale + config.step, 3.0);
            applySettings();
            saveConfig(config);
            createMenu();
        }, { autoClose: false });

        menuHandles.decrease = GM_registerMenuCommand("🔠 减小字号 (-0.1)", () => {
            config.baseScale = Math.max(config.baseScale - config.step, 0.5);
            applySettings();
            saveConfig(config);
            createMenu();
        }, { autoClose: false });

        // 字体控制
        menuHandles.font = GM_registerMenuCommand("🎨 设置当前网站字体", () => {
            const newFont = prompt(
                `当前网站:${getSiteKey()}\n推荐字体:${DEFAULT_FONTS.join(', ')}\n输入新字体名称:`,
                config.fontFamily
            );
            if (newFont) {
                config.fontFamily = newFont;
                applySettings();
                saveConfig(config);
                createMenu();
            }
        }, { autoClose: false });

        // 独立恢复功能
        menuHandles.resetSize = GM_registerMenuCommand("↔️ 恢复字号", () => {
            restoreFontSize();
            createMenu();
        }, { autoClose: false });

        menuHandles.resetFont = GM_registerMenuCommand("🔄 恢复字体", () => {
            restoreFontFamily();
            createMenu();
        }, { autoClose: false });

        // 高级设置
        menuHandles.settings = GM_registerMenuCommand("⚙️ 设置步长和定时刷新", () => {
            // 步长设置
            const newStep = parseFloat(prompt("设置当前网站调整步长 (0.1-1.0):", config.step));
            if (!isNaN(newStep) && newStep >= 0.1 && newStep <= 1) {
                config.step = newStep;
                saveConfig(config);
            }

            // 定时刷新
            const newInterval = parseInt(prompt("设置当前网站定时刷新间隔 (秒):", config.intervalSec));
            if (!isNaN(newInterval) && newInterval >= 0) {
                config.intervalSec = newInterval;
                initInterval();
                saveConfig(config);
            }
            createMenu();
        }, { autoClose: false });
    }

    // 辅助功能
    function initObserver() {
        observer?.disconnect();
        if (config.dynamicWatch) {
            observer = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    mutation.addedNodes.forEach(node => {
                        saveOriginalStyles(node);
                        applySettings(node);
                    });
                });
            });
            observer.observe(document.body, { subtree: true, childList: true });
        }
    }

    function initInterval() {
        clearInterval(intervalTimer);
        if (config.intervalSec > 0) {
            intervalTimer = setInterval(() => {
                saveOriginalStyles();
                applySettings();
            }, config.intervalSec * 1000);
        }
    }

    // 主初始化
    (function init() {
        const initProcedure = () => {
            saveOriginalStyles();
            applySettings();
            initObserver();
            initInterval();
            createMenu();
        };

        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', initProcedure);
        } else {
            initProcedure();
        }
    })();
})();