h-yabi2 / react-quill

react-quill × Next.js
0 stars 0 forks source link

Next.js の app router だとビルド時にエラーが出る(ReferenceError: document is not defined) #3

Open h-yabi2 opened 2 months ago

h-yabi2 commented 2 months ago
スクリーンショット 2024-06-22 9 57 14

参考

以下で解決できそう?

// ... existing code ...

const ReactQuillBase = dynamic(
  async () => {
    const { default: RQ } = await import("react-quill");

    function QuillJS({ forwardedRef, ...props }: IWrappedComponent) {
      const Quill = RQ.Quill; // ReactQuillからQuillを取得
      const BlockEmbed = Quill.import("blots/embed"); // ここで "blots/embed" をインポート

      // ここで ImgBlot を定義し、Quill に登録します
      // ... ImgBlot の定義と登録 ...

      return <RQ ref={forwardedRef} {...props} />;
    }

    return QuillJS;
  },
  {
    ssr: false,
  }
);

// ... existing code ...
h-yabi2 commented 1 month ago

コード全文 ※blotあり

import React, { useMemo, useRef, LegacyRef } from "react"; import dynamic from "next/dynamic"; import type ReactQuill from "react-quill"; import "react-quill/dist/quill.snow.css";

interface IWrappedComponent extends React.ComponentProps { forwardedRef: LegacyRef; }

const ReactQuillBase = dynamic( async () => { const { default: RQ } = await import("react-quill");

function QuillJS({ forwardedRef, ...props }: IWrappedComponent) {
  const Quill = RQ.Quill; // ReactQuillからQuillを取得
  const BlockEmbed = Quill.import("blots/embed"); // ここで "blots/embed" をインポート
  // ImgBlotに渡すデータの型定義
  interface FileBlotProps {
    src: string;
    name: string;
  }
  // BlockEmbedを継承して、画像のブロックを作成
  class ImgBlot extends BlockEmbed {
    // 出力したいHTMLエレメントの生成
    static create(data: FileBlotProps): any {
      const node = super.create();
      node.setAttribute("data-name", data.name); // ↓のvalueと対応している
      node.src = data.src; // ↓のvalueと対応している
      node.alt = "画像";
      console.log(node);
      return node;
    }

    // 渡すデータの定義(HTML属性から抽出)
    static value(domNode: HTMLIFrameElement): any {
      return {
        // ここで定義したデータは、↑のcreateで、必ず同じAttributeにセットするようにしてください。
        name: domNode.getAttribute("data-name"),
        src: domNode.getAttribute("src"),
      };
    }
  }
  // Quillに入るタグを定義します。今回はimgタグを入れます。場合によってはdivなど使います。
  ImgBlot.tagName = "img";
  // 作成したBlotの登録
  ImgBlot.blotName = "img_blot"; // blotの名前≒IDです。既存のBlot(Quill標準のもの+先にカスタムで作ったもの)と重複しないように命名します。
  ImgBlot.className = "~Img"; // 出力されるHTMLタグにつけるクラス名です。「このHTMLタグはどのblotなのか」を判別するのにも利用されるので、他と重複しないクラス名をつけます。
  Quill.register(ImgBlot, true); // Quillに登録します。これをやっておかないと、QuillがImgBlotを認識してくれません。
  // ここで ImgBlot を定義し、Quill に登録します
  // ... ImgBlot の定義と登録 ...

  return <RQ ref={forwardedRef} {...props} />;
}
return QuillJS;

}, { ssr: false, } );

