Movable Type.netのカスタムブロックでよくある目次とページ内リンクを設定してみた
Movable Type.netのカスタムブロックが登場して久しいですね。 カスタムブロックでどんなことが出来るのか、よくあるレイアウトで試してみます。
01カスタムブロックを追加する
こちらはMovable Type Advent Calendar 202017日の記事です。初参加で緊張しております。
今回は、よくあるブログ記事のレイアウト、「記事の目次」と「ページ内リンク」をカスタムブロックを使って作成したいと思います。目指しているレイアウトは下図のような感じ。
ポイントは、各見出しにページ内リンク設定用のIDを割り振ること。そして、その見出しに対応する目次をページ上部に表示することですね。今回目指す完成形は、目次部分を手作業で入力せずとも、見出しを設定するだけでこれらの処理を自動化するところまでにしてみます。
見出しのカスタムブロックを作成する
それでは、今回利用するカスタムブロックを追加してみます。
識別子 | headline | ||||||||
---|---|---|---|---|---|---|---|---|---|
ブロック1 |
|
||||||||
ブロック2 |
|
||||||||
コンテナ要素で包む | チェック無し | ||||||||
ブロックの追加と削除 | チェック無し |
このカスタムブロックを記事の編集画面で追加すると下図のような入力フィールドになります。
見出しレベルを選択するドロップダウンで1を選択すると大見出し、2を選択すると中見出しになるようにしたいと思います。
02MTMLを使ってカスタムブロックをカスタマイズする
色々と試行錯誤した結果、かなり無理やり感はあるものの、<mt:BlockEditorBlocks>
を使ったMTMLでの加工によって実装が出来ました。もうソースを載せちゃいますね。
MTML
<mt:SetVar name="count1" value="0" />
<mt:SetVar name="count2" value="0" />
<mt:SetVar name="open" value="0" />
<mt:BlockEditorBlocks tag="EntryBody">
<mt:If name="type" eq="custom-headline">
<mt:BlockEditorBlocks>
<mt:If name="type" eq="sixapart-input">
<mt:Var name="__value__" setvar ="label" />
<mt:ElseIf name="type" eq="sixapart-select">
<mt:Var name="__value__" setvar="level" />
</mt:If>
</mt:BlockEditorBlocks>
<mt:If name="level" eq="1">
<mt:SetVar name="count1" op="++" />
<mt:SetVar name="count2" value="0" />
<mt:SetVarBlock name="link"><a href="#section<mt:Var name='count1' />"><mt:Var name="label" /></a></mt:SetVarBlock>
<mt:Else>
<mt:SetVar name="count2" op="++" />
<mt:SetVarBlock name="link"><a href="#section<mt:Var name='count1' />-<mt:Var name='count2' />"><mt:Var name="label" /></a></mt:SetVarBlock>
</mt:If>
<mt:If name="count1" eq="1">
<mt:If name="level" eq="1">
<div class="pageIndex"><ol class="pageIndex-list"><li>
<mt:Else>
<mt:If name="count2" eq="1">
<mt:SetVar name="open" value="1" />
<ol class="pageIndex-list"><li>
<mt:Else>
</li><li>
</mt:If>
</mt:If>
<mt:ElseIf name="count1" gt="1">
<mt:If name="level" eq="1">
<mt:If name="open" eq="1">
</li></ol>
<mt:SetVar name="open" value="0" />
</mt:If>
</li><li>
<mt:Else>
<mt:If name="count2" eq="1">
<ol class="pageIndex-list"><li>
<mt:SetVar name="open" value="1" />
<mt:Else>
</li><li>
</mt:If>
</mt:If>
</mt:If>
<mt:Var name="link" />
</mt:If>
</mt:BlockEditorBlocks>
<mt:If name="count1" ne="0">
<mt:If name="open" eq="1"></li></ol></mt:If>
</li></ol></div>
</mt:If>
<mt:SetVar name="count1" value="0" />
<mt:SetVar name="count2" value="0" />
<mt:BlockEditorBlocks tag="EntryBody">
<mt:If name="type" eq="custom-headline">
<mt:BlockEditorBlocks>
<mt:If name="type" eq="sixapart-input">
<mt:Var name="__value__" setvar ="label" />
<mt:ElseIf name="type" eq="sixapart-select">
<mt:Var name="__value__" setvar="level" />
</mt:If>
</mt:BlockEditorBlocks>
<mt:If name="level" eq="1">
<mt:SetVar name="count1" op="++" />
<mt:SetVar name="count2" value="0" />
<h2 class="hl __level1" id="section<mt:Var name='count1' />"><mt:Var name="label" /></h2>
<mt:Else>
<mt:SetVar name="count2" op="++" />
<h2 class="hl __level2" id="section<mt:Var name='count1' />-<mt:Var name='count2' />"><mt:Var name="label" /></h2>
</mt:If>
<mt:Else>
<mt:Var name="__value__" />
</mt:If>
</mt:BlockEditorBlocks>
ちょっと無理やり感がありますね…。今回、見出しを大見出しと中見出しの2つ用意して、目次を入れ子構造にしたかったので複雑になっています。.netの場合、<mt:Loop>タグがない、というのもしんどさの一因ですね…。あれがあればもっとシンプルだったかも。
タグの仕様については私も探り探り使ってみたので、あまり細かい解説は難しいのですが、ポイントをお話ししますね。
目次と本文とで2回カスタムブロックを出力する。
よく見ると<mt:BlockEditorBlocks>タグは4行目と58行目の両方に登場しています。.netのカスタムブロックを出力する際に、カスタムスクリプトではなく<mt:BlockEditorBlocks>タグを使うメリットとして、「1つのソースから複数の出力を得ることが出来る」という利点があります。今回はその利点を生かし、見出しのカスタムブロックを本文の中に差し込むだけで、目次が自動で出力されるようにしてあるのです。
<mt:If name="type" eq="custom-headline">
という部分で、カスタムブロックのタイプを判別して条件分岐していますね。目次の欄では、今回追加したheadlineのカスタムブロックのみしか使わないので、<mt:Else>の処理は省いています。
MTタグを組み合わせてセクションのIDを自動で割り振り
カスタムスクリプトではなく<mt:BlockEditorBlocks>タグを使うメリットとして、他に「MTタグを組み合わせたカスタマイズを行うことが出来る」というものがあります。
今回、記事の入力画面では、わざわざ手作業でIDを指定する項目を設けていません。MTタグを使うことによって、各見出しに自動で一意のIDを自動付与するようにしているのです。
そのために、変数のcount1とcount2を使って、インデックス番号を数えています。
もちろん、IDを番号ではなく、よりセマンティックになるように個別に指定したい場合には、それ用のフィールドを用意すれば可能です。ただし、「見出し」の入力欄と「ID」の入力欄の2つは、どちらも同じテキストブロックになります。カスタムブロックに内包しているブロックを個別に判別する識別子がないので(私は方法が見つけられず…。あるのなら教えてほしいです!!)、処理はすこし煩雑になりそうです。
ちなみに今のところ、入力欄は「見出し」と「見出しレベル」の2種類だけで、タイプが「テキスト」と「ドロップダウン」に分かれています。なので、7行目から11行目のif文で、無理やり判別しているような状態ですね。ここのタイプがかぶったときには、おそらく「1つ目の」「2つ目の」というような方法になってしまうのでしょうか…。悩み中です。
If文を駆使して頑張ってリストの閉じタグを入れる
目次の<ol>や<li>は、ご覧の通り変数とif文を駆使してどうにかこうにか閉じています。頑張りました。<mt:Loop>恋しいです。
03カスタムスクリプトも試したのだけど…
ちなみに、最初カスタムスクリプトでいい感じにできないか試してみたのでした。しかしいったん挫折しました。全然別の仕様なら実装できるのですが、今回のような仕様だと無理めでした。恥ずかしながら、どのへんで挫折したのか紹介しますね。
本文中の見出しにインデックス番号をふれない
よく考えれば当たり前です。カスタムスクリプトは、そのブロックのみで処理するスクリプトです。ほかのブロックと共通の変数を所持してインデックス番号をインクリメントしていく…とか、無理ですよね?ですよね?なので番号自動付与は無理でした。
ということで、IDは自動付与ではなく、個別にテキストブロックを用意して入力できるようにしてみました。
本文はカスタムスクリプト、目次はMTタグにしようと思ったけど無理だった
HTMLの成型は、<mt:BlockEditorBlocks>を使うより、カスタムスクリプトを使った方が自由度が高く簡単だと思います。なので、目次は<mt:BlockEditorBlocks>を採用するにしても、本文欄の見出しはカスタムスクリプトでもいいんじゃないか?と思ったのがカスタムスクリプト込みの実装にチャレンジした理由です。
しかしながら、カスタムスクリプトでHTMLを成型すると、<mt:BlockEditorBlocks>でブロックを呼び出した際に、先述した個別のブロックを判別することが出来ないようでした。上では「見出し」と「見出しレベル」それぞれの値を拾ってこれていたものが、まとまった一つの値に代わってしまうようです。なので、カスタムスクリプト×<mt:BlockEditorBlocks>の合わせ技は無理そうだな、という結論に至ったのでした。
<mt:BlockEditorBlocks>を使わずカスタムスクリプトのみで実装したいなら
<mt:BlockEditorBlocks>を使った実装は、MTMLに慣れている人じゃないと大変そうです。javascriptなら分かる人も多いので、カスタムスクリプトのほうが敷居が低そうですよね。だけど先述したような諸々から、上のような仕様ではカスタムスクリプトのみの実装はできないです。じゃあ、どんな仕様ならできるかな?というと…。
- 見出しのカスタムブロックにはIDの入力欄も設ける
- 目次のカスタムブロックを見出しとは別に個別に設ける
ということではないかなぁ~と思います。目次を手入力するのがどうしてもだるいのなら、javascriptで動的に目次を生成しちゃう、という手もありますよね。Vueとか使ってみるのも試したいところ…。
ただし多階層のリストをカスタムブロックで作るのは、それなりに苦労がありそうなので今度またチャレンジしたいです。カスタムブロックの入れ子は、スクリプトエラーが起こりがち、という悩みがあるので、いつか時間があれば悩んでいることについて記事を書ければな。
04今後への期待
MTのカスタムブロックは、カスタムスクリプトの便利さがピックアップされがちですが、<mt:BlockEditorBlocks>の使いどころもかなりありそうですね!!!
今後さらなるバージョンアップを期待したいです。例えば
- 「ブロックの追加と削除」を許可して、カスタムブロックの入れ子を実装したいときに、デフォルトで用意していたブロックも削除可能になってしまうので、必須と任意を選べたらうれしい。
- 上記の点に関連して、「ブロック」ではなくGutenbergのInspectorのように設定出来たら使い勝手が爆上がりしそう…
- 各ブロックの識別子が設定出来たらうれしい。
- 私だけかもしれないけどカスタムスクリプトで成型しているカスタムブロックを入れ子にすると動作が不安定になる…。いつか機会があれば相談させてください。
とか、夢は膨らみます。
この記事が役に立ったらシェアしてください!