mbeditor 0.3.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/app/assets/javascripts/mbeditor/application_iife_head.js +7 -0
- data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +1 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +52 -11
- data/app/assets/javascripts/mbeditor/components/GitPanel.js +14 -4
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +499 -149
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +39 -1
- data/app/assets/javascripts/mbeditor/components/TabBar.js +3 -2
- data/app/assets/javascripts/mbeditor/editor_store.js +10 -2
- data/app/assets/javascripts/mbeditor/file_service.js +23 -23
- data/app/assets/javascripts/mbeditor/git_service.js +7 -11
- data/app/assets/javascripts/mbeditor/search_service.js +45 -12
- data/app/assets/javascripts/mbeditor/tab_manager.js +3 -3
- data/app/assets/stylesheets/mbeditor/editor.css +89 -6
- data/app/controllers/mbeditor/editors_controller.rb +139 -128
- data/app/controllers/mbeditor/git_controller.rb +5 -40
- data/app/services/mbeditor/git_blame_service.rb +6 -0
- data/app/services/mbeditor/git_commit_graph_service.rb +2 -0
- data/app/services/mbeditor/git_service.rb +97 -28
- data/app/services/mbeditor/redmine_service.rb +7 -0
- data/app/services/mbeditor/ruby_definition_service.rb +23 -2
- data/lib/mbeditor/configuration.rb +7 -1
- data/lib/mbeditor/engine.rb +27 -0
- data/lib/mbeditor/version.rb +3 -1
- data/lib/mbeditor.rb +2 -0
- metadata +2 -2
|
@@ -24,6 +24,8 @@ var DEFAULT_EDITOR_PREFS = {
|
|
|
24
24
|
theme: 'vs-dark',
|
|
25
25
|
fontSize: 13,
|
|
26
26
|
fontFamily: "'JetBrains Mono', 'Fira Code', Consolas, 'Courier New', monospace",
|
|
27
|
+
lineHeight: 0,
|
|
28
|
+
letterSpacing: 0,
|
|
27
29
|
tabSize: 4,
|
|
28
30
|
insertSpaces: false,
|
|
29
31
|
wordWrap: 'off',
|
|
@@ -32,6 +34,20 @@ var DEFAULT_EDITOR_PREFS = {
|
|
|
32
34
|
scrollBeyondLastLine: false,
|
|
33
35
|
minimap: false,
|
|
34
36
|
bracketPairColorization: true,
|
|
37
|
+
renderLineHighlight: 'none',
|
|
38
|
+
cursorStyle: 'line',
|
|
39
|
+
cursorBlinking: 'blink',
|
|
40
|
+
folding: true,
|
|
41
|
+
smoothScrolling: false,
|
|
42
|
+
mouseWheelZoom: false,
|
|
43
|
+
autoClosingBrackets: 'always',
|
|
44
|
+
autoClosingQuotes: 'always',
|
|
45
|
+
autoIndent: 'full',
|
|
46
|
+
formatOnPaste: true,
|
|
47
|
+
formatOnType: true,
|
|
48
|
+
quickSuggestions: true,
|
|
49
|
+
wordBasedSuggestions: 'matchingDocuments',
|
|
50
|
+
acceptSuggestionOnEnter: 'on',
|
|
35
51
|
autoRevealInExplorer: true,
|
|
36
52
|
toolbarIconOnly: false,
|
|
37
53
|
rubocopLintEnabled: true,
|
|
@@ -44,7 +60,8 @@ var DEFAULT_EDITOR_PREFS = {
|
|
|
44
60
|
prettierBracketSpacing: true,
|
|
45
61
|
vimMode: false,
|
|
46
62
|
fileTreeTypeahead: true,
|
|
47
|
-
quickOpenShowFolders: false
|
|
63
|
+
quickOpenShowFolders: false,
|
|
64
|
+
tabDisplayMode: 'scroll'
|
|
48
65
|
};
|
|
49
66
|
|
|
50
67
|
var SidebarActionButton = function SidebarActionButton(_ref) {
|
|
@@ -170,20 +187,17 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
170
187
|
var searchHasMore = _useState33h2[0];
|
|
171
188
|
var setSearchHasMore = _useState33h2[1];
|
|
172
189
|
|
|
173
|
-
var
|
|
174
|
-
var
|
|
175
|
-
var
|
|
176
|
-
var
|
|
190
|
+
var _useState33tc = useState(0);
|
|
191
|
+
var _useState33tc2 = _slicedToArray(_useState33tc, 2);
|
|
192
|
+
var searchTotalCount = _useState33tc2[0];
|
|
193
|
+
var setSearchTotalCount = _useState33tc2[1];
|
|
177
194
|
|
|
178
|
-
var
|
|
179
|
-
var
|
|
180
|
-
var
|
|
181
|
-
var searchVirtStartRef = useRef(0); // first visible item index (for virtual list)
|
|
195
|
+
var searchHasMoreRef = useRef(false);
|
|
196
|
+
var searchOffsetRef = useRef(0);
|
|
197
|
+
var searchLoadingMoreRef = useRef(false);
|
|
182
198
|
|
|
183
|
-
var
|
|
184
|
-
var
|
|
185
|
-
var searchVirtStart = _useState33j2[0];
|
|
186
|
-
var setSearchVirtStart = _useState33j2[1];
|
|
199
|
+
var searchQueryRef = useRef('');
|
|
200
|
+
var searchResultsContainerRef = useRef(null);
|
|
187
201
|
|
|
188
202
|
var _useState8 = useState("explorer");
|
|
189
203
|
|
|
@@ -404,6 +418,36 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
404
418
|
var prevGitBranchRef = useRef(null);
|
|
405
419
|
var isSwitchingBranchRef = useRef(false);
|
|
406
420
|
|
|
421
|
+
// ── Draft backup helpers ─────────────────────────────────────────────────
|
|
422
|
+
var draftWriteTimerRef = useRef({});
|
|
423
|
+
var serverOnlineRef = useRef(true);
|
|
424
|
+
|
|
425
|
+
var _draftKey = function _draftKey(path) {
|
|
426
|
+
var base = typeof window.mbeditorBasePath === 'function' ? window.mbeditorBasePath() : '';
|
|
427
|
+
return 'mbeditor_draft\x00' + base + '\x00' + path;
|
|
428
|
+
};
|
|
429
|
+
var _saveDraftNow = function _saveDraftNow(path, content) {
|
|
430
|
+
try { localStorage.setItem(_draftKey(path), JSON.stringify({ content: content, ts: Date.now() })); } catch (e) {}
|
|
431
|
+
};
|
|
432
|
+
var _clearDraft = function _clearDraft(path) {
|
|
433
|
+
try { localStorage.removeItem(_draftKey(path)); } catch (e) {}
|
|
434
|
+
};
|
|
435
|
+
var _loadDraft = function _loadDraft(path) {
|
|
436
|
+
try { return JSON.parse(localStorage.getItem(_draftKey(path))); } catch (e) { return null; }
|
|
437
|
+
};
|
|
438
|
+
var _scheduleDraftWrite = function _scheduleDraftWrite(path, content) {
|
|
439
|
+
if (draftWriteTimerRef.current[path]) clearTimeout(draftWriteTimerRef.current[path]);
|
|
440
|
+
draftWriteTimerRef.current[path] = setTimeout(function () {
|
|
441
|
+
delete draftWriteTimerRef.current[path];
|
|
442
|
+
_saveDraftNow(path, content);
|
|
443
|
+
}, 500);
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
var _useState_dro = useState(null);
|
|
447
|
+
var _useState_dro2 = _slicedToArray(_useState_dro, 2);
|
|
448
|
+
var draftRestoreOffer = _useState_dro2[0];
|
|
449
|
+
var setDraftRestoreOffer = _useState_dro2[1];
|
|
450
|
+
|
|
407
451
|
var clamp = function clamp(value, min, max) {
|
|
408
452
|
return Math.min(max, Math.max(min, value));
|
|
409
453
|
};
|
|
@@ -924,6 +968,28 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
924
968
|
return function () { clearTimeout(timeoutId); };
|
|
925
969
|
}, []);
|
|
926
970
|
|
|
971
|
+
// On reconnect: scan open dirty tabs for newer localStorage drafts and offer restore.
|
|
972
|
+
useEffect(function () {
|
|
973
|
+
if (!serverOnline) {
|
|
974
|
+
serverOnlineRef.current = false;
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
if (serverOnlineRef.current) return; // was already online — no transition
|
|
978
|
+
serverOnlineRef.current = true;
|
|
979
|
+
var st = EditorStore.getState();
|
|
980
|
+
var offers = [];
|
|
981
|
+
st.panes.forEach(function (pane) {
|
|
982
|
+
pane.tabs.forEach(function (tab) {
|
|
983
|
+
if (!tab.dirty || !tab.path || tab.path.startsWith('mbeditor://')) return;
|
|
984
|
+
var draft = _loadDraft(tab.path);
|
|
985
|
+
if (draft && draft.content !== tab.content) {
|
|
986
|
+
offers.push({ paneId: pane.id, tabId: tab.id, path: tab.path, name: tab.name, draftContent: draft.content });
|
|
987
|
+
}
|
|
988
|
+
});
|
|
989
|
+
});
|
|
990
|
+
if (offers.length > 0) setDraftRestoreOffer(offers);
|
|
991
|
+
}, [serverOnline]);
|
|
992
|
+
|
|
927
993
|
// Auto-refresh the file tree every 10s to pick up external changes (new files, deletions, etc.)
|
|
928
994
|
// Uses functional setTreeData to skip the re-render when nothing has changed.
|
|
929
995
|
useEffect(function () {
|
|
@@ -1235,6 +1301,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1235
1301
|
});
|
|
1236
1302
|
EditorStore.setState({ panes: newPanes });
|
|
1237
1303
|
EditorStore.setStatus("Saved", "success");
|
|
1304
|
+
_clearDraft(tab.path);
|
|
1238
1305
|
|
|
1239
1306
|
// Hot reload for Markdown: sync preview tab after save
|
|
1240
1307
|
if (/\.(md|markdown)$/i.test(tab.path)) {
|
|
@@ -1289,16 +1356,21 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1289
1356
|
var pane2 = EditorStore.getState().panes.find(function (p) {
|
|
1290
1357
|
return p.id === 2;
|
|
1291
1358
|
});
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
}
|
|
1359
|
+
var alreadySplit = pane2 && pane2.tabs.length > 0;
|
|
1360
|
+
// Only set the split ref; do NOT pre-split the view width here.
|
|
1361
|
+
// Pane 2 appears as a drop zone only when the cursor actually hovers
|
|
1362
|
+
// over the right-half editor content, keeping the tab bar intact.
|
|
1363
|
+
dragSplitWidthRef.current = alreadySplit ? pane1Width : 50;
|
|
1298
1364
|
setDraggedTab({ sourcePaneId: sourcePaneId, tabId: tabId });
|
|
1299
1365
|
};
|
|
1300
1366
|
|
|
1301
1367
|
var clearDragState = function clearDragState() {
|
|
1368
|
+
// If pane 2 is still empty after the drag, restore pane 1 to full width.
|
|
1369
|
+
var pane2 = EditorStore.getState().panes.find(function (p) { return p.id === 2; });
|
|
1370
|
+
if (!pane2 || pane2.tabs.length === 0) {
|
|
1371
|
+
setPane1Width(100);
|
|
1372
|
+
dragSplitWidthRef.current = 50;
|
|
1373
|
+
}
|
|
1302
1374
|
setDraggedTab(null);
|
|
1303
1375
|
setDragOverPaneId(null);
|
|
1304
1376
|
};
|
|
@@ -1481,40 +1553,61 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1481
1553
|
var onFormatRef = useRef(handleFormat);
|
|
1482
1554
|
onFormatRef.current = handleFormat;
|
|
1483
1555
|
|
|
1556
|
+
// Eagerly load all remaining pages sequentially in the background.
|
|
1557
|
+
// Self-chains via .then() so only one request is in-flight at a time.
|
|
1558
|
+
// Uses only refs so it's safe to call from async callbacks without
|
|
1559
|
+
// worrying about stale closure state.
|
|
1484
1560
|
var _debouncedSearch = useRef(window._.debounce(function (q) {
|
|
1485
1561
|
if (!q.trim()) {
|
|
1486
1562
|
searchRequestIdRef.current += 1;
|
|
1487
1563
|
setSearchLoading(false);
|
|
1488
|
-
setSearchHasMore(false);
|
|
1564
|
+
setSearchHasMore(false); searchHasMoreRef.current = false;
|
|
1565
|
+
setSearchTotalCount(0);
|
|
1489
1566
|
searchOffsetRef.current = 0;
|
|
1567
|
+
searchLoadingMoreRef.current = false;
|
|
1490
1568
|
searchQueryRef.current = '';
|
|
1491
1569
|
EditorStore.setState({ searchResults: [], searchHasMore: false });
|
|
1492
1570
|
return;
|
|
1493
1571
|
}
|
|
1494
1572
|
var requestId = ++searchRequestIdRef.current;
|
|
1495
1573
|
setSearchLoading(true);
|
|
1496
|
-
setSearchHasMore(false);
|
|
1574
|
+
setSearchHasMore(false); searchHasMoreRef.current = false;
|
|
1575
|
+
setSearchTotalCount(0);
|
|
1497
1576
|
searchOffsetRef.current = 0;
|
|
1577
|
+
searchLoadingMoreRef.current = false;
|
|
1498
1578
|
searchQueryRef.current = q;
|
|
1499
|
-
setSearchVirtStart(0);
|
|
1500
|
-
searchVirtStartRef.current = 0;
|
|
1501
1579
|
EditorStore.setState({ searchResults: [], searchHasMore: false });
|
|
1502
1580
|
EditorStore.setStatus("Searching project...", "info");
|
|
1503
1581
|
SearchService.projectSearch(q, 0, SearchService.PAGE_SIZE).then(function (res) {
|
|
1504
|
-
if (searchRequestIdRef.current
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1582
|
+
if (searchRequestIdRef.current !== requestId) return;
|
|
1583
|
+
var hasMore = !!(res && res.hasMore);
|
|
1584
|
+
setSearchHasMore(hasMore); searchHasMoreRef.current = hasMore;
|
|
1585
|
+
searchOffsetRef.current = SearchService.PAGE_SIZE;
|
|
1586
|
+
if (res && res.totalCount != null) setSearchTotalCount(res.totalCount);
|
|
1587
|
+
var total = (res && res.totalCount != null) ? res.totalCount : (res && res.results ? res.results.length : 0);
|
|
1588
|
+
EditorStore.setStatus("Found " + total + (hasMore ? '+' : '') + " result" + (total !== 1 ? "s" : ""), "success");
|
|
1511
1589
|
}).finally(function () {
|
|
1512
|
-
if (searchRequestIdRef.current === requestId)
|
|
1513
|
-
setSearchLoading(false);
|
|
1514
|
-
}
|
|
1590
|
+
if (searchRequestIdRef.current === requestId) setSearchLoading(false);
|
|
1515
1591
|
});
|
|
1516
1592
|
}, 400)).current;
|
|
1517
1593
|
|
|
1594
|
+
var loadMoreSearchResults = function loadMoreSearchResults() {
|
|
1595
|
+
var q = searchQueryRef.current;
|
|
1596
|
+
if (!q || searchLoadingMoreRef.current || !searchHasMoreRef.current) return;
|
|
1597
|
+
searchLoadingMoreRef.current = true;
|
|
1598
|
+
var offset = searchOffsetRef.current;
|
|
1599
|
+
SearchService.projectSearch(q, offset, SearchService.PAGE_SIZE).then(function(res) {
|
|
1600
|
+
if (searchQueryRef.current !== q) { searchLoadingMoreRef.current = false; return; }
|
|
1601
|
+
var hasMore = !!(res && res.hasMore);
|
|
1602
|
+
searchHasMoreRef.current = hasMore;
|
|
1603
|
+
setSearchHasMore(hasMore);
|
|
1604
|
+
searchOffsetRef.current = offset + SearchService.PAGE_SIZE;
|
|
1605
|
+
searchLoadingMoreRef.current = false;
|
|
1606
|
+
}).catch(function() {
|
|
1607
|
+
searchLoadingMoreRef.current = false;
|
|
1608
|
+
});
|
|
1609
|
+
};
|
|
1610
|
+
|
|
1518
1611
|
var handleSearchChange = function handleSearchChange(e) {
|
|
1519
1612
|
var val = e.target.value;
|
|
1520
1613
|
if (!val) { clearSearch(); return; }
|
|
@@ -1527,12 +1620,11 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1527
1620
|
if (_debouncedSearch.cancel) _debouncedSearch.cancel();
|
|
1528
1621
|
setSearchQuery("");
|
|
1529
1622
|
setSearchLoading(false);
|
|
1530
|
-
setSearchHasMore(false);
|
|
1531
|
-
|
|
1623
|
+
setSearchHasMore(false); searchHasMoreRef.current = false;
|
|
1624
|
+
setSearchTotalCount(0);
|
|
1532
1625
|
searchOffsetRef.current = 0;
|
|
1626
|
+
searchLoadingMoreRef.current = false;
|
|
1533
1627
|
searchQueryRef.current = '';
|
|
1534
|
-
setSearchVirtStart(0);
|
|
1535
|
-
searchVirtStartRef.current = 0;
|
|
1536
1628
|
EditorStore.setState({ searchResults: [], searchHasMore: false });
|
|
1537
1629
|
};
|
|
1538
1630
|
|
|
@@ -1541,35 +1633,11 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1541
1633
|
_debouncedSearch(searchQuery);
|
|
1542
1634
|
};
|
|
1543
1635
|
|
|
1544
|
-
|
|
1545
|
-
var q = searchQueryRef.current;
|
|
1546
|
-
if (!q || searchLoadingMore || !searchHasMore) return;
|
|
1547
|
-
var offset = searchOffsetRef.current;
|
|
1548
|
-
setSearchLoadingMore(true);
|
|
1549
|
-
SearchService.projectSearch(q, offset, SearchService.PAGE_SIZE).then(function (res) {
|
|
1550
|
-
var hasMore = !!(res && res.hasMore);
|
|
1551
|
-
setSearchHasMore(hasMore);
|
|
1552
|
-
searchOffsetRef.current = offset + SearchService.PAGE_SIZE;
|
|
1553
|
-
}).finally(function () {
|
|
1554
|
-
setSearchLoadingMore(false);
|
|
1555
|
-
});
|
|
1556
|
-
};
|
|
1557
|
-
|
|
1558
|
-
// Scroll handler for the virtualized search results list.
|
|
1559
|
-
var SEARCH_ITEM_H = 40;
|
|
1560
|
-
var SEARCH_OVERSCAN = 8;
|
|
1636
|
+
// Load more results when the user scrolls near the bottom of the list.
|
|
1561
1637
|
var handleSearchResultsScroll = function handleSearchResultsScroll(e) {
|
|
1562
1638
|
var el = e.currentTarget;
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
searchVirtStartRef.current = newStart;
|
|
1566
|
-
setSearchVirtStart(newStart);
|
|
1567
|
-
}
|
|
1568
|
-
// Trigger load-more when within 200px of the bottom
|
|
1569
|
-
if (!searchLoadingMore && searchHasMore) {
|
|
1570
|
-
if (el.scrollHeight - el.scrollTop - el.clientHeight < 200) {
|
|
1571
|
-
loadMoreSearchResults();
|
|
1572
|
-
}
|
|
1639
|
+
if (el.scrollHeight - el.scrollTop - el.clientHeight < 200) {
|
|
1640
|
+
loadMoreSearchResults();
|
|
1573
1641
|
}
|
|
1574
1642
|
};
|
|
1575
1643
|
|
|
@@ -2053,6 +2121,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2053
2121
|
tabs: tabs,
|
|
2054
2122
|
activeId: activeId,
|
|
2055
2123
|
paneId: paneId,
|
|
2124
|
+
tabDisplayMode: editorPrefs.tabDisplayMode || 'scroll',
|
|
2056
2125
|
onSelect: function (id) {
|
|
2057
2126
|
// Sync explorer selection with the newly active tab so there's only one highlight
|
|
2058
2127
|
var tab = tabs.find(function(t) { return t.id === id; });
|
|
@@ -2523,88 +2592,58 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2523
2592
|
React.createElement("i", { className: searchLoading ? "fas fa-spinner fa-spin" : "fas fa-search" })
|
|
2524
2593
|
)
|
|
2525
2594
|
),
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
onScroll: handleSearchResultsScroll
|
|
2532
|
-
},
|
|
2533
|
-
(function() {
|
|
2534
|
-
var allResults = state.searchResults || [];
|
|
2535
|
-
var totalCount = allResults.length;
|
|
2595
|
+
(function() {
|
|
2596
|
+
var allResults = state.searchResults || [];
|
|
2597
|
+
var loadedCount = allResults.length;
|
|
2598
|
+
var total = searchTotalCount > 0 ? searchTotalCount : loadedCount;
|
|
2599
|
+
var hasAny = loadedCount > 0;
|
|
2536
2600
|
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
return React.createElement(
|
|
2558
|
-
"div",
|
|
2559
|
-
{
|
|
2560
|
-
key: absoluteIdx,
|
|
2561
|
-
className: "search-result-item",
|
|
2562
|
-
onClick: function() { handleSelectFile(res.file, res.file.split('/').pop(), res.line); }
|
|
2563
|
-
},
|
|
2564
|
-
React.createElement("i", { className: (window.getFileIcon ? window.getFileIcon(fileName) : 'far fa-file-code') + " search-result-icon" }),
|
|
2565
|
-
React.createElement(
|
|
2601
|
+
return React.createElement(
|
|
2602
|
+
React.Fragment,
|
|
2603
|
+
null,
|
|
2604
|
+
searchQuery && !searchLoading && React.createElement(
|
|
2605
|
+
"div",
|
|
2606
|
+
{ className: "search-results-header" },
|
|
2607
|
+
hasAny
|
|
2608
|
+
? (total + (searchHasMore ? '+' : '') + " result" + (total !== 1 ? "s" : ""))
|
|
2609
|
+
: "No results"
|
|
2610
|
+
),
|
|
2611
|
+
hasAny && React.createElement(
|
|
2612
|
+
"div",
|
|
2613
|
+
{
|
|
2614
|
+
className: "search-results",
|
|
2615
|
+
ref: searchResultsContainerRef,
|
|
2616
|
+
onScroll: handleSearchResultsScroll
|
|
2617
|
+
},
|
|
2618
|
+
allResults.map(function(res, i) {
|
|
2619
|
+
var fileName = res.file.split('/').pop();
|
|
2620
|
+
return React.createElement(
|
|
2566
2621
|
"div",
|
|
2567
|
-
{
|
|
2622
|
+
{
|
|
2623
|
+
key: i,
|
|
2624
|
+
className: "search-result-item",
|
|
2625
|
+
onClick: (function(r) { return function() { handleSelectFile(r.file, r.file.split('/').pop(), r.line); }; })(res)
|
|
2626
|
+
},
|
|
2627
|
+
React.createElement("i", { className: (window.getFileIcon ? window.getFileIcon(fileName) : 'far fa-file-code') + " search-result-icon" }),
|
|
2568
2628
|
React.createElement(
|
|
2569
|
-
"div",
|
|
2570
|
-
{ className: "search-result-file" },
|
|
2571
|
-
fileName,
|
|
2629
|
+
"div", { className: "search-result-body" },
|
|
2572
2630
|
React.createElement(
|
|
2573
|
-
"
|
|
2574
|
-
|
|
2575
|
-
" ", res.file, ":", res.line
|
|
2576
|
-
)
|
|
2577
|
-
|
|
2578
|
-
React.createElement(
|
|
2579
|
-
"div",
|
|
2580
|
-
{ className: "search-result-text" },
|
|
2581
|
-
res.text
|
|
2631
|
+
"div", { className: "search-result-file" },
|
|
2632
|
+
fileName,
|
|
2633
|
+
React.createElement("span", { className: "search-result-line-num" }, " ", res.file, ":", res.line)
|
|
2634
|
+
),
|
|
2635
|
+
React.createElement("div", { className: "search-result-text" }, res.text)
|
|
2582
2636
|
)
|
|
2583
|
-
)
|
|
2584
|
-
)
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
null,
|
|
2590
|
-
React.createElement(
|
|
2591
|
-
"div",
|
|
2592
|
-
{ className: "search-results-meta" },
|
|
2593
|
-
totalCount,
|
|
2594
|
-
" result" + (totalCount !== 1 ? "s" : ""),
|
|
2595
|
-
searchHasMore && React.createElement("span", { className: "search-results-capped" }, " — more available, scroll for next page")
|
|
2596
|
-
),
|
|
2597
|
-
React.createElement("div", { style: { height: paddingTop + 'px', flexShrink: 0 } }),
|
|
2598
|
-
visibleItems,
|
|
2599
|
-
React.createElement("div", { style: { height: paddingBottom + 'px', flexShrink: 0 } }),
|
|
2600
|
-
searchLoadingMore && React.createElement(
|
|
2601
|
-
"div",
|
|
2602
|
-
{ className: "search-results-loading-more", 'aria-busy': 'true' },
|
|
2603
|
-
'Loading more…'
|
|
2637
|
+
);
|
|
2638
|
+
}),
|
|
2639
|
+
searchHasMore && React.createElement(
|
|
2640
|
+
"div", { className: "search-loading-more" },
|
|
2641
|
+
React.createElement("i", { className: "fas fa-spinner fa-spin" }),
|
|
2642
|
+
" Loading more\u2026"
|
|
2604
2643
|
)
|
|
2605
|
-
)
|
|
2606
|
-
|
|
2607
|
-
)
|
|
2644
|
+
)
|
|
2645
|
+
);
|
|
2646
|
+
})()
|
|
2608
2647
|
)
|
|
2609
2648
|
)
|
|
2610
2649
|
),
|
|
@@ -2625,16 +2664,40 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2625
2664
|
if (!draggedTab) return;
|
|
2626
2665
|
e.preventDefault();
|
|
2627
2666
|
|
|
2667
|
+
// If the cursor is over the tab bar, suppress the cross-pane split overlay
|
|
2668
|
+
// so same-pane tab reordering within any tab bar is unaffected.
|
|
2669
|
+
if (e.target && e.target.closest && e.target.closest('.tab-bar')) {
|
|
2670
|
+
if (dragOverPaneId !== null) setDragOverPaneId(null);
|
|
2671
|
+
e.dataTransfer.dropEffect = 'move';
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2628
2675
|
var rect = e.currentTarget.getBoundingClientRect();
|
|
2629
2676
|
var splitAtX = rect.left + rect.width * (dragSplitWidthRef.current / 100);
|
|
2630
2677
|
var hoverPaneId = e.clientX >= splitAtX ? 2 : 1;
|
|
2631
2678
|
var nextDropPane = hoverPaneId === draggedTab.sourcePaneId ? null : hoverPaneId;
|
|
2632
2679
|
|
|
2633
|
-
|
|
2680
|
+
// When cursor first enters the right-half content area and pane 2 is empty,
|
|
2681
|
+
// apply the 50% split width so the drop zone becomes visible.
|
|
2682
|
+
if (nextDropPane === 2) {
|
|
2683
|
+
var pane2Empty = EditorStore.getState().panes.find(function(p) { return p.id === 2; });
|
|
2684
|
+
if (!pane2Empty || pane2Empty.tabs.length === 0) {
|
|
2685
|
+
setPane1Width(50);
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
e.dataTransfer.dropEffect = 'move';
|
|
2634
2690
|
if (dragOverPaneId !== nextDropPane) setDragOverPaneId(nextDropPane);
|
|
2635
2691
|
},
|
|
2636
2692
|
onDropCapture: function (e) {
|
|
2637
2693
|
if (!draggedTab) return;
|
|
2694
|
+
|
|
2695
|
+
// If dropping onto a tab bar element, let the tab item's own onDrop
|
|
2696
|
+
// bubble-phase handler manage the reorder — don't intercept here.
|
|
2697
|
+
if (e.target && e.target.closest && e.target.closest('.tab-bar')) {
|
|
2698
|
+
return;
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2638
2701
|
e.preventDefault();
|
|
2639
2702
|
|
|
2640
2703
|
var rect = e.currentTarget.getBoundingClientRect();
|
|
@@ -2649,10 +2712,12 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2649
2712
|
}
|
|
2650
2713
|
},
|
|
2651
2714
|
state.panes.map(function (pane, idx) {
|
|
2652
|
-
|
|
2715
|
+
// Show empty pane 2 as a drop zone only when the cursor is actively hovering
|
|
2716
|
+
// over its half of the editor content (dragOverPaneId === 2).
|
|
2717
|
+
if (pane.id === 2 && pane.tabs.length === 0 && dragOverPaneId !== 2) return null;
|
|
2653
2718
|
|
|
2654
2719
|
// Dynamic width distribution
|
|
2655
|
-
var isSplit = state.panes[1].tabs.length > 0 ||
|
|
2720
|
+
var isSplit = state.panes[1].tabs.length > 0 || dragOverPaneId === 2;
|
|
2656
2721
|
var flexBasis = '100%';
|
|
2657
2722
|
if (isSplit) flexBasis = pane.id === 1 ? pane1Width + "%" : 100 - pane1Width + "%";
|
|
2658
2723
|
|
|
@@ -2735,6 +2800,32 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2735
2800
|
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { fontFamily: e.target.value }); }); }
|
|
2736
2801
|
})
|
|
2737
2802
|
),
|
|
2803
|
+
React.createElement(
|
|
2804
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Row height in pixels. 0 = auto (roughly font size × 1.5)' },
|
|
2805
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Line height (0=auto)'),
|
|
2806
|
+
React.createElement('input', {
|
|
2807
|
+
type: 'number', min: '0', max: '100', step: '1',
|
|
2808
|
+
className: 'ide-settings-input',
|
|
2809
|
+
value: editorPrefs.lineHeight != null ? editorPrefs.lineHeight : 0,
|
|
2810
|
+
onChange: function(e) {
|
|
2811
|
+
var v = parseInt(e.target.value, 10);
|
|
2812
|
+
if (!isNaN(v) && v >= 0 && v <= 100) setEditorPrefs(function(p) { return Object.assign({}, p, { lineHeight: v }); });
|
|
2813
|
+
}
|
|
2814
|
+
})
|
|
2815
|
+
),
|
|
2816
|
+
React.createElement(
|
|
2817
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Extra space between characters in pixels. 0 = default' },
|
|
2818
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Letter spacing (px)'),
|
|
2819
|
+
React.createElement('input', {
|
|
2820
|
+
type: 'number', min: '-5', max: '20', step: '0.5',
|
|
2821
|
+
className: 'ide-settings-input',
|
|
2822
|
+
value: editorPrefs.letterSpacing != null ? editorPrefs.letterSpacing : 0,
|
|
2823
|
+
onChange: function(e) {
|
|
2824
|
+
var v = parseFloat(e.target.value);
|
|
2825
|
+
if (!isNaN(v) && v >= -5 && v <= 20) setEditorPrefs(function(p) { return Object.assign({}, p, { letterSpacing: v }); });
|
|
2826
|
+
}
|
|
2827
|
+
})
|
|
2828
|
+
),
|
|
2738
2829
|
|
|
2739
2830
|
/* ── Indentation (unified editor + Prettier) ── */
|
|
2740
2831
|
React.createElement('div', { className: 'ide-settings-section-header' }, 'Indentation'),
|
|
@@ -2844,6 +2935,184 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2844
2935
|
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { vimMode: v }); }); }
|
|
2845
2936
|
})
|
|
2846
2937
|
),
|
|
2938
|
+
React.createElement(
|
|
2939
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'When to insert a matching closing bracket automatically' },
|
|
2940
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Auto-close brackets'),
|
|
2941
|
+
React.createElement(
|
|
2942
|
+
'select', {
|
|
2943
|
+
value: editorPrefs.autoClosingBrackets || 'always',
|
|
2944
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { autoClosingBrackets: e.target.value }); }); }
|
|
2945
|
+
},
|
|
2946
|
+
React.createElement('option', { value: 'always' }, 'Always'),
|
|
2947
|
+
React.createElement('option', { value: 'languageDefined' }, 'Per language rules'),
|
|
2948
|
+
React.createElement('option', { value: 'beforeWhitespace' }, 'Only before whitespace'),
|
|
2949
|
+
React.createElement('option', { value: 'never' }, 'Never')
|
|
2950
|
+
)
|
|
2951
|
+
),
|
|
2952
|
+
React.createElement(
|
|
2953
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'When to insert a matching closing quote automatically' },
|
|
2954
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Auto-close quotes'),
|
|
2955
|
+
React.createElement(
|
|
2956
|
+
'select', {
|
|
2957
|
+
value: editorPrefs.autoClosingQuotes || 'always',
|
|
2958
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { autoClosingQuotes: e.target.value }); }); }
|
|
2959
|
+
},
|
|
2960
|
+
React.createElement('option', { value: 'always' }, 'Always'),
|
|
2961
|
+
React.createElement('option', { value: 'languageDefined' }, 'Per language rules'),
|
|
2962
|
+
React.createElement('option', { value: 'beforeWhitespace' }, 'Only before whitespace'),
|
|
2963
|
+
React.createElement('option', { value: 'never' }, 'Never')
|
|
2964
|
+
)
|
|
2965
|
+
),
|
|
2966
|
+
React.createElement(
|
|
2967
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'What to highlight on the current editor line' },
|
|
2968
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Line highlight'),
|
|
2969
|
+
React.createElement(
|
|
2970
|
+
'select', {
|
|
2971
|
+
value: editorPrefs.renderLineHighlight || 'none',
|
|
2972
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { renderLineHighlight: e.target.value }); }); }
|
|
2973
|
+
},
|
|
2974
|
+
React.createElement('option', { value: 'none' }, 'None'),
|
|
2975
|
+
React.createElement('option', { value: 'gutter' }, 'Line number only'),
|
|
2976
|
+
React.createElement('option', { value: 'line' }, 'Current line background'),
|
|
2977
|
+
React.createElement('option', { value: 'all' }, 'Line number + background')
|
|
2978
|
+
)
|
|
2979
|
+
),
|
|
2980
|
+
React.createElement(
|
|
2981
|
+
'label', { className: 'ide-settings-row ide-settings-row-half' },
|
|
2982
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Cursor style'),
|
|
2983
|
+
React.createElement(
|
|
2984
|
+
'select', {
|
|
2985
|
+
value: editorPrefs.cursorStyle || 'line',
|
|
2986
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { cursorStyle: e.target.value }); }); }
|
|
2987
|
+
},
|
|
2988
|
+
React.createElement('option', { value: 'line' }, 'Line (|)'),
|
|
2989
|
+
React.createElement('option', { value: 'block' }, 'Block (filled)'),
|
|
2990
|
+
React.createElement('option', { value: 'underline' }, 'Underline (_)'),
|
|
2991
|
+
React.createElement('option', { value: 'line-thin' }, 'Line thin'),
|
|
2992
|
+
React.createElement('option', { value: 'block-outline' }, 'Block outline'),
|
|
2993
|
+
React.createElement('option', { value: 'underline-thin' }, 'Underline thin')
|
|
2994
|
+
)
|
|
2995
|
+
),
|
|
2996
|
+
React.createElement(
|
|
2997
|
+
'label', { className: 'ide-settings-row ide-settings-row-half' },
|
|
2998
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Cursor blinking'),
|
|
2999
|
+
React.createElement(
|
|
3000
|
+
'select', {
|
|
3001
|
+
value: editorPrefs.cursorBlinking || 'blink',
|
|
3002
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { cursorBlinking: e.target.value }); }); }
|
|
3003
|
+
},
|
|
3004
|
+
React.createElement('option', { value: 'blink' }, 'Blink (on/off)'),
|
|
3005
|
+
React.createElement('option', { value: 'smooth' }, 'Smooth (fade)'),
|
|
3006
|
+
React.createElement('option', { value: 'phase' }, 'Phase (offset fade)'),
|
|
3007
|
+
React.createElement('option', { value: 'expand' }, 'Expand (grow/shrink)'),
|
|
3008
|
+
React.createElement('option', { value: 'solid' }, 'Solid (no blink)')
|
|
3009
|
+
)
|
|
3010
|
+
),
|
|
3011
|
+
React.createElement(
|
|
3012
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Show collapse arrows next to foldable regions (functions, classes, blocks)' },
|
|
3013
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Code folding'),
|
|
3014
|
+
React.createElement('input', {
|
|
3015
|
+
type: 'checkbox',
|
|
3016
|
+
className: 'ide-settings-checkbox',
|
|
3017
|
+
checked: editorPrefs.folding !== false,
|
|
3018
|
+
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { folding: v }); }); }
|
|
3019
|
+
})
|
|
3020
|
+
),
|
|
3021
|
+
React.createElement(
|
|
3022
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Animate scrolling instead of jumping instantly' },
|
|
3023
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Smooth scrolling'),
|
|
3024
|
+
React.createElement('input', {
|
|
3025
|
+
type: 'checkbox',
|
|
3026
|
+
className: 'ide-settings-checkbox',
|
|
3027
|
+
checked: !!(editorPrefs.smoothScrolling),
|
|
3028
|
+
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { smoothScrolling: v }); }); }
|
|
3029
|
+
})
|
|
3030
|
+
),
|
|
3031
|
+
React.createElement(
|
|
3032
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Hold Ctrl (or Cmd) and scroll the mouse wheel to zoom the font size' },
|
|
3033
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Ctrl+scroll to zoom'),
|
|
3034
|
+
React.createElement('input', {
|
|
3035
|
+
type: 'checkbox',
|
|
3036
|
+
className: 'ide-settings-checkbox',
|
|
3037
|
+
checked: !!(editorPrefs.mouseWheelZoom),
|
|
3038
|
+
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { mouseWheelZoom: v }); }); }
|
|
3039
|
+
})
|
|
3040
|
+
),
|
|
3041
|
+
|
|
3042
|
+
/* ── Behaviour ───────────────────────────────── */
|
|
3043
|
+
React.createElement('div', { className: 'ide-settings-section-header' }, 'Behaviour'),
|
|
3044
|
+
React.createElement(
|
|
3045
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'How aggressively the editor re-indents lines as you type' },
|
|
3046
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Auto indent'),
|
|
3047
|
+
React.createElement(
|
|
3048
|
+
'select', {
|
|
3049
|
+
value: editorPrefs.autoIndent || 'full',
|
|
3050
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { autoIndent: e.target.value }); }); }
|
|
3051
|
+
},
|
|
3052
|
+
React.createElement('option', { value: 'none' }, 'None (disabled)'),
|
|
3053
|
+
React.createElement('option', { value: 'keep' }, 'Keep current level'),
|
|
3054
|
+
React.createElement('option', { value: 'brackets' }, 'Indent on { and ['),
|
|
3055
|
+
React.createElement('option', { value: 'advanced' }, 'Language indent rules'),
|
|
3056
|
+
React.createElement('option', { value: 'full' }, 'Full (language grammar)')
|
|
3057
|
+
)
|
|
3058
|
+
),
|
|
3059
|
+
React.createElement(
|
|
3060
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Whether pressing Enter accepts the highlighted autocomplete suggestion' },
|
|
3061
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Accept suggestion on Enter'),
|
|
3062
|
+
React.createElement(
|
|
3063
|
+
'select', {
|
|
3064
|
+
value: editorPrefs.acceptSuggestionOnEnter || 'on',
|
|
3065
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { acceptSuggestionOnEnter: e.target.value }); }); }
|
|
3066
|
+
},
|
|
3067
|
+
React.createElement('option', { value: 'on' }, 'Always'),
|
|
3068
|
+
React.createElement('option', { value: 'smart' }, 'Only when navigated (↑↓)'),
|
|
3069
|
+
React.createElement('option', { value: 'off' }, 'Never (Tab only)')
|
|
3070
|
+
)
|
|
3071
|
+
),
|
|
3072
|
+
React.createElement(
|
|
3073
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Suggest completions based on words already present in open files' },
|
|
3074
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Word-based suggestions'),
|
|
3075
|
+
React.createElement(
|
|
3076
|
+
'select', {
|
|
3077
|
+
value: editorPrefs.wordBasedSuggestions || 'matchingDocuments',
|
|
3078
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { wordBasedSuggestions: e.target.value }); }); }
|
|
3079
|
+
},
|
|
3080
|
+
React.createElement('option', { value: 'off' }, 'Off'),
|
|
3081
|
+
React.createElement('option', { value: 'currentDocument' }, 'Current file only'),
|
|
3082
|
+
React.createElement('option', { value: 'matchingDocuments' }, 'Same language files'),
|
|
3083
|
+
React.createElement('option', { value: 'allDocuments' }, 'All open files')
|
|
3084
|
+
)
|
|
3085
|
+
),
|
|
3086
|
+
React.createElement(
|
|
3087
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Auto-format pasted code using the language formatter' },
|
|
3088
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Format on paste'),
|
|
3089
|
+
React.createElement('input', {
|
|
3090
|
+
type: 'checkbox',
|
|
3091
|
+
className: 'ide-settings-checkbox',
|
|
3092
|
+
checked: editorPrefs.formatOnPaste !== false,
|
|
3093
|
+
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { formatOnPaste: v }); }); }
|
|
3094
|
+
})
|
|
3095
|
+
),
|
|
3096
|
+
React.createElement(
|
|
3097
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Re-indent and auto-close blocks as you type (e.g. after pressing Enter inside {})' },
|
|
3098
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Format on type'),
|
|
3099
|
+
React.createElement('input', {
|
|
3100
|
+
type: 'checkbox',
|
|
3101
|
+
className: 'ide-settings-checkbox',
|
|
3102
|
+
checked: editorPrefs.formatOnType !== false,
|
|
3103
|
+
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { formatOnType: v }); }); }
|
|
3104
|
+
})
|
|
3105
|
+
),
|
|
3106
|
+
React.createElement(
|
|
3107
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Show autocomplete suggestions while typing (not just on trigger characters like .)' },
|
|
3108
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Quick suggestions'),
|
|
3109
|
+
React.createElement('input', {
|
|
3110
|
+
type: 'checkbox',
|
|
3111
|
+
className: 'ide-settings-checkbox',
|
|
3112
|
+
checked: editorPrefs.quickSuggestions !== false,
|
|
3113
|
+
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { quickSuggestions: v }); }); }
|
|
3114
|
+
})
|
|
3115
|
+
),
|
|
2847
3116
|
|
|
2848
3117
|
/* ── Formatting (Prettier) ───────────────────── */
|
|
2849
3118
|
React.createElement('div', { className: 'ide-settings-section-header' }, 'Formatting'),
|
|
@@ -2926,6 +3195,18 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2926
3195
|
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { fileTreeTypeahead: v }); }); }
|
|
2927
3196
|
})
|
|
2928
3197
|
),
|
|
3198
|
+
React.createElement(
|
|
3199
|
+
'label', { className: 'ide-settings-row ide-settings-row-half' },
|
|
3200
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Tab bar layout'),
|
|
3201
|
+
React.createElement(
|
|
3202
|
+
'select', {
|
|
3203
|
+
value: editorPrefs.tabDisplayMode || 'scroll',
|
|
3204
|
+
onChange: function(e) { var v = e.target.value; setEditorPrefs(function(p) { return Object.assign({}, p, { tabDisplayMode: v }); }); }
|
|
3205
|
+
},
|
|
3206
|
+
React.createElement('option', { value: 'scroll' }, 'Scroll'),
|
|
3207
|
+
React.createElement('option', { value: 'wrap' }, 'Wrap (multi-row)')
|
|
3208
|
+
)
|
|
3209
|
+
),
|
|
2929
3210
|
React.createElement(
|
|
2930
3211
|
'label', { className: 'ide-settings-row ide-settings-row-check' },
|
|
2931
3212
|
React.createElement('span', { className: 'ide-settings-label' }, 'Quick Open: show folders'),
|
|
@@ -3023,8 +3304,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3023
3304
|
var valNorm = val.replace(/\r\n/g, '\n');
|
|
3024
3305
|
if (valNorm === cleanNorm) {
|
|
3025
3306
|
TabManager.markClean(pane.id, pActiveTab.id, val);
|
|
3307
|
+
_clearDraft(pActiveTab.path);
|
|
3026
3308
|
} else {
|
|
3027
3309
|
TabManager.markDirty(pane.id, pActiveTab.id, val);
|
|
3310
|
+
_scheduleDraftWrite(pActiveTab.path, val);
|
|
3028
3311
|
}
|
|
3029
3312
|
}
|
|
3030
3313
|
});
|
|
@@ -3226,12 +3509,22 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3226
3509
|
state.gitInfo.behind
|
|
3227
3510
|
)
|
|
3228
3511
|
),
|
|
3229
|
-
!serverOnline &&
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3512
|
+
!serverOnline && (function () {
|
|
3513
|
+
var dirtyCount = state.panes.reduce(function (acc, p) {
|
|
3514
|
+
return acc + p.tabs.filter(function (t) { return t.dirty; }).length;
|
|
3515
|
+
}, 0);
|
|
3516
|
+
return React.createElement(
|
|
3517
|
+
"div",
|
|
3518
|
+
{
|
|
3519
|
+
className: "statusbar-offline",
|
|
3520
|
+
title: dirtyCount > 0 ? dirtyCount + " unsaved file" + (dirtyCount !== 1 ? "s" : "") + " — changes are backed up locally" : "Server offline"
|
|
3521
|
+
},
|
|
3522
|
+
React.createElement("i", { className: "fas fa-exclamation-triangle" }),
|
|
3523
|
+
dirtyCount > 0
|
|
3524
|
+
? " Offline \u2014 " + dirtyCount + " unsaved"
|
|
3525
|
+
: " Server offline"
|
|
3526
|
+
);
|
|
3527
|
+
})(),
|
|
3235
3528
|
activeFileCommit && React.createElement(
|
|
3236
3529
|
"div",
|
|
3237
3530
|
{ className: "statusbar-file-commit", title: activeFileCommit.title + " — " + activeFileCommit.author },
|
|
@@ -3366,6 +3659,63 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3366
3659
|
onSelectFolder: handleOpenFolderInExplorer,
|
|
3367
3660
|
onClose: function () { return setQuickOpen(false); }
|
|
3368
3661
|
}),
|
|
3662
|
+
draftRestoreOffer && React.createElement(
|
|
3663
|
+
"div",
|
|
3664
|
+
{
|
|
3665
|
+
className: "ide-draft-restore-overlay",
|
|
3666
|
+
role: "dialog",
|
|
3667
|
+
"aria-modal": "true",
|
|
3668
|
+
"aria-label": "Restore unsaved drafts"
|
|
3669
|
+
},
|
|
3670
|
+
React.createElement(
|
|
3671
|
+
"div",
|
|
3672
|
+
{ className: "ide-draft-restore-dialog" },
|
|
3673
|
+
React.createElement("div", { className: "ide-draft-restore-title" },
|
|
3674
|
+
React.createElement("i", { className: "fas fa-save", style: { marginRight: 8 } }),
|
|
3675
|
+
"Unsaved drafts found"
|
|
3676
|
+
),
|
|
3677
|
+
React.createElement("div", { className: "ide-draft-restore-body" },
|
|
3678
|
+
draftRestoreOffer.length + " file" + (draftRestoreOffer.length !== 1 ? "s have" : " has") + " locally backed-up drafts from when the server was offline:"
|
|
3679
|
+
),
|
|
3680
|
+
React.createElement(
|
|
3681
|
+
"ul",
|
|
3682
|
+
{ className: "ide-draft-restore-list" },
|
|
3683
|
+
draftRestoreOffer.map(function (o) {
|
|
3684
|
+
return React.createElement("li", { key: o.path }, o.name || o.path);
|
|
3685
|
+
})
|
|
3686
|
+
),
|
|
3687
|
+
React.createElement(
|
|
3688
|
+
"div",
|
|
3689
|
+
{ className: "ide-draft-restore-actions" },
|
|
3690
|
+
React.createElement(
|
|
3691
|
+
"button",
|
|
3692
|
+
{
|
|
3693
|
+
type: "button",
|
|
3694
|
+
className: "ide-draft-restore-btn ide-draft-restore-btn-primary",
|
|
3695
|
+
onClick: function () {
|
|
3696
|
+
draftRestoreOffer.forEach(function (offer) {
|
|
3697
|
+
TabManager.markDirty(offer.paneId, offer.tabId, offer.draftContent);
|
|
3698
|
+
});
|
|
3699
|
+
setDraftRestoreOffer(null);
|
|
3700
|
+
}
|
|
3701
|
+
},
|
|
3702
|
+
"Restore all"
|
|
3703
|
+
),
|
|
3704
|
+
React.createElement(
|
|
3705
|
+
"button",
|
|
3706
|
+
{
|
|
3707
|
+
type: "button",
|
|
3708
|
+
className: "ide-draft-restore-btn",
|
|
3709
|
+
onClick: function () {
|
|
3710
|
+
draftRestoreOffer.forEach(function (offer) { _clearDraft(offer.path); });
|
|
3711
|
+
setDraftRestoreOffer(null);
|
|
3712
|
+
}
|
|
3713
|
+
},
|
|
3714
|
+
"Discard drafts"
|
|
3715
|
+
)
|
|
3716
|
+
)
|
|
3717
|
+
)
|
|
3718
|
+
),
|
|
3369
3719
|
contextMenu && React.createElement(
|
|
3370
3720
|
React.Fragment,
|
|
3371
3721
|
null,
|