const Component02: React.FC = () => { const quillRef = useRef(null);

// 画像の選択肢 ---------------- const imgSelectList = useMemo(() => { return [ { id: "idea", name: "アイディア", }, { id: "project", name: "プロジェクト", }, { id: "knowledge", name: "ナレッジ", }, { id: "news", name: "ニュース", }, ]; }, []);

// modules -------------------------------- // MEMO:useMemoで制御しないと、エディターが表示されなくなります const modules = useMemo(() => { return { toolbar: { container: "#toolbar-blot", // id="toorbar"のHTMLエレメントにツールバーを入れる handlers: { // ここに、オリジナルの機能で使う処理を追加します。 image: (selectedImg: string): void => { console.log(selectedImg); // 現在選択中の画像についての情報 const selectedDataIndex = imgSelectList.findIndex( (p) => p.id === selectedImg ); const insImgData = imgSelectList[selectedDataIndex]; console.log(insImgData); // ReactQuillコンポーネントのrefからquillの中身を抽出 const quill = quillRef.current?.editor; if ( quill !== undefined && quill !== undefined && quill !== null && insImgData !== undefined ) { // 現在のQuillエディタ内のカーソル位置を取得 const selection = quill?.getSelection(true); const cursorIndex = selection !== undefined && selection !== null ? selection.index : 1; quill.insertText(cursorIndex, "\n"); // img_blotをQillエディタ内に入れる quill.insertEmbed( cursorIndex, // 現在のカーソル位置 "img_blot", // img_blotを入れる { name: insImgData.name, src: /img/${insImgData.id}.webp, }, // ImgBlotのcreateに渡すvalue "api" // 入力タイプ(ここでは気にせずただ「api」と入れてください) ); } }, }, }, }; }, []);

// ReactQuillコンポーネント ---------------- return (

※画像ボタンは、押すとそれぞれアイディア、プロジェクト、ナレッジ、ニュース のサムネイルが入ります

); }; export default Component02;

h-yabi2 commented 1 month ago

最小構成

"use client";

import React, { useMemo, useRef, LegacyRef } from "react";
import dynamic from "next/dynamic";
import type ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";

interface IWrappedComponent extends React.ComponentProps<typeof ReactQuill> {
  forwardedRef: LegacyRef<ReactQuill>;
}

const ReactQuillBase = dynamic(
  async () => {
    const { default: RQ } = await import("react-quill");

    function QuillJS({ forwardedRef, ...props }: IWrappedComponent) {
      const Quill = RQ.Quill;
      // ------------------------------------------------------------
      // ここで Quill のカスタムモジュールを定義
      // ------------------------------------------------------------
      const BlockEmbed = Quill.import("blots/embed");
      class ImgBlot extends BlockEmbed {
        // ここにblotの作成処理を記述
      }
      Quill.register(ImgBlot, true);
      // ------------------------------------------------------------

      return <RQ ref={forwardedRef} {...props} />;
    }
    return QuillJS;
  },
  {
    ssr: false,
  }
);

const Component08: React.FC = () => {
  const quillRef = useRef<ReactQuill>(null);

  // modules --------------------------------
  const modules = useMemo(() => {
    return {
      toolbar: {
        handlers: {},
      },
    };
  }, []);

  // ReactQuillコンポーネント ----------------
  return (
    <ReactQuillBase
      forwardedRef={quillRef}
      modules={modules}
      placeholder="ここにテキストを入力..."
    />
  );
};
export default Component08;
h-yabi2 commented 1 month ago

quillRef が null になる件

  useEffect(() => {
    let counter = 0; // Add a counter
    const interval = setInterval(() => {
      if (quillRef.current && quillRef.current.editor) {
        console.log("quillRef.current.editor", quillRef.current.editor);
        quillRef.current.setEditorContents(
          quillRef.current.editor,
          JSON.parse(collectionDetail.body)
        );
        // 編集可能にする
        quillRef.current.editor.enable();
      }
      counter += 1; // Increment the counter
      // If the counter reaches 3 or quillRef.current.editor exists, clear the interval
      if (counter >= 3 || (quillRef.current && quillRef.current.editor)) {
        clearInterval(interval);
      }
    }, 100); // 100msごとにチェック

    return () => clearInterval(interval); // クリーンアップ
  }, []);