mdv-live 0.5.8 → 0.5.10
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 +56 -0
- package/package.json +7 -7
- package/src/api/pdf.js +52 -66
- package/src/static/app.js +4 -119
- package/src/static/index.html +0 -20
- package/src/static/styles.css +0 -34
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,62 @@ 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.10] - 2026-05-09
|
|
9
|
+
|
|
10
|
+
### Fixed (UX revert)
|
|
11
|
+
|
|
12
|
+
- **Markdown PDF ボタンを OS 印刷ダイアログに戻す** (本来の UX 復元):
|
|
13
|
+
- 0.5.9 (実体は 2026-01-31 `e5526f9` から) で plain Markdown も server-side
|
|
14
|
+
md-to-pdf 経由の PDF DL に切り替わっていたが、本来の UX は `Cmd+P` 相当の
|
|
15
|
+
OS 印刷ダイアログ (`window.print()`) で「PDF として保存」を選ぶフロー
|
|
16
|
+
- `src/static/app.js`: `print()` の markdown 分岐を削除 (`else` 分岐の
|
|
17
|
+
`browserPrint()` に落ちる)
|
|
18
|
+
- Marp / HTML preview の挙動は変更なし (server-side marp-cli を維持)
|
|
19
|
+
|
|
20
|
+
### Fixed (codex review)
|
|
21
|
+
|
|
22
|
+
- `/api/pdf/export` の `fs.readFile` を `try/catch` 外で実行していた問題を修正
|
|
23
|
+
(directory 指定や読み取り不可ファイルで Express デフォルトエラーに落ちて
|
|
24
|
+
controlled JSON が返らなかった)。stat による file 判定を追加
|
|
25
|
+
|
|
26
|
+
### Removed
|
|
27
|
+
|
|
28
|
+
- `PdfStyleManager` UI モジュール (markdown が server PDF を使わなくなったため
|
|
29
|
+
orphan)、`pdfStyleToggle` / `pdfStylePanel` HTML、関連 CSS、`normalizeUserPath`
|
|
30
|
+
helper、`pdf-style-preview` クラス
|
|
31
|
+
- `md-to-pdf` runtime dependency。web UI で使わなくなったため削除。
|
|
32
|
+
`bin/mdv.js convert` は元々 `npx md-to-pdf` 経由で 0.5.9 以前と同じ挙動
|
|
33
|
+
- `/api/pdf/export` の markdown 分岐 (Marp 専用エンドポイントに整理)。
|
|
34
|
+
非 Marp ファイルは 415 で拒否
|
|
35
|
+
|
|
36
|
+
### Tests
|
|
37
|
+
|
|
38
|
+
- 241 → **242 件 (+1)**、全 PASS
|
|
39
|
+
- 追加: directory path → 404 controlled JSON (codex round 3 regression)
|
|
40
|
+
- 変更: plain markdown PDF テスト → 415 テストに置換
|
|
41
|
+
|
|
42
|
+
## [0.5.9] - 2026-05-09
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- **PDF export hang for plain Markdown** (regression since 2026-01-31 `e5526f9`):
|
|
47
|
+
- 原因 1: `md-to-pdf` が `package.json` に未宣言だったため `npx md-to-pdf` 経路に
|
|
48
|
+
依存。npx キャッシュ / TTY / レジストリ状況により挙動が不安定
|
|
49
|
+
- 原因 2: `child_process.execFile` は `stdio` オプションを受け付けない (Node 仕様)。
|
|
50
|
+
`md-to-pdf` 内部の `get-stdin` が EOF を待ち続け 180s SIGTERM していた
|
|
51
|
+
- 直し方: `md-to-pdf` を `dependencies` に追加 / `npx` 経由をやめて
|
|
52
|
+
`node_modules/.bin/md-to-pdf` を直接 spawn / `stdio: ['ignore', ...]` で stdin 即 EOF
|
|
53
|
+
- Marp 経路 (`marp-cli`) も同じ helper (`runPdfTool`) に統一して防御
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
|
|
57
|
+
- `tests/test-pdf-export.js`: PDF export 経路の自動テスト 5 件 (400 / 403 / 404 / plain
|
|
58
|
+
PDF / Marp PDF)。リグレッション再発防止
|
|
59
|
+
|
|
60
|
+
### Tests
|
|
61
|
+
|
|
62
|
+
- 236 → **241 件 (+5)**、全 PASS
|
|
63
|
+
|
|
8
64
|
## [0.5.8] - 2026-05-08
|
|
9
65
|
|
|
10
66
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mdv-live",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.10",
|
|
4
4
|
"description": "Markdown Viewer - File tree + Live preview + Marp support + Hot reload",
|
|
5
5
|
"main": "src/server.js",
|
|
6
6
|
"bin": {
|
|
@@ -45,16 +45,16 @@
|
|
|
45
45
|
"LICENSE"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"
|
|
49
|
-
"ws": "^8.18.0",
|
|
48
|
+
"@marp-team/marp-core": "^4.0.0",
|
|
50
49
|
"chokidar": "^4.0.3",
|
|
50
|
+
"express": "^4.21.2",
|
|
51
|
+
"highlight.js": "^11.10.0",
|
|
51
52
|
"markdown-it": "^14.1.0",
|
|
52
53
|
"markdown-it-task-lists": "^2.1.1",
|
|
53
|
-
"@marp-team/marp-core": "^4.0.0",
|
|
54
|
-
"highlight.js": "^11.10.0",
|
|
55
|
-
"open": "^10.1.0",
|
|
56
54
|
"mime-types": "^2.1.35",
|
|
57
|
-
"multer": "^1.4.5-lts.1"
|
|
55
|
+
"multer": "^1.4.5-lts.1",
|
|
56
|
+
"open": "^10.1.0",
|
|
57
|
+
"ws": "^8.18.0"
|
|
58
58
|
},
|
|
59
59
|
"optionalDependencies": {
|
|
60
60
|
"@marp-team/marp-cli": "^4.0.3"
|
package/src/api/pdf.js
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { promisify } from 'util';
|
|
1
|
+
import { spawn } from 'child_process';
|
|
3
2
|
import fs from 'fs/promises';
|
|
4
3
|
import path from 'path';
|
|
5
4
|
import { fileURLToPath } from 'url';
|
|
6
5
|
import os from 'os';
|
|
7
|
-
import { createRequire } from 'module';
|
|
8
6
|
import { isMarp } from '../rendering/markdown.js';
|
|
9
|
-
import { validatePath
|
|
10
|
-
import { resolvePdfOptions } from '../styles/index.js';
|
|
7
|
+
import { validatePath } from '../utils/path.js';
|
|
11
8
|
|
|
12
|
-
const execFileAsync = promisify(execFile);
|
|
13
|
-
const require = createRequire(import.meta.url);
|
|
14
|
-
const highlightStylesheet = path.resolve(path.dirname(require.resolve('highlight.js')), '..', 'styles', 'atom-one-dark.css');
|
|
15
9
|
const marpBin = path.join(
|
|
16
10
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
17
11
|
'..',
|
|
@@ -23,53 +17,46 @@ const marpBin = path.join(
|
|
|
23
17
|
const PDF_EXPORT_TIMEOUT_MS = 180000;
|
|
24
18
|
|
|
25
19
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* @returns {Promise<string | null>} Absolute path or null.
|
|
20
|
+
* Spawn marp-cli with stdin closed.
|
|
21
|
+
*
|
|
22
|
+
* 注意: execFile は stdio オプションを受け付けない (Node 仕様) ため spawn を使う。
|
|
30
23
|
*/
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
args.push('--stylesheet', highlightStylesheet);
|
|
58
|
-
args.push('--stylesheet', stylesheetPath);
|
|
59
|
-
args.push('--highlight-style', 'atom-one-dark');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
await execFileAsync('npx', args, {
|
|
63
|
-
cwd: path.dirname(inputPath),
|
|
64
|
-
timeout: PDF_EXPORT_TIMEOUT_MS,
|
|
24
|
+
function runMarp(args) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const child = spawn(marpBin, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
27
|
+
let stderr = '';
|
|
28
|
+
child.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
|
|
29
|
+
|
|
30
|
+
const timer = setTimeout(() => {
|
|
31
|
+
child.kill('SIGTERM');
|
|
32
|
+
}, PDF_EXPORT_TIMEOUT_MS);
|
|
33
|
+
|
|
34
|
+
child.on('error', (err) => {
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
reject(err);
|
|
37
|
+
});
|
|
38
|
+
child.on('close', (code, signal) => {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
if (code === 0) {
|
|
41
|
+
resolve();
|
|
42
|
+
} else {
|
|
43
|
+
const err = new Error(`marp exited with code=${code} signal=${signal}`);
|
|
44
|
+
err.code = code;
|
|
45
|
+
err.signal = signal;
|
|
46
|
+
err.stderr = stderr;
|
|
47
|
+
reject(err);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
65
50
|
});
|
|
66
|
-
|
|
67
|
-
const generatedPdf = inputPath.replace(/\.(md|markdown)$/i, '.pdf');
|
|
68
|
-
await fs.rename(generatedPdf, outputPath);
|
|
69
51
|
}
|
|
70
52
|
|
|
71
53
|
/**
|
|
72
|
-
* Setup PDF export routes
|
|
54
|
+
* Setup PDF export routes.
|
|
55
|
+
*
|
|
56
|
+
* Web UI からは Marp ファイルのみがこの経路を使う。通常 Markdown は
|
|
57
|
+
* クライアント側で window.print() (OS 印刷ダイアログ) に流す設計のため、
|
|
58
|
+
* このエンドポイントは Marp 以外を 415 で拒否する。
|
|
59
|
+
*
|
|
73
60
|
* @param {Express} app - Express application
|
|
74
61
|
* @returns {void}
|
|
75
62
|
*/
|
|
@@ -77,7 +64,7 @@ export function setupPdfRoutes(app) {
|
|
|
77
64
|
const { rootDir } = app.locals;
|
|
78
65
|
|
|
79
66
|
app.post('/api/pdf/export', async (req, res) => {
|
|
80
|
-
const { filePath
|
|
67
|
+
const { filePath } = req.body;
|
|
81
68
|
|
|
82
69
|
if (!filePath) {
|
|
83
70
|
return res.status(400).json({ error: 'filePath is required' });
|
|
@@ -88,29 +75,28 @@ export function setupPdfRoutes(app) {
|
|
|
88
75
|
}
|
|
89
76
|
|
|
90
77
|
const fullPath = path.join(rootDir, filePath);
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
await fs.access(fullPath);
|
|
94
|
-
} catch {
|
|
95
|
-
return res.status(404).json({ error: 'File not found' });
|
|
96
|
-
}
|
|
97
|
-
|
|
98
78
|
const baseName = path.basename(fullPath, '.md');
|
|
99
79
|
const outputPath = path.join(os.tmpdir(), `mdv-${Date.now()}-${baseName}.pdf`);
|
|
100
80
|
const outputFileName = `${baseName}.pdf`;
|
|
101
81
|
|
|
102
82
|
try {
|
|
83
|
+
let stat;
|
|
84
|
+
try {
|
|
85
|
+
stat = await fs.stat(fullPath);
|
|
86
|
+
} catch {
|
|
87
|
+
return res.status(404).json({ error: 'File not found' });
|
|
88
|
+
}
|
|
89
|
+
if (!stat.isFile()) {
|
|
90
|
+
return res.status(404).json({ error: 'File not found' });
|
|
91
|
+
}
|
|
92
|
+
|
|
103
93
|
const content = await fs.readFile(fullPath, 'utf-8');
|
|
104
|
-
if (isMarp(content)) {
|
|
105
|
-
|
|
106
|
-
} else {
|
|
107
|
-
const [stylesheetPath, resolvedPdfOptionsPath] = await Promise.all([
|
|
108
|
-
resolveOptionalUserFile(stylePath, rootDir),
|
|
109
|
-
resolveOptionalUserFile(pdfOptionsPath, rootDir),
|
|
110
|
-
]);
|
|
111
|
-
await exportMarkdownPdf(fullPath, outputPath, stylesheetPath, resolvedPdfOptionsPath);
|
|
94
|
+
if (!isMarp(content)) {
|
|
95
|
+
return res.status(415).json({ error: 'Server-side PDF export supports Marp files only. Use the browser print dialog for regular Markdown.' });
|
|
112
96
|
}
|
|
113
97
|
|
|
98
|
+
await runMarp([fullPath, '-o', outputPath, '--html', '--allow-local-files', '--no-stdin']);
|
|
99
|
+
|
|
114
100
|
res.download(outputPath, outputFileName, async (err) => {
|
|
115
101
|
if (err) {
|
|
116
102
|
console.error('Download error:', err);
|
package/src/static/app.js
CHANGED
|
@@ -11,9 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
const STORAGE_KEYS = {
|
|
13
13
|
THEME: 'mdv-theme',
|
|
14
|
-
SIDEBAR_WIDTH: 'mdv-sidebar-width'
|
|
15
|
-
PDF_STYLE_PATH: 'mdv-pdf-style-path',
|
|
16
|
-
PDF_OPTIONS_PATH: 'mdv-pdf-options-path'
|
|
14
|
+
SIDEBAR_WIDTH: 'mdv-sidebar-width'
|
|
17
15
|
};
|
|
18
16
|
|
|
19
17
|
const HLJS_THEMES = {
|
|
@@ -87,9 +85,7 @@
|
|
|
87
85
|
isResizing: false,
|
|
88
86
|
skipScrollRestore: false,
|
|
89
87
|
uploadTargetPath: '',
|
|
90
|
-
rootPath: ''
|
|
91
|
-
pdfStylePath: localStorage.getItem(STORAGE_KEYS.PDF_STYLE_PATH) || '',
|
|
92
|
-
pdfOptionsPath: localStorage.getItem(STORAGE_KEYS.PDF_OPTIONS_PATH) || ''
|
|
88
|
+
rootPath: ''
|
|
93
89
|
};
|
|
94
90
|
|
|
95
91
|
// ============================================================
|
|
@@ -125,12 +121,6 @@
|
|
|
125
121
|
statusText: document.getElementById('statusText'),
|
|
126
122
|
resizeHandle: document.getElementById('resizeHandle'),
|
|
127
123
|
editToggle: document.getElementById('editToggle'),
|
|
128
|
-
pdfStyleToggle: document.getElementById('pdfStyleToggle'),
|
|
129
|
-
pdfStylePanel: document.getElementById('pdfStylePanel'),
|
|
130
|
-
pdfStylePath: document.getElementById('pdfStylePath'),
|
|
131
|
-
pdfOptionsPath: document.getElementById('pdfOptionsPath'),
|
|
132
|
-
pdfStyleApply: document.getElementById('pdfStyleApply'),
|
|
133
|
-
pdfStyleClear: document.getElementById('pdfStyleClear'),
|
|
134
124
|
editLabel: document.getElementById('editLabel'),
|
|
135
125
|
editorStatus: document.getElementById('editorStatus'),
|
|
136
126
|
shutdownBtn: document.getElementById('shutdownBtn'),
|
|
@@ -173,10 +163,6 @@
|
|
|
173
163
|
});
|
|
174
164
|
}
|
|
175
165
|
|
|
176
|
-
function normalizeUserPath(path) {
|
|
177
|
-
return path.trim().replace(/^\/+/, '');
|
|
178
|
-
}
|
|
179
|
-
|
|
180
166
|
async function apiRequest(url, options = {}) {
|
|
181
167
|
const response = await fetch(url, options);
|
|
182
168
|
const data = await response.json();
|
|
@@ -249,101 +235,6 @@
|
|
|
249
235
|
}
|
|
250
236
|
};
|
|
251
237
|
|
|
252
|
-
// ============================================================
|
|
253
|
-
// PDF Style Preview
|
|
254
|
-
// ============================================================
|
|
255
|
-
|
|
256
|
-
const PdfStyleManager = {
|
|
257
|
-
scopedCssId: 'pdf-style-preview-css',
|
|
258
|
-
|
|
259
|
-
init() {
|
|
260
|
-
elements.pdfStylePath.value = state.pdfStylePath;
|
|
261
|
-
elements.pdfOptionsPath.value = state.pdfOptionsPath;
|
|
262
|
-
elements.pdfStyleToggle.addEventListener('click', () => {
|
|
263
|
-
elements.pdfStylePanel.classList.toggle('hidden');
|
|
264
|
-
});
|
|
265
|
-
elements.pdfStyleApply.addEventListener('click', () => this.applyFromInputs());
|
|
266
|
-
elements.pdfStyleClear.addEventListener('click', () => this.clear());
|
|
267
|
-
elements.pdfStylePath.addEventListener('keydown', (event) => {
|
|
268
|
-
if (event.key === 'Enter') this.applyFromInputs();
|
|
269
|
-
});
|
|
270
|
-
elements.pdfOptionsPath.addEventListener('keydown', (event) => {
|
|
271
|
-
if (event.key === 'Enter') this.applyFromInputs();
|
|
272
|
-
});
|
|
273
|
-
this.loadPreviewCss();
|
|
274
|
-
},
|
|
275
|
-
|
|
276
|
-
getExportOptions() {
|
|
277
|
-
return {
|
|
278
|
-
stylePath: normalizeUserPath(state.pdfStylePath),
|
|
279
|
-
pdfOptionsPath: normalizeUserPath(state.pdfOptionsPath)
|
|
280
|
-
};
|
|
281
|
-
},
|
|
282
|
-
|
|
283
|
-
async applyFromInputs() {
|
|
284
|
-
state.pdfStylePath = normalizeUserPath(elements.pdfStylePath.value);
|
|
285
|
-
state.pdfOptionsPath = normalizeUserPath(elements.pdfOptionsPath.value);
|
|
286
|
-
elements.pdfStylePath.value = state.pdfStylePath;
|
|
287
|
-
elements.pdfOptionsPath.value = state.pdfOptionsPath;
|
|
288
|
-
localStorage.setItem(STORAGE_KEYS.PDF_STYLE_PATH, state.pdfStylePath);
|
|
289
|
-
localStorage.setItem(STORAGE_KEYS.PDF_OPTIONS_PATH, state.pdfOptionsPath);
|
|
290
|
-
await this.loadPreviewCss();
|
|
291
|
-
TabManager.renderActive();
|
|
292
|
-
},
|
|
293
|
-
|
|
294
|
-
clear() {
|
|
295
|
-
state.pdfStylePath = '';
|
|
296
|
-
state.pdfOptionsPath = '';
|
|
297
|
-
elements.pdfStylePath.value = '';
|
|
298
|
-
elements.pdfOptionsPath.value = '';
|
|
299
|
-
localStorage.removeItem(STORAGE_KEYS.PDF_STYLE_PATH);
|
|
300
|
-
localStorage.removeItem(STORAGE_KEYS.PDF_OPTIONS_PATH);
|
|
301
|
-
const oldStyle = document.getElementById(this.scopedCssId);
|
|
302
|
-
if (oldStyle) oldStyle.remove();
|
|
303
|
-
TabManager.renderActive();
|
|
304
|
-
elements.statusText.textContent = 'PDF style cleared';
|
|
305
|
-
setTimeout(() => { elements.statusText.textContent = 'Connected'; }, 1600);
|
|
306
|
-
},
|
|
307
|
-
|
|
308
|
-
async loadPreviewCss() {
|
|
309
|
-
const oldStyle = document.getElementById(this.scopedCssId);
|
|
310
|
-
if (oldStyle) oldStyle.remove();
|
|
311
|
-
if (!state.pdfStylePath) return;
|
|
312
|
-
|
|
313
|
-
try {
|
|
314
|
-
const response = await fetch(`/raw/${state.pdfStylePath}`);
|
|
315
|
-
if (!response.ok) throw new Error('CSS file not found');
|
|
316
|
-
const cssText = await response.text();
|
|
317
|
-
const style = document.createElement('style');
|
|
318
|
-
style.id = this.scopedCssId;
|
|
319
|
-
style.textContent = this.scopeCss(cssText);
|
|
320
|
-
document.head.appendChild(style);
|
|
321
|
-
elements.statusText.textContent = 'PDF style applied';
|
|
322
|
-
setTimeout(() => { elements.statusText.textContent = 'Connected'; }, 1600);
|
|
323
|
-
} catch (error) {
|
|
324
|
-
console.error('PDF style preview error:', error);
|
|
325
|
-
elements.statusText.textContent = 'PDF style failed';
|
|
326
|
-
setTimeout(() => { elements.statusText.textContent = 'Connected'; }, 2500);
|
|
327
|
-
}
|
|
328
|
-
},
|
|
329
|
-
|
|
330
|
-
scopeCss(cssText) {
|
|
331
|
-
const scope = '.markdown-body.pdf-style-preview';
|
|
332
|
-
const withoutComments = cssText.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
333
|
-
return withoutComments.replace(/([^{}]+)\{/g, (match, selectorText) => {
|
|
334
|
-
const selectors = selectorText.trim();
|
|
335
|
-
if (!selectors || selectors.startsWith('@')) return match;
|
|
336
|
-
const scopedSelectors = selectors.split(',').map((selector) => {
|
|
337
|
-
const trimmed = selector.trim();
|
|
338
|
-
if (trimmed === ':root' || trimmed === 'body') return scope;
|
|
339
|
-
if (trimmed.startsWith(scope)) return trimmed;
|
|
340
|
-
return `${scope} ${trimmed}`;
|
|
341
|
-
});
|
|
342
|
-
return `${scopedSelectors.join(', ')} {`;
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
};
|
|
346
|
-
|
|
347
238
|
// ============================================================
|
|
348
239
|
// Sidebar Management
|
|
349
240
|
// ============================================================
|
|
@@ -880,9 +771,7 @@
|
|
|
880
771
|
render(htmlContent, fileType) {
|
|
881
772
|
const containerClass = fileType === 'code'
|
|
882
773
|
? 'markdown-body code-view-container'
|
|
883
|
-
:
|
|
884
|
-
? 'markdown-body pdf-style-preview'
|
|
885
|
-
: 'markdown-body';
|
|
774
|
+
: 'markdown-body';
|
|
886
775
|
elements.content.innerHTML = `<div class="${containerClass}">${htmlContent}</div>`;
|
|
887
776
|
|
|
888
777
|
elements.content.querySelectorAll('pre code').forEach(block => {
|
|
@@ -1709,8 +1598,6 @@
|
|
|
1709
1598
|
await this.exportPdf(tab.path);
|
|
1710
1599
|
} else if (this.isHtmlPreview()) {
|
|
1711
1600
|
this.printHtmlPreview(tab.name);
|
|
1712
|
-
} else if (tab.fileType === 'markdown') {
|
|
1713
|
-
await this.exportPdf(tab.path);
|
|
1714
1601
|
} else {
|
|
1715
1602
|
this.browserPrint(tab.name);
|
|
1716
1603
|
}
|
|
@@ -1738,9 +1625,8 @@
|
|
|
1738
1625
|
|
|
1739
1626
|
try {
|
|
1740
1627
|
statusText.textContent = 'Generating PDF...';
|
|
1741
|
-
const exportOptions = PdfStyleManager.getExportOptions();
|
|
1742
1628
|
|
|
1743
|
-
const response = await MDVApi.exportPdf({ filePath
|
|
1629
|
+
const response = await MDVApi.exportPdf({ filePath });
|
|
1744
1630
|
|
|
1745
1631
|
if (!response.ok) {
|
|
1746
1632
|
const error = await response.json();
|
|
@@ -2366,7 +2252,6 @@
|
|
|
2366
2252
|
async function init() {
|
|
2367
2253
|
// Initialize all managers
|
|
2368
2254
|
ThemeManager.init();
|
|
2369
|
-
PdfStyleManager.init();
|
|
2370
2255
|
SidebarManager.init();
|
|
2371
2256
|
ResizeHandler.init();
|
|
2372
2257
|
EditorManager.init();
|
package/src/static/index.html
CHANGED
|
@@ -95,13 +95,6 @@
|
|
|
95
95
|
PDF
|
|
96
96
|
</button>
|
|
97
97
|
|
|
98
|
-
<button class="toolbar-btn" id="pdfStyleToggle" title="PDF style settings" aria-label="PDF style settings">
|
|
99
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
|
100
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
|
|
101
|
-
</svg>
|
|
102
|
-
Style
|
|
103
|
-
</button>
|
|
104
|
-
|
|
105
98
|
<div class="toolbar-spacer"></div>
|
|
106
99
|
|
|
107
100
|
<span class="editor-status" id="editorStatus" style="display: none;">Ready</span>
|
|
@@ -117,19 +110,6 @@
|
|
|
117
110
|
</div>
|
|
118
111
|
</div>
|
|
119
112
|
|
|
120
|
-
<div class="pdf-style-panel hidden" id="pdfStylePanel">
|
|
121
|
-
<label>
|
|
122
|
-
<span>CSS</span>
|
|
123
|
-
<input type="text" id="pdfStylePath" placeholder="src/styles/report.example.css">
|
|
124
|
-
</label>
|
|
125
|
-
<label>
|
|
126
|
-
<span>PDF options</span>
|
|
127
|
-
<input type="text" id="pdfOptionsPath" placeholder="src/styles/report.pdf-options.example.json">
|
|
128
|
-
</label>
|
|
129
|
-
<button class="toolbar-btn" id="pdfStyleApply">Apply</button>
|
|
130
|
-
<button class="toolbar-btn" id="pdfStyleClear">Clear</button>
|
|
131
|
-
</div>
|
|
132
|
-
|
|
133
113
|
<!-- Tab bar -->
|
|
134
114
|
<div class="tab-bar" id="tabBar" role="tablist" aria-label="Open files"></div>
|
|
135
115
|
|
package/src/static/styles.css
CHANGED
|
@@ -241,40 +241,6 @@ body {
|
|
|
241
241
|
|
|
242
242
|
.toolbar-spacer { flex: 1; }
|
|
243
243
|
|
|
244
|
-
.pdf-style-panel {
|
|
245
|
-
display: flex;
|
|
246
|
-
align-items: center;
|
|
247
|
-
gap: 10px;
|
|
248
|
-
padding: 8px 16px;
|
|
249
|
-
border-bottom: 1px solid var(--border);
|
|
250
|
-
background: var(--bg-secondary);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
.pdf-style-panel label {
|
|
254
|
-
display: flex;
|
|
255
|
-
align-items: center;
|
|
256
|
-
gap: 6px;
|
|
257
|
-
min-width: 0;
|
|
258
|
-
color: var(--text-secondary);
|
|
259
|
-
font-size: 12px;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
.pdf-style-panel input {
|
|
263
|
-
width: min(34vw, 360px);
|
|
264
|
-
min-width: 160px;
|
|
265
|
-
padding: 6px 8px;
|
|
266
|
-
background: var(--bg-primary);
|
|
267
|
-
border: 1px solid var(--border);
|
|
268
|
-
border-radius: 6px;
|
|
269
|
-
color: var(--text-primary);
|
|
270
|
-
font-size: 12px;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
.pdf-style-panel input:focus {
|
|
274
|
-
border-color: var(--accent);
|
|
275
|
-
outline: none;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
244
|
.status {
|
|
279
245
|
display: flex;
|
|
280
246
|
align-items: center;
|