pig's diary

何でも忘れるので万年初心者ね

キーワードをハイライトできるテキストボックスをReactで作った

デモ

github.com

山場

ハイライト自体は、contenteditable+keyup+el.innerHTML.replaceですぐに動く。問題はel.innerHTML = el.innerHTML.replace(...)したあとカーソルが先頭に戻ってしまうこと。これによりユーザーが入力し続けることができず、入力装置として使い物にならない。

賢者がいた

師「この問題、好きなんだよね。けっこうやった。だいたい動いてると思うよ」。

stackoverflow.com

カーソルの位置を特定するために、テキストノードまで潜って数を数える。最終的な位置セットやもろもろのユーティリティーに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擬似要素が文字カウントに影響しているようだ。こちらはなんとかなる気はする。