打开设置
'
+ '
'
+ ''
+ ''
+ '
';
document.body.appendChild(settingsPanel);
// 所有交互只修改 ui 副本
document.querySelectorAll('input[name="ms-btn-mode"]').forEach(function(el) {
el.onchange = function() { ui.btnMode = this.value; };
});
// 快捷键捕获:点击输入框后按下组合键自动记录(支持修饰键)
settingsPanel.addEventListener('keydown', function(e) {
var active = document.activeElement;
if (!active || !active.classList.contains('ms-keycapture') || !active._cap) return;
e.preventDefault();
e.stopPropagation();
if (['Control','Alt','Shift','Meta'].indexOf(e.key) >= 0) return;
var parts = [];
if (e.ctrlKey) parts.push('Ctrl');
if (e.altKey) parts.push('Alt');
if (e.shiftKey) parts.push('Shift');
var key = e.key;
if (key === ' ') key = 'Space';
else if (key.length > 1 && !/^F\d+$/.test(key)) return;
parts.push(key);
var combo = parts.join('+');
active.value = combo;
if (active.id === 'ms-set-pagekey') ui.pageShortcut = combo;
else if (active.id === 'ms-set-selkey') ui.selShortcut = combo;
else if (active.id === 'ms-set-opensettingskey') ui.openSettingsKey = combo;
active._cap = false;
active.blur();
}, true);
document.querySelectorAll('.ms-keycapture').forEach(function(inp) {
inp.addEventListener('focus', function() { this._cap = true; this.value = ''; this.placeholder = '点击后按键'; });
inp.addEventListener('blur', function() { this._cap = false; this.placeholder = '点击后按键'; });
});
document.getElementById('ms-set-sel').onchange = function() { ui.selTranslate = this.checked; };
document.getElementById('ms-set-auto').onchange = function() { ui.autoTranslate = this.checked; };
document.getElementById('ms-set-hide-menu').onchange = function() { ui.hideMenuEntry = this.checked; };
// 保存:写入真实 settings
document.getElementById('ms-set-save').onclick = function() {
settings = { ...ui };
saveSettings();
// 刷新按钮位置:触发现有的 mouseleave 事件处理器来应用新 btnMode
const btn = document.getElementById('ms-manual-trans-btn');
if (btn) btn.dispatchEvent(new Event('mouseleave'));
if (settings.selTranslate && !selBtn) {
createSelectionUI();
initSelectionEvents();
} else if (!settings.selTranslate && selBtn) {
if (selBtn.parentNode) selBtn.remove();
if (selResult.parentNode) selResult.remove();
selBtn = null;
selResult = null;
}
closeSettings();
};
// 取消:丢弃 ui,直接关闭
document.getElementById('ms-set-cancel').onclick = closeSettings;
}
function closeSettings() {
if (settingsPanel) { settingsPanel.remove(); settingsPanel = null; }
}
// ---------- 按钮 ----------
function createButton() {
if (document.getElementById('ms-manual-trans-btn')) return;
const btn = document.createElement('button');
btn.id = 'ms-manual-trans-btn';
btn.textContent = '翻译';
Object.assign(btn.style, {
fontSize: 'medium',
position: 'fixed',
top: '50%',
transform: 'translateY(-50%)',
zIndex: '999999',
padding: '10px 18px',
backgroundColor: '#4285f4',
color: 'white',
border: 'none',
borderRadius: '0 8px 8px 0',
transition: 'left 0.3s ease'
});
let hideLeft = '0px';
btn._isHovered = false;
function syncPosition() {
if (isTranslated || translateInProgress || (settings.btnMode === 'show' && everTranslated)) {
btn.style.left = '0px';
return;
}
if (btn._isHovered) {
btn.style.left = '0px';
} else {
btn.style.left = hideLeft;
}
}
function updateHideLeft() {
const showEdge = 10;
hideLeft = -(btn.offsetWidth - showEdge) + 'px';
syncPosition();
}
requestAnimationFrame(() => updateHideLeft());
if (typeof ResizeObserver !== 'undefined') {
new ResizeObserver(() => updateHideLeft()).observe(btn);
}
btn.addEventListener('mouseenter', () => { btn._isHovered = true; syncPosition(); });
btn.addEventListener('mouseleave', () => { btn._isHovered = false; syncPosition(); });
btn.addEventListener('click', () => {
if (translateInProgress) return;
if (isTranslated) {
restoreManual();
btn.textContent = '翻译';
btn.style.backgroundColor = '#4285f4';
syncPosition();
} else {
btn.textContent = '翻译中';
btn.style.backgroundColor = '#f0ad4e';
syncPosition();
updateBtnProgress(0, 0);
startManual((success, result) => {
clearBtnProgress();
if (success) {
btn.textContent = '恢复';
btn.style.backgroundColor = '#db4437';
} else {
btn.textContent = '翻译';
btn.style.backgroundColor = '#4285f4';
console.error('翻译失败:', result);
var tip = document.createElement('div');
tip.textContent = '翻译失败: ' + (result || '未知错误');
Object.assign(tip.style, { position: 'fixed', bottom: '90px', left: '20px', backgroundColor: 'rgba(0,0,0,0.7)', color: 'white', padding: '6px 12px', borderRadius: '6px', fontSize: '12px', zIndex: '999999' });
document.body.appendChild(tip);
setTimeout(function() { tip.remove(); }, 2000);
}
syncPosition();
});
}
});
document.body.appendChild(btn);
requestAnimationFrame(() => updateHideLeft());
}
function initSelectionEvents() {
document.addEventListener('mouseup', function(e) {
if (e.target && (e.target.id === 'ms-manual-trans-btn' || e.target.id === 'ms-sel-trans-btn' || (selResult && selResult.contains(e.target)))) return;
setTimeout(function() {
if (!settings.selTranslate) { hideSelBtn(); return; }
const text = getSelText();
if (!text || text.length < 2 || ZH_RE.test(text)) {
hideSelBtn();
return;
}
const range = window.getSelection().getRangeAt(0);
const rect = range.getBoundingClientRect();
if (rect && (rect.width > 0 || rect.height > 0)) {
showSelBtn(rect.right + 4, rect.top - 30);
}
}, 10);
});
document.addEventListener('mousedown', function(e) {
if (e.target && (e.target.id === 'ms-sel-trans-btn' || (selResult && selResult.contains(e.target)))) return;
hideSelBtn();
hideSelResult();
});
if (selBtn) {
selBtn.addEventListener('click', function(e) {
e.stopPropagation();
const rect = selBtn.getBoundingClientRect();
showSelResult('翻译中…', rect.left, rect.bottom + 4);
translateSelection();
});
}
}
function initSettings() {
loadSettings();
if (!settings.hideMenuEntry && typeof GM_registerMenuCommand === 'function') {
GM_registerMenuCommand('设置', openSettings);
}
}
function matchShortcut(combo, e) {
if (!combo) return false;
var parts = combo.split('+'), key = parts.pop();
var mods = { Ctrl: false, Alt: false, Shift: false };
parts.forEach(function(p) { mods[p] = true; });
if (mods.Ctrl !== e.ctrlKey || mods.Alt !== e.altKey || mods.Shift !== e.shiftKey) return false;
if (/^[A-Z]$/.test(key)) return e.code === 'Key' + key;
if (/^\d$/.test(key)) return e.code === 'Digit' + key;
return e.code === key || e.key === key;
}
function initShortcuts() {
document.addEventListener('keydown', function(e) {
// 打开设置快捷键始终生效(面板打开时可关闭)
if (settings.openSettingsKey && matchShortcut(settings.openSettingsKey, e)) {
e.preventDefault();
openSettings();
return;
}
if (settingsPanel) return;
if (e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable)) return;
if (matchShortcut(settings.pageShortcut, e)) {
e.preventDefault();
var btn = document.getElementById('ms-manual-trans-btn');
if (btn) btn.click();
return;
}
if (matchShortcut(settings.selShortcut, e)) {
if (!settings.selTranslate) return;
e.preventDefault();
var text = getSelText();
if (text && text.length >= 2 && !ZH_RE.test(text)) {
if (!selBtn) { createSelectionUI(); initSelectionEvents(); }
if (selResult) {
selResult.textContent = '翻译中…';
selResult.style.display = 'block';
var rect = window.getSelection().getRangeAt(0).getBoundingClientRect();
selResult.style.left = rect.left + 'px';
selResult.style.top = (rect.bottom + 4) + 'px';
translateSelection();
}
}
return;
}
});
}
function tryAutoTranslate() {
if (!settings.autoTranslate) return;
const doIt = function() {
setTimeout(function() {
const btn = document.getElementById('ms-manual-trans-btn');
if (btn && !isTranslated) btn.click();
}, 1500);
};
if (document.readyState === 'complete') { doIt(); }
else { window.addEventListener('load', doIt); }
}
function initUI() {
createButton();
if (settings.selTranslate) {
createSelectionUI();
initSelectionEvents();
}
initShortcuts();
tryAutoTranslate();
}
initSettings();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initUI);
} else {
initUI();
}
getToken(() => {});
})();