Tiptap で標準のExtensionを拡張して独自のオプションや属性を追加する

Tiptapは便利な拡張機能が沢山提供されていますが、さらに拡張して使うこともできます
2023.03.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ヘッドレスWYSIWYGエディターフレームワークであるTiptapでは、標準で拡張機能が用意されており様々な拡張機能を追加することでエディターの動作をカスタマイズできます。

このエントリでは、標準の拡張機能をさらに拡張して独自のオプションやアトリビュートを追加して使用する方法を紹介します。

Tiptapとは

Tiptap はリッチテキストエディターのツールキットであるProseMirrorのヘッドレスラッパーで、サクッとWYSIWYGエディターを構築できるオープンソースのライブラリです。

公式ドキュメントが充実しており、デフォルトで多くの拡張機能が用意されているためカスタマイズ性も良好なライブラリになっています。(一部の拡張機能は有償としてプライベートレジストリで提供)
Tiptap はVue.js(Nuxt.js)/React(Next.js)/Svelteなど、様々なフレームワークで利用することができます。

また、共同編集機能も提供しており、リアルタイムでのコラボレーションを実現することができます。

Tiptapにおける拡張機能

TiptapではNodeExtensionといった拡張機能をインポートすることで、エディターの動作や機能をカスタマイズできます。
例えば、エディター上で特定の行を見出し化させる/リスト化する、文字数をカウントするといった機能を、対象の拡張機能をインポートすることですぐに実現できます。

代表的な機能はスターターキットに含まれているので、スターターキットをインポートすれば標準で用意されている大体の機能はすぐに試すことができます。

また、拡張機能は自分で実装したものをインポートすることも可能です。

標準の拡張機能を拡張する

Tiptapで用意されている拡張機能は便利なものが多く、標準の拡張機能を活用することでシンプルなWYSIWYGエディターはサクッと構築できます。
ただし、実際の実装要件によっては標準の拡張機能だけではちょっと足りない・・といったケースも出てくると思います。

一から拡張機能を実装するのも手ですが、 Tiptapの標準拡張機能はさらに拡張して利用することもできる ので、まずはそれで要件を満たせないか検討してみます。

以下は、標準のHeading Node(行を見出し化する機能)に id という独自の属性を追加する例です。

import { Heading } from "@tiptap/extension-heading";

const customHeading = Heading.extend({
  addAttributes() {
    return {
      ...this.parent?.(),
      id: { default: Math.floor(Math.random() * 100) + 1 },
    };
  },
});

既存の Heading Extensionに id という属性を追加し、デフォルト値としてランダムな数値を割り当てています。

これで既存の拡張機能が拡張できたので、実際に組み込んでみて動作するのか確認してみます。
なお、以下は Nuxt 3(Vue.js) で実装しています。(script部分だけを抜粋)

components/TiptapEditor.vue

<script>
import { Editor, EditorContent } from "@tiptap/vue-3";
import StarterKit from "@tiptap/starter-kit";
import { Heading } from "@tiptap/extension-heading";

export default {
  components: {
    EditorContent,
  },

  data() {
    return {
      editor: null,
    };
  },

  mounted() {
    const customHeading = Heading.extend({
      addAttributes() {
        return {
          ...this.parent?.(),
          id: { default: Math.floor(Math.random() * 100) + 1 },
        };
      },
    });

    this.editor = new Editor({
      content: "",
      extensions: [
        StarterKit,
        customHeading,
      ],
      onUpdate: () => {
        const contents = this.editor?.getJSON() ?? "";
        console.log(JSON.stringify(contents));
      },
    });
  },

  beforeDestroy() {
    this.editor.destroy();
  },
};
</script>

Headingid属性が追加されているか確認するため、onUpdateイベント時にエディター内のコンテンツデータを出力させています。

実際にビルドしてエディターに ## を入力して見出し行を打ってみたところ、以下の出力が得られました。

{
  "type": "doc",
  "content": [
    {
      "type": "heading",
      "attrs": { "level": 2, "id": 44 },
      "content": [{ "type": "text", "text": "見出し1" }]
    }
  ]
}

attrsid 属性が追加されており、定義した通りランダムな数値が指定されていますね。
無事にHeading Extensionをサクッと拡張することができました。

おわりに

ヘッドレスWYSIWYGエディターフレームワークであるTiptapにおいて、標準で提供されている拡張機能をさらに拡張して利用する方法をご紹介しました。
拡張機能を自作するまでもないけれど、標準の拡張機能をちょっとカスタマイズしたり追加できないかな・・という場合は、このエントリで紹介したような手法を検討するのも良いかもしれません。

どなたかの参考になれば幸いです。

参考

Nuxt3 + Tiptap + Y.js でリアルタイム共同編集が可能なエディターを作る