mbeditor 0.5.3 → 0.7.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +77 -0
- data/README.md +7 -0
- data/app/assets/javascripts/mbeditor/application.js +3 -0
- data/app/assets/javascripts/mbeditor/components/ChangelogView.js +145 -0
- data/app/assets/javascripts/mbeditor/components/DiffViewer.js +1 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +359 -31
- data/app/assets/javascripts/mbeditor/components/FileTree.js +177 -116
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +952 -143
- data/app/assets/javascripts/mbeditor/components/TabBar.js +9 -0
- data/app/assets/javascripts/mbeditor/conflict_parser.js +48 -0
- data/app/assets/javascripts/mbeditor/editor_plugins.js +420 -67
- data/app/assets/javascripts/mbeditor/editor_store.js +1 -0
- data/app/assets/javascripts/mbeditor/file_service.js +34 -6
- data/app/assets/javascripts/mbeditor/git_service.js +2 -1
- data/app/assets/javascripts/mbeditor/history_service.js +177 -0
- data/app/assets/javascripts/mbeditor/search_service.js +1 -0
- data/app/assets/javascripts/mbeditor/tab_manager.js +8 -5
- data/app/assets/stylesheets/mbeditor/application.css +112 -0
- data/app/assets/stylesheets/mbeditor/editor.css +443 -78
- data/app/channels/mbeditor/editor_channel.rb +5 -41
- data/app/controllers/mbeditor/application_controller.rb +8 -1
- data/app/controllers/mbeditor/editors_controller.rb +276 -654
- data/app/controllers/mbeditor/git_controller.rb +2 -61
- data/app/services/mbeditor/availability_probe.rb +83 -0
- data/app/services/mbeditor/code_search_service.rb +42 -0
- data/app/services/mbeditor/editor_state_service.rb +91 -0
- data/app/services/mbeditor/exclusion_matcher.rb +23 -0
- data/app/services/mbeditor/file_operation_service.rb +68 -0
- data/app/services/mbeditor/file_tree_service.rb +69 -0
- data/app/services/mbeditor/git_combined_diff_service.rb +43 -0
- data/app/services/mbeditor/git_commit_detail_service.rb +46 -0
- data/app/services/mbeditor/git_info_service.rb +151 -0
- data/app/services/mbeditor/git_service.rb +36 -26
- data/app/services/mbeditor/js_definition_service.rb +59 -0
- data/app/services/mbeditor/js_members_service.rb +62 -0
- data/app/services/mbeditor/process_runner.rb +48 -0
- data/app/services/mbeditor/rails_related_files_service.rb +282 -0
- data/app/services/mbeditor/ruby_definition_service.rb +77 -101
- data/app/services/mbeditor/schema_service.rb +270 -0
- data/app/services/mbeditor/search_replace_service.rb +184 -0
- data/app/services/mbeditor/test_runner_service.rb +5 -27
- data/app/views/layouts/mbeditor/application.html.erb +2 -2
- data/config/routes.rb +8 -1
- data/lib/mbeditor/configuration.rb +4 -2
- data/lib/mbeditor/version.rb +1 -1
- data/public/monaco-editor/vs/language/css/cssMode.js +13 -0
- data/public/monaco-editor/vs/language/css/cssWorker.js +77 -0
- data/public/monaco-editor/vs/language/html/htmlMode.js +13 -0
- data/public/monaco-editor/vs/language/html/htmlWorker.js +454 -0
- data/public/monaco-editor/vs/language/json/jsonMode.js +19 -0
- data/public/monaco-editor/vs/language/json/jsonWorker.js +42 -0
- metadata +26 -3
- data/app/services/mbeditor/unused_methods_service.rb +0 -139
|
@@ -63,18 +63,53 @@ var FileTree = function FileTree(_ref) {
|
|
|
63
63
|
// Ref that always points to the latest onNodeSelect prop, avoiding stale closures in the effect.
|
|
64
64
|
var onNodeSelectRef = useRef(onNodeSelect);
|
|
65
65
|
onNodeSelectRef.current = onNodeSelect;
|
|
66
|
+
// Refs that track pending create/rename state for the typeahead guard.
|
|
67
|
+
// Since these props are not in the effect's dependency array, we use refs to
|
|
68
|
+
// always read the current values without stale closures.
|
|
69
|
+
var pendingCreateRef = useRef(pendingCreate);
|
|
70
|
+
var pendingRenameRef = useRef(pendingRename);
|
|
71
|
+
pendingCreateRef.current = pendingCreate;
|
|
72
|
+
pendingRenameRef.current = pendingRename;
|
|
66
73
|
// Tracks whether the user's most recent mousedown was inside the sidebar.
|
|
67
74
|
// Monaco re-steals keyboard focus after any click so e.target on keydown is
|
|
68
75
|
// always Monaco's textarea — the only reliable "is the user in the explorer?"
|
|
69
76
|
// signal is where they last clicked.
|
|
70
77
|
var sidebarActiveRef = useRef(false);
|
|
71
78
|
|
|
79
|
+
// Virtual scroll state
|
|
80
|
+
var ROW_HEIGHT = 22;
|
|
81
|
+
var BUFFER = 5;
|
|
82
|
+
var flatItemsRef = useRef([]);
|
|
83
|
+
var scrollParentRef = useRef(null);
|
|
84
|
+
|
|
85
|
+
var _scrollState = useState(0);
|
|
86
|
+
var scrollTop = _scrollState[0];
|
|
87
|
+
var setScrollTop = _scrollState[1];
|
|
88
|
+
|
|
89
|
+
var _heightState = useState(400);
|
|
90
|
+
var containerHeight = _heightState[0];
|
|
91
|
+
var setContainerHeight = _heightState[1];
|
|
92
|
+
|
|
93
|
+
// Scroll item at idx into view via the parent scroll container
|
|
94
|
+
var scrollToItem = function scrollToItem(idx) {
|
|
95
|
+
var parent = scrollParentRef.current;
|
|
96
|
+
if (!parent || !containerRef.current) return;
|
|
97
|
+
var parentTop = parent.getBoundingClientRect().top;
|
|
98
|
+
var treeTop = containerRef.current.getBoundingClientRect().top;
|
|
99
|
+
var treeOffset = parent.scrollTop + (treeTop - parentTop);
|
|
100
|
+
var itemAbsTop = treeOffset + idx * ROW_HEIGHT;
|
|
101
|
+
var itemAbsBottom = itemAbsTop + ROW_HEIGHT;
|
|
102
|
+
if (itemAbsTop < parent.scrollTop || itemAbsBottom > parent.scrollTop + parent.clientHeight) {
|
|
103
|
+
parent.scrollTop = Math.max(0, itemAbsTop - Math.floor((parent.clientHeight - ROW_HEIGHT) / 2));
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
72
107
|
// Scroll the highlighted node into view when anchorPath changes (e.g. Find in Explorer)
|
|
73
108
|
useEffect(function () {
|
|
74
|
-
if (!anchorPath
|
|
109
|
+
if (!anchorPath) return;
|
|
75
110
|
var timer = setTimeout(function () {
|
|
76
|
-
var
|
|
77
|
-
if (
|
|
111
|
+
var idx = flatItemsRef.current.findIndex(function(item) { return item.kind === 'node' && item.node.path === anchorPath; });
|
|
112
|
+
if (idx >= 0) scrollToItem(idx);
|
|
78
113
|
}, 60);
|
|
79
114
|
return function () { clearTimeout(timer); };
|
|
80
115
|
}, [anchorPath]);
|
|
@@ -97,15 +132,8 @@ var FileTree = function FileTree(_ref) {
|
|
|
97
132
|
|
|
98
133
|
// After the DOM updates, scroll the active item into view only if not already visible
|
|
99
134
|
var timer = setTimeout(function () {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (el) {
|
|
103
|
-
var elRect = el.getBoundingClientRect();
|
|
104
|
-
var containerRect = containerRef.current.getBoundingClientRect();
|
|
105
|
-
if (elRect.top < containerRect.top || elRect.bottom > containerRect.bottom) {
|
|
106
|
-
el.scrollIntoView({ block: 'nearest' });
|
|
107
|
-
}
|
|
108
|
-
}
|
|
135
|
+
var idx = flatItemsRef.current.findIndex(function(item) { return item.kind === 'node' && item.node.path === activePath; });
|
|
136
|
+
if (idx >= 0) scrollToItem(idx);
|
|
109
137
|
}, 80);
|
|
110
138
|
return function () { clearTimeout(timer); };
|
|
111
139
|
}, [activePath]);
|
|
@@ -134,6 +162,8 @@ var FileTree = function FileTree(_ref) {
|
|
|
134
162
|
if (pendingCreate) {
|
|
135
163
|
setInlineValue('');
|
|
136
164
|
committedRef.current = false;
|
|
165
|
+
var createIdx = flatItemsRef.current.findIndex(function(item) { return item.kind === 'create'; });
|
|
166
|
+
if (createIdx >= 0) scrollToItem(createIdx);
|
|
137
167
|
setTimeout(function () {
|
|
138
168
|
if (inlineRef.current) inlineRef.current.focus();
|
|
139
169
|
}, 0);
|
|
@@ -146,6 +176,35 @@ var FileTree = function FileTree(_ref) {
|
|
|
146
176
|
return function() { clearTimeout(hoverTimerRef.current); };
|
|
147
177
|
}, []);
|
|
148
178
|
|
|
179
|
+
// Track the parent scroll container (.ide-sidebar-scrollable) for virtual scroll.
|
|
180
|
+
// The file-tree-root itself does not scroll — the parent does.
|
|
181
|
+
useEffect(function() {
|
|
182
|
+
if (!containerRef.current) return;
|
|
183
|
+
var parent = containerRef.current.closest('.ide-sidebar-scrollable');
|
|
184
|
+
if (!parent) return;
|
|
185
|
+
scrollParentRef.current = parent;
|
|
186
|
+
|
|
187
|
+
var update = function() {
|
|
188
|
+
if (!containerRef.current || !parent) return;
|
|
189
|
+
var parentRect = parent.getBoundingClientRect();
|
|
190
|
+
var treeRect = containerRef.current.getBoundingClientRect();
|
|
191
|
+
var treeOffset = parent.scrollTop + (treeRect.top - parentRect.top);
|
|
192
|
+
setScrollTop(Math.max(0, parent.scrollTop - treeOffset));
|
|
193
|
+
setContainerHeight(parent.clientHeight);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
parent.addEventListener('scroll', update, { passive: true });
|
|
197
|
+
var obs = new ResizeObserver(update);
|
|
198
|
+
obs.observe(parent);
|
|
199
|
+
obs.observe(containerRef.current);
|
|
200
|
+
update();
|
|
201
|
+
|
|
202
|
+
return function() {
|
|
203
|
+
parent.removeEventListener('scroll', update);
|
|
204
|
+
obs.disconnect();
|
|
205
|
+
};
|
|
206
|
+
}, []);
|
|
207
|
+
|
|
149
208
|
var toggleFolder = function toggleFolder(path, e) {
|
|
150
209
|
e.stopPropagation();
|
|
151
210
|
var next = !(expandedDirs && expandedDirs[path]);
|
|
@@ -186,22 +245,10 @@ var FileTree = function FileTree(_ref) {
|
|
|
186
245
|
};
|
|
187
246
|
|
|
188
247
|
// Returns all visible paths in depth-first render order (for shift+click range select)
|
|
189
|
-
var computeVisiblePaths = function computeVisiblePaths(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (a.type !== b.type) return a.type === 'folder' ? -1 : 1;
|
|
194
|
-
return a.name.localeCompare(b.name);
|
|
195
|
-
});
|
|
196
|
-
sorted.forEach(function (node) {
|
|
197
|
-
paths.push(node.path);
|
|
198
|
-
if (node.type === 'folder' && expandedDirs && expandedDirs[node.path] && node.children) {
|
|
199
|
-
visit(node.children);
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
};
|
|
203
|
-
visit(nodes);
|
|
204
|
-
return paths;
|
|
248
|
+
var computeVisiblePaths = function computeVisiblePaths(_nodes) {
|
|
249
|
+
return flatItemsRef.current
|
|
250
|
+
.filter(function(item) { return item.kind === 'node'; })
|
|
251
|
+
.map(function(item) { return item.node.path; });
|
|
205
252
|
};
|
|
206
253
|
|
|
207
254
|
// Global type-ahead: tracks the last mousedown to know if the user is "in the explorer",
|
|
@@ -219,6 +266,8 @@ var FileTree = function FileTree(_ref) {
|
|
|
219
266
|
if (!typeaheadEnabled) return;
|
|
220
267
|
if (!sidebarActiveRef.current) return;
|
|
221
268
|
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
269
|
+
// Prevent typeahead from jumping files while typing a new name in inline create/rename.
|
|
270
|
+
if (pendingCreateRef.current || pendingRenameRef.current) return;
|
|
222
271
|
if (e.key.length !== 1) return;
|
|
223
272
|
// Still skip when an inline rename/create input is the actual focused element.
|
|
224
273
|
if (e.target && e.target.tagName === 'INPUT') return;
|
|
@@ -232,17 +281,14 @@ var FileTree = function FileTree(_ref) {
|
|
|
232
281
|
}, 600);
|
|
233
282
|
|
|
234
283
|
var prefix = typeaheadBufferRef.current;
|
|
235
|
-
var
|
|
236
|
-
for (var i = 0; i <
|
|
237
|
-
var
|
|
238
|
-
if (
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
var nodeType = allItems[i].getAttribute('data-type');
|
|
244
|
-
if (nodePath && onNodeSelectRef.current) {
|
|
245
|
-
onNodeSelectRef.current({ path: nodePath, name: nodeName || nodePath.split('/').pop(), type: nodeType || 'file' });
|
|
284
|
+
var flat = flatItemsRef.current;
|
|
285
|
+
for (var i = 0; i < flat.length; i++) {
|
|
286
|
+
var fItem = flat[i];
|
|
287
|
+
if (fItem.kind !== 'node') continue;
|
|
288
|
+
if (fItem.node.name.toLowerCase().indexOf(prefix) === 0) {
|
|
289
|
+
scrollToItem(i);
|
|
290
|
+
if (onNodeSelectRef.current) {
|
|
291
|
+
onNodeSelectRef.current({ path: fItem.node.path, name: fItem.node.name, type: fItem.node.type });
|
|
246
292
|
}
|
|
247
293
|
break;
|
|
248
294
|
}
|
|
@@ -348,60 +394,93 @@ var FileTree = function FileTree(_ref) {
|
|
|
348
394
|
);
|
|
349
395
|
};
|
|
350
396
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
397
|
+
// Flatten the visible tree into a linear array for virtual scrolling.
|
|
398
|
+
var flattenVisible = function flattenVisible(nodes, depth, parentPath) {
|
|
399
|
+
var result = [];
|
|
400
|
+
var sorted = [].concat(_toConsumableArray(nodes)).sort(function(a, b) {
|
|
401
|
+
if (a.type !== b.type) return a.type === 'folder' ? -1 : 1;
|
|
354
402
|
return a.name.localeCompare(b.name);
|
|
355
403
|
});
|
|
404
|
+
sorted.forEach(function(node) {
|
|
405
|
+
result.push({ kind: 'node', node: node, depth: depth });
|
|
406
|
+
if (node.type === 'folder' && expandedDirs && expandedDirs[node.path] && node.children) {
|
|
407
|
+
var children = flattenVisible(node.children, depth + 1, node.path);
|
|
408
|
+
for (var ci = 0; ci < children.length; ci++) result.push(children[ci]);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
if (pendingCreate && pendingCreate.parentPath === parentPath) {
|
|
412
|
+
result.push({ kind: 'create', depth: depth });
|
|
413
|
+
}
|
|
414
|
+
return result;
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
var flatItems = flattenVisible(items || [], 0, '');
|
|
418
|
+
flatItemsRef.current = flatItems;
|
|
419
|
+
|
|
420
|
+
var totalHeight = flatItems.length * ROW_HEIGHT;
|
|
421
|
+
var startIdx = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - BUFFER);
|
|
422
|
+
var endIdx = Math.min(flatItems.length - 1, Math.ceil((scrollTop + containerHeight) / ROW_HEIGHT) + BUFFER);
|
|
356
423
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
var isExpanded = !!(expandedDirs && expandedDirs[node.path]);
|
|
360
|
-
var isRenamingThisNode = !!(pendingRename && pendingRename.path === node.path);
|
|
361
|
-
var isOpenFile = activePath === node.path;
|
|
362
|
-
var isSelected = !!(selectedPaths && selectedPaths.has(node.path));
|
|
363
|
-
var isDragOver = isFolder && dragOverFolder === node.path;
|
|
364
|
-
var status = getGitStatus(node.path);
|
|
365
|
-
var statusMeta = getTreeStatusMeta(status);
|
|
366
|
-
var isModified = statusMeta && (statusMeta.cssKey === "M" || statusMeta.cssKey === "A");
|
|
367
|
-
|
|
368
|
-
var classNames = 'tree-item' +
|
|
369
|
-
(isOpenFile ? ' active' : '') +
|
|
370
|
-
(isSelected ? ' selected' : '') +
|
|
371
|
-
(isModified ? ' modified' : '') +
|
|
372
|
-
(isDragOver ? ' drag-over' : '');
|
|
424
|
+
var renderRow = function renderRow(item, idx) {
|
|
425
|
+
var indentPx = 8 + item.depth * 12;
|
|
373
426
|
|
|
427
|
+
if (item.kind === 'create') {
|
|
374
428
|
return React.createElement(
|
|
375
429
|
'div',
|
|
376
|
-
{ key:
|
|
377
|
-
|
|
430
|
+
{ key: '__inline-create__', style: { position: 'absolute', top: idx * ROW_HEIGHT, left: 0, right: 0 } },
|
|
431
|
+
React.createElement('div', { style: { paddingLeft: indentPx } }, renderInlineRow())
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
var node = item.node;
|
|
436
|
+
var isFolder = node.type === 'folder';
|
|
437
|
+
var isExpanded = !!(expandedDirs && expandedDirs[node.path]);
|
|
438
|
+
var isRenamingThisNode = !!(pendingRename && pendingRename.path === node.path);
|
|
439
|
+
var isOpenFile = activePath === node.path;
|
|
440
|
+
var isSelected = !!(selectedPaths && selectedPaths.has(node.path));
|
|
441
|
+
var isDragOver = isFolder && dragOverFolder === node.path;
|
|
442
|
+
var status = getGitStatus(node.path);
|
|
443
|
+
var statusMeta = getTreeStatusMeta(status);
|
|
444
|
+
var isModified = statusMeta && (statusMeta.cssKey === 'M' || statusMeta.cssKey === 'A');
|
|
445
|
+
|
|
446
|
+
var classNames = 'tree-item' +
|
|
447
|
+
(isOpenFile ? ' active' : '') +
|
|
448
|
+
(isSelected ? ' selected' : '') +
|
|
449
|
+
(isModified ? ' modified' : '') +
|
|
450
|
+
(isDragOver ? ' drag-over' : '');
|
|
451
|
+
|
|
452
|
+
return React.createElement(
|
|
453
|
+
'div',
|
|
454
|
+
{ key: node.path, style: { position: 'absolute', top: idx * ROW_HEIGHT, left: 0, right: 0 } },
|
|
455
|
+
isRenamingThisNode
|
|
456
|
+
? React.createElement('div', { style: { paddingLeft: indentPx } }, renderInlineRenameRow(node))
|
|
457
|
+
: React.createElement(
|
|
378
458
|
'div',
|
|
379
459
|
{
|
|
380
460
|
className: classNames,
|
|
461
|
+
style: { paddingLeft: indentPx },
|
|
381
462
|
'data-path': node.path,
|
|
382
463
|
'data-name': node.name,
|
|
383
464
|
'data-type': node.type,
|
|
384
465
|
draggable: true,
|
|
385
|
-
onDragStart: function
|
|
386
|
-
// If the dragged node is part of a multi-selection, drag all selected; otherwise just this node
|
|
466
|
+
onDragStart: function(e) {
|
|
387
467
|
var srcPaths = (selectedPaths && selectedPaths.has(node.path) && selectedPaths.size > 1)
|
|
388
468
|
? Array.from(selectedPaths)
|
|
389
469
|
: [node.path];
|
|
390
470
|
e.dataTransfer.setData('text/plain', JSON.stringify(srcPaths));
|
|
391
471
|
e.dataTransfer.effectAllowed = 'move';
|
|
392
472
|
},
|
|
393
|
-
onDragOver: function
|
|
473
|
+
onDragOver: function(e) {
|
|
394
474
|
if (!isFolder) return;
|
|
395
475
|
e.preventDefault();
|
|
396
476
|
e.stopPropagation();
|
|
397
477
|
e.dataTransfer.dropEffect = 'move';
|
|
398
478
|
if (dragOverFolder !== node.path) setDragOverFolder(node.path);
|
|
399
479
|
},
|
|
400
|
-
onDragLeave: function
|
|
401
|
-
// Only clear if we're leaving the folder item itself, not entering a child
|
|
480
|
+
onDragLeave: function() {
|
|
402
481
|
if (dragOverFolder === node.path) setDragOverFolder(null);
|
|
403
482
|
},
|
|
404
|
-
onDrop: function
|
|
483
|
+
onDrop: function(e) {
|
|
405
484
|
e.preventDefault();
|
|
406
485
|
e.stopPropagation();
|
|
407
486
|
setDragOverFolder(null);
|
|
@@ -411,66 +490,51 @@ var FileTree = function FileTree(_ref) {
|
|
|
411
490
|
if (onMove && srcPaths && srcPaths.length > 0) onMove(srcPaths, node.path);
|
|
412
491
|
} catch (err) {}
|
|
413
492
|
},
|
|
414
|
-
onDragEnd: function
|
|
415
|
-
onClick: function
|
|
493
|
+
onDragEnd: function() { setDragOverFolder(null); },
|
|
494
|
+
onClick: function(e) {
|
|
416
495
|
if (e.ctrlKey || e.metaKey) {
|
|
417
|
-
// Ctrl/Cmd+click: toggle this node in/out of selection
|
|
418
496
|
if (onMultiSelect) {
|
|
419
497
|
var newPaths = new Set(selectedPaths || []);
|
|
420
|
-
if (newPaths.has(node.path)) {
|
|
421
|
-
newPaths.delete(node.path);
|
|
422
|
-
} else {
|
|
423
|
-
newPaths.add(node.path);
|
|
424
|
-
}
|
|
498
|
+
if (newPaths.has(node.path)) { newPaths.delete(node.path); } else { newPaths.add(node.path); }
|
|
425
499
|
onMultiSelect(newPaths);
|
|
426
500
|
}
|
|
427
|
-
// Don't open file on ctrl-click
|
|
428
501
|
} else if (e.shiftKey && anchorPath) {
|
|
429
|
-
// Shift+click: range-select from anchor to this node
|
|
430
502
|
if (onMultiSelect) {
|
|
431
503
|
var visiblePaths = computeVisiblePaths(items);
|
|
432
|
-
var
|
|
433
|
-
var
|
|
434
|
-
if (
|
|
435
|
-
var start = Math.min(
|
|
436
|
-
var end = Math.max(
|
|
504
|
+
var anchorIdx2 = visiblePaths.indexOf(anchorPath);
|
|
505
|
+
var currentIdx2 = visiblePaths.indexOf(node.path);
|
|
506
|
+
if (anchorIdx2 >= 0 && currentIdx2 >= 0) {
|
|
507
|
+
var start = Math.min(anchorIdx2, currentIdx2);
|
|
508
|
+
var end = Math.max(anchorIdx2, currentIdx2);
|
|
437
509
|
onMultiSelect(new Set(visiblePaths.slice(start, end + 1)));
|
|
438
510
|
} else {
|
|
439
511
|
selectNode(node);
|
|
440
512
|
}
|
|
441
513
|
}
|
|
442
514
|
} else {
|
|
443
|
-
// Normal single click
|
|
444
515
|
selectNode(node);
|
|
445
|
-
if (isFolder) {
|
|
446
|
-
toggleFolder(node.path, e);
|
|
447
|
-
} else {
|
|
448
|
-
onSelect(node.path, node.name);
|
|
449
|
-
}
|
|
516
|
+
if (isFolder) { toggleFolder(node.path, e); } else { onSelect(node.path, node.name); }
|
|
450
517
|
if (containerRef.current) containerRef.current.focus();
|
|
451
518
|
}
|
|
452
519
|
},
|
|
453
|
-
onDoubleClick: function
|
|
454
|
-
if (!isFolder && onFileDoubleClick) {
|
|
455
|
-
e.stopPropagation();
|
|
456
|
-
onFileDoubleClick(node.path, node.name);
|
|
457
|
-
}
|
|
520
|
+
onDoubleClick: function(e) {
|
|
521
|
+
if (!isFolder && onFileDoubleClick) { e.stopPropagation(); onFileDoubleClick(node.path, node.name); }
|
|
458
522
|
},
|
|
459
|
-
onMouseEnter: function
|
|
523
|
+
onMouseEnter: function() {
|
|
460
524
|
if (isFolder) return;
|
|
461
525
|
clearTimeout(hoverTimerRef.current);
|
|
462
|
-
hoverTimerRef.current = setTimeout(function
|
|
526
|
+
hoverTimerRef.current = setTimeout(function() {
|
|
463
527
|
hoverPathRef.current = node.path;
|
|
464
528
|
FileService.prefetch(node.path);
|
|
465
529
|
}, 200);
|
|
466
530
|
},
|
|
467
|
-
onMouseLeave: function
|
|
531
|
+
onMouseLeave: function() {
|
|
468
532
|
if (isFolder) return;
|
|
469
533
|
clearTimeout(hoverTimerRef.current);
|
|
470
534
|
FileService.cancelPrefetch(hoverPathRef.current);
|
|
471
535
|
hoverPathRef.current = null;
|
|
472
536
|
},
|
|
473
|
-
onContextMenu: function
|
|
537
|
+
onContextMenu: function(e) {
|
|
474
538
|
e.preventDefault();
|
|
475
539
|
e.stopPropagation();
|
|
476
540
|
selectNode(node);
|
|
@@ -478,9 +542,10 @@ var FileTree = function FileTree(_ref) {
|
|
|
478
542
|
}
|
|
479
543
|
},
|
|
480
544
|
React.createElement(
|
|
481
|
-
'div',
|
|
482
|
-
|
|
483
|
-
|
|
545
|
+
'div', { className: 'tree-item-icon' },
|
|
546
|
+
isFolder
|
|
547
|
+
? React.createElement('i', { className: 'fas fa-folder' + (isExpanded ? '-open' : '') + ' tree-folder-icon' })
|
|
548
|
+
: React.createElement('i', { className: window.getFileIcon(node.name) + ' tree-file-icon' })
|
|
484
549
|
),
|
|
485
550
|
React.createElement(
|
|
486
551
|
'div',
|
|
@@ -492,27 +557,23 @@ var FileTree = function FileTree(_ref) {
|
|
|
492
557
|
{ className: 'git-status-badge git-' + statusMeta.cssKey, title: statusMeta.title },
|
|
493
558
|
statusMeta.badge
|
|
494
559
|
)
|
|
495
|
-
),
|
|
496
|
-
isFolder && isExpanded && node.children && React.createElement(
|
|
497
|
-
'div',
|
|
498
|
-
{ style: { paddingLeft: "12px" } },
|
|
499
|
-
renderTree(node.children, node.path)
|
|
500
560
|
)
|
|
501
|
-
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
// Inject inline create row at the end of this directory's list
|
|
505
|
-
if (pendingCreate && pendingCreate.parentPath === folderPath) {
|
|
506
|
-
rows.push(renderInlineRow());
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
return rows;
|
|
561
|
+
);
|
|
510
562
|
};
|
|
511
563
|
|
|
564
|
+
var visibleRows = [];
|
|
565
|
+
for (var vi = startIdx; vi <= endIdx; vi++) {
|
|
566
|
+
visibleRows.push(renderRow(flatItems[vi], vi));
|
|
567
|
+
}
|
|
568
|
+
|
|
512
569
|
return React.createElement(
|
|
513
570
|
'div',
|
|
514
|
-
{ className: 'file-tree-root', ref: containerRef, tabIndex: 0, style: { outline: 'none' } },
|
|
515
|
-
|
|
571
|
+
{ className: 'file-tree file-tree-root', ref: containerRef, tabIndex: 0, style: { outline: 'none', padding: 0 } },
|
|
572
|
+
React.createElement(
|
|
573
|
+
'div',
|
|
574
|
+
{ style: { height: totalHeight, position: 'relative' } },
|
|
575
|
+
visibleRows
|
|
576
|
+
)
|
|
516
577
|
);
|
|
517
578
|
};
|
|
518
579
|
|
|
@@ -527,7 +588,7 @@ var FileTreeMemo = React.memo(FileTree, function(prev, next) {
|
|
|
527
588
|
prev.activePath === next.activePath &&
|
|
528
589
|
prev.selectedPaths === next.selectedPaths &&
|
|
529
590
|
prev.anchorPath === next.anchorPath &&
|
|
530
|
-
|
|
591
|
+
prev.gitFiles === next.gitFiles &&
|
|
531
592
|
prev.expandedDirs === next.expandedDirs &&
|
|
532
593
|
prev.pendingCreate === next.pendingCreate &&
|
|
533
594
|
prev.pendingRename === next.pendingRename;
|