AIの出力を「咀嚼」するMarkdownエディタを作った
作ったもの
Mark It Down - Chrome拡張機能のMarkdownエディタ。
AIチャット(ChatGPT、Claude、Gemini)の横で書く。
New TabとSide Panelの2モード。
なぜ作ったか
Claude Codeなどで誤って送信していらっとすることがある、Obsidianは拡張入れ過ぎて重い。Sublime textも使っていたが、IDEはコーディングで使いたいので、
マークダウンに特化したエディタが欲しかった。
高速で処理するならプレビューウィンドウが分かれているほうがよいが、
1ペインで済むものが欲しかった。
あえて自作してみようかと思った。
技術的設計判断
1. なぜChrome拡張機能か
判断: ファイルシステムではなく、Chrome拡張機能専用ストレージ(chrome.storage.local)を使用。
理由:
| 比較項目 | ファイルシステム | chrome.storage.local |
|---|---|---|
| 起動速度 | ファイル読み込み | 即座にアクセス |
| 同期 | 自動(クラウドに同期) | 手動(意図的) |
| 容量 | 無制限 | ~10MB(拡張機能) |
| 抽象度 | ユーザーが.mdを意識 | 意識しない、保存するときだけ意識 |
トレードオフ:
-
❌ 容量制限(画像埋め込み不可、リンクは貼れる)->UnlimitedStorage権限で無制限に
(画像はbase64エンコードが増大しファイルサイズが増大する) -
❌ 他ツールからの直接アクセス不可、mdで受け渡しは可能
(AIとの連携はできないし、ウェブサイトではないのでGoogle翻訳も効かない) -
❌ キャッシュを消しても消えないが拡張ごと消すと全て消える
(git連携機能やmdによるExport機能実装でバックアップはとれる) -
✅ 起動高速(New Tabか拡張ボタンからSide Panelで)
(Side Panelは機能制限あり、例えばCtrl+Fがブラウザ側が有効になるので使えない) -
✅ Side PanelでAIチャットやウェブサイトを見ながら並列作業
-
✅ オフラインで使用可能。データ漏洩は気にしなくていい。
「大量のデータを保存する場所」ではなく「作業場」という位置づけ。
だから容量制限は許容できる。
2. なぜMilkdown(ProseMirror)か
判断: WYSIWYGエディタとして Milkdown を採用。
比較検討:
| エディタ | 特徴 | 不採用理由 |
|---|---|---|
| CodeMirror単体 | 軽量、シンタックスハイライト | プレビューと編集が分離 |
| Monaco Editor | VS Code互換 | 重い(2MB+) |
| TipTap | 人気、活発 | Markdown保存に変換必要 |
| Milkdown | ProseMirror + Markdown native | ✅ 採用 |
Milkdownの決め手:
-
Markdownをそのまま内部表現として保持
-
ProseMirrorの堅牢なドキュメントモデル
-
プラグインアーキテクチャ(Mermaid、KaTeX追加容易)
ハマりポイント: ProseMirrorのDOM操作ルール
// ❌ アンチパターン(再描画で消える)
element.classList.add('highlight')
// ✅ 正しいパターン(Decoration API)
const decoration = Decoration.node(from, to, { class: 'highlight' })
tr.setMeta(decorationKey, DecorationSet.create(doc, [decoration]))
DOM直接操作はProseMirrorの再描画で消える。スタイル適用はDecoration APIを使う。
他にも色々、カスタムには手間取った
3. なぜ4フォルダ固定か
判断: カスタムフォルダ作成機能を初期に削除した。
Inbox - 作業中
Template - 複製用
Archive - 完成品(読み取り専用)
Trash - 削除待ち
理由: 整理に時間を取られたくない。
「このノート、どのフォルダに入れよう...」
この判断コストが積み重なると、書くことから遠ざかる。
4つの固定フォルダなら「定位置に戻す」だけ。
深く考えない。
人間もAIも咀嚼しきれない知識はノイズでしかない。
必要以上にためずに処理して気持ちよく送り出すような設計に。(Export)
新しいNoteはInboxに入ってそこで作業する、
TemplateにするならTemplateフォルダに入れればTemplateから作成可能。
Archiveに入れるとレンダリングを読み取り専用にして軽く開けるように。
Archiveでスター(ピン留め)するとまとめてエクスポートできる。
Trashはその名の通りゴミ箱。
整理するならObsidianなど他のアプリでやればいい。
軽量化の為に整理の機能は排除した。
4. なぜ手動Git同期か
判断: OneDriveのような自動同期ではなく、手動コミット。
ローカル: 自動保存(履歴なし)
リモート: 手動プッシュ(履歴あり)
理由: GUIによる「意図的なバージョン管理」
自動同期は便利だが:
-
作業途中の状態がリモートに残る
-
「この時点に戻したい」が難しい
-
競合解決が自動で行われる(怖い)
手動コミットなら:
-
「片付けてからプッシュ」のリズムが生まれる
-
意図した状態だけがリモートに残る
-
競合は目で見て解決
実装: ツールバーに Git ▼ ドロップダウン。Sync/Push/Pull/Historyを明示的に操作。
競合の解決画面もあるし、差分確認も可能。古いバージョンに戻す機能も。
5. 情報フロー設計
ツールバーのボタン配置は情報の流れを反映:
[Notes] [Import] [Paste] | [Edit ▼] | [Move ▼] [Git ▼] | [Export] [Copy]
↓ ↓ ↓ ↓ ↓
Entry Entry Edit Clear Exit
-
Entry: 情報の入り口
-
Edit: 咀嚼・編集
-
Clear: 片付け(定位置に戻す、Git同期)
-
Exit: 卒業(他ツールへ)
UIは哲学を反映する。
技術スタック
Framework: Preact - Reactの軽量版
Editor: Milkdown (ProseMirror + CodeMirror)
State: Zustand
Styling: Tailwind CSS
Diagram: Mermaid.js
Math: KaTeX
Build: Vite + TypeScript
Test: Vitest + Playwright
まとめ
| 設計判断 | 選択 | 理由 |
|---|---|---|
| ストレージ | chrome.storage.local | 起動速度、Side Panel対応 |
| エディタ | Milkdown | Markdown native、プラグイン拡張 |
| フォルダ | 4つ固定 | 判断コスト削減 |
| 同期 | 手動Git | 意図的なバージョン管理 |
| UI配置 | 情報フロー順 | Entry → Edit → Clear → Exit |
「機能を追加する前に、既存の機能で代用できないか考える。できるなら追加しない。」
複雑さは敵。シンプルに保つ。
リンク:
Mark It Down -Chrome拡張機能のMarkdownエディタ
AIチャット(ChatGPT、Claude、Gemini)の横で書く。New TabとSide Panelの2モード。
この記事は Mark It Down で書きました。
Discussion