31
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

モバイルWEB用の Qiita 新着投稿リーダーを作った

Last updated at Posted at 2015-01-15

[2014/01/23追記]

デザインを刷新&検索機能をつけました。
[電車やカフェでの暇つぶしに] Qiita 新着投稿リーダーに検索機能をつけた

--

ionic を利用して作ってみました。Qiita の投稿の取得には Qiita API v1 を利用しています。
v2 だとクロスオリジン制約にひっかかるので。)

URL

静的リソース(JavaScript/CSS/HTML/画像)のみで構成されてるので、GitHub Pages から配信しています。

新着投稿を引っ張ってくるだけの単純なものですが、Qiita API を駆使すればもっと色々できそう。

ソースコードは GitHub に置いてあります。ご自由にどうぞ。
https://github.com/hkusu/qiita-newpost-reader

環境

ソースコードについて

ほぼ ionic の機能で実装してるので、凝ったことはしていないのです。主要なコードは下記の2ファイルのみ。

Controller

ファットコントローラーになっていますが.. AngularJS をぼーっと使ってると、どうしてもコントローラは大きくなっちゃいますね。

src/app/main/main.controller.js
'use strict';

angular.module('qiitaNewpostReader')
  .controller('MainCtrl', function ($scope, $http, $ionicLoading, $ionicModal, $ionicScrollDelegate, $timeout) {
    // ページ下部の infinite-scroll を表示するか否かのフラグ
    $scope.initial_loaded = false;

    $scope.load = function(_page){
      // Loading... を表示
      $ionicLoading.show({
        template: 'Loading...',
        noBackdrop: true
      });
      $http.get('https://qiita.com/api/v1/items?per_page=10&page=' + _page).success(function(items) {
        // 初期表示とページ上部のリフレッシュの際はクリア
        if(_page == 1){
          $scope.items= [];
        }
        // 既存の内容に結合
        $scope.items = $scope.items.concat(items);
        // Loading... を隠す
        $ionicLoading.hide();
        // ページ上部のリフレッシュ表示を終了
        $scope.$broadcast('scroll.refreshComplete');
        // 初期表示で infinite-scroll が有効になってしまうので、数秒まつ
        $timeout(function() {
          $scope.initial_loaded = true;
        }, 1000);
        // 数秒まってからページ下部の infinite-scroll を終了する。でないとループしてしまうため
        $timeout(function() {
          $scope.$broadcast('scroll.infiniteScrollComplete');
        }, 3000);

        $scope.next_page = _page + 1;
      });
    };

    // 初期表示
    $scope.load(1);

    // 記事詳細 モーダル表示
    $scope.modal_title = "";
    $scope.modal_body = ""; // bodyの方はマークダウン(コードハイライト付き)&絵文字を表示
    $ionicModal.fromTemplateUrl("post.modal.html", {
      scope: $scope,
      animation: "slide-in-up"
    }).then(function(modal) {
      $scope.post_modal = modal;
    });
    $scope.openPostModal = function(index) {
      $scope.modal_title = $scope.items[index].title;
      $scope.modal_body = $scope.items[index].raw_body;
      // スクロールをTOPへ。ハンドルを指定して、背後の ScrollView がTOPにスクロールされないようにする
      $ionicScrollDelegate.$getByHandle('subScroll').scrollTop();
      $scope.post_modal.show();
    };
    $scope.closePostModal = function() {
      $scope.post_modal.hide();
    };
    $scope.$on("$destroy", function() {
      $scope.post_modal.remove();
    });

    // info モーダル表示
    $ionicModal.fromTemplateUrl("info.modal.html", {
      scope: $scope,
      animation: "slide-in-up"
    }).then(function(modal) {
      $scope.info_modal = modal;
    });
    $scope.openInfoModal = function(index) {
      $scope.info_modal.show();
    };
    $scope.closeInfoModal = function() {
      $scope.info_modal.hide();
    };
    $scope.$on("$destroy", function() {
      $scope.info_modal.remove();
    });
  });

View

こちらも3画面分を1テンプレートに詰め込んでいるので、ファットになっています。ちゃんとやるなら分割した方がよいです。

Markdown を表示する部分は <div class="markdown" marked="modal_body|emoji"></div> です。利用しているライブラリは angular-markedHighlight.jsAngularJS Emoji Filter です。
(導入方法は、この投稿の 環境 の項からリンクしている記事を参照ください。)

src/app/main/main.html
<ion-header-bar align-title="center" class="bar-balanced">
  <div class="buttons">
    <a
      class="button button-clear button-light button-small"
      ng-click="openInfoModal()"
      >
      <i class="ion-information-circled"></i>
    </a>
  </div>
  <h1 class="title">Qiita 新着投稿</h1>
  <div class="buttons">
    <a
      class="button button-outline button-light button-small"
      ng-href="http://qiita.com"
      >
      <font size="1px">
        本サイトへ
      </font>
    </a>
  </div>
