チャットサービスの「エンターキー誤送信」を防ぐ「送信ダメ、ゼッタイ!」拡張をつくってみた

こんにちは❗フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、私のようなフリーランスは特にそうだと思うんですが、多数いらっしゃるクライアントさんたちへの連絡手段って、以下のように「それぞれ違ったチャットサービス」を使っていたりするんじゃないでしょうか。

  • Microsoft Teams
  • Slack
  • Chatwork
  • メール

個人的には、多数のチャットサービスを使うこと自体は問題ないんですけど、1個だけどうしても「これだけはやめて❗」と思うことがあります。

それは・・・・・・

Enter キーを押した瞬間にメッセージ送信する

機能です。

特に日本語圏ではEnterキーが変換の確定に使われるので、間違って押してしまって「あ…途中でメッセージ送信しちゃった😭」となりやすいですよね。

しかも、Ctrl + Enterの場合は送信するパターンもあったりするので、「どのサイトがどうだったかなんて覚えてないよ…」となり戸惑うばかりです。

今調べてみたら、以下のように混在しています。モウヤメテ…

📝 参考ページ: Enterキーで送信なの? 改行なの? チャットツールの違いをまとめた

そこで❗

もういっそのこと、Enter関連で送信はしないようにするGoogle Chromeの独自拡張「送信ダメ、ゼッタイ!」をつくってみることにしました。

ぜひ何かの参考になりましたら嬉しいです。😄✨

「改行しようと思って、
Enter で送信されたら
こうなりますよね😅」

開発環境: Chrome Extension Manifest V3, JavaScript

前提として

正直なところ、この拡張を作り始めたときは「ま、シンプルにいけるっしょ!」と思っていましたが、TeamsSlacktextareaではなくcontenteditable="true"を使っている関係上、複雑な構造にせざるを得ませんでした…。

また、それぞれのサイトで独自のコードが動いている関係上、今後サイトにアップデートがあったら機能しなくなる可能性もあるのでご注意ください。

なので、記事の内容としては「読みもの」程度で考えていただいた方がいいかもしれません。

ファイル構成について

今回ファイル構成は以下のようになっています。

soushin-dame-zettai/
 └ images
  └ icon_16.png
  └ icon_32.png
  └ icon_128.png
 └ js
  └ content-script.js
manifest.json

ちなみにアイコンはChatGPTDALL·E 3でつくりました。

ホント便利になったもんです😄👍

マニフェスト(V3)をつくる

以前このブログでChrome拡張の記事を書いたときはマニフェストのV2だったのですが、バージョンが上がっているようでした。

ただ、それほど変更があるわけではなかったのでそれほど戸惑うこともなかっtです。

ちなみにもしV2からV3へアップデートしたい場合は以下のページが参考になると思います。

📝 参考ページ: Update the manifest

では、実際のマニフェストは以下のようにしてください。

manifest.json

{
  "manifest_version": 3,
  "name": "送信ダメ、ゼッタイ!",
  "version": "1.0.0",
  "description": "Enter キーでメッセージ送信しないようにします(対応: Microsoft Teams, Slack, Chatwork)",
  "icons": {
    "16": "images/icon_16.png",
    "48": "images/icon_48.png",
    "128": "images/icon_128.png"
  },
  "content_scripts": [
    {
      "matches": [
        "https://teams.microsoft.com/*",
        "https://app.slack.com/*",
        "https://www.chatwork.com/*"
      ],
      "all_frames": true,
      "js": ["js/content-script.js"],
      "run_at": "document_start"
    }
  ],
  "author": "九保すこひ"
}

この中で重要なのが、次の項目でご紹介する「content scripts」です。

簡単に言うと、「このサイトの場合は、独自のJavaScriptを挿入してね」というものになっています。

そのため、今回の内容としては「以下に該当するサイトの場合は、js/content-script.jsを実行してね(ニッコリ)」となります。

  • https://teams.microsoft.com/*
  • https://app.slack.com/*
  • https://www.chatwork.com/*

Content Scripts をつくる

では、次にメインのContent Scriptsです。

中身は以下のようにしてください。

js/content-script.js

// 定数
const BACKGROUND_COLOR = '#d1ffd5';
const TEXT_COLOR = '#000';

// ヘルパー関数
const dd = console.log; // for TEST
const isCombinationKey = e => { // 組み合わせキーが押されているか

    return e.ctrlKey || e.shiftKey || e.altKey;

};

// Teams
const initTeams = () => {

    window.addEventListener('load', () => {

        setTimeout(() => {

            setTeamsEvents();

        }, 3000); // Teams は遅いので 3 秒待つ

    });

};
const setTeamsEvents = () => {

    const elements = document.querySelectorAll('[contenteditable="true"]');

    [].forEach.call(elements, el => {

        el.style.backgroundColor = BACKGROUND_COLOR;
        el.style.color = TEXT_COLOR;
        el.addEventListener('keydown', e => {

            if (e.key === 'Enter') {

                if(! e.ctrlKey && ! e.shiftKey && ! e.altKey) {

                    insertLineBreakOnTeams();

                }

                e.stopPropagation();

            }

        }, true);

    });

};
const insertLineBreakOnTeams = () => {

    const selection = window.getSelection();

    if (selection.rangeCount > 0) {

        const range = selection.getRangeAt(0);
        range.deleteContents();

        const br = document.createTextNode("\n");
        range.insertNode(br);

        const newRange = document.createRange();
        newRange.setStartAfter(br);
        newRange.setEndAfter(br);
        selection.removeAllRanges();
        selection.addRange(newRange);

    }

};

