GROWIカレンダー表示プラグインの紹介

[オープンソースのWikiであるGROWI](https://growi.org/ja/)にはプラグイン機能が用意されています。自社のデータを表示したり、表示をカスタマイズするのに利用できます。

今回は、GROWIプラグインとして作ったカレンダー表示プラグインを紹介します。Pukiwikiでも人気のあったプラグインなので、真似してみました。

![calendar.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/53aff884-d898-8361-91f6-60b8d4c4b49b.jpeg)


## プラグインを追加する

利用する際には、GROWIの管理画面の `プラグイン` にて追加してください。URLは `https://github.com/goofmint/growi-plugin-calendar` です。

![Admin](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/f5b3e2d3-ccf3-d536-d59e-7301ea1b2f7e.jpeg)

## 使い方

利用する際には、ページの中で `$calendar` と書きます。記述した場所にカレンダーが表示されます。

```
$calendar
```

日付をクリックすると、その日付で新しいページが作成できます。日報や日記などに利用できます。

![new-page.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/6092f85c-fa2b-ec2b-5413-9a8ff847ba4c.jpeg)


## オプション

利用できるオプションは以下があります。

### 表示する年月を変更する

1つ目の引数に月、2つ目の引数に年を指定することで、表示する年月を変更できます。

```
$calendar(12,2021)
```

### 表示言語を変更する

デフォルトは英語ですが、表示言語を `lang` オプションで変更できます。日本語は `ja` です。

```
$calendar(lang=fr)
```

![French](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/4efe4649-00ab-42e3-a30a-4b4794095d35.jpeg)


### 日付をクリックした際のページ名生成基準を変える

デフォルトでは日付をクリックすると `YYYY/MM/DD` というページ名が生成されます。つまり階層構造になります。これを変更することができます。

```
$calendar(separator=-)
```

上記の指定だと、 `2024-10-02` のようなページが生成されます。階層構造が嫌な場合は、こちらを利用してください。

### 設定の組み合わせ

上記オプションは組み合わせて利用できます。

```
$calendar(12,2021,lang=fr,separator=-)
$calendar(12,2021,separator=-)
```

## コードについて

コードは[goofmint/growi-plugin-calendar: GROWI plugin shows calendar](https://github.com/goofmint/growi-plugin-calendar)
にあります。見るべきファイルとしては、以下の2つになります。

- [client\-entry\.tsx](https://github.com/goofmint/growi-plugin-embed-site/blob/main/client-entry.tsx)
- [src/calendar.ts](https://github.com/goofmint/growi-plugin-calendar/blob/main/src/calendar.ts)

### client-entry.tsxについて

client-entry.tsxはプラグインの登録部分です。カレンダープラグインをRemarkプラグインとして登録しています。

```tsx
const activate = (): void => {
  if (growiFacade == null || growiFacade.markdownRenderer == null) {
    return;
  }

  const { optionsGenerators } = growiFacade.markdownRenderer;

  optionsGenerators.customGenerateViewOptions = (...args) => {
    const options = optionsGenerators.generateViewOptions(...args);
    options.remarkPlugins.push(plugin as any); // プラグイン登録
    return options;
  };
};
```

### src/calendar.tsについて

カレンダー表示は [Vanilla Calendar - Lightweight calendar on pure JavaScript](https://vanilla-calendar.pro/) を利用しています。HTMLで表示する必要があるので、JavaScriptで利用できるものを採用しています。

```ts
import VanillaCalendar from 'vanilla-calendar-pro';
import 'vanilla-calendar-pro/build/vanilla-calendar.min.css';
```

Remarkプラグインの場合、描画処理がすべて visit 関数に送られてきますので、この中からカレンダー表示を行う部分だけを抜き出しています。

```ts
export const plugin: Plugin = function() {
  return (tree) => {
    visit(tree, (node) => {
      const n = node as unknown as GrowiNode;
      try {
        if (n.type === 'leafGrowiPluginDirective' && n.name === 'calendar') {
					// この中で処理
				}
			} catch (e) {
				n.type = 'html';
        n.value = `<div style="color: red;">Error: ${(e as Error).message}</div>`;
      }
		});
	});
};
```

### オプションの取得

オプションデータは `node.attributes` で取得できます。

```ts
const [month, year] = Object.keys(n.attributes);
const lang = n.attributes.lang || 'en';
const separator = n.attributes.separator || '/';
```

そして、一旦カレンダーのタグだけ出力して終了します。

```ts
n.type = 'html';
n.value = '<div id="calendar"></div>';
```

HTMLのレンダリングが行われて、 `#calendar` があるのを確認してから描画処理に入ります。

```ts
const id = setInterval(() => {
	if (document.querySelector('#calendar') != null) {
		// カレンダーオブジェクト作成
		const cal = new VanillaCalendar('#calendar', {
			settings: {
				lang, // 表示言語
				// 表示年月
				selected: {
					month: isNaN(month as unknown as number) ? new Date().getMonth() : parseInt(month) - 1,
					year: isNaN(year as unknown as number) ? new Date().getFullYear() : parseInt(year),
				},
			},
			actions: {
				// 日付クリック時の処理
				async clickDay(event, self) {
					const page = self.selectedDates[0].replaceAll(/-/g, separator);
					const path = await getPagePath();
					location.href = `${path}${page}`;
				},
			},
		});
		// 表示
		cal.init();
		// 終了
		clearInterval(id);
	}
}, 100);
```

### 日付をクリックした際の処理

日付をクリックした際に、ページ情報を取得してページ名を正しく取得しています。

```ts
const getPagePath = async() => {
	if (location.pathname === '/') return '/';
	const pageId = location.pathname.replace(/\//, '');
	const res = await fetch(`/_api/v3/page?pageId=${pageId}`);
	const json = await res.json();
	const { path } = json.page;
	return `${path}/`;
};
```

## まとめ

GROWIプラグインを使うと、表示を自由に拡張できます。足りない機能があれば、どんどん追加できます。ぜひ、自分のWikiをカスタマイズしましょう。

[OSS開発wikiツールのGROWI | 快適な情報共有を、全ての人へ](https://growi.org/ja/)