Hugo上でGithubライクなカレンダーパーツを実装してみる

HugoでGitHubのようなアクティビティカレンダーを実装したので、その方法を紹介しておきます。このパーツは汎用的に使えるので、どのHugoサイトでも簡単に導入できます(たぶん)。

完成イメージ

  • 過去365日の投稿活動をヒートマップで可視化
  • 投稿数に応じた5段階の色分け(GitHub風)
  • 日付をクリックすると記事一覧を表示
  • 月ラベルと曜日ラベル付き
  • ダークモード対応
  • レスポンシブデザイン

実装方法

2つの導入方法があります:

方法A: Hugo Module経由(推奨)

1. moduleの追加

プロジェクトルートで以下を実行:

hugo mod init github.com/yourusername/yoursite
hugo mod get github.com/mkontani/hugo-github-calendar-parts

2. config.tomlの設定

[params]
  # カレンダーに表示するセクション(任意)
  calendarSections = ["post", "diary", "tips"]
  
  # トップページに表示するセクション(任意)
  mainSections = ["post"]

[module]
  [[module.imports]]
    path = 'github.com/mkontani/hugo-github-calendar-parts'

モジュールのlayouts/ディレクトリは自動的にマウントされるため、追加の設定は不要です。

3. 表示したい場所で呼び出し

例えば、トップページに表示する場合は layouts/index.html に追加:

{{ partial "calendar/activity-calendar.html" . }}

moduleの更新

カレンダーを最新版に更新する場合:

hugo mod get -u github.com/mkontani/hugo-github-calendar-parts
hugo mod tidy

方法B: ファイルコピー方式

1. パーツファイルの作成

layouts/partials/calendar/activity-calendar.html を作成します。

このファイルには以下の要素が含まれています:

  • Hugoテンプレート部分: 過去365日分の記事データを集計し、カレンダーのHTMLを生成
  • CSS: GitHub風のスタイル(ライト/ダークモード対応)
  • JavaScript: クリックイベント処理と月ラベルの動的生成

完全なコードはこちらを参照。

2. 設定の追加

config.toml に以下を追加:

[params]
  # カレンダーに表示するセクション(任意)
  calendarSections = ["post", "diary", "tips"]
  
  # トップページに表示するセクション(任意)
  mainSections = ["post"]

calendarSections を指定しない場合は、自動的に mainSections が使用されます。

3. 表示したい場所で呼び出し

例えば、トップページに表示する場合は layouts/index.html に追加:

{{ partial "calendar/activity-calendar.html" . }}

技術的なポイント

1. データ構造の設計

記事データを日付ごとに集計し、JSONとしてJavaScriptに渡しています:

{{- $postsByDate := dict -}}
{{- range where site.RegularPages "Type" "in" $calendarSections -}}
  {{- $dateStr := .Date.Format "2006-01-02" -}}
  {{- $posts := slice -}}
  {{- if isset $postsByDate $dateStr -}}
    {{- $posts = index $postsByDate $dateStr -}}
  {{- end -}}
  {{- $posts = $posts | append . -}}
  {{- $postsByDate = merge $postsByDate (dict $dateStr $posts) -}}
{{- end -}}

2. グリッドレイアウト

CSS Gridを使って、縦7行(曜日)× 横に週が並ぶレイアウトを実現:

.calendar-grid {
  display: grid;
  grid-template-rows: repeat(7, 12px);
  grid-auto-flow: column;
  grid-auto-columns: 12px;
  gap: 3px;
}

3. 月ラベルの動的生成

JavaScriptで各月の開始位置を計算し、適切な位置にラベルを配置:

days.forEach((day, index) => {
  const date = day.getAttribute('data-date');
  const month = date.substring(0, 7);
  
  if (month !== lastMonth) {
    const monthName = new Date(date).toLocaleDateString('en-US', { month: 'short' });
    const weekColumn = Math.floor((index + emptyCount) / 7);
    monthPositions.push({ month: monthName, column: weekColumn });
    lastMonth = month;
  }
});

4. 二重エンコード対策

Hugo の jsonify が環境によって二重エンコードする場合があるため、JavaScript側で対応:

postsData = JSON.parse(rawData);
if (typeof postsData === 'string') {
  postsData = JSON.parse(postsData);
}

カスタマイズ例

色の変更

GitHub風の緑以外の色に変更したい場合は、CSS部分を修正:

.calendar-day.level-1 {
  background-color: #9be9a8; /* お好みの色に変更 */
}

表示期間の変更

過去365日ではなく、別の期間を表示したい場合:

{{- $startDate := $endDate.AddDate 0 0 -364 -}}  {{/* -364を変更 */}}
{{- $totalDays := 365 -}}  {{/* 365を変更 */}}

セクションごとに色を変える

複数セクションで異なる色を使いたい場合は、データ収集時にセクション情報も保存し、CSSクラスを追加できます。

汎用性について

  1. Hugo標準機能のみ: 特殊なプラグインや外部依存なし
  2. テーマ非依存: どのHugoテーマでも動作するはず
  3. Hugo Module対応: moduleとして管理可能で更新も簡単

どちらの方法を選ぶべきか

  • Hugo Module方式(推奨): 更新を追従したい場合、複数サイトで使う場合
  • ファイルコピー方式: カスタマイズを自由にしたい場合、moduleを使いたくない場合

Hugo Moduleを使えば、カレンダーの更新を簡単に追跡でき、メンテナンスが楽になります。

今までSubmoduleで管理してましたが、いつの間にかHugo Moduleなんてのができてたんですね、これは便利。

まとめ

HugoでGitHub風のアクティビティカレンダーを実装してみました。記事の投稿頻度を視覚的に把握でき、過去の記事にアクセスしやすくなるUIパーツです。Hugo Moduleとして公開しているので、他のサイトでも簡単に導入できます。

リンク

参考

hugo