Obsidianでdataviewjsを使って目次を作成する
Obsidianでちょっと長めの文章書くと目次を表示したくなります。
色々な方法で目次を表示することはできますが、文章を更新したら目次も自動的に変わってほしいのでdataviewjsを使って目次を動的に表示してみました。
目次の表示方法を紹介していきますが、今回はdataviewjsの基本的な知識があることを前提に進めます。
dataviewjsのdv.elとは
今回目次を表示するにあたって、dataviewjsの`dv.el`を使用します。
dataviewjsを使用する際のdv.viewは、ObsidianのDataviewプラグインの中の一つの関数です。
この機能を用いることで、Obsidian内で定義された既存のビューやカスタムビューをプログラム的に呼び出して利用することができます。
たとえば、ある特定のカスタムビューを表示したい場合、以下のようにdv.viewを用いて実現できます。
1
dv.view('views/example')
目次を表示するためのスクリプト作成
dataviewjsのdv.viewを使用して、目次を表示するためにファイルを2つ作成します。
スクリプトファイルを起きたいフォルダに`view.js`と`view.css`を作成してください。
例えばこんな感じ。
- Obsidian root/scripts/toc/view.js
- Obsidian root/scripts/toc/view.css
HTMLを作成するためのJavaScript。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
class Toc { constructor(markdown) { this.markdown = markdown this.container = dv.el('div', '') } getOutline() { return this.markdown .split('\n') .filter((line) => line.match(/^#+\s/)) .map((heading) => { const depth = heading.split(' ')[0].length const text = heading.substr(depth + 1) return { depth, text, subHeadings: [], } }) } getHeadings() { const toc = [] const outline = this.getOutline() const parentHeadings = new Map() outline.forEach((item) => { parentHeadings.set(item.depth, item) if (item.depth === 2) { toc.push(item) } else { parentHeadings.get(item.depth - 1).subHeadings.push(item) } }) return toc } createItem(heading) { return `<li> <a data-href="#${heading.text}" href="#${heading.text}" class="internal-link" target="_blank" rel="noopener">${heading.text}</a> ${ heading.subHeadings.length > 0 ? ` <ol class="x-toc__list x-toc__list--level${heading.depth + 1}"> ${heading.subHeadings .map((subHeading) => { return this.createItem(subHeading) }) .join('')} </ol> </li> ` : '' }` } createHTML() { const headings = this.getHeadings() const html = headings .map((heading) => { return `${this.createItem(heading)}` }) .join('') return html } render() { const container = dv.el('div', '') const html = this.createHTML() container.innerHTML = `<div class="x-toc"> <div class="x-toc__title">Table of Contents</div> <ol class="x-toc__list">${html}</ol> </div>` } } const page = dv.current() const markdown = await dv.io.load(page.file.path) const toc = new Toc(markdown) toc.render()
作成したHTMLを装飾するためのCSS。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
.x-toc { border: 1px solid #eee; border-radius: 5px; padding: 1rem; } .x-toc ol { list-style: none; } .x-toc .x-toc__title { margin-bottom: 10px; font-weight: bold; } .x-toc .x-toc__list { margin: 0; padding: 0; counter-reset: item; } .x-toc .x-toc__list li::before { counter-increment: item; content: counters(item, '.') ' '; } .x-toc .x-toc__list--level3 { padding-left: 1rem; }
今回目次表示のアウトラインを作るにあたり、下記の記事を参考にさせていただきました。
Create Table of contents in Astro and sectionize the Markdown content | by Reza Zahedi | Medium
目次の表示
準備ができたらあとはノート内でスクリプトを実行するだけです。
dataviewjsの呼び出しブロックで
1
await dv.view('views/toc')
を実行することで、目次が表示されます。
例えば下記ようなのマークダウンがあるとします。
1 2 3 4 5 6 7
## Sample Heading2 ### Sample Hading3 ### Sample Hading3-1 ## Sample Heading2-1
そうするとこんな感じで目次が表示されます。
もしCSSがわかる人は、view.cssをカスタマイズして目次の見た目を変更することもできます。
まとめ
dv.viewで作成したら目次を表示することで文章を更新すれば動的に目次が作られるので、少し長めの文章を書くときなんかはとても快適になりました。
JavaScriptとCSSがわからないと少し敷居の高いdv.viewですが、自由度がぐんと上がりいい感じ!