PC版Geminiのチャット入力欄で、Enterキーで改行、Shift+Enterキーで送信できるようにします。
// ==UserScript==
// @name Gemini: Enterで改行、Shift+Enterで送信
// @name:en Gemini: Enter for Newline, Shift+Enter to Send
// @namespace http://tampermonkey.net/
// @version 1.01
// @description PC版Geminiのチャット入力欄で、Enterキーで改行、Shift+Enterキーで送信できるようにします。
// @description:en Use Enter for a new line and Shift+Enter to send messages in the Gemini web UI chat input field.
// @author Hayaokuri
// @match https://gemini.google.com/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use_strict';
const inputSelectorPrimary = 'div.ql-editor[contenteditable="true"]';
const inputSelectorsFallback = [
'textarea[enterkeyhint="send"]',
'textarea[aria-label*="Prompt"]',
'textarea[placeholder*="Message Gemini"]',
'div[role="textbox"][contenteditable="true"]'
];
const sendButtonSelector = 'button[aria-label*="Send"], button[aria-label*="送信"], button[data-test-id="send-button"]';
let textarea = null;
function findTextarea() {
let el = document.querySelector(inputSelectorPrimary);
if (el) { return el; }
for (const selector of inputSelectorsFallback) {
el = document.querySelector(selector);
if (el) { return el; }
}
return null;
}
function findSendButton() {
const selectors = [ sendButtonSelector, 'button:has(span.material-symbols-outlined:contains("send"))' ];
for (const selector of selectors) {
const el = document.querySelector(selector);
if (el) return el;
}
return null;
}
function handleKeydown(event) {
if (event.target !== textarea && (!textarea || !textarea.contains(event.target))) {
return;
}
if (event.key === 'Enter') {
if (event.shiftKey) {
event.preventDefault();
const sendButton = findSendButton();
if (sendButton && !sendButton.disabled) {
sendButton.click();
} else {
const form = event.target.closest('form');
if (form) {
if (typeof form.requestSubmit === 'function') { form.requestSubmit(); } else { form.submit(); }
}
}
} else { // Enterキーのみの場合
event.preventDefault();
event.stopImmediatePropagation();
const effectiveInputArea = textarea;
if (effectiveInputArea.isContentEditable) {
effectiveInputArea.focus();
let success = document.execCommand('insertParagraph', false, null);
if (!success) {
effectiveInputArea.focus(); // 再度フォーカス
document.execCommand('insertHTML', false, '<br>');
}
} else if (effectiveInputArea.tagName && effectiveInputArea.tagName.toUpperCase() === 'TEXTAREA') {
const start = effectiveInputArea.selectionStart;
const end = effectiveInputArea.selectionEnd;
effectiveInputArea.value = effectiveInputArea.value.substring(0, start) + "\n" + effectiveInputArea.value.substring(end);
effectiveInputArea.selectionStart = effectiveInputArea.selectionEnd = start + 1;
}
}
}
}
function observeDOM() {
const observer = new MutationObserver(() => {
const newTextarea = findTextarea();
if (newTextarea) {
if (textarea !== newTextarea || !newTextarea.dataset.keydownListenerAttached) {
if (textarea && textarea.dataset.keydownListenerAttached) {
textarea.removeEventListener('keydown', handleKeydown, true);
delete textarea.dataset.keydownListenerAttached;
}
textarea = newTextarea;
textarea.addEventListener('keydown', handleKeydown, true);
textarea.dataset.keydownListenerAttached = 'true';
}
} else {
if (textarea && textarea.dataset.keydownListenerAttached) {
textarea.removeEventListener('keydown', handleKeydown, true);
delete textarea.dataset.keydownListenerAttached;
textarea = null;
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => {
const initialTextarea = findTextarea();
if (initialTextarea && !initialTextarea.dataset.keydownListenerAttached) {
textarea = initialTextarea;
textarea.addEventListener('keydown', handleKeydown, true);
textarea.dataset.keydownListenerAttached = 'true';
}
}, 500);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => setTimeout(observeDOM, 0));
} else {
setTimeout(observeDOM, 0);
}
})();