mbeditor 0.3.8 → 0.3.9

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.
@@ -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(basePath() + '/workspace').then(function(res) { return res.data; });
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(basePath() + '/files').then(function(res) { return res.data; });
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(basePath() + '/file', { params: params }).then(function(res) { return res.data; });
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(basePath() + '/file', { path: path, code: code }).then(function(res) { return res.data; });
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(basePath() + '/create_file', { path: path, code: code || '' }).then(function(res) { return res.data; });
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(basePath() + '/create_dir', { path: path }).then(function(res) { return res.data; });
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(basePath() + '/rename', { path: path, new_path: newPath }).then(function(res) { return res.data; });
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(basePath() + '/delete', { data: { path: path } }).then(function(res) { return res.data; });
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(basePath() + '/lint', { path: path, code: code }).then(function(res) { return res.data; });
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(basePath() + '/quick_fix', { path: path, code: code, cop_name: copName }).then(function(res) { return res.data; });
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(basePath() + '/format', { path: path, code: code }).then(function(res) { return res.data; });
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(basePath() + '/test', { path: path }).then(function(res) { return res.data; });
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(basePath() + '/ping', { timeout: 4000 }).then(function(res) { return res.data; });
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(basePath() + '/state').then(function(res) { return res.data; });
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(basePath() + '/state', { state: state }).then(function(res) { return res.data; });
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(basePath() + '/branch_state', { params: { branch: branch } }).then(function(res) { return res.data; });
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(basePath() + '/branch_state', { branch: branch, state: state }).then(function(res) { return res.data; });
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(basePath() + '/prune_branch_states').then(function(res) { return res.data; });
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(basePath() + '/definition', config).then(function(res) { return res.data; });
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(basePath() + '/git_info')
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(basePath() + '/git_status')
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(basePath() + '/git/diff' + query).then(function(res) {
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(basePath() + '/git/blame?file=' + encodeURIComponent(path)).then(function(res) {
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(basePath() + '/git/file_history?file=' + encodeURIComponent(path)).then(function(res) {
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(basePath() + '/git/commit_graph').then(function(res) {
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(basePath() + '/git/commit_detail?sha=' + encodeURIComponent(sha)).then(function(res) {
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
- // Returns raw MiniSearch results; caller can filter by .type.
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
- return _miniSearch.search(query, { prefix: true, fuzzy: 0.2 });
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(basePath() + '/search', {
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 = Array.isArray(data) ? data : (data && data.results || []);
59
- var hasMore = !Array.isArray(data) && !!(data && data.has_more);
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
- var basePath = (window.MBEDITOR_BASE_PATH || '/mbeditor').replace(/\/$/, '');
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 res.data === 'string' ? res.data : '',
264
+ combinedDiffText: typeof data === 'string' ? data : (data && data.diff) || '',
265
265
  combinedDiffLoaded: true
266
266
  });
267
267
  })
@@ -475,16 +475,23 @@ html, body, #mbeditor-root {
475
475
  .welcome-tips li { font-size: 12px; color: var(--ide-text-muted); }
476
476
  .welcome-tips li i { color: var(--ide-text-muted); width: 14px; text-align: center; }
477
477
 
478
- /* ── Search results meta ─────────────────────────────────── */
479
- .search-results-meta {
480
- padding: 4px 8px 6px;
478
+ /* ── Search results header (fixed count above scroll area) ── */
479
+ .search-results-header {
480
+ flex-shrink: 0;
481
+ padding: 4px 10px 5px;
481
482
  font-size: 11px;
482
483
  color: var(--ide-text-muted);
483
484
  border-bottom: 1px solid var(--ide-panel-bg-alt);
484
- margin-bottom: 4px;
485
+ user-select: none;
485
486
  }
486
- .search-results-capped { color: #f0a040; }
487
487
  .search-results-empty { padding: 12px 8px; font-size: 12px; color: var(--ide-text-muted); }
488
+ .search-loading-more {
489
+ padding: 10px;
490
+ font-size: 11px;
491
+ color: var(--ide-text-muted);
492
+ text-align: center;
493
+ user-select: none;
494
+ }
488
495
 
489
496
  /* ── Shortcut Help Drawer ────────────────────────────────── */
490
497
  .shelp-backdrop {