mbeditor 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +157 -1
- data/app/assets/javascripts/mbeditor/application.js +3 -1
- data/app/assets/javascripts/mbeditor/audit_log.js +165 -0
- data/app/assets/javascripts/mbeditor/collaboration_service.js +264 -22
- data/app/assets/javascripts/mbeditor/components/ChangelogView.js +89 -94
- data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +6 -9
- data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +12 -7
- data/app/assets/javascripts/mbeditor/components/CombinedDiffViewer.js +20 -0
- data/app/assets/javascripts/mbeditor/components/DiffViewer.js +1 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +440 -334
- 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/ImportDialog.js +9 -1
- data/app/assets/javascripts/mbeditor/components/LogPanel.js +3 -44
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +1322 -1431
- data/app/assets/javascripts/mbeditor/components/ModelGraph.js +94 -37
- data/app/assets/javascripts/mbeditor/components/ProblemsPanel.js +82 -72
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +121 -81
- data/app/assets/javascripts/mbeditor/components/SettingsModal.js +342 -0
- data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +2 -1
- data/app/assets/javascripts/mbeditor/components/TabBar.js +67 -98
- data/app/assets/javascripts/mbeditor/editor_plugins.js +694 -306
- data/app/assets/javascripts/mbeditor/file_import.js +13 -15
- data/app/assets/javascripts/mbeditor/file_service.js +46 -57
- 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 +29 -2
- data/app/assets/javascripts/mbeditor/tab_manager.js +135 -54
- 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 +762 -297
- data/app/assets/stylesheets/mbeditor/glass.css +163 -0
- data/app/assets/stylesheets/mbeditor/themes.css +90 -30
- data/app/channels/mbeditor/collaboration_channel.rb +25 -6
- data/app/controllers/mbeditor/application_controller.rb +26 -3
- data/app/controllers/mbeditor/editors_controller.rb +125 -465
- data/app/services/mbeditor/archive_service.rb +137 -0
- data/app/services/mbeditor/collaboration_doc_store.rb +180 -12
- data/app/services/mbeditor/duplicate_content_scanner.rb +105 -0
- 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_globals_service.rb +12 -1
- 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/rubocop_run_service.rb +17 -5
- data/app/services/mbeditor/ruby_lsp_result_translator.rb +226 -0
- data/app/services/mbeditor/schema_service.rb +8 -2
- data/app/services/mbeditor/search_replace_service.rb +19 -2
- data/app/services/mbeditor/test_runner_service.rb +3 -56
- data/app/views/layouts/mbeditor/application.html.erb +1 -1
- data/lib/mbeditor/audit_log.rb +203 -0
- data/lib/mbeditor/configuration.rb +6 -9
- data/lib/mbeditor/rack/pending_migration_bypass.rb +15 -9
- data/lib/mbeditor/route_map.rb +4 -1
- data/lib/mbeditor/ruby_lsp_client.rb +82 -14
- data/lib/mbeditor/version.rb +1 -1
- data/lib/mbeditor.rb +1 -0
- data/lib/tasks/mbeditor.rake +23 -0
- metadata +14 -3
- data/app/assets/javascripts/mbeditor/components/TestRunPanel.js +0 -312
|
@@ -145,10 +145,12 @@ var TabManager = (function () {
|
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
//
|
|
149
|
-
// PageUp/PageDown
|
|
150
|
-
|
|
151
|
-
var
|
|
148
|
+
// Back/forward navigation history: every real jump or file change pushes
|
|
149
|
+
// an entry; PageUp/PageDown walk it like browser back/forward.
|
|
150
|
+
var _navStack = [];
|
|
151
|
+
var _navIndex = -1;
|
|
152
|
+
var _navigating = false;
|
|
153
|
+
var NAV_STACK_MAX = 50;
|
|
152
154
|
|
|
153
155
|
function _snapshotPosition() {
|
|
154
156
|
var editor = window.__mbeditorActiveEditor;
|
|
@@ -161,16 +163,40 @@ var TabManager = (function () {
|
|
|
161
163
|
return { path: tab.path, name: tab.name, line: pos.lineNumber, col: pos.column };
|
|
162
164
|
}
|
|
163
165
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (
|
|
166
|
+
// Truncates any forward entries, then pushes. Skips a push that would
|
|
167
|
+
// duplicate the current top entry's path+line, and is a no-op entirely
|
|
168
|
+
// while a navigateBack/navigateForward-driven openTab is replaying.
|
|
169
|
+
function _pushNavEntry(entry) {
|
|
170
|
+
if (_navigating || !entry || !entry.path) return;
|
|
171
|
+
if (_navIndex > -1) _navStack = _navStack.slice(0, _navIndex + 1);
|
|
172
|
+
var top = _navStack[_navStack.length - 1];
|
|
173
|
+
if (top && top.path === entry.path && top.line === entry.line) return;
|
|
174
|
+
_navStack.push(entry);
|
|
175
|
+
if (_navStack.length > NAV_STACK_MAX) _navStack.shift();
|
|
176
|
+
_navIndex = _navStack.length - 1;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Refreshing the entry we are leaving from the live cursor is what makes
|
|
180
|
+
// going back land where you actually were, not where the entry was created.
|
|
181
|
+
function _navigate(step) {
|
|
182
|
+
var next = _navIndex + step;
|
|
183
|
+
if (_navIndex < 0 || next < 0 || next >= _navStack.length) return;
|
|
184
|
+
var live = _snapshotPosition();
|
|
185
|
+
if (live) _navStack[_navIndex] = live;
|
|
186
|
+
_navIndex = next;
|
|
187
|
+
var target = _navStack[next];
|
|
188
|
+
_navigating = true;
|
|
189
|
+
try { openTab(target.path, target.name, target.line, null, false, target.col); }
|
|
190
|
+
finally { _navigating = false; }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function navigateBack() { _navigate(-1); }
|
|
194
|
+
function navigateForward() { _navigate(1); }
|
|
195
|
+
|
|
196
|
+
// col/endCol are the 1-based bounds of the thing being jumped to. Pass both
|
|
197
|
+
// and the editor selects it, leaving the cursor at its end; pass col alone
|
|
198
|
+
// and it just parks the cursor there.
|
|
199
|
+
function openTab(path, name, line, forcePaneId, isSoftOpen, col, endCol) {
|
|
174
200
|
var state = EditorStore.getState();
|
|
175
201
|
var paneId = forcePaneId || state.focusedPaneId;
|
|
176
202
|
var pane = state.panes.find(function(p) { return p.id === paneId; });
|
|
@@ -186,13 +212,14 @@ var TabManager = (function () {
|
|
|
186
212
|
|
|
187
213
|
if (!pane) return;
|
|
188
214
|
|
|
215
|
+
_pushNavEntry({ path: path, name: name, line: line || null, col: col || null });
|
|
189
216
|
_recordRecentFile(path, name);
|
|
190
217
|
|
|
191
218
|
var existing = pane.tabs.find(function(t) { return t.path === path; });
|
|
192
219
|
|
|
193
220
|
if (existing) {
|
|
194
|
-
if (line) _updateTab(paneId, path, { gotoLine: line, gotoCol: col || null });
|
|
195
|
-
|
|
221
|
+
if (line) _updateTab(paneId, path, { gotoLine: line, gotoCol: col || null, gotoEndCol: endCol || null });
|
|
222
|
+
_switchTab(paneId, path);
|
|
196
223
|
if (_isMarkdownPath(path)) {
|
|
197
224
|
_ensureMarkdownPreview(paneId, path, existing.name || name, existing.content || "");
|
|
198
225
|
}
|
|
@@ -217,7 +244,7 @@ var TabManager = (function () {
|
|
|
217
244
|
isSoftOpen: isSoftOpen ? true : false,
|
|
218
245
|
loading: true
|
|
219
246
|
};
|
|
220
|
-
if (line) { newTab.gotoLine = line; newTab.gotoCol = col || null; }
|
|
247
|
+
if (line) { newTab.gotoLine = line; newTab.gotoCol = col || null; newTab.gotoEndCol = endCol || null; }
|
|
221
248
|
|
|
222
249
|
var newPanes = state.panes.map(function(p) {
|
|
223
250
|
if (p.id === paneId) {
|
|
@@ -227,6 +254,7 @@ var TabManager = (function () {
|
|
|
227
254
|
});
|
|
228
255
|
|
|
229
256
|
EditorStore.setState({ panes: newPanes, focusedPaneId: paneId, activeTabId: path });
|
|
257
|
+
_promoteMru(paneId, path);
|
|
230
258
|
|
|
231
259
|
// Virtual paths (diff://, combined-diff://) should never hit the file endpoint
|
|
232
260
|
if (path.indexOf('diff://') === 0 || path.indexOf('combined-diff://') === 0) {
|
|
@@ -234,6 +262,7 @@ var TabManager = (function () {
|
|
|
234
262
|
}
|
|
235
263
|
|
|
236
264
|
// Use a prefetched result if available (hover-prefetch hit), otherwise fetch normally.
|
|
265
|
+
var startedAt = Date.now();
|
|
237
266
|
var prefetchPromise = FileService.getPrefetched(path);
|
|
238
267
|
var filePromise = prefetchPromise || FileService.getFile(path, { allowMissing: true });
|
|
239
268
|
|
|
@@ -246,6 +275,11 @@ var TabManager = (function () {
|
|
|
246
275
|
if (!data) { closeTab(paneId, path); return; }
|
|
247
276
|
var loadedContent = typeof data.content === 'string' ? data.content : "";
|
|
248
277
|
var fileNotFound = data && data.missing === true;
|
|
278
|
+
var audit = window.MbeditorAudit;
|
|
279
|
+
if (audit) {
|
|
280
|
+
audit.rec(audit.EV.OPEN, audit.code('ext', String(path).split('.').pop().toLowerCase()),
|
|
281
|
+
loadedContent.length, Date.now() - startedAt);
|
|
282
|
+
}
|
|
249
283
|
_updateTab(paneId, path, {
|
|
250
284
|
content: loadedContent,
|
|
251
285
|
cleanContent: loadedContent,
|
|
@@ -260,6 +294,8 @@ var TabManager = (function () {
|
|
|
260
294
|
_syncMarkdownPreviewContent(path, typeof data.content === 'string' ? data.content : "");
|
|
261
295
|
}
|
|
262
296
|
}).catch(function(err) {
|
|
297
|
+
var audit = window.MbeditorAudit;
|
|
298
|
+
if (audit) audit.rec(audit.EV.ERR, audit.EV.OPEN);
|
|
263
299
|
if (path.startsWith('diff://')) return; // diff tabs handle their own loading
|
|
264
300
|
if (err.response && err.response.status === 413) {
|
|
265
301
|
FileService.getFileChunk(path, 0, 500).then(function(data) {
|
|
@@ -423,18 +459,34 @@ var TabManager = (function () {
|
|
|
423
459
|
if (typeof HistoryService !== 'undefined') {
|
|
424
460
|
HistoryService.flushForPath(path);
|
|
425
461
|
}
|
|
462
|
+
_removeMru(paneId, path);
|
|
426
463
|
var state = EditorStore.getState();
|
|
464
|
+
// Closing a markdown source also closes its preview tab, wherever it lives.
|
|
465
|
+
// Guard against recursion: a preview tab has no preview of its own.
|
|
466
|
+
var closingPane = state.panes.find(function(p) { return p.id === paneId; });
|
|
467
|
+
var closingTab = closingPane && closingPane.tabs.find(function(t) { return t.id === path; });
|
|
468
|
+
var closePreviewsFor = (closingTab && !closingTab.isPreview) ? path : null;
|
|
469
|
+
|
|
427
470
|
var newPanes = state.panes.map(function(pane) {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
471
|
+
// Match by t.id so diff tabs (id = 'diff://...') are closed correctly
|
|
472
|
+
var newTabs = pane.tabs.filter(function(t) {
|
|
473
|
+
if (pane.id === paneId && t.id === path) return false;
|
|
474
|
+
if (closePreviewsFor && t.isPreview && t.previewFor === closePreviewsFor) return false;
|
|
475
|
+
return true;
|
|
476
|
+
});
|
|
477
|
+
if (newTabs.length === pane.tabs.length) return pane;
|
|
478
|
+
|
|
479
|
+
var newActive = pane.activeTabId;
|
|
480
|
+
if (!newTabs.some(function(t) { return t.id === newActive; })) {
|
|
481
|
+
// Reactivate the tab the user was actually last in, not just the rightmost one.
|
|
482
|
+
var mruList = _mru[pane.id] || [];
|
|
483
|
+
var mruPick = null;
|
|
484
|
+
for (var i = 0; i < mruList.length; i++) {
|
|
485
|
+
if (newTabs.some(function(t) { return t.id === mruList[i]; })) { mruPick = mruList[i]; break; }
|
|
434
486
|
}
|
|
435
|
-
|
|
487
|
+
newActive = mruPick || (newTabs.length > 0 ? newTabs[newTabs.length - 1].id : null);
|
|
436
488
|
}
|
|
437
|
-
return pane;
|
|
489
|
+
return Object.assign({}, pane, { tabs: newTabs, activeTabId: newActive });
|
|
438
490
|
});
|
|
439
491
|
|
|
440
492
|
var nextFocusedPaneId = state.focusedPaneId;
|
|
@@ -494,7 +546,7 @@ var TabManager = (function () {
|
|
|
494
546
|
if (!pane || pane.tabs.length === 0) return;
|
|
495
547
|
|
|
496
548
|
pane.tabs.slice().forEach(function(tab) {
|
|
497
|
-
closeTab(paneId, tab.
|
|
549
|
+
closeTab(paneId, tab.id);
|
|
498
550
|
});
|
|
499
551
|
}
|
|
500
552
|
|
|
@@ -525,13 +577,43 @@ var TabManager = (function () {
|
|
|
525
577
|
});
|
|
526
578
|
}
|
|
527
579
|
|
|
528
|
-
|
|
580
|
+
// Most-recently-used tab ids per pane, most-recent first. Lets closeTab
|
|
581
|
+
// reactivate the tab the user was actually last in, not just the rightmost one.
|
|
582
|
+
var _mru = {};
|
|
583
|
+
var MRU_MAX = 20;
|
|
584
|
+
|
|
585
|
+
function _promoteMru(paneId, tabId) {
|
|
586
|
+
if (!tabId) return;
|
|
587
|
+
var list = _mru[paneId] || (_mru[paneId] = []);
|
|
588
|
+
var idx = list.indexOf(tabId);
|
|
589
|
+
if (idx !== -1) list.splice(idx, 1);
|
|
590
|
+
list.unshift(tabId);
|
|
591
|
+
if (list.length > MRU_MAX) list.length = MRU_MAX;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function _removeMru(paneId, tabId) {
|
|
595
|
+
var list = _mru[paneId];
|
|
596
|
+
if (!list) return;
|
|
597
|
+
var idx = list.indexOf(tabId);
|
|
598
|
+
if (idx !== -1) list.splice(idx, 1);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function _switchTab(paneId, path) {
|
|
529
602
|
var state = EditorStore.getState();
|
|
530
603
|
var newPanes = state.panes.map(function(p) {
|
|
531
604
|
if (p.id === paneId) return Object.assign({}, p, { activeTabId: path });
|
|
532
605
|
return p;
|
|
533
606
|
});
|
|
534
607
|
EditorStore.setState({ panes: newPanes, focusedPaneId: paneId, activeTabId: path });
|
|
608
|
+
_promoteMru(paneId, path);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function switchTab(paneId, path) {
|
|
612
|
+
_switchTab(paneId, path);
|
|
613
|
+
var state = EditorStore.getState();
|
|
614
|
+
var pane = state.panes.find(function(p) { return p.id === paneId; });
|
|
615
|
+
var tab = pane && pane.tabs.find(function(t) { return t.id === path; });
|
|
616
|
+
_pushNavEntry({ path: path, name: tab ? tab.name : null, line: null, col: null });
|
|
535
617
|
}
|
|
536
618
|
|
|
537
619
|
function focusPane(paneId) {
|
|
@@ -561,12 +643,12 @@ var TabManager = (function () {
|
|
|
561
643
|
function _queueContent(paneId, path, content, updates, markdown) {
|
|
562
644
|
var key = paneId + '' + path;
|
|
563
645
|
if (updates) {
|
|
564
|
-
// A
|
|
565
|
-
|
|
566
|
-
|
|
646
|
+
// A clean<->dirty transition must land now, but the full buffer is still
|
|
647
|
+
// materialized only on the trailing edge below. Callers pass a provider
|
|
648
|
+
// (a function) from the per-keystroke content listener so getValue() runs
|
|
649
|
+
// once per flush instead of once per keypress; everything else passes the
|
|
650
|
+
// string it already holds.
|
|
567
651
|
_updateTab(paneId, path, updates);
|
|
568
|
-
if (markdown) _syncMarkdownPreviewContent(path, content);
|
|
569
|
-
return;
|
|
570
652
|
}
|
|
571
653
|
_pendingContent[key] = { paneId: paneId, path: path, content: content, markdown: markdown };
|
|
572
654
|
if (_contentTimer === null) {
|
|
@@ -574,6 +656,13 @@ var TabManager = (function () {
|
|
|
574
656
|
}
|
|
575
657
|
}
|
|
576
658
|
|
|
659
|
+
// A pending content slot holds either a string or a provider invoked at flush
|
|
660
|
+
// time. A provider whose model has gone away returns nothing and is skipped.
|
|
661
|
+
function _resolveContent(content) {
|
|
662
|
+
if (typeof content !== 'function') return content;
|
|
663
|
+
try { return content(); } catch (e) { return null; }
|
|
664
|
+
}
|
|
665
|
+
|
|
577
666
|
function flushContent() {
|
|
578
667
|
if (_contentTimer !== null) {
|
|
579
668
|
clearTimeout(_contentTimer);
|
|
@@ -583,8 +672,10 @@ var TabManager = (function () {
|
|
|
583
672
|
_pendingContent = {};
|
|
584
673
|
Object.keys(pending).forEach(function (k) {
|
|
585
674
|
var p = pending[k];
|
|
586
|
-
|
|
587
|
-
if (
|
|
675
|
+
var content = _resolveContent(p.content);
|
|
676
|
+
if (typeof content !== 'string') return;
|
|
677
|
+
_updateTab(p.paneId, p.path, { content: content });
|
|
678
|
+
if (p.markdown) _syncMarkdownPreviewContent(p.path, content);
|
|
588
679
|
});
|
|
589
680
|
}
|
|
590
681
|
|
|
@@ -614,24 +705,13 @@ var TabManager = (function () {
|
|
|
614
705
|
_updateTab(paneId, path, { isSoftOpen: false });
|
|
615
706
|
}
|
|
616
707
|
|
|
617
|
-
function saveTabViewState(
|
|
618
|
-
var
|
|
619
|
-
var
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
var state = EditorStore.getState();
|
|
625
|
-
path = paneIdOrPath;
|
|
626
|
-
viewState = pathOrViewState;
|
|
627
|
-
var containingPane = state.panes.find(function(p) {
|
|
628
|
-
return p.tabs.some(function(t) { return t.path === path; });
|
|
629
|
-
});
|
|
630
|
-
if (!containingPane) return;
|
|
631
|
-
paneId = containingPane.id;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
_updateTab(paneId, path, { viewState: viewState });
|
|
708
|
+
function saveTabViewState(path, viewState) {
|
|
709
|
+
var state = EditorStore.getState();
|
|
710
|
+
var containingPane = state.panes.find(function(p) {
|
|
711
|
+
return p.tabs.some(function(t) { return t.path === path; });
|
|
712
|
+
});
|
|
713
|
+
if (!containingPane) return;
|
|
714
|
+
_updateTab(containingPane.id, path, { viewState: viewState });
|
|
635
715
|
}
|
|
636
716
|
|
|
637
717
|
function reorderTabInPane(paneId, tabId, insertBeforeTabId) {
|
|
@@ -699,7 +779,7 @@ var TabManager = (function () {
|
|
|
699
779
|
}
|
|
700
780
|
|
|
701
781
|
function clearGotoLine(paneId, path) {
|
|
702
|
-
_updateTab(paneId, path, { gotoLine: null, gotoCol: null });
|
|
782
|
+
_updateTab(paneId, path, { gotoLine: null, gotoCol: null, gotoEndCol: null });
|
|
703
783
|
}
|
|
704
784
|
|
|
705
785
|
// VS Code-style scratch buffer: a tab with no file behind it. Nothing is
|
|
@@ -752,7 +832,8 @@ var TabManager = (function () {
|
|
|
752
832
|
reorderTabInPane: reorderTabInPane,
|
|
753
833
|
moveTabToPane: moveTabToPane,
|
|
754
834
|
clearGotoLine: clearGotoLine,
|
|
755
|
-
|
|
835
|
+
navigateBack: navigateBack,
|
|
836
|
+
navigateForward: navigateForward,
|
|
756
837
|
closeAllTabsInPane: closeAllTabsInPane,
|
|
757
838
|
closeOtherTabsInPane: closeOtherTabsInPane,
|
|
758
839
|
closeSavedTabsInPane: closeSavedTabsInPane,
|
|
@@ -75,6 +75,17 @@ var WebSocketService = (function () {
|
|
|
75
75
|
return window.ActionCable.createConsumer(_cableUrl());
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
// Single consumer for both the EditorChannel and every CollaborationChannel.
|
|
79
|
+
// subscribeCollaboration used to mint its own consumer whenever _consumer was
|
|
80
|
+
// null (the state after a drop), and _attemptConnect then created a second one
|
|
81
|
+
// — the first socket was never disconnected, so every outage during which a
|
|
82
|
+
// file was opened leaked a WebSocket. Whoever asks first creates it; everyone
|
|
83
|
+
// else reuses it.
|
|
84
|
+
function _ensureConsumer() {
|
|
85
|
+
if (!_consumer) _consumer = _getConsumer();
|
|
86
|
+
return _consumer;
|
|
87
|
+
}
|
|
88
|
+
|
|
78
89
|
function _cleanupConsumer() {
|
|
79
90
|
if (_subscription) {
|
|
80
91
|
try { _subscription.unsubscribe(); } catch (e) { /* ignore */ }
|
|
@@ -146,7 +157,7 @@ var WebSocketService = (function () {
|
|
|
146
157
|
if (_status !== 'rejected') _status = 'connecting';
|
|
147
158
|
|
|
148
159
|
try {
|
|
149
|
-
_consumer =
|
|
160
|
+
_consumer = _ensureConsumer();
|
|
150
161
|
_subscription = _consumer.subscriptions.create(
|
|
151
162
|
{ channel: 'Mbeditor::EditorChannel' },
|
|
152
163
|
{
|
|
@@ -344,10 +355,7 @@ var WebSocketService = (function () {
|
|
|
344
355
|
if (!isCableAvailable()) return null;
|
|
345
356
|
handlers = handlers || {};
|
|
346
357
|
try {
|
|
347
|
-
|
|
348
|
-
_consumer = _getConsumer();
|
|
349
|
-
}
|
|
350
|
-
return _consumer.subscriptions.create(
|
|
358
|
+
return _ensureConsumer().subscriptions.create(
|
|
351
359
|
{ channel: 'Mbeditor::CollaborationChannel', path: path },
|
|
352
360
|
{
|
|
353
361
|
connected: function () { if (handlers.connected) handlers.connected(); },
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
*= require pico.classless
|
|
3
3
|
*= require mbeditor/themes
|
|
4
4
|
*= require mbeditor/editor
|
|
5
|
+
*= require mbeditor/glass
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
/* ── Git & Code Review Styles ───────────────────────────────────────── */
|
|
@@ -641,7 +642,7 @@
|
|
|
641
642
|
}
|
|
642
643
|
.redmine-badge {
|
|
643
644
|
background-color: var(--ide-accent);
|
|
644
|
-
color: var(--ide-accent
|
|
645
|
+
color: var(--ide-on-accent);
|
|
645
646
|
font-size: 10px;
|
|
646
647
|
padding: 2px 6px;
|
|
647
648
|
border-radius: 10px;
|
|
@@ -858,6 +859,10 @@
|
|
|
858
859
|
.search-loading-spinner {
|
|
859
860
|
animation-duration: 0.7s !important;
|
|
860
861
|
}
|
|
862
|
+
|
|
863
|
+
.statusbar-offline {
|
|
864
|
+
animation: none;
|
|
865
|
+
}
|
|
861
866
|
}
|
|
862
867
|
|
|
863
868
|
.cdiff-ctx { color: var(--ide-text-muted); }
|