[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の導入手順についてはこちら。
- マークダウン(コードハイライト付き)と絵文字の表示についてはこちら。
ソースコードについて
ほぼ ionic の機能で実装してるので、凝ったことはしていないのです。主要なコードは下記の2ファイルのみ。
Controller
ファットコントローラーになっていますが.. AngularJS をぼーっと使ってると、どうしてもコントローラは大きくなっちゃいますね。
'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-marked と Highlight.js 、AngularJS Emoji Filter です。
(導入方法は、この投稿の 環境
の項からリンクしている記事を参照ください。)
<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}}
<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 ぽく表示されるように工夫しています。
.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 等でネィティブアプリにパッケージしてみようと思います。