mbeditor 0.13.0 → 0.14.0
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 +157 -1
- data/app/assets/javascripts/mbeditor/application.js +3 -1
- data/app/assets/javascripts/mbeditor/audit_log.js +165 -0
- data/app/assets/javascripts/mbeditor/collaboration_service.js +264 -22
- data/app/assets/javascripts/mbeditor/components/ChangelogView.js +89 -94
- data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +6 -9
- data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +12 -7
- data/app/assets/javascripts/mbeditor/components/CombinedDiffViewer.js +20 -0
- data/app/assets/javascripts/mbeditor/components/DiffViewer.js +1 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +440 -334
- data/app/assets/javascripts/mbeditor/components/FileHistoryPanel.js +6 -9
- data/app/assets/javascripts/mbeditor/components/FileTree.js +26 -12
- data/app/assets/javascripts/mbeditor/components/GitPanel.js +3 -0
- data/app/assets/javascripts/mbeditor/components/Gutter.js +51 -0
- data/app/assets/javascripts/mbeditor/components/ImportDialog.js +9 -1
- data/app/assets/javascripts/mbeditor/components/LogPanel.js +3 -44
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +1322 -1431
- data/app/assets/javascripts/mbeditor/components/ModelGraph.js +94 -37
- data/app/assets/javascripts/mbeditor/components/ProblemsPanel.js +82 -72
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +121 -81
- data/app/assets/javascripts/mbeditor/components/SettingsModal.js +342 -0
- data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +2 -1
- data/app/assets/javascripts/mbeditor/components/TabBar.js +67 -98
- data/app/assets/javascripts/mbeditor/editor_plugins.js +694 -306
- data/app/assets/javascripts/mbeditor/file_import.js +13 -15
- data/app/assets/javascripts/mbeditor/file_service.js +46 -57
- data/app/assets/javascripts/mbeditor/git_service.js +15 -1
- data/app/assets/javascripts/mbeditor/history_service.js +5 -13
- data/app/assets/javascripts/mbeditor/search_service.js +29 -2
- data/app/assets/javascripts/mbeditor/tab_manager.js +135 -54
- data/app/assets/javascripts/mbeditor/websocket_service.js +13 -5
- data/app/assets/stylesheets/mbeditor/application.css +6 -1
- data/app/assets/stylesheets/mbeditor/editor.css +762 -297
- data/app/assets/stylesheets/mbeditor/glass.css +163 -0
- data/app/assets/stylesheets/mbeditor/themes.css +90 -30
- data/app/channels/mbeditor/collaboration_channel.rb +25 -6
- data/app/controllers/mbeditor/application_controller.rb +26 -3
- data/app/controllers/mbeditor/editors_controller.rb +125 -465
- data/app/services/mbeditor/archive_service.rb +137 -0
- data/app/services/mbeditor/collaboration_doc_store.rb +180 -12
- data/app/services/mbeditor/duplicate_content_scanner.rb +105 -0
- data/app/services/mbeditor/editor_state_service.rb +14 -51
- data/app/services/mbeditor/file_history_service.rb +222 -0
- data/app/services/mbeditor/git_info_service.rb +6 -0
- data/app/services/mbeditor/js_globals_service.rb +12 -1
- data/app/services/mbeditor/js_syntax_check_service.rb +42 -16
- data/app/services/mbeditor/lint_service.rb +137 -0
- data/app/services/mbeditor/locked_json_file.rb +67 -0
- data/app/services/mbeditor/process_runner.rb +32 -0
- data/app/services/mbeditor/rubocop_run_service.rb +17 -5
- data/app/services/mbeditor/ruby_lsp_result_translator.rb +226 -0
- data/app/services/mbeditor/schema_service.rb +8 -2
- data/app/services/mbeditor/search_replace_service.rb +19 -2
- data/app/services/mbeditor/test_runner_service.rb +3 -56
- data/app/views/layouts/mbeditor/application.html.erb +1 -1
- data/lib/mbeditor/audit_log.rb +203 -0
- data/lib/mbeditor/configuration.rb +6 -9
- data/lib/mbeditor/rack/pending_migration_bypass.rb +15 -9
- data/lib/mbeditor/route_map.rb +4 -1
- data/lib/mbeditor/ruby_lsp_client.rb +82 -14
- data/lib/mbeditor/version.rb +1 -1
- data/lib/mbeditor.rb +1 -0
- data/lib/tasks/mbeditor.rake +23 -0
- metadata +14 -3
- data/app/assets/javascripts/mbeditor/components/TestRunPanel.js +0 -312
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/* ── Liquid Glass chrome (opt-in) ─────────────────────────────────────────────
|
|
2
|
+
*
|
|
3
|
+
* Scoped entirely under html[data-glass], set alongside data-theme by the same
|
|
4
|
+
* effect in MbeditorApp.js. It is a layer over whichever theme is active, not a
|
|
5
|
+
* theme of its own, so every colour here is derived from the --ide-* tokens
|
|
6
|
+
* with color-mix — hardcoding a palette would have made it a twelfth theme that
|
|
7
|
+
* silently replaced the user's chosen one.
|
|
8
|
+
*
|
|
9
|
+
* The functional layer (title bar, activity bar, sidebar, tabs, status bar) is
|
|
10
|
+
* glass. The content layer — Monaco — stays solid: legibility beats the effect,
|
|
11
|
+
* and a blurred backdrop under a text editor costs a re-composite per repaint.
|
|
12
|
+
* ─────────────────────────────────────────────────────────────────────────── */
|
|
13
|
+
html[data-glass] {
|
|
14
|
+
--g-surface: color-mix(in srgb, var(--ide-panel-bg) 62%, transparent);
|
|
15
|
+
--g-surface-2: color-mix(in srgb, var(--ide-panel-bg) 44%, transparent);
|
|
16
|
+
--g-lift: color-mix(in srgb, var(--ide-hover-bg) 82%, transparent);
|
|
17
|
+
--g-hover: color-mix(in srgb, var(--ide-text) 12%, transparent);
|
|
18
|
+
--g-edge: color-mix(in srgb, var(--ide-text) 14%, transparent);
|
|
19
|
+
--g-edge-strong: color-mix(in srgb, var(--ide-text) 28%, transparent);
|
|
20
|
+
--g-shadow: 0 1px 2px rgba(0,0,0,0.28), 0 8px 28px rgba(0,0,0,0.26);
|
|
21
|
+
--g-blur: blur(20px) saturate(180%);
|
|
22
|
+
|
|
23
|
+
/* The field the glass refracts. Two accent-tinted pools over the theme's own
|
|
24
|
+
background, so every theme keeps its own hue instead of turning navy. */
|
|
25
|
+
--g-bg:
|
|
26
|
+
radial-gradient(120% 90% at 12% 0%, color-mix(in srgb, var(--ide-accent) 34%, transparent) 0%, transparent 58%),
|
|
27
|
+
radial-gradient(120% 110% at 82% 100%, color-mix(in srgb, var(--ide-accent-fg) 26%, transparent) 0%, transparent 60%),
|
|
28
|
+
var(--ide-bg);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
html[data-glass] .ide-shell {
|
|
32
|
+
background: var(--g-bg) fixed;
|
|
33
|
+
padding: 6px 3px;
|
|
34
|
+
gap: 6px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
html[data-glass] .ide-activity-bar,
|
|
38
|
+
html[data-glass] .ide-sidebar,
|
|
39
|
+
html[data-glass] .ide-git-right-panel,
|
|
40
|
+
html[data-glass] .ide-main,
|
|
41
|
+
html[data-glass] .ide-log-drawer,
|
|
42
|
+
html[data-glass] .ide-problems-drawer,
|
|
43
|
+
html[data-glass] .ide-statusbar {
|
|
44
|
+
backdrop-filter: var(--g-blur);
|
|
45
|
+
-webkit-backdrop-filter: var(--g-blur);
|
|
46
|
+
background: var(--g-surface);
|
|
47
|
+
border: 1px solid var(--g-edge);
|
|
48
|
+
box-shadow: var(--g-shadow);
|
|
49
|
+
border-radius: 12px;
|
|
50
|
+
/* overflow stays hidden: it is what clips the children to the rounded
|
|
51
|
+
corners. The prototype set the body to `overflow: visible` so the card
|
|
52
|
+
shadows could spill, and that silently removed the scroll container the
|
|
53
|
+
search results live in. A clipped shadow is the cheaper loss. */
|
|
54
|
+
overflow: hidden;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* The centre column is only a layout box: the editor and each bottom drawer
|
|
58
|
+
are separate cards with a gutter between them, like VS Code's panel. */
|
|
59
|
+
/* No flex gap: the 6px Gutter between the editor and a drawer is the gap. */
|
|
60
|
+
html[data-glass] .ide-center-column { gap: 0; background: transparent; }
|
|
61
|
+
html[data-glass] .ide-log-drawer,
|
|
62
|
+
html[data-glass] .ide-problems-drawer { border-top: 1px solid var(--g-edge); }
|
|
63
|
+
|
|
64
|
+
/* The activity bar and the sidebar are one card with a seam, not two cards
|
|
65
|
+
butting rounded corners together — there is no gutter between them to fill. */
|
|
66
|
+
html[data-glass] .ide-activity-bar { border-radius: 12px 0 0 12px; border-right: 1px solid var(--g-edge); }
|
|
67
|
+
html[data-glass] .ide-body:not(:has(.ide-sidebar)) .ide-activity-bar { border-radius: 12px; }
|
|
68
|
+
html[data-glass] .ide-sidebar { border-radius: 0 12px 12px 0; border-left: none; }
|
|
69
|
+
|
|
70
|
+
/* ── Title bar ────────────────────────────────────────────────────────────── */
|
|
71
|
+
/* The title bar is not a card: it sits flat on the backdrop like VS Code's,
|
|
72
|
+
so the first card row starts under a plain strip rather than a second box.
|
|
73
|
+
It carries no fill at all: a filled strip stops short of the window edges
|
|
74
|
+
where the cards below it are inset, and reads as a floating box. The blur
|
|
75
|
+
stays, and is what keeps the icons and the theme swatches off the raw
|
|
76
|
+
accent-tinted gradient without painting a surface. */
|
|
77
|
+
html[data-glass] .ide-titlebar {
|
|
78
|
+
background: transparent;
|
|
79
|
+
backdrop-filter: var(--g-blur);
|
|
80
|
+
-webkit-backdrop-filter: var(--g-blur);
|
|
81
|
+
border: none;
|
|
82
|
+
box-shadow: none;
|
|
83
|
+
height: 35px;
|
|
84
|
+
margin: -6px 0 -6px;
|
|
85
|
+
padding: 0 8px;
|
|
86
|
+
}
|
|
87
|
+
html[data-glass] .ide-titlebar-icon { color: var(--ide-accent-fg); }
|
|
88
|
+
html[data-glass] .ide-titlebar-title { color: var(--ide-text); font-weight: 600; }
|
|
89
|
+
html[data-glass] .ide-titlebar-search {
|
|
90
|
+
background: var(--g-surface-2);
|
|
91
|
+
border: 1px solid var(--g-edge);
|
|
92
|
+
}
|
|
93
|
+
html[data-glass] .ide-titlebar-search:hover { background: var(--g-hover); color: var(--ide-text); }
|
|
94
|
+
|
|
95
|
+
/* ── The gutter ───────────────────────────────────────────────────────────── */
|
|
96
|
+
/* Same 6px as everywhere else — a wider one reads as a gap in the layout
|
|
97
|
+
rather than a handle. The 3-dot grip comes from editor.css. */
|
|
98
|
+
html[data-glass] .ide-gutter { background: transparent; border: none; }
|
|
99
|
+
html[data-glass] .ide-gutter:hover,
|
|
100
|
+
html[data-glass] .ide-gutter.active { background: var(--g-hover); }
|
|
101
|
+
|
|
102
|
+
/* ── Tabs: same chips as Modern, on a transparent strip ───────────────────── */
|
|
103
|
+
html[data-glass] .tab-bar { background: transparent; border-bottom: 1px solid var(--g-edge); }
|
|
104
|
+
html[data-glass] .tab-item:hover { background: var(--g-hover); }
|
|
105
|
+
html[data-glass] .tab-item.active { background: var(--g-lift); }
|
|
106
|
+
|
|
107
|
+
/* ── Editor: the content layer stays solid ────────────────────────────────── */
|
|
108
|
+
html[data-glass] .ide-editor-toolbar { background: transparent; border-bottom: 1px solid var(--g-edge); }
|
|
109
|
+
html[data-glass] .ide-icon-btn:hover { background: var(--g-hover); }
|
|
110
|
+
html[data-glass] .ide-editor-wrapper { background: var(--ide-bg); }
|
|
111
|
+
html[data-glass] .ide-editor-wrapper .monaco-editor,
|
|
112
|
+
html[data-glass] .ide-editor-wrapper .monaco-editor .overflow-guard,
|
|
113
|
+
html[data-glass] .ide-editor-wrapper .monaco-editor-background,
|
|
114
|
+
html[data-glass] .ide-editor-wrapper .monaco-editor .margin { background-color: var(--ide-bg) !important; }
|
|
115
|
+
|
|
116
|
+
/* ── Status bar ───────────────────────────────────────────────────────────── */
|
|
117
|
+
/* Taller than Modern's 22px so the chips have room; the 6px gutter above it
|
|
118
|
+
stays, matching the explorer gutter, and it eats 2px of shell padding below. */
|
|
119
|
+
html[data-glass] .ide-statusbar { border-top: 1px solid var(--g-edge); height: 28px; margin: 0 0 -2px; }
|
|
120
|
+
/* The drawer-open handle lies over the 6px gutter between the editor card and
|
|
121
|
+
the status bar rather than adding a strip of its own above it. */
|
|
122
|
+
/* Sits in the shell's own 6px gap between the body and the status bar. */
|
|
123
|
+
html[data-glass] .ide-gutter-drawer-open { margin: -6px 0; }
|
|
124
|
+
/* .statusbar-version carries .statusbar-btn too, so one rule covers both. */
|
|
125
|
+
html[data-glass] .statusbar-btn { border-radius: 999px; padding: 0 9px; }
|
|
126
|
+
html[data-glass] .statusbar-btn:hover { background: var(--g-hover); }
|
|
127
|
+
html[data-glass] .statusbar-sep { background: var(--g-edge); }
|
|
128
|
+
|
|
129
|
+
/* ── Panels and inputs ────────────────────────────────────────────────────── */
|
|
130
|
+
html[data-glass] .search-panel { background: transparent; }
|
|
131
|
+
/* A sticky header is the one surface in the sidebar that cannot be glass: the
|
|
132
|
+
tree scrolls underneath it, so at 62% the rows showed straight through the
|
|
133
|
+
header's own label and buttons. Opaque against the sidebar it sits in. */
|
|
134
|
+
html[data-glass] .ide-sidebar-scrollable .collapsible-header { background: var(--ide-panel-bg); }
|
|
135
|
+
html[data-glass] #mbeditor-root :is(input[type="text"], input[type="search"], input[type="number"], textarea, select),
|
|
136
|
+
html[data-glass] .search-input {
|
|
137
|
+
background: var(--g-surface-2);
|
|
138
|
+
border: 1px solid var(--g-edge);
|
|
139
|
+
border-radius: 10px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* ── Quick open ───────────────────────────────────────────────────────────── */
|
|
143
|
+
/* The frosted box itself is the default look now (editor.css) regardless of
|
|
144
|
+
the glass setting — only the capsule-row treatment is glass-specific. */
|
|
145
|
+
html[data-glass] .quick-open-overlay { background: color-mix(in srgb, var(--ide-bg) 30%, transparent); }
|
|
146
|
+
html[data-glass] .quick-open-result { border-radius: 999px; margin: 0 6px; }
|
|
147
|
+
html[data-glass] .quick-open-result:hover { background: var(--g-hover); }
|
|
148
|
+
|
|
149
|
+
/* Reduced transparency: the same layout with solid fills. The blur is the
|
|
150
|
+
expensive part and the part that hurts, so it goes first. */
|
|
151
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
152
|
+
html[data-glass] {
|
|
153
|
+
--g-surface: var(--ide-panel-bg);
|
|
154
|
+
--g-surface-2: var(--ide-input-bg);
|
|
155
|
+
--g-lift: var(--ide-hover-bg);
|
|
156
|
+
--g-hover: var(--ide-hover-bg);
|
|
157
|
+
--g-bg: var(--ide-bg);
|
|
158
|
+
}
|
|
159
|
+
html[data-glass] * {
|
|
160
|
+
backdrop-filter: none !important;
|
|
161
|
+
-webkit-backdrop-filter: none !important;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -7,33 +7,47 @@
|
|
|
7
7
|
* document.documentElement.setAttribute('data-theme', editorPrefs.theme)
|
|
8
8
|
* ──────────────────────────────────────────────────────────────────────────── */
|
|
9
9
|
|
|
10
|
-
/* ══ DEFAULT / vs-dark
|
|
10
|
+
/* ══ DEFAULT / vs-dark — VS Code "Dark Modern" ══════════════════════════════
|
|
11
|
+
* Values are Dark Modern's, with two raised for WCAG AA: --ide-text-muted
|
|
12
|
+
* (#9d9d9d, ≥5.2:1 on every surface it lands on, including the inactive tab)
|
|
13
|
+
* and --ide-border-input (#808080, 3.29:1 on the input fill — Dark Modern's
|
|
14
|
+
* own #3c3c3c is 1.18:1 and effectively invisible).
|
|
15
|
+
* --ide-accent is the chrome blue; --ide-accent-fg is the lighter foreground
|
|
16
|
+
* blue, because #0078d4 as text on #1f1f1f is only 3.64:1.
|
|
17
|
+
* ──────────────────────────────────────────────────────────────────────────── */
|
|
11
18
|
:root,
|
|
12
19
|
[data-theme="vs-dark"] {
|
|
13
20
|
color-scheme: dark;
|
|
14
|
-
--status-hover-bg: rgba(255,255,255,0.
|
|
15
|
-
|
|
21
|
+
--status-hover-bg: rgba(255,255,255,0.10);
|
|
22
|
+
/* Chrome foreground: the title bar and status bar are panel-coloured in
|
|
23
|
+
Dark Modern, not accent-filled, so this is a text colour, not "on blue". */
|
|
24
|
+
--ide-accent-text: #cccccc;
|
|
25
|
+
/* Status bar while migrations are pending. Both halves are per-theme:
|
|
26
|
+
* --ide-warning is authored as a foreground colour, so several themes
|
|
27
|
+
* pick a dark ochre that is unreadable as a bar background. */
|
|
28
|
+
--ide-statusbar-warn-bg: #cca700;
|
|
29
|
+
--ide-statusbar-warn-text: #1e1e1e;
|
|
16
30
|
|
|
17
31
|
/* Backgrounds */
|
|
18
|
-
--ide-bg: #
|
|
19
|
-
--ide-panel-bg: #
|
|
20
|
-
--ide-panel-bg-alt: #
|
|
21
|
-
--ide-hover-bg: #
|
|
22
|
-
--ide-active-bg: #
|
|
23
|
-
--ide-input-bg: #
|
|
32
|
+
--ide-bg: #1f1f1f;
|
|
33
|
+
--ide-panel-bg: #181818;
|
|
34
|
+
--ide-panel-bg-alt: #202020;
|
|
35
|
+
--ide-hover-bg: #2a2a2a;
|
|
36
|
+
--ide-active-bg: #04395e;
|
|
37
|
+
--ide-input-bg: #313131;
|
|
24
38
|
|
|
25
39
|
/* Borders */
|
|
26
|
-
--ide-border: #
|
|
27
|
-
--ide-border-input: #
|
|
40
|
+
--ide-border: #2b2b2b;
|
|
41
|
+
--ide-border-input: #808080;
|
|
28
42
|
|
|
29
43
|
/* Text */
|
|
30
44
|
--ide-text: #cccccc;
|
|
31
45
|
--ide-text-dim: #bbbbbb;
|
|
32
|
-
--ide-text-muted: #
|
|
46
|
+
--ide-text-muted: #9d9d9d;
|
|
33
47
|
|
|
34
48
|
/* Accent */
|
|
35
|
-
--ide-accent: #
|
|
36
|
-
--ide-accent-fg: #
|
|
49
|
+
--ide-accent: #0078d4;
|
|
50
|
+
--ide-accent-fg: #4daafc;
|
|
37
51
|
|
|
38
52
|
/* Status */
|
|
39
53
|
--ide-success: #89d185;
|
|
@@ -41,37 +55,43 @@
|
|
|
41
55
|
--ide-warning: #f9a825;
|
|
42
56
|
|
|
43
57
|
/* Misc */
|
|
44
|
-
--ide-scrollbar: #
|
|
58
|
+
--ide-scrollbar: #3c3c3c;
|
|
45
59
|
--ide-graph-line: #555555;
|
|
46
60
|
}
|
|
47
61
|
|
|
48
|
-
/* ══ LIGHT / vs
|
|
62
|
+
/* ══ LIGHT / vs — VS Code "Light Modern" ════════════════════════════════════
|
|
63
|
+
* Same treatment as Dark Modern: --ide-text-muted #646464 (≥4.8:1 on bg,
|
|
64
|
+
* panel, hover and the inactive tab), --ide-border-input #8a8a8a (3.45:1 on
|
|
65
|
+
* white), and warning/success darkened to clear 4.5:1 on white.
|
|
66
|
+
* ──────────────────────────────────────────────────────────────────────────── */
|
|
49
67
|
[data-theme="vs"] {
|
|
50
68
|
color-scheme: light;
|
|
51
|
-
--ide-accent-text: #
|
|
52
|
-
--status-hover-bg: rgba(0,0,0,0.
|
|
69
|
+
--ide-accent-text: #3b3b3b;
|
|
70
|
+
--status-hover-bg: rgba(0,0,0,0.08);
|
|
71
|
+
--ide-statusbar-warn-bg: #e9a700;
|
|
72
|
+
--ide-statusbar-warn-text: #1f1f1f;
|
|
53
73
|
--ide-bg: #ffffff;
|
|
54
|
-
--ide-panel-bg: #
|
|
55
|
-
--ide-panel-bg-alt: #
|
|
74
|
+
--ide-panel-bg: #f8f8f8;
|
|
75
|
+
--ide-panel-bg-alt: #f0f0f0;
|
|
56
76
|
--ide-hover-bg: #e8e8e8;
|
|
57
|
-
--ide-active-bg: #
|
|
77
|
+
--ide-active-bg: #e4e6f1;
|
|
58
78
|
--ide-input-bg: #ffffff;
|
|
59
79
|
|
|
60
|
-
--ide-border: #
|
|
61
|
-
--ide-border-input: #
|
|
80
|
+
--ide-border: #e5e5e5;
|
|
81
|
+
--ide-border-input: #8a8a8a;
|
|
62
82
|
|
|
63
|
-
--ide-text: #
|
|
83
|
+
--ide-text: #3b3b3b;
|
|
64
84
|
--ide-text-dim: #616161;
|
|
65
|
-
--ide-text-muted: #
|
|
85
|
+
--ide-text-muted: #646464;
|
|
66
86
|
|
|
67
|
-
--ide-accent: #
|
|
68
|
-
--ide-accent-fg: #
|
|
87
|
+
--ide-accent: #005fb8;
|
|
88
|
+
--ide-accent-fg: #005fb8;
|
|
69
89
|
|
|
70
|
-
--ide-success: #
|
|
90
|
+
--ide-success: #2e7d31;
|
|
71
91
|
--ide-danger: #d32f2f;
|
|
72
|
-
--ide-warning: #
|
|
92
|
+
--ide-warning: #8a6800;
|
|
73
93
|
|
|
74
|
-
--ide-scrollbar: #
|
|
94
|
+
--ide-scrollbar: #cecece;
|
|
75
95
|
--ide-graph-line: #bdbdbd;
|
|
76
96
|
}
|
|
77
97
|
|
|
@@ -79,6 +99,8 @@
|
|
|
79
99
|
[data-theme="hc-black"] {
|
|
80
100
|
color-scheme: dark;
|
|
81
101
|
--ide-accent-text: #000000;
|
|
102
|
+
--ide-statusbar-warn-bg: #ffd700;
|
|
103
|
+
--ide-statusbar-warn-text: #000000;
|
|
82
104
|
--status-hover-bg: rgba(255,255,255,0.12);
|
|
83
105
|
--ide-bg: #000000;
|
|
84
106
|
--ide-panel-bg: #0a0a0a;
|
|
@@ -109,6 +131,8 @@
|
|
|
109
131
|
[data-theme="hc-light"] {
|
|
110
132
|
color-scheme: light;
|
|
111
133
|
--ide-accent-text: #ffffff;
|
|
134
|
+
--ide-statusbar-warn-bg: #7d4f01;
|
|
135
|
+
--ide-statusbar-warn-text: #ffffff;
|
|
112
136
|
--status-hover-bg: rgba(0,0,0,0.06);
|
|
113
137
|
--ide-bg: #ffffff;
|
|
114
138
|
--ide-panel-bg: #f8f8f8;
|
|
@@ -139,6 +163,8 @@
|
|
|
139
163
|
[data-theme="dracula"] {
|
|
140
164
|
color-scheme: dark;
|
|
141
165
|
--ide-accent-text: #ffffff;
|
|
166
|
+
--ide-statusbar-warn-bg: #ffb86c;
|
|
167
|
+
--ide-statusbar-warn-text: #282a36;
|
|
142
168
|
--status-hover-bg: rgba(255,255,255,0.08);
|
|
143
169
|
--ide-bg: #282a36;
|
|
144
170
|
--ide-panel-bg: #21222c;
|
|
@@ -169,6 +195,8 @@
|
|
|
169
195
|
[data-theme="night-owl"] {
|
|
170
196
|
color-scheme: dark;
|
|
171
197
|
--ide-accent-text: #ffffff;
|
|
198
|
+
--ide-statusbar-warn-bg: #ffcb8b;
|
|
199
|
+
--ide-statusbar-warn-text: #011627;
|
|
172
200
|
--status-hover-bg: rgba(255,255,255,0.08);
|
|
173
201
|
--ide-bg: #011627;
|
|
174
202
|
--ide-panel-bg: #01111d;
|
|
@@ -199,6 +227,8 @@
|
|
|
199
227
|
[data-theme="monokai"] {
|
|
200
228
|
color-scheme: dark;
|
|
201
229
|
--ide-accent-text: #000000;
|
|
230
|
+
--ide-statusbar-warn-bg: #fd971f;
|
|
231
|
+
--ide-statusbar-warn-text: #272822;
|
|
202
232
|
--status-hover-bg: rgba(255,255,255,0.08);
|
|
203
233
|
--ide-bg: #272822;
|
|
204
234
|
--ide-panel-bg: #1e1f1c;
|
|
@@ -229,6 +259,8 @@
|
|
|
229
259
|
[data-theme="nord"] {
|
|
230
260
|
color-scheme: dark;
|
|
231
261
|
--ide-accent-text: #000000;
|
|
262
|
+
--ide-statusbar-warn-bg: #ebcb8b;
|
|
263
|
+
--ide-statusbar-warn-text: #2e3440;
|
|
232
264
|
--status-hover-bg: rgba(255,255,255,0.08);
|
|
233
265
|
--ide-bg: #2e3440;
|
|
234
266
|
--ide-panel-bg: #252a34;
|
|
@@ -259,6 +291,8 @@
|
|
|
259
291
|
[data-theme="github-dark"] {
|
|
260
292
|
color-scheme: dark;
|
|
261
293
|
--ide-accent-text: #ffffff;
|
|
294
|
+
--ide-statusbar-warn-bg: #d29922;
|
|
295
|
+
--ide-statusbar-warn-text: #0d1117;
|
|
262
296
|
--status-hover-bg: rgba(255,255,255,0.08);
|
|
263
297
|
--ide-bg: #0d1117;
|
|
264
298
|
--ide-panel-bg: #161b22;
|
|
@@ -289,6 +323,8 @@
|
|
|
289
323
|
[data-theme="tomorrow-night"] {
|
|
290
324
|
color-scheme: dark;
|
|
291
325
|
--ide-accent-text: #ffffff;
|
|
326
|
+
--ide-statusbar-warn-bg: #de935f;
|
|
327
|
+
--ide-statusbar-warn-text: #1d1f21;
|
|
292
328
|
--status-hover-bg: rgba(255,255,255,0.08);
|
|
293
329
|
--ide-bg: #1d1f21;
|
|
294
330
|
--ide-panel-bg: #161719;
|
|
@@ -319,6 +355,8 @@
|
|
|
319
355
|
[data-theme="github-light"] {
|
|
320
356
|
color-scheme: light;
|
|
321
357
|
--ide-accent-text: #ffffff;
|
|
358
|
+
--ide-statusbar-warn-bg: #9a6700;
|
|
359
|
+
--ide-statusbar-warn-text: #ffffff;
|
|
322
360
|
--status-hover-bg: rgba(0,0,0,0.06);
|
|
323
361
|
--ide-bg: #ffffff;
|
|
324
362
|
--ide-panel-bg: #f6f8fa;
|
|
@@ -483,3 +521,25 @@
|
|
|
483
521
|
:root[data-theme="vs"],
|
|
484
522
|
:root[data-theme="hc-light"],
|
|
485
523
|
:root[data-theme="github-light"] { color-scheme: light; }
|
|
524
|
+
|
|
525
|
+
/* ══ CHROME SURFACE TOKENS ══════════════════════════════════════════════════
|
|
526
|
+
* The title bar and status bar are accent-filled in the nine legacy themes and
|
|
527
|
+
* panel-coloured in the two Modern ones, so the status bar reads its fill from
|
|
528
|
+
* a token rather than from --ide-accent directly. --ide-on-accent is the text
|
|
529
|
+
* colour for the few things that really do sit on an accent fill (badges,
|
|
530
|
+
* primary buttons); it tracks --ide-accent-text everywhere except Modern,
|
|
531
|
+
* where --ide-accent-text became a chrome text colour rather than "on blue".
|
|
532
|
+
*
|
|
533
|
+
* Specificity: :root and :root[data-theme=…] tie on the rule below only
|
|
534
|
+
* because the Modern block comes last; keep that order.
|
|
535
|
+
* ─────────────────────────────────────────────────────────────────────────── */
|
|
536
|
+
:root {
|
|
537
|
+
--ide-statusbar-bg: var(--ide-accent);
|
|
538
|
+
--ide-on-accent: var(--ide-accent-text);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
:root[data-theme="vs-dark"],
|
|
542
|
+
:root[data-theme="vs"] {
|
|
543
|
+
--ide-statusbar-bg: var(--ide-panel-bg);
|
|
544
|
+
--ide-on-accent: #ffffff;
|
|
545
|
+
}
|
|
@@ -21,27 +21,40 @@ module Mbeditor
|
|
|
21
21
|
return reject if @path.empty? && respond_to?(:reject, true)
|
|
22
22
|
|
|
23
23
|
stream_from stream_name if respond_to?(:stream_from)
|
|
24
|
+
CollaborationDocStore.join(room_key)
|
|
25
|
+
@joined = true
|
|
24
26
|
transmit_initial_state
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def unsubscribed
|
|
28
|
-
|
|
30
|
+
CollaborationDocStore.leave(room_key) if @joined
|
|
31
|
+
@joined = false
|
|
29
32
|
end
|
|
30
33
|
|
|
31
34
|
def doc_update(data)
|
|
32
35
|
bytes = data["update"]
|
|
33
36
|
return if bytes.nil?
|
|
34
37
|
|
|
35
|
-
CollaborationDocStore.record_update(room_key, bytes)
|
|
36
|
-
relay("type" => "doc_update", "update" => bytes)
|
|
38
|
+
seq = CollaborationDocStore.record_update(room_key, bytes)
|
|
39
|
+
relay("type" => "doc_update", "update" => bytes, "seq" => seq)
|
|
37
40
|
end
|
|
38
41
|
|
|
39
42
|
def snapshot(data)
|
|
40
43
|
bytes = data["snapshot"]
|
|
41
44
|
return if bytes.nil?
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
applied_seq = data["applied_seq"]
|
|
47
|
+
CollaborationDocStore.replace_snapshot(room_key, bytes, applied_seq: applied_seq)
|
|
48
|
+
relay("type" => "snapshot", "snapshot" => bytes, "applied_seq" => applied_seq)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# A client whose delta replay was incomplete (missing dependencies after the
|
|
52
|
+
# buffer overflowed, or a room restarted from scratch) asks the room for a
|
|
53
|
+
# full snapshot. It is relayed to the other subscribers; any bound peer
|
|
54
|
+
# answers with its own state via #snapshot. The requester attaches once that
|
|
55
|
+
# lands (see CollaborationService).
|
|
56
|
+
def request_snapshot(_data = nil)
|
|
57
|
+
relay("type" => "request_snapshot")
|
|
45
58
|
end
|
|
46
59
|
|
|
47
60
|
def awareness(data)
|
|
@@ -66,7 +79,13 @@ module Mbeditor
|
|
|
66
79
|
return unless respond_to?(:transmit, true)
|
|
67
80
|
|
|
68
81
|
state = CollaborationDocStore.state_for(room_key)
|
|
69
|
-
|
|
82
|
+
# Exactly one client per empty room is told to seed it from disk; see
|
|
83
|
+
# CollaborationDocStore.claim_seed. Everyone else waits for that content.
|
|
84
|
+
# delta_seqs lets the joiner track how far the replay reaches, so its own
|
|
85
|
+
# later snapshot can tell the server which deltas to keep.
|
|
86
|
+
transmit({ "type" => "sync", "snapshot" => state[:snapshot], "deltas" => state[:deltas],
|
|
87
|
+
"delta_seqs" => state[:delta_seqs], "snapshot_seq" => state[:snapshot_seq],
|
|
88
|
+
"seed" => CollaborationDocStore.claim_seed(room_key) })
|
|
70
89
|
end
|
|
71
90
|
|
|
72
91
|
# A room is identified by workspace *and* relative path. Keying on the path
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "open3"
|
|
4
|
-
require "pathname"
|
|
5
|
-
|
|
6
3
|
module Mbeditor
|
|
4
|
+
# No `rescue_from StandardError` here, and the repeated
|
|
5
|
+
# `rescue StandardError => e; render json: { error: e.message },
|
|
6
|
+
# status: :unprocessable_content` at the foot of ~29 actions is deliberate,
|
|
7
|
+
# not something waiting to be dried up. Most actions in this engine have no
|
|
8
|
+
# rescue at all and rely on an unhandled error surfacing as a real 500:
|
|
9
|
+
# resilient_routing_test.rb asserts exactly that, to prove ResilientRouter
|
|
10
|
+
# sits below Rails' own exception middleware. A blanket handler swallows that
|
|
11
|
+
# signal for 18 actions here plus `tail` in LogsController. Collapsing it to
|
|
12
|
+
# one handler plus a per-controller allowlist was tried and reverted: it saved
|
|
13
|
+
# 21 lines, moved each action's error behaviour a thousand lines away from the
|
|
14
|
+
# action, and left the same action names duplicated in the allowlist.
|
|
7
15
|
class ApplicationController < ActionController::Base
|
|
8
16
|
protect_from_forgery with: :exception
|
|
9
17
|
# Before the auth hook, and on every controller: a disallowed environment is
|
|
@@ -12,6 +20,9 @@ module Mbeditor
|
|
|
12
20
|
# missed it entirely and served the environment's log file anywhere.
|
|
13
21
|
before_action :ensure_allowed_environment!
|
|
14
22
|
before_action :run_authentication
|
|
23
|
+
# Registered after the two gates above so it times the action itself, not
|
|
24
|
+
# the environment check or the host app's authenticate_with proc.
|
|
25
|
+
around_action :audit_request
|
|
15
26
|
|
|
16
27
|
private
|
|
17
28
|
|
|
@@ -32,6 +43,18 @@ module Mbeditor
|
|
|
32
43
|
render plain: 'Not found', status: :not_found unless allowed.include?(Rails.env.to_sym)
|
|
33
44
|
end
|
|
34
45
|
|
|
46
|
+
def audit_request
|
|
47
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
48
|
+
yield
|
|
49
|
+
ensure
|
|
50
|
+
# One record per request: `action` is a fixed method name of this
|
|
51
|
+
# controller, never request data; `ms` wall time; `status` the HTTP code.
|
|
52
|
+
AuditLog.record(:request,
|
|
53
|
+
action: action_name.to_sym,
|
|
54
|
+
ms: ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started) * 1000).round,
|
|
55
|
+
status: response.status)
|
|
56
|
+
end
|
|
57
|
+
|
|
35
58
|
def workspace_root
|
|
36
59
|
WorkspaceRootResolver.call
|
|
37
60
|
end
|