您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Modifies Enter key behavior in Gemini and AI Studio. Gemini: Enter for newline, Modifier+Enter to send. AI Studio: Configurable send key (modifier, Enter-sends, or native). Includes a settings panel.
当前为
// ==UserScript== // @name Gemini & AI Studio Enter Key Customizer // @name:en Gemini & AI Studio Enter Key Customizer // @name:ja Gemini & AI Studio Enterキーカスタマイザー // @name:zh-TW Gemini 與 AI Studio Enter 鍵自訂器 // @namespace https://gf.qytechs.cn/en/users/1467948-stonedkhajiit // @version 1.0.0 // @description Modifies Enter key behavior in Gemini and AI Studio. Gemini: Enter for newline, Modifier+Enter to send. AI Studio: Configurable send key (modifier, Enter-sends, or native). Includes a settings panel. // @description:en Modifies Enter key behavior in Gemini and AI Studio. Gemini: Enter for newline, Modifier+Enter to send. AI Studio: Configurable send key (modifier, Enter-sends, or native). Includes a settings panel. // @description:ja GeminiとAI StudioのEnterキー動作を変更。Gemini: Enterで改行、修飾キー+Enterで送信。AI Studio: 送信キーを選択可 (修飾キー、Enter送信、標準)。設定パネルあり。 // @description:zh-TW 調整 Gemini 與 AI Studio 的 Enter 鍵行為。Gemini:Enter 鍵換行,組合鍵送出。AI Studio:可自訂傳送鍵 (組合鍵、Enter即送、或預設)。附設定面板。 // @author StonedKhajiit // @match https://gemini.google.com/* // @match https://aistudio.google.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @license MIT // ==/UserScript== (function() { 'use strict'; // --- Constants and Configuration --- const SCRIPT_ID = 'GeminiEnterNewlineMultiSite_v1.0.0'; // CSS Selectors for input fields and send buttons const GEMINI_INPUT_SELECTOR_PRIMARY = 'div.ql-editor[contenteditable="true"]'; const GEMINI_INPUT_SELECTORS_FALLBACK = [ 'textarea[enterkeyhint="send"]', 'textarea[aria-label*="Prompt"]', 'textarea[placeholder*="Message Gemini"]', 'div[role="textbox"][contenteditable="true"]' ]; const AISTUDIO_INPUT_SELECTORS = [ 'ms-autosize-textarea textarea[aria-label="Type something or tab to choose an example prompt"]', 'ms-autosize-textarea textarea', 'ms-autosize-textarea textarea[aria-label="Start typing a prompt"]' ]; const GEMINI_SEND_BUTTON_SELECTORS = [ 'button[aria-label*="Send"]', 'button[aria-label*="傳送"]', 'button[aria-label*="送信"]', 'button[data-test-id="send-button"]', ]; const AISTUDIO_SEND_BUTTON_SELECTORS = ['button[aria-label="Run"]']; const AISTUDIO_SEND_BUTTON_MODIFIER_HINT_SELECTOR = 'span.secondary-key'; // GM Storage keys and default values for settings const GM_GLOBAL_ENABLE_KEY_STORAGE = 'geminiEnterGlobalEnable'; const GM_MODIFIER_KEY_STORAGE = 'geminiEnterModifierKey'; const MODIFIER_KEYS = { NONE: 'none', CTRL: 'ctrl', SHIFT: 'shift', ALT: 'alt', NATIVE_GEMINI: 'native_gemini' }; const DEFAULT_MODIFIER_KEY = MODIFIER_KEYS.CTRL; const GM_AISTUDIO_MODE_STORAGE = 'aiStudioKeyMode'; const AISTUDIO_KEY_MODES = { SHIFT_SEND: 'shift_send', ALT_SEND: 'alt_send', AISTUDIO_SPECIFIC: 'aistudio_specific', NATIVE_BEHAVIOR: 'native_behavior' }; const DEFAULT_AISTUDIO_KEY_MODE = AISTUDIO_KEY_MODES.NATIVE_BEHAVIOR; // --- State Variables --- let activeTextarea = null; let isScriptGloballyEnabled = true; let currentGlobalModifierKey = DEFAULT_MODIFIER_KEY; let currentAIStudioKeyMode = DEFAULT_AISTUDIO_KEY_MODE; let menuCommandIds = []; // --- Debounce Function --- function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // --- Internationalization (i18n) --- const i18n = { currentLang: 'en', strings: { 'en': { notifySettingsSaved: 'Settings saved!', notifyScriptEnabled: 'Custom Enter key behavior enabled. Reload page if needed.', notifyScriptDisabled: 'Custom Enter key behavior disabled. Reload page if needed.', settingsTitle: 'Script Settings', geminiKeyModeLabel: 'Gemini Key Mode:', aiStudioKeyModeLabel: 'AI Studio Key Mode:', closeButton: 'Close', saveButton: 'Save', openSettingsMenu: 'Configure Enter Key Behavior...', enableScriptMenu: 'Enable Custom Enter Key Behavior', disableScriptMenu: 'Disable Custom Enter Key Behavior', geminiCtrl: 'Ctrl+Enter to send', geminiShift: 'Shift+Enter to send', geminiAlt: 'Alt+Enter to send', geminiNative: 'Use Gemini Native Behavior (Enter sends)', aiStudioShift: 'Shift+Enter to send', aiStudioAlt: 'Alt+Enter to send', aiStudioSpecific: 'Enter to Send, Shift+Enter for Newline', aiStudioNative: 'Use AI Studio Native Behavior (Ctrl+Enter sends)', modifierCtrl: 'Ctrl', modifierShift: 'Shift', modifierAlt: 'Alt', }, 'zh-TW': { notifySettingsSaved: '設定已儲存!', notifyScriptEnabled: '自訂 Enter 鍵行為已啟用。若未立即生效請重載頁面。', notifyScriptDisabled: '自訂 Enter 鍵行為已停用。若未立即生效請重載頁面。', settingsTitle: '腳本設定', geminiKeyModeLabel: 'Gemini 按鍵模式:', aiStudioKeyModeLabel: 'AI Studio 按鍵模式:', closeButton: '關閉', saveButton: '儲存', openSettingsMenu: '設定 Enter 鍵行為...', enableScriptMenu: '啟用自訂 Enter 鍵行為', disableScriptMenu: '停用自訂 Enter 鍵行為', geminiCtrl: 'Ctrl+Enter 送出', geminiShift: 'Shift+Enter 送出', geminiAlt: 'Alt+Enter 送出', geminiNative: '使用 Gemini 原生行為 (Enter 送出)', aiStudioShift: 'Shift+Enter 送出', aiStudioAlt: 'Alt+Enter 送出', aiStudioSpecific: 'Enter 送出,Shift+Enter 換行', aiStudioNative: '使用 AI Studio 原生行為 (Ctrl+Enter 送出)', modifierCtrl: 'Ctrl', modifierShift: 'Shift', modifierAlt: 'Alt', }, 'ja': { notifySettingsSaved: '設定を保存しました!', notifyScriptEnabled: 'Enterキーのカスタム動作が有効になりました。必要に応じてページを再読み込みしてください。', notifyScriptDisabled: 'Enterキーのカスタム動作が無効になりました。必要に応じてページを再読み込みしてください。', settingsTitle: 'スクリプト設定', geminiKeyModeLabel: 'Gemini のキーモード:', aiStudioKeyModeLabel: 'AI Studio のキーモード:', closeButton: '閉じる', saveButton: '保存', openSettingsMenu: 'Enterキーの動作を設定...', enableScriptMenu: 'Enterキーのカスタム動作を有効化', disableScriptMenu: 'Enterキーのカスタム動作を無効化', geminiCtrl: 'Ctrl+Enter で送信', geminiShift: 'Shift+Enter で送信', geminiAlt: 'Alt+Enter で送信', geminiNative: 'Gemini ネイティブ動作を使用 (Enter で送信)', aiStudioShift: 'Shift+Enter で送信', aiStudioAlt: 'Alt+Enter で送信', aiStudioSpecific: 'Enter で送信、Shift+Enter で改行', aiStudioNative: 'AI Studio ネイティブ動作を使用 (Ctrl+Enter で送信)', modifierCtrl: 'Ctrl', modifierShift: 'Shift', modifierAlt: 'Alt', } }, detectLanguage() { const lang = navigator.language || navigator.userLanguage; if (lang) { if (lang.startsWith('ja')) this.currentLang = 'ja'; else if (lang.startsWith('zh-TW') || lang.startsWith('zh-Hant')) this.currentLang = 'zh-TW'; else if (lang.startsWith('en')) this.currentLang = 'en'; else this.currentLang = 'en'; } else { this.currentLang = 'en'; } }, get(key, ...args) { const langStrings = this.strings[this.currentLang] || this.strings.en; const template = langStrings[key] || (this.strings.en && this.strings.en[key]); if (typeof template === 'function') return template(...args); if (typeof template === 'string') return template; console.warn(`[${SCRIPT_ID}] Missing i18n string for key: ${key} in lang: ${this.currentLang}`); return `Missing string: ${key}`; } }; // --- UI Functions --- function createSettingsUI() { if (document.getElementById('gemini-ai-settings-overlay')) return; const overlay = document.createElement('div'); overlay.id = 'gemini-ai-settings-overlay'; overlay.classList.add('hidden'); const style = document.createElement('style'); style.textContent = ` @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); #gemini-ai-settings-overlay { position: fixed; inset: 0px; background-color: rgba(0, 0, 0, 0.6); display: flex; align-items: center; justify-content: center; z-index: 2147483647; font-family: 'Inter', Arial, sans-serif; opacity: 0; transition: opacity 0.2s ease-in-out; } #gemini-ai-settings-overlay.visible { opacity: 1; } #gemini-ai-settings-overlay.hidden { display: none !important; } #gemini-ai-settings-panel { background-color: #ffffff; color: #1f2937; padding: 18px; border-radius: 8px; box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04); width: 90%; max-width: 420px; position: relative; overflow-y: auto; max-height: 90vh; } body.userscript-dark-mode #gemini-ai-settings-panel { background-color: #2d3748; color: #e2e8f0; } body.userscript-dark-mode #gemini-ai-settings-panel h2, body.userscript-dark-mode #gemini-ai-settings-panel h3, body.userscript-dark-mode #gemini-ai-settings-panel label { color: #e2e8f0; } body.userscript-dark-mode #gemini-ai-settings-panel button#gemini-ai-close-btn { background-color: #4a5568; color: #e2e8f0; } body.userscript-dark-mode #gemini-ai-settings-panel button#gemini-ai-close-btn:hover { background-color: #718096; } body.userscript-dark-mode #gemini-ai-settings-panel input[type="radio"] { filter: invert(1) hue-rotate(180deg); } #gemini-ai-settings-panel h2 { font-size: 1.15rem; font-weight: 600; margin-bottom: 0.8rem; } #gemini-ai-settings-panel h3 { font-size: 1rem; font-weight: 500; margin-bottom: 0.5rem; margin-top: 0.7rem; } #gemini-ai-settings-panel .section-divider { border-top: 1px solid #e5e7eb; margin-top: 1rem; margin-bottom: 1rem; } body.userscript-dark-mode #gemini-ai-settings-panel .section-divider { border-top-color: #4a5568; } #gemini-ai-settings-panel .options-group > div { margin-bottom: 0.3rem; } #gemini-ai-settings-panel label { display: inline-flex; align-items: center; cursor: pointer; font-size: 0.875rem; } #gemini-ai-settings-panel input[type="radio"] { margin-right: 0.4rem; cursor: pointer; transform: scale(0.95); } .settings-buttons-container { display: flex; justify-content: flex-end; margin-top: 1rem; gap: 0.5rem; } #gemini-ai-settings-panel button { padding: 0.4rem 0.9rem; border-radius: 6px; font-weight: 500; transition: background-color 0.2s ease, box-shadow 0.2s ease; border: none; cursor: pointer; font-size: 0.875rem; } #gemini-ai-settings-panel button#gemini-ai-close-btn { background-color: #e5e7eb; color: #374151; } #gemini-ai-settings-panel button#gemini-ai-close-btn:hover { background-color: #d1d5db; } #gemini-ai-settings-panel button#gemini-ai-save-btn { background-color: #3b82f6; color: white; } #gemini-ai-settings-panel button#gemini-ai-save-btn:hover { background-color: #2563eb; } #gemini-ai-notification { position: fixed; bottom: 25px; left: 50%; transform: translateX(-50%); background-color: #10b981; color: white; padding: 0.8rem 1.5rem; border-radius: 6px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06); z-index: 2147483647; opacity: 0; transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out; font-family: 'Inter', Arial, sans-serif; font-size: 0.9rem; } #gemini-ai-notification.visible { opacity: 1; transform: translateX(-50%) translateY(0px); } #gemini-ai-notification.hidden { display: none !important; } `; document.head.appendChild(style); const settingsPanel = document.createElement('div'); settingsPanel.id = 'gemini-ai-settings-panel'; const title = document.createElement('h2'); title.textContent = i18n.get('settingsTitle'); settingsPanel.appendChild(title); const geminiTitle = document.createElement('h3'); geminiTitle.textContent = i18n.get('geminiKeyModeLabel'); settingsPanel.appendChild(geminiTitle); const geminiOptionsDiv = document.createElement('div'); geminiOptionsDiv.id = 'gemini-key-options'; geminiOptionsDiv.className = 'options-group'; settingsPanel.appendChild(geminiOptionsDiv); settingsPanel.appendChild(document.createElement('div')).className = 'section-divider'; const aistudioTitle = document.createElement('h3'); aistudioTitle.textContent = i18n.get('aiStudioKeyModeLabel'); settingsPanel.appendChild(aistudioTitle); const aistudioOptionsDiv = document.createElement('div'); aistudioOptionsDiv.id = 'aistudio-key-options'; aistudioOptionsDiv.className = 'options-group'; settingsPanel.appendChild(aistudioOptionsDiv); const buttonDiv = document.createElement('div'); buttonDiv.className = 'settings-buttons-container'; const closeButton = document.createElement('button'); closeButton.id = 'gemini-ai-close-btn'; closeButton.textContent = i18n.get('closeButton'); buttonDiv.appendChild(closeButton); const saveButton = document.createElement('button'); saveButton.id = 'gemini-ai-save-btn'; saveButton.textContent = i18n.get('saveButton'); buttonDiv.appendChild(saveButton); settingsPanel.appendChild(buttonDiv); overlay.appendChild(settingsPanel); document.body.appendChild(overlay); const notificationDiv = document.createElement('div'); notificationDiv.id = 'gemini-ai-notification'; notificationDiv.classList.add('hidden'); document.body.appendChild(notificationDiv); closeButton.addEventListener('click', closeSettings); saveButton.addEventListener('click', saveSettingsFromUI); overlay.addEventListener('click', (e) => { if (e.target === overlay) closeSettings(); }); if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { document.body.classList.add('userscript-dark-mode'); } window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { document.body.classList.toggle('userscript-dark-mode', e.matches); }); } function populateSettingsUI() { const geminiOptionsDiv = document.getElementById('gemini-key-options'); const aistudioOptionsDiv = document.getElementById('aistudio-key-options'); if (!geminiOptionsDiv || !aistudioOptionsDiv) return; while (geminiOptionsDiv.firstChild) geminiOptionsDiv.removeChild(geminiOptionsDiv.firstChild); while (aistudioOptionsDiv.firstChild) aistudioOptionsDiv.removeChild(aistudioOptionsDiv.firstChild); const geminiModifierOptions = [ { key: MODIFIER_KEYS.CTRL, labelKey: 'geminiCtrl' }, { key: MODIFIER_KEYS.SHIFT, labelKey: 'geminiShift' }, { key: MODIFIER_KEYS.ALT, labelKey: 'geminiAlt' }, { key: MODIFIER_KEYS.NATIVE_GEMINI, labelKey: 'geminiNative' }, ]; geminiModifierOptions.forEach(opt => { const div = document.createElement('div'); const input = document.createElement('input'); input.type = 'radio'; input.name = 'geminiKeyMode'; input.id = `gemini-${opt.key}`; input.value = opt.key; if (currentGlobalModifierKey === opt.key) input.checked = true; const label = document.createElement('label'); label.htmlFor = `gemini-${opt.key}`; label.textContent = i18n.get(opt.labelKey); div.appendChild(input); div.appendChild(label); geminiOptionsDiv.appendChild(div); }); const aiStudioModeOptions = [ { mode: AISTUDIO_KEY_MODES.SHIFT_SEND, labelKey: 'aiStudioShift' }, { mode: AISTUDIO_KEY_MODES.ALT_SEND, labelKey: 'aiStudioAlt' }, { mode: AISTUDIO_KEY_MODES.AISTUDIO_SPECIFIC, labelKey: 'aiStudioSpecific' }, { mode: AISTUDIO_KEY_MODES.NATIVE_BEHAVIOR, labelKey: 'aiStudioNative' }, ]; aiStudioModeOptions.forEach(opt => { const div = document.createElement('div'); const input = document.createElement('input'); input.type = 'radio'; input.name = 'aistudioKeyMode'; input.id = `aistudio-${opt.mode}`; input.value = opt.mode; if (currentAIStudioKeyMode === opt.mode) input.checked = true; const label = document.createElement('label'); label.htmlFor = `aistudio-${opt.mode}`; label.textContent = i18n.get(opt.labelKey); div.appendChild(input); div.appendChild(label); aistudioOptionsDiv.appendChild(div); }); } function openSettings() { loadSettings(); populateSettingsUI(); const overlay = document.getElementById('gemini-ai-settings-overlay'); if (overlay) { overlay.classList.remove('hidden'); void overlay.offsetWidth; overlay.classList.add('visible'); } } function closeSettings() { const overlay = document.getElementById('gemini-ai-settings-overlay'); if (overlay) { overlay.classList.remove('visible'); setTimeout(() => { if (!overlay.classList.contains('visible')) { overlay.classList.add('hidden'); } }, 200); } } function showNotification(message) { const notificationDiv = document.getElementById('gemini-ai-notification'); if (notificationDiv) { notificationDiv.textContent = message; notificationDiv.classList.remove('hidden'); void notificationDiv.offsetWidth; notificationDiv.classList.add('visible'); setTimeout(() => { notificationDiv.classList.remove('visible'); setTimeout(() => { if (!notificationDiv.classList.contains('visible')) { notificationDiv.classList.add('hidden'); } }, 300); }, 2500); } } // --- Core Logic Functions --- function loadSettings() { isScriptGloballyEnabled = GM_getValue(GM_GLOBAL_ENABLE_KEY_STORAGE, true); const savedGeminiModifier = GM_getValue(GM_MODIFIER_KEY_STORAGE, DEFAULT_MODIFIER_KEY); currentGlobalModifierKey = Object.values(MODIFIER_KEYS).includes(savedGeminiModifier) ? savedGeminiModifier : DEFAULT_MODIFIER_KEY; const savedAIStudioMode = GM_getValue(GM_AISTUDIO_MODE_STORAGE, DEFAULT_AISTUDIO_KEY_MODE); currentAIStudioKeyMode = Object.values(AISTUDIO_KEY_MODES).includes(savedAIStudioMode) ? savedAIStudioMode : DEFAULT_AISTUDIO_KEY_MODE; } function saveSettingsFromUI() { const selectedGeminiMode = document.querySelector('input[name="geminiKeyMode"]:checked')?.value; if (selectedGeminiMode) { saveGeminiKeyModeSetting(selectedGeminiMode); } else { saveGeminiKeyModeSetting(DEFAULT_MODIFIER_KEY); } const selectedAIStudioMode = document.querySelector('input[name="aistudioKeyMode"]:checked')?.value; if (selectedAIStudioMode) { saveAIStudioKeyModeSetting(selectedAIStudioMode); } else { saveAIStudioKeyModeSetting(DEFAULT_AISTUDIO_KEY_MODE); } registerMenuCommand(); showNotification(i18n.get('notifySettingsSaved')); closeSettings(); } function saveGeminiKeyModeSetting(key) { if (Object.values(MODIFIER_KEYS).includes(key) && key !== MODIFIER_KEYS.NONE) { currentGlobalModifierKey = key; GM_setValue(GM_MODIFIER_KEY_STORAGE, key); updateActiveTextareaListener(); // Important to update listener status } } function saveAIStudioKeyModeSetting(mode) { if (Object.values(AISTUDIO_KEY_MODES).includes(mode)) { currentAIStudioKeyMode = mode; GM_setValue(GM_AISTUDIO_MODE_STORAGE, mode); updateAIStudioButtonModifierHint(); updateActiveTextareaListener(); } } function updateAIStudioButtonModifierHint() { if (!window.location.hostname.includes('aistudio.google.com')) { return; } const sendButton = findSendButton(); if (sendButton) { const hintSpan = sendButton.querySelector(AISTUDIO_SEND_BUTTON_MODIFIER_HINT_SELECTOR); if (hintSpan) { hintSpan.style.display = 'inline'; let hintText = i18n.get('modifierCtrl'); switch (currentAIStudioKeyMode) { case AISTUDIO_KEY_MODES.SHIFT_SEND: hintText = i18n.get('modifierShift'); break; case AISTUDIO_KEY_MODES.ALT_SEND: hintText = i18n.get('modifierAlt'); break; case AISTUDIO_KEY_MODES.AISTUDIO_SPECIFIC: hintSpan.style.display = 'none'; hintText = ''; break; case AISTUDIO_KEY_MODES.NATIVE_BEHAVIOR: hintText = i18n.get('modifierCtrl'); break; } hintSpan.textContent = hintSpan.style.display !== 'none' ? hintText + ' ' : ''; } } } function handleKeydown(event) { if (window.location.hostname.includes('gemini.google.com') && !isScriptGloballyEnabled) { return; } if (event.target !== activeTextarea && (!activeTextarea || !activeTextarea.contains(event.target))) { return; } const currentHost = window.location.hostname; const ctrlOnly = event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey; const shiftOnly = event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey; const altOnly = event.altKey && !event.ctrlKey && !event.shiftKey && !event.metaKey; const plainEnter = !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey; if (event.key === 'Enter') { if (currentHost.includes('gemini.google.com')) { if (currentGlobalModifierKey === MODIFIER_KEYS.NATIVE_GEMINI) { return; } applyModifierSendBehavior(event, currentGlobalModifierKey, ctrlOnly, shiftOnly, altOnly, plainEnter, activeTextarea); } else if (currentHost.includes('aistudio.google.com')) { if (currentAIStudioKeyMode === AISTUDIO_KEY_MODES.NATIVE_BEHAVIOR) { return; } else if (currentAIStudioKeyMode === AISTUDIO_KEY_MODES.AISTUDIO_SPECIFIC) { if (plainEnter) { event.preventDefault(); event.stopImmediatePropagation(); const sendButton = findSendButton(); if (sendButton && !sendButton.disabled) sendButton.click(); else { const form = event.target?.closest('form'); if (form) form.requestSubmit ? form.requestSubmit() : form.submit(); } } else if (shiftOnly) { event.preventDefault(); event.stopImmediatePropagation(); insertNewline(activeTextarea); } else { event.preventDefault(); event.stopImmediatePropagation(); } } else if (currentAIStudioKeyMode === AISTUDIO_KEY_MODES.SHIFT_SEND) { applyModifierSendBehavior(event, MODIFIER_KEYS.SHIFT, ctrlOnly, shiftOnly, altOnly, plainEnter, activeTextarea); } else if (currentAIStudioKeyMode === AISTUDIO_KEY_MODES.ALT_SEND) { applyModifierSendBehavior(event, MODIFIER_KEYS.ALT, ctrlOnly, shiftOnly, altOnly, plainEnter, activeTextarea); } } } } function insertNewline(element) { if (!element) return; if (element.isContentEditable) { element.focus(); let success = false; try { success = document.execCommand('insertParagraph', false, null); } catch (e) { /* console.warn("execCommand('insertParagraph') failed:", e); */ } if (!success) { try { const selection = window.getSelection(); if (selection && selection.rangeCount > 0) { const range = selection.getRangeAt(0); const br = document.createElement('br'); range.deleteContents(); range.insertNode(br); const newRange = document.createRange(); newRange.setStartAfter(br); newRange.collapse(true); selection.removeAllRanges(); selection.addRange(newRange); } else { document.execCommand('insertHTML', false, '<br>'); } } catch (e) { /* console.warn("Fallback newline insertion for contentEditable failed:", e); */ } } } else if (element.tagName === 'TEXTAREA') { const start = element.selectionStart; const end = element.selectionEnd; element.value = `${element.value.substring(0, start)}\n${element.value.substring(end)}`; element.selectionStart = element.selectionEnd = start + 1; element.dispatchEvent(new Event('input', { bubbles: true, cancelable: true })); } } function applyModifierSendBehavior(event, modifierKeyToSend, ctrlOnly, shiftOnly, altOnly, plainEnter, activeTextareaElement) { let send = false; if ((modifierKeyToSend === MODIFIER_KEYS.CTRL && ctrlOnly) || (modifierKeyToSend === MODIFIER_KEYS.SHIFT && shiftOnly) || (modifierKeyToSend === MODIFIER_KEYS.ALT && altOnly)) { send = true; } if (send) { event.preventDefault(); event.stopImmediatePropagation(); const sendButton = findSendButton(); if (sendButton && !sendButton.disabled) sendButton.click(); else { const form = event.target?.closest('form'); if (form) form.requestSubmit ? form.requestSubmit() : form.submit(); } } else if (plainEnter) { event.preventDefault(); event.stopImmediatePropagation(); insertNewline(activeTextareaElement); } else { const isShiftEnterForNativeNewline = shiftOnly && modifierKeyToSend !== MODIFIER_KEYS.SHIFT; if (!isShiftEnterForNativeNewline) { event.preventDefault(); event.stopImmediatePropagation(); } } } function updateActiveTextareaListener() { if (activeTextarea) { const listenerAttached = activeTextarea.dataset.keydownListenerAttached === 'true'; const onGemini = window.location.hostname.includes('gemini.google.com'); const onAIStudio = window.location.hostname.includes('aistudio.google.com'); let shouldCurrentlyBeAttached = false; if (onGemini) { shouldCurrentlyBeAttached = isScriptGloballyEnabled && currentGlobalModifierKey !== MODIFIER_KEYS.NATIVE_GEMINI; } else if (onAIStudio) { shouldCurrentlyBeAttached = currentAIStudioKeyMode !== AISTUDIO_KEY_MODES.NATIVE_BEHAVIOR; } if (listenerAttached && !shouldCurrentlyBeAttached) { activeTextarea.removeEventListener('keydown', handleKeydown, true); delete activeTextarea.dataset.keydownListenerAttached; // console.log(`[${SCRIPT_ID}] Keydown listener removed from:`, activeTextarea); } else if (!listenerAttached && shouldCurrentlyBeAttached) { activeTextarea.addEventListener('keydown', handleKeydown, true); activeTextarea.dataset.keydownListenerAttached = 'true'; // console.log(`[${SCRIPT_ID}] Keydown listener attached to:`, activeTextarea); } } } function toggleScriptGlobally() { isScriptGloballyEnabled = !isScriptGloballyEnabled; GM_setValue(GM_GLOBAL_ENABLE_KEY_STORAGE, isScriptGloballyEnabled); updateActiveTextareaListener(); registerMenuCommand(); showNotification(isScriptGloballyEnabled ? i18n.get('notifyScriptEnabled') : i18n.get('notifyScriptDisabled')); } function registerMenuCommand() { menuCommandIds.forEach(id => { if (typeof GM_unregisterMenuCommand === 'function') try { GM_unregisterMenuCommand(id); } catch (e) { /* console.warn("Error unregistering menu command:", e); */ } }); menuCommandIds = []; try { const settingsCmdId = GM_registerMenuCommand(i18n.get('openSettingsMenu'), openSettings, 's'); if (settingsCmdId) menuCommandIds.push(settingsCmdId); } catch (e) { console.error(`[${SCRIPT_ID}] Error registering 'Open Settings' menu command:`, e); } try { const toggleCmdText = isScriptGloballyEnabled ? i18n.get('disableScriptMenu') : i18n.get('enableScriptMenu'); const toggleCmdId = GM_registerMenuCommand(toggleCmdText, toggleScriptGlobally, 't'); if (toggleCmdId) menuCommandIds.push(toggleCmdId); } catch (e) { console.error(`[${SCRIPT_ID}] Error registering toggle script menu command:`, e); } } // --- Initialization and Observation --- function findTextarea() { let el; const currentHost = window.location.hostname; if (currentHost.includes('aistudio.google.com')) { for (const selector of AISTUDIO_INPUT_SELECTORS) { el = document.querySelector(selector); if (el) return el; } } else if (currentHost.includes('gemini.google.com')) { el = document.querySelector(GEMINI_INPUT_SELECTOR_PRIMARY); if (el) return el; for (const selector of GEMINI_INPUT_SELECTORS_FALLBACK) { el = document.querySelector(selector); if (el) return el; } } return null; } function findSendButton() { let el; const currentHost = window.location.hostname; if (currentHost.includes('aistudio.google.com')) { for (const selector of AISTUDIO_SEND_BUTTON_SELECTORS) { el = document.querySelector(selector); if (el) return el; } } else if (currentHost.includes('gemini.google.com')) { for (const selector of GEMINI_SEND_BUTTON_SELECTORS) { el = document.querySelector(selector); if (el) return el; } } return null; } // Debounced handler for DOM changes const debouncedDOMChangeHandler = debounce(() => { const newTextarea = findTextarea(); if (newTextarea) { const onGemini = window.location.hostname.includes('gemini.google.com'); const onAIStudio = window.location.hostname.includes('aistudio.google.com'); const needsListenerOnGemini = onGemini && isScriptGloballyEnabled && currentGlobalModifierKey !== MODIFIER_KEYS.NATIVE_GEMINI; const needsListenerOnAIStudio = onAIStudio && currentAIStudioKeyMode !== AISTUDIO_KEY_MODES.NATIVE_BEHAVIOR; const listenerShouldBeAttached = needsListenerOnGemini || needsListenerOnAIStudio; if (activeTextarea !== newTextarea || (activeTextarea && activeTextarea.dataset.keydownListenerAttached !== 'true' && listenerShouldBeAttached)) { if (activeTextarea && activeTextarea.dataset.keydownListenerAttached === 'true') { activeTextarea.removeEventListener('keydown', handleKeydown, true); delete activeTextarea.dataset.keydownListenerAttached; } activeTextarea = newTextarea; updateActiveTextareaListener(); // This will attach/detach based on current state } } else if (activeTextarea && activeTextarea.dataset.keydownListenerAttached === 'true') { activeTextarea.removeEventListener('keydown', handleKeydown, true); delete activeTextarea.dataset.keydownListenerAttached; activeTextarea = null; } if (window.location.hostname.includes('aistudio.google.com')) { updateAIStudioButtonModifierHint(); } }, 300); // 300ms debounce time const observeDOM = function() { const observer = new MutationObserver((mutationsList, obs) => { debouncedDOMChangeHandler(); }); observer.observe(document.body, { childList: true, subtree: true }); }; function init() { i18n.detectLanguage(); loadSettings(); createSettingsUI(); registerMenuCommand(); observeDOM(); // Initial setup for textarea and button hint activeTextarea = findTextarea(); updateActiveTextareaListener(); // Delay initial AI Studio button hint update slightly to allow page to settle if (window.location.hostname.includes('aistudio.google.com')) { setTimeout(updateAIStudioButtonModifierHint, 500); // Increased delay } else { updateAIStudioButtonModifierHint(); // For Gemini or other contexts, update immediately (though it does nothing) } console.log(`[${SCRIPT_ID}] Initialized. Gemini Mode: ${currentGlobalModifierKey}, AI Studio Mode: ${currentAIStudioKeyMode}, Script Enabled: ${isScriptGloballyEnabled}`); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址