Google spreadsheets で Medium 目次作成支援

今年に入ってから [Medium] で楽しく遊んでいるのだが、日記コンテンツ風に目次が欲しいと思ったり。
昨年 [note で目次を作ってた](http://www.baldanders.info/spiegel/log2/000693.shtml)のを思い出し、 [Medium] でも同じようにやってみることにした。

## 準備

目次を作成するためには RSS feed が必要である。
note では RSS を出力する機能がなくて往生した（結局諦めた）が、 [Medium] では公式に RSS feed が用意されている。

```
https://medium.com/feed/@<username>
```

`<username>` の部分に各自のユーザ名を当てはめればよい。
私の場合は [https://medium.com/feed/@spiegel](https://medium.com/feed/@spiegel) となる。

この feed を Google spreadsheets を読み込ませるのだが、読み込んだ feed を処理する Google Apps Script を Gist で公開している。

- [spiegel-im-spiegel / note-toc.js](https://gist.github.com/spiegel-im-spiegel/22c720a4a54bbea75ec0)

以前 [note 用に作った](http://www.baldanders.info/spiegel/log2/000693.shtml)ものだが、そのまま使い回せる。
このスクリプトを [ツール] → [スクリプトエディタ...] で [note-toc.gs] として登録する。

## feed から目次（HTML）を生成する

Spreadsheets に feed を読み込ませるには `IMPORTFEED` 関数を使う。

- [IMPORTFEED - ドキュメント エディタ ヘルプ](https://support.google.com/docs/answer/3093337?hl=ja)

以下の記述を Spreadsheets 上の適当なセルに書けば、そのセルを起点として feed の内容が展開される。

```
=ImportFeed("https://medium.com/feed/@spiegel", "items", true, 40)
```

|Title                             |Author                  |URL                              |Date Created                   |Summary|
|:---------------------------------|:-----------------------|:--------------------------------|:------------------------------|:------|
| The message is: consent matters. | Der Spiegel im Spiegel | https://medium.com/@spiegel/... | Fri, 06 Feb 2015 03:30:39 GMT | ...   |
| ...                              | ...                    | ...                             | ...                           | ...   |

この状態で [note-toc.gs] の `getNoteTocHTML` 関数を、タイトル・URL・日付の列を引数として呼び出す。

```
=IF(OR(C2="",E2="",F2=""),"",getNoteTocHTML(C1:C41,E1:E41,F1:F41))
```

これは、先ほどの `IMPORTFEED` 関数を C1 セルにセットし、 `Title` が C 列、 `URL` が E 列、 `Date Created` が F 列にあると仮定している。
`getNoteTocHTML` 関数の結果は、関数を指定したセルに以下のように表示される。

```html
<div class='note-toc'><h2>Topics 2015</h2>
<dl>
<dt>2015年01月</dt>
<dd><ul>
<li>11日 : <a href='https://medium.com/@spiegel/windows-live-writer-movable-type-308d5eff543e'>Windows Live Writer で Movable Type へ記事をアップできるようになった</a></li>
<li>28日 : <a href='https://medium.com/@spiegel/-cf152c164602'>「我々を信用してほしい」ほど信用できない言葉はない</a></li>
<li>29日 : <a href='https://medium.com/@spiegel/ghost-glibc-gethostbyname-vulnerability-cve-2015-0235-32cd766ccb6e'>“GHOST glibc gethostbyname() vulnerability CVE-2015–0235”</a></li>
<li>30日 : <a href='https://medium.com/@spiegel/e2e-peerio-f713873d3f81'>ふむ。今どき E2E で暗号化できるだけではダメなのだが、 Peerio はどんな感じなのだろう。</a></li>
<li>31日 : <a href='https://medium.com/@spiegel/islamic-state-d3d01bea4878'>“Islamic State” は何を指しているのか</a> / <a href='https://medium.com/@spiegel/-492f8f898afc'>そもそも音楽を序列化することに意味があるの？</a> / <a href='https://medium.com/@spiegel/-b16550731255'>ロボットに知的財産権はあるか？</a></li>
</ul></dd>
<dt>2015年02月</dt>
<dd><ul>
<li>01日 : <a href='https://medium.com/@spiegel/1-flattr-65e6bebc0131'>1月の flattr</a> / <a href='https://medium.com/@spiegel/freedome-20ed0c6ba4f2'>Freedome に「日本」登場</a> / <a href='https://medium.com/@spiegel/-11469ed3a240'>ほほう</a></li>
<li>02日 : <a href='https://medium.com/@spiegel/stix-d89c37fef5f3'>脅威情報構造化記述形式 STIX</a> / <a href='https://medium.com/@spiegel/2016-85f139ee5a1c'>2016年の暦</a> / <a href='https://medium.com/@spiegel/-38efb1dc7a6e'>私は賛同も反対もしない</a> / <a href='https://medium.com/@spiegel/-297546550e2f'>『まだスマホで消耗してるの？』</a> / <a href='https://medium.com/@spiegel/-aeebe8fed2f9'>そうだ！ 青空文庫に寄付すればいいんじゃん</a></li>
<li>03日 : <a href='https://medium.com/@spiegel/-74edb058e82'>ダイエットしたきゃ医者に行け！</a> / <a href='https://medium.com/@spiegel/-1ffa6fc745e4'>「一時的な安全を得るために本質的な自由を放棄する人は、自由も安全も得る資格はない」</a></li>
<li>04日 : <a href='https://medium.com/@spiegel/-4958cea6de4a'>田山歴</a> / <a href='https://medium.com/@spiegel/by-sa-4-0-gplv3-b506969d363c'>BY-SA 4.0 と GPLv3 との互換性に関する議論が始まっている</a> / <a href='https://medium.com/@spiegel/storia-d385b0351191'>「まんがライフSTORIA」感想</a></li>
<li>05日 : <a href='https://medium.com/@spiegel/-b0e87cb5f8de'>「重要インフラにおける情報セキュリティ確保に係る「安全基準等」策定指針（第４版）(案)」に関するパブリックコメント募集</a> / <a href='https://medium.com/@spiegel/-dc9c90e36011'>バンアレン帯の内帯は、無かった！？</a> / <a href='https://medium.com/@spiegel/-c5cc0bde0960'>パスワードを定期的に変更しても，えっと変わらん</a> / <a href='https://medium.com/@spiegel/boinc-github-ff449a9fbfe9'>BOINC プロジェクトが GitHub へ移行</a></li>
<li>06日 : <a href='https://medium.com/@spiegel/the-message-is-consent-matters-c42f48bd276a'>The message is: consent matters.</a></li>
</ul></dd>
</dl>
</div>
```

見栄えはともかく、年・月・日ごとに記事が整理されているのが分かると思う。

## note-toc.gs 概要

[note-toc.gs] は、2つの呼び出し関数と、5つのクラスで構成されている。

関数は以下の通り

- `getNoteTocHTML` : 指定したタイトル・URL・日付の各列情報から目次用の HTML ドキュメントを出力する
- `createMatrix` : 列ごとの行配列を、行ごとの列配列（連想配列）に変換する

`createMatrix` について少し補足しておくと、カスタム関数の呼び出し側の引数（2次元配列）は列ごとの行配列を指定するのがふつうである。
こんな感じ。

```
=getNoteTocHTML(C1:C41,E1:E41,F1:F41)
```

しかし返値として2次元配列を返す場合は行ごとの列配列で返さなくてはならない。

```javascript
//回転行列ごっこ（笑）
function rotation_matrix(){
  return [["cosθ","-sinθ"],
          ["sinθ","cosθ" ]];
}
```

このため、あらかじめ呼び出し側の2次元配列の包含関係を入れ替える処理を行うと便利である。
今回は `getNoteTocHTML` 関数内で（日付をキーに）行単位の sort を行うため、あらかじめ `createMatrix` 関数で変換を行う必要があった。

```javascript
noteRss.sort(
  function(a,b){
    if( a["Date Created"] < b["Date Created"] ) return -1;
    if( a["Date Created"] > b["Date Created"] ) return 1;
    return 0;
  }
);
```

`createMatrix` は汎用的に作ってるつもりなので、よかったら使ってみてください。（[note-toc.gs] は [CC0](https://creativecommons.org/publicdomain/zero/1.0/) で提供しています）

クラスは以下の通り

- `NoteToc` : 目次情報クラス
- `YearOfToc` : 目次情報「年」クラス
- `MonthOfToc` : 目次情報「月」クラス
- `DayOfToc` : 目次情報「日」クラス
- `TitleOfToc` : 目次情報「タイトル」クラス

各目次クラスは以下のような has a（包含）関係になっている。

> NoteToc ◇― YearOfToc ◇― MonthOfToc ◇― DayOfToc ◇― TitleOfToc

各クラスを入れ子にすることで年・月・日ごとの整理を容易にしている。

[Medium]: https://medium.com/@spiegel "Der Spiegel im Spiegel on Medium"
[note-toc.gs]: https://gist.github.com/spiegel-im-spiegel/22c720a4a54bbea75ec0 "spiegel-im-spiegel / note-toc.js"