4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Tiptapを使ってリッチエディタを自由にカスタマイズ

Posted at

はじめに

フロントエンドにリッチエディタを追加したい場合、多くの候補がありますが
UI変更に関するAPIは多数提供されていてもHTML属性の追加やパース・レンダリング処理のフックなど
細かい部分まで自由に変更可能なものは少ないように思います。

Tiptapはヘッドレスエディタフレームワークで自由に拡張機能の追加変更が可能で
コンテンツの構造もスキーマで管理されておりjsonなどで結果が得られるため内容の解析も容易となっています。

エディタのここの機能がもう少し変更出来たら要件にぴったりなのに、というときに使えるフレームワークです。
今回は主に拡張機能について使い方の解説をしていきます。

Tiptapの特徴

ヘッドレス

UIの提供をせず機能のみが提供されています。
UIフレームワークと組み合わせることで他のリッチエディタと同等の機能も再現可能です。

フレームワークに依存しない

有名なフレームワークではだいたい動作するようです。

  • Vanilla JavaScript
  • React
  • Next.js
  • Vue 3
  • Vue 2
  • Nuxt.js
  • Svelte

拡張が容易

すべての拡張機能にはextend()メソッドが定義されていて必要な部分のみ変更や追加が可能です。

TypeScript対応

Tiptapのコードベース全体はTypeScriptで記述されています。

取得可能なコンテンツ

HTML、JSON、Textに対応しているため必要に応じた形式で取得して解析が可能です

初期化可能なコンテンツ

HTML、JSONどちらでも初期化可能です。

contentがエディタの初期値

const editor = useEditor({
    content: '<p>Hello World!</p>',
})

contentの型

export type Content = HTMLContent | JSONContent | JSONContent[] | null

既存の拡張機能を拡張する

メソッドの上書き

元にしたい拡張機能をimportしてextendでメソッドを上書き出来ます。

import BulletList from '@tiptap/extension-bullet-list'

const CustomBulletList = BulletList.extend({
  addKeyboardShortcuts() {
    return {
      'Mod-l': () => this.editor.commands.toggleBulletList(),
    }
  },
})

オプションの追加

export interface CustomSomeExtensionOptions extends SomeExtensionOptions {
  awesomeness: number,
}

const CustomSomeExtension = SomeExtensionOptions.extend<CustomSomeExtensionOptions>({
  addOptions() {
    return {
      ...this.parent?.(),
      awesomeness: 100,
    }
  },
})

オプションはロード時に拡張機能を初期化するときに使います。
デフォルト以外のオプションを追加したい場合addOptions()を追加します。
元の拡張機能のオプションを引き継ぎたい場合は継承元のオプションinterfaceをextendsして
interfaceを追加して拡張機能のの1つ目のジェネリック型に設定。
addOptions()...this.parent?.()を追加します。

追加したオプションはuseEditorのextensionsで
使用する拡張機能を設定するときconfigureでオプションを指定できます。
例えば下記のような形になります。

CustomSomeExtension.configure({ awesomeness: 200 })

属性の追加

addAttributes() {
    return {
        ...this.parent?.(),
        ticket: {
            default: null,
            parseHTML: element => element.style.ticket,
            renderHTML: (attributes) => {
                return {
                    style: `ticket: ${attributes.ticket}`,
                }
            },
        },
    }
}

属性はロード後に拡張機能に対して設定したい値がある場合などで使います。
addAttributes()を定義して属性を追加出来ます。
...this.parent?.()は拡張元の属性を継承しています。

属性の設定として下記のようなものがあります。

renderHTML: HTML出力時にカスタマイズする
parseHTML: HTMLに設定した値をパースする
rendered: HTML出力時にレンダリングするかどうかを指定できます

renderHTMLparseHTMLの組み合わせでエディタに自由な名前と型(HTMLで解析できる範囲)で属性を設定できます。

グローバル属性

import { Extension } from '@tiptap/core'

const TextAlign = Extension.create({
  addGlobalAttributes() {
    return [
      {
        // Extend the following extensions
        types: [
          'heading',
          'paragraph',
        ],
        // … with those attributes
        attributes: {
          textAlign: {
            default: 'left',
            renderHTML: attributes => ({
              style: `text-align: ${attributes.textAlign}`,
            }),
            parseHTML: element => element.style.textAlign || 'left',
          },
        },
      },
    ]
  },
})

他の拡張機能に対して属性を設定したいときがあります。
その時はaddGlobalAttributes()で指定できます。

例えばParagraph拡張機能はブロックレベル要素の<p>タグに対して
renderHTMLparseHTMLをしていますが同じ場所に属性を追加したいときに使います。

コマンドの追加

import { Extension } from '@tiptap/core'

declare module '@tiptap/core' {
  interface Commands<ReturnType> {
    customExtension: {
      /**
       * Comments will be added to the autocomplete.
       */
      yourCommand: (someProp: any) => ReturnType,
    }
  }
}

const CustomExtension = Extension.create({
  addCommands() {
    return {
       ...this.parent?.(),
        yourCommand: someProp => ({ commands }) => {
            // …
        },
    }
  },
})

...this.parent?.()は拡張元の属性を継承しています。
継承しない場合はaddOptions()が上書きされてyourCommandのみが有効になります

現在の値を取得する

this.editor.getAttributes('link').href

getAttributesで拡張機能の名前を指定して属性の現在の値を取得出来ます
addGlobalAttributesで別の拡張機能に設定している場合は設定した拡張機能の名前を指定します。

まとめ

フロントエンド開発でUIフレームワークを既に使っていてもヘッドレスなので他の表示を邪魔せず
エディタ機能だけTiptap利用して拡張して使うことも出来るので便利だなと感じています。
わりと思ったとおり作れるのでリッチエディタのカスタマイズで困ったときにTiptapは良い選択肢の一つだと思います。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?