// Slack
const initSlack = () => {

    window.addEventListener('load', () => {

        setTimeout(() => {

            setSlackEvents();

        }, 1000);

    });

};
const setSlackEvents = () => {

    const elements = document.querySelectorAll('[contenteditable="true"]');

    [].forEach.call(elements, el => {

        el.style.backgroundColor = BACKGROUND_COLOR;
        el.style.color = TEXT_COLOR;
        el.addEventListener('keydown', e => {

            if (e.key === 'Enter') {

                e.stopPropagation();

                // Enter + Shift が送信なので入力内容の改変は不要

            }

        }, true);

    });

};

// Chatwork
const initChatwork = () => {

    window.addEventListener('load', () => {

        setTimeout(() => {

            setChatworkEvents();

        }, 1000);

    });

};
const setChatworkEvents = () => {

    const elements = document.querySelectorAll('textarea');

    [].forEach.call(elements, el => {

        el.style.backgroundColor = BACKGROUND_COLOR;
        el.style.color = TEXT_COLOR;
        el.addEventListener('keydown', e => {

            if (e.key === 'Enter') {

                if(! isCombinationKey(e)) {

                    e.preventDefault();
                    insertLineBreakOnChatwork(e);

                }

                e.stopPropagation();

            }

        });

    });

};
const insertLineBreakOnChatwork = (e) => {

    const el = e.target;
    const currentText = el.value;
    const selectionStart = el.selectionStart;
    const selectionEnd = el.selectionEnd;
    const lineBreak = "\n";

    const textBefore = currentText.substring(0, selectionStart);
    const textAfter = currentText.substring(selectionEnd, currentText.length);

    el.value = textBefore + lineBreak + textAfter;

    const newPosition = selectionStart + lineBreak.length;
    el.selectionStart = newPosition;
    el.selectionEnd = newPosition;

}

// 初期化
const init = () => {

    const hostName = window.location.hostname;
    const initFunctions = {
        'teams.microsoft.com': initTeams,
        'app.slack.com': initSlack,
        'www.chatwork.com': initChatwork,
    };
    const initFunction = initFunctions[hostName];

    if(typeof initFunction === 'function') {

        initFunction(); // 該当する初期化関数だけ実行

    }

};

init();

見ていただくと分かるようにバニラ(素のJavaScript)で書くことができます。

また、それぞれサイトに個性があるので微妙に違う部分がありますが、基本的にやっていることは以下のとおりです。

  • 入力項目の「keydown」イベントをつくる
  • その中でエンターキー(+ Ctrl など)で勝手に送信されないようにする
  • ただし、エンターキーが押されると入力内容が改行されるようにする

※ このあたりの兼ね合いが難しかったです…😭

今回は短いですが、作業はこれで終了です。
お疲れ様でした😄✨

拡張をインストールする

(ローカル環境にある)Chrome拡張のインストールは、以前公開した記事の中で紹介しているので、そちらを参考にしてみてください。

📝 参考ページ: エクステンションを実行してみる

GitHub で公開しました

Google Chrome Web storeできちんと公開してもいいのですが、やはりメンテナンスができなくなりそうなので、GitHubにリポジトリをつくりました。

できたら誰かがこれを使って他のチャットサービスも追加して完成させてくれたら嬉しいです😄👍

📝 該当ページ: 送信ダメ、ゼッタイ!

テストしてみる

今回は記事上でのテストを紹介しにくいので、見た目だけにします。(色が変わってます)

(Teams)

(slack)

(Chatwork)

企業様へのご提案

今回のように、Chrome拡張を使えば「特定のサイトのみ」に何か追加で機能を追加することができます。

例えば、以下のようなものです。

  • テーブルごとに CSV ダウンロードができるボタンを追加する
  • 画像をワンクリックでダウンロードできるようにする
  • 反転選択した文字をPDFとしてダウンロードできるようにする
  • 毎回チェックを入れないといけないめんどうな作業を自動でショートカットする

などなど

もしこういった機能をご希望でしたらいつでもお気軽にご相談ください。

お待ちしております。😄✨

おわりに

ということで、今回はいつもとは少し違った「Chrome 拡張」を作ってみました。

たしか、Chrome extensionをウェブストアで公開するために5ドル払ったはずなので、せっかくだから他の拡張もつくってみようかな…なんて思うのですがやはりメンテナンスできなくなるので、二の足を踏んでしまいますね。(もちろんお仕事ならキッチリやりますよ👍)

また、Chrome拡張が「うーん…」な部分は、スマホ用のChromeにはインストールできないところです。(おそらくアドブロッカーを使われないようにしたいんですかね…🤔)

なので、その辺が改善すれば拡張の世界ももっと盛り上がるはずなんですよね。

そんなカンジで、ぜひ皆さんも面白そうな拡張をつくってみてくださいね。

ではでは〜❗

「少し太ったからか、
新しく買ったズボンがピッチピチ…
ビールってうまいからなぁ🍻」

開発のご依頼お待ちしております 😊✨
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします!
このエントリーをはてなブックマークに追加       follow us in feedly  

開発効率を上げるための機材・まとめ