0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GROWIでGitHubのIssueを表示するプラグインを作りました

Posted at

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

今回は、GROWIプラグインとしてGitHubのIssueを一覧表示するプラグインを作りました。プロジェクト関連の情報共有にGROWIを利用しているケースは多いので、そのIssueも一覧で表示できれば情報の一元管理が進みます。

プラグインの動作

Markdownは以下のように記述します。Remark Directiveを利用しています。

::github[issue]{repo=weseek/growi per_page=10}

将来的にIssue以外にも対応できそうなので、 github というキーワードにしました。{} 内はオプションです。各オプションはスペースで繋ぎます。オプションは REST API endpoints for issues - GitHub Docs に準拠しています。

FireShot Capture 292 - GitHub - GROWI - localhost.jpg

コードについて

コードはgoofmint/growi-plugin-githubにて公開しています。ライセンスはMIT Licenseになります。

プラグインを追加する

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

Admin

注意点

GitHub APIを利用する際に、APIキーを通していません。そのため、GitHubのAPI制限に引っかかる可能性があります。

コードについて

このプラグインでは、以下のような手順を取っています。

  1. Remark Directiveをcodeタグに変換
  2. codeタグを取得してGitHub APIを実行、結果を表示
client-entry.tsx
import { gitHub, githubPlugin } from './src/GitHub';

const activate = (): void => {
  if (growiFacade == null || growiFacade.markdownRenderer == null) {
    return;
  }
  const { optionsGenerators } = growiFacade.markdownRenderer;
  const originalCustomViewOptions = optionsGenerators.customGenerateViewOptions;
  optionsGenerators.customGenerateViewOptions = (...args) => {
    const options = originalCustomViewOptions ? originalCustomViewOptions(...args) : optionsGenerators.generateViewOptions(...args);
    const { code } = options.components;
    // replace
    options.components.code = gitHub(code);
    options.remarkPlugins.push(githubPlugin as any);
    return options;
  };

  // For preview
  const originalGeneratePreviewOptions = optionsGenerators.customGeneratePreviewOptions;
  optionsGenerators.customGeneratePreviewOptions = (...args) => {
    const preview = originalGeneratePreviewOptions ? originalGeneratePreviewOptions(...args) : optionsGenerators.generatePreviewOptions(...args);
    const { code } = preview.components;
    preview.components.code = gitHub(code); // Wrap the default component
    preview.remarkPlugins.push(githubPlugin as any);
    return preview;
  };
};

Remark Directiveの処理

Remark Directiveでは、渡されたオプションを解析して、 code タグに変換します。

GitHub.tsx
export const githubPlugin: Plugin = () => {
  return (tree: Node) => {
    visit(tree, 'leafDirective', (node: Node) => {
      const n = node as unknown as GrowiNode;
      if (n.name !== 'github') return;
      const data = n.data || (n.data = {});
      const type = n.children[0].value;
      data.hName = 'code';
      data.hProperties = { className: `language-github-${type}` };
      data.hChildren = [{ type: 'text', value: JSON.stringify(n.attributes) }];
    });
  };
};

イメージとしては、以下のようになります。

::github[issue]{repo=weseek/growi per_page=10}

というMarkdownが、以下のようなHTMLになっています。

<code class="language-github-issue">
  {"repo":"weseek/growi","per_page":"10"}
</code>

コンポーネントとしての処理

そして、 code タグに対する処理を行います。この場合は、GitHubのAPIを叩いて、Issueを取得しています。

GitHub.tsx
export const gitHub = (Tag: React.FunctionComponent<any>): React.FunctionComponent<any> => {
  return ({
    children, ...props
  }) => {
    try {
      // クラスの判定
      if (!props.className.match(/language-github-/)) {
        return <Tag {...props}>{children}</Tag>; // 対象以外は普通のコンポーネントとして扱う
      }
      // JSONをパース
      const params = JSON.parse(children);
      switch (props.className) {
        case 'language-github-issue': // Issue表示
          return GitHubIssue(params);
        default:
          return <Tag {...props}>{children}</Tag>; // 対象以外は普通のコンポーネントとして扱う
      }
    }
    catch (err) {
      // console.error(err);
    }
    // Return the original component if an error occurs
    return (
      <Tag {...props}>{children}</Tag>
    );
  };
};

GitHubIssueの処理

GitHubIssue では、GitHubのAPIを叩いてIssueを取得しています。データの取得は react-async を使い、非同期処理にて行います。そして、受け取った結果は table タグで描画しています。

GitHub.tsx
const getUrl = async({ url }: any) => {
  const res = await fetch(url);
  const json = await res.json();
  return json;
};

const GitHubIssue = (params: GitHubIssueProps) => {
  // URLの作成
  const baseUrl = `https://api.github.com/repos/${params.repo}/issues`;
  // クエリーの作成
  const query = Object.entries(params).map(([key, value]) => {
    if (key !== 'repo' && value !== null) {
      return `${key}=${value}`;
    }
    return '';
  }).join('&');
  const url = `${baseUrl}?${query}`;
  return (
    <Async promiseFn={getUrl} url={url}>
      {({ data, error, isPending }) => {
        if (isPending) return 'Loading...';
        if (error) return `Something went wrong: ${error.message}`;
        if (data) {
          return (
            <>
              <table className='table striped'>
                <tbody>
                  {data.map((item: any) => (
                    <tr key={item.id}>
                      <td><a href={item.html_url} target='_blank'>
                        {item.title}</a><br />
                        🕒 {format(item.created_at)} by {item.user.login}
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </>
          );
        }
        return null;
      }}
    </Async>
  );
};

GROWIコミュニティについて

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

GROWI Slackへの参加はこちらから

まとめ

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

OSS開発wikiツールのGROWI | 快適な情報共有を、全ての人へ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?