リッチテキストを変換してコンテンツを移行する
このチュートリアルでは、ほかのCMSやファイルで管理している既存のコンテンツを、Management APIでCraft Cross CMSに移行する手順を説明します。とくに、本文のリッチテキストをHTMLからCraft Cross CMSのJSON形式に変換する方法を中心に扱います。
なぜ変換が必要か
リッチテキストフィールドの値は、APIで取得すると html、text、json の3つの形式で返ります。一方で、Management APIでコンテンツを作成・更新するときに有効なのは json だけです。text と html を渡しても無視されます。
既存のコンテンツの本文は、多くの場合HTMLで管理されています。そのため、移行ではHTMLをCraft Cross CMSのJSON形式へ変換します。この変換には、Craft Cross CMSが公式に提供する @craft-cross-cms/rich-text-core を使います。
リッチテキストの各書式がHTMLとJSONでどのように表現されるかは、コンセプトのリッチテキストエディタを参照してください。
前提
Craft Cross CMS側の準備
- 移行先のモデルを作成する(本文用のリッチテキストフィールドを含む)
- Management API用のアクセストークンを発行する
コンテンツの作成と公開には、次のスコープが必要です。アクセストークンの発行方法は、API v2アプリと認証を参照してください。
beta.cms.content.create: コンテンツを作成するbeta.cms.content.publish: コンテンツを公開する
このチュートリアルでは、移行先のモデルに次のフィールドが定義されている前提で進めます。
| フィールド名 | フィールドID | フィールドタイプ |
|---|---|---|
| タイトル | title | テキスト |
| 本文 | body | リッチテキスト |
実行環境
@craft-cross-cms/rich-text-core はNode.js 20以降が必要です。移行用のスクリプトをNode.jsで実行する想定で進めます。
1. SDKをインストールする
移行用のスクリプトを置くディレクトリで、@craft-cross-cms/rich-text-core をインストールします。
pnpm add @craft-cross-cms/rich-text-core2. HTMLをJSONに変換する
generateJSON にHTMLと拡張機能(extensions)を渡すと、Craft Cross CMSのリッチテキストのJSONに変換されます。拡張機能は buildTiptapExtensions で組み立てます。
Node.jsで実行する場合は、@craft-cross-cms/rich-text-core/server からインポートします。このサブパスはDOM環境を内部で用意するため、追加の設定は不要です。ブラウザで実行する場合は @craft-cross-cms/rich-text-core からインポートします。
import { generateJSON, buildTiptapExtensions,} from "@craft-cross-cms/rich-text-core/server";
const extensions = buildTiptapExtensions({});
const html = "<p>Hello <strong>World</strong>!</p>";const json = generateJSON(html, extensions);
console.log(JSON.stringify(json, null, 2));上記を実行すると、次のようなJSONが出力されます。これがManagement APIに渡す json の値です。
{ "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": "Hello " }, { "type": "text", "marks": [{ "type": "bold" }], "text": "World" }, { "type": "text", "text": "!" } ] } ]}3. 画像と埋め込みの扱い
本文に画像や埋め込みが含まれる場合は、変換の前に対応が必要です。
画像
リッチテキスト内の画像は、メディアライブラリに登録されたアセットを参照します。そのため移行では、先に画像をアセットとしてアップロードしてアセットIDを取得し、そのIDを img タグに設定してから変換します。アセットのアップロードには、Management APIの asset/upload を利用します。
変換の対象とする img タグには、次の5つの属性をすべて設定してください。いずれかが欠けていると、正しく変換されません。
data-asset-id: アップロードしたアセットのIDsrc: 画像のURLalt: 代替テキストwidth: 幅height: 高さ
<img data-asset-id="xxxxxxxx" src="https://example.com/image.jpg" alt="画像の説明" width="800" height="600"/>埋め込み
埋め込み(動画やSNSの投稿など)は、SDKでJSONを生成できません。埋め込みを含むコンテンツは、移行後に管理画面から手動で挿入してください。
4. コンテンツを作成して公開する
変換したJSONを使って、Management APIでコンテンツを作成します。コンテンツの内容は、リクエストボディの data にフィールドIDをキーとして指定します。リッチテキストフィールドには、json 要素を持つオブジェクトを渡します。
スクリプトでは、次の2つの値を環境変数から読み込みます。アクセストークンは第三者に渡るとコンテンツを操作される可能性があるため、コードに直接書かず環境変数などで管理してください。
CRAFT_MANAGEMENT_API_TOKEN: 前提で発行したアクセストークンCRAFT_MODEL_ID: 移行先のモデルID
移行元のデータを読み込み、本文を変換してから作成・公開する一連の流れは次のとおりです。ここでは移行元のデータを配列で表現しています。
import { generateJSON, buildTiptapExtensions,} from "@craft-cross-cms/rich-text-core/server";
const BASE_URL = "https://api.karte.io";const TOKEN = process.env.CRAFT_MANAGEMENT_API_TOKEN!;
const extensions = buildTiptapExtensions({});
// Management APIのクライアントを作成するconst createClient = () => { const headers = { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", };
return { // コンテンツを作成する(下書きの状態で作成される) async createContent(modelId: string, data: object): Promise<{ id: string }> { const res = await fetch(`${BASE_URL}/v2beta/cms/content/create`, { method: "POST", headers, body: JSON.stringify({ modelId, data }), });
if (!res.ok) { throw new Error(`Failed to create content: ${res.status} ${await res.text()}`); }
return res.json(); },
// コンテンツを公開する async publishContent(modelId: string, contentId: string): Promise<void> { const res = await fetch(`${BASE_URL}/v2beta/cms/content/publish`, { method: "POST", headers, body: JSON.stringify({ modelId, contentId }), });
if (!res.ok) { throw new Error(`Failed to publish content: ${res.status} ${await res.text()}`); } }, };};
const client = createClient();
const modelId = process.env.CRAFT_MODEL_ID!;
// 移行元のコンテンツ(本文はHTML)const sourceArticles = [ { title: "1つ目の記事", body: "<h1>見出し</h1><p>本文です。</p>" }, { title: "2つ目の記事", body: "<p>別の記事の本文です。</p>" },];
for (const article of sourceArticles) { const json = generateJSON(article.body, extensions); const created = await client.createContent(modelId, { title: article.title, body: { json }, }); await client.publishContent(modelId, created.id); console.log(`Migrated: ${article.title} (${created.id})`);}ポイントは次のとおりです。
createClientで、createContentとpublishContentのメソッドを持つクライアントを作成します- 本文のHTMLを
generateJSONでJSONに変換し、body: { json: ... }の形でコンテンツのdataに含めます createで作成したコンテンツは下書きの状態です。CDN APIで取得できるようにするには、publishで公開しますcreateのレスポンスに含まれるidを、publishのcontentIdに渡します
各操作のパラメータやレスポンスの詳細は、Craft Cross CMSのAPIリファレンスも参照してください。