POE2 Trade ST工具箱

自动转换简繁中文(页面转简体,输入转繁体)- stomtian

目前為 2025-01-07 提交的版本,檢視 最新版本

// ==UserScript==
// @name         POE2 Trade ST工具箱
// @namespace    http://tampermonkey.net/
// @version      2.0.0
// @description  自动转换简繁中文(页面转简体,输入转繁体)- stomtian
// @author       stomtian
// @match        https://www.pathofexile.com/trade*
// @match        https://pathofexile.com/trade*
// @grant        GM_getValue
// @grant        GM_setValue
// @license      MIT
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/full.min.js
// @run-at       document-idle
// @noframes     true
// ==/UserScript==

(function() {
    'use strict';

    const STATE = {
        pageSimplified: GM_getValue('pageSimplified', true),
        inputTraditional: GM_getValue('inputTraditional', true),
        originalTexts: new WeakMap(),
        configs: GM_getValue('savedConfigs', {})  // 保存的配置
    };

    const CUSTOM_DICT = [
        ['回覆', '回復'],
        ['恢覆', '恢復'],
    ];

    const CONFIG = {
        maxAttempts: 50,
        checkInterval: 100,
        inputSelector: 'input[type="text"]:not(#config-name):not(#config-category), textarea',
        textSelector: '.search-bar, .search-advanced-pane, .results-container, .resultset',
        excludeSelector: 'script, style, input, textarea, select, .converter-controls'
    };

    function waitForElement(selector) {
        return new Promise(resolve => {
            if (document.querySelector(selector)) {
                resolve();
                return;
            }

            const observer = new MutationObserver(() => {
                try {
                    if (document.querySelector(selector)) {
                        observer.disconnect();
                        resolve();
                    }
                } catch (error) {}
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        });
    }

    function waitForOpenCC() {
        return new Promise((resolve, reject) => {
            if (typeof window.OpenCC !== 'undefined') {
                resolve(window.OpenCC);
                return;
            }

            let attempts = 0;
            const checkInterval = setInterval(() => {
                if (typeof window.OpenCC !== 'undefined') {
                    clearInterval(checkInterval);
                    resolve(window.OpenCC);
                    return;
                }

                if (++attempts >= CONFIG.maxAttempts) {
                    clearInterval(checkInterval);
                    reject(new Error('OpenCC 加载超时'));
                }
            }, CONFIG.checkInterval);
        });
    }

    function createConverters(OpenCC) {
        const toTraditional = OpenCC.ConverterFactory(
            OpenCC.Locale.from.cn,
            OpenCC.Locale.to.tw.concat([CUSTOM_DICT])
        );

        const toSimplified = OpenCC.ConverterFactory(
            OpenCC.Locale.from.tw,
            OpenCC.Locale.to.cn
        );

        return { toTraditional, toSimplified };
    }

    function createInputHandler(converter) {
        return function handleInput(e) {
            if (!STATE.inputTraditional) return;
            if (!e?.target?.value) return;

            const cursorPosition = e.target.selectionStart;
            const text = e.target.value;

            requestAnimationFrame(() => {
                try {
                    const convertedText = converter.toTraditional(text);
                    if (text === convertedText) return;

                    e.target.value = convertedText;

                    if (typeof cursorPosition === 'number') {
                        e.target.setSelectionRange(cursorPosition, cursorPosition);
                    }

                    e.target.dispatchEvent(new Event('input', {
                        bubbles: true,
                        cancelable: true
                    }));
                } catch (error) {}
            });
        };
    }

    function convertPageText(converter, forceRestore = false) {
        if (!STATE.pageSimplified && !forceRestore) return;

        try {
            const elements = document.querySelectorAll(CONFIG.textSelector);
            if (!elements.length) return;

            elements.forEach(root => {
                try {
                    const walker = document.createTreeWalker(
                        root,
                        NodeFilter.SHOW_TEXT,
                        {
                            acceptNode: function(node) {
                                try {
                                    if (!node.textContent.trim()) return NodeFilter.FILTER_REJECT;

                                    const parent = node.parentNode;
                                    if (!parent) return NodeFilter.FILTER_REJECT;

                                    if (parent.closest?.(CONFIG.excludeSelector)) {
                                        return NodeFilter.FILTER_REJECT;
                                    }

                                    return NodeFilter.FILTER_ACCEPT;
                                } catch (error) {
                                    return NodeFilter.FILTER_REJECT;
                                }
                            }
                        }
                    );

                    let node;
                    while (node = walker.nextNode()) {
                        try {
                            const text = node.textContent.trim();
                            if (!text) continue;

                            if (!STATE.originalTexts.has(node)) {
                                STATE.originalTexts.set(node, text);
                            }

                            if (STATE.pageSimplified) {
                                const convertedText = converter.toSimplified(text);
                                if (text !== convertedText) {
                                    node.textContent = convertedText;
                                }
                            } else {
                                const originalText = STATE.originalTexts.get(node);
                                if (originalText && node.textContent !== originalText) {
                                    node.textContent = originalText;
                                }
                            }
                        } catch (error) {}
                    }
                } catch (error) {}
            });
        } catch (error) {}
    }

    function attachInputListener(handleInput) {
        try {
            const inputElements = document.querySelectorAll(CONFIG.inputSelector);

            inputElements.forEach(element => {
                try {
                    if (element?.dataset?.hasConverter) return;
                    element.addEventListener('input', handleInput);
                    element.dataset.hasConverter = 'true';
                } catch (error) {}
            });
        } catch (error) {}
    }

    function createObserver(handleInput, converter) {
        return new MutationObserver(mutations => {
            try {
                let needsTextConversion = false;

                for (const mutation of mutations) {
                    if (!mutation.addedNodes.length) continue;

                    try {
                        const hasNewInputs = Array.from(mutation.addedNodes).some(node => {
                            try {
                                return node.querySelectorAll?.(CONFIG.inputSelector)?.length > 0;
                            } catch (error) {
                                return false;
                            }
                        });

                        if (hasNewInputs) {
                            attachInputListener(handleInput);
                        }

                        needsTextConversion = true;
                    } catch (error) {}
                }

                if (needsTextConversion) {
                    setTimeout(() => convertPageText(converter), 100);
                }
            } catch (error) {}
        });
    }

    function createConfigModal() {
        const modalHtml = `
            <div id="config-modal" style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
                background: #1a1a1a; padding: 20px; border-radius: 8px; z-index: 10000; min-width: 600px; color: #fff;">
                <div style="display: flex; justify-content: space-between; margin-bottom: 15px;">
                    <h3 style="margin: 0;">ST工具箱</h3>
                    <button id="close-config-modal" style="background: none; border: none; color: #fff; cursor: pointer;">✕</button>
                </div>

                <!-- 功能开关部分 -->
                <div style="margin-bottom: 20px; padding: 15px; background: #2d2d2d; border-radius: 4px;">
                    <div style="font-weight: bold; margin-bottom: 10px; color: #4a90e2;">功能开关</div>
                    <div style="display: flex; gap: 10px;">
                        <button id="toggle-page-simplified" style="padding: 5px 10px; background: ${STATE.pageSimplified ? '#4a90e2' : '#3d3d3d'}; border: none; color: #fff; cursor: pointer; border-radius: 3px; transition: background-color 0.2s; flex: 1;">
                            ${STATE.pageSimplified ? '关闭页面简体' : '开启页面简体'}
                        </button>
                        <button id="toggle-input-traditional" style="padding: 5px 10px; background: ${STATE.inputTraditional ? '#4a90e2' : '#3d3d3d'}; border: none; color: #fff; cursor: pointer; border-radius: 3px; transition: background-color 0.2s; flex: 1;">
                            ${STATE.inputTraditional ? '关闭输入繁体' : '开启输入繁体'}
                        </button>
                    </div>
                </div>

                <!-- 配置管理部分 -->
                <div style="padding: 15px; background: #2d2d2d; border-radius: 4px;">
                    <div style="font-weight: bold; margin-bottom: 10px; color: #4a90e2;">配置管理</div>
                    <div style="margin-bottom: 15px;">
                        <input type="text" id="config-name" placeholder="配置名称"
                            style="padding: 5px; margin-right: 10px; background: #3d3d3d; border: 1px solid #444; color: #fff; width: 200px;">
                        <div class="custom-select" style="display: inline-block; position: relative; width: 150px; margin-right: 10px;">
                            <input type="text" id="config-category" placeholder="选择或输入分类"
                                style="padding: 5px; background: #3d3d3d; border: 1px solid #444; color: #fff; width: 100%; cursor: pointer;">
                            <div id="category-dropdown" style="display: none; position: absolute; top: 100%; left: 0; width: 100%;
                                background: #3d3d3d; border: 1px solid #444; border-top: none; max-height: 200px; overflow-y: auto; z-index: 1000;">
                            </div>
                        </div>
                        <button id="save-config" style="padding: 5px 10px; background: #4a90e2; border: none; color: #fff; cursor: pointer;">
                            保存当前配置
                        </button>
                    </div>
                    <div id="category-tabs" style="margin-bottom: 15px; border-bottom: 1px solid #444; padding-bottom: 10px;"></div>
                    <div id="config-list" style="max-height: 300px; overflow-y: auto;">
                    </div>
                </div>
            </div>
        `;

        document.body.insertAdjacentHTML('beforeend', modalHtml);

        // 添加遮罩
        const overlay = document.createElement('div');
        overlay.id = 'config-modal-overlay';
        overlay.style.cssText = `
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.5);
            z-index: 9999;
        `;
        document.body.appendChild(overlay);

        // 添加下拉列表样式
        const style = document.createElement('style');
        style.textContent = `
            #category-dropdown::-webkit-scrollbar {
                width: 8px;
            }
            #category-dropdown::-webkit-scrollbar-track {
                background: #1a1a1a;
            }
            #category-dropdown::-webkit-scrollbar-thumb {
                background: #444;
                border-radius: 4px;
            }
            #category-dropdown::-webkit-scrollbar-thumb:hover {
                background: #555;
            }
            .dropdown-item {
                padding: 8px 12px;
                cursor: pointer;
                transition: background-color 0.2s;
            }
            .dropdown-item:hover {
                background: #3d3d3d;
            }
        `;
        document.head.appendChild(style);

        setupConfigModalEvents();
        updateConfigList();
        setupCategoryDropdown();
    }

    function setupCategoryDropdown() {
        const categoryInput = document.getElementById('config-category');
        const dropdown = document.getElementById('category-dropdown');
        let isDropdownVisible = false;

        function updateDropdown() {
            const categories = Object.keys(STATE.configs);
            const inputValue = categoryInput.value.toLowerCase();

            dropdown.innerHTML = '';

            categories
                .filter(category => category.toLowerCase().includes(inputValue))
                .forEach(category => {
                    const item = document.createElement('div');
                    item.className = 'dropdown-item';
                    item.textContent = category;
                    item.onclick = () => {
                        categoryInput.value = category;
                        hideDropdown();
                    };
                    dropdown.appendChild(item);
                });

            if (categories.length === 0) {
                const item = document.createElement('div');
                item.className = 'dropdown-item';
                item.textContent = '无已有分类';
                item.style.color = '#666';
                dropdown.appendChild(item);
            }
        }

        function showDropdown() {
            updateDropdown();
            dropdown.style.display = 'block';
            isDropdownVisible = true;
        }

        function hideDropdown() {
            dropdown.style.display = 'none';
            isDropdownVisible = false;
        }

        categoryInput.addEventListener('focus', showDropdown);
        categoryInput.addEventListener('input', updateDropdown);

        // 点击外部区域时隐藏下拉列表
        document.addEventListener('click', (e) => {
            const isClickInside = categoryInput.contains(e.target) || dropdown.contains(e.target);
            if (!isClickInside && isDropdownVisible) {
                hideDropdown();
            }
        });

        // 阻止事件冒泡,避免点击下拉列表时触发外部点击事件
        dropdown.addEventListener('click', (e) => {
            e.stopPropagation();
        });
    }

    function setupConfigModalEvents() {
        const modal = document.getElementById('config-modal');
        const overlay = document.getElementById('config-modal-overlay');
        const closeBtn = document.getElementById('close-config-modal');
        const saveBtn = document.getElementById('save-config');
        const togglePageBtn = document.getElementById('toggle-page-simplified');
        const toggleInputBtn = document.getElementById('toggle-input-traditional');

        closeBtn.addEventListener('click', () => {
            modal.style.display = 'none';
            overlay.style.display = 'none';
        });

        overlay.addEventListener('click', () => {
            modal.style.display = 'none';
            overlay.style.display = 'none';
        });

        togglePageBtn.addEventListener('click', () => {
            STATE.pageSimplified = !STATE.pageSimplified;
            GM_setValue('pageSimplified', STATE.pageSimplified);
            togglePageBtn.textContent = STATE.pageSimplified ? '关闭页面简体' : '开启页面简体';
            togglePageBtn.style.backgroundColor = STATE.pageSimplified ? '#4a90e2' : '#2d2d2d';
            convertPageText(window.converter, true);
        });

        toggleInputBtn.addEventListener('click', () => {
            STATE.inputTraditional = !STATE.inputTraditional;
            GM_setValue('inputTraditional', STATE.inputTraditional);
            toggleInputBtn.textContent = STATE.inputTraditional ? '关闭输入繁体' : '开启输入繁体';
            toggleInputBtn.style.backgroundColor = STATE.inputTraditional ? '#4a90e2' : '#2d2d2d';
        });

        saveBtn.addEventListener('click', saveCurrentConfig);
    }

    function saveCurrentConfig() {
        const name = document.getElementById('config-name').value.trim();
        const category = document.getElementById('config-category').value.trim();

        if (!name) {
            alert('请输入配置名称');
            return;
        }

        if (!category) {
            alert('请输入分类名称');
            return;
        }

        if (!STATE.configs[category]) {
            STATE.configs[category] = {};
        }

        STATE.configs[category][name] = {
            url: window.location.href,
            timestamp: new Date().toISOString()
        };

        GM_setValue('savedConfigs', STATE.configs);
        updateConfigList();
        updateCategoryDatalist();

        document.getElementById('config-name').value = '';
        document.getElementById('config-category').value = '';
    }

    function updateConfigList() {
        const configList = document.getElementById('config-list');
        const categoryTabs = document.getElementById('category-tabs');
        configList.innerHTML = '';
        categoryTabs.innerHTML = '';

        // 获取所有分类
        const categories = Object.keys(STATE.configs);

        // 如果没有配置,显示提示信息
        if (categories.length === 0) {
            configList.innerHTML = '<div style="text-align: center; color: #666;">暂无保存的配置</div>';
            return;
        }

        // 创建标签
        categories.forEach((category, index) => {
            const tabButton = document.createElement('button');
            tabButton.textContent = category;
            tabButton.style.cssText = `
                background: ${index === 0 ? '#4a90e2' : '#3d3d3d'};
                border: none;
                color: #fff;
                padding: 5px 15px;
                cursor: pointer;
                border-radius: 3px;
                transition: background-color 0.2s;
                margin-right: 10px;
            `;
            tabButton.dataset.category = category;
            tabButton.title = '双击删除分类';

            tabButton.addEventListener('click', (e) => {
                document.querySelectorAll('#category-tabs button[data-category]').forEach(btn => {
                    btn.style.backgroundColor = '#3d3d3d';
                });
                tabButton.style.backgroundColor = '#4a90e2';
                showCategoryConfigs(category);
            });

            tabButton.addEventListener('dblclick', (e) => {
                e.stopPropagation();
                deleteCategory(category);
            });

            categoryTabs.appendChild(tabButton);
        });

        // 默认显示第一个分类的配置
        showCategoryConfigs(categories[0]);
    }

    function deleteCategory(category) {
        const configCount = Object.keys(STATE.configs[category]).length;
        if (confirm(`确定要删除分类 "${category}" 及其包含的 ${configCount} 个配置吗?`)) {
            delete STATE.configs[category];
            GM_setValue('savedConfigs', STATE.configs);
            updateConfigList();
        }
    }

    function showCategoryConfigs(category) {
        const configList = document.getElementById('config-list');
        configList.innerHTML = '';
        const configs = STATE.configs[category];

        Object.entries(configs).forEach(([name, data]) => {
            const configItem = document.createElement('div');
            configItem.style.cssText = `
                display: grid;
                grid-template-columns: 1fr auto auto auto;
                align-items: center;
                padding: 8px;
                margin: 5px 0;
                background: #3d3d3d;
                border-radius: 4px;
                gap: 10px;
            `;

            const nameSpan = document.createElement('span');
            nameSpan.textContent = name;
            nameSpan.style.cssText = `
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
            `;

            const loadBtn = document.createElement('button');
            loadBtn.textContent = '读取';
            loadBtn.style.cssText = `
                background: #4a90e2;
                border: none;
                color: #fff;
                padding: 3px 12px;
                cursor: pointer;
                border-radius: 3px;
                transition: background-color 0.2s;
            `;
            loadBtn.onclick = () => loadConfig(data.url);

            const updateBtn = document.createElement('button');
            updateBtn.textContent = '更新';
            updateBtn.style.cssText = `
                background: #27ae60;
                border: none;
                color: #fff;
                padding: 3px 12px;
                cursor: pointer;
                border-radius: 3px;
                transition: background-color 0.2s;
            `;
            updateBtn.onclick = (e) => {
                e.stopPropagation();
                updateConfig(category, name);
            };

            const deleteBtn = document.createElement('button');
            deleteBtn.textContent = '删除';
            deleteBtn.style.cssText = `
                background: #e74c3c;
                border: none;
                color: #fff;
                padding: 3px 12px;
                cursor: pointer;
                border-radius: 3px;
                transition: background-color 0.2s;
            `;
            deleteBtn.onclick = (e) => {
                e.stopPropagation();
                deleteConfig(category, name);
            };

            configItem.appendChild(nameSpan);
            configItem.appendChild(loadBtn);
            configItem.appendChild(updateBtn);
            configItem.appendChild(deleteBtn);
            configList.appendChild(configItem);
        });
    }

    function loadConfig(url) {
        window.location.href = url;
    }

    function deleteConfig(category, name) {
        if (confirm(`确定要删除配置 "${name}" 吗?`)) {
            delete STATE.configs[category][name];
            if (Object.keys(STATE.configs[category]).length === 0) {
                delete STATE.configs[category];
            }
            GM_setValue('savedConfigs', STATE.configs);
            updateConfigList();
        }
    }

    function createConfigButton() {
        const floatingButton = document.createElement('div');
        floatingButton.style.cssText = `
            position: fixed;
            right: 20px;
            top: 50%;
            transform: translateY(-50%);
            width: 50px;
            height: 50px;
            background: linear-gradient(135deg, #2c3137 0%, #1a1f24 100%);
            border-radius: 25px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            color: #c0984e;
            font-weight: bold;
            font-family: 'Fontin SmallCaps', Arial, sans-serif;
            font-size: 18px;
            box-shadow: 0 0 10px rgba(0,0,0,0.5),
                        inset 0 0 2px rgba(192, 152, 78, 0.3),
                        0 0 15px rgba(192, 152, 78, 0.2);
            border: 1px solid rgba(192, 152, 78, 0.3);
            z-index: 9998;
            transition: all 0.3s ease;
            user-select: none;
            touch-action: none;
            text-shadow: 0 0 5px rgba(192, 152, 78, 0.5);
        `;
        floatingButton.textContent = 'ST';
        floatingButton.title = 'ST工具箱 (按住可拖动)';

        // 添加悬停效果
        floatingButton.addEventListener('mouseenter', () => {
            if (!isDragging) {
                floatingButton.style.transform = 'translateY(-50%) scale(1.1)';
                floatingButton.style.boxShadow = `
                    0 0 15px rgba(0,0,0,0.5),
                    inset 0 0 3px rgba(192, 152, 78, 0.5),
                    0 0 20px rgba(192, 152, 78, 0.3)
                `;
                floatingButton.style.color = '#e3b76c';
                floatingButton.style.textShadow = '0 0 8px rgba(192, 152, 78, 0.7)';
                floatingButton.style.border = '1px solid rgba(192, 152, 78, 0.5)';

                // 如果按钮被隐藏,则显示出来
                if (isHidden) {
                    showButton();
                }
            }
        });

        floatingButton.addEventListener('mouseleave', () => {
            if (!isDragging) {
                floatingButton.style.transform = 'translateY(-50%) scale(1)';
                floatingButton.style.boxShadow = `
                    0 0 10px rgba(0,0,0,0.5),
                    inset 0 0 2px rgba(192, 152, 78, 0.3),
                    0 0 15px rgba(192, 152, 78, 0.2)
                `;
                floatingButton.style.color = '#c0984e';
                floatingButton.style.textShadow = '0 0 5px rgba(192, 152, 78, 0.5)';
                floatingButton.style.border = '1px solid rgba(192, 152, 78, 0.3)';

                // 检查是否需要隐藏按钮
                checkAndHideButton();
            }
        });

        // 添加拖拽功能
        let isDragging = false;
        let startX, startY;
        let lastX = 0, lastY = 0;
        let dragDistance = 0;
        let mouseDownTime = 0;
        let isHidden = false;

        function dragStart(e) {
            isDragging = true;
            dragDistance = 0;
            mouseDownTime = Date.now();
            startX = e.clientX - lastX;
            startY = e.clientY - lastY;
            floatingButton.style.transition = 'none';
            floatingButton.style.transform = 'none';
        }

        function drag(e) {
            if (!isDragging) return;
            e.preventDefault();

            const x = e.clientX - startX;
            const y = e.clientY - startY;

            // 计算拖动距离
            const dx = x - lastX;
            const dy = y - lastY;
            dragDistance += Math.sqrt(dx * dx + dy * dy);

            // 限制拖动范围
            const maxX = window.innerWidth - floatingButton.offsetWidth;
            const maxY = window.innerHeight - floatingButton.offsetHeight;
            lastX = Math.max(0, Math.min(x, maxX));
            lastY = Math.max(0, Math.min(y, maxY));

            floatingButton.style.left = lastX + 'px';
            floatingButton.style.top = lastY + 'px';
            floatingButton.style.right = 'auto';
        }

        function dragEnd(e) {
            if (!isDragging) return;

            const dragDuration = Date.now() - mouseDownTime;
            isDragging = false;
            floatingButton.style.transition = 'all 0.3s ease';

            // 调整位置,使按钮居中对齐边缘
            const buttonWidth = floatingButton.offsetWidth;
            const buttonHeight = floatingButton.offsetHeight;
            const windowWidth = window.innerWidth;
            const windowHeight = window.innerHeight;
            const threshold = 20;

            if (lastX < threshold) {
                // 左边缘
                lastX = 0;
            } else if (lastX + buttonWidth > windowWidth - threshold) {
                // 右边缘
                lastX = windowWidth - buttonWidth;
            }

            if (lastY < threshold) {
                // 上边缘
                lastY = 0;
            } else if (lastY + buttonHeight > windowHeight - threshold) {
                // 下边缘
                lastY = windowHeight - buttonHeight;
            }

            floatingButton.style.left = lastX + 'px';
            floatingButton.style.top = lastY + 'px';

            // 保存位置
            GM_setValue('floatingButtonX', lastX);
            GM_setValue('floatingButtonY', lastY);

            // 检查是否需要隐藏按钮
            checkAndHideButton();

            // 如果拖动距离小于5像素且时间小于200ms,则认为是点击
            if (dragDistance < 5 && dragDuration < 200) {
                document.getElementById('config-modal').style.display = 'block';
                document.getElementById('config-modal-overlay').style.display = 'block';
            }
        }

        function checkAndHideButton() {
            const threshold = 20; // 距离边缘多少像素时触发隐藏
            const buttonWidth = floatingButton.offsetWidth;
            const buttonHeight = floatingButton.offsetHeight;
            const buttonRight = lastX + buttonWidth;
            const buttonBottom = lastY + buttonHeight;
            const windowWidth = window.innerWidth;
            const windowHeight = window.innerHeight;

            // 检查各个边缘
            if (buttonRight > windowWidth - threshold) {
                // 右边缘
                hideButton('right');
            } else if (lastX < threshold) {
                // 左边缘
                hideButton('left');
            } else if (lastY < threshold) {
                // 上边缘
                hideButton('top');
            } else if (buttonBottom > windowHeight - threshold) {
                // 下边缘
                hideButton('bottom');
            }
        }

        function hideButton(direction) {
            isHidden = true;
            floatingButton.style.transition = 'all 0.3s ease';

            // 添加金光动画
            floatingButton.style.animation = 'none';
            floatingButton.offsetHeight; // 触发重绘
            floatingButton.style.animation = 'glowing 1.5s ease-in-out infinite';
            floatingButton.style.background = 'linear-gradient(135deg, #3c3c28 0%, #2a2a1c 100%)';

            switch (direction) {
                case 'right':
                    floatingButton.style.transform = 'translateY(-50%) translateX(75%)';
                    break;
                case 'left':
                    floatingButton.style.transform = 'translateY(-50%) translateX(-80%)';
                    break;
                case 'top':
                    floatingButton.style.transform = 'translateX(-50%) translateY(-80%)';
                    break;
                case 'bottom':
                    floatingButton.style.transform = 'translateX(-50%) translateY(80%)';
                    break;
            }
        }

        function showButton() {
            isHidden = false;
            floatingButton.style.transition = 'all 0.3s ease';
            floatingButton.style.animation = 'none';
            floatingButton.style.background = 'linear-gradient(135deg, #2c3137 0%, #1a1f24 100%)';

            // 根据当前位置判断显示方向
            const buttonWidth = floatingButton.offsetWidth;
            const buttonHeight = floatingButton.offsetHeight;
            const buttonRight = lastX + buttonWidth;
            const buttonBottom = lastY + buttonHeight;
            const windowWidth = window.innerWidth;
            const windowHeight = window.innerHeight;
            const threshold = 20;

            if (buttonRight > windowWidth - threshold) {
                floatingButton.style.transform = 'translateY(-50%) translateX(0)';
            } else if (lastX < threshold) {
                floatingButton.style.transform = 'translateY(-50%) translateX(0)';
            } else if (lastY < threshold) {
                floatingButton.style.transform = 'translateX(-50%) translateY(0)';
            } else if (buttonBottom > windowHeight - threshold) {
                floatingButton.style.transform = 'translateX(-50%) translateY(0)';
            } else {
                floatingButton.style.transform = 'none';
            }
        }

        // 添加金光动画样式
        const glowingStyle = document.createElement('style');
        glowingStyle.textContent = `
            @keyframes glowing {
                0% {
                    box-shadow: 0 0 15px rgba(0,0,0,0.5),
                                inset 0 0 4px rgba(192, 152, 78, 0.5),
                                0 0 20px rgba(192, 152, 78, 0.4),
                                0 0 40px rgba(192, 152, 78, 0.2);
                    border-color: rgba(192, 152, 78, 0.5);
                    color: #e3b76c;
                    text-shadow: 0 0 10px rgba(192, 152, 78, 0.8);
                }
                50% {
                    box-shadow: 0 0 20px rgba(0,0,0,0.5),
                                inset 0 0 8px rgba(192, 152, 78, 0.8),
                                0 0 35px rgba(192, 152, 78, 0.6),
                                0 0 60px rgba(192, 152, 78, 0.4),
                                0 0 80px rgba(192, 152, 78, 0.2);
                    border-color: rgba(192, 152, 78, 1);
                    color: #ffd700;
                    text-shadow: 0 0 15px rgba(255, 215, 0, 1);
                }
                100% {
                    box-shadow: 0 0 15px rgba(0,0,0,0.5),
                                inset 0 0 4px rgba(192, 152, 78, 0.5),
                                0 0 20px rgba(192, 152, 78, 0.4),
                                0 0 40px rgba(192, 152, 78, 0.2);
                    border-color: rgba(192, 152, 78, 0.5);
                    color: #e3b76c;
                    text-shadow: 0 0 10px rgba(192, 152, 78, 0.8);
                }
            }
        `;
        document.head.appendChild(glowingStyle);

        // 监听窗口大小变化
        window.addEventListener('resize', () => {
            if (!isDragging) {
                // 确保按钮不会超出窗口
                const maxX = window.innerWidth - floatingButton.offsetWidth;
                const maxY = window.innerHeight - floatingButton.offsetHeight;
                lastX = Math.min(lastX, maxX);
                lastY = Math.min(lastY, maxY);

                floatingButton.style.left = lastX + 'px';
                floatingButton.style.top = lastY + 'px';

                // 检查是否需要隐藏按钮
                checkAndHideButton();
            }
        });

        floatingButton.addEventListener('mousedown', dragStart);
        document.addEventListener('mousemove', drag);
        document.addEventListener('mouseup', dragEnd);

        // 恢复上次保存的位置
        const savedX = GM_getValue('floatingButtonX');
        const savedY = GM_getValue('floatingButtonY');
        if (savedX !== undefined && savedY !== undefined) {
            lastX = savedX;
            lastY = savedY;
            floatingButton.style.right = 'auto';
            floatingButton.style.top = lastY + 'px';
            floatingButton.style.left = lastX + 'px';
            floatingButton.style.transform = 'none';

            // 检查初始位置是否需要隐藏
            setTimeout(checkAndHideButton, 100);
        }

        return floatingButton;
    }

    function createControls() {
        const floatingButton = createConfigButton();
        document.body.appendChild(floatingButton);
    }

    function watchSearchResults(converter) {
        let lastUrl = location.href;
        const urlObserver = setInterval(() => {
            if (location.href !== lastUrl) {
                lastUrl = location.href;
                STATE.originalTexts = new WeakMap();
                setTimeout(() => convertPageText(converter), 500);
            }
        }, 100);

        const resultObserver = new MutationObserver((mutations) => {
            let needsConversion = false;
            for (const mutation of mutations) {
                if (mutation.type === 'childList' || mutation.type === 'characterData') {
                    needsConversion = true;
                    break;
                }
            }
            if (needsConversion) {
                setTimeout(() => convertPageText(converter), 100);
            }
        });

        const resultsContainer = document.querySelector('.results-container');
        if (resultsContainer) {
            resultObserver.observe(resultsContainer, {
                childList: true,
                subtree: true,
                characterData: true
            });
        }
    }

    async function init() {
        try {
            await waitForElement('.search-bar');

            const OpenCC = await waitForOpenCC();
            const converter = createConverters(OpenCC);
            window.converter = converter;

            const handleInput = createInputHandler(converter);

            const observer = createObserver(handleInput, converter);
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });

            attachInputListener(handleInput);
            createConfigModal(); // 创建配置管理模态框
            createControls();

            if (STATE.pageSimplified) {
                convertPageText(converter);
            }

            watchSearchResults(converter);

            setInterval(() => {
                if (STATE.pageSimplified) {
                    convertPageText(converter);
                }
            }, 1000);

        } catch (error) {}
    }

    function updateConfig(category, name) {
        if (confirm(`确定要用当前页面更新配置 "${name}" 吗?`)) {
            STATE.configs[category][name] = {
                url: window.location.href,
                timestamp: new Date().toISOString()
            };
            GM_setValue('savedConfigs', STATE.configs);
            updateConfigList();
        }
    }

    setTimeout(init, 2000);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址