Electron で Live Dwango Reader（旧 Live Door Reader）クライアントを作ってみる

まだまだ機能は乏しいし、既読化も実装していません（テストでフィードがなくなっちゃうと痛いので後回し）が、ぼく自身、常用しているショートカットまでを実装できたので、ご紹介。

[レポジトリはこちら。](https://github.com/k0sukey/Electron-LDR)

![capture1.png](https://qiita-image-store.s3.amazonaws.com/0/47686/01eac0ef-9d3a-d68d-4d5c-7d364c7f8f5a.png)

![capture2.png](https://qiita-image-store.s3.amazonaws.com/0/47686/c0889e8b-0478-d58a-db00-1a296510d15a.png)

### 使っている npm パッケージ

* React：コンポーネントとかレンダリングとか
* react-modal：元記事表示用のモーダルを作る時に
* mousetrap：キーボードショートカットで
* request：API と通信
* lodash：なんやかんやでいつも使う

### できること

* フィード一覧の取得、表示
* 選択されたフィードを読み込んで、記事一覧を表示
* 元記事の表示
* 最低限のキーボードショートカット

### （まだ）できないこと

* 認証
* その他いっぱい

### 構成

```
main.js
app/
  component/
    feeds.js
    items.js
  html/
    index.html
  style/
    style.css
  app.js
src/
  feeds.jsx
  items.jsx
```

**main.js** がアプリのエントリーポイントになります。package.json からアクセストークンを取得してウィンドウを作っているだけですね。app/html/index.html がウィンドウに読み込まれます。

**app/html/index.html** は VirtualDOM のコンテナを作って、app/app.js を読み込んでいるだけです。コンテナは、サイドバーのフィード一覧と、フィードが読み込まれた際のアイテム一覧、元記事表示ようのモーダルになります。あとは CSS を読み込んだり。

**app/app.js** で実際にページを作っていきます。API を叩いているだけですね。mousetrap で ```r``` キーをフィード一覧の更新に割り当てていますが、サイドバーをまるっと読み込みなおしています。たぶん React 的にもっとかっちょいい方法があると思いますが、今後書きなおすかもしれません。

**app/component/feeds.js** は、src/feeds.jsx からコンパイルされて作られます。サイドバーのフィード一覧ですね。mousetrap で ```a``` と ```s``` が割り当てられています。前と次のフィードへ戻ったり進んだりするショートカットです。

render の中で \<ul> と \<li> を作っているのですが、当初、\<li> は別コンポーネントだったんですね。マウスオーバー・アウトした時やショートカットで移動する時、コンポーネント間のやりとりはどうするんだろう？って感じだったのですが、[ドキュメントを見てみると](https://facebook.github.io/react/tips/communicate-between-components.html)、この書き方が一般的なようで書き直しました。

```jsx
render: function(){
    return (
        <ul>{this.props.feeds.map(function(item, index){
                return (
                    <Feed item={item}/>
                );
            }, this)}
        </ul>
    );
}
```

これが、

```jsx
render: function(){
    return (
        <ul>{this.props.feeds.map(function(item, index){
                return (
                    <li key={item.subscribe_id}
                        onMouseOver={this.doMouseOver.bind(this, index)}
                        onMouseOut={this.doMouseOut.bind(this, index)}
                        onClick={this.doClick.bind(this, index)}>
                        <img src={item.icon}/> {item.title} ({item.unread_count})
                    </li>
                );
            }, this)}
        </ul>
    );
}
```

こうなりました。

書いてみると、なるほどね、と納得です。クリックやマウスオーバー・アウトのイベントが一つ一つのコンポーネントに貼られてしまうし、```this.props.feeds``` があるので他のフィードを意識できるしで、コンポーネントを分けすぎるのもダメですねって感じでした。
あとは、フィードをクリックすれば記事のアイテムが右ペインに読み込まれます。ほぼ LDR と同じですね。

**app/component/items.js** は、src/items.jsx からコンパイルされて作られます。記事のアイテムがずらーっと読み込まれます。mousetrap で ```j``` ```k``` ```v``` ```n``` が割り当てられています。記事の移動と元記事の開閉になります。なんでぼくが LDR のアプリ化をしたかというと、ほぼ、ココのためになります。ブラウザだと、元記事開くとタブが開かれるんですよね。タブを移動したくないのです。このアプリでは ```v``` で react-modal を使って、モーダルで元記事を開きます。LDR にはありませんが、```n``` で元記事のモーダルを閉じます。
app/component/feeds.js と同じように子コンポーネントを作っていたのですが、ここも書き直しました。記事の移動のためですね。フィードの移動はクリックイベントを偽装していたのですが、記事の移動は ```scrollIntoView``` を使っています。

元記事の表示には Electron の \<webview> を使っています。ここ、React って楽だなあと思ったところです。```src``` は ```this.state.url``` になっていますので、```this.setState({ url: '元記事の URL' });``` してあげるだけで表示する元記事を切り替えられます。とっても楽！モーダルの開閉も ```this.state``` 使っていますね。ホント楽！

```jsx
render: function(){
    return (
        <ul>{this.props.items.map(function(item, index){
                return (
                    <li id={item.id} key={item.id}>
                        <p style={style.title} onClick={this.doOpen.bind(this, index)}>{item.title}</p>
                        <div dangerouslySetInnerHTML={{__html: item.body}}/>
                    </li>
                );
            }, this)}
            <Modal isOpen={this.state.modalIsOpen}>
                <div style={style.close}><button onClick={this.doClose}>閉じる</button></div>
                <webview src={this.state.url} style={style.browser}></webview>
            </Modal>
        </ul>
    );
}
```

### うごかしかた

まずはソースコードをチェックアウトしてきましょう。

```bash
$ git clone git@github.com:k0sukey/Electron-LDR.git
$ cd Electron-LDR
```

で、npm パッケージをインストールします。

```bash
$ npm install
```

認証ができていないので、[http://api.ma.la/reader.html](http://api.ma.la/reader.html) からアクセストークンをもらってきます。authorize して、インスペクタ等でローカルストレージにあるアクセストークンを package.json にコピペしてください。有効期限が切れたらもう一度 authorize すれば OK です。

```json
{
  "access_token": "****************************************",
  "token_type": "Bearer",
  "expires_in": 7200,
  "created_at": 1442133518
}
```

↑の access_token を、

```json
{
  "name": "Electron-LDR",
  // 略
  "devDependencies": {
    "electron-packager": "^5.0.2",
    "electron-prebuilt": "^0.31.0",
    "gulp": "^3.9.0",
    "gulp-babel": "^5.2.1",
    "gulp-load-plugins": "^0.10.0"
  },
  "token": "ここにコピペしてください"
}
```

コマンドラインで起動します。

```sh
$ npm start
```

### まとめ

あらためて、Electron と React って親和性高いなって思いました。あと、自前で認証できないのは致命的ですね...。何とかしないと。