mbeditor 0.4.5 → 0.5.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 +23 -0
- data/app/assets/javascripts/mbeditor/application_iife_tail.js +1 -0
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +205 -18
- data/app/assets/javascripts/mbeditor/components/FileTree.js +23 -1
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +324 -48
- data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +2 -0
- data/app/assets/javascripts/mbeditor/editor_plugins.js +226 -0
- data/app/assets/javascripts/mbeditor/file_service.js +93 -1
- data/app/assets/javascripts/mbeditor/git_service.js +7 -3
- data/app/assets/javascripts/mbeditor/search_service.js +91 -2
- data/app/assets/javascripts/mbeditor/tab_manager.js +78 -2
- data/app/assets/stylesheets/mbeditor/editor.css +29 -0
- data/app/controllers/mbeditor/editors_controller.rb +318 -41
- data/app/services/mbeditor/ruby_definition_service.rb +163 -21
- data/app/services/mbeditor/unused_methods_service.rb +139 -0
- data/app/views/layouts/mbeditor/application.html.erb +86 -56
- data/config/routes.rb +4 -0
- data/lib/mbeditor/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a6e34c84c4c7e3a0ef9d4382dd49c29ac3bd13fdc42f8b38d50666cdd30439b6
|
|
4
|
+
data.tar.gz: 07a9658a8d2faa837969d7c8b05556eaba59afeb48af63b9c8482d7693805b64
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4506a5f5c0e7d7c5b111f9e6cb665cf5d95b7a2e9be5c140948c49f3fbb3a7ee9c13e78d1924d891522131cb85d512242074202298172a10915ea57e6e48f71a
|
|
7
|
+
data.tar.gz: c7b06da62965639c18f0531a9459da9ddcfeb14ba29ecceadeb600aeec8eb28197657c3dcca4dec5311978fef1397dc610cc2d9f27ead2513ec2e768daceda62
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.5.1] - 2026-04-30
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Large file pagination** — files over 5 MB now open in read-only paginated mode (500 lines per page) instead of showing an error. A bar below the toolbar shows the current line range, total line count, and file size, with Prev/Next navigation. The backend streams only the requested line slice via `File.foreach` so arbitrarily large files never load fully into memory.
|
|
12
|
+
- **JSON auto pretty-print** — `.json` files are automatically formatted with 2-space indentation when opened. Invalid JSON falls back to raw display with Monaco's built-in error markers. The formatted content is set as the editor baseline so the file does not appear dirty after opening.
|
|
13
|
+
|
|
14
|
+
## [0.5.0] - 2026-04-30
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **Zen / focus mode** — `Cmd+Shift+Z` hides the sidebar and git panel for a distraction-free editing experience.
|
|
18
|
+
- **Bulk find-and-replace** — search and replace across all workspace files in one operation.
|
|
19
|
+
- **File content prefetch on hover** — opening a file from the tree is now instant; content is fetched while hovering the row.
|
|
20
|
+
- **Client-side search cache** — search results are cached client-side for 30 s and invalidated automatically on save.
|
|
21
|
+
- **Monaco model cache with LRU eviction** — in-memory Monaco models are capped at 15 and evicted in LRU order to keep memory use bounded.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- **Faster initial render** — Monaco startup is now decoupled from the React mount lifecycle, cutting time-to-first-edit.
|
|
25
|
+
- **Robust dirty-state tracking** — dirty state is now driven by Monaco's `alternativeVersionId`; `cleanVersionId` is reset correctly on save-on-close so re-opened tabs start clean.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- ActionCable reconnect logic hardened; regression tests added to confirm websocket lifecycle log filtering still works after reconnect.
|
|
29
|
+
- Bulk replace: fixed a security issue and a correctness bug in the replacement pipeline; added covering tests.
|
|
30
|
+
|
|
8
31
|
## [0.4.5] - 2026-04-23
|
|
9
32
|
|
|
10
33
|
### Fixed
|
|
@@ -30,6 +30,7 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
30
30
|
var testLoading = _ref.testLoading;
|
|
31
31
|
var testInlineVisible = _ref.testInlineVisible;
|
|
32
32
|
var editorPrefs = _ref.editorPrefs || {};
|
|
33
|
+
var monacoReady = _ref.monacoReady !== false; // undefined means Monaco already loaded (legacy callers)
|
|
33
34
|
|
|
34
35
|
var editorRef = useRef(null);
|
|
35
36
|
var monacoRef = useRef(null);
|
|
@@ -85,6 +86,27 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
85
86
|
|
|
86
87
|
var methodsBtnRef = useRef(null);
|
|
87
88
|
|
|
89
|
+
// Local pagination state — initialized from tab props; updated on page navigation
|
|
90
|
+
var _useState17 = useState(tab.startLine || 0);
|
|
91
|
+
var _useState18 = _slicedToArray(_useState17, 2);
|
|
92
|
+
var pageStartLine = _useState18[0];
|
|
93
|
+
var setPageStartLine = _useState18[1];
|
|
94
|
+
|
|
95
|
+
var _useState19 = useState(tab.lineCount || 0);
|
|
96
|
+
var _useState20 = _slicedToArray(_useState19, 2);
|
|
97
|
+
var pageLineCount = _useState20[0];
|
|
98
|
+
var setPageLineCount = _useState20[1];
|
|
99
|
+
|
|
100
|
+
var _useState21 = useState(tab.totalLines || 0);
|
|
101
|
+
var _useState22 = _slicedToArray(_useState21, 2);
|
|
102
|
+
var pageTotalLines = _useState22[0];
|
|
103
|
+
var setPageTotalLines = _useState22[1];
|
|
104
|
+
|
|
105
|
+
var _useState23 = useState(tab.totalBytes || 0);
|
|
106
|
+
var _useState24 = _slicedToArray(_useState23, 2);
|
|
107
|
+
var pageTotalBytes = _useState24[0];
|
|
108
|
+
var setPageTotalBytes = _useState24[1];
|
|
109
|
+
|
|
88
110
|
var onFormatRef = useRef(onFormat);
|
|
89
111
|
onFormatRef.current = onFormat;
|
|
90
112
|
|
|
@@ -94,6 +116,12 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
94
116
|
var vimStatusRef = useRef(null);
|
|
95
117
|
var vimModeObjRef = useRef(null);
|
|
96
118
|
|
|
119
|
+
function humanSize(bytes) {
|
|
120
|
+
if (bytes < 1024) return bytes + ' B';
|
|
121
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
122
|
+
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
123
|
+
}
|
|
124
|
+
|
|
97
125
|
var clearTestZones = function clearTestZones(editor) {
|
|
98
126
|
if (!editor) return;
|
|
99
127
|
if (testZoneIdsRef.current.length === 0) return;
|
|
@@ -132,7 +160,7 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
132
160
|
|
|
133
161
|
useEffect(function () {
|
|
134
162
|
if (tab.isPreview) return;
|
|
135
|
-
if (!editorRef.current || !window.monaco) return;
|
|
163
|
+
if (!monacoReady || !editorRef.current || !window.monaco) return;
|
|
136
164
|
|
|
137
165
|
if (window.MbeditorEditorPlugins && window.MbeditorEditorPlugins.registerGlobalExtensions) {
|
|
138
166
|
window.MbeditorEditorPlugins.registerGlobalExtensions(window.monaco);
|
|
@@ -478,13 +506,28 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
478
506
|
if (_modelEntry && _modelEntry.model && !_modelEntry.model.isDisposed()) {
|
|
479
507
|
modelObj = _modelEntry.model;
|
|
480
508
|
_reusingModel = true;
|
|
509
|
+
// Update access timestamp so LRU eviction knows this model was recently used.
|
|
510
|
+
_modelEntry.lastAccessed = Date.now();
|
|
481
511
|
// Re-apply language in case it changed (e.g. file renamed)
|
|
482
512
|
if (modelObj.getLanguageId() !== language) {
|
|
483
513
|
window.monaco.editor.setModelLanguage(modelObj, language);
|
|
484
514
|
}
|
|
485
515
|
} else {
|
|
486
|
-
|
|
487
|
-
|
|
516
|
+
// Evict the LRU model if the cache is at capacity before creating a new one.
|
|
517
|
+
TabManager.evictLruModel();
|
|
518
|
+
|
|
519
|
+
// Pretty-print JSON content before initial load
|
|
520
|
+
var contentForModel = tab.content;
|
|
521
|
+
if (language === 'json' && contentForModel) {
|
|
522
|
+
try {
|
|
523
|
+
contentForModel = JSON.stringify(JSON.parse(contentForModel), null, 2);
|
|
524
|
+
} catch (_) {
|
|
525
|
+
// invalid JSON — use raw content; Monaco will show error markers
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
modelObj = window.monaco.editor.createModel(contentForModel, language);
|
|
530
|
+
window.__mbeditorModels[tab.path] = { model: modelObj, aviBase: null, aviMax: null, lastAccessed: Date.now(), cleanVersionId: null };
|
|
488
531
|
_modelEntry = window.__mbeditorModels[tab.path];
|
|
489
532
|
}
|
|
490
533
|
|
|
@@ -573,6 +616,10 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
573
616
|
|
|
574
617
|
monacoRef.current = editor;
|
|
575
618
|
window.__mbeditorActiveEditor = editor;
|
|
619
|
+
// Apply read-only for paginated (truncated) files
|
|
620
|
+
if (tab.truncated) {
|
|
621
|
+
editor.updateOptions({ readOnly: true });
|
|
622
|
+
}
|
|
576
623
|
setEditorReady(true);
|
|
577
624
|
|
|
578
625
|
// Stash the workspace-relative path on the model so code-action providers
|
|
@@ -645,6 +692,8 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
645
692
|
} else {
|
|
646
693
|
aviBaseRef.current = avi;
|
|
647
694
|
aviMaxRef.current = avi;
|
|
695
|
+
// Record the clean baseline for dirty-state tracking on initial model creation.
|
|
696
|
+
_modelEntry.cleanVersionId = avi;
|
|
648
697
|
}
|
|
649
698
|
EditorStore.setState({ canUndo: avi > aviBaseRef.current, canRedo: avi < aviMaxRef.current });
|
|
650
699
|
|
|
@@ -660,19 +709,16 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
660
709
|
|
|
661
710
|
var val = editor.getValue();
|
|
662
711
|
|
|
663
|
-
//
|
|
664
|
-
//
|
|
665
|
-
//
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
if (_valNorm === _cleanNorm) {
|
|
674
|
-
TabManager.markClean(paneId, tab.id, val);
|
|
675
|
-
}
|
|
712
|
+
// Dirty-state tracking via alternativeVersionId — O(1), no string comparison.
|
|
713
|
+
// AVI decrements on undo so it returns to cleanVersionId after a full undo.
|
|
714
|
+
// Skip entirely when cleanVersionId is null — file is mid-load, not yet settled.
|
|
715
|
+
var _entry = window.__mbeditorModels && window.__mbeditorModels[tab.path];
|
|
716
|
+
var _cleanAvi = _entry && _entry.cleanVersionId;
|
|
717
|
+
if (_cleanAvi !== null && _cleanAvi !== undefined) {
|
|
718
|
+
if (currentAvi !== _cleanAvi) {
|
|
719
|
+
TabManager.markDirty(paneId, tab.id, val);
|
|
720
|
+
} else {
|
|
721
|
+
TabManager.markClean(paneId, tab.id, val);
|
|
676
722
|
}
|
|
677
723
|
}
|
|
678
724
|
|
|
@@ -734,7 +780,7 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
734
780
|
editor.setModel(null);
|
|
735
781
|
editor.dispose();
|
|
736
782
|
};
|
|
737
|
-
}, [tab.id, tab.isPreview]); // re-run
|
|
783
|
+
}, [tab.id, tab.isPreview, monacoReady]); // re-run on tab switch or when Monaco becomes ready
|
|
738
784
|
|
|
739
785
|
// Listen for external content changes (e.g. after Format/Load)
|
|
740
786
|
// Only applies when externalContentVersion advances — prevents stale typing-originated
|
|
@@ -760,12 +806,29 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
760
806
|
if (!vNorm) {
|
|
761
807
|
// If the editor is currently completely empty, treat it as an initial load.
|
|
762
808
|
// setValue clears the undo stack which is correct for initial load.
|
|
763
|
-
|
|
809
|
+
// Null cleanVersionId before setValue so the synchronous onDidChangeContent
|
|
810
|
+
// fires during setValue and skips the dirty check (cleanVersionId is null).
|
|
811
|
+
var _initEntry = window.__mbeditorModels && window.__mbeditorModels[tab.path];
|
|
812
|
+
if (_initEntry) _initEntry.cleanVersionId = null;
|
|
813
|
+
|
|
814
|
+
// Pretty-print JSON content before initial load
|
|
815
|
+
var contentToSet = tab.content;
|
|
816
|
+
var modelLang = model.getLanguageId();
|
|
817
|
+
if (modelLang === 'json' && contentToSet) {
|
|
818
|
+
try {
|
|
819
|
+
contentToSet = JSON.stringify(JSON.parse(contentToSet), null, 2);
|
|
820
|
+
} catch (_) {
|
|
821
|
+
// invalid JSON — use raw content; Monaco will show error markers
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
editor.setValue(contentToSet);
|
|
764
826
|
// Reset the AVI baseline: setValue clears the undo stack so anything before
|
|
765
827
|
// this point is no longer reachable. Also clear the canUndo/canRedo display.
|
|
766
828
|
var newBase = model.getAlternativeVersionId();
|
|
767
829
|
aviBaseRef.current = newBase;
|
|
768
830
|
aviMaxRef.current = newBase;
|
|
831
|
+
if (_initEntry) _initEntry.cleanVersionId = newBase;
|
|
769
832
|
EditorStore.setState({ canUndo: false, canRedo: false });
|
|
770
833
|
} else {
|
|
771
834
|
// Keep undo stack for formats or replaces by using executeEdits
|
|
@@ -908,6 +971,21 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
908
971
|
}
|
|
909
972
|
}, [markers, tab.id]);
|
|
910
973
|
|
|
974
|
+
// Sync pagination state when tab changes (different file or fresh load)
|
|
975
|
+
useEffect(function () {
|
|
976
|
+
setPageStartLine(tab.startLine || 0);
|
|
977
|
+
setPageLineCount(tab.lineCount || 0);
|
|
978
|
+
setPageTotalLines(tab.totalLines || 0);
|
|
979
|
+
setPageTotalBytes(tab.totalBytes || 0);
|
|
980
|
+
}, [tab.id]);
|
|
981
|
+
|
|
982
|
+
// Apply read-only mode based on tab.truncated whenever the editor or truncated flag changes
|
|
983
|
+
useEffect(function () {
|
|
984
|
+
if (monacoRef.current) {
|
|
985
|
+
monacoRef.current.updateOptions({ readOnly: !!tab.truncated });
|
|
986
|
+
}
|
|
987
|
+
}, [tab.truncated, editorReady]);
|
|
988
|
+
|
|
911
989
|
// Reset blame + test decorations when file path changes
|
|
912
990
|
useEffect(function () {
|
|
913
991
|
setBlameData(null);
|
|
@@ -1363,6 +1441,16 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
1363
1441
|
return '\u2026/' + parts.slice(-2).join('/');
|
|
1364
1442
|
}
|
|
1365
1443
|
|
|
1444
|
+
// While Monaco is still loading, show a lightweight skeleton so the UI is
|
|
1445
|
+
// visible immediately without calling monaco.editor.create() too early.
|
|
1446
|
+
if (!monacoReady) {
|
|
1447
|
+
return React.createElement(
|
|
1448
|
+
'div',
|
|
1449
|
+
{ className: 'monaco-container monaco-loading-skeleton', style: { display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', color: '#888', fontSize: '13px' } },
|
|
1450
|
+
'Loading editor…'
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1366
1454
|
// Always render the same wrapper structure so the editorRef div is never
|
|
1367
1455
|
// unmounted when gitAvailable changes (e.g. loaded async after workspace
|
|
1368
1456
|
// call returns). The toolbar is conditionally included inside the wrapper.
|
|
@@ -1437,6 +1525,105 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
1437
1525
|
!editorPrefs.toolbarIconOnly && !testLoading && React.createElement('span', { className: 'ide-toolbar-label' }, 'Test')
|
|
1438
1526
|
)
|
|
1439
1527
|
),
|
|
1528
|
+
tab.truncated && React.createElement(
|
|
1529
|
+
'div',
|
|
1530
|
+
{
|
|
1531
|
+
className: 'ide-pagination-bar',
|
|
1532
|
+
style: {
|
|
1533
|
+
display: 'flex',
|
|
1534
|
+
alignItems: 'center',
|
|
1535
|
+
gap: '8px',
|
|
1536
|
+
padding: '4px 10px',
|
|
1537
|
+
background: 'var(--ide-toolbar-bg, #252526)',
|
|
1538
|
+
borderBottom: '1px solid var(--ide-border, #3e3e3e)',
|
|
1539
|
+
fontSize: '12px',
|
|
1540
|
+
color: 'var(--ide-toolbar-fg, #ccc)',
|
|
1541
|
+
flexShrink: 0,
|
|
1542
|
+
userSelect: 'none'
|
|
1543
|
+
}
|
|
1544
|
+
},
|
|
1545
|
+
React.createElement(
|
|
1546
|
+
'button',
|
|
1547
|
+
{
|
|
1548
|
+
className: 'ide-icon-btn',
|
|
1549
|
+
style: { padding: '2px 8px', fontSize: '12px' },
|
|
1550
|
+
disabled: pageStartLine === 0,
|
|
1551
|
+
onClick: function() {
|
|
1552
|
+
var newStart = Math.max(0, pageStartLine - 500);
|
|
1553
|
+
FileService.getFileChunk(tab.path, newStart, 500).then(function(data) {
|
|
1554
|
+
var sl = data.start_line || 0;
|
|
1555
|
+
var lc = data.line_count || 0;
|
|
1556
|
+
var tl = data.total_lines || pageTotalLines;
|
|
1557
|
+
var tb = data.total_bytes || pageTotalBytes;
|
|
1558
|
+
setPageStartLine(sl);
|
|
1559
|
+
setPageLineCount(lc);
|
|
1560
|
+
setPageTotalLines(tl);
|
|
1561
|
+
setPageTotalBytes(tb);
|
|
1562
|
+
if (monacoRef.current) {
|
|
1563
|
+
monacoRef.current.setValue(data.content || '');
|
|
1564
|
+
var _paginatedEntry = window.__mbeditorModels && window.__mbeditorModels[tab.path];
|
|
1565
|
+
if (_paginatedEntry) {
|
|
1566
|
+
var _newBase = monacoRef.current.getModel().getAlternativeVersionId();
|
|
1567
|
+
aviBaseRef.current = _newBase;
|
|
1568
|
+
aviMaxRef.current = _newBase;
|
|
1569
|
+
_paginatedEntry.cleanVersionId = _newBase;
|
|
1570
|
+
_paginatedEntry.aviBase = _newBase;
|
|
1571
|
+
_paginatedEntry.aviMax = _newBase;
|
|
1572
|
+
}
|
|
1573
|
+
EditorStore.setState({ canUndo: false, canRedo: false });
|
|
1574
|
+
monacoRef.current.updateOptions({ readOnly: true });
|
|
1575
|
+
}
|
|
1576
|
+
}).catch(function(err) {
|
|
1577
|
+
EditorStore.setStatus('Failed to load page: ' + (err && err.message || 'Unknown error'), 'error');
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
},
|
|
1581
|
+
'← Prev'
|
|
1582
|
+
),
|
|
1583
|
+
React.createElement(
|
|
1584
|
+
'span',
|
|
1585
|
+
{ style: { flex: 1, textAlign: 'center' } },
|
|
1586
|
+
'Lines ' + (pageStartLine + 1) + '–' + (pageStartLine + pageLineCount) + ' of ' + pageTotalLines + ' (' + humanSize(pageTotalBytes) + ')'
|
|
1587
|
+
),
|
|
1588
|
+
React.createElement(
|
|
1589
|
+
'button',
|
|
1590
|
+
{
|
|
1591
|
+
className: 'ide-icon-btn',
|
|
1592
|
+
style: { padding: '2px 8px', fontSize: '12px' },
|
|
1593
|
+
disabled: pageStartLine + pageLineCount >= pageTotalLines,
|
|
1594
|
+
onClick: function() {
|
|
1595
|
+
var newStart = pageStartLine + pageLineCount;
|
|
1596
|
+
FileService.getFileChunk(tab.path, newStart, 500).then(function(data) {
|
|
1597
|
+
var sl = data.start_line || 0;
|
|
1598
|
+
var lc = data.line_count || 0;
|
|
1599
|
+
var tl = data.total_lines || pageTotalLines;
|
|
1600
|
+
var tb = data.total_bytes || pageTotalBytes;
|
|
1601
|
+
setPageStartLine(sl);
|
|
1602
|
+
setPageLineCount(lc);
|
|
1603
|
+
setPageTotalLines(tl);
|
|
1604
|
+
setPageTotalBytes(tb);
|
|
1605
|
+
if (monacoRef.current) {
|
|
1606
|
+
monacoRef.current.setValue(data.content || '');
|
|
1607
|
+
var _paginatedEntry = window.__mbeditorModels && window.__mbeditorModels[tab.path];
|
|
1608
|
+
if (_paginatedEntry) {
|
|
1609
|
+
var _newBase = monacoRef.current.getModel().getAlternativeVersionId();
|
|
1610
|
+
aviBaseRef.current = _newBase;
|
|
1611
|
+
aviMaxRef.current = _newBase;
|
|
1612
|
+
_paginatedEntry.cleanVersionId = _newBase;
|
|
1613
|
+
_paginatedEntry.aviBase = _newBase;
|
|
1614
|
+
_paginatedEntry.aviMax = _newBase;
|
|
1615
|
+
}
|
|
1616
|
+
EditorStore.setState({ canUndo: false, canRedo: false });
|
|
1617
|
+
monacoRef.current.updateOptions({ readOnly: true });
|
|
1618
|
+
}
|
|
1619
|
+
}).catch(function(err) {
|
|
1620
|
+
EditorStore.setStatus('Failed to load page: ' + (err && err.message || 'Unknown error'), 'error');
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
},
|
|
1624
|
+
'Next →'
|
|
1625
|
+
)
|
|
1626
|
+
),
|
|
1440
1627
|
React.createElement('div', { ref: editorRef, className: 'monaco-container', style: { flex: 1, minHeight: 0 } }),
|
|
1441
1628
|
methodsOpen && methodsDropdownPos && React.createElement(
|
|
1442
1629
|
'div',
|
|
@@ -51,6 +51,8 @@ var FileTree = function FileTree(_ref) {
|
|
|
51
51
|
var containerRef = useRef(null);
|
|
52
52
|
var typeaheadBufferRef = useRef('');
|
|
53
53
|
var typeaheadTimerRef = useRef(null);
|
|
54
|
+
var hoverTimerRef = useRef(null);
|
|
55
|
+
var hoverPathRef = useRef(null);
|
|
54
56
|
// Ref that always points to the latest onNodeSelect prop, avoiding stale closures in the effect.
|
|
55
57
|
var onNodeSelectRef = useRef(onNodeSelect);
|
|
56
58
|
onNodeSelectRef.current = onNodeSelect;
|
|
@@ -131,6 +133,12 @@ var FileTree = function FileTree(_ref) {
|
|
|
131
133
|
}
|
|
132
134
|
}, [pendingCreate, pendingRename]);
|
|
133
135
|
|
|
136
|
+
// Clear any pending hover timer when the component unmounts to prevent
|
|
137
|
+
// a prefetch from firing against an unmounted component.
|
|
138
|
+
useEffect(function() {
|
|
139
|
+
return function() { clearTimeout(hoverTimerRef.current); };
|
|
140
|
+
}, []);
|
|
141
|
+
|
|
134
142
|
var toggleFolder = function toggleFolder(path, e) {
|
|
135
143
|
e.stopPropagation();
|
|
136
144
|
var next = !(expandedDirs && expandedDirs[path]);
|
|
@@ -441,6 +449,20 @@ var FileTree = function FileTree(_ref) {
|
|
|
441
449
|
onFileDoubleClick(node.path, node.name);
|
|
442
450
|
}
|
|
443
451
|
},
|
|
452
|
+
onMouseEnter: function () {
|
|
453
|
+
if (isFolder) return;
|
|
454
|
+
clearTimeout(hoverTimerRef.current);
|
|
455
|
+
hoverTimerRef.current = setTimeout(function () {
|
|
456
|
+
hoverPathRef.current = node.path;
|
|
457
|
+
FileService.prefetch(node.path);
|
|
458
|
+
}, 200);
|
|
459
|
+
},
|
|
460
|
+
onMouseLeave: function () {
|
|
461
|
+
if (isFolder) return;
|
|
462
|
+
clearTimeout(hoverTimerRef.current);
|
|
463
|
+
FileService.cancelPrefetch(hoverPathRef.current);
|
|
464
|
+
hoverPathRef.current = null;
|
|
465
|
+
},
|
|
444
466
|
onContextMenu: function (e) {
|
|
445
467
|
e.preventDefault();
|
|
446
468
|
e.stopPropagation();
|
|
@@ -498,7 +520,7 @@ var FileTreeMemo = React.memo(FileTree, function(prev, next) {
|
|
|
498
520
|
prev.activePath === next.activePath &&
|
|
499
521
|
prev.selectedPaths === next.selectedPaths &&
|
|
500
522
|
prev.anchorPath === next.anchorPath &&
|
|
501
|
-
prev.gitFiles === next.gitFiles &&
|
|
523
|
+
JSON.stringify(prev.gitFiles) === JSON.stringify(next.gitFiles) &&
|
|
502
524
|
prev.expandedDirs === next.expandedDirs &&
|
|
503
525
|
prev.pendingCreate === next.pendingCreate &&
|
|
504
526
|
prev.pendingRename === next.pendingRename;
|