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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -0
- data/app/assets/javascripts/mbeditor/application.js +1 -0
- data/app/assets/javascripts/mbeditor/application_iife_head.js +7 -0
- data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +1 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +213 -11
- data/app/assets/javascripts/mbeditor/components/GitPanel.js +14 -4
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +673 -160
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +41 -1
- data/app/assets/javascripts/mbeditor/components/TabBar.js +3 -2
- data/app/assets/javascripts/mbeditor/editor_plugins.js +21 -0
- data/app/assets/javascripts/mbeditor/editor_store.js +10 -2
- data/app/assets/javascripts/mbeditor/file_service.js +29 -23
- data/app/assets/javascripts/mbeditor/git_service.js +7 -11
- data/app/assets/javascripts/mbeditor/search_service.js +51 -14
- data/app/assets/javascripts/mbeditor/tab_manager.js +3 -3
- data/app/assets/javascripts/mbeditor/websocket_service.js +126 -0
- data/app/assets/stylesheets/mbeditor/editor.css +237 -15
- data/app/channels/mbeditor/editor_channel.rb +79 -0
- data/app/controllers/mbeditor/editors_controller.rb +177 -136
- data/app/controllers/mbeditor/git_controller.rb +5 -40
- data/app/services/mbeditor/git_blame_service.rb +6 -0
- data/app/services/mbeditor/git_commit_graph_service.rb +2 -0
- data/app/services/mbeditor/git_service.rb +97 -28
- data/app/services/mbeditor/redmine_service.rb +7 -0
- data/app/services/mbeditor/ruby_definition_service.rb +23 -2
- data/app/views/layouts/mbeditor/application.html.erb +4 -0
- data/lib/mbeditor/cable_log_filter.rb +28 -0
- data/lib/mbeditor/configuration.rb +7 -1
- data/lib/mbeditor/engine.rb +37 -0
- data/lib/mbeditor/rack/silence_ping_request.rb +4 -1
- data/lib/mbeditor/version.rb +3 -1
- data/lib/mbeditor.rb +2 -0
- 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
|
|
479
|
-
.search-results-
|
|
480
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
867
|
+
padding-right: 88px; /* 3 × 22px buttons + 4px clear + gaps */
|
|
833
868
|
}
|
|
834
869
|
|
|
835
|
-
/*
|
|
836
|
-
.search-input-
|
|
870
|
+
/* Adornment container — absolutely positioned inside the shell */
|
|
871
|
+
.search-input-adornments {
|
|
837
872
|
position: absolute;
|
|
838
873
|
right: 4px;
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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-
|
|
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
|