mbeditor 0.3.8 → 0.4.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 +22 -0
- data/app/assets/javascripts/mbeditor/application_iife_head.js +7 -0
- data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +1 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +52 -11
- data/app/assets/javascripts/mbeditor/components/GitPanel.js +14 -4
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +499 -149
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +39 -1
- data/app/assets/javascripts/mbeditor/components/TabBar.js +3 -2
- data/app/assets/javascripts/mbeditor/editor_store.js +10 -2
- data/app/assets/javascripts/mbeditor/file_service.js +23 -23
- data/app/assets/javascripts/mbeditor/git_service.js +7 -11
- data/app/assets/javascripts/mbeditor/search_service.js +45 -12
- data/app/assets/javascripts/mbeditor/tab_manager.js +3 -3
- data/app/assets/stylesheets/mbeditor/editor.css +89 -6
- data/app/controllers/mbeditor/editors_controller.rb +139 -128
- data/app/controllers/mbeditor/git_controller.rb +5 -40
- data/app/services/mbeditor/git_blame_service.rb +6 -0
- data/app/services/mbeditor/git_commit_graph_service.rb +2 -0
- data/app/services/mbeditor/git_service.rb +97 -28
- data/app/services/mbeditor/redmine_service.rb +7 -0
- data/app/services/mbeditor/ruby_definition_service.rb +23 -2
- data/lib/mbeditor/configuration.rb +7 -1
- data/lib/mbeditor/engine.rb +27 -0
- data/lib/mbeditor/version.rb +3 -1
- data/lib/mbeditor.rb +2 -0
- metadata +2 -2
|
@@ -76,6 +76,35 @@ var QuickOpenDialog = function QuickOpenDialog(_ref) {
|
|
|
76
76
|
if (inputRef.current) inputRef.current.focus();
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
+
// Priority tier for a file path: lower = shown first.
|
|
80
|
+
// Order: controller > model > helper > concern > view > job > other > noise
|
|
81
|
+
function getFilePriority(path) {
|
|
82
|
+
var p = (path || '').toLowerCase();
|
|
83
|
+
if (p.indexOf('/controllers/') >= 0) return 1;
|
|
84
|
+
if (p.indexOf('/models/') >= 0) return 2;
|
|
85
|
+
if (p.indexOf('/helpers/') >= 0) return 3;
|
|
86
|
+
if (p.indexOf('/concerns/') >= 0) return 4;
|
|
87
|
+
if (p.indexOf('/views/') >= 0) return 5;
|
|
88
|
+
if (p.indexOf('/jobs/') >= 0) return 6;
|
|
89
|
+
// Deprioritise: migrations, schema, compiled assets, vendor, lock files
|
|
90
|
+
if (p.indexOf('/migrate/') >= 0 || p.indexOf('schema.rb') >= 0) return 90;
|
|
91
|
+
if (p.indexOf('/public/') >= 0 || p.indexOf('/vendor/') >= 0) return 91;
|
|
92
|
+
if (p.slice(-7) === '.min.js' || p.slice(-8) === '.min.css' ||
|
|
93
|
+
p.slice(-4) === '.map' || p.slice(-5) === '.lock') return 92;
|
|
94
|
+
return 50;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Match relevance within a priority tier: exact basename > prefix > substring > other.
|
|
98
|
+
function getMatchRelevance(result, q) {
|
|
99
|
+
if (!q) return 3;
|
|
100
|
+
var name = (result.name || (result.path || '').split('/').pop() || '').toLowerCase();
|
|
101
|
+
var lq = q.toLowerCase();
|
|
102
|
+
if (name === lq) return 0;
|
|
103
|
+
if (name.slice(0, lq.length) === lq) return 1;
|
|
104
|
+
if (name.indexOf(lq) >= 0) return 2;
|
|
105
|
+
return 3;
|
|
106
|
+
}
|
|
107
|
+
|
|
79
108
|
var getQuickOpenIcon = function getQuickOpenIcon(path, name, type) {
|
|
80
109
|
if (type === 'dir') {
|
|
81
110
|
return React.createElement('i', { className: 'fas fa-folder quick-open-result-icon quick-open-folder-icon', 'aria-hidden': 'true' });
|
|
@@ -99,7 +128,16 @@ var QuickOpenDialog = function QuickOpenDialog(_ref) {
|
|
|
99
128
|
var res = SearchService.searchFiles(query);
|
|
100
129
|
// Filter by type: always include files; include dirs only when showFolders is on
|
|
101
130
|
var filtered = showFolders ? res : res.filter(function(r) { return r.type !== 'dir'; });
|
|
102
|
-
|
|
131
|
+
// Sort: files always beat dirs; within the same tier exact basename > prefix > substring > other.
|
|
132
|
+
// JS sort is stable in modern engines so MiniSearch relevance score order is the tiebreaker
|
|
133
|
+
// when match relevance is equal.
|
|
134
|
+
filtered.sort(function(a, b) {
|
|
135
|
+
var aPriority = getFilePriority(a.path) + (a.type === 'dir' ? 100 : 0);
|
|
136
|
+
var bPriority = getFilePriority(b.path) + (b.type === 'dir' ? 100 : 0);
|
|
137
|
+
if (aPriority !== bPriority) return aPriority - bPriority;
|
|
138
|
+
return getMatchRelevance(a, query) - getMatchRelevance(b, query);
|
|
139
|
+
});
|
|
140
|
+
setResults(filtered.slice(0, 200));
|
|
103
141
|
setSelectedIndex(0);
|
|
104
142
|
}, [query, showFolders]);
|
|
105
143
|
|
|
@@ -18,6 +18,7 @@ var TabBar = function TabBar(_ref) {
|
|
|
18
18
|
var onHardenTab = _ref.onHardenTab;
|
|
19
19
|
var onShowHistory = _ref.onShowHistory;
|
|
20
20
|
var onRevealInExplorer = _ref.onRevealInExplorer;
|
|
21
|
+
var tabDisplayMode = _ref.tabDisplayMode || 'scroll';
|
|
21
22
|
|
|
22
23
|
var containerRef = useRef(null);
|
|
23
24
|
|
|
@@ -87,8 +88,8 @@ var TabBar = function TabBar(_ref) {
|
|
|
87
88
|
null,
|
|
88
89
|
React.createElement(
|
|
89
90
|
'div',
|
|
90
|
-
{ className: 'tab-bar', ref: containerRef, onWheel: function (e) {
|
|
91
|
-
if (containerRef.current) {
|
|
91
|
+
{ className: 'tab-bar tab-bar-' + tabDisplayMode, ref: containerRef, onWheel: function (e) {
|
|
92
|
+
if (tabDisplayMode !== 'wrap' && containerRef.current) {
|
|
92
93
|
containerRef.current.scrollLeft += e.deltaY;
|
|
93
94
|
}
|
|
94
95
|
} },
|
|
@@ -20,6 +20,7 @@ var EditorStore = (function () {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
var _listeners = [];
|
|
23
|
+
var _statusTimer = null;
|
|
23
24
|
|
|
24
25
|
function getState() { return _state; }
|
|
25
26
|
|
|
@@ -42,6 +43,9 @@ var EditorStore = (function () {
|
|
|
42
43
|
// Subscribe to changes in a specific subset of state keys.
|
|
43
44
|
// The listener is only called when at least one of the watched keys changes
|
|
44
45
|
// by reference (===), preventing unnecessary re-renders for unrelated updates.
|
|
46
|
+
// IMPORTANT: all state updates MUST produce a new object reference for any
|
|
47
|
+
// nested value (use Object.assign / spread — never mutate in place), otherwise
|
|
48
|
+
// subscribeToSlice will not detect the change.
|
|
45
49
|
function subscribeToSlice(keys, fn) {
|
|
46
50
|
var prev = {};
|
|
47
51
|
keys.forEach(function(k) { prev[k] = _state[k]; });
|
|
@@ -57,9 +61,13 @@ var EditorStore = (function () {
|
|
|
57
61
|
function setStatus(text, kind) {
|
|
58
62
|
kind = kind || "info";
|
|
59
63
|
setState({ statusMessage: { text: text, kind: kind } });
|
|
60
|
-
|
|
64
|
+
if (_statusTimer !== null) {
|
|
65
|
+
clearTimeout(_statusTimer);
|
|
66
|
+
_statusTimer = null;
|
|
67
|
+
}
|
|
61
68
|
if (kind !== "error") {
|
|
62
|
-
setTimeout(function () {
|
|
69
|
+
_statusTimer = setTimeout(function () {
|
|
70
|
+
_statusTimer = null;
|
|
63
71
|
if (_state.statusMessage.text === text) {
|
|
64
72
|
setState({ statusMessage: { text: "", kind: "info" } });
|
|
65
73
|
}
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
// The server uses this header to silence editor logs and guard non-GET requests.
|
|
3
3
|
axios.defaults.headers.common['X-Mbeditor-Client'] = '1';
|
|
4
4
|
|
|
5
|
+
// Cap all API calls at 30 s so a hung Rails server never blocks the UI forever.
|
|
6
|
+
// The ping endpoint overrides this with a tighter 4 s timeout per-request.
|
|
7
|
+
axios.defaults.timeout = 30000;
|
|
8
|
+
|
|
5
9
|
// Surface pending-migration errors as a dismissible banner instead of silently failing.
|
|
6
10
|
axios.interceptors.response.use(null, function(error) {
|
|
7
11
|
if (error.response && error.response.data && error.response.data.pending_migration_error) {
|
|
@@ -28,16 +32,12 @@ axios.interceptors.response.use(null, function(error) {
|
|
|
28
32
|
});
|
|
29
33
|
|
|
30
34
|
var FileService = (function () {
|
|
31
|
-
function basePath() {
|
|
32
|
-
return (window.MBEDITOR_BASE_PATH || '/mbeditor').replace(/\/$/, '');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
35
|
function getWorkspace() {
|
|
36
|
-
return axios.get(
|
|
36
|
+
return axios.get(window.mbeditorBasePath() + '/workspace').then(function(res) { return res.data; });
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
function getTree() {
|
|
40
|
-
return axios.get(
|
|
40
|
+
return axios.get(window.mbeditorBasePath() + '/files').then(function(res) { return res.data; });
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function getFile(path, options) {
|
|
@@ -45,72 +45,72 @@ var FileService = (function () {
|
|
|
45
45
|
if (options && options.allowMissing) {
|
|
46
46
|
params.allow_missing = '1';
|
|
47
47
|
}
|
|
48
|
-
return axios.get(
|
|
48
|
+
return axios.get(window.mbeditorBasePath() + '/file', { params: params }).then(function(res) { return res.data; });
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
function saveFile(path, code) {
|
|
52
|
-
return axios.post(
|
|
52
|
+
return axios.post(window.mbeditorBasePath() + '/file', { path: path, code: code }).then(function(res) { return res.data; });
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
function createFile(path, code) {
|
|
56
|
-
return axios.post(
|
|
56
|
+
return axios.post(window.mbeditorBasePath() + '/create_file', { path: path, code: code || '' }).then(function(res) { return res.data; });
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
function createDir(path) {
|
|
60
|
-
return axios.post(
|
|
60
|
+
return axios.post(window.mbeditorBasePath() + '/create_dir', { path: path }).then(function(res) { return res.data; });
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
function renamePath(path, newPath) {
|
|
64
|
-
return axios.patch(
|
|
64
|
+
return axios.patch(window.mbeditorBasePath() + '/rename', { path: path, new_path: newPath }).then(function(res) { return res.data; });
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
function deletePath(path) {
|
|
68
|
-
return axios.delete(
|
|
68
|
+
return axios.delete(window.mbeditorBasePath() + '/delete', { data: { path: path } }).then(function(res) { return res.data; });
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
function lintFile(path, code) {
|
|
72
|
-
return axios.post(
|
|
72
|
+
return axios.post(window.mbeditorBasePath() + '/lint', { path: path, code: code }).then(function(res) { return res.data; });
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
function quickFixOffense(path, code, copName) {
|
|
76
|
-
return axios.post(
|
|
76
|
+
return axios.post(window.mbeditorBasePath() + '/quick_fix', { path: path, code: code, cop_name: copName }).then(function(res) { return res.data; });
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
function formatFile(path, code) {
|
|
80
|
-
return axios.post(
|
|
80
|
+
return axios.post(window.mbeditorBasePath() + '/format', { path: path, code: code }).then(function(res) { return res.data; });
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
function runTests(path) {
|
|
84
|
-
return axios.post(
|
|
84
|
+
return axios.post(window.mbeditorBasePath() + '/test', { path: path }).then(function(res) { return res.data; });
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
function ping() {
|
|
88
|
-
return axios.get(
|
|
88
|
+
return axios.get(window.mbeditorBasePath() + '/ping', { timeout: 4000 }).then(function(res) { return res.data; });
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
function getState() {
|
|
92
|
-
return axios.get(
|
|
92
|
+
return axios.get(window.mbeditorBasePath() + '/state').then(function(res) { return res.data; });
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
function saveState(state) {
|
|
96
|
-
return axios.post(
|
|
96
|
+
return axios.post(window.mbeditorBasePath() + '/state', { state: state }).then(function(res) { return res.data; });
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
function getBranchState(branch) {
|
|
100
|
-
return axios.get(
|
|
100
|
+
return axios.get(window.mbeditorBasePath() + '/branch_state', { params: { branch: branch } }).then(function(res) { return res.data; });
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
function saveBranchState(branch, state) {
|
|
104
|
-
return axios.post(
|
|
104
|
+
return axios.post(window.mbeditorBasePath() + '/branch_state', { branch: branch, state: state }).then(function(res) { return res.data; });
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
function pruneBranchStates() {
|
|
108
|
-
return axios.post(
|
|
108
|
+
return axios.post(window.mbeditorBasePath() + '/prune_branch_states').then(function(res) { return res.data; });
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
function getDefinition(symbol, language, extraOptions) {
|
|
112
112
|
var config = Object.assign({ params: { symbol: symbol, language: language }, timeout: 5000 }, extraOptions || {});
|
|
113
|
-
return axios.get(
|
|
113
|
+
return axios.get(window.mbeditorBasePath() + '/definition', config).then(function(res) { return res.data; });
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
return {
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
var GitService = (function () {
|
|
2
|
-
function basePath() {
|
|
3
|
-
return (window.MBEDITOR_BASE_PATH || '/mbeditor').replace(/\/$/, '');
|
|
4
|
-
}
|
|
5
|
-
|
|
6
2
|
function applyGitInfo(data) {
|
|
7
3
|
var files = data.workingTree || data.files || [];
|
|
8
4
|
EditorStore.setState({
|
|
@@ -14,7 +10,7 @@ var GitService = (function () {
|
|
|
14
10
|
}
|
|
15
11
|
|
|
16
12
|
function fetchInfo() {
|
|
17
|
-
return axios.get(
|
|
13
|
+
return axios.get(window.mbeditorBasePath() + '/git_info')
|
|
18
14
|
.then(function(res) {
|
|
19
15
|
if (res.data && res.data.ok) {
|
|
20
16
|
applyGitInfo(res.data);
|
|
@@ -33,7 +29,7 @@ var GitService = (function () {
|
|
|
33
29
|
return fetchInfo().then(function(data) {
|
|
34
30
|
if (data && data.ok) return data;
|
|
35
31
|
|
|
36
|
-
return axios.get(
|
|
32
|
+
return axios.get(window.mbeditorBasePath() + '/git_status')
|
|
37
33
|
.then(function(res) {
|
|
38
34
|
if (res.data.ok) {
|
|
39
35
|
EditorStore.setState({
|
|
@@ -63,31 +59,31 @@ var GitService = (function () {
|
|
|
63
59
|
if (baseSha) query += '&base=' + encodeURIComponent(baseSha);
|
|
64
60
|
if (headSha) query += '&head=' + encodeURIComponent(headSha);
|
|
65
61
|
|
|
66
|
-
return axios.get(
|
|
62
|
+
return axios.get(window.mbeditorBasePath() + '/git/diff' + query).then(function(res) {
|
|
67
63
|
return res.data;
|
|
68
64
|
});
|
|
69
65
|
}
|
|
70
66
|
|
|
71
67
|
function fetchBlame(path) {
|
|
72
|
-
return axios.get(
|
|
68
|
+
return axios.get(window.mbeditorBasePath() + '/git/blame?file=' + encodeURIComponent(path)).then(function(res) {
|
|
73
69
|
return res.data;
|
|
74
70
|
});
|
|
75
71
|
}
|
|
76
72
|
|
|
77
73
|
function fetchFileHistory(path) {
|
|
78
|
-
return axios.get(
|
|
74
|
+
return axios.get(window.mbeditorBasePath() + '/git/file_history?file=' + encodeURIComponent(path)).then(function(res) {
|
|
79
75
|
return res.data;
|
|
80
76
|
});
|
|
81
77
|
}
|
|
82
78
|
|
|
83
79
|
function fetchCommitGraph() {
|
|
84
|
-
return axios.get(
|
|
80
|
+
return axios.get(window.mbeditorBasePath() + '/git/commit_graph').then(function(res) {
|
|
85
81
|
return res.data;
|
|
86
82
|
});
|
|
87
83
|
}
|
|
88
84
|
|
|
89
85
|
function fetchCommitDetail(sha) {
|
|
90
|
-
return axios.get(
|
|
86
|
+
return axios.get(window.mbeditorBasePath() + '/git/commit_detail?sha=' + encodeURIComponent(sha)).then(function(res) {
|
|
91
87
|
return res.data;
|
|
92
88
|
});
|
|
93
89
|
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
var SearchService = (function () {
|
|
2
2
|
var SEARCH_PAGE_SIZE = 50;
|
|
3
3
|
|
|
4
|
-
function basePath() {
|
|
5
|
-
return (window.MBEDITOR_BASE_PATH || '/mbeditor').replace(/\/$/, '');
|
|
6
|
-
}
|
|
7
|
-
|
|
8
4
|
var _miniSearch = new MiniSearch({
|
|
9
5
|
fields: ['path', 'name'], // indexed fields
|
|
10
6
|
storeFields: ['path', 'name', 'type'] // returned fields (type: 'file'|'dir')
|
|
11
7
|
});
|
|
12
8
|
|
|
9
|
+
// Flat doc list kept in sync with _miniSearch so we can do substring lookups.
|
|
10
|
+
var _allDocs = [];
|
|
11
|
+
|
|
13
12
|
function buildIndex(treeData) {
|
|
14
13
|
// Capture the tree data immediately so a subsequent refresh doesn't
|
|
15
14
|
// clobber us before the idle callback fires.
|
|
@@ -32,31 +31,46 @@ var SearchService = (function () {
|
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
traverse(snapshot);
|
|
34
|
+
_allDocs = docs.slice();
|
|
35
35
|
_miniSearch.removeAll();
|
|
36
36
|
_miniSearch.addAll(docs);
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
// Search files (and optionally folders) in the local MiniSearch index.
|
|
41
|
-
//
|
|
41
|
+
// Also performs a case-insensitive substring scan so that partial-word
|
|
42
|
+
// queries like "project" reliably find "projects_controller.rb".
|
|
43
|
+
// Returns merged results; MiniSearch scored entries come first.
|
|
42
44
|
function searchFiles(query) {
|
|
43
45
|
if (!query) return [];
|
|
44
|
-
|
|
46
|
+
var msResults = _miniSearch.search(query, { prefix: true, fuzzy: 0.2 });
|
|
47
|
+
// Substring fallback — catch anything MiniSearch missed
|
|
48
|
+
var q = query.toLowerCase();
|
|
49
|
+
var msIds = new Set(msResults.map(function(r) { return r.id; }));
|
|
50
|
+
var subResults = _allDocs.filter(function(doc) {
|
|
51
|
+
if (msIds.has(doc.id)) return false;
|
|
52
|
+
return doc.path.toLowerCase().indexOf(q) >= 0 || doc.name.toLowerCase().indexOf(q) >= 0;
|
|
53
|
+
}).map(function(doc) {
|
|
54
|
+
return { id: doc.id, path: doc.path, name: doc.name, type: doc.type, score: 0 };
|
|
55
|
+
});
|
|
56
|
+
return msResults.concat(subResults);
|
|
45
57
|
}
|
|
46
58
|
|
|
47
59
|
// Fetch one page of project-wide full-text search results.
|
|
48
60
|
// offset=0 replaces the EditorStore results list; offset>0 appends.
|
|
61
|
+
// The server includes total_count only on offset=0 (fast rg --count pass).
|
|
49
62
|
function projectSearch(query, offset, limit) {
|
|
50
|
-
if (!query) return Promise.resolve({ results: [], hasMore: false });
|
|
63
|
+
if (!query) return Promise.resolve({ results: [], hasMore: false, totalCount: 0 });
|
|
51
64
|
var off = (typeof offset === 'number') ? offset : 0;
|
|
52
65
|
var lim = (typeof limit === 'number') ? limit : SEARCH_PAGE_SIZE;
|
|
53
66
|
|
|
54
|
-
return axios.get(
|
|
67
|
+
return axios.get(window.mbeditorBasePath() + '/search', {
|
|
55
68
|
params: { q: query, offset: off, limit: lim }
|
|
56
69
|
}).then(function(res) {
|
|
57
70
|
var data = res.data;
|
|
58
|
-
var results
|
|
59
|
-
var hasMore
|
|
71
|
+
var results = Array.isArray(data) ? data : (data && data.results || []);
|
|
72
|
+
var hasMore = !Array.isArray(data) && !!(data && data.has_more);
|
|
73
|
+
var totalCount = (data && data.total_count != null) ? data.total_count : null;
|
|
60
74
|
|
|
61
75
|
if (off === 0) {
|
|
62
76
|
EditorStore.setState({ searchResults: results, searchHasMore: hasMore });
|
|
@@ -64,18 +78,37 @@ var SearchService = (function () {
|
|
|
64
78
|
var prev = EditorStore.getState().searchResults || [];
|
|
65
79
|
EditorStore.setState({ searchResults: prev.concat(results), searchHasMore: hasMore });
|
|
66
80
|
}
|
|
67
|
-
return { results: results, hasMore: hasMore };
|
|
81
|
+
return { results: results, hasMore: hasMore, totalCount: totalCount };
|
|
68
82
|
})
|
|
69
83
|
.catch(function(err) {
|
|
70
84
|
EditorStore.setStatus("Search failed: " + err.message, "error");
|
|
71
|
-
return { results: [], hasMore: false };
|
|
85
|
+
return { results: [], hasMore: false, totalCount: null };
|
|
72
86
|
});
|
|
73
87
|
}
|
|
74
88
|
|
|
89
|
+
// Fetch a specific page by index without touching EditorStore.
|
|
90
|
+
// Used by the random-access virtual scroll loader.
|
|
91
|
+
function fetchPage(query, pageIndex) {
|
|
92
|
+
if (!query) return Promise.resolve({ results: [], hasMore: false });
|
|
93
|
+
var offset = pageIndex * SEARCH_PAGE_SIZE;
|
|
94
|
+
return axios.get(basePath() + '/search', {
|
|
95
|
+
params: { q: query, offset: offset, limit: SEARCH_PAGE_SIZE }
|
|
96
|
+
}).then(function(res) {
|
|
97
|
+
var data = res.data;
|
|
98
|
+
var results = Array.isArray(data) ? data : (data && data.results || []);
|
|
99
|
+
var hasMore = !Array.isArray(data) && !!(data && data.has_more);
|
|
100
|
+
return { results: results, hasMore: hasMore };
|
|
101
|
+
}).catch(function(err) {
|
|
102
|
+
EditorStore.setStatus("Search failed: " + err.message, "error");
|
|
103
|
+
return { results: [], hasMore: false };
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
75
107
|
return {
|
|
76
108
|
buildIndex: buildIndex,
|
|
77
109
|
searchFiles: searchFiles,
|
|
78
110
|
projectSearch: projectSearch,
|
|
111
|
+
fetchPage: fetchPage,
|
|
79
112
|
PAGE_SIZE: SEARCH_PAGE_SIZE
|
|
80
113
|
};
|
|
81
114
|
})();
|
|
@@ -257,11 +257,11 @@ var TabManager = (function () {
|
|
|
257
257
|
});
|
|
258
258
|
EditorStore.setState({ panes: newPanes, focusedPaneId: paneId, activeTabId: tabId });
|
|
259
259
|
|
|
260
|
-
|
|
261
|
-
axios.get(basePath + '/git/combined_diff', { params: { scope: scope || 'local' } })
|
|
260
|
+
axios.get(window.mbeditorBasePath() + '/git/combined_diff', { params: { scope: scope || 'local' } })
|
|
262
261
|
.then(function(res) {
|
|
262
|
+
var data = res.data;
|
|
263
263
|
_updateTab(paneId, tabId, {
|
|
264
|
-
combinedDiffText: typeof
|
|
264
|
+
combinedDiffText: typeof data === 'string' ? data : (data && data.diff) || '',
|
|
265
265
|
combinedDiffLoaded: true
|
|
266
266
|
});
|
|
267
267
|
})
|
|
@@ -327,6 +327,18 @@ html, body, #mbeditor-root {
|
|
|
327
327
|
.tab-bar::-webkit-scrollbar { height: 3px; }
|
|
328
328
|
.tab-bar::-webkit-scrollbar-thumb { background: var(--ide-hover-bg); }
|
|
329
329
|
|
|
330
|
+
/* Wrap (multi-row) tab layout */
|
|
331
|
+
.tab-bar.tab-bar-wrap {
|
|
332
|
+
flex-wrap: wrap;
|
|
333
|
+
overflow-x: hidden;
|
|
334
|
+
overflow-y: visible;
|
|
335
|
+
height: auto;
|
|
336
|
+
}
|
|
337
|
+
.tab-bar.tab-bar-wrap .tab-item {
|
|
338
|
+
flex-shrink: 1;
|
|
339
|
+
flex-grow: 0;
|
|
340
|
+
}
|
|
341
|
+
|
|
330
342
|
.tab-item {
|
|
331
343
|
display: flex;
|
|
332
344
|
align-items: center;
|
|
@@ -475,16 +487,23 @@ html, body, #mbeditor-root {
|
|
|
475
487
|
.welcome-tips li { font-size: 12px; color: var(--ide-text-muted); }
|
|
476
488
|
.welcome-tips li i { color: var(--ide-text-muted); width: 14px; text-align: center; }
|
|
477
489
|
|
|
478
|
-
/* ── Search results
|
|
479
|
-
.search-results-
|
|
480
|
-
|
|
490
|
+
/* ── Search results header (fixed count above scroll area) ── */
|
|
491
|
+
.search-results-header {
|
|
492
|
+
flex-shrink: 0;
|
|
493
|
+
padding: 4px 10px 5px;
|
|
481
494
|
font-size: 11px;
|
|
482
495
|
color: var(--ide-text-muted);
|
|
483
496
|
border-bottom: 1px solid var(--ide-panel-bg-alt);
|
|
484
|
-
|
|
497
|
+
user-select: none;
|
|
485
498
|
}
|
|
486
|
-
.search-results-capped { color: #f0a040; }
|
|
487
499
|
.search-results-empty { padding: 12px 8px; font-size: 12px; color: var(--ide-text-muted); }
|
|
500
|
+
.search-loading-more {
|
|
501
|
+
padding: 10px;
|
|
502
|
+
font-size: 11px;
|
|
503
|
+
color: var(--ide-text-muted);
|
|
504
|
+
text-align: center;
|
|
505
|
+
user-select: none;
|
|
506
|
+
}
|
|
488
507
|
|
|
489
508
|
/* ── Shortcut Help Drawer ────────────────────────────────── */
|
|
490
509
|
.shelp-backdrop {
|
|
@@ -785,7 +804,7 @@ html, body, #mbeditor-root {
|
|
|
785
804
|
|
|
786
805
|
.statusbar-msg {
|
|
787
806
|
margin-left: auto;
|
|
788
|
-
color:
|
|
807
|
+
color: var(--ide-accent-text);
|
|
789
808
|
font-size: 11px;
|
|
790
809
|
overflow: hidden;
|
|
791
810
|
text-overflow: ellipsis;
|
|
@@ -1676,6 +1695,70 @@ html, body, #mbeditor-root {
|
|
|
1676
1695
|
to { opacity: 1; transform: translateY(0); }
|
|
1677
1696
|
}
|
|
1678
1697
|
|
|
1698
|
+
/* ── Draft restore dialog ───────────────────────────────────── */
|
|
1699
|
+
.ide-draft-restore-overlay {
|
|
1700
|
+
position: fixed;
|
|
1701
|
+
inset: 0;
|
|
1702
|
+
background: rgba(0,0,0,0.55);
|
|
1703
|
+
z-index: 10001;
|
|
1704
|
+
display: flex;
|
|
1705
|
+
align-items: center;
|
|
1706
|
+
justify-content: center;
|
|
1707
|
+
}
|
|
1708
|
+
.ide-draft-restore-dialog {
|
|
1709
|
+
background: var(--ide-panel-bg-alt);
|
|
1710
|
+
border: 1px solid var(--ide-border-input);
|
|
1711
|
+
border-radius: 8px;
|
|
1712
|
+
padding: 20px 24px;
|
|
1713
|
+
min-width: 320px;
|
|
1714
|
+
max-width: 480px;
|
|
1715
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.6);
|
|
1716
|
+
color: var(--ide-text);
|
|
1717
|
+
font-size: 13px;
|
|
1718
|
+
}
|
|
1719
|
+
.ide-draft-restore-title {
|
|
1720
|
+
font-size: 14px;
|
|
1721
|
+
font-weight: 600;
|
|
1722
|
+
margin-bottom: 10px;
|
|
1723
|
+
color: var(--ide-warning, #e5c07b);
|
|
1724
|
+
}
|
|
1725
|
+
.ide-draft-restore-body { margin-bottom: 8px; color: var(--ide-text-muted); }
|
|
1726
|
+
.ide-draft-restore-list {
|
|
1727
|
+
list-style: none;
|
|
1728
|
+
padding: 0;
|
|
1729
|
+
margin: 0 0 16px 0;
|
|
1730
|
+
max-height: 160px;
|
|
1731
|
+
overflow-y: auto;
|
|
1732
|
+
}
|
|
1733
|
+
.ide-draft-restore-list li {
|
|
1734
|
+
padding: 3px 0;
|
|
1735
|
+
font-family: monospace;
|
|
1736
|
+
font-size: 12px;
|
|
1737
|
+
color: var(--ide-accent-fg, #9cdcfe);
|
|
1738
|
+
}
|
|
1739
|
+
.ide-draft-restore-actions {
|
|
1740
|
+
display: flex;
|
|
1741
|
+
gap: 8px;
|
|
1742
|
+
justify-content: flex-end;
|
|
1743
|
+
}
|
|
1744
|
+
.ide-draft-restore-btn {
|
|
1745
|
+
padding: 5px 14px;
|
|
1746
|
+
border-radius: 4px;
|
|
1747
|
+
border: 1px solid var(--ide-border-input);
|
|
1748
|
+
background: var(--ide-input-bg);
|
|
1749
|
+
color: var(--ide-text);
|
|
1750
|
+
font-size: 12px;
|
|
1751
|
+
cursor: pointer;
|
|
1752
|
+
transition: background 0.1s;
|
|
1753
|
+
}
|
|
1754
|
+
.ide-draft-restore-btn:hover { background: var(--ide-hover-bg); }
|
|
1755
|
+
.ide-draft-restore-btn-primary {
|
|
1756
|
+
background: var(--ide-accent);
|
|
1757
|
+
color: var(--ide-accent-text);
|
|
1758
|
+
border-color: var(--ide-accent);
|
|
1759
|
+
}
|
|
1760
|
+
.ide-draft-restore-btn-primary:hover { opacity: 0.9; }
|
|
1761
|
+
|
|
1679
1762
|
/* ── Settings panel ─────────────────────────────────────────── */
|
|
1680
1763
|
.ide-settings-panel { display: flex; flex-direction: column; height: 100%; overflow-y: auto; }
|
|
1681
1764
|
.ide-settings-body { padding: 8px 12px; display: grid; grid-template-columns: 1fr 1fr; gap: 4px 10px; align-items: start; }
|