Greasy Fork is available in English.
自动转换简繁中文(页面转简体,输入转繁体)- stomtian
当前为
// ==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); })();