JavaScript で Markdown 文書を表示してみた


結果として出来たもののデモはこちらです。この記事自体の Markdown を、JavaScript で描写しています :smile:
⇒ http://hkusu.github.io/angularjs-markdown-demo/dist
（改行がうまくいっていない気がしますが、細かいことは気にしない..）

## 今回の方針

- Markdown は GitHub 形式とします
	- コードハイライトに対応します
	- 絵文字に対応します
- AngularJS でやります
	- AngularJS でなくても [marked](https://github.com/chjj/marked) や [Highlight.js](https://github.com/components/highlightjs) を使えばできると思います
- 動作確認として Qiita の新着投稿を表示します
	- [Qiita API v1](https://qiita.com/api/v1/docs) を利用します
		- `v2` を利用ない理由としては、`v2` だとクロスオリジン制約にひっかかる為

### 前提環境

手元の環境は次のとおりです。

> OS：`Mac OS X 10.9.5`
> gulp：`3.8.10`
> YEOMAN(yo)：`1.3.2`
> generator-gulp-angular：`0.7.3`

また下記の手順で結果的にインストールされたもののバージョンは次のとおり。

> AngularJS：`1.3.9`
> anguar-marked：`0.0.12`
> maked：`0.3.2`
> highlightjs：`8.4.0`
> angular-emoji-filter：`0.0.2`

## 手順

### ① AngularJS プロジェクトの雛形を生成

適当なディレクトリを作成してジェネレータを起動します。

```shell
$ mkir angularjs-markdown-demo
$ cd angularjs-markdown-demo
$ yo gulp-angular
```

今回はなんでもいいので適当にオプションを選択する。

![スクリーンショット 2015-01-15 15.19.34.png](https://qiita-image-store.s3.amazonaws.com/0/35733/181d24e6-23fb-ee02-45d5-6c9d4d8b2904.png "スクリーンショット 2015-01-15 15.19.34.png")

雛形ができたら `$ gulp serve` で動作確認。

![スクリーンショット 2015-01-15 15.24.35.png](https://qiita-image-store.s3.amazonaws.com/0/35733/c9e484a5-271d-bd86-5674-ed29db301b73.png "スクリーンショット 2015-01-15 15.24.35.png")

### ② angular-marked と Highlight.js の導入

[angular-marked](https://github.com/Hypercubed/angular-marked) と [Highlight.js](https://github.com/components/highlightjs) を導入します。

> ちなみに Highlight.js は下記が本家？ぽいのですが上手く動かなかった。
> https://github.com/isagalaev/highlight.js

導入手順は基本的に、上記の GitHub の README に記載されているままです。

```shell
$ bower install angular-marked --save
$ bower install highlightjs --save
```

`$ gulp wiredep` とするか、`index.html` を修正します。

```diff:src/index.html
〜

<!-- build:css({.tmp,src}) styles/vendor.css -->
<link rel="stylesheet" href="../bower_components/bootstrap/dist/css/bootstrap.css">
<!-- bower:css -->
+<link rel="stylesheet" href="../bower_components/highlightjs/styles/default.css" />
<!-- endbower -->
<!-- endbuild -->

〜

<!-- build:js scripts/vendor.js -->
<!-- bower:js -->
<script src="../bower_components/jquery/dist/jquery.js"></script>
<script src="../bower_components/angular/angular.js"></script>
<script src="../bower_components/angular-sanitize/angular-sanitize.js"></script>
<script src="../bower_components/angular-ui-router/release/angular-ui-router.js"></script>
+<script src="../bower_components/angular-marked/angular-marked.js"></script>
+<script src="../bower_components/highlightjs/highlight.pack.js"></script>
<!-- endbower -->
+<script src="../bower_components/marked/lib/marked.js"></script>
<!-- endbuild -->

〜
```

> 私の場合は `marked.js` が `$ gulp wiredep` で追記されなかったので、手で追記した。

AngularJS アプリケーションに組み込みます。
（`'hc.marked'` を追記したのと、２つめの `.config` を丸ごと追記）

```src/app/index.js
'use strict';

angular.module('angularjsMarkdownDemo', ['ngSanitize', 'ui.router', 'hc.marked'])
  .config(function ($stateProvider, $urlRouterProvider) {
    $stateProvider
      .state('home', {
        url: '/',
        templateUrl: 'app/main/main.html',
        controller: 'MainCtrl'
      });

    $urlRouterProvider.otherwise('/');
  })
  .config(['markedProvider', function(markedProvider) {
    markedProvider.setOptions({
      gfm: true,
      tables: true,
      highlight: function (code) {
        return hljs.highlightAuto(code).value;
      }
    });
  }])
;
```

これで Markdown をコードハイライト付きで表示する準備はOKです。

### ③ AngularJS Emoji Filter の導入

さらに絵文字（`:smale:`など）を表示するために、[AngularJS Emoji Filter](https://github.com/globaldev/angular-emoji-filter) を導入します。

```shell:
$ bower install angular-emoji-filter --save
```

`$ gulp wiredep` するか手動で `index.html` を修正します。

```diff:src/index.html
〜

<!-- build:css({.tmp,src}) styles/vendor.css -->
<link rel="stylesheet" href="../bower_components/bootstrap/dist/css/bootstrap.css">
<!-- bower:css -->
<link rel="stylesheet" href="../bower_components/highlightjs/styles/default.css" />
<!-- endbower -->
+<link rel="stylesheet" href="../bower_components/angular-emoji-filter/dist/emoji.min.css" />
<!-- endbuild -->

〜

<!-- build:js scripts/vendor.js -->
<!-- bower:js -->
<script src="../bower_components/jquery/dist/jquery.js"></script>
<script src="../bower_components/angular/angular.js"></script>
<script src="../bower_components/angular-sanitize/angular-sanitize.js"></script>
<script src="../bower_components/angular-ui-router/release/angular-ui-router.js"></script>
<script src="../bower_components/angular-marked/angular-marked.js"></script>
<script src="../bower_components/highlightjs/highlight.pack.js"></script>
+<script src="../bower_components/angular-emoji-filter/dist/emoji.min.js"></script>
<!-- endbower -->
<script src="../bower_components/marked/lib/marked.js"></script>
<!-- endbuild -->

〜
```

> 私の場合は `emoji.min.css` が `$ gulp wiredep` で追記されなかったので、手で追記した。

AngularJS アプリケーションに組み込みます。（`'emoji'` を追記）

```src/app/index.js
'use strict';

angular.module('angularjsMarkdownDemo', ['ngSanitize', 'ui.router', 'hc.marked', 'emoji'])
  .config(function ($stateProvider, $urlRouterProvider) {
〜
```

以上で絵文字を表示する準備までOK。

## Markdown を表示してみる

[Qiita API v1](https://qiita.com/api/v1/docs) を利用して、適当な記事を取得します。記事の内容は、レスポンスされた JSON の `raw_body` に入ってます。

```javascript:src/app/main/main.controller.js
'use strict';

angular.module('angularjsMarkdownDemo')
  .controller('MainCtrl', function ($scope, $http) {

    $scope.raw_body = "";

    $http.get('https://qiita.com/api/v1/items/f77211edf0f4ab7561ca').success(function(item) {
      $scope.raw_body = item.raw_body;
    });

  });
```

取得した記事の内容を Markdown で表示します。

```html:src/app/main/main.html
<div class="container">
  <ng-if raw_body != "">
    <div marked="raw_body|emoji"></div>
  </ng-if>
</div>
```

[angular-marked](https://github.com/Hypercubed/angular-marked) の使い方は README に書いてありますが、今回のポイントとしては `<div marked="$scopeの変数名|emoji"></div>` で動的に値を差し込んでいるところです。
（ただこの使い方だと、値の差し込み前にディレクティブが作動するとエラーになるので、`ng-if`を利用して値が存在したら表示、としています。）

また画像が大きい場合に横にはみ出したり、絵文字の縦位置が中央でなかったりするので、次のように調整しました。

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

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

デプロイ用にbuildする場合は `$ gulp build` とすると `dist` ディレクトリにデプロイ用のファイルが展開されるのですが、絵文字用の画像（`emoji.png`）は `bower_components/angular-emoji-filter/dist/emoji.ping` を `dist/style/emoji.png` へ手動でコピーしました。（もしくは Gulp のタスクを修正すれば良いかと思います。）

デモはこちらです。
⇒ http://hkusu.github.io/angularjs-markdown-demo/dist

## おわりに

ソースコードはこちらに置いておきます。
⇒ https://github.com/hkusu/angularjs-markdown-demo