ObsidianでNotionみたいなカレンダービューを作る方法

ObsidianのCalendarプラグインはデイリーノートのナビゲーションが目的であるため、Notionのカレンダービューのような使い勝手ではありません。
そこで、Notionのような「ノートへのリンクとタイトルが並ぶカレンダービュー」をObsidianで作ってみました。

2024年10月22日追記。
せっかくなので自分でプラグインを作ってみました。Obsidian Embed Calendarをというものです。
別の記事Obsidianの埋め込みカレンダービュープラグインの紹介に書きました。


どんなものができたのか

カレンダービューの例

筆者がカレンダービューを使うにあたり、必要な要件を次のように考えました。

  • 指定したプロパティに基づいてノートの一覧をカレンダー表示
  • リンクテキストはfrontmatterのタイトル
  • 「前の月」「次の月」へ切り替えるボタン
  • ピッカーで該当する月へ切り替えるボタン
  • 埋め込みに対応

既存のプラグイン1つだけでこれを満たせるものはありませんでしたが、複数組み合わせれば実現できましたのでその方法を紹介します。

ただし、Notionのような「ドラッグ&ドロップによる日付変更」には対応はしていない点に注意です。これも実現したかったのですが、必須条件ではないため今回は諦めました。

必要なプラグインのインストール

まず必要なプラグインのインストールリンクを並べておきます。

役割とドキュメントのリンク

次に、各プラグインの役割と公式ドキュメントへのリンクを書いておきます。

Dataview

Dataviewは指定したノートを集めてプロパティを参照するために使います。
設定でEnable JavaScript Queriesはオンにしてください。

Habit

Habit CalendarはDataviewを使ってカレンダーを表示できるプラグインです。
必要に応じて設定で曜日ラベルを変更してください。

曜日ラベルの変更

Meta Bind

Meta Bindはノート内にボタンや入力欄を配置してインタラクティブな操作を可能にします。
設定はデフォルトのままで大丈夫です。

プロパティの作成

ここから実際にカレンダービューを作ります。
ビューを設置するノートを用意し、日付タイプのプロパティ_monthを追加して適当な日付を入力しておきます。

プロパティの用意

プロパティの名前は何でもいいです。変えた人はこの記事の_monthの部分を読み替えてください。

カレンダービューを表示する

dataviewjsのコードブロックに次のように書きます。次の項目から小分けにして解説するのでご安心を。

const _month = dv.date(dv.current().file.frontmatter._month);
const monthTest = new RegExp(_month.toFormat("yyyy年MM月"))
const groupByDate = Object.groupBy(dv.pages('"inbox/test"')
.filter(p => monthTest.test(p.file.frontmatter["endDate"])) , (p => {
return p.file.frontmatter["endDate"]
}))
const table = Object.keys(groupByDate)
.map(key => {
const links = groupByDate[key].map(p =>
`[[${p.file.path}|${p.file.frontmatter.title.substring(0, 28)}]]`
)
return {
date: key,
content: links.join("\n\n"),
link: groupByDate[key][0].file.path,
}
})
renderHabitCalendar(this.container, dv, {
format: "markdown",
year: _month.year,
month: _month.month,
data: table
})

ノートを集める

まず1行目は先ほど追加したプロパティ_monthを取得し、扱いやすい形に変換しています。

const _month = dv.date(dv.current().file.frontmatter._month);

dv.dateを使うとLuxonDateTimeオブジェクトになります。_month.year_month.plus({month:1})のような扱いができます。

次に、プロパティ_monthで指定した年と月に当てはまるノートを、日付ごとのリストにして集めます。

const monthTest = new RegExp(_month.toFormat("yyyy年MM月"))
const groupByDate = Object.groupBy(dv.pages('"inbox/test"')
.filter(p => monthTest.test(p.file.frontmatter["endDate"])) , (p => {
return p.file.frontmatter["endDate"]
}))

今回、カレンダービューに表示するノートは次のように定義しました。ここらへんは各自のノートに合わせて変えましょう。

  • フォルダinbox/testの中にあるノート
  • 日付のプロパティはendDate
  • 日付のフォーマットは2024年4月24日(水)のような形式
日付のフォーマットの例

これに対し、プロパティ_month2024-04-24のように異なる形式です。そのため、プロパティ_monthを日付プロパティendDateのフォーマットに変換してからノートを集めています。toFormatはLuxonの関数です。

レンダリング

ここから先はHabit Calendarの出番です。まずはノート情報をHabit Calendarで扱えるように加工します。

