mbeditor 0.13.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +91 -0
- data/app/assets/javascripts/mbeditor/application.js +3 -0
- data/app/assets/javascripts/mbeditor/audit_log.js +165 -0
- data/app/assets/javascripts/mbeditor/collaboration_service.js +248 -26
- data/app/assets/javascripts/mbeditor/components/ChangelogView.js +89 -94
- data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +6 -9
- data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +12 -7
- data/app/assets/javascripts/mbeditor/components/CombinedDiffViewer.js +20 -0
- data/app/assets/javascripts/mbeditor/components/DiffViewer.js +1 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +429 -247
- data/app/assets/javascripts/mbeditor/components/FileHistoryPanel.js +6 -9
- data/app/assets/javascripts/mbeditor/components/FileTree.js +26 -12
- data/app/assets/javascripts/mbeditor/components/GitPanel.js +3 -0
- data/app/assets/javascripts/mbeditor/components/Gutter.js +51 -0
- data/app/assets/javascripts/mbeditor/components/LogPanel.js +3 -44
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +1168 -1243
- data/app/assets/javascripts/mbeditor/components/ModelGraph.js +94 -37
- data/app/assets/javascripts/mbeditor/components/ProblemsPanel.js +47 -65
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +26 -8
- data/app/assets/javascripts/mbeditor/components/SettingsModal.js +342 -0
- data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +2 -1
- data/app/assets/javascripts/mbeditor/components/TabBar.js +67 -98
- data/app/assets/javascripts/mbeditor/editor_plugins.js +687 -305
- data/app/assets/javascripts/mbeditor/file_import.js +13 -15
- data/app/assets/javascripts/mbeditor/file_service.js +38 -39
- data/app/assets/javascripts/mbeditor/git_service.js +15 -1
- data/app/assets/javascripts/mbeditor/history_service.js +5 -13
- data/app/assets/javascripts/mbeditor/search_service.js +9 -0
- data/app/assets/javascripts/mbeditor/tab_manager.js +127 -49
- data/app/assets/javascripts/mbeditor/websocket_service.js +13 -5
- data/app/assets/stylesheets/mbeditor/application.css +6 -1
- data/app/assets/stylesheets/mbeditor/editor.css +642 -248
- data/app/assets/stylesheets/mbeditor/glass.css +163 -0
- data/app/assets/stylesheets/mbeditor/themes.css +90 -30
- data/app/channels/mbeditor/collaboration_channel.rb +17 -4
- data/app/controllers/mbeditor/application_controller.rb +26 -3
- data/app/controllers/mbeditor/editors_controller.rb +117 -439
- data/app/services/mbeditor/archive_service.rb +137 -0
- data/app/services/mbeditor/collaboration_doc_store.rb +143 -14
- data/app/services/mbeditor/editor_state_service.rb +14 -51
- data/app/services/mbeditor/file_history_service.rb +222 -0
- data/app/services/mbeditor/git_info_service.rb +6 -0
- data/app/services/mbeditor/js_syntax_check_service.rb +42 -16
- data/app/services/mbeditor/lint_service.rb +137 -0
- data/app/services/mbeditor/locked_json_file.rb +67 -0
- data/app/services/mbeditor/process_runner.rb +32 -0
- data/app/services/mbeditor/ruby_lsp_result_translator.rb +226 -0
- data/app/services/mbeditor/search_replace_service.rb +8 -0
- data/app/views/layouts/mbeditor/application.html.erb +1 -1
- data/lib/mbeditor/audit_log.rb +203 -0
- data/lib/mbeditor/configuration.rb +6 -1
- data/lib/mbeditor/rack/pending_migration_bypass.rb +15 -9
- data/lib/mbeditor/route_map.rb +4 -0
- data/lib/mbeditor/ruby_lsp_client.rb +82 -14
- data/lib/mbeditor/version.rb +1 -1
- data/lib/mbeditor.rb +1 -0
- metadata +12 -2
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; })();
|
|
4
3
|
|
|
5
4
|
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
|
6
5
|
|
|
@@ -25,14 +24,23 @@ var useMemo = _React.useMemo;
|
|
|
25
24
|
// the re-render happened anyway, and the quick-open index was never rebuilt.
|
|
26
25
|
// JSON.stringify over the whole tree measures 0.33 ms for ~1600 nodes, well
|
|
27
26
|
// under the render it saves.
|
|
28
|
-
|
|
27
|
+
// newSignature lets a caller that has already stringified newData hand the
|
|
28
|
+
// result in. The tree poll needs its own changed/unchanged answer and cannot
|
|
29
|
+
// read one out of a functional setState updater, which React invokes during a
|
|
30
|
+
// later render and which has to stay pure. Without this the poll stringified
|
|
31
|
+
// the whole tree a second time on every tick, forever.
|
|
32
|
+
function _treeUpdater(newData, newSignature) {
|
|
29
33
|
return function (prevData) {
|
|
30
|
-
|
|
34
|
+
var signature = newSignature != null ? newSignature : JSON.stringify(newData);
|
|
35
|
+
if (signature === JSON.stringify(prevData)) return prevData;
|
|
31
36
|
SearchService.buildIndex(newData);
|
|
32
37
|
return newData;
|
|
33
38
|
};
|
|
34
39
|
}
|
|
35
40
|
|
|
41
|
+
// The tree poll's own view of the last tree it saw.
|
|
42
|
+
var _lastPolledTree = null;
|
|
43
|
+
|
|
36
44
|
var SIDEBAR_MIN_WIDTH = 280;
|
|
37
45
|
var SIDEBAR_MAX_WIDTH = 560;
|
|
38
46
|
var EDITOR_MIN_WIDTH = 320;
|
|
@@ -40,6 +48,7 @@ var GIT_PANEL_MIN_WIDTH = 280;
|
|
|
40
48
|
var PANE_MIN_WIDTH_PERCENT = 20;
|
|
41
49
|
var PANE_MAX_WIDTH_PERCENT = 80;
|
|
42
50
|
var SIDEBAR_COLLAPSED_WIDTH = 48;
|
|
51
|
+
var DRAWER_MIN_HEIGHT = 120;
|
|
43
52
|
// Extension -> Prettier parser. Limited to what the vendored plugins actually
|
|
44
53
|
// parse (babel, estree, html, postcss, markdown); anything outside this map
|
|
45
54
|
// falls through to Monaco's re-indent.
|
|
@@ -61,6 +70,27 @@ function prettierParserFor(path) {
|
|
|
61
70
|
return PRETTIER_PARSERS[String(path || '').split('.').pop().toLowerCase()] || null;
|
|
62
71
|
}
|
|
63
72
|
|
|
73
|
+
// The audit ring takes numbers only, so a path travels as the index of its
|
|
74
|
+
// extension in the legend and can never be reconstructed from the log.
|
|
75
|
+
function _auditExt(path) {
|
|
76
|
+
var audit = window.MbeditorAudit;
|
|
77
|
+
return audit ? audit.code('ext', String(path || '').split('.').pop().toLowerCase()) : 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Named rather than passed an EV code so callers need no audit reference.
|
|
81
|
+
function _recErr(evName) {
|
|
82
|
+
var audit = window.MbeditorAudit;
|
|
83
|
+
if (audit) audit.rec(audit.EV.ERR, audit.EV[evName]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// null means no formatter covers this file type, which is not a round trip.
|
|
87
|
+
function _recFormat(path, startedAt, formatted) {
|
|
88
|
+
var audit = window.MbeditorAudit;
|
|
89
|
+
if (audit && formatted != null) {
|
|
90
|
+
audit.rec(audit.EV.FORMAT, _auditExt(path), Date.now() - startedAt, formatted.length);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
64
94
|
// Indentation comes from the editor's own tabSize/insertSpaces, so formatting
|
|
65
95
|
// produces what the user set up. `insertSpaces !== true` mirrors how
|
|
66
96
|
// EditorPanel configures Monaco: anything but an explicit true means tabs.
|
|
@@ -116,7 +146,8 @@ var DEFAULT_EDITOR_PREFS = {
|
|
|
116
146
|
wordBasedSuggestions: 'matchingDocuments',
|
|
117
147
|
acceptSuggestionOnEnter: 'on',
|
|
118
148
|
autoRevealInExplorer: true,
|
|
119
|
-
|
|
149
|
+
toolbarLabels: false,
|
|
150
|
+
glass: false,
|
|
120
151
|
rubocopLintEnabled: true,
|
|
121
152
|
routeHints: true,
|
|
122
153
|
prettierPrintWidth: 80,
|
|
@@ -130,7 +161,8 @@ var DEFAULT_EDITOR_PREFS = {
|
|
|
130
161
|
tabDisplayMode: 'scroll',
|
|
131
162
|
persistFindState: true,
|
|
132
163
|
showDotFiles: false,
|
|
133
|
-
branchStateRestore: true
|
|
164
|
+
branchStateRestore: true,
|
|
165
|
+
auditLog: true
|
|
134
166
|
};
|
|
135
167
|
|
|
136
168
|
// Indentation of formatted output is the formatter's job, not a post-pass:
|
|
@@ -139,6 +171,52 @@ var DEFAULT_EDITOR_PREFS = {
|
|
|
139
171
|
// "Convert Indentation to Tabs / to Spaces" commands (F1) cover converting a
|
|
140
172
|
// file that is already open, using its own indentation guesser.
|
|
141
173
|
|
|
174
|
+
// Drops both rings — the browser's and the server's — so the next download is
|
|
175
|
+
// a trace of one session and nothing else.
|
|
176
|
+
function clearAuditLog() {
|
|
177
|
+
if (window.MbeditorAudit) window.MbeditorAudit.clear();
|
|
178
|
+
fetch(window.mbeditorBasePath() + '/audit_log', {
|
|
179
|
+
method: 'DELETE',
|
|
180
|
+
headers: { 'X-Mbeditor-Client': '1' }
|
|
181
|
+
}).then(function () {
|
|
182
|
+
EditorStore.setStatus('Audit log cleared', 'info');
|
|
183
|
+
})["catch"](function () {
|
|
184
|
+
EditorStore.setStatus('Could not clear the audit log', 'error');
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Flush first, so the entries recorded in this session are on the server
|
|
189
|
+
// before it builds the payload.
|
|
190
|
+
function downloadAuditLog() {
|
|
191
|
+
var fetchLog = function () {
|
|
192
|
+
return fetch(window.mbeditorBasePath() + '/audit_log').then(function (resp) {
|
|
193
|
+
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
|
194
|
+
return resp.json();
|
|
195
|
+
}).then(function (payload) {
|
|
196
|
+
// The legend lives only in the browser bundle, so it is stitched on here
|
|
197
|
+
// rather than uploaded — see audit_log.js.
|
|
198
|
+
if (payload.client && window.MbeditorAudit) payload.client.legend = window.MbeditorAudit.LEGEND;
|
|
199
|
+
var blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
|
|
200
|
+
var objectUrl = URL.createObjectURL(blob);
|
|
201
|
+
var link = document.createElement('a');
|
|
202
|
+
link.href = objectUrl;
|
|
203
|
+
link.download = 'mbeditor-audit-' + Date.now() + '.json';
|
|
204
|
+
link.rel = 'noopener';
|
|
205
|
+
document.body.appendChild(link);
|
|
206
|
+
link.click();
|
|
207
|
+
document.body.removeChild(link);
|
|
208
|
+
// Revoking in the same tick can free the blob before the browser has
|
|
209
|
+
// started reading it, which cancels the download instead of saving it.
|
|
210
|
+
setTimeout(function () { URL.revokeObjectURL(objectUrl); }, 60000);
|
|
211
|
+
EditorStore.setStatus('Downloading audit log...', 'info');
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
var flushed = window.MbeditorAudit ? window.MbeditorAudit.flush() : Promise.resolve();
|
|
215
|
+
flushed.then(fetchLog, fetchLog)["catch"](function (err) {
|
|
216
|
+
EditorStore.setStatus('Audit log download failed: ' + (err && err.message || 'network error'), 'error');
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
142
220
|
function diffLines(oldLines, newLines) {
|
|
143
221
|
var n = oldLines.length, m = newLines.length;
|
|
144
222
|
var dp = [];
|
|
@@ -157,6 +235,46 @@ function diffLines(oldLines, newLines) {
|
|
|
157
235
|
return changed;
|
|
158
236
|
}
|
|
159
237
|
|
|
238
|
+
// Cursor line/column for the status bar.
|
|
239
|
+
//
|
|
240
|
+
// Its own component holding its own state, deliberately: the cursor moves on
|
|
241
|
+
// every keystroke and every click, and MbeditorApp subscribes to the whole
|
|
242
|
+
// store — reading the position there would re-render the entire app, tree and
|
|
243
|
+
// all, on each keypress. Here a move re-renders one span.
|
|
244
|
+
var CursorPosition = function CursorPosition() {
|
|
245
|
+
var _pos = React.useState(null);
|
|
246
|
+
var pos = _pos[0];
|
|
247
|
+
var setPos = _pos[1];
|
|
248
|
+
|
|
249
|
+
React.useEffect(function () {
|
|
250
|
+
var disposable = null;
|
|
251
|
+
var attach = function () {
|
|
252
|
+
if (disposable) { disposable.dispose(); disposable = null; }
|
|
253
|
+
var editor = window.__mbeditorActiveEditor;
|
|
254
|
+
if (!editor) { setPos(null); return; }
|
|
255
|
+
var read = function () {
|
|
256
|
+
var p = editor.getPosition();
|
|
257
|
+
setPos(p ? { line: p.lineNumber, col: p.column } : null);
|
|
258
|
+
};
|
|
259
|
+
read();
|
|
260
|
+
disposable = editor.onDidChangeCursorPosition(read);
|
|
261
|
+
};
|
|
262
|
+
attach();
|
|
263
|
+
window.addEventListener('mbeditor:active-editor', attach);
|
|
264
|
+
return function () {
|
|
265
|
+
window.removeEventListener('mbeditor:active-editor', attach);
|
|
266
|
+
if (disposable) disposable.dispose();
|
|
267
|
+
};
|
|
268
|
+
}, []);
|
|
269
|
+
|
|
270
|
+
if (!pos) return null;
|
|
271
|
+
return React.createElement(
|
|
272
|
+
'span',
|
|
273
|
+
{ className: 'statusbar-cursor', title: 'Line ' + pos.line + ', column ' + pos.col },
|
|
274
|
+
'Ln ' + pos.line + ', Col ' + pos.col
|
|
275
|
+
);
|
|
276
|
+
};
|
|
277
|
+
|
|
160
278
|
var SidebarActionButton = function SidebarActionButton(_ref) {
|
|
161
279
|
var title = _ref.title;
|
|
162
280
|
var iconClass = _ref.iconClass;
|
|
@@ -191,7 +309,13 @@ var SidebarActionButton = function SidebarActionButton(_ref) {
|
|
|
191
309
|
className: "project-action-btn" + (danger ? " danger" : ""),
|
|
192
310
|
"aria-label": ariaLabel || title,
|
|
193
311
|
"aria-busy": !!ariaBusy,
|
|
194
|
-
onClick:
|
|
312
|
+
onClick: function (e) {
|
|
313
|
+
if (onClick) onClick(e);
|
|
314
|
+
// Clicking leaves the button focused, which keeps the :focus-within
|
|
315
|
+
// tooltip open until the user clicks elsewhere. Blur to drop it
|
|
316
|
+
// immediately; a Tab-focused button still shows it on keyboard focus.
|
|
317
|
+
e.currentTarget.blur();
|
|
318
|
+
},
|
|
195
319
|
disabled: !!disabled
|
|
196
320
|
},
|
|
197
321
|
!ariaBusy && React.createElement("i", { className: iconClass })
|
|
@@ -271,107 +395,126 @@ function FileReloadBanner(_ref) {
|
|
|
271
395
|
);
|
|
272
396
|
}
|
|
273
397
|
|
|
398
|
+
// ── Test-result cache (localStorage) ────────────────────────────────────
|
|
399
|
+
// One entry per test file, capped so an unbounded suite can't fill the
|
|
400
|
+
// origin's quota and take the unsaved-draft backup down with it (#84).
|
|
401
|
+
var TEST_CACHE_PREFIX = 'mbeditor_test_result_';
|
|
402
|
+
var TEST_CACHE_MAX_ENTRIES = 50;
|
|
403
|
+
|
|
404
|
+
// ponytail: full localStorage scan per save, fine at a 50-entry cap.
|
|
405
|
+
function _trimTestResultCache(maxEntries) {
|
|
406
|
+
var entries = [];
|
|
407
|
+
for (var i = 0; i < localStorage.length; i++) {
|
|
408
|
+
var key = localStorage.key(i);
|
|
409
|
+
if (key && key.indexOf(TEST_CACHE_PREFIX) === 0) {
|
|
410
|
+
var ts = 0;
|
|
411
|
+
try { ts = (JSON.parse(localStorage.getItem(key)) || {}).ts || 0; } catch (e) {}
|
|
412
|
+
entries.push({ key: key, ts: ts });
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
entries.sort(function (a, b) { return a.ts - b.ts; });
|
|
416
|
+
while (entries.length > maxEntries) localStorage.removeItem(entries.shift().key);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function saveCachedTestResult(filePath, result) {
|
|
420
|
+
var payload = JSON.stringify(_extends({}, result, { ts: Date.now() }));
|
|
421
|
+
try {
|
|
422
|
+
localStorage.setItem(TEST_CACHE_PREFIX + filePath, payload);
|
|
423
|
+
} catch (e) {
|
|
424
|
+
_trimTestResultCache(Math.floor(TEST_CACHE_MAX_ENTRIES / 2));
|
|
425
|
+
try { localStorage.setItem(TEST_CACHE_PREFIX + filePath, payload); } catch (e2) { return; }
|
|
426
|
+
}
|
|
427
|
+
_trimTestResultCache(TEST_CACHE_MAX_ENTRIES);
|
|
428
|
+
}
|
|
429
|
+
|
|
274
430
|
var MbeditorApp = function MbeditorApp() {
|
|
275
431
|
var _useState = useState(EditorStore.getState());
|
|
276
432
|
|
|
277
|
-
var _useState2 = _slicedToArray(_useState, 2);
|
|
278
433
|
|
|
279
|
-
var state =
|
|
280
|
-
var setState =
|
|
434
|
+
var state = _useState[0];
|
|
435
|
+
var setState = _useState[1];
|
|
281
436
|
|
|
282
437
|
var _useState21 = useState(null);
|
|
283
|
-
var
|
|
284
|
-
var
|
|
285
|
-
var setHistoryPanelPath = _useState22[1];
|
|
438
|
+
var historyPanelPath = _useState21[0];
|
|
439
|
+
var setHistoryPanelPath = _useState21[1];
|
|
286
440
|
|
|
287
441
|
var _useState23 = useState(false);
|
|
288
|
-
var
|
|
289
|
-
var
|
|
290
|
-
var setIsNavigating = _useState24[1];
|
|
442
|
+
var isNavigating = _useState23[0];
|
|
443
|
+
var setIsNavigating = _useState23[1];
|
|
291
444
|
|
|
292
445
|
var _useState25 = useState(false);
|
|
293
|
-
var
|
|
294
|
-
var
|
|
295
|
-
var setIsReviewOpen = _useState26[1];
|
|
446
|
+
var isReviewOpen = _useState25[0];
|
|
447
|
+
var setIsReviewOpen = _useState25[1];
|
|
296
448
|
|
|
297
449
|
var _useState27 = useState(null);
|
|
298
|
-
var
|
|
299
|
-
var
|
|
300
|
-
var setSelectedCommit = _useState28[1];
|
|
450
|
+
var selectedCommit = _useState27[0];
|
|
451
|
+
var setSelectedCommit = _useState27[1];
|
|
301
452
|
|
|
302
453
|
var _useState29 = useState(null);
|
|
303
|
-
var
|
|
304
|
-
var
|
|
305
|
-
|
|
454
|
+
var commitDetailFiles = _useState29[0];
|
|
455
|
+
var setCommitDetailFiles = _useState29[1];
|
|
456
|
+
|
|
457
|
+
var _useStateCFI = useState(0);
|
|
458
|
+
var conflictFileIndex = _useStateCFI[0];
|
|
459
|
+
var setConflictFileIndex = _useStateCFI[1];
|
|
306
460
|
|
|
307
461
|
var _useState4 = useState([]);
|
|
308
462
|
|
|
309
|
-
var _useState42 = _slicedToArray(_useState4, 2);
|
|
310
463
|
|
|
311
|
-
var treeData =
|
|
312
|
-
var setTreeData =
|
|
464
|
+
var treeData = _useState4[0];
|
|
465
|
+
var setTreeData = _useState4[1];
|
|
313
466
|
|
|
314
467
|
var _useState5 = useState("");
|
|
315
468
|
|
|
316
|
-
var _useState52 = _slicedToArray(_useState5, 2);
|
|
317
469
|
|
|
318
|
-
var projectRootName =
|
|
319
|
-
var setProjectRootName =
|
|
470
|
+
var projectRootName = _useState5[0];
|
|
471
|
+
var setProjectRootName = _useState5[1];
|
|
320
472
|
|
|
321
473
|
var _useState6 = useState(null);
|
|
322
474
|
|
|
323
|
-
var _useState62 = _slicedToArray(_useState6, 2);
|
|
324
475
|
|
|
325
|
-
var selectedTreeNode =
|
|
326
|
-
var setSelectedTreeNode =
|
|
476
|
+
var selectedTreeNode = _useState6[0];
|
|
477
|
+
var setSelectedTreeNode = _useState6[1];
|
|
327
478
|
|
|
328
479
|
var _useStateSP = useState(new Set());
|
|
329
|
-
var
|
|
330
|
-
var
|
|
331
|
-
var setSelectedPaths = _useStateSP2[1];
|
|
480
|
+
var selectedPaths = _useStateSP[0];
|
|
481
|
+
var setSelectedPaths = _useStateSP[1];
|
|
332
482
|
|
|
333
483
|
var _useState7 = useState("");
|
|
334
484
|
|
|
335
|
-
var _useState72 = _slicedToArray(_useState7, 2);
|
|
336
485
|
|
|
337
|
-
var searchQuery =
|
|
338
|
-
var setSearchQuery =
|
|
486
|
+
var searchQuery = _useState7[0];
|
|
487
|
+
var setSearchQuery = _useState7[1];
|
|
339
488
|
|
|
340
489
|
var _useState33 = useState(false);
|
|
341
|
-
var
|
|
342
|
-
var
|
|
343
|
-
var setSearchLoading = _useState332[1];
|
|
490
|
+
var searchLoading = _useState33[0];
|
|
491
|
+
var setSearchLoading = _useState33[1];
|
|
344
492
|
|
|
345
493
|
var searchRequestIdRef = useRef(0);
|
|
346
494
|
|
|
347
495
|
var _useState33h = useState(false);
|
|
348
|
-
var
|
|
349
|
-
var
|
|
350
|
-
var setSearchHasMore = _useState33h2[1];
|
|
496
|
+
var searchHasMore = _useState33h[0];
|
|
497
|
+
var setSearchHasMore = _useState33h[1];
|
|
351
498
|
|
|
352
499
|
var _useState33tc = useState(0);
|
|
353
|
-
var
|
|
354
|
-
var
|
|
355
|
-
var setSearchTotalCount = _useState33tc2[1];
|
|
500
|
+
var searchTotalCount = _useState33tc[0];
|
|
501
|
+
var setSearchTotalCount = _useState33tc[1];
|
|
356
502
|
|
|
357
503
|
var searchHasMoreRef = useRef(false);
|
|
358
504
|
var searchOffsetRef = useRef(0);
|
|
359
505
|
var searchLoadingMoreRef = useRef(false);
|
|
360
506
|
|
|
361
507
|
var _useStateRx = useState(false);
|
|
362
|
-
var
|
|
363
|
-
var
|
|
364
|
-
var setSearchUseRegex = _useStateRx2[1];
|
|
508
|
+
var searchUseRegex = _useStateRx[0];
|
|
509
|
+
var setSearchUseRegex = _useStateRx[1];
|
|
365
510
|
|
|
366
511
|
var _useStateMC = useState(false);
|
|
367
|
-
var
|
|
368
|
-
var
|
|
369
|
-
var setSearchMatchCase = _useStateMC2[1];
|
|
512
|
+
var searchMatchCase = _useStateMC[0];
|
|
513
|
+
var setSearchMatchCase = _useStateMC[1];
|
|
370
514
|
|
|
371
515
|
var _useStateWW = useState(false);
|
|
372
|
-
var
|
|
373
|
-
var
|
|
374
|
-
var setSearchWholeWord = _useStateWW2[1];
|
|
516
|
+
var searchWholeWord = _useStateWW[0];
|
|
517
|
+
var setSearchWholeWord = _useStateWW[1];
|
|
375
518
|
|
|
376
519
|
var searchQueryRef = useRef('');
|
|
377
520
|
var searchUseRegexRef = useRef(false);
|
|
@@ -395,9 +538,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
395
538
|
// File paths whose match list is folded away. Keyed by path, so a file that
|
|
396
539
|
// scrolls out of the window keeps its state.
|
|
397
540
|
var _useStateSC = useState({});
|
|
398
|
-
var
|
|
399
|
-
var
|
|
400
|
-
var setSearchCollapsedFiles = _useStateSC2[1];
|
|
541
|
+
var searchCollapsedFiles = _useStateSC[0];
|
|
542
|
+
var setSearchCollapsedFiles = _useStateSC[1];
|
|
401
543
|
var toggleSearchFile = function toggleSearchFile(file) {
|
|
402
544
|
setSearchCollapsedFiles(function (prev) {
|
|
403
545
|
var next = Object.assign({}, prev);
|
|
@@ -406,9 +548,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
406
548
|
});
|
|
407
549
|
};
|
|
408
550
|
var _useStateSV = useState({ scrollTop: 0, height: 0 });
|
|
409
|
-
var
|
|
410
|
-
var
|
|
411
|
-
var setSearchViewport = _useStateSV2[1];
|
|
551
|
+
var searchViewport = _useStateSV[0];
|
|
552
|
+
var setSearchViewport = _useStateSV[1];
|
|
412
553
|
|
|
413
554
|
// The container's height is only known once it is on screen, and a viewport
|
|
414
555
|
// of 0 would render a single row and never grow — nothing would scroll, so
|
|
@@ -441,31 +582,26 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
441
582
|
}).current;
|
|
442
583
|
|
|
443
584
|
var _useStateRM = useState(false);
|
|
444
|
-
var
|
|
445
|
-
var
|
|
446
|
-
var setReplaceMode = _useStateRM2[1];
|
|
585
|
+
var replaceMode = _useStateRM[0];
|
|
586
|
+
var setReplaceMode = _useStateRM[1];
|
|
447
587
|
|
|
448
588
|
var _useStateRQ = useState('');
|
|
449
|
-
var
|
|
450
|
-
var
|
|
451
|
-
var setReplaceQuery = _useStateRQ2[1];
|
|
589
|
+
var replaceQuery = _useStateRQ[0];
|
|
590
|
+
var setReplaceQuery = _useStateRQ[1];
|
|
452
591
|
|
|
453
592
|
var _useStateRL = useState(false);
|
|
454
|
-
var
|
|
455
|
-
var
|
|
456
|
-
var setReplaceLoading = _useStateRL2[1];
|
|
593
|
+
var replaceLoading = _useStateRL[0];
|
|
594
|
+
var setReplaceLoading = _useStateRL[1];
|
|
457
595
|
|
|
458
596
|
var _useState8 = useState("explorer");
|
|
459
597
|
|
|
460
|
-
var _useState82 = _slicedToArray(_useState8, 2);
|
|
461
598
|
|
|
462
|
-
var activeSidebarTab =
|
|
463
|
-
var setActiveSidebarTab =
|
|
599
|
+
var activeSidebarTab = _useState8[0];
|
|
600
|
+
var setActiveSidebarTab = _useState8[1];
|
|
464
601
|
|
|
465
602
|
var _useStateSC = useState(false);
|
|
466
|
-
var
|
|
467
|
-
var
|
|
468
|
-
var setSidebarCollapsed = _useStateSC2[1];
|
|
603
|
+
var sidebarCollapsed = _useStateSC[0];
|
|
604
|
+
var setSidebarCollapsed = _useStateSC[1];
|
|
469
605
|
|
|
470
606
|
// Ref mirrors for use inside long-lived event handlers registered with
|
|
471
607
|
// empty-dependency effects (live search refresh on files_changed).
|
|
@@ -477,130 +613,110 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
477
613
|
var jsSyntaxCheckAvailableRef = useRef(false);
|
|
478
614
|
|
|
479
615
|
var _useStateRFMap = useState({});
|
|
480
|
-
var
|
|
481
|
-
var
|
|
482
|
-
var setRailsFilesMap = _useStateRFMap2[1];
|
|
616
|
+
var railsFilesMap = _useStateRFMap[0];
|
|
617
|
+
var setRailsFilesMap = _useStateRFMap[1];
|
|
483
618
|
|
|
484
619
|
var _useStateRFC = useState({});
|
|
485
|
-
var
|
|
486
|
-
var
|
|
487
|
-
var setRailsGroupsCollapsed = _useStateRFC2[1];
|
|
620
|
+
var railsGroupsCollapsed = _useStateRFC[0];
|
|
621
|
+
var setRailsGroupsCollapsed = _useStateRFC[1];
|
|
488
622
|
|
|
489
623
|
var _useStateChangelog = useState(null); // null | { content, loading, error }
|
|
490
|
-
var
|
|
491
|
-
var
|
|
492
|
-
var setChangelogState = _useStateChangelog2[1];
|
|
624
|
+
var changelogState = _useStateChangelog[0];
|
|
625
|
+
var setChangelogState = _useStateChangelog[1];
|
|
493
626
|
|
|
494
627
|
var _useStateSchemaModal = useState(null);
|
|
495
|
-
var
|
|
496
|
-
var
|
|
497
|
-
var setSchemaModal = _useStateSchemaModal2[1];
|
|
628
|
+
var schemaModal = _useStateSchemaModal[0];
|
|
629
|
+
var setSchemaModal = _useStateSchemaModal[1];
|
|
498
630
|
|
|
499
631
|
var _useStateImportConflict = useState(null);
|
|
500
|
-
var
|
|
501
|
-
var
|
|
502
|
-
var setImportConflict = _useStateImportConflict2[1];
|
|
632
|
+
var importConflict = _useStateImportConflict[0];
|
|
633
|
+
var setImportConflict = _useStateImportConflict[1];
|
|
503
634
|
|
|
504
635
|
// { initialFolder } while the upload dialog is open, null otherwise.
|
|
505
636
|
var _useStateImportDialog = useState(null);
|
|
506
|
-
var
|
|
507
|
-
var
|
|
508
|
-
var setImportDialog = _useStateImportDialog2[1];
|
|
637
|
+
var importDialog = _useStateImportDialog[0];
|
|
638
|
+
var setImportDialog = _useStateImportDialog[1];
|
|
509
639
|
|
|
510
640
|
var _useStateSchemaLoading = useState(null);
|
|
511
|
-
var
|
|
512
|
-
var
|
|
513
|
-
var setSchemaLoadingLabel = _useStateSchemaLoading2[1];
|
|
641
|
+
var schemaLoadingLabel = _useStateSchemaLoading[0];
|
|
642
|
+
var setSchemaLoadingLabel = _useStateSchemaLoading[1];
|
|
514
643
|
|
|
515
644
|
var _useState9 = useState({});
|
|
516
645
|
|
|
517
|
-
var _useState92 = _slicedToArray(_useState9, 2);
|
|
518
646
|
|
|
519
|
-
var markers =
|
|
520
|
-
var setMarkers =
|
|
647
|
+
var markers = _useState9[0];
|
|
648
|
+
var setMarkers = _useState9[1];
|
|
521
649
|
// { tabId: [] }
|
|
522
650
|
|
|
523
651
|
var _useState10 = useState({});
|
|
524
652
|
|
|
525
|
-
var _useState102 = _slicedToArray(_useState10, 2);
|
|
526
653
|
|
|
527
|
-
var loading =
|
|
528
|
-
var setLoading =
|
|
654
|
+
var loading = _useState10[0];
|
|
655
|
+
var setLoading = _useState10[1];
|
|
529
656
|
|
|
530
657
|
var _useState11 = useState(null);
|
|
531
658
|
|
|
532
|
-
var _useState112 = _slicedToArray(_useState11, 2);
|
|
533
659
|
|
|
534
|
-
var closingTabId =
|
|
535
|
-
var setClosingTabId =
|
|
660
|
+
var closingTabId = _useState11[0];
|
|
661
|
+
var setClosingTabId = _useState11[1];
|
|
536
662
|
|
|
537
663
|
var _useState12 = useState(null);
|
|
538
664
|
|
|
539
|
-
var _useState122 = _slicedToArray(_useState12, 2);
|
|
540
665
|
|
|
541
|
-
var closingPaneId =
|
|
542
|
-
var setClosingPaneId =
|
|
666
|
+
var closingPaneId = _useState12[0];
|
|
667
|
+
var setClosingPaneId = _useState12[1];
|
|
543
668
|
|
|
544
669
|
var _useState13 = useState(SIDEBAR_MIN_WIDTH);
|
|
545
670
|
|
|
546
|
-
var _useState132 = _slicedToArray(_useState13, 2);
|
|
547
671
|
|
|
548
|
-
var sidebarWidth =
|
|
549
|
-
var setSidebarWidth =
|
|
672
|
+
var sidebarWidth = _useState13[0];
|
|
673
|
+
var setSidebarWidth = _useState13[1];
|
|
550
674
|
|
|
551
675
|
var _useState14 = useState(50);
|
|
552
676
|
|
|
553
|
-
var _useState142 = _slicedToArray(_useState14, 2);
|
|
554
677
|
|
|
555
|
-
var pane1Width =
|
|
556
|
-
var setPane1Width =
|
|
678
|
+
var pane1Width = _useState14[0];
|
|
679
|
+
var setPane1Width = _useState14[1];
|
|
557
680
|
// percentage
|
|
558
681
|
|
|
559
682
|
var dragSplitWidthRef = useRef(pane1Width);
|
|
560
683
|
|
|
561
684
|
var _useState15 = useState(null);
|
|
562
685
|
|
|
563
|
-
var _useState152 = _slicedToArray(_useState15, 2);
|
|
564
686
|
|
|
565
|
-
var activeResizeMode =
|
|
566
|
-
var setActiveResizeMode =
|
|
687
|
+
var activeResizeMode = _useState15[0];
|
|
688
|
+
var setActiveResizeMode = _useState15[1];
|
|
567
689
|
|
|
568
690
|
var _useState16 = useState(null);
|
|
569
691
|
|
|
570
|
-
var _useState162 = _slicedToArray(_useState16, 2);
|
|
571
692
|
|
|
572
|
-
var draggedTab =
|
|
573
|
-
var setDraggedTab =
|
|
693
|
+
var draggedTab = _useState16[0];
|
|
694
|
+
var setDraggedTab = _useState16[1];
|
|
574
695
|
|
|
575
696
|
var _useState17 = useState(null);
|
|
576
697
|
|
|
577
|
-
var _useState172 = _slicedToArray(_useState17, 2);
|
|
578
698
|
|
|
579
|
-
var dragOverPaneId =
|
|
580
|
-
var setDragOverPaneId =
|
|
699
|
+
var dragOverPaneId = _useState17[0];
|
|
700
|
+
var setDragOverPaneId = _useState17[1];
|
|
581
701
|
|
|
582
702
|
var _useState18 = useState(false);
|
|
583
|
-
var
|
|
584
|
-
var
|
|
585
|
-
var setShowGitPanel = _useState182[1];
|
|
703
|
+
var showGitPanel = _useState18[0];
|
|
704
|
+
var setShowGitPanel = _useState18[1];
|
|
586
705
|
var showGitPanelRef = useRef(showGitPanel);
|
|
587
706
|
showGitPanelRef.current = showGitPanel;
|
|
588
707
|
|
|
589
708
|
var _useStateLog = useState(false);
|
|
590
|
-
var
|
|
591
|
-
var
|
|
592
|
-
var setShowLogPanel = _useStateLog2[1];
|
|
709
|
+
var showLogPanel = _useStateLog[0];
|
|
710
|
+
var setShowLogPanel = _useStateLog[1];
|
|
593
711
|
|
|
594
712
|
var _useStateProblems = useState(false);
|
|
595
|
-
var
|
|
596
|
-
var
|
|
597
|
-
var setShowProblemsPanel = _useStateProblems2[1];
|
|
713
|
+
var showProblemsPanel = _useStateProblems[0];
|
|
714
|
+
var setShowProblemsPanel = _useStateProblems[1];
|
|
598
715
|
|
|
599
716
|
// Error/warning tallies across the open tabs, mirrored into the status bar.
|
|
600
717
|
var _useStateProblemCounts = useState({ errors: 0, warnings: 0 });
|
|
601
|
-
var
|
|
602
|
-
var
|
|
603
|
-
var setProblemCounts = _useStateProblemCounts2[1];
|
|
718
|
+
var problemCounts = _useStateProblemCounts[0];
|
|
719
|
+
var setProblemCounts = _useStateProblemCounts[1];
|
|
604
720
|
|
|
605
721
|
// Below this the toolbar's labelled buttons no longer fit beside the title
|
|
606
722
|
// and the file search, and start pushing each other out of the bar.
|
|
@@ -612,9 +728,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
612
728
|
return typeof window.matchMedia === 'function' &&
|
|
613
729
|
window.matchMedia('(max-width: ' + TOOLBAR_LABEL_MIN_WIDTH + 'px)').matches;
|
|
614
730
|
});
|
|
615
|
-
var
|
|
616
|
-
var
|
|
617
|
-
var setNarrowToolbar = _useStateNarrow2[1];
|
|
731
|
+
var narrowToolbar = _useStateNarrow[0];
|
|
732
|
+
var setNarrowToolbar = _useStateNarrow[1];
|
|
618
733
|
|
|
619
734
|
useEffect(function () {
|
|
620
735
|
if (typeof window.matchMedia !== 'function') return;
|
|
@@ -634,146 +749,183 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
634
749
|
// Model graph. Built lazily — generating it eager-loads the host app — and
|
|
635
750
|
// only when the Models tab is opened.
|
|
636
751
|
var _useStateModelGraph = useState(null);
|
|
637
|
-
var
|
|
638
|
-
var
|
|
639
|
-
var setModelGraph = _useStateModelGraph2[1];
|
|
752
|
+
var modelGraph = _useStateModelGraph[0];
|
|
753
|
+
var setModelGraph = _useStateModelGraph[1];
|
|
640
754
|
|
|
641
755
|
var _useStateModelGraphLoading = useState(false);
|
|
642
|
-
var
|
|
643
|
-
var
|
|
644
|
-
var setModelGraphLoading = _useStateModelGraphLoading2[1];
|
|
756
|
+
var modelGraphLoading = _useStateModelGraphLoading[0];
|
|
757
|
+
var setModelGraphLoading = _useStateModelGraphLoading[1];
|
|
645
758
|
|
|
646
759
|
var _useState18g = useState(320);
|
|
647
|
-
var
|
|
648
|
-
var
|
|
649
|
-
var setGitPanelWidth = _useState18g2[1];
|
|
760
|
+
var gitPanelWidth = _useState18g[0];
|
|
761
|
+
var setGitPanelWidth = _useState18g[1];
|
|
650
762
|
var gitPanelWidthRef = useRef(gitPanelWidth);
|
|
651
763
|
gitPanelWidthRef.current = gitPanelWidth;
|
|
652
764
|
|
|
653
765
|
var _useState18h = useState(false);
|
|
654
766
|
|
|
655
|
-
var _useState18h2 = _slicedToArray(_useState18h, 2);
|
|
656
767
|
|
|
657
|
-
var showHelp =
|
|
658
|
-
var setShowHelp =
|
|
768
|
+
var showHelp = _useState18h[0];
|
|
769
|
+
var setShowHelp = _useState18h[1];
|
|
770
|
+
|
|
771
|
+
var _useState18s = useState(false);
|
|
772
|
+
var showSettings = _useState18s[0];
|
|
773
|
+
var setShowSettings = _useState18s[1];
|
|
659
774
|
|
|
660
775
|
var _useStatePwa = useState(null);
|
|
661
|
-
var
|
|
662
|
-
var
|
|
663
|
-
var setPwaInstallPrompt = _useStatePwa2[1];
|
|
776
|
+
var pwaInstallPrompt = _useStatePwa[0];
|
|
777
|
+
var setPwaInstallPrompt = _useStatePwa[1];
|
|
664
778
|
|
|
665
779
|
var _useState18b = useState(true);
|
|
666
780
|
|
|
667
|
-
var _useState18b2 = _slicedToArray(_useState18b, 2);
|
|
668
781
|
|
|
669
|
-
var serverOnline =
|
|
670
|
-
var setServerOnline =
|
|
782
|
+
var serverOnline = _useState18b[0];
|
|
783
|
+
var setServerOnline = _useState18b[1];
|
|
784
|
+
|
|
785
|
+
// Pending migrations. file_service.js owns the truth (it is what sees the
|
|
786
|
+
// response header) and publishes it as a <body> class plus an event; this
|
|
787
|
+
// only mirrors it into the status bar.
|
|
788
|
+
var _useStateMig = useState(function () {
|
|
789
|
+
return document.body.classList.contains('mbeditor-migration-pending');
|
|
790
|
+
});
|
|
791
|
+
var migrationsPending = _useStateMig[0];
|
|
792
|
+
var setMigrationsPending = _useStateMig[1];
|
|
793
|
+
|
|
794
|
+
useEffect(function () {
|
|
795
|
+
function onChange(e) { setMigrationsPending(!!e.detail); }
|
|
796
|
+
window.addEventListener('mbeditor:pending-migration', onChange);
|
|
797
|
+
return function () { window.removeEventListener('mbeditor:pending-migration', onChange); };
|
|
798
|
+
}, []);
|
|
671
799
|
|
|
672
800
|
var _useState18c = useState(false);
|
|
673
801
|
|
|
674
|
-
var _useState18c2 = _slicedToArray(_useState18c, 2);
|
|
675
802
|
|
|
676
|
-
var rubocopAvailable =
|
|
677
|
-
var setRubocopAvailable =
|
|
803
|
+
var rubocopAvailable = _useState18c[0];
|
|
804
|
+
var setRubocopAvailable = _useState18c[1];
|
|
678
805
|
|
|
679
806
|
var _useState18d = useState(false);
|
|
680
807
|
|
|
681
|
-
var _useState18d2 = _slicedToArray(_useState18d, 2);
|
|
682
808
|
|
|
683
|
-
var hamlLintAvailable =
|
|
684
|
-
var setHamlLintAvailable =
|
|
809
|
+
var hamlLintAvailable = _useState18d[0];
|
|
810
|
+
var setHamlLintAvailable = _useState18d[1];
|
|
685
811
|
|
|
686
812
|
var _useState18e = useState(false);
|
|
687
|
-
var
|
|
688
|
-
var
|
|
689
|
-
var setGitAvailable = _useState18e2[1];
|
|
813
|
+
var gitAvailable = _useState18e[0];
|
|
814
|
+
var setGitAvailable = _useState18e[1];
|
|
690
815
|
|
|
691
816
|
var _useState18f = useState(false);
|
|
692
|
-
var
|
|
693
|
-
var
|
|
694
|
-
var setRedmineEnabled = _useState18f2[1];
|
|
817
|
+
var redmineEnabled = _useState18f[0];
|
|
818
|
+
var setRedmineEnabled = _useState18f[1];
|
|
695
819
|
|
|
696
820
|
var _useState18rc = useState(null);
|
|
697
|
-
var
|
|
698
|
-
var
|
|
699
|
-
var setRubocopConfigPath = _useState18rc2[1];
|
|
821
|
+
var rubocopConfigPath = _useState18rc[0];
|
|
822
|
+
var setRubocopConfigPath = _useState18rc[1];
|
|
700
823
|
|
|
701
824
|
var _useState18u = useState(null);
|
|
702
|
-
var
|
|
703
|
-
var
|
|
704
|
-
var setTestResult = _useState18u2[1];
|
|
825
|
+
var testResult = _useState18u[0];
|
|
826
|
+
var setTestResult = _useState18u[1];
|
|
705
827
|
|
|
706
828
|
var _useState18v = useState(false);
|
|
707
|
-
var
|
|
708
|
-
var
|
|
709
|
-
var setTestLoading = _useState18v2[1];
|
|
829
|
+
var testLoading = _useState18v[0];
|
|
830
|
+
var setTestLoading = _useState18v[1];
|
|
710
831
|
|
|
711
832
|
var _useState18w = useState(true);
|
|
712
|
-
var
|
|
713
|
-
var
|
|
714
|
-
var setTestInlineVisible = _useState18w2[1];
|
|
833
|
+
var testInlineVisible = _useState18w[0];
|
|
834
|
+
var setTestInlineVisible = _useState18w[1];
|
|
715
835
|
|
|
716
836
|
var _useState18x = useState(null);
|
|
717
|
-
var
|
|
718
|
-
var
|
|
719
|
-
var setTestPanelFile = _useState18x2[1];
|
|
837
|
+
var testPanelFile = _useState18x[0];
|
|
838
|
+
var setTestPanelFile = _useState18x[1];
|
|
720
839
|
|
|
721
840
|
var _useState18y = useState(false);
|
|
722
|
-
var
|
|
723
|
-
var
|
|
724
|
-
var setTestPanelOpen = _useState18y2[1];
|
|
841
|
+
var testPanelOpen = _useState18y[0];
|
|
842
|
+
var setTestPanelOpen = _useState18y[1];
|
|
725
843
|
|
|
726
844
|
var _useState18p = useState(DEFAULT_EDITOR_PREFS);
|
|
727
|
-
var
|
|
728
|
-
var
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
//
|
|
732
|
-
// too narrow for
|
|
733
|
-
var toolbarIconOnly = editorPrefs.
|
|
845
|
+
var editorPrefs = _useState18p[0];
|
|
846
|
+
var setEditorPrefs = _useState18p[1];
|
|
847
|
+
|
|
848
|
+
// Icon-only toolbar: the default, since every button carries its name in a
|
|
849
|
+
// title attribute. Labels are opt-in, and are dropped regardless once the
|
|
850
|
+
// window is too narrow for them to fit beside the title and file search.
|
|
851
|
+
var toolbarIconOnly = !editorPrefs.toolbarLabels || narrowToolbar;
|
|
852
|
+
|
|
853
|
+
// The audit ring never reads preferences itself; enablement is pushed in.
|
|
854
|
+
// BOOT is recorded on the first enable rather than at mount, because the
|
|
855
|
+
// stored preference arrives from /state after the default has already been
|
|
856
|
+
// applied and switching off discards anything buffered before it.
|
|
857
|
+
var auditBooted = useRef(false);
|
|
858
|
+
useEffect(function () {
|
|
859
|
+
var audit = window.MbeditorAudit;
|
|
860
|
+
if (!audit) return;
|
|
861
|
+
var on = editorPrefs.auditLog !== false;
|
|
862
|
+
audit.setEnabled(on);
|
|
863
|
+
if (!on || auditBooted.current) return;
|
|
864
|
+
auditBooted.current = true;
|
|
865
|
+
// Ready is when Monaco is up and the file can be typed in, not when React
|
|
866
|
+
// mounted: the gap between the two is most of the boot.
|
|
867
|
+
Promise.resolve(window.__monacoReady).then(function () {
|
|
868
|
+
audit.rec(audit.EV.BOOT, performance.now() | 0);
|
|
869
|
+
});
|
|
870
|
+
}, [editorPrefs.auditLog]);
|
|
734
871
|
|
|
735
872
|
var _useState19 = useState({
|
|
736
873
|
openEditors: false,
|
|
737
874
|
projects: false
|
|
738
875
|
});
|
|
739
876
|
|
|
740
|
-
var _useState192 = _slicedToArray(_useState19, 2);
|
|
741
877
|
|
|
742
|
-
var collapsedSections =
|
|
743
|
-
var setCollapsedSections =
|
|
878
|
+
var collapsedSections = _useState19[0];
|
|
879
|
+
var setCollapsedSections = _useState19[1];
|
|
744
880
|
|
|
745
881
|
var _useState20 = useState({});
|
|
746
882
|
|
|
747
|
-
var _useState202 = _slicedToArray(_useState20, 2);
|
|
748
883
|
|
|
749
|
-
var expandedDirs =
|
|
750
|
-
var setExpandedDirs =
|
|
884
|
+
var expandedDirs = _useState20[0];
|
|
885
|
+
var setExpandedDirs = _useState20[1];
|
|
751
886
|
|
|
752
887
|
var _useState21 = useState(null);
|
|
753
888
|
|
|
754
|
-
var _useState212 = _slicedToArray(_useState21, 2);
|
|
755
889
|
|
|
756
|
-
var pendingCreate =
|
|
757
|
-
var setPendingCreate =
|
|
890
|
+
var pendingCreate = _useState21[0];
|
|
891
|
+
var setPendingCreate = _useState21[1];
|
|
758
892
|
|
|
759
893
|
var _useState22 = useState(null);
|
|
760
894
|
|
|
761
|
-
var _useState222 = _slicedToArray(_useState22, 2);
|
|
762
895
|
|
|
763
|
-
var pendingRename =
|
|
764
|
-
var setPendingRename =
|
|
896
|
+
var pendingRename = _useState22[0];
|
|
897
|
+
var setPendingRename = _useState22[1];
|
|
765
898
|
|
|
766
899
|
var _useState23 = useState(null);
|
|
767
900
|
|
|
768
|
-
var _useState232 = _slicedToArray(_useState23, 2);
|
|
769
901
|
|
|
770
|
-
var contextMenu =
|
|
771
|
-
var setContextMenu =
|
|
902
|
+
var contextMenu = _useState23[0];
|
|
903
|
+
var setContextMenu = _useState23[1];
|
|
904
|
+
var contextMenuRef = useRef(null);
|
|
772
905
|
|
|
773
906
|
var _useState24 = useState(140);
|
|
774
|
-
var
|
|
775
|
-
var
|
|
776
|
-
|
|
907
|
+
var openEditorsHeight = _useState24[0];
|
|
908
|
+
var setOpenEditorsHeight = _useState24[1];
|
|
909
|
+
|
|
910
|
+
// Both bottom drawers are resized by a gutter that sits between the editor
|
|
911
|
+
// card and the drawer, so the height lives here rather than in the drawer —
|
|
912
|
+
// a panel cannot own the handle that is no longer inside it.
|
|
913
|
+
var _drawerHeight = function (key) {
|
|
914
|
+
var saved = parseInt(window.localStorage.getItem(key), 10);
|
|
915
|
+
return (saved && saved >= DRAWER_MIN_HEIGHT) ? saved : 240;
|
|
916
|
+
};
|
|
917
|
+
var _useStateLogH = useState(function () { return _drawerHeight('mbeditorLogHeight'); });
|
|
918
|
+
var logHeight = _useStateLogH[0];
|
|
919
|
+
var setLogHeight = _useStateLogH[1];
|
|
920
|
+
var _useStateProblemsH = useState(function () { return _drawerHeight('mbeditorProblemsHeight'); });
|
|
921
|
+
var problemsHeight = _useStateProblemsH[0];
|
|
922
|
+
var setProblemsHeight = _useStateProblemsH[1];
|
|
923
|
+
|
|
924
|
+
// One-shot target for post-operation scroll reveal (create/rename/delete).
|
|
925
|
+
// A fresh object each time so the effect re-fires even for a repeated path.
|
|
926
|
+
var _useState25 = useState(null);
|
|
927
|
+
var revealTarget = _useState25[0];
|
|
928
|
+
var setRevealTarget = _useState25[1];
|
|
777
929
|
|
|
778
930
|
var resizeSessionRef = useRef(null);
|
|
779
931
|
var resizeRafRef = useRef(null);
|
|
@@ -783,9 +935,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
783
935
|
var ctrlWPendingRef = useRef(false);
|
|
784
936
|
var ctrlWTimeoutRef = useRef(null);
|
|
785
937
|
var _useStateCP = useState([]);
|
|
786
|
-
var
|
|
787
|
-
var
|
|
788
|
-
var setCustomPaths = _useStateCP2[1];
|
|
938
|
+
var customPaths = _useStateCP[0];
|
|
939
|
+
var setCustomPaths = _useStateCP[1];
|
|
789
940
|
var customPathsRef = useRef([]);
|
|
790
941
|
customPathsRef.current = customPaths;
|
|
791
942
|
// Whether to show the presence chips at all. Read at render rather than held in
|
|
@@ -798,33 +949,29 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
798
949
|
var _useStateIdent = useState(
|
|
799
950
|
typeof CollaborationIdentity !== 'undefined' ? CollaborationIdentity.get() : null
|
|
800
951
|
);
|
|
801
|
-
var
|
|
802
|
-
var
|
|
803
|
-
var setCollabIdentity = _useStateIdent2[1];
|
|
952
|
+
var collabIdentity = _useStateIdent[0];
|
|
953
|
+
var setCollabIdentity = _useStateIdent[1];
|
|
804
954
|
// Presence roster: other connected participants, keyed by client_id →
|
|
805
955
|
// { name, colour, current_file }. Fed by the global presence stream; rendered
|
|
806
956
|
// as click-to-jump chips in the status bar.
|
|
807
957
|
var _useStateRoster = useState({});
|
|
808
|
-
var
|
|
809
|
-
var
|
|
810
|
-
var setCollabRoster = _useStateRoster2[1];
|
|
958
|
+
var collabRoster = _useStateRoster[0];
|
|
959
|
+
var setCollabRoster = _useStateRoster[1];
|
|
811
960
|
|
|
812
961
|
// Follow mode (slice 8): the presence client_id of the participant whose file +
|
|
813
962
|
// viewport we're tracking, or null when navigating independently. Toggled from a
|
|
814
963
|
// roster chip. The file-open is driven by the effect below; the scroll-tracking
|
|
815
964
|
// lives in CollaborationService.setFollow().
|
|
816
965
|
var _useStateFollow = useState(null);
|
|
817
|
-
var
|
|
818
|
-
var
|
|
819
|
-
var setFollowedClientId = _useStateFollow2[1];
|
|
966
|
+
var followedClientId = _useStateFollow[0];
|
|
967
|
+
var setFollowedClientId = _useStateFollow[1];
|
|
820
968
|
var recentSavesRef = useRef({});
|
|
821
969
|
var isSavingRef = useRef(false);
|
|
822
970
|
// True once the saved session has finished loading into the panes. Anything
|
|
823
971
|
// that opens a tab on startup must wait for this, or the restore overwrites it.
|
|
824
972
|
var _useStateSR = useState(false);
|
|
825
|
-
var
|
|
826
|
-
var
|
|
827
|
-
var setSessionRestored = _useStateSR2[1];
|
|
973
|
+
var sessionRestored = _useStateSR[0];
|
|
974
|
+
var setSessionRestored = _useStateSR[1];
|
|
828
975
|
var pendingChangelogRef = useRef(false);
|
|
829
976
|
// path -> the file's content as last seen ON DISK (newline-normalised).
|
|
830
977
|
// External-change detection compares disk-to-disk; comparing disk to the
|
|
@@ -853,15 +1000,37 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
853
1000
|
|
|
854
1001
|
// ── Draft backup helpers ─────────────────────────────────────────────────
|
|
855
1002
|
var draftWriteTimerRef = useRef({});
|
|
1003
|
+
var draftWriteWarnedRef = useRef({});
|
|
856
1004
|
var serverOnlineRef = useRef(true);
|
|
857
1005
|
|
|
858
1006
|
var _draftKey = function _draftKey(path) {
|
|
859
1007
|
var base = typeof window.mbeditorBasePath === 'function' ? window.mbeditorBasePath() : '';
|
|
860
1008
|
return 'mbeditor_draft\x00' + base + '\x00' + path;
|
|
861
1009
|
};
|
|
1010
|
+
// localStorage is UTF-16 and most browsers cap it near 5MB, so a draft for a
|
|
1011
|
+
// file past this can never fit: every debounced write failed, warned once, and
|
|
1012
|
+
// then kept stringifying + attempting the whole buffer every 500ms of typing.
|
|
1013
|
+
// Skip it outright; an explicit save is the safety net for huge files.
|
|
1014
|
+
var MAX_DRAFT_CHARS = 1000000;
|
|
862
1015
|
var _saveDraftNow = function _saveDraftNow(path, content) {
|
|
1016
|
+
if (typeof content === 'string' && content.length > MAX_DRAFT_CHARS) {
|
|
1017
|
+
if (!draftWriteWarnedRef.current[path]) {
|
|
1018
|
+
draftWriteWarnedRef.current[path] = true;
|
|
1019
|
+
EditorStore.setStatus('Crash-recovery backup skipped for ' + path + ' (file too large)', 'info');
|
|
1020
|
+
}
|
|
1021
|
+
_clearDraft(path);
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
863
1024
|
var doWrite = function() {
|
|
864
|
-
try {
|
|
1025
|
+
try {
|
|
1026
|
+
localStorage.setItem(_draftKey(path), JSON.stringify({ content: content, ts: Date.now() }));
|
|
1027
|
+
} catch (e) {
|
|
1028
|
+
// Once per path: this fires on every debounced keystroke otherwise.
|
|
1029
|
+
if (!draftWriteWarnedRef.current[path]) {
|
|
1030
|
+
draftWriteWarnedRef.current[path] = true;
|
|
1031
|
+
EditorStore.setStatus('Crash-recovery backup failed for ' + path + ' (storage full)', 'error');
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
865
1034
|
};
|
|
866
1035
|
if (typeof requestIdleCallback !== 'undefined') {
|
|
867
1036
|
requestIdleCallback(doWrite, { timeout: 2000 });
|
|
@@ -884,24 +1053,25 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
884
1053
|
};
|
|
885
1054
|
|
|
886
1055
|
var _useState_dro = useState(null);
|
|
887
|
-
var
|
|
888
|
-
var
|
|
889
|
-
var setDraftRestoreOffer = _useState_dro2[1];
|
|
1056
|
+
var draftRestoreOffer = _useState_dro[0];
|
|
1057
|
+
var setDraftRestoreOffer = _useState_dro[1];
|
|
890
1058
|
|
|
891
1059
|
var _useStateMR = useState(false);
|
|
892
|
-
var
|
|
893
|
-
var
|
|
894
|
-
var setMonacoReady = _useStateMR2[1];
|
|
1060
|
+
var monacoReady = _useStateMR[0];
|
|
1061
|
+
var setMonacoReady = _useStateMR[1];
|
|
895
1062
|
|
|
896
1063
|
var _useStateZen = useState(false);
|
|
897
|
-
var
|
|
898
|
-
var
|
|
899
|
-
|
|
1064
|
+
var zenMode = _useStateZen[0];
|
|
1065
|
+
var setZenMode = _useStateZen[1];
|
|
1066
|
+
|
|
1067
|
+
// Transient, like zen mode — never persisted, always starts closed.
|
|
1068
|
+
var _useStateMGV = useState(false);
|
|
1069
|
+
var showModelGraphView = _useStateMGV[0];
|
|
1070
|
+
var setShowModelGraphView = _useStateMGV[1];
|
|
900
1071
|
|
|
901
1072
|
var _useStateSB = useState(false);
|
|
902
|
-
var
|
|
903
|
-
var
|
|
904
|
-
var setIsSwitchingBranch = _useStateSB2[1];
|
|
1073
|
+
var isSwitchingBranch = _useStateSB[0];
|
|
1074
|
+
var setIsSwitchingBranch = _useStateSB[1];
|
|
905
1075
|
|
|
906
1076
|
var clamp = function clamp(value, min, max) {
|
|
907
1077
|
return Math.min(max, Math.max(min, value));
|
|
@@ -955,6 +1125,48 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
955
1125
|
});
|
|
956
1126
|
};
|
|
957
1127
|
|
|
1128
|
+
// Finds the children array of parentPath within tree ('' means the root).
|
|
1129
|
+
var findTreeChildren = function findTreeChildren(tree, parentPath) {
|
|
1130
|
+
if (!parentPath) return tree;
|
|
1131
|
+
for (var i = 0; i < tree.length; i++) {
|
|
1132
|
+
var item = tree[i];
|
|
1133
|
+
if (item.path === parentPath && item.type === 'folder') return item.children || [];
|
|
1134
|
+
if (item.type === 'folder' && item.children) {
|
|
1135
|
+
var found = findTreeChildren(item.children, parentPath);
|
|
1136
|
+
if (found) return found;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
return null;
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
// Row to reveal after a delete: the next surviving sibling in display
|
|
1143
|
+
// order, else the previous one, else the parent directory. Sorted the same
|
|
1144
|
+
// way FileTree sorts (folders first, then name) so it matches what the
|
|
1145
|
+
// user actually sees.
|
|
1146
|
+
var computeDeleteRevealTarget = function computeDeleteRevealTarget(tree, deletedPaths) {
|
|
1147
|
+
var anchor = deletedPaths[0];
|
|
1148
|
+
var parent = parentDir(anchor);
|
|
1149
|
+
var siblings = findTreeChildren(tree, parent);
|
|
1150
|
+
if (siblings) {
|
|
1151
|
+
var sorted = siblings.concat().sort(function (a, b) {
|
|
1152
|
+
if (a.type !== b.type) return a.type === 'folder' ? -1 : 1;
|
|
1153
|
+
return a.name.localeCompare(b.name);
|
|
1154
|
+
});
|
|
1155
|
+
var idx = sorted.findIndex(function (n) { return n.path === anchor; });
|
|
1156
|
+
if (idx >= 0) {
|
|
1157
|
+
var deletedSet = new Set(deletedPaths);
|
|
1158
|
+
var j, k;
|
|
1159
|
+
for (j = idx + 1; j < sorted.length; j++) {
|
|
1160
|
+
if (!deletedSet.has(sorted[j].path)) return sorted[j].path;
|
|
1161
|
+
}
|
|
1162
|
+
for (k = idx - 1; k >= 0; k--) {
|
|
1163
|
+
if (!deletedSet.has(sorted[k].path)) return sorted[k].path;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
return parent || null;
|
|
1168
|
+
};
|
|
1169
|
+
|
|
958
1170
|
// Every structural mutation (create, delete, rename, import) funnels through
|
|
959
1171
|
// here, so this is where the project-search cache has to be dropped. Saves
|
|
960
1172
|
// invalidated it at their own call sites, which is why editing a file
|
|
@@ -1085,6 +1297,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1085
1297
|
EditorStore.setStatus('Linting...', 'info');
|
|
1086
1298
|
}
|
|
1087
1299
|
|
|
1300
|
+
var startedAt = Date.now();
|
|
1301
|
+
|
|
1088
1302
|
// ruby-lsp answers diagnostics for Ruby files when it's available: same
|
|
1089
1303
|
// markers, plus Prism syntax errors, without a per-keystroke rubocop boot.
|
|
1090
1304
|
// Anything short of a usable answer falls through to the HTTP lint for
|
|
@@ -1107,6 +1321,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1107
1321
|
|
|
1108
1322
|
return lintRequest.then(function (res) {
|
|
1109
1323
|
var nextMarkers = res.markers || [];
|
|
1324
|
+
var audit = window.MbeditorAudit;
|
|
1325
|
+
if (audit) audit.rec(audit.EV.LINT, _auditExt(tab.path), Date.now() - startedAt, nextMarkers.length);
|
|
1110
1326
|
applyMarkersForTab(tab.id, nextMarkers);
|
|
1111
1327
|
|
|
1112
1328
|
if (options.showStatus) {
|
|
@@ -1116,6 +1332,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1116
1332
|
|
|
1117
1333
|
return res;
|
|
1118
1334
|
})["catch"](function (err) {
|
|
1335
|
+
_recErr('LINT');
|
|
1119
1336
|
if (options.showStatus) {
|
|
1120
1337
|
EditorStore.setStatus('Lint failed: ' + err.message, 'error');
|
|
1121
1338
|
}
|
|
@@ -1191,9 +1408,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1191
1408
|
Promise.all([FileService.getWorkspace()["catch"](function () {
|
|
1192
1409
|
return null;
|
|
1193
1410
|
}), refreshProjectTree()]).then(function (_ref) {
|
|
1194
|
-
var
|
|
1195
|
-
|
|
1196
|
-
var workspace = _ref2[0];
|
|
1411
|
+
var workspace = _ref[0];
|
|
1197
1412
|
|
|
1198
1413
|
if (workspace && workspace.rootName) {
|
|
1199
1414
|
setProjectRootName(workspace.rootName);
|
|
@@ -1214,6 +1429,9 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1214
1429
|
if (workspace && typeof workspace.rubyLspAvailable === 'boolean') {
|
|
1215
1430
|
window.MBEDITOR_RUBY_LSP_AVAILABLE = workspace.rubyLspAvailable;
|
|
1216
1431
|
}
|
|
1432
|
+
if (workspace && workspace.searchBackend) {
|
|
1433
|
+
window.MBEDITOR_SEARCH_BACKEND = workspace.searchBackend;
|
|
1434
|
+
}
|
|
1217
1435
|
if (workspace && typeof workspace.gitAvailable === 'boolean') {
|
|
1218
1436
|
setGitAvailable(workspace.gitAvailable);
|
|
1219
1437
|
}
|
|
@@ -1231,11 +1449,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1231
1449
|
// Helper: load tab content for a set of panes and restore them into EditorStore
|
|
1232
1450
|
function loadPaneState(panesToLoad, focusedPaneId) {
|
|
1233
1451
|
if (!panesToLoad || panesToLoad.length === 0) return Promise.resolve();
|
|
1452
|
+
// Settings is a modal now; a state saved when it was a tab must not
|
|
1453
|
+
// restore one.
|
|
1454
|
+
panesToLoad = panesToLoad.map(function (p) {
|
|
1455
|
+
return _extends({}, p, {
|
|
1456
|
+
tabs: p.tabs.filter(function (t) { return !t.isSettings && t.path !== '__settings__'; })
|
|
1457
|
+
});
|
|
1458
|
+
});
|
|
1234
1459
|
var allTabs = panesToLoad.flatMap(function (p) { return p.tabs; });
|
|
1235
1460
|
return Promise.all(allTabs.map(function (t) {
|
|
1236
|
-
if (t.isSettings || t.path === '__settings__') {
|
|
1237
|
-
return Promise.resolve({ content: '' });
|
|
1238
|
-
}
|
|
1239
1461
|
if (t.isChangelog || t.path === 'mbeditor://changelog') {
|
|
1240
1462
|
return Promise.resolve({ content: '' });
|
|
1241
1463
|
}
|
|
@@ -1247,9 +1469,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1247
1469
|
if (t.isCombinedDiff || (t.path || '').startsWith('combined-diff://') || (t.path || '').startsWith('diff://')) {
|
|
1248
1470
|
return Promise.resolve({ content: '' });
|
|
1249
1471
|
}
|
|
1250
|
-
if (t.isModelGraph || t.path === 'mbeditor://model-graph') {
|
|
1251
|
-
return Promise.resolve({ content: '' });
|
|
1252
|
-
}
|
|
1253
1472
|
var sourcePath = t.isPreview || /::preview$/.test(t.path || '') ? t.previewFor || (t.path || '').replace(/::preview$/, '') : t.path;
|
|
1254
1473
|
return FileService.getFile(sourcePath, { allowMissing: true }).then(function (data) {
|
|
1255
1474
|
return {
|
|
@@ -1272,8 +1491,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1272
1491
|
var tabs = [];
|
|
1273
1492
|
p.tabs.forEach(function (t) {
|
|
1274
1493
|
var res = results[resIdx++];
|
|
1275
|
-
var isPlainFile = t.path && !t.isDiff && !t.isCombinedDiff &&
|
|
1276
|
-
!t.isChangelog && !t.isPreview &&
|
|
1494
|
+
var isPlainFile = t.path && !t.isDiff && !t.isCombinedDiff &&
|
|
1495
|
+
!t.isChangelog && !t.isPreview &&
|
|
1277
1496
|
!/^(diff|combined-diff):\/\//.test(t.path) && !/::preview$/.test(t.path);
|
|
1278
1497
|
if (isPlainFile) {
|
|
1279
1498
|
if (seenPaths[t.path]) return;
|
|
@@ -1283,9 +1502,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1283
1502
|
content: res.content,
|
|
1284
1503
|
externalContentVersion: (t.externalContentVersion || 0) + 1
|
|
1285
1504
|
},
|
|
1286
|
-
// A state saved before this tab type existed carries the path but
|
|
1287
|
-
// not the flag, and would restore as a missing file.
|
|
1288
|
-
t.path === 'mbeditor://model-graph' ? { isModelGraph: true } : {},
|
|
1289
1505
|
res._isDiffResult ? { diffOriginal: res.diffOriginal, diffModified: res.diffModified } : {},
|
|
1290
1506
|
typeof res.fileNotFound === 'boolean' ? { fileNotFound: res.fileNotFound, dirty: res.fileNotFound ? false : t.dirty } : {},
|
|
1291
1507
|
res.image === true ? { isImage: true } : {}));
|
|
@@ -1397,10 +1613,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1397
1613
|
return {
|
|
1398
1614
|
id: p.id,
|
|
1399
1615
|
activeTabId: p.activeTabId,
|
|
1400
|
-
tabs: p.tabs.filter(function (t) { return !t.isCombinedDiff && !t.
|
|
1616
|
+
tabs: p.tabs.filter(function (t) { return !t.isCombinedDiff && !t.isUntitled; }).map(function (t) {
|
|
1401
1617
|
return {
|
|
1402
1618
|
id: t.id, path: t.path, name: t.name, dirty: t.dirty, viewState: t.viewState,
|
|
1403
|
-
|
|
1619
|
+
isPreview: !!t.isPreview, previewFor: t.previewFor || null,
|
|
1404
1620
|
isDiff: !!t.isDiff, diffBaseSha: t.diffBaseSha || null, diffHeadSha: t.diffHeadSha || null,
|
|
1405
1621
|
repoPath: t.repoPath || null, isChangelog: !!t.isChangelog
|
|
1406
1622
|
};
|
|
@@ -1483,6 +1699,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1483
1699
|
if (e.key === 'Escape') {
|
|
1484
1700
|
setContextMenu(null);
|
|
1485
1701
|
setShowHelp(false);
|
|
1702
|
+
setCollabOverflowOpen(false);
|
|
1486
1703
|
}
|
|
1487
1704
|
};
|
|
1488
1705
|
|
|
@@ -1516,6 +1733,9 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1516
1733
|
var reservedRight = EDITOR_MIN_WIDTH + (showGitPanelRef.current ? gitPanelWidthRef.current : 0);
|
|
1517
1734
|
var maxSidebarWidth = Math.min(SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_MIN_WIDTH, rect.width - reservedRight));
|
|
1518
1735
|
var nextWidth = clientX - rect.left - SIDEBAR_COLLAPSED_WIDTH;
|
|
1736
|
+
// Remembered for mouseup: dragged well under the minimum, the
|
|
1737
|
+
// explorer snaps shut instead of pinning at SIDEBAR_MIN_WIDTH.
|
|
1738
|
+
s.snapClose = nextWidth < SIDEBAR_MIN_WIDTH - 80;
|
|
1519
1739
|
setSidebarWidth(clamp(nextWidth, SIDEBAR_MIN_WIDTH, maxSidebarWidth));
|
|
1520
1740
|
}
|
|
1521
1741
|
|
|
@@ -1533,11 +1753,26 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1533
1753
|
var nextHeight = Math.max(60, Math.min(400, s.startHeight + delta));
|
|
1534
1754
|
setOpenEditorsHeight(nextHeight);
|
|
1535
1755
|
}
|
|
1756
|
+
|
|
1757
|
+
// Bottom drawers. Delta-based and inverted: dragging the gutter up
|
|
1758
|
+
// grows the drawer. Independent of where the drawer is anchored and
|
|
1759
|
+
// survives a zero/unknown viewport height.
|
|
1760
|
+
if (s.mode === 'log' || s.mode === 'problems') {
|
|
1761
|
+
var vh = window.innerHeight || document.documentElement.clientHeight || 0;
|
|
1762
|
+
var maxH = vh > 0 ? Math.round(vh * 0.85) : Infinity;
|
|
1763
|
+
s.lastHeight = Math.min(maxH, Math.max(DRAWER_MIN_HEIGHT, s.startHeight + (s.startY - clientY)));
|
|
1764
|
+
(s.mode === 'log' ? setLogHeight : setProblemsHeight)(s.lastHeight);
|
|
1765
|
+
}
|
|
1536
1766
|
});
|
|
1537
1767
|
};
|
|
1538
1768
|
|
|
1539
1769
|
var handleMouseUp = function handleMouseUp() {
|
|
1540
1770
|
if (!resizeSessionRef.current) return;
|
|
1771
|
+
if (resizeSessionRef.current.snapClose) setSidebarCollapsed(true);
|
|
1772
|
+
var s = resizeSessionRef.current;
|
|
1773
|
+
if (s.storeKey && s.lastHeight) {
|
|
1774
|
+
try { window.localStorage.setItem(s.storeKey, String(s.lastHeight)); } catch (err) {}
|
|
1775
|
+
}
|
|
1541
1776
|
|
|
1542
1777
|
if (resizeRafRef.current) {
|
|
1543
1778
|
cancelAnimationFrame(resizeRafRef.current);
|
|
@@ -1850,7 +2085,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1850
2085
|
function handleFileSaved(data) {
|
|
1851
2086
|
var path = data && data.path;
|
|
1852
2087
|
if (!path) return;
|
|
1853
|
-
|
|
2088
|
+
// Only an attached (CRDT-bound) tab has the peer's content. A room that is
|
|
2089
|
+
// still deferring, or degraded to local, holds its own buffer — a peer save
|
|
2090
|
+
// is an external change for it, handled by the files_changed path.
|
|
2091
|
+
if (typeof CollaborationService === 'undefined' || !CollaborationService.isAttached(path)) return;
|
|
1854
2092
|
|
|
1855
2093
|
var st = EditorStore.getState();
|
|
1856
2094
|
var changed = false;
|
|
@@ -1911,7 +2149,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1911
2149
|
!path.startsWith('combined-diff://') &&
|
|
1912
2150
|
!path.startsWith('untitled://') &&
|
|
1913
2151
|
!pt.tab.isCombinedDiff &&
|
|
1914
|
-
!pt.tab.isSettings &&
|
|
1915
2152
|
!pt.tab.isImage &&
|
|
1916
2153
|
!pt.tab.isDiff &&
|
|
1917
2154
|
typeof pt.tab.content === 'string';
|
|
@@ -2018,8 +2255,18 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2018
2255
|
useEffect(function () {
|
|
2019
2256
|
var intervalId = setInterval(function () {
|
|
2020
2257
|
if (document.hidden) return;
|
|
2258
|
+
var startedAt = Date.now();
|
|
2021
2259
|
FileService.getTree({ background: true }).then(function (data) {
|
|
2022
|
-
|
|
2260
|
+
var next = data || [];
|
|
2261
|
+
var audit = window.MbeditorAudit;
|
|
2262
|
+
// Stringified once and reused by the updater, so instrumenting the poll
|
|
2263
|
+
// costs no extra pass over the tree.
|
|
2264
|
+
var signature = audit ? JSON.stringify(next) : null;
|
|
2265
|
+
setTreeData(_treeUpdater(next, signature));
|
|
2266
|
+
if (!audit) return;
|
|
2267
|
+
var changed = signature !== _lastPolledTree;
|
|
2268
|
+
_lastPolledTree = signature;
|
|
2269
|
+
audit.rec(audit.EV.TREE_POLL, Date.now() - startedAt, changed ? 1 : 0);
|
|
2023
2270
|
}).catch(function () {}); // silently ignore auto-refresh errors
|
|
2024
2271
|
}, 10000);
|
|
2025
2272
|
return function () { clearInterval(intervalId); };
|
|
@@ -2086,6 +2333,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2086
2333
|
}, [monacoReady]);
|
|
2087
2334
|
|
|
2088
2335
|
var handleSelectFile = function handleSelectFile(path, name, line, col, endCol) {
|
|
2336
|
+
// The one place every reachable "open a file" action funnels through while
|
|
2337
|
+
// the model graph view is up (quick open is the only such surface left —
|
|
2338
|
+
// explorer, search, rails and git panel are all hidden with it).
|
|
2339
|
+
setShowModelGraphView(false);
|
|
2089
2340
|
TabManager.openTab(path, name, line, null, false, col, endCol);
|
|
2090
2341
|
handleNodeSelect({ path: path, name: name || path.split('/').pop(), type: 'file' });
|
|
2091
2342
|
setQuickOpen(false);
|
|
@@ -2141,6 +2392,17 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2141
2392
|
});
|
|
2142
2393
|
};
|
|
2143
2394
|
|
|
2395
|
+
// Drop any pending-reload prompt whose tab is no longer open in any pane.
|
|
2396
|
+
var _prunePendingReloads = function _prunePendingReloads() {
|
|
2397
|
+
EditorStore.setState({
|
|
2398
|
+
pendingReloads: EditorStore.getState().pendingReloads.filter(function (r) {
|
|
2399
|
+
return EditorStore.getState().panes.some(function (p) {
|
|
2400
|
+
return p.tabs.some(function (t) { return t.id === r.tabId; });
|
|
2401
|
+
});
|
|
2402
|
+
})
|
|
2403
|
+
});
|
|
2404
|
+
};
|
|
2405
|
+
|
|
2144
2406
|
var requestCloseTab = function requestCloseTab(paneId, id) {
|
|
2145
2407
|
var pane = state.panes.find(function (p) {
|
|
2146
2408
|
return p.id === paneId;
|
|
@@ -2153,13 +2415,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2153
2415
|
setClosingTabId(id);
|
|
2154
2416
|
} else {
|
|
2155
2417
|
TabManager.closeTab(paneId, id);
|
|
2156
|
-
|
|
2157
|
-
pendingReloads: EditorStore.getState().pendingReloads.filter(function (r) {
|
|
2158
|
-
return EditorStore.getState().panes.some(function (p) {
|
|
2159
|
-
return p.tabs.some(function (t) { return t.id === r.tabId; });
|
|
2160
|
-
});
|
|
2161
|
-
})
|
|
2162
|
-
});
|
|
2418
|
+
_prunePendingReloads();
|
|
2163
2419
|
forgetClosedTabs();
|
|
2164
2420
|
}
|
|
2165
2421
|
};
|
|
@@ -2178,6 +2434,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2178
2434
|
}
|
|
2179
2435
|
|
|
2180
2436
|
if (save) {
|
|
2437
|
+
tab = _freshTab(closingPaneId, tab);
|
|
2181
2438
|
if (tab.isUntitled) {
|
|
2182
2439
|
// Save-as converts the scratch tab to a real one (closing the scratch
|
|
2183
2440
|
// tab in the process); a cancelled prompt keeps the tab open.
|
|
@@ -2191,49 +2448,20 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2191
2448
|
});
|
|
2192
2449
|
return;
|
|
2193
2450
|
}
|
|
2194
|
-
|
|
2195
|
-
return _extends({}, prev, { save: true });
|
|
2196
|
-
});
|
|
2197
|
-
EditorStore.setStatus("Saving " + tab.name + "...", "info");
|
|
2198
|
-
isSavingRef.current = true;
|
|
2199
|
-
FileService.saveFile(tab.path, tab.content).then(function () {
|
|
2200
|
-
noteLocalSave(tab.path, tab.content);
|
|
2451
|
+
_runSave(tab, function () {
|
|
2201
2452
|
EditorStore.setStatus("Saved", "success");
|
|
2202
2453
|
SearchService.invalidate();
|
|
2203
2454
|
GitService.fetchStatusLite({ background: true });
|
|
2204
|
-
// Reset the AVI clean baseline so undo past this save point shows dirty correctly.
|
|
2205
|
-
var _closeEntry = window.__mbeditorModels && window.__mbeditorModels[tab.path];
|
|
2206
|
-
if (_closeEntry && _closeEntry.model && !_closeEntry.model.isDisposed()) {
|
|
2207
|
-
_closeEntry.cleanVersionId = _closeEntry.model.getAlternativeVersionId();
|
|
2208
|
-
}
|
|
2209
2455
|
TabManager.closeTab(closingPaneId, tab.id);
|
|
2210
|
-
|
|
2211
|
-
pendingReloads: EditorStore.getState().pendingReloads.filter(function (r) {
|
|
2212
|
-
return EditorStore.getState().panes.some(function (p) {
|
|
2213
|
-
return p.tabs.some(function (t) { return t.id === r.tabId; });
|
|
2214
|
-
});
|
|
2215
|
-
})
|
|
2216
|
-
});
|
|
2456
|
+
_prunePendingReloads();
|
|
2217
2457
|
forgetClosedTabs();
|
|
2218
|
-
}
|
|
2219
|
-
EditorStore.setStatus("Save failed: " + err.message, "error");
|
|
2220
|
-
})["finally"](function () {
|
|
2221
|
-
isSavingRef.current = false;
|
|
2222
|
-
setLoading(function (prev) {
|
|
2223
|
-
return _extends({}, prev, { save: false });
|
|
2224
|
-
});
|
|
2458
|
+
}, function () {
|
|
2225
2459
|
setClosingTabId(null);
|
|
2226
2460
|
setClosingPaneId(null);
|
|
2227
2461
|
});
|
|
2228
2462
|
} else {
|
|
2229
2463
|
TabManager.closeTab(closingPaneId, tab.id);
|
|
2230
|
-
|
|
2231
|
-
pendingReloads: EditorStore.getState().pendingReloads.filter(function (r) {
|
|
2232
|
-
return EditorStore.getState().panes.some(function (p) {
|
|
2233
|
-
return p.tabs.some(function (t) { return t.id === r.tabId; });
|
|
2234
|
-
});
|
|
2235
|
-
})
|
|
2236
|
-
});
|
|
2464
|
+
_prunePendingReloads();
|
|
2237
2465
|
forgetClosedTabs();
|
|
2238
2466
|
setClosingTabId(null);
|
|
2239
2467
|
setClosingPaneId(null);
|
|
@@ -2308,7 +2536,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2308
2536
|
var input = window.prompt('Save as (path relative to workspace root):', tab.name + '.txt');
|
|
2309
2537
|
if (!input || !input.trim()) return Promise.reject({ cancelled: true });
|
|
2310
2538
|
var newPath = input.trim().replace(/^\/+/, '');
|
|
2539
|
+
var startedAt = Date.now();
|
|
2311
2540
|
return FileService.saveFile(newPath, tab.content).then(function () {
|
|
2541
|
+
var audit = window.MbeditorAudit;
|
|
2542
|
+
if (audit) audit.rec(audit.EV.SAVE, _auditExt(newPath), tab.content.length, Date.now() - startedAt);
|
|
2312
2543
|
noteLocalSave(newPath, tab.content);
|
|
2313
2544
|
SearchService.invalidate();
|
|
2314
2545
|
GitService.fetchStatusLite({ background: true });
|
|
@@ -2333,14 +2564,13 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2333
2564
|
return {
|
|
2334
2565
|
id: p.id,
|
|
2335
2566
|
activeTabId: p.activeTabId,
|
|
2336
|
-
tabs: p.tabs.filter(function(t) { return !t.isCombinedDiff && !t.
|
|
2567
|
+
tabs: p.tabs.filter(function(t) { return !t.isCombinedDiff && !t.isUntitled; }).map(function (t) {
|
|
2337
2568
|
return {
|
|
2338
2569
|
id: t.id,
|
|
2339
2570
|
path: t.path,
|
|
2340
2571
|
name: t.name,
|
|
2341
2572
|
dirty: t.dirty,
|
|
2342
2573
|
viewState: t.viewState,
|
|
2343
|
-
isSettings: !!t.isSettings,
|
|
2344
2574
|
isChangelog: !!t.isChangelog,
|
|
2345
2575
|
isPreview: !!t.isPreview,
|
|
2346
2576
|
previewFor: t.previewFor || null,
|
|
@@ -2367,7 +2597,11 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2367
2597
|
|
|
2368
2598
|
useEffect(function() {
|
|
2369
2599
|
document.documentElement.setAttribute('data-theme', editorPrefs.theme || 'vs-dark');
|
|
2370
|
-
|
|
2600
|
+
// Glass is a layer over the active theme, not a theme, so it rides on its
|
|
2601
|
+
// own attribute and glass.css derives every colour from the theme tokens.
|
|
2602
|
+
if (editorPrefs.glass) document.documentElement.dataset.glass = '1';
|
|
2603
|
+
else delete document.documentElement.dataset.glass;
|
|
2604
|
+
}, [editorPrefs.theme, editorPrefs.glass]);
|
|
2371
2605
|
|
|
2372
2606
|
useEffect(function() {
|
|
2373
2607
|
EditorStore.setState({ editorPrefs: editorPrefs });
|
|
@@ -2491,7 +2725,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2491
2725
|
});
|
|
2492
2726
|
tabs.forEach(function(t) {
|
|
2493
2727
|
if (Object.keys(deps).length >= RAILS_MAX_RESOURCES) return;
|
|
2494
|
-
if (!t.path || t.path
|
|
2728
|
+
if (!t.path || t.path.startsWith('mbeditor://')) return;
|
|
2495
2729
|
var label = resourceLabelFromPath(t.path);
|
|
2496
2730
|
if (label && !deps[label]) deps[label] = t.path;
|
|
2497
2731
|
});
|
|
@@ -2504,7 +2738,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2504
2738
|
var all = {};
|
|
2505
2739
|
state.panes.forEach(function(p) {
|
|
2506
2740
|
p.tabs.forEach(function(t) {
|
|
2507
|
-
if (!t.path || t.path
|
|
2741
|
+
if (!t.path || t.path.startsWith('mbeditor://')) return;
|
|
2508
2742
|
var label = resourceLabelFromPath(t.path);
|
|
2509
2743
|
if (label) all[label] = true;
|
|
2510
2744
|
});
|
|
@@ -2564,14 +2798,14 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2564
2798
|
|
|
2565
2799
|
// ── Collaboration presence (slice 7) ──────────────────────────────────────
|
|
2566
2800
|
// Only real, openable files belong in presence — virtual tabs (diffs, previews,
|
|
2567
|
-
//
|
|
2801
|
+
// changelog) are reported as "no file" so a peer's chip stays blank
|
|
2568
2802
|
// rather than pointing at something click-to-jump can't open.
|
|
2569
2803
|
var _presenceFileFor = function (tab) {
|
|
2570
2804
|
if (!tab || !tab.path) return null;
|
|
2571
2805
|
var p = tab.path;
|
|
2572
|
-
if (tab.isDiff || tab.isCombinedDiff || tab.isCommitGraph || tab.isPreview || tab.
|
|
2806
|
+
if (tab.isDiff || tab.isCombinedDiff || tab.isCommitGraph || tab.isPreview || tab.isChangelog) return null;
|
|
2573
2807
|
if (p.indexOf('diff://') === 0 || p.indexOf('combined-diff://') === 0 || p.indexOf('mbeditor://') === 0) return null;
|
|
2574
|
-
if (p.indexOf('::preview') !== -1
|
|
2808
|
+
if (p.indexOf('::preview') !== -1) return null;
|
|
2575
2809
|
return p;
|
|
2576
2810
|
};
|
|
2577
2811
|
var presenceFile = _presenceFileFor(activeTab);
|
|
@@ -2777,14 +3011,13 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2777
3011
|
|
|
2778
3012
|
var collabPeerIds = Object.keys(collabRoster);
|
|
2779
3013
|
|
|
2780
|
-
//
|
|
2781
|
-
//
|
|
2782
|
-
//
|
|
2783
|
-
//
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
var
|
|
2787
|
-
var collabPeerLabels = !toolbarIconOnly && collabPeerIds.length <= COLLAB_LABEL_LIMIT;
|
|
3014
|
+
// One button per peer stopped working past ~4 people: the titlebar cluster
|
|
3015
|
+
// does not shrink or wrap, so it squeezed the search pill to its floor and
|
|
3016
|
+
// pushed Help / Install off the right edge. Fixed-width stack instead: up to
|
|
3017
|
+
// three overlapping avatars, the rest collapse into one "+N" with a popover.
|
|
3018
|
+
var COLLAB_AVATAR_LIMIT = 3;
|
|
3019
|
+
var collabAvatarIds = collabPeerIds.slice(0, COLLAB_AVATAR_LIMIT);
|
|
3020
|
+
var collabOverflowIds = collabPeerIds.slice(COLLAB_AVATAR_LIMIT);
|
|
2788
3021
|
|
|
2789
3022
|
// Colour is minted from a hash before any peer is known, so it has to be
|
|
2790
3023
|
// reconciled against the roster once one exists. Runs on every roster change;
|
|
@@ -2801,9 +3034,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2801
3034
|
// chips sit against the right edge of the titlebar and a left-anchored card
|
|
2802
3035
|
// would run off screen.
|
|
2803
3036
|
var _useStateHover = useState(null);
|
|
2804
|
-
var
|
|
2805
|
-
var
|
|
2806
|
-
var setCollabHover = _useStateHover2[1];
|
|
3037
|
+
var collabHover = _useStateHover[0];
|
|
3038
|
+
var setCollabHover = _useStateHover[1];
|
|
2807
3039
|
|
|
2808
3040
|
var openCollabHover = function (cid, e) {
|
|
2809
3041
|
var r = e.currentTarget.getBoundingClientRect();
|
|
@@ -2813,8 +3045,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2813
3045
|
// Latency and last-seen only need to tick while the card is actually on screen,
|
|
2814
3046
|
// so the interval lives and dies with it. Idle cost stays zero.
|
|
2815
3047
|
var _useStateHoverTick = useState(0);
|
|
2816
|
-
var
|
|
2817
|
-
var setCollabHoverTick = _useStateHoverTick2[1];
|
|
3048
|
+
var setCollabHoverTick = _useStateHoverTick[1];
|
|
2818
3049
|
useEffect(function () {
|
|
2819
3050
|
if (!collabHover) return;
|
|
2820
3051
|
var id = setInterval(function () { setCollabHoverTick(function (n) { return n + 1; }); }, 1000);
|
|
@@ -2845,17 +3076,57 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2845
3076
|
handleSelectFile(followedFile, followedFile.split('/').pop());
|
|
2846
3077
|
}, [followedClientId, followedFile]);
|
|
2847
3078
|
|
|
3079
|
+
// Shared by the avatar stack and the overflow popover so "which peer, what
|
|
3080
|
+
// file, are we following them" is computed once per id instead of twice.
|
|
3081
|
+
var collabPeerInfo = function (cid) {
|
|
3082
|
+
var peer = collabRoster[cid];
|
|
3083
|
+
var file = peer.current_file;
|
|
3084
|
+
return {
|
|
3085
|
+
name: peer.name || 'Anonymous',
|
|
3086
|
+
colour: peer.colour || '#888888',
|
|
3087
|
+
file: file,
|
|
3088
|
+
following: followedClientId === cid,
|
|
3089
|
+
elsewhere: file !== presenceFile
|
|
3090
|
+
};
|
|
3091
|
+
};
|
|
3092
|
+
|
|
3093
|
+
// The "+N" overflow popover. A plain onMouseLeave that closes immediately
|
|
3094
|
+
// makes it uncrossable — the pointer has to travel off the button and onto
|
|
3095
|
+
// the popover — so the close is delayed and cancelled by either one's
|
|
3096
|
+
// onMouseEnter. Click still toggles it directly, which is what makes it
|
|
3097
|
+
// reachable without a mouse at all.
|
|
3098
|
+
// The anchor, not a boolean: the popover renders at the root next to the
|
|
3099
|
+
// hovercard, because an absolutely-positioned child of the title bar is
|
|
3100
|
+
// painted under the editor whatever its z-index.
|
|
3101
|
+
var _useStateOverflow = useState(null);
|
|
3102
|
+
var collabOverflowAnchor = _useStateOverflow[0];
|
|
3103
|
+
var setCollabOverflowAnchor = _useStateOverflow[1];
|
|
3104
|
+
var collabOverflowOpen = !!collabOverflowAnchor;
|
|
3105
|
+
var anchorFrom = function (el) {
|
|
3106
|
+
var r = el.getBoundingClientRect();
|
|
3107
|
+
return { top: r.bottom + 4, right: window.innerWidth - r.right };
|
|
3108
|
+
};
|
|
3109
|
+
var collabOverflowCloseTimerRef = useRef(null);
|
|
3110
|
+
var cancelOverflowClose = function () {
|
|
3111
|
+
if (collabOverflowCloseTimerRef.current) {
|
|
3112
|
+
clearTimeout(collabOverflowCloseTimerRef.current);
|
|
3113
|
+
collabOverflowCloseTimerRef.current = null;
|
|
3114
|
+
}
|
|
3115
|
+
};
|
|
3116
|
+
var scheduleOverflowClose = function () {
|
|
3117
|
+
cancelOverflowClose();
|
|
3118
|
+
collabOverflowCloseTimerRef.current = setTimeout(function () { setCollabOverflowAnchor(null); }, 250);
|
|
3119
|
+
};
|
|
3120
|
+
|
|
2848
3121
|
// Phase 7: Per-file last-commit info shown in the status bar
|
|
2849
3122
|
var _useState31 = useState(null);
|
|
2850
|
-
var
|
|
2851
|
-
var
|
|
2852
|
-
var setActiveFileCommit = _useState32[1];
|
|
3123
|
+
var activeFileCommit = _useState31[0];
|
|
3124
|
+
var setActiveFileCommit = _useState31[1];
|
|
2853
3125
|
|
|
2854
3126
|
// EOL indicator — tracks current line-ending style of the active file
|
|
2855
3127
|
var _useState31e = useState(null);
|
|
2856
|
-
var
|
|
2857
|
-
var
|
|
2858
|
-
var setActiveEOL = _useState31e2[1];
|
|
3128
|
+
var activeEOL = _useState31e[0];
|
|
3129
|
+
var setActiveEOL = _useState31e[1];
|
|
2859
3130
|
|
|
2860
3131
|
useEffect(function () {
|
|
2861
3132
|
if (!gitAvailable || !activeTab || activeTab.isDiff || activeTab.isCombinedDiff || activeTab.isCommitGraph || !activeTab.path || activeTab.path.indexOf('://') >= 0) {
|
|
@@ -2875,7 +3146,9 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2875
3146
|
});
|
|
2876
3147
|
}, [activeTab ? activeTab.id : null, gitAvailable]);
|
|
2877
3148
|
|
|
2878
|
-
// Update EOL indicator
|
|
3149
|
+
// Update EOL indicator on external loads, not on every content write. The
|
|
3150
|
+
// content effect fires on the 250ms typing flush, and scanning the whole
|
|
3151
|
+
// buffer for '\r' there was pure waste — typing cannot change the line ending.
|
|
2879
3152
|
useEffect(function () {
|
|
2880
3153
|
if (!activeTab || typeof activeTab.content !== 'string' ||
|
|
2881
3154
|
activeTab.isDiff || activeTab.isCombinedDiff || activeTab.isCommitGraph || activeTab.isPreview) {
|
|
@@ -2889,7 +3162,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2889
3162
|
} else {
|
|
2890
3163
|
setActiveEOL('LF');
|
|
2891
3164
|
}
|
|
2892
|
-
}, [activeTab ? activeTab.id : null, activeTab ? activeTab.
|
|
3165
|
+
}, [activeTab ? activeTab.id : null, activeTab ? activeTab.externalContentVersion : null]);
|
|
2893
3166
|
|
|
2894
3167
|
useEffect(function () {
|
|
2895
3168
|
if (!activeTab || typeof activeTab.content !== 'string') return;
|
|
@@ -2966,15 +3239,35 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2966
3239
|
// by a formatter problem).
|
|
2967
3240
|
var _formatContentForSave = function _formatContentForSave(tab) {
|
|
2968
3241
|
var isRubyLang = /\.(rb|rake|gemspec)$/.test(tab.path) || /(?:^|\/)(Rakefile|Gemfile)$/.test(tab.path);
|
|
3242
|
+
var startedAt = Date.now();
|
|
2969
3243
|
if (isRubyLang) {
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
3244
|
+
// Same route as the Format button: formatRubySource tries ruby-lsp and
|
|
3245
|
+
// falls back to /format's `rubocop -A`. Calling /format directly here
|
|
3246
|
+
// meant saving a Ruby file could produce different bytes from formatting
|
|
3247
|
+
// the same file by hand — the two-formatters-disagree trap this file
|
|
3248
|
+
// already documents for JS — and gating on RuboCop alone made
|
|
3249
|
+
// format-on-save silently do nothing when only ruby-lsp was present.
|
|
3250
|
+
if (!rubocopAvailable && !window.MBEDITOR_RUBY_LSP_AVAILABLE) return Promise.resolve(null);
|
|
3251
|
+
return formatRubySource(tab.path, tab.content)
|
|
3252
|
+
.then(function (res) {
|
|
3253
|
+
var out = (res && res.content) || null;
|
|
3254
|
+
_recFormat(tab.path, startedAt, out);
|
|
3255
|
+
return out;
|
|
3256
|
+
})
|
|
3257
|
+
["catch"](function () {
|
|
3258
|
+
_recErr('FORMAT');
|
|
3259
|
+
return null;
|
|
3260
|
+
});
|
|
2974
3261
|
}
|
|
2975
3262
|
var parserName = prettierParserFor(tab.path);
|
|
2976
3263
|
if (!parserName) return Promise.resolve(null);
|
|
2977
|
-
return runPrettier(tab.content, editorPrefs, parserName)
|
|
3264
|
+
return runPrettier(tab.content, editorPrefs, parserName).then(function (out) {
|
|
3265
|
+
_recFormat(tab.path, startedAt, out);
|
|
3266
|
+
return out;
|
|
3267
|
+
})["catch"](function () {
|
|
3268
|
+
_recErr('FORMAT');
|
|
3269
|
+
return null;
|
|
3270
|
+
});
|
|
2978
3271
|
};
|
|
2979
3272
|
|
|
2980
3273
|
// Re-read a tab from the store after flushing TabManager's throttled content
|
|
@@ -3013,6 +3306,40 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3013
3306
|
_doSave(paneId, tab);
|
|
3014
3307
|
};
|
|
3015
3308
|
|
|
3309
|
+
// Shared save-execution flow for a known-on-disk (non-untitled) tab: the
|
|
3310
|
+
// saving status/loading flag, the FileService write, the AVI clean-baseline
|
|
3311
|
+
// reset, and save-failed/cleanup handling. `onSuccess` runs whatever the
|
|
3312
|
+
// caller does after a successful write (before the finally cleanup);
|
|
3313
|
+
// `onFinally` runs any extra caller-specific cleanup after the standard one.
|
|
3314
|
+
var _runSave = function _runSave(tab, onSuccess, onFinally) {
|
|
3315
|
+
setLoading(function (prev) {
|
|
3316
|
+
return _extends({}, prev, { save: true });
|
|
3317
|
+
});
|
|
3318
|
+
EditorStore.setStatus("Saving " + tab.name + "...", "info");
|
|
3319
|
+
isSavingRef.current = true;
|
|
3320
|
+
var startedAt = Date.now();
|
|
3321
|
+
return FileService.saveFile(tab.path, tab.content).then(function () {
|
|
3322
|
+
var audit = window.MbeditorAudit;
|
|
3323
|
+
if (audit) audit.rec(audit.EV.SAVE, _auditExt(tab.path), tab.content.length, Date.now() - startedAt);
|
|
3324
|
+
noteLocalSave(tab.path, tab.content);
|
|
3325
|
+
// Reset the AVI clean baseline so undo past this save point shows dirty correctly.
|
|
3326
|
+
var _modelEntry = window.__mbeditorModels && window.__mbeditorModels[tab.path];
|
|
3327
|
+
if (_modelEntry && _modelEntry.model && !_modelEntry.model.isDisposed()) {
|
|
3328
|
+
_modelEntry.cleanVersionId = _modelEntry.model.getAlternativeVersionId();
|
|
3329
|
+
}
|
|
3330
|
+
onSuccess();
|
|
3331
|
+
})["catch"](function (err) {
|
|
3332
|
+
_recErr('SAVE');
|
|
3333
|
+
EditorStore.setStatus("Save failed: " + err.message, "error");
|
|
3334
|
+
})["finally"](function () {
|
|
3335
|
+
isSavingRef.current = false;
|
|
3336
|
+
setLoading(function (prev) {
|
|
3337
|
+
return _extends({}, prev, { save: false });
|
|
3338
|
+
});
|
|
3339
|
+
if (onFinally) onFinally();
|
|
3340
|
+
});
|
|
3341
|
+
};
|
|
3342
|
+
|
|
3016
3343
|
var _doSave = function _doSave(paneId, tab) {
|
|
3017
3344
|
if (tab.isUntitled) {
|
|
3018
3345
|
saveUntitledTab(paneId, tab)["catch"](function (err) {
|
|
@@ -3021,13 +3348,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3021
3348
|
});
|
|
3022
3349
|
return;
|
|
3023
3350
|
}
|
|
3024
|
-
|
|
3025
|
-
return _extends({}, prev, { save: true });
|
|
3026
|
-
});
|
|
3027
|
-
EditorStore.setStatus("Saving " + tab.name + "...", "info");
|
|
3028
|
-
isSavingRef.current = true;
|
|
3029
|
-
FileService.saveFile(tab.path, tab.content).then(function () {
|
|
3030
|
-
noteLocalSave(tab.path, tab.content);
|
|
3351
|
+
_runSave(tab, function () {
|
|
3031
3352
|
var newPanes = EditorStore.getState().panes.map(function (p) {
|
|
3032
3353
|
if (p.id === paneId) {
|
|
3033
3354
|
return _extends({}, p, { tabs: p.tabs.map(function (t) {
|
|
@@ -3037,13 +3358,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3037
3358
|
return p;
|
|
3038
3359
|
});
|
|
3039
3360
|
EditorStore.setState({ panes: newPanes });
|
|
3040
|
-
// Reset the AVI clean baseline so undo past this save point shows dirty correctly.
|
|
3041
|
-
var _modelEntry = window.__mbeditorModels && window.__mbeditorModels[tab.path];
|
|
3042
|
-
if (_modelEntry && _modelEntry.model && !_modelEntry.model.isDisposed()) {
|
|
3043
|
-
_modelEntry.cleanVersionId = _modelEntry.model.getAlternativeVersionId();
|
|
3044
|
-
}
|
|
3045
3361
|
// Collab: push a fresh snapshot so the server compacts the buffered deltas.
|
|
3046
|
-
|
|
3362
|
+
// Only for an attached tab — a deferred or degraded room pushing its local
|
|
3363
|
+
// buffer as a snapshot could seed a second copy into what a peer holds.
|
|
3364
|
+
if (typeof CollaborationService !== 'undefined' && CollaborationService.isAttached(tab.path)) {
|
|
3047
3365
|
CollaborationService.pushSnapshot(tab.path);
|
|
3048
3366
|
}
|
|
3049
3367
|
EditorStore.setStatus("Saved", "success");
|
|
@@ -3062,8 +3380,11 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3062
3380
|
// sprockets/react-rails pipeline can't transform (errors Monaco's TS
|
|
3063
3381
|
// worker misses). Save only — never per keystroke.
|
|
3064
3382
|
if (jsSyntaxCheckAvailableRef.current && /\.(js|jsx)$/i.test(tab.path)) {
|
|
3383
|
+
var lintStartedAt = Date.now();
|
|
3065
3384
|
FileService.lintFile(tab.path, tab.content, 'javascript').then(function (res) {
|
|
3066
3385
|
var babelMarkers = (res && res.markers) || [];
|
|
3386
|
+
var audit = window.MbeditorAudit;
|
|
3387
|
+
if (audit) audit.rec(audit.EV.LINT, _auditExt(tab.path), Date.now() - lintStartedAt, babelMarkers.length);
|
|
3067
3388
|
if (babelMarkers.length > 0) {
|
|
3068
3389
|
applyMarkersForTab(tab.id, babelMarkers);
|
|
3069
3390
|
EditorStore.setStatus('Saved — babel: ' + babelMarkers[0].message, 'warning');
|
|
@@ -3073,7 +3394,9 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3073
3394
|
// applyMarkersForTab keeps the previous map when it is already empty.
|
|
3074
3395
|
applyMarkersForTab(tab.id, []);
|
|
3075
3396
|
}
|
|
3076
|
-
})["catch"](function () {
|
|
3397
|
+
})["catch"](function () {
|
|
3398
|
+
_recErr('LINT');
|
|
3399
|
+
});
|
|
3077
3400
|
}
|
|
3078
3401
|
|
|
3079
3402
|
// The server broadcasts files_changed for this very write, and the
|
|
@@ -3084,13 +3407,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3084
3407
|
// file every time does by definition, so the duplicate was the expensive
|
|
3085
3408
|
// one. Without a socket no broadcast arrives, so keep it for that case.
|
|
3086
3409
|
if (!_socketWillBroadcast()) GitService.fetchStatusLite({ background: true });
|
|
3087
|
-
})["catch"](function (err) {
|
|
3088
|
-
EditorStore.setStatus("Save failed: " + err.message, "error");
|
|
3089
|
-
})["finally"](function () {
|
|
3090
|
-
isSavingRef.current = false;
|
|
3091
|
-
return setLoading(function (prev) {
|
|
3092
|
-
return _extends({}, prev, { save: false });
|
|
3093
|
-
});
|
|
3094
3410
|
});
|
|
3095
3411
|
};
|
|
3096
3412
|
|
|
@@ -3179,7 +3495,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3179
3495
|
// disk stayed dirty and the only thing on offer was to hit Save All again.
|
|
3180
3496
|
// Each file is now settled on its own result.
|
|
3181
3497
|
var promises = dirtyTabs.map(function (tab) {
|
|
3182
|
-
|
|
3498
|
+
var startedAt = Date.now();
|
|
3499
|
+
return FileService.saveFile(tab.path, tab.content).then(function (res) {
|
|
3500
|
+
var audit = window.MbeditorAudit;
|
|
3501
|
+
if (audit) audit.rec(audit.EV.SAVE, _auditExt(tab.path), tab.content.length, Date.now() - startedAt);
|
|
3502
|
+
return res;
|
|
3503
|
+
}, function (err) {
|
|
3504
|
+
_recErr('SAVE');
|
|
3505
|
+
throw err;
|
|
3506
|
+
});
|
|
3183
3507
|
});
|
|
3184
3508
|
Promise.allSettled(promises).then(function (results) {
|
|
3185
3509
|
var saved = dirtyTabs.filter(function (tab, i) { return results[i].status === "fulfilled"; });
|
|
@@ -3327,6 +3651,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3327
3651
|
// rather than the button appearing to do nothing.
|
|
3328
3652
|
var formatTabContent = function formatTabContent(tab, prefs) {
|
|
3329
3653
|
prefs = prefs || editorPrefs;
|
|
3654
|
+
var startedAt = Date.now();
|
|
3330
3655
|
if (isRubyPath(tab.path) || tab.path.endsWith('.rake')) {
|
|
3331
3656
|
if (!rubocopAvailable) return Promise.reject(new Error("RuboCop is not available for this workspace."));
|
|
3332
3657
|
// RuboCop's output is taken as-is. Ruby indentation belongs to the
|
|
@@ -3335,13 +3660,18 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3335
3660
|
// the same file. The old code tried the opposite — converting the source
|
|
3336
3661
|
// to tabs *before* handing it over — which RuboCop simply discarded.
|
|
3337
3662
|
return formatRubySource(tab.path, tab.content).then(function (res) {
|
|
3338
|
-
|
|
3663
|
+
var out = (res && res.content) || null;
|
|
3664
|
+
_recFormat(tab.path, startedAt, out);
|
|
3665
|
+
return out;
|
|
3339
3666
|
});
|
|
3340
3667
|
}
|
|
3341
3668
|
|
|
3342
3669
|
var parserName = prettierParserFor(tab.path);
|
|
3343
3670
|
if (!parserName) return Promise.resolve(null);
|
|
3344
|
-
return runPrettier(tab.content, prefs, parserName)
|
|
3671
|
+
return runPrettier(tab.content, prefs, parserName).then(function (out) {
|
|
3672
|
+
_recFormat(tab.path, startedAt, out);
|
|
3673
|
+
return out;
|
|
3674
|
+
});
|
|
3345
3675
|
};
|
|
3346
3676
|
|
|
3347
3677
|
// Markers are per-model and only the mounted editor re-lints, so a document
|
|
@@ -3434,6 +3764,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3434
3764
|
EditorStore.setStatus(formatted === originalContent ? "Already formatted" : "Formatted (Unsaved)", "success");
|
|
3435
3765
|
GitService.fetchStatus();
|
|
3436
3766
|
})["catch"](function (err) {
|
|
3767
|
+
_recErr('FORMAT');
|
|
3437
3768
|
EditorStore.setStatus("Format failed: " + (err && err.message ? err.message : err), "error");
|
|
3438
3769
|
})["finally"](function () {
|
|
3439
3770
|
setLoading(function (prev) { return _extends({}, prev, { format: false }); });
|
|
@@ -3451,7 +3782,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3451
3782
|
var targets = [];
|
|
3452
3783
|
EditorStore.getState().panes.forEach(function (p) {
|
|
3453
3784
|
p.tabs.forEach(function (t) {
|
|
3454
|
-
if (t.path && !t.path.startsWith('mbeditor://') &&
|
|
3785
|
+
if (t.path && !t.path.startsWith('mbeditor://') && typeof t.content === 'string') {
|
|
3455
3786
|
targets.push({ paneId: p.id, tab: t });
|
|
3456
3787
|
}
|
|
3457
3788
|
});
|
|
@@ -3492,14 +3823,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3492
3823
|
});
|
|
3493
3824
|
};
|
|
3494
3825
|
|
|
3495
|
-
var TEST_CACHE_PREFIX = 'mbeditor_test_result_';
|
|
3496
|
-
|
|
3497
|
-
var saveCachedTestResult = function saveCachedTestResult(filePath, result) {
|
|
3498
|
-
try {
|
|
3499
|
-
localStorage.setItem(TEST_CACHE_PREFIX + filePath, JSON.stringify(result));
|
|
3500
|
-
} catch (e) {}
|
|
3501
|
-
};
|
|
3502
|
-
|
|
3503
3826
|
var executeTestRun = function executeTestRun(filePath, line) {
|
|
3504
3827
|
setTestLoading(true);
|
|
3505
3828
|
EditorStore.setStatus(line ? 'Running test at line ' + line + '...' : 'Running tests...', 'info');
|
|
@@ -3608,6 +3931,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3608
3931
|
});
|
|
3609
3932
|
};
|
|
3610
3933
|
|
|
3934
|
+
// Paging is scroll-driven, so a list too short to scroll — few hits, or a
|
|
3935
|
+
// big file group just collapsed — would show "Loading more…" and never load
|
|
3936
|
+
// it. Fill the viewport whenever the list stops being scrollable.
|
|
3937
|
+
useEffect(function () {
|
|
3938
|
+
var el = searchResultsContainerRef.current;
|
|
3939
|
+
if (!searchHasMore || !el) return;
|
|
3940
|
+
if (el.scrollHeight <= el.clientHeight + 200) loadMoreSearchResults();
|
|
3941
|
+
}, [searchHasMore, state.searchResults, searchCollapsedFiles]);
|
|
3942
|
+
|
|
3611
3943
|
var handleSearchChange = function handleSearchChange(e) {
|
|
3612
3944
|
var val = e.target.value;
|
|
3613
3945
|
if (!val) { clearSearch(); return; }
|
|
@@ -3850,10 +4182,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3850
4182
|
if (imported > 0) {
|
|
3851
4183
|
// Expand down to what was just written, so the tree actually shows it —
|
|
3852
4184
|
// importing into a collapsed folder otherwise leaves the explorer looking
|
|
3853
|
-
// untouched. Deliberately does not *select* the folder
|
|
3854
|
-
// selection
|
|
3855
|
-
// destination, and pinning it here would make every later upload default
|
|
3856
|
-
// to this import's folder.
|
|
4185
|
+
// untouched. Deliberately does not *select* the folder, which would move
|
|
4186
|
+
// the selection somewhere the user did not put it.
|
|
3857
4187
|
var landed = parentDir(written[0].path);
|
|
3858
4188
|
var toExpand = {};
|
|
3859
4189
|
var bits = landed ? landed.split('/') : [];
|
|
@@ -3872,8 +4202,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3872
4202
|
// rather than a disaster. Real targets stopPropagation before this runs.
|
|
3873
4203
|
useEffect(function () {
|
|
3874
4204
|
var swallow = function (e) {
|
|
3875
|
-
|
|
3876
|
-
if (Array.prototype.indexOf.call(types, 'Files') === -1) return;
|
|
4205
|
+
if (!FileImport.hasExternalFiles(e.dataTransfer)) return;
|
|
3877
4206
|
e.preventDefault();
|
|
3878
4207
|
// dragover is left alone beyond preventDefault: setting dropEffect here
|
|
3879
4208
|
// would override the 'copy' cursor the tree sets on a valid folder.
|
|
@@ -3901,7 +4230,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3901
4230
|
(entries.length === 1 ? '' : 's') + '...', 'info');
|
|
3902
4231
|
}
|
|
3903
4232
|
|
|
3904
|
-
|
|
4233
|
+
var where = targetFolderPath || 'the workspace root';
|
|
4234
|
+
var noun = entries.length + ' file' + (entries.length === 1 ? '' : 's');
|
|
4235
|
+
var progress = function (percent) {
|
|
4236
|
+
EditorStore.setStatus(percent === null
|
|
4237
|
+
? 'Uploaded ' + noun + ' to ' + where + ', writing them...'
|
|
4238
|
+
: 'Uploading ' + noun + ' to ' + where + '... ' + percent + '%', 'info');
|
|
4239
|
+
};
|
|
4240
|
+
|
|
4241
|
+
return FileService.importFiles(FileImport.buildFormData(entries, targetFolderPath, 'ask'), progress)
|
|
3905
4242
|
.then(function(result) {
|
|
3906
4243
|
if (result.conflicts && result.conflicts.length > 0) {
|
|
3907
4244
|
setImportConflict({ result: result, entries: entries, targetFolderPath: targetFolderPath });
|
|
@@ -3941,32 +4278,81 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3941
4278
|
});
|
|
3942
4279
|
};
|
|
3943
4280
|
|
|
3944
|
-
//
|
|
3945
|
-
//
|
|
3946
|
-
//
|
|
3947
|
-
//
|
|
3948
|
-
//
|
|
4281
|
+
// /raw stays a bare anchor for the single-file case — nothing buffered
|
|
4282
|
+
// client-side. /archive can fail with a JSON error body a plain anchor
|
|
4283
|
+
// navigation can't see, so that path goes through fetch instead.
|
|
4284
|
+
// Right-clicking a node inside a multi-selection acts on the whole selection;
|
|
4285
|
+
// right-clicking outside it acts on that node alone. The standard file-manager
|
|
4286
|
+
// rule, and why FileTree's onContextMenu stopped collapsing the set. Every
|
|
4287
|
+
// menu action that can operate on more than one path asks this.
|
|
4288
|
+
var actsOnSelection = function actsOnSelection(node) {
|
|
4289
|
+
return !!(node && selectedPaths && selectedPaths.size > 1 && selectedPaths.has(node.path));
|
|
4290
|
+
};
|
|
4291
|
+
|
|
3949
4292
|
var handleDownloadFile = function handleDownloadFile(node) {
|
|
3950
|
-
if (!node
|
|
3951
|
-
var
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
4293
|
+
if (!node) return;
|
|
4294
|
+
var isMultiSelect = actsOnSelection(node);
|
|
4295
|
+
if (!isMultiSelect && node.type === 'file') {
|
|
4296
|
+
var link = document.createElement('a');
|
|
4297
|
+
link.href = window.mbeditorBasePath() + '/raw?download=1&path=' + encodeURIComponent(node.path);
|
|
4298
|
+
link.download = node.name;
|
|
4299
|
+
link.rel = 'noopener';
|
|
4300
|
+
document.body.appendChild(link);
|
|
4301
|
+
link.click();
|
|
4302
|
+
document.body.removeChild(link);
|
|
4303
|
+
EditorStore.setStatus('Downloading ' + node.name + '...', 'info');
|
|
4304
|
+
return;
|
|
4305
|
+
}
|
|
4306
|
+
var paths = isMultiSelect ? Array.from(selectedPaths) : [node.path];
|
|
4307
|
+
var url = window.mbeditorBasePath() + '/archive?' + paths.map(function (p) {
|
|
4308
|
+
return 'paths[]=' + encodeURIComponent(p);
|
|
4309
|
+
}).join('&');
|
|
4310
|
+
fetch(url).then(function (resp) {
|
|
4311
|
+
if (!resp.ok) {
|
|
4312
|
+
return resp.json().catch(function () { return {}; }).then(function (body) {
|
|
4313
|
+
var message = (body && body.error) || ('HTTP ' + resp.status);
|
|
4314
|
+
EditorStore.setStatus('Download failed: ' + message, 'error');
|
|
4315
|
+
});
|
|
4316
|
+
}
|
|
4317
|
+
var disposition = resp.headers.get('Content-Disposition');
|
|
4318
|
+
var match = disposition && disposition.match(/filename="([^"]+)"/);
|
|
4319
|
+
var filename = (match && match[1]) || 'archive.tar.gz';
|
|
4320
|
+
return resp.blob().then(function (blob) {
|
|
4321
|
+
var objectUrl = URL.createObjectURL(blob);
|
|
4322
|
+
var archiveLink = document.createElement('a');
|
|
4323
|
+
archiveLink.href = objectUrl;
|
|
4324
|
+
archiveLink.download = filename;
|
|
4325
|
+
archiveLink.rel = 'noopener';
|
|
4326
|
+
document.body.appendChild(archiveLink);
|
|
4327
|
+
archiveLink.click();
|
|
4328
|
+
document.body.removeChild(archiveLink);
|
|
4329
|
+
// Revoking in the same tick can free the blob before the browser has
|
|
4330
|
+
// started reading it, which cancels the download instead of saving it.
|
|
4331
|
+
setTimeout(function () { URL.revokeObjectURL(objectUrl); }, 60000);
|
|
4332
|
+
EditorStore.setStatus('Downloading ' + filename + '...', 'info');
|
|
4333
|
+
});
|
|
4334
|
+
}).catch(function (err) {
|
|
4335
|
+
EditorStore.setStatus('Download failed: ' + (err && err.message || 'network error'), 'error');
|
|
4336
|
+
});
|
|
4337
|
+
};
|
|
4338
|
+
|
|
4339
|
+
var downloadMenuLabel = function downloadMenuLabel(node) {
|
|
4340
|
+
if (!node) return 'Download';
|
|
4341
|
+
if (actsOnSelection(node)) return 'Download ' + selectedPaths.size + ' items';
|
|
4342
|
+
if (node.type === 'folder') return 'Download folder';
|
|
4343
|
+
return 'Download';
|
|
4344
|
+
};
|
|
4345
|
+
|
|
4346
|
+
// Deleting is irreversible, so the item has to say how much it will take —
|
|
4347
|
+
// "Delete" over a 3-item selection would understate it.
|
|
4348
|
+
var deleteMenuLabel = function deleteMenuLabel(node) {
|
|
4349
|
+
return actsOnSelection(node) ? 'Delete ' + selectedPaths.size + ' items' : 'Delete';
|
|
3959
4350
|
};
|
|
3960
4351
|
|
|
3961
4352
|
// A file node means "upload alongside this file", so the dialog opens on its
|
|
3962
4353
|
// parent — right-clicking a file to upload next to it is the common gesture.
|
|
3963
4354
|
var openImportDialog = function openImportDialog(node) {
|
|
3964
|
-
var folder = '';
|
|
3965
|
-
if (node && node.type === 'folder') {
|
|
3966
|
-
folder = node.path;
|
|
3967
|
-
} else if (node && node.path) {
|
|
3968
|
-
folder = node.path.split('/').slice(0, -1).join('/');
|
|
3969
|
-
}
|
|
4355
|
+
var folder = node ? (node.type === 'folder' ? node.path : parentDir(node.path)) : '';
|
|
3970
4356
|
setImportDialog({ initialFolder: folder });
|
|
3971
4357
|
};
|
|
3972
4358
|
|
|
@@ -3977,13 +4363,18 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3977
4363
|
|
|
3978
4364
|
var openContextMenu = function openContextMenu(e, node) {
|
|
3979
4365
|
setContextMenu({ x: e.clientX, y: e.clientY, node: node });
|
|
3980
|
-
handleNodeSelect(node);
|
|
4366
|
+
if (!actsOnSelection(node)) handleNodeSelect(node);
|
|
3981
4367
|
};
|
|
3982
4368
|
|
|
3983
4369
|
var closeContextMenu = function closeContextMenu() {
|
|
3984
4370
|
return setContextMenu(null);
|
|
3985
4371
|
};
|
|
3986
4372
|
|
|
4373
|
+
// Clamp into view whenever the menu opens at a new position.
|
|
4374
|
+
useEffect(function () {
|
|
4375
|
+
if (contextMenu) window.clampMenuIntoView(contextMenuRef.current);
|
|
4376
|
+
}, [contextMenu]);
|
|
4377
|
+
|
|
3987
4378
|
var handleContextMenuAction = function handleContextMenuAction(action) {
|
|
3988
4379
|
var node = contextMenu && contextMenu.node;
|
|
3989
4380
|
closeContextMenu();
|
|
@@ -4042,17 +4433,34 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4042
4433
|
document.body.style.userSelect = 'none';
|
|
4043
4434
|
};
|
|
4044
4435
|
|
|
4436
|
+
var startDrawerResize = function startDrawerResize(mode, startHeight, storeKey) {
|
|
4437
|
+
return function (e) {
|
|
4438
|
+
e.preventDefault();
|
|
4439
|
+
resizeSessionRef.current = { mode: mode, startY: e.clientY, startHeight: startHeight, storeKey: storeKey };
|
|
4440
|
+
setActiveResizeMode(mode);
|
|
4441
|
+
document.body.style.cursor = 'row-resize';
|
|
4442
|
+
document.body.style.userSelect = 'none';
|
|
4443
|
+
};
|
|
4444
|
+
};
|
|
4445
|
+
|
|
4045
4446
|
var handleActivityBarClick = function handleActivityBarClick(tab) {
|
|
4046
4447
|
if (tab === 'settings') {
|
|
4047
|
-
|
|
4448
|
+
setShowModelGraphView(false);
|
|
4449
|
+
setShowSettings(!showSettings);
|
|
4048
4450
|
return;
|
|
4049
4451
|
}
|
|
4050
4452
|
// The model graph is a view, not a panel: it takes over the central area
|
|
4051
|
-
// and needs the width. There is no sidebar half to show
|
|
4453
|
+
// and needs the width. There is no sidebar half to show, so it toggles
|
|
4454
|
+
// instead of behaving like the other sidebar tabs.
|
|
4052
4455
|
if (tab === 'models') {
|
|
4053
|
-
|
|
4456
|
+
setShowModelGraphView(function (prev) {
|
|
4457
|
+
var next = !prev;
|
|
4458
|
+
if (next) loadModelGraph(false);
|
|
4459
|
+
return next;
|
|
4460
|
+
});
|
|
4054
4461
|
return;
|
|
4055
4462
|
}
|
|
4463
|
+
setShowModelGraphView(false);
|
|
4056
4464
|
if (!sidebarCollapsed && activeSidebarTab === tab) {
|
|
4057
4465
|
setSidebarCollapsed(true);
|
|
4058
4466
|
} else {
|
|
@@ -4207,6 +4615,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4207
4615
|
// Optimistically insert the new node so the tree doesn't flash empty while waiting for the server
|
|
4208
4616
|
var optimisticNode = { path: path, name: name, type: type === 'folder' ? 'folder' : 'file', children: type === 'folder' ? [] : undefined };
|
|
4209
4617
|
setTreeData(function (prev) { return insertNodeIntoTree(prev, parentPath, optimisticNode); });
|
|
4618
|
+
setRevealTarget({ path: path });
|
|
4210
4619
|
|
|
4211
4620
|
if (type === 'file') {
|
|
4212
4621
|
setLoading(function (prev) {
|
|
@@ -4337,6 +4746,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4337
4746
|
EditorStore.setStatus('Renamed to: ' + renamedPath, 'success');
|
|
4338
4747
|
return refreshProjectTree().then(function () {
|
|
4339
4748
|
GitService.fetchStatusLite({ background: true });
|
|
4749
|
+
setRevealTarget({ path: renamedPath });
|
|
4340
4750
|
});
|
|
4341
4751
|
})["catch"](function (err) {
|
|
4342
4752
|
var message = err && err.response && err.response.data && err.response.data.error || err.message;
|
|
@@ -4353,10 +4763,12 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4353
4763
|
};
|
|
4354
4764
|
|
|
4355
4765
|
var handleDeletePath = function handleDeletePath(targetNode) {
|
|
4356
|
-
//
|
|
4357
|
-
//
|
|
4766
|
+
// A node from the context menu deletes just that node — unless it is part
|
|
4767
|
+
// of a live multi-selection, in which case the whole selection goes, the
|
|
4768
|
+
// same rule Download follows. With no node (the toolbar button) it is
|
|
4769
|
+
// always the selection, falling back to selectedTreeNode.
|
|
4358
4770
|
var pathsToDelete;
|
|
4359
|
-
if (targetNode
|
|
4771
|
+
if (targetNode && !actsOnSelection(targetNode)) {
|
|
4360
4772
|
pathsToDelete = [targetNode.path];
|
|
4361
4773
|
} else if (selectedPaths && selectedPaths.size > 0) {
|
|
4362
4774
|
pathsToDelete = Array.from(selectedPaths);
|
|
@@ -4380,6 +4792,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4380
4792
|
var confirmed = window.confirm('Delete ' + label + '? This cannot be undone.');
|
|
4381
4793
|
if (!confirmed) return;
|
|
4382
4794
|
|
|
4795
|
+
// Computed against the pre-delete tree, since the deleted paths' siblings
|
|
4796
|
+
// are only known before the tree is refreshed.
|
|
4797
|
+
var deleteRevealTarget = computeDeleteRevealTarget(treeData, pathsToDelete);
|
|
4798
|
+
|
|
4383
4799
|
setLoading(function (prev) {
|
|
4384
4800
|
return _extends({}, prev, { deletePath: true });
|
|
4385
4801
|
});
|
|
@@ -4398,6 +4814,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4398
4814
|
}
|
|
4399
4815
|
return refreshProjectTree().then(function () {
|
|
4400
4816
|
GitService.fetchStatusLite({ background: true });
|
|
4817
|
+
if (failures.length === 0 && deleteRevealTarget) setRevealTarget({ path: deleteRevealTarget });
|
|
4401
4818
|
});
|
|
4402
4819
|
})["finally"](function () {
|
|
4403
4820
|
setLoading(function (prev) {
|
|
@@ -4415,6 +4832,35 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4415
4832
|
var canLintAndFormat = !!activeTab;
|
|
4416
4833
|
var hasGitBranch = !!(state.gitBranch && state.gitBranch.trim());
|
|
4417
4834
|
|
|
4835
|
+
// Derived at render from state already in the store — not stored via setState,
|
|
4836
|
+
// so the 5s git-status poll (MbeditorApp.js re-render rules) never fires an
|
|
4837
|
+
// extra re-render on an unchanged tick. Only conflictFileIndex is state, and
|
|
4838
|
+
// it changes only on explicit user click, never from a poll.
|
|
4839
|
+
var conflictedFiles = (state.gitInfo && state.gitInfo.workingTree || []).filter(function (f) {
|
|
4840
|
+
return window.MBEDITOR_UNMERGED_STATUSES[(f.status || '').trim()];
|
|
4841
|
+
});
|
|
4842
|
+
|
|
4843
|
+
// Opens the conflicted file at index idx (wrapping), landing the cursor/reveal
|
|
4844
|
+
// on its first `<<<<<<<` marker. Prefers an already-open tab's live content
|
|
4845
|
+
// (may hold unsaved edits) over a fresh disk fetch.
|
|
4846
|
+
var openConflictedFileAt = function openConflictedFileAt(idx) {
|
|
4847
|
+
if (!conflictedFiles.length) return;
|
|
4848
|
+
var i = ((idx % conflictedFiles.length) + conflictedFiles.length) % conflictedFiles.length;
|
|
4849
|
+
var file = conflictedFiles[i];
|
|
4850
|
+
setConflictFileIndex(i);
|
|
4851
|
+
var openTab = state.panes.reduce(function (found, p) {
|
|
4852
|
+
return found || p.tabs.find(function (t) { return t.path === file.path; });
|
|
4853
|
+
}, null);
|
|
4854
|
+
var name = file.path.split('/').pop();
|
|
4855
|
+
var contentPromise = openTab ? Promise.resolve({ content: openTab.content }) : FileService.getFile(file.path);
|
|
4856
|
+
contentPromise.then(function (data) {
|
|
4857
|
+
var content = typeof data.content === 'string' ? data.content : '';
|
|
4858
|
+
var blocks = ConflictParser.parse(content);
|
|
4859
|
+
var line = blocks.length ? blocks[0].startLine + 1 : null;
|
|
4860
|
+
TabManager.openTab(file.path, name, line);
|
|
4861
|
+
});
|
|
4862
|
+
};
|
|
4863
|
+
|
|
4418
4864
|
var renderTabBar = function renderTabBar(paneId, tabs, activeId) {
|
|
4419
4865
|
return React.createElement(TabBar, {
|
|
4420
4866
|
tabs: tabs,
|
|
@@ -4427,7 +4873,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4427
4873
|
onSelect: function (id) {
|
|
4428
4874
|
// Sync explorer selection with the newly active tab so there's only one highlight
|
|
4429
4875
|
var tab = tabs.find(function(t) { return t.id === id; });
|
|
4430
|
-
if (tab && tab.path && !tab.path.startsWith('mbeditor://')
|
|
4876
|
+
if (tab && tab.path && !tab.path.startsWith('mbeditor://')) {
|
|
4431
4877
|
handleNodeSelect({ path: tab.path, name: tab.name, type: 'file' });
|
|
4432
4878
|
}
|
|
4433
4879
|
TabManager.switchTab(paneId, id);
|
|
@@ -4464,78 +4910,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4464
4910
|
});
|
|
4465
4911
|
};
|
|
4466
4912
|
|
|
4467
|
-
function openSettingsTab() {
|
|
4468
|
-
var st = EditorStore.getState();
|
|
4469
|
-
var foundPaneId = null;
|
|
4470
|
-
var foundTab = null;
|
|
4471
|
-
st.panes.forEach(function(p) {
|
|
4472
|
-
if (!foundTab) {
|
|
4473
|
-
var t = p.tabs.find(function(tab) { return tab.path === '__settings__'; });
|
|
4474
|
-
if (t) { foundTab = t; foundPaneId = p.id; }
|
|
4475
|
-
}
|
|
4476
|
-
});
|
|
4477
|
-
if (foundTab) {
|
|
4478
|
-
var newPanes = st.panes.map(function(p) {
|
|
4479
|
-
if (p.id === foundPaneId) return Object.assign({}, p, { activeTabId: '__settings__' });
|
|
4480
|
-
return p;
|
|
4481
|
-
});
|
|
4482
|
-
EditorStore.setState({ panes: newPanes, focusedPaneId: foundPaneId, activeTabId: '__settings__' });
|
|
4483
|
-
return;
|
|
4484
|
-
}
|
|
4485
|
-
var paneId = st.focusedPaneId;
|
|
4486
|
-
var pane = st.panes.find(function(p) { return p.id === paneId; }) || st.panes[0];
|
|
4487
|
-
if (!pane) return;
|
|
4488
|
-
paneId = pane.id;
|
|
4489
|
-
var newTab = { id: '__settings__', path: '__settings__', name: 'Settings', dirty: false, content: '', isSettings: true };
|
|
4490
|
-
var newPanes2 = st.panes.map(function(p) {
|
|
4491
|
-
if (p.id === paneId) return Object.assign({}, p, { tabs: p.tabs.concat(newTab), activeTabId: '__settings__' });
|
|
4492
|
-
return p;
|
|
4493
|
-
});
|
|
4494
|
-
EditorStore.setState({ panes: newPanes2, focusedPaneId: paneId, activeTabId: '__settings__' });
|
|
4495
|
-
}
|
|
4496
|
-
|
|
4497
|
-
// The diagram lives in an editor tab, not the sidebar: a layered graph is
|
|
4498
|
-
// inherently wide and a ~300px panel can only ever show its first column.
|
|
4499
|
-
// The sidebar tab is the entry point and the searchable model list.
|
|
4500
|
-
var MODEL_GRAPH_TAB_ID = 'mbeditor://model-graph';
|
|
4501
|
-
function openModelGraphTab() {
|
|
4502
|
-
var st = EditorStore.getState();
|
|
4503
|
-
var paneId = st.focusedPaneId;
|
|
4504
|
-
|
|
4505
|
-
var existing = null;
|
|
4506
|
-
st.panes.forEach(function (p) {
|
|
4507
|
-
if (!existing && p.tabs.some(function (t) { return t.id === MODEL_GRAPH_TAB_ID; })) {
|
|
4508
|
-
existing = p.id;
|
|
4509
|
-
}
|
|
4510
|
-
});
|
|
4511
|
-
if (existing) {
|
|
4512
|
-
EditorStore.setState({
|
|
4513
|
-
panes: st.panes.map(function (p) {
|
|
4514
|
-
return p.id === existing ? Object.assign({}, p, { activeTabId: MODEL_GRAPH_TAB_ID }) : p;
|
|
4515
|
-
}),
|
|
4516
|
-
focusedPaneId: existing
|
|
4517
|
-
});
|
|
4518
|
-
return;
|
|
4519
|
-
}
|
|
4520
|
-
|
|
4521
|
-
var pane = st.panes.find(function (p) { return p.id === paneId; }) || st.panes[0];
|
|
4522
|
-
if (!pane) return;
|
|
4523
|
-
|
|
4524
|
-
var newTab = {
|
|
4525
|
-
id: MODEL_GRAPH_TAB_ID, path: MODEL_GRAPH_TAB_ID, name: 'Model Graph',
|
|
4526
|
-
dirty: false, content: '', isModelGraph: true
|
|
4527
|
-
};
|
|
4528
|
-
EditorStore.setState({
|
|
4529
|
-
panes: st.panes.map(function (p) {
|
|
4530
|
-
return p.id === pane.id
|
|
4531
|
-
? Object.assign({}, p, { tabs: p.tabs.concat(newTab), activeTabId: MODEL_GRAPH_TAB_ID })
|
|
4532
|
-
: p;
|
|
4533
|
-
}),
|
|
4534
|
-
focusedPaneId: pane.id
|
|
4535
|
-
});
|
|
4536
|
-
loadModelGraph(false);
|
|
4537
|
-
}
|
|
4538
|
-
|
|
4539
4913
|
var CHANGELOG_TAB_ID = 'mbeditor://changelog';
|
|
4540
4914
|
function openChangelogTab() {
|
|
4541
4915
|
var st = EditorStore.getState();
|
|
@@ -4593,10 +4967,11 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4593
4967
|
{ className: "ide-titlebar" },
|
|
4594
4968
|
React.createElement("i", { className: "fas fa-layer-group ide-titlebar-icon" }),
|
|
4595
4969
|
React.createElement(
|
|
4970
|
+
// The product name, not the window title: the host and port are in the
|
|
4971
|
+
// browser's own address bar, and the long form crowded the search pill.
|
|
4596
4972
|
"div",
|
|
4597
|
-
{ className: "ide-titlebar-title" },
|
|
4598
|
-
"
|
|
4599
|
-
window.location.host
|
|
4973
|
+
{ className: "ide-titlebar-title", title: "Mini Browser Editor — " + window.location.host },
|
|
4974
|
+
"Mbeditor"
|
|
4600
4975
|
),
|
|
4601
4976
|
// The slot claims all the room between the title and the buttons; the
|
|
4602
4977
|
// search pill then takes 75% of it, centred. Sizing the pill against a
|
|
@@ -4686,6 +5061,35 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4686
5061
|
!toolbarIconOnly && " Git"
|
|
4687
5062
|
)
|
|
4688
5063
|
),
|
|
5064
|
+
conflictedFiles.length > 0 && React.createElement(
|
|
5065
|
+
React.Fragment,
|
|
5066
|
+
null,
|
|
5067
|
+
React.createElement("div", { className: "statusbar-sep" }),
|
|
5068
|
+
React.createElement(
|
|
5069
|
+
"button",
|
|
5070
|
+
{
|
|
5071
|
+
type: "button",
|
|
5072
|
+
className: "statusbar-btn mbeditor-conflict-chip",
|
|
5073
|
+
title: "Open the first of " + conflictedFiles.length + " conflicted file" + (conflictedFiles.length === 1 ? "" : "s"),
|
|
5074
|
+
onClick: function () { openConflictedFileAt(0); }
|
|
5075
|
+
},
|
|
5076
|
+
React.createElement("i", { className: "fas fa-exclamation-circle" }),
|
|
5077
|
+
// The count always renders, even icon-only: it is the chip's
|
|
5078
|
+
// content, not a label. A bare warning icon says less than nothing.
|
|
5079
|
+
" " + conflictedFiles.length,
|
|
5080
|
+
!toolbarIconOnly && (conflictedFiles.length === 1 ? " conflict" : " conflicts")
|
|
5081
|
+
),
|
|
5082
|
+
conflictedFiles.length > 1 && React.createElement(
|
|
5083
|
+
"button",
|
|
5084
|
+
{
|
|
5085
|
+
type: "button",
|
|
5086
|
+
className: "statusbar-btn",
|
|
5087
|
+
title: "Open the next conflicted file",
|
|
5088
|
+
onClick: function () { openConflictedFileAt(conflictFileIndex + 1); }
|
|
5089
|
+
},
|
|
5090
|
+
React.createElement("i", { className: "fas fa-arrow-right" })
|
|
5091
|
+
)
|
|
5092
|
+
),
|
|
4689
5093
|
// Collaboration trouble chip. Deliberately not shown when everything is
|
|
4690
5094
|
// healthy and you are simply alone — that was the whole point of hiding
|
|
4691
5095
|
// the solo chip. It appears only when a condition pairing needs has
|
|
@@ -4737,45 +5141,48 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4737
5141
|
React.Fragment,
|
|
4738
5142
|
null,
|
|
4739
5143
|
React.createElement("div", { className: "statusbar-sep" }),
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
:
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
5144
|
+
React.createElement(
|
|
5145
|
+
"div",
|
|
5146
|
+
{ className: "collab-avatar-stack" },
|
|
5147
|
+
collabAvatarIds.map(function (cid) {
|
|
5148
|
+
var info = collabPeerInfo(cid);
|
|
5149
|
+
return React.createElement(
|
|
5150
|
+
"button",
|
|
5151
|
+
{
|
|
5152
|
+
key: cid,
|
|
5153
|
+
type: "button",
|
|
5154
|
+
className: "collab-avatar" +
|
|
5155
|
+
(info.following ? " collab-avatar-following" : "") +
|
|
5156
|
+
(info.elsewhere ? " collab-avatar-elsewhere" : ""),
|
|
5157
|
+
style: { background: info.colour },
|
|
5158
|
+
// The dot carries no glyph, so the name has to reach a
|
|
5159
|
+
// screen reader some other way.
|
|
5160
|
+
"aria-label": info.name,
|
|
5161
|
+
onMouseEnter: function (e) { openCollabHover(cid, e); },
|
|
5162
|
+
onMouseLeave: function () { setCollabHover(null); },
|
|
5163
|
+
onClick: function () { toggleFollow(cid); }
|
|
5164
|
+
}
|
|
5165
|
+
);
|
|
5166
|
+
}),
|
|
5167
|
+
collabOverflowIds.length > 0 && React.createElement(
|
|
5168
|
+
"div",
|
|
5169
|
+
{ className: "collab-avatar-overflow" },
|
|
5170
|
+
React.createElement(
|
|
5171
|
+
"button",
|
|
5172
|
+
{
|
|
5173
|
+
type: "button",
|
|
5174
|
+
className: "collab-avatar collab-avatar-more",
|
|
5175
|
+
onMouseEnter: function (e) { cancelOverflowClose(); setCollabOverflowAnchor(anchorFrom(e.currentTarget)); },
|
|
5176
|
+
onMouseLeave: scheduleOverflowClose,
|
|
5177
|
+
onClick: function (e) {
|
|
5178
|
+
var el = e.currentTarget;
|
|
5179
|
+
setCollabOverflowAnchor(function (prev) { return prev ? null : anchorFrom(el); });
|
|
5180
|
+
}
|
|
5181
|
+
},
|
|
5182
|
+
"+" + collabOverflowIds.length
|
|
5183
|
+
)
|
|
5184
|
+
)
|
|
5185
|
+
)
|
|
4779
5186
|
),
|
|
4780
5187
|
React.createElement("div", { className: "statusbar-sep" }),
|
|
4781
5188
|
React.createElement(
|
|
@@ -4805,6 +5212,31 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4805
5212
|
)
|
|
4806
5213
|
)
|
|
4807
5214
|
),
|
|
5215
|
+
collabOverflowOpen && React.createElement(
|
|
5216
|
+
"div",
|
|
5217
|
+
{
|
|
5218
|
+
className: "collab-overflow-popover",
|
|
5219
|
+
style: { top: collabOverflowAnchor.top + 'px', right: collabOverflowAnchor.right + 'px' },
|
|
5220
|
+
onMouseEnter: cancelOverflowClose,
|
|
5221
|
+
onMouseLeave: scheduleOverflowClose
|
|
5222
|
+
},
|
|
5223
|
+
collabOverflowIds.map(function (cid) {
|
|
5224
|
+
var info = collabPeerInfo(cid);
|
|
5225
|
+
return React.createElement(
|
|
5226
|
+
"button",
|
|
5227
|
+
{
|
|
5228
|
+
key: cid,
|
|
5229
|
+
type: "button",
|
|
5230
|
+
className: "collab-overflow-row" + (info.following ? " is-following" : ""),
|
|
5231
|
+
onClick: function () { toggleFollow(cid); }
|
|
5232
|
+
},
|
|
5233
|
+
React.createElement("span", { className: "collab-overflow-swatch", style: { background: info.colour } }),
|
|
5234
|
+
React.createElement("span", { className: "collab-overflow-name" }, info.name),
|
|
5235
|
+
React.createElement("span", { className: "collab-overflow-file" }, info.file ? info.file.split('/').pop() : 'No file open'),
|
|
5236
|
+
info.following && React.createElement("i", { className: "fas fa-eye collab-overflow-eye" })
|
|
5237
|
+
);
|
|
5238
|
+
})
|
|
5239
|
+
),
|
|
4808
5240
|
collabHover && (function () {
|
|
4809
5241
|
var isMe = collabHover.cid === '__me__';
|
|
4810
5242
|
var peer = isMe ? null : collabRoster[collabHover.cid];
|
|
@@ -4893,6 +5325,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4893
5325
|
);
|
|
4894
5326
|
})(),
|
|
4895
5327
|
showHelp && React.createElement(ShortcutHelp, { onClose: function () { return setShowHelp(false); } }),
|
|
5328
|
+
showSettings && React.createElement(window.SettingsModal || SettingsModal, {
|
|
5329
|
+
editorPrefs: editorPrefs,
|
|
5330
|
+
setEditorPrefs: setEditorPrefs,
|
|
5331
|
+
rubocopAvailable: rubocopAvailable,
|
|
5332
|
+
rubocopConfigPath: rubocopConfigPath,
|
|
5333
|
+
onOpenRubocopConfig: function () { handleSelectFile(rubocopConfigPath, rubocopConfigPath.split('/').pop()); },
|
|
5334
|
+
onReset: function () { setEditorPrefs(Object.assign({}, DEFAULT_EDITOR_PREFS)); },
|
|
5335
|
+
onClose: function () { return setShowSettings(false); }
|
|
5336
|
+
}),
|
|
4896
5337
|
React.createElement(
|
|
4897
5338
|
"div",
|
|
4898
5339
|
{ className: "ide-body", id: "ide-body-container" },
|
|
@@ -4937,7 +5378,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4937
5378
|
"button",
|
|
4938
5379
|
{
|
|
4939
5380
|
type: "button",
|
|
4940
|
-
className: "ide-activity-btn" + (
|
|
5381
|
+
className: "ide-activity-btn" + (showModelGraphView ? ' active' : ''),
|
|
4941
5382
|
title: "Model graph",
|
|
4942
5383
|
onClick: function() { handleActivityBarClick('models'); }
|
|
4943
5384
|
},
|
|
@@ -4951,16 +5392,16 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4951
5392
|
"button",
|
|
4952
5393
|
{
|
|
4953
5394
|
type: "button",
|
|
4954
|
-
className: "ide-activity-btn" + (
|
|
5395
|
+
className: "ide-activity-btn" + (showSettings ? ' active' : ''),
|
|
4955
5396
|
title: "Editor Preferences",
|
|
4956
|
-
onClick:
|
|
5397
|
+
onClick: function() { handleActivityBarClick('settings'); }
|
|
4957
5398
|
},
|
|
4958
5399
|
React.createElement("i", { className: "fas fa-cog" })
|
|
4959
5400
|
)
|
|
4960
5401
|
)
|
|
4961
5402
|
),
|
|
4962
|
-
/* Panel content — shown when not collapsed
|
|
4963
|
-
!sidebarCollapsed && !zenMode && React.createElement(
|
|
5403
|
+
/* Panel content — shown when not collapsed, not in zen mode, and not showing the model graph */
|
|
5404
|
+
!sidebarCollapsed && !zenMode && !showModelGraphView && React.createElement(
|
|
4964
5405
|
"div",
|
|
4965
5406
|
{ className: "ide-sidebar", style: { width: sidebarWidth + "px" } },
|
|
4966
5407
|
React.createElement("div", { className: "sidebar-panel-title" },
|
|
@@ -4998,108 +5439,126 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4998
5439
|
React.createElement(
|
|
4999
5440
|
"div",
|
|
5000
5441
|
{ style: { marginBottom: "12px" } },
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
return
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
},
|
|
5011
|
-
React.createElement(
|
|
5442
|
+
(function () {
|
|
5443
|
+
// VS Code drops the "GROUP N" sub-header when only one group
|
|
5444
|
+
// is populated — it's only useful to tell groups apart.
|
|
5445
|
+
var activePanes = state.panes.filter(function (p) { return p.tabs.length > 0; });
|
|
5446
|
+
var showGroupHeaders = activePanes.length > 1;
|
|
5447
|
+
return state.panes.map(function (pane) {
|
|
5448
|
+
if (pane.tabs.length === 0) return null;
|
|
5449
|
+
var isPane2 = pane.id === 2;
|
|
5450
|
+
return React.createElement(
|
|
5012
5451
|
"div",
|
|
5013
|
-
{
|
|
5014
|
-
|
|
5015
|
-
"
|
|
5016
|
-
{
|
|
5017
|
-
|
|
5018
|
-
|
|
5452
|
+
{
|
|
5453
|
+
key: pane.id,
|
|
5454
|
+
className: "open-editors-group",
|
|
5455
|
+
style: { marginBottom: pane.id === 1 && state.panes[1].tabs.length > 0 ? "10px" : "0" }
|
|
5456
|
+
},
|
|
5457
|
+
showGroupHeaders && React.createElement(
|
|
5458
|
+
"div",
|
|
5459
|
+
{ className: "ide-sidebar-header open-editors-group-header" },
|
|
5460
|
+
React.createElement(
|
|
5461
|
+
"span",
|
|
5462
|
+
{ className: "open-editors-group-title" },
|
|
5463
|
+
"GROUP ",
|
|
5464
|
+
pane.id
|
|
5465
|
+
),
|
|
5466
|
+
React.createElement(
|
|
5467
|
+
SectionActionGroup,
|
|
5468
|
+
{ ariaLabel: "Group " + pane.id + " actions", className: "collapsible-actions open-editors-group-actions" },
|
|
5469
|
+
React.createElement(SidebarActionButton, {
|
|
5470
|
+
title: "Close all editors in Group " + pane.id,
|
|
5471
|
+
ariaLabel: "Close all editors in Group " + pane.id,
|
|
5472
|
+
iconClass: "far fa-window-close",
|
|
5473
|
+
onClick: function (e) {
|
|
5474
|
+
e.stopPropagation();handleCloseEditorsInGroup(pane.id);
|
|
5475
|
+
}
|
|
5476
|
+
})
|
|
5477
|
+
)
|
|
5019
5478
|
),
|
|
5020
5479
|
React.createElement(
|
|
5021
|
-
|
|
5022
|
-
{
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
onClick: function (e) {
|
|
5028
|
-
e.stopPropagation();handleCloseEditorsInGroup(pane.id);
|
|
5029
|
-
}
|
|
5030
|
-
})
|
|
5031
|
-
)
|
|
5032
|
-
),
|
|
5033
|
-
React.createElement(
|
|
5034
|
-
"div",
|
|
5035
|
-
{ className: "file-tree" },
|
|
5036
|
-
pane.tabs.map(function (tab) {
|
|
5037
|
-
return React.createElement(
|
|
5038
|
-
"div",
|
|
5039
|
-
{
|
|
5040
|
-
key: tab.id,
|
|
5041
|
-
className: "tree-item " + (pane.activeTabId === tab.id && state.focusedPaneId === pane.id ? "active" : ""),
|
|
5042
|
-
// The name ellipsises in a narrow sidebar, so the row
|
|
5043
|
-
// carries the full path the way file-tree rows do.
|
|
5044
|
-
title: tab.path || tab.name,
|
|
5045
|
-
onClick: function () {
|
|
5046
|
-
if (tab.path && !tab.path.startsWith('mbeditor://') && tab.path !== '__settings__') {
|
|
5047
|
-
handleNodeSelect({ path: tab.path, name: tab.name, type: 'file' });
|
|
5048
|
-
}
|
|
5049
|
-
TabManager.focusPane(pane.id);TabManager.switchTab(pane.id, tab.id);
|
|
5050
|
-
}
|
|
5051
|
-
},
|
|
5052
|
-
React.createElement("i", { className: "tree-item-icon " + (window.getFileIcon ? window.getFileIcon(tab.name) : 'far fa-file-code') + " tree-file-icon" }),
|
|
5053
|
-
React.createElement(
|
|
5054
|
-
"div",
|
|
5055
|
-
// minWidth:0 on both the row's name cell and the label
|
|
5056
|
-
// itself: without it a flex item refuses to shrink
|
|
5057
|
-
// below its content, so a long filename pushed out
|
|
5058
|
-
// under the (formerly absolute) action buttons.
|
|
5059
|
-
{ className: "tree-item-name", style: { display: 'flex', alignItems: 'center', minWidth: 0 } },
|
|
5060
|
-
React.createElement(
|
|
5061
|
-
"span",
|
|
5062
|
-
{ style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', minWidth: 0 } },
|
|
5063
|
-
tab.name
|
|
5064
|
-
),
|
|
5065
|
-
tab.dirty && React.createElement("i", { className: "fas fa-circle", style: { fontSize: '5px', color: '#e3d286', marginLeft: '6px', marginTop: '1px', flexShrink: 0 } })
|
|
5066
|
-
),
|
|
5067
|
-
React.createElement(
|
|
5480
|
+
"div",
|
|
5481
|
+
{ className: "file-tree" },
|
|
5482
|
+
pane.tabs.map(function (tab) {
|
|
5483
|
+
var isActive = pane.activeTabId === tab.id && state.focusedPaneId === pane.id;
|
|
5484
|
+
var dir = tab.path ? tab.path.split('/').slice(0, -1).join('/') : '';
|
|
5485
|
+
return React.createElement(
|
|
5068
5486
|
"div",
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5487
|
+
{
|
|
5488
|
+
key: tab.id,
|
|
5489
|
+
className: "tree-item open-editors-item " + (isActive ? "active" : ""),
|
|
5490
|
+
// The name ellipsises in a narrow sidebar, so the row
|
|
5491
|
+
// carries the full path the way file-tree rows do.
|
|
5492
|
+
title: tab.path || tab.name,
|
|
5493
|
+
onClick: function () {
|
|
5494
|
+
if (tab.path && !tab.path.startsWith('mbeditor://')) {
|
|
5495
|
+
handleNodeSelect({ path: tab.path, name: tab.name, type: 'file' });
|
|
5496
|
+
}
|
|
5497
|
+
TabManager.focusPane(pane.id);TabManager.switchTab(pane.id, tab.id);
|
|
5498
|
+
}
|
|
5499
|
+
},
|
|
5500
|
+
React.createElement("i", { className: "tree-item-icon " + (window.getFileIcon ? window.getFileIcon(tab.name) : 'far fa-file-code') + " tree-file-icon" }),
|
|
5073
5501
|
React.createElement(
|
|
5074
5502
|
"div",
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5503
|
+
// minWidth:0 on both the row's name cell and the label
|
|
5504
|
+
// itself: without it a flex item refuses to shrink
|
|
5505
|
+
// below its content, so a long filename pushed out
|
|
5506
|
+
// under the (formerly absolute) action buttons.
|
|
5507
|
+
{ className: "tree-item-name", style: { display: 'flex', alignItems: 'center', minWidth: 0 } },
|
|
5508
|
+
React.createElement(
|
|
5509
|
+
"span",
|
|
5510
|
+
{ className: "open-editors-name", style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', minWidth: 0, flexShrink: 0 } },
|
|
5511
|
+
tab.name
|
|
5512
|
+
),
|
|
5513
|
+
dir && React.createElement(
|
|
5514
|
+
"span",
|
|
5515
|
+
{ className: "open-editors-dir" },
|
|
5516
|
+
dir
|
|
5517
|
+
)
|
|
5079
5518
|
),
|
|
5080
5519
|
React.createElement(
|
|
5081
5520
|
"div",
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5521
|
+
// In flow, not absolute — the buttons now claim their
|
|
5522
|
+
// own width so the name truncates instead of running
|
|
5523
|
+
// underneath them.
|
|
5524
|
+
{ className: "tab-actions", style: { display: 'flex', alignItems: 'center', flexShrink: 0, marginLeft: 'auto' } },
|
|
5525
|
+
React.createElement(
|
|
5526
|
+
"div",
|
|
5527
|
+
{ className: "tab-split", onClick: function (e) {
|
|
5528
|
+
e.stopPropagation();TabManager.moveTabToPane(pane.id, pane.id === 1 ? 2 : 1, tab.id);
|
|
5529
|
+
}, style: { padding: '0 4px', cursor: 'pointer' }, title: "Move to Group " + (pane.id === 1 ? 2 : 1) },
|
|
5530
|
+
React.createElement("i", { className: isPane2 ? "fas fa-chevron-left" : "fas fa-chevron-right" })
|
|
5531
|
+
),
|
|
5532
|
+
React.createElement(
|
|
5533
|
+
"div",
|
|
5534
|
+
{ className: "open-editors-close-slot" },
|
|
5535
|
+
tab.dirty && React.createElement("i", { className: "fas fa-circle open-editors-dirty-dot", 'aria-hidden': 'true' }),
|
|
5536
|
+
React.createElement(
|
|
5537
|
+
"button",
|
|
5538
|
+
{ type: "button", className: "tab-close", onClick: function (e) {
|
|
5539
|
+
e.stopPropagation();requestCloseTab(pane.id, tab.id);
|
|
5540
|
+
}, style: { padding: '0 4px' },
|
|
5541
|
+
title: "Close " + tab.name + (tab.dirty ? " (unsaved changes)" : ""),
|
|
5542
|
+
'aria-label': "Close " + tab.name + (tab.dirty ? " (unsaved changes)" : "") },
|
|
5543
|
+
React.createElement("i", { className: "fas fa-times" })
|
|
5544
|
+
)
|
|
5545
|
+
)
|
|
5089
5546
|
)
|
|
5090
|
-
)
|
|
5091
|
-
)
|
|
5092
|
-
|
|
5093
|
-
)
|
|
5094
|
-
);
|
|
5095
|
-
})
|
|
5547
|
+
);
|
|
5548
|
+
})
|
|
5549
|
+
)
|
|
5550
|
+
);
|
|
5551
|
+
});
|
|
5552
|
+
})()
|
|
5096
5553
|
)
|
|
5097
5554
|
)
|
|
5098
5555
|
),
|
|
5099
|
-
state.panes.flatMap(function (p) { return p.tabs; }).length > 0 && React.createElement(
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5556
|
+
state.panes.flatMap(function (p) { return p.tabs; }).length > 0 && React.createElement((window.Gutter || Gutter), {
|
|
5557
|
+
orientation: 'horizontal',
|
|
5558
|
+
label: "Resize open editors list",
|
|
5559
|
+
active: activeResizeMode === 'openeditors',
|
|
5560
|
+
onDragStart: startOpenEditorsResize
|
|
5561
|
+
}),
|
|
5103
5562
|
React.createElement(
|
|
5104
5563
|
"div",
|
|
5105
5564
|
{ className: "ide-sidebar-scrollable" },
|
|
@@ -5107,10 +5566,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5107
5566
|
CollapsibleSection,
|
|
5108
5567
|
{
|
|
5109
5568
|
title: projectSectionTitle,
|
|
5110
|
-
|
|
5111
|
-
onToggle: function (isCollapsed) {
|
|
5112
|
-
return handleToggleSection('projects', isCollapsed);
|
|
5113
|
-
},
|
|
5569
|
+
collapsible: false,
|
|
5114
5570
|
actions: React.createElement(
|
|
5115
5571
|
SectionActionGroup,
|
|
5116
5572
|
{ ariaLabel: "Project actions" },
|
|
@@ -5145,10 +5601,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5145
5601
|
disabled: !!loading.createDir
|
|
5146
5602
|
}),
|
|
5147
5603
|
React.createElement(SidebarActionButton, {
|
|
5148
|
-
title: "Upload files",
|
|
5604
|
+
title: "Upload files to the workspace root",
|
|
5149
5605
|
iconClass: 'fas fa-upload',
|
|
5606
|
+
// Defaults to the workspace root, not whatever happens to be
|
|
5607
|
+
// selected in the tree: an upload that silently follows the
|
|
5608
|
+
// selection lands files somewhere the user was not looking,
|
|
5609
|
+
// and the count reads as a success either way. The context
|
|
5610
|
+
// menu's "Upload Files Here..." is the way to target a folder.
|
|
5150
5611
|
onClick: function () {
|
|
5151
|
-
return openImportDialog(
|
|
5612
|
+
return openImportDialog(null);
|
|
5152
5613
|
}
|
|
5153
5614
|
}),
|
|
5154
5615
|
React.createElement(SidebarActionButton, {
|
|
@@ -5178,6 +5639,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5178
5639
|
activePath: editorPrefs.autoRevealInExplorer !== false ? (activeTab && activeTab.path) : null,
|
|
5179
5640
|
selectedPaths: selectedPaths,
|
|
5180
5641
|
anchorPath: selectedTreePath,
|
|
5642
|
+
revealTarget: revealTarget,
|
|
5181
5643
|
onNodeSelect: handleNodeSelect,
|
|
5182
5644
|
onMultiSelect: handleMultiSelect,
|
|
5183
5645
|
onMove: handleMoveNodes,
|
|
@@ -5516,18 +5978,32 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5516
5978
|
})()
|
|
5517
5979
|
)
|
|
5518
5980
|
),
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
"aria-label": "Resize explorer panel"
|
|
5981
|
+
// Collapsed: the same gutter is a handle. Drag it right and the
|
|
5982
|
+
// explorer snaps open; there is no intermediate width to preview.
|
|
5983
|
+
sidebarCollapsed && !zenMode && !showModelGraphView && React.createElement((window.Gutter || Gutter), {
|
|
5984
|
+
orientation: 'vertical',
|
|
5985
|
+
label: "Drag right to open the explorer",
|
|
5986
|
+
onSnap: function () { setSidebarCollapsed(false); }
|
|
5526
5987
|
}),
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5988
|
+
/* Sidebar resize gutter — only when panel is open */
|
|
5989
|
+
!sidebarCollapsed && !zenMode && !showModelGraphView && React.createElement((window.Gutter || Gutter), {
|
|
5990
|
+
orientation: 'vertical',
|
|
5991
|
+
label: "Resize explorer panel",
|
|
5992
|
+
active: activeResizeMode === 'sidebar',
|
|
5993
|
+
onDragStart: startSidebarResize
|
|
5994
|
+
}),
|
|
5995
|
+
// The graph replaces the whole column — no tab bar, no panes, no drawers —
|
|
5996
|
+
// rather than living inside ide-main, so it gets the full center width.
|
|
5997
|
+
showModelGraphView ? React.createElement(
|
|
5998
|
+
"div",
|
|
5999
|
+
{ className: "ide-model-graph-view" },
|
|
6000
|
+
React.createElement(ModelGraph, {
|
|
6001
|
+
graph: modelGraph,
|
|
6002
|
+
loading: modelGraphLoading,
|
|
6003
|
+
onRefresh: function () { loadModelGraph(true); },
|
|
6004
|
+
onOpenModel: function (model) { openSchemaModal(model.name); }
|
|
6005
|
+
})
|
|
6006
|
+
) : React.createElement(
|
|
5531
6007
|
"div",
|
|
5532
6008
|
{ className: "ide-center-column" },
|
|
5533
6009
|
React.createElement(
|
|
@@ -5625,13 +6101,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5625
6101
|
commits: pActiveTab.commits || [],
|
|
5626
6102
|
onSelectCommit: handleSelectCommit
|
|
5627
6103
|
});
|
|
5628
|
-
} else if (pActiveTab.isModelGraph) {
|
|
5629
|
-
content = React.createElement(ModelGraph, {
|
|
5630
|
-
graph: modelGraph,
|
|
5631
|
-
loading: modelGraphLoading,
|
|
5632
|
-
onRefresh: function () { loadModelGraph(true); },
|
|
5633
|
-
onOpenModel: function (model) { openSchemaModal(model.name); }
|
|
5634
|
-
});
|
|
5635
6104
|
} else if (pActiveTab.isChangelog) {
|
|
5636
6105
|
content = React.createElement(ChangelogView, {
|
|
5637
6106
|
changelogState: changelogState,
|
|
@@ -5644,597 +6113,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5644
6113
|
}
|
|
5645
6114
|
}
|
|
5646
6115
|
});
|
|
5647
|
-
} else if (pActiveTab.isSettings) {
|
|
5648
|
-
content = React.createElement(
|
|
5649
|
-
'div',
|
|
5650
|
-
{ className: 'ide-settings-tab-content' },
|
|
5651
|
-
React.createElement(
|
|
5652
|
-
'div',
|
|
5653
|
-
{ className: 'ide-settings-body' },
|
|
5654
|
-
|
|
5655
|
-
/* ── Appearance ──────────────────────────────── */
|
|
5656
|
-
React.createElement('div', { className: 'ide-settings-section-header' }, 'Appearance'),
|
|
5657
|
-
React.createElement(
|
|
5658
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Color theme for the editor' },
|
|
5659
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Theme'),
|
|
5660
|
-
React.createElement(
|
|
5661
|
-
'select', {
|
|
5662
|
-
value: editorPrefs.theme || 'vs-dark',
|
|
5663
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { theme: e.target.value }); }); }
|
|
5664
|
-
},
|
|
5665
|
-
React.createElement('option', { value: 'vs-dark' }, 'Dark'),
|
|
5666
|
-
React.createElement('option', { value: 'vs' }, 'Light'),
|
|
5667
|
-
React.createElement('option', { value: 'hc-black' }, 'HC Dark'),
|
|
5668
|
-
React.createElement('option', { value: 'hc-light' }, 'HC Light'),
|
|
5669
|
-
React.createElement('option', { value: 'dracula' }, 'Dracula'),
|
|
5670
|
-
React.createElement('option', { value: 'night-owl' }, 'Night Owl'),
|
|
5671
|
-
React.createElement('option', { value: 'monokai' }, 'Monokai'),
|
|
5672
|
-
React.createElement('option', { value: 'nord' }, 'Nord'),
|
|
5673
|
-
React.createElement('option', { value: 'github-dark' }, 'GitHub Dark'),
|
|
5674
|
-
React.createElement('option', { value: 'tomorrow-night' }, 'Tomorrow Night'),
|
|
5675
|
-
React.createElement('option', { value: 'github-light' }, 'GitHub Light')
|
|
5676
|
-
)
|
|
5677
|
-
),
|
|
5678
|
-
React.createElement(
|
|
5679
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Editor font size in pixels (8–32)' },
|
|
5680
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Font size'),
|
|
5681
|
-
React.createElement('input', {
|
|
5682
|
-
key: String(editorPrefs.fontSize || 13),
|
|
5683
|
-
type: 'number', min: '8', max: '32', step: '1',
|
|
5684
|
-
className: 'ide-settings-input',
|
|
5685
|
-
defaultValue: editorPrefs.fontSize || 13,
|
|
5686
|
-
onChange: function(e) {
|
|
5687
|
-
var v = parseInt(e.target.value, 10);
|
|
5688
|
-
if (v >= 8 && v <= 32) setEditorPrefs(function(p) { return Object.assign({}, p, { fontSize: v }); });
|
|
5689
|
-
},
|
|
5690
|
-
onBlur: function(e) {
|
|
5691
|
-
var v = parseInt(e.target.value, 10);
|
|
5692
|
-
if (isNaN(v) || v < 8 || v > 32) e.target.value = String(editorPrefs.fontSize || 13);
|
|
5693
|
-
}
|
|
5694
|
-
})
|
|
5695
|
-
),
|
|
5696
|
-
React.createElement(
|
|
5697
|
-
'label', { className: 'ide-settings-row-full', title: 'Font stack used in the editor — the first font available on your system is used' },
|
|
5698
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Font family'),
|
|
5699
|
-
React.createElement('input', {
|
|
5700
|
-
type: 'text',
|
|
5701
|
-
className: 'ide-settings-input ide-settings-input-wide',
|
|
5702
|
-
value: editorPrefs.fontFamily || "'JetBrains Mono', 'Fira Code', Consolas, 'Courier New', monospace",
|
|
5703
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { fontFamily: e.target.value }); }); }
|
|
5704
|
-
})
|
|
5705
|
-
),
|
|
5706
|
-
React.createElement(
|
|
5707
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Row height in pixels. 0 = auto (roughly font size × 1.5)' },
|
|
5708
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Line height (0=auto)'),
|
|
5709
|
-
React.createElement('input', {
|
|
5710
|
-
key: String(editorPrefs.lineHeight != null ? editorPrefs.lineHeight : 0),
|
|
5711
|
-
type: 'number', min: '0', max: '100', step: '1',
|
|
5712
|
-
className: 'ide-settings-input',
|
|
5713
|
-
defaultValue: editorPrefs.lineHeight != null ? editorPrefs.lineHeight : 0,
|
|
5714
|
-
onChange: function(e) {
|
|
5715
|
-
var v = parseInt(e.target.value, 10);
|
|
5716
|
-
if (!isNaN(v) && v >= 0 && v <= 100) setEditorPrefs(function(p) { return Object.assign({}, p, { lineHeight: v }); });
|
|
5717
|
-
},
|
|
5718
|
-
onBlur: function(e) {
|
|
5719
|
-
var v = parseInt(e.target.value, 10);
|
|
5720
|
-
if (isNaN(v) || v < 0 || v > 100) e.target.value = String(editorPrefs.lineHeight != null ? editorPrefs.lineHeight : 0);
|
|
5721
|
-
}
|
|
5722
|
-
})
|
|
5723
|
-
),
|
|
5724
|
-
React.createElement(
|
|
5725
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Extra space between characters in pixels. 0 = default' },
|
|
5726
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Letter spacing (px)'),
|
|
5727
|
-
React.createElement('input', {
|
|
5728
|
-
key: String(editorPrefs.letterSpacing != null ? editorPrefs.letterSpacing : 0),
|
|
5729
|
-
type: 'number', min: '-5', max: '20', step: '0.5',
|
|
5730
|
-
className: 'ide-settings-input',
|
|
5731
|
-
defaultValue: editorPrefs.letterSpacing != null ? editorPrefs.letterSpacing : 0,
|
|
5732
|
-
onChange: function(e) {
|
|
5733
|
-
var v = parseFloat(e.target.value);
|
|
5734
|
-
if (!isNaN(v) && v >= -5 && v <= 20) setEditorPrefs(function(p) { return Object.assign({}, p, { letterSpacing: v }); });
|
|
5735
|
-
},
|
|
5736
|
-
onBlur: function(e) {
|
|
5737
|
-
var v = parseFloat(e.target.value);
|
|
5738
|
-
if (isNaN(v) || v < -5 || v > 20) e.target.value = String(editorPrefs.letterSpacing != null ? editorPrefs.letterSpacing : 0);
|
|
5739
|
-
}
|
|
5740
|
-
})
|
|
5741
|
-
),
|
|
5742
|
-
|
|
5743
|
-
/* ── Indentation (unified editor + Prettier) ── */
|
|
5744
|
-
React.createElement('div', { className: 'ide-settings-section-header' }, 'Indentation'),
|
|
5745
|
-
React.createElement(
|
|
5746
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Number of spaces per indentation level (also sets Prettier tab width)' },
|
|
5747
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Tab size'),
|
|
5748
|
-
React.createElement('input', {
|
|
5749
|
-
key: String(editorPrefs.tabSize || 4),
|
|
5750
|
-
type: 'number', min: '1', max: '8', step: '1',
|
|
5751
|
-
className: 'ide-settings-input',
|
|
5752
|
-
defaultValue: editorPrefs.tabSize || 4,
|
|
5753
|
-
onChange: function(e) {
|
|
5754
|
-
var v = parseInt(e.target.value, 10);
|
|
5755
|
-
if (v >= 1 && v <= 8) setEditorPrefs(function(p) { return Object.assign({}, p, { tabSize: v }); });
|
|
5756
|
-
},
|
|
5757
|
-
onBlur: function(e) {
|
|
5758
|
-
var v = parseInt(e.target.value, 10);
|
|
5759
|
-
if (isNaN(v) || v < 1 || v > 8) e.target.value = String(editorPrefs.tabSize || 4);
|
|
5760
|
-
}
|
|
5761
|
-
})
|
|
5762
|
-
),
|
|
5763
|
-
React.createElement(
|
|
5764
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Insert spaces instead of tab characters when pressing Tab' },
|
|
5765
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Use spaces'),
|
|
5766
|
-
React.createElement('input', {
|
|
5767
|
-
type: 'checkbox',
|
|
5768
|
-
className: 'ide-settings-checkbox',
|
|
5769
|
-
checked: !!(editorPrefs.insertSpaces),
|
|
5770
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { insertSpaces: v }); }); }
|
|
5771
|
-
})
|
|
5772
|
-
),
|
|
5773
|
-
|
|
5774
|
-
/* ── Editor ──────────────────────────────────── */
|
|
5775
|
-
React.createElement('div', { className: 'ide-settings-section-header' }, 'Editor'),
|
|
5776
|
-
React.createElement(
|
|
5777
|
-
'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' },
|
|
5778
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Word wrap'),
|
|
5779
|
-
React.createElement(
|
|
5780
|
-
'select', {
|
|
5781
|
-
value: editorPrefs.wordWrap || 'off',
|
|
5782
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { wordWrap: e.target.value }); }); }
|
|
5783
|
-
},
|
|
5784
|
-
React.createElement('option', { value: 'off' }, 'Off'),
|
|
5785
|
-
React.createElement('option', { value: 'on' }, 'On'),
|
|
5786
|
-
React.createElement('option', { value: 'wordWrapColumn' }, 'Column')
|
|
5787
|
-
)
|
|
5788
|
-
),
|
|
5789
|
-
React.createElement(
|
|
5790
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Show line numbers in the gutter — On, Off, or Relative (useful with Vim mode)' },
|
|
5791
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Line numbers'),
|
|
5792
|
-
React.createElement(
|
|
5793
|
-
'select', {
|
|
5794
|
-
value: editorPrefs.lineNumbers || 'on',
|
|
5795
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { lineNumbers: e.target.value }); }); }
|
|
5796
|
-
},
|
|
5797
|
-
React.createElement('option', { value: 'on' }, 'On'),
|
|
5798
|
-
React.createElement('option', { value: 'off' }, 'Off'),
|
|
5799
|
-
React.createElement('option', { value: 'relative' }, 'Relative')
|
|
5800
|
-
)
|
|
5801
|
-
),
|
|
5802
|
-
React.createElement(
|
|
5803
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Render whitespace characters visually — None, Selection only, Boundary (leading/trailing), or All' },
|
|
5804
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Whitespace'),
|
|
5805
|
-
React.createElement(
|
|
5806
|
-
'select', {
|
|
5807
|
-
value: editorPrefs.renderWhitespace || 'none',
|
|
5808
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { renderWhitespace: e.target.value }); }); }
|
|
5809
|
-
},
|
|
5810
|
-
React.createElement('option', { value: 'none' }, 'None'),
|
|
5811
|
-
React.createElement('option', { value: 'selection' }, 'Selection'),
|
|
5812
|
-
React.createElement('option', { value: 'boundary' }, 'Boundary'),
|
|
5813
|
-
React.createElement('option', { value: 'all' }, 'All')
|
|
5814
|
-
)
|
|
5815
|
-
),
|
|
5816
|
-
React.createElement(
|
|
5817
|
-
'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' },
|
|
5818
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Minimap'),
|
|
5819
|
-
React.createElement('input', {
|
|
5820
|
-
type: 'checkbox',
|
|
5821
|
-
className: 'ide-settings-checkbox',
|
|
5822
|
-
checked: !!(editorPrefs.minimap),
|
|
5823
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { minimap: v }); }); }
|
|
5824
|
-
})
|
|
5825
|
-
),
|
|
5826
|
-
React.createElement(
|
|
5827
|
-
'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' },
|
|
5828
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Scroll past end'),
|
|
5829
|
-
React.createElement('input', {
|
|
5830
|
-
type: 'checkbox',
|
|
5831
|
-
className: 'ide-settings-checkbox',
|
|
5832
|
-
checked: !!(editorPrefs.scrollBeyondLastLine),
|
|
5833
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { scrollBeyondLastLine: v }); }); }
|
|
5834
|
-
})
|
|
5835
|
-
),
|
|
5836
|
-
React.createElement(
|
|
5837
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Colorize matching bracket pairs with distinct colors to make nesting easier to read' },
|
|
5838
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Bracket colors'),
|
|
5839
|
-
React.createElement('input', {
|
|
5840
|
-
type: 'checkbox',
|
|
5841
|
-
className: 'ide-settings-checkbox',
|
|
5842
|
-
checked: !!(editorPrefs.bracketPairColorization),
|
|
5843
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { bracketPairColorization: v }); }); }
|
|
5844
|
-
})
|
|
5845
|
-
),
|
|
5846
|
-
React.createElement(
|
|
5847
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Enable Vim keybindings (Normal/Insert/Visual modes). Press Escape to return to Normal mode.' },
|
|
5848
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Vim mode'),
|
|
5849
|
-
React.createElement('input', {
|
|
5850
|
-
type: 'checkbox',
|
|
5851
|
-
className: 'ide-settings-checkbox',
|
|
5852
|
-
checked: !!(editorPrefs.vimMode),
|
|
5853
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { vimMode: v }); }); }
|
|
5854
|
-
})
|
|
5855
|
-
),
|
|
5856
|
-
React.createElement(
|
|
5857
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'When to insert a matching closing bracket automatically' },
|
|
5858
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Auto-close brackets'),
|
|
5859
|
-
React.createElement(
|
|
5860
|
-
'select', {
|
|
5861
|
-
value: editorPrefs.autoClosingBrackets || 'always',
|
|
5862
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { autoClosingBrackets: e.target.value }); }); }
|
|
5863
|
-
},
|
|
5864
|
-
React.createElement('option', { value: 'always' }, 'Always'),
|
|
5865
|
-
React.createElement('option', { value: 'languageDefined' }, 'Per language rules'),
|
|
5866
|
-
React.createElement('option', { value: 'beforeWhitespace' }, 'Only before whitespace'),
|
|
5867
|
-
React.createElement('option', { value: 'never' }, 'Never')
|
|
5868
|
-
)
|
|
5869
|
-
),
|
|
5870
|
-
React.createElement(
|
|
5871
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'When to insert a matching closing quote automatically' },
|
|
5872
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Auto-close quotes'),
|
|
5873
|
-
React.createElement(
|
|
5874
|
-
'select', {
|
|
5875
|
-
value: editorPrefs.autoClosingQuotes || 'always',
|
|
5876
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { autoClosingQuotes: e.target.value }); }); }
|
|
5877
|
-
},
|
|
5878
|
-
React.createElement('option', { value: 'always' }, 'Always'),
|
|
5879
|
-
React.createElement('option', { value: 'languageDefined' }, 'Per language rules'),
|
|
5880
|
-
React.createElement('option', { value: 'beforeWhitespace' }, 'Only before whitespace'),
|
|
5881
|
-
React.createElement('option', { value: 'never' }, 'Never')
|
|
5882
|
-
)
|
|
5883
|
-
),
|
|
5884
|
-
React.createElement(
|
|
5885
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'What to highlight on the current editor line' },
|
|
5886
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Line highlight'),
|
|
5887
|
-
React.createElement(
|
|
5888
|
-
'select', {
|
|
5889
|
-
value: editorPrefs.renderLineHighlight || 'none',
|
|
5890
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { renderLineHighlight: e.target.value }); }); }
|
|
5891
|
-
},
|
|
5892
|
-
React.createElement('option', { value: 'none' }, 'None'),
|
|
5893
|
-
React.createElement('option', { value: 'gutter' }, 'Line number only'),
|
|
5894
|
-
React.createElement('option', { value: 'line' }, 'Current line background'),
|
|
5895
|
-
React.createElement('option', { value: 'all' }, 'Line number + background')
|
|
5896
|
-
)
|
|
5897
|
-
),
|
|
5898
|
-
React.createElement(
|
|
5899
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Shape of the text cursor in the editor' },
|
|
5900
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Cursor style'),
|
|
5901
|
-
React.createElement(
|
|
5902
|
-
'select', {
|
|
5903
|
-
value: editorPrefs.cursorStyle || 'line',
|
|
5904
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { cursorStyle: e.target.value }); }); }
|
|
5905
|
-
},
|
|
5906
|
-
React.createElement('option', { value: 'line' }, 'Line (|)'),
|
|
5907
|
-
React.createElement('option', { value: 'block' }, 'Block (filled)'),
|
|
5908
|
-
React.createElement('option', { value: 'underline' }, 'Underline (_)'),
|
|
5909
|
-
React.createElement('option', { value: 'line-thin' }, 'Line thin'),
|
|
5910
|
-
React.createElement('option', { value: 'block-outline' }, 'Block outline'),
|
|
5911
|
-
React.createElement('option', { value: 'underline-thin' }, 'Underline thin')
|
|
5912
|
-
)
|
|
5913
|
-
),
|
|
5914
|
-
React.createElement(
|
|
5915
|
-
'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)' },
|
|
5916
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Cursor blinking'),
|
|
5917
|
-
React.createElement(
|
|
5918
|
-
'select', {
|
|
5919
|
-
value: editorPrefs.cursorBlinking || 'blink',
|
|
5920
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { cursorBlinking: e.target.value }); }); }
|
|
5921
|
-
},
|
|
5922
|
-
React.createElement('option', { value: 'blink' }, 'Blink (on/off)'),
|
|
5923
|
-
React.createElement('option', { value: 'smooth' }, 'Smooth (fade)'),
|
|
5924
|
-
React.createElement('option', { value: 'phase' }, 'Phase (offset fade)'),
|
|
5925
|
-
React.createElement('option', { value: 'expand' }, 'Expand (grow/shrink)'),
|
|
5926
|
-
React.createElement('option', { value: 'solid' }, 'Solid (no blink)')
|
|
5927
|
-
)
|
|
5928
|
-
),
|
|
5929
|
-
React.createElement(
|
|
5930
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Show collapse arrows next to foldable regions (functions, classes, blocks)' },
|
|
5931
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Code folding'),
|
|
5932
|
-
React.createElement('input', {
|
|
5933
|
-
type: 'checkbox',
|
|
5934
|
-
className: 'ide-settings-checkbox',
|
|
5935
|
-
checked: editorPrefs.folding !== false,
|
|
5936
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { folding: v }); }); }
|
|
5937
|
-
})
|
|
5938
|
-
),
|
|
5939
|
-
React.createElement(
|
|
5940
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Animate scrolling instead of jumping instantly' },
|
|
5941
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Smooth scrolling'),
|
|
5942
|
-
React.createElement('input', {
|
|
5943
|
-
type: 'checkbox',
|
|
5944
|
-
className: 'ide-settings-checkbox',
|
|
5945
|
-
checked: !!(editorPrefs.smoothScrolling),
|
|
5946
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { smoothScrolling: v }); }); }
|
|
5947
|
-
})
|
|
5948
|
-
),
|
|
5949
|
-
React.createElement(
|
|
5950
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Hold Ctrl (or Cmd) and scroll the mouse wheel to zoom the font size' },
|
|
5951
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Ctrl+scroll to zoom'),
|
|
5952
|
-
React.createElement('input', {
|
|
5953
|
-
type: 'checkbox',
|
|
5954
|
-
className: 'ide-settings-checkbox',
|
|
5955
|
-
checked: !!(editorPrefs.mouseWheelZoom),
|
|
5956
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { mouseWheelZoom: v }); }); }
|
|
5957
|
-
})
|
|
5958
|
-
),
|
|
5959
|
-
|
|
5960
|
-
/* ── Behaviour ───────────────────────────────── */
|
|
5961
|
-
React.createElement('div', { className: 'ide-settings-section-header' }, 'Behaviour'),
|
|
5962
|
-
React.createElement(
|
|
5963
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'How aggressively the editor re-indents lines as you type' },
|
|
5964
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Auto indent'),
|
|
5965
|
-
React.createElement(
|
|
5966
|
-
'select', {
|
|
5967
|
-
value: editorPrefs.autoIndent || 'full',
|
|
5968
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { autoIndent: e.target.value }); }); }
|
|
5969
|
-
},
|
|
5970
|
-
React.createElement('option', { value: 'none' }, 'None (disabled)'),
|
|
5971
|
-
React.createElement('option', { value: 'keep' }, 'Keep current level'),
|
|
5972
|
-
React.createElement('option', { value: 'brackets' }, 'Indent on { and ['),
|
|
5973
|
-
React.createElement('option', { value: 'advanced' }, 'Language indent rules'),
|
|
5974
|
-
React.createElement('option', { value: 'full' }, 'Full (language grammar)')
|
|
5975
|
-
)
|
|
5976
|
-
),
|
|
5977
|
-
React.createElement(
|
|
5978
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Whether pressing Enter accepts the highlighted autocomplete suggestion' },
|
|
5979
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Accept suggestion on Enter'),
|
|
5980
|
-
React.createElement(
|
|
5981
|
-
'select', {
|
|
5982
|
-
value: editorPrefs.acceptSuggestionOnEnter || 'on',
|
|
5983
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { acceptSuggestionOnEnter: e.target.value }); }); }
|
|
5984
|
-
},
|
|
5985
|
-
React.createElement('option', { value: 'on' }, 'Always'),
|
|
5986
|
-
React.createElement('option', { value: 'smart' }, 'Only when navigated (↑↓)'),
|
|
5987
|
-
React.createElement('option', { value: 'off' }, 'Never (Tab only)')
|
|
5988
|
-
)
|
|
5989
|
-
),
|
|
5990
|
-
React.createElement(
|
|
5991
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Suggest completions based on words already present in open files' },
|
|
5992
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Word-based suggestions'),
|
|
5993
|
-
React.createElement(
|
|
5994
|
-
'select', {
|
|
5995
|
-
value: editorPrefs.wordBasedSuggestions || 'matchingDocuments',
|
|
5996
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { wordBasedSuggestions: e.target.value }); }); }
|
|
5997
|
-
},
|
|
5998
|
-
React.createElement('option', { value: 'off' }, 'Off'),
|
|
5999
|
-
React.createElement('option', { value: 'currentDocument' }, 'Current file only'),
|
|
6000
|
-
React.createElement('option', { value: 'matchingDocuments' }, 'Same language files'),
|
|
6001
|
-
React.createElement('option', { value: 'allDocuments' }, 'All open files')
|
|
6002
|
-
)
|
|
6003
|
-
),
|
|
6004
|
-
React.createElement(
|
|
6005
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Re-indent and auto-close blocks as you type (e.g. after pressing Enter inside {})' },
|
|
6006
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Format on type'),
|
|
6007
|
-
React.createElement('input', {
|
|
6008
|
-
type: 'checkbox',
|
|
6009
|
-
className: 'ide-settings-checkbox',
|
|
6010
|
-
checked: editorPrefs.formatOnType === true,
|
|
6011
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { formatOnType: v }); }); }
|
|
6012
|
-
})
|
|
6013
|
-
),
|
|
6014
|
-
React.createElement(
|
|
6015
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Format the file before every save — RuboCop -A for Ruby, Prettier for JS/JSX/CSS/HTML/Markdown' },
|
|
6016
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Format on save'),
|
|
6017
|
-
React.createElement('input', {
|
|
6018
|
-
type: 'checkbox',
|
|
6019
|
-
className: 'ide-settings-checkbox',
|
|
6020
|
-
checked: editorPrefs.formatOnSave === true,
|
|
6021
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { formatOnSave: v }); }); }
|
|
6022
|
-
})
|
|
6023
|
-
),
|
|
6024
|
-
React.createElement(
|
|
6025
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Show autocomplete suggestions while typing (not just on trigger characters like .)' },
|
|
6026
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Quick suggestions'),
|
|
6027
|
-
React.createElement('input', {
|
|
6028
|
-
type: 'checkbox',
|
|
6029
|
-
className: 'ide-settings-checkbox',
|
|
6030
|
-
checked: editorPrefs.quickSuggestions !== false,
|
|
6031
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { quickSuggestions: v }); }); }
|
|
6032
|
-
})
|
|
6033
|
-
),
|
|
6034
|
-
|
|
6035
|
-
/* ── Formatting (Prettier) ───────────────────── */
|
|
6036
|
-
React.createElement('div', { className: 'ide-settings-section-header' }, 'Formatting'),
|
|
6037
|
-
React.createElement(
|
|
6038
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Prettier: maximum line length before wrapping (40–200)' },
|
|
6039
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Print width'),
|
|
6040
|
-
React.createElement('input', {
|
|
6041
|
-
key: String(editorPrefs.prettierPrintWidth != null ? editorPrefs.prettierPrintWidth : 80),
|
|
6042
|
-
type: 'number', min: '40', max: '200', step: '1',
|
|
6043
|
-
className: 'ide-settings-input',
|
|
6044
|
-
defaultValue: editorPrefs.prettierPrintWidth != null ? editorPrefs.prettierPrintWidth : 80,
|
|
6045
|
-
onChange: function(e) {
|
|
6046
|
-
var v = parseInt(e.target.value, 10);
|
|
6047
|
-
if (v >= 40 && v <= 200) setEditorPrefs(function(p) { return Object.assign({}, p, { prettierPrintWidth: v }); });
|
|
6048
|
-
},
|
|
6049
|
-
onBlur: function(e) {
|
|
6050
|
-
var v = parseInt(e.target.value, 10);
|
|
6051
|
-
if (isNaN(v) || v < 40 || v > 200) e.target.value = String(editorPrefs.prettierPrintWidth != null ? editorPrefs.prettierPrintWidth : 80);
|
|
6052
|
-
}
|
|
6053
|
-
})
|
|
6054
|
-
),
|
|
6055
|
-
React.createElement(
|
|
6056
|
-
'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' },
|
|
6057
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Trailing commas'),
|
|
6058
|
-
React.createElement(
|
|
6059
|
-
'select', {
|
|
6060
|
-
value: editorPrefs.prettierTrailingComma || 'all',
|
|
6061
|
-
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { prettierTrailingComma: e.target.value }); }); }
|
|
6062
|
-
},
|
|
6063
|
-
React.createElement('option', { value: 'all' }, 'All'),
|
|
6064
|
-
React.createElement('option', { value: 'es5' }, 'ES5'),
|
|
6065
|
-
React.createElement('option', { value: 'none' }, 'None')
|
|
6066
|
-
)
|
|
6067
|
-
),
|
|
6068
|
-
React.createElement(
|
|
6069
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Prettier: add semicolons at the end of statements' },
|
|
6070
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Semicolons'),
|
|
6071
|
-
React.createElement('input', {
|
|
6072
|
-
type: 'checkbox',
|
|
6073
|
-
className: 'ide-settings-checkbox',
|
|
6074
|
-
checked: editorPrefs.prettierSemi !== false,
|
|
6075
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { prettierSemi: v }); }); }
|
|
6076
|
-
})
|
|
6077
|
-
),
|
|
6078
|
-
React.createElement(
|
|
6079
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: "Prettier: use single quotes instead of double quotes for strings" },
|
|
6080
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Single quotes'),
|
|
6081
|
-
React.createElement('input', {
|
|
6082
|
-
type: 'checkbox',
|
|
6083
|
-
className: 'ide-settings-checkbox',
|
|
6084
|
-
checked: !!editorPrefs.prettierSingleQuote,
|
|
6085
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { prettierSingleQuote: v }); }); }
|
|
6086
|
-
})
|
|
6087
|
-
),
|
|
6088
|
-
React.createElement(
|
|
6089
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Prettier: add spaces inside object literal braces, e.g. { a: 1 } vs {a: 1}' },
|
|
6090
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Bracket spacing'),
|
|
6091
|
-
React.createElement('input', {
|
|
6092
|
-
type: 'checkbox',
|
|
6093
|
-
className: 'ide-settings-checkbox',
|
|
6094
|
-
checked: editorPrefs.prettierBracketSpacing !== false,
|
|
6095
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { prettierBracketSpacing: v }); }); }
|
|
6096
|
-
})
|
|
6097
|
-
),
|
|
6098
|
-
|
|
6099
|
-
/* ── Interface ───────────────────────────────── */
|
|
6100
|
-
React.createElement('div', { className: 'ide-settings-section-header' }, 'Interface'),
|
|
6101
|
-
React.createElement(
|
|
6102
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Automatically scroll the file explorer to reveal and highlight the file you are editing' },
|
|
6103
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Explorer follows active file'),
|
|
6104
|
-
React.createElement('input', {
|
|
6105
|
-
type: 'checkbox',
|
|
6106
|
-
className: 'ide-settings-checkbox',
|
|
6107
|
-
checked: !!(editorPrefs.autoRevealInExplorer),
|
|
6108
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { autoRevealInExplorer: v }); }); }
|
|
6109
|
-
})
|
|
6110
|
-
),
|
|
6111
|
-
React.createElement(
|
|
6112
|
-
'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' },
|
|
6113
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Explorer type-ahead'),
|
|
6114
|
-
React.createElement('input', {
|
|
6115
|
-
type: 'checkbox',
|
|
6116
|
-
className: 'ide-settings-checkbox',
|
|
6117
|
-
checked: !!(editorPrefs.fileTreeTypeahead !== false),
|
|
6118
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { fileTreeTypeahead: v }); }); }
|
|
6119
|
-
})
|
|
6120
|
-
),
|
|
6121
|
-
React.createElement(
|
|
6122
|
-
'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' },
|
|
6123
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Show dotfiles'),
|
|
6124
|
-
React.createElement('input', {
|
|
6125
|
-
type: 'checkbox',
|
|
6126
|
-
className: 'ide-settings-checkbox',
|
|
6127
|
-
checked: !!(editorPrefs.showDotFiles),
|
|
6128
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { showDotFiles: v }); }); }
|
|
6129
|
-
})
|
|
6130
|
-
),
|
|
6131
|
-
React.createElement(
|
|
6132
|
-
'label', { className: 'ide-settings-row ide-settings-row-half', title: 'Scroll: tabs overflow horizontally with a scrollbar; Wrap: tabs flow onto multiple rows' },
|
|
6133
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Tab bar layout'),
|
|
6134
|
-
React.createElement(
|
|
6135
|
-
'select', {
|
|
6136
|
-
value: editorPrefs.tabDisplayMode || 'scroll',
|
|
6137
|
-
onChange: function(e) { var v = e.target.value; setEditorPrefs(function(p) { return Object.assign({}, p, { tabDisplayMode: v }); }); }
|
|
6138
|
-
},
|
|
6139
|
-
React.createElement('option', { value: 'scroll' }, 'Scroll'),
|
|
6140
|
-
React.createElement('option', { value: 'wrap' }, 'Wrap (multi-row)')
|
|
6141
|
-
)
|
|
6142
|
-
),
|
|
6143
|
-
React.createElement(
|
|
6144
|
-
'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' },
|
|
6145
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Quick Open: show folders'),
|
|
6146
|
-
React.createElement('input', {
|
|
6147
|
-
type: 'checkbox',
|
|
6148
|
-
className: 'ide-settings-checkbox',
|
|
6149
|
-
checked: !!(editorPrefs.quickOpenShowFolders),
|
|
6150
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { quickOpenShowFolders: v }); }); }
|
|
6151
|
-
})
|
|
6152
|
-
),
|
|
6153
|
-
React.createElement(
|
|
6154
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Hide toolbar button labels and show only icons, giving more horizontal space' },
|
|
6155
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Toolbar: icons only'),
|
|
6156
|
-
React.createElement('input', {
|
|
6157
|
-
type: 'checkbox',
|
|
6158
|
-
className: 'ide-settings-checkbox',
|
|
6159
|
-
// The stored preference, not the derived value: at a
|
|
6160
|
-
// narrow width the box would otherwise show as checked
|
|
6161
|
-
// and unchecking it would appear to do nothing.
|
|
6162
|
-
checked: !!(editorPrefs.toolbarIconOnly),
|
|
6163
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { toolbarIconOnly: v }); }); }
|
|
6164
|
-
})
|
|
6165
|
-
),
|
|
6166
|
-
|
|
6167
|
-
React.createElement(
|
|
6168
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Keep the search/replace text when switching between files in the editor' },
|
|
6169
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Persist find state across files'),
|
|
6170
|
-
React.createElement('input', {
|
|
6171
|
-
type: 'checkbox',
|
|
6172
|
-
className: 'ide-settings-checkbox',
|
|
6173
|
-
checked: editorPrefs.persistFindState !== false,
|
|
6174
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { persistFindState: v }); }); }
|
|
6175
|
-
})
|
|
6176
|
-
),
|
|
6177
|
-
React.createElement(
|
|
6178
|
-
'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.' },
|
|
6179
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Restore tabs on branch switch'),
|
|
6180
|
-
React.createElement('input', {
|
|
6181
|
-
type: 'checkbox',
|
|
6182
|
-
className: 'ide-settings-checkbox',
|
|
6183
|
-
checked: editorPrefs.branchStateRestore !== false,
|
|
6184
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { branchStateRestore: v }); }); }
|
|
6185
|
-
})
|
|
6186
|
-
),
|
|
6187
|
-
|
|
6188
|
-
React.createElement(
|
|
6189
|
-
'label', { className: 'ide-settings-row ide-settings-row-check', title: 'Show the verb and path that route to each controller action after its def line, and mark public actions nothing routes to' },
|
|
6190
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Controller route hints'),
|
|
6191
|
-
React.createElement('input', {
|
|
6192
|
-
type: 'checkbox',
|
|
6193
|
-
className: 'ide-settings-checkbox',
|
|
6194
|
-
checked: editorPrefs.routeHints !== false,
|
|
6195
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { routeHints: v }); }); }
|
|
6196
|
-
})
|
|
6197
|
-
),
|
|
6198
|
-
|
|
6199
|
-
/* ── RuboCop ─────────────────────────────────── */
|
|
6200
|
-
React.createElement('div', { className: 'ide-settings-section-header' }, 'RuboCop'),
|
|
6201
|
-
React.createElement(
|
|
6202
|
-
'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' },
|
|
6203
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Enable RuboCop linting'),
|
|
6204
|
-
React.createElement('input', {
|
|
6205
|
-
type: 'checkbox',
|
|
6206
|
-
className: 'ide-settings-checkbox',
|
|
6207
|
-
checked: editorPrefs.rubocopLintEnabled !== false,
|
|
6208
|
-
onChange: function(e) { var v = e.target.checked; setEditorPrefs(function(p) { return Object.assign({}, p, { rubocopLintEnabled: v }); }); }
|
|
6209
|
-
})
|
|
6210
|
-
),
|
|
6211
|
-
rubocopAvailable && rubocopConfigPath ? React.createElement(
|
|
6212
|
-
'div', { className: 'ide-settings-row ide-settings-row-link' },
|
|
6213
|
-
React.createElement('span', { className: 'ide-settings-label' }, 'Config file'),
|
|
6214
|
-
React.createElement(
|
|
6215
|
-
'button', {
|
|
6216
|
-
type: 'button',
|
|
6217
|
-
className: 'ide-settings-config-link',
|
|
6218
|
-
title: 'Open ' + rubocopConfigPath,
|
|
6219
|
-
onClick: function() { handleSelectFile(rubocopConfigPath, rubocopConfigPath.split('/').pop()); }
|
|
6220
|
-
},
|
|
6221
|
-
React.createElement('i', { className: 'fas fa-file-alt', style: { marginRight: 5 } }),
|
|
6222
|
-
rubocopConfigPath
|
|
6223
|
-
)
|
|
6224
|
-
) : null,
|
|
6225
|
-
React.createElement(
|
|
6226
|
-
'button',
|
|
6227
|
-
{
|
|
6228
|
-
className: 'ide-settings-reset-btn',
|
|
6229
|
-
type: 'button',
|
|
6230
|
-
title: 'Restore every editor preference on this page to its default',
|
|
6231
|
-
onClick: function() { setEditorPrefs(Object.assign({}, DEFAULT_EDITOR_PREFS)); }
|
|
6232
|
-
},
|
|
6233
|
-
React.createElement('i', { className: 'fas fa-undo', style: { marginRight: 6 } }),
|
|
6234
|
-
'Reset to defaults'
|
|
6235
|
-
)
|
|
6236
|
-
)
|
|
6237
|
-
);
|
|
6238
6116
|
} else if (pActiveTab.isDiff) {
|
|
6239
6117
|
var _t = editorPrefs.theme || 'vs-dark';
|
|
6240
6118
|
var isDiffDark = _t !== 'vs' && _t !== 'hc-light' && _t !== 'github-light';
|
|
@@ -6283,9 +6161,12 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6283
6161
|
return React.createElement(
|
|
6284
6162
|
React.Fragment,
|
|
6285
6163
|
{ key: pane.id },
|
|
6286
|
-
idx === 1 && isSplit && React.createElement(
|
|
6287
|
-
|
|
6288
|
-
|
|
6164
|
+
idx === 1 && isSplit && React.createElement((window.Gutter || Gutter), {
|
|
6165
|
+
orientation: 'vertical',
|
|
6166
|
+
className: 'ide-gutter-pane',
|
|
6167
|
+
label: "Resize editor groups",
|
|
6168
|
+
active: activeResizeMode === 'pane',
|
|
6169
|
+
onDragStart: startPaneResize
|
|
6289
6170
|
}),
|
|
6290
6171
|
React.createElement(
|
|
6291
6172
|
"div",
|
|
@@ -6293,10 +6174,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6293
6174
|
className: "ide-pane " + (isFocused ? 'focused' : '') + " " + (isDropTarget ? 'drop-target' : ''),
|
|
6294
6175
|
style: { flexBasis: flexBasis, flexShrink: 0, flexGrow: 0, display: 'flex', flexDirection: 'column', minWidth: 0 },
|
|
6295
6176
|
onClickCapture: function (e) {
|
|
6296
|
-
// Do not steal click events from controls inside the Settings tab.
|
|
6297
|
-
// Focusing the pane in capture phase can rerender before checkbox
|
|
6298
|
-
// change events are processed, making toggles appear stuck.
|
|
6299
|
-
if (e.target && e.target.closest && e.target.closest('.ide-settings-tab-content')) return;
|
|
6300
6177
|
return TabManager.focusPane(pane.id);
|
|
6301
6178
|
},
|
|
6302
6179
|
onDragOver: function (e) {
|
|
@@ -6438,10 +6315,26 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6438
6315
|
);
|
|
6439
6316
|
})
|
|
6440
6317
|
),
|
|
6318
|
+
showLogPanel && !zenMode && React.createElement((window.Gutter || Gutter), {
|
|
6319
|
+
orientation: 'horizontal',
|
|
6320
|
+
label: "Resize log drawer",
|
|
6321
|
+
active: activeResizeMode === 'log',
|
|
6322
|
+
onDragStart: startDrawerResize('log', logHeight, 'mbeditorLogHeight')
|
|
6323
|
+
}),
|
|
6441
6324
|
showLogPanel && !zenMode && React.createElement(window.LogPanel || LogPanel, {
|
|
6325
|
+
height: logHeight,
|
|
6442
6326
|
onClose: function () { setShowLogPanel(false); }
|
|
6443
6327
|
}),
|
|
6328
|
+
// With the drawer closed, the gutter above the status bar is a handle:
|
|
6329
|
+
// drag it up to open Problems at the dragged height.
|
|
6330
|
+
showProblemsPanel && !zenMode && React.createElement((window.Gutter || Gutter), {
|
|
6331
|
+
orientation: 'horizontal',
|
|
6332
|
+
label: "Resize problems drawer",
|
|
6333
|
+
active: activeResizeMode === 'problems',
|
|
6334
|
+
onDragStart: startDrawerResize('problems', problemsHeight, 'mbeditorProblemsHeight')
|
|
6335
|
+
}),
|
|
6444
6336
|
showProblemsPanel && !zenMode && React.createElement(window.ProblemsPanel || ProblemsPanel, {
|
|
6337
|
+
height: problemsHeight,
|
|
6445
6338
|
onClose: function () { setShowProblemsPanel(false); },
|
|
6446
6339
|
onOpenFile: function (path, line, col) {
|
|
6447
6340
|
handleSelectFile(path, path.split('/').pop(), line, col);
|
|
@@ -6456,14 +6349,20 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6456
6349
|
),
|
|
6457
6350
|
|
|
6458
6351
|
// Right-side Git panel (children of ide-body, alongside sidebar and ide-main)
|
|
6459
|
-
showGitPanel && !zenMode && React.createElement(
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
"aria-label": "Resize git panel"
|
|
6352
|
+
!showGitPanel && !zenMode && !showModelGraphView && React.createElement((window.Gutter || Gutter), {
|
|
6353
|
+
orientation: 'vertical',
|
|
6354
|
+
label: "Drag left to open the git panel",
|
|
6355
|
+
snapDirection: -1,
|
|
6356
|
+
onSnap: function () { setShowGitPanel(true); }
|
|
6465
6357
|
}),
|
|
6466
|
-
showGitPanel && !zenMode && React.createElement(
|
|
6358
|
+
showGitPanel && !zenMode && !showModelGraphView && React.createElement((window.Gutter || Gutter), {
|
|
6359
|
+
orientation: 'vertical',
|
|
6360
|
+
className: 'ide-gutter-gitpanel',
|
|
6361
|
+
label: "Resize git panel",
|
|
6362
|
+
active: activeResizeMode === 'gitpanel',
|
|
6363
|
+
onDragStart: startGitPanelResize
|
|
6364
|
+
}),
|
|
6365
|
+
showGitPanel && !zenMode && !showModelGraphView && React.createElement(
|
|
6467
6366
|
"div",
|
|
6468
6367
|
{ className: "ide-git-right-panel", style: { width: gitPanelWidth + "px" } },
|
|
6469
6368
|
React.createElement(window.GitPanel || GitPanel, {
|
|
@@ -6479,6 +6378,19 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6479
6378
|
})
|
|
6480
6379
|
),
|
|
6481
6380
|
),
|
|
6381
|
+
// ponytail: opens on release, no live preview; track the drag if that grates.
|
|
6382
|
+
!showProblemsPanel && !zenMode && !showModelGraphView && React.createElement((window.Gutter || Gutter), {
|
|
6383
|
+
orientation: 'horizontal',
|
|
6384
|
+
className: 'ide-gutter-drawer-open',
|
|
6385
|
+
label: "Drag up to open Problems",
|
|
6386
|
+
snapThreshold: 40,
|
|
6387
|
+
onSnap: function (dragged) {
|
|
6388
|
+
var h = Math.max(DRAWER_MIN_HEIGHT, dragged);
|
|
6389
|
+
try { window.localStorage.setItem('mbeditorProblemsHeight', String(h)); } catch (err) {}
|
|
6390
|
+
setProblemsHeight(h);
|
|
6391
|
+
setShowProblemsPanel(true);
|
|
6392
|
+
}
|
|
6393
|
+
}),
|
|
6482
6394
|
React.createElement(
|
|
6483
6395
|
"div",
|
|
6484
6396
|
{ className: "ide-statusbar" },
|
|
@@ -6515,6 +6427,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6515
6427
|
React.createElement("i", { className: "fas fa-exclamation-triangle statusbar-problems-warning-icon" }),
|
|
6516
6428
|
React.createElement("span", { className: "statusbar-problems-count" }, problemCounts.warnings)
|
|
6517
6429
|
),
|
|
6430
|
+
migrationsPending && React.createElement(
|
|
6431
|
+
"div",
|
|
6432
|
+
{
|
|
6433
|
+
className: "statusbar-migration",
|
|
6434
|
+
title: "Pending migrations \u2014 run `rails db:migrate`. Editing still works in the meantime."
|
|
6435
|
+
},
|
|
6436
|
+
React.createElement("i", { className: "fas fa-database" }),
|
|
6437
|
+
" Migrations pending"
|
|
6438
|
+
),
|
|
6518
6439
|
!serverOnline && (function () {
|
|
6519
6440
|
var dirtyCount = state.panes.reduce(function (acc, p) {
|
|
6520
6441
|
return acc + p.tabs.filter(function (t) { return t.dirty; }).length;
|
|
@@ -6574,6 +6495,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6574
6495
|
},
|
|
6575
6496
|
React.createElement("i", { className: "fas fa-paragraph" })
|
|
6576
6497
|
),
|
|
6498
|
+
React.createElement(CursorPosition, null),
|
|
6577
6499
|
activeEOL && React.createElement(
|
|
6578
6500
|
"button",
|
|
6579
6501
|
{
|
|
@@ -6786,6 +6708,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6786
6708
|
"div",
|
|
6787
6709
|
{
|
|
6788
6710
|
className: "context-menu",
|
|
6711
|
+
ref: contextMenuRef,
|
|
6789
6712
|
style: { left: contextMenu.x, top: contextMenu.y },
|
|
6790
6713
|
onClick: function (e) {
|
|
6791
6714
|
return e.stopPropagation();
|
|
@@ -6830,16 +6753,16 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6830
6753
|
return handleContextMenuAction('delete');
|
|
6831
6754
|
} },
|
|
6832
6755
|
React.createElement("i", { className: "far fa-trash-alt context-menu-icon" }),
|
|
6833
|
-
"
|
|
6756
|
+
" " + deleteMenuLabel(contextMenu.node)
|
|
6834
6757
|
),
|
|
6835
6758
|
React.createElement("div", { className: "context-menu-divider" }),
|
|
6836
|
-
contextMenu.node &&
|
|
6759
|
+
contextMenu.node && React.createElement(
|
|
6837
6760
|
"div",
|
|
6838
6761
|
{ className: "context-menu-item", onClick: function () {
|
|
6839
6762
|
return handleContextMenuAction('download');
|
|
6840
6763
|
} },
|
|
6841
6764
|
React.createElement("i", { className: "fas fa-download context-menu-icon" }),
|
|
6842
|
-
"
|
|
6765
|
+
" " + downloadMenuLabel(contextMenu.node)
|
|
6843
6766
|
),
|
|
6844
6767
|
React.createElement(
|
|
6845
6768
|
"div",
|
|
@@ -7033,4 +6956,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
7033
6956
|
};
|
|
7034
6957
|
|
|
7035
6958
|
window.MbeditorApp = MbeditorApp;
|
|
6959
|
+
// Test-only: lets the system test seed/inspect the test-result cache directly.
|
|
6960
|
+
window.__mbeditorTestCache = { save: saveCachedTestResult, prefix: TEST_CACHE_PREFIX };
|
|
7036
6961
|
/* TITLE BAR */ /* SIDEBAR */ /* EDITOR AREA */ /* STATUS BAR */ /* Right-click context menu */
|