6
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?

More than 5 years have passed since last update.

WordPressAdvent Calendar 2017

Day 16

GutenbergにMarkdown Blockを追加するWordPressプラグイン

Last updated at Posted at 2017-12-22

本記事は、WordPress Advent Calendar 2017 16日目の代理投稿です。

WordPressの新エディタ、GutenbergでMarkdownを書けるようにするブロックを作ったので、動的ブロックの作例として紹介します。

本記事のサンプルコード(zipをダウンロードすればWordPressプラグインとして使えます)は下記リポジトリで公開しています。
https://github.com/ryo-utsunomiya/markdown-block

2018-08-08追記

現在のGutenberg(3.4)では、普通にMarkdownを書いていくと自然にブロックに変換されるようになっています。したがって、実用上はこのプラグインの必要性はありません。。。あくまで、サーバサイドに保存したデータをPHPで動的に変換するブロックの作例として眺めていただければと思います。

基本設計

Markdownのレンダリングはサーバサイドで行うようにします。ライブラリとしてはcebe/markdownがよさそうです。

開発環境

Gutenberg公式の開発環境を使用します。プラグイン作成用のカスタマイズ方法は以下の記事を参考にしてください。

実装

まず、registerBlockType関数でブロックを登録します。今回はテキストの編集領域を出したいので、Editableコンポーネントも使います。

index.js
/* global wp */
const { registerBlockType, Editable } = wp.blocks;

registerBlockType( 'markdown-block/markdown-block', {
	title: 'Markdown',
	icon: 'universal-access-alt', // todo: MD icon
	category: 'common',
	attributes: {
		content: {
			type: 'text',
		},
	},
	edit: ( { attributes, setAttributes, focus, setFocus } ) => {
		const onChange = content => {
			setAttributes( { content } );
		};
		return (
			<Editable
				onChange={ onChange }
				value={ toJSX( attributes.content ) }
				focus={ focus }
				onFocus={ setFocus }
			/>
		);
	},
	save: () => null,
} );

ここでのポイントは2つ。Block typeのattributesと、save関数です。

attributesでは、このブロックが使用する属性を定義します。ここでは、contentプロパティを定義し、その型はtextであるとしています。このattributesは、JSONにシリアライズされてpost_contentに埋め込まれます。(この辺の話はGutenbergことはじめにも書きました)。

もう1つはsave関数でnullを返すこと。こうすると、post_contentにはattributesだけがHTMLコメント内に保存されます。

この状態で、記事を保存して、post_contentを見てみましょう。

Markdownブロックに以下のようなMarkdownを記入してみます。

Screen Shot 2017-12-22 at 20.08.45.png

これを保存すると、以下のようにHTMLが描画されます。

Screen Shot 2017-12-22 at 20.19.29.png

post_contentには以下のような文字列が書き込まれます。

<!-- wp:markdown-block/markdown-block {"content":["# Hello",{"type":"br","key":"_domReact1","ref":null,"props":[],"_owner":null},{"type":"br","key":"_domReact2","ref":null,"props":[],"_owner":null},"- a",{"type":"br","key":"_domReact4","ref":null,"props":[],"_owner":null},"- b",{"type":"br","key":"_domReact6","ref":null,"props":[],"_owner":null},{"type":"br","key":"_domReact7","ref":null,"props":[],"_owner":null},"```",{"type":"br","key":"_domReact9","ref":null,"props":[],"_owner":null},"\u003c?php",{"type":"br","key":"_domReact11","ref":null,"props":[],"_owner":null},"echo 'Github Flavored Markdown.';",{"type":"br","key":"_domReact13","ref":null,"props":[],"_owner":null},"```","","","","","",""]} /-->

↑だとわかりづらいので、JSON部分を整形してみます。改行がJSXの<br />オブジェクトとしてシリアライズされていることがわかります。これは編集時にJSXを復元するのに便利なので、このままにしておきます。

<!-- wp:markdown-block/markdown-block 
{
  "content": [
    "# Hello",
    {
      "type": "br",
      "key": "_domReact1",
      "ref": null,
      "props": [],
      "_owner": null
    },
    {
      "type": "br",
      "key": "_domReact2",
      "ref": null,
      "props": [],
      "_owner": null
    },
    "- a",
    {
      "type": "br",
      "key": "_domReact4",
      "ref": null,
      "props": [],
      "_owner": null
    },
    "- b",
    {
      "type": "br",
      "key": "_domReact6",
      "ref": null,
      "props": [],
      "_owner": null
    },
    {
      "type": "br",
      "key": "_domReact7",
      "ref": null,
      "props": [],
      "_owner": null
    },
    "```",
    {
      "type": "br",
      "key": "_domReact9",
      "ref": null,
      "props": [],
      "_owner": null
    },
    "\u003c?php",
    {
      "type": "br",
      "key": "_domReact11",
      "ref": null,
      "props": [],
      "_owner": null
    },
    "echo 'Github Flavored Markdown.';",
    {
      "type": "br",
      "key": "_domReact13",
      "ref": null,
      "props": [],
      "_owner": null
    },
    "```",
    "",
    "",
    "",
    "",
    "",
    ""
  ]
}
 /-->