const table = Object.keys(groupByDate)
.map(key => {
const links = groupByDate[key].map(p =>
`[[${p.file.path}|${p.file.frontmatter.title.substring(0, 28)}]]`
)
return {
date: key,
content: links.join("\n\n"),
link: groupByDate[key][0].file.path,
}
})

frontmatterのtitleは28文字以内に切り出し、[[パス|tile]]のようにリンク化します。ノートが複数ある場合は改行しています。

returnに書いてあるlinkは、「カレンダーで日付にカーソルを合わせた時に表示されるリンク」です。今回は適当に配列の最初のノートのパスにしておきました。

あとはレンダリングするだけです。

renderHabitCalendar(this.container, dv, {
format: "markdown",
year: _month.year,
month: _month.month,
data: table
})

ボタンの作成

カレンダービューの表示はできました。
ここからは併せて設置しておくと便利なボタンの作成です。

ボタンの配置

先にボタンを表示するインラインコードをノートに書きます。場所はカレンダーの上がよいでしょう。

`BUTTON[my-previous-month-button]` `INPUT[datePicker:_month]` `BUTTON[my-refresh-button]` `BUTTON[my-next-month-button]`

このBUTTON[id]Meta BindのInline Buttonという機能です。のちほど定義するボタンのidを書いておきます。
まだ定義していないため、表示はエラーになります。

表示はエラーになるが今はこれでよい

INPUT[type:property]Meta BindのInput Fieldという機能です。ノートのプロパティを更新できるボタンを表示できます。
今回は日付ピッカーを使ってプロパティ_monthを操作します。
プロパティの方にあるピッカーは埋め込みでは表示されないため、このようにノート内で操作できるようにしています。

Meta Bindの日付ピッカー

dataview更新ボタンの設置

DataviewのRefresh Intervalを長く設定したりAutomatic View Refreshingをオフにしている人向けのボタンです。

meta-bind-buttonのコードブロックの中に次のように定義しましょう。

style: default
icon: refresh-cw
label: 最新のテーブルを取得
id: my-refresh-button
hidden: true
action:
type: command
command: dataview:dataview-rebuild-current-view

さきほど設置した日付ピッカーを押してプロパティが更新され後に押します。任意のタイミングでDataviewの描画を更新できます。

前の月と次の月

カレンダービューを使うなら、「前の月」「次の月」をワンクリックで移動したいですよね。
meta-bind-buttonのコードブロックの中に次のように定義しましょう。

style: default
icon: arrow-left
label: 前の月へ
id: my-previous-month-button
hidden: true
actions:
- type: updateMetadata
bindTarget: _month
evaluate: true
value: "this.app.plugins.plugins['dataview'].api.date(x).minus({month:1}).toISODate()"
- type: sleep
ms: 500
- type: command
command: dataview:dataview-rebuild-current-view

このボタンで実行するのは次の3つです。

  1. プロパティ_monthを1か月前にする
  2. 書き換えのために少し待機
  3. dataviewを再読み込みする

2と3は、Dataviewの「Refresh Intervalを長く設定している」「Automatic View Refreshingをオフにしている」という人以外は不要です。

Meta Bindでは、updatemetadataタイプを使うことでプロパティを更新できるボタンを作れます。今回はその機能を使ってプロパティ_monthを書き換えています。
valueにJavaScriptの式を書きます。xは前の値が代入されます。

「次の月へ」ボタンもほぼ同じです。

style: default
icon: arrow-right
label: 次の月へ
id: my-next-month-button
hidden: true
actions:
- type: updateMetadata
bindTarget: _month
evaluate: true
value: "this.app.plugins.plugins['dataview'].api.date(x).plus({month:1}).toISODate()"
- type: sleep
ms: 500
- type: command
command: dataview:dataview-rebuild-current-view

これでカレンダービューの完成です。

カレンダービューの例

埋め込み時の注意

埋め込んだ場合、テーマによってはボタンと「ノートを開くリンク」が被ってしまう場合があります。

埋め込みでボタンが被ってしまう例

そんなときは、ボタンの前に1行追加してスペースを開けてあげれば問題ありません。

正しく埋め込みのボタンも実行できる例

正常であれば、ボタンの上にカーソルを持っていくと画像のように「ツールチップが表示される」などの変化が起こります。


Obsidianでノートリンクを表示するカレンダービューを作ってみました。本来Habit Calendarは習慣トラッカー的に使うプラグインのようですが、まあそれはそれ。
実現できたので各制作者に感謝です。

冒頭にも書きましたが、別の記事Obsidianの埋め込みカレンダービュープラグインの紹介で自作プラグインも紹介していますので併せてどうぞ。