サイトを埋め込み表示するGROWIプラグインの紹介

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

今回は、GROWIプラグインとして作ったWebサイト埋め込み表示プラグインを紹介します。非同期でデータを取得する際の注意点も合わせて紹介します。

![FireShot Capture 545 - API Test - GROWI - localhost 545.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/7f45abb3-c17a-74d1-5a5f-564c776ad743.jpeg)

## コードについて

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

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

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

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

![FireShot Capture 359 - プラグイン - GROWI - localhost 359.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/f5b3e2d3-ccf3-d536-d59e-7301ea1b2f7e.jpeg)


## 使い方

利用する際には、Markdown記法のリンクを使います。その際にリンクのタイトルを `OGP` としてください。以下はその例です。

```
[OGP](https://example.com)
```

これでサイトの埋め込み表示になります。

## 実装について

プラグインの登録部分 `client-entry.tsx` のコードです。 `optionsGenerators.customGenerateViewOptions` の中でデフォルトの `a` タグの動作を変更しています。 `EmbedOGP` でラップすることで、動作を変更できます。

```tsx
import { useEffect, useState } from 'react';

import config from './package.json';
import { EmbedOGP } from './src/EmbedOGP';
import { Options, Func, ViewOptions } from './types/utils';

declare const growiFacade : {
  markdownRenderer?: {
    optionsGenerators: {
      customGenerateViewOptions: (path: string, options: Options, toc: Func) => ViewOptions,
      generateViewOptions: (path: string, options: Options, toc: Func) => ViewOptions,
    },
  },
};

const activate = (): void => {
  if (growiFacade == null || growiFacade.markdownRenderer == null) {
    return;
  }
  const { optionsGenerators } = growiFacade.markdownRenderer;
  optionsGenerators.customGenerateViewOptions = (...args) => {
    const options = optionsGenerators.generateViewOptions(...args);
    const A = options.components.a;
    // replace
    options.components.a = EmbedOGP(A);
    return options;
  };
};

const deactivate = (): void => {
};

// register activate
if ((window as any).pluginActivators == null) {
  (window as any).pluginActivators = {};
}
(window as any).pluginActivators[config.name] = {
  activate,
  deactivate,
};
```

## OGPの取得・表示

今回のプラグインでの肝になるのが、データの非同期での取得になります。GROWI全体がReactで構築されているため、単純に `useEffect` や `useState` は利用できません。その解決策として利用するのが `react-async` です。

```tsx
import Async from 'react-async';
```

このコンポーネントで全体を囲みます。そして、利用時には `promiseFn` にて非同期関数、そして関数に渡す引数を指定します。戻り値で `data` にデータ、 `error` にエラー、 `isPending` にローディング状態が入ります。

```tsx
return (
	<Async promiseFn={getOGP} href={href}>
		{({ data, error, isPending }) => {
			if (isPending) return 'Loading...';
			if (error) return `Something went wrong: ${error.message}`;
			if (data) {
				return (
					<div className='row ogp'>
						<div className='col-3'>
							<a href={href} target='_blank' rel='noopener noreferrer'>
								<img
									src={data.ogp['og:image'] || PLACEHOLDER_IMAGE}
									alt={data.html.title}
								/>
							</a>
						</div>
						<div className='col-9'>
							<a href={href} target='_blank' rel='noopener noreferrer'>
								<strong>{data.html.title}</strong>
							</a>
							<p>
								{ data.html.description || data.ogp['og:description'].join('') }
							</p>
							{ data.ogp['og:site_name'] && (
								<div
									style={{
										color: '#777',
									}}
								>
									{data.ogp['og:site_name']}
								</div>
							)}
						</div>
					</div>
				);
			}
			return null;
		}}
	</Async>
);
```

非同期関数は以下のように定義します。今回は[ウェブサイトのOGP情報を取得できるAPIをリリースしました！ | たくろぐ。](https://blog.kunon.jp/article/9)をお借りしています。同様の仕組みとして `ogp-parser` をExpressから利用する方法もあります。デフォルトの画像は[placehold.jp | ダミー画像生成 モック用画像作成](https://placehold.jp/)を利用しています。

```tsx
const OGP_API = 'https://ogp-scanner.kunon.jp/v1/ogp_info';
const PLACEHOLDER_IMAGE = 'https://placehold.jp/24/5C9A29/ffffff/480x360.png';

const getOGP = async({ href }: any) => {
  const res = await fetch(`${OGP_API}?url=${encodeURIComponent(href)}`);
  const json = await res.json();
  return json;
};
```

## 注意点

[ウェブサイトのOGP情報を取得できるAPIをリリースしました！ | たくろぐ。](https://blog.kunon.jp/article/9)にある禁止事項を守った上でご利用ください。

## まとめ

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

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