GROWIでページ内リンクをページタイトルに変換するプラグインを作成しました

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

今回は、GROWIプラグインとしてGROWI内のURLをページタイトルに変換するプラグインを作りました。ページタイトルの変更にも追従するので、意外と便利に使えるかなと思います。

## プラグインの動作

インストールすると、以下のようになります。URLを貼り付けると、そのページタイトルが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/ce4bb2e2-b630-cede-4fb8-3d2b53753dde.png)


## コードについて

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

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

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

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

## 使い方

利用する際には、GROWIのページURLをそのまま貼り付けてください。

```markdown
https://YOUR_GROWI_URL/673da0dc7f208f8050a2b965
```

そうすると、プレビュー画面やページ表示の際には `Test Page` といったGROWIページのタイトルが表示されます。

## 注意点

URLは `https://` または `http://` から始まるものに限ります。 パス （`/673da0dc7f208f8050a2b965` のような）だけでは反応しません。

## コードについて

今回は a タグに対してプラグイン機構を追加しています。Reactコンポーネントとして、デフォルトの動作をラップしています。

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

  const { optionsGenerators } = growiFacade.markdownRenderer;

  // For page view
  optionsGenerators.customGenerateViewOptions = (...args) => {
    const options = optionsGenerators.generateViewOptions(...args);
    const { a } = options.components;
    options.components.a = replaceLinkName(a); // Wrap the default component
    return options;
  };

  // For preview
  optionsGenerators.customGeneratePreviewOptions = (...args) => {
    const preview = optionsGenerators.generatePreviewOptions(...args);
    const { a } = preview.components;
    preview.components.a = replaceLinkName(a); // Wrap the default component
    return preview;
  };
};
```

### 変換処理について

aタグの中身を取得して、GROWIのURLかどうかを判定します。GROWIのURLであれば、ページタイトルを取得して返します。

```tsx
export const replaceLinkName = (Tag: React.FunctionComponent<any>): React.FunctionComponent<any> => {
  return ({ children, ...props }) => {
    const { href } = props;
    try {
      // パスであれば処理を行わない
      if (href.match(/^\//)) {
        return <Tag {...props}>{children}</Tag>;
      }
      // あらかじめchildren（aタグの中身）が指定されている場合には、そのまま返す
      if (href !== children) {
        return <Tag {...props}>{children}</Tag>;
      }
      // GROWIのURLかどうか判断
      const url = new URL(href);
      if (url.hostname !== window.location.hostname) {
        return <Tag {...props}>{children}</Tag>;
      }
      // ページのIDを取得
      const id = url.pathname.replace(/^\//, '');
      // ページのタイトルを取得
      return <Async promiseFn={getPage} id={id}>
        {({ data, error, isPending }) => {
          if (isPending) return 'Loading...';
          if (error) return `Something went wrong: ${error.message}`;
          if (data) {
            return <Tag {...props}>{data}</Tag>;
          }
        }}
      </Async>;
    }
    catch (err) {
      console.error(err);
    }
    // エラーがあれば、とりあえずそのまま返す
    return (
      <Tag {...props}>{children}</Tag>
    );
  };
};
```

### ページタイトルの取得

ページのタイトルを取得する処理は非同期になります。その処理は `react-async` を使って行っています。上記コードの `<Async />` が非同期処理です。そして、 `<Async />` で指定された `promiseFn` が非同期処理を行います。

```tsx
const getPage = async({ id }: any) => {
  const res = await fetch(`/_api/v3/page?pageId=${id}`);
  const json = await res.json();
  const { page } = json;
  const ary = page.path.split('/'); // 階層の場合、 ['チームA', '議事録', '2024年10月20日'] のようになります
  return ary[ary.length - 1]; // 一番最後の要素を返します
};
```

GROWIのAPIドキュメントはこちらです。

[GROWI REST API v3 \| GROWI Docs](https://docs.growi.org/en/api/rest-v3.html)

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

本プラグインは、GROWIコミュニティ（Slack）内で要望があり、開発に着手しました。プラグインで実現できそうなことであれば、XやSlackで要望を出してもらえれば、前向きに検討しますのでぜひ！

他にもヘルプチャンネルなどもありますので、ぜひ参加してください！

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

## まとめ

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

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