GROWIで別ページの内容を表示するプラグインを作りました

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

今回は、GROWIプラグインとして、GROWIの別なページの内容を表示するプラグインを作りました。共通テンプレートにしたり、お知らせを別なページに表示したりするのに使えます。

![FireShot Capture 635 - Include - GROWI - localhost.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/026ea83d-5046-4d72-9591-890c780768dd.jpeg)


## プラグインの動作

Remark Directiveとして、以下のように記述します。 `[]` には表示するページのパスを入力します。

```markdown
::include[/Shared/Message]
```

そうすると、以下のように表示されます。 `出しておきたいメッセージ` やリストなどは別ページの内容を持ってきています

![FireShot Capture 635 - Include - GROWI - localhost.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/026ea83d-5046-4d72-9591-890c780768dd.jpeg)


注意点として、include内ではGROWIプラグインは利用できません。また、 `code` タグのように追加の装飾が発生する表示には対応していません。

以下は共通テンプレートのページの内容です。リストや見出しくらいであれば問題ありません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/3c891151-f748-4534-a4e0-ccc93d10cce9.png)

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

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

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

## コードについて

コードは[goofmint/growi\-plugin\-include](https://github.com/goofmint/growi-plugin-include)にて公開しています。ライセンスはMIT Licenseになります。

最初に `::include` というRemark Directiveを処理します。この記述があれば、 `a` タグに変換します。また、 `include = true` を追加して、他の `a` タグとの区別をつけています。

`vist` メソッドで、RemarkのAST `leafDirective` を処理します。2つ目の引数を指定すると、そのディレクティブの場合のみ呼び出されるので便利です。 `leafDirective` は、行頭で `::` から始まるディレクティブです。

```tsx
export const remarkPlugin: Plugin = () => {
  return (tree: Node) => {
    visit(tree, 'leafDirective', (node: Node) => {
      const n = node as unknown as GrowiNode;
      if (n.name !== 'include') return;
      console.log(n);
      const data = n.data || (n.data = {});
      // Render your component
      const { value } = n.children[0] || { value: '' };
      data.hName = 'a'; // Tag name
      data.hChildren = [{ type: 'text', value }]; // Children
      // Set properties
      data.hProperties = {
        href: 'https://example.com/rss',
        title: JSON.stringify({ ...n.attributes, ...{ include: true } }), // Pass to attributes to the component
      };
    });
  };
};
```

### 外部データを取得して表示

[GROWI 7.2から、React Hooksに対応しました](https://qiita.com/goofmint/items/865d24b88e3641375d51)。今回のプラグインではそれを使って別なページの内容を取得&表示に反映しています。

```tsx
export const helloGROWI = (Tag: React.FunctionComponent<any>): React.FunctionComponent<any> => {
  return ({ children, ...props }) => {
    try {
      const { include } = JSON.parse(props.title);
      if (include) {
        const { react } = growiFacade;
        const { useEffect, useState } = react;
        const [contents, setContents] = useState('');
        const getContent = async() => {
          // ページの内容を取得する処理
        };

        useEffect(() => {
          getContent();
        }, []);
        return (
          <>
            <div dangerouslySetInnerHTML={{ __html: contents }} />
          </>
        );
      }
    }
    catch (err) {
      // console.error(err);
    }
    // Return the original component if an error occurs
    return (
      <Tag {...props}>{children}</Tag>
    );
  };
};
```

### ページを取得する処理

`getContent` では、GROWI JS SDKを使ってページの内容を取得しています。ページの内容を取得するためには、 `growi.page` でページを取得し、 `contents` で内容を取得します。

取得した内容はMarkdownなので、 `unified` でHTMLに変換しています。

```tsx
const getContent = async() => {
  const page = await growi.page({ path: children });
  const contents = await page.contents();
  const file = await unified()
    .use(remarkParse)
    .use(remarkRehype)
    .use(rehypeStringify)
    .process(contents);
  setContents(String(file));
};
```

## GROWIコミュニティについて

プラグインの使い方や要望などがあれば、ぜひGROWIコミュニティにお寄せください。実現できそうなものがあれば、なるべく対応します。他にもヘルプチャンネルなどもありますので、ぜひ参加してください！

[GROWI Slackへの参加はこちらから](https://communityinviter.com/apps/wsgrowi/invite)

## まとめ

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

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