vimd 0.5.1 → 0.5.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.
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
:root {
|
|
6
|
+
/* Sidebar */
|
|
6
7
|
--sidebar-bg: #252526;
|
|
7
8
|
--sidebar-text: #cccccc;
|
|
8
9
|
--sidebar-text-muted: #858585;
|
|
@@ -10,11 +11,24 @@
|
|
|
10
11
|
--sidebar-selected: #094771;
|
|
11
12
|
--sidebar-border: #3c3c3c;
|
|
12
13
|
--sidebar-header-bg: #3c3c3c;
|
|
13
|
-
--sidebar-width:
|
|
14
|
+
--sidebar-width: 300px;
|
|
14
15
|
--sidebar-min-width: 150px;
|
|
15
16
|
--sidebar-max-width: 500px;
|
|
16
|
-
--toggle-bar-width: 20px;
|
|
17
17
|
--resizer-width: 4px;
|
|
18
|
+
|
|
19
|
+
/* Panel header (light theme) */
|
|
20
|
+
--panel-header-border: #e0e0e0;
|
|
21
|
+
--panel-filename-color: #666;
|
|
22
|
+
--panel-close-color: #999;
|
|
23
|
+
--panel-close-hover: #333;
|
|
24
|
+
--panel-active-color: #007acc;
|
|
25
|
+
|
|
26
|
+
/* Context menu */
|
|
27
|
+
--context-menu-bg: #ffffff;
|
|
28
|
+
--context-menu-border: #e0e0e0;
|
|
29
|
+
--context-menu-hover: #f5f5f5;
|
|
30
|
+
--context-menu-text: #333;
|
|
31
|
+
--context-menu-shadow: rgba(0, 0, 0, 0.15);
|
|
18
32
|
}
|
|
19
33
|
|
|
20
34
|
* {
|
|
@@ -24,6 +38,8 @@
|
|
|
24
38
|
html, body {
|
|
25
39
|
margin: 0;
|
|
26
40
|
padding: 0;
|
|
41
|
+
width: 100%;
|
|
42
|
+
max-width: none;
|
|
27
43
|
height: 100%;
|
|
28
44
|
overflow: hidden;
|
|
29
45
|
}
|
|
@@ -31,27 +47,12 @@ html, body {
|
|
|
31
47
|
/* Container */
|
|
32
48
|
.vimd-container {
|
|
33
49
|
display: flex;
|
|
50
|
+
width: 100vw;
|
|
34
51
|
height: 100vh;
|
|
35
52
|
overflow: hidden;
|
|
36
53
|
background: #1e1e1e;
|
|
37
54
|
}
|
|
38
55
|
|
|
39
|
-
/* Toggle bar (visible when sidebar is collapsed) */
|
|
40
|
-
.vimd-sidebar-toggle-bar {
|
|
41
|
-
width: var(--toggle-bar-width);
|
|
42
|
-
min-width: var(--toggle-bar-width);
|
|
43
|
-
background: var(--sidebar-bg);
|
|
44
|
-
border-right: 1px solid var(--sidebar-border);
|
|
45
|
-
display: none;
|
|
46
|
-
flex-direction: column;
|
|
47
|
-
align-items: center;
|
|
48
|
-
padding-top: 8px;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.vimd-sidebar-toggle-bar.visible {
|
|
52
|
-
display: flex;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
56
|
/* Sidebar */
|
|
56
57
|
.vimd-sidebar {
|
|
57
58
|
width: var(--sidebar-width);
|
|
@@ -63,15 +64,6 @@ html, body {
|
|
|
63
64
|
flex-direction: column;
|
|
64
65
|
position: relative;
|
|
65
66
|
border-right: 1px solid var(--sidebar-border);
|
|
66
|
-
transition: width 0.15s ease, min-width 0.15s ease, opacity 0.15s ease;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.vimd-sidebar.collapsed {
|
|
70
|
-
width: 0;
|
|
71
|
-
min-width: 0;
|
|
72
|
-
border-right: none;
|
|
73
|
-
overflow: hidden;
|
|
74
|
-
opacity: 0;
|
|
75
67
|
}
|
|
76
68
|
|
|
77
69
|
/* Sidebar header */
|
|
@@ -98,26 +90,6 @@ html, body {
|
|
|
98
90
|
flex: 1;
|
|
99
91
|
}
|
|
100
92
|
|
|
101
|
-
/* Toggle button */
|
|
102
|
-
.vimd-toggle-btn {
|
|
103
|
-
background: transparent;
|
|
104
|
-
border: none;
|
|
105
|
-
color: var(--sidebar-text);
|
|
106
|
-
cursor: pointer;
|
|
107
|
-
padding: 4px;
|
|
108
|
-
display: flex;
|
|
109
|
-
align-items: center;
|
|
110
|
-
justify-content: center;
|
|
111
|
-
border-radius: 3px;
|
|
112
|
-
opacity: 0.7;
|
|
113
|
-
transition: opacity 0.1s, background 0.1s;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.vimd-toggle-btn:hover {
|
|
117
|
-
opacity: 1;
|
|
118
|
-
background: var(--sidebar-hover);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
93
|
/* File tree */
|
|
122
94
|
.vimd-tree {
|
|
123
95
|
flex: 1;
|
|
@@ -238,10 +210,117 @@ html, body {
|
|
|
238
210
|
|
|
239
211
|
/* Preview area */
|
|
240
212
|
.vimd-preview {
|
|
213
|
+
flex: 1;
|
|
214
|
+
display: flex;
|
|
215
|
+
overflow: hidden;
|
|
216
|
+
background: #ffffff;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* Panel */
|
|
220
|
+
.vimd-panel {
|
|
221
|
+
flex: 1;
|
|
222
|
+
display: flex;
|
|
223
|
+
flex-direction: column;
|
|
224
|
+
overflow: hidden;
|
|
225
|
+
min-width: 100px;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Split view: Panel 1 (initial 720px, resizable) */
|
|
229
|
+
.vimd-panel.split-panel1 {
|
|
230
|
+
flex: none;
|
|
231
|
+
/* width is set by JS */
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/* Split view: Panel 2 (takes remaining space) */
|
|
235
|
+
.vimd-panel.split-panel2 {
|
|
236
|
+
flex: 1;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.vimd-panel-body {
|
|
241
240
|
flex: 1;
|
|
242
241
|
overflow-y: auto;
|
|
243
242
|
overflow-x: hidden;
|
|
244
|
-
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* Panel header */
|
|
246
|
+
.vimd-panel-header {
|
|
247
|
+
display: none;
|
|
248
|
+
align-items: center;
|
|
249
|
+
justify-content: flex-start;
|
|
250
|
+
gap: 8px;
|
|
251
|
+
height: 28px;
|
|
252
|
+
padding: 4px 16px;
|
|
253
|
+
background: transparent;
|
|
254
|
+
border-bottom: 1px solid var(--panel-header-border);
|
|
255
|
+
position: relative;
|
|
256
|
+
flex-shrink: 0;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.vimd-panel-header.visible {
|
|
260
|
+
display: flex;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.vimd-panel-header.active::after {
|
|
264
|
+
content: '';
|
|
265
|
+
position: absolute;
|
|
266
|
+
bottom: 0;
|
|
267
|
+
left: 0;
|
|
268
|
+
right: 0;
|
|
269
|
+
height: 2px;
|
|
270
|
+
background: var(--panel-active-color);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.vimd-panel-filename {
|
|
274
|
+
font-size: 12px;
|
|
275
|
+
color: var(--panel-filename-color);
|
|
276
|
+
overflow: hidden;
|
|
277
|
+
text-overflow: ellipsis;
|
|
278
|
+
white-space: nowrap;
|
|
279
|
+
max-width: calc(100% - 30px);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.vimd-panel-filename:hover {
|
|
283
|
+
overflow: visible;
|
|
284
|
+
white-space: normal;
|
|
285
|
+
word-break: break-all;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.vimd-panel-close {
|
|
289
|
+
background: none;
|
|
290
|
+
border: none;
|
|
291
|
+
font-size: 16px;
|
|
292
|
+
color: var(--panel-close-color);
|
|
293
|
+
cursor: pointer;
|
|
294
|
+
flex-shrink: 0;
|
|
295
|
+
width: 20px;
|
|
296
|
+
height: 20px;
|
|
297
|
+
display: flex;
|
|
298
|
+
align-items: center;
|
|
299
|
+
justify-content: center;
|
|
300
|
+
border-radius: 3px;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.vimd-panel-close:hover {
|
|
304
|
+
color: var(--panel-close-hover);
|
|
305
|
+
background: rgba(0, 0, 0, 0.05);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* Panel resizer (between panels) */
|
|
309
|
+
.vimd-panel-resizer {
|
|
310
|
+
width: 4px;
|
|
311
|
+
background: transparent;
|
|
312
|
+
cursor: ew-resize;
|
|
313
|
+
flex-shrink: 0;
|
|
314
|
+
display: none;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.vimd-panel-resizer.visible {
|
|
318
|
+
display: block;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.vimd-panel-resizer:hover,
|
|
322
|
+
.vimd-panel-resizer.active {
|
|
323
|
+
background: var(--panel-active-color);
|
|
245
324
|
}
|
|
246
325
|
|
|
247
326
|
/* Welcome screen */
|
|
@@ -274,10 +353,15 @@ html, body {
|
|
|
274
353
|
.vimd-content {
|
|
275
354
|
display: none;
|
|
276
355
|
padding: 32px;
|
|
277
|
-
max-width:
|
|
356
|
+
max-width: 720px;
|
|
278
357
|
margin: 0 auto;
|
|
279
358
|
}
|
|
280
359
|
|
|
360
|
+
.vimd-content pre,
|
|
361
|
+
.vimd-content table {
|
|
362
|
+
overflow-x: auto;
|
|
363
|
+
}
|
|
364
|
+
|
|
281
365
|
.vimd-content.visible {
|
|
282
366
|
display: block;
|
|
283
367
|
}
|
|
@@ -307,3 +391,35 @@ html, body {
|
|
|
307
391
|
margin: 0;
|
|
308
392
|
font-size: 14px;
|
|
309
393
|
}
|
|
394
|
+
|
|
395
|
+
/* Context menu */
|
|
396
|
+
.vimd-context-menu {
|
|
397
|
+
display: none;
|
|
398
|
+
position: fixed;
|
|
399
|
+
background: var(--context-menu-bg);
|
|
400
|
+
border: 1px solid var(--context-menu-border);
|
|
401
|
+
border-radius: 4px;
|
|
402
|
+
box-shadow: 0 2px 8px var(--context-menu-shadow);
|
|
403
|
+
min-width: 150px;
|
|
404
|
+
z-index: 1000;
|
|
405
|
+
padding: 4px 0;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.vimd-context-menu.visible {
|
|
409
|
+
display: block;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.vimd-context-item {
|
|
413
|
+
padding: 6px 12px;
|
|
414
|
+
cursor: pointer;
|
|
415
|
+
font-size: 13px;
|
|
416
|
+
color: var(--context-menu-text);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.vimd-context-item:hover {
|
|
420
|
+
background: var(--context-menu-hover);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.vimd-context-item.hidden {
|
|
424
|
+
display: none;
|
|
425
|
+
}
|
|
@@ -6,25 +6,53 @@
|
|
|
6
6
|
|
|
7
7
|
// Storage keys
|
|
8
8
|
var STORAGE_KEY_EXPANDED = 'vimd-folder-expanded';
|
|
9
|
-
var
|
|
9
|
+
var STORAGE_KEY_SIDEBAR_WIDTH = 'vimd-sidebar-width';
|
|
10
|
+
var STORAGE_KEY_STATE = 'vimd-folder-mode-state';
|
|
10
11
|
|
|
11
12
|
// State
|
|
12
13
|
var fileTree = [];
|
|
13
|
-
var currentPath = null;
|
|
14
14
|
var ws = null;
|
|
15
15
|
var expandedFolders = new Set();
|
|
16
|
-
var
|
|
16
|
+
var isSidebarResizing = false;
|
|
17
|
+
var isPanelResizing = false;
|
|
18
|
+
|
|
19
|
+
// Panel state
|
|
20
|
+
var state = {
|
|
21
|
+
isSplitView: false,
|
|
22
|
+
panels: [
|
|
23
|
+
{ file: null },
|
|
24
|
+
{ file: null }
|
|
25
|
+
],
|
|
26
|
+
activePanel: 0,
|
|
27
|
+
panelWidth: 720
|
|
28
|
+
};
|
|
17
29
|
|
|
18
30
|
// DOM elements
|
|
19
31
|
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
32
|
var fileTreeEl = document.getElementById('file-tree');
|
|
33
|
+
var preview = document.getElementById('preview');
|
|
34
|
+
var sidebarResizer = document.getElementById('resizer');
|
|
35
|
+
|
|
36
|
+
// Panel elements
|
|
37
|
+
var panel1 = document.getElementById('panel1');
|
|
38
|
+
var panel1Header = document.getElementById('panel1-header');
|
|
39
|
+
var panel1Filename = document.getElementById('panel1-filename');
|
|
40
|
+
var panel1Close = document.getElementById('panel1-close');
|
|
24
41
|
var welcome = document.getElementById('welcome');
|
|
25
42
|
var welcomeMessage = document.getElementById('welcome-message');
|
|
26
43
|
var content = document.getElementById('content');
|
|
27
|
-
|
|
44
|
+
|
|
45
|
+
// Panel 2 elements (created dynamically)
|
|
46
|
+
var panel2 = null;
|
|
47
|
+
var panel2Header = null;
|
|
48
|
+
var panel2Filename = null;
|
|
49
|
+
var panel2Close = null;
|
|
50
|
+
var panel2Content = null;
|
|
51
|
+
var panelResizer = null;
|
|
52
|
+
|
|
53
|
+
// Context menu
|
|
54
|
+
var contextMenu = document.getElementById('context-menu');
|
|
55
|
+
var contextTarget = null;
|
|
28
56
|
|
|
29
57
|
/**
|
|
30
58
|
* Initialize the application
|
|
@@ -34,6 +62,7 @@
|
|
|
34
62
|
connectWebSocket();
|
|
35
63
|
setupEventListeners();
|
|
36
64
|
handleInitialPath();
|
|
65
|
+
updatePanelUI();
|
|
37
66
|
}
|
|
38
67
|
|
|
39
68
|
/**
|
|
@@ -53,13 +82,27 @@
|
|
|
53
82
|
|
|
54
83
|
// Load sidebar width
|
|
55
84
|
try {
|
|
56
|
-
var width = localStorage.getItem(
|
|
85
|
+
var width = localStorage.getItem(STORAGE_KEY_SIDEBAR_WIDTH);
|
|
57
86
|
if (width) {
|
|
58
87
|
sidebar.style.width = width + 'px';
|
|
59
88
|
}
|
|
60
89
|
} catch (e) {
|
|
61
90
|
console.warn('[vimd] Failed to load sidebar width:', e);
|
|
62
91
|
}
|
|
92
|
+
|
|
93
|
+
// Load panel state
|
|
94
|
+
try {
|
|
95
|
+
var savedState = localStorage.getItem(STORAGE_KEY_STATE);
|
|
96
|
+
if (savedState) {
|
|
97
|
+
var parsed = JSON.parse(savedState);
|
|
98
|
+
state.isSplitView = parsed.isSplitView || false;
|
|
99
|
+
state.panels = parsed.panels || [{ file: null }, { file: null }];
|
|
100
|
+
state.activePanel = parsed.activePanel || 0;
|
|
101
|
+
state.panelWidth = parsed.panelWidth || 720;
|
|
102
|
+
}
|
|
103
|
+
} catch (e) {
|
|
104
|
+
console.warn('[vimd] Failed to load panel state:', e);
|
|
105
|
+
}
|
|
63
106
|
}
|
|
64
107
|
|
|
65
108
|
/**
|
|
@@ -80,13 +123,29 @@
|
|
|
80
123
|
try {
|
|
81
124
|
var width = parseInt(sidebar.style.width, 10);
|
|
82
125
|
if (width) {
|
|
83
|
-
localStorage.setItem(
|
|
126
|
+
localStorage.setItem(STORAGE_KEY_SIDEBAR_WIDTH, width.toString());
|
|
84
127
|
}
|
|
85
128
|
} catch (e) {
|
|
86
129
|
console.warn('[vimd] Failed to save sidebar width:', e);
|
|
87
130
|
}
|
|
88
131
|
}
|
|
89
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Save panel state to localStorage
|
|
135
|
+
*/
|
|
136
|
+
function savePanelState() {
|
|
137
|
+
try {
|
|
138
|
+
localStorage.setItem(STORAGE_KEY_STATE, JSON.stringify({
|
|
139
|
+
isSplitView: state.isSplitView,
|
|
140
|
+
panels: state.panels,
|
|
141
|
+
activePanel: state.activePanel,
|
|
142
|
+
panelWidth: state.panelWidth
|
|
143
|
+
}));
|
|
144
|
+
} catch (e) {
|
|
145
|
+
console.warn('[vimd] Failed to save panel state:', e);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
90
149
|
/**
|
|
91
150
|
* Connect to WebSocket server
|
|
92
151
|
*/
|
|
@@ -126,6 +185,8 @@
|
|
|
126
185
|
fileTree = msg.data;
|
|
127
186
|
renderTree();
|
|
128
187
|
updateWelcomeMessage();
|
|
188
|
+
// Restore files after tree loads
|
|
189
|
+
restoreFilesFromState();
|
|
129
190
|
break;
|
|
130
191
|
|
|
131
192
|
case 'content':
|
|
@@ -141,11 +202,7 @@
|
|
|
141
202
|
break;
|
|
142
203
|
|
|
143
204
|
case 'fileDeleted':
|
|
144
|
-
|
|
145
|
-
showWelcome();
|
|
146
|
-
currentPath = null;
|
|
147
|
-
updateURL('/');
|
|
148
|
-
}
|
|
205
|
+
handleFileDeleted(msg.data.path);
|
|
149
206
|
break;
|
|
150
207
|
|
|
151
208
|
default:
|
|
@@ -153,6 +210,96 @@
|
|
|
153
210
|
}
|
|
154
211
|
}
|
|
155
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Restore files from saved state
|
|
215
|
+
*/
|
|
216
|
+
function restoreFilesFromState() {
|
|
217
|
+
// Check if saved files exist
|
|
218
|
+
var panel1File = state.panels[0].file;
|
|
219
|
+
var panel2File = state.panels[1].file;
|
|
220
|
+
|
|
221
|
+
// Reset if files don't exist
|
|
222
|
+
if (panel1File && !fileExists(panel1File)) {
|
|
223
|
+
state.panels[0].file = null;
|
|
224
|
+
}
|
|
225
|
+
if (panel2File && !fileExists(panel2File)) {
|
|
226
|
+
state.panels[1].file = null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// If both files are gone, reset to single panel welcome
|
|
230
|
+
if (!state.panels[0].file && !state.panels[1].file) {
|
|
231
|
+
state.isSplitView = false;
|
|
232
|
+
state.activePanel = 0;
|
|
233
|
+
savePanelState();
|
|
234
|
+
updatePanelUI();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Restore split view if needed
|
|
239
|
+
if (state.isSplitView && state.panels[1].file) {
|
|
240
|
+
createPanel2();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Restore panel 1 file
|
|
244
|
+
if (state.panels[0].file) {
|
|
245
|
+
requestFile(state.panels[0].file, 0);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Restore panel 2 file
|
|
249
|
+
if (state.isSplitView && state.panels[1].file) {
|
|
250
|
+
requestFile(state.panels[1].file, 1);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
updatePanelUI();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Check if file exists in tree
|
|
258
|
+
*/
|
|
259
|
+
function fileExists(path) {
|
|
260
|
+
function search(nodes) {
|
|
261
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
262
|
+
var node = nodes[i];
|
|
263
|
+
if (node.type === 'file' && node.path === path) {
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
if (node.type === 'folder' && node.children) {
|
|
267
|
+
if (search(node.children)) {
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
return search(fileTree);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Handle file deleted event
|
|
279
|
+
*/
|
|
280
|
+
function handleFileDeleted(path) {
|
|
281
|
+
var needsUpdate = false;
|
|
282
|
+
|
|
283
|
+
if (state.panels[0].file === path) {
|
|
284
|
+
state.panels[0].file = null;
|
|
285
|
+
needsUpdate = true;
|
|
286
|
+
}
|
|
287
|
+
if (state.panels[1].file === path) {
|
|
288
|
+
state.panels[1].file = null;
|
|
289
|
+
needsUpdate = true;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (needsUpdate) {
|
|
293
|
+
// If both panels are empty, close split view
|
|
294
|
+
if (!state.panels[0].file && !state.panels[1].file) {
|
|
295
|
+
closeSplitView();
|
|
296
|
+
} else {
|
|
297
|
+
updatePanelUI();
|
|
298
|
+
savePanelState();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
156
303
|
/**
|
|
157
304
|
* Render the file tree
|
|
158
305
|
*/
|
|
@@ -168,10 +315,8 @@
|
|
|
168
315
|
fileTreeEl.appendChild(el);
|
|
169
316
|
});
|
|
170
317
|
|
|
171
|
-
// Restore selection
|
|
172
|
-
|
|
173
|
-
updateSelection(currentPath);
|
|
174
|
-
}
|
|
318
|
+
// Restore selection
|
|
319
|
+
updateSelection();
|
|
175
320
|
}
|
|
176
321
|
|
|
177
322
|
/**
|
|
@@ -185,6 +330,7 @@
|
|
|
185
330
|
item.className = 'vimd-tree-item';
|
|
186
331
|
item.setAttribute('data-path', node.path);
|
|
187
332
|
item.setAttribute('data-depth', depth.toString());
|
|
333
|
+
item.setAttribute('data-type', node.type);
|
|
188
334
|
|
|
189
335
|
if (node.type === 'folder') {
|
|
190
336
|
// Folder node
|
|
@@ -254,6 +400,13 @@
|
|
|
254
400
|
selectFile(node.path);
|
|
255
401
|
});
|
|
256
402
|
|
|
403
|
+
// Context menu handler for file
|
|
404
|
+
item.addEventListener('contextmenu', function(e) {
|
|
405
|
+
e.preventDefault();
|
|
406
|
+
e.stopPropagation();
|
|
407
|
+
showContextMenu(e, node.path);
|
|
408
|
+
});
|
|
409
|
+
|
|
257
410
|
container.appendChild(item);
|
|
258
411
|
}
|
|
259
412
|
|
|
@@ -296,75 +449,133 @@
|
|
|
296
449
|
}
|
|
297
450
|
|
|
298
451
|
/**
|
|
299
|
-
* Select a file
|
|
452
|
+
* Select a file (open in active panel)
|
|
300
453
|
*/
|
|
301
454
|
function selectFile(path) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
455
|
+
openFileInPanel(path, state.activePanel);
|
|
456
|
+
}
|
|
305
457
|
|
|
306
|
-
|
|
307
|
-
|
|
458
|
+
/**
|
|
459
|
+
* Open file in specific panel
|
|
460
|
+
*/
|
|
461
|
+
function openFileInPanel(path, panelIndex) {
|
|
462
|
+
state.panels[panelIndex].file = path;
|
|
463
|
+
state.activePanel = panelIndex;
|
|
464
|
+
requestFile(path, panelIndex);
|
|
465
|
+
updateSelection();
|
|
466
|
+
updatePanelUI();
|
|
467
|
+
savePanelState();
|
|
468
|
+
updateURL();
|
|
469
|
+
}
|
|
308
470
|
|
|
309
|
-
|
|
471
|
+
/**
|
|
472
|
+
* Request file content from server
|
|
473
|
+
*/
|
|
474
|
+
function requestFile(path, panelIndex) {
|
|
310
475
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
311
|
-
ws.send(JSON.stringify({
|
|
476
|
+
ws.send(JSON.stringify({
|
|
477
|
+
type: 'selectFile',
|
|
478
|
+
path: path,
|
|
479
|
+
panelIndex: panelIndex
|
|
480
|
+
}));
|
|
312
481
|
}
|
|
313
|
-
|
|
314
|
-
// Update URL
|
|
315
|
-
updateURL('/' + encodeURIComponent(path).replace(/%2F/g, '/'));
|
|
316
|
-
|
|
317
|
-
currentPath = path;
|
|
318
482
|
}
|
|
319
483
|
|
|
320
484
|
/**
|
|
321
485
|
* Update visual selection in tree
|
|
322
486
|
*/
|
|
323
|
-
function updateSelection(
|
|
487
|
+
function updateSelection() {
|
|
324
488
|
// Remove previous selection
|
|
325
489
|
var selected = fileTreeEl.querySelectorAll('.vimd-tree-item.selected');
|
|
326
490
|
selected.forEach(function(el) {
|
|
327
491
|
el.classList.remove('selected');
|
|
328
492
|
});
|
|
329
493
|
|
|
330
|
-
// Add
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
494
|
+
// Add selection for all open files
|
|
495
|
+
state.panels.forEach(function(panel) {
|
|
496
|
+
if (panel.file) {
|
|
497
|
+
var item = fileTreeEl.querySelector('.vimd-tree-item[data-path="' + panel.file + '"]');
|
|
498
|
+
if (item) {
|
|
499
|
+
item.classList.add('selected');
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
});
|
|
335
503
|
}
|
|
336
504
|
|
|
337
505
|
/**
|
|
338
506
|
* Show file content
|
|
339
507
|
*/
|
|
340
508
|
function showContent(path, html) {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
509
|
+
// Find which panel this content belongs to
|
|
510
|
+
var panelIndex = -1;
|
|
511
|
+
for (var i = 0; i < state.panels.length; i++) {
|
|
512
|
+
if (state.panels[i].file === path) {
|
|
513
|
+
panelIndex = i;
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
344
517
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
518
|
+
if (panelIndex === -1) {
|
|
519
|
+
// If not found, use active panel
|
|
520
|
+
panelIndex = state.activePanel;
|
|
521
|
+
state.panels[panelIndex].file = path;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (panelIndex === 0) {
|
|
525
|
+
welcome.classList.add('hidden');
|
|
526
|
+
content.classList.add('visible');
|
|
527
|
+
content.innerHTML = html;
|
|
528
|
+
panel1Filename.textContent = getFileName(path);
|
|
529
|
+
panel1Header.classList.add('visible');
|
|
530
|
+
} else if (panelIndex === 1 && panel2Content) {
|
|
531
|
+
panel2Content.innerHTML = html;
|
|
532
|
+
panel2Content.classList.add('visible');
|
|
533
|
+
panel2Filename.textContent = getFileName(path);
|
|
534
|
+
panel2Header.classList.add('visible');
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
updatePanelUI();
|
|
348
538
|
|
|
349
539
|
// Re-render MathJax if available
|
|
350
540
|
if (window.MathJax && window.MathJax.typeset) {
|
|
351
|
-
|
|
541
|
+
if (panelIndex === 0) {
|
|
542
|
+
window.MathJax.typeset([content]);
|
|
543
|
+
} else if (panel2Content) {
|
|
544
|
+
window.MathJax.typeset([panel2Content]);
|
|
545
|
+
}
|
|
352
546
|
}
|
|
353
547
|
}
|
|
354
548
|
|
|
549
|
+
/**
|
|
550
|
+
* Get file name from path
|
|
551
|
+
*/
|
|
552
|
+
function getFileName(path) {
|
|
553
|
+
return path.split('/').pop();
|
|
554
|
+
}
|
|
555
|
+
|
|
355
556
|
/**
|
|
356
557
|
* Show welcome screen
|
|
357
558
|
*/
|
|
358
|
-
function showWelcome() {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
559
|
+
function showWelcome(panelIndex) {
|
|
560
|
+
if (panelIndex === 0 || panelIndex === undefined) {
|
|
561
|
+
content.classList.remove('visible');
|
|
562
|
+
content.innerHTML = '';
|
|
563
|
+
welcome.classList.remove('hidden');
|
|
564
|
+
panel1Header.classList.remove('visible');
|
|
565
|
+
panel1Filename.textContent = '';
|
|
566
|
+
}
|
|
567
|
+
if (panelIndex === 1 && panel2Content) {
|
|
568
|
+
panel2Content.classList.remove('visible');
|
|
569
|
+
panel2Content.innerHTML = '';
|
|
570
|
+
panel2Header.classList.remove('visible');
|
|
571
|
+
panel2Filename.textContent = '';
|
|
572
|
+
}
|
|
362
573
|
}
|
|
363
574
|
|
|
364
575
|
/**
|
|
365
576
|
* Show error message
|
|
366
577
|
*/
|
|
367
|
-
function showError(
|
|
578
|
+
function showError(_type, message) {
|
|
368
579
|
welcome.classList.add('hidden');
|
|
369
580
|
content.classList.add('visible');
|
|
370
581
|
content.innerHTML = '<div class="vimd-error"><h2>Error</h2><p>' + escapeHtml(message) + '</p></div>';
|
|
@@ -384,8 +595,13 @@
|
|
|
384
595
|
/**
|
|
385
596
|
* Update URL without page reload
|
|
386
597
|
*/
|
|
387
|
-
function updateURL(
|
|
388
|
-
|
|
598
|
+
function updateURL() {
|
|
599
|
+
var path = state.panels[state.activePanel].file;
|
|
600
|
+
if (path) {
|
|
601
|
+
history.pushState({ path: path }, '', '/' + encodeURIComponent(path).replace(/%2F/g, '/'));
|
|
602
|
+
} else {
|
|
603
|
+
history.pushState({ path: '/' }, '', '/');
|
|
604
|
+
}
|
|
389
605
|
}
|
|
390
606
|
|
|
391
607
|
/**
|
|
@@ -393,12 +609,14 @@
|
|
|
393
609
|
*/
|
|
394
610
|
function handleInitialPath() {
|
|
395
611
|
var path = decodeURIComponent(location.pathname.slice(1));
|
|
396
|
-
if (path && path !== '') {
|
|
612
|
+
if (path && path !== '' && !state.panels[0].file) {
|
|
397
613
|
// Wait for tree to load then select file
|
|
398
614
|
var checkTree = setInterval(function() {
|
|
399
615
|
if (fileTree.length > 0) {
|
|
400
616
|
clearInterval(checkTree);
|
|
401
|
-
|
|
617
|
+
if (fileExists(path)) {
|
|
618
|
+
selectFile(path);
|
|
619
|
+
}
|
|
402
620
|
}
|
|
403
621
|
}, 100);
|
|
404
622
|
|
|
@@ -413,86 +631,373 @@
|
|
|
413
631
|
* Setup event listeners
|
|
414
632
|
*/
|
|
415
633
|
function setupEventListeners() {
|
|
416
|
-
// Sidebar
|
|
417
|
-
|
|
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) {
|
|
634
|
+
// Sidebar resizer
|
|
635
|
+
sidebarResizer.addEventListener('mousedown', function(e) {
|
|
439
636
|
e.preventDefault();
|
|
440
|
-
|
|
441
|
-
|
|
637
|
+
isSidebarResizing = true;
|
|
638
|
+
sidebarResizer.classList.add('active');
|
|
442
639
|
document.body.style.cursor = 'ew-resize';
|
|
443
640
|
document.body.style.userSelect = 'none';
|
|
444
641
|
});
|
|
445
642
|
|
|
446
643
|
document.addEventListener('mousemove', function(e) {
|
|
447
|
-
if (
|
|
644
|
+
if (isSidebarResizing) {
|
|
645
|
+
var width = e.clientX;
|
|
646
|
+
var minWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-min-width'), 10) || 150;
|
|
647
|
+
var maxWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-max-width'), 10) || 500;
|
|
448
648
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
649
|
+
if (width >= minWidth && width <= maxWidth) {
|
|
650
|
+
sidebar.style.width = width + 'px';
|
|
651
|
+
}
|
|
652
|
+
}
|
|
452
653
|
|
|
453
|
-
if (
|
|
454
|
-
|
|
654
|
+
if (isPanelResizing && panelResizer) {
|
|
655
|
+
var sidebarWidth = sidebar.offsetWidth;
|
|
656
|
+
var panelWidth = e.clientX - sidebarWidth;
|
|
657
|
+
var minPanelWidth = 100;
|
|
658
|
+
var maxPanelWidth = preview.offsetWidth - minPanelWidth - 4; // 4px for resizer
|
|
659
|
+
|
|
660
|
+
if (panelWidth >= minPanelWidth && panelWidth <= maxPanelWidth) {
|
|
661
|
+
panel1.style.width = panelWidth + 'px';
|
|
662
|
+
state.panelWidth = panelWidth;
|
|
663
|
+
}
|
|
455
664
|
}
|
|
456
665
|
});
|
|
457
666
|
|
|
458
667
|
document.addEventListener('mouseup', function() {
|
|
459
|
-
if (
|
|
460
|
-
|
|
461
|
-
|
|
668
|
+
if (isSidebarResizing) {
|
|
669
|
+
isSidebarResizing = false;
|
|
670
|
+
sidebarResizer.classList.remove('active');
|
|
462
671
|
document.body.style.cursor = '';
|
|
463
672
|
document.body.style.userSelect = '';
|
|
464
673
|
saveSidebarWidth();
|
|
465
674
|
}
|
|
675
|
+
|
|
676
|
+
if (isPanelResizing) {
|
|
677
|
+
isPanelResizing = false;
|
|
678
|
+
if (panelResizer) {
|
|
679
|
+
panelResizer.classList.remove('active');
|
|
680
|
+
}
|
|
681
|
+
document.body.style.cursor = '';
|
|
682
|
+
document.body.style.userSelect = '';
|
|
683
|
+
savePanelState();
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
// Panel 1 close button
|
|
688
|
+
panel1Close.addEventListener('click', function(e) {
|
|
689
|
+
e.stopPropagation();
|
|
690
|
+
closePanel(0);
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
// Panel 1 activation
|
|
694
|
+
panel1.addEventListener('click', function() {
|
|
695
|
+
setActivePanel(0);
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
// Context menu
|
|
699
|
+
document.addEventListener('click', function() {
|
|
700
|
+
hideContextMenu();
|
|
466
701
|
});
|
|
467
702
|
|
|
468
703
|
// Browser history
|
|
469
704
|
window.addEventListener('popstate', function(e) {
|
|
470
705
|
if (e.state && e.state.path) {
|
|
471
|
-
var path = e.state.path
|
|
472
|
-
if (path) {
|
|
473
|
-
|
|
706
|
+
var path = e.state.path;
|
|
707
|
+
if (path === '/') {
|
|
708
|
+
closePanel(state.activePanel);
|
|
474
709
|
} else {
|
|
475
|
-
|
|
476
|
-
|
|
710
|
+
path = path.replace(/^\//, '');
|
|
711
|
+
if (fileExists(path)) {
|
|
712
|
+
selectFile(path);
|
|
713
|
+
}
|
|
477
714
|
}
|
|
478
715
|
}
|
|
479
716
|
});
|
|
480
717
|
}
|
|
481
718
|
|
|
482
719
|
/**
|
|
483
|
-
*
|
|
720
|
+
* Set active panel
|
|
721
|
+
*/
|
|
722
|
+
function setActivePanel(index) {
|
|
723
|
+
if (state.activePanel !== index) {
|
|
724
|
+
state.activePanel = index;
|
|
725
|
+
updatePanelUI();
|
|
726
|
+
savePanelState();
|
|
727
|
+
updateURL();
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Close panel
|
|
733
|
+
*/
|
|
734
|
+
function closePanel(index) {
|
|
735
|
+
if (state.isSplitView) {
|
|
736
|
+
if (index === 0) {
|
|
737
|
+
// Move panel 2 to panel 1
|
|
738
|
+
state.panels[0].file = state.panels[1].file;
|
|
739
|
+
state.panels[1].file = null;
|
|
740
|
+
|
|
741
|
+
// Move content from panel 2 to panel 1
|
|
742
|
+
if (panel2Content && state.panels[0].file) {
|
|
743
|
+
content.innerHTML = panel2Content.innerHTML;
|
|
744
|
+
content.classList.add('visible');
|
|
745
|
+
welcome.classList.add('hidden');
|
|
746
|
+
panel1Filename.textContent = getFileName(state.panels[0].file);
|
|
747
|
+
panel1Header.classList.add('visible');
|
|
748
|
+
} else {
|
|
749
|
+
showWelcome(0);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Close split view
|
|
754
|
+
closeSplitView();
|
|
755
|
+
} else {
|
|
756
|
+
// Single panel - show welcome
|
|
757
|
+
state.panels[0].file = null;
|
|
758
|
+
showWelcome(0);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Update sidebar selection and save state
|
|
762
|
+
updateSelection();
|
|
763
|
+
savePanelState();
|
|
764
|
+
updateURL();
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Close split view
|
|
769
|
+
*/
|
|
770
|
+
function closeSplitView() {
|
|
771
|
+
state.isSplitView = false;
|
|
772
|
+
state.panels[1].file = null;
|
|
773
|
+
state.activePanel = 0;
|
|
774
|
+
|
|
775
|
+
// Remove panel 2
|
|
776
|
+
if (panel2) {
|
|
777
|
+
panel2.remove();
|
|
778
|
+
panel2 = null;
|
|
779
|
+
panel2Header = null;
|
|
780
|
+
panel2Filename = null;
|
|
781
|
+
panel2Close = null;
|
|
782
|
+
panel2Content = null;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Remove resizer
|
|
786
|
+
if (panelResizer) {
|
|
787
|
+
panelResizer.remove();
|
|
788
|
+
panelResizer = null;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// Reset panel 1 to single panel mode
|
|
792
|
+
panel1.classList.remove('split-panel1');
|
|
793
|
+
panel1.style.width = '';
|
|
794
|
+
|
|
795
|
+
updatePanelUI();
|
|
796
|
+
savePanelState();
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Create panel 2 for split view
|
|
801
|
+
*/
|
|
802
|
+
function createPanel2() {
|
|
803
|
+
if (panel2) return;
|
|
804
|
+
|
|
805
|
+
// Create resizer
|
|
806
|
+
panelResizer = document.createElement('div');
|
|
807
|
+
panelResizer.className = 'vimd-panel-resizer visible';
|
|
808
|
+
panelResizer.addEventListener('mousedown', function(e) {
|
|
809
|
+
e.preventDefault();
|
|
810
|
+
isPanelResizing = true;
|
|
811
|
+
panelResizer.classList.add('active');
|
|
812
|
+
document.body.style.cursor = 'ew-resize';
|
|
813
|
+
document.body.style.userSelect = 'none';
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
// Create panel 2
|
|
817
|
+
panel2 = document.createElement('div');
|
|
818
|
+
panel2.className = 'vimd-panel split-panel2';
|
|
819
|
+
panel2.id = 'panel2';
|
|
820
|
+
|
|
821
|
+
panel2Header = document.createElement('div');
|
|
822
|
+
panel2Header.className = 'vimd-panel-header';
|
|
823
|
+
panel2Header.id = 'panel2-header';
|
|
824
|
+
|
|
825
|
+
panel2Filename = document.createElement('span');
|
|
826
|
+
panel2Filename.className = 'vimd-panel-filename';
|
|
827
|
+
panel2Filename.id = 'panel2-filename';
|
|
828
|
+
|
|
829
|
+
panel2Close = document.createElement('button');
|
|
830
|
+
panel2Close.className = 'vimd-panel-close';
|
|
831
|
+
panel2Close.id = 'panel2-close';
|
|
832
|
+
panel2Close.title = '閉じる';
|
|
833
|
+
panel2Close.innerHTML = '×';
|
|
834
|
+
panel2Close.addEventListener('click', function(e) {
|
|
835
|
+
e.stopPropagation();
|
|
836
|
+
closePanel(1);
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
panel2Header.appendChild(panel2Filename);
|
|
840
|
+
panel2Header.appendChild(panel2Close);
|
|
841
|
+
|
|
842
|
+
var panel2Body = document.createElement('div');
|
|
843
|
+
panel2Body.className = 'vimd-panel-body';
|
|
844
|
+
|
|
845
|
+
panel2Content = document.createElement('article');
|
|
846
|
+
panel2Content.className = 'vimd-content markdown-body';
|
|
847
|
+
panel2Content.id = 'content2';
|
|
848
|
+
|
|
849
|
+
panel2Body.appendChild(panel2Content);
|
|
850
|
+
panel2.appendChild(panel2Header);
|
|
851
|
+
panel2.appendChild(panel2Body);
|
|
852
|
+
|
|
853
|
+
// Panel 2 activation
|
|
854
|
+
panel2.addEventListener('click', function() {
|
|
855
|
+
setActivePanel(1);
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
// Insert into DOM
|
|
859
|
+
preview.appendChild(panelResizer);
|
|
860
|
+
preview.appendChild(panel2);
|
|
861
|
+
|
|
862
|
+
// Set panel 1 to split mode
|
|
863
|
+
panel1.classList.add('split-panel1');
|
|
864
|
+
panel1.style.width = state.panelWidth + 'px';
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Open split view
|
|
869
|
+
*/
|
|
870
|
+
function openSplitView(path) {
|
|
871
|
+
if (!state.isSplitView) {
|
|
872
|
+
state.isSplitView = true;
|
|
873
|
+
state.panelWidth = 720; // Reset to default
|
|
874
|
+
createPanel2();
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
openFileInPanel(path, 1);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* Show context menu
|
|
882
|
+
*/
|
|
883
|
+
function showContextMenu(e, path) {
|
|
884
|
+
contextTarget = path;
|
|
885
|
+
|
|
886
|
+
// Update menu items visibility
|
|
887
|
+
var openItem = contextMenu.querySelector('[data-action="open"]');
|
|
888
|
+
var openSplitItem = contextMenu.querySelector('[data-action="open-split"]');
|
|
889
|
+
var openPanel1Item = contextMenu.querySelector('[data-action="open-panel1"]');
|
|
890
|
+
var openPanel2Item = contextMenu.querySelector('[data-action="open-panel2"]');
|
|
891
|
+
|
|
892
|
+
if (state.isSplitView) {
|
|
893
|
+
// 2 panels mode
|
|
894
|
+
openItem.classList.add('hidden');
|
|
895
|
+
openSplitItem.classList.add('hidden');
|
|
896
|
+
openPanel1Item.classList.remove('hidden');
|
|
897
|
+
openPanel2Item.classList.remove('hidden');
|
|
898
|
+
} else if (state.panels[0].file) {
|
|
899
|
+
// 1 panel with file
|
|
900
|
+
openItem.classList.remove('hidden');
|
|
901
|
+
openSplitItem.classList.remove('hidden');
|
|
902
|
+
openPanel1Item.classList.add('hidden');
|
|
903
|
+
openPanel2Item.classList.add('hidden');
|
|
904
|
+
} else {
|
|
905
|
+
// Welcome screen
|
|
906
|
+
openItem.classList.remove('hidden');
|
|
907
|
+
openSplitItem.classList.add('hidden');
|
|
908
|
+
openPanel1Item.classList.add('hidden');
|
|
909
|
+
openPanel2Item.classList.add('hidden');
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Position menu
|
|
913
|
+
contextMenu.style.left = e.clientX + 'px';
|
|
914
|
+
contextMenu.style.top = e.clientY + 'px';
|
|
915
|
+
contextMenu.classList.add('visible');
|
|
916
|
+
|
|
917
|
+
// Adjust position if off-screen
|
|
918
|
+
var rect = contextMenu.getBoundingClientRect();
|
|
919
|
+
if (rect.right > window.innerWidth) {
|
|
920
|
+
contextMenu.style.left = (window.innerWidth - rect.width - 5) + 'px';
|
|
921
|
+
}
|
|
922
|
+
if (rect.bottom > window.innerHeight) {
|
|
923
|
+
contextMenu.style.top = (window.innerHeight - rect.height - 5) + 'px';
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Hide context menu
|
|
484
929
|
*/
|
|
485
|
-
function
|
|
486
|
-
|
|
487
|
-
|
|
930
|
+
function hideContextMenu() {
|
|
931
|
+
contextMenu.classList.remove('visible');
|
|
932
|
+
contextTarget = null;
|
|
488
933
|
}
|
|
489
934
|
|
|
490
935
|
/**
|
|
491
|
-
*
|
|
936
|
+
* Handle context menu action
|
|
492
937
|
*/
|
|
493
|
-
function
|
|
494
|
-
|
|
495
|
-
|
|
938
|
+
function handleContextAction(action) {
|
|
939
|
+
if (!contextTarget) return;
|
|
940
|
+
|
|
941
|
+
switch (action) {
|
|
942
|
+
case 'open':
|
|
943
|
+
selectFile(contextTarget);
|
|
944
|
+
break;
|
|
945
|
+
case 'open-split':
|
|
946
|
+
openSplitView(contextTarget);
|
|
947
|
+
break;
|
|
948
|
+
case 'open-panel1':
|
|
949
|
+
openFileInPanel(contextTarget, 0);
|
|
950
|
+
break;
|
|
951
|
+
case 'open-panel2':
|
|
952
|
+
openFileInPanel(contextTarget, 1);
|
|
953
|
+
break;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
hideContextMenu();
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Setup context menu item click handlers
|
|
960
|
+
contextMenu.addEventListener('click', function(e) {
|
|
961
|
+
var item = e.target.closest('.vimd-context-item');
|
|
962
|
+
if (item) {
|
|
963
|
+
var action = item.getAttribute('data-action');
|
|
964
|
+
handleContextAction(action);
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Update panel UI based on state
|
|
970
|
+
*/
|
|
971
|
+
function updatePanelUI() {
|
|
972
|
+
// Update active panel indicator
|
|
973
|
+
panel1Header.classList.remove('active');
|
|
974
|
+
if (panel2Header) {
|
|
975
|
+
panel2Header.classList.remove('active');
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
if (state.isSplitView) {
|
|
979
|
+
if (state.activePanel === 0) {
|
|
980
|
+
panel1Header.classList.add('active');
|
|
981
|
+
} else if (panel2Header) {
|
|
982
|
+
panel2Header.classList.add('active');
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// Update panel 1 header visibility
|
|
987
|
+
if (state.panels[0].file) {
|
|
988
|
+
panel1Header.classList.add('visible');
|
|
989
|
+
} else {
|
|
990
|
+
panel1Header.classList.remove('visible');
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// Update panel 2 header visibility
|
|
994
|
+
if (panel2Header) {
|
|
995
|
+
if (state.panels[1].file) {
|
|
996
|
+
panel2Header.classList.add('visible');
|
|
997
|
+
} else {
|
|
998
|
+
panel2Header.classList.remove('visible');
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
496
1001
|
}
|
|
497
1002
|
|
|
498
1003
|
/**
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<meta name="generator" content="vimd">
|
|
7
7
|
<title>vimd - {{folder_name}}</title>
|
|
8
|
-
<style>
|
|
9
|
-
{{folder_mode_css}}
|
|
10
|
-
</style>
|
|
11
8
|
<style id="theme-styles">
|
|
12
9
|
{{theme_css}}
|
|
13
10
|
</style>
|
|
11
|
+
<style>
|
|
12
|
+
{{folder_mode_css}}
|
|
13
|
+
</style>
|
|
14
14
|
{{#if math_enabled}}
|
|
15
15
|
<style>
|
|
16
16
|
/* Block math centering */
|
|
@@ -40,24 +40,10 @@
|
|
|
40
40
|
</head>
|
|
41
41
|
<body>
|
|
42
42
|
<div class="vimd-container">
|
|
43
|
-
<!-- Sidebar toggle bar (visible when sidebar is collapsed) -->
|
|
44
|
-
<div class="vimd-sidebar-toggle-bar" id="toggle-bar">
|
|
45
|
-
<button class="vimd-toggle-btn" id="toggle-btn-collapsed" title="Open Sidebar (Ctrl+B)">
|
|
46
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
47
|
-
<path d="M6 2L10 8L6 14" stroke="currentColor" stroke-width="1.5" fill="none"/>
|
|
48
|
-
</svg>
|
|
49
|
-
</button>
|
|
50
|
-
</div>
|
|
51
|
-
|
|
52
43
|
<!-- Sidebar -->
|
|
53
44
|
<aside class="vimd-sidebar" id="sidebar">
|
|
54
45
|
<div class="vimd-sidebar-header">
|
|
55
46
|
<span class="vimd-folder-name">エクスプローラー</span>
|
|
56
|
-
<button class="vimd-toggle-btn" id="toggle-btn" title="Close Sidebar (Ctrl+B)">
|
|
57
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
58
|
-
<path d="M10 2L6 8L10 14" stroke="currentColor" stroke-width="1.5" fill="none"/>
|
|
59
|
-
</svg>
|
|
60
|
-
</button>
|
|
61
47
|
</div>
|
|
62
48
|
<div class="vimd-tree" id="file-tree">
|
|
63
49
|
<!-- File tree will be rendered by JavaScript -->
|
|
@@ -67,18 +53,35 @@
|
|
|
67
53
|
|
|
68
54
|
<!-- Preview -->
|
|
69
55
|
<main class="vimd-preview" id="preview">
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<
|
|
56
|
+
<!-- Panel 1 -->
|
|
57
|
+
<div class="vimd-panel" id="panel1">
|
|
58
|
+
<div class="vimd-panel-header" id="panel1-header">
|
|
59
|
+
<span class="vimd-panel-filename" id="panel1-filename"></span>
|
|
60
|
+
<button class="vimd-panel-close" id="panel1-close" title="閉じる">×</button>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="vimd-panel-body">
|
|
63
|
+
<div class="vimd-welcome" id="welcome">
|
|
64
|
+
<div class="vimd-welcome-content">
|
|
65
|
+
<h1>vimd</h1>
|
|
66
|
+
<p id="welcome-message">Select a file from the sidebar</p>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<article class="vimd-content markdown-body" id="content">
|
|
70
|
+
<!-- File content will be rendered here -->
|
|
71
|
+
</article>
|
|
74
72
|
</div>
|
|
75
73
|
</div>
|
|
76
|
-
<article class="vimd-content markdown-body" id="content">
|
|
77
|
-
<!-- File content will be rendered here -->
|
|
78
|
-
</article>
|
|
79
74
|
</main>
|
|
80
75
|
</div>
|
|
81
76
|
|
|
77
|
+
<!-- Context Menu -->
|
|
78
|
+
<div class="vimd-context-menu" id="context-menu">
|
|
79
|
+
<div class="vimd-context-item" data-action="open">開く</div>
|
|
80
|
+
<div class="vimd-context-item" data-action="open-split">分割画面で開く</div>
|
|
81
|
+
<div class="vimd-context-item" data-action="open-panel1">パネル1で開く</div>
|
|
82
|
+
<div class="vimd-context-item" data-action="open-panel2">パネル2で開く</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
82
85
|
<script>
|
|
83
86
|
{{folder_mode_js}}
|
|
84
87
|
</script>
|
package/package.json
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<meta name="generator" content="vimd">
|
|
7
7
|
<title>vimd - {{folder_name}}</title>
|
|
8
|
-
<style>
|
|
9
|
-
{{folder_mode_css}}
|
|
10
|
-
</style>
|
|
11
8
|
<style id="theme-styles">
|
|
12
9
|
{{theme_css}}
|
|
13
10
|
</style>
|
|
11
|
+
<style>
|
|
12
|
+
{{folder_mode_css}}
|
|
13
|
+
</style>
|
|
14
14
|
{{#if math_enabled}}
|
|
15
15
|
<style>
|
|
16
16
|
/* Block math centering */
|
|
@@ -40,24 +40,10 @@
|
|
|
40
40
|
</head>
|
|
41
41
|
<body>
|
|
42
42
|
<div class="vimd-container">
|
|
43
|
-
<!-- Sidebar toggle bar (visible when sidebar is collapsed) -->
|
|
44
|
-
<div class="vimd-sidebar-toggle-bar" id="toggle-bar">
|
|
45
|
-
<button class="vimd-toggle-btn" id="toggle-btn-collapsed" title="Open Sidebar (Ctrl+B)">
|
|
46
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
47
|
-
<path d="M6 2L10 8L6 14" stroke="currentColor" stroke-width="1.5" fill="none"/>
|
|
48
|
-
</svg>
|
|
49
|
-
</button>
|
|
50
|
-
</div>
|
|
51
|
-
|
|
52
43
|
<!-- Sidebar -->
|
|
53
44
|
<aside class="vimd-sidebar" id="sidebar">
|
|
54
45
|
<div class="vimd-sidebar-header">
|
|
55
46
|
<span class="vimd-folder-name">エクスプローラー</span>
|
|
56
|
-
<button class="vimd-toggle-btn" id="toggle-btn" title="Close Sidebar (Ctrl+B)">
|
|
57
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
58
|
-
<path d="M10 2L6 8L10 14" stroke="currentColor" stroke-width="1.5" fill="none"/>
|
|
59
|
-
</svg>
|
|
60
|
-
</button>
|
|
61
47
|
</div>
|
|
62
48
|
<div class="vimd-tree" id="file-tree">
|
|
63
49
|
<!-- File tree will be rendered by JavaScript -->
|
|
@@ -67,18 +53,35 @@
|
|
|
67
53
|
|
|
68
54
|
<!-- Preview -->
|
|
69
55
|
<main class="vimd-preview" id="preview">
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<
|
|
56
|
+
<!-- Panel 1 -->
|
|
57
|
+
<div class="vimd-panel" id="panel1">
|
|
58
|
+
<div class="vimd-panel-header" id="panel1-header">
|
|
59
|
+
<span class="vimd-panel-filename" id="panel1-filename"></span>
|
|
60
|
+
<button class="vimd-panel-close" id="panel1-close" title="閉じる">×</button>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="vimd-panel-body">
|
|
63
|
+
<div class="vimd-welcome" id="welcome">
|
|
64
|
+
<div class="vimd-welcome-content">
|
|
65
|
+
<h1>vimd</h1>
|
|
66
|
+
<p id="welcome-message">Select a file from the sidebar</p>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<article class="vimd-content markdown-body" id="content">
|
|
70
|
+
<!-- File content will be rendered here -->
|
|
71
|
+
</article>
|
|
74
72
|
</div>
|
|
75
73
|
</div>
|
|
76
|
-
<article class="vimd-content markdown-body" id="content">
|
|
77
|
-
<!-- File content will be rendered here -->
|
|
78
|
-
</article>
|
|
79
74
|
</main>
|
|
80
75
|
</div>
|
|
81
76
|
|
|
77
|
+
<!-- Context Menu -->
|
|
78
|
+
<div class="vimd-context-menu" id="context-menu">
|
|
79
|
+
<div class="vimd-context-item" data-action="open">開く</div>
|
|
80
|
+
<div class="vimd-context-item" data-action="open-split">分割画面で開く</div>
|
|
81
|
+
<div class="vimd-context-item" data-action="open-panel1">パネル1で開く</div>
|
|
82
|
+
<div class="vimd-context-item" data-action="open-panel2">パネル2で開く</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
82
85
|
<script>
|
|
83
86
|
{{folder_mode_js}}
|
|
84
87
|
</script>
|