やりたかったこと
ここ最近,vscode上でmarkdownをひたすら使い潰すためだけに色々とやってきているのだが(以下はこれまでの記事),
これまでの研究によりmarkdown pdfのextension.jsを書き換えることで任意の.html・.js・.cssファイルをプラグインとして利用できる環境を作れてしまうことが判明し,これにkatexを混ぜることで概ね万能な数式の表示すら可能となった.
しかし大元の発端は「latexが面倒だからmarkdownを使おう」というものであり,つまるところlatexにある機能がkatexさらにはそれが対応できるmarkdownには搭載し得ないもので,これは下位互換にすぎない不完全な環境であったのだ.
そこで私が考えついたのが,「latexに備わる組版用の各種コマンドをhtmlの機能で代替できないか?」というものである.
目標
完全に独立した名前を持ち,余計な指定をする必要がなく,かつidとstyleが自在に設定できる「真のカスタムエレメント」を実現する.これによりlatexコマンドに対応するカスタムエレメントを自由に作成,柔軟な組版をmarkdownで実現する.
方針
かつてはdocument.registerElementで好き放題にできていたらしく,その手法を扱った記事がいまだに検索にヒットするが,
現在では全ブラウザ非対応の憂き目に合っている(リンク先は英語ページのみ).ゆえに代替であるCustomElementRegistry
インターフェースが持つcustomElements.define(あっちはElementなのにこっちはElementsなの,控えめに言って最悪)
を使用せざるを得ないが,現在では数多くの記事においてこのページが示す「自律型」「拡張型」の2種類のみを扱っている.
しかし前者はタグの間の文字を扱えない(DOMパースがシカトされる)・後者はis=''の指定が必要(DOMパースしてくれる)ため,私の要求には満たない.他方でwhatwg(html系開発コミュニティ)によるcustom elementsのリファレンスでは,
カスタム要素定義を登録するのは、 それに関連する要素が初期時に — 構文解析器などにより — 作成された後の方が,好ましいこともある。 昇格は、 そのような用法を可能化して,[ カスタム要素の内容を漸進的に増強する ]ことを許容する。
という内容もあり,つまりは「名前に-が入っているエレメントはカスタム要素として解析が後回しにされる」程度には動作の許容がある.更には「任意のhtml要素を指す」HTMLElement(任意?あっ…)が現在でも全てのブラウザで実装されている
ため,この2つを足がかりにして攻略していった結果,なぜだか無事に攻略に成功した.
困難
カスタムエレメントなだけあって処理順の設定が難しく,上記2点の仕様が変わらなくても他の関数の仕様が変わるだけで死ぬ可能性は否定できない.これはあくまで2021年12月現在の情報であることはお許しいただきたい(未来のことは知らない).
やり方
まずは上の3つの記事を読むことこそお勧めする(プラグイン管理が格段に楽になるので)が,実際は読まなくても大丈夫だ.
原理
以下では<div></div>由来のボックスとしての<d-box></d-box>を作成する.
template.js
class DBox2 extends HTMLDivElement {
constructor() {
super();
this.id = 'box';
}
}
customElements.define('d-box2', DBox2, { extends: 'div' });
class DBox extends HTMLElement {
constructor() {
super();
let wrapper = document.createElement('div');
wrapper.id = 'box';
let content = this.innerHTML;
console.log(content);
this.innerHTML = '';
wrapper.insertAdjacentHTML("afterbegin", content);
this.insertAdjacentElement("beforeend", wrapper);
}
}
document.addEventListener("DOMContentLoaded", function() {
customElements.define('d-box', DBox);
});
template.css
body {
color: #222;
background-color: #fff;
}
#box {
color: #444;
border: 5px solid #666;
border-radius: 10px;
padding: 5px;
margin: 5px;
}
test.md
# これはなんですの?
## `custom element`ですわ!
マニュアル通りの手法では
<div is='d-box2'>
sasa
</div>
わたくしが極めた手法では
<d-box>
二重動作もOKですわ!
<d-box>
$$
\begin{align}
e=mc^2 \\\\
e^{i\pi}+1=0
\end{align}
$$
</d-box>
</d-box>
↓
何をやったか
なんの情報も持たない<d-box></d-box>の中身を丸ごと文字列で抜き取り<div id='box'></div>を埋め込んで更にそこに元の文字列を入れただけ,要するに作業としては大したことはやっていない…が,処理同期手法の試行錯誤が面倒だったことに加え,そもそも「そういうやり方しかない」という結論に至るまでに半日ほど要してしまったことがあり,少し辛かった.
ちなみに<div is='d-box2'></div>はよく紹介されているやり方である,どちらも同じ結果を得ている以上これは成功だろう.
最適化
superのリファレンスに目を通した時に「この処理はチェーン化できるのでは」と気づいたので,実際に実装に成功してみた.
結果は省略するが以下にクラス処理に関する記述を載せる,例としてAncestorOfAllからフォールバックし3段組を実現する.
template.js
class AncestorOfAll extends HTMLElement {
constructor(element, purpose) {
super();
let wrapper = document.createElement(element);
wrapper.id = purpose;
let content = this.innerHTML;
console.log(content);
this.innerHTML = '';
wrapper.insertAdjacentHTML("afterbegin", content);
this.insertAdjacentElement("beforeend", wrapper);
}
}
class DCol3 extends AncestorOfAll {
constructor() {
super('div', 'col3');
}
}
document.addEventListener("DOMContentLoaded", function() {
customElements.define('d-col3', DCol3);
});
template.css
#col3 {
column-count: 3;
padding: 20px;
margin: 10px;
}
要はsuper()でsuper()を呼び出しているに過ぎず,あとは先程と同じように<d-col3></d-col3>で呼び出せば良いだろう.
感想
恐らく初めてjavascriptのクラスのお勉強をしたし,処理の同期や非同期について考えた.template.jsは<head><\head>に入っているし,恐らくどこに置いても正常に動作すると考えられる(一部のリファレンスは末尾設置を推奨しているけど).
ただし念のためにkatexよりも前に読み込む設定にはした方が良い,レンダリングのどこかで不都合が起きるかもしれない.
今後よりlatexライクな環境に近づけようなどと考えているが,もしこれに使い道があれば是非とも使ってもらえると嬉しい.
↧