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
|
@@ -6,19 +6,16 @@ var FileHistoryPanel = function FileHistoryPanel(_ref) {
|
|
|
6
6
|
var onClose = _ref.onClose;
|
|
7
7
|
|
|
8
8
|
var _React$useState = React.useState([]),
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
setCommits = _React$useState2[1];
|
|
9
|
+
commits = _React$useState[0],
|
|
10
|
+
setCommits = _React$useState[1];
|
|
12
11
|
|
|
13
12
|
var _React$useState3 = React.useState(true),
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
setLoading = _React$useState4[1];
|
|
13
|
+
loading = _React$useState3[0],
|
|
14
|
+
setLoading = _React$useState3[1];
|
|
17
15
|
|
|
18
16
|
var _React$useState5 = React.useState(null),
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
setError = _React$useState6[1];
|
|
17
|
+
error = _React$useState5[0],
|
|
18
|
+
setError = _React$useState5[1];
|
|
22
19
|
|
|
23
20
|
React.useEffect(function () {
|
|
24
21
|
if (!path) return;
|
|
@@ -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
|
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } }
|
|
6
5
|
|
|
@@ -25,6 +24,7 @@ var FileTree = function FileTree(_ref) {
|
|
|
25
24
|
var activePath = _ref.activePath;
|
|
26
25
|
var selectedPaths = _ref.selectedPaths; // Set<string> — all selected paths
|
|
27
26
|
var anchorPath = _ref.anchorPath; // string — anchor for shift-click range
|
|
27
|
+
var revealTarget = _ref.revealTarget; // { path } — one-shot scroll target after create/rename/delete
|
|
28
28
|
var onNodeSelect = _ref.onNodeSelect; // fn(node) — single select (also clears multi)
|
|
29
29
|
var onMultiSelect = _ref.onMultiSelect; // fn(Set<string>) — multi-select update
|
|
30
30
|
var onMove = _ref.onMove; // fn(srcPaths[], destFolderPath) — DnD move
|
|
@@ -44,23 +44,20 @@ var FileTree = function FileTree(_ref) {
|
|
|
44
44
|
|
|
45
45
|
var _useState = useState('');
|
|
46
46
|
|
|
47
|
-
var _useState2 = _slicedToArray(_useState, 2);
|
|
48
47
|
|
|
49
|
-
var inlineValue =
|
|
50
|
-
var setInlineValue =
|
|
48
|
+
var inlineValue = _useState[0];
|
|
49
|
+
var setInlineValue = _useState[1];
|
|
51
50
|
|
|
52
51
|
var _useStateDnD = useState(null);
|
|
53
|
-
var
|
|
54
|
-
var
|
|
55
|
-
var setDragOverFolder = _useStateDnD2[1];
|
|
52
|
+
var dragOverFolder = _useStateDnD[0];
|
|
53
|
+
var setDragOverFolder = _useStateDnD[1];
|
|
56
54
|
|
|
57
55
|
// Target of an in-flight *external* drag: a folder path, '' for the tree
|
|
58
56
|
// root, or null when no external drag is over the tree. Kept separate from
|
|
59
57
|
// dragOverFolder so the two drop kinds highlight differently.
|
|
60
58
|
var _useStateExt = useState(null);
|
|
61
|
-
var
|
|
62
|
-
var
|
|
63
|
-
var setExternalDragOver = _useStateExt2[1];
|
|
59
|
+
var externalDragOver = _useStateExt[0];
|
|
60
|
+
var setExternalDragOver = _useStateExt[1];
|
|
64
61
|
|
|
65
62
|
var inlineRef = useRef(null);
|
|
66
63
|
var committedRef = useRef(false);
|
|
@@ -123,6 +120,18 @@ var FileTree = function FileTree(_ref) {
|
|
|
123
120
|
return function () { clearTimeout(timer); };
|
|
124
121
|
}, [anchorPath]);
|
|
125
122
|
|
|
123
|
+
// Scroll the affected row into view after a create/rename/delete. The
|
|
124
|
+
// caller expands any ancestor dirs before setting revealTarget, so this
|
|
125
|
+
// only has to wait for the tree re-render and locate the row.
|
|
126
|
+
useEffect(function () {
|
|
127
|
+
if (!revealTarget || !revealTarget.path) return;
|
|
128
|
+
var timer = setTimeout(function () {
|
|
129
|
+
var idx = flatItemsRef.current.findIndex(function(item) { return item.kind === 'node' && item.node.path === revealTarget.path; });
|
|
130
|
+
if (idx >= 0) scrollToItem(idx);
|
|
131
|
+
}, 80);
|
|
132
|
+
return function () { clearTimeout(timer); };
|
|
133
|
+
}, [revealTarget]);
|
|
134
|
+
|
|
126
135
|
// Auto-reveal: when activePath changes, expand all ancestor dirs and scroll into view
|
|
127
136
|
useEffect(function () {
|
|
128
137
|
if (!activePath) return;
|
|
@@ -651,7 +660,8 @@ var FileTree = function FileTree(_ref) {
|
|
|
651
660
|
onContextMenu: function(e) {
|
|
652
661
|
e.preventDefault();
|
|
653
662
|
e.stopPropagation();
|
|
654
|
-
|
|
663
|
+
var isPartOfMultiSelect = selectedPaths && selectedPaths.size > 1 && selectedPaths.has(node.path);
|
|
664
|
+
if (!isPartOfMultiSelect) selectNode(node);
|
|
655
665
|
if (onContextMenu) onContextMenu(e, node);
|
|
656
666
|
}
|
|
657
667
|
},
|
|
@@ -715,7 +725,11 @@ var FileTreeMemo = React.memo(FileTree, function(prev, next) {
|
|
|
715
725
|
prev.gitFiles === next.gitFiles &&
|
|
716
726
|
prev.expandedDirs === next.expandedDirs &&
|
|
717
727
|
prev.pendingCreate === next.pendingCreate &&
|
|
718
|
-
prev.pendingRename === next.pendingRename
|
|
728
|
+
prev.pendingRename === next.pendingRename &&
|
|
729
|
+
// A fresh object per reveal request, so identity is the signal. Without it
|
|
730
|
+
// the reveal only lands when the tree happened to change in the same batch,
|
|
731
|
+
// which is most of the time and never all of it.
|
|
732
|
+
prev.revealTarget === next.revealTarget;
|
|
719
733
|
});
|
|
720
734
|
|
|
721
735
|
// Expose globally for sprockets require
|
|
@@ -84,6 +84,9 @@ var GitPanel = function GitPanel(_ref) {
|
|
|
84
84
|
var statusMeta = function statusMeta(rawStatus) {
|
|
85
85
|
var raw = (rawStatus || '').trim();
|
|
86
86
|
if (raw === '??') return { badge: 'NEW', cssKey: 'A', description: 'Untracked' };
|
|
87
|
+
// Before the first-character switch below: AA and DD are conflicts, and it
|
|
88
|
+
// would call them "added" and "deleted".
|
|
89
|
+
if (window.MBEDITOR_UNMERGED_STATUSES[raw]) return { badge: 'U', cssKey: 'Q', description: 'Conflict' };
|
|
87
90
|
if (raw.startsWith('R')) return { badge: 'R', cssKey: 'R', description: 'Renamed' };
|
|
88
91
|
switch (raw.charAt(0)) {
|
|
89
92
|
case 'M': return { badge: 'M', cssKey: 'M', description: 'Modified' };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Gutter — the one draggable strip between two cards.
|
|
4
|
+
//
|
|
5
|
+
// It lives in the gap, never inside the panel it resizes: a grip rendered
|
|
6
|
+
// inside a card sits on the card's own background and reads as decoration
|
|
7
|
+
// rather than as the seam you can pull. Every gutter in the app is this
|
|
8
|
+
// component; the panel next to it owns the resize maths and gets a bare
|
|
9
|
+
// mousedown through `onDragStart`.
|
|
10
|
+
//
|
|
11
|
+
// `onSnap` is the one behaviour Gutter owns itself, because it is the same
|
|
12
|
+
// three lines wherever it appears: a gutter with nothing to resize yet (a
|
|
13
|
+
// collapsed explorer, a closed drawer) tracks its own drag and fires once on
|
|
14
|
+
// mouseup if the pointer travelled far enough in the opening direction —
|
|
15
|
+
// right for a vertical gutter, up for a horizontal one, or the reverse with
|
|
16
|
+
// `snapDirection: -1` (a panel that opens from the right edge). It is handed the
|
|
17
|
+
// distance so a caller can open to the dragged size. In that mode
|
|
18
|
+
// `onDragStart` is not called.
|
|
19
|
+
var Gutter = function Gutter(props) {
|
|
20
|
+
var vertical = props.orientation === 'vertical';
|
|
21
|
+
var onSnap = props.onSnap;
|
|
22
|
+
|
|
23
|
+
var onMouseDown = onSnap ? function (e) {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
var start = vertical ? e.clientX : e.clientY;
|
|
26
|
+
var threshold = props.snapThreshold == null ? 24 : props.snapThreshold;
|
|
27
|
+
var onMove = function (ev) { ev.preventDefault(); };
|
|
28
|
+
var onUp = function (ev) {
|
|
29
|
+
document.removeEventListener('mousemove', onMove);
|
|
30
|
+
document.removeEventListener('mouseup', onUp);
|
|
31
|
+
var dir = props.snapDirection == null ? 1 : props.snapDirection;
|
|
32
|
+
var moved = (vertical ? ev.clientX - start : start - ev.clientY) * dir;
|
|
33
|
+
if (moved >= threshold) onSnap(moved);
|
|
34
|
+
};
|
|
35
|
+
document.addEventListener('mousemove', onMove);
|
|
36
|
+
document.addEventListener('mouseup', onUp);
|
|
37
|
+
} : props.onDragStart;
|
|
38
|
+
|
|
39
|
+
return React.createElement('div', {
|
|
40
|
+
className: 'ide-gutter ide-gutter-' + (vertical ? 'v' : 'h') +
|
|
41
|
+
(props.active ? ' active' : '') +
|
|
42
|
+
(props.className ? ' ' + props.className : ''),
|
|
43
|
+
role: 'separator',
|
|
44
|
+
'aria-orientation': vertical ? 'vertical' : 'horizontal',
|
|
45
|
+
'aria-label': props.label,
|
|
46
|
+
title: props.label,
|
|
47
|
+
onMouseDown: onMouseDown
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
window.Gutter = Gutter;
|
|
@@ -88,7 +88,15 @@ var ImportDialog = function ImportDialog(_ref) {
|
|
|
88
88
|
options.push(destOption(prefix, label, hint));
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
// The folder this upload was started from is always first and always the
|
|
92
|
+
// active one. Offering it after the suggestions meant that once you had
|
|
93
|
+
// imported a folder anywhere, that same folder scored top of the suggestion
|
|
94
|
+
// list next time and sat at the head of the options — so the dialog read as
|
|
95
|
+
// "it remembered where I last put things". With nothing selected it was
|
|
96
|
+
// worse: the previous destination was the first option and the actually
|
|
97
|
+
// chosen one ("Workspace root") was last.
|
|
98
|
+
offer(initialFolder, initialFolder || 'Workspace root',
|
|
99
|
+
initialFolder ? 'selected folder' : 'create the folders from the root');
|
|
92
100
|
suggestions.forEach(function (s) {
|
|
93
101
|
var bits = [];
|
|
94
102
|
if (s.files) bits.push(s.files + ' matching file' + (s.files === 1 ? '' : 's'));
|
|
@@ -48,6 +48,9 @@ function classifyLogLine(line) {
|
|
|
48
48
|
|
|
49
49
|
var LogPanel = function LogPanel(_ref) {
|
|
50
50
|
var onClose = _ref.onClose;
|
|
51
|
+
// Height is owned by MbeditorApp: the gutter that drags it lives between the
|
|
52
|
+
// editor card and this drawer, not inside it.
|
|
53
|
+
var height = _ref.height;
|
|
51
54
|
|
|
52
55
|
var _lines = React.useState([]);
|
|
53
56
|
var lines = _lines[0], setLines = _lines[1];
|
|
@@ -56,46 +59,7 @@ var LogPanel = function LogPanel(_ref) {
|
|
|
56
59
|
var _autoScroll = React.useState(true);
|
|
57
60
|
var autoScroll = _autoScroll[0], setAutoScroll = _autoScroll[1];
|
|
58
61
|
|
|
59
|
-
var MIN_HEIGHT = 120;
|
|
60
|
-
var _height = React.useState(function () {
|
|
61
|
-
var saved = parseInt(window.localStorage.getItem('mbeditorLogHeight'), 10);
|
|
62
|
-
return (saved && saved >= MIN_HEIGHT) ? saved : 240;
|
|
63
|
-
});
|
|
64
|
-
var height = _height[0], setHeight = _height[1];
|
|
65
|
-
|
|
66
62
|
var bodyRef = React.useRef(null);
|
|
67
|
-
// Mirror height into a ref so the drag end-handler can persist the final value
|
|
68
|
-
// (setHeight is async, so reading state in onMouseUp would be stale).
|
|
69
|
-
var heightRef = React.useRef(height);
|
|
70
|
-
heightRef.current = height;
|
|
71
|
-
|
|
72
|
-
// Drag the top edge to resize the drawer. Delta-based: track how far the
|
|
73
|
-
// pointer has moved from where the drag started and add that to the starting
|
|
74
|
-
// height. Dragging up grows the drawer. This is independent of where the
|
|
75
|
-
// drawer is anchored and survives a zero/unknown viewport height.
|
|
76
|
-
var onResizeMouseDown = function (e) {
|
|
77
|
-
e.preventDefault();
|
|
78
|
-
var startY = e.clientY;
|
|
79
|
-
var startHeight = heightRef.current;
|
|
80
|
-
var onMove = function (ev) {
|
|
81
|
-
var vh = window.innerHeight || document.documentElement.clientHeight || 0;
|
|
82
|
-
var maxH = vh > 0 ? Math.round(vh * 0.85) : Infinity;
|
|
83
|
-
var next = Math.min(maxH, Math.max(MIN_HEIGHT, startHeight + (startY - ev.clientY)));
|
|
84
|
-
heightRef.current = next;
|
|
85
|
-
setHeight(next);
|
|
86
|
-
};
|
|
87
|
-
var onUp = function () {
|
|
88
|
-
document.removeEventListener('mousemove', onMove);
|
|
89
|
-
document.removeEventListener('mouseup', onUp);
|
|
90
|
-
document.body.style.cursor = '';
|
|
91
|
-
document.body.style.userSelect = '';
|
|
92
|
-
window.localStorage.setItem('mbeditorLogHeight', String(heightRef.current));
|
|
93
|
-
};
|
|
94
|
-
document.body.style.cursor = 'ns-resize';
|
|
95
|
-
document.body.style.userSelect = 'none';
|
|
96
|
-
document.addEventListener('mousemove', onMove);
|
|
97
|
-
document.addEventListener('mouseup', onUp);
|
|
98
|
-
};
|
|
99
63
|
|
|
100
64
|
React.useEffect(function () {
|
|
101
65
|
LogService.start();
|
|
@@ -130,11 +94,6 @@ var LogPanel = function LogPanel(_ref) {
|
|
|
130
94
|
return React.createElement(
|
|
131
95
|
'div',
|
|
132
96
|
{ className: 'ide-log-drawer', style: { height: height + 'px' } },
|
|
133
|
-
React.createElement('div', {
|
|
134
|
-
className: 'ide-log-resize',
|
|
135
|
-
title: 'Drag to resize',
|
|
136
|
-
onMouseDown: onResizeMouseDown
|
|
137
|
-
}),
|
|
138
97
|
React.createElement(
|
|
139
98
|
'div',
|
|
140
99
|
{ className: 'ide-log-header' },
|