mdv-live 0.3.9 → 0.4.0
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/mdv.js +11 -4
- package/package.json +1 -1
- package/scripts/setup-macos-app.sh +2 -2
- package/src/server.js +3 -2
- package/src/static/app.js +66 -4
- package/src/watcher.js +6 -1
package/bin/mdv.js
CHANGED
|
@@ -23,6 +23,10 @@ const OPTIONS = {
|
|
|
23
23
|
type: 'string',
|
|
24
24
|
short: 'p',
|
|
25
25
|
},
|
|
26
|
+
depth: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
short: 'd',
|
|
29
|
+
},
|
|
26
30
|
'no-browser': {
|
|
27
31
|
type: 'boolean',
|
|
28
32
|
default: false
|
|
@@ -76,6 +80,7 @@ Arguments:
|
|
|
76
80
|
|
|
77
81
|
Server Options:
|
|
78
82
|
-p, --port <n> Server port (default: ${DEFAULT_PORT})
|
|
83
|
+
-d, --depth <n> Directory watch depth (default: 3, prevents EMFILE errors)
|
|
79
84
|
--no-browser Don't open browser automatically
|
|
80
85
|
|
|
81
86
|
Server Management:
|
|
@@ -382,8 +387,9 @@ async function resolveTargetPath(targetPath) {
|
|
|
382
387
|
* @param {string} targetPath - Target directory or file path
|
|
383
388
|
* @param {number} startPort - Starting port number
|
|
384
389
|
* @param {boolean} openBrowser - Whether to open browser automatically
|
|
390
|
+
* @param {number} depth - Directory watch depth
|
|
385
391
|
*/
|
|
386
|
-
async function startViewer(targetPath, startPort, openBrowser) {
|
|
392
|
+
async function startViewer(targetPath, startPort, openBrowser, depth) {
|
|
387
393
|
const { rootDir, initialFile } = await resolveTargetPath(targetPath);
|
|
388
394
|
|
|
389
395
|
const port = await findAvailablePort(startPort);
|
|
@@ -396,11 +402,11 @@ async function startViewer(targetPath, startPort, openBrowser) {
|
|
|
396
402
|
console.log(`ポート ${startPort} は使用中のため、${port} で起動します`);
|
|
397
403
|
}
|
|
398
404
|
|
|
399
|
-
const mdv = createMdvServer({ rootDir, port });
|
|
405
|
+
const mdv = createMdvServer({ rootDir, port, depth });
|
|
400
406
|
await mdv.start();
|
|
401
407
|
|
|
402
408
|
const url = initialFile
|
|
403
|
-
? `http://localhost:${port}?
|
|
409
|
+
? `http://localhost:${port}?path=${encodeURIComponent(initialFile)}`
|
|
404
410
|
: `http://localhost:${port}`;
|
|
405
411
|
|
|
406
412
|
console.log(`
|
|
@@ -472,9 +478,10 @@ async function main() {
|
|
|
472
478
|
// Default: start viewer
|
|
473
479
|
const targetPath = positionals[0] || '.';
|
|
474
480
|
const port = parseInt(values.port, 10) || DEFAULT_PORT;
|
|
481
|
+
const depth = parseInt(values.depth, 10) || 3;
|
|
475
482
|
const openBrowser = !values['no-browser'];
|
|
476
483
|
|
|
477
|
-
await startViewer(targetPath, port, openBrowser);
|
|
484
|
+
await startViewer(targetPath, port, openBrowser, depth);
|
|
478
485
|
}
|
|
479
486
|
|
|
480
487
|
main().catch(err => {
|
package/package.json
CHANGED
|
@@ -77,7 +77,7 @@ for i in {1..25}; do
|
|
|
77
77
|
echo "Found port: $PORT"
|
|
78
78
|
echo "Opening browser..."
|
|
79
79
|
# Use osascript to open URL (works better from AppleScript context)
|
|
80
|
-
osascript -e "open location \"http://localhost:$PORT?
|
|
80
|
+
osascript -e "open location \"http://localhost:$PORT?path=$FILE_NAME\""
|
|
81
81
|
echo "Done"
|
|
82
82
|
exit 0
|
|
83
83
|
fi
|
|
@@ -85,7 +85,7 @@ done
|
|
|
85
85
|
|
|
86
86
|
echo "Timeout - using fallback"
|
|
87
87
|
# Fallback
|
|
88
|
-
osascript -e "open location \"http://localhost:8642?
|
|
88
|
+
osascript -e "open location \"http://localhost:8642?path=$FILE_NAME\""
|
|
89
89
|
LAUNCHSCRIPT
|
|
90
90
|
|
|
91
91
|
# Replace placeholder with actual path
|
package/src/server.js
CHANGED
|
@@ -47,10 +47,11 @@ function setupApiRoutes(app) {
|
|
|
47
47
|
* @param {Object} options - Server options
|
|
48
48
|
* @param {string} options.rootDir - Root directory to serve
|
|
49
49
|
* @param {number} [options.port=8080] - Port to listen on
|
|
50
|
+
* @param {number} [options.depth=3] - Directory watch depth (prevents EMFILE errors)
|
|
50
51
|
* @returns {{ app: express.Application, server: http.Server, watcher: FSWatcher, wss: WebSocketServer, port: number, start: () => Promise<{port: number}>, stop: () => Promise<void> }}
|
|
51
52
|
*/
|
|
52
53
|
export function createMdvServer(options) {
|
|
53
|
-
const { rootDir, port = 8080 } = options;
|
|
54
|
+
const { rootDir, port = 8080, depth = 3 } = options;
|
|
54
55
|
|
|
55
56
|
const app = express();
|
|
56
57
|
const server = createServer(app);
|
|
@@ -68,7 +69,7 @@ export function createMdvServer(options) {
|
|
|
68
69
|
});
|
|
69
70
|
|
|
70
71
|
const wss = setupWebSocket(server);
|
|
71
|
-
const watcher = setupWatcher(app.locals.rootDir, wss);
|
|
72
|
+
const watcher = setupWatcher(app.locals.rootDir, wss, { depth });
|
|
72
73
|
|
|
73
74
|
app.locals.watcher = watcher;
|
|
74
75
|
app.locals.wss = wss;
|
package/src/static/app.js
CHANGED
|
@@ -88,6 +88,21 @@
|
|
|
88
88
|
rootPath: ''
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
+
// ============================================================
|
|
92
|
+
// URL State Management
|
|
93
|
+
// ============================================================
|
|
94
|
+
|
|
95
|
+
function updateUrlPath(path) {
|
|
96
|
+
const url = new URL(window.location);
|
|
97
|
+
if (path) {
|
|
98
|
+
// パスの/をエンコードせずに表示
|
|
99
|
+
url.search = '?path=' + encodeURIComponent(path).replace(/%2F/g, '/');
|
|
100
|
+
} else {
|
|
101
|
+
url.search = '';
|
|
102
|
+
}
|
|
103
|
+
history.replaceState(null, '', url);
|
|
104
|
+
}
|
|
105
|
+
|
|
91
106
|
// ============================================================
|
|
92
107
|
// DOM Elements
|
|
93
108
|
// ============================================================
|
|
@@ -477,6 +492,35 @@
|
|
|
477
492
|
}
|
|
478
493
|
},
|
|
479
494
|
|
|
495
|
+
async expandToPath(filePath) {
|
|
496
|
+
// パスを分割して親フォルダのリストを作成
|
|
497
|
+
const parts = filePath.split('/');
|
|
498
|
+
parts.pop(); // ファイル名を除外
|
|
499
|
+
|
|
500
|
+
let currentPath = '';
|
|
501
|
+
for (const part of parts) {
|
|
502
|
+
currentPath = currentPath ? `${currentPath}/${part}` : part;
|
|
503
|
+
|
|
504
|
+
const item = document.querySelector(`.tree-item[data-path="${currentPath}"]`);
|
|
505
|
+
if (!item) continue;
|
|
506
|
+
|
|
507
|
+
const children = item.querySelector('.tree-children');
|
|
508
|
+
const chevron = item.querySelector('.chevron');
|
|
509
|
+
|
|
510
|
+
if (children && children.classList.contains('collapsed')) {
|
|
511
|
+
// 未読み込みの場合は子要素を取得
|
|
512
|
+
if (item.dataset.loaded !== 'true') {
|
|
513
|
+
await this.expandDirectory(currentPath, children);
|
|
514
|
+
}
|
|
515
|
+
children.classList.remove('collapsed');
|
|
516
|
+
if (chevron) chevron.classList.add('expanded');
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// ファイルをハイライト
|
|
521
|
+
this.updateHighlight();
|
|
522
|
+
},
|
|
523
|
+
|
|
480
524
|
renderFile(item) {
|
|
481
525
|
const iconClass = item.icon ? `icon-${item.icon}` : '';
|
|
482
526
|
const iconSvg = getFileIcon(item.icon);
|
|
@@ -835,7 +879,7 @@
|
|
|
835
879
|
elements.content.innerHTML = `
|
|
836
880
|
<div class="html-preview">
|
|
837
881
|
<iframe src="${htmlUrl}" title="${name}"
|
|
838
|
-
sandbox="allow-scripts allow-same-origin allow-forms">
|
|
882
|
+
sandbox="allow-scripts allow-same-origin allow-forms allow-modals">
|
|
839
883
|
</iframe>
|
|
840
884
|
</div>
|
|
841
885
|
`;
|
|
@@ -935,6 +979,7 @@
|
|
|
935
979
|
this.renderActive();
|
|
936
980
|
WebSocketManager.watchFile(path);
|
|
937
981
|
FileTreeManager.updateHighlight();
|
|
982
|
+
updateUrlPath(path);
|
|
938
983
|
},
|
|
939
984
|
|
|
940
985
|
switch(index) {
|
|
@@ -965,6 +1010,7 @@
|
|
|
965
1010
|
this.renderActive();
|
|
966
1011
|
WebSocketManager.watchFile(state.tabs[index].path);
|
|
967
1012
|
FileTreeManager.updateHighlight();
|
|
1013
|
+
updateUrlPath(state.tabs[index].path);
|
|
968
1014
|
},
|
|
969
1015
|
|
|
970
1016
|
close(index) {
|
|
@@ -975,6 +1021,7 @@
|
|
|
975
1021
|
this.render();
|
|
976
1022
|
ContentRenderer.showWelcome();
|
|
977
1023
|
FileTreeManager.updateHighlight();
|
|
1024
|
+
updateUrlPath(null);
|
|
978
1025
|
return;
|
|
979
1026
|
}
|
|
980
1027
|
|
|
@@ -986,6 +1033,7 @@
|
|
|
986
1033
|
this.render();
|
|
987
1034
|
this.renderActive();
|
|
988
1035
|
FileTreeManager.updateHighlight();
|
|
1036
|
+
updateUrlPath(state.tabs[state.activeTabIndex].path);
|
|
989
1037
|
},
|
|
990
1038
|
|
|
991
1039
|
render() {
|
|
@@ -1271,6 +1319,10 @@
|
|
|
1271
1319
|
return !!elements.content.querySelector('.marpit');
|
|
1272
1320
|
},
|
|
1273
1321
|
|
|
1322
|
+
isHtmlPreview() {
|
|
1323
|
+
return !!elements.content.querySelector('.html-preview iframe');
|
|
1324
|
+
},
|
|
1325
|
+
|
|
1274
1326
|
async print() {
|
|
1275
1327
|
if (state.activeTabIndex < 0) return;
|
|
1276
1328
|
|
|
@@ -1278,6 +1330,8 @@
|
|
|
1278
1330
|
|
|
1279
1331
|
if (this.isMarpPresentation()) {
|
|
1280
1332
|
await this.exportMarpPdf(tab.path);
|
|
1333
|
+
} else if (this.isHtmlPreview()) {
|
|
1334
|
+
this.printHtmlPreview(tab.name);
|
|
1281
1335
|
} else {
|
|
1282
1336
|
this.browserPrint(tab.name);
|
|
1283
1337
|
}
|
|
@@ -1292,6 +1346,13 @@
|
|
|
1292
1346
|
document.title = originalTitle;
|
|
1293
1347
|
},
|
|
1294
1348
|
|
|
1349
|
+
printHtmlPreview(fileName) {
|
|
1350
|
+
const iframe = elements.content.querySelector('.html-preview iframe');
|
|
1351
|
+
if (iframe && iframe.contentWindow) {
|
|
1352
|
+
iframe.contentWindow.print();
|
|
1353
|
+
}
|
|
1354
|
+
},
|
|
1355
|
+
|
|
1295
1356
|
async exportMarpPdf(filePath) {
|
|
1296
1357
|
const statusText = elements.statusText;
|
|
1297
1358
|
const originalStatus = statusText.textContent;
|
|
@@ -1945,9 +2006,10 @@
|
|
|
1945
2006
|
});
|
|
1946
2007
|
window.addEventListener('focus', handleFocusChange);
|
|
1947
2008
|
|
|
1948
|
-
const
|
|
1949
|
-
if (
|
|
1950
|
-
|
|
2009
|
+
const initialPath = new URLSearchParams(window.location.search).get('path');
|
|
2010
|
+
if (initialPath) {
|
|
2011
|
+
await FileTreeManager.expandToPath(initialPath);
|
|
2012
|
+
await TabManager.open(initialPath);
|
|
1951
2013
|
}
|
|
1952
2014
|
}
|
|
1953
2015
|
|
package/src/watcher.js
CHANGED
|
@@ -34,13 +34,18 @@ const TREE_CHANGE_EVENTS = ['add', 'unlink', 'addDir', 'unlinkDir'];
|
|
|
34
34
|
* Setup file watcher
|
|
35
35
|
* @param {string} rootDir - Root directory to watch
|
|
36
36
|
* @param {WebSocketServer} wss - WebSocket server for broadcasting
|
|
37
|
+
* @param {Object} [options] - Watcher options
|
|
38
|
+
* @param {number} [options.depth=3] - Directory depth to watch (prevents EMFILE errors)
|
|
37
39
|
* @returns {FSWatcher} Chokidar watcher instance
|
|
38
40
|
*/
|
|
39
|
-
export function setupWatcher(rootDir, wss) {
|
|
41
|
+
export function setupWatcher(rootDir, wss, options = {}) {
|
|
42
|
+
const { depth = 3 } = options;
|
|
43
|
+
|
|
40
44
|
const watcher = chokidar.watch(rootDir, {
|
|
41
45
|
ignored: IGNORED_PATTERNS,
|
|
42
46
|
persistent: true,
|
|
43
47
|
ignoreInitial: true,
|
|
48
|
+
depth,
|
|
44
49
|
awaitWriteFinish: {
|
|
45
50
|
stabilityThreshold: 100,
|
|
46
51
|
pollInterval: 50
|