はじめに
フロントエンドにリッチエディタを追加したい場合、多くの候補がありますが
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出力時にレンダリングするかどうかを指定できます
renderHTML
とparseHTML
の組み合わせでエディタに自由な名前と型(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>
タグに対して
renderHTML
とparseHTML
をしていますが同じ場所に属性を追加したいときに使います。
コマンドの追加
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は良い選択肢の一つだと思います。