Markdownレンダリング

↑のようなデータをMarkdownとしてレンダリングするには、まずデータをMarkdown文字列に変換する必要があります。といっても話は簡単で、以下のような処理でMarkdown文字列に変換できます。

$markdown = '';

if ( isset( $attributes['content'] ) && is_array( $attributes['content'] ) ) {
  foreach ( $attributes['content'] as $line ) {
    if ( is_string( $line ) ) {
      $markdown .= $line;
    }
    if ( is_array( $line ) ) {
      if ( isset( $line['type'] ) && 'br' === $line['type'] ) {
        $markdown .= "\n";
      }
    }
  }
}

Markdown => HTMLへの変換はライブラリがやってくれます。プラグインのPHP部分の全体を載せておきます:

markdown-block.php
<?php
/**
 * A WordPress plugin for Markdown addicted.
 *
 * @package WordPress
 * @author Ryo Utsunomiya
 * @license GPLv2
 * @link https://github.com/ryo-utsunomiya/markdown-block
 */

/**
 * Register Block JS file.
 */
function markdown_block_enqueue_block_editor_assets() {
	wp_enqueue_script(
		'markdown-block',
		plugins_url( 'index.build.js', __FILE__ ),
		array( 'wp-blocks', 'wp-element' )
	);
}

add_action( 'enqueue_block_editor_assets', 'markdown_block_enqueue_block_editor_assets' );

/**
 * Render Markdown contents.
 *
 * @param array $attributes Block attributes.
 *
 * @return string
 */
function render_markdown( $attributes ) {
	$markdown = '';

	if ( isset( $attributes['content'] ) && is_array( $attributes['content'] ) ) {
		foreach ( $attributes['content'] as $line ) {
			if ( is_string( $line ) ) {
				$markdown .= $line;
			}
			if ( is_array( $line ) ) {
				if ( isset( $line['type'] ) && 'br' === $line['type'] ) {
					$markdown .= "\n";
				}
			}
		}
	}

	// Use GitHub Flavored Markdown(MarkdownExtra).
	$parser = new \cebe\markdown\MarkdownExtra();

	return $parser->parse( $markdown );
}

register_block_type( 'markdown-block/markdown-block', array(
	'render_callback' => 'render_markdown',
) );

保存した値の再編集

最後に、post_contentに保存した値を再編集できるようにします。post_contentにはJSXをシリアライズしたオブジェクトが保存されているので、これをJSXに復元します。今回は<br />だけ考えれば良いので、以下のように簡単なコードで復元できます。

/**
 * Convert content into JSX.
 * @param {array} content Gutenberg content.
 * @returns {array} JSX rendered Gutenberg content.
 */
const toJSX = content => {
	if ( ! Array.isArray( content ) ) {
		return [];
	}

	return content.map( line => {
		if ( typeof line === 'string' ) {
			return line;
		}
		if ( typeof line === 'object' ) {
			if ( line.type === 'br' ) {
				return <br />;
			}
		}
	} );
};

これをEditableコンポーネントへの値の注入のところで使うのがミソです。

<Editable
	onChange={ onChange }
	value={ toJSX( attributes.content ) }
	focus={ focus }
	onFocus={ setFocus }
/>

まとめ

単機能のブロックなら、100行程度で作れます。データの保存形式にややクセがあるため、再編集のことも考えるとやや面倒な場面がありそうな気がします。あと、現状だとドキュメントが少ないのがつらいですね。ハマったときはGutenbergのコードも読んでいく必要があります。

余談:プラグインの実用性について

Markdownで記事を書きたいなら、エディタのテキストモード + JetpackプラグインのMarkdownサポートを使った方が良いです。。。ビジュアルモードはGutenbergに置き換えられる予定ですが、テキストモードは従来通り使えます。GutenbergはWYSIWYGであることに価値のあるエディタなので、変換後でないとコンテンツがプレビューできないMarkdownは、Gutenbergの思想とは相容れません。

2017-01-13追記

Gutenberg 2.0(もしかしたらもっと前のバージョン)で、編集領域にペーストされた文字列がプレインテキストで、かつ、Markdownとしてパースできるなら、Markdownとみなしてブロックに変換する、という機能が加わりました。

したがって、エディタにMarkdownを書く => Gutenbergにペーストする というワークフローであれば、プラグイン不要でMarkdownによる記事執筆が可能になります。

2018-08-08追記

現在のGutenberg(3.4)では、↑に書いたコピペすら不要で、普通にMarkdownを書いていくと自然にブロックに変換されるようになっています。

6
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
6
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?