Movable Type ContentField Plugin TipTapFieldを作ってみた
この記事は Movable Type Advent Calendar 2023 の10日目の記事です。
今年は1件だけ投稿(あまりネタがなく)にするつもりが、とある方
からお願い?されたので即席記事を書かせてもらいました。
即席ネタになるので、作ったプラグインのソースコードについてはご愛嬌を。。
(改良の余地だったり時間がなかったのでちゃんと作るなら時間をかけて検討します)
今年は、本業ではMovable Typeはアップデート作業くらいしかやってなかったので、ネタを探すのに苦労しました。
今回は、個人的にフロントエンドは情報収集した中と組み合わせた感じで記事用にプラグインを作ってみました。
TipTapFieldを作ってみた。
以前、コンテンツフィールドプラグイン MarkdownItField 1.0.0 をリリース というプラグインを作りました。
このプラグインは、コンテンツフィールドにmarkdown-itを組み合わせたプラグインになります。
このプラグインのMT8対応はまだ行っていない(どちらかというとbootstrap 5対応?)のですが、その前に今年気になってたエディタがあったので試しにプラグイン化してみました。
実装自体は、mt-plugin-MarkdownItField がベースです。
TipTapとは
Headless Editor Framework と呼ばれています。
従来のエディタ(TinyMCE 等)とは違い名前の通り、エディタで必要なコア機能だけを提供して、UIや機能拡張を自由にできる仕組みを取ってるエディタです。
最近話題になってた https://sizu.me/ のエディタでも採用されているようでした。
自分も最近、このサイトで思ったこととかを書いてるのですが、入力のしやすさが良く気になっていました。(実際には、作り手がすごいのですが。)
個人的に、Movable Typeでも Headlessな構成にするのが好きなタイプで、エディタもHeadlessな構成になってるのが気に入りました。
TipTapFieldを実装
TipTapでは、Vue.jsやReactやSvelteといったフレームワークからCDNといった幅広く実装導入することが可能なエディタです。
Movable Typeで利用する場合は、コンテンツフィールドで利用できるかなと思い、プラグイン化して導入しました。
技術選定
最初は、Svelteを使って実装しようと思ったのですが、Svelte 5系の使い方など調べて来なかったので今回は簡易にCDNで実装しました。
いずれはちゃんとWebComponentにして提供できればと思っています。(時間があれば…)
TipTap 実装部分
基本的にコンテンツフィールドの実装部分(主にPerl部分)は、 MarkdownItField と同じになります。
コンテンツフィールドの入力部分の実装箇所で TipTap を読み込んで取得データ(HTML)を保存や取得を行いました。
TipTapの入力表示部分は、この部分になります。
コンテンツフィールドは、複数のフィールドを利用するケースにも対応できるように class
の末尾には必ず content_field_id
が付与されるようにしました。
<div class="tiptap-editor-<mt:var name="content_field_id" escape="html">"></div>
TipTapを利用するためには、以下のように読み込みを行いました。
MTでは tiptap_data という部分が複数定義すると moduleが複数になってしまいます。
改良の余地はたくさんあるのですが、まずは動かす感じで定義しました。
content
部分は <mt:Var name="body" />
を定義して、再取得(保存後)の初期値としてセットしました。
<script type="module">
import { Editor } from 'https://esm.sh/@tiptap/core'
import StarterKit from 'https://esm.sh/@tiptap/starter-kit'
const editor = new Editor({
element: document.querySelector('.tiptap-editor-<mt:var name="content_field_id" escape="html">'),
extensions: [
StarterKit,
],
content: '<mt:Var name="body" />',
onUpdate({ editor }) {
const html = editor.getHTML()
document.querySelector('textarea[name="tiptap-field-<mt:Var name="content_field_id" escape="html" />-body"]').value = html;
}
})
</script>
TipTap以外でHTMLを用意したのは、保存したHTMLを格納する textarea
の部分です。
TipTapの入力値は、ブラウザで見えてるだけなのでデータを保持出来ません。
そのため、Movable Typeで利用できるデータとして保存させるフィールドを用意することが必要です。
MTMLで使えるように データ形式はHTML
にしました。
他にもJSONなどデータ形式は選ぶことができるようです。
自分は、以下の入力値は textarea
に保存させるようにしました。
<p class="mb-3 mt-3">
<button
class="btn btn-primary"
type="button"
data-bs-toggle="collapse"
data-bs-target="#tiptap-body-<mt:Var name="content_field_id" escape="html" />"
aria-expanded="false"
aria-controls="#tiptap-body-<mt:Var name="content_field_id" escape="html" />"
>
保存データ
</button>
</p>
<div class="collapse" id="tiptap-body-<mt:Var name="content_field_id" escape="html" />">
<textarea name="tiptap-field-<mt:Var name="content_field_id" escape="html" />-body" class="form-control" rows="3" mt:watch-change="1"><mt:Var name="body" escape="html" /></textarea>
</div>
tiptapには onUpdate というイベントが用意されています。
入力した値は、 getHTML() で取得できるため、この2つを利用して保存処理を行いました。
onUpdate({ editor }) {
const html = editor.getHTML()
document.querySelector('textarea[name="tiptap-field-<mt:Var name="content_field_id" escape="html" />-body"]').value = html;
}
入力イメージ
マークダウンに近い形の入力方式で エディタを入力できます。
コンテンツタイプの設定
プラグインのインストールは割愛しますが、インストール後コンテンツタイプで TipTap Editor
のフィールドが表示されます。
フィールド名・説明などを入力することで、登録したコンテンツタイプで表示できます。
TipTapのカスタマイズ
今回の即席で作ったTipTapFieldは、TipTapのカスタマイズを行っていません。
公式サイトの Example では WYSIWYGエディタのような入力選択を表示させることが可能です。
TipTapField をきちんと作るときは参考に実装したいなと思っています。(時間があれば...)
まとめ
今回は、即席で TipTapというHeadless Editor を利用したコンテンツフィールドプラグインを作ってみました。
フロントエンドは毎年さまざまなフレームワークやツールが出たり消えたりが激しいですが、試したみた系でプラグインを作ると理解が深まります。
今回は簡易的なプラグインなので、残念ながらプロダクト環境では使えないですが触ってもらえると嬉しいです。
気分が乗れば、きちんとプロダクト環境でも使えるように改良したいと思っています。
Discussion