</ion-header-bar>
<ion-content
  delegate-handle="mainScroll"
  padding="true"
  style="background: floralwhite; margin-top: -10px"
  >
  <ion-refresher
    on-refresh="load(1)"
    >
  </ion-refresher>
  <div align="center">
    <font size="1px" color="gray">
      pull to refresh..
    </font>
  </div>

  <ion-list>
    <ion-item ng-repeat="item in items"
              can-swipe="false"
              class="item-thumbnail-left" ng-click="openPostModal($index)">
      <img ng-src="{{item.user.profile_image_url}}">
      <h5>
        <font color="gray">
          {{item.user.url_name}} が {{item.created_at_in_words}}前 に投稿しました。
        </font>
      </h5>
      <h3><font color="#3cb371">{{item.title}}</font></h3>
      <h5>
        <font color="gray">
          <i class="ion-ios-folder-outline"></i> {{item.stock_count}}
          &nbsp;&nbsp;<i class="ion-ios-chatbubble-outline"></i> {{item.comment_count}}
        </font>
      </h5>
      <h6><br></h6>
      <h4>
        <font color="gray">
          {{item.raw_body}}
        </font>
      </h4>
    </ion-item>
  </ion-list>
  <ion-infinite-scroll
    ng-if="initial_loaded"
    on-infinite="load(next_page)"
    distance="20%"
    >
  </ion-infinite-scroll>

  <!-- 記事詳細 モーダル表示 -->
  <script id="post.modal.html" type="text/ng-template">
    <ion-modal-view>
      <ion-header-bar align-title="center" class="bar-balanced">
        <h1 class="title">{{modal_title}}</h1>
        <div class="buttons">
          <button class="button button-clear" ng-click="closePostModal()">
            <i class="ion-close"></i>
          </button>
        </div>
      </ion-header-bar>
      <ion-content
        delegate-handle="subScroll"
        padding="true"
        style="background: floralwhite; padding: 0 10px 10px 10px"
        ng-click="closePostModal()"
        >
        <h4><font color="#3cb371">{{modal_title}}</font></h4>
        <hr>
        <div class="markdown" marked="modal_body|emoji"></div>
      </ion-content>
    </ion-modal-view>
  </script>

  <!-- info モーダル表示 -->
  <script id="info.modal.html" type="text/ng-template">
    <ion-modal-view>
      <ion-header-bar align-title="center" class="bar-balanced">
        <h1 class="title">About</h1>
        <div class="buttons">
          <button class="button button-clear" ng-click="closeInfoModal()">
            <i class="ion-close"></i>
          </button>
        </div>
      </ion-header-bar>
      <ion-content
        padding="true"
        style="background: floralwhite; padding: 30px 10px 10px 70px"
        ng-click="closeInfoModal()"
        >
        <div align="left">
          <i class="ion-social-octocat"></i>
          <a ng-href="https://github.com/hkusu/qiita-newpost-reader"
             target="_blank"
            >
            hkusu/qiita-newpost-reader
          </a>
          <br>
          <i class="ion-person"></i>
          <a ng-href="https://hkusu.github.io"
             target="_blank"
            >
            hkusu.github.io
          </a>
        </div>
      </ion-content>
    </ion-modal-view>
  </script>
</ion-content>

あと、ionic と Markdown のライブラリが競合して、Markdown がうまい具合に表示されない問題がありました。なので次のような CSS を用意して、<code><pre> タグをキャッチして Markdown ぽく表示されるように工夫しています。

src/app/index.css
.markdown img {
  max-width: 100%;
  height: auto;
}

.markdown code {
  background-color: #f5f5f5;
  padding: 2px 2px 2px 2px;
  border-radius: 2px;
  -webkit-border-radius: 2px;
  -moz-border-radius: 2px;
}

.markdown pre {
  background-color: #f5f5f5;
  padding: 5px 5px 5px 5px;
  border-radius: 5px;
  -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  margin: 10px 0 10px 0;
  font-size: 0.8em;
}

.markdown i.emoji {
  vertical-align: middle;
}

a:link { color: gray; }
a:visited { color: gray; }
a:hover { color: #ff0000; }
a:active { color: #ff8000; }

おわりに

扱うデータが大きいので、実際にモバイル端末で表示するとちょっと重いかな^^;

Qiita API は認証すればもっと色々できるので、次回はここからストックしたり、あとは Cordova 等でネィティブアプリにパッケージしてみようと思います。

31
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?