划词翻译
'
+ '
翻译按钮
'
+ '
'
+ '
'
+ '
'
+ ''
+ ''
+ '
';
document.body.appendChild(settingsPanel);
// 所有交互只修改 ui 副本
document.querySelectorAll('input[name="ms-btn-mode"]').forEach(function(el) {
el.onchange = function() { ui.btnMode = this.value; };
});
document.querySelectorAll('input[name="ms-key-mod"]').forEach(function(el) {
el.onchange = function() {
ui.keyMod = this.value;
// 修饰键标签已内联在 HTML 中,无需额外更新
};
});
// 快捷键捕获:点击输入框后按下键盘自动记录
settingsPanel.addEventListener('keydown', function(e) {
var active = document.activeElement;
if (!active || !active.classList.contains('ms-keycapture') || !active._cap) return;
e.preventDefault();
e.stopPropagation();
var key = e.key === ' ' ? 'Space' : e.key.toUpperCase();
if (key.length !== 1 || !/^[A-Z]$/.test(key)) return;
active.value = key;
ui[active.id === 'ms-set-pagekey' ? 'pageKey' : 'selKey'] = key;
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; };
// 保存:写入真实 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 (typeof GM_registerMenuCommand === 'function') {
GM_registerMenuCommand('设置', openSettings);
}
}
function initShortcuts() {
document.addEventListener('keydown', function(e) {
if (e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable)) return;
var modOk = settings.keyMod === 'ctrl+alt' ? (e.ctrlKey && e.altKey && !e.shiftKey) : settings.keyMod === 'alt' ? (e.altKey && !e.ctrlKey && !e.shiftKey) : (e.ctrlKey && !e.altKey && !e.shiftKey);
if (modOk && e.code === 'Key' + settings.pageKey) {
e.preventDefault();
const btn = document.getElementById('ms-manual-trans-btn');
if (btn) btn.click();
return;
}
if (modOk && e.code === 'Key' + settings.selKey) {
if (!settings.selTranslate) return;
e.preventDefault();
const text = getSelText();
if (text && text.length >= 2 && !ZH_RE.test(text)) {
if (!selBtn) { createSelectionUI(); initSelectionEvents(); }
if (selResult) {
selResult.textContent = '翻译中…';
selResult.style.display = 'block';
const 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(() => {});
})();