Movable Type(以下MT)のブロックエディタの使い方、主にカスタマイズ方法について解説します。
カスタマイズとはブロックエディタにオリジナルのブロックを追加し、より柔軟にコンテンツを制作できるようにする事を指しています。
ブロックエディタと聞くとWordPressのブロックエディタが思い浮かびますが、WordPressのブロックエディタとはカスタマイズできる範囲が大きく異なったり、使い勝手が異なります。
WordPressのブロックエディタと結びつけて理解しようとすると混乱しますので、MTはMT独自のブロックエディタと切り分け、まったく新しいものと考える方がよいでしょう。
導入方法
ブロックエディタは2021-08-05現在も日々アップデートされております。
ですので、デフォルトパッケージには含まれておらず、今の所はMTのプラグインとして使用する事が可能です。
プラグインは以下のGit Hubからダウンロードし、FTP経由でサーバーに設置する事で使用できるようになります。
mt-plugin-MTBlockEditor[Git Hub]
プラグインの導入方法
Git Hubにアクセスしたら最新のバージョンの『MTBlockEditor-***.zip』というファイルをダウンロードします。
ダウンロードしたプラグインを解凍すると以下のような構成になっているかと思います。使用するのは『mt-static』と『plugins』フォルダです。
この2つのファイルそれぞれに『MTBlockEditor』というフォルダが入っているので、そのフォルダをFTPなどを使用してMTの構成フォルダの『mt-static』と『plugins』ディレクトリにアップします。
root plugins MTBlockEditor --- このファイルをアップする mt-static plugins MTBlockEditor --- このファイルをアップする
同じ名前とファイル構造になっているのでわかりやすいですね。
プラグインを有効にする
プラグインをサーバーにアップしましたら、MT管理画面のシステム > プラグインへアクセスします。
『MTBlockEditor ***』とプラグインが表示されていますのでこれを有効にします。
『BlockEditor』という旧ブロックエディタがデフォルトで入っていますが、こちらは使用していなければ無効にしておきましょう。
これでブロックエディタを使用する事ができるようになりました。
基本的な使い方はマニュアルを見れはすぐにわかるので割愛して、カスタマイズ方法について紹介します。
ブロックエディタを把握
ブロックエディタをカスタマイズするためには全体像とできる事、できない事を把握する事が大切です。
ブロックエディタの見た目
ブロックエディタを実際に触る事ができるデモサイトがありますのでそちらのページで実際に触ってみると理解が早いでしょう。
全体像
私が勝手に考えたものですが、ブロックエディタを把握すう際に以下の3つ分けて考えると理解しやすいです。
- 入力モード
- ビューモード
- 公開画面
入力モード
テキストを入力できる状態の事を『入力モード』と勝手に呼びます。
ビューモード
テキストを入力後、入力エリアからフォーカスを外すと見た目が切り替わり、ユーザーが実際に目にする見た目になります。
この状態の事を『ビューモード』と勝手に呼びます。
公開画面
実際にユーザーが目にする状態、ページを『公開画面』と勝手に呼びます。
上記、各状態でそれぞれ特長がある
- 出力されるhtmlの編集方法は、jsから編集する方法とテンプレートから編集する方法の2つある。
- jsでカスタムする方法は、編集時に内容を変更して変更したものを保存するという流れなので、もし、すでに別の記事などがある場合に全ての記事に一気に反映する事はできない。
- テンプレートで制作する方法だと、htmlファイルを再構築する事になるのですべての記事に反映できる。
- jsはブロックごとで独立する。入力モードとビューモード(勝手に命名)の2つの状態があるが、ビューモードではiframeタグを使用して各ブロックを表示している。そしてカスタマイズのためのjsは各ブロックそれぞれのiframeの中にインラインで記載されるので独立して動く。なので、同じブロック(同じclass)があっても干渉しあうことはない。
- オリジナルのブロックを追加する際のデフォルト機能でもある程度出力されるhtmlをコントロールする事は出来る。
- 編集画面でのcssはブロックを制作する時にstyleタグからcssを直書きしてもいいし、編集ページにinkeタグでcssファイルを読み込んでもいい。一括管理したいからcssファイルを読み込む方が良いと思っている。
- ユーザーが見る画面でのcssは別途cssを用意しなければいけない。テンプレートからcssファイルを読み込んでの対応でいいだろう。
- 同じブロックどおしでの入れ子はできないが、別のブロック間での入れ子はok
できる事、できない事
- オリジナル制作したブロックの中にオリジナル制作した同じブロックを追加して入れ子にして使用する事はできない。(デフォルトのブロックは追加できる)
- MTBlockEditorSetCompiledHtmlで出力されるhtmlを変更するとそれ以外の入力内容は表示されなくなる(AとBの入力について関数を利用し出力の設定をすると、AとB以外に入れ子でデフォルトのフィールドを追加しても、後から追加したデフォルドのフィールドの値はメソッドに定義されていないので表示されない。フィールドに入力するモードの時は表示される)
- 入力モードの画面は編集できない
ブロックエディタをカスタマイズ
ブロックエディタをカスタマイズする方法は2つ存在します。
その前に、ここでのカスタマイズを正確言うと、ブロックエディタの入力モードのUIをオリジナルにすることはできず、入力した値をビューモードと公開画面で出力する際の見た目を変更する事をカスタマイズと呼んでいます。
WordPressのブロックエディタのように入力モードのUIも変更できるようなイメージをすると理解の邪魔になりますので、MTのブロックエディタでできる事とできない事をきちんと把握しましょう。
話を戻すとカスタマイズ方法は2つあり、以下になります。
- Java Scriptからカスタマイズする方法
- テーマからMTタグを使用してカスタマイズする方法
どちらもメリット・デメリットが存在しますが総合的にみてJava Scriptを使用してカスタムする方がメリットが大きいと感じております。
このページでは両方のやり方を説明します。
まずは共通で必要となるオリジナルのブロックを制作しましょう。
オリジナルのブロックを追加する
今回は『Q&A』のコーナーをブロックエディタで制作してみましょう。
Q&Aですので必要なブロックは質問内容と回答内容を入力するフィールドが1つのセットになったブロックです。
ブロックの制作はサイドバーの『ブロックエディタ > カスタムブロック』から作ることができます。
新規制作をクリックし、以下のようにブロックの設定を行いましょう。
ブロックの『名前』『識別子』を入力します。
アイコンは任意ですが、セットすることでブロックを選択する際の助けとなります。設定が無ければ、デフォルトのアイコンが適応されます。
『ブロックを追加』ボタンをクリックし、『質問内容』と『答え』のテキストフィールドを追加しましょう。
設定する項目はテキストフィールドの場合、『ラベル』『説明文』『クラス名』『規定値』となります。
次の章で説明しますが、クラス名を指定すると出力時にクラスをつける事が可能です。ここでは一旦無視してラベルと説明文を先に埋めておきましょう。
その他にも設定項目はいろいろありますが、『質問内容』と『答え』のテキストフィールドを追加できましたら一旦保存ボタンを押します。
これで最低限必要な設定が完了し、ブロックを追加する事ができました。
使用する事が可能です。
今の状態で使用すると出力されるhtmlは以下のようになります。
<p>質問の内容</p> <p>答え</p>
これではスタイルを当てる事ができないのでここにclassをあてる設定をします。
デフォルトの機能で出力されるhtmlをコントロールする
classを当てる事はデフォルトの機能で可能です。
ここからさらに別のhtmlタグを追加して複雑なデザインを再現しようとするとjsでのカスタマイズが必要となってきます。
先ほど一旦飛ばした各フィールドの『クラス名』から設定します。
ここに入力された値がそのタグのclassとなります。
『質問の内容』にc-pa__q、『答え』にc-qa__aとセットすると以下のようなhtmlが出力されます。
<p class="c-qa__q">質問の内容</p> <p class="c-qa__a">答え</p>
さらにこの2つのhtmlの親をつくる事ができます。
下の方にスクロールすると『コンテナ要素で包む』というチェックボックスがあります。
こちらをチェックすると『クラス名』という入力エリアが表示されるのでこちらに親のクラス名を記載します。
c-qaと記載しました。すると以下のようなhtmlが出力されます。
<div class="c-qa"> <p class="c-qa__q">質問の内容</p> <p class="c-qa__a">答え</p> </div>
さらにほかのdivで囲ったり、これ以上複雑なhtml構造にしたい場合、jsでのカスタマイズが必要となってきます。
jsでカスタムする方法
『カスタムスクリプト』にjsやcssを記載する事で出力されるhtmlを複雑にカスタマイズする事が可能です。
こちらに記載されたコードはビジュアルモードの時に実行されます。
cssを記載するとビジュアルモードでの表示の見た目を変更する事ができ、jsを記載するとビジュアルモードで出力されるhtmlコードを書き換える事が可能です。
htmlコードの書き換えはMTBlockEditorSetCompiledHtml関数の第1引数にhtmlコードを渡す事で渡したhtmlコードの内容に書き換えてくれます。
動きとしてはinnerHTMLメソッドと同じように、DOM要素が書き換えられます。追加ではなく、書き換えですので、引数に渡すhtmlコードに記載された内容しか表示されなくなることに注意してください。
ですので、せっかくブロックエディタで後からコンテンツを自由に追加できる仕組みがあるにも関わらず、引数に渡すhtmlが追加に対応していなければ、追加した要素は表示されないといいう事です。
コンテンツの制作から表示までの全体の流れ
入力モードでテキストを入力後、ビジュアルモードに切り替る際に『カスタムスクリプト』に記載したjsが実行されます。
jsから入力内容のDOM要素を取得し、値を加工してMTBlockEditorSetCompiledHtml関数で再レンダリングします。
この時、『カスタムスクリプト』にcssも記載されていれば、デザインも変更する事が可能です。ユーザーが見る画面で表示されるデザインと同じにするとUIが向上します。
その後、ページを保存する事でその書き換えられた状態のhtmlがコンテンツとして保存され、公開画面に表示されます。
ですが、あくまでもビューモードの時に働くjsとcssを記載しているだけなので、公開画面では『カスタムスクリプト』に記載したcssは適応されません。
別で読み込む必要があります。
js処理の書き方
以下のjsが処理のベースとなります。
document.addEventListener("DOMContentLoaded", () => { // ↓これは消したらダメ if (document.body.dataset.hasCompiledHtml) { return; } // ↑これは消したらダメ // 処理はここより後に記載する(サンプル) // 入力内容を取得 let source = document.querySelector("取得したい要素のクラス名"); // htmlを制作 const res = `<div class="c-test-block">${source1}</div>`; // 再レンダリング MTBlockEditorSetCompiledHtml(res, true); });
4-6行目は入力モードになった際に処理を終了するコードです。これが無いと処理がループします。
MTBlockEditorSetCompiledHtml関数には第二引数でaddEditHistoryというオプションをbooleabで選択できるようです。
名前から変更履歴を残す事ができるようなイメージはできますが、具体的に何ができるのかは現在わかっていません。
jsから制作する際のデメリットと注意点
コンテンツを編集する際にhtmlが評価されますので、すでに記事が存在している場合、全ての記事に変更を一気に適応するというような事はできません。(テンプレートから制作する場合はできます)
MTBlockEditorSetCompiledHtml関数は1つのブロック(処理)に複数書くことはできないません。2つ書くと、最後に書いたものが適応されます。
テンプレートからカスタマイズする方法
テンプレートからカスタマイズする方法はいままでテンプレートを制作してきた方法と大きく変わりません。
mt:BlockEditorBlocksというタグを使用する事で入力した値を表示する事ができます。
<mt:BlockEditorBlocks> <div class="c-test-block"> <mt:Var name="__value__" /> </div> </mt:BlockEditorBlocks>
他にも、コンテンツタイプとしてブロックエディタを使用しているなら通常のコンテンツタイプの取得方法でも可能です。
<MT:Contents> <div class="c-test-block"> <mt:ContentField content_field="本文"><mt:ContentFieldValue language="ja"></mt:ContentField> </div> </MT:Contents>
だた、見てわかるようにテンプレートとして制作すると値の選別がきず、全て一緒に表示されます。
例えば今回の『よくある質問』のブロックですと、『質問内容』と『答え』を別々に取得して加工を行いたいでしょう。そのほかにも、チェックボックスの内容から判断してコンテンツを変更するというような処理を実装する事は難しいです。
MTBlockEditorSetCompiledHtmlの仕様
iframe内にMTBlockEditorSetCompiledHtml関数のコードが記載されており、そこから追う事ができます。
20行目です。
MTBlockEditorSetCompiledHtml関数は1つのブロック(処理)に複数書くことはできないません。2つ書くと、最後に書いたものが適応されます。
引数から受け取ったデータを使用してオブジェクトデータを制作し親のフレームにpostしています。
受け取った親のjsでいろいろ加工しているのでしょう。(追えなかった)
引数は2つ。
- e:string – 出力するhtmlデータ
- t:boolean – addEditHistoryとあるので変更履歴系?(どうなるのかわからない)
["alert", "confirm", "prompt"].forEach(function (name) { window[name] = function () { console.log(name + " is disabled in a preview iframe") }; }); setInterval(function Aa() { var e = document.body, t = document.documentElement, n = Math.max(e.scrollHeight, e.offsetHeight, t.clientHeight, t.scrollHeight, t.offsetHeight), r = Math.max(e.scrollWidth, e.offsetWidth, t.clientWidth, t.scrollWidth, t.offsetWidth); parent.postMessage({ method: "MTBlockEditorSetSize", blockId: e.dataset.blockId, arguments: { height: n, width: r } }, "*") }, 1000) var MTBlockEditorSetCompiledHtml = (function () { return function Fa(e, t) { parent.postMessage({ method: "MTBlockEditorSetCompiledHtml", blockId: document.body.dataset.blockId, html: e, arguments: { addEditHistory: t && t.addEditHistory } }, "*") }; })(); var MTBlockEditorAddDroppable = (function () { return function za(e) { return new Promise((function (t) { function n(n) { n.classList.add("mt-block-editor-mt-be-droppable-area"), n.addEventListener("click", (function (t) { if (!(t.target instanceof HTMLInputElement)) { t.preventDefault(); var n = document.createElement("INPUT"); n.type = "file", n.style.display = "none", n.addEventListener("change", (function (t) { e(t) })), document.body.appendChild(n), n.click() } })), n.addEventListener("dragover", (function (e) { e.preventDefault(), e.stopPropagation(), e.dataTransfer && (e.dataTransfer.dropEffect = "copy"), n.classList.add("mt-block-editor-mt-be-droppable") })), n.addEventListener("dragenter", (function (e) { e.preventDefault(), e.stopPropagation() })), n.addEventListener("dragleave", (function () { n.classList.remove("mt-block-editor-mt-be-droppable") })), n.addEventListener("drop", (function (t) { t.preventDefault(), t.stopPropagation(), e(t) })), t() } "complete" === document.readyState || "interactive" === document.readyState ? n(document.body) : document.addEventListener("DOMContentLoaded", (function () { n(document.body) })) })) }; })(); (function () { (function Ba() { document.addEventListener("click", (function () { parent.postMessage({ method: "MTBlockEditorOnClick", blockId: document.body.dataset.blockId }, "*") }), { capture: !0 }), document.addEventListener("keydown", (function (e) { parent.postMessage({ method: "MTBlockEditorOnKeydown", blockId: document.body.dataset.blockId, arguments: { key: e.key, ctrlKey: e.ctrlKey || e.metaKey, shiftKey: e.shiftKey } }, "*") }), { capture: !0 }) })(); })();