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,9 +63,51 @@ var DEFAULT_EDITOR_PREFS = {
|
|
|
63
63
|
quickOpenShowFolders: false,
|
|
64
64
|
tabDisplayMode: 'scroll',
|
|
65
65
|
persistFindState: true,
|
|
66
|
-
showDotFiles: false
|
|
66
|
+
showDotFiles: false,
|
|
67
|
+
branchStateRestore: true
|
|
67
68
|
};
|
|
68
69
|
|
|
70
|
+
// Detect the minimum number of leading spaces used for indentation across all
|
|
71
|
+
// indented lines in the code. Returns 0 if no space-indented lines are found
|
|
72
|
+
// (e.g. file already uses tabs or has no indented lines).
|
|
73
|
+
function detectIndentWidth(code) {
|
|
74
|
+
var min = Infinity;
|
|
75
|
+
code.split('\n').forEach(function(line) {
|
|
76
|
+
if (!line.trim()) return;
|
|
77
|
+
var m = line.match(/^( +)/);
|
|
78
|
+
if (m) min = Math.min(min, m[1].length);
|
|
79
|
+
});
|
|
80
|
+
return min === Infinity ? 0 : min;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Convert leading space-based indentation to tabs using the detected unit size.
|
|
84
|
+
function spacesToTabs(code, indentSize) {
|
|
85
|
+
var unit = ' '.repeat(indentSize);
|
|
86
|
+
return code.split('\n').map(function(line) {
|
|
87
|
+
var tabs = '';
|
|
88
|
+
while (line.startsWith(unit)) { tabs += '\t'; line = line.slice(unit.length); }
|
|
89
|
+
return tabs + line;
|
|
90
|
+
}).join('\n');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function diffLines(oldLines, newLines) {
|
|
94
|
+
var n = oldLines.length, m = newLines.length;
|
|
95
|
+
var dp = [];
|
|
96
|
+
for (var i = 0; i <= n; i++) { dp.push(new Array(m + 1).fill(0)); }
|
|
97
|
+
for (var i = 1; i <= n; i++) {
|
|
98
|
+
for (var j = 1; j <= m; j++) {
|
|
99
|
+
dp[i][j] = oldLines[i-1] === newLines[j-1] ? dp[i-1][j-1] + 1 : Math.max(dp[i-1][j], dp[i][j-1]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
var changed = [], i = n, j = m;
|
|
103
|
+
while (i > 0 || j > 0) {
|
|
104
|
+
if (i > 0 && j > 0 && oldLines[i-1] === newLines[j-1]) { i--; j--; }
|
|
105
|
+
else if (j > 0 && (i === 0 || dp[i][j-1] >= dp[i-1][j])) { changed.push(j); j--; }
|
|
106
|
+
else { i--; }
|
|
107
|
+
}
|
|
108
|
+
return changed;
|
|
109
|
+
}
|
|
110
|
+
|
|
69
111
|
var SidebarActionButton = function SidebarActionButton(_ref) {
|
|
70
112
|
var title = _ref.title;
|
|
71
113
|
var iconClass = _ref.iconClass;
|
|
@@ -111,6 +153,44 @@ var SectionActionGroup = function SectionActionGroup(_ref2) {
|
|
|
111
153
|
);
|
|
112
154
|
};
|
|
113
155
|
|
|
156
|
+
function FileReloadBanner(_ref) {
|
|
157
|
+
var pendingReloads = _ref.pendingReloads;
|
|
158
|
+
var onSaveAndReload = _ref.onSaveAndReload;
|
|
159
|
+
var onDiscardAndReload = _ref.onDiscardAndReload;
|
|
160
|
+
var onKeepMine = _ref.onKeepMine;
|
|
161
|
+
if (!pendingReloads || pendingReloads.length === 0) return null;
|
|
162
|
+
return React.createElement(
|
|
163
|
+
'div', { className: 'mb-file-reload-banner' },
|
|
164
|
+
pendingReloads.map(function (r) {
|
|
165
|
+
return React.createElement(
|
|
166
|
+
'div', { key: r.paneId + ':' + r.tabId, className: 'mb-file-reload-item' },
|
|
167
|
+
React.createElement(
|
|
168
|
+
'span', { className: 'mb-file-reload-msg' },
|
|
169
|
+
React.createElement('i', { className: 'fas fa-sync-alt' }),
|
|
170
|
+
' ',
|
|
171
|
+
React.createElement('strong', null, r.name),
|
|
172
|
+
' was updated externally'
|
|
173
|
+
),
|
|
174
|
+
React.createElement(
|
|
175
|
+
'div', { className: 'mb-file-reload-actions' },
|
|
176
|
+
React.createElement('button', {
|
|
177
|
+
className: 'mb-btn mb-btn-sm mb-btn-primary',
|
|
178
|
+
onClick: function () { onSaveAndReload(r); }
|
|
179
|
+
}, 'Save & Reload'),
|
|
180
|
+
React.createElement('button', {
|
|
181
|
+
className: 'mb-btn mb-btn-sm mb-btn-warning',
|
|
182
|
+
onClick: function () { onDiscardAndReload(r); }
|
|
183
|
+
}, 'Discard & Reload'),
|
|
184
|
+
React.createElement('button', {
|
|
185
|
+
className: 'mb-btn mb-btn-sm',
|
|
186
|
+
onClick: function () { onKeepMine(r); }
|
|
187
|
+
}, 'Keep Mine')
|
|
188
|
+
)
|
|
189
|
+
);
|
|
190
|
+
})
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
114
194
|
var MbeditorApp = function MbeditorApp() {
|
|
115
195
|
var _useState = useState(EditorStore.getState());
|
|
116
196
|
|
|
@@ -246,6 +326,31 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
246
326
|
var sidebarCollapsed = _useStateSC2[0];
|
|
247
327
|
var setSidebarCollapsed = _useStateSC2[1];
|
|
248
328
|
|
|
329
|
+
var _useStateRFMap = useState({});
|
|
330
|
+
var _useStateRFMap2 = _slicedToArray(_useStateRFMap, 2);
|
|
331
|
+
var railsFilesMap = _useStateRFMap2[0];
|
|
332
|
+
var setRailsFilesMap = _useStateRFMap2[1];
|
|
333
|
+
|
|
334
|
+
var _useStateRFC = useState({});
|
|
335
|
+
var _useStateRFC2 = _slicedToArray(_useStateRFC, 2);
|
|
336
|
+
var railsGroupsCollapsed = _useStateRFC2[0];
|
|
337
|
+
var setRailsGroupsCollapsed = _useStateRFC2[1];
|
|
338
|
+
|
|
339
|
+
var _useStateChangelog = useState(null); // null | { content, loading, error }
|
|
340
|
+
var _useStateChangelog2 = _slicedToArray(_useStateChangelog, 2);
|
|
341
|
+
var changelogState = _useStateChangelog2[0];
|
|
342
|
+
var setChangelogState = _useStateChangelog2[1];
|
|
343
|
+
|
|
344
|
+
var _useStateSchemaModal = useState(null);
|
|
345
|
+
var _useStateSchemaModal2 = _slicedToArray(_useStateSchemaModal, 2);
|
|
346
|
+
var schemaModal = _useStateSchemaModal2[0];
|
|
347
|
+
var setSchemaModal = _useStateSchemaModal2[1];
|
|
348
|
+
|
|
349
|
+
var _useStateSchemaLoading = useState(null);
|
|
350
|
+
var _useStateSchemaLoading2 = _slicedToArray(_useStateSchemaLoading, 2);
|
|
351
|
+
var schemaLoadingLabel = _useStateSchemaLoading2[0];
|
|
352
|
+
var setSchemaLoadingLabel = _useStateSchemaLoading2[1];
|
|
353
|
+
|
|
249
354
|
var _useState9 = useState({});
|
|
250
355
|
|
|
251
356
|
var _useState92 = _slicedToArray(_useState9, 2);
|
|
@@ -458,6 +563,16 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
458
563
|
var prevGitBranchRef = useRef(null);
|
|
459
564
|
var isSwitchingBranchRef = useRef(false);
|
|
460
565
|
var stateRestoredRef = useRef(false);
|
|
566
|
+
var ctrlWPendingRef = useRef(false);
|
|
567
|
+
var ctrlWTimeoutRef = useRef(null);
|
|
568
|
+
var _useStateCP = useState([]);
|
|
569
|
+
var _useStateCP2 = _slicedToArray(_useStateCP, 2);
|
|
570
|
+
var customPaths = _useStateCP2[0];
|
|
571
|
+
var setCustomPaths = _useStateCP2[1];
|
|
572
|
+
var customPathsRef = useRef([]);
|
|
573
|
+
customPathsRef.current = customPaths;
|
|
574
|
+
var recentSavesRef = useRef({});
|
|
575
|
+
var isSavingRef = useRef(false);
|
|
461
576
|
|
|
462
577
|
// ── Draft backup helpers ─────────────────────────────────────────────────
|
|
463
578
|
var draftWriteTimerRef = useRef({});
|
|
@@ -468,7 +583,14 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
468
583
|
return 'mbeditor_draft\x00' + base + '\x00' + path;
|
|
469
584
|
};
|
|
470
585
|
var _saveDraftNow = function _saveDraftNow(path, content) {
|
|
471
|
-
|
|
586
|
+
var doWrite = function() {
|
|
587
|
+
try { localStorage.setItem(_draftKey(path), JSON.stringify({ content: content, ts: Date.now() })); } catch (e) {}
|
|
588
|
+
};
|
|
589
|
+
if (typeof requestIdleCallback !== 'undefined') {
|
|
590
|
+
requestIdleCallback(doWrite, { timeout: 2000 });
|
|
591
|
+
} else {
|
|
592
|
+
doWrite();
|
|
593
|
+
}
|
|
472
594
|
};
|
|
473
595
|
var _clearDraft = function _clearDraft(path) {
|
|
474
596
|
try { localStorage.removeItem(_draftKey(path)); } catch (e) {}
|
|
@@ -499,6 +621,11 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
499
621
|
var zenMode = _useStateZen2[0];
|
|
500
622
|
var setZenMode = _useStateZen2[1];
|
|
501
623
|
|
|
624
|
+
var _useStateSB = useState(false);
|
|
625
|
+
var _useStateSB2 = _slicedToArray(_useStateSB, 2);
|
|
626
|
+
var isSwitchingBranch = _useStateSB2[0];
|
|
627
|
+
var setIsSwitchingBranch = _useStateSB2[1];
|
|
628
|
+
|
|
502
629
|
var clamp = function clamp(value, min, max) {
|
|
503
630
|
return Math.min(max, Math.max(min, value));
|
|
504
631
|
};
|
|
@@ -746,6 +873,9 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
746
873
|
if (t.isSettings || t.path === '__settings__') {
|
|
747
874
|
return Promise.resolve({ content: '' });
|
|
748
875
|
}
|
|
876
|
+
if (t.isChangelog || t.path === 'mbeditor://changelog') {
|
|
877
|
+
return Promise.resolve({ content: '' });
|
|
878
|
+
}
|
|
749
879
|
if (t.isDiff && t.repoPath) {
|
|
750
880
|
return GitService.fetchDiff(t.repoPath, t.diffBaseSha, t.diffHeadSha)
|
|
751
881
|
.then(function (d) { return { content: 'Diff loaded', diffOriginal: d.original || '', diffModified: d.modified || '', _isDiffResult: true }; })
|
|
@@ -849,26 +979,33 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
849
979
|
if (!newBranch || newBranch === oldBranch) return;
|
|
850
980
|
prevGitBranchRef.current = newBranch;
|
|
851
981
|
if (!oldBranch || isSwitchingBranchRef.current) return;
|
|
982
|
+
// Ignore spurious branch changes triggered by saves (race condition)
|
|
983
|
+
if (isSavingRef.current) return;
|
|
852
984
|
|
|
853
985
|
isSwitchingBranchRef.current = true;
|
|
986
|
+
setIsSwitchingBranch(true);
|
|
854
987
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
988
|
+
var shouldRestore = (EditorStore.getState().editorPrefs || {}).branchStateRestore !== false;
|
|
989
|
+
|
|
990
|
+
if (shouldRestore) {
|
|
991
|
+
// Save pane state for old branch before switching
|
|
992
|
+
var cur = EditorStore.getState();
|
|
993
|
+
var lightweightPanes = cur.panes.map(function (p) {
|
|
994
|
+
return {
|
|
995
|
+
id: p.id,
|
|
996
|
+
activeTabId: p.activeTabId,
|
|
997
|
+
tabs: p.tabs.filter(function (t) { return !t.isCombinedDiff; }).map(function (t) {
|
|
998
|
+
return {
|
|
999
|
+
id: t.id, path: t.path, name: t.name, dirty: t.dirty, viewState: t.viewState,
|
|
1000
|
+
isSettings: !!t.isSettings, isPreview: !!t.isPreview, previewFor: t.previewFor || null,
|
|
1001
|
+
isDiff: !!t.isDiff, diffBaseSha: t.diffBaseSha || null, diffHeadSha: t.diffHeadSha || null,
|
|
1002
|
+
repoPath: t.repoPath || null, isChangelog: !!t.isChangelog
|
|
1003
|
+
};
|
|
1004
|
+
})
|
|
1005
|
+
};
|
|
1006
|
+
});
|
|
1007
|
+
FileService.saveBranchState(oldBranch, { panes: lightweightPanes, focusedPaneId: cur.focusedPaneId })["catch"](function () {});
|
|
1008
|
+
}
|
|
872
1009
|
|
|
873
1010
|
// Clear all open tabs for the new branch
|
|
874
1011
|
EditorStore.setState({
|
|
@@ -878,18 +1015,27 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
878
1015
|
});
|
|
879
1016
|
|
|
880
1017
|
// Load pane state for new branch (or start empty)
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1018
|
+
var restorePromise;
|
|
1019
|
+
if (shouldRestore) {
|
|
1020
|
+
restorePromise = FileService.getBranchState(newBranch)["catch"](function () { return null; }).then(function (branchState) {
|
|
1021
|
+
var hasBranchPanes = branchState && branchState.panes && branchState.panes.some(function (p) { return p.tabs && p.tabs.length > 0; });
|
|
1022
|
+
if (hasBranchPanes) {
|
|
1023
|
+
return loadPaneState(branchState.panes, branchState.focusedPaneId || 1);
|
|
1024
|
+
}
|
|
1025
|
+
return null;
|
|
1026
|
+
});
|
|
1027
|
+
} else {
|
|
1028
|
+
restorePromise = Promise.resolve(null);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
restorePromise.then(function () {
|
|
888
1032
|
// Prune states for deleted branches
|
|
889
1033
|
FileService.pruneBranchStates()["catch"](function () {});
|
|
890
1034
|
isSwitchingBranchRef.current = false;
|
|
1035
|
+
setIsSwitchingBranch(false);
|
|
891
1036
|
})["catch"](function () {
|
|
892
1037
|
isSwitchingBranchRef.current = false;
|
|
1038
|
+
setIsSwitchingBranch(false);
|
|
893
1039
|
});
|
|
894
1040
|
});
|
|
895
1041
|
|
|
@@ -962,7 +1108,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
962
1108
|
var rect = body.getBoundingClientRect();
|
|
963
1109
|
var reservedRight = EDITOR_MIN_WIDTH + (showGitPanelRef.current ? gitPanelWidthRef.current : 0);
|
|
964
1110
|
var maxSidebarWidth = Math.min(SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_MIN_WIDTH, rect.width - reservedRight));
|
|
965
|
-
var nextWidth = clientX - rect.left;
|
|
1111
|
+
var nextWidth = clientX - rect.left - SIDEBAR_COLLAPSED_WIDTH;
|
|
966
1112
|
setSidebarWidth(clamp(nextWidth, SIDEBAR_MIN_WIDTH, maxSidebarWidth));
|
|
967
1113
|
}
|
|
968
1114
|
|
|
@@ -1006,8 +1152,46 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1006
1152
|
}
|
|
1007
1153
|
};
|
|
1008
1154
|
|
|
1155
|
+
// Capture-phase listener for vim Ctrl+W window navigation.
|
|
1156
|
+
// Runs before Monaco (and the browser) so neither can swallow the keystrokes.
|
|
1157
|
+
// Phase 1 — Ctrl+W: prevent tab-close and arm the pending flag.
|
|
1158
|
+
// Phase 2 — next key: act on it here (still capture phase) so Monaco-vim
|
|
1159
|
+
// cannot consume it as a word-motion or other binding.
|
|
1160
|
+
var onCtrlWCapture = function(e) {
|
|
1161
|
+
// Phase 2: a previous Ctrl+W is pending — consume the follow-up key.
|
|
1162
|
+
if (ctrlWPendingRef.current) {
|
|
1163
|
+
ctrlWPendingRef.current = false;
|
|
1164
|
+
if (ctrlWTimeoutRef.current) { clearTimeout(ctrlWTimeoutRef.current); ctrlWTimeoutRef.current = null; }
|
|
1165
|
+
var _st = EditorStore.getState();
|
|
1166
|
+
var _cur = _st.focusedPaneId;
|
|
1167
|
+
var _target;
|
|
1168
|
+
if (e.key === '1') _target = 1;
|
|
1169
|
+
else if (e.key === '2') _target = 2;
|
|
1170
|
+
else if (e.key === 'h') _target = 1;
|
|
1171
|
+
else _target = _cur === 1 ? 2 : 1; // w, l, Ctrl+W, or anything else → cycle
|
|
1172
|
+
if (_target !== _cur) {
|
|
1173
|
+
if (typeof TabManager !== 'undefined') TabManager.focusPane(_target);
|
|
1174
|
+
window.dispatchEvent(new CustomEvent('mbeditor:focusPane', { detail: { paneId: _target } }));
|
|
1175
|
+
}
|
|
1176
|
+
e.preventDefault();
|
|
1177
|
+
e.stopPropagation();
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
// Phase 1: intercept Ctrl+W itself when vim mode is on.
|
|
1181
|
+
if (e.metaKey || e.shiftKey || e.altKey) return;
|
|
1182
|
+
if (!e.ctrlKey || (e.key !== 'w' && e.key !== 'W')) return;
|
|
1183
|
+
var prefs = EditorStore.getState().editorPrefs;
|
|
1184
|
+
if (!prefs || !prefs.vimMode) return;
|
|
1185
|
+
e.preventDefault();
|
|
1186
|
+
e.stopPropagation();
|
|
1187
|
+
if (ctrlWTimeoutRef.current) clearTimeout(ctrlWTimeoutRef.current);
|
|
1188
|
+
ctrlWPendingRef.current = true;
|
|
1189
|
+
ctrlWTimeoutRef.current = setTimeout(function() { ctrlWPendingRef.current = false; }, 1500);
|
|
1190
|
+
};
|
|
1191
|
+
|
|
1009
1192
|
window.addEventListener('keydown', onKeyDown);
|
|
1010
1193
|
document.addEventListener('keydown', onZenCapture, true);
|
|
1194
|
+
document.addEventListener('keydown', onCtrlWCapture, true);
|
|
1011
1195
|
window.addEventListener('mousemove', handleMouseMove);
|
|
1012
1196
|
window.addEventListener('mouseup', handleMouseUp);
|
|
1013
1197
|
return function () {
|
|
@@ -1018,8 +1202,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1018
1202
|
cancelAnimationFrame(resizeRafRef.current);
|
|
1019
1203
|
resizeRafRef.current = null;
|
|
1020
1204
|
}
|
|
1205
|
+
if (ctrlWTimeoutRef.current) { clearTimeout(ctrlWTimeoutRef.current); ctrlWTimeoutRef.current = null; }
|
|
1021
1206
|
window.removeEventListener('keydown', onKeyDown);
|
|
1022
1207
|
document.removeEventListener('keydown', onZenCapture, true);
|
|
1208
|
+
document.removeEventListener('keydown', onCtrlWCapture, true);
|
|
1023
1209
|
window.removeEventListener('mousemove', handleMouseMove);
|
|
1024
1210
|
window.removeEventListener('mouseup', handleMouseUp);
|
|
1025
1211
|
document.body.style.cursor = '';
|
|
@@ -1095,16 +1281,83 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1095
1281
|
FileService.getTree().then(function (data) {
|
|
1096
1282
|
var newData = data || [];
|
|
1097
1283
|
setTreeData(function (prevData) {
|
|
1098
|
-
|
|
1099
|
-
SearchService.buildIndex(newData);
|
|
1284
|
+
var sig = function(d) { return d.length + ':' + d.map(function(n) { return n.name; }).join(','); };
|
|
1285
|
+
if (sig(newData) !== sig(prevData)) SearchService.buildIndex(newData);
|
|
1100
1286
|
return newData;
|
|
1101
1287
|
});
|
|
1288
|
+
checkOpenTabsForExternalChanges();
|
|
1102
1289
|
})["catch"](function () {});
|
|
1103
1290
|
}
|
|
1104
1291
|
WebSocketService.onFilesChanged(handleFilesChanged);
|
|
1105
1292
|
return function () { WebSocketService.offFilesChanged(handleFilesChanged); };
|
|
1106
1293
|
}, []);
|
|
1107
1294
|
|
|
1295
|
+
function checkOpenTabsForExternalChanges() {
|
|
1296
|
+
var st = EditorStore.getState();
|
|
1297
|
+
var allTabs = st.panes.reduce(function (acc, p) {
|
|
1298
|
+
return acc.concat(p.tabs.map(function (t) { return { paneId: p.id, tab: t }; }));
|
|
1299
|
+
}, []);
|
|
1300
|
+
var fileTabs = allTabs.filter(function (pt) {
|
|
1301
|
+
var path = pt.tab.path || '';
|
|
1302
|
+
return path &&
|
|
1303
|
+
!path.startsWith('mbeditor://') &&
|
|
1304
|
+
!path.startsWith('diff://') &&
|
|
1305
|
+
!path.startsWith('combined-diff://') &&
|
|
1306
|
+
!pt.tab.isCombinedDiff &&
|
|
1307
|
+
!pt.tab.isSettings &&
|
|
1308
|
+
!pt.tab.isImage &&
|
|
1309
|
+
!pt.tab.isDiff &&
|
|
1310
|
+
typeof pt.tab.content === 'string';
|
|
1311
|
+
});
|
|
1312
|
+
fileTabs.forEach(function (pt) {
|
|
1313
|
+
var savedAt = recentSavesRef.current[pt.tab.path];
|
|
1314
|
+
if (savedAt && Date.now() - savedAt < 3000) return;
|
|
1315
|
+
FileService.getFile(pt.tab.path, { allowMissing: true }).then(function (data) {
|
|
1316
|
+
if (!data || typeof data.content !== 'string') return;
|
|
1317
|
+
var serverNorm = data.content.replace(/\r\n/g, '\n');
|
|
1318
|
+
var tabNorm = (pt.tab.content || '').replace(/\r\n/g, '\n');
|
|
1319
|
+
if (serverNorm === tabNorm) return;
|
|
1320
|
+
if (!pt.tab.dirty) {
|
|
1321
|
+
EditorStore.setState({
|
|
1322
|
+
panes: EditorStore.getState().panes.map(function (p) {
|
|
1323
|
+
if (p.id !== pt.paneId) return p;
|
|
1324
|
+
return Object.assign({}, p, {
|
|
1325
|
+
tabs: p.tabs.map(function (t) {
|
|
1326
|
+
if (t.id !== pt.tab.id) return t;
|
|
1327
|
+
return Object.assign({}, t, {
|
|
1328
|
+
content: data.content,
|
|
1329
|
+
externalContentVersion: (t.externalContentVersion || 0) + 1
|
|
1330
|
+
});
|
|
1331
|
+
})
|
|
1332
|
+
});
|
|
1333
|
+
})
|
|
1334
|
+
});
|
|
1335
|
+
} else {
|
|
1336
|
+
// Re-verify the tab still exists before queuing
|
|
1337
|
+
var currentState = EditorStore.getState();
|
|
1338
|
+
var stillExists = currentState.panes.some(function (p) {
|
|
1339
|
+
return p.id === pt.paneId && p.tabs.some(function (t) { return t.id === pt.tab.id; });
|
|
1340
|
+
});
|
|
1341
|
+
if (!stillExists) return;
|
|
1342
|
+
var existing = EditorStore.getState().pendingReloads.find(function (r) {
|
|
1343
|
+
return r.paneId === pt.paneId && r.tabId === pt.tab.id;
|
|
1344
|
+
});
|
|
1345
|
+
if (!existing) {
|
|
1346
|
+
EditorStore.setState({
|
|
1347
|
+
pendingReloads: EditorStore.getState().pendingReloads.concat([{
|
|
1348
|
+
paneId: pt.paneId,
|
|
1349
|
+
tabId: pt.tab.id,
|
|
1350
|
+
path: pt.tab.path,
|
|
1351
|
+
name: pt.tab.name,
|
|
1352
|
+
serverContent: data.content
|
|
1353
|
+
}])
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
})["catch"](function () {});
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1108
1361
|
// Auto-refresh the file tree every 10s to pick up external changes (new files, deletions, etc.)
|
|
1109
1362
|
// When an ActionCable WebSocket is connected this acts only as a safety-net fallback —
|
|
1110
1363
|
// the WebSocket push above handles immediate invalidation after mbeditor mutations.
|
|
@@ -1118,8 +1371,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1118
1371
|
FileService.getTree().then(function (data) {
|
|
1119
1372
|
var newData = data || [];
|
|
1120
1373
|
setTreeData(function (prevData) {
|
|
1121
|
-
|
|
1122
|
-
SearchService.buildIndex(newData);
|
|
1374
|
+
var sig = function(d) { return d.length + ':' + d.map(function(n) { return n.name; }).join(','); };
|
|
1375
|
+
if (sig(newData) !== sig(prevData)) SearchService.buildIndex(newData);
|
|
1123
1376
|
return newData;
|
|
1124
1377
|
});
|
|
1125
1378
|
}).catch(function () {}); // silently ignore auto-refresh errors
|
|
@@ -1127,8 +1380,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1127
1380
|
return function () { clearInterval(intervalId); };
|
|
1128
1381
|
}, []);
|
|
1129
1382
|
|
|
1130
|
-
var handleSelectFile = function handleSelectFile(path, name, line) {
|
|
1131
|
-
TabManager.openTab(path, name, line);
|
|
1383
|
+
var handleSelectFile = function handleSelectFile(path, name, line, col) {
|
|
1384
|
+
TabManager.openTab(path, name, line, null, false, col);
|
|
1132
1385
|
handleNodeSelect({ path: path, name: name || path.split('/').pop(), type: 'file' });
|
|
1133
1386
|
setQuickOpen(false);
|
|
1134
1387
|
};
|
|
@@ -1168,6 +1421,13 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1168
1421
|
setClosingTabId(id);
|
|
1169
1422
|
} else {
|
|
1170
1423
|
TabManager.closeTab(paneId, id);
|
|
1424
|
+
EditorStore.setState({
|
|
1425
|
+
pendingReloads: EditorStore.getState().pendingReloads.filter(function (r) {
|
|
1426
|
+
return EditorStore.getState().panes.some(function (p) {
|
|
1427
|
+
return p.tabs.some(function (t) { return t.id === r.tabId; });
|
|
1428
|
+
});
|
|
1429
|
+
})
|
|
1430
|
+
});
|
|
1171
1431
|
}
|
|
1172
1432
|
};
|
|
1173
1433
|
|
|
@@ -1189,7 +1449,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1189
1449
|
return _extends({}, prev, { save: true });
|
|
1190
1450
|
});
|
|
1191
1451
|
EditorStore.setStatus("Saving " + tab.name + "...", "info");
|
|
1452
|
+
isSavingRef.current = true;
|
|
1192
1453
|
FileService.saveFile(tab.path, tab.content).then(function () {
|
|
1454
|
+
recentSavesRef.current[tab.path] = Date.now();
|
|
1455
|
+
setTimeout(function() { delete recentSavesRef.current[tab.path]; }, 3500);
|
|
1193
1456
|
EditorStore.setStatus("Saved", "success");
|
|
1194
1457
|
SearchService.invalidate();
|
|
1195
1458
|
GitService.fetchStatus();
|
|
@@ -1199,9 +1462,17 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1199
1462
|
_closeEntry.cleanVersionId = _closeEntry.model.getAlternativeVersionId();
|
|
1200
1463
|
}
|
|
1201
1464
|
TabManager.closeTab(closingPaneId, tab.id);
|
|
1465
|
+
EditorStore.setState({
|
|
1466
|
+
pendingReloads: EditorStore.getState().pendingReloads.filter(function (r) {
|
|
1467
|
+
return EditorStore.getState().panes.some(function (p) {
|
|
1468
|
+
return p.tabs.some(function (t) { return t.id === r.tabId; });
|
|
1469
|
+
});
|
|
1470
|
+
})
|
|
1471
|
+
});
|
|
1202
1472
|
})["catch"](function (err) {
|
|
1203
1473
|
EditorStore.setStatus("Save failed: " + err.message, "error");
|
|
1204
1474
|
})["finally"](function () {
|
|
1475
|
+
isSavingRef.current = false;
|
|
1205
1476
|
setLoading(function (prev) {
|
|
1206
1477
|
return _extends({}, prev, { save: false });
|
|
1207
1478
|
});
|
|
@@ -1210,6 +1481,13 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1210
1481
|
});
|
|
1211
1482
|
} else {
|
|
1212
1483
|
TabManager.closeTab(closingPaneId, tab.id);
|
|
1484
|
+
EditorStore.setState({
|
|
1485
|
+
pendingReloads: EditorStore.getState().pendingReloads.filter(function (r) {
|
|
1486
|
+
return EditorStore.getState().panes.some(function (p) {
|
|
1487
|
+
return p.tabs.some(function (t) { return t.id === r.tabId; });
|
|
1488
|
+
});
|
|
1489
|
+
})
|
|
1490
|
+
});
|
|
1213
1491
|
setClosingTabId(null);
|
|
1214
1492
|
setClosingPaneId(null);
|
|
1215
1493
|
}
|
|
@@ -1311,6 +1589,142 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1311
1589
|
return function() { window.removeEventListener('beforeinstallprompt', handler); };
|
|
1312
1590
|
}, []);
|
|
1313
1591
|
|
|
1592
|
+
useEffect(function() {
|
|
1593
|
+
FileService.getClientConfig().then(function(cfg) {
|
|
1594
|
+
setCustomPaths(Array.isArray(cfg.related_files_custom_paths) ? cfg.related_files_custom_paths : []);
|
|
1595
|
+
})['catch'](function() {});
|
|
1596
|
+
}, []);
|
|
1597
|
+
|
|
1598
|
+
// Version-update detection: open the changelog tab automatically when the
|
|
1599
|
+
// gem version has changed since last time the editor was opened.
|
|
1600
|
+
useEffect(function() {
|
|
1601
|
+
var SEEN_KEY = 'mbeditor_seen_version';
|
|
1602
|
+
var current = document.body.dataset.mbeditorVersion || '';
|
|
1603
|
+
var seen = localStorage.getItem(SEEN_KEY) || '';
|
|
1604
|
+
if (current && seen && seen !== current) {
|
|
1605
|
+
// Delay slightly so the editor finishes restoring saved tabs first
|
|
1606
|
+
setTimeout(function() { openChangelogTab(); }, 800);
|
|
1607
|
+
}
|
|
1608
|
+
if (current) localStorage.setItem(SEEN_KEY, current);
|
|
1609
|
+
}, []);
|
|
1610
|
+
|
|
1611
|
+
var resourceLabelFromPath = function(p) {
|
|
1612
|
+
if (!p) return null;
|
|
1613
|
+
var parts = p.split('/');
|
|
1614
|
+
var file = parts[parts.length - 1];
|
|
1615
|
+
var name;
|
|
1616
|
+
if (parts[0] === 'app') {
|
|
1617
|
+
if (parts[1] === 'controllers') name = file.replace(/_controller\.rb$/, '');
|
|
1618
|
+
else if (parts[1] === 'models') name = file.replace(/\.rb$/, '');
|
|
1619
|
+
else if (parts[1] === 'views' && parts.length >= 4) name = parts[2];
|
|
1620
|
+
else if (parts[1] === 'helpers') name = file.replace(/_helper\.rb$/, '');
|
|
1621
|
+
else return null;
|
|
1622
|
+
} else if (parts[0] === 'test' || parts[0] === 'spec') {
|
|
1623
|
+
if (parts[1] === 'controllers') name = file.replace(/_controller_(test|spec)\.rb$/, '');
|
|
1624
|
+
else if (parts[1] === 'models') name = file.replace(/_(test|spec)\.rb$/, '');
|
|
1625
|
+
else return null;
|
|
1626
|
+
} else {
|
|
1627
|
+
// Check custom paths
|
|
1628
|
+
var customPaths = customPathsRef.current;
|
|
1629
|
+
for (var ci = 0; ci < customPaths.length; ci++) {
|
|
1630
|
+
var base = customPaths[ci];
|
|
1631
|
+
if (p.startsWith(base + '/')) {
|
|
1632
|
+
var rest = p.slice(base.length + 1);
|
|
1633
|
+
var resource = rest.split('/')[0].replace(/\.[^.]+$/, '');
|
|
1634
|
+
if (resource) {
|
|
1635
|
+
var seg = resource.replace(/_/g, ' ').replace(/\b\w/g, function(c) { return c.toUpperCase(); });
|
|
1636
|
+
return seg;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
return null;
|
|
1641
|
+
}
|
|
1642
|
+
var seg = (name || '').split('/').pop() || name || '';
|
|
1643
|
+
// Normalize plural→singular so views/users and models/user share one group
|
|
1644
|
+
seg = seg.replace(/ies$/, 'y')
|
|
1645
|
+
.replace(/([^aeiou])es$/, '$1')
|
|
1646
|
+
.replace(/([^s])s$/, '$1');
|
|
1647
|
+
return seg.replace(/_/g, ' ').replace(/\b\w/g, function(c) { return c.toUpperCase(); });
|
|
1648
|
+
};
|
|
1649
|
+
|
|
1650
|
+
var RAILS_MAX_RESOURCES = 10;
|
|
1651
|
+
|
|
1652
|
+
// Map resource label → representative path (capped at RAILS_MAX_RESOURCES, focused pane first)
|
|
1653
|
+
var railsResourceDeps = (function() {
|
|
1654
|
+
var deps = {};
|
|
1655
|
+
var panesOrdered = state.panes.slice().sort(function(a, b) {
|
|
1656
|
+
return a.id === state.focusedPaneId ? -1 : b.id === state.focusedPaneId ? 1 : 0;
|
|
1657
|
+
});
|
|
1658
|
+
panesOrdered.forEach(function(p) {
|
|
1659
|
+
var tabs = p.tabs.slice().sort(function(a, b) {
|
|
1660
|
+
return a.id === p.activeTabId ? -1 : b.id === p.activeTabId ? 1 : 0;
|
|
1661
|
+
});
|
|
1662
|
+
tabs.forEach(function(t) {
|
|
1663
|
+
if (Object.keys(deps).length >= RAILS_MAX_RESOURCES) return;
|
|
1664
|
+
if (!t.path || t.path === '__settings__' || t.path.startsWith('mbeditor://')) return;
|
|
1665
|
+
var label = resourceLabelFromPath(t.path);
|
|
1666
|
+
if (label && !deps[label]) deps[label] = t.path;
|
|
1667
|
+
});
|
|
1668
|
+
});
|
|
1669
|
+
return deps;
|
|
1670
|
+
})();
|
|
1671
|
+
var railsResourceDepStr = Object.keys(railsResourceDeps).sort().join('|');
|
|
1672
|
+
|
|
1673
|
+
var railsOverflow = (function() {
|
|
1674
|
+
var all = {};
|
|
1675
|
+
state.panes.forEach(function(p) {
|
|
1676
|
+
p.tabs.forEach(function(t) {
|
|
1677
|
+
if (!t.path || t.path === '__settings__' || t.path.startsWith('mbeditor://')) return;
|
|
1678
|
+
var label = resourceLabelFromPath(t.path);
|
|
1679
|
+
if (label) all[label] = true;
|
|
1680
|
+
});
|
|
1681
|
+
});
|
|
1682
|
+
return Math.max(0, Object.keys(all).length - Object.keys(railsResourceDeps).length);
|
|
1683
|
+
})();
|
|
1684
|
+
|
|
1685
|
+
var dirtyPaths = (function() {
|
|
1686
|
+
var set = {};
|
|
1687
|
+
state.panes.forEach(function(p) {
|
|
1688
|
+
p.tabs.forEach(function(t) {
|
|
1689
|
+
if (t.dirty && t.path) set[t.path] = true;
|
|
1690
|
+
});
|
|
1691
|
+
});
|
|
1692
|
+
return set;
|
|
1693
|
+
})();
|
|
1694
|
+
|
|
1695
|
+
useEffect(function() {
|
|
1696
|
+
if (activeSidebarTab !== 'rails') return;
|
|
1697
|
+
var labels = Object.keys(railsResourceDeps);
|
|
1698
|
+
if (labels.length === 0) { setRailsFilesMap({}); return; }
|
|
1699
|
+
setRailsFilesMap(function(prev) {
|
|
1700
|
+
var next = {};
|
|
1701
|
+
labels.forEach(function(label) {
|
|
1702
|
+
next[label] = prev[label] ? { files: prev[label].files, loading: true } : { files: null, loading: true };
|
|
1703
|
+
});
|
|
1704
|
+
return next;
|
|
1705
|
+
});
|
|
1706
|
+
labels.forEach(function(label) {
|
|
1707
|
+
var path = railsResourceDeps[label];
|
|
1708
|
+
FileService.getRelatedFiles(path).then(function(data) {
|
|
1709
|
+
setRailsFilesMap(function(prev) {
|
|
1710
|
+
if (!prev.hasOwnProperty(label)) return prev;
|
|
1711
|
+
var next = Object.assign({}, prev);
|
|
1712
|
+
var update = {};
|
|
1713
|
+
update[label] = { files: data, loading: false };
|
|
1714
|
+
return Object.assign(next, update);
|
|
1715
|
+
});
|
|
1716
|
+
})['catch'](function() {
|
|
1717
|
+
setRailsFilesMap(function(prev) {
|
|
1718
|
+
if (!prev.hasOwnProperty(label)) return prev;
|
|
1719
|
+
var next = Object.assign({}, prev);
|
|
1720
|
+
var update = {};
|
|
1721
|
+
update[label] = { files: null, loading: false };
|
|
1722
|
+
return Object.assign(next, update);
|
|
1723
|
+
});
|
|
1724
|
+
});
|
|
1725
|
+
});
|
|
1726
|
+
}, [activeSidebarTab, railsResourceDepStr]);
|
|
1727
|
+
|
|
1314
1728
|
var focusedPane = state.panes.find(function (p) {
|
|
1315
1729
|
return p.id === state.focusedPaneId;
|
|
1316
1730
|
}) || state.panes[0] || null;
|
|
@@ -1438,7 +1852,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1438
1852
|
return _extends({}, prev, { save: true });
|
|
1439
1853
|
});
|
|
1440
1854
|
EditorStore.setStatus("Saving " + tab.name + "...", "info");
|
|
1855
|
+
isSavingRef.current = true;
|
|
1441
1856
|
FileService.saveFile(tab.path, tab.content).then(function () {
|
|
1857
|
+
recentSavesRef.current[tab.path] = Date.now();
|
|
1858
|
+
setTimeout(function() { delete recentSavesRef.current[tab.path]; }, 3500);
|
|
1442
1859
|
var newPanes = EditorStore.getState().panes.map(function (p) {
|
|
1443
1860
|
if (p.id === paneId) {
|
|
1444
1861
|
return _extends({}, p, { tabs: p.tabs.map(function (t) {
|
|
@@ -1455,6 +1872,9 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1455
1872
|
}
|
|
1456
1873
|
EditorStore.setStatus("Saved", "success");
|
|
1457
1874
|
_clearDraft(tab.path);
|
|
1875
|
+
if (typeof HistoryService !== 'undefined') {
|
|
1876
|
+
HistoryService.flushForPath(tab.path);
|
|
1877
|
+
}
|
|
1458
1878
|
SearchService.invalidate();
|
|
1459
1879
|
|
|
1460
1880
|
// Hot reload for Markdown: sync preview tab after save
|
|
@@ -1466,12 +1886,76 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1466
1886
|
})["catch"](function (err) {
|
|
1467
1887
|
EditorStore.setStatus("Save failed: " + err.message, "error");
|
|
1468
1888
|
})["finally"](function () {
|
|
1889
|
+
isSavingRef.current = false;
|
|
1469
1890
|
return setLoading(function (prev) {
|
|
1470
1891
|
return _extends({}, prev, { save: false });
|
|
1471
1892
|
});
|
|
1472
1893
|
});
|
|
1473
1894
|
};
|
|
1474
1895
|
|
|
1896
|
+
function dismissPendingReload(reload) {
|
|
1897
|
+
EditorStore.setState({
|
|
1898
|
+
pendingReloads: EditorStore.getState().pendingReloads.filter(function (r) {
|
|
1899
|
+
return !(r.paneId === reload.paneId && r.tabId === reload.tabId);
|
|
1900
|
+
})
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
function handleSaveAndReload(reload) {
|
|
1905
|
+
var st = EditorStore.getState();
|
|
1906
|
+
var pane = st.panes.find(function (p) { return p.id === reload.paneId; });
|
|
1907
|
+
var tab = pane && pane.tabs.find(function (t) { return t.id === reload.tabId; });
|
|
1908
|
+
if (!tab) { dismissPendingReload(reload); return; }
|
|
1909
|
+
isSavingRef.current = true;
|
|
1910
|
+
FileService.saveFile(tab.path, tab.content).then(function () {
|
|
1911
|
+
recentSavesRef.current[tab.path] = Date.now();
|
|
1912
|
+
setTimeout(function() { delete recentSavesRef.current[tab.path]; }, 3500);
|
|
1913
|
+
EditorStore.setState({
|
|
1914
|
+
panes: EditorStore.getState().panes.map(function (p) {
|
|
1915
|
+
if (p.id !== reload.paneId) return p;
|
|
1916
|
+
return Object.assign({}, p, {
|
|
1917
|
+
tabs: p.tabs.map(function (t) {
|
|
1918
|
+
if (t.id !== reload.tabId) return t;
|
|
1919
|
+
return Object.assign({}, t, {
|
|
1920
|
+
content: tab.content,
|
|
1921
|
+
dirty: false,
|
|
1922
|
+
externalContentVersion: (t.externalContentVersion || 0) + 1
|
|
1923
|
+
});
|
|
1924
|
+
})
|
|
1925
|
+
});
|
|
1926
|
+
})
|
|
1927
|
+
});
|
|
1928
|
+
dismissPendingReload(reload);
|
|
1929
|
+
})["catch"](function () {
|
|
1930
|
+
EditorStore.setStatus('Save failed — cannot reload', 'error');
|
|
1931
|
+
})["finally"](function () {
|
|
1932
|
+
isSavingRef.current = false;
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
function handleDiscardAndReload(reload) {
|
|
1937
|
+
EditorStore.setState({
|
|
1938
|
+
panes: EditorStore.getState().panes.map(function (p) {
|
|
1939
|
+
if (p.id !== reload.paneId) return p;
|
|
1940
|
+
return Object.assign({}, p, {
|
|
1941
|
+
tabs: p.tabs.map(function (t) {
|
|
1942
|
+
if (t.id !== reload.tabId) return t;
|
|
1943
|
+
return Object.assign({}, t, {
|
|
1944
|
+
content: reload.serverContent,
|
|
1945
|
+
dirty: false,
|
|
1946
|
+
externalContentVersion: (t.externalContentVersion || 0) + 1
|
|
1947
|
+
});
|
|
1948
|
+
})
|
|
1949
|
+
});
|
|
1950
|
+
})
|
|
1951
|
+
});
|
|
1952
|
+
dismissPendingReload(reload);
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
function handleKeepMine(reload) {
|
|
1956
|
+
dismissPendingReload(reload);
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1475
1959
|
var handleSaveAll = function handleSaveAll() {
|
|
1476
1960
|
var dirtyTabs = state.panes.flatMap(function (p) {
|
|
1477
1961
|
return p.tabs;
|
|
@@ -1484,10 +1968,16 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1484
1968
|
return _extends({}, prev, { saveAll: true });
|
|
1485
1969
|
});
|
|
1486
1970
|
EditorStore.setStatus("Saving " + dirtyTabs.length + " files...", "info");
|
|
1971
|
+
isSavingRef.current = true;
|
|
1487
1972
|
var promises = dirtyTabs.map(function (tab) {
|
|
1488
1973
|
return FileService.saveFile(tab.path, tab.content);
|
|
1489
1974
|
});
|
|
1490
1975
|
Promise.all(promises).then(function () {
|
|
1976
|
+
var now = Date.now();
|
|
1977
|
+
dirtyTabs.forEach(function(tab) {
|
|
1978
|
+
recentSavesRef.current[tab.path] = now;
|
|
1979
|
+
setTimeout(function() { delete recentSavesRef.current[tab.path]; }, 3500);
|
|
1980
|
+
});
|
|
1491
1981
|
var newPanes = EditorStore.getState().panes.map(function (p) {
|
|
1492
1982
|
return _extends({}, p, { tabs: p.tabs.map(function (t) {
|
|
1493
1983
|
return _extends({}, t, { dirty: false, cleanContent: t.content });
|
|
@@ -1508,6 +1998,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1508
1998
|
})["catch"](function (err) {
|
|
1509
1999
|
EditorStore.setStatus("Failed to save some files", "error");
|
|
1510
2000
|
})["finally"](function () {
|
|
2001
|
+
isSavingRef.current = false;
|
|
1511
2002
|
return setLoading(function (prev) {
|
|
1512
2003
|
return _extends({}, prev, { saveAll: false });
|
|
1513
2004
|
});
|
|
@@ -1558,10 +2049,33 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1558
2049
|
EditorStore.setStatus('Line endings changed to ' + newEOL, 'info');
|
|
1559
2050
|
};
|
|
1560
2051
|
|
|
2052
|
+
var handleRefreshWorkspace = function handleRefreshWorkspace() {
|
|
2053
|
+
setLoading(function (prev) {
|
|
2054
|
+
return _extends({}, prev, { refreshWorkspace: true });
|
|
2055
|
+
});
|
|
2056
|
+
GitService.fetchStatus()["catch"](function () {});
|
|
2057
|
+
FileService.getTree().then(function (data) {
|
|
2058
|
+
var newData = data || [];
|
|
2059
|
+
setTreeData(function (prevData) {
|
|
2060
|
+
if (JSON.stringify(newData) === JSON.stringify(prevData)) return prevData;
|
|
2061
|
+
SearchService.buildIndex(newData);
|
|
2062
|
+
return newData;
|
|
2063
|
+
});
|
|
2064
|
+
checkOpenTabsForExternalChanges();
|
|
2065
|
+
EditorStore.setStatus("Workspace refreshed", "success");
|
|
2066
|
+
})["catch"](function (err) {
|
|
2067
|
+
EditorStore.setStatus("Failed to refresh workspace", "error");
|
|
2068
|
+
})["finally"](function () {
|
|
2069
|
+
setLoading(function (prev) {
|
|
2070
|
+
return _extends({}, prev, { refreshWorkspace: false });
|
|
2071
|
+
});
|
|
2072
|
+
});
|
|
2073
|
+
};
|
|
2074
|
+
|
|
1561
2075
|
var handleFormat = function handleFormat() {
|
|
1562
2076
|
if (!activeTab) return;
|
|
1563
2077
|
|
|
1564
|
-
var isRubyLang = activeTab.path.endsWith('.rb') || activeTab.path.endsWith('.gemspec') || activeTab.path.endsWith("Rakefile") || activeTab.path.endsWith("Gemfile");
|
|
2078
|
+
var isRubyLang = activeTab.path.endsWith('.rb') || activeTab.path.endsWith('.rake') || activeTab.path.endsWith('.gemspec') || activeTab.path.endsWith("Rakefile") || activeTab.path.endsWith("Gemfile");
|
|
1565
2079
|
|
|
1566
2080
|
if (isRubyLang && !rubocopAvailable) {
|
|
1567
2081
|
EditorStore.setStatus("RuboCop is not available for this workspace.", "warning");
|
|
@@ -1573,7 +2087,13 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1573
2087
|
return _extends({}, prev, { format: true });
|
|
1574
2088
|
});
|
|
1575
2089
|
EditorStore.setStatus("Formatting...", "info");
|
|
1576
|
-
|
|
2090
|
+
var originalContent = activeTab.content;
|
|
2091
|
+
var codeToFormat = originalContent;
|
|
2092
|
+
if (editorPrefs.insertSpaces === false) {
|
|
2093
|
+
var detectedWidth = detectIndentWidth(originalContent);
|
|
2094
|
+
if (detectedWidth > 0) codeToFormat = spacesToTabs(originalContent, detectedWidth);
|
|
2095
|
+
}
|
|
2096
|
+
FileService.formatFile(activeTab.path, codeToFormat).then(function (res) {
|
|
1577
2097
|
if (res.content) {
|
|
1578
2098
|
// Update content and mark dirty — user decides when to save.
|
|
1579
2099
|
// The executeEdits path in EditorPanel preserves the undo stack.
|
|
@@ -1584,6 +2104,19 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1584
2104
|
return p;
|
|
1585
2105
|
});
|
|
1586
2106
|
EditorStore.setState({ panes: newPanes });
|
|
2107
|
+
|
|
2108
|
+
// Highlight changed lines briefly
|
|
2109
|
+
var monacoEditor = window.__mbeditorActiveEditor;
|
|
2110
|
+
if (monacoEditor && res.content !== originalContent) {
|
|
2111
|
+
var changedLineNums = diffLines(originalContent.split('\n'), res.content.split('\n'));
|
|
2112
|
+
if (changedLineNums.length > 0) {
|
|
2113
|
+
var decorations = changedLineNums.map(function(ln) {
|
|
2114
|
+
return { range: new monaco.Range(ln, 1, ln, 1), options: { isWholeLine: true, className: 'mbeditor-format-changed' } };
|
|
2115
|
+
});
|
|
2116
|
+
var ids = monacoEditor.deltaDecorations([], decorations);
|
|
2117
|
+
setTimeout(function() { monacoEditor.deltaDecorations(ids, []); }, 3000);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
1587
2120
|
}
|
|
1588
2121
|
EditorStore.setStatus("Formatted (Unsaved)", "success");
|
|
1589
2122
|
GitService.fetchStatus();
|
|
@@ -2061,13 +2594,17 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2061
2594
|
document.body.style.userSelect = 'none';
|
|
2062
2595
|
};
|
|
2063
2596
|
|
|
2064
|
-
var
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2597
|
+
var handleActivityBarClick = function handleActivityBarClick(tab) {
|
|
2598
|
+
if (tab === 'settings') {
|
|
2599
|
+
openSettingsTab();
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2602
|
+
if (!sidebarCollapsed && activeSidebarTab === tab) {
|
|
2603
|
+
setSidebarCollapsed(true);
|
|
2604
|
+
} else {
|
|
2605
|
+
setActiveSidebarTab(tab);
|
|
2606
|
+
setSidebarCollapsed(false);
|
|
2607
|
+
}
|
|
2071
2608
|
};
|
|
2072
2609
|
|
|
2073
2610
|
var openFileFromGitPanel = function openFileFromGitPanel(path, name) {
|
|
@@ -2483,6 +3020,45 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2483
3020
|
EditorStore.setState({ panes: newPanes2, focusedPaneId: paneId, activeTabId: '__settings__' });
|
|
2484
3021
|
}
|
|
2485
3022
|
|
|
3023
|
+
var CHANGELOG_TAB_ID = 'mbeditor://changelog';
|
|
3024
|
+
function openChangelogTab() {
|
|
3025
|
+
var st = EditorStore.getState();
|
|
3026
|
+
// Focus existing tab if already open
|
|
3027
|
+
var foundPaneId = null, foundTab = null;
|
|
3028
|
+
st.panes.forEach(function(p) {
|
|
3029
|
+
if (!foundTab) {
|
|
3030
|
+
var t = p.tabs.find(function(tab) { return tab.id === CHANGELOG_TAB_ID; });
|
|
3031
|
+
if (t) { foundTab = t; foundPaneId = p.id; }
|
|
3032
|
+
}
|
|
3033
|
+
});
|
|
3034
|
+
if (foundTab) {
|
|
3035
|
+
var switchPanes = st.panes.map(function(p) {
|
|
3036
|
+
if (p.id === foundPaneId) return Object.assign({}, p, { activeTabId: CHANGELOG_TAB_ID });
|
|
3037
|
+
return p;
|
|
3038
|
+
});
|
|
3039
|
+
EditorStore.setState({ panes: switchPanes, focusedPaneId: foundPaneId });
|
|
3040
|
+
return;
|
|
3041
|
+
}
|
|
3042
|
+
// Open in focused pane
|
|
3043
|
+
var paneId = st.focusedPaneId;
|
|
3044
|
+
var pane = st.panes.find(function(p) { return p.id === paneId; }) || st.panes[0];
|
|
3045
|
+
if (!pane) return;
|
|
3046
|
+
paneId = pane.id;
|
|
3047
|
+
var newTab = { id: CHANGELOG_TAB_ID, path: CHANGELOG_TAB_ID, name: "What's New", dirty: false, content: '', isChangelog: true };
|
|
3048
|
+
var newPanes = st.panes.map(function(p) {
|
|
3049
|
+
if (p.id === paneId) return Object.assign({}, p, { tabs: p.tabs.concat(newTab), activeTabId: CHANGELOG_TAB_ID });
|
|
3050
|
+
return p;
|
|
3051
|
+
});
|
|
3052
|
+
EditorStore.setState({ panes: newPanes, focusedPaneId: paneId });
|
|
3053
|
+
// Fetch content if not already loaded
|
|
3054
|
+
if (!changelogState || changelogState.error) {
|
|
3055
|
+
setChangelogState({ loading: true, content: null, error: null });
|
|
3056
|
+
FileService.getChangelog()
|
|
3057
|
+
.then(function(data) { setChangelogState({ loading: false, content: data.content || '', error: null }); })
|
|
3058
|
+
['catch'](function() { setChangelogState({ loading: false, content: null, error: 'Could not load changelog.' }); });
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
|
|
2486
3062
|
return React.createElement(
|
|
2487
3063
|
"div",
|
|
2488
3064
|
{ className: "ide-shell" },
|
|
@@ -2589,74 +3165,69 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2589
3165
|
React.createElement(
|
|
2590
3166
|
"div",
|
|
2591
3167
|
{ className: "ide-body", id: "ide-body-container" },
|
|
2592
|
-
|
|
3168
|
+
/* Activity bar — always visible, 48px wide */
|
|
3169
|
+
!zenMode && React.createElement(
|
|
2593
3170
|
"div",
|
|
2594
|
-
{ className: "ide-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
"button",
|
|
2655
|
-
{ type: "button", className: "sidebar-strip-btn", title: "Collapse sidebar", onClick: toggleSidebarCollapsed },
|
|
2656
|
-
React.createElement("i", { className: "fas fa-chevron-left" })
|
|
2657
|
-
)
|
|
2658
|
-
),
|
|
2659
|
-
activeSidebarTab === 'explorer' && React.createElement(
|
|
3171
|
+
{ className: "ide-activity-bar" },
|
|
3172
|
+
React.createElement(
|
|
3173
|
+
"div",
|
|
3174
|
+
{ className: "ide-activity-bar-top" },
|
|
3175
|
+
React.createElement(
|
|
3176
|
+
"button",
|
|
3177
|
+
{
|
|
3178
|
+
type: "button",
|
|
3179
|
+
className: "ide-activity-btn" + (!sidebarCollapsed && activeSidebarTab === 'explorer' ? ' active' : ''),
|
|
3180
|
+
title: "Explorer",
|
|
3181
|
+
onClick: function() { handleActivityBarClick('explorer'); }
|
|
3182
|
+
},
|
|
3183
|
+
React.createElement("i", { className: "far fa-folder" })
|
|
3184
|
+
),
|
|
3185
|
+
React.createElement(
|
|
3186
|
+
"button",
|
|
3187
|
+
{
|
|
3188
|
+
type: "button",
|
|
3189
|
+
className: "ide-activity-btn" + (!sidebarCollapsed && activeSidebarTab === 'search' ? ' active' : ''),
|
|
3190
|
+
title: "Search",
|
|
3191
|
+
onClick: function() { handleActivityBarClick('search'); }
|
|
3192
|
+
},
|
|
3193
|
+
React.createElement("i", { className: "fas fa-search" })
|
|
3194
|
+
),
|
|
3195
|
+
React.createElement(
|
|
3196
|
+
"button",
|
|
3197
|
+
{
|
|
3198
|
+
type: "button",
|
|
3199
|
+
className: "ide-activity-btn" + (!sidebarCollapsed && activeSidebarTab === 'rails' ? ' active' : ''),
|
|
3200
|
+
title: "Rails",
|
|
3201
|
+
onClick: function() { handleActivityBarClick('rails'); }
|
|
3202
|
+
},
|
|
3203
|
+
React.createElement("i", { className: "far fa-gem" })
|
|
3204
|
+
)
|
|
3205
|
+
),
|
|
3206
|
+
React.createElement(
|
|
3207
|
+
"div",
|
|
3208
|
+
{ className: "ide-activity-bar-bottom" },
|
|
3209
|
+
React.createElement(
|
|
3210
|
+
"button",
|
|
3211
|
+
{
|
|
3212
|
+
type: "button",
|
|
3213
|
+
className: "ide-activity-btn" + (activeTab && activeTab.isSettings ? ' active' : ''),
|
|
3214
|
+
title: "Editor Preferences",
|
|
3215
|
+
onClick: openSettingsTab
|
|
3216
|
+
},
|
|
3217
|
+
React.createElement("i", { className: "fas fa-cog" })
|
|
3218
|
+
)
|
|
3219
|
+
)
|
|
3220
|
+
),
|
|
3221
|
+
/* Panel content — shown when not collapsed and not in zen mode */
|
|
3222
|
+
!sidebarCollapsed && !zenMode && React.createElement(
|
|
3223
|
+
"div",
|
|
3224
|
+
{ className: "ide-sidebar", style: { width: sidebarWidth + "px" } },
|
|
3225
|
+
React.createElement("div", { className: "sidebar-panel-title" },
|
|
3226
|
+
activeSidebarTab === 'explorer' ? 'Explorer' :
|
|
3227
|
+
activeSidebarTab === 'search' ? 'Search' :
|
|
3228
|
+
activeSidebarTab === 'rails' ? 'Rails' : ''
|
|
3229
|
+
),
|
|
3230
|
+
activeSidebarTab === 'explorer' && React.createElement(
|
|
2660
3231
|
"div",
|
|
2661
3232
|
{ className: "ide-sidebar-content" },
|
|
2662
3233
|
React.createElement(
|
|
@@ -2789,6 +3360,13 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2789
3360
|
actions: React.createElement(
|
|
2790
3361
|
SectionActionGroup,
|
|
2791
3362
|
{ ariaLabel: "Project actions" },
|
|
3363
|
+
React.createElement(SidebarActionButton, {
|
|
3364
|
+
title: "Refresh workspace",
|
|
3365
|
+
iconClass: "fas fa-sync-alt",
|
|
3366
|
+
ariaBusy: !!loading.refreshWorkspace,
|
|
3367
|
+
onClick: handleRefreshWorkspace,
|
|
3368
|
+
disabled: !!loading.refreshWorkspace
|
|
3369
|
+
}),
|
|
2792
3370
|
React.createElement(SidebarActionButton, {
|
|
2793
3371
|
title: "Collapse all folders",
|
|
2794
3372
|
iconClass: "fas fa-compress-alt",
|
|
@@ -2986,7 +3564,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2986
3564
|
{
|
|
2987
3565
|
key: i,
|
|
2988
3566
|
className: "search-result-item",
|
|
2989
|
-
onClick: (function(r) { return function() { handleSelectFile(r.file, r.file.split('/').pop(), r.line); }; })(res)
|
|
3567
|
+
onClick: (function(r) { return function() { handleSelectFile(r.file, r.file.split('/').pop(), r.line, r.col); }; })(res)
|
|
2990
3568
|
},
|
|
2991
3569
|
React.createElement("i", { className: (window.getFileIcon ? window.getFileIcon(fileName) : 'far fa-file-code') + " search-result-icon" }),
|
|
2992
3570
|
React.createElement(
|
|
@@ -3014,10 +3592,120 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3014
3592
|
)
|
|
3015
3593
|
);
|
|
3016
3594
|
})()
|
|
3595
|
+
),
|
|
3596
|
+
activeSidebarTab === 'rails' && React.createElement(
|
|
3597
|
+
"div",
|
|
3598
|
+
{ className: "rails-panel" },
|
|
3599
|
+
(function() {
|
|
3600
|
+
var labels = Object.keys(railsFilesMap).sort();
|
|
3601
|
+
if (labels.length === 0) {
|
|
3602
|
+
return React.createElement("div", { className: "rails-panel-empty" }, "Open a Rails file to see related files.");
|
|
3603
|
+
}
|
|
3604
|
+
var sections = labels.map(function(label) {
|
|
3605
|
+
var entry = railsFilesMap[label];
|
|
3606
|
+
var files = entry && entry.files;
|
|
3607
|
+
var loading = entry && entry.loading;
|
|
3608
|
+
if (loading && !files) {
|
|
3609
|
+
return React.createElement("div", { key: label + '_loading', className: "rails-panel-loading" },
|
|
3610
|
+
React.createElement("i", { className: "fas fa-spinner fa-spin" }),
|
|
3611
|
+
" Loading…"
|
|
3612
|
+
);
|
|
3613
|
+
}
|
|
3614
|
+
if (!files || Object.keys(files).length === 0) return null;
|
|
3615
|
+
var allFiles = [];
|
|
3616
|
+
['model', 'controller', 'helper', 'concerns', 'tests', 'views'].forEach(function(key) {
|
|
3617
|
+
var group = files[key];
|
|
3618
|
+
if (group && group.length) allFiles = allFiles.concat(group);
|
|
3619
|
+
});
|
|
3620
|
+
var customGroups = files['custom'];
|
|
3621
|
+
if (customGroups && typeof customGroups === 'object') {
|
|
3622
|
+
Object.keys(customGroups).forEach(function(base) {
|
|
3623
|
+
var grpFiles = customGroups[base];
|
|
3624
|
+
if (grpFiles && grpFiles.length) allFiles = allFiles.concat(grpFiles);
|
|
3625
|
+
});
|
|
3626
|
+
}
|
|
3627
|
+
if (allFiles.length === 0) return null;
|
|
3628
|
+
var schemaBtn = React.createElement(
|
|
3629
|
+
'button',
|
|
3630
|
+
{
|
|
3631
|
+
className: 'rails-schema-btn' + (schemaLoadingLabel === label ? ' rails-schema-btn-loading' : ''),
|
|
3632
|
+
title: 'View database schema for ' + label,
|
|
3633
|
+
onClick: (function(lbl) { return function(e) {
|
|
3634
|
+
e.stopPropagation();
|
|
3635
|
+
if (schemaLoadingLabel === lbl) return;
|
|
3636
|
+
setSchemaLoadingLabel(lbl);
|
|
3637
|
+
var modelName = lbl.replace(/\s+/g, '');
|
|
3638
|
+
FileService.getModelSchema(modelName)
|
|
3639
|
+
.then(function(data) {
|
|
3640
|
+
setSchemaLoadingLabel(null);
|
|
3641
|
+
if (data && data.columns) {
|
|
3642
|
+
setSchemaModal({ label: lbl, data: data });
|
|
3643
|
+
} else {
|
|
3644
|
+
setSchemaModal({ label: lbl, error: 'No schema found for ' + lbl });
|
|
3645
|
+
}
|
|
3646
|
+
})
|
|
3647
|
+
['catch'](function(err) {
|
|
3648
|
+
setSchemaLoadingLabel(null);
|
|
3649
|
+
var msg = (err && err.response && err.response.data && err.response.data.error)
|
|
3650
|
+
? err.response.data.error
|
|
3651
|
+
: 'No db/schema.rb found or table not defined';
|
|
3652
|
+
setSchemaModal({ label: lbl, error: msg });
|
|
3653
|
+
});
|
|
3654
|
+
}; })(label)
|
|
3655
|
+
},
|
|
3656
|
+
React.createElement('i', {
|
|
3657
|
+
className: schemaLoadingLabel === label
|
|
3658
|
+
? 'fas fa-spinner fa-spin'
|
|
3659
|
+
: 'fas fa-table'
|
|
3660
|
+
})
|
|
3661
|
+
);
|
|
3662
|
+
return React.createElement(
|
|
3663
|
+
CollapsibleSection,
|
|
3664
|
+
{
|
|
3665
|
+
key: label,
|
|
3666
|
+
title: label.toUpperCase(),
|
|
3667
|
+
isCollapsed: !!railsGroupsCollapsed[label],
|
|
3668
|
+
actions: schemaBtn,
|
|
3669
|
+
onToggle: (function(captured) { return function(isCollapsed) {
|
|
3670
|
+
setRailsGroupsCollapsed(function(prev) {
|
|
3671
|
+
var next = Object.assign({}, prev);
|
|
3672
|
+
next[captured] = isCollapsed;
|
|
3673
|
+
return next;
|
|
3674
|
+
});
|
|
3675
|
+
}; })(label)
|
|
3676
|
+
},
|
|
3677
|
+
React.createElement(
|
|
3678
|
+
"div",
|
|
3679
|
+
null,
|
|
3680
|
+
allFiles.map(function(f) {
|
|
3681
|
+
return React.createElement(
|
|
3682
|
+
"div", {
|
|
3683
|
+
key: f.path,
|
|
3684
|
+
className: "rails-group-item",
|
|
3685
|
+
onClick: (function(file) { return function() { handleSelectFile(file.path, file.name); }; })(f),
|
|
3686
|
+
title: f.path
|
|
3687
|
+
},
|
|
3688
|
+
React.createElement("i", { className: "tree-item-icon " + (window.getFileIcon ? window.getFileIcon(f.name) : 'far fa-file-code') + " tree-file-icon" }),
|
|
3689
|
+
React.createElement("span", { className: "rails-group-item-name" }, f.name),
|
|
3690
|
+
f.kind && React.createElement("span", { className: "rails-group-item-kind" }, f.kind),
|
|
3691
|
+
dirtyPaths[f.path] && React.createElement("span", { className: "rails-group-item-dirty" }, "●")
|
|
3692
|
+
);
|
|
3693
|
+
})
|
|
3694
|
+
)
|
|
3695
|
+
);
|
|
3696
|
+
});
|
|
3697
|
+
if (railsOverflow > 0) {
|
|
3698
|
+
sections = sections.concat([React.createElement(
|
|
3699
|
+
"div", { key: '__overflow', className: "rails-panel-overflow" },
|
|
3700
|
+
"+" + railsOverflow + " more — close tabs to show all"
|
|
3701
|
+
)]);
|
|
3702
|
+
}
|
|
3703
|
+
return sections;
|
|
3704
|
+
})()
|
|
3017
3705
|
)
|
|
3018
|
-
)
|
|
3019
|
-
|
|
3020
|
-
React.createElement("div", {
|
|
3706
|
+
),
|
|
3707
|
+
/* Sidebar resize divider — only when panel is open */
|
|
3708
|
+
!sidebarCollapsed && !zenMode && React.createElement("div", {
|
|
3021
3709
|
className: "panel-divider sidebar-divider " + (activeResizeMode === 'sidebar' ? 'active' : ''),
|
|
3022
3710
|
onMouseDown: startSidebarResize,
|
|
3023
3711
|
role: "separator",
|
|
@@ -3029,7 +3717,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3029
3717
|
{
|
|
3030
3718
|
id: "ide-main-split-container",
|
|
3031
3719
|
className: "ide-main",
|
|
3032
|
-
style: { display: 'flex', flexDirection: 'row', width: '100%', height: '100%', cursor: activeResizeMode === 'pane' ? 'col-resize' : 'default', userSelect: activeResizeMode ? 'none' : 'auto' },
|
|
3720
|
+
style: { position: 'relative', display: 'flex', flexDirection: 'row', width: '100%', height: '100%', cursor: activeResizeMode === 'pane' ? 'col-resize' : 'default', userSelect: activeResizeMode ? 'none' : 'auto' },
|
|
3033
3721
|
onDragOverCapture: function (e) {
|
|
3034
3722
|
if (!draggedTab) return;
|
|
3035
3723
|
e.preventDefault();
|
|
@@ -3081,6 +3769,9 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3081
3769
|
}
|
|
3082
3770
|
}
|
|
3083
3771
|
},
|
|
3772
|
+
isSwitchingBranch && React.createElement('div', { className: 'branch-switch-overlay' },
|
|
3773
|
+
React.createElement('span', null, 'Switching branch…')
|
|
3774
|
+
),
|
|
3084
3775
|
state.panes.map(function (pane, idx) {
|
|
3085
3776
|
// Show empty pane 2 as a drop zone only when the cursor is actively hovering
|
|
3086
3777
|
// over its half of the editor content (dragOverPaneId === 2).
|
|
@@ -3116,6 +3807,18 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3116
3807
|
commits: pActiveTab.commits || [],
|
|
3117
3808
|
onSelectCommit: handleSelectCommit
|
|
3118
3809
|
});
|
|
3810
|
+
} else if (pActiveTab.isChangelog) {
|
|
3811
|
+
content = React.createElement(ChangelogView, {
|
|
3812
|
+
changelogState: changelogState,
|
|
3813
|
+
onLoad: function() {
|
|
3814
|
+
if (!changelogState || (!changelogState.content && !changelogState.loading && !changelogState.error)) {
|
|
3815
|
+
setChangelogState({ loading: true, content: null, error: null });
|
|
3816
|
+
FileService.getChangelog()
|
|
3817
|
+
.then(function(data) { setChangelogState({ loading: false, content: data.content || '', error: null }); })
|
|
3818
|
+
['catch'](function() { setChangelogState({ loading: false, content: null, error: 'Could not load changelog.' }); });
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
});
|
|
3119
3822
|
} else if (pActiveTab.isSettings) {
|
|
3120
3823
|
content = React.createElement(
|
|
3121
3824
|
'div',
|
|
@@ -3127,7 +3830,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3127
3830
|
/* ── Appearance ──────────────────────────────── */
|
|
3128
3831
|
React.createElement('div', { className: 'ide-settings-section-header' }, 'Appearance'),
|
|
3129
3832
|
React.createElement(
|
|
3130
|
-
'label', { className: 'ide-settings-row ide-settings-row-half' },
|
|
3833
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Color theme for the editor' },
|
|
3131
3834
|
React.createElement('span', { className: 'ide-settings-label' }, 'Theme'),
|
|
3132
3835
|
React.createElement(
|
|
3133
3836
|
'select', {
|
|
@@ -3148,7 +3851,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3148
3851
|
)
|
|
3149
3852
|
),
|
|
3150
3853
|
React.createElement(
|
|
3151
|
-
'label', { className: 'ide-settings-row ide-settings-row-half' },
|
|
3854
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Editor font size in pixels (8–32)' },
|
|
3152
3855
|
React.createElement('span', { className: 'ide-settings-label' }, 'Font size'),
|
|
3153
3856
|
React.createElement('input', {
|
|
3154
3857
|
key: String(editorPrefs.fontSize || 13),
|
|
@@ -3166,7 +3869,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3166
3869
|
})
|
|
3167
3870
|
),
|
|
3168
3871
|
React.createElement(
|
|
3169
|
-
'label', { className: 'ide-settings-row-full' },
|
|
3872
|
+
'label', { className: 'ide-settings-row-full', title: 'Font stack used in the editor — the first font available on your system is used' },
|
|
3170
3873
|
React.createElement('span', { className: 'ide-settings-label' }, 'Font family'),
|
|
3171
3874
|
React.createElement('input', {
|
|
3172
3875
|
type: 'text',
|
|
@@ -3215,7 +3918,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3215
3918
|
/* ── Indentation (unified editor + Prettier) ── */
|
|
3216
3919
|
React.createElement('div', { className: 'ide-settings-section-header' }, 'Indentation'),
|
|
3217
3920
|
React.createElement(
|
|
3218
|
-
'label', { className: 'ide-settings-row ide-settings-row-half' },
|
|
3921
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Number of spaces per indentation level (also sets Prettier tab width)' },
|
|
3219
3922
|
React.createElement('span', { className: 'ide-settings-label' }, 'Tab size'),
|
|
3220
3923
|
React.createElement('input', {
|
|
3221
3924
|
key: String(editorPrefs.tabSize || 4),
|
|
@@ -3233,7 +3936,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3233
3936
|
})
|
|
3234
3937
|
),
|
|
3235
3938
|
React.createElement(
|
|
3236
|
-
'label', { className: 'ide-settings-row ide-settings-row-check' },
|
|
3939
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Insert spaces instead of tab characters when pressing Tab' },
|
|
3237
3940
|
React.createElement('span', { className: 'ide-settings-label' }, 'Use spaces'),
|
|
3238
3941
|
React.createElement('input', {
|
|
3239
3942
|
type: 'checkbox',
|
|
@@ -3246,7 +3949,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3246
3949
|
/* ── Editor ──────────────────────────────────── */
|
|
3247
3950
|
React.createElement('div', { className: 'ide-settings-section-header' }, 'Editor'),
|
|
3248
3951
|
React.createElement(
|
|
3249
|
-
'label', { className: 'ide-settings-row ide-settings-row-half' },
|
|
3952
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'How long lines are handled — Off: scroll horizontally, On: wrap at viewport width, Column: wrap at a fixed column' },
|
|
3250
3953
|
React.createElement('span', { className: 'ide-settings-label' }, 'Word wrap'),
|
|
3251
3954
|
React.createElement(
|
|
3252
3955
|
'select', {
|
|
@@ -3259,7 +3962,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3259
3962
|
)
|
|
3260
3963
|
),
|
|
3261
3964
|
React.createElement(
|
|
3262
|
-
'label', { className: 'ide-settings-row ide-settings-row-half' },
|
|
3965
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Show line numbers in the gutter — On, Off, or Relative (useful with Vim mode)' },
|
|
3263
3966
|
React.createElement('span', { className: 'ide-settings-label' }, 'Line numbers'),
|
|
3264
3967
|
React.createElement(
|
|
3265
3968
|
'select', {
|
|
@@ -3272,7 +3975,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3272
3975
|
)
|
|
3273
3976
|
),
|
|
3274
3977
|
React.createElement(
|
|
3275
|
-
'label', { className: 'ide-settings-row ide-settings-row-half' },
|
|
3978
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Render whitespace characters visually — None, Selection only, Boundary (leading/trailing), or All' },
|
|
3276
3979
|
React.createElement('span', { className: 'ide-settings-label' }, 'Whitespace'),
|
|
3277
3980
|
React.createElement(
|
|
3278
3981
|
'select', {
|
|
@@ -3286,7 +3989,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3286
3989
|
)
|
|
3287
3990
|
),
|
|
3288
3991
|
React.createElement(
|
|
3289
|
-
'label', { className: 'ide-settings-row ide-settings-row-check' },
|
|
3992
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Show a scaled-down overview of the file on the right edge of the editor' },
|
|
3290
3993
|
React.createElement('span', { className: 'ide-settings-label' }, 'Minimap'),
|
|
3291
3994
|
React.createElement('input', {
|
|
3292
3995
|
type: 'checkbox',
|
|
@@ -3296,7 +3999,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3296
3999
|
})
|
|
3297
4000
|
),
|
|
3298
4001
|
React.createElement(
|
|
3299
|
-
'label', { className: 'ide-settings-row ide-settings-row-check' },
|
|
4002
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Allow scrolling past the last line so it can be positioned at the top of the viewport' },
|
|
3300
4003
|
React.createElement('span', { className: 'ide-settings-label' }, 'Scroll past end'),
|
|
3301
4004
|
React.createElement('input', {
|
|
3302
4005
|
type: 'checkbox',
|
|
@@ -3306,7 +4009,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3306
4009
|
})
|
|
3307
4010
|
),
|
|
3308
4011
|
React.createElement(
|
|
3309
|
-
'label', { className: 'ide-settings-row ide-settings-row-check' },
|
|
4012
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Colorize matching bracket pairs with distinct colors to make nesting easier to read' },
|
|
3310
4013
|
React.createElement('span', { className: 'ide-settings-label' }, 'Bracket colors'),
|
|
3311
4014
|
React.createElement('input', {
|
|
3312
4015
|
type: 'checkbox',
|
|
@@ -3316,7 +4019,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3316
4019
|
})
|
|
3317
4020
|
),
|
|
3318
4021
|
React.createElement(
|
|
3319
|
-
'label', { className: 'ide-settings-row ide-settings-row-check' },
|
|
4022
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Enable Vim keybindings (Normal/Insert/Visual modes). Press Escape to return to Normal mode.' },
|
|
3320
4023
|
React.createElement('span', { className: 'ide-settings-label' }, 'Vim mode'),
|
|
3321
4024
|
React.createElement('input', {
|
|
3322
4025
|
type: 'checkbox',
|
|
@@ -3368,7 +4071,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3368
4071
|
)
|
|
3369
4072
|
),
|
|
3370
4073
|
React.createElement(
|
|
3371
|
-
'label', { className: 'ide-settings-row ide-settings-row-half' },
|
|
4074
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Shape of the text cursor in the editor' },
|
|
3372
4075
|
React.createElement('span', { className: 'ide-settings-label' }, 'Cursor style'),
|
|
3373
4076
|
React.createElement(
|
|
3374
4077
|
'select', {
|
|
@@ -3384,7 +4087,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3384
4087
|
)
|
|
3385
4088
|
),
|
|
3386
4089
|
React.createElement(
|
|
3387
|
-
'label', { className: 'ide-settings-row ide-settings-row-half' },
|
|
4090
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Cursor animation style — Blink (on/off), Smooth (fade), Phase (offset fade), Expand (grow), or Solid (no animation)' },
|
|
3388
4091
|
React.createElement('span', { className: 'ide-settings-label' }, 'Cursor blinking'),
|
|
3389
4092
|
React.createElement(
|
|
3390
4093
|
'select', {
|
|
@@ -3507,7 +4210,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3507
4210
|
/* ── Formatting (Prettier) ───────────────────── */
|
|
3508
4211
|
React.createElement('div', { className: 'ide-settings-section-header' }, 'Formatting'),
|
|
3509
4212
|
React.createElement(
|
|
3510
|
-
'label', { className: 'ide-settings-row ide-settings-row-half' },
|
|
4213
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Prettier: maximum line length before wrapping (40–200)' },
|
|
3511
4214
|
React.createElement('span', { className: 'ide-settings-label' }, 'Print width'),
|
|
3512
4215
|
React.createElement('input', {
|
|
3513
4216
|
key: String(editorPrefs.prettierPrintWidth != null ? editorPrefs.prettierPrintWidth : 80),
|
|
@@ -3525,7 +4228,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3525
4228
|
})
|
|
3526
4229
|
),
|
|
3527
4230
|
React.createElement(
|
|
3528
|
-
'label', { className: 'ide-settings-row ide-settings-row-half' },
|
|
4231
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Prettier: add trailing commas in multi-line expressions — All (ES2017+), ES5 (objects/arrays only), or None' },
|
|
3529
4232
|
React.createElement('span', { className: 'ide-settings-label' }, 'Trailing commas'),
|
|
3530
4233
|
React.createElement(
|
|
3531
4234
|
'select', {
|
|
@@ -3538,7 +4241,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3538
4241
|
)
|
|
3539
4242
|
),
|
|
3540
4243
|
React.createElement(
|
|
3541
|
-
'label', { className: 'ide-settings-row ide-settings-row-check' },
|
|
4244
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Prettier: add semicolons at the end of statements' },
|
|
3542
4245
|
React.createElement('span', { className: 'ide-settings-label' }, 'Semicolons'),
|
|
3543
4246
|
React.createElement('input', {
|
|
3544
4247
|
type: 'checkbox',
|
|
@@ -3548,7 +4251,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3548
4251
|
})
|
|
3549
4252
|
),
|
|
3550
4253
|
React.createElement(
|
|
3551
|
-
'label', { className: 'ide-settings-row ide-settings-row-check' },
|
|
4254
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: "Prettier: use single quotes instead of double quotes for strings" },
|
|
3552
4255
|
React.createElement('span', { className: 'ide-settings-label' }, 'Single quotes'),
|
|
3553
4256
|
React.createElement('input', {
|
|
3554
4257
|
type: 'checkbox',
|
|
@@ -3558,7 +4261,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3558
4261
|
})
|
|
3559
4262
|
),
|
|
3560
4263
|
React.createElement(
|
|
3561
|
-
'label', { className: 'ide-settings-row ide-settings-row-check' },
|
|
4264
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Prettier: add spaces inside object literal braces, e.g. { a: 1 } vs {a: 1}' },
|
|
3562
4265
|
React.createElement('span', { className: 'ide-settings-label' }, 'Bracket spacing'),
|
|
3563
4266
|
React.createElement('input', {
|
|
3564
4267
|
type: 'checkbox',
|
|
@@ -3571,7 +4274,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3571
4274
|
/* ── Interface ───────────────────────────────── */
|
|
3572
4275
|
React.createElement('div', { className: 'ide-settings-section-header' }, 'Interface'),
|
|
3573
4276
|
React.createElement(
|
|
3574
|
-
'label', { className: 'ide-settings-row ide-settings-row-check' },
|
|
4277
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Automatically scroll the file explorer to reveal and highlight the file you are editing' },
|
|
3575
4278
|
React.createElement('span', { className: 'ide-settings-label' }, 'Explorer follows active file'),
|
|
3576
4279
|
React.createElement('input', {
|
|
3577
4280
|
type: 'checkbox',
|
|
@@ -3581,7 +4284,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3581
4284
|
})
|
|
3582
4285
|
),
|
|
3583
4286
|
React.createElement(
|
|
3584
|
-
'label', { className: 'ide-settings-row ide-settings-row-check' },
|
|
4287
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Jump to a file in the explorer by typing its name when the sidebar is focused' },
|
|
3585
4288
|
React.createElement('span', { className: 'ide-settings-label' }, 'Explorer type-ahead'),
|
|
3586
4289
|
React.createElement('input', {
|
|
3587
4290
|
type: 'checkbox',
|
|
@@ -3591,7 +4294,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3591
4294
|
})
|
|
3592
4295
|
),
|
|
3593
4296
|
React.createElement(
|
|
3594
|
-
'label', { className: 'ide-settings-row ide-settings-row-check' },
|
|
4297
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Show hidden files and directories (those starting with a dot, e.g. .env, .gitignore) in the file explorer' },
|
|
3595
4298
|
React.createElement('span', { className: 'ide-settings-label' }, 'Show dotfiles'),
|
|
3596
4299
|
React.createElement('input', {
|
|
3597
4300
|
type: 'checkbox',
|
|
@@ -3601,7 +4304,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3601
4304
|
})
|
|
3602
4305
|
),
|
|
3603
4306
|
React.createElement(
|
|
3604
|
-
'label', { className: 'ide-settings-row ide-settings-row-half' },
|
|
4307
|
+
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Scroll: tabs overflow horizontally with a scrollbar; Wrap: tabs flow onto multiple rows' },
|
|
3605
4308
|
React.createElement('span', { className: 'ide-settings-label' }, 'Tab bar layout'),
|
|
3606
4309
|
React.createElement(
|
|
3607
4310
|
'select', {
|
|
@@ -3613,7 +4316,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3613
4316
|
)
|
|
3614
4317
|
),
|
|
3615
4318
|
React.createElement(
|
|
3616
|
-
'label', { className: 'ide-settings-row ide-settings-row-check' },
|
|
4319
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Include folder names in the Quick Open picker (Ctrl+P / Cmd+P) results, not just files' },
|
|
3617
4320
|
React.createElement('span', { className: 'ide-settings-label' }, 'Quick Open: show folders'),
|
|
3618
4321
|
React.createElement('input', {
|
|
3619
4322
|
type: 'checkbox',
|
|
@@ -3623,7 +4326,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3623
4326
|
})
|
|
3624
4327
|
),
|
|
3625
4328
|
React.createElement(
|
|
3626
|
-
'label', { className: 'ide-settings-row ide-settings-row-check' },
|
|
4329
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Hide toolbar button labels and show only icons, giving more horizontal space' },
|
|
3627
4330
|
React.createElement('span', { className: 'ide-settings-label' }, 'Toolbar: icons only'),
|
|
3628
4331
|
React.createElement('input', {
|
|
3629
4332
|
type: 'checkbox',
|
|
@@ -3634,7 +4337,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3634
4337
|
),
|
|
3635
4338
|
|
|
3636
4339
|
React.createElement(
|
|
3637
|
-
'label', { className: 'ide-settings-row ide-settings-row-check' },
|
|
4340
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Keep the search/replace text when switching between files in the editor' },
|
|
3638
4341
|
React.createElement('span', { className: 'ide-settings-label' }, 'Persist find state across files'),
|
|
3639
4342
|
React.createElement('input', {
|
|
3640
4343
|
type: 'checkbox',
|
|
@@ -3643,11 +4346,21 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3643
4346
|
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { persistFindState: v }); }); }
|
|
3644
4347
|
})
|
|
3645
4348
|
),
|
|
4349
|
+
React.createElement(
|
|
4350
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Save which files are open per branch and restore them when switching branches. Disable to always start with a clean slate when switching.' },
|
|
4351
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Restore tabs on branch switch'),
|
|
4352
|
+
React.createElement('input', {
|
|
4353
|
+
type: 'checkbox',
|
|
4354
|
+
className: 'ide-settings-checkbox',
|
|
4355
|
+
checked: editorPrefs.branchStateRestore !== false,
|
|
4356
|
+
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { branchStateRestore: v }); }); }
|
|
4357
|
+
})
|
|
4358
|
+
),
|
|
3646
4359
|
|
|
3647
4360
|
/* ── RuboCop ─────────────────────────────────── */
|
|
3648
4361
|
React.createElement('div', { className: 'ide-settings-section-header' }, 'RuboCop'),
|
|
3649
4362
|
React.createElement(
|
|
3650
|
-
'label', { className: 'ide-settings-row ide-settings-row-check' },
|
|
4363
|
+
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Run RuboCop in the background and show lint warnings/errors as markers in the editor gutter' },
|
|
3651
4364
|
React.createElement('span', { className: 'ide-settings-label' }, 'Enable RuboCop linting'),
|
|
3652
4365
|
React.createElement('input', {
|
|
3653
4366
|
type: 'checkbox',
|
|
@@ -3775,6 +4488,12 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3775
4488
|
React.Fragment,
|
|
3776
4489
|
null,
|
|
3777
4490
|
renderTabBar(pane.id, pane.tabs, pane.activeTabId),
|
|
4491
|
+
React.createElement(FileReloadBanner, {
|
|
4492
|
+
pendingReloads: (state.pendingReloads || []).filter(function (r) { return r.paneId === pane.id; }),
|
|
4493
|
+
onSaveAndReload: handleSaveAndReload,
|
|
4494
|
+
onDiscardAndReload: handleDiscardAndReload,
|
|
4495
|
+
onKeepMine: handleKeepMine
|
|
4496
|
+
}),
|
|
3778
4497
|
React.createElement(
|
|
3779
4498
|
"div",
|
|
3780
4499
|
{ style: { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', visibility: activeResizeMode === 'pane' ? 'hidden' : 'visible' } },
|
|
@@ -3978,8 +4697,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3978
4697
|
"ZEN"
|
|
3979
4698
|
),
|
|
3980
4699
|
React.createElement(
|
|
3981
|
-
"
|
|
3982
|
-
{ className: "statusbar-version" },
|
|
4700
|
+
"button",
|
|
4701
|
+
{ type: "button", className: "statusbar-version statusbar-btn", onClick: openChangelogTab, title: "What's New — click to open changelog" },
|
|
3983
4702
|
"v" + (document.body.dataset.mbeditorVersion || "")
|
|
3984
4703
|
)
|
|
3985
4704
|
),
|
|
@@ -4289,6 +5008,96 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4289
5008
|
)
|
|
4290
5009
|
)
|
|
4291
5010
|
)
|
|
5011
|
+
),
|
|
5012
|
+
|
|
5013
|
+
/* ── Schema modal ──────────────────────────────────────────────────── */
|
|
5014
|
+
schemaModal && React.createElement(
|
|
5015
|
+
'div',
|
|
5016
|
+
{
|
|
5017
|
+
className: 'schema-modal-overlay',
|
|
5018
|
+
onClick: function() { setSchemaModal(null); }
|
|
5019
|
+
},
|
|
5020
|
+
React.createElement(
|
|
5021
|
+
'div',
|
|
5022
|
+
{
|
|
5023
|
+
className: 'schema-modal',
|
|
5024
|
+
onClick: function(e) { e.stopPropagation(); }
|
|
5025
|
+
},
|
|
5026
|
+
/* Header */
|
|
5027
|
+
React.createElement(
|
|
5028
|
+
'div', { className: 'schema-modal-header' },
|
|
5029
|
+
React.createElement(
|
|
5030
|
+
'div', { className: 'schema-modal-title' },
|
|
5031
|
+
React.createElement('i', { className: 'fas fa-table', style: { marginRight: '8px', opacity: 0.7 } }),
|
|
5032
|
+
schemaModal.label,
|
|
5033
|
+
!schemaModal.error && schemaModal.data && React.createElement(
|
|
5034
|
+
'span', { className: 'schema-modal-table-name' }, schemaModal.data.table
|
|
5035
|
+
)
|
|
5036
|
+
),
|
|
5037
|
+
React.createElement(
|
|
5038
|
+
'button',
|
|
5039
|
+
{ className: 'schema-modal-close', onClick: function() { setSchemaModal(null); }, title: 'Close' },
|
|
5040
|
+
React.createElement('i', { className: 'fas fa-times' })
|
|
5041
|
+
)
|
|
5042
|
+
),
|
|
5043
|
+
/* Body */
|
|
5044
|
+
React.createElement(
|
|
5045
|
+
'div', { className: 'schema-modal-body' },
|
|
5046
|
+
schemaModal.error
|
|
5047
|
+
? React.createElement('div', { className: 'schema-modal-error' },
|
|
5048
|
+
React.createElement('i', { className: 'fas fa-exclamation-circle', style: { marginRight: '8px' } }),
|
|
5049
|
+
schemaModal.error
|
|
5050
|
+
)
|
|
5051
|
+
: [
|
|
5052
|
+
/* Columns table */
|
|
5053
|
+
React.createElement(
|
|
5054
|
+
'table', { key: 'cols', className: 'schema-table' },
|
|
5055
|
+
React.createElement(
|
|
5056
|
+
'thead', null,
|
|
5057
|
+
React.createElement(
|
|
5058
|
+
'tr', null,
|
|
5059
|
+
React.createElement('th', null, 'Column'),
|
|
5060
|
+
React.createElement('th', null, 'Type'),
|
|
5061
|
+
React.createElement('th', null, 'Options')
|
|
5062
|
+
)
|
|
5063
|
+
),
|
|
5064
|
+
React.createElement(
|
|
5065
|
+
'tbody', null,
|
|
5066
|
+
schemaModal.data.columns.map(function(col) {
|
|
5067
|
+
var opts = [];
|
|
5068
|
+
if (col.null === false) opts.push('NOT NULL');
|
|
5069
|
+
if (col.default !== undefined && col.default !== null) opts.push('default: ' + col.default);
|
|
5070
|
+
if (col.limit) opts.push('limit: ' + col.limit);
|
|
5071
|
+
if (col.precision) opts.push('precision: ' + col.precision + (col.scale ? ', scale: ' + col.scale : ''));
|
|
5072
|
+
if (col.primary_key) opts.push('PK');
|
|
5073
|
+
return React.createElement(
|
|
5074
|
+
'tr', { key: col.name },
|
|
5075
|
+
React.createElement('td', { className: 'schema-col-name' }, col.name),
|
|
5076
|
+
React.createElement('td', { className: 'schema-col-type schema-type-' + col.type }, col.type),
|
|
5077
|
+
React.createElement('td', { className: 'schema-col-opts' }, opts.join(' · ') || '—')
|
|
5078
|
+
);
|
|
5079
|
+
})
|
|
5080
|
+
)
|
|
5081
|
+
),
|
|
5082
|
+
/* Indexes */
|
|
5083
|
+
schemaModal.data.indexes && schemaModal.data.indexes.length > 0 && React.createElement(
|
|
5084
|
+
'div', { key: 'idxs', className: 'schema-indexes' },
|
|
5085
|
+
React.createElement('div', { className: 'schema-indexes-header' }, 'Indexes'),
|
|
5086
|
+
schemaModal.data.indexes.map(function(idx, i) {
|
|
5087
|
+
return React.createElement(
|
|
5088
|
+
'div', { key: idx.name || i, className: 'schema-index-row' },
|
|
5089
|
+
React.createElement('span', { className: 'schema-index-cols' },
|
|
5090
|
+
React.createElement('i', { className: 'fas fa-key', style: { fontSize: '9px', marginRight: '5px', opacity: 0.5 } }),
|
|
5091
|
+
idx.columns.join(', ')
|
|
5092
|
+
),
|
|
5093
|
+
idx.unique && React.createElement('span', { className: 'schema-index-unique' }, 'UNIQUE'),
|
|
5094
|
+
idx.name && React.createElement('span', { className: 'schema-index-name' }, idx.name)
|
|
5095
|
+
);
|
|
5096
|
+
})
|
|
5097
|
+
)
|
|
5098
|
+
]
|
|
5099
|
+
)
|
|
5100
|
+
)
|
|
4292
5101
|
)
|
|
4293
5102
|
);
|
|
4294
5103
|
};
|