Enter=Newline スクリプト[edit]

// ==UserScript==
// @name         ChatGPT: Enter=Newline, Ctrl+Enter=Send (stable CE)
// @match        https://chatgpt.com/*
// @match        https://chat.openai.com/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function () {
 'use strict';
 console.log('[TM] ChatGPT key-remap loaded');

 function isEditable(el) {
   if (!el) return false;
   if (el.tagName === 'TEXTAREA') return true;
   if (el.tagName === 'INPUT') return false;
   return el.isContentEditable === true;
 }

 function insertNewlineTextarea(el) {
   const start = el.selectionStart ?? el.value.length;
   const end = el.selectionEnd ?? el.value.length;
   el.setRangeText('\n', start, end, 'end');
   el.dispatchEvent(new Event('input', { bubbles: true }));
 }

 function insertNewlineContentEditable(el) {
   // Selection/Range で <br> と ZWSP を挿入し,カーソルを次行へ
   const sel = window.getSelection();
   if (!sel || sel.rangeCount === 0) return;
   const range = sel.getRangeAt(0);
   range.deleteContents();

   const br = document.createElement('br');
   const zwsp = document.createTextNode('\u200B'); // 次行でタイピング可能にするおまじない
   range.insertNode(br);
   range.setStartAfter(br);
   range.insertNode(zwsp);

   // キャレットを ZWSP の後ろへ
   const after = document.createRange();
   after.setStartAfter(zwsp);
   after.collapse(true);
   sel.removeAllRanges();
   sel.addRange(after);

   // React/TipTap 等に変更通知
   el.dispatchEvent(new InputEvent('input', { bubbles: true, data: '\n' }));
 }

 function insertNewline(el) {
   if (el.tagName === 'TEXTAREA') return insertNewlineTextarea(el);
   // 念のため focus
   el.focus();
   insertNewlineContentEditable(el);
 }

 function clickSendButton() {
   const candidates = [
     'button[type="submit"]',
     'button[data-testid="send-button"]',
     'button[aria-label*="Send"]',
     'button[aria-label*="送信"]',
   ];
   for (const sel of candidates) {
     const btn = document.querySelector(sel);
     if (btn && !btn.disabled) { btn.click(); return true; }
   }
   return false;
 }

 window.addEventListener('keydown', (e) => {
   const el = document.activeElement;
   if (!isEditable(el)) return;

   // IME 変換確定中は介入しない
   if (e.isComposing) return;

   if (e.key !== 'Enter') return;

   // Ctrl+Enter → 送信
   if (e.ctrlKey) {
     e.preventDefault();
     e.stopImmediatePropagation();
     clickSendButton();
     return;
   }

   // Enter 単体 → 改行(誤送信防止)
   if (!e.shiftKey && !e.altKey && !e.metaKey) {
     e.preventDefault();
     e.stopImmediatePropagation();
     insertNewline(el);
     return;
   }
   // Shift+Enter 等は既定挙動(多くは改行)
 }, true);
})();

トップ   新規 ページ一覧 検索 最終更新   ヘルプ   最終更新のRSS