mdv-live 0.5.4 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,133 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.5.8] - 2026-05-08
9
+
10
+ ### Fixed
11
+
12
+ - **Symlink TOCTOU on note auto-save** (codex-loop で 4 round 連鎖修正):
13
+ - 旧コードの TOCTOU guard が `earlyDeck.realPath` (lock 取得前) と比較
14
+ していた → 進入後 swap、戻し、書き込みで別ファイル読み出しが original
15
+ path に書ける race を塞ぐため `deck.realPath` (in-lock) と比較に修正
16
+ - in-lock で realpath が変わったら mutex 範囲外の書込みになる →
17
+ detection を入れて再 lock 取得
18
+ - 再 lock 取得を server-side 自動 retry で実装 (client は STALE 以外を
19
+ terminal 扱いするため)
20
+ - retarget retry の入れ子 lock が opposite retarget で deadlock し得る
21
+ → trampoline で **outer lock 解放後に新 realpath を取得**
22
+
23
+ ### Added
24
+
25
+ - TOCTOU 正常系の API regression test
26
+ - SaveQueue coalesce/serialize/dropPath/例外耐性 5 件
27
+ - Sec-Fetch-Site=same-origin の B 受理パス + cross-site 拒否
28
+
29
+ ### Architecture (refactor)
30
+
31
+ - `src/api/marpNote.js` orchestration を 38 行に。実装は `src/api/marpNote/`
32
+ 配下の `guards.js` / `readDeck.js` / `handleGet.js` / `handlePut.js` に分割
33
+ - `src/static/lib/saveQueue.js` (per-deck queue + per-slide coalesce、純 JS)
34
+ - `src/static/lib/tabRegistry.js` (tab close hook → メモリリーク解消)
35
+ - `src/static/lib/apiClient.js` を deck/file/tree/info/pdf 用に拡張、
36
+ app.js の fetch 直叩きを 13 → 2 (WebSocket / /raw/ のみ残存)
37
+ - `src/concurrency/pathLock.js`: promise-chain ベースの正しい mutex
38
+ (旧 naive Map 実装の thundering-herd race を排除)
39
+ - `src/utils/errors.js`: mkError + ERROR_STATUS テーブル + sendError SSOT
40
+ - `src/utils/etag.js`: ETag 計算 SSOT
41
+ - placeholder を CSS pseudo-element 化 (`:empty::before`) で
42
+ contenteditable に placeholder text が混入する罠を構造的に解消
43
+ - STALE 通知時に編集テキストを localStorage に自動退避
44
+
45
+ ### Tests
46
+
47
+ - 222 → **236 件 (+14)**、全 PASS
48
+
49
+ ## [0.5.7] - 2026-05-08
50
+
51
+ ### Added
52
+
53
+ - **Presenter View** (Marp スピーカーノート別ウィンドウ表示・編集)
54
+ - P キー (Cmd/Ctrl 修飾なし) または Marp ナビボタンで起動
55
+ - Current / Next スライド + Speaker Notes + 経過タイマーを並列表示
56
+ - パネルサイズはドラッグハンドルで変更可能 (localStorage 永続化)
57
+ - BroadcastChannel `mdv-marp-presenter` でメイン⇄presenter 双方向同期
58
+ - **スピーカーノートの自動保存**: presenter ノートパネルをクリック→編集→
59
+ 800ms デバウンスでサーバへ PUT。ソース markdown の HTML コメントを書き換え
60
+ - **`/api/marp/decks/:path` エンドポイント** (GET/PUT/OPTIONS)
61
+ - **ETag 楽観ロック** (`sha256:`) で外部編集との衝突検出
62
+ - **per-path 非同期 mutex** で同時 PUT を直列化
63
+ - **Multi-note Guard**: 1 slide に複数ノートがある場合は read-only
64
+ - **CSRF**: Origin + Sec-Fetch-Site + Content-Type 厳密検証
65
+ - **PNA preflight 拒否** (localhost 同一オリジン要求)
66
+ - **128KB body limit + 専用 413 ハンドラ** で情報漏洩防止
67
+
68
+ ### Architecture
69
+
70
+ - Marp スライド範囲・ノート位置の特定を **Marpit token** に委譲する
71
+ `marpitAdapter` を新設。regex 再実装の脆弱性を構造的に解消
72
+ - `validatePathReal` + `O_NOFOLLOW` + realpath 二重解決で symlink swap
73
+ best-effort 防御
74
+ - `atomicWrite` で `O_EXCL` temp + chmod EPERM 限定 + EXDEV 二段 rename +
75
+ uid+mtime sweep
76
+ - BOM/CRLF/CR/UTF-8 surrogate pair 安全な行↔バイト変換ヘルパに集約
77
+ - 共通 error コード/HTTP status マッピングを `utils/errors.js` に SSOT 化
78
+ - promise-chain ベースの正しい mutex (`concurrency/pathLock.js`) で
79
+ thundering-herd race を排除
80
+ - HTTP client / BroadcastChannel 名 / message schema を専用ライブラリに分離
81
+ - セキュリティ脆弱性 5 件 (basic-ftp / ip-address / postcss) を `npm audit fix`
82
+
83
+ ### Tests
84
+
85
+ - 既存 119 → **228 件 (+109)** すべて PASS
86
+ - 性能: 500 slides / 155 KiB ファイルで parseDeck+rewrite 86ms
87
+
88
+ ## [0.5.6] - 2026-04-27
89
+
90
+ ### Added
91
+
92
+ - Markdown PDF変換用の `mdv convert` サブコマンドを追加
93
+ - `-s <css-file>` によるPDF変換用CSS指定を追加
94
+ - `--pdf-options <json-file>` によるPuppeteer PDF options指定を追加
95
+ - Web UIのStyleパネルを追加
96
+ - CSSファイルパスを指定可能
97
+ - PDF options JSONファイルパスを指定可能
98
+ - 指定CSSをMarkdownプレビューに反映
99
+ - `Clear` でスタイル指定を解除可能
100
+ - 通常MarkdownのWeb UI PDF exportを `md-to-pdf` に対応
101
+ - PDFスタイル指定のサンプルを追加
102
+ - `src/styles/report.example.css`
103
+ - `src/styles/report.pdf-options.example.json`
104
+
105
+ ### Changed
106
+
107
+ - PDF出力設定をCSSとPDF options JSONに分離
108
+ - Marp PDF出力は従来どおりMarp CLIを使用し、通常Markdown PDF出力のみ `md-to-pdf` を使用
109
+
110
+ ## [0.5.5] - 2026-04-05
111
+
112
+ ### Fixed
113
+
114
+ - タスクリストのインライン要素(太字・リンク・コード)が二重表示されるバグを修正
115
+ - markdown-it-task-lists の labelAfter オプション誤用が原因
116
+ - Mermaidプレースホルダがユーザーコンテンツと衝突する問題を修正(nonce付与)
117
+ - 空frontmatter(`---\n\n---`)で空のyamlコードブロックが生成される問題を修正
118
+
119
+ ### Removed
120
+
121
+ - 未使用の `src/rendering/slides.js` を削除(marp.jsに統合済み)
122
+
123
+ ### Added
124
+
125
+ - WebSocketテスト7件(接続追跡・watch・broadcast・通知・cleanup・不正入力耐性)
126
+ - レンダリングテスト10件(strikethrough・CJK emphasis・linkify・breaks・mermaid edge cases)
127
+ - テスト総数: 92 → 109
128
+
129
+ ## [0.5.4] - 2026-04-04
130
+
131
+ ### Fixed
132
+
133
+ - 4件の依存関係脆弱性を修正
134
+
8
135
  ## [0.5.3] - 2026-03-29
