Qiita
JavaScript
vue.js
Electron

Vue.jsとElectronでQiitaの簡単記事リーダーを作成してみました。

More than 1 year has passed since last update.

この記事はElectron Advent Calender 2016 17日目の記事です。

NodeJS,Electronの知識がゼロで、Qiitaリーダーをつくってみました。
Qiitaリーダーにしたのは普段Qiitaはアプリで見ていて、PCでもその延長でアプリを立ち上げてみるという感覚で見たかったので、個人用につくって見ました。(名前のchiqiitaには特に意味はないです……汗)

qitta_electron.png

作ってみて分からなかったことなど、書いてみようと思います。

使用したライブラリ

環境はVue.js2.0 + browserify。
routerは公式のvue-router。ajaxはaxiosを利用しました。
開発環境については基本的にvue-cli頼りです。初めてライブラリのcliツールを利用して見ましたが、
とっても便利ですね。
レイアウト用のフレームワークはuikitを利用しました。

構成

  • top - 一覧画面
    • 詳細画面
  • ストック済み
    • 詳細画面

今回はElectronとvue.jsになるためにものすごくシンプルな構成としました。
自分はどちらも初心者なので、下手に範囲を広げて終わりが見えないことがないようにしました。

一覧画面

<template>
    <div id="home">
        <h1>最新記事一覧 50件</h1>
        <div v-if="isLoading">
            <div class="uk-text-center"><i class="uk-icon-large uk-icon-spinner uk-icon-spin"></i></div>
        </div>
        <div v-if="!isLoading">
            <ul class="uk-nav uk-nav-side uk-list uk-list-line">
                <li v-for="item in stocks">
                    <router-link :to="{ name: 'entry', params: { id: item.id }}">
                        <div class="uk-grid">
                            <div class="uk-width-1-10">
                                <img :src="item.user.profile_image_url" alt=""/>
                            </div>
                            <div class="uk-width-9-10">
                                <h2>{{ item.title }}</h2>
                                <div>
                                    <span v-for="tag in item.tags" class="uk-badge">{{ tag.name }}</span>
                                </div>
                            </div>
                        </div>
                    </router-link>
                </li>
            </ul>
            <ul class="uk-pagination">
                <li v-if="links.prev" class="uk-pagination-previous"><router-link :to="{ name: 'home', params: { page: links.prev }}">前のページへ</router-link></li>
                <li v-if="links.next" class="uk-pagination-next"><router-link :to="{ name: 'home', params: { page: links.next }}">次のページへ</router-link></li>
            </ul>
        </div>
    </div>
</template>
<script>
    import api from '../util/Api';
    import url from 'url';

    export default {
        name: 'home',
        data () {
            return {
                isLoading: true,
                stocks: [],
                links: {}
            }
        },
        watch: {
            '$route' () {
                this.fetchItem();
            }
        },
        created () {
            this.fetchItem();
        },
        methods: {
            fetchItem () {
                this.isLoading = true;
                api({
                    method: 'get',
                    url: '/items',
                    params: {
                        page: this.$route.params.page,
                        per_page: 50
                    }
                }).then(res => {

                    let links = res.headers.link.split(',');
                    let tmp = {};
                    links.forEach(data => {
                        let rel = data.split(';')[1];
                        rel = rel.trim().replace(/rel="(.*)"/g, (match, p1) => p1);
                        let urlStr = data.split(';')[0].replace(/<(.*)>/g, (match, p1) => p1).trim();
                        tmp[rel] = url.parse(urlStr, true).query.page;
                    });
                    links = tmp;

                    this.links = links;
                    this.stocks = res.data;
                    this.isLoading = false;
                }).catch(err => {
                    this.stocks = [{
                        title: 'エラーが発生しました',
                        id: '0'
                    }];
                    this.isLoading = false;
                });
            }
        }
    }
</script>
<style scoped>
    .uk-badge {
        margin-right: 2px;
    }
</style>

apiからデータを取得して一覧に表示という流れはよくある流れなのですが、
qiitaのapiのページネーションはレスポンスヘッダーに含まれているので、
それだけを処理処理して、プロパティに追加しています。

この部分を作成しているときはブラウザで作成していたのですが、
ブラウザとElectronのときではレスポンスヘッダーの帰ってくる量が違っていて、
ブラウザのときにはページネーションのLinkが含まれておらず焦りました……。

ライブラリが理由なのか、それとも、Electronのリクエストがブラウザと違うとかなんですかね。。

詳細画面

<template>
    <div id="entry">
        <div class="uk-article" v-if="isLoading">
            <h1 class="uk-article-title">ローディング中</h1>
        </div>
        <div class="uk-article" v-else>
            <h1 class="uk-article-title"><a v-bind:href="url">{{ post.title }}</a></h1>
            <div class="p-article-meta uk-margin-bottom">
                <dl class="uk-article-meta uk-description-list-horizontal">
                    <dt>Author:</dt><dd>{{ post.user.name }}</dd>
                    <dt>Time:</dt><dd>{{ moment(post.created_at).format('Y-MM-DD') }}</dd>
                    <dt>Profile:</dt><dd>{{ post.user.description }}</dd>

                </dl>
                <div class="p-article-meta-labels">
                    <span class="uk-badge" v-for="tag in post.tags">
                        {{ tag.name}}
                    </span>
                </div>
            </div>
            <hr class="uk-article-divider">
            <div class="p-article-content" v-html="post.rendered_body">
            </div>
            <div class="uk-text-left">
                <a href="#" @click.prevent="historyBack" class="uk-button uk-button-large"><i class="uk-icon-angle-double-left"></i> 戻る</a>
            </div>
        </div>
    </div>
</template>

<script>
    import moment from 'moment';
    import moment_ja from 'moment/locale/ja';
    import api from '../util/Api';

    moment.locale('ja');

    export default {
        name: 'entry',
        data  () {
            return {
                isLoading: true,
                post: null
            }
        },
        watch: {
            '$route': 'fetchItem'
        },
        created () {
            this.fetchItem();
        },
        methods: {
            moment,
            fetchItem() {
                this.isLoading = true;
                api({
                    method: 'get',
                    url: '/items/' + this.$router.currentRoute.params.id
                }).then(res => {
                    this.post = res.data;
                    this.isLoading = false;
                }).catch(err => {
                    this.title = 'エラー';
                    this.rendered_body = err;
                    this.isLoading = false;
                });
            },
            historyBack () {
                window.history.back();
            }
        }
    }
</script>

<style scoped>
    .uk-badge {
        margin-right: 2px;
    }

    .uk-article {
        margin: 0 0 20px;
    }
</style>

ページに関してもルーティングのparamに合わせてデータを取得してページに表示させるだけのシンプルなものです。

日時のフォーマット用にはmomentを利用しました。

終わりに

あとはElectronで表示して、パッケージング化しました。
ただ、実際はページを表示させてるだけになってしまい、Electron、NodeJSの機能を全然利用できていませんでした。

今後は少しずつ拡張していってもっと試していけるように頑張っていきたいと思います。

ありがとうございました。