mbeditor 0.3.4 → 0.3.7

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: 60248cb43c14e620f33c0f04e557a06b0c32336d60d1ee372dbea9c1e4cd26c9
4
- data.tar.gz: 98a214b932e0f0ea8e4aacaa2bde02c7f5c50ab611ec664d2ed8eefecce523df
3
+ metadata.gz: 87f1238b8d3d09661d74c224711427832fdc1dd5cb3a714bc144bc8fb527ebc9
4
+ data.tar.gz: 9925f093f7abd3305def9efa3c7407f0daa0cb4ad0e4989ab03f970f52fa426f
5
5
  SHA512:
6
- metadata.gz: 6d42e6cf28c4adfb62d381a2be48f85d61401b99198eabac8eac555ebb675782d43911da81399a615869077e1c02c83deae2663976939932fc475fa6cc6f0809
7
- data.tar.gz: ce2c4bbe5176b03cbc5eccbd6159db8cc742127b20dc681b5507043f4115a7e77d76f167b9a5b0878fa7dc395ba54aa7c1f03ce906c89106817d80cf72b9aa87
6
+ metadata.gz: 04adda24a44677546707a0b024c109cac1b171e6ac436eea005c09b529ec55f7df18cce8753f4f570287922c89b2fa75b2cf3b2ed6290450dfc60fee48d80bad
7
+ data.tar.gz: 4c4b2e9e731882058901d2df8a72ea25a510a5b1c04d8a57e5b2af5c81a30ee1b76982f6b019b81b2452f407395ca1d9a11655b4617e5ea458021b19c614d3eb
data/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ 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.3.7] - 2026-04-16
9
+
10
+ ### Fixed
11
+ - Corrected the `destroy_path` missing-path controller test to expect `200 OK` for idempotent DELETE behavior, which unblocks the tag-based publish workflow for this release.
12
+
13
+ ## [0.3.6] - 2026-04-15
14
+
15
+ ### Fixed
16
+ - Multi-file/folder delete now deduplicates child paths covered by a selected ancestor directory, preventing redundant requests and Rails 404 console errors.
17
+ - Switched multi-delete from `Promise.all` to `Promise.allSettled` so all deletions complete before checking for failures rather than bailing on the first rejection.
18
+ - `destroy_path` controller action is now idempotent — returns 200 when the path is already gone, matching correct REST DELETE semantics.
19
+
8
20
  ## [0.2.8] - 2026-04-15
9
21
 
10
22
  ### Changed
