このサイトは非公式の日本語訳です。Cloudflare・EmDashプロジェクトが運営するサイトではありません。

このページで分かること

  • Block Kitの仕組み(プラグインがJSONでブロックを返し、管理画面が描画する)と、プラグインのJavaScriptがブラウザーで動かないこと
  • 管理画面のページの宣言と、admin ルートとのやり取り(page_loadform_submit など)
  • 使えるブロックと要素の一覧、ビルダー、条件付きで表示するフィールド、Block Playground
難易度
上級
読む時間
3分
このページの目次

EmDashのBlock Kitを使うと、サンドボックス型プラグインは管理画面のUIをJSONで記述できます。ブロックを描画するのはホストです。プラグインが提供したJavaScriptがブラウザーで実行されることはありません。

仕組み

  1. ユーザーがプラグインの管理画面のページに移動します。
  2. 管理画面が、プラグインの管理用ルートに page_load インタラクションを送ります。
  3. プラグインが、ブロックの配列を含む BlockResponse を返します。
  4. 管理画面が、BlockRenderer コンポーネントを使ってブロックを描画します。
  5. ユーザーが操作する(ボタンをクリックする、フォームを送信する)と、管理画面がそのインタラクションをプラグインに送り返します。
  6. プラグインが新しいブロックを返し、この流れが繰り返されます。

プラグインでBlock Kitのページを定義する場合は、プラグインに @emdash-cms/blockszod を追加します。

pnpm add @emdash-cms/blocks zod

管理画面が読み込むナビゲーション項目を持てるように、プラグインのマニフェストでページを宣言します。

emdash-plugin.jsonc
"admin": {
	"pages": [{ "path": "/settings", "label": "Settings", "icon": "settings" }],
}

次の admin ルートは、インタラクションを検証し、ページの読み込み時にフォームを描画し、送信時にその値を保存します。

src/plugin.ts
import type { SandboxedPlugin } from "emdash/plugin";
import type { BlockResponse } from "@emdash-cms/blocks";
import { z } from "zod";

const interactionSchema = z.discriminatedUnion("type", [
	z.object({ type: z.literal("page_load"), page: z.string() }),
	z.object({
		type: z.literal("block_action"),
		action_id: z.string(),
		block_id: z.string().optional(),
		value: z.unknown().optional(),
	}),
	z.object({
		type: z.literal("form_submit"),
		action_id: z.string(),
		block_id: z.string().optional(),
		values: z.object({ api_url: z.url(), enabled: z.boolean() }),
	}),
]);

function renderSettings(): BlockResponse {
	return {
		blocks: [
			{ type: "header", text: "Save Log settings" },
			{
				type: "form",
				block_id: "settings",
				fields: [
					{ type: "text_input", action_id: "api_url", label: "API URL" },
					{ type: "toggle", action_id: "enabled", label: "Enabled", initial_value: true },
				],
				submit: { label: "Save", action_id: "save" },
			},
		],
	};
}

const plugin: SandboxedPlugin = {
	routes: {
		admin: {
			handler: async (routeCtx, ctx) => {
				const parsed = interactionSchema.safeParse(routeCtx.input);
				if (!parsed.success) return { blocks: [] };
				const interaction = parsed.data;

				if (interaction.type === "page_load") {
					return renderSettings();
				}

				if (interaction.type === "form_submit" && interaction.action_id === "save") {
					await ctx.kv.set("settings:apiUrl", interaction.values.api_url);
					await ctx.kv.set("settings:enabled", interaction.values.enabled);
					return {
						...renderSettings(),
						toast: { message: "Settings saved", type: "success" },
					};
				}

				return { blocks: [] };
			},
		},
	},
};

export default plugin;

admin ルートは、デフォルトで非公開です。管理画面がこのルートを呼び出すとき、EmDashは正しいCSRFヘッダーを送ります。それでもハンドラーは routeCtx.input を検証します。routeCtx.input のTypeScriptの型は unknown であり、呼び出し元はBlock Kitのページの外から非公開のプラグインルートを呼び出せるためです。

本サイトの補足 やさしい解説

