mbeditor 0.3.8 → 0.4.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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/app/assets/javascripts/mbeditor/application.js +1 -0
  4. data/app/assets/javascripts/mbeditor/application_iife_head.js +7 -0
  5. data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +1 -1
  6. data/app/assets/javascripts/mbeditor/components/EditorPanel.js +213 -11
  7. data/app/assets/javascripts/mbeditor/components/GitPanel.js +14 -4
  8. data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +673 -160
  9. data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +41 -1
  10. data/app/assets/javascripts/mbeditor/components/TabBar.js +3 -2
  11. data/app/assets/javascripts/mbeditor/editor_plugins.js +21 -0
  12. data/app/assets/javascripts/mbeditor/editor_store.js +10 -2
  13. data/app/assets/javascripts/mbeditor/file_service.js +29 -23
  14. data/app/assets/javascripts/mbeditor/git_service.js +7 -11
  15. data/app/assets/javascripts/mbeditor/search_service.js +51 -14
  16. data/app/assets/javascripts/mbeditor/tab_manager.js +3 -3
  17. data/app/assets/javascripts/mbeditor/websocket_service.js +126 -0
  18. data/app/assets/stylesheets/mbeditor/editor.css +237 -15
  19. data/app/channels/mbeditor/editor_channel.rb +79 -0
  20. data/app/controllers/mbeditor/editors_controller.rb +177 -136
  21. data/app/controllers/mbeditor/git_controller.rb +5 -40
  22. data/app/services/mbeditor/git_blame_service.rb +6 -0
  23. data/app/services/mbeditor/git_commit_graph_service.rb +2 -0
  24. data/app/services/mbeditor/git_service.rb +97 -28
  25. data/app/services/mbeditor/redmine_service.rb +7 -0
  26. data/app/services/mbeditor/ruby_definition_service.rb +23 -2
  27. data/app/views/layouts/mbeditor/application.html.erb +4 -0
  28. data/lib/mbeditor/cable_log_filter.rb +28 -0
  29. data/lib/mbeditor/configuration.rb +7 -1
  30. data/lib/mbeditor/engine.rb +37 -0
  31. data/lib/mbeditor/rack/silence_ping_request.rb +4 -1
  32. data/lib/mbeditor/version.rb +3 -1
  33. data/lib/mbeditor.rb +2 -0
  34. metadata +5 -2
@@ -1,6 +1,13 @@
1
1
  /* ── Mini Browser Editor Global Styles ──────────────────── */
2
2
  *, *::before, *::after { box-sizing: border-box; }
3
3
 
4
+ /* Suppress all focus outlines / coloured glows inside the IDE shell */
5
+ #mbeditor-root *:focus,
6
+ #mbeditor-root *:focus-visible {
7
+ outline: none !important;
8
+ box-shadow: none !important;
9
+ }
10
+
4
11
  html, body, #mbeditor-root {
5
12
  margin: 0; padding: 0;
6
13
  width: 100%; height: 100%;
@@ -327,6 +334,18 @@ html, body, #mbeditor-root {
327
334
  .tab-bar::-webkit-scrollbar { height: 3px; }
328
335
  .tab-bar::-webkit-scrollbar-thumb { background: var(--ide-hover-bg); }
329
336
 
