【Quill.js】アイコンの追加方法

最近、個人的にQuill.jsを使用したアプリを開発しています。
ヘッダーにアイコンを追加する処理で少し詰まったのでその備忘録です。

## 全体像
今回は `react` を用いて開発しました。
全体像はこんな感じです。

```react  
import { useEffect, useRef, useState } from "react";
import Quill from "quill";
import "quill/dist/quill.snow.css";
import { MemoContent } from "@/app/constants/interfaces";

// ツールバーに表示するアイコンのオプション
const toolbarOptions = [
  ["bold", "italic", "underline", "strike"],
  ["blockquote", "code-block"],
  ["link", "image"],
  [{ list: "ordered" }, { list: "bullet" }],
  [{ indent: "-1" }, { indent: "+1" }], 
  [{ header: [1, 2, 3, 4, 5, 6, false] }],
  [{ align: [] }],
  ["clean"], 
  ["markdown"], // 追加
];

export default function Editor({ onValueChange, content }: MemoContent) {
  const editorRef = useRef<HTMLDivElement>(null);
  const [quill, setQuill] = useState<Quill | null>(null);

  const isMounted = useRef(false);

  const icons = Quill.import("ui/icons") as Record<string, string>;
  
  // 追加するアイコンのSVG
  icons["markdown"] = `
    <svg  xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 20" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
      <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
      <path d="M3 5m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" />
      <path d="M7 15v-6l2 2l2 -2v6" />
      <path d="M14 13l2 2l2 -2m-2 2v-6" />
    </svg>
  `;

  useEffect(() => {
    if (!editorRef.current || quill) return;

    if (!isMounted.current) {
      const quillInstance = new Quill(editorRef.current, {
        theme: "snow",
        modules: {
          toolbar: {
            container: toolbarOptions,
            handlers: {
	            // 追加したアイコンをクリックしたときの処理　
              markdown: function () {
                console.log("markdown");
              },
            },
          },
        },
      });

      setQuill(quillInstance);

      isMounted.current = true;

      quillInstance.on("text-change", () => {
        const editorContent = quillInstance.getContents();
        if (editorContent) onValueChange(editorContent);
      });
    }
  }, []);

  useEffect(() => {
    if (quill) {
      if (content && content.ops.length > 0) {
        const currentContent = quill.getContents();
        if (JSON.stringify(currentContent) !== JSON.stringify(content)) {
          quill.setContents(content);
        }
      } else {
        quill.setText("");
      }
    }
  }, [content, quill]);

  return (
    <>
      <div ref={editorRef} />
    </>
  );
}

```

## 解説
### `toolbarOptions` への追加
まずはここに追加したいアイコン名を追加します。
    今回はmarkdown形式に変換する機能を実装したかったので `markdown` という名前で追加しました。

```react
const toolbarOptions = [
  ["bold", "italic", "underline", "strike"],
  ["blockquote", "code-block"],
  ["link", "image"],
  [{ list: "ordered" }, { list: "bullet" }],
  [{ indent: "-1" }, { indent: "+1" }], 
  [{ header: [1, 2, 3, 4, 5, 6, false] }],
  [{ align: [] }],
  ["clean"], 
  ["markdown"], // 追加
];
```

### アイコンの追加
ここで追加したいアイコンのSVG要素を追加します。
今回はべた書きでSVG要素を記述していますが、
`font-awesome` のアイコンとかでも問題ないみたいです。

```react
const icons = Quill.import("ui/icons") as Record<string, string>;
  
  // 追加するアイコンのSVG
  icons["markdown"] = `
    <svg  xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 20" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
      <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
      <path d="M3 5m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" />
      <path d="M7 15v-6l2 2l2 -2v6" />
      <path d="M14 13l2 2l2 -2m-2 2v-6" />
    </svg>
  `;
```

### `handlers` にクリック時の処理を追加
`ql-'クラス名'` の 'クラス名' にあたる部分と同じfunction名を追加します。

```react
const quillInstance = new Quill(editorRef.current, {
        theme: "snow",
        modules: {
          toolbar: {
            container: toolbarOptions,
            handlers: {
	            // 追加したアイコンをクリックしたときの処理　
              markdown: function () {
                console.log("markdown");
              },
            },
          },
        },
      });
```

## おわりに
少し複雑ですが、任意のアイコンを追加できるのは面白いなと思いました。

既存のアイコンも変更できるようなので、
またタイミングで変更してみたいと思います。

## 参考文献
https://github.com/slab/quill/issues/885