⌨️

MacのSafariでClaude.aiを快適に使う:日本語入力時のEnterキー問題を解決するUserScript

2025/03/01に公開

はじめに

最近AIアシスタントとして注目を集めている「Claude.ai」ですが、MacのSafariで利用する際に日本語入力で困ったことはありませんか?特に日本語変換確定時のEnterキー押下が、意図せずメッセージを送信してしまう問題は多くのユーザーを悩ませています。

この記事では、その問題を解決するためのTampermonkeyスクリプトを紹介します。Safari特有の日本語入力とClaude.aiの相性問題を解消し、ストレスフリーな体験を実現しましょう。

問題の詳細

なぜ問題が発生するのか?

日本語などの変換入力が必要な言語では、IME(Input Method Editor)を使用して入力し、Enterキーで確定します。しかしClaude.aiをはじめとするチャットUIでは、Enterキーがメッセージ送信のショートカットとして機能しています。

これにより以下の問題が発生します:

  1. 日本語入力中にEnterで変換を確定すると、同時にメッセージが送信されてしまう
  2. 文章の途中で送信されるため、会話が分断される
  3. 特にSafariではこの問題が顕著(Chromeなどでは対策が施されている場合も)

解決策:UserScriptの導入

今回紹介するUserScriptは、Safari上のClaude.aiで日本語入力時のEnterキー競合を解決します。

スクリプトの機能

  • 日本語入力中のEnterキーによるフォーム送信を防止
  • 入力確定後の一定時間(デフォルト300ms)は送信をブロック
  • 送信ボタンクリック時の誤送信も防止
  • Claude.aiのUIが変更されても動的に対応

使用方法

  1. SafariにTampermonkey拡張機能をインストール
  2. 新規スクリプトを作成し、以下のコードを貼り付け
  3. 保存して有効化
  4. Claude.aiを開き、快適な日本語入力を体験

完全なコード

以下のコードをTampermonkeyの新規スクリプトとして保存してください:

// ==UserScript==
// @name         Claude.ai用日本語入力修正(Safari用)
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  MacOSのSafariでClaude.aiを使用する際に日本語入力時のEnterキー競合を解決
// @author       You
// @match        https://*.claude.ai/*
// @match        https://claude.ai/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // フラグとタイムスタンプの保存用
    let isComposing = false;
    let lastCompositionEndTime = 0;
    const THRESHOLD_MS = 300; // 日本語確定後の送信防止時間(ミリ秒)

    // Enter送信が有効かをチェックする
    function isEnterSubmitAllowed() {
        // 現在入力中か、または入力確定から閾値時間以内ならfalse
        if (isComposing) return false;

        const now = Date.now();
        if (now - lastCompositionEndTime < THRESHOLD_MS) return false;

        return true;
    }

    // キーイベントの完全なインターセプト(キャプチャフェーズで)
    function interceptKeyEvents() {
        document.addEventListener('keydown', function(event) {
            // Enterキーで、ShiftKeyが押されていない場合
            if (event.key === 'Enter' && !event.shiftKey) {
                // 日本語入力中または直後なら、イベントを完全にキャンセル
                if (!isEnterSubmitAllowed()) {
                    event.stopImmediatePropagation();
                    event.preventDefault();
                    return false;
                }
            }
        }, true); // キャプチャフェーズが重要

        // 入力開始イベント
        document.addEventListener('compositionstart', function(event) {
            isComposing = true;
            console.log('日本語入力開始');
        }, true);

        // 入力確定イベント
        document.addEventListener('compositionend', function(event) {
            isComposing = false;
            lastCompositionEndTime = Date.now();
            console.log('日本語入力確定');
        }, true);
    }

    // テキストエリア固有の処理
    function setupTextareas() {
        // Claude.aiの入力欄を取得(標準的なテキストエリアとプロンプト入力欄の両方を対象)
        const inputElements = document.querySelectorAll('textarea, [contenteditable="true"], [role="textbox"]');

        inputElements.forEach(input => {
            // 既に処理済みの場合はスキップ
            if (input.getAttribute('japanese-fix-applied')) return;

            // 入力要素固有のkeydownハンドラ
            input.addEventListener('keydown', function(event) {
                if (event.key === 'Enter' && !event.shiftKey && !isEnterSubmitAllowed()) {
                    event.stopImmediatePropagation();
                    event.preventDefault();
                    console.log('入力欄でのEnterキーブロック');
                    return false;
                }
            }, true);

            // 入力要素特有のcomposition処理
            input.addEventListener('compositionstart', function() {
                isComposing = true;
                console.log('入力欄で日本語入力開始');
            }, true);

            input.addEventListener('compositionend', function() {
                isComposing = false;
                lastCompositionEndTime = Date.now();
                console.log('入力欄で日本語入力確定');
            }, true);

            // 処理済みマーク
            input.setAttribute('japanese-fix-applied', 'true');
        });
    }

    // 送信ボタンをオーバーライドする関数
    function overrideSendButton() {
        // 一定間隔で送信ボタンを検索
        setInterval(() => {
            // Claude.aiの送信ボタンを探す(複数のセレクタをカバー)
            const sendButtons = document.querySelectorAll('button[type="submit"], button[aria-label*="send"], button[aria-label*="Send"], button.send-button, form button');

            sendButtons.forEach(button => {
                // 既に処理済みならスキップ
                if (button.getAttribute('japanese-fix-applied')) return;

                // クリックイベントリスナーを上書き
                const originalClick = button.onclick;
                button.onclick = function(event) {
                    // 入力中なら送信をブロック
                    if (!isEnterSubmitAllowed()) {
                        event.stopImmediatePropagation();
                        event.preventDefault();
                        console.log('送信ボタンクリックをブロック');
                        return false;
                    }

                    // そうでなければ通常の処理を実行
                    if (originalClick) return originalClick.call(this, event);
                    return true;
                };

                // 処理済みマーク
                button.setAttribute('japanese-fix-applied', 'true');
            });
        }, 1000);
    }

    // フォーム送信を直接制御
    function overrideFormSubmission() {
        // フォーム送信の監視
        document.addEventListener('submit', function(event) {
            if (!isEnterSubmitAllowed()) {
                event.stopImmediatePropagation();
                event.preventDefault();
                console.log('フォーム送信をブロック');
                return false;
            }
        }, true);

        // Claude.aiで可能性のあるカスタム送信処理をインターセプト
        const originalFetch = window.fetch;
        window.fetch = function(...args) {
            if (!isEnterSubmitAllowed() && args[0] && typeof args[0] === 'string' &&
                (args[0].includes('/api/') || args[0].includes('/conversation'))) {
                console.log('fetch APIによる送信をブロック', args[0]);
                return new Promise(() => {}); // 処理をブロック
            }
            return originalFetch.apply(this, args);
        };
    }

    // DOM変更の監視
    function observeDOM() {
        const observer = new MutationObserver((mutations) => {
            setupTextareas();
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    // 初期化関数
    function initialize() {
        console.log('Claude.ai日本語入力修正スクリプトを初期化');
        interceptKeyEvents();
        setupTextareas();
        overrideSendButton();
        overrideFormSubmission();
        observeDOM();
    }

    // ページ読み込み時に実行
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize);
    } else {
        initialize();
    }

    // 早期実行(document-startで)
    interceptKeyEvents();
    overrideFormSubmission();
})();

