vimd 0.4.1 → 0.5.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/README.md +34 -14
- package/dist/cli/commands/dev.d.ts +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +57 -5
- package/dist/core/folder-mode/assets/folder-mode.css +308 -0
- package/dist/core/folder-mode/assets/folder-mode.js +513 -0
- package/dist/core/folder-mode/folder-mode-server.d.ts +84 -0
- package/dist/core/folder-mode/folder-mode-server.d.ts.map +1 -0
- package/dist/core/folder-mode/folder-mode-server.js +375 -0
- package/dist/core/folder-mode/folder-scanner.d.ts +34 -0
- package/dist/core/folder-mode/folder-scanner.d.ts.map +1 -0
- package/dist/core/folder-mode/folder-scanner.js +105 -0
- package/dist/core/folder-mode/index.d.ts +8 -0
- package/dist/core/folder-mode/index.d.ts.map +1 -0
- package/dist/core/folder-mode/index.js +6 -0
- package/dist/core/folder-mode/types.d.ts +101 -0
- package/dist/core/folder-mode/types.d.ts.map +1 -0
- package/dist/core/folder-mode/types.js +15 -0
- package/dist/templates/folder-mode.html +86 -0
- package/package.json +1 -1
- package/templates/folder-mode.html +86 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Folder mode client-side JavaScript
|
|
3
|
+
*/
|
|
4
|
+
(function() {
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
// Storage keys
|
|
8
|
+
var STORAGE_KEY_EXPANDED = 'vimd-folder-expanded';
|
|
9
|
+
var STORAGE_KEY_WIDTH = 'vimd-sidebar-width';
|
|
10
|
+
|
|
11
|
+
// State
|
|
12
|
+
var fileTree = [];
|
|
13
|
+
var currentPath = null;
|
|
14
|
+
var ws = null;
|
|
15
|
+
var expandedFolders = new Set();
|
|
16
|
+
var isResizing = false;
|
|
17
|
+
|
|
18
|
+
// DOM elements
|
|
19
|
+
var sidebar = document.getElementById('sidebar');
|
|
20
|
+
var toggleBar = document.getElementById('toggle-bar');
|
|
21
|
+
var toggleBtn = document.getElementById('toggle-btn');
|
|
22
|
+
var toggleBtnCollapsed = document.getElementById('toggle-btn-collapsed');
|
|
23
|
+
var fileTreeEl = document.getElementById('file-tree');
|
|
24
|
+
var welcome = document.getElementById('welcome');
|
|
25
|
+
var welcomeMessage = document.getElementById('welcome-message');
|
|
26
|
+
var content = document.getElementById('content');
|
|
27
|
+
var resizer = document.getElementById('resizer');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Initialize the application
|
|
31
|
+
*/
|
|
32
|
+
function init() {
|
|
33
|
+
loadState();
|
|
34
|
+
connectWebSocket();
|
|
35
|
+
setupEventListeners();
|
|
36
|
+
handleInitialPath();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Load saved state from localStorage
|
|
41
|
+
*/
|
|
42
|
+
function loadState() {
|
|
43
|
+
// Load expanded folders
|
|
44
|
+
try {
|
|
45
|
+
var saved = localStorage.getItem(STORAGE_KEY_EXPANDED);
|
|
46
|
+
if (saved) {
|
|
47
|
+
var arr = JSON.parse(saved);
|
|
48
|
+
expandedFolders = new Set(arr);
|
|
49
|
+
}
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.warn('[vimd] Failed to load expanded state:', e);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Load sidebar width
|
|
55
|
+
try {
|
|
56
|
+
var width = localStorage.getItem(STORAGE_KEY_WIDTH);
|
|
57
|
+
if (width) {
|
|
58
|
+
sidebar.style.width = width + 'px';
|
|
59
|
+
}
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.warn('[vimd] Failed to load sidebar width:', e);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Save expanded state to localStorage
|
|
67
|
+
*/
|
|
68
|
+
function saveExpandedState() {
|
|
69
|
+
try {
|
|
70
|
+
localStorage.setItem(STORAGE_KEY_EXPANDED, JSON.stringify(Array.from(expandedFolders)));
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.warn('[vimd] Failed to save expanded state:', e);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Save sidebar width to localStorage
|
|
78
|
+
*/
|
|
79
|
+
function saveSidebarWidth() {
|
|
80
|
+
try {
|
|
81
|
+
var width = parseInt(sidebar.style.width, 10);
|
|
82
|
+
if (width) {
|
|
83
|
+
localStorage.setItem(STORAGE_KEY_WIDTH, width.toString());
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.warn('[vimd] Failed to save sidebar width:', e);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Connect to WebSocket server
|
|
92
|
+
*/
|
|
93
|
+
function connectWebSocket() {
|
|
94
|
+
var protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
95
|
+
ws = new WebSocket(protocol + '//' + location.host);
|
|
96
|
+
|
|
97
|
+
ws.onopen = function() {
|
|
98
|
+
console.log('[vimd] WebSocket connected');
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
ws.onmessage = function(event) {
|
|
102
|
+
try {
|
|
103
|
+
var msg = JSON.parse(event.data);
|
|
104
|
+
handleMessage(msg);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
console.error('[vimd] Failed to parse message:', e);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
ws.onclose = function() {
|
|
111
|
+
console.log('[vimd] WebSocket disconnected, reconnecting...');
|
|
112
|
+
setTimeout(connectWebSocket, 1000);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
ws.onerror = function(error) {
|
|
116
|
+
console.error('[vimd] WebSocket error:', error);
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Handle incoming WebSocket message
|
|
122
|
+
*/
|
|
123
|
+
function handleMessage(msg) {
|
|
124
|
+
switch (msg.type) {
|
|
125
|
+
case 'tree':
|
|
126
|
+
fileTree = msg.data;
|
|
127
|
+
renderTree();
|
|
128
|
+
updateWelcomeMessage();
|
|
129
|
+
break;
|
|
130
|
+
|
|
131
|
+
case 'content':
|
|
132
|
+
showContent(msg.data.path, msg.data.html);
|
|
133
|
+
break;
|
|
134
|
+
|
|
135
|
+
case 'reload':
|
|
136
|
+
location.reload();
|
|
137
|
+
break;
|
|
138
|
+
|
|
139
|
+
case 'error':
|
|
140
|
+
showError(msg.data.type, msg.data.message);
|
|
141
|
+
break;
|
|
142
|
+
|
|
143
|
+
case 'fileDeleted':
|
|
144
|
+
if (currentPath === msg.data.path) {
|
|
145
|
+
showWelcome();
|
|
146
|
+
currentPath = null;
|
|
147
|
+
updateURL('/');
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
|
|
151
|
+
default:
|
|
152
|
+
console.warn('[vimd] Unknown message type:', msg.type);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Render the file tree
|
|
158
|
+
*/
|
|
159
|
+
function renderTree() {
|
|
160
|
+
fileTreeEl.innerHTML = '';
|
|
161
|
+
|
|
162
|
+
if (fileTree.length === 0) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fileTree.forEach(function(node) {
|
|
167
|
+
var el = createTreeNode(node, 0);
|
|
168
|
+
fileTreeEl.appendChild(el);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Restore selection if current path exists
|
|
172
|
+
if (currentPath) {
|
|
173
|
+
updateSelection(currentPath);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Create a tree node element
|
|
179
|
+
*/
|
|
180
|
+
function createTreeNode(node, depth) {
|
|
181
|
+
var container = document.createElement('div');
|
|
182
|
+
container.className = 'vimd-tree-node';
|
|
183
|
+
|
|
184
|
+
var item = document.createElement('div');
|
|
185
|
+
item.className = 'vimd-tree-item';
|
|
186
|
+
item.setAttribute('data-path', node.path);
|
|
187
|
+
item.setAttribute('data-depth', depth.toString());
|
|
188
|
+
|
|
189
|
+
if (node.type === 'folder') {
|
|
190
|
+
// Folder node
|
|
191
|
+
var isExpanded = depth === 0 || expandedFolders.has(node.path);
|
|
192
|
+
|
|
193
|
+
// Chevron
|
|
194
|
+
var chevron = document.createElement('span');
|
|
195
|
+
chevron.className = 'vimd-tree-chevron' + (isExpanded ? '' : ' collapsed');
|
|
196
|
+
chevron.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16"><path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" fill="none"/></svg>';
|
|
197
|
+
item.appendChild(chevron);
|
|
198
|
+
|
|
199
|
+
// Folder icon
|
|
200
|
+
var icon = document.createElement('span');
|
|
201
|
+
icon.className = 'vimd-tree-icon';
|
|
202
|
+
icon.innerHTML = isExpanded
|
|
203
|
+
? '<svg width="16" height="16" viewBox="0 0 16 16" fill="#dcb67a"><path d="M1.5 3A1.5 1.5 0 013 1.5h3.586a1.5 1.5 0 011.06.44l.708.706a.5.5 0 00.353.147H13a1.5 1.5 0 011.5 1.5v.5H1.5V3z"/><path d="M1.5 5h13v7.5a1.5 1.5 0 01-1.5 1.5H3a1.5 1.5 0 01-1.5-1.5V5z"/></svg>'
|
|
204
|
+
: '<svg width="16" height="16" viewBox="0 0 16 16" fill="#dcb67a"><path d="M1.5 3A1.5 1.5 0 013 1.5h3.586a1.5 1.5 0 011.06.44l.708.706a.5.5 0 00.353.147H13a1.5 1.5 0 011.5 1.5v8a1.5 1.5 0 01-1.5 1.5H3a1.5 1.5 0 01-1.5-1.5V3z"/></svg>';
|
|
205
|
+
item.appendChild(icon);
|
|
206
|
+
|
|
207
|
+
// Folder name
|
|
208
|
+
var name = document.createElement('span');
|
|
209
|
+
name.className = 'vimd-tree-name';
|
|
210
|
+
name.textContent = node.name;
|
|
211
|
+
item.appendChild(name);
|
|
212
|
+
|
|
213
|
+
// Click handler for folder
|
|
214
|
+
item.addEventListener('click', function(e) {
|
|
215
|
+
e.stopPropagation();
|
|
216
|
+
toggleFolder(node.path, container, chevron, icon);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
container.appendChild(item);
|
|
220
|
+
|
|
221
|
+
// Children container
|
|
222
|
+
var children = document.createElement('div');
|
|
223
|
+
children.className = 'vimd-tree-children' + (isExpanded ? '' : ' collapsed');
|
|
224
|
+
|
|
225
|
+
node.children.forEach(function(child) {
|
|
226
|
+
var childEl = createTreeNode(child, depth + 1);
|
|
227
|
+
children.appendChild(childEl);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
container.appendChild(children);
|
|
231
|
+
|
|
232
|
+
// Initialize expanded state
|
|
233
|
+
if (isExpanded && depth > 0) {
|
|
234
|
+
expandedFolders.add(node.path);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
} else {
|
|
238
|
+
// File node
|
|
239
|
+
// File icon
|
|
240
|
+
var fileIcon = document.createElement('span');
|
|
241
|
+
fileIcon.className = 'vimd-tree-icon';
|
|
242
|
+
fileIcon.innerHTML = getFileIcon(node.extension);
|
|
243
|
+
item.appendChild(fileIcon);
|
|
244
|
+
|
|
245
|
+
// File name
|
|
246
|
+
var fileName = document.createElement('span');
|
|
247
|
+
fileName.className = 'vimd-tree-name';
|
|
248
|
+
fileName.textContent = node.name;
|
|
249
|
+
item.appendChild(fileName);
|
|
250
|
+
|
|
251
|
+
// Click handler for file
|
|
252
|
+
item.addEventListener('click', function(e) {
|
|
253
|
+
e.stopPropagation();
|
|
254
|
+
selectFile(node.path);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
container.appendChild(item);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return container;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Get file icon SVG based on extension
|
|
265
|
+
*/
|
|
266
|
+
function getFileIcon(ext) {
|
|
267
|
+
if (ext === '.md') {
|
|
268
|
+
// Markdown icon
|
|
269
|
+
return '<svg width="16" height="16" viewBox="0 0 16 16" fill="#519aba"><path d="M2 3.5A1.5 1.5 0 013.5 2h9A1.5 1.5 0 0114 3.5v9a1.5 1.5 0 01-1.5 1.5h-9A1.5 1.5 0 012 12.5v-9zM3.5 3a.5.5 0 00-.5.5v9a.5.5 0 00.5.5h9a.5.5 0 00.5-.5v-9a.5.5 0 00-.5-.5h-9z"/><path d="M4 6v4h1V7.5l1 1.5 1-1.5V10h1V6H7l-1 1.5L5 6H4zm5 0v4h1V8h1V7H10V6H9zm3 0v4h1V6h-1z"/></svg>';
|
|
270
|
+
} else {
|
|
271
|
+
// TeX/LaTeX icon
|
|
272
|
+
return '<svg width="16" height="16" viewBox="0 0 16 16" fill="#3d8137"><path d="M2 3.5A1.5 1.5 0 013.5 2h9A1.5 1.5 0 0114 3.5v9a1.5 1.5 0 01-1.5 1.5h-9A1.5 1.5 0 012 12.5v-9zM3.5 3a.5.5 0 00-.5.5v9a.5.5 0 00.5.5h9a.5.5 0 00.5-.5v-9a.5.5 0 00-.5-.5h-9z"/><path d="M4 5h3v1H5.5v4h2V9H9v2H4V5zm5 0h3v1h-1v5h-1V6H9V5z"/></svg>';
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Toggle folder expanded/collapsed state
|
|
278
|
+
*/
|
|
279
|
+
function toggleFolder(path, container, chevron, icon) {
|
|
280
|
+
var children = container.querySelector('.vimd-tree-children');
|
|
281
|
+
var isExpanded = !children.classList.contains('collapsed');
|
|
282
|
+
|
|
283
|
+
if (isExpanded) {
|
|
284
|
+
children.classList.add('collapsed');
|
|
285
|
+
chevron.classList.add('collapsed');
|
|
286
|
+
icon.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" fill="#dcb67a"><path d="M1.5 3A1.5 1.5 0 013 1.5h3.586a1.5 1.5 0 011.06.44l.708.706a.5.5 0 00.353.147H13a1.5 1.5 0 011.5 1.5v8a1.5 1.5 0 01-1.5 1.5H3a1.5 1.5 0 01-1.5-1.5V3z"/></svg>';
|
|
287
|
+
expandedFolders.delete(path);
|
|
288
|
+
} else {
|
|
289
|
+
children.classList.remove('collapsed');
|
|
290
|
+
chevron.classList.remove('collapsed');
|
|
291
|
+
icon.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" fill="#dcb67a"><path d="M1.5 3A1.5 1.5 0 013 1.5h3.586a1.5 1.5 0 011.06.44l.708.706a.5.5 0 00.353.147H13a1.5 1.5 0 011.5 1.5v.5H1.5V3z"/><path d="M1.5 5h13v7.5a1.5 1.5 0 01-1.5 1.5H3a1.5 1.5 0 01-1.5-1.5V5z"/></svg>';
|
|
292
|
+
expandedFolders.add(path);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
saveExpandedState();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Select a file
|
|
300
|
+
*/
|
|
301
|
+
function selectFile(path) {
|
|
302
|
+
if (currentPath === path) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Update selection
|
|
307
|
+
updateSelection(path);
|
|
308
|
+
|
|
309
|
+
// Send to server
|
|
310
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
311
|
+
ws.send(JSON.stringify({ type: 'selectFile', path: path }));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Update URL
|
|
315
|
+
updateURL('/' + encodeURIComponent(path).replace(/%2F/g, '/'));
|
|
316
|
+
|
|
317
|
+
currentPath = path;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Update visual selection in tree
|
|
322
|
+
*/
|
|
323
|
+
function updateSelection(path) {
|
|
324
|
+
// Remove previous selection
|
|
325
|
+
var selected = fileTreeEl.querySelectorAll('.vimd-tree-item.selected');
|
|
326
|
+
selected.forEach(function(el) {
|
|
327
|
+
el.classList.remove('selected');
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Add new selection
|
|
331
|
+
var item = fileTreeEl.querySelector('.vimd-tree-item[data-path="' + path + '"]');
|
|
332
|
+
if (item) {
|
|
333
|
+
item.classList.add('selected');
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Show file content
|
|
339
|
+
*/
|
|
340
|
+
function showContent(path, html) {
|
|
341
|
+
welcome.classList.add('hidden');
|
|
342
|
+
content.classList.add('visible');
|
|
343
|
+
content.innerHTML = html;
|
|
344
|
+
|
|
345
|
+
// Update selection
|
|
346
|
+
updateSelection(path);
|
|
347
|
+
currentPath = path;
|
|
348
|
+
|
|
349
|
+
// Re-render MathJax if available
|
|
350
|
+
if (window.MathJax && window.MathJax.typeset) {
|
|
351
|
+
window.MathJax.typeset([content]);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Show welcome screen
|
|
357
|
+
*/
|
|
358
|
+
function showWelcome() {
|
|
359
|
+
content.classList.remove('visible');
|
|
360
|
+
content.innerHTML = '';
|
|
361
|
+
welcome.classList.remove('hidden');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Show error message
|
|
366
|
+
*/
|
|
367
|
+
function showError(type, message) {
|
|
368
|
+
welcome.classList.add('hidden');
|
|
369
|
+
content.classList.add('visible');
|
|
370
|
+
content.innerHTML = '<div class="vimd-error"><h2>Error</h2><p>' + escapeHtml(message) + '</p></div>';
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Update welcome message based on file tree
|
|
375
|
+
*/
|
|
376
|
+
function updateWelcomeMessage() {
|
|
377
|
+
if (fileTree.length === 0) {
|
|
378
|
+
welcomeMessage.textContent = 'No markdown or LaTeX files found';
|
|
379
|
+
} else {
|
|
380
|
+
welcomeMessage.textContent = 'Select a file from the sidebar';
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Update URL without page reload
|
|
386
|
+
*/
|
|
387
|
+
function updateURL(path) {
|
|
388
|
+
history.pushState({ path: path }, '', path);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Handle initial URL path
|
|
393
|
+
*/
|
|
394
|
+
function handleInitialPath() {
|
|
395
|
+
var path = decodeURIComponent(location.pathname.slice(1));
|
|
396
|
+
if (path && path !== '') {
|
|
397
|
+
// Wait for tree to load then select file
|
|
398
|
+
var checkTree = setInterval(function() {
|
|
399
|
+
if (fileTree.length > 0) {
|
|
400
|
+
clearInterval(checkTree);
|
|
401
|
+
selectFile(path);
|
|
402
|
+
}
|
|
403
|
+
}, 100);
|
|
404
|
+
|
|
405
|
+
// Timeout after 5 seconds
|
|
406
|
+
setTimeout(function() {
|
|
407
|
+
clearInterval(checkTree);
|
|
408
|
+
}, 5000);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Setup event listeners
|
|
414
|
+
*/
|
|
415
|
+
function setupEventListeners() {
|
|
416
|
+
// Sidebar toggle buttons
|
|
417
|
+
toggleBtn.addEventListener('click', function() {
|
|
418
|
+
collapseSidebar();
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
toggleBtnCollapsed.addEventListener('click', function() {
|
|
422
|
+
expandSidebar();
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Keyboard shortcut (Ctrl+B or Cmd+B)
|
|
426
|
+
document.addEventListener('keydown', function(e) {
|
|
427
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'b') {
|
|
428
|
+
e.preventDefault();
|
|
429
|
+
if (sidebar.classList.contains('collapsed')) {
|
|
430
|
+
expandSidebar();
|
|
431
|
+
} else {
|
|
432
|
+
collapseSidebar();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// Resizer
|
|
438
|
+
resizer.addEventListener('mousedown', function(e) {
|
|
439
|
+
e.preventDefault();
|
|
440
|
+
isResizing = true;
|
|
441
|
+
resizer.classList.add('active');
|
|
442
|
+
document.body.style.cursor = 'ew-resize';
|
|
443
|
+
document.body.style.userSelect = 'none';
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
document.addEventListener('mousemove', function(e) {
|
|
447
|
+
if (!isResizing) return;
|
|
448
|
+
|
|
449
|
+
var width = e.clientX;
|
|
450
|
+
var minWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-min-width'), 10) || 150;
|
|
451
|
+
var maxWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-max-width'), 10) || 500;
|
|
452
|
+
|
|
453
|
+
if (width >= minWidth && width <= maxWidth) {
|
|
454
|
+
sidebar.style.width = width + 'px';
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
document.addEventListener('mouseup', function() {
|
|
459
|
+
if (isResizing) {
|
|
460
|
+
isResizing = false;
|
|
461
|
+
resizer.classList.remove('active');
|
|
462
|
+
document.body.style.cursor = '';
|
|
463
|
+
document.body.style.userSelect = '';
|
|
464
|
+
saveSidebarWidth();
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// Browser history
|
|
469
|
+
window.addEventListener('popstate', function(e) {
|
|
470
|
+
if (e.state && e.state.path) {
|
|
471
|
+
var path = e.state.path.slice(1); // Remove leading /
|
|
472
|
+
if (path) {
|
|
473
|
+
selectFile(path);
|
|
474
|
+
} else {
|
|
475
|
+
showWelcome();
|
|
476
|
+
currentPath = null;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Collapse sidebar
|
|
484
|
+
*/
|
|
485
|
+
function collapseSidebar() {
|
|
486
|
+
sidebar.classList.add('collapsed');
|
|
487
|
+
toggleBar.classList.add('visible');
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Expand sidebar
|
|
492
|
+
*/
|
|
493
|
+
function expandSidebar() {
|
|
494
|
+
sidebar.classList.remove('collapsed');
|
|
495
|
+
toggleBar.classList.remove('visible');
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Escape HTML special characters
|
|
500
|
+
*/
|
|
501
|
+
function escapeHtml(text) {
|
|
502
|
+
var div = document.createElement('div');
|
|
503
|
+
div.textContent = text;
|
|
504
|
+
return div.innerHTML;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Initialize when DOM is ready
|
|
508
|
+
if (document.readyState === 'loading') {
|
|
509
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
510
|
+
} else {
|
|
511
|
+
init();
|
|
512
|
+
}
|
|
513
|
+
})();
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { FolderModeOptions, ServerMessage } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Result of server start operation
|
|
4
|
+
*/
|
|
5
|
+
export interface ServerStartResult {
|
|
6
|
+
actualPort: number;
|
|
7
|
+
requestedPort: number;
|
|
8
|
+
portChanged: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Folder mode server for multi-file preview
|
|
12
|
+
*/
|
|
13
|
+
export declare class FolderModeServer {
|
|
14
|
+
private httpServer;
|
|
15
|
+
private wsServer;
|
|
16
|
+
private clients;
|
|
17
|
+
private scanner;
|
|
18
|
+
private options;
|
|
19
|
+
private _port;
|
|
20
|
+
private fileTree;
|
|
21
|
+
private renderedHtml;
|
|
22
|
+
constructor(options: FolderModeOptions);
|
|
23
|
+
/**
|
|
24
|
+
* Get the actual port the server is running on
|
|
25
|
+
*/
|
|
26
|
+
get port(): number;
|
|
27
|
+
/**
|
|
28
|
+
* Get the root path
|
|
29
|
+
*/
|
|
30
|
+
get rootPath(): string;
|
|
31
|
+
/**
|
|
32
|
+
* Start the HTTP and WebSocket servers
|
|
33
|
+
*/
|
|
34
|
+
start(): Promise<ServerStartResult>;
|
|
35
|
+
/**
|
|
36
|
+
* Stop the server
|
|
37
|
+
*/
|
|
38
|
+
stop(): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Handle new WebSocket connection
|
|
41
|
+
*/
|
|
42
|
+
private handleConnection;
|
|
43
|
+
/**
|
|
44
|
+
* Handle message from client
|
|
45
|
+
*/
|
|
46
|
+
private handleClientMessage;
|
|
47
|
+
/**
|
|
48
|
+
* Handle file selection
|
|
49
|
+
*/
|
|
50
|
+
private handleSelectFile;
|
|
51
|
+
/**
|
|
52
|
+
* Convert a file to HTML
|
|
53
|
+
*/
|
|
54
|
+
private convertFile;
|
|
55
|
+
/**
|
|
56
|
+
* Validate path and return absolute path if valid
|
|
57
|
+
*/
|
|
58
|
+
private validatePath;
|
|
59
|
+
/**
|
|
60
|
+
* Send message to a specific client
|
|
61
|
+
*/
|
|
62
|
+
private sendMessage;
|
|
63
|
+
/**
|
|
64
|
+
* Broadcast message to all clients
|
|
65
|
+
*/
|
|
66
|
+
broadcast(message: ServerMessage): void;
|
|
67
|
+
/**
|
|
68
|
+
* Serve folder mode HTML
|
|
69
|
+
*/
|
|
70
|
+
private serveFolderModeHtml;
|
|
71
|
+
/**
|
|
72
|
+
* Render the folder mode template
|
|
73
|
+
*/
|
|
74
|
+
private renderTemplate;
|
|
75
|
+
/**
|
|
76
|
+
* Escape HTML special characters
|
|
77
|
+
*/
|
|
78
|
+
private escapeHtml;
|
|
79
|
+
/**
|
|
80
|
+
* Count total files in tree
|
|
81
|
+
*/
|
|
82
|
+
private countFiles;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=folder-mode-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"folder-mode-server.d.ts","sourceRoot":"","sources":["../../../src/core/folder-mode/folder-mode-server.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACV,iBAAiB,EAEjB,aAAa,EAEd,MAAM,YAAY,CAAC;AAKpB;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;CACtB;AAUD;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,OAAO,CAA0C;IACzD,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,YAAY,CAAuB;gBAE/B,OAAO,EAAE,iBAAiB;IAMtC;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAuEzC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA+B3B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAuCxB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAU3B;;OAEG;YACW,gBAAgB;IAuF9B;;OAEG;YACW,WAAW;IASzB;;OAEG;IACH,OAAO,CAAC,YAAY;IAoBpB;;OAEG;IACH,OAAO,CAAC,WAAW;IAMnB;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IASvC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAW3B;;OAEG;YACW,cAAc;IAkC5B;;OAEG;IACH,OAAO,CAAC,UAAU;IASlB;;OAEG;IACH,OAAO,CAAC,UAAU;CAWnB"}
|