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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6faf6632c3072f98fe34083c809069a4e37ed9e956fd6722fd2fb188ed13c55
4
- data.tar.gz: 24e179af4efd250cef7ddeec7d81a8c033891af89cb286980ddbb3541f543239
3
+ metadata.gz: ee847e8e0df709bf840fedf585eafa79ef7097336b60f081174d5fc464244c9d
4
+ data.tar.gz: b787b613243fa9848f785d9931a6464b1356ee9207b7ace7bd27c92b8ea6064f
5
5
  SHA512:
6
- metadata.gz: 800cbc5afda678e3ae3298dc781f39054c32ecb0098dc82e68df9c6f0f8222ba4e5c06d0c6b424e50360dda0f02ed678169c4f4922353c432c8c0b446f28f428
7
- data.tar.gz: 49bca37c5e4a696a4ba1ec4f5f01454899d006cb827f109e11a96e46feb65544ac7c85afdc55756076302618a2af30ced210e1b149b85690899b6801f5dcd37b
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 method exports only those.
80
- function buildMarkdown(paths, files, info, picked) {
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 () { downloadText(buildMarkdown(paths, files, info, picked), bundleName()); } }, 'Download .md'),
184
- React.createElement('button', { type: 'button', className: 'combined-file-btn', onClick: function () { copyText(buildMarkdown(paths, files, info, picked), 'Copied Markdown bundle', 'Could not copy Markdown bundle'); } }, 'Copy .md')
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
- onClickCapture: function (e) {
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-methods { display: flex; flex-wrap: wrap; gap: 2px 12px; max-height: 96px; overflow-y: auto; padding: 4px 16px; font-size: 11px; border-bottom: 1px solid var(--ide-border, #333); }
4154
- .combined-method { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; cursor: pointer; }
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; }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mbeditor
4
- VERSION = "0.14.1"
4
+ VERSION = "0.14.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mbeditor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.1
4
+ version: 0.14.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oliver Noonan