mbeditor 0.13.1 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +132 -0
- data/app/assets/javascripts/mbeditor/application.js +5 -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/CombinedFileView.js +214 -0
- data/app/assets/javascripts/mbeditor/components/DiffViewer.js +1 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +442 -248
- 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/ImportConflictModal.js +60 -5
- data/app/assets/javascripts/mbeditor/components/LogPanel.js +3 -44
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +1241 -1256
- data/app/assets/javascripts/mbeditor/components/ModelGraph.js +94 -37
- data/app/assets/javascripts/mbeditor/components/PastePlaceModal.js +329 -0
- 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 +18 -1
- data/app/assets/javascripts/mbeditor/components/TabBar.js +67 -98
- data/app/assets/javascripts/mbeditor/editor_plugins.js +705 -323
- data/app/assets/javascripts/mbeditor/file_import.js +13 -15
- data/app/assets/javascripts/mbeditor/file_service.js +48 -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 +672 -249
- data/app/assets/stylesheets/mbeditor/glass.css +174 -0
- data/app/assets/stylesheets/mbeditor/themes.css +93 -33
- 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 +124 -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/ruby_method_ranges_service.rb +120 -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 +31 -10
- data/lib/mbeditor/route_map.rb +5 -0
- data/lib/mbeditor/ruby_lsp_client.rb +82 -14
- data/lib/mbeditor/version.rb +1 -1
- data/lib/mbeditor.rb +1 -0
- data/public/monaco-editor/VERSIONS +1 -1
- data/public/monaco-editor/codicon.ttf +0 -0
- data/public/monaco-editor/css.worker.js +41 -37
- data/public/monaco-editor/editor.worker.js +19 -15
- data/public/monaco-editor/html.worker.js +53 -49
- data/public/monaco-editor/json.worker.js +36 -32
- data/public/monaco-editor/monaco.css +1 -1
- data/public/monaco-editor/monaco.js +388 -301
- data/public/monaco-editor/ts.worker.js +226 -222
- data/vendor/assets/javascripts/axios.min.js +3 -3
- metadata +15 -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__' && !t.isCombinedView; })
|
|
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.
|
|
1494
|
+
var isPlainFile = t.path && !t.isDiff && !t.isCombinedDiff &&
|
|
1495
|
+
!t.isChangelog && !t.isCombinedView && !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.isCombinedView && !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,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1911
2149
|
!path.startsWith('combined-diff://') &&
|
|
1912
2150
|
!path.startsWith('untitled://') &&
|
|
1913
2151
|
!pt.tab.isCombinedDiff &&
|
|
1914
|
-
!pt.tab.
|
|
2152
|
+
!pt.tab.isCombinedView &&
|
|
1915
2153
|
!pt.tab.isImage &&
|
|
1916
2154
|
!pt.tab.isDiff &&
|
|
1917
2155
|
typeof pt.tab.content === 'string';
|
|
@@ -2018,8 +2256,18 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2018
2256
|
useEffect(function () {
|
|
2019
2257
|
var intervalId = setInterval(function () {
|
|
2020
2258
|
if (document.hidden) return;
|
|
2259
|
+
var startedAt = Date.now();
|
|
2021
2260
|
FileService.getTree({ background: true }).then(function (data) {
|
|
2022
|
-
|
|
2261
|
+
var next = data || [];
|
|
2262
|
+
var audit = window.MbeditorAudit;
|
|
2263
|
+
// Stringified once and reused by the updater, so instrumenting the poll
|
|
2264
|
+
// costs no extra pass over the tree.
|
|
2265
|
+
var signature = audit ? JSON.stringify(next) : null;
|
|
2266
|
+
setTreeData(_treeUpdater(next, signature));
|
|
2267
|
+
if (!audit) return;
|
|
2268
|
+
var changed = signature !== _lastPolledTree;
|
|
2269
|
+
_lastPolledTree = signature;
|
|
2270
|
+
audit.rec(audit.EV.TREE_POLL, Date.now() - startedAt, changed ? 1 : 0);
|
|
2023
2271
|
}).catch(function () {}); // silently ignore auto-refresh errors
|
|
2024
2272
|
}, 10000);
|
|
2025
2273
|
return function () { clearInterval(intervalId); };
|
|
@@ -2086,6 +2334,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2086
2334
|
}, [monacoReady]);
|
|
2087
2335
|
|
|
2088
2336
|
var handleSelectFile = function handleSelectFile(path, name, line, col, endCol) {
|
|
2337
|
+
// The one place every reachable "open a file" action funnels through while
|
|
2338
|
+
// the model graph view is up (quick open is the only such surface left —
|
|
2339
|
+
// explorer, search, rails and git panel are all hidden with it).
|
|
2340
|
+
setShowModelGraphView(false);
|
|
2089
2341
|
TabManager.openTab(path, name, line, null, false, col, endCol);
|
|
2090
2342
|
handleNodeSelect({ path: path, name: name || path.split('/').pop(), type: 'file' });
|
|
2091
2343
|
setQuickOpen(false);
|
|
@@ -2141,6 +2393,17 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2141
2393
|
});
|
|
2142
2394
|
};
|
|
2143
2395
|
|
|
2396
|
+
// Drop any pending-reload prompt whose tab is no longer open in any pane.
|
|
2397
|
+
var _prunePendingReloads = function _prunePendingReloads() {
|
|
2398
|
+
EditorStore.setState({
|
|
2399
|
+
pendingReloads: EditorStore.getState().pendingReloads.filter(function (r) {
|
|
2400
|
+
return EditorStore.getState().panes.some(function (p) {
|
|
2401
|
+
return p.tabs.some(function (t) { return t.id === r.tabId; });
|
|
2402
|
+
});
|
|
2403
|
+
})
|
|
2404
|
+
});
|
|
2405
|
+
};
|
|
2406
|
+
|
|
2144
2407
|
var requestCloseTab = function requestCloseTab(paneId, id) {
|
|
2145
2408
|
var pane = state.panes.find(function (p) {
|
|
2146
2409
|
return p.id === paneId;
|
|
@@ -2153,13 +2416,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2153
2416
|
setClosingTabId(id);
|
|
2154
2417
|
} else {
|
|
2155
2418
|
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
|
-
});
|
|
2419
|
+
_prunePendingReloads();
|
|
2163
2420
|
forgetClosedTabs();
|
|
2164
2421
|
}
|
|
2165
2422
|
};
|
|
@@ -2178,6 +2435,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2178
2435
|
}
|
|
2179
2436
|
|
|
2180
2437
|
if (save) {
|
|
2438
|
+
tab = _freshTab(closingPaneId, tab);
|
|
2181
2439
|
if (tab.isUntitled) {
|
|
2182
2440
|
// Save-as converts the scratch tab to a real one (closing the scratch
|
|
2183
2441
|
// tab in the process); a cancelled prompt keeps the tab open.
|
|
@@ -2191,49 +2449,20 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2191
2449
|
});
|
|
2192
2450
|
return;
|
|
2193
2451
|
}
|
|
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);
|
|
2452
|
+
_runSave(tab, function () {
|
|
2201
2453
|
EditorStore.setStatus("Saved", "success");
|
|
2202
2454
|
SearchService.invalidate();
|
|
2203
2455
|
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
2456
|
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
|
-
});
|
|
2457
|
+
_prunePendingReloads();
|
|
2217
2458
|
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
|
-
});
|
|
2459
|
+
}, function () {
|
|
2225
2460
|
setClosingTabId(null);
|
|
2226
2461
|
setClosingPaneId(null);
|
|
2227
2462
|
});
|
|
2228
2463
|
} else {
|
|
2229
2464
|
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
|
-
});
|
|
2465
|
+
_prunePendingReloads();
|
|
2237
2466
|
forgetClosedTabs();
|
|
2238
2467
|
setClosingTabId(null);
|
|
2239
2468
|
setClosingPaneId(null);
|
|
@@ -2308,7 +2537,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2308
2537
|
var input = window.prompt('Save as (path relative to workspace root):', tab.name + '.txt');
|
|
2309
2538
|
if (!input || !input.trim()) return Promise.reject({ cancelled: true });
|
|
2310
2539
|
var newPath = input.trim().replace(/^\/+/, '');
|
|
2540
|
+
var startedAt = Date.now();
|
|
2311
2541
|
return FileService.saveFile(newPath, tab.content).then(function () {
|
|
2542
|
+
var audit = window.MbeditorAudit;
|
|
2543
|
+
if (audit) audit.rec(audit.EV.SAVE, _auditExt(newPath), tab.content.length, Date.now() - startedAt);
|
|
2312
2544
|
noteLocalSave(newPath, tab.content);
|
|
2313
2545
|
SearchService.invalidate();
|
|
2314
2546
|
GitService.fetchStatusLite({ background: true });
|
|
@@ -2333,14 +2565,13 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2333
2565
|
return {
|
|
2334
2566
|
id: p.id,
|
|
2335
2567
|
activeTabId: p.activeTabId,
|
|
2336
|
-
tabs: p.tabs.filter(function(t) { return !t.isCombinedDiff && !t.
|
|
2568
|
+
tabs: p.tabs.filter(function(t) { return !t.isCombinedDiff && !t.isCombinedView && !t.isUntitled; }).map(function (t) {
|
|
2337
2569
|
return {
|
|
2338
2570
|
id: t.id,
|
|
2339
2571
|
path: t.path,
|
|
2340
2572
|
name: t.name,
|
|
2341
2573
|
dirty: t.dirty,
|
|
2342
2574
|
viewState: t.viewState,
|
|
2343
|
-
isSettings: !!t.isSettings,
|
|
2344
2575
|
isChangelog: !!t.isChangelog,
|
|
2345
2576
|
isPreview: !!t.isPreview,
|
|
2346
2577
|
previewFor: t.previewFor || null,
|
|
@@ -2367,7 +2598,11 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2367
2598
|
|
|
2368
2599
|
useEffect(function() {
|
|
2369
2600
|
document.documentElement.setAttribute('data-theme', editorPrefs.theme || 'vs-dark');
|
|
2370
|
-
|
|
2601
|
+
// Glass is a layer over the active theme, not a theme, so it rides on its
|
|
2602
|
+
// own attribute and glass.css derives every colour from the theme tokens.
|
|
2603
|
+
if (editorPrefs.glass) document.documentElement.dataset.glass = '1';
|
|
2604
|
+
else delete document.documentElement.dataset.glass;
|
|
2605
|
+
}, [editorPrefs.theme, editorPrefs.glass]);
|
|
2371
2606
|
|
|
2372
2607
|
useEffect(function() {
|
|
2373
2608
|
EditorStore.setState({ editorPrefs: editorPrefs });
|
|
@@ -2491,7 +2726,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2491
2726
|
});
|
|
2492
2727
|
tabs.forEach(function(t) {
|
|
2493
2728
|
if (Object.keys(deps).length >= RAILS_MAX_RESOURCES) return;
|
|
2494
|
-
if (!t.path || t.path
|
|
2729
|
+
if (!t.path || t.path.startsWith('mbeditor://')) return;
|
|
2495
2730
|
var label = resourceLabelFromPath(t.path);
|
|
2496
2731
|
if (label && !deps[label]) deps[label] = t.path;
|
|
2497
2732
|
});
|
|
@@ -2504,7 +2739,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2504
2739
|
var all = {};
|
|
2505
2740
|
state.panes.forEach(function(p) {
|
|
2506
2741
|
p.tabs.forEach(function(t) {
|
|
2507
|
-
if (!t.path || t.path
|
|
2742
|
+
if (!t.path || t.path.startsWith('mbeditor://')) return;
|
|
2508
2743
|
var label = resourceLabelFromPath(t.path);
|
|
2509
2744
|
if (label) all[label] = true;
|
|
2510
2745
|
});
|
|
@@ -2564,14 +2799,14 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2564
2799
|
|
|
2565
2800
|
// ── Collaboration presence (slice 7) ──────────────────────────────────────
|
|
2566
2801
|
// Only real, openable files belong in presence — virtual tabs (diffs, previews,
|
|
2567
|
-
//
|
|
2802
|
+
// changelog) are reported as "no file" so a peer's chip stays blank
|
|
2568
2803
|
// rather than pointing at something click-to-jump can't open.
|
|
2569
2804
|
var _presenceFileFor = function (tab) {
|
|
2570
2805
|
if (!tab || !tab.path) return null;
|
|
2571
2806
|
var p = tab.path;
|
|
2572
|
-
if (tab.isDiff || tab.isCombinedDiff || tab.isCommitGraph || tab.isPreview || tab.
|
|
2807
|
+
if (tab.isDiff || tab.isCombinedDiff || tab.isCommitGraph || tab.isPreview || tab.isChangelog || tab.isCombinedView) return null;
|
|
2573
2808
|
if (p.indexOf('diff://') === 0 || p.indexOf('combined-diff://') === 0 || p.indexOf('mbeditor://') === 0) return null;
|
|
2574
|
-
if (p.indexOf('::preview') !== -1
|
|
2809
|
+
if (p.indexOf('::preview') !== -1) return null;
|
|
2575
2810
|
return p;
|
|
2576
2811
|
};
|
|
2577
2812
|
var presenceFile = _presenceFileFor(activeTab);
|
|
@@ -2777,14 +3012,13 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2777
3012
|
|
|
2778
3013
|
var collabPeerIds = Object.keys(collabRoster);
|
|
2779
3014
|
|
|
2780
|
-
//
|
|
2781
|
-
//
|
|
2782
|
-
//
|
|
2783
|
-
//
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
var
|
|
2787
|
-
var collabPeerLabels = !toolbarIconOnly && collabPeerIds.length <= COLLAB_LABEL_LIMIT;
|
|
3015
|
+
// One button per peer stopped working past ~4 people: the titlebar cluster
|
|
3016
|
+
// does not shrink or wrap, so it squeezed the search pill to its floor and
|
|
3017
|
+
// pushed Help / Install off the right edge. Fixed-width stack instead: up to
|
|
3018
|
+
// three overlapping avatars, the rest collapse into one "+N" with a popover.
|
|
3019
|
+
var COLLAB_AVATAR_LIMIT = 3;
|
|
3020
|
+
var collabAvatarIds = collabPeerIds.slice(0, COLLAB_AVATAR_LIMIT);
|
|
3021
|
+
var collabOverflowIds = collabPeerIds.slice(COLLAB_AVATAR_LIMIT);
|
|
2788
3022
|
|
|
2789
3023
|
// Colour is minted from a hash before any peer is known, so it has to be
|
|
2790
3024
|
// reconciled against the roster once one exists. Runs on every roster change;
|
|
@@ -2801,9 +3035,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2801
3035
|
// chips sit against the right edge of the titlebar and a left-anchored card
|
|
2802
3036
|
// would run off screen.
|
|
2803
3037
|
var _useStateHover = useState(null);
|
|
2804
|
-
var
|
|
2805
|
-
var
|
|
2806
|
-
var setCollabHover = _useStateHover2[1];
|
|
3038
|
+
var collabHover = _useStateHover[0];
|
|
3039
|
+
var setCollabHover = _useStateHover[1];
|
|
2807
3040
|
|
|
2808
3041
|
var openCollabHover = function (cid, e) {
|
|
2809
3042
|
var r = e.currentTarget.getBoundingClientRect();
|
|
@@ -2813,8 +3046,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2813
3046
|
// Latency and last-seen only need to tick while the card is actually on screen,
|
|
2814
3047
|
// so the interval lives and dies with it. Idle cost stays zero.
|
|
2815
3048
|
var _useStateHoverTick = useState(0);
|
|
2816
|
-
var
|
|
2817
|
-
var setCollabHoverTick = _useStateHoverTick2[1];
|
|
3049
|
+
var setCollabHoverTick = _useStateHoverTick[1];
|
|
2818
3050
|
useEffect(function () {
|
|
2819
3051
|
if (!collabHover) return;
|
|
2820
3052
|
var id = setInterval(function () { setCollabHoverTick(function (n) { return n + 1; }); }, 1000);
|
|
@@ -2845,20 +3077,60 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2845
3077
|
handleSelectFile(followedFile, followedFile.split('/').pop());
|
|
2846
3078
|
}, [followedClientId, followedFile]);
|
|
2847
3079
|
|
|
3080
|
+
// Shared by the avatar stack and the overflow popover so "which peer, what
|
|
3081
|
+
// file, are we following them" is computed once per id instead of twice.
|
|
3082
|
+
var collabPeerInfo = function (cid) {
|
|
3083
|
+
var peer = collabRoster[cid];
|
|
3084
|
+
var file = peer.current_file;
|
|
3085
|
+
return {
|
|
3086
|
+
name: peer.name || 'Anonymous',
|
|
3087
|
+
colour: peer.colour || '#888888',
|
|
3088
|
+
file: file,
|
|
3089
|
+
following: followedClientId === cid,
|
|
3090
|
+
elsewhere: file !== presenceFile
|
|
3091
|
+
};
|
|
3092
|
+
};
|
|
3093
|
+
|
|
3094
|
+
// The "+N" overflow popover. A plain onMouseLeave that closes immediately
|
|
3095
|
+
// makes it uncrossable — the pointer has to travel off the button and onto
|
|
3096
|
+
// the popover — so the close is delayed and cancelled by either one's
|
|
3097
|
+
// onMouseEnter. Click still toggles it directly, which is what makes it
|
|
3098
|
+
// reachable without a mouse at all.
|
|
3099
|
+
// The anchor, not a boolean: the popover renders at the root next to the
|
|
3100
|
+
// hovercard, because an absolutely-positioned child of the title bar is
|
|
3101
|
+
// painted under the editor whatever its z-index.
|
|
3102
|
+
var _useStateOverflow = useState(null);
|
|
3103
|
+
var collabOverflowAnchor = _useStateOverflow[0];
|
|
3104
|
+
var setCollabOverflowAnchor = _useStateOverflow[1];
|
|
3105
|
+
var collabOverflowOpen = !!collabOverflowAnchor;
|
|
3106
|
+
var anchorFrom = function (el) {
|
|
3107
|
+
var r = el.getBoundingClientRect();
|
|
3108
|
+
return { top: r.bottom + 4, right: window.innerWidth - r.right };
|
|
3109
|
+
};
|
|
3110
|
+
var collabOverflowCloseTimerRef = useRef(null);
|
|
3111
|
+
var cancelOverflowClose = function () {
|
|
3112
|
+
if (collabOverflowCloseTimerRef.current) {
|
|
3113
|
+
clearTimeout(collabOverflowCloseTimerRef.current);
|
|
3114
|
+
collabOverflowCloseTimerRef.current = null;
|
|
3115
|
+
}
|
|
3116
|
+
};
|
|
3117
|
+
var scheduleOverflowClose = function () {
|
|
3118
|
+
cancelOverflowClose();
|
|
3119
|
+
collabOverflowCloseTimerRef.current = setTimeout(function () { setCollabOverflowAnchor(null); }, 250);
|
|
3120
|
+
};
|
|
3121
|
+
|
|
2848
3122
|
// Phase 7: Per-file last-commit info shown in the status bar
|
|
2849
3123
|
var _useState31 = useState(null);
|
|
2850
|
-
var
|
|
2851
|
-
var
|
|
2852
|
-
var setActiveFileCommit = _useState32[1];
|
|
3124
|
+
var activeFileCommit = _useState31[0];
|
|
3125
|
+
var setActiveFileCommit = _useState31[1];
|
|
2853
3126
|
|
|
2854
3127
|
// EOL indicator — tracks current line-ending style of the active file
|
|
2855
3128
|
var _useState31e = useState(null);
|
|
2856
|
-
var
|
|
2857
|
-
var
|
|
2858
|
-
var setActiveEOL = _useState31e2[1];
|
|
3129
|
+
var activeEOL = _useState31e[0];
|
|
3130
|
+
var setActiveEOL = _useState31e[1];
|
|
2859
3131
|
|
|
2860
3132
|
useEffect(function () {
|
|
2861
|
-
if (!gitAvailable || !activeTab || activeTab.isDiff || activeTab.isCombinedDiff || activeTab.isCommitGraph || !activeTab.path || activeTab.path.indexOf('://') >= 0) {
|
|
3133
|
+
if (!gitAvailable || !activeTab || activeTab.isDiff || activeTab.isCombinedDiff || activeTab.isCombinedView || activeTab.isCommitGraph || !activeTab.path || activeTab.path.indexOf('://') >= 0) {
|
|
2862
3134
|
setActiveFileCommit(null);
|
|
2863
3135
|
return;
|
|
2864
3136
|
}
|
|
@@ -2875,10 +3147,12 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2875
3147
|
});
|
|
2876
3148
|
}, [activeTab ? activeTab.id : null, gitAvailable]);
|
|
2877
3149
|
|
|
2878
|
-
// Update EOL indicator
|
|
3150
|
+
// Update EOL indicator on external loads, not on every content write. The
|
|
3151
|
+
// content effect fires on the 250ms typing flush, and scanning the whole
|
|
3152
|
+
// buffer for '\r' there was pure waste — typing cannot change the line ending.
|
|
2879
3153
|
useEffect(function () {
|
|
2880
3154
|
if (!activeTab || typeof activeTab.content !== 'string' ||
|
|
2881
|
-
activeTab.isDiff || activeTab.isCombinedDiff || activeTab.isCommitGraph || activeTab.isPreview) {
|
|
3155
|
+
activeTab.isDiff || activeTab.isCombinedDiff || activeTab.isCombinedView || activeTab.isCommitGraph || activeTab.isPreview) {
|
|
2882
3156
|
setActiveEOL(null);
|
|
2883
3157
|
return;
|
|
2884
3158
|
}
|
|
@@ -2889,11 +3163,11 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2889
3163
|
} else {
|
|
2890
3164
|
setActiveEOL('LF');
|
|
2891
3165
|
}
|
|
2892
|
-
}, [activeTab ? activeTab.id : null, activeTab ? activeTab.
|
|
3166
|
+
}, [activeTab ? activeTab.id : null, activeTab ? activeTab.externalContentVersion : null]);
|
|
2893
3167
|
|
|
2894
3168
|
useEffect(function () {
|
|
2895
3169
|
if (!activeTab || typeof activeTab.content !== 'string') return;
|
|
2896
|
-
if (activeTab.isDiff || activeTab.isCombinedDiff || activeTab.isCommitGraph) return;
|
|
3170
|
+
if (activeTab.isDiff || activeTab.isCombinedDiff || activeTab.isCombinedView || activeTab.isCommitGraph) return;
|
|
2897
3171
|
if (isRubyPath(activeTab.path) && !rubocopAvailable) return;
|
|
2898
3172
|
if (activeTab.path.endsWith('.haml') && !hamlLintAvailable) return;
|
|
2899
3173
|
|
|
@@ -2966,15 +3240,35 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2966
3240
|
// by a formatter problem).
|
|
2967
3241
|
var _formatContentForSave = function _formatContentForSave(tab) {
|
|
2968
3242
|
var isRubyLang = /\.(rb|rake|gemspec)$/.test(tab.path) || /(?:^|\/)(Rakefile|Gemfile)$/.test(tab.path);
|
|
3243
|
+
var startedAt = Date.now();
|
|
2969
3244
|
if (isRubyLang) {
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
3245
|
+
// Same route as the Format button: formatRubySource tries ruby-lsp and
|
|
3246
|
+
// falls back to /format's `rubocop -A`. Calling /format directly here
|
|
3247
|
+
// meant saving a Ruby file could produce different bytes from formatting
|
|
3248
|
+
// the same file by hand — the two-formatters-disagree trap this file
|
|
3249
|
+
// already documents for JS — and gating on RuboCop alone made
|
|
3250
|
+
// format-on-save silently do nothing when only ruby-lsp was present.
|
|
3251
|
+
if (!rubocopAvailable && !window.MBEDITOR_RUBY_LSP_AVAILABLE) return Promise.resolve(null);
|
|
3252
|
+
return formatRubySource(tab.path, tab.content)
|
|
3253
|
+
.then(function (res) {
|
|
3254
|
+
var out = (res && res.content) || null;
|
|
3255
|
+
_recFormat(tab.path, startedAt, out);
|
|
3256
|
+
return out;
|
|
3257
|
+
})
|
|
3258
|
+
["catch"](function () {
|
|
3259
|
+
_recErr('FORMAT');
|
|
3260
|
+
return null;
|
|
3261
|
+
});
|
|
2974
3262
|
}
|
|
2975
3263
|
var parserName = prettierParserFor(tab.path);
|
|
2976
3264
|
if (!parserName) return Promise.resolve(null);
|
|
2977
|
-
return runPrettier(tab.content, editorPrefs, parserName)
|
|
3265
|
+
return runPrettier(tab.content, editorPrefs, parserName).then(function (out) {
|
|
3266
|
+
_recFormat(tab.path, startedAt, out);
|
|
3267
|
+
return out;
|
|
3268
|
+
})["catch"](function () {
|
|
3269
|
+
_recErr('FORMAT');
|
|
3270
|
+
return null;
|
|
3271
|
+
});
|
|
2978
3272
|
};
|
|
2979
3273
|
|
|
2980
3274
|
// Re-read a tab from the store after flushing TabManager's throttled content
|
|
@@ -3013,6 +3307,40 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3013
3307
|
_doSave(paneId, tab);
|
|
3014
3308
|
};
|
|
3015
3309
|
|
|
3310
|
+
// Shared save-execution flow for a known-on-disk (non-untitled) tab: the
|
|
3311
|
+
// saving status/loading flag, the FileService write, the AVI clean-baseline
|
|
3312
|
+
// reset, and save-failed/cleanup handling. `onSuccess` runs whatever the
|
|
3313
|
+
// caller does after a successful write (before the finally cleanup);
|
|
3314
|
+
// `onFinally` runs any extra caller-specific cleanup after the standard one.
|
|
3315
|
+
var _runSave = function _runSave(tab, onSuccess, onFinally) {
|
|
3316
|
+
setLoading(function (prev) {
|
|
3317
|
+
return _extends({}, prev, { save: true });
|
|
3318
|
+
});
|
|
3319
|
+
EditorStore.setStatus("Saving " + tab.name + "...", "info");
|
|
3320
|
+
isSavingRef.current = true;
|
|
3321
|
+
var startedAt = Date.now();
|
|
3322
|
+
return FileService.saveFile(tab.path, tab.content).then(function () {
|
|
3323
|
+
var audit = window.MbeditorAudit;
|
|
3324
|
+
if (audit) audit.rec(audit.EV.SAVE, _auditExt(tab.path), tab.content.length, Date.now() - startedAt);
|
|
3325
|
+
noteLocalSave(tab.path, tab.content);
|
|
3326
|
+
// Reset the AVI clean baseline so undo past this save point shows dirty correctly.
|
|
3327
|
+
var _modelEntry = window.__mbeditorModels && window.__mbeditorModels[tab.path];
|
|
3328
|
+
if (_modelEntry && _modelEntry.model && !_modelEntry.model.isDisposed()) {
|
|
3329
|
+
_modelEntry.cleanVersionId = _modelEntry.model.getAlternativeVersionId();
|
|
3330
|
+
}
|
|
3331
|
+
onSuccess();
|
|
3332
|
+
})["catch"](function (err) {
|
|
3333
|
+
_recErr('SAVE');
|
|
3334
|
+
EditorStore.setStatus("Save failed: " + err.message, "error");
|
|
3335
|
+
})["finally"](function () {
|
|
3336
|
+
isSavingRef.current = false;
|
|
3337
|
+
setLoading(function (prev) {
|
|
3338
|
+
return _extends({}, prev, { save: false });
|
|
3339
|
+
});
|
|
3340
|
+
if (onFinally) onFinally();
|
|
3341
|
+
});
|
|
3342
|
+
};
|
|
3343
|
+
|
|
3016
3344
|
var _doSave = function _doSave(paneId, tab) {
|
|
3017
3345
|
if (tab.isUntitled) {
|
|
3018
3346
|
saveUntitledTab(paneId, tab)["catch"](function (err) {
|
|
@@ -3021,13 +3349,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3021
3349
|
});
|
|
3022
3350
|
return;
|
|
3023
3351
|
}
|
|
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);
|
|
3352
|
+
_runSave(tab, function () {
|
|
3031
3353
|
var newPanes = EditorStore.getState().panes.map(function (p) {
|
|
3032
3354
|
if (p.id === paneId) {
|
|
3033
3355
|
return _extends({}, p, { tabs: p.tabs.map(function (t) {
|
|
@@ -3037,13 +3359,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3037
3359
|
return p;
|
|
3038
3360
|
});
|
|
3039
3361
|
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
3362
|
// Collab: push a fresh snapshot so the server compacts the buffered deltas.
|
|
3046
|
-
|
|
3363
|
+
// Only for an attached tab — a deferred or degraded room pushing its local
|
|
3364
|
+
// buffer as a snapshot could seed a second copy into what a peer holds.
|
|
3365
|
+
if (typeof CollaborationService !== 'undefined' && CollaborationService.isAttached(tab.path)) {
|
|
3047
3366
|
CollaborationService.pushSnapshot(tab.path);
|
|
3048
3367
|
}
|
|
3049
3368
|
EditorStore.setStatus("Saved", "success");
|
|
@@ -3062,8 +3381,11 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3062
3381
|
// sprockets/react-rails pipeline can't transform (errors Monaco's TS
|
|
3063
3382
|
// worker misses). Save only — never per keystroke.
|
|
3064
3383
|
if (jsSyntaxCheckAvailableRef.current && /\.(js|jsx)$/i.test(tab.path)) {
|
|
3384
|
+
var lintStartedAt = Date.now();
|
|
3065
3385
|
FileService.lintFile(tab.path, tab.content, 'javascript').then(function (res) {
|
|
3066
3386
|
var babelMarkers = (res && res.markers) || [];
|
|
3387
|
+
var audit = window.MbeditorAudit;
|
|
3388
|
+
if (audit) audit.rec(audit.EV.LINT, _auditExt(tab.path), Date.now() - lintStartedAt, babelMarkers.length);
|
|
3067
3389
|
if (babelMarkers.length > 0) {
|
|
3068
3390
|
applyMarkersForTab(tab.id, babelMarkers);
|
|
3069
3391
|
EditorStore.setStatus('Saved — babel: ' + babelMarkers[0].message, 'warning');
|
|
@@ -3073,7 +3395,9 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3073
3395
|
// applyMarkersForTab keeps the previous map when it is already empty.
|
|
3074
3396
|
applyMarkersForTab(tab.id, []);
|
|
3075
3397
|
}
|
|
3076
|
-
})["catch"](function () {
|
|
3398
|
+
})["catch"](function () {
|
|
3399
|
+
_recErr('LINT');
|
|
3400
|
+
});
|
|
3077
3401
|
}
|
|
3078
3402
|
|
|
3079
3403
|
// The server broadcasts files_changed for this very write, and the
|
|
@@ -3084,13 +3408,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3084
3408
|
// file every time does by definition, so the duplicate was the expensive
|
|
3085
3409
|
// one. Without a socket no broadcast arrives, so keep it for that case.
|
|
3086
3410
|
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
3411
|
});
|
|
3095
3412
|
};
|
|
3096
3413
|
|
|
@@ -3179,7 +3496,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3179
3496
|
// disk stayed dirty and the only thing on offer was to hit Save All again.
|
|
3180
3497
|
// Each file is now settled on its own result.
|
|
3181
3498
|
var promises = dirtyTabs.map(function (tab) {
|
|
3182
|
-
|
|
3499
|
+
var startedAt = Date.now();
|
|
3500
|
+
return FileService.saveFile(tab.path, tab.content).then(function (res) {
|
|
3501
|
+
var audit = window.MbeditorAudit;
|
|
3502
|
+
if (audit) audit.rec(audit.EV.SAVE, _auditExt(tab.path), tab.content.length, Date.now() - startedAt);
|
|
3503
|
+
return res;
|
|
3504
|
+
}, function (err) {
|
|
3505
|
+
_recErr('SAVE');
|
|
3506
|
+
throw err;
|
|
3507
|
+
});
|
|
3183
3508
|
});
|
|
3184
3509
|
Promise.allSettled(promises).then(function (results) {
|
|
3185
3510
|
var saved = dirtyTabs.filter(function (tab, i) { return results[i].status === "fulfilled"; });
|
|
@@ -3327,6 +3652,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3327
3652
|
// rather than the button appearing to do nothing.
|
|
3328
3653
|
var formatTabContent = function formatTabContent(tab, prefs) {
|
|
3329
3654
|
prefs = prefs || editorPrefs;
|
|
3655
|
+
var startedAt = Date.now();
|
|
3330
3656
|
if (isRubyPath(tab.path) || tab.path.endsWith('.rake')) {
|
|
3331
3657
|
if (!rubocopAvailable) return Promise.reject(new Error("RuboCop is not available for this workspace."));
|
|
3332
3658
|
// RuboCop's output is taken as-is. Ruby indentation belongs to the
|
|
@@ -3335,13 +3661,18 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3335
3661
|
// the same file. The old code tried the opposite — converting the source
|
|
3336
3662
|
// to tabs *before* handing it over — which RuboCop simply discarded.
|
|
3337
3663
|
return formatRubySource(tab.path, tab.content).then(function (res) {
|
|
3338
|
-
|
|
3664
|
+
var out = (res && res.content) || null;
|
|
3665
|
+
_recFormat(tab.path, startedAt, out);
|
|
3666
|
+
return out;
|
|
3339
3667
|
});
|
|
3340
3668
|
}
|
|
3341
3669
|
|
|
3342
3670
|
var parserName = prettierParserFor(tab.path);
|
|
3343
3671
|
if (!parserName) return Promise.resolve(null);
|
|
3344
|
-
return runPrettier(tab.content, prefs, parserName)
|
|
3672
|
+
return runPrettier(tab.content, prefs, parserName).then(function (out) {
|
|
3673
|
+
_recFormat(tab.path, startedAt, out);
|
|
3674
|
+
return out;
|
|
3675
|
+
});
|
|
3345
3676
|
};
|
|
3346
3677
|
|
|
3347
3678
|
// Markers are per-model and only the mounted editor re-lints, so a document
|
|
@@ -3434,6 +3765,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3434
3765
|
EditorStore.setStatus(formatted === originalContent ? "Already formatted" : "Formatted (Unsaved)", "success");
|
|
3435
3766
|
GitService.fetchStatus();
|
|
3436
3767
|
})["catch"](function (err) {
|
|
3768
|
+
_recErr('FORMAT');
|
|
3437
3769
|
EditorStore.setStatus("Format failed: " + (err && err.message ? err.message : err), "error");
|
|
3438
3770
|
})["finally"](function () {
|
|
3439
3771
|
setLoading(function (prev) { return _extends({}, prev, { format: false }); });
|
|
@@ -3451,7 +3783,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3451
3783
|
var targets = [];
|
|
3452
3784
|
EditorStore.getState().panes.forEach(function (p) {
|
|
3453
3785
|
p.tabs.forEach(function (t) {
|
|
3454
|
-
if (t.path && !t.path.startsWith('mbeditor://') &&
|
|
3786
|
+
if (t.path && !t.path.startsWith('mbeditor://') && typeof t.content === 'string') {
|
|
3455
3787
|
targets.push({ paneId: p.id, tab: t });
|
|
3456
3788
|
}
|
|
3457
3789
|
});
|
|
@@ -3492,14 +3824,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3492
3824
|
});
|
|
3493
3825
|
};
|
|
3494
3826
|
|
|
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
3827
|
var executeTestRun = function executeTestRun(filePath, line) {
|
|
3504
3828
|
setTestLoading(true);
|
|
3505
3829
|
EditorStore.setStatus(line ? 'Running test at line ' + line + '...' : 'Running tests...', 'info');
|
|
@@ -3608,6 +3932,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3608
3932
|
});
|
|
3609
3933
|
};
|
|
3610
3934
|
|
|
3935
|
+
// Paging is scroll-driven, so a list too short to scroll — few hits, or a
|
|
3936
|
+
// big file group just collapsed — would show "Loading more…" and never load
|
|
3937
|
+
// it. Fill the viewport whenever the list stops being scrollable.
|
|
3938
|
+
useEffect(function () {
|
|
3939
|
+
var el = searchResultsContainerRef.current;
|
|
3940
|
+
if (!searchHasMore || !el) return;
|
|
3941
|
+
if (el.scrollHeight <= el.clientHeight + 200) loadMoreSearchResults();
|
|
3942
|
+
}, [searchHasMore, state.searchResults, searchCollapsedFiles]);
|
|
3943
|
+
|
|
3611
3944
|
var handleSearchChange = function handleSearchChange(e) {
|
|
3612
3945
|
var val = e.target.value;
|
|
3613
3946
|
if (!val) { clearSearch(); return; }
|
|
@@ -3850,10 +4183,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3850
4183
|
if (imported > 0) {
|
|
3851
4184
|
// Expand down to what was just written, so the tree actually shows it —
|
|
3852
4185
|
// 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.
|
|
4186
|
+
// untouched. Deliberately does not *select* the folder, which would move
|
|
4187
|
+
// the selection somewhere the user did not put it.
|
|
3857
4188
|
var landed = parentDir(written[0].path);
|
|
3858
4189
|
var toExpand = {};
|
|
3859
4190
|
var bits = landed ? landed.split('/') : [];
|
|
@@ -3872,8 +4203,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3872
4203
|
// rather than a disaster. Real targets stopPropagation before this runs.
|
|
3873
4204
|
useEffect(function () {
|
|
3874
4205
|
var swallow = function (e) {
|
|
3875
|
-
|
|
3876
|
-
if (Array.prototype.indexOf.call(types, 'Files') === -1) return;
|
|
4206
|
+
if (!FileImport.hasExternalFiles(e.dataTransfer)) return;
|
|
3877
4207
|
e.preventDefault();
|
|
3878
4208
|
// dragover is left alone beyond preventDefault: setting dropEffect here
|
|
3879
4209
|
// would override the 'copy' cursor the tree sets on a valid folder.
|
|
@@ -3901,7 +4231,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3901
4231
|
(entries.length === 1 ? '' : 's') + '...', 'info');
|
|
3902
4232
|
}
|
|
3903
4233
|
|
|
3904
|
-
|
|
4234
|
+
var where = targetFolderPath || 'the workspace root';
|
|
4235
|
+
var noun = entries.length + ' file' + (entries.length === 1 ? '' : 's');
|
|
4236
|
+
var progress = function (percent) {
|
|
4237
|
+
EditorStore.setStatus(percent === null
|
|
4238
|
+
? 'Uploaded ' + noun + ' to ' + where + ', writing them...'
|
|
4239
|
+
: 'Uploading ' + noun + ' to ' + where + '... ' + percent + '%', 'info');
|
|
4240
|
+
};
|
|
4241
|
+
|
|
4242
|
+
return FileService.importFiles(FileImport.buildFormData(entries, targetFolderPath, 'ask'), progress)
|
|
3905
4243
|
.then(function(result) {
|
|
3906
4244
|
if (result.conflicts && result.conflicts.length > 0) {
|
|
3907
4245
|
setImportConflict({ result: result, entries: entries, targetFolderPath: targetFolderPath });
|
|
@@ -3941,32 +4279,112 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3941
4279
|
});
|
|
3942
4280
|
};
|
|
3943
4281
|
|
|
3944
|
-
//
|
|
3945
|
-
//
|
|
3946
|
-
//
|
|
3947
|
-
//
|
|
3948
|
-
//
|
|
4282
|
+
// /raw stays a bare anchor for the single-file case — nothing buffered
|
|
4283
|
+
// client-side. /archive can fail with a JSON error body a plain anchor
|
|
4284
|
+
// navigation can't see, so that path goes through fetch instead.
|
|
4285
|
+
// Right-clicking a node inside a multi-selection acts on the whole selection;
|
|
4286
|
+
// right-clicking outside it acts on that node alone. The standard file-manager
|
|
4287
|
+
// rule, and why FileTree's onContextMenu stopped collapsing the set. Every
|
|
4288
|
+
// menu action that can operate on more than one path asks this.
|
|
4289
|
+
var actsOnSelection = function actsOnSelection(node) {
|
|
4290
|
+
return !!(node && selectedPaths && selectedPaths.size > 1 && selectedPaths.has(node.path));
|
|
4291
|
+
};
|
|
4292
|
+
|
|
4293
|
+
var MAX_COMBINED_FILES = 200;
|
|
4294
|
+
var handleOpenCombinedView = function handleOpenCombinedView(node) {
|
|
4295
|
+
if (!node) return;
|
|
4296
|
+
var byPath = {};
|
|
4297
|
+
(function index(items) {
|
|
4298
|
+
(items || []).forEach(function (n) { byPath[n.path] = n; index(n.children); });
|
|
4299
|
+
})(treeData);
|
|
4300
|
+
var files = [];
|
|
4301
|
+
var seen = {};
|
|
4302
|
+
var walk = function walk(n) {
|
|
4303
|
+
if (!n) return;
|
|
4304
|
+
if (n.type === 'file') {
|
|
4305
|
+
if (!seen[n.path]) { seen[n.path] = true; files.push(n.path); }
|
|
4306
|
+
return;
|
|
4307
|
+
}
|
|
4308
|
+
(n.children || []).forEach(walk);
|
|
4309
|
+
};
|
|
4310
|
+
(actsOnSelection(node) ? Array.from(selectedPaths) : [node.path]).forEach(function (p) { walk(byPath[p]); });
|
|
4311
|
+
if (!files.length) { EditorStore.setStatus('No files to show', 'info'); return; }
|
|
4312
|
+
var total = files.length;
|
|
4313
|
+
var id = 'mbeditor://combined/' + Date.now();
|
|
4314
|
+
var st = EditorStore.getState();
|
|
4315
|
+
var pane = st.panes.find(function (p) { return p.id === st.focusedPaneId; }) || st.panes[0];
|
|
4316
|
+
if (!pane) return;
|
|
4317
|
+
var tab = { id: id, path: id, name: 'Combined (' + Math.min(total, MAX_COMBINED_FILES) + ' files)', dirty: false, content: '', isCombinedView: true, combinedPaths: files.slice(0, MAX_COMBINED_FILES), combinedTotal: total };
|
|
4318
|
+
EditorStore.setState({
|
|
4319
|
+
panes: st.panes.map(function (p) { return p.id === pane.id ? Object.assign({}, p, { tabs: p.tabs.concat(tab), activeTabId: id }) : p; }),
|
|
4320
|
+
focusedPaneId: pane.id
|
|
4321
|
+
});
|
|
4322
|
+
};
|
|
4323
|
+
|
|
3949
4324
|
var handleDownloadFile = function handleDownloadFile(node) {
|
|
3950
|
-
if (!node
|
|
3951
|
-
var
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
4325
|
+
if (!node) return;
|
|
4326
|
+
var isMultiSelect = actsOnSelection(node);
|
|
4327
|
+
if (!isMultiSelect && node.type === 'file') {
|
|
4328
|
+
var link = document.createElement('a');
|
|
4329
|
+
link.href = window.mbeditorBasePath() + '/raw?download=1&path=' + encodeURIComponent(node.path);
|
|
4330
|
+
link.download = node.name;
|
|
4331
|
+
link.rel = 'noopener';
|
|
4332
|
+
document.body.appendChild(link);
|
|
4333
|
+
link.click();
|
|
4334
|
+
document.body.removeChild(link);
|
|
4335
|
+
EditorStore.setStatus('Downloading ' + node.name + '...', 'info');
|
|
4336
|
+
return;
|
|
4337
|
+
}
|
|
4338
|
+
var paths = isMultiSelect ? Array.from(selectedPaths) : [node.path];
|
|
4339
|
+
var url = window.mbeditorBasePath() + '/archive?' + paths.map(function (p) {
|
|
4340
|
+
return 'paths[]=' + encodeURIComponent(p);
|
|
4341
|
+
}).join('&');
|
|
4342
|
+
fetch(url).then(function (resp) {
|
|
4343
|
+
if (!resp.ok) {
|
|
4344
|
+
return resp.json().catch(function () { return {}; }).then(function (body) {
|
|
4345
|
+
var message = (body && body.error) || ('HTTP ' + resp.status);
|
|
4346
|
+
EditorStore.setStatus('Download failed: ' + message, 'error');
|
|
4347
|
+
});
|
|
4348
|
+
}
|
|
4349
|
+
var disposition = resp.headers.get('Content-Disposition');
|
|
4350
|
+
var match = disposition && disposition.match(/filename="([^"]+)"/);
|
|
4351
|
+
var filename = (match && match[1]) || 'archive.tar.gz';
|
|
4352
|
+
return resp.blob().then(function (blob) {
|
|
4353
|
+
var objectUrl = URL.createObjectURL(blob);
|
|
4354
|
+
var archiveLink = document.createElement('a');
|
|
4355
|
+
archiveLink.href = objectUrl;
|
|
4356
|
+
archiveLink.download = filename;
|
|
4357
|
+
archiveLink.rel = 'noopener';
|
|
4358
|
+
document.body.appendChild(archiveLink);
|
|
4359
|
+
archiveLink.click();
|
|
4360
|
+
document.body.removeChild(archiveLink);
|
|
4361
|
+
// Revoking in the same tick can free the blob before the browser has
|
|
4362
|
+
// started reading it, which cancels the download instead of saving it.
|
|
4363
|
+
setTimeout(function () { URL.revokeObjectURL(objectUrl); }, 60000);
|
|
4364
|
+
EditorStore.setStatus('Downloading ' + filename + '...', 'info');
|
|
4365
|
+
});
|
|
4366
|
+
}).catch(function (err) {
|
|
4367
|
+
EditorStore.setStatus('Download failed: ' + (err && err.message || 'network error'), 'error');
|
|
4368
|
+
});
|
|
4369
|
+
};
|
|
4370
|
+
|
|
4371
|
+
var downloadMenuLabel = function downloadMenuLabel(node) {
|
|
4372
|
+
if (!node) return 'Download';
|
|
4373
|
+
if (actsOnSelection(node)) return 'Download ' + selectedPaths.size + ' items';
|
|
4374
|
+
if (node.type === 'folder') return 'Download folder';
|
|
4375
|
+
return 'Download';
|
|
4376
|
+
};
|
|
4377
|
+
|
|
4378
|
+
// Deleting is irreversible, so the item has to say how much it will take —
|
|
4379
|
+
// "Delete" over a 3-item selection would understate it.
|
|
4380
|
+
var deleteMenuLabel = function deleteMenuLabel(node) {
|
|
4381
|
+
return actsOnSelection(node) ? 'Delete ' + selectedPaths.size + ' items' : 'Delete';
|
|
3959
4382
|
};
|
|
3960
4383
|
|
|
3961
4384
|
// A file node means "upload alongside this file", so the dialog opens on its
|
|
3962
4385
|
// parent — right-clicking a file to upload next to it is the common gesture.
|
|
3963
4386
|
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
|
-
}
|
|
4387
|
+
var folder = node ? (node.type === 'folder' ? node.path : parentDir(node.path)) : '';
|
|
3970
4388
|
setImportDialog({ initialFolder: folder });
|
|
3971
4389
|
};
|
|
3972
4390
|
|
|
@@ -3977,13 +4395,18 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
3977
4395
|
|
|
3978
4396
|
var openContextMenu = function openContextMenu(e, node) {
|
|
3979
4397
|
setContextMenu({ x: e.clientX, y: e.clientY, node: node });
|
|
3980
|
-
handleNodeSelect(node);
|
|
4398
|
+
if (!actsOnSelection(node)) handleNodeSelect(node);
|
|
3981
4399
|
};
|
|
3982
4400
|
|
|
3983
4401
|
var closeContextMenu = function closeContextMenu() {
|
|
3984
4402
|
return setContextMenu(null);
|
|
3985
4403
|
};
|
|
3986
4404
|
|
|
4405
|
+
// Clamp into view whenever the menu opens at a new position.
|
|
4406
|
+
useEffect(function () {
|
|
4407
|
+
if (contextMenu) window.clampMenuIntoView(contextMenuRef.current);
|
|
4408
|
+
}, [contextMenu]);
|
|
4409
|
+
|
|
3987
4410
|
var handleContextMenuAction = function handleContextMenuAction(action) {
|
|
3988
4411
|
var node = contextMenu && contextMenu.node;
|
|
3989
4412
|
closeContextMenu();
|
|
@@ -4002,6 +4425,9 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4002
4425
|
if (action === 'delete') {
|
|
4003
4426
|
handleDeletePath(node);return;
|
|
4004
4427
|
}
|
|
4428
|
+
if (action === 'combinedView' && node) {
|
|
4429
|
+
handleOpenCombinedView(node);return;
|
|
4430
|
+
}
|
|
4005
4431
|
if (action === 'download' && node) {
|
|
4006
4432
|
handleDownloadFile(node);return;
|
|
4007
4433
|
}
|
|
@@ -4042,17 +4468,34 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4042
4468
|
document.body.style.userSelect = 'none';
|
|
4043
4469
|
};
|
|
4044
4470
|
|
|
4471
|
+
var startDrawerResize = function startDrawerResize(mode, startHeight, storeKey) {
|
|
4472
|
+
return function (e) {
|
|
4473
|
+
e.preventDefault();
|
|
4474
|
+
resizeSessionRef.current = { mode: mode, startY: e.clientY, startHeight: startHeight, storeKey: storeKey };
|
|
4475
|
+
setActiveResizeMode(mode);
|
|
4476
|
+
document.body.style.cursor = 'row-resize';
|
|
4477
|
+
document.body.style.userSelect = 'none';
|
|
4478
|
+
};
|
|
4479
|
+
};
|
|
4480
|
+
|
|
4045
4481
|
var handleActivityBarClick = function handleActivityBarClick(tab) {
|
|
4046
4482
|
if (tab === 'settings') {
|
|
4047
|
-
|
|
4483
|
+
setShowModelGraphView(false);
|
|
4484
|
+
setShowSettings(!showSettings);
|
|
4048
4485
|
return;
|
|
4049
4486
|
}
|
|
4050
4487
|
// 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
|
|
4488
|
+
// and needs the width. There is no sidebar half to show, so it toggles
|
|
4489
|
+
// instead of behaving like the other sidebar tabs.
|
|
4052
4490
|
if (tab === 'models') {
|
|
4053
|
-
|
|
4491
|
+
setShowModelGraphView(function (prev) {
|
|
4492
|
+
var next = !prev;
|
|
4493
|
+
if (next) loadModelGraph(false);
|
|
4494
|
+
return next;
|
|
4495
|
+
});
|
|
4054
4496
|
return;
|
|
4055
4497
|
}
|
|
4498
|
+
setShowModelGraphView(false);
|
|
4056
4499
|
if (!sidebarCollapsed && activeSidebarTab === tab) {
|
|
4057
4500
|
setSidebarCollapsed(true);
|
|
4058
4501
|
} else {
|
|
@@ -4207,6 +4650,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4207
4650
|
// Optimistically insert the new node so the tree doesn't flash empty while waiting for the server
|
|
4208
4651
|
var optimisticNode = { path: path, name: name, type: type === 'folder' ? 'folder' : 'file', children: type === 'folder' ? [] : undefined };
|
|
4209
4652
|
setTreeData(function (prev) { return insertNodeIntoTree(prev, parentPath, optimisticNode); });
|
|
4653
|
+
setRevealTarget({ path: path });
|
|
4210
4654
|
|
|
4211
4655
|
if (type === 'file') {
|
|
4212
4656
|
setLoading(function (prev) {
|
|
@@ -4337,6 +4781,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4337
4781
|
EditorStore.setStatus('Renamed to: ' + renamedPath, 'success');
|
|
4338
4782
|
return refreshProjectTree().then(function () {
|
|
4339
4783
|
GitService.fetchStatusLite({ background: true });
|
|
4784
|
+
setRevealTarget({ path: renamedPath });
|
|
4340
4785
|
});
|
|
4341
4786
|
})["catch"](function (err) {
|
|
4342
4787
|
var message = err && err.response && err.response.data && err.response.data.error || err.message;
|
|
@@ -4353,10 +4798,12 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4353
4798
|
};
|
|
4354
4799
|
|
|
4355
4800
|
var handleDeletePath = function handleDeletePath(targetNode) {
|
|
4356
|
-
//
|
|
4357
|
-
//
|
|
4801
|
+
// A node from the context menu deletes just that node — unless it is part
|
|
4802
|
+
// of a live multi-selection, in which case the whole selection goes, the
|
|
4803
|
+
// same rule Download follows. With no node (the toolbar button) it is
|
|
4804
|
+
// always the selection, falling back to selectedTreeNode.
|
|
4358
4805
|
var pathsToDelete;
|
|
4359
|
-
if (targetNode
|
|
4806
|
+
if (targetNode && !actsOnSelection(targetNode)) {
|
|
4360
4807
|
pathsToDelete = [targetNode.path];
|
|
4361
4808
|
} else if (selectedPaths && selectedPaths.size > 0) {
|
|
4362
4809
|
pathsToDelete = Array.from(selectedPaths);
|
|
@@ -4380,6 +4827,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4380
4827
|
var confirmed = window.confirm('Delete ' + label + '? This cannot be undone.');
|
|
4381
4828
|
if (!confirmed) return;
|
|
4382
4829
|
|
|
4830
|
+
// Computed against the pre-delete tree, since the deleted paths' siblings
|
|
4831
|
+
// are only known before the tree is refreshed.
|
|
4832
|
+
var deleteRevealTarget = computeDeleteRevealTarget(treeData, pathsToDelete);
|
|
4833
|
+
|
|
4383
4834
|
setLoading(function (prev) {
|
|
4384
4835
|
return _extends({}, prev, { deletePath: true });
|
|
4385
4836
|
});
|
|
@@ -4398,6 +4849,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4398
4849
|
}
|
|
4399
4850
|
return refreshProjectTree().then(function () {
|
|
4400
4851
|
GitService.fetchStatusLite({ background: true });
|
|
4852
|
+
if (failures.length === 0 && deleteRevealTarget) setRevealTarget({ path: deleteRevealTarget });
|
|
4401
4853
|
});
|
|
4402
4854
|
})["finally"](function () {
|
|
4403
4855
|
setLoading(function (prev) {
|
|
@@ -4415,6 +4867,35 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4415
4867
|
var canLintAndFormat = !!activeTab;
|
|
4416
4868
|
var hasGitBranch = !!(state.gitBranch && state.gitBranch.trim());
|
|
4417
4869
|
|
|
4870
|
+
// Derived at render from state already in the store — not stored via setState,
|
|
4871
|
+
// so the 5s git-status poll (MbeditorApp.js re-render rules) never fires an
|
|
4872
|
+
// extra re-render on an unchanged tick. Only conflictFileIndex is state, and
|
|
4873
|
+
// it changes only on explicit user click, never from a poll.
|
|
4874
|
+
var conflictedFiles = (state.gitInfo && state.gitInfo.workingTree || []).filter(function (f) {
|
|
4875
|
+
return window.MBEDITOR_UNMERGED_STATUSES[(f.status || '').trim()];
|
|
4876
|
+
});
|
|
4877
|
+
|
|
4878
|
+
// Opens the conflicted file at index idx (wrapping), landing the cursor/reveal
|
|
4879
|
+
// on its first `<<<<<<<` marker. Prefers an already-open tab's live content
|
|
4880
|
+
// (may hold unsaved edits) over a fresh disk fetch.
|
|
4881
|
+
var openConflictedFileAt = function openConflictedFileAt(idx) {
|
|
4882
|
+
if (!conflictedFiles.length) return;
|
|
4883
|
+
var i = ((idx % conflictedFiles.length) + conflictedFiles.length) % conflictedFiles.length;
|
|
4884
|
+
var file = conflictedFiles[i];
|
|
4885
|
+
setConflictFileIndex(i);
|
|
4886
|
+
var openTab = state.panes.reduce(function (found, p) {
|
|
4887
|
+
return found || p.tabs.find(function (t) { return t.path === file.path; });
|
|
4888
|
+
}, null);
|
|
4889
|
+
var name = file.path.split('/').pop();
|
|
4890
|
+
var contentPromise = openTab ? Promise.resolve({ content: openTab.content }) : FileService.getFile(file.path);
|
|
4891
|
+
contentPromise.then(function (data) {
|
|
4892
|
+
var content = typeof data.content === 'string' ? data.content : '';
|
|
4893
|
+
var blocks = ConflictParser.parse(content);
|
|
4894
|
+
var line = blocks.length ? blocks[0].startLine + 1 : null;
|
|
4895
|
+
TabManager.openTab(file.path, name, line);
|
|
4896
|
+
});
|
|
4897
|
+
};
|
|
4898
|
+
|
|
4418
4899
|
var renderTabBar = function renderTabBar(paneId, tabs, activeId) {
|
|
4419
4900
|
return React.createElement(TabBar, {
|
|
4420
4901
|
tabs: tabs,
|
|
@@ -4427,7 +4908,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4427
4908
|
onSelect: function (id) {
|
|
4428
4909
|
// Sync explorer selection with the newly active tab so there's only one highlight
|
|
4429
4910
|
var tab = tabs.find(function(t) { return t.id === id; });
|
|
4430
|
-
if (tab && tab.path && !tab.path.startsWith('mbeditor://')
|
|
4911
|
+
if (tab && tab.path && !tab.path.startsWith('mbeditor://')) {
|
|
4431
4912
|
handleNodeSelect({ path: tab.path, name: tab.name, type: 'file' });
|
|
4432
4913
|
}
|
|
4433
4914
|
TabManager.switchTab(paneId, id);
|
|
@@ -4464,78 +4945,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4464
4945
|
});
|
|
4465
4946
|
};
|
|
4466
4947
|
|
|
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
4948
|
var CHANGELOG_TAB_ID = 'mbeditor://changelog';
|
|
4540
4949
|
function openChangelogTab() {
|
|
4541
4950
|
var st = EditorStore.getState();
|
|
@@ -4593,10 +5002,11 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4593
5002
|
{ className: "ide-titlebar" },
|
|
4594
5003
|
React.createElement("i", { className: "fas fa-layer-group ide-titlebar-icon" }),
|
|
4595
5004
|
React.createElement(
|
|
5005
|
+
// The product name, not the window title: the host and port are in the
|
|
5006
|
+
// browser's own address bar, and the long form crowded the search pill.
|
|
4596
5007
|
"div",
|
|
4597
|
-
{ className: "ide-titlebar-title" },
|
|
4598
|
-
"
|
|
4599
|
-
window.location.host
|
|
5008
|
+
{ className: "ide-titlebar-title", title: "Mini Browser Editor — " + window.location.host },
|
|
5009
|
+
"Mbeditor"
|
|
4600
5010
|
),
|
|
4601
5011
|
// The slot claims all the room between the title and the buttons; the
|
|
4602
5012
|
// search pill then takes 75% of it, centred. Sizing the pill against a
|
|
@@ -4686,13 +5096,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4686
5096
|
!toolbarIconOnly && " Git"
|
|
4687
5097
|
)
|
|
4688
5098
|
),
|
|
4689
|
-
|
|
4690
|
-
// healthy and you are simply alone — that was the whole point of hiding
|
|
4691
|
-
// the solo chip. It appears only when a condition pairing needs has
|
|
4692
|
-
// actually failed, so silence means "nobody here" and never "quietly
|
|
4693
|
-
// broken", which is exactly the ambiguity that made a real pairing
|
|
4694
|
-
// failure impossible to diagnose from the other machine.
|
|
4695
|
-
collabTrouble && React.createElement(
|
|
5099
|
+
conflictedFiles.length > 0 && React.createElement(
|
|
4696
5100
|
React.Fragment,
|
|
4697
5101
|
null,
|
|
4698
5102
|
React.createElement("div", { className: "statusbar-sep" }),
|
|
@@ -4700,11 +5104,46 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4700
5104
|
"button",
|
|
4701
5105
|
{
|
|
4702
5106
|
type: "button",
|
|
4703
|
-
className: "statusbar-btn mbeditor-
|
|
4704
|
-
title: "
|
|
4705
|
-
onClick: function () {
|
|
5107
|
+
className: "statusbar-btn mbeditor-conflict-chip",
|
|
5108
|
+
title: "Open the first of " + conflictedFiles.length + " conflicted file" + (conflictedFiles.length === 1 ? "" : "s"),
|
|
5109
|
+
onClick: function () { openConflictedFileAt(0); }
|
|
4706
5110
|
},
|
|
4707
|
-
React.createElement("i", { className: "fas fa-
|
|
5111
|
+
React.createElement("i", { className: "fas fa-exclamation-circle" }),
|
|
5112
|
+
// The count always renders, even icon-only: it is the chip's
|
|
5113
|
+
// content, not a label. A bare warning icon says less than nothing.
|
|
5114
|
+
" " + conflictedFiles.length,
|
|
5115
|
+
!toolbarIconOnly && (conflictedFiles.length === 1 ? " conflict" : " conflicts")
|
|
5116
|
+
),
|
|
5117
|
+
conflictedFiles.length > 1 && React.createElement(
|
|
5118
|
+
"button",
|
|
5119
|
+
{
|
|
5120
|
+
type: "button",
|
|
5121
|
+
className: "statusbar-btn",
|
|
5122
|
+
title: "Open the next conflicted file",
|
|
5123
|
+
onClick: function () { openConflictedFileAt(conflictFileIndex + 1); }
|
|
5124
|
+
},
|
|
5125
|
+
React.createElement("i", { className: "fas fa-arrow-right" })
|
|
5126
|
+
)
|
|
5127
|
+
),
|
|
5128
|
+
// Collaboration trouble chip. Deliberately not shown when everything is
|
|
5129
|
+
// healthy and you are simply alone — that was the whole point of hiding
|
|
5130
|
+
// the solo chip. It appears only when a condition pairing needs has
|
|
5131
|
+
// actually failed, so silence means "nobody here" and never "quietly
|
|
5132
|
+
// broken", which is exactly the ambiguity that made a real pairing
|
|
5133
|
+
// failure impossible to diagnose from the other machine.
|
|
5134
|
+
collabTrouble && React.createElement(
|
|
5135
|
+
React.Fragment,
|
|
5136
|
+
null,
|
|
5137
|
+
React.createElement("div", { className: "statusbar-sep" }),
|
|
5138
|
+
React.createElement(
|
|
5139
|
+
"button",
|
|
5140
|
+
{
|
|
5141
|
+
type: "button",
|
|
5142
|
+
className: "statusbar-btn mbeditor-collab-trouble",
|
|
5143
|
+
title: "Pairing is not available — click for details",
|
|
5144
|
+
onClick: function () { setCollabDiagOpen(true); }
|
|
5145
|
+
},
|
|
5146
|
+
React.createElement("i", { className: "fas fa-user-slash" }),
|
|
4708
5147
|
!toolbarIconOnly && " Pairing off"
|
|
4709
5148
|
)
|
|
4710
5149
|
),
|
|
@@ -4737,45 +5176,48 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4737
5176
|
React.Fragment,
|
|
4738
5177
|
null,
|
|
4739
5178
|
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
|
-
|
|
5179
|
+
React.createElement(
|
|
5180
|
+
"div",
|
|
5181
|
+
{ className: "collab-avatar-stack" },
|
|
5182
|
+
collabAvatarIds.map(function (cid) {
|
|
5183
|
+
var info = collabPeerInfo(cid);
|
|
5184
|
+
return React.createElement(
|
|
5185
|
+
"button",
|
|
5186
|
+
{
|
|
5187
|
+
key: cid,
|
|
5188
|
+
type: "button",
|
|
5189
|
+
className: "collab-avatar" +
|
|
5190
|
+
(info.following ? " collab-avatar-following" : "") +
|
|
5191
|
+
(info.elsewhere ? " collab-avatar-elsewhere" : ""),
|
|
5192
|
+
style: { background: info.colour },
|
|
5193
|
+
// The dot carries no glyph, so the name has to reach a
|
|
5194
|
+
// screen reader some other way.
|
|
5195
|
+
"aria-label": info.name,
|
|
5196
|
+
onMouseEnter: function (e) { openCollabHover(cid, e); },
|
|
5197
|
+
onMouseLeave: function () { setCollabHover(null); },
|
|
5198
|
+
onClick: function () { toggleFollow(cid); }
|
|
5199
|
+
}
|
|
5200
|
+
);
|
|
5201
|
+
}),
|
|
5202
|
+
collabOverflowIds.length > 0 && React.createElement(
|
|
5203
|
+
"div",
|
|
5204
|
+
{ className: "collab-avatar-overflow" },
|
|
5205
|
+
React.createElement(
|
|
5206
|
+
"button",
|
|
5207
|
+
{
|
|
5208
|
+
type: "button",
|
|
5209
|
+
className: "collab-avatar collab-avatar-more",
|
|
5210
|
+
onMouseEnter: function (e) { cancelOverflowClose(); setCollabOverflowAnchor(anchorFrom(e.currentTarget)); },
|
|
5211
|
+
onMouseLeave: scheduleOverflowClose,
|
|
5212
|
+
onClick: function (e) {
|
|
5213
|
+
var el = e.currentTarget;
|
|
5214
|
+
setCollabOverflowAnchor(function (prev) { return prev ? null : anchorFrom(el); });
|
|
5215
|
+
}
|
|
5216
|
+
},
|
|
5217
|
+
"+" + collabOverflowIds.length
|
|
4776
5218
|
)
|
|
4777
|
-
)
|
|
4778
|
-
|
|
5219
|
+
)
|
|
5220
|
+
)
|
|
4779
5221
|
),
|
|
4780
5222
|
React.createElement("div", { className: "statusbar-sep" }),
|
|
4781
5223
|
React.createElement(
|
|
@@ -4805,6 +5247,31 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4805
5247
|
)
|
|
4806
5248
|
)
|
|
4807
5249
|
),
|
|
5250
|
+
collabOverflowOpen && React.createElement(
|
|
5251
|
+
"div",
|
|
5252
|
+
{
|
|
5253
|
+
className: "collab-overflow-popover",
|
|
5254
|
+
style: { top: collabOverflowAnchor.top + 'px', right: collabOverflowAnchor.right + 'px' },
|
|
5255
|
+
onMouseEnter: cancelOverflowClose,
|
|
5256
|
+
onMouseLeave: scheduleOverflowClose
|
|
5257
|
+
},
|
|
5258
|
+
collabOverflowIds.map(function (cid) {
|
|
5259
|
+
var info = collabPeerInfo(cid);
|
|
5260
|
+
return React.createElement(
|
|
5261
|
+
"button",
|
|
5262
|
+
{
|
|
5263
|
+
key: cid,
|
|
5264
|
+
type: "button",
|
|
5265
|
+
className: "collab-overflow-row" + (info.following ? " is-following" : ""),
|
|
5266
|
+
onClick: function () { toggleFollow(cid); }
|
|
5267
|
+
},
|
|
5268
|
+
React.createElement("span", { className: "collab-overflow-swatch", style: { background: info.colour } }),
|
|
5269
|
+
React.createElement("span", { className: "collab-overflow-name" }, info.name),
|
|
5270
|
+
React.createElement("span", { className: "collab-overflow-file" }, info.file ? info.file.split('/').pop() : 'No file open'),
|
|
5271
|
+
info.following && React.createElement("i", { className: "fas fa-eye collab-overflow-eye" })
|
|
5272
|
+
);
|
|
5273
|
+
})
|
|
5274
|
+
),
|
|
4808
5275
|
collabHover && (function () {
|
|
4809
5276
|
var isMe = collabHover.cid === '__me__';
|
|
4810
5277
|
var peer = isMe ? null : collabRoster[collabHover.cid];
|
|
@@ -4893,6 +5360,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4893
5360
|
);
|
|
4894
5361
|
})(),
|
|
4895
5362
|
showHelp && React.createElement(ShortcutHelp, { onClose: function () { return setShowHelp(false); } }),
|
|
5363
|
+
showSettings && React.createElement(window.SettingsModal || SettingsModal, {
|
|
5364
|
+
editorPrefs: editorPrefs,
|
|
5365
|
+
setEditorPrefs: setEditorPrefs,
|
|
5366
|
+
rubocopAvailable: rubocopAvailable,
|
|
5367
|
+
rubocopConfigPath: rubocopConfigPath,
|
|
5368
|
+
onOpenRubocopConfig: function () { handleSelectFile(rubocopConfigPath, rubocopConfigPath.split('/').pop()); },
|
|
5369
|
+
onReset: function () { setEditorPrefs(Object.assign({}, DEFAULT_EDITOR_PREFS)); },
|
|
5370
|
+
onClose: function () { return setShowSettings(false); }
|
|
5371
|
+
}),
|
|
4896
5372
|
React.createElement(
|
|
4897
5373
|
"div",
|
|
4898
5374
|
{ className: "ide-body", id: "ide-body-container" },
|
|
@@ -4937,7 +5413,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4937
5413
|
"button",
|
|
4938
5414
|
{
|
|
4939
5415
|
type: "button",
|
|
4940
|
-
className: "ide-activity-btn" + (
|
|
5416
|
+
className: "ide-activity-btn" + (showModelGraphView ? ' active' : ''),
|
|
4941
5417
|
title: "Model graph",
|
|
4942
5418
|
onClick: function() { handleActivityBarClick('models'); }
|
|
4943
5419
|
},
|
|
@@ -4951,16 +5427,16 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4951
5427
|
"button",
|
|
4952
5428
|
{
|
|
4953
5429
|
type: "button",
|
|
4954
|
-
className: "ide-activity-btn" + (
|
|
5430
|
+
className: "ide-activity-btn" + (showSettings ? ' active' : ''),
|
|
4955
5431
|
title: "Editor Preferences",
|
|
4956
|
-
onClick:
|
|
5432
|
+
onClick: function() { handleActivityBarClick('settings'); }
|
|
4957
5433
|
},
|
|
4958
5434
|
React.createElement("i", { className: "fas fa-cog" })
|
|
4959
5435
|
)
|
|
4960
5436
|
)
|
|
4961
5437
|
),
|
|
4962
|
-
/* Panel content — shown when not collapsed
|
|
4963
|
-
!sidebarCollapsed && !zenMode && React.createElement(
|
|
5438
|
+
/* Panel content — shown when not collapsed, not in zen mode, and not showing the model graph */
|
|
5439
|
+
!sidebarCollapsed && !zenMode && !showModelGraphView && React.createElement(
|
|
4964
5440
|
"div",
|
|
4965
5441
|
{ className: "ide-sidebar", style: { width: sidebarWidth + "px" } },
|
|
4966
5442
|
React.createElement("div", { className: "sidebar-panel-title" },
|
|
@@ -4998,108 +5474,126 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4998
5474
|
React.createElement(
|
|
4999
5475
|
"div",
|
|
5000
5476
|
{ style: { marginBottom: "12px" } },
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
return
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
},
|
|
5011
|
-
React.createElement(
|
|
5477
|
+
(function () {
|
|
5478
|
+
// VS Code drops the "GROUP N" sub-header when only one group
|
|
5479
|
+
// is populated — it's only useful to tell groups apart.
|
|
5480
|
+
var activePanes = state.panes.filter(function (p) { return p.tabs.length > 0; });
|
|
5481
|
+
var showGroupHeaders = activePanes.length > 1;
|
|
5482
|
+
return state.panes.map(function (pane) {
|
|
5483
|
+
if (pane.tabs.length === 0) return null;
|
|
5484
|
+
var isPane2 = pane.id === 2;
|
|
5485
|
+
return React.createElement(
|
|
5012
5486
|
"div",
|
|
5013
|
-
{
|
|
5014
|
-
|
|
5015
|
-
"
|
|
5016
|
-
{
|
|
5017
|
-
|
|
5018
|
-
|
|
5487
|
+
{
|
|
5488
|
+
key: pane.id,
|
|
5489
|
+
className: "open-editors-group",
|
|
5490
|
+
style: { marginBottom: pane.id === 1 && state.panes[1].tabs.length > 0 ? "10px" : "0" }
|
|
5491
|
+
},
|
|
5492
|
+
showGroupHeaders && React.createElement(
|
|
5493
|
+
"div",
|
|
5494
|
+
{ className: "ide-sidebar-header open-editors-group-header" },
|
|
5495
|
+
React.createElement(
|
|
5496
|
+
"span",
|
|
5497
|
+
{ className: "open-editors-group-title" },
|
|
5498
|
+
"GROUP ",
|
|
5499
|
+
pane.id
|
|
5500
|
+
),
|
|
5501
|
+
React.createElement(
|
|
5502
|
+
SectionActionGroup,
|
|
5503
|
+
{ ariaLabel: "Group " + pane.id + " actions", className: "collapsible-actions open-editors-group-actions" },
|
|
5504
|
+
React.createElement(SidebarActionButton, {
|
|
5505
|
+
title: "Close all editors in Group " + pane.id,
|
|
5506
|
+
ariaLabel: "Close all editors in Group " + pane.id,
|
|
5507
|
+
iconClass: "far fa-window-close",
|
|
5508
|
+
onClick: function (e) {
|
|
5509
|
+
e.stopPropagation();handleCloseEditorsInGroup(pane.id);
|
|
5510
|
+
}
|
|
5511
|
+
})
|
|
5512
|
+
)
|
|
5019
5513
|
),
|
|
5020
5514
|
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(
|
|
5515
|
+
"div",
|
|
5516
|
+
{ className: "file-tree" },
|
|
5517
|
+
pane.tabs.map(function (tab) {
|
|
5518
|
+
var isActive = pane.activeTabId === tab.id && state.focusedPaneId === pane.id;
|
|
5519
|
+
var dir = tab.path ? tab.path.split('/').slice(0, -1).join('/') : '';
|
|
5520
|
+
return React.createElement(
|
|
5068
5521
|
"div",
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5522
|
+
{
|
|
5523
|
+
key: tab.id,
|
|
5524
|
+
className: "tree-item open-editors-item " + (isActive ? "active" : ""),
|
|
5525
|
+
// The name ellipsises in a narrow sidebar, so the row
|
|
5526
|
+
// carries the full path the way file-tree rows do.
|
|
5527
|
+
title: tab.path || tab.name,
|
|
5528
|
+
onClick: function () {
|
|
5529
|
+
if (tab.path && !tab.path.startsWith('mbeditor://')) {
|
|
5530
|
+
handleNodeSelect({ path: tab.path, name: tab.name, type: 'file' });
|
|
5531
|
+
}
|
|
5532
|
+
TabManager.focusPane(pane.id);TabManager.switchTab(pane.id, tab.id);
|
|
5533
|
+
}
|
|
5534
|
+
},
|
|
5535
|
+
React.createElement("i", { className: "tree-item-icon " + (window.getFileIcon ? window.getFileIcon(tab.name) : 'far fa-file-code') + " tree-file-icon" }),
|
|
5073
5536
|
React.createElement(
|
|
5074
5537
|
"div",
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5538
|
+
// minWidth:0 on both the row's name cell and the label
|
|
5539
|
+
// itself: without it a flex item refuses to shrink
|
|
5540
|
+
// below its content, so a long filename pushed out
|
|
5541
|
+
// under the (formerly absolute) action buttons.
|
|
5542
|
+
{ className: "tree-item-name", style: { display: 'flex', alignItems: 'center', minWidth: 0 } },
|
|
5543
|
+
React.createElement(
|
|
5544
|
+
"span",
|
|
5545
|
+
{ className: "open-editors-name", style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', minWidth: 0, flexShrink: 0 } },
|
|
5546
|
+
tab.name
|
|
5547
|
+
),
|
|
5548
|
+
dir && React.createElement(
|
|
5549
|
+
"span",
|
|
5550
|
+
{ className: "open-editors-dir" },
|
|
5551
|
+
dir
|
|
5552
|
+
)
|
|
5079
5553
|
),
|
|
5080
5554
|
React.createElement(
|
|
5081
5555
|
"div",
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5556
|
+
// In flow, not absolute — the buttons now claim their
|
|
5557
|
+
// own width so the name truncates instead of running
|
|
5558
|
+
// underneath them.
|
|
5559
|
+
{ className: "tab-actions", style: { display: 'flex', alignItems: 'center', flexShrink: 0, marginLeft: 'auto' } },
|
|
5560
|
+
React.createElement(
|
|
5561
|
+
"div",
|
|
5562
|
+
{ className: "tab-split", onClick: function (e) {
|
|
5563
|
+
e.stopPropagation();TabManager.moveTabToPane(pane.id, pane.id === 1 ? 2 : 1, tab.id);
|
|
5564
|
+
}, style: { padding: '0 4px', cursor: 'pointer' }, title: "Move to Group " + (pane.id === 1 ? 2 : 1) },
|
|
5565
|
+
React.createElement("i", { className: isPane2 ? "fas fa-chevron-left" : "fas fa-chevron-right" })
|
|
5566
|
+
),
|
|
5567
|
+
React.createElement(
|
|
5568
|
+
"div",
|
|
5569
|
+
{ className: "open-editors-close-slot" },
|
|
5570
|
+
tab.dirty && React.createElement("i", { className: "fas fa-circle open-editors-dirty-dot", 'aria-hidden': 'true' }),
|
|
5571
|
+
React.createElement(
|
|
5572
|
+
"button",
|
|
5573
|
+
{ type: "button", className: "tab-close", onClick: function (e) {
|
|
5574
|
+
e.stopPropagation();requestCloseTab(pane.id, tab.id);
|
|
5575
|
+
}, style: { padding: '0 4px' },
|
|
5576
|
+
title: "Close " + tab.name + (tab.dirty ? " (unsaved changes)" : ""),
|
|
5577
|
+
'aria-label': "Close " + tab.name + (tab.dirty ? " (unsaved changes)" : "") },
|
|
5578
|
+
React.createElement("i", { className: "fas fa-times" })
|
|
5579
|
+
)
|
|
5580
|
+
)
|
|
5089
5581
|
)
|
|
5090
|
-
)
|
|
5091
|
-
)
|
|
5092
|
-
|
|
5093
|
-
)
|
|
5094
|
-
);
|
|
5095
|
-
})
|
|
5582
|
+
);
|
|
5583
|
+
})
|
|
5584
|
+
)
|
|
5585
|
+
);
|
|
5586
|
+
});
|
|
5587
|
+
})()
|
|
5096
5588
|
)
|
|
5097
5589
|
)
|
|
5098
5590
|
),
|
|
5099
|
-
state.panes.flatMap(function (p) { return p.tabs; }).length > 0 && React.createElement(
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5591
|
+
state.panes.flatMap(function (p) { return p.tabs; }).length > 0 && React.createElement((window.Gutter || Gutter), {
|
|
5592
|
+
orientation: 'horizontal',
|
|
5593
|
+
label: "Resize open editors list",
|
|
5594
|
+
active: activeResizeMode === 'openeditors',
|
|
5595
|
+
onDragStart: startOpenEditorsResize
|
|
5596
|
+
}),
|
|
5103
5597
|
React.createElement(
|
|
5104
5598
|
"div",
|
|
5105
5599
|
{ className: "ide-sidebar-scrollable" },
|
|
@@ -5107,10 +5601,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5107
5601
|
CollapsibleSection,
|
|
5108
5602
|
{
|
|
5109
5603
|
title: projectSectionTitle,
|
|
5110
|
-
|
|
5111
|
-
onToggle: function (isCollapsed) {
|
|
5112
|
-
return handleToggleSection('projects', isCollapsed);
|
|
5113
|
-
},
|
|
5604
|
+
collapsible: false,
|
|
5114
5605
|
actions: React.createElement(
|
|
5115
5606
|
SectionActionGroup,
|
|
5116
5607
|
{ ariaLabel: "Project actions" },
|
|
@@ -5145,10 +5636,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5145
5636
|
disabled: !!loading.createDir
|
|
5146
5637
|
}),
|
|
5147
5638
|
React.createElement(SidebarActionButton, {
|
|
5148
|
-
title: "Upload files",
|
|
5639
|
+
title: "Upload files to the workspace root",
|
|
5149
5640
|
iconClass: 'fas fa-upload',
|
|
5641
|
+
// Defaults to the workspace root, not whatever happens to be
|
|
5642
|
+
// selected in the tree: an upload that silently follows the
|
|
5643
|
+
// selection lands files somewhere the user was not looking,
|
|
5644
|
+
// and the count reads as a success either way. The context
|
|
5645
|
+
// menu's "Upload Files Here..." is the way to target a folder.
|
|
5150
5646
|
onClick: function () {
|
|
5151
|
-
return openImportDialog(
|
|
5647
|
+
return openImportDialog(null);
|
|
5152
5648
|
}
|
|
5153
5649
|
}),
|
|
5154
5650
|
React.createElement(SidebarActionButton, {
|
|
@@ -5178,6 +5674,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5178
5674
|
activePath: editorPrefs.autoRevealInExplorer !== false ? (activeTab && activeTab.path) : null,
|
|
5179
5675
|
selectedPaths: selectedPaths,
|
|
5180
5676
|
anchorPath: selectedTreePath,
|
|
5677
|
+
revealTarget: revealTarget,
|
|
5181
5678
|
onNodeSelect: handleNodeSelect,
|
|
5182
5679
|
onMultiSelect: handleMultiSelect,
|
|
5183
5680
|
onMove: handleMoveNodes,
|
|
@@ -5516,18 +6013,32 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5516
6013
|
})()
|
|
5517
6014
|
)
|
|
5518
6015
|
),
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
"aria-label": "Resize explorer panel"
|
|
6016
|
+
// Collapsed: the same gutter is a handle. Drag it right and the
|
|
6017
|
+
// explorer snaps open; there is no intermediate width to preview.
|
|
6018
|
+
sidebarCollapsed && !zenMode && !showModelGraphView && React.createElement((window.Gutter || Gutter), {
|
|
6019
|
+
orientation: 'vertical',
|
|
6020
|
+
label: "Drag right to open the explorer",
|
|
6021
|
+
onSnap: function () { setSidebarCollapsed(false); }
|
|
5526
6022
|
}),
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
6023
|
+
/* Sidebar resize gutter — only when panel is open */
|
|
6024
|
+
!sidebarCollapsed && !zenMode && !showModelGraphView && React.createElement((window.Gutter || Gutter), {
|
|
6025
|
+
orientation: 'vertical',
|
|
6026
|
+
label: "Resize explorer panel",
|
|
6027
|
+
active: activeResizeMode === 'sidebar',
|
|
6028
|
+
onDragStart: startSidebarResize
|
|
6029
|
+
}),
|
|
6030
|
+
// The graph replaces the whole column — no tab bar, no panes, no drawers —
|
|
6031
|
+
// rather than living inside ide-main, so it gets the full center width.
|
|
6032
|
+
showModelGraphView ? React.createElement(
|
|
6033
|
+
"div",
|
|
6034
|
+
{ className: "ide-model-graph-view" },
|
|
6035
|
+
React.createElement(ModelGraph, {
|
|
6036
|
+
graph: modelGraph,
|
|
6037
|
+
loading: modelGraphLoading,
|
|
6038
|
+
onRefresh: function () { loadModelGraph(true); },
|
|
6039
|
+
onOpenModel: function (model) { openSchemaModal(model.name); }
|
|
6040
|
+
})
|
|
6041
|
+
) : React.createElement(
|
|
5531
6042
|
"div",
|
|
5532
6043
|
{ className: "ide-center-column" },
|
|
5533
6044
|
React.createElement(
|
|
@@ -5625,13 +6136,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5625
6136
|
commits: pActiveTab.commits || [],
|
|
5626
6137
|
onSelectCommit: handleSelectCommit
|
|
5627
6138
|
});
|
|
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
6139
|
} else if (pActiveTab.isChangelog) {
|
|
5636
6140
|
content = React.createElement(ChangelogView, {
|
|
5637
6141
|
changelogState: changelogState,
|
|
@@ -5644,597 +6148,12 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5644
6148
|
}
|
|
5645
6149
|
}
|
|
5646
6150
|
});
|
|
5647
|
-
} else if (pActiveTab.
|
|
5648
|
-
content = React.createElement(
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
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
|
-
);
|
|
6151
|
+
} else if (pActiveTab.isCombinedView) {
|
|
6152
|
+
content = React.createElement(CombinedFileView, {
|
|
6153
|
+
key: pActiveTab.id,
|
|
6154
|
+
paths: pActiveTab.combinedPaths || [],
|
|
6155
|
+
total: pActiveTab.combinedTotal || 0
|
|
6156
|
+
});
|
|
6238
6157
|
} else if (pActiveTab.isDiff) {
|
|
6239
6158
|
var _t = editorPrefs.theme || 'vs-dark';
|
|
6240
6159
|
var isDiffDark = _t !== 'vs' && _t !== 'hc-light' && _t !== 'github-light';
|
|
@@ -6283,9 +6202,12 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6283
6202
|
return React.createElement(
|
|
6284
6203
|
React.Fragment,
|
|
6285
6204
|
{ key: pane.id },
|
|
6286
|
-
idx === 1 && isSplit && React.createElement(
|
|
6287
|
-
|
|
6288
|
-
|
|
6205
|
+
idx === 1 && isSplit && React.createElement((window.Gutter || Gutter), {
|
|
6206
|
+
orientation: 'vertical',
|
|
6207
|
+
className: 'ide-gutter-pane',
|
|
6208
|
+
label: "Resize editor groups",
|
|
6209
|
+
active: activeResizeMode === 'pane',
|
|
6210
|
+
onDragStart: startPaneResize
|
|
6289
6211
|
}),
|
|
6290
6212
|
React.createElement(
|
|
6291
6213
|
"div",
|
|
@@ -6293,10 +6215,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6293
6215
|
className: "ide-pane " + (isFocused ? 'focused' : '') + " " + (isDropTarget ? 'drop-target' : ''),
|
|
6294
6216
|
style: { flexBasis: flexBasis, flexShrink: 0, flexGrow: 0, display: 'flex', flexDirection: 'column', minWidth: 0 },
|
|
6295
6217
|
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
6218
|
return TabManager.focusPane(pane.id);
|
|
6301
6219
|
},
|
|
6302
6220
|
onDragOver: function (e) {
|
|
@@ -6438,10 +6356,26 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6438
6356
|
);
|
|
6439
6357
|
})
|
|
6440
6358
|
),
|
|
6359
|
+
showLogPanel && !zenMode && React.createElement((window.Gutter || Gutter), {
|
|
6360
|
+
orientation: 'horizontal',
|
|
6361
|
+
label: "Resize log drawer",
|
|
6362
|
+
active: activeResizeMode === 'log',
|
|
6363
|
+
onDragStart: startDrawerResize('log', logHeight, 'mbeditorLogHeight')
|
|
6364
|
+
}),
|
|
6441
6365
|
showLogPanel && !zenMode && React.createElement(window.LogPanel || LogPanel, {
|
|
6366
|
+
height: logHeight,
|
|
6442
6367
|
onClose: function () { setShowLogPanel(false); }
|
|
6443
6368
|
}),
|
|
6369
|
+
// With the drawer closed, the gutter above the status bar is a handle:
|
|
6370
|
+
// drag it up to open Problems at the dragged height.
|
|
6371
|
+
showProblemsPanel && !zenMode && React.createElement((window.Gutter || Gutter), {
|
|
6372
|
+
orientation: 'horizontal',
|
|
6373
|
+
label: "Resize problems drawer",
|
|
6374
|
+
active: activeResizeMode === 'problems',
|
|
6375
|
+
onDragStart: startDrawerResize('problems', problemsHeight, 'mbeditorProblemsHeight')
|
|
6376
|
+
}),
|
|
6444
6377
|
showProblemsPanel && !zenMode && React.createElement(window.ProblemsPanel || ProblemsPanel, {
|
|
6378
|
+
height: problemsHeight,
|
|
6445
6379
|
onClose: function () { setShowProblemsPanel(false); },
|
|
6446
6380
|
onOpenFile: function (path, line, col) {
|
|
6447
6381
|
handleSelectFile(path, path.split('/').pop(), line, col);
|
|
@@ -6456,14 +6390,20 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6456
6390
|
),
|
|
6457
6391
|
|
|
6458
6392
|
// 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"
|
|
6393
|
+
!showGitPanel && !zenMode && !showModelGraphView && React.createElement((window.Gutter || Gutter), {
|
|
6394
|
+
orientation: 'vertical',
|
|
6395
|
+
label: "Drag left to open the git panel",
|
|
6396
|
+
snapDirection: -1,
|
|
6397
|
+
onSnap: function () { setShowGitPanel(true); }
|
|
6465
6398
|
}),
|
|
6466
|
-
showGitPanel && !zenMode && React.createElement(
|
|
6399
|
+
showGitPanel && !zenMode && !showModelGraphView && React.createElement((window.Gutter || Gutter), {
|
|
6400
|
+
orientation: 'vertical',
|
|
6401
|
+
className: 'ide-gutter-gitpanel',
|
|
6402
|
+
label: "Resize git panel",
|
|
6403
|
+
active: activeResizeMode === 'gitpanel',
|
|
6404
|
+
onDragStart: startGitPanelResize
|
|
6405
|
+
}),
|
|
6406
|
+
showGitPanel && !zenMode && !showModelGraphView && React.createElement(
|
|
6467
6407
|
"div",
|
|
6468
6408
|
{ className: "ide-git-right-panel", style: { width: gitPanelWidth + "px" } },
|
|
6469
6409
|
React.createElement(window.GitPanel || GitPanel, {
|
|
@@ -6479,6 +6419,19 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6479
6419
|
})
|
|
6480
6420
|
),
|
|
6481
6421
|
),
|
|
6422
|
+
// ponytail: opens on release, no live preview; track the drag if that grates.
|
|
6423
|
+
!showProblemsPanel && !zenMode && !showModelGraphView && React.createElement((window.Gutter || Gutter), {
|
|
6424
|
+
orientation: 'horizontal',
|
|
6425
|
+
className: 'ide-gutter-drawer-open',
|
|
6426
|
+
label: "Drag up to open Problems",
|
|
6427
|
+
snapThreshold: 40,
|
|
6428
|
+
onSnap: function (dragged) {
|
|
6429
|
+
var h = Math.max(DRAWER_MIN_HEIGHT, dragged);
|
|
6430
|
+
try { window.localStorage.setItem('mbeditorProblemsHeight', String(h)); } catch (err) {}
|
|
6431
|
+
setProblemsHeight(h);
|
|
6432
|
+
setShowProblemsPanel(true);
|
|
6433
|
+
}
|
|
6434
|
+
}),
|
|
6482
6435
|
React.createElement(
|
|
6483
6436
|
"div",
|
|
6484
6437
|
{ className: "ide-statusbar" },
|
|
@@ -6515,6 +6468,15 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6515
6468
|
React.createElement("i", { className: "fas fa-exclamation-triangle statusbar-problems-warning-icon" }),
|
|
6516
6469
|
React.createElement("span", { className: "statusbar-problems-count" }, problemCounts.warnings)
|
|
6517
6470
|
),
|
|
6471
|
+
migrationsPending && React.createElement(
|
|
6472
|
+
"div",
|
|
6473
|
+
{
|
|
6474
|
+
className: "statusbar-migration",
|
|
6475
|
+
title: "Pending migrations \u2014 run `rails db:migrate`. Editing still works in the meantime."
|
|
6476
|
+
},
|
|
6477
|
+
React.createElement("i", { className: "fas fa-database" }),
|
|
6478
|
+
" Migrations pending"
|
|
6479
|
+
),
|
|
6518
6480
|
!serverOnline && (function () {
|
|
6519
6481
|
var dirtyCount = state.panes.reduce(function (acc, p) {
|
|
6520
6482
|
return acc + p.tabs.filter(function (t) { return t.dirty; }).length;
|
|
@@ -6574,6 +6536,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6574
6536
|
},
|
|
6575
6537
|
React.createElement("i", { className: "fas fa-paragraph" })
|
|
6576
6538
|
),
|
|
6539
|
+
React.createElement(CursorPosition, null),
|
|
6577
6540
|
activeEOL && React.createElement(
|
|
6578
6541
|
"button",
|
|
6579
6542
|
{
|
|
@@ -6786,6 +6749,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6786
6749
|
"div",
|
|
6787
6750
|
{
|
|
6788
6751
|
className: "context-menu",
|
|
6752
|
+
ref: contextMenuRef,
|
|
6789
6753
|
style: { left: contextMenu.x, top: contextMenu.y },
|
|
6790
6754
|
onClick: function (e) {
|
|
6791
6755
|
return e.stopPropagation();
|
|
@@ -6830,16 +6794,24 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6830
6794
|
return handleContextMenuAction('delete');
|
|
6831
6795
|
} },
|
|
6832
6796
|
React.createElement("i", { className: "far fa-trash-alt context-menu-icon" }),
|
|
6833
|
-
"
|
|
6797
|
+
" " + deleteMenuLabel(contextMenu.node)
|
|
6834
6798
|
),
|
|
6835
6799
|
React.createElement("div", { className: "context-menu-divider" }),
|
|
6836
|
-
contextMenu.node && contextMenu.node.type === '
|
|
6800
|
+
contextMenu.node && (actsOnSelection(contextMenu.node) || contextMenu.node.type === 'folder') && React.createElement(
|
|
6801
|
+
"div",
|
|
6802
|
+
{ className: "context-menu-item", onClick: function () {
|
|
6803
|
+
return handleContextMenuAction('combinedView');
|
|
6804
|
+
} },
|
|
6805
|
+
React.createElement("i", { className: "fas fa-layer-group context-menu-icon" }),
|
|
6806
|
+
" Open in Combined View"
|
|
6807
|
+
),
|
|
6808
|
+
contextMenu.node && React.createElement(
|
|
6837
6809
|
"div",
|
|
6838
6810
|
{ className: "context-menu-item", onClick: function () {
|
|
6839
6811
|
return handleContextMenuAction('download');
|
|
6840
6812
|
} },
|
|
6841
6813
|
React.createElement("i", { className: "fas fa-download context-menu-icon" }),
|
|
6842
|
-
"
|
|
6814
|
+
" " + downloadMenuLabel(contextMenu.node)
|
|
6843
6815
|
),
|
|
6844
6816
|
React.createElement(
|
|
6845
6817
|
"div",
|
|
@@ -6937,9 +6909,20 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6937
6909
|
importConflict && React.createElement(ImportConflictModal, {
|
|
6938
6910
|
conflicts: importConflict.result.conflicts,
|
|
6939
6911
|
errors: importConflict.result.errors,
|
|
6912
|
+
entries: importConflict.entries,
|
|
6913
|
+
targetFolderPath: importConflict.targetFolderPath,
|
|
6914
|
+
isDark: !['vs', 'hc-light', 'github-light'].includes(editorPrefs.theme || 'vs-dark'),
|
|
6915
|
+
editorPrefs: editorPrefs,
|
|
6940
6916
|
onResolve: resolveImportConflict
|
|
6941
6917
|
}),
|
|
6942
6918
|
|
|
6919
|
+
/* ── Paste & Place ─────────────────────────────────────────────────── */
|
|
6920
|
+
state.isPastePlaceVisible && React.createElement(PastePlaceModal, {
|
|
6921
|
+
isDark: !['vs', 'hc-light', 'github-light'].includes(editorPrefs.theme || 'vs-dark'),
|
|
6922
|
+
editorPrefs: editorPrefs,
|
|
6923
|
+
onClose: function () { EditorStore.setState({ isPastePlaceVisible: false }); }
|
|
6924
|
+
}),
|
|
6925
|
+
|
|
6943
6926
|
/* ── Schema modal ──────────────────────────────────────────────────── */
|
|
6944
6927
|
schemaModal && React.createElement(
|
|
6945
6928
|
'div',
|
|
@@ -7033,4 +7016,6 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
7033
7016
|
};
|
|
7034
7017
|
|
|
7035
7018
|
window.MbeditorApp = MbeditorApp;
|
|
7019
|
+
// Test-only: lets the system test seed/inspect the test-result cache directly.
|
|
7020
|
+
window.__mbeditorTestCache = { save: saveCachedTestResult, prefix: TEST_CACHE_PREFIX };
|
|
7036
7021
|
/* TITLE BAR */ /* SIDEBAR */ /* EDITOR AREA */ /* STATUS BAR */ /* Right-click context menu */
|