// ==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);
})();