mbeditor 0.14.1 → 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 +23 -0
- data/app/assets/javascripts/mbeditor/components/CombinedFileView.js +42 -28
- data/app/assets/javascripts/mbeditor/components/DiffViewer.js +2 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +5 -0
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +30 -1
- data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +1 -1
- data/app/assets/javascripts/mbeditor/tab_manager.js +3 -0
- data/app/assets/stylesheets/mbeditor/editor.css +5 -2
- data/lib/mbeditor/version.rb +1 -1
- metadata +1 -1
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,29 @@ 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
|
+
|
|
8
31
|
## [0.14.1] - 2026-10-19
|
|
9
32
|
|
|
10
33
|
### Added
|
|
@@ -18,17 +18,6 @@
|
|
|
18
18
|
return lang ? lang.id : null;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function download(path) {
|
|
22
|
-
var link = document.createElement('a');
|
|
23
|
-
link.href = window.mbeditorBasePath() + '/raw?download=1&path=' + encodeURIComponent(path);
|
|
24
|
-
link.download = path.split('/').pop();
|
|
25
|
-
link.rel = 'noopener';
|
|
26
|
-
document.body.appendChild(link);
|
|
27
|
-
link.click();
|
|
28
|
-
document.body.removeChild(link);
|
|
29
|
-
EditorStore.setStatus('Downloading ' + link.download + '...', 'info');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
21
|
function copyText(text, okMsg, failMsg) {
|
|
33
22
|
if (!navigator.clipboard) return;
|
|
34
23
|
navigator.clipboard.writeText(text).then(
|
|
@@ -76,23 +65,28 @@
|
|
|
76
65
|
}
|
|
77
66
|
|
|
78
67
|
// paths: ordered file paths; files: path -> loaded entry; info: path -> {methods, owners};
|
|
79
|
-
// picked: path -> {methodIndex: true}. A file with any picked
|
|
80
|
-
|
|
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 = [];
|
|
81
72
|
var parts = [];
|
|
82
73
|
paths.forEach(function (path) {
|
|
83
74
|
var e = files[path];
|
|
84
|
-
if (!e || e.content == null) return;
|
|
75
|
+
if (excluded[path] || !e || e.content == null) return;
|
|
85
76
|
var sel = picked[path] || {};
|
|
86
77
|
var body = e.content;
|
|
78
|
+
var line = '- ' + path;
|
|
87
79
|
if (info[path] && info[path].methods && Object.keys(sel).some(function (k) { return sel[k]; })) {
|
|
88
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(', ') + ')';
|
|
89
82
|
}
|
|
83
|
+
index.push(line);
|
|
90
84
|
var fence = fenceFor(body);
|
|
91
85
|
var ext = (/\.([^./]+)$/.exec(path) || [])[1] || '';
|
|
92
86
|
var lang = { rb: 'ruby', js: 'javascript', jsx: 'jsx', ts: 'typescript', tsx: 'tsx', yml: 'yaml', md: 'markdown', sh: 'bash' }[ext] || ext;
|
|
93
87
|
parts.push('## ' + path + '\n\n' + fence + lang + '\n' + body.replace(/\n$/, '') + '\n' + fence + '\n');
|
|
94
88
|
});
|
|
95
|
-
return parts.join('\n');
|
|
89
|
+
return index.length ? '# Files\n' + index.join('\n') + '\n\n' + parts.join('\n') : '';
|
|
96
90
|
}
|
|
97
91
|
|
|
98
92
|
function bundleName() {
|
|
@@ -143,6 +137,10 @@
|
|
|
143
137
|
});
|
|
144
138
|
}, [paths, files]);
|
|
145
139
|
|
|
140
|
+
var _x = useState({});
|
|
141
|
+
var excluded = _x[0];
|
|
142
|
+
var setExcluded = _x[1];
|
|
143
|
+
|
|
146
144
|
var toggle = function (path, i) {
|
|
147
145
|
setPicked(function (prev) {
|
|
148
146
|
var o = Object.assign({}, prev);
|
|
@@ -151,8 +149,18 @@
|
|
|
151
149
|
o[path] = sel;
|
|
152
150
|
return o;
|
|
153
151
|
});
|
|
152
|
+
setExcluded(function (prev) { var o = Object.assign({}, prev); delete o[path]; return o; });
|
|
154
153
|
};
|
|
155
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
|
+
};
|
|
156
164
|
|
|
157
165
|
useEffect(function () {
|
|
158
166
|
var cancelled = false;
|
|
@@ -180,8 +188,24 @@
|
|
|
180
188
|
|
|
181
189
|
return React.createElement('div', { className: 'combined-view' },
|
|
182
190
|
React.createElement('div', { className: 'combined-toolbar' },
|
|
183
|
-
React.createElement('button', { type: 'button', className: 'combined-file-btn', onClick: function () {
|
|
184
|
-
React.createElement('button', { type: 'button', className: 'combined-file-btn', onClick: function () {
|
|
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
|
+
})
|
|
185
209
|
),
|
|
186
210
|
props.total > paths.length && React.createElement('div', { className: 'combined-note' },
|
|
187
211
|
'Showing the first ' + paths.length + ' of ' + props.total + ' files.'),
|
|
@@ -189,17 +213,7 @@
|
|
|
189
213
|
var e = files[path];
|
|
190
214
|
return React.createElement('section', { key: path, className: 'combined-file' },
|
|
191
215
|
React.createElement('div', { className: 'combined-file-header' },
|
|
192
|
-
React.createElement('span', { className: 'combined-file-path' }, path)
|
|
193
|
-
React.createElement('button', { type: 'button', className: 'combined-file-btn', disabled: !e || e.content == null, onClick: function () { copyText(e.content, 'Copied ' + path, 'Could not copy ' + path); } }, 'Copy'),
|
|
194
|
-
React.createElement('button', { type: 'button', className: 'combined-file-btn', onClick: function () { download(path); } }, 'Download')
|
|
195
|
-
),
|
|
196
|
-
info[path] && info[path].methods && info[path].methods.length > 0 && React.createElement('div', { className: 'combined-methods' },
|
|
197
|
-
info[path].methods.map(function (m, i) {
|
|
198
|
-
var label = (m.owner ? m.owner + (m.singleton ? '.' : '#') : (m.singleton ? '.' : '#')) + m.name;
|
|
199
|
-
return React.createElement('label', { key: i, className: 'combined-method', title: 'lines ' + m.start_line + '-' + m.end_line },
|
|
200
|
-
React.createElement('input', { type: 'checkbox', checked: !!(picked[path] && picked[path][i]), onChange: function () { toggle(path, i); } }),
|
|
201
|
-
label);
|
|
202
|
-
})
|
|
216
|
+
React.createElement('span', { className: 'combined-file-path' }, path)
|
|
203
217
|
),
|
|
204
218
|
!e ? React.createElement('div', { className: 'combined-note' }, 'Loading...') :
|
|
205
219
|
e.note ? React.createElement('div', { className: 'combined-note' }, e.note) :
|
|
@@ -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
|
|
|
@@ -4308,6 +4308,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4308
4308
|
(n.children || []).forEach(walk);
|
|
4309
4309
|
};
|
|
4310
4310
|
(actsOnSelection(node) ? Array.from(selectedPaths) : [node.path]).forEach(function (p) { walk(byPath[p]); });
|
|
4311
|
+
openCombinedTab(files);
|
|
4312
|
+
};
|
|
4313
|
+
|
|
4314
|
+
var openCombinedTab = function openCombinedTab(files) {
|
|
4311
4315
|
if (!files.length) { EditorStore.setStatus('No files to show', 'info'); return; }
|
|
4312
4316
|
var total = files.length;
|
|
4313
4317
|
var id = 'mbeditor://combined/' + Date.now();
|
|
@@ -4321,6 +4325,22 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
4321
4325
|
});
|
|
4322
4326
|
};
|
|
4323
4327
|
|
|
4328
|
+
// Every plain file tab across both panes, in tab order. Virtual tabs (diffs,
|
|
4329
|
+
// previews, settings, other combined views) carry a scheme or a flag.
|
|
4330
|
+
var handleOpenEditorsCombined = function handleOpenEditorsCombined() {
|
|
4331
|
+
var seen = {};
|
|
4332
|
+
var files = [];
|
|
4333
|
+
EditorStore.getState().panes.forEach(function (p) {
|
|
4334
|
+
p.tabs.forEach(function (t) {
|
|
4335
|
+
if (!t.path || t.isSettings || t.isUntitled || t.isImage || t.isDiff || t.isCombinedDiff || t.isCombinedView || t.isChangelog || t.isPreview || t.isCommitGraph) return;
|
|
4336
|
+
if (t.path.indexOf('://') !== -1 || /::preview$/.test(t.path) || seen[t.path]) return;
|
|
4337
|
+
seen[t.path] = true;
|
|
4338
|
+
files.push(t.path);
|
|
4339
|
+
});
|
|
4340
|
+
});
|
|
4341
|
+
openCombinedTab(files);
|
|
4342
|
+
};
|
|
4343
|
+
|
|
4324
4344
|
var handleDownloadFile = function handleDownloadFile(node) {
|
|
4325
4345
|
if (!node) return;
|
|
4326
4346
|
var isMultiSelect = actsOnSelection(node);
|
|
@@ -5463,6 +5483,12 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
5463
5483
|
actions: React.createElement(
|
|
5464
5484
|
SectionActionGroup,
|
|
5465
5485
|
{ ariaLabel: "Open editor actions" },
|
|
5486
|
+
React.createElement(SidebarActionButton, {
|
|
5487
|
+
title: "Open all in Combined View",
|
|
5488
|
+
ariaLabel: "Open all open editors in Combined View",
|
|
5489
|
+
iconClass: "fas fa-layer-group",
|
|
5490
|
+
onClick: handleOpenEditorsCombined
|
|
5491
|
+
}),
|
|
5466
5492
|
React.createElement(SidebarActionButton, {
|
|
5467
5493
|
title: "Close all editors",
|
|
5468
5494
|
ariaLabel: "Close all open editors",
|
|
@@ -6214,7 +6240,10 @@ var MbeditorApp = function MbeditorApp() {
|
|
|
6214
6240
|
{
|
|
6215
6241
|
className: "ide-pane " + (isFocused ? 'focused' : '') + " " + (isDropTarget ? 'drop-target' : ''),
|
|
6216
6242
|
style: { flexBasis: flexBasis, flexShrink: 0, flexGrow: 0, display: 'flex', flexDirection: 'column', minWidth: 0 },
|
|
6217
|
-
|
|
6243
|
+
// Mousedown, not click: focusing re-renders the app, and doing that
|
|
6244
|
+
// during a click's capture phase reverts a checkbox before React
|
|
6245
|
+
// sees it change, so its onChange never fires.
|
|
6246
|
+
onMouseDownCapture: function (e) {
|
|
6218
6247
|
return TabManager.focusPane(pane.id);
|
|
6219
6248
|
},
|
|
6220
6249
|
onDragOver: function (e) {
|
|
@@ -191,7 +191,7 @@ var ShortcutHelp = function ShortcutHelp(_ref) {
|
|
|
191
191
|
Tips,
|
|
192
192
|
{
|
|
193
193
|
items: [
|
|
194
|
-
'Combined View: Ctrl/Cmd-click several files or folders, right-click, "Open in Combined View" — every file on one page with Copy and Download',
|
|
194
|
+
'Combined View: Ctrl/Cmd-click several files or folders, right-click, "Open in Combined View" — every file on one page with Copy and Download. The stacked-layers icon on OPEN EDITORS does the same for all open files',
|
|
195
195
|
'Pick methods: in Combined View, tick methods under a Ruby file to send only those; "Download .md" / "Copy .md" bundles everything into one attachment',
|
|
196
196
|
'Paste & Place: right-click inside a Ruby file (or F1) → "Paste & Place Ruby Method...", paste the reply, "Find placement", check the diff, "Apply to editor" — then save; Ctrl+Z undoes',
|
|
197
197
|
'Upload compare: drop or upload a file over an existing one and click "Compare" to see the diff before choosing Skip / Keep both / Overwrite'
|
|
@@ -620,6 +620,9 @@ var TabManager = (function () {
|
|
|
620
620
|
var state = EditorStore.getState();
|
|
621
621
|
var pane = state.panes.find(function(p) { return p.id === paneId; });
|
|
622
622
|
var newActive = pane ? pane.activeTabId : null;
|
|
623
|
+
// Every click in a pane lands here; a no-op setState still re-renders the
|
|
624
|
+
// app, which mid-click resets controlled inputs (checkboxes) to their old value.
|
|
625
|
+
if (state.focusedPaneId === paneId && state.activeTabId === newActive) return;
|
|
623
626
|
EditorStore.setState({ focusedPaneId: paneId, activeTabId: newActive });
|
|
624
627
|
}
|
|
625
628
|
|
|
@@ -2367,6 +2367,8 @@ body.mbeditor-migration-pending .ide-statusbar {
|
|
|
2367
2367
|
/* Nothing to click, so nothing that looks clickable. */
|
|
2368
2368
|
.collapsible-header-static { cursor: default; }
|
|
2369
2369
|
.collapsible-header-static:hover { background: none; }
|
|
2370
|
+
/* A sticky header still needs its own fill, or scrolled rows show through it. */
|
|
2371
|
+
.ide-sidebar-scrollable .collapsible-header-static:hover { background: var(--ide-panel-bg); }
|
|
2370
2372
|
|
|
2371
2373
|
|
|
2372
2374
|
.collapsible-toggle {
|
|
@@ -4150,8 +4152,9 @@ input.ide-model-graph-search[type="search"] {
|
|
|
4150
4152
|
|
|
4151
4153
|
/* Combined view: method picking + Markdown export; import Compare; Paste & Place */
|
|
4152
4154
|
.combined-toolbar { display: flex; gap: 8px; padding: 6px 16px; border-bottom: 1px solid var(--ide-border, #333); }
|
|
4153
|
-
.combined-
|
|
4154
|
-
.combined-method { display:
|
|
4155
|
+
.combined-picker { max-height: 30vh; overflow-y: auto; padding: 4px 16px; font-size: 12px; border-bottom: 1px solid var(--ide-border, #333); }
|
|
4156
|
+
.combined-pick-file, .combined-pick-method { display: flex; align-items: center; gap: 6px; white-space: nowrap; cursor: pointer; }
|
|
4157
|
+
.combined-pick-method { padding-left: 22px; font-size: 11px; }
|
|
4155
4158
|
.import-conflict-compare { margin-left: 8px; padding: 0; background: none; border: 0; color: var(--ide-accent, #0e639c); font-size: 11px; cursor: pointer; }
|
|
4156
4159
|
.import-conflict-compare:hover { text-decoration: underline; }
|
|
4157
4160
|
.import-conflict-modal.import-conflict-wide { min-width: min(900px, 94vw); max-width: 94vw; }
|
data/lib/mbeditor/version.rb
CHANGED