3
2

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でRSSフィードを表示するプラグインを作りました

Last updated at Posted at 2024-12-06

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

今回は、GROWIプラグインとしてRSSフィードを一覧表示するプラグインを作りました。ニュースサイトの表示を行ったり、QiitaのGROWIタグを表示したりするのに便利です。最近ではRSSリーダーを利用する方が減っているので、リーダー代わりに最新情報を表示する場として利用できます。

プラグインの動作

Markdownは以下のように記述します。リンクに対して RSS と記述するのがポイントです。

[RSS](https://qiita.com/tags/growi/feed)

すると、以下のように表示されます。

FireShot Capture 281 - RSS Reader - GROWI - localhost.jpg

アドバンス版

RSSフィードをJSONに変換する(CORS対策)ため、RSS to JSON Converter onlineを利用しています。認証なしでも利用できますが、認証することでオプションが幾つか利用できます(並び替え、件数制限など)。

この場合はリンクではなく、Remark Directiveを利用します。{} 内はapiKeyを除いてオプションです。各オプションはスペースで繋ぎます。

::rss[https://qiita.com/tags/growi/feed]{apiKey=API_KEY count=2 order=pubDate}

コードについて

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

プラグインを追加する

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

Admin

注意点

前述の通り、RSSフィードの変換に外部サービスを利用しています。そのため、LAN内のデータは利用できません(ポータルサイトのRSSフィードなど)。また、RSS to JSON Converter onlineの利用制限もあるので、ご注意ください(課金すると制限は解除できます)。

コードについて

今回は a タグに対する処理(シンプルな使い方)と、Remark Directive の両方をサポートしています。

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 { a } = options.components.a;
    options.components.a = rssReader(a);
    // Remark Directive版
    options.remarkPlugins.push(rssReaderPlugin as any);
    return options;
  };

  // For preview
  const originalGeneratePreviewOptions = optionsGenerators.customGeneratePreviewOptions;
  optionsGenerators.customGeneratePreviewOptions = (...args) => {
    const preview = originalGeneratePreviewOptions ? originalGeneratePreviewOptions(...args) : optionsGenerators.generatePreviewOptions(...args);
    const { a } = preview.components;
    preview.components.a = rssReader(a);
    preview.remarkPlugins.push(rssReaderPlugin as any);
    return preview;
  };
};

Remark Directive版の処理

Remark Directive版は、渡されたオプションを解析して、 a タグに変換します。 data- 要素が使えなかったので、 title 要素にデータを渡しています。

つまり、 Remark Directiveでは、シンプル版と同じように a タグを生成する役割になります。

export const rssReaderPlugin: Plugin = () => {
  return (tree: Node) => {
    // leafDirective(::)のみを指定
    visit(tree, 'leafDirective', (node: Node) => {
      const n = node as unknown as GrowiNode;
      if (n.name !== 'rss') return;
      const data = n.data || (n.data = {});
      data.hName = 'a'; // 描画をaタグに変換
      data.hChildren = [{ type: 'text', value: 'RSS' }];
      const href = n.children[0].url;
      data.hProperties = {
        href,
        title: JSON.stringify(n.attributes),
      };
    });
  };
};

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

::rss[https://qiita.com/tags/growi/feed]{apiKey=API_KEY count=2 order=pubDate}

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

変換後
<a
  href="https://qiita.com/tags/growi/feed"
  title='{"apiKey":"API_KEY","count":2,"order":"pubDate"}'
>
  RSS
</a>

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

そして、シンプル版では、title要素がJSONパースできるかどうかを判定して、処理を行います。

// オプションを解析。エラーなら空で返す
const parseOptions = (str: string) => {
  try {
    return JSON.parse(str);
  }
  catch (err) {
    return {
      apiKey: null,
    };
  }
};

const API_ENDPOINT = 'https://api.rss2json.com/v1/api.json?';

export const rssReader = (Tag: React.FunctionComponent<any>): React.FunctionComponent<any> => {
  return ({
    children, title, href, ...props
  }) => {
    try {
      if (children === null || children !== 'RSS') {
        return <Tag {...props}>{children}</Tag>;
      }
      // オプションを解析
      const { apiKey, count, order } = parseOptions(title);
      // リクエストするURLを生成
      const params = new URLSearchParams();
      params.append('rss_url', href);
      if (apiKey) {
        params.append('api_key', apiKey);
        params.append('count', count || '10');
        params.append('order_by', order || 'pubDate');
      }
      const url = `${API_ENDPOINT}${params.toString()}`;
      // 後述
    } 
    catch (err) {
      // console.error(err);
    }
    // Return the original component if an error occurs
    return (
      <a {...props}>{children}</a>
    );
  };
};

データの取得と描画

データの取得は、 react-async を使って非同期で行います。

// 指定されたURLを呼んで、結果をJSONで返すのみ
const getRss = async({ url }: any) => {
  const res = await fetch(url);
  const json = await res.json();
  return json;
};

データが取得できたら、それを table タグで描画します。

return (
  <Async promiseFn={getRss} url={url}>
    {({ data, error, isPending }) => {
      if (isPending) return 'Loading...';
      if (error) return `Something went wrong: ${error.message}`;
      if (data) {
        return (
          <>
            <table className='table table-striped'>
              <thead>
                <tr>
                  <th>Title</th>
                  <th>Author</th>
                  <th>PubDate</th>
                </tr>
              </thead>
              <tbody>
                {(data.items || []).map((item: any) => (
                  <tr key={item.guid}>
                    <td><a href={item.link} target='_blank'>{item.title}</a></td>
                    <td>{item.author}</td>
                    <td>{item.pubDate}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </>
        );
      }
      return null;
    }}
  </Async>
);

GROWIコミュニティについて

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

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

まとめ

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

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

3
2
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
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?