Aim Developer

i.e. blog

tl;dr

GitHubにあげてます。https://github.com/shmurakami/chatwork-completion 今のところ公開するつもりはありません。 このExtensionでなにかあったとしても責任は取れませんので、自己責任でご利用ください。


そういえば先週からChatWork株式会社に入社してます。よろしくお願いします。Happyさんどうもありがとう。お礼を言いそびれているのでここでw またいずれ飯でも。

なお、この記事に限らず私が発信する内容は私個人の考えであり所属する組織には一切関係ありません。


さて、そういうわけで仕事でチャットワークを使い始めているのだけど。前職では完全にSlackでChatOpsだぜーとかやってたくらいだからSlackの手癖がまだ残っている状態なわけです。

基本的にキーボードでいろんな作業を完結したいタイプで、チャット上でもそれは当然同じ。 Slackはキーボードショートカットや/コマンドが充実していて大体のことにマウスを使う必要がなくとても便利でした。

先日のSpecイベントでもslash commandの拡充が発表されてました。 イメージですがSlackのユーザにはエンジニアが多いので相性が良いのでしょう。

チャットワークにもキーボードショートカットがいくつかあってよく使わせてもらってますが、個人的に1番ほしい、そして手癖が抜けないメンションを送りたいときのショートカットが無いのがつらい。 Slackだと@を打つとchannel内のユーザリストが表示されて、誰にメンションを送るか選択できます。インクリメンタルにフィルタもかけてくれます。

こんなやつ

image

こんなリストを表示するショートカットが欲しい、ということでChrome Extensionで実装することにしました。

ということでこの記事は、こんなリストを作るためにどんなことをしようとしたのか、という記録です。 しようとしたのか、ということで、実際にはしていません。

何故かと言うと、チャットワークはすでに似たようなToリストを持っていて

image

このボタンのクリックイベントだけ発火させてやれば十分だということに気づいてしまったのでそれをするだけになりました。

そもそもこのボタンを押すためにキーボードから手を動かしたくない、ということだったんだから、代わりにクリックイベントを発火させられればそれで十分だったんだよね。

実装するつもりだったもの

上記のような事情で、以下は実際にやるつもりだったこと、最終的にやったことです。

要件

こんな感じの要件が自分の期待するUIです。

@で発火するところ以外はチャットワークにすでに実装されてたので実際には何も考える必要がなかったです。なんか申し訳なくなった。

技術要素

この要件をブラウザ上で満たそうと思ったときに、「メッセージ入力欄(textarea)にブラウザ上のフォーカスは残しつつ、つまり文字入力は続けつづ、上下でリストを移動できるようにする」というのがあまりピンと来てません。 selectタグでやろうとするとtextareaからフォーカス外れちゃうし、上下キーのイベントも取得して制御してるの?もっと良い方法あるのでは?といった感じ。

よく分からなかったのでブラウザ上でSlackを開き、メンションリストを表示させてDev ToolでHTMLを見ることに。大体以下のような感じでした。liタグ以下は関係ないのでバッサリ切ってます。

<ul class="type_members" role="listbox">
    <li role="option" 
        tabindex="-1"
        class="tab_complete_ui_item active" 
        id="tab_complete_ui_item_0" 
        data-type="member" 
        data-index="0" 
        aria-selected="true"> 
        <span>...</span>
    </li>
</ul>

普通のリストとして記述されてますね。 気になったのは role="listbox"。role属性はデザイン系のフレームワークだったりツールを使うとたまに見かけるけど、これで良い感じのリストとして何か表現できるのでは?と当たりをつけてググることにした。

Slackの実装方法はこれ以上深く追ってないので特にそっちには期待しないでください。

WAI-ARIA

listboxでググったらWAI-ARIAのページが見つかりました。WAI-ARIAって見たことあるけどよく知らないっていうものだったけど、W3CによるWebアクセシビリティに関するプロジェクトのことらしい。

その辺を見てたら欲しいものそのまんまのlistboxのサンプルを載せてるページを発見。 https://www.w3.org/TR/wai-aria-practices/examples/listbox/listbox-scrollable.html

image

このページで読み込まれてるjsの中にlistbox.jsというのがあって、W3C的にどうしてるのかが分かって非常に良かった。興味があれば読んでみると良いのではないでしょうか。500行くらいの大して難しくない実装でした。

listbox.jsの中では、カーソルキーのキーボードイベントを取得してフォーカスを移動してることが分かりました。予想した通りじゃないか。それだけだとフォーカスが描画範囲から外れてもスクロールの追従とかやってくれないからカーソルイベントに合わせてスクロール位置の調整とかもしないといけない。まぁそうだよなぁ、と理解できるけど、泥くさくやってるのだなぁ。

描画要素へのフォーカス

W3Cがそうしてるならしょうがない、ということで自分でlistboxを実装しようとしたけど、同じようにやってももul要素にキーボードのフォーカスがあたらない。 ググったところ、 ulとかのような本来フォーカスが当たらない要素にフォーカスを当てるには(?)、 tabindex 属性を追加しないといけないらしい。というかtabindex属性を追加するとそういうことができるらしい。 見返してみたらSlackにもW3Cのサンプルにもあったわ。

HTMLのドキュメントにはこんなふうに記載されてる模様

image

Extension側の実装

Chrome Extensionは数年前にも実装したことがあったけど、その頃からあんまり変わってないな?という印象。 manifest書いてbackgroundで動くスクリプトやら、content_scriptとして動かすjsを書いてっていう感じ。

querySelectorも賢いし特にjQueryとか使う必要も無いな、といった状態なのは余計なもの入れなくて済むのでありがたい。

今のところ1ファイルに全部押し込んでるけど、Chromeがまだimport/exportをサポートしてるわけでもないし、requireもできないから普段のノリでjsを書こうとするとビルドツールを入れなきゃいけなさそうだなぁとなった。

しかしたった数個のスクリプトのためにwebpackやらbrowserifyを入れるのはとてもToo much感が… とはいえ、jsを分割して読み込み順に気をつけながら、とかもうやってらんない。babelでこの辺どうにかできたかな?

非同期コンテンツへの対応

チャットワークを使ったことがある人だと分かるかもしれないけど、サイトにアクセスすると非同期でコンテンツを取ってきてレンダリングする方式なんですよね。そういうサイトだとChrome拡張のcontent_scriptの実行タイミングではどうしてもDOMが出来上がってないので要素が見つからなくてエラーになります。

そういうときに MutationObserver っていうのが使える模様。

MutationObserver provides developers with a way to react to changes in a DOM. It is designed as a replacement for Mutation Events defined in the DOM3 Events specification.

DOMの変更に対してリアクションする機能を提供してくれる。もともとMutation EventsっていうのがあったけどこれはもうWeb Standardから除外されてて、今はこのMutationObserverを使うらしい。

このObserverをキレイに実装してくれてるgistを見つけたのでほぼそのまま使わせてもらってる https://gist.github.com/jwilson8767/db379026efcbd932f64382db4b02853e

MutationObserverで目的の要素がレンダリングされたらextensionの処理を動かすようにして無事に実行できた。

Observerを使って目的のDOMが生成されたのを検知したらDOMに対してもろもろイベントリスナーをセットする、というわけです。

このあと

Toリストはこんな事情で割と簡単に実装できたけど、あとはできれば>で始まる行を引用タグで囲うようにしたい。

こんな拡張作りましたよっていう話をフロントエンドチームの方に話したらプルリクくれって言われたので、時間を作ってやっていきたい。けど既存のUXから見ると異質な動きな気がするからちょっと気後れしますね。

最近プライベートでフロントまわりしか触ってないな