はじめに
Obsidian 用の日本語カレンダープラグインを作り、Community Plugins に公開しました。
プラグインの機能よりも審査対応の方が学びが多かったという経験を共有します。公開を目指している方の参考になれば幸いです。
プラグイン: https://obsidian.md/plugins?id=japanese-calendar
GitHub: https://github.com/kojiman55/obsidian-japanese-calendar
プラグインの機能
サイドバーにカレンダーを表示します。
- 祝日・振替休日の赤色表示(内閣府データ基準)
- 祝日名をセル内に表示(「成人の日」「文化の日」など)
- 和暦表示(ヘッダーに「令和 8 年」と表示)
- 六曜表示(大安・仏滅など、デフォルトはオフ)
- 日付クリックでデイリーノートを自動作成・オープン
- 祝日のノートを開くと callout を自動挿入
- ステータスバーに今月の祝日数を表示
技術的なポイント
祝日データ:npm パッケージで完結
オフラインでも使える @holiday-jp/holiday_jp を採用しました。内閣府の公式データをパッケージ化したもので、振替休日にも対応しています。
import * as HolidayJp from '@holiday-jp/holiday_jp';
getHolidayName(date: Date): string | null {
const holidays = HolidayJp.between(date, date);
return holidays.length > 0 ? (holidays[0].name ?? null) : null;
}
between(start, end) に同じ日付を渡せば、その日が祝日かどうかを判定できます。API 通信なし・データファイル不要でオフライン動作します。
六曜計算:外部データなしで自前実装
旧暦の月日を求めて 6 で割った余りを使います。ユリウス通日を経由した旧暦変換を採用しました。
getRokuyo(date: Date): string {
const lunar = this.toLunarMonthDay(date);
const names = ['大安', '赤口', '先勝', '友引', '先負', '仏滅'];
return names[(lunar.month + lunar.day) % 6] ?? '大安';
}
private toLunarMonthDay(date: Date): { month: number; day: number } {
const jd = this.julianDay(
date.getFullYear(),
date.getMonth() + 1,
date.getDate()
);
const elapsed = jd - 2343155;
return {
month: Math.floor(elapsed / 29.530589) % 12 + 1,
day: Math.floor(elapsed % 29.530589) + 1,
};
}
private julianDay(year: number, month: number, day: number): number {
const a = Math.floor((14 - month) / 12);
const y = year + 4800 - a;
const m = month + 12 * a - 3;
return day + Math.floor((153 * m + 2) / 5) + 365 * y +
Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) - 32045;
}
CSS はすべて Obsidian の変数で書く
テーマ対応のために、色はすべて Obsidian の CSS 変数を使用し、ハードコードを一切しません。
.jhc-day.sunday .jhc-day-num,
.jhc-day.holiday .jhc-day-num {
color: var(--color-red);
}
.jhc-day.saturday .jhc-day-num {
color: var(--color-blue);
}
.jhc-day.today .jhc-day-num {
background: var(--color-accent);
color: var(--text-on-accent);
}
.jhc-day.other-month {
opacity: 0.4;
}
ライト/ダークテーマの両方に自動対応できます。ハードコードすると特定テーマでのみ見やすい UI になるため、Obsidian プラグインでは変数の使用が事実上必須です。
祝日 callout の自動挿入
祝日のデイリーノートを開くと、冒頭に callout を自動挿入します。
this.registerEvent(
this.app.workspace.on('file-open', file => {
if (file) void this.onFileOpen(file);
})
);
private async onFileOpen(file: TFile): Promise<void> {
const dateStr = file.basename; // "2026-05-04" 形式を想定
const date = moment(dateStr, 'YYYY-MM-DD', true);
if (!date.isValid()) return;
const holidayName = this.getHolidayName(date.toDate());
if (!holidayName) return;
const content = await this.app.vault.read(file);
const callout = `> [!info] ${holidayName}\n> 今日は祝日です。\n\n`;
// 二重挿入を防ぐ
if (content.startsWith('> [!info]')) return;
await this.app.vault.modify(file, callout + content);
}
Community Plugins 審査で詰まったこと
申請プロセスが変わっていた
以前の情報では「obsidianmd/obsidian-releases に PR を出せ」とあったのですが、現在は community.obsidian.md ポータルからの申請に変更されていました。
- Obsidian アカウントを作成
- GitHub 連携
- ポータルのフォームにリポジトリ URL を入力
古い記事を参考にすると詰まります。必ず公式の最新ドキュメントを確認してください。
ESLint の sentence-case ルール
Obsidian プラグイン用 ESLint ルールセット(obsidianmd/ui/sentence-case)では、UI に表示する文字列の最初の単語以外を小文字にする必要があります。
// NG
setName('Japanese Calendar')
setDesc('Show Japanese holidays')
setPlaceholder('例:Daily Notes の設定')
// OK
setName('Japanese calendar')
setDesc('Show Japanese holidays') // ← これは OK(固有名詞以外は小文字)
setPlaceholder('例:daily notes の設定')
日本語混じりの文字列も対象です。 英単語部分が sentence-case になっていないと指摘されます。eslint-disable でごまかすことは禁止されているため、素直に修正が必要でした。
全大文字の format 文字列('YYYY-MM-DD')も NG で、'yyyy-MM-dd' に変更する必要があります。
minAppVersion が低すぎた
manifest.json の minAppVersion を "1.0.0" にしていたら指摘されました。実際に使用している API が要求するバージョンを確認して修正が必要です。
// NG
{
"minAppVersion": "1.0.0"
}
// OK(使用 API の最低要件を確認)
{
"minAppVersion": "1.7.2"
}
versions.json も同様に更新が必要です。
CSS の !important 禁止
セレクターの詳細度を上げることで !important を回避します。
/* NG */
.jhc-today-btn {
font-size: 11px !important;
}
/* OK: 親クラスを追加して詳細度を上げる */
.jhc-nav .jhc-today-btn {
font-size: 11px;
}
スコアカード 100% を目指した
GitHub artifact attestation の追加
permissions:
contents: write
id-token: write
attestations: write
steps:
- name: Build
run: npm run build
- name: Attest build provenance
uses: actions/attest-build-provenance@v2
with:
subject-path: |
main.js
styles.css
- name: Create release
uses: softprops/action-gh-release@v2
with:
files: |
main.js
styles.css
manifest.json
sentence-case はプレースホルダーにも適用される
setDesc() や setPlaceholder() に渡す文字列にも sentence-case ルールが適用されます。
// すべて NG
.setDesc('Rokuyo (大安, 友引, 先勝...)')
.setPlaceholder('Format: YYYY-MM-DD')
// すべて OK
.setDesc('Rokuyo (大安, 友引, 先勝...)') // 日本語は対象外
.setPlaceholder('Format: yyyy-MM-dd') // 全大文字の format は NG
CONTRIBUTING.md で 99% → 100%
CONTRIBUTING.md を追加したら 100% になりました。開発環境セットアップとプルリクエスト手順を英語で記載すれば十分です。
審査から掲載までの流れ
1. プラグイン開発・GitHub リリース作成
2. community.obsidian.md ポータルで申請
3. 自動チェック(ESLint・スコアカード)
4. レビュアーによる手動確認
5. 指摘対応 → 再審査
6. マージ → Community Plugins に掲載
審査から掲載まで数週間かかることがあります。最終的にスコア 100%・Health: Excellent・Review: Passed で公開完了しました。
まとめ
プラグイン本体の実装よりも、審査プロセスへの対応から多くを学びました。
| 指摘内容 | 対応 |
|---|---|
| sentence-case ルール(英語・日本語混じり) | 全テキストを確認・修正 |
minAppVersion |
使用 API の最低要件を確認して更新 |
CSS の !important
|
セレクター詳細度で対応 |
| 英語 README がない | 英語セクションを追加 |
| GitHub artifact attestation | GitHub Actions に追加 |
| CONTRIBUTING.md がない | 作成(99% → 100%) |
TypeScript で Obsidian Plugin API を触るのは書きやすく、ItemView を継承してサイドパネルを作る流れはシンプルです。@holiday-jp/holiday_jp のおかげで祝日データ周りも楽に実装できました。
Community Plugins への公開を目指している方は、申請プロセスの最新情報を公式ドキュメントで確認することをお勧めします。
