2017-12-3 22:33
markdown vue

目次の自動生成

Vueでマークダウン&シンタックスハイライト」の続き

ゴール

  • マークダウンで書いたページの目次を自動で生成したい。
  • 目次に入れるのは h1、h2、h3 タグ。

ざっくりな流れ

  • hタグをカウントしながらアンカーオブジェクトを配列に追加していく。
  • アンカーオブジェクト配列を元に目次を生成する。

スクリプト

アンカー

  • ハイフンで区切られた3桁の数字をアンカーに用いる。
  • それぞれの桁で h1、h2、h3 タグをカウントしていく。
    • h1がカウントされると、h2 と h3 は 0 にリセットされる。
    • h2がカウントされると、h3 は 0 にリセットされる。
  • これでアンカーがページ内でかぶることはない。
  data: function() {
    return {
      anchor: [0, 0, 0]
    }
  },
  method: {
    getAnchor: function(level) {
      this.anchor[level - 1] += 1
      for (var i = level; i < this.anchor.length; i++) {
        this.anchor[i] = 0
      }
      return (
        'index_' + this.anchor[0] + '-' + this.anchor[1] + '-' + this.anchor[2]
      )
    }
  }
/*
結果こうなる
#a      // <h1 id="#index_1-0-0">a</h1>
##b    // <h2 id="#index_1-1-0">b</h2>
###c  // <h3 id="#index_1-1-1">b</h3>
##d    // <h2 id="#index_1-2-0">b</h2>
#e      // <h1 id="#index_2-0-0">a</h1>
*/

目次生成

  • marked の renderer をオーバーライドする。
    • new marked.Renderer() でRenederオブジェクトを作成し、renderer.headingでh1、h2、h3要素のレンダー処理を上書き。
  • 上書き処理中に目次オブジェクトに以下項目を追加していく
    • レベル
    • アンカー
    • 見出し
  data: function() {
    return {
      renderer: null,
      toc: [], // 目次
      anchor: [0, 0, 0]  // アンカー
    }
  },
  computed: {
    compiledMarkdown: function() {
      return marked(this.note.body, { sanitize: true, renderer: this.renderer })
    }
  },
  created: function() {
    this.renderer = new marked.Renderer()
    let vm = this
    this.renderer.heading = function(text, level) {
      let escapedText = text.replace(/<("[^"]*"|'[^']*'|[^'">])*>/g, '')
      if(level < 4) {  // h4以降は無視
        let anchor = vm.getAnchor(level)
        vm.toc.push({ level, anchor, escapedText })  // 目次オブジェクトに追加
        return '<h' + level + ' id="' + anchor + '">' + text + '</h' + level + '>'
      } else {
        return '<h' + level + '>' + text + '</h' + level + '>'
      }
    }
  },
  • あとは以下のテンプレートで目次を表示
    <section>
      <ul id="toc">
        <li>index</li>
        <li v-for="t in toc" :class="'level-'+t.level"><a :href="'#'+t.anchor" @click="scroll">{{t.escapedText}}</a></li>
      </ul>
    </section>