md-lv 1.0.2 → 1.0.3
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/bin/mdlv.js +5 -5
- package/package.json +1 -1
- package/src/routes/assets.js +65 -0
- package/src/routes/directory.js +2 -1
- package/src/routes/markdown.js +2 -1
- package/src/routes/raw.js +2 -1
- package/src/server.js +5 -0
package/bin/mdlv.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { run } from '../src/cli.js';
|
|
4
|
-
|
|
5
|
-
run(process.argv);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { run } from '../src/cli.js';
|
|
4
|
+
|
|
5
|
+
run(process.argv);
|
package/package.json
CHANGED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { validatePath } from '../utils/path.js';
|
|
5
|
+
|
|
6
|
+
const router = Router();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 画像ファイルの MIME タイプマップ
|
|
10
|
+
*/
|
|
11
|
+
const IMAGE_MIME_TYPES = {
|
|
12
|
+
'.png': 'image/png',
|
|
13
|
+
'.jpg': 'image/jpeg',
|
|
14
|
+
'.jpeg': 'image/jpeg',
|
|
15
|
+
'.gif': 'image/gif',
|
|
16
|
+
'.svg': 'image/svg+xml',
|
|
17
|
+
'.webp': 'image/webp',
|
|
18
|
+
'.ico': 'image/x-icon',
|
|
19
|
+
'.bmp': 'image/bmp',
|
|
20
|
+
'.avif': 'image/avif'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 画像ファイル配信ルート
|
|
25
|
+
* Markdownから相対パスで参照される画像を配信
|
|
26
|
+
*/
|
|
27
|
+
router.get(/^\/.*/, async (req, res, next) => {
|
|
28
|
+
try {
|
|
29
|
+
const requestPath = req.path;
|
|
30
|
+
const ext = path.extname(requestPath).toLowerCase();
|
|
31
|
+
|
|
32
|
+
// 画像ファイル以外は次のハンドラへ
|
|
33
|
+
const mimeType = IMAGE_MIME_TYPES[ext];
|
|
34
|
+
if (!mimeType) {
|
|
35
|
+
return next();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const docRoot = req.app.get('docRoot');
|
|
39
|
+
let filePath;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
filePath = validatePath(requestPath, docRoot);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error.code === 'ETRAVERSAL') {
|
|
45
|
+
return res.status(403).send('Access denied');
|
|
46
|
+
}
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ファイル存在確認
|
|
51
|
+
try {
|
|
52
|
+
await fs.access(filePath);
|
|
53
|
+
} catch {
|
|
54
|
+
return next(); // 存在しない場合は次のハンドラへ
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 画像ファイルを配信
|
|
58
|
+
const imageData = await fs.readFile(filePath);
|
|
59
|
+
res.type(mimeType).send(imageData);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
next(error);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export default router;
|
package/src/routes/directory.js
CHANGED
|
@@ -94,8 +94,9 @@ router.get(/^\/.*/, async (req, res, next) => {
|
|
|
94
94
|
? `<li class="folder parent"><a href="${path.dirname(requestPath)}/">..</a><span class="size">-</span></li>\n`
|
|
95
95
|
: '';
|
|
96
96
|
|
|
97
|
+
const docRootName = req.app.get('docRootName');
|
|
97
98
|
const html = renderTemplate('page', {
|
|
98
|
-
title:
|
|
99
|
+
title: `${requestPath === '/' ? docRootName : requestPath} - ${docRootName}`,
|
|
99
100
|
content: `<h1>Index of ${escapeHtml(requestPath)}</h1>
|
|
100
101
|
<ul class="directory-listing">${parentLink}${listHtml}</ul>`,
|
|
101
102
|
breadcrumbs: generateBreadcrumbs(requestPath)
|
package/src/routes/markdown.js
CHANGED
|
@@ -31,8 +31,9 @@ router.get(/.*\.md$/i, async (req, res, next) => {
|
|
|
31
31
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
32
32
|
|
|
33
33
|
// テンプレートにMarkdownを埋め込み(クライアント側でレンダリング)
|
|
34
|
+
const docRootName = req.app.get('docRootName');
|
|
34
35
|
const html = renderTemplate('page', {
|
|
35
|
-
title: path.basename(filePath)
|
|
36
|
+
title: `${path.basename(filePath)} - ${docRootName}`,
|
|
36
37
|
content: `<div id="markdown-source" style="display:none">${escapeHtml(content)}</div>
|
|
37
38
|
<div id="markdown-rendered"></div>`,
|
|
38
39
|
breadcrumbs: generateBreadcrumbs(requestPath)
|
package/src/routes/raw.js
CHANGED
|
@@ -58,8 +58,9 @@ router.get(/^\/.*/, async (req, res, next) => {
|
|
|
58
58
|
const language = getLanguageFromExtension(ext);
|
|
59
59
|
const fileName = path.basename(filePath);
|
|
60
60
|
|
|
61
|
+
const docRootName = req.app.get('docRootName');
|
|
61
62
|
const html = renderTemplate('page', {
|
|
62
|
-
title: fileName
|
|
63
|
+
title: `${fileName} - ${docRootName}`,
|
|
63
64
|
content: `<h1>${escapeHtml(fileName)}</h1>
|
|
64
65
|
<pre><code class="language-${language}">${escapeHtml(content)}</code></pre>`,
|
|
65
66
|
breadcrumbs: generateBreadcrumbs(requestPath)
|
package/src/server.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import markdownRouter from './routes/markdown.js';
|
|
5
5
|
import rawRouter from './routes/raw.js';
|
|
6
|
+
import assetsRouter from './routes/assets.js';
|
|
6
7
|
import directoryRouter from './routes/directory.js';
|
|
7
8
|
import apiRouter from './routes/api.js';
|
|
8
9
|
import { securityHeaders, pathTraversalGuard } from './middleware/security.js';
|
|
@@ -23,6 +24,7 @@ export function createServer(options = {}) {
|
|
|
23
24
|
// ドキュメントルートの設定
|
|
24
25
|
const docRoot = path.resolve(options.dir || '.');
|
|
25
26
|
app.set('docRoot', docRoot);
|
|
27
|
+
app.set('docRootName', path.basename(docRoot));
|
|
26
28
|
|
|
27
29
|
// セキュリティミドルウェア(最初に登録)
|
|
28
30
|
app.use(securityHeaders);
|
|
@@ -50,6 +52,9 @@ export function createServer(options = {}) {
|
|
|
50
52
|
// Raw コード表示ルート(.js, .py 等)
|
|
51
53
|
app.use(rawRouter);
|
|
52
54
|
|
|
55
|
+
// 画像ファイル配信ルート
|
|
56
|
+
app.use(assetsRouter);
|
|
57
|
+
|
|
53
58
|
// ディレクトリ一覧ルート
|
|
54
59
|
app.use(directoryRouter);
|
|
55
60
|
|