9
136
 
10
137
  ### Fixed
package/README.md CHANGED
@@ -10,16 +10,18 @@
10
10
  - 📁 左側にフォルダツリー表示(遅延読み込み対応)
11
11
  - 📄 Markdownをリアルタイムレンダリング
12
12
  - 🎬 **Marp完全対応**(公式テーマ・ディレクティブ・数式)
13
+ - 🎤 **Presenter View**(スピーカーノート別ウィンドウ・自動保存・タイマー) — `P` キーで起動
13
14
  - 🔄 ファイル更新時に自動リロード(WebSocket)
14
15
  - 🎨 シンタックスハイライト(highlight.js)
15
16
  - 📊 Mermaid図のレンダリング
16
17
  - 🌙 ダーク/ライトテーマ切り替え
17
18
  - ✏️ インラインエディタ(Cmd+E)
18
19
  - ✅ タスクリスト(チェックボックス)対応
19
- - 📥 PDF出力(Cmd+P)
20
+ - 📥 PDF出力(Cmd+P / CLI convert
21
+ - 🎛️ PDF用CSS・PDF options指定(CLI / Web UI)
20
22
  - 🎬 動画/音声ストリーミング再生(Range Request対応)
21
23
  - 📤 ファイルアップロード(ドラッグ&ドロップ)
22
- - 🔒 セキュリティ強化(パストラバーサル防止)
24
+ - 🔒 セキュリティ強化(パストラバーサル防止 + ETag 楽観ロック + CSRF 防御)
23
25
 
24
26
  ## Installation
25
27
 
@@ -59,13 +61,59 @@ mdv -k 12345
59
61
  mdv -k -a
60
62
 
61
63
  # PDFに変換
62
- mdv --pdf slide.md
63
- mdv --pdf slide.md -o output.pdf
64
+ mdv convert -i report.md -o report.pdf
65
+
66
+ # PDFに変換(CSSとPDF optionsを指定)
67
+ mdv convert \
68
+ -i report.md \
69
+ -o report.pdf \
70
+ -s ./src/styles/report.example.css \
71
+ --pdf-options ./src/styles/report.pdf-options.example.json
64
72
 
65
73
  # バージョン表示
66
74
  mdv -v
67
75
  ```
68
76
 
77
+ ## PDF Export
78
+
79
+ Markdown ファイルは CLI または Web UI から PDF に変換できます。
80
+
81
+ ### CLI
82
+
83
+ ```bash
84
+ mdv convert -i input.md -o output.pdf
85
+ ```
86
+
87
+ CSS を指定する場合は `-s` に CSS ファイルパスを渡します。
88
+
89
+ ```bash
90
+ mdv convert \
91
+ -i input.md \
92
+ -o output.pdf \
93
+ -s ./src/styles/report.example.css
94
+ ```
95
+
96
+ `printBackground` や余白などの PDF 生成オプションは、CSS と分離して JSON ファイルで指定できます。
97
+
98
+ ```bash
99
+ mdv convert \
100
+ -i input.md \
101
+ -o output.pdf \
102
+ -s ./src/styles/report.example.css \
103
+ --pdf-options ./src/styles/report.pdf-options.example.json
104
+ ```
105
+
106
+ `src/styles/report.example.css` と `src/styles/report.pdf-options.example.json` はサンプルです。必要に応じて任意の CSS / JSON ファイルを指定してください。
107
+
108
+ ### Web UI
109
+
110
+ ビューア上部の `Style` から以下を指定できます。
111
+
112
+ - CSS ファイルパス
113
+ - PDF options JSON ファイルパス
114
+
115
+ CSS は Markdown プレビューにも反映されます。指定を解除する場合は `Clear` を押してください。
116
+
69
117
  ### ポート自動増分
70
118
 
71
119
  ポートが使用中の場合、自動的に次のポート番号を試します。
@@ -118,6 +166,8 @@ paginate: true
118
166
 
119
167
  内容...
120
168
 
169
+ <!-- スピーカーノート (Presenter View で表示・編集できます) -->
170
+
121
171
  ---
122
172
 
123
173
  # 次のスライド
@@ -129,9 +179,55 @@ paginate: true
129
179
  ### サポートされるMarp機能
130
180
 
131
181
  - **テーマ**: default, gaia, uncover
132
- - **ディレクティブ**: paginate, header, footer, backgroundColor, etc.
182
+ - **ディレクティブ**: paginate, header, footer, backgroundColor, lang, headingDivider, etc.
183
+ - **headingDivider**: scalar (`headingDivider: 2`) / inline-array (`[1, 2]`) / block-array 全形式
184
+ - **スライド区切り**: `---` / `***` / `___` (CommonMark thematic break 全形式)
133
185
  - **画像構文**: `![bg]`, `![w:100px]`, `![bg left]`
134
186
  - **数式**: KaTeX対応(インライン `$...$`、ブロック `$$...$$`)
187
+ - **スピーカーノート**: HTML コメント (`<!-- ... -->`) で記述
188
+
189
+ ## Presenter View
190
+
191
+ Marp ファイルを開いた状態で **`P` キー** を押すと、別ウィンドウで登壇者ビューが起動します。
192
+
193
+ ### 機能
194
+
195
+ - **3 ペインレイアウト**: 現在のスライド (大) / 次のスライド (小) / スピーカーノート
196
+ - **経過タイマー**: 上部に MM:SS 表示、Reset ボタンで 0 にリセット
197
+ - **ノート編集 → 自動保存**: ノートパネルをクリックして編集 → 800ms デバウンスで markdown ソースのコメントを書き戻し
198
+ - **キーボードナビ**: ← / → でスライド移動、メイン画面と双方向同期
199
+ - **レイアウト調整**: ペイン境界をドラッグで自由に変更、ダブルクリックでデフォルト復元 (localStorage 永続化)
200
+ - **Multi-note Guard**: 1 スライドに複数のノートコメントがある場合は自動保存を無効化(先頭ノート消失防止)
201
+ - **STALE 検出**: 外部エディタによる変更を ETag 楽観ロックで検出、編集中テキストを localStorage に自動退避
202
+
203
+ ### スピーカーノートの書き方
204
+
205
+ ```markdown
206
+ # スライドタイトル
207
+
208
+ スライドの本文
209
+
210
+ <!-- ここがスピーカーノート。Presenter View で編集すると
211
+ このコメントが書き換わります。 -->
212
+ ```
213
+
214
+ 複数行のノートも OK:
215
+
216
+ ```markdown
217
+ <!--
218
+ - ポイント 1: 〜を強調する
219
+ - ポイント 2: ここで質問を投げかける
220
+ - 想定時間: 2 分
221
+ -->
222
+ ```
223
+
224
+ ### Presenter View ショートカット
225
+
226
+ | キー | 動作 |
227
+ |---|---|
228
+ | `← / →` | スライド移動 |
229
+ | `Space / PageDown` | 次のスライド |
230
+ | `Home / End` | 最初 / 最後のスライド |
135
231
 
136
232
  ## Keyboard Shortcuts
137
233
 
@@ -143,6 +239,10 @@ paginate: true
143
239
  | Cmd/Ctrl + P | PDF出力 |
144
240
  | Cmd/Ctrl + W | タブを閉じる |
145
241
  | ← / → | スライド移動(Marp時) |
242
+ | F | フルスクリーン切替(Marp時) |
243
+ | N | ナビバー表示切替(Marp時) |
244
+ | **P** | **Presenter View 起動(Marp時)** |
245
+ | Esc | フルスクリーン解除 |
146
246
  | F2 | ファイル名変更 |
147
247
  | Delete | ファイル削除 |
148
248
 
@@ -150,7 +250,7 @@ paginate: true
150
250
 
151
251
  | Endpoint | Method | Description |
152
252
  |----------|--------|-------------|
153
- | `/api/file` | GET | ファイル内容取得 |
253
+ | `/api/file` | GET | ファイル内容取得 (Marp 時は etag/notes/notesMultiplicity も同梱) |
154
254
  | `/api/file` | POST | ファイル保存 |
155
255
  | `/api/file` | DELETE | ファイル/ディレクトリ削除 |
156
256
  | `/api/tree` | GET | ファイルツリー取得 |
@@ -159,7 +259,12 @@ paginate: true
159
259
  | `/api/move` | POST | ファイル移動/リネーム |
160
260
  | `/api/download` | GET | ファイルダウンロード |
161
261
  | `/api/upload` | POST | ファイルアップロード |
262
+ | `/api/pdf/export` | POST | PDF出力 |
162
263
  | `/api/info` | GET | サーバー情報 |
264
+ | `/api/marp/decks/:path` | GET | Marp デッキ情報取得 (etag, notes, notesMultiplicity) |
265
+ | `/api/marp/decks/:path/slides/:N/note` | PUT | スピーカーノート更新 (`If-Match` 必須、ETag 楽観ロック) |
266
+
267
+ `/api/marp/decks/*` は Origin / Sec-Fetch-Site / Content-Type を厳密に検証し、cross-origin / cross-site / non-JSON リクエストは `403 ORIGIN_REJECTED` または `415 UNSUPPORTED_MEDIA_TYPE` で拒否します(CSRF / DNS rebinding 防御)。
163
268
 
164
269
  ## Tech Stack
165
270
 
@@ -192,28 +297,54 @@ npm test
192
297
 
193
298
  ```
194
299
  mdv/
195
- ├── bin/mdv.js # CLI entry point
300
+ ├── bin/mdv.js # CLI entry point
196
301
  ├── src/
197
- │ ├── server.js # Express server setup
198
- │ ├── watcher.js # File watching (chokidar)
302
+ │ ├── server.js # Express server setup
303
+ │ ├── watcher.js # File watching (chokidar)
304
+ │ ├── websocket.js # WebSocket setup
199
305
  │ ├── api/
200
- │ │ ├── file.js # File operations API
201
- │ │ ├── tree.js # File tree API
202
- │ │ └── upload.js # Upload API
306
+ │ │ ├── file.js # File operations API
307
+ │ │ ├── pdf.js # PDF export API
308
+ │ │ ├── tree.js # File tree API
309
+ │ │ ├── upload.js # Upload API
310
+ │ │ ├── marpNote.js # Marp note autosave routes (orchestration)
311
+ │ │ └── marpNote/
312
+ │ │ ├── guards.js # Origin / Host / Content-Type / If-Match guards
313
+ │ │ ├── readDeck.js # Path-safe deck reader (O_NOFOLLOW + realpath)
314
+ │ │ ├── handleGet.js # GET /api/marp/decks/:path
315
+ │ │ └── handlePut.js # PUT /api/marp/decks/:path/slides/:N/note
203
316
  │ ├── rendering/
204
- │ │ ├── index.js # Rendering entry
205
- │ │ ├── markdown.js # Markdown rendering
206
- │ │ └── marp.js # Marp rendering
317
+ │ │ ├── index.js # Rendering entry
318
+ │ │ ├── markdown.js # Markdown rendering
319
+ │ │ ├── marp.js # Marp rendering (delegates to adapter)
320
+ │ │ ├── marpitAdapter.js # Marpit token adapter (SSOT)
321
+ │ │ └── marpNoteWriter.js # Pure-function note splice
322
+ │ ├── concurrency/
323
+ │ │ └── pathLock.js # Promise-chain mutex (per-path serialization)
207
324
  │ ├── utils/
208
- │ │ ├── fileTypes.js # File type detection
209
- │ │ └── path.js # Path security utilities
210
- └── static/ # Frontend files
211
- ├── index.html
212
- ├── app.js
213
- └── styles.css
325
+ │ │ ├── errors.js # Error codes / status mapping (SSOT)
326
+ │ │ ├── etag.js # sha256 ETag (SSOT)
327
+ │ ├── lineMath.js # BOM / CRLF / line ↔ byte conversion
328
+ ├── atomicWrite.js # Atomic file write (O_EXCL + EXDEV fallback)
329
+ ├── fileTypes.js # File type detection
330
+ └── path.js # Path security (validatePath / validatePathReal)
331
+ │ ├── static/ # Frontend files
332
+ │ │ ├── index.html
333
+ │ │ ├── app.js
334
+ │ │ ├── presenter.html # Presenter View (3-pane + autosave)
335
+ │ │ ├── styles.css
336
+ │ │ └── lib/
337
+ │ │ ├── apiClient.js # HTTP client wrapper
338
+ │ │ ├── presenterChannel.js # BroadcastChannel SSOT
339
+ │ │ ├── saveQueue.js # Per-deck save queue + per-slide coalesce
340
+ │ │ └── tabRegistry.js # Tab life-cycle hooks
341
+ │ └── styles/
342
+ │ ├── index.js
343
+ │ ├── report.example.css
344
+ │ └── report.pdf-options.example.json
214
345
  ├── scripts/
215
- │ └── setup-macos-app.sh # macOS app setup
216
- └── tests/ # Test files
346
+ │ └── setup-macos-app.sh # macOS app setup
347
+ └── tests/ # Test files (236 件、全 PASS)
217
348
  ```
218
349
 
219
350
  ## Requirements