公式の対応表では、WordPressのプラグインは、EmDashのサンドボックス型またはネイティブ型のプラグインにあたります。サンドボックス型プラグインがBlock Kitを使う場合、プラグインは「見出し」「フォーム」などの部品をJSONで返すだけで、画面に描画するのはEmDashの管理画面です。プラグイン自身のJavaScriptはブラウザーで動きません。ユーザーがボタンを押したりフォームを送信したりするたびに、管理画面がその内容をプラグインの admin ルートに送り、プラグインが次に表示するブロックを返します。

ブロックタイプ

タイプ 説明
header 大きな太字の見出し
section テキスト。付属の要素を1つ付けられる
divider 水平線
fields ラベルと値を並べる2列のグリッド
table 書式設定・並べ替え・ページ分割に対応したデータテーブル
actions ボタンや操作部品を横1列に並べたもの
stats 増減の表示付きの、ダッシュボードの指標カード
form 条件付き表示と送信に対応した入力フィールド
image 代替テキストと、必要に応じてタイトルを持つブロック単位の画像
context 小さく控えめなヘルプテキスト
columns ブロックを入れ子にできる2〜3列のレイアウト
empty 空の状態を示すタイトル。必要に応じて説明、コマンド、操作ボタンを付けられる
accordion 入れ子のブロックを囲む折りたたみ可能なセクション
chart 折れ線または棒の時系列グラフ、または独自のオプションを指定したグラフ
banner タイトルまたは説明を持つ、状態や警告のメッセージ
meter 最小値と最大値に対して表示する数値
code 読み取り専用のTypeScript、TSX、JSONC、Bash、CSSのコード
tab 入れ子のブロックを含む、ラベル付きのパネル

要素タイプ

タイプ 説明
button 操作ボタン。確認ダイアログを付けられる
text_input 1行または複数行のテキスト入力
number_input 最小値・最大値を指定できる数値入力
select ドロップダウンの選択
toggle オン/オフのスイッチ
secret_input APIキーやトークン用の、入力内容を伏せ字にする入力
checkbox 決まった一覧から複数の値を選択
combobox 検索できる、1つの値の選択
date_input 日付の値
radio 表示された選択肢の一覧から1つを選択

Portable Textのフィールドエディターは、repeatermedia_picker にも対応しています。これらは、サンドボックス型プラグインの管理画面のページで使うフォームフィールドではありません。

ビルダーヘルパー

@emdash-cms/blocks パッケージは、同じ形のデータを blockselements のビルダーオブジェクトからもエクスポートしています。ビルダーはプロパティ名の間違いを減らしつつ、通常のJSON互換のオブジェクトを返します。

import { blocks, elements } from "@emdash-cms/blocks";

const { header, form } = blocks;
const { textInput, toggle, select } = elements;

return {
	blocks: [
		header("SEO Settings"),
		form({
			blockId: "settings",
			fields: [
				textInput("site_title", "Site Title", { initialValue: "My Site" }),
				toggle("generate_sitemap", "Generate Sitemap", { initialValue: true }),
				select("robots", "Default Robots", [
					{ label: "Index, Follow", value: "index,follow" },
					{ label: "No Index", value: "noindex,follow" },
				]),
			],
			submit: { label: "Save", actionId: "save" },
		}),
	],
};

条件付きフィールド

フォームのフィールドは、ほかのフィールドの値に応じて表示を切り替えられます。

{
	"type": "toggle",
	"action_id": "auth_enabled",
	"label": "Enable Authentication"
}
{
	"type": "secret_input",
	"action_id": "api_key",
	"label": "API Key",
	"condition": { "field": "auth_enabled", "eq": true }
}

api_key フィールドは、auth_enabled がオンのときだけ表示されます。条件はクライアント側で評価され、サーバーとの往復は発生しません。

secret_input は、has_value: true を使って、値がすでに存在することを示します。ページの読み込み時に、保存済みの値を受け取ることも返すこともありません。このフィールドはブラウザーでの入力内容を伏せ字にしますが、ctx.kv に書き込む値を暗号化するわけではありません。認証情報を保存する前に、秘密情報の設定に従ってください。

本サイトの補足 やさしい解説

secret_input は、画面上で入力を「●●●」のように隠すだけの部品です。保存する値そのものは暗号化されません。APIキーなどを保存するときは、原文がリンクしている「秘密情報の設定」の手順に従う必要があります。

試用

Block Playgroundを使うと、ブロックのレイアウトを対話的に組み立てて試せます。