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

  • 50
    いいね
  • 1
    コメント
この記事は最終更新日から1年以上が経過しています。

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

レポジトリはこちら。

capture1.png

capture2.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 で as が割り当てられています。前と次のフィードへ戻ったり進んだりするショートカットです。

render の中で <ul> と <li> を作っているのですが、当初、<li> は別コンポーネントだったんですね。マウスオーバー・アウトした時やショートカットで移動する時、コンポーネント間のやりとりはどうするんだろう?って感じだったのですが、ドキュメントを見てみると、この書き方が一般的なようで書き直しました。

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

これが、

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 って楽だなあと思ったところです。srcthis.state.url になっていますので、this.setState({ url: '元記事の URL' }); してあげるだけで表示する元記事を切り替えられます。とっても楽!モーダルの開閉も this.state 使っていますね。ホント楽!

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>
    );
}

うごかしかた

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

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

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

$ npm install

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

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

↑の access_token を、

{
  "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": "ここにコピペしてください"
}

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

$ npm start

まとめ

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