React で RichTextEditor に画像ギャラリーを追加する

Vite + React のサンプルで開いた「Insert Gallery」ダイアログ。Express サーバーの blog、products、team フォルダーと 4 つの画像が一覧表示されている

RichTextEditor の「Insert Gallery」ダイアログは、React アプリでも通常のページと同じように動作します。変わるのは組み込み方です。エディターは React が管理しないブラウザー用スクリプトであり、コンポーネントがその後始末をする必要があります。また、開発中はダイアログのリクエストを、開発サーバー経由で API サーバーに届ける必要があります。このページでは、この 3 つをすべて満たし、Express のサンプルを相手にテストした Vite + React の構成を紹介します。

アプリより前にエディターのスクリプトを読み込む

エディターは、window.RichTextEditor を定義する通常のスクリプトとして提供されています。Vite アプリでは、React を起動するモジュールスクリプトより前の index.html に置くのが、もっとも簡単で正しい方法です。

HTML · index.html
<!-- Plain browser scripts from public/, loaded before the React bundle. -->
<link rel="stylesheet" href="/richtexteditor/rte_theme_default.css">
<script src="/richtexteditor/rte.js"></script>
<script src="/richtexteditor/plugins/all_plugins.js"></script>
<script src="/rte-files.js"></script>

ファイルは public/richtexteditor/ に置きます。Vite はここにあるファイルをそのまま配信します。rte-files.js は、エディターのアップロード用フックを /rte/upload に接続します。これがないと、ギャラリーで閲覧はできても、「Upload」ボタンから呼び出す先がありません。<head> 内の従来型のスクリプトはモジュールバンドルより前に実行されるため、コンポーネントがマウントされる時点で window.RichTextEditor は存在しています。

コンポーネント

JavaScript · src/RichTextEditor.jsx
// window.RichTextEditor comes from the scripts in index.html.
export default function RichTextEditor({ options, initialHtml = "", editorRef }) {
  const container = useRef(null);

  useEffect(() => {
    const editor = new window.RichTextEditor(container.current, options);
    editor.setHTMLCode(initialHtml);
    if (editorRef) editorRef.current = editor;
    // Runs on unmount, and once in development: Strict Mode mounts, unmounts, mounts again.
    return () => {
      if (editorRef) editorRef.current = null;
      editor.destroy();
    };
  }, []); // create once; options are read at creation

  return <div ref={container} />;
}

エディターは useEffect の中で、ref が指す div に対して 1 回だけ作成されます。クリーンアップでは destroy() を呼び出します。これは開発時に重要です。React の Strict Mode では、すべてのコンポーネントがマウントされ、アンマウントされ、もう一度マウントされるからです。クリーンアップがあれば、2 つ目のエディターが作成される前に 1 つ目が破棄されます。<StrictMode> を有効にしてブラウザーで確認したところ、ページ上のエディターはちょうど 1 つで、コンソールにはエディターの試用版の通知が 2 回(作成ごとに 1 回)表示されました。クリーンアップの書き忘れは見落としやすい問題です。destroy() を呼ばずに同じ div に 2 つ目のエディターを作成したところ、ページ上のエディターは 1 つのままでしたが、1 つ目のインスタンスは後始末されないまま残りました。

依存配列が空なのは意図的です。オプションはエディターの作成時に読み取られるので、レンダリングのたびにエディターを作り直すと、ユーザーが入力した内容が失われます。コンテンツを読み取るには、インスタンスを ref に保持して getHTMLCode() を呼び出します。

JavaScript · src/App.jsx
const options = {
  toolbar: "full",
  galleryEndpoint: "/rte/gallery", // forwarded to Express by vite.config.js
  maxUploadFileSize: 4000000,
};

サンプルでは、「Show HTML」ボタンが editor.current.getHTMLCode() を呼び出し、<p>Hello from React.</p> と出力しました。ギャラリーから画像を挿入すると、HTML に <img src="/uploads/..."> が含まれます。

開発中に API サーバーへ届ける

galleryEndpoint: "/rte/gallery" はページからの相対パスで、そのページは Vite から配信されます。Vite の開発用プロキシは、/rte/(ギャラリーとアップロードのルート)と /uploads/(画像)を、ポート 5101 で動く Express のサンプルに転送します。

JavaScript · vite.config.js
// /rte/ and /uploads/ go to the Express sample (node-express, port 5101).
// Keep the trailing slashes: "/rte" alone would also catch /rte-files.js.
proxy: {
  "/rte/": "http://localhost:5101",
  "/uploads/": "http://localhost:5101",
},

ブラウザーからはオリジンが 1 つに見えるため、CORS ヘッダーを設定する必要も、エディターのオプションに絶対 URL を書く必要もありません。末尾のスラッシュは残してください。Vite は単純なキーを URL のプレフィックスとして照合するので、"/rte" というキーは /rte-files.js にも一致してしまいます。実際に試したところ、"/rte" では /rte-files.js が X-Powered-By: Express 付きで返され、アプリではなく API サーバーから配信されていました。"/rte/" にすると、Vite が自分のコピーを配信しました。

プロキシは開発用の機能です。本番環境では、ビルドしたアプリと API を 1 つのオリジンから配信してください。たとえば、Express でルートと並べて dist/ を配信するか、1 つのリバースプロキシの背後に置きます。

ダイアログに表示されるもの

ページ上部の画像は、この React アプリで開いたダイアログで、Express サーバーのフォルダーと画像が表示されています。サーバー側は Node.js のギャラリーガイドと同じです。React ではなく、リリース版のギャラリーに由来する制限が 2 つあります。形式のヒントには JFIF、BMP、AVIF が含まれていますが、サンプルのサーバーはこれらを拒否します。また、拒否されたファイルについては、サーバーのメッセージではなく「Upload failed for <name>.」と表示されます。

テストする

このガイドのサンプルプロジェクトと Express のサンプルをダウンロードします。エディターのダウンロードに含まれる richtexteditor フォルダーを Express のサンプルにコピーするか RTE_DIR を設定し、npm install と npm start で起動します。次に、React プロジェクトで以下を実行します。

Shell
npm install
npm run dev
  VITE v8.3.5  ready in 440 ms
  ➜  Local:   http://localhost:5111/

curl "http://localhost:5111/rte/gallery?folder="
{"currentFolder":"","currentFolderDisplay":"/","parentFolder":null,"folders":[...],"images":[...]}

npm run dev は、最初にエディターのファイルを public/ にコピーします。RTE_DIR にエディターのフォルダーを設定するか、そのフォルダーを自分で public/richtexteditor に置いてください。http://localhost:5111 を開き、Insert Gallery をクリックして画像を選び、Insert をクリックします。

ほかのプラットフォーム向けの同じガイド

Vue · Angular

次に読む