キーワードをハイライトできるテキストボックスをReactで作った
山場
ハイライト自体は、contenteditable
+keyup
+el.innerHTML.replace
ですぐに動く。問題はel.innerHTML = el.innerHTML.replace(...)
したあとカーソルが先頭に戻ってしまうこと。これによりユーザーが入力し続けることができず、入力装置として使い物にならない。
賢者がいた
師「この問題、好きなんだよね。けっこうやった。だいたい動いてると思うよ」。
カーソルの位置を特定するために、テキストノードまで潜って数を数える。最終的な位置セットやもろもろのユーティリティーにrangy
というライブラリを使っている。これがnpmにあってとても助かった。
npmモジュール
賢者からの返事を待ってreact-highlightable
という名前でnpm publishしようと思ってる。
インターフェース
とりあえずこんな風にした。
const Highlightable = require('../react-highlightable'); const validUsers = ["ariel", "belle", "jasmine"]; ReactDOM.render( <div> <Highlightable className="highlightable" onChange={onChange} pairs={[ [Highlightable.TokenPreset.URL, 'hilite hilite--link'], [Highlightable.TokenPreset.USER_MENTION, token => `hilite hilite--user hilite--user-${ validUsers.includes(token.slice('@'.length)) ? 'valid' : 'invalid'}`], ['yeah|ohh', 'hilite hilite--token'] ]} value='@jasmine and @belle are you still there? Visit www.google.com' ></Highlightable> </div> )
ハイライトする文字列のパターンとclassNameまたはclassNameを作る関数をペアで渡す形式にした。
よくありそうなURL、EMAIL、USER_MENTIONはプリセットとして正規表現を用意しておいた。
使いどころ
URLハイライトとかはコメント投稿のプレビューとかに使えるかも。全ユーザー一覧をローカルに持ってることは多くないはず。サーバーに問い合わせて&ハイライトの条件を変えて再び描画、という使い方になると思う。
既知の問題
ヒットしたキーワードにカーソルが接触した状態で改行されると、contenteditable
のデフォルトの動きがそうであるように、ハイライト用のスタイルを引き継いで新規行が作られてしまう。賢者のデモでも同じだった。スタイル次第では目立たないが、背景色があると結構めだつ。その後文字を入力すると問題は直る。
またハイライトしたキーワードにfontawesomeでアイコンをつけたところ、カーソル補正が壊れた。CSS擬似要素が文字カウントに影響しているようだ。こちらはなんとかなる気はする。