はじめに
今日は、開発中のメモ管理アプリ「SmartMemo」にKaTeXによる数式表示機能を実装しました。
これまでSmartMemoではMarkdownに対応し、CodeMirrorを使った編集やリアルタイムプレビューを実装してきました。
今回はさらに、Markdownの中に数式を記述できるようにし、
$...$ によるインライン数式
$$...$$ による独立した数式
を表示できるようにしました。
1. KaTeXを導入
まず、KaTeXに必要なファイルをCDNから読み込みました。
katex.min.css
katex.min.js
auto-render.min.js
KaTeXを利用することで、例えば次のような記述を数式として表示できます。
$E = mc^2$
また、独立した数式として、
$$
\int_0^1 x^2 dx = \frac{1}{3}
$$
のような記述にも対応しました。
2. 入力中のリアルタイムプレビューにKaTeXを反映
次に、メモ作成画面と編集画面のリアルタイムプレビューにも数式表示を追加しました。
対象となるのは、
create.html
edit.html
です。
SmartMemoでは、入力したMarkdownをJavaScriptで変換してプレビューしています。
そこで、updatePreview() の中で、
今回の処理の流れは、
Markdown変換(marked.js)
↓
DOMPurifyでサニタイズ(XSS対策)
↓
KaTeXによる数式レンダリング
↓
プレビュー表示
という流れにしました。
ポイントは、renderMathInElement() をプレビュー更新処理の中で呼び出すことです。
これにより、入力内容が変更されるたびにMarkdownと数式の表示も更新されるようになりました。
また、このタイミングでCodeMirrorのテーマをdraculaからmonokaiへ変更しました。
3. 保存済みメモにもKaTeXを反映
入力中だけでなく、保存したメモの詳細画面でも数式を表示できるようにしました。
対象は、
detail.html
です。
ページ読み込み時に、
document.addEventListener("DOMContentLoaded", ...)
を利用してKaTeXのレンダリングを実行するようにしました。
これにより、
入力
↓
リアルタイムプレビュー
↓
保存
↓
詳細画面
という一連の流れで数式を表示できるようになりました。
なお、一覧画面のindex.htmlは現在「タイトル+更新日」の表示のみとしているため、メモ本文を表示していません。
そのため、今回は一覧画面へのKaTeX対応は見送ることにしました。
4. 実装中にハマったポイント
今回、一番勉強になったのがrenderMathInElement()をどこで実行するかという問題でした。
最初は、renderMathInElement()をプレビュー更新関数の外に書いていました。
そのため、ページの初回読み込み時には数式が表示されるものの、その後入力内容を変更しても数式が更新されませんでした。
原因は、updatePreview()が入力のたびに呼ばれているのに対して、KaTeXのレンダリング処理は最初の1回しか実行されていなかったことでした。
そこで、
updatePreview()
↓
Markdown変換
↓
サニタイズ
↓
renderMathInElement()
という形に変更しました。
今回のことで、**「処理を関数の中に書くのか、外に書くのかによって実行されるタイミングが変わる」**ということを実感しました。
5. JavaScript側とPython側の役割
今回もう一つ学んだのが、リアルタイムプレビューと保存後の表示では処理の仕組みが異なるということです。
SmartMemoでは、Markdownの処理にPythonのmarkdownライブラリとbleachを使用しています。
しかし、PythonのMarkdownライブラリ自体はLaTeXの数式を描画するものではありません。
そのため、今回は、
入力中
JavaScript + Markdown + KaTeX
↓
リアルタイムプレビュー
保存後
Python + Markdown + Bleach
↓
HTML生成
↓
ブラウザ側でKaTeX
↓
数式表示
という役割分担にしました。
数式そのものをPython側で変換するのではなく、数式の描画はブラウザ側のKaTeXに任せるという設計です。
6. タイトル未入力で保存できてしまうバグを修正
KaTeXの実装とは別に、今回の開発中に一つバグを発見しました。
タイトルとカテゴリを入力せずに保存したところ、タイトルが空のメモが作成されてしまいました。
その結果、一覧画面ではタイトルが表示されないため、詳細画面へ移動するリンクもクリックできない「見えないメモ」のような状態になりました。
そこで、タイトルを必須項目として扱うように修正しました。
views.pyのcreateビューに空欄チェックを追加し、create.htmlにはエラーメッセージを表示するようにしました。
また、すでに作成されてしまった空タイトルのメモについては、Django Adminから削除して対応しました。
今回のように、実際にアプリを使ってみることで、想定していなかった入力パターンによるバグを発見できました。
今回学んだこと
今回の実装を通して、以下のことを学びました。
KaTeXによるLaTeX数式の表示方法
auto-render.min.jsによる自動レンダリング
リアルタイムプレビューと数式レンダリングの組み合わせ
JavaScriptの関数が実行されるタイミング
JavaScript側とPython側の役割分担
MarkdownとLaTeXは別の仕組みであること
入力値のバリデーションの重要性
実際にアプリを操作してバグを発見することの大切さ
今後の予定
SmartMemoは、単なるメモ管理アプリではなく、
「メモ Write・コード Code・実行 Run」
を一つの場所で管理できるアプリを目指しています。
今後はさらに、
- コード編集機能の改善
- コード実行機能
- AIアシスタント機能
- PostgreSQL対応
- UI改善
などにも取り組んでいく予定です。
少しずつですが、Djangoを学びながらSmartMemoを成長させていきたいと思います。