0
1

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プラグイン開発Tips(Remark/Rehype/Reactコンポーネント)

Posted at

GROWIでタグの動作を変更したり、独自のマークアップを追加する際にはスクリプトプラグインを開発します。その際、知っておきたいのがRemark/Rehype/Reactコンポーネントの使い方です。この記事では、GROWIプラグイン開発Tipsとして、これらの使い方を紹介します。

呼ばれる順番

GROWIのMarkdownパーサーはRemarkを使っています。RemarkはMarkdownをAST(抽象構文木)に変換するライブラリです。ASTはMarkdownの構造を表現したオブジェクトです。そして、ASTを変換するライブラリとしてRehypeがあります。RehypeはASTをHTMLに変換します。

HTMLになった内容は、そのタグの動作をReactコンポーネントで変更できます。つまり、呼ばれる順番としてはRemark → Rehype → Reactコンポーネントです。

Remarkプラグイン

Remarkプラグインを追加する場合には、以下のように記述します。

const activate = (): void => {
  if (growiFacade == null || growiFacade.markdownRenderer == null) {
    return;
  }
  const { optionsGenerators } = growiFacade.markdownRenderer;
  optionsGenerators.customGenerateViewOptions = (...args) => {
    const options = optionsGenerators.generateViewOptions(...args);
    options.remarkPlugins.push(remarkPlugin as any); // プラグイン登録
    return options;
  };
};

remarkPlugin の基本形は以下のようになります。

const remarkPlugin: Plugin = function() {
  return (tree) => {
    visit(tree, (node) => {
      console.log(JSON.stringify(node));
      try {
            // ここに処理を記述
      }
      catch (e) {
            n.type = 'html';
            n.value = `<div style="color: red;">Error: ${(e as Error).message}</div>`;
      }
    });
  };
};

node の内容は以下のようになっています。すべての情報が送られてくるので、 typeleafGrowiPluginDirective であること、 name がプラグインで利用するものであるかを判定して、処理を記述します。

以下は $map(東京都新宿区西早稲田2-20-15) という記述をした場合です。

{
	"type": "leafGrowiPluginDirective",
	"name": "map",
	"attributes": {
		"東京都新宿区西早稲田2-20-15": ""
	},
	"children": [],
	"position": {
		"start": {
			"line": 5,
			"column": 1,
			"offset": 22
		},
		"end": {
			"line": 5,
			"column": 24,
			"offset": 45
		}
	},
	"data": {}
}

Rehypeプラグイン

Rehypeプラグインを追加する場合には、以下のように記述します。

const activate = (): void => {
  if (growiFacade == null || growiFacade.markdownRenderer == null) {
    return;
  }
  const { optionsGenerators } = growiFacade.markdownRenderer;
  optionsGenerators.customGenerateViewOptions = (...args) => {
    const options = optionsGenerators.generateViewOptions(...args);
    options.rehypePlugins.push(rehypePlugin as any); // プラグイン登録
    return options;
  };
};

rehypePlugin の基本形は以下のようになります。

export const rehypePlugin: Plugin = function() {
  return (tree) => {
    visit(tree, (node) => {
      console.log(JSON.stringify(node));
      try {
            // ここに処理を記述
      }
      catch (e) {
            n.type = 'html';
            n.value = `<div style="color: red;">Error: ${(e as Error).message}</div>`;
      }
    });
  };
};

node の内容は以下のようになっています。プラグインの処理に送る場合には data- 要素を使って判定します。

{
	"type": "element",
	"tagName": "a",
	"properties": {
		"style": "height: 400px; width: 100%",
		"dataType": "map",
		"dataLatitude": "undefined",
		"dataLongitude": "undefined"
	},
	"children": [
		{
			"type": "text",
			"value": "\n            東京都新宿区西早稲田2-20-15\n          ",
			"position": {
				"start": {
					"line": 10,
					"column": 12,
					"offset": 188
				},
				"end": {
					"line": 12,
					"column": 11,
					"offset": 229
				}
			}
		}
	],
	"position": {
		"start": {
			"line": 5,
			"column": 1,
			"offset": 22
		},
		"end": {
			"line": 12,
			"column": 15,
			"offset": 233
		}
	}
}

Reactコンポーネント

Reactコンポーネントでは、指定したタグの挙動を変更します。以下は a タグを変更する例です。指定できるタグは GROWIプラグインを開発する(スクリプト編) #TypeScript - Qiitaを参照してください。

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;
    options.components.a = EmbedMap(A); // replace
    return options;
  };
};

EmbedMap の基本形は以下のようになります。

const EmbedMap = (A: React.FunctionComponent<any>): React.FunctionComponent<any> => {
  return ({ children, href, ...props }) => {
		// 指定したものでなければ終了
    if (props['data-type'] !== 'map') {
      return (
        <A
          href
          {...props}
        >
		  {children}
        </A>
      );
    }
    try {
      // タグの動作を変える処理を記述
    }
    catch {
      // エラーが出たら、最低限初期表示にする
      return (
        <A
          href
          {...props}
        >
		  {children}
        </A>
      );
    }
  };
};

注意点

Reactコンポーネントは、GROWI本体側のReactとバッティングするため、useStateなどが使えません。非同期処理を行う場合には、async-library/react-async: 🍾 Flexible promise-based React data loaderを利用してください。

実際の使い方は下記記事を参照してください。

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

まとめ

GROWIプラグインを作ることで、GROWIの可能性が大きく広がります。ぜひ、自分の使いたい機能をプラグインとして追加してみてください。

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

0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?