mbeditor 0.14.0 → 0.14.2
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 +64 -0
- data/app/assets/javascripts/mbeditor/application.js +2 -0
- data/app/assets/javascripts/mbeditor/components/CombinedFileView.js +228 -0
- data/app/assets/javascripts/mbeditor/components/DiffViewer.js +2 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +18 -1
- data/app/assets/javascripts/mbeditor/components/ImportConflictModal.js +60 -5
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +98 -9
- data/app/assets/javascripts/mbeditor/components/PastePlaceModal.js +329 -0
- data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +16 -0
- data/app/assets/javascripts/mbeditor/editor_plugins.js +20 -20
- data/app/assets/javascripts/mbeditor/file_service.js +10 -0
- data/app/assets/javascripts/mbeditor/tab_manager.js +3 -0
- data/app/assets/stylesheets/mbeditor/editor.css +33 -1
- data/app/assets/stylesheets/mbeditor/glass.css +11 -0
- data/app/assets/stylesheets/mbeditor/themes.css +3 -3
- data/app/controllers/mbeditor/editors_controller.rb +7 -0
- data/app/services/mbeditor/ruby_method_ranges_service.rb +120 -0
- data/lib/mbeditor/rack/pending_migration_bypass.rb +16 -1
- data/lib/mbeditor/route_map.rb +1 -0
- data/lib/mbeditor/version.rb +1 -1
- data/public/monaco-editor/VERSIONS +1 -1
- data/public/monaco-editor/codicon.ttf +0 -0
- data/public/monaco-editor/css.worker.js +41 -37
- data/public/monaco-editor/editor.worker.js +19 -15
- data/public/monaco-editor/html.worker.js +53 -49
- data/public/monaco-editor/json.worker.js +36 -32
- data/public/monaco-editor/monaco.css +1 -1
- data/public/monaco-editor/monaco.js +388 -301
- data/public/monaco-editor/ts.worker.js +226 -222
- data/vendor/assets/javascripts/axios.min.js +3 -3
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ee847e8e0df709bf840fedf585eafa79ef7097336b60f081174d5fc464244c9d
|
|
4
|
+
data.tar.gz: b787b613243fa9848f785d9931a6464b1356ee9207b7ace7bd27c92b8ea6064f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e49e0b4921e6e84e4064d7c5f497f57f7ea8992671b9deb773e11adf97c77e7dd2fd6fc83e177147b03ab2d79942b08569b6284b12e75bb6d67771e27d50950
|
|
7
|
+
data.tar.gz: c55fa561506959fdd07b43dabeddab66900e82e3795cb5e4a2c264f89cec4862a298bee59e5419b4c8c61ec6a10fa74a1b699cc078aae4788cb1faad8c539297
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,70 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.14.2] - 2026-10-20
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Combined View picker.** One "Copy .md" / "Download .md" at the top
|
|
12
|
+
replaces the per-file buttons, above a scrollable picker with a checkbox per
|
|
13
|
+
file and, for `.rb` files, per method. Unticked files are left out; ticked
|
|
14
|
+
methods limit a file to those methods.
|
|
15
|
+
- The Markdown export now opens with a `# Files` index of the included paths.
|
|
16
|
+
- **Open all editors in Combined View**: a stacked-layers action on the OPEN
|
|
17
|
+
EDITORS header.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- **Checkboxes inside editor panes did not respond to clicks.** The pane's
|
|
21
|
+
click-capture focus handler set store state on every click, and under the
|
|
22
|
+
legacy React root that re-render reset the controlled checkbox before React
|
|
23
|
+
saw it change. Focusing is now a no-op when nothing changes, and happens on
|
|
24
|
+
mousedown.
|
|
25
|
+
- **The editor right-click menu was dim and unreadable** (since Monaco 0.57):
|
|
26
|
+
it rendered in a shadow root the theme's `--vscode-*` variables cannot reach.
|
|
27
|
+
Editors now use `useShadowDOM: false`.
|
|
28
|
+
- **The explorer's workspace header went transparent on hover** with Liquid
|
|
29
|
+
Glass off, letting the scrolled tree show through.
|
|
30
|
+
|
|
31
|
+
## [0.14.1] - 2026-10-19
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- **Combined View.** Ctrl/Cmd-select files or folders in the explorer, right-click,
|
|
35
|
+
"Open in Combined View": every file on one scrolling page under its path, with
|
|
36
|
+
Copy and Download per file. Folders expand recursively; capped at 200 files with
|
|
37
|
+
a visible note. The tab is virtual and is not restored on reload.
|
|
38
|
+
- **Method picking and Markdown export.** In Combined View, Ruby files get a
|
|
39
|
+
method checklist; "Download .md" / "Copy .md" bundle the view into one file,
|
|
40
|
+
method-limited files keeping their `class`/`module` skeleton with `# ...`
|
|
41
|
+
between pieces. Built for chat assistants that take only a few attachments.
|
|
42
|
+
- **Paste & Place** (editor context menu or F1, Ruby). Paste methods from a
|
|
43
|
+
chat reply and mbeditor finds where each goes: the active file, then a repo
|
|
44
|
+
search for `def name` narrowed by class. New methods go into the file that
|
|
45
|
+
holds the class, before `private`. Diff preview first; Apply is one undoable
|
|
46
|
+
edit in the buffer and nothing is saved.
|
|
47
|
+
- **Compare on upload conflicts.** When an upload or drop would overwrite a
|
|
48
|
+
file, each text file gets a "Compare" diff of disk vs incoming.
|
|
49
|
+
- `POST /ruby_methods`: Ripper-based method ranges (owner, singleton, doc
|
|
50
|
+
comment start) behind the two features above.
|
|
51
|
+
- A help-panel section describing all of the above.
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
- **Unreadable status bar and buttons.** The Liquid Glass status bar and the
|
|
55
|
+
title-bar buttons painted "text on the accent fill" onto panel-coloured
|
|
56
|
+
surfaces; glass also hid the amber pending-migration background; Dracula,
|
|
57
|
+
Night Owl and Tomorrow Night had white text on light accents. 44 combinations
|
|
58
|
+
(11 themes × glass × pending) now all clear 4.5:1 on the status bar.
|
|
59
|
+
- **Flicker while migrations are pending.** `/cable` was not covered by the
|
|
60
|
+
pending-migration bypass, so every reconnect raised and the live channel
|
|
61
|
+
(presence, collaboration, file-change push) stayed dead.
|
|
62
|
+
- Paste & Place searches use POSIX classes, because the `git grep` search tier
|
|
63
|
+
is ERE and silently matches nothing for `\s`.
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
- **Monaco 0.57.0** (from 0.55.1). Its TypeScript API moved from
|
|
67
|
+
`monaco.languages.typescript` to `monaco.typescript`; every call site is
|
|
68
|
+
migrated. DOMPurify inside Monaco is still 3.4.15 (advisory rated low) until a
|
|
69
|
+
Monaco release ships 3.4.16+.
|
|
70
|
+
- **axios 1.20.0** (from 1.17.0 vendored), for new high-severity advisories.
|
|
71
|
+
|
|
8
72
|
## [0.14.0] - 2026-09-21
|
|
9
73
|
|
|
10
74
|
### Added
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
//= require mbeditor/components/CommitGraph
|
|
27
27
|
//= require mbeditor/components/ModelGraph
|
|
28
28
|
//= require mbeditor/components/ChangelogView
|
|
29
|
+
//= require mbeditor/components/CombinedFileView
|
|
29
30
|
//= require mbeditor/components/FileHistoryPanel
|
|
30
31
|
//= require mbeditor/components/TestResultsPanel
|
|
31
32
|
//= require mbeditor/components/LogPanel
|
|
@@ -42,5 +43,6 @@
|
|
|
42
43
|
// whole app down the first time it renders.
|
|
43
44
|
//= require mbeditor/components/ImportConflictModal
|
|
44
45
|
//= require mbeditor/components/ImportDialog
|
|
46
|
+
//= require mbeditor/components/PastePlaceModal
|
|
45
47
|
//= require mbeditor/components/MbeditorApp
|
|
46
48
|
//= require mbeditor/application_iife_tail
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// CombinedFileView — read-only stack of several files on one scrolling page.
|
|
4
|
+
// `paths` are workspace-relative file paths (already capped by the caller);
|
|
5
|
+
// `total` is the pre-cap count so truncation is shown, never silent.
|
|
6
|
+
|
|
7
|
+
(function() {
|
|
8
|
+
var useEffect = React.useEffect;
|
|
9
|
+
var useState = React.useState;
|
|
10
|
+
var CONCURRENCY = 6;
|
|
11
|
+
|
|
12
|
+
function languageFor(path) {
|
|
13
|
+
if (!(window.monaco && monaco.editor && monaco.editor.colorize)) return null;
|
|
14
|
+
var m = /\.[^./]+$/.exec(path);
|
|
15
|
+
if (!m) return null;
|
|
16
|
+
var ext = m[0].toLowerCase();
|
|
17
|
+
var lang = monaco.languages.getLanguages().find(function (l) { return (l.extensions || []).indexOf(ext) !== -1; });
|
|
18
|
+
return lang ? lang.id : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function copyText(text, okMsg, failMsg) {
|
|
22
|
+
if (!navigator.clipboard) return;
|
|
23
|
+
navigator.clipboard.writeText(text).then(
|
|
24
|
+
function () { EditorStore.setStatus(okMsg, 'info'); },
|
|
25
|
+
function () { EditorStore.setStatus(failMsg, 'error'); }
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function indentOf(line) { return /^\s*/.exec(line || '')[0]; }
|
|
30
|
+
|
|
31
|
+
// The selected methods (indexes into info.methods) with the class/module
|
|
32
|
+
// skeleton that holds them, in source order. Pieces that are not adjacent
|
|
33
|
+
// inside the same owner are separated by a `# ...` line.
|
|
34
|
+
function methodSlice(content, info, picked) {
|
|
35
|
+
var lines = content.split('\n');
|
|
36
|
+
var out = [];
|
|
37
|
+
var open = [];
|
|
38
|
+
var prev = null;
|
|
39
|
+
function contains(o, m) { return o.start_line <= m.start_line && o.end_line >= m.end_line; }
|
|
40
|
+
function close(n) {
|
|
41
|
+
while (open.length > n) { var o = open.pop(); out.push(indentOf(lines[o.start_line - 1]) + 'end'); }
|
|
42
|
+
}
|
|
43
|
+
info.methods.filter(function (m, i) { return picked[i]; })
|
|
44
|
+
.sort(function (a, b) { return a.start_line - b.start_line; })
|
|
45
|
+
.forEach(function (m) {
|
|
46
|
+
var chain = info.owners.filter(function (o) { return contains(o, m); });
|
|
47
|
+
var keep = 0;
|
|
48
|
+
while (keep < open.length && keep < chain.length && open[keep] === chain[keep]) keep++;
|
|
49
|
+
close(keep);
|
|
50
|
+
var reopened = open.length < chain.length;
|
|
51
|
+
for (var k = open.length; k < chain.length; k++) { out.push(lines[chain[k].start_line - 1]); open.push(chain[k]); }
|
|
52
|
+
if (prev && !reopened && m.doc_start_line > prev.end_line + 1) out.push(indentOf(lines[m.start_line - 1]) + '# ...');
|
|
53
|
+
else if (prev && !reopened) out.push('');
|
|
54
|
+
out.push.apply(out, lines.slice(m.doc_start_line - 1, m.end_line));
|
|
55
|
+
prev = m;
|
|
56
|
+
});
|
|
57
|
+
close(0);
|
|
58
|
+
return out.join('\n');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function fenceFor(text) {
|
|
62
|
+
var f = '```';
|
|
63
|
+
while (text.indexOf(f) !== -1) f += '`';
|
|
64
|
+
return f;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// paths: ordered file paths; files: path -> loaded entry; info: path -> {methods, owners};
|
|
68
|
+
// picked: path -> {methodIndex: true}; excluded: path -> true. A file with any picked
|
|
69
|
+
// method exports only those. Returns '' when no loaded file is included.
|
|
70
|
+
function buildMarkdown(paths, files, info, picked, excluded) {
|
|
71
|
+
var index = [];
|
|
72
|
+
var parts = [];
|
|
73
|
+
paths.forEach(function (path) {
|
|
74
|
+
var e = files[path];
|
|
75
|
+
if (excluded[path] || !e || e.content == null) return;
|
|
76
|
+
var sel = picked[path] || {};
|
|
77
|
+
var body = e.content;
|
|
78
|
+
var line = '- ' + path;
|
|
79
|
+
if (info[path] && info[path].methods && Object.keys(sel).some(function (k) { return sel[k]; })) {
|
|
80
|
+
body = methodSlice(e.content, info[path], sel);
|
|
81
|
+
line += ' (methods: ' + info[path].methods.filter(function (m, i) { return sel[i]; }).map(function (m) { return m.name; }).join(', ') + ')';
|
|
82
|
+
}
|
|
83
|
+
index.push(line);
|
|
84
|
+
var fence = fenceFor(body);
|
|
85
|
+
var ext = (/\.([^./]+)$/.exec(path) || [])[1] || '';
|
|
86
|
+
var lang = { rb: 'ruby', js: 'javascript', jsx: 'jsx', ts: 'typescript', tsx: 'tsx', yml: 'yaml', md: 'markdown', sh: 'bash' }[ext] || ext;
|
|
87
|
+
parts.push('## ' + path + '\n\n' + fence + lang + '\n' + body.replace(/\n$/, '') + '\n' + fence + '\n');
|
|
88
|
+
});
|
|
89
|
+
return index.length ? '# Files\n' + index.join('\n') + '\n\n' + parts.join('\n') : '';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function bundleName() {
|
|
93
|
+
return 'mbeditor-bundle-' + new Date().toISOString().replace(/[-:]/g, '').replace(/\..*$/, '').replace('T', '-') + '.md';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function downloadText(text, filename) {
|
|
97
|
+
var objectUrl = URL.createObjectURL(new Blob([text], { type: 'text/markdown' }));
|
|
98
|
+
var link = document.createElement('a');
|
|
99
|
+
link.href = objectUrl;
|
|
100
|
+
link.download = filename;
|
|
101
|
+
link.rel = 'noopener';
|
|
102
|
+
document.body.appendChild(link);
|
|
103
|
+
link.click();
|
|
104
|
+
document.body.removeChild(link);
|
|
105
|
+
// Revoking in the same tick can free the blob before the browser has
|
|
106
|
+
// started reading it, which cancels the download instead of saving it.
|
|
107
|
+
setTimeout(function () { URL.revokeObjectURL(objectUrl); }, 60000);
|
|
108
|
+
EditorStore.setStatus('Downloading ' + filename + '...', 'info');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
var CombinedFileView = function CombinedFileView(props) {
|
|
112
|
+
var paths = props.paths;
|
|
113
|
+
var _s = useState({});
|
|
114
|
+
var files = _s[0];
|
|
115
|
+
var setFiles = _s[1];
|
|
116
|
+
var _m = useState({});
|
|
117
|
+
var info = _m[0];
|
|
118
|
+
var setInfo = _m[1];
|
|
119
|
+
var asked = React.useRef({});
|
|
120
|
+
var _p = useState({});
|
|
121
|
+
var picked = _p[0];
|
|
122
|
+
var setPicked = _p[1];
|
|
123
|
+
|
|
124
|
+
// Method ranges for Ruby files, fetched once each file has loaded. No
|
|
125
|
+
// cancel-on-rerun: every file load re-runs this, and cancelling would drop
|
|
126
|
+
// the requests still in flight. The view is keyed per tab, so paths never
|
|
127
|
+
// change underneath it.
|
|
128
|
+
useEffect(function () {
|
|
129
|
+
paths.forEach(function (path) {
|
|
130
|
+
if (!/\.rb$/.test(path) || !files[path] || files[path].content == null || asked.current[path]) return;
|
|
131
|
+
asked.current[path] = true;
|
|
132
|
+
FileService.getRubyMethods(files[path].content).then(function (r) {
|
|
133
|
+
setInfo(function (prev) { var o = Object.assign({}, prev); o[path] = r.error ? { error: r.error } : r; return o; });
|
|
134
|
+
}, function () {
|
|
135
|
+
setInfo(function (prev) { var o = Object.assign({}, prev); o[path] = { error: 'Could not read methods' }; return o; });
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}, [paths, files]);
|
|
139
|
+
|
|
140
|
+
var _x = useState({});
|
|
141
|
+
var excluded = _x[0];
|
|
142
|
+
var setExcluded = _x[1];
|
|
143
|
+
|
|
144
|
+
var toggle = function (path, i) {
|
|
145
|
+
setPicked(function (prev) {
|
|
146
|
+
var o = Object.assign({}, prev);
|
|
147
|
+
var sel = Object.assign({}, o[path] || {});
|
|
148
|
+
if (sel[i]) delete sel[i]; else sel[i] = true;
|
|
149
|
+
o[path] = sel;
|
|
150
|
+
return o;
|
|
151
|
+
});
|
|
152
|
+
setExcluded(function (prev) { var o = Object.assign({}, prev); delete o[path]; return o; });
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
var toggleFile = function (path) {
|
|
156
|
+
setExcluded(function (prev) { var o = Object.assign({}, prev); if (o[path]) delete o[path]; else o[path] = true; return o; });
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
var exportMd = function (action) {
|
|
160
|
+
var md = buildMarkdown(paths, files, info, picked, excluded);
|
|
161
|
+
if (!md) { EditorStore.setStatus('No files selected', 'info'); return; }
|
|
162
|
+
action(md);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
useEffect(function () {
|
|
166
|
+
var cancelled = false;
|
|
167
|
+
var next = 0;
|
|
168
|
+
function put(path, entry) {
|
|
169
|
+
if (!cancelled) setFiles(function (prev) { var o = Object.assign({}, prev); o[path] = entry; return o; });
|
|
170
|
+
}
|
|
171
|
+
function worker() {
|
|
172
|
+
if (cancelled || next >= paths.length) return Promise.resolve();
|
|
173
|
+
var path = paths[next++];
|
|
174
|
+
return FileService.getFile(path).then(function (data) {
|
|
175
|
+
if (data && data.image) { put(path, { note: 'Image file, not shown' }); return; }
|
|
176
|
+
if (!data || typeof data.content !== 'string') { put(path, { note: 'Binary or unreadable file, not shown' }); return; }
|
|
177
|
+
var lang = languageFor(path);
|
|
178
|
+
if (!lang) { put(path, { content: data.content }); return; }
|
|
179
|
+
return monaco.editor.colorize(data.content, lang, { tabSize: 2 }).then(
|
|
180
|
+
function (html) { put(path, { content: data.content, html: html }); },
|
|
181
|
+
function () { put(path, { content: data.content }); }
|
|
182
|
+
);
|
|
183
|
+
})['catch'](function () { put(path, { note: 'Could not load file' }); }).then(worker);
|
|
184
|
+
}
|
|
185
|
+
for (var i = 0; i < CONCURRENCY; i++) worker();
|
|
186
|
+
return function () { cancelled = true; };
|
|
187
|
+
}, [paths]);
|
|
188
|
+
|
|
189
|
+
return React.createElement('div', { className: 'combined-view' },
|
|
190
|
+
React.createElement('div', { className: 'combined-toolbar' },
|
|
191
|
+
React.createElement('button', { type: 'button', className: 'combined-file-btn', onClick: function () { exportMd(function (md) { copyText(md, 'Copied Markdown bundle', 'Could not copy Markdown bundle'); }); } }, 'Copy .md'),
|
|
192
|
+
React.createElement('button', { type: 'button', className: 'combined-file-btn', onClick: function () { exportMd(function (md) { downloadText(md, bundleName()); }); } }, 'Download .md')
|
|
193
|
+
),
|
|
194
|
+
React.createElement('div', { className: 'combined-picker' },
|
|
195
|
+
paths.map(function (path) {
|
|
196
|
+
var methods = info[path] && info[path].methods;
|
|
197
|
+
return React.createElement('div', { key: path },
|
|
198
|
+
React.createElement('label', { className: 'combined-pick-file' },
|
|
199
|
+
React.createElement('input', { type: 'checkbox', checked: !excluded[path], onChange: function () { toggleFile(path); } }),
|
|
200
|
+
path),
|
|
201
|
+
methods && methods.map(function (m, i) {
|
|
202
|
+
var label = (m.owner ? m.owner + (m.singleton ? '.' : '#') : (m.singleton ? '.' : '#')) + m.name;
|
|
203
|
+
return React.createElement('label', { key: i, className: 'combined-pick-method', title: 'lines ' + m.start_line + '-' + m.end_line },
|
|
204
|
+
React.createElement('input', { type: 'checkbox', checked: !!(picked[path] && picked[path][i]), onChange: function () { toggle(path, i); } }),
|
|
205
|
+
label);
|
|
206
|
+
})
|
|
207
|
+
);
|
|
208
|
+
})
|
|
209
|
+
),
|
|
210
|
+
props.total > paths.length && React.createElement('div', { className: 'combined-note' },
|
|
211
|
+
'Showing the first ' + paths.length + ' of ' + props.total + ' files.'),
|
|
212
|
+
paths.map(function (path) {
|
|
213
|
+
var e = files[path];
|
|
214
|
+
return React.createElement('section', { key: path, className: 'combined-file' },
|
|
215
|
+
React.createElement('div', { className: 'combined-file-header' },
|
|
216
|
+
React.createElement('span', { className: 'combined-file-path' }, path)
|
|
217
|
+
),
|
|
218
|
+
!e ? React.createElement('div', { className: 'combined-note' }, 'Loading...') :
|
|
219
|
+
e.note ? React.createElement('div', { className: 'combined-note' }, e.note) :
|
|
220
|
+
e.html ? React.createElement('pre', { className: 'combined-file-body', dangerouslySetInnerHTML: { __html: e.html } }) :
|
|
221
|
+
React.createElement('pre', { className: 'combined-file-body' }, e.content)
|
|
222
|
+
);
|
|
223
|
+
})
|
|
224
|
+
);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
window.CombinedFileView = CombinedFileView;
|
|
228
|
+
})();
|
|
@@ -39,7 +39,8 @@ var DiffViewer = function DiffViewer(_ref) {
|
|
|
39
39
|
scrollBeyondLastLine: false,
|
|
40
40
|
fontFamily: editorPrefs.fontFamily || "'JetBrains Mono', 'Fira Code', 'Menlo', 'Consolas', monospace",
|
|
41
41
|
fontSize: editorPrefs.fontSize || 13,
|
|
42
|
-
wordWrap: editorPrefs.wordWrap || 'off'
|
|
42
|
+
wordWrap: editorPrefs.wordWrap || 'off',
|
|
43
|
+
useShadowDOM: false // same unthemed-context-menu reason as EditorPanel
|
|
43
44
|
});
|
|
44
45
|
|
|
45
46
|
diffEditor.setModel({
|
|
@@ -661,6 +661,11 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
661
661
|
acceptSuggestionOnEnter: editorPrefs.acceptSuggestionOnEnter || 'on',
|
|
662
662
|
linkedEditing: true,
|
|
663
663
|
fixedOverflowWidgets: true,
|
|
664
|
+
// The context menu otherwise renders in a shadow root hung off our
|
|
665
|
+
// container, out of reach of the theme's --vscode-* variables (they live
|
|
666
|
+
// on .monaco-editor/.monaco-component), so it drew transparent with black
|
|
667
|
+
// text. Light DOM puts it in .context-view.monaco-component, which is themed.
|
|
668
|
+
useShadowDOM: false,
|
|
664
669
|
hover: { above: false }
|
|
665
670
|
});
|
|
666
671
|
|
|
@@ -792,6 +797,17 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
792
797
|
}
|
|
793
798
|
});
|
|
794
799
|
|
|
800
|
+
// Paste & Place: Ruby only. An editor action gets the context-menu entry and
|
|
801
|
+
// the F1 command palette listing without another toolbar button.
|
|
802
|
+
var pastePlaceDisposable = editor.addAction({
|
|
803
|
+
id: 'mbeditor.pastePlace',
|
|
804
|
+
label: 'Paste & Place Ruby Method...',
|
|
805
|
+
precondition: 'editorLangId == ruby',
|
|
806
|
+
contextMenuGroupId: 'navigation',
|
|
807
|
+
contextMenuOrder: 1.7,
|
|
808
|
+
run: function() { EditorStore.setState({ isPastePlaceVisible: true }); }
|
|
809
|
+
});
|
|
810
|
+
|
|
795
811
|
// No custom "Format Document" action: Ruby now has a real formatting
|
|
796
812
|
// provider, so Monaco contributes that context-menu entry itself, and a
|
|
797
813
|
// second one of our own showed up as a duplicate. Languages with no
|
|
@@ -1139,6 +1155,7 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
1139
1155
|
if (editorPluginDisposable) editorPluginDisposable.dispose();
|
|
1140
1156
|
if (formatActionDisposable) formatActionDisposable.dispose();
|
|
1141
1157
|
runTestAtCursorDisposable.dispose();
|
|
1158
|
+
pastePlaceDisposable.dispose();
|
|
1142
1159
|
columnSelectDisposable.dispose();
|
|
1143
1160
|
contentDisposable.dispose();
|
|
1144
1161
|
EditorStore.setState({ canUndo: false, canRedo: false });
|
|
@@ -2183,7 +2200,7 @@ var EditorPanel = function EditorPanel(_ref) {
|
|
|
2183
2200
|
// our own, which makes it async — the worker is where the parse already
|
|
2184
2201
|
// lives, and it tracks the live buffer, so unsaved edits are included.
|
|
2185
2202
|
function loadJsOutline(model) {
|
|
2186
|
-
var ts = window.monaco && window.monaco.
|
|
2203
|
+
var ts = window.monaco && window.monaco.typescript;
|
|
2187
2204
|
if (!ts || !window.JsOutline) return Promise.reject(new Error('outline unavailable'));
|
|
2188
2205
|
|
|
2189
2206
|
var workerFor = model.getLanguageId() === 'typescript' ? ts.getTypeScriptWorker : ts.getJavaScriptWorker;
|
|
@@ -58,13 +58,51 @@ window.useModalFocusTrap = function useModalFocusTrap(modalRef, onDismiss) {
|
|
|
58
58
|
}, [onDismiss]);
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
+
// Compare is offered only for files that look like text and are small enough
|
|
62
|
+
// to diff in a modal. file.type is '' for most source files, so this rules
|
|
63
|
+
// out the known-binary families rather than requiring a text/* type.
|
|
64
|
+
var COMPARE_MAX_BYTES = 512 * 1024;
|
|
65
|
+
function canCompare(file) {
|
|
66
|
+
if (!file || file.size > COMPARE_MAX_BYTES) return false;
|
|
67
|
+
return !/^(image|audio|video|font)\/|^application\/(octet-stream|zip|pdf|gzip|x-)/.test(file.type || '');
|
|
68
|
+
}
|
|
69
|
+
|
|
61
70
|
var ImportConflictModal = function ImportConflictModal(_ref) {
|
|
62
71
|
var conflicts = _ref.conflicts || [];
|
|
63
72
|
var errors = _ref.errors || [];
|
|
64
73
|
var onResolve = _ref.onResolve;
|
|
65
74
|
var modalRef = React.useRef(null);
|
|
75
|
+
var _cmp = React.useState(null);
|
|
76
|
+
var compare = _cmp[0];
|
|
77
|
+
var setCompare = _cmp[1];
|
|
78
|
+
|
|
79
|
+
// Incoming file per conflicting target path.
|
|
80
|
+
var incoming = React.useMemo(function () {
|
|
81
|
+
var byPath = {};
|
|
82
|
+
FileImport.conflictedEntries(_ref.entries || [], _ref.targetFolderPath, conflicts).forEach(function (e) {
|
|
83
|
+
byPath[FileImport.joinPath(_ref.targetFolderPath, e.relativePath)] = e.file;
|
|
84
|
+
});
|
|
85
|
+
return byPath;
|
|
86
|
+
}, [_ref.entries, _ref.targetFolderPath, conflicts]);
|
|
66
87
|
|
|
67
|
-
|
|
88
|
+
// Escape closes an open comparison first; only then does it mean skip.
|
|
89
|
+
var dismiss = React.useCallback(function () {
|
|
90
|
+
if (compare) setCompare(null); else onResolve('skip');
|
|
91
|
+
}, [onResolve, compare]);
|
|
92
|
+
window.useModalFocusTrap(modalRef, dismiss);
|
|
93
|
+
|
|
94
|
+
var openCompare = function (path) {
|
|
95
|
+
var file = incoming[path];
|
|
96
|
+
setCompare({ path: path, loading: true });
|
|
97
|
+
Promise.all([FileService.getFile(path), file.text()]).then(function (r) {
|
|
98
|
+
var current = r[0] && r[0].content;
|
|
99
|
+
if (typeof current !== 'string' || r[1].indexOf('\u0000') !== -1) {
|
|
100
|
+
setCompare({ path: path, note: 'Binary or unreadable file, cannot compare.' });
|
|
101
|
+
} else {
|
|
102
|
+
setCompare({ path: path, original: current, modified: r[1] });
|
|
103
|
+
}
|
|
104
|
+
}, function () { setCompare({ path: path, note: 'Could not load the current file.' }); });
|
|
105
|
+
};
|
|
68
106
|
|
|
69
107
|
var button = function (label, mode, className) {
|
|
70
108
|
return React.createElement(
|
|
@@ -84,7 +122,7 @@ var ImportConflictModal = function ImportConflictModal(_ref) {
|
|
|
84
122
|
'div',
|
|
85
123
|
{
|
|
86
124
|
ref: modalRef,
|
|
87
|
-
className: 'schema-modal import-conflict-modal',
|
|
125
|
+
className: 'schema-modal import-conflict-modal' + (compare ? ' import-conflict-wide' : ''),
|
|
88
126
|
role: 'dialog',
|
|
89
127
|
'aria-modal': 'true',
|
|
90
128
|
'aria-label': 'Resolve import conflicts',
|
|
@@ -95,16 +133,33 @@ var ImportConflictModal = function ImportConflictModal(_ref) {
|
|
|
95
133
|
'div', { className: 'schema-modal-header' },
|
|
96
134
|
React.createElement(
|
|
97
135
|
'div', { className: 'schema-modal-title' },
|
|
98
|
-
conflicts.length + ' file' + (conflicts.length === 1 ? '' : 's') + ' already exist' +
|
|
136
|
+
compare ? 'Current (left) vs incoming (right)' : conflicts.length + ' file' + (conflicts.length === 1 ? '' : 's') + ' already exist' +
|
|
99
137
|
(conflicts.length === 1 ? 's' : '')
|
|
100
138
|
)
|
|
101
139
|
),
|
|
102
|
-
React.createElement(
|
|
140
|
+
compare && React.createElement(
|
|
141
|
+
'div', { className: 'schema-modal-body import-conflict-compare-body' },
|
|
142
|
+
React.createElement('button', { type: 'button', className: 'import-conflict-btn', onClick: function () { setCompare(null); } }, 'Back'),
|
|
143
|
+
compare.original != null
|
|
144
|
+
? React.createElement('div', { className: 'import-conflict-diff' },
|
|
145
|
+
React.createElement(window.DiffViewer, {
|
|
146
|
+
path: compare.path, original: compare.original, modified: compare.modified,
|
|
147
|
+
isDark: _ref.isDark, editorPrefs: _ref.editorPrefs
|
|
148
|
+
}))
|
|
149
|
+
: React.createElement('div', { className: 'combined-note' }, compare.note || 'Loading...')
|
|
150
|
+
),
|
|
151
|
+
!compare && React.createElement(
|
|
103
152
|
'div', { className: 'schema-modal-body' },
|
|
104
153
|
React.createElement(
|
|
105
154
|
'ul', { className: 'import-conflict-list' },
|
|
106
155
|
conflicts.map(function (c) {
|
|
107
|
-
return React.createElement('li', { key: c.path, title: c.path },
|
|
156
|
+
return React.createElement('li', { key: c.path, title: c.path },
|
|
157
|
+
c.path,
|
|
158
|
+
canCompare(incoming[c.path]) && React.createElement('button', {
|
|
159
|
+
type: 'button', className: 'import-conflict-compare',
|
|
160
|
+
onClick: function () { openCompare(c.path); }
|
|
161
|
+
}, 'Compare')
|
|
162
|
+
);
|
|
108
163
|
})
|
|
109
164
|
),
|
|
110
165
|
errors.length > 0 && React.createElement(
|