@@ -451,9 +451,32 @@ var EditorPanel = function EditorPanel(_ref) {
451
451
  if (language === 'image') return;
452
452
 
453
453
  lastAppliedExternalVersionRef.current = -1;
454
+
455
+ // Look up or create a persistent Monaco model for this file path.
456
+ // Reusing the model across tab switches preserves the undo/redo history.
457
+ if (!window.__mbeditorModels) window.__mbeditorModels = {};
458
+ var _modelEntry = window.__mbeditorModels[tab.path];
459
+ var _reusingModel = false;
460
+ var modelObj;
461
+ if (_modelEntry && _modelEntry.model && !_modelEntry.model.isDisposed()) {
462
+ modelObj = _modelEntry.model;
463
+ _reusingModel = true;
464
+ // Re-apply language in case it changed (e.g. file renamed)
465
+ if (modelObj.getLanguageId() !== language) {
466
+ window.monaco.editor.setModelLanguage(modelObj, language);
467
+ }
468
+ } else {
469
+ modelObj = window.monaco.editor.createModel(tab.content, language);
470
+ window.__mbeditorModels[tab.path] = { model: modelObj, aviBase: null, aviMax: null };
471
+ _modelEntry = window.__mbeditorModels[tab.path];
472
+ }
473
+
474
+ // Sync latestContentRef from the actual model content so the onDidChangeContent
475
+ // handler doesn't fire a spurious onContentChange call on the first keystroke.
476
+ latestContentRef.current = _reusingModel ? modelObj.getValue() : (tab.content || '');
477
+
454
478
  var editor = window.monaco.editor.create(editorRef.current, {
455
- value: tab.content,
456
- language: language,
479
+ model: modelObj,
457
480
  theme: editorPrefs.theme || 'vs-dark',
458
481
  automaticLayout: true,
459
482
  minimap: { enabled: !!(editorPrefs.minimap) },
@@ -487,7 +510,6 @@ var EditorPanel = function EditorPanel(_ref) {
487
510
 
488
511
  // Stash the workspace-relative path on the model so code-action providers
489
512
  // can identify which file they are operating on without needing React state.
490
- var modelObj = editor.getModel();
491
513
  if (modelObj) modelObj._mbeditorPath = tab.path;
492
514
 
493
515
  var formatActionDisposable = editor.addAction({
@@ -547,10 +569,17 @@ var EditorPanel = function EditorPanel(_ref) {
547
569
 
548
570
  // Track undo/redo availability via Monaco's alternativeVersionId.
549
571
  // AVI goes up on every edit, down on undo, back up on redo.
572
+ // When reusing an existing model, restore the saved AVI thresholds so the
573
+ // canUndo/canRedo buttons reflect the real state of the undo stack.
550
574
  var avi = modelObj.getAlternativeVersionId();
551
- aviBaseRef.current = avi;
552
- aviMaxRef.current = avi;
553
- EditorStore.setState({ canUndo: false, canRedo: false });
575
+ if (_reusingModel && _modelEntry.aviBase !== null) {
576
+ aviBaseRef.current = _modelEntry.aviBase;
577
+ aviMaxRef.current = _modelEntry.aviMax !== null ? _modelEntry.aviMax : avi;
578
+ } else {
579
+ aviBaseRef.current = avi;
580
+ aviMaxRef.current = avi;
581
+ }
582
+ EditorStore.setState({ canUndo: avi > aviBaseRef.current, canRedo: avi < aviMaxRef.current });
554
583
 
555
584
  var contentDisposable = modelObj.onDidChangeContent(function (e) {
556
585
  var currentAvi = modelObj.getAlternativeVersionId();
@@ -599,6 +628,12 @@ var EditorPanel = function EditorPanel(_ref) {
599
628
  clearBlameZones(editor);
600
629
  clearTestZones(editor);
601
630
  TabManager.saveTabViewState(tab.id, editor.saveViewState());
631
+ // Persist AVI thresholds on the model entry so undo/redo state is correct on return.
632
+ var _me = window.__mbeditorModels && window.__mbeditorModels[tab.path];
633
+ if (_me) {
634
+ _me.aviBase = aviBaseRef.current;
635
+ _me.aviMax = aviMaxRef.current;
636
+ }
602
637
  if (window.__mbeditorActiveEditor === editor) {
603
638
  window.__mbeditorActiveEditor = null;
604
639
  }
@@ -607,6 +642,9 @@ var EditorPanel = function EditorPanel(_ref) {
607
642
  columnSelectDisposable.dispose();
608
643
  contentDisposable.dispose();
609
644
  EditorStore.setState({ canUndo: false, canRedo: false });
645
+ // Detach the model before disposing the editor so the model (and its undo
646
+ // history) survives for when the user returns to this tab.
647
+ editor.setModel(null);
610
648
  editor.dispose();
611
649
  };
612
650
  }, [tab.id, tab.isPreview]); // re-run ONLY on tab switch, not on content change (Monaco handles its own content state)
@@ -2000,6 +2000,15 @@ var MbeditorApp = function MbeditorApp() {
2000
2000
  return;
2001
2001
  }
2002
2002
 
2003
+ // Remove paths that are already covered by a selected ancestor directory.
2004
+ // This prevents redundant requests (and resulting 404s) when a folder and
2005
+ // its children are both in the selection.
2006
+ pathsToDelete = pathsToDelete.filter(function(p) {
2007
+ return !pathsToDelete.some(function(other) {
2008
+ return other !== p && p.startsWith(other.endsWith('/') ? other : other + '/');
2009
+ });
2010
+ });
2011
+
2003
2012
  var label = pathsToDelete.length === 1 ? pathsToDelete[0] : pathsToDelete.length + ' items';
2004
2013
  var confirmed = window.confirm('Delete ' + label + '? This cannot be undone.');
2005
2014
  if (!confirmed) return;
@@ -2007,19 +2016,22 @@ var MbeditorApp = function MbeditorApp() {
2007
2016
  setLoading(function (prev) {
2008
2017
  return _extends({}, prev, { deletePath: true });
2009
2018
  });
2010
- Promise.all(pathsToDelete.map(function(p) {
2019
+ Promise.allSettled(pathsToDelete.map(function(p) {
2011
2020
  return FileService.deletePath(p).then(function() {
2012
2021
  removeDeletedPathFromOpenTabs(p);
2013
2022
  });
2014
- })).then(function () {
2015
- handleNodeSelect(null);
2016
- EditorStore.setStatus('Deleted: ' + label, 'success');
2023
+ })).then(function (results) {
2024
+ var failures = results.filter(function(r) { return r.status === 'rejected'; });
2025
+ if (failures.length === 0) {
2026
+ handleNodeSelect(null);
2027
+ EditorStore.setStatus('Deleted: ' + label, 'success');
2028
+ } else {
2029
+ var message = failures[0].reason && failures[0].reason.response && failures[0].reason.response.data && failures[0].reason.response.data.error || (failures[0].reason && failures[0].reason.message) || 'Unknown error';
2030
+ EditorStore.setStatus('Delete failed: ' + message, 'error');
2031
+ }
2017
2032
  return refreshProjectTree().then(function () {
2018
2033
  GitService.fetchStatus();
2019
2034
  });
2020
- })["catch"](function (err) {
2021
- var message = err && err.response && err.response.data && err.response.data.error || err.message;
2022
- EditorStore.setStatus('Delete failed: ' + message, 'error');
2023
2035
  })["finally"](function () {
2024
2036
  setLoading(function (prev) {
2025
2037
  return _extends({}, prev, { deletePath: false });
@@ -318,6 +318,21 @@ var TabManager = (function () {
318
318
  focusedPaneId: nextFocusedPaneId,
319
319
  activeTabId: maybeNewGlobalActiveTab
320
320
  });
321
+
322
+ // Dispose the cached Monaco model for this path if it is no longer open in
323
+ // any pane. This keeps the model registry clean and frees memory.
324
+ if (window.__mbeditorModels && window.__mbeditorModels[path]) {
325
+ var _stillOpen = newPanes.some(function(p) {
326
+ return p.tabs.some(function(t) { return t.path === path; });
327
+ });
328
+ if (!_stillOpen) {
329
+ var _entry = window.__mbeditorModels[path];
330
+ if (_entry.model && !_entry.model.isDisposed()) {
331
+ _entry.model.dispose();
332
+ }
333
+ delete window.__mbeditorModels[path];
334
+ }
335
+ }
321
336
  }
322
337
 
323
338
  function closeAllTabsInPane(paneId) {
@@ -816,6 +816,59 @@ html, body, #mbeditor-root {
816
816
  .search-input-wrap {
817
817
  display: flex;
818
818
  flex-shrink: 0;
819
+ align-items: center;
820
+ gap: 4px;
821
+ }
822
+
823
+ .search-input-shell {
824
+ flex: 1;
825
+ position: relative;
826
+ display: flex;
827
+ align-items: center;
828
+ min-width: 0;
829
+ }
830
+
831
+ .search-input-shell .search-input {
832
+ padding-right: 24px;
833
+ }
834
+
835
+ /* search-clear-btn sits absolutely inside the input shell */
836
+ .search-input-shell .search-clear-btn {
837
+ position: absolute;
838
+ right: 4px;
839
+ width: 16px;
840
+ height: 16px;
841
+ font-size: 10px;
842
+ color: var(--ide-text-muted);
843
+ background: transparent;
844
+ }
845
+
846
+ /* Use .search-panel ancestor for higher specificity than button:not(.pico-btn) */
847
+ .search-panel .search-btn {
848
+ flex-shrink: 0;
849
+ width: 26px;
850
+ height: 26px;
851
+ padding: 0;
852
+ margin: 0;
853
+ border: 1px solid var(--ide-border-input);
854
+ border-radius: 4px;
855
+ background: var(--ide-input-bg);
856
+ color: var(--ide-text-muted);
857
+ font-size: 12px;
858
+ display: inline-flex;
859
+ align-items: center;
860
+ justify-content: center;
861
+ cursor: pointer;
862
+ }
863
+
864
+ .search-panel .search-btn:hover {
865
+ color: var(--ide-text);
866
+ border-color: var(--ide-accent-fg);
867
+ }
868
+
869
+ .search-panel .search-btn:disabled {
870
+ opacity: 0.5;
871
+ cursor: not-allowed;
819
872
  }
820
873
 
821
874
  .search-input {
@@ -872,6 +925,9 @@ html, body, #mbeditor-root {
872
925
  font-size: 11px;
873
926
  color: var(--ide-accent-fg);
874
927
  font-weight: 500;
928
+ white-space: nowrap;
929
+ overflow: hidden;
930
+ text-overflow: ellipsis;
875
931
  }
876
932
 
877
933
  .search-result-line-num {
@@ -240,7 +240,8 @@ module Mbeditor
240
240
  def destroy_path
241
241
  path = resolve_path(params[:path])
242
242
  return render json: { error: "Forbidden" }, status: :forbidden unless path
243
- return render json: { error: "Path not found" }, status: :not_found unless File.exist?(path)
243
+ # Idempotent: if already gone the desired state is already achieved.
244
+ return render json: { ok: true } unless File.exist?(path)
244
245
  return render json: { error: "Cannot delete this path" }, status: :forbidden if path_blocked_for_operations?(path)
245
246
 
246
247
  if File.directory?(path)
@@ -1,3 +1,3 @@
1
1
  module Mbeditor
2
- VERSION = "0.3.4"
2
+ VERSION = "0.3.7"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mbeditor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.3.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oliver Noonan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-15 00:00:00.000000000 Z
11
+ date: 2026-04-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails