コンテンツにスキップ

リッチテキストを変換してコンテンツを移行する

このチュートリアルでは、ほかのCMSやファイルで管理している既存のコンテンツを、Management APIでCraft Cross CMSに移行する手順を説明します。とくに、本文のリッチテキストをHTMLからCraft Cross CMSのJSON形式に変換する方法を中心に扱います。

なぜ変換が必要か

リッチテキストフィールドの値は、APIで取得すると htmltextjson の3つの形式で返ります。一方で、Management APIでコンテンツを作成・更新するときに有効なのは json だけです。texthtml を渡しても無視されます。

既存のコンテンツの本文は、多くの場合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 をインストールします。

Terminal window
pnpm add @craft-cross-cms/rich-text-core

2. 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: アップロードしたアセットのID
  • src: 画像のURL
  • alt: 代替テキスト
  • 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 で、createContentpublishContent のメソッドを持つクライアントを作成します
  • 本文のHTMLを generateJSON でJSONに変換し、body: { json: ... } の形でコンテンツの data に含めます
  • create で作成したコンテンツは下書きの状態です。CDN APIで取得できるようにするには、publish で公開します
  • create のレスポンスに含まれる id を、publishcontentId に渡します

各操作のパラメータやレスポンスの詳細は、Craft Cross CMSのAPIリファレンスも参照してください。