JavaScript
Node.js
React
Electron

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

More than 3 years have passed since last update.

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

レポジトリはこちら。

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 って親和性高いなって思いました。あと、自前で認証できないのは致命的ですね...。何とかしないと。