スクリプトの解説

このスクリプトは以下の主要な機能で構成されています:

1. 日本語入力状態の追跡

let isComposing = false;
let lastCompositionEndTime = 0;
const THRESHOLD_MS = 300; // 日本語確定後の送信防止時間(ミリ秒)

// Enter送信が有効かをチェックする
function isEnterSubmitAllowed() {
    // 現在入力中か、または入力確定から閾値時間以内ならfalse
    if (isComposing) return false;

    const now = Date.now();
    if (now - lastCompositionEndTime < THRESHOLD_MS) return false;

    return true;
}

compositionstartcompositionendイベントを監視して、日本語入力中かどうかを追跡します。また、入力確定後も一定時間(300ms)は送信をブロックすることで、誤送信を防止します。

2. キーイベントのインターセプト

document.addEventListener('keydown', function(event) {
    // Enterキーで、ShiftKeyが押されていない場合
    if (event.key === 'Enter' && !event.shiftKey) {
        // 日本語入力中または直後なら、イベントを完全にキャンセル
        if (!isEnterSubmitAllowed()) {
            event.stopImmediatePropagation();
            event.preventDefault();
            return false;
        }
    }
}, true); // キャプチャフェーズが重要

キャプチャフェーズでEnterキーイベントをインターセプトして、日本語入力中や入力確定直後の送信を防止します。

3. 送信ボタンの制御

function overrideSendButton() {
    // 一定間隔で送信ボタンを検索
    setInterval(() => {
        // Claude.aiの送信ボタンを探す(複数のセレクタをカバー)
        const sendButtons = document.querySelectorAll('button[type="submit"], button[aria-label*="send"], button[aria-label*="Send"], button.send-button, form button');

        sendButtons.forEach(button => {
            // 既に処理済みならスキップ
            if (button.getAttribute('japanese-fix-applied')) return;

            // クリックイベントリスナーを上書き
            const originalClick = button.onclick;
            button.onclick = function(event) {
                // 入力中なら送信をブロック
                if (!isEnterSubmitAllowed()) {
                    event.stopImmediatePropagation();
                    event.preventDefault();
                    console.log('送信ボタンクリックをブロック');
                    return false;
                }

                // そうでなければ通常の処理を実行
                if (originalClick) return originalClick.call(this, event);
                return true;
            };

            // 処理済みマーク
            button.setAttribute('japanese-fix-applied', 'true');
        });
    }, 1000);
}

送信ボタンのクリックイベントも制御し、日本語入力中の誤クリックによる送信を防ぎます。

4. 動的なDOM変更への対応

function observeDOM() {
    const observer = new MutationObserver((mutations) => {
        setupTextareas();
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
}

MutationObserverを使用してDOM変更を監視し、動的に追加される入力欄にも対応します。

動作確認環境

  • macOS Ventura 13.5以降
  • Safari 16.5以降
  • Tampermonkey 4.18.0
  • Claude.ai(2025年3月現在)

カスタマイズポイント

  • THRESHOLD_MSの値を調整することで、入力確定後の送信防止時間を変更できます
  • セレクタを追加/調整することで、他のウェブサイトにも応用可能です

注意点

  • Claude.aiのUI変更により、将来的に機能しなくなる可能性があります
  • Safariのバージョンや環境によって挙動が異なる場合があります
  • このスクリプトはTampermonkeyの機能を使用しているため、他の拡張機能と競合する可能性があります

まとめ

このUserScriptを利用することで、MacのSafariでもClaude.aiを快適に使用できるようになります。日本語入力の問題に悩んでいた方は、ぜひお試しください。

また、このスクリプトはオープンソースで提供していますので、改善点や他のブラウザへの対応など、カスタマイズして活用していただければ幸いです。


最後まで読んでいただき、ありがとうございました。このスクリプトが皆さんのAI活用をより便利にする一助となれば幸いです。もし質問やフィードバックがあれば、コメントでお知らせください。

Discussion