mbeditor 0.1.5 → 0.1.6
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/README.md +16 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +54 -9
- data/app/assets/javascripts/mbeditor/components/GitPanel.js +94 -0
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +186 -10
- data/app/assets/javascripts/mbeditor/components/TabBar.js +2 -2
- data/app/assets/javascripts/mbeditor/editor_plugins.js +49 -0
- data/app/assets/javascripts/mbeditor/editor_store.js +1 -0
- data/app/assets/javascripts/mbeditor/file_service.js +6 -1
- data/app/assets/javascripts/mbeditor/search_service.js +8 -4
- data/app/assets/stylesheets/mbeditor/application.css +51 -0
- data/app/assets/stylesheets/mbeditor/editor.css +281 -0
- data/app/controllers/mbeditor/editors_controller.rb +120 -10
- data/app/controllers/mbeditor/git_controller.rb +14 -4
- data/app/services/mbeditor/git_service.rb +11 -1
- data/app/views/layouts/mbeditor/application.html.erb +5 -1
- data/config/routes.rb +1 -0
- data/lib/mbeditor/rack/silence_ping_request.rb +20 -6
- data/lib/mbeditor/version.rb +1 -1
- data/public/ts_worker.js +5 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 706d2307424a25e3a1b19683326d62de74d98568f755971d6eeb592ad5bae1a4
|
|
4
|
+
data.tar.gz: 8cfb289f2cc0fd18eb2e8c55ae3a2f766712d68231e695b2e02a1e7778207c2d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8b691b32511249206e9af0e7ceeb6b26f9cf5bf63cd4b035e8b7fa9411ba05480b4c8795cdcf7a20ffefd536803a61e79c84edb7874fad7d7308aa4dca622b2f
|
|
7
|
+
data.tar.gz: 41c25d7ad6113629fd50267ceff4e9f07490627f9fc2a8eba2fc2804743e8ba7e3c3c6bedc30c4a225e6a177c3354b1282a88df874086bf2e969bced2d20ea65
|
data/README.md
CHANGED
|
@@ -47,7 +47,7 @@ mount Mbeditor::Engine, at: "/mbeditor"
|
|
|
47
47
|
Use a single initializer to set the engine options you need:
|
|
48
48
|
|
|
49
49
|
```ruby
|
|
50
|
-
|
|
50
|
+
Mbeditor.configure do |config|
|
|
51
51
|
config.allowed_environments = [:development]
|
|
52
52
|
# config.workspace_root = Rails.root
|
|
53
53
|
config.excluded_paths = %w[.git tmp log node_modules .bundle coverage vendor/bundle]
|
|
@@ -70,6 +70,17 @@ Available options:
|
|
|
70
70
|
- `redmine_url` sets the Redmine base URL. Required when `redmine_enabled` is `true`.
|
|
71
71
|
- `redmine_api_key` sets the Redmine API key. Required when `redmine_enabled` is `true`.
|
|
72
72
|
|
|
73
|
+
## Keyboard Shortcuts
|
|
74
|
+
|
|
75
|
+
| Shortcut | Action |
|
|
76
|
+
|----------|--------|
|
|
77
|
+
| `Ctrl+P` | Quick-open file by name |
|
|
78
|
+
| `Ctrl+S` | Save the active file |
|
|
79
|
+
| `Ctrl+Shift+S` | Save all dirty files |
|
|
80
|
+
| `Alt+Shift+F` | Format the active file |
|
|
81
|
+
| `Ctrl+Shift+G` | Toggle the git panel |
|
|
82
|
+
| `Ctrl+Z` / `Ctrl+Y` | Undo / Redo (Monaco built-in) |
|
|
83
|
+
|
|
73
84
|
## Host Requirements (Optional)
|
|
74
85
|
The gem keeps host/tooling responsibilities in the host app:
|
|
75
86
|
- `rubocop` and `rubocop-rails` gems (optional, required for Ruby lint/format endpoints)
|
|
@@ -99,6 +110,10 @@ The gem includes syntax highlighting for common Rails and React development file
|
|
|
99
110
|
|
|
100
111
|
These language modules are packaged locally with the gem for true offline operation. No network fallback is needed—all highlighting works without internet connectivity.
|
|
101
112
|
|
|
113
|
+
## Asset Pipeline
|
|
114
|
+
|
|
115
|
+
Mbeditor requires **Sprockets** (`sprockets-rails >= 3.4`). Host apps using **Propshaft** as their asset pipeline are not supported — the engine depends on Sprockets directives to load its JavaScript and CSS assets.
|
|
116
|
+
|
|
102
117
|
## Development
|
|
103
118
|
|
|
104
119
|
A minimal dummy Rails app is included for local development and testing:
|
|
@@ -15,9 +15,12 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
15
15
|
var onContentChange = _ref.onContentChange;
|
|
16
16
|
var markers = _ref.markers;
|
|
17
17
|
var gitAvailable = _ref.gitAvailable === true;
|
|
18
|
+
var onFormat = _ref.onFormat;
|
|
19
|
+
var editorPrefs = _ref.editorPrefs || {};
|
|
18
20
|
|
|
19
21
|
var editorRef = useRef(null);
|
|
20
22
|
var monacoRef = useRef(null);
|
|
23
|
+
var latestContentRef = useRef('');
|
|
21
24
|
|
|
22
25
|
var _useState = useState('');
|
|
23
26
|
var _useState2 = _slicedToArray(_useState, 2);
|
|
@@ -42,6 +45,9 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
42
45
|
var blameDecorationsRef = useRef([]);
|
|
43
46
|
var blameZoneIdsRef = useRef([]);
|
|
44
47
|
|
|
48
|
+
var onFormatRef = useRef(onFormat);
|
|
49
|
+
onFormatRef.current = onFormat;
|
|
50
|
+
|
|
45
51
|
var clearBlameZones = function clearBlameZones(editor) {
|
|
46
52
|
if (!editor) return;
|
|
47
53
|
if (blameZoneIdsRef.current.length === 0) return;
|
|
@@ -116,15 +122,15 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
116
122
|
var editor = window.monaco.editor.create(editorRef.current, {
|
|
117
123
|
value: tab.content,
|
|
118
124
|
language: language,
|
|
119
|
-
theme: 'vs-dark',
|
|
125
|
+
theme: editorPrefs.theme || 'vs-dark',
|
|
120
126
|
automaticLayout: true,
|
|
121
127
|
minimap: { enabled: false },
|
|
122
128
|
renderLineHighlight: 'none',
|
|
123
129
|
bracketPairColorization: { enabled: true },
|
|
124
|
-
fontFamily: "'JetBrains Mono', 'Fira Code', Consolas, 'Courier New', monospace",
|
|
125
|
-
fontSize: 13,
|
|
126
|
-
tabSize: 4,
|
|
127
|
-
insertSpaces:
|
|
130
|
+
fontFamily: editorPrefs.fontFamily || "'JetBrains Mono', 'Fira Code', Consolas, 'Courier New', monospace",
|
|
131
|
+
fontSize: editorPrefs.fontSize || 13,
|
|
132
|
+
tabSize: editorPrefs.tabSize || 4,
|
|
133
|
+
insertSpaces: typeof editorPrefs.insertSpaces === 'boolean' ? editorPrefs.insertSpaces : false,
|
|
128
134
|
wordWrap: 'on',
|
|
129
135
|
linkedEditing: true, // Enables Auto-Rename Tag natively!
|
|
130
136
|
fixedOverflowWidgets: true,
|
|
@@ -143,7 +149,20 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
143
149
|
monacoRef.current = editor;
|
|
144
150
|
window.__mbeditorActiveEditor = editor;
|
|
145
151
|
|
|
152
|
+
// Stash the workspace-relative path on the model so code-action providers
|
|
153
|
+
// can identify which file they are operating on without needing React state.
|
|
146
154
|
var modelObj = editor.getModel();
|
|
155
|
+
if (modelObj) modelObj._mbeditorPath = tab.path;
|
|
156
|
+
|
|
157
|
+
var formatActionDisposable = editor.addAction({
|
|
158
|
+
id: 'mbeditor.formatDocument',
|
|
159
|
+
label: 'Format Document',
|
|
160
|
+
contextMenuGroupId: '1_modification',
|
|
161
|
+
contextMenuOrder: 1.5,
|
|
162
|
+
run: function() {
|
|
163
|
+
if (onFormatRef.current) onFormatRef.current();
|
|
164
|
+
}
|
|
165
|
+
});
|
|
147
166
|
|
|
148
167
|
var editorPluginDisposable = null;
|
|
149
168
|
if (window.MbeditorEditorPlugins && window.MbeditorEditorPlugins.attachEditorFeatures) {
|
|
@@ -153,7 +172,7 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
153
172
|
// Change listener
|
|
154
173
|
var contentDisposable = modelObj.onDidChangeContent(function (e) {
|
|
155
174
|
var val = editor.getValue();
|
|
156
|
-
var currentContent =
|
|
175
|
+
var currentContent = latestContentRef.current;
|
|
157
176
|
|
|
158
177
|
// Normalize before comparing to prevent false positive dirty edits
|
|
159
178
|
var vNorm = val.replace(/\r\n/g, '\n');
|
|
@@ -171,6 +190,7 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
171
190
|
window.__mbeditorActiveEditor = null;
|
|
172
191
|
}
|
|
173
192
|
if (editorPluginDisposable) editorPluginDisposable.dispose();
|
|
193
|
+
formatActionDisposable.dispose();
|
|
174
194
|
contentDisposable.dispose();
|
|
175
195
|
editor.dispose();
|
|
176
196
|
};
|
|
@@ -179,7 +199,7 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
179
199
|
// Listen for external content changes (e.g. after Format/Save/Load)
|
|
180
200
|
useEffect(function () {
|
|
181
201
|
var editor = monacoRef.current;
|
|
182
|
-
if (editor)
|
|
202
|
+
if (editor) latestContentRef.current = tab.content; // keep ref in sync for closure
|
|
183
203
|
|
|
184
204
|
if (editor && editor.getValue() !== tab.content) {
|
|
185
205
|
if (typeof tab.content !== 'string') return;
|
|
@@ -207,6 +227,21 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
207
227
|
}
|
|
208
228
|
}, [tab.content]);
|
|
209
229
|
|
|
230
|
+
// Apply editorPrefs changes to a running editor without remounting
|
|
231
|
+
useEffect(function () {
|
|
232
|
+
if (!window.monaco) return;
|
|
233
|
+
var theme = editorPrefs.theme || 'vs-dark';
|
|
234
|
+
window.monaco.editor.setTheme(theme);
|
|
235
|
+
if (monacoRef.current) {
|
|
236
|
+
monacoRef.current.updateOptions({
|
|
237
|
+
fontSize: editorPrefs.fontSize || 13,
|
|
238
|
+
fontFamily: editorPrefs.fontFamily || "'JetBrains Mono', 'Fira Code', Consolas, 'Courier New', monospace",
|
|
239
|
+
tabSize: editorPrefs.tabSize || 4,
|
|
240
|
+
insertSpaces: typeof editorPrefs.insertSpaces === 'boolean' ? editorPrefs.insertSpaces : false
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}, [editorPrefs]);
|
|
244
|
+
|
|
210
245
|
// Jump to line if specified
|
|
211
246
|
useEffect(function () {
|
|
212
247
|
if (tab.gotoLine && monacoRef.current) {
|
|
@@ -230,8 +265,13 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
230
265
|
var model = monacoRef.current.getModel();
|
|
231
266
|
if (model) {
|
|
232
267
|
var monacoMarkers = markers.map(function (m) {
|
|
268
|
+
var sev = m.severity === 'error'
|
|
269
|
+
? window.monaco.MarkerSeverity.Error
|
|
270
|
+
: window.monaco.MarkerSeverity.Warning;
|
|
233
271
|
return {
|
|
234
|
-
severity:
|
|
272
|
+
severity: sev,
|
|
273
|
+
source: 'rubocop',
|
|
274
|
+
code: m.copName || '',
|
|
235
275
|
message: m.message,
|
|
236
276
|
startLineNumber: m.startLine,
|
|
237
277
|
startColumn: m.startCol,
|
|
@@ -240,6 +280,11 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
240
280
|
};
|
|
241
281
|
});
|
|
242
282
|
window.monaco.editor.setModelMarkers(model, 'rubocop', monacoMarkers);
|
|
283
|
+
// Track which cops are autocorrectable so the quick-fix provider can
|
|
284
|
+
// skip lightbulbs for cops that can never be machine-fixed.
|
|
285
|
+
model._mbeditorCorrectableCops = new Set(
|
|
286
|
+
markers.filter(function(m) { return m.correctable && m.copName; }).map(function(m) { return m.copName; })
|
|
287
|
+
);
|
|
243
288
|
}
|
|
244
289
|
}
|
|
245
290
|
}, [markers, tab.id]);
|
|
@@ -393,7 +438,7 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
393
438
|
}, [markdownContent, isMarkdown]);
|
|
394
439
|
|
|
395
440
|
if (tab.isDiff) {
|
|
396
|
-
var isDiffDark =
|
|
441
|
+
var isDiffDark = (editorPrefs.theme || 'vs-dark') !== 'vs' && (editorPrefs.theme || 'vs-dark') !== 'hc-light';
|
|
397
442
|
return React.createElement(window.DiffViewer || DiffViewer, {
|
|
398
443
|
path: tab.repoPath || tab.path,
|
|
399
444
|
original: tab.diffOriginal || "",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
var GitPanel = function GitPanel(_ref) {
|
|
2
2
|
var gitInfo = _ref.gitInfo;
|
|
3
3
|
var error = _ref.error;
|
|
4
|
+
var redmineEnabled = _ref.redmineEnabled;
|
|
4
5
|
var onOpenFile = _ref.onOpenFile;
|
|
5
6
|
var onOpenDiff = _ref.onOpenDiff;
|
|
6
7
|
var onOpenAllChanges = _ref.onOpenAllChanges;
|
|
@@ -8,6 +9,7 @@ var GitPanel = function GitPanel(_ref) {
|
|
|
8
9
|
var onClose = _ref.onClose;
|
|
9
10
|
|
|
10
11
|
var useState = React.useState;
|
|
12
|
+
var useEffect = React.useEffect;
|
|
11
13
|
|
|
12
14
|
var _s1 = useState(true); var localExpanded = _s1[0]; var setLocalExpanded = _s1[1];
|
|
13
15
|
var _s2 = useState(true); var branchExpanded = _s2[0]; var setBranchExpanded = _s2[1];
|
|
@@ -18,6 +20,12 @@ var GitPanel = function GitPanel(_ref) {
|
|
|
18
20
|
var _s5 = useState({}); var commitFiles = _s5[0]; var setCommitFiles = _s5[1];
|
|
19
21
|
var _s6 = useState(false); var refreshing = _s6[0]; var setRefreshing = _s6[1];
|
|
20
22
|
|
|
23
|
+
// ── Redmine state ───────────────────────────────────────────────────────────
|
|
24
|
+
var _sr1 = useState(null); var redmineIssue = _sr1[0]; var setRedmineIssue = _sr1[1];
|
|
25
|
+
var _sr2 = useState(null); var redmineError = _sr2[0]; var setRedmineError = _sr2[1];
|
|
26
|
+
var _sr3 = useState(false); var redmineLoading = _sr3[0]; var setRedmineLoading = _sr3[1];
|
|
27
|
+
var _sr4 = useState(true); var redmineExpanded = _sr4[0]; var setRedmineExpanded = _sr4[1];
|
|
28
|
+
|
|
21
29
|
var workingTree = gitInfo && gitInfo.workingTree || [];
|
|
22
30
|
var unpushedFiles = gitInfo && gitInfo.unpushedFiles || [];
|
|
23
31
|
var unpushedCommits = gitInfo && gitInfo.unpushedCommits || [];
|
|
@@ -32,6 +40,39 @@ var GitPanel = function GitPanel(_ref) {
|
|
|
32
40
|
return Object.assign({}, c, { isLocal: !!localHashes[c.hash] });
|
|
33
41
|
});
|
|
34
42
|
|
|
43
|
+
// Extract the first Redmine ticket ID (#123) from recent branch commit messages
|
|
44
|
+
var redmineTicketId = null;
|
|
45
|
+
if (redmineEnabled) {
|
|
46
|
+
for (var ci = 0; ci < branchCommits.length; ci++) {
|
|
47
|
+
var titleMatch = branchCommits[ci] && branchCommits[ci].title && branchCommits[ci].title.match(/#(\d+)/);
|
|
48
|
+
if (titleMatch) { redmineTicketId = titleMatch[1]; break; }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Fetch Redmine issue whenever the ticket ID changes
|
|
53
|
+
useEffect(function () {
|
|
54
|
+
if (!redmineEnabled) return;
|
|
55
|
+
if (!redmineTicketId) {
|
|
56
|
+
setRedmineIssue(null);
|
|
57
|
+
setRedmineError(null);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
setRedmineLoading(true);
|
|
61
|
+
setRedmineIssue(null);
|
|
62
|
+
setRedmineError(null);
|
|
63
|
+
var basePath = (window.MBEDITOR_BASE_PATH || '/mbeditor').replace(/\/$/, '');
|
|
64
|
+
axios.get(basePath + '/redmine/issue/' + redmineTicketId)
|
|
65
|
+
.then(function (res) {
|
|
66
|
+
setRedmineIssue(res.data);
|
|
67
|
+
setRedmineLoading(false);
|
|
68
|
+
})
|
|
69
|
+
.catch(function (err) {
|
|
70
|
+
var msg = (err.response && err.response.data && err.response.data.error) || err.message || 'Failed to load Redmine issue.';
|
|
71
|
+
setRedmineError(msg);
|
|
72
|
+
setRedmineLoading(false);
|
|
73
|
+
});
|
|
74
|
+
}, [redmineEnabled, redmineTicketId]);
|
|
75
|
+
|
|
35
76
|
var statusMeta = function statusMeta(rawStatus) {
|
|
36
77
|
var raw = (rawStatus || '').trim();
|
|
37
78
|
if (raw === '??') return { badge: 'NEW', cssKey: 'A', description: 'Untracked' };
|
|
@@ -307,6 +348,59 @@ var GitPanel = function GitPanel(_ref) {
|
|
|
307
348
|
|
|
308
349
|
error && React.createElement('div', { className: 'git-error' }, error),
|
|
309
350
|
|
|
351
|
+
// ── Redmine Issue Section ───────────────────────────────────────────────
|
|
352
|
+
redmineEnabled && React.createElement(
|
|
353
|
+
'div',
|
|
354
|
+
{ className: 'git-section ' + (redmineExpanded ? 'expanded' : '') + ' git-section--redmine' },
|
|
355
|
+
React.createElement(
|
|
356
|
+
'div',
|
|
357
|
+
{ className: 'git-section-title' },
|
|
358
|
+
React.createElement(
|
|
359
|
+
'div',
|
|
360
|
+
{ className: 'git-section-title-main hoverable', onClick: function () { setRedmineExpanded(!redmineExpanded); } },
|
|
361
|
+
React.createElement(
|
|
362
|
+
'span',
|
|
363
|
+
{ className: 'git-redmine-section-label' },
|
|
364
|
+
React.createElement('i', { className: 'fas ' + (redmineExpanded ? 'fa-chevron-down' : 'fa-chevron-right'), style: { width: '14px', fontSize: '10px' } }),
|
|
365
|
+
'\u2002Redmine',
|
|
366
|
+
redmineTicketId && React.createElement('span', { className: 'git-section-count', style: { marginLeft: '4px' } }, '#' + redmineTicketId)
|
|
367
|
+
),
|
|
368
|
+
redmineIssue && React.createElement('span', { className: 'redmine-badge redmine-badge--section' }, redmineIssue.status)
|
|
369
|
+
)
|
|
370
|
+
),
|
|
371
|
+
redmineExpanded && React.createElement(
|
|
372
|
+
'div',
|
|
373
|
+
{ className: 'git-redmine-content' },
|
|
374
|
+
redmineLoading
|
|
375
|
+
? React.createElement('div', { className: 'git-empty' },
|
|
376
|
+
React.createElement('i', { className: 'fas fa-spinner fa-spin', style: { marginRight: '6px' } }),
|
|
377
|
+
'Loading issue\u2026'
|
|
378
|
+
)
|
|
379
|
+
: !redmineTicketId
|
|
380
|
+
? React.createElement('div', { className: 'git-empty' }, 'No matching ticket found in branch commits.')
|
|
381
|
+
: redmineError
|
|
382
|
+
? React.createElement('div', { className: 'git-redmine-error' },
|
|
383
|
+
React.createElement('i', { className: 'fas fa-exclamation-circle', style: { marginRight: '6px', color: '#f48771' } }),
|
|
384
|
+
redmineError
|
|
385
|
+
)
|
|
386
|
+
: redmineIssue
|
|
387
|
+
? React.createElement(
|
|
388
|
+
'div',
|
|
389
|
+
{ className: 'git-redmine-issue' },
|
|
390
|
+
React.createElement('div', { className: 'git-redmine-title' }, redmineIssue.title),
|
|
391
|
+
redmineIssue.description
|
|
392
|
+
? React.createElement('div', { className: 'git-redmine-desc' }, redmineIssue.description)
|
|
393
|
+
: null,
|
|
394
|
+
React.createElement(
|
|
395
|
+
'div',
|
|
396
|
+
{ className: 'git-redmine-footer' },
|
|
397
|
+
redmineIssue.author || ''
|
|
398
|
+
)
|
|
399
|
+
)
|
|
400
|
+
: null
|
|
401
|
+
)
|
|
402
|
+
),
|
|
403
|
+
|
|
310
404
|
// ── Section 1: Local Changes ────────────────────────────────────────────
|
|
311
405
|
React.createElement(
|
|
312
406
|
'div',
|
|
@@ -18,6 +18,14 @@ var GIT_PANEL_MIN_WIDTH = 280;
|
|
|
18
18
|
var PANE_MIN_WIDTH_PERCENT = 20;
|
|
19
19
|
var PANE_MAX_WIDTH_PERCENT = 80;
|
|
20
20
|
|
|
21
|
+
var DEFAULT_EDITOR_PREFS = {
|
|
22
|
+
theme: 'vs-dark',
|
|
23
|
+
fontSize: 13,
|
|
24
|
+
fontFamily: "'JetBrains Mono', 'Fira Code', Consolas, 'Courier New', monospace",
|
|
25
|
+
tabSize: 4,
|
|
26
|
+
insertSpaces: false
|
|
27
|
+
};
|
|
28
|
+
|
|
21
29
|
var SidebarActionButton = function SidebarActionButton(_ref) {
|
|
22
30
|
var title = _ref.title;
|
|
23
31
|
var iconClass = _ref.iconClass;
|
|
@@ -247,6 +255,16 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
247
255
|
var gitAvailable = _useState18e2[0];
|
|
248
256
|
var setGitAvailable = _useState18e2[1];
|
|
249
257
|
|
|
258
|
+
var _useState18f = useState(false);
|
|
259
|
+
var _useState18f2 = _slicedToArray(_useState18f, 2);
|
|
260
|
+
var redmineEnabled = _useState18f2[0];
|
|
261
|
+
var setRedmineEnabled = _useState18f2[1];
|
|
262
|
+
|
|
263
|
+
var _useState18p = useState(DEFAULT_EDITOR_PREFS);
|
|
264
|
+
var _useState18p2 = _slicedToArray(_useState18p, 2);
|
|
265
|
+
var editorPrefs = _useState18p2[0];
|
|
266
|
+
var setEditorPrefs = _useState18p2[1];
|
|
267
|
+
|
|
250
268
|
var _useState19 = useState({
|
|
251
269
|
openEditors: false,
|
|
252
270
|
projects: false
|
|
@@ -461,6 +479,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
461
479
|
}
|
|
462
480
|
if (workspace && typeof workspace.rubocopAvailable === 'boolean') {
|
|
463
481
|
setRubocopAvailable(workspace.rubocopAvailable);
|
|
482
|
+
window.MBEDITOR_RUBOCOP_AVAILABLE = workspace.rubocopAvailable;
|
|
464
483
|
}
|
|
465
484
|
if (workspace && typeof workspace.hamlLintAvailable === 'boolean') {
|
|
466
485
|
setHamlLintAvailable(workspace.hamlLintAvailable);
|
|
@@ -468,6 +487,9 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
468
487
|
if (workspace && typeof workspace.gitAvailable === 'boolean') {
|
|
469
488
|
setGitAvailable(workspace.gitAvailable);
|
|
470
489
|
}
|
|
490
|
+
if (workspace && typeof workspace.redmineEnabled === 'boolean') {
|
|
491
|
+
setRedmineEnabled(workspace.redmineEnabled);
|
|
492
|
+
}
|
|
471
493
|
});
|
|
472
494
|
GitService.fetchStatus();
|
|
473
495
|
|
|
@@ -521,6 +543,9 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
521
543
|
if (typeof savedState.gitPanelWidth === 'number') {
|
|
522
544
|
setGitPanelWidth(savedState.gitPanelWidth);
|
|
523
545
|
}
|
|
546
|
+
if (savedState.editorPrefs && typeof savedState.editorPrefs === 'object') {
|
|
547
|
+
setEditorPrefs(Object.assign({}, DEFAULT_EDITOR_PREFS, savedState.editorPrefs));
|
|
548
|
+
}
|
|
524
549
|
});
|
|
525
550
|
}
|
|
526
551
|
});
|
|
@@ -546,6 +571,18 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
546
571
|
}
|
|
547
572
|
})();
|
|
548
573
|
}
|
|
574
|
+
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'S') {
|
|
575
|
+
e.preventDefault();
|
|
576
|
+
handleSaveAll();
|
|
577
|
+
}
|
|
578
|
+
if (e.altKey && e.shiftKey && e.key === 'F') {
|
|
579
|
+
e.preventDefault();
|
|
580
|
+
handleFormat();
|
|
581
|
+
}
|
|
582
|
+
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'G') {
|
|
583
|
+
e.preventDefault();
|
|
584
|
+
toggleGitPanel();
|
|
585
|
+
}
|
|
549
586
|
if (e.key === 'Escape') {
|
|
550
587
|
setContextMenu(null);
|
|
551
588
|
setShowHelp(false);
|
|
@@ -790,7 +827,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
790
827
|
return {
|
|
791
828
|
id: p.id,
|
|
792
829
|
activeTabId: p.activeTabId,
|
|
793
|
-
tabs: p.tabs.filter(function(t) { return !t.isCombinedDiff; }).map(function (t) {
|
|
830
|
+
tabs: p.tabs.filter(function(t) { return !t.isCombinedDiff && !t.isSettings; }).map(function (t) {
|
|
794
831
|
return {
|
|
795
832
|
id: t.id,
|
|
796
833
|
path: t.path,
|
|
@@ -807,17 +844,21 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
807
844
|
})
|
|
808
845
|
};
|
|
809
846
|
});
|
|
810
|
-
FileService.saveState({ panes: lightweightPanes, focusedPaneId: st.focusedPaneId, collapsedSections: collapsedSections, expandedDirs: expandedDirs, showGitPanel: showGitPanel, gitPanelWidth: gitPanelWidth });
|
|
847
|
+
FileService.saveState({ panes: lightweightPanes, focusedPaneId: st.focusedPaneId, collapsedSections: collapsedSections, expandedDirs: expandedDirs, showGitPanel: showGitPanel, gitPanelWidth: gitPanelWidth, editorPrefs: editorPrefs });
|
|
811
848
|
}, 1000);
|
|
812
849
|
return function () {
|
|
813
850
|
return clearTimeout(timeoutId);
|
|
814
851
|
};
|
|
815
|
-
}, [state.panes, state.focusedPaneId, collapsedSections, expandedDirs, showGitPanel, gitPanelWidth]);
|
|
852
|
+
}, [state.panes, state.focusedPaneId, collapsedSections, expandedDirs, showGitPanel, gitPanelWidth, editorPrefs]);
|
|
853
|
+
|
|
854
|
+
useEffect(function() {
|
|
855
|
+
document.documentElement.setAttribute('data-theme', editorPrefs.theme || 'vs-dark');
|
|
856
|
+
}, [editorPrefs.theme]);
|
|
816
857
|
|
|
817
858
|
var focusedPane = state.panes.find(function (p) {
|
|
818
859
|
return p.id === state.focusedPaneId;
|
|
819
|
-
}) || state.panes[0];
|
|
820
|
-
var activeTab = focusedPane.tabs.find(function (t) {
|
|
860
|
+
}) || state.panes[0] || null;
|
|
861
|
+
var activeTab = focusedPane && focusedPane.tabs.find(function (t) {
|
|
821
862
|
return t.id === focusedPane.activeTabId;
|
|
822
863
|
});
|
|
823
864
|
|
|
@@ -851,12 +892,12 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
851
892
|
if (isRubyPath(activeTab.path) && !rubocopAvailable) return;
|
|
852
893
|
if (activeTab.path.endsWith('.haml') && !hamlLintAvailable) return;
|
|
853
894
|
|
|
854
|
-
_debouncedAutoLint(activeTab, focusedPane.id);
|
|
895
|
+
_debouncedAutoLint(activeTab, focusedPane ? focusedPane.id : null);
|
|
855
896
|
|
|
856
897
|
return function () {
|
|
857
898
|
_debouncedAutoLint.cancel();
|
|
858
899
|
};
|
|
859
|
-
}, [focusedPane.id, activeTab ? activeTab.id : null, activeTab ? activeTab.content : null, rubocopAvailable, hamlLintAvailable]);
|
|
900
|
+
}, [focusedPane ? focusedPane.id : null, activeTab ? activeTab.id : null, activeTab ? activeTab.content : null, rubocopAvailable, hamlLintAvailable]);
|
|
860
901
|
|
|
861
902
|
var handleOpenCommitGraph = function handleOpenCommitGraph() {
|
|
862
903
|
var paneId = state.focusedPaneId || 1;
|
|
@@ -1072,6 +1113,9 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1072
1113
|
}
|
|
1073
1114
|
};
|
|
1074
1115
|
|
|
1116
|
+
var onFormatRef = useRef(handleFormat);
|
|
1117
|
+
onFormatRef.current = handleFormat;
|
|
1118
|
+
|
|
1075
1119
|
var _debouncedSearch = useRef(window._.debounce(function (q) {
|
|
1076
1120
|
if (!q.trim()) {
|
|
1077
1121
|
searchRequestIdRef.current += 1;
|
|
@@ -1084,7 +1128,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1084
1128
|
EditorStore.setStatus("Searching project...", "info");
|
|
1085
1129
|
SearchService.projectSearch(q).then(function (res) {
|
|
1086
1130
|
if (searchRequestIdRef.current === requestId) {
|
|
1087
|
-
|
|
1131
|
+
var count = res && res.results ? res.results.length : (Array.isArray(res) ? res.length : 0);
|
|
1132
|
+
EditorStore.setStatus("Found " + count + " result" + (count !== 1 ? "s" : ""), "success");
|
|
1088
1133
|
}
|
|
1089
1134
|
}).finally(function () {
|
|
1090
1135
|
if (searchRequestIdRef.current === requestId) {
|
|
@@ -1514,6 +1559,36 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1514
1559
|
});
|
|
1515
1560
|
};
|
|
1516
1561
|
|
|
1562
|
+
function openSettingsTab() {
|
|
1563
|
+
var st = EditorStore.getState();
|
|
1564
|
+
var foundPaneId = null;
|
|
1565
|
+
var foundTab = null;
|
|
1566
|
+
st.panes.forEach(function(p) {
|
|
1567
|
+
if (!foundTab) {
|
|
1568
|
+
var t = p.tabs.find(function(tab) { return tab.path === '__settings__'; });
|
|
1569
|
+
if (t) { foundTab = t; foundPaneId = p.id; }
|
|
1570
|
+
}
|
|
1571
|
+
});
|
|
1572
|
+
if (foundTab) {
|
|
1573
|
+
var newPanes = st.panes.map(function(p) {
|
|
1574
|
+
if (p.id === foundPaneId) return Object.assign({}, p, { activeTabId: '__settings__' });
|
|
1575
|
+
return p;
|
|
1576
|
+
});
|
|
1577
|
+
EditorStore.setState({ panes: newPanes, focusedPaneId: foundPaneId, activeTabId: '__settings__' });
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
var paneId = st.focusedPaneId;
|
|
1581
|
+
var pane = st.panes.find(function(p) { return p.id === paneId; }) || st.panes[0];
|
|
1582
|
+
if (!pane) return;
|
|
1583
|
+
paneId = pane.id;
|
|
1584
|
+
var newTab = { id: '__settings__', path: '__settings__', name: 'Settings', dirty: false, content: '', isSettings: true };
|
|
1585
|
+
var newPanes2 = st.panes.map(function(p) {
|
|
1586
|
+
if (p.id === paneId) return Object.assign({}, p, { tabs: p.tabs.concat(newTab), activeTabId: '__settings__' });
|
|
1587
|
+
return p;
|
|
1588
|
+
});
|
|
1589
|
+
EditorStore.setState({ panes: newPanes2, focusedPaneId: paneId, activeTabId: '__settings__' });
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1517
1592
|
return React.createElement(
|
|
1518
1593
|
"div",
|
|
1519
1594
|
{ className: "ide-shell" },
|
|
@@ -1603,6 +1678,11 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1603
1678
|
return setActiveSidebarTab('search');
|
|
1604
1679
|
} },
|
|
1605
1680
|
"SEARCH"
|
|
1681
|
+
),
|
|
1682
|
+
React.createElement(
|
|
1683
|
+
"button",
|
|
1684
|
+
{ type: "button", className: "ide-sidebar-tab ide-sidebar-tab-icon", title: "Editor Preferences", onClick: openSettingsTab },
|
|
1685
|
+
React.createElement("i", { className: "fas fa-cog" })
|
|
1606
1686
|
)
|
|
1607
1687
|
),
|
|
1608
1688
|
activeSidebarTab === 'explorer' && React.createElement(
|
|
@@ -1825,7 +1905,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1825
1905
|
{ className: "search-results-meta" },
|
|
1826
1906
|
state.searchResults.length,
|
|
1827
1907
|
" result" + (state.searchResults.length !== 1 ? "s" : ""),
|
|
1828
|
-
state.
|
|
1908
|
+
state.searchCapped && React.createElement(
|
|
1829
1909
|
"span",
|
|
1830
1910
|
{ className: "search-results-capped" },
|
|
1831
1911
|
" — refine query to see more"
|
|
@@ -1944,13 +2024,94 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1944
2024
|
commits: pActiveTab.commits || [],
|
|
1945
2025
|
onSelectCommit: handleSelectCommit
|
|
1946
2026
|
});
|
|
2027
|
+
} else if (pActiveTab.isSettings) {
|
|
2028
|
+
content = React.createElement(
|
|
2029
|
+
'div',
|
|
2030
|
+
{ className: 'ide-settings-tab-content' },
|
|
2031
|
+
React.createElement(
|
|
2032
|
+
'div',
|
|
2033
|
+
{ className: 'ide-settings-body' },
|
|
2034
|
+
React.createElement(
|
|
2035
|
+
'label', { className: 'ide-settings-row' },
|
|
2036
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Theme'),
|
|
2037
|
+
React.createElement(
|
|
2038
|
+
'select', {
|
|
2039
|
+
className: 'ide-settings-select',
|
|
2040
|
+
value: editorPrefs.theme || 'vs-dark',
|
|
2041
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { theme: e.target.value }); }); }
|
|
2042
|
+
},
|
|
2043
|
+
React.createElement('option', { value: 'vs-dark' }, 'Dark (vs-dark)'),
|
|
2044
|
+
React.createElement('option', { value: 'vs' }, 'Light (vs)'),
|
|
2045
|
+
React.createElement('option', { value: 'hc-black' }, 'High Contrast Dark'),
|
|
2046
|
+
React.createElement('option', { value: 'hc-light' }, 'High Contrast Light')
|
|
2047
|
+
)
|
|
2048
|
+
),
|
|
2049
|
+
React.createElement(
|
|
2050
|
+
'label', { className: 'ide-settings-row' },
|
|
2051
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Font size'),
|
|
2052
|
+
React.createElement('input', {
|
|
2053
|
+
type: 'number', min: '8', max: '32', step: '1',
|
|
2054
|
+
className: 'ide-settings-input',
|
|
2055
|
+
value: editorPrefs.fontSize || 13,
|
|
2056
|
+
onChange: function(e) {
|
|
2057
|
+
var v = parseInt(e.target.value, 10);
|
|
2058
|
+
if (v >= 8 && v <= 32) setEditorPrefs(function(p) { return Object.assign({}, p, { fontSize: v }); });
|
|
2059
|
+
}
|
|
2060
|
+
})
|
|
2061
|
+
),
|
|
2062
|
+
React.createElement(
|
|
2063
|
+
'label', { className: 'ide-settings-row' },
|
|
2064
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Tab size'),
|
|
2065
|
+
React.createElement('input', {
|
|
2066
|
+
type: 'number', min: '1', max: '8', step: '1',
|
|
2067
|
+
className: 'ide-settings-input',
|
|
2068
|
+
value: editorPrefs.tabSize || 1,
|
|
2069
|
+
onChange: function(e) {
|
|
2070
|
+
var v = parseInt(e.target.value, 10);
|
|
2071
|
+
if (v >= 1 && v <= 8) setEditorPrefs(function(p) { return Object.assign({}, p, { tabSize: v }); });
|
|
2072
|
+
}
|
|
2073
|
+
})
|
|
2074
|
+
),
|
|
2075
|
+
React.createElement(
|
|
2076
|
+
'label', { className: 'ide-settings-row ide-settings-row-check' },
|
|
2077
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Use spaces'),
|
|
2078
|
+
React.createElement('input', {
|
|
2079
|
+
type: 'checkbox',
|
|
2080
|
+
className: 'ide-settings-checkbox',
|
|
2081
|
+
checked: !!(editorPrefs.insertSpaces),
|
|
2082
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { insertSpaces: e.target.checked }); }); }
|
|
2083
|
+
})
|
|
2084
|
+
),
|
|
2085
|
+
React.createElement(
|
|
2086
|
+
'label', { className: 'ide-settings-row' },
|
|
2087
|
+
React.createElement('span', { className: 'ide-settings-label' }, 'Font family'),
|
|
2088
|
+
React.createElement('input', {
|
|
2089
|
+
type: 'text',
|
|
2090
|
+
className: 'ide-settings-input ide-settings-input-wide',
|
|
2091
|
+
value: editorPrefs.fontFamily || "'JetBrains Mono', 'Fira Code', Consolas, 'Courier New', monospace",
|
|
2092
|
+
onChange: function(e) { setEditorPrefs(function(p) { return Object.assign({}, p, { fontFamily: e.target.value }); }); }
|
|
2093
|
+
})
|
|
2094
|
+
),
|
|
2095
|
+
React.createElement(
|
|
2096
|
+
'button',
|
|
2097
|
+
{
|
|
2098
|
+
className: 'ide-settings-reset-btn',
|
|
2099
|
+
type: 'button',
|
|
2100
|
+
onClick: function() { setEditorPrefs(Object.assign({}, DEFAULT_EDITOR_PREFS)); }
|
|
2101
|
+
},
|
|
2102
|
+
React.createElement('i', { className: 'fas fa-undo', style: { marginRight: 6 } }),
|
|
2103
|
+
'Reset to defaults'
|
|
2104
|
+
)
|
|
2105
|
+
)
|
|
2106
|
+
);
|
|
1947
2107
|
} else if (pActiveTab.isDiff) {
|
|
2108
|
+
var isDiffDark = (editorPrefs.theme || 'vs-dark') !== 'vs' && (editorPrefs.theme || 'vs-dark') !== 'hc-light';
|
|
1948
2109
|
content = React.createElement(window.DiffViewer || DiffViewer, {
|
|
1949
2110
|
key: pActiveTab.id,
|
|
1950
2111
|
path: pActiveTab.path,
|
|
1951
2112
|
original: pActiveTab.diffOriginal || '',
|
|
1952
2113
|
modified: pActiveTab.diffModified || '',
|
|
1953
|
-
isDark:
|
|
2114
|
+
isDark: isDiffDark,
|
|
1954
2115
|
onClose: function() { requestCloseTab(pane.id, pActiveTab.id); }
|
|
1955
2116
|
});
|
|
1956
2117
|
} else {
|
|
@@ -1960,6 +2121,8 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
1960
2121
|
paneId: pane.id,
|
|
1961
2122
|
markers: markers[pActiveTab.id] || [],
|
|
1962
2123
|
gitAvailable: gitAvailable,
|
|
2124
|
+
editorPrefs: editorPrefs,
|
|
2125
|
+
onFormat: function() { onFormatRef.current(); },
|
|
1963
2126
|
onContentChange: function onContentChange(val) {
|
|
1964
2127
|
var st = EditorStore.getState();
|
|
1965
2128
|
var cp = st.panes.find(function(p) { return p.id === pane.id; });
|
|
@@ -2069,6 +2232,18 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2069
2232
|
React.createElement("td", null, React.createElement("kbd", null, "Ctrl+S")),
|
|
2070
2233
|
React.createElement("td", null, "Save the active file")
|
|
2071
2234
|
),
|
|
2235
|
+
React.createElement("tr", null,
|
|
2236
|
+
React.createElement("td", null, React.createElement("kbd", null, "Ctrl+Shift+S")),
|
|
2237
|
+
React.createElement("td", null, "Save all dirty files")
|
|
2238
|
+
),
|
|
2239
|
+
React.createElement("tr", null,
|
|
2240
|
+
React.createElement("td", null, React.createElement("kbd", null, "Alt+Shift+F")),
|
|
2241
|
+
React.createElement("td", null, "Format the active file")
|
|
2242
|
+
),
|
|
2243
|
+
React.createElement("tr", null,
|
|
2244
|
+
React.createElement("td", null, React.createElement("kbd", null, "Ctrl+Shift+G")),
|
|
2245
|
+
React.createElement("td", null, "Toggle git panel")
|
|
2246
|
+
),
|
|
2072
2247
|
React.createElement("tr", null,
|
|
2073
2248
|
React.createElement("td", null, React.createElement("kbd", null, "Ctrl+Z\u00a0/\u00a0Ctrl+Y")),
|
|
2074
2249
|
React.createElement("td", null, "Undo / Redo")
|
|
@@ -2123,6 +2298,7 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
2123
2298
|
React.createElement(window.GitPanel || GitPanel, {
|
|
2124
2299
|
gitInfo: state.gitInfo,
|
|
2125
2300
|
error: state.gitInfoError,
|
|
2301
|
+
redmineEnabled: redmineEnabled,
|
|
2126
2302
|
onRefresh: function () { return GitService.fetchInfo(); },
|
|
2127
2303
|
onClose: function () { return setShowGitPanel(false); },
|
|
2128
2304
|
onOpenFile: openFileFromGitPanel,
|