このページで分かること

  • プラグインが定義するPortable Textのブロックは、編集画面用の定義と公開サイト用のAstroコンポーネントの2つからなること
  • admin.portableTextBlocks でのブロックの定義と、各項目の働き
  • Astroコンポーネントの作成と登録(blockComponentscomponentsEntry)、描画コンポーネントの優先順位とサイト側での上書き
難易度
上級
読む時間
3分
このページの目次

プラグインが定義するPortable Textのブロックは、2つの部分からなります。

  • 宣言的なブロックの定義は、ブロックを挿入・編集する方法をEmDashのエディターに伝えます。
  • Astroコンポーネントは、保存されたブロックを公開サイトで描画します。

ブロックの定義は共通のプラグインのランタイムを使い、サンドボックス型プラグインでも使えます。パッケージからAstroコンポーネントを読み込むには、ネイティブ型のディスクリプターが必要です。Astroがホストのサイトをビルドするときに、そのコンポーネントをインポートする必要があるためです。

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

公式の対応表では、WordPressの the_content() は、EmDashの <PortableText /> にあたります。プラグインで独自のブロックを作る場合、エディターでの入力項目は admin.portableTextBlocks に宣言し、公開サイトでの見た目はAstroコンポーネントで作ります。入力項目の宣言はただのデータなのでサンドボックス型でも書けますが、Astroコンポーネントはサイトのビルド時に読み込む必要があるため、ネイティブ型でしか提供できません。

エディター用のブロックの定義

definePlugin() の中の admin.portableTextBlocks で、ブロックを宣言します。次の定義は、見出し、本文、トーンを持つコールアウトを追加します。

src/index.ts
return definePlugin({
	id: "plugin-callout",
	version: "0.1.0",
	admin: {
		portableTextBlocks: [
			{
				type: "callout",
				label: "Callout",
				icon: "info",
				description: "Highlight supporting information",
				category: "Sections",
				fields: [
					{
						type: "text_input",
						action_id: "heading",
						label: "Heading",
					},
					{
						type: "text_input",
						action_id: "body",
						label: "Body",
						multiline: true,
					},
					{
						type: "select",
						action_id: "tone",
						label: "Tone",
						options: [
							{ value: "note", label: "Note" },
							{ value: "warning", label: "Warning" },
						],
						initial_value: "note",
					},
				],
			},
		],
	},
});

ブロックのフィールドは、スラッシュメニューと編集ダイアログでのブロックの表示を制御します。

フィールド 必須 動作
type はい 保存されるPortable Textのブロックの _type と、描画コンポーネントの対応表のキーになる。有効なプラグイン全体で一意にする。
label はい エディターでのブロックの名前。
icon いいえ 一致するものがあれば、エディターが対応しているアイコンのキーを使う。ない場合、エディターは汎用のブロックのアイコンを表示する。
description いいえ スラッシュメニューに補足のテキストを追加する。
category いいえ スラッシュメニューでブロックをグループにまとめる。デフォルトのカテゴリーは Embeds
placeholder いいえ fields を省略したときの、シンプルなURL入力欄のプレースホルダーを設定する。
fields いいえ シンプルなURL入力欄を、Block Kitのフォーム要素に置き換える。それぞれの action_id が、保存されるブロックのプロパティになる。

fields には、Block Kitで説明しているBlock Kitの要素の形を使います。この例では、次の値のような形のブロックが保存されます。

{
	"_type": "callout",
	"_key": "01JEXAMPLEKEY",
	"heading": "Before you publish",
	"body": "Check the preview on a small screen.",
	"tone": "warning"
}

Astroコンポーネントの追加

  1. 保存されたブロックを node として受け取るAstroコンポーネントを作成します。

    src/astro/Callout.astro
    ---
    interface CalloutNode {
        _type: "callout";
        _key: string;
        heading?: string;
        body?: string;
        tone?: "note" | "warning";
    }
    
    interface Props {
        node: CalloutNode;
    }
    
    const { node } = Astro.props;
    ---
    
    <aside class:list={["callout", `callout--${node.tone ?? "note"}`]}>
        {node.heading && <p class="callout__heading"><strong>{node.heading}</strong></p>}
        {node.body && <p>{node.body}</p>}
    </aside>
    

    astro-portabletext は、独自のタイプのコンポーネントに node プロパティを渡します。ブロックを value として渡すことはありません。この描画コンポーネントでは、省略可能な見出しに、スタイルを付けたテキストを使っています。ブロックはドキュメントのどの深さにも現れる可能性があるため、見出しの要素を固定すると、見出しのレベルが飛んだり重複したりする場合があるためです。

  2. blockComponents という名前の対応表で、コンポーネントをエクスポートします。

    src/astro/index.ts
    import Callout from "./Callout.astro";
    
    export const blockComponents = {
        callout: Callout,
    };
    

    各キーは、ブロックの定義の type と一致している必要があります。

  3. ディスクリプターのファクトリーに componentsEntry を追加します。

    src/index.ts
    export function calloutPlugin(): PluginDescriptor {
        return {
            id: "plugin-callout",
            version: "0.1.0",
            format: "native",
            entrypoint: "@example/plugin-callout",
            componentsEntry: "@example/plugin-callout/astro",
        };
    }
    
  4. npmパッケージから ./astro のエントリーをエクスポートします。このエントリーは、blockComponents をエクスポートするモジュールを指している必要があります。パッケージの完全な定義は、ネイティブ型プラグインの配布にあります。

描画の優先順位と上書き

EmDashは、プラグインのコンポーネントを自動的に <PortableText> コンポーネントに追加します。コンポーネントは、次の順に統合されます。

  1. EmDashの組み込みのコンポーネント
  2. プラグインの blockComponents
  3. サイトが <PortableText> に渡したコンポーネント

同じタイプのキーを持つものは、後のものが前のものを上書きします。そのため、サイトは、プラグインのエディター用の定義を有効にしたまま、コールアウトの描画コンポーネントだけを置き換えられます。

src/pages/article.astro
---
import { PortableText } from "emdash/ui";
import SiteCallout from "../components/SiteCallout.astro";
---

<PortableText
	value={entry.body}
	components={{
		type: {
			callout: SiteCallout,
		},
	}}
/>