Block Kit
このページで分かること
- Block Kitの仕組み(プラグインがJSONでブロックを返し、管理画面が描画する)と、プラグインのJavaScriptがブラウザーで動かないこと
- 管理画面のページの宣言と、
adminルートとのやり取り(page_load、form_submitなど) - 使えるブロックと要素の一覧、ビルダー、条件付きで表示するフィールド、Block Playground
このページの目次
EmDashのBlock Kitを使うと、サンドボックス型プラグインは管理画面のUIをJSONで記述できます。ブロックを描画するのはホストです。プラグインが提供したJavaScriptがブラウザーで実行されることはありません。
仕組み
- ユーザーがプラグインの管理画面のページに移動します。
- 管理画面が、プラグインの管理用ルートに
page_loadインタラクションを送ります。 - プラグインが、ブロックの配列を含む
BlockResponseを返します。 - 管理画面が、
BlockRendererコンポーネントを使ってブロックを描画します。 - ユーザーが操作する(ボタンをクリックする、フォームを送信する)と、管理画面がそのインタラクションをプラグインに送り返します。
- プラグインが新しいブロックを返し、この流れが繰り返されます。
プラグインでBlock Kitのページを定義する場合は、プラグインに @emdash-cms/blocks と zod を追加します。
pnpm add @emdash-cms/blocks zod
管理画面が読み込むナビゲーション項目を持てるように、プラグインのマニフェストでページを宣言します。
"admin": {
"pages": [{ "path": "/settings", "label": "Settings", "icon": "settings" }],
}
次の admin ルートは、インタラクションを検証し、ページの読み込み時にフォームを描画し、送信時にその値を保存します。
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のフィールドエディターは、repeater と media_picker にも対応しています。これらは、サンドボックス型プラグインの管理画面のページで使うフォームフィールドではありません。
ビルダーヘルパー
@emdash-cms/blocks パッケージは、同じ形のデータを blocks と elements のビルダーオブジェクトからもエクスポートしています。ビルダーはプロパティ名の間違いを減らしつつ、通常の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を使うと、ブロックのレイアウトを対話的に組み立てて試せます。