337
+ /* Wrap (multi-row) tab layout */
338
+ .tab-bar.tab-bar-wrap {
339
+ flex-wrap: wrap;
340
+ overflow-x: hidden;
341
+ overflow-y: visible;
342
+ height: auto;
343
+ }
344
+ .tab-bar.tab-bar-wrap .tab-item {
345
+ flex-shrink: 1;
346
+ flex-grow: 0;
347
+ }
348
+
330
349
  .tab-item {
331
350
  display: flex;
332
351
  align-items: center;
@@ -475,16 +494,23 @@ html, body, #mbeditor-root {
475
494
  .welcome-tips li { font-size: 12px; color: var(--ide-text-muted); }
476
495
  .welcome-tips li i { color: var(--ide-text-muted); width: 14px; text-align: center; }
477
496
 
478
- /* ── Search results meta ─────────────────────────────────── */
479
- .search-results-meta {
480
- padding: 4px 8px 6px;
497
+ /* ── Search results header (fixed count above scroll area) ── */
498
+ .search-results-header {
499
+ flex-shrink: 0;
500
+ padding: 4px 10px 5px;
481
501
  font-size: 11px;
482
502
  color: var(--ide-text-muted);
483
503
  border-bottom: 1px solid var(--ide-panel-bg-alt);
484
- margin-bottom: 4px;
504
+ user-select: none;
485
505
  }
486
- .search-results-capped { color: #f0a040; }
487
506
  .search-results-empty { padding: 12px 8px; font-size: 12px; color: var(--ide-text-muted); }
507
+ .search-loading-more {
508
+ padding: 10px;
509
+ font-size: 11px;
510
+ color: var(--ide-text-muted);
511
+ text-align: center;
512
+ user-select: none;
513
+ }
488
514
 
489
515
  /* ── Shortcut Help Drawer ────────────────────────────────── */
490
516
  .shelp-backdrop {
@@ -785,7 +811,7 @@ html, body, #mbeditor-root {
785
811
 
786
812
  .statusbar-msg {
787
813
  margin-left: auto;
788
- color: color-mix(in srgb, var(--ide-accent-text) 80%, transparent);
814
+ color: var(--ide-accent-text);
789
815
  font-size: 11px;
790
816
  overflow: hidden;
791
817
  text-overflow: ellipsis;
@@ -803,6 +829,14 @@ html, body, #mbeditor-root {
803
829
  padding-left: 8px;
804
830
  }
805
831
 
832
+ .statusbar-eol-btn {
833
+ font-size: 10px;
834
+ font-weight: 600;
835
+ letter-spacing: 0.04em;
836
+ opacity: 0.85;
837
+ padding: 0 6px;
838
+ }
839
+
806
840
  /* ── Search Panel ─────────────────────────────────────────── */
807
841
  .search-panel {
808
842
  padding: 8px;
@@ -821,28 +855,64 @@ html, body, #mbeditor-root {
821
855
  }
822
856
 
823
857
  .search-input-shell {
824
- flex: 1;
858
+ flex-shrink: 0;
825
859
  position: relative;
826
860
  display: flex;
827
861
  align-items: center;
828
862
  min-width: 0;
829
863
  }
830
864
 
865
+ /* The input grows to fill; right padding makes room for adornments */
831
866
  .search-input-shell .search-input {
832
- padding-right: 24px;
867
+ padding-right: 88px; /* 3 × 22px buttons + 4px clear + gaps */
833
868
  }
834
869
 
835
- /* search-clear-btn sits absolutely inside the input shell */
836
- .search-input-shell .search-clear-btn {
870
+ /* Adornment container absolutely positioned inside the shell */
871
+ .search-input-adornments {
837
872
  position: absolute;
838
873
  right: 4px;
839
- width: 16px;
840
- height: 16px;
841
- font-size: 10px;
842
- color: var(--ide-text-muted);
874
+ top: 50%;
875
+ transform: translateY(-50%);
876
+ display: flex;
877
+ align-items: center;
878
+ gap: 1px;
879
+ }
880
+
881
+ /* Each small icon button inside the input */
882
+ .search-adornment-btn {
883
+ width: 20px;
884
+ height: 20px;
885
+ padding: 0;
886
+ border: 1px solid transparent;
887
+ border-radius: 3px;
843
888
  background: transparent;
889
+ color: var(--ide-text-muted);
890
+ font-size: 13px;
891
+ display: inline-flex;
892
+ align-items: center;
893
+ justify-content: center;
894
+ cursor: pointer;
895
+ flex-shrink: 0;
896
+ transition: color 0.1s, background 0.1s, border-color 0.1s;
897
+ }
898
+
899
+ .search-adornment-btn:hover {
900
+ color: var(--ide-text);
901
+ background: var(--ide-hover-bg);
902
+ }
903
+
904
+ .search-adornment-btn.active {
905
+ color: var(--ide-accent-fg);
906
+ background: rgba(88, 166, 255, 0.15);
907
+ border-color: var(--ide-accent-fg);
908
+ }
909
+
910
+ .search-adornment-clear {
911
+ font-size: 10px;
844
912
  }
845
913
 
914
+ /* ── old .search-options-row / .search-option-btn removed ── */
915
+
846
916
  /* Use .search-panel ancestor for higher specificity than button:not(.pico-btn) */
847
917
  .search-panel .search-btn {
848
918
  flex-shrink: 0;
@@ -884,11 +954,50 @@ html, body, #mbeditor-root {
884
954
  box-sizing: border-box;
885
955
  }
886
956
  .search-input::placeholder { color: var(--ide-text-muted); }
887
- .search-input:focus { border-color: var(--ide-accent-fg); }
957
+ .search-input:focus { border-color: var(--ide-border-input); }
958
+
959
+ /* ── Results area wrapper (positions the loading overlay) ── */
960
+ .search-results-area {
961
+ position: relative;
962
+ flex: 1;
963
+ min-height: 0;
964
+ display: flex;
965
+ flex-direction: column;
966
+ }
888
967
 
889
968
  .search-results {
890
969
  flex: 1;
891
970
  overflow-y: auto;
971
+ transition: filter 0.15s;
972
+ }
973
+
974
+ .search-results.search-results-blurred {
975
+ filter: blur(2px);
976
+ pointer-events: none;
977
+ user-select: none;
978
+ }
979
+
980
+ /* Centered circular loading overlay */
981
+ .search-loading-overlay {
982
+ position: absolute;
983
+ inset: 0;
984
+ display: flex;
985
+ align-items: center;
986
+ justify-content: center;
987
+ pointer-events: none;
988
+ }
989
+
990
+ .search-loading-spinner {
991
+ width: 28px;
992
+ height: 28px;
993
+ border: 3px solid var(--ide-border-input);
994
+ border-top-color: var(--ide-accent-fg);
995
+ border-radius: 50%;
996
+ animation: search-spin 0.7s linear infinite;
997
+ }
998
+
999
+ @keyframes search-spin {
1000
+ to { transform: rotate(360deg); }
892
1001
  }
893
1002
 
894
1003
  .search-result-item {
@@ -1424,6 +1533,55 @@ html, body, #mbeditor-root {
1424
1533
  min-width: 0;
1425
1534
  }
1426
1535
 
1536
+ .ide-methods-dropdown {
1537
+ background: #252526;
1538
+ border: 1px solid #454545;
1539
+ border-radius: 4px;
1540
+ box-shadow: 0 4px 14px rgba(0,0,0,0.55);
1541
+ min-width: 200px;
1542
+ max-width: 340px;
1543
+ max-height: 320px;
1544
+ overflow-y: auto;
1545
+ padding: 4px 0;
1546
+ }
1547
+
1548
+ .ide-methods-dropdown-item {
1549
+ display: flex;
1550
+ align-items: center;
1551
+ gap: 8px;
1552
+ padding: 5px 12px;
1553
+ cursor: pointer;
1554
+ color: #ccc;
1555
+ font-size: 12px;
1556
+ font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
1557
+ white-space: nowrap;
1558
+ overflow: hidden;
1559
+ text-overflow: ellipsis;
1560
+ }
1561
+
1562
+ .ide-methods-dropdown-item:hover {
1563
+ background: #094771;
1564
+ color: #fff;
1565
+ }
1566
+
1567
+ .ide-methods-dropdown-line {
1568
+ color: #858585;
1569
+ font-size: 11px;
1570
+ min-width: 28px;
1571
+ text-align: right;
1572
+ flex-shrink: 0;
1573
+ user-select: none;
1574
+ }
1575
+
1576
+ .ide-methods-dropdown-empty {
1577
+ padding: 8px 12px;
1578
+ color: #858585;
1579
+ font-size: 12px;
1580
+ font-style: italic;
1581
+ }
1582
+
1583
+
1584
+
1427
1585
  .project-action-btn {
1428
1586
  width: 24px;
1429
1587
  height: 24px;
@@ -1676,6 +1834,70 @@ html, body, #mbeditor-root {
1676
1834
  to { opacity: 1; transform: translateY(0); }
1677
1835
  }
1678
1836
 
1837
+ /* ── Draft restore dialog ───────────────────────────────────── */
1838
+ .ide-draft-restore-overlay {
1839
+ position: fixed;
1840
+ inset: 0;
1841
+ background: rgba(0,0,0,0.55);
1842
+ z-index: 10001;
1843
+ display: flex;
1844
+ align-items: center;
1845
+ justify-content: center;
1846
+ }
1847
+ .ide-draft-restore-dialog {
1848
+ background: var(--ide-panel-bg-alt);
1849
+ border: 1px solid var(--ide-border-input);
1850
+ border-radius: 8px;
1851
+ padding: 20px 24px;
1852
+ min-width: 320px;
1853
+ max-width: 480px;
1854
+ box-shadow: 0 8px 32px rgba(0,0,0,0.6);
1855
+ color: var(--ide-text);
1856
+ font-size: 13px;
1857
+ }
1858
+ .ide-draft-restore-title {
1859
+ font-size: 14px;
1860
+ font-weight: 600;
1861
+ margin-bottom: 10px;
1862
+ color: var(--ide-warning, #e5c07b);
1863
+ }
1864
+ .ide-draft-restore-body { margin-bottom: 8px; color: var(--ide-text-muted); }
1865
+ .ide-draft-restore-list {
1866
+ list-style: none;
1867
+ padding: 0;
1868
+ margin: 0 0 16px 0;
1869
+ max-height: 160px;
1870
+ overflow-y: auto;
1871
+ }
1872
+ .ide-draft-restore-list li {
1873
+ padding: 3px 0;
1874
+ font-family: monospace;
1875
+ font-size: 12px;
1876
+ color: var(--ide-accent-fg, #9cdcfe);
1877
+ }
1878
+ .ide-draft-restore-actions {
1879
+ display: flex;
1880
+ gap: 8px;
1881
+ justify-content: flex-end;
1882
+ }
1883
+ .ide-draft-restore-btn {
1884
+ padding: 5px 14px;
1885
+ border-radius: 4px;
1886
+ border: 1px solid var(--ide-border-input);
1887
+ background: var(--ide-input-bg);
1888
+ color: var(--ide-text);
1889
+ font-size: 12px;
1890
+ cursor: pointer;
1891
+ transition: background 0.1s;
1892
+ }
1893
+ .ide-draft-restore-btn:hover { background: var(--ide-hover-bg); }
1894
+ .ide-draft-restore-btn-primary {
1895
+ background: var(--ide-accent);
1896
+ color: var(--ide-accent-text);
1897
+ border-color: var(--ide-accent);
1898
+ }
1899
+ .ide-draft-restore-btn-primary:hover { opacity: 0.9; }
1900
+
1679
1901
  /* ── Settings panel ─────────────────────────────────────────── */
1680
1902
  .ide-settings-panel { display: flex; flex-direction: column; height: 100%; overflow-y: auto; }
1681
1903
  .ide-settings-body { padding: 8px 12px; display: grid; grid-template-columns: 1fr 1fr; gap: 4px 10px; align-items: start; }
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "pathname"
5
+
6
+ module Mbeditor
7
+ class EditorChannel < ActionCable::Channel::Base
8
+ STATE_MAX_BYTES = 1 * 1024 * 1024
9
+ SAFE_BRANCH_NAME = /\A[a-zA-Z0-9._\-\/]+\z/
10
+
11
+ def subscribed
12
+ stream_from "mbeditor_editor"
13
+ end
14
+
15
+ def unsubscribed
16
+ # no-op
17
+ end
18
+
19
+ # Called via WebSocketService.perform('save_state', { state: ... })
20
+ def save_state(data)
21
+ Rails.logger.silence do
22
+ payload = (data["state"] || data).to_json
23
+ return if payload.bytesize > STATE_MAX_BYTES
24
+
25
+ root = workspace_root
26
+ path = root.join("tmp", "mbeditor_workspace.json")
27
+ FileUtils.mkdir_p(root.join("tmp"))
28
+ File.open(path, File::RDWR | File::CREAT) do |f|
29
+ f.flock(File::LOCK_EX)
30
+ f.truncate(0)
31
+ f.rewind
32
+ f.write(payload)
33
+ end
34
+ end
35
+ rescue StandardError
36
+ # Never let a state-save failure crash the WebSocket connection
37
+ end
38
+
39
+ # Called via WebSocketService.perform('save_branch_state', { branch: ..., state: ... })
40
+ def save_branch_state(data)
41
+ Rails.logger.silence do
42
+ branch = data["branch"].to_s.strip
43
+ return unless branch.match?(SAFE_BRANCH_NAME)
44
+
45
+ state_data = data["state"]
46
+ payload_json = state_data.to_json
47
+ return if payload_json.bytesize > STATE_MAX_BYTES
48
+
49
+ root = workspace_root
50
+ path = root.join("tmp", "mbeditor_branch_states.json")
51
+ FileUtils.mkdir_p(root.join("tmp"))
52
+ File.open(path, File::RDWR | File::CREAT) do |f|
53
+ f.flock(File::LOCK_EX)
54
+ existing = f.size > 0 ? JSON.parse(f.read) : {}
55
+ existing[branch] = state_data
56
+ f.truncate(0)
57
+ f.rewind
58
+ f.write(existing.to_json)
59
+ end
60
+ end
61
+ rescue StandardError
62
+ # Never let a state-save failure crash the WebSocket connection
63
+ end
64
+
65
+ private
66
+
67
+ def workspace_root
68
+ configured = Mbeditor.configuration.workspace_root
69
+ return Pathname.new(configured.to_s) if configured.present?
70
+
71
+ # Fall back to git root, same logic as ApplicationController
72
+ rails_root = Rails.root.to_s
73
+ out, _err, status = Open3.capture3("git", "-C", rails_root, "rev-parse", "--show-toplevel")
74
+ Pathname.new(status.success? && out.strip.present? ? out.strip : rails_root)
75
+ rescue StandardError
76
+ Rails.root
77
+ end
78
+ end
79
+ end