キーワードをハイライトできるテキストボックスを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擬似要素が文字カウントに影響しているようだ。こちらはなんとかなる気はする。
MutationObserver/Mutation Events は孫DOMへの操作も検知するのか
MutationObserver/Mutation Events は、孫DOMへの操作も検知するのだろうか。Chrome48.0.2564.103 とFirefox44.0.2で検証した。
※ざっくりした調査なのでご参考までに。
結論
MutationObserverは仕様にsubtree
オプションがあり、ChromeもFirefoxもこれを実装している。問題なく検知する。
Mutation Events (廃止された仕様) は、Chromeで一部検知しない。Firefoxでは検知できた。
Mutation Events
廃止された仕様。使ってはいけない。Mutation events - Web developer guides | MDN
とりあえず、この3つだけを調査の対象とする。全イベント一覧はこちら
- DOMAttrModified
- DOMNodeInserted
- DOMNodeRemoved
HTML
<body> <div> <div id="descendant"></div> </div> <script src="./main.js"></script> </body>
JavaScript
'use strict'; /* * Mutation Events を document.body に登録する */ [ 'DOMAttrModified', 'DOMNodeInserted', 'DOMNodeRemoved' ] .forEach(type => { document.body.addEventListener(type, e => { console.log('=======', e.type); }); }); /* * 孫DOMに対し、操作を加えてみる */ test(); function test() { let el = document.getElementById('descendant'); let textNode = document.createTextNode('boooom'); el.setAttribute('boom', 'boom'); el.appendChild(textNode); setTimeout(() => el.removeChild(textNode), 800); }
実行結果
Firefox
全てのイベントがdocument.body
まで伝播している。
======= DOMAttrModified a.js:10:5 ======= DOMNodeInserted a.js:10:5 ======= DOMNodeRemoved a.js:10:5
Chrome
DOMAttrModifiedだけdocument.body
に来ない。
======= DOMNodeInserted a.js:10 ======= DOMNodeRemoved a.js:10
MutationObserver
MutationObserver - Web API インターフェイス | MDN
結果として、FirefoxもChromeも仕様通り機能した。平和平和。
JavaScript
HTMLと、検証用test()
関数は上記と同じ。MutationObserverのコードをこうした。
/* * Mutation Observerを登録。 */ let mo = new MutationObserver(mutationRecords => { console.log('=======', mutationRecords.map(r => r.type)); }); mo.observe(document.body, { childList: true, attributes: true, characterData: true, subtree: true, /* ここポイント */ attributeOldValue: true, characterDataOldValue: true });
Firefox
======= Array [ "attributes", "childList" ] a.js:19:3 ======= Array [ "childList", "characterData", "characterData" ] a.js:19:3 ======= Array [ "childList" ] a.js:19:3
Chrome
======= ["attributes", "childList"] a.js:19 ======= ["childList", "characterData", "characterData"] a.js:19 ======= ["childList"] a.js:19
同じ結果を返していることが分かる。
参考にした記事
非推奨になった Mutation events を Mutation Observers に置き換えよう - ログろいど
urxvt が unicode 記号を見つけるのに失敗
軽量ターミナル urxvt (rxvt-unicode) でチェックマーク(✓)が出てくれなかった。
失敗したときのフォント設定はこちら。
! ~/.Xdefaults urxvt.font: xft:Ricty:Normal:size=14:antialias=true
✓ がでないと「ヨシッ」って気持ちになれないですよ。
rxvt FAQ によると、フォントは複数指定でき、見つかるまで順に探していくとのこと。
rxvt-unicode は最初にベースフォントから文字を探す。見つからなければ、次、次という感じ。持ってるフォントを予め宣言しておけば、探索も速いし rxvt-unicode と X-server のリソース消費も少なくて済む。
速そうで良い。他のターミナルがどうフォントを探すのかを知りたいところ。
チェックマークを求めて手持ちのフォントを試したのだが、この宣言で表示することができた。
! ~/.Xdefaults urxvt.font: xft:Ricty:Normal:size=14:antialias=true, \ xft:DejaVu Sans Mono:size=13
ヨシッ。
- rxvt FAQで説明のある
urxvt -fn basefont,font2,font3...
と、私の書いた .Xdefaults の設定が同等であるかどうかは未検証。 - DejaVu Sans Mono を size=14 にしたらまたダメになった。理由知っている方いたら教えてください。
- 参考
VirtualBox で VMDK 形式のディスク容量を大きくする
前提
- VirtulaBox を使ってる
- vm のディスクが VMDK 形式だ(vm情報のストレージ項目 に "box-disk1.vmdk" のような拡張子のファイル名があったらそう)
- ディスク容量が足らなくなっちゃった
ディスクをリサイズする
VMDK形式を直接リサイズはできないようで、いちどVDI形式に変換してから戻しています。
$ cd ~/VirtualBox\ VMs/my_vm_01/ $ ls Logs Snapshots box-disk1.vmdk my_vm_01.vbox my_vm_01.vbox-prev $ VBoxManage clonehd "box-disk1.vmdk" "cloned.vdi" --format vdi $ VBoxManage modifyhd "cloned.vdi" --resize 16384 # 単位はMB $ VBoxManage clonehd "cloned.vdi" "resized.vmdk" --format vmdk
ディスクを交換
パーティションのリサイズ
どうやってもいいと思うのですが、パーティションのリサイズ専用のOSが紹介されていたので使ってみました。
- GParted http://gparted.sourceforge.net/download.php から gparted-live-xxx.iso をダウンロード
- my_vm_01 を gparted-live-xxx.iso で起動
- いろいろ適当に答える。debian風デスクトップになり、パーティション編集アプリが起動するので、GUIで広げて変更を適用。
- reboot
確認
します。
$ df -h Filesystem Size Used Avail Use% Mounted on /dev/sda3 16G 7.4G 6.9G 52% /
オッキくなってます。変換前の "box-disk1.vmdk" はもう要らないと思います。
お得情報
こちらから抜粋。VirtualBox は3つのディスク形式をサポートしているらしい。
http://www.atmarkit.co.jp/ait/articles/1009/30/news122.html
仮想ディスク・ファイルのタイプとしては、.VDI(業界標準のOVF:Open Virtualization Format規約に沿った仮想ディスク形式)か.VMDK(VMware形式)、.VHD(Virtual PCやVirtual Server、Hyper-Vなどのファイル形式)をサポート
参考:
Soy の boolean 評価はどうなってる?
基本的には JavaScript の boolean 評価にならっているようです。
/** * ここからレンダリングをスタート。 */ {template .soyweb} {call .myTemplate} {param myString: '' /} {param myBoolean: false /} {param myNumber: 0 /} {param myNullable: null /} // $myUndefined を引数なし(undefined)で呼んでみる {/call} {/template} /** * @param myString * @param myBoolean * @param myNumber * @param myNullable * @param? myUndefined */ {template .myTemplate} 下のどれも出力されません。 {if $myString} 出ません {/if} {if $myBoolean} 出ません {/if} {if $myNumber} 出ません {/if} {if $myNullable} 出ません {/if} {if $myUndefined} 出ません {/if} {/template}
JavaScript書きにはとても使いやすい形ですが、空文字列や数字0には注意が必要。
しかし、?: 演算子の真偽評価は if とは違います。?: は isNonnull() を使う動きをします。上と同じ .soyweb から呼んだとして、こうなります。
{template .myTemplate} {$myString?:'boom'} // 空文字を出力 {$myBoolean?:'boom'} // "false" を出力 {$myNumber?:'boom'} // 0 を出力 {$myNullable?:'boom'} // "boom" を出力 {$myUndefined?:'boom'} // "boom" を出力 {/template}
もしかすると注意を要したのは私だけかも知れませんがね。ガハッハッハッハ!
ブログの移転
ブログを引っ越してがんばり直します。
http://pig.hatenadiary.com/
ArchLinux の Wi-Fi 設定
Mac mini (Late 2012) にパーティション切ってArchLinux を入れている。Wi-Fi 設定の過程で試したことをメモしておく。-- 起動時に自動的にWi-Fi を探すよう加筆。
まず前提として、pacstrap などは Ethernet 経由で入れ、ネットに繋がるようにはしていた。aur パッケージを入れるための yaourt も持っていた。
その上で以下モジュールを入れる。
$ yaourt -S b43-fwcutter b43-firmware crda wireless-regdb # reboot
再起動する(たぶん b43 のロードに必要)。
続いてwifi-menu設定のためのパッケージを入れる(/etc/netctl/xxx さえ作れればなくてもいいんだろうけど)。
$ yaourt -S dialog wpa_supplicant
必要なものは揃っているので、繋げるか確認。
# modprobe ath5k # modprobe b43 # ip link # デバイスIDは wlp2s0 だった # ip link set wlp2s0 up # wifi-menu # これにより /etc/netctl/wlp2s0-networkname が作成される # ping google.com # 繋がっていたらOK
b43 はロード済みのはずだが、ath5k のあとに再度ロードしないと動かなかった。ロードの順番があるようだ。
接続が確認できたら、起動時に自動的にWi-Fiを探すよう設定する。起動時に必要なモジュールをロードさせ、Wi-Fiを探すデーモンを自動起動させる。
# echo ath5k > /etc/modules-load.d/ath5k.conf # echo b43 > /etc/modules-load.d/b43.conf # pacman -S wpa_actiond # netctl-auto のため # systemctl enable netctl-auto@wlp2s0
これにより、先ほどwifi-menuで作った設定が自動的に使われるはず。reboot して起動後、すぐネットに繋がるだろうか?
# reboot
繋がれば成功。
参考: