termbeam 1.4.0 → 1.7.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.
- package/README.md +2 -29
- package/package.json +1 -1
- package/public/css/themes.css +217 -0
- package/public/index.html +42 -242
- package/public/js/keybar.js +180 -0
- package/public/js/search.js +95 -0
- package/public/js/shared.js +39 -0
- package/public/js/terminal-themes.js +291 -0
- package/public/js/themes.js +54 -0
- package/public/terminal.html +713 -855
- package/src/git.js +125 -0
- package/src/routes.js +6 -2
- package/src/sessions.js +101 -4
- package/src/websocket.js +31 -4
package/public/terminal.html
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
/>
|
|
16
16
|
<link rel="manifest" href="/manifest.json" />
|
|
17
17
|
<link rel="apple-touch-icon" href="/icons/icon-192.png" />
|
|
18
|
+
<link rel="stylesheet" href="/css/themes.css" />
|
|
18
19
|
<title>TermBeam — Terminal</title>
|
|
19
20
|
<link
|
|
20
21
|
rel="stylesheet"
|
|
@@ -22,257 +23,23 @@
|
|
|
22
23
|
/>
|
|
23
24
|
<style>
|
|
24
25
|
:root {
|
|
25
|
-
--bg: #1e1e1e;
|
|
26
|
-
--surface: #252526;
|
|
27
|
-
--border: #3c3c3c;
|
|
28
|
-
--border-subtle: #474747;
|
|
29
|
-
--text: #d4d4d4;
|
|
30
|
-
--text-secondary: #858585;
|
|
31
|
-
--text-dim: #6e6e6e;
|
|
32
|
-
--text-muted: #555555;
|
|
33
|
-
--accent: #0078d4;
|
|
34
|
-
--accent-hover: #1a8ae8;
|
|
35
|
-
--accent-active: #005a9e;
|
|
36
|
-
--danger: #f14c4c;
|
|
37
|
-
--danger-hover: #d73a3a;
|
|
38
|
-
--success: #89d185;
|
|
39
26
|
--key-bg: #4a4a4c;
|
|
40
27
|
--key-border: #5a5a5c;
|
|
41
28
|
--key-shadow: rgba(0, 0, 0, 0.5);
|
|
42
29
|
--key-special-bg: #333335;
|
|
43
30
|
--overlay-bg: rgba(0, 0, 0, 0.85);
|
|
44
31
|
}
|
|
45
|
-
[data-theme='light'] {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
--accent-active: #005a9e;
|
|
57
|
-
--danger: #e51400;
|
|
58
|
-
--danger-hover: #c20000;
|
|
59
|
-
--success: #16825d;
|
|
60
|
-
--key-bg: #ffffff;
|
|
61
|
-
--key-border: #b5b5b5;
|
|
62
|
-
--key-shadow: rgba(0, 0, 0, 0.12);
|
|
63
|
-
--key-special-bg: #adb5bd;
|
|
64
|
-
--overlay-bg: rgba(0, 0, 0, 0.5);
|
|
65
|
-
}
|
|
66
|
-
[data-theme='monokai'] {
|
|
67
|
-
--bg: #272822;
|
|
68
|
-
--surface: #1e1f1c;
|
|
69
|
-
--border: #49483e;
|
|
70
|
-
--border-subtle: #5c5c4f;
|
|
71
|
-
--text: #f8f8f2;
|
|
72
|
-
--text-secondary: #a59f85;
|
|
73
|
-
--text-dim: #75715e;
|
|
74
|
-
--text-muted: #5a5854;
|
|
75
|
-
--accent: #a6e22e;
|
|
76
|
-
--accent-hover: #b8f53c;
|
|
77
|
-
--accent-active: #8acc16;
|
|
78
|
-
--danger: #f92672;
|
|
79
|
-
--danger-hover: #e0155d;
|
|
80
|
-
--success: #a6e22e;
|
|
81
|
-
--key-bg: #49483e;
|
|
82
|
-
--key-border: #5c5c4f;
|
|
83
|
-
--key-shadow: rgba(0, 0, 0, 0.4);
|
|
84
|
-
--key-special-bg: #3e3d32;
|
|
85
|
-
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
86
|
-
}
|
|
87
|
-
[data-theme='solarized-dark'] {
|
|
88
|
-
--bg: #002b36;
|
|
89
|
-
--surface: #073642;
|
|
90
|
-
--border: #586e75;
|
|
91
|
-
--border-subtle: #657b83;
|
|
92
|
-
--text: #839496;
|
|
93
|
-
--text-secondary: #657b83;
|
|
94
|
-
--text-dim: #586e75;
|
|
95
|
-
--text-muted: #4a5a62;
|
|
96
|
-
--accent: #268bd2;
|
|
97
|
-
--accent-hover: #379ce3;
|
|
98
|
-
--accent-active: #1a7abf;
|
|
99
|
-
--danger: #dc322f;
|
|
100
|
-
--danger-hover: #c8221f;
|
|
101
|
-
--success: #859900;
|
|
102
|
-
--key-bg: #073642;
|
|
103
|
-
--key-border: #586e75;
|
|
104
|
-
--key-shadow: rgba(0, 0, 0, 0.3);
|
|
105
|
-
--key-special-bg: #002b36;
|
|
106
|
-
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
107
|
-
}
|
|
108
|
-
[data-theme='solarized-light'] {
|
|
109
|
-
--bg: #fdf6e3;
|
|
110
|
-
--surface: #eee8d5;
|
|
111
|
-
--border: #93a1a1;
|
|
112
|
-
--border-subtle: #839496;
|
|
113
|
-
--text: #657b83;
|
|
114
|
-
--text-secondary: #93a1a1;
|
|
115
|
-
--text-dim: #a0a0a0;
|
|
116
|
-
--text-muted: #b0b0b0;
|
|
117
|
-
--accent: #268bd2;
|
|
118
|
-
--accent-hover: #379ce3;
|
|
119
|
-
--accent-active: #1a7abf;
|
|
120
|
-
--danger: #dc322f;
|
|
121
|
-
--danger-hover: #c8221f;
|
|
122
|
-
--success: #859900;
|
|
123
|
-
--key-bg: #ffffff;
|
|
124
|
-
--key-border: #b5b5b5;
|
|
125
|
-
--key-shadow: rgba(0, 0, 0, 0.12);
|
|
126
|
-
--key-special-bg: #adb5bd;
|
|
127
|
-
--overlay-bg: rgba(0, 0, 0, 0.4);
|
|
128
|
-
}
|
|
129
|
-
[data-theme='nord'] {
|
|
130
|
-
--bg: #2e3440;
|
|
131
|
-
--surface: #3b4252;
|
|
132
|
-
--border: #434c5e;
|
|
133
|
-
--border-subtle: #4c566a;
|
|
134
|
-
--text: #d8dee9;
|
|
135
|
-
--text-secondary: #b0bac9;
|
|
136
|
-
--text-dim: #7b88a1;
|
|
137
|
-
--text-muted: #5c6a85;
|
|
138
|
-
--accent: #88c0d0;
|
|
139
|
-
--accent-hover: #9fd4e4;
|
|
140
|
-
--accent-active: #6aafbf;
|
|
141
|
-
--danger: #bf616a;
|
|
142
|
-
--danger-hover: #a84d57;
|
|
143
|
-
--success: #a3be8c;
|
|
144
|
-
--key-bg: #434c5e;
|
|
145
|
-
--key-border: #4c566a;
|
|
146
|
-
--key-shadow: rgba(0, 0, 0, 0.3);
|
|
147
|
-
--key-special-bg: #3b4252;
|
|
148
|
-
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
149
|
-
}
|
|
150
|
-
[data-theme='dracula'] {
|
|
151
|
-
--bg: #282a36;
|
|
152
|
-
--surface: #343746;
|
|
153
|
-
--border: #44475a;
|
|
154
|
-
--border-subtle: #525568;
|
|
155
|
-
--text: #f8f8f2;
|
|
156
|
-
--text-secondary: #c1c4d2;
|
|
157
|
-
--text-dim: #8e92a4;
|
|
158
|
-
--text-muted: #6272a4;
|
|
159
|
-
--accent: #bd93f9;
|
|
160
|
-
--accent-hover: #d0b0ff;
|
|
161
|
-
--accent-active: #a77de7;
|
|
162
|
-
--danger: #ff5555;
|
|
163
|
-
--danger-hover: #e03d3d;
|
|
164
|
-
--success: #50fa7b;
|
|
165
|
-
--key-bg: #44475a;
|
|
166
|
-
--key-border: #525568;
|
|
167
|
-
--key-shadow: rgba(0, 0, 0, 0.4);
|
|
168
|
-
--key-special-bg: #343746;
|
|
169
|
-
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
170
|
-
}
|
|
171
|
-
[data-theme='github-dark'] {
|
|
172
|
-
--bg: #0d1117;
|
|
173
|
-
--surface: #161b22;
|
|
174
|
-
--border: #30363d;
|
|
175
|
-
--border-subtle: #3d444d;
|
|
176
|
-
--text: #c9d1d9;
|
|
177
|
-
--text-secondary: #8b949e;
|
|
178
|
-
--text-dim: #6e7681;
|
|
179
|
-
--text-muted: #484f58;
|
|
180
|
-
--accent: #58a6ff;
|
|
181
|
-
--accent-hover: #79b8ff;
|
|
182
|
-
--accent-active: #388bfd;
|
|
183
|
-
--danger: #f85149;
|
|
184
|
-
--danger-hover: #da3633;
|
|
185
|
-
--success: #3fb950;
|
|
186
|
-
--key-bg: #161b22;
|
|
187
|
-
--key-border: #30363d;
|
|
188
|
-
--key-shadow: rgba(0, 0, 0, 0.4);
|
|
189
|
-
--key-special-bg: #0d1117;
|
|
190
|
-
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
191
|
-
}
|
|
192
|
-
[data-theme='one-dark'] {
|
|
193
|
-
--bg: #282c34;
|
|
194
|
-
--surface: #21252b;
|
|
195
|
-
--border: #3e4452;
|
|
196
|
-
--border-subtle: #4b5263;
|
|
197
|
-
--text: #abb2bf;
|
|
198
|
-
--text-secondary: #7f848e;
|
|
199
|
-
--text-dim: #5c6370;
|
|
200
|
-
--text-muted: #4b5263;
|
|
201
|
-
--accent: #61afef;
|
|
202
|
-
--accent-hover: #7dc0ff;
|
|
203
|
-
--accent-active: #4d9ede;
|
|
204
|
-
--danger: #e06c75;
|
|
205
|
-
--danger-hover: #c95c67;
|
|
206
|
-
--success: #98c379;
|
|
207
|
-
--key-bg: #3e4452;
|
|
208
|
-
--key-border: #4b5263;
|
|
209
|
-
--key-shadow: rgba(0, 0, 0, 0.3);
|
|
210
|
-
--key-special-bg: #21252b;
|
|
211
|
-
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
212
|
-
}
|
|
213
|
-
[data-theme='catppuccin'] {
|
|
214
|
-
--bg: #1e1e2e;
|
|
215
|
-
--surface: #313244;
|
|
216
|
-
--border: #45475a;
|
|
217
|
-
--border-subtle: #585b70;
|
|
218
|
-
--text: #cdd6f4;
|
|
219
|
-
--text-secondary: #a6adc8;
|
|
220
|
-
--text-dim: #7f849c;
|
|
221
|
-
--text-muted: #585b70;
|
|
222
|
-
--accent: #89b4fa;
|
|
223
|
-
--accent-hover: #b4d0ff;
|
|
224
|
-
--accent-active: #5c9de3;
|
|
225
|
-
--danger: #f38ba8;
|
|
226
|
-
--danger-hover: #eb7c9d;
|
|
227
|
-
--success: #a6e3a1;
|
|
228
|
-
--key-bg: #45475a;
|
|
229
|
-
--key-border: #585b70;
|
|
230
|
-
--key-shadow: rgba(0, 0, 0, 0.3);
|
|
231
|
-
--key-special-bg: #313244;
|
|
232
|
-
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
233
|
-
}
|
|
234
|
-
[data-theme='gruvbox'] {
|
|
235
|
-
--bg: #282828;
|
|
236
|
-
--surface: #3c3836;
|
|
237
|
-
--border: #504945;
|
|
238
|
-
--border-subtle: #665c54;
|
|
239
|
-
--text: #ebdbb2;
|
|
240
|
-
--text-secondary: #d5c4a1;
|
|
241
|
-
--text-dim: #a89984;
|
|
242
|
-
--text-muted: #7c6f64;
|
|
243
|
-
--accent: #83a598;
|
|
244
|
-
--accent-hover: #9dbfb4;
|
|
245
|
-
--accent-active: #6a8f8a;
|
|
246
|
-
--danger: #fb4934;
|
|
247
|
-
--danger-hover: #e33826;
|
|
248
|
-
--success: #b8bb26;
|
|
249
|
-
--key-bg: #504945;
|
|
250
|
-
--key-border: #665c54;
|
|
251
|
-
--key-shadow: rgba(0, 0, 0, 0.4);
|
|
252
|
-
--key-special-bg: #3c3836;
|
|
253
|
-
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
254
|
-
}
|
|
255
|
-
[data-theme='night-owl'] {
|
|
256
|
-
--bg: #011627;
|
|
257
|
-
--surface: #0d2a45;
|
|
258
|
-
--border: #1d3b53;
|
|
259
|
-
--border-subtle: #264863;
|
|
260
|
-
--text: #d6deeb;
|
|
261
|
-
--text-secondary: #8badc1;
|
|
262
|
-
--text-dim: #5f7e97;
|
|
263
|
-
--text-muted: #3f5f7d;
|
|
264
|
-
--accent: #7fdbca;
|
|
265
|
-
--accent-hover: #9ff0e0;
|
|
266
|
-
--accent-active: #62c5b5;
|
|
267
|
-
--danger: #ef5350;
|
|
268
|
-
--danger-hover: #d83130;
|
|
269
|
-
--success: #addb67;
|
|
270
|
-
--key-bg: #1d3b53;
|
|
271
|
-
--key-border: #264863;
|
|
272
|
-
--key-shadow: rgba(0, 0, 0, 0.4);
|
|
273
|
-
--key-special-bg: #0d2a45;
|
|
274
|
-
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
275
|
-
}
|
|
32
|
+
[data-theme='light'] { --key-bg: #ffffff; --key-border: #b5b5b5; --key-shadow: rgba(0, 0, 0, 0.12); --key-special-bg: #adb5bd; --overlay-bg: rgba(0, 0, 0, 0.5); }
|
|
33
|
+
[data-theme='monokai'] { --key-bg: #49483e; --key-border: #5c5c4f; --key-shadow: rgba(0, 0, 0, 0.4); --key-special-bg: #3e3d32; }
|
|
34
|
+
[data-theme='solarized-dark'] { --key-bg: #073642; --key-border: #586e75; --key-shadow: rgba(0, 0, 0, 0.3); --key-special-bg: #002b36; }
|
|
35
|
+
[data-theme='solarized-light'] { --key-bg: #ffffff; --key-border: #b5b5b5; --key-shadow: rgba(0, 0, 0, 0.12); --key-special-bg: #adb5bd; }
|
|
36
|
+
[data-theme='nord'] { --key-bg: #434c5e; --key-border: #4c566a; --key-shadow: rgba(0, 0, 0, 0.3); --key-special-bg: #3b4252; }
|
|
37
|
+
[data-theme='dracula'] { --key-bg: #44475a; --key-border: #525568; --key-shadow: rgba(0, 0, 0, 0.4); --key-special-bg: #343746; }
|
|
38
|
+
[data-theme='github-dark'] { --key-bg: #161b22; --key-border: #30363d; --key-shadow: rgba(0, 0, 0, 0.4); --key-special-bg: #0d1117; }
|
|
39
|
+
[data-theme='one-dark'] { --key-bg: #3e4452; --key-border: #4b5263; --key-shadow: rgba(0, 0, 0, 0.3); --key-special-bg: #21252b; }
|
|
40
|
+
[data-theme='catppuccin'] { --key-bg: #45475a; --key-border: #585b70; --key-shadow: rgba(0, 0, 0, 0.3); --key-special-bg: #313244; }
|
|
41
|
+
[data-theme='gruvbox'] { --key-bg: #504945; --key-border: #665c54; --key-shadow: rgba(0, 0, 0, 0.4); --key-special-bg: #3c3836; }
|
|
42
|
+
[data-theme='night-owl'] { --key-bg: #1d3b53; --key-border: #264863; --key-shadow: rgba(0, 0, 0, 0.4); --key-special-bg: #0d2a45; }
|
|
276
43
|
@font-face {
|
|
277
44
|
font-family: 'NerdFont';
|
|
278
45
|
src: url('https://cdn.jsdelivr.net/gh/ryanoasis/nerd-fonts@latest/patched-fonts/JetBrainsMono/Ligatures/Regular/JetBrainsMonoNerdFont-Regular.ttf')
|
|
@@ -611,7 +378,7 @@
|
|
|
611
378
|
}
|
|
612
379
|
.theme-wrap {
|
|
613
380
|
position: relative;
|
|
614
|
-
display:
|
|
381
|
+
display: none;
|
|
615
382
|
align-items: center;
|
|
616
383
|
}
|
|
617
384
|
.theme-picker {
|
|
@@ -662,7 +429,7 @@
|
|
|
662
429
|
height: 30px;
|
|
663
430
|
border-radius: 8px;
|
|
664
431
|
cursor: pointer;
|
|
665
|
-
display:
|
|
432
|
+
display: none;
|
|
666
433
|
align-items: center;
|
|
667
434
|
justify-content: center;
|
|
668
435
|
gap: 4px;
|
|
@@ -685,6 +452,75 @@
|
|
|
685
452
|
transform: scale(0.9);
|
|
686
453
|
}
|
|
687
454
|
|
|
455
|
+
/* ===== Notification Toggle ===== */
|
|
456
|
+
.notify-btn.active {
|
|
457
|
+
color: var(--accent) !important;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/* ===== Search Bar ===== */
|
|
461
|
+
.search-bar {
|
|
462
|
+
display: none;
|
|
463
|
+
position: absolute;
|
|
464
|
+
top: 4px;
|
|
465
|
+
right: 12px;
|
|
466
|
+
z-index: 100;
|
|
467
|
+
background: var(--surface);
|
|
468
|
+
border: 1px solid var(--border);
|
|
469
|
+
border-radius: 8px;
|
|
470
|
+
padding: 4px 6px;
|
|
471
|
+
gap: 4px;
|
|
472
|
+
align-items: center;
|
|
473
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
474
|
+
font-size: 13px;
|
|
475
|
+
color: var(--text);
|
|
476
|
+
}
|
|
477
|
+
.search-bar.visible {
|
|
478
|
+
display: flex;
|
|
479
|
+
}
|
|
480
|
+
.search-bar input {
|
|
481
|
+
background: var(--bg);
|
|
482
|
+
border: 1px solid var(--border);
|
|
483
|
+
border-radius: 4px;
|
|
484
|
+
color: var(--text);
|
|
485
|
+
padding: 3px 6px;
|
|
486
|
+
font-size: 13px;
|
|
487
|
+
font-family: inherit;
|
|
488
|
+
width: 160px;
|
|
489
|
+
outline: none;
|
|
490
|
+
}
|
|
491
|
+
.search-bar input:focus {
|
|
492
|
+
border-color: var(--accent);
|
|
493
|
+
}
|
|
494
|
+
.search-bar .search-count {
|
|
495
|
+
color: var(--text-secondary);
|
|
496
|
+
font-size: 11px;
|
|
497
|
+
min-width: 40px;
|
|
498
|
+
text-align: center;
|
|
499
|
+
white-space: nowrap;
|
|
500
|
+
}
|
|
501
|
+
.search-bar button {
|
|
502
|
+
background: none;
|
|
503
|
+
border: 1px solid transparent;
|
|
504
|
+
color: var(--text-dim);
|
|
505
|
+
width: 24px;
|
|
506
|
+
height: 24px;
|
|
507
|
+
border-radius: 4px;
|
|
508
|
+
cursor: pointer;
|
|
509
|
+
display: flex;
|
|
510
|
+
align-items: center;
|
|
511
|
+
justify-content: center;
|
|
512
|
+
font-size: 13px;
|
|
513
|
+
padding: 0;
|
|
514
|
+
}
|
|
515
|
+
.search-bar button:hover {
|
|
516
|
+
background: var(--border);
|
|
517
|
+
color: var(--text);
|
|
518
|
+
}
|
|
519
|
+
.search-bar button.active {
|
|
520
|
+
color: var(--accent);
|
|
521
|
+
border-color: var(--accent);
|
|
522
|
+
}
|
|
523
|
+
|
|
688
524
|
/* ===== Terminals Wrapper ===== */
|
|
689
525
|
#terminals-wrapper {
|
|
690
526
|
position: absolute;
|
|
@@ -1661,6 +1497,30 @@
|
|
|
1661
1497
|
white-space: normal;
|
|
1662
1498
|
}
|
|
1663
1499
|
|
|
1500
|
+
.side-panel-card-git {
|
|
1501
|
+
display: flex;
|
|
1502
|
+
flex-wrap: wrap;
|
|
1503
|
+
gap: 3px 6px;
|
|
1504
|
+
padding: 0 12px 4px;
|
|
1505
|
+
font-size: 10px;
|
|
1506
|
+
color: var(--text-secondary);
|
|
1507
|
+
align-items: center;
|
|
1508
|
+
}
|
|
1509
|
+
.side-panel-card-git .git-badge {
|
|
1510
|
+
display: inline-flex;
|
|
1511
|
+
align-items: center;
|
|
1512
|
+
gap: 3px;
|
|
1513
|
+
background: var(--surface);
|
|
1514
|
+
padding: 1px 6px;
|
|
1515
|
+
border-radius: 3px;
|
|
1516
|
+
}
|
|
1517
|
+
.side-panel-card-git .git-status-clean {
|
|
1518
|
+
color: var(--success);
|
|
1519
|
+
}
|
|
1520
|
+
.side-panel-card-git .git-status-dirty {
|
|
1521
|
+
color: var(--warning, #fbbf24);
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1664
1524
|
@media (max-width: 640px) {
|
|
1665
1525
|
#panel-toggle {
|
|
1666
1526
|
display: flex;
|
|
@@ -1668,17 +1528,12 @@
|
|
|
1668
1528
|
#tab-list {
|
|
1669
1529
|
display: none;
|
|
1670
1530
|
}
|
|
1671
|
-
|
|
1672
|
-
display: none;
|
|
1673
|
-
}
|
|
1674
|
-
#version-text {
|
|
1675
|
-
display: none;
|
|
1676
|
-
}
|
|
1531
|
+
|
|
1677
1532
|
#back-btn {
|
|
1678
1533
|
display: none;
|
|
1679
1534
|
}
|
|
1680
1535
|
#theme-wrap {
|
|
1681
|
-
display:
|
|
1536
|
+
display: none;
|
|
1682
1537
|
}
|
|
1683
1538
|
#stop-btn {
|
|
1684
1539
|
padding: 0 8px;
|
|
@@ -1695,6 +1550,195 @@
|
|
|
1695
1550
|
width: auto;
|
|
1696
1551
|
}
|
|
1697
1552
|
}
|
|
1553
|
+
|
|
1554
|
+
/* ===== Command Palette / Tool Panel ===== */
|
|
1555
|
+
.palette-backdrop {
|
|
1556
|
+
position: fixed;
|
|
1557
|
+
inset: 0;
|
|
1558
|
+
background: rgba(0, 0, 0, 0.4);
|
|
1559
|
+
z-index: 250;
|
|
1560
|
+
opacity: 0;
|
|
1561
|
+
pointer-events: none;
|
|
1562
|
+
transition: opacity 0.3s;
|
|
1563
|
+
}
|
|
1564
|
+
.palette-backdrop.open {
|
|
1565
|
+
opacity: 1;
|
|
1566
|
+
pointer-events: auto;
|
|
1567
|
+
}
|
|
1568
|
+
.palette-panel {
|
|
1569
|
+
position: fixed;
|
|
1570
|
+
top: 0;
|
|
1571
|
+
right: 0;
|
|
1572
|
+
width: 280px;
|
|
1573
|
+
max-width: 85vw;
|
|
1574
|
+
height: 100%;
|
|
1575
|
+
background: var(--surface);
|
|
1576
|
+
border-left: 1px solid var(--border);
|
|
1577
|
+
z-index: 260;
|
|
1578
|
+
transform: translateX(100%);
|
|
1579
|
+
transition: transform 0.3s ease;
|
|
1580
|
+
display: flex;
|
|
1581
|
+
flex-direction: column;
|
|
1582
|
+
overflow-y: auto;
|
|
1583
|
+
-webkit-overflow-scrolling: touch;
|
|
1584
|
+
}
|
|
1585
|
+
.palette-panel.open {
|
|
1586
|
+
transform: translateX(0);
|
|
1587
|
+
}
|
|
1588
|
+
.palette-header {
|
|
1589
|
+
display: flex;
|
|
1590
|
+
align-items: center;
|
|
1591
|
+
justify-content: space-between;
|
|
1592
|
+
padding: 14px 16px;
|
|
1593
|
+
border-bottom: 1px solid var(--border);
|
|
1594
|
+
font-weight: 600;
|
|
1595
|
+
font-size: 15px;
|
|
1596
|
+
color: var(--text);
|
|
1597
|
+
}
|
|
1598
|
+
.palette-close {
|
|
1599
|
+
background: none;
|
|
1600
|
+
border: none;
|
|
1601
|
+
color: var(--text-secondary);
|
|
1602
|
+
font-size: 18px;
|
|
1603
|
+
cursor: pointer;
|
|
1604
|
+
padding: 4px 8px;
|
|
1605
|
+
border-radius: 6px;
|
|
1606
|
+
}
|
|
1607
|
+
.palette-close:hover {
|
|
1608
|
+
background: var(--hover-bg, rgba(255, 255, 255, 0.08));
|
|
1609
|
+
color: var(--text);
|
|
1610
|
+
}
|
|
1611
|
+
.palette-body {
|
|
1612
|
+
padding: 8px 0;
|
|
1613
|
+
flex: 1;
|
|
1614
|
+
}
|
|
1615
|
+
.palette-category {
|
|
1616
|
+
padding: 8px 16px 4px;
|
|
1617
|
+
font-size: 11px;
|
|
1618
|
+
font-weight: 600;
|
|
1619
|
+
text-transform: uppercase;
|
|
1620
|
+
letter-spacing: 0.5px;
|
|
1621
|
+
color: var(--text-muted, var(--text-secondary));
|
|
1622
|
+
}
|
|
1623
|
+
.palette-action {
|
|
1624
|
+
display: flex;
|
|
1625
|
+
align-items: center;
|
|
1626
|
+
gap: 10px;
|
|
1627
|
+
width: 100%;
|
|
1628
|
+
padding: 10px 16px;
|
|
1629
|
+
background: none;
|
|
1630
|
+
border: none;
|
|
1631
|
+
color: var(--text);
|
|
1632
|
+
font-size: 13px;
|
|
1633
|
+
cursor: pointer;
|
|
1634
|
+
text-align: left;
|
|
1635
|
+
transition: background 0.15s;
|
|
1636
|
+
}
|
|
1637
|
+
.palette-action:hover {
|
|
1638
|
+
background: rgba(255, 255, 255, 0.06);
|
|
1639
|
+
}
|
|
1640
|
+
.palette-action:active {
|
|
1641
|
+
background: rgba(255, 255, 255, 0.1);
|
|
1642
|
+
}
|
|
1643
|
+
[data-theme='light'] .palette-action:hover,
|
|
1644
|
+
[data-theme='solarized-light'] .palette-action:hover {
|
|
1645
|
+
background: rgba(0, 0, 0, 0.06);
|
|
1646
|
+
}
|
|
1647
|
+
[data-theme='light'] .palette-action:active,
|
|
1648
|
+
[data-theme='solarized-light'] .palette-action:active {
|
|
1649
|
+
background: rgba(0, 0, 0, 0.1);
|
|
1650
|
+
}
|
|
1651
|
+
.palette-action-icon {
|
|
1652
|
+
width: 20px;
|
|
1653
|
+
height: 20px;
|
|
1654
|
+
display: flex;
|
|
1655
|
+
align-items: center;
|
|
1656
|
+
justify-content: center;
|
|
1657
|
+
flex-shrink: 0;
|
|
1658
|
+
color: var(--text-secondary);
|
|
1659
|
+
}
|
|
1660
|
+
.palette-action-icon svg {
|
|
1661
|
+
width: 16px;
|
|
1662
|
+
}
|
|
1663
|
+
/* ===== Theme Sub-Panel ===== */
|
|
1664
|
+
.theme-subpanel {
|
|
1665
|
+
display: none;
|
|
1666
|
+
position: fixed;
|
|
1667
|
+
top: 50%;
|
|
1668
|
+
left: 50%;
|
|
1669
|
+
transform: translate(-50%, -50%);
|
|
1670
|
+
width: 240px;
|
|
1671
|
+
max-height: 70vh;
|
|
1672
|
+
background: var(--surface);
|
|
1673
|
+
border: 1px solid var(--border);
|
|
1674
|
+
border-radius: 12px;
|
|
1675
|
+
z-index: 270;
|
|
1676
|
+
overflow-y: auto;
|
|
1677
|
+
-webkit-overflow-scrolling: touch;
|
|
1678
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
1679
|
+
}
|
|
1680
|
+
.theme-subpanel.open {
|
|
1681
|
+
display: block;
|
|
1682
|
+
}
|
|
1683
|
+
.theme-subpanel-header {
|
|
1684
|
+
display: flex;
|
|
1685
|
+
align-items: center;
|
|
1686
|
+
justify-content: space-between;
|
|
1687
|
+
padding: 12px 14px;
|
|
1688
|
+
border-bottom: 1px solid var(--border);
|
|
1689
|
+
font-weight: 600;
|
|
1690
|
+
font-size: 13px;
|
|
1691
|
+
color: var(--text);
|
|
1692
|
+
}
|
|
1693
|
+
.theme-subpanel-close {
|
|
1694
|
+
background: none;
|
|
1695
|
+
border: none;
|
|
1696
|
+
color: var(--text-secondary);
|
|
1697
|
+
font-size: 16px;
|
|
1698
|
+
cursor: pointer;
|
|
1699
|
+
padding: 2px 6px;
|
|
1700
|
+
border-radius: 6px;
|
|
1701
|
+
}
|
|
1702
|
+
.theme-subpanel-close:hover {
|
|
1703
|
+
background: var(--hover-bg, rgba(255, 255, 255, 0.08));
|
|
1704
|
+
color: var(--text);
|
|
1705
|
+
}
|
|
1706
|
+
.theme-subpanel-list {
|
|
1707
|
+
padding: 6px 0;
|
|
1708
|
+
}
|
|
1709
|
+
.theme-subpanel-item {
|
|
1710
|
+
display: flex;
|
|
1711
|
+
align-items: center;
|
|
1712
|
+
gap: 10px;
|
|
1713
|
+
width: 100%;
|
|
1714
|
+
padding: 9px 14px;
|
|
1715
|
+
background: none;
|
|
1716
|
+
border: none;
|
|
1717
|
+
color: var(--text);
|
|
1718
|
+
font-size: 13px;
|
|
1719
|
+
cursor: pointer;
|
|
1720
|
+
text-align: left;
|
|
1721
|
+
transition: background 0.15s;
|
|
1722
|
+
}
|
|
1723
|
+
.theme-subpanel-item:hover {
|
|
1724
|
+
background: rgba(255, 255, 255, 0.06);
|
|
1725
|
+
}
|
|
1726
|
+
[data-theme='light'] .theme-subpanel-item:hover,
|
|
1727
|
+
[data-theme='solarized-light'] .theme-subpanel-item:hover {
|
|
1728
|
+
background: rgba(0, 0, 0, 0.06);
|
|
1729
|
+
}
|
|
1730
|
+
.theme-subpanel-item.active {
|
|
1731
|
+
color: var(--accent);
|
|
1732
|
+
}
|
|
1733
|
+
.theme-subpanel-swatch {
|
|
1734
|
+
width: 14px;
|
|
1735
|
+
height: 14px;
|
|
1736
|
+
border-radius: 50%;
|
|
1737
|
+
flex-shrink: 0;
|
|
1738
|
+
border: 1px solid rgba(128, 128, 128, 0.3);
|
|
1739
|
+
}
|
|
1740
|
+
height: 16px;
|
|
1741
|
+
}
|
|
1698
1742
|
</style>
|
|
1699
1743
|
</head>
|
|
1700
1744
|
<body>
|
|
@@ -1792,7 +1836,6 @@
|
|
|
1792
1836
|
</div>
|
|
1793
1837
|
<div id="tab-list"></div>
|
|
1794
1838
|
<div class="right">
|
|
1795
|
-
<span id="version-text" style="font-size: 11px; color: var(--text-muted)"></span>
|
|
1796
1839
|
<button class="tab-bar-btn" id="tab-new-btn" title="New session">
|
|
1797
1840
|
<svg
|
|
1798
1841
|
width="14"
|
|
@@ -1807,67 +1850,6 @@
|
|
|
1807
1850
|
<line x1="5" y1="12" x2="19" y2="12" /></svg
|
|
1808
1851
|
><span class="new-btn-label">New</span>
|
|
1809
1852
|
</button>
|
|
1810
|
-
<button class="tab-bar-btn" id="split-toggle" title="Split view">
|
|
1811
|
-
<svg
|
|
1812
|
-
width="16"
|
|
1813
|
-
height="16"
|
|
1814
|
-
viewBox="0 0 24 24"
|
|
1815
|
-
fill="none"
|
|
1816
|
-
stroke="currentColor"
|
|
1817
|
-
stroke-width="2"
|
|
1818
|
-
stroke-linecap="round"
|
|
1819
|
-
stroke-linejoin="round"
|
|
1820
|
-
>
|
|
1821
|
-
<rect x="3" y="3" width="18" height="18" rx="2" />
|
|
1822
|
-
<line x1="12" y1="3" x2="12" y2="21" />
|
|
1823
|
-
</svg>
|
|
1824
|
-
</button>
|
|
1825
|
-
<div class="bar-group">
|
|
1826
|
-
<button class="bar-btn" id="zoom-out" title="Decrease font size">−</button>
|
|
1827
|
-
<button class="bar-btn" id="zoom-in" title="Increase font size">+</button>
|
|
1828
|
-
</div>
|
|
1829
|
-
<button
|
|
1830
|
-
class="bar-btn"
|
|
1831
|
-
id="preview-btn"
|
|
1832
|
-
title="Preview local port"
|
|
1833
|
-
onclick="openPreviewModal()"
|
|
1834
|
-
>
|
|
1835
|
-
🌐
|
|
1836
|
-
</button>
|
|
1837
|
-
<button class="bar-btn" id="share-btn" title="Share link">
|
|
1838
|
-
<svg
|
|
1839
|
-
width="16"
|
|
1840
|
-
height="16"
|
|
1841
|
-
viewBox="0 0 24 24"
|
|
1842
|
-
fill="none"
|
|
1843
|
-
stroke="currentColor"
|
|
1844
|
-
stroke-width="2"
|
|
1845
|
-
stroke-linecap="round"
|
|
1846
|
-
stroke-linejoin="round"
|
|
1847
|
-
>
|
|
1848
|
-
<circle cx="18" cy="5" r="3" />
|
|
1849
|
-
<circle cx="6" cy="12" r="3" />
|
|
1850
|
-
<circle cx="18" cy="19" r="3" />
|
|
1851
|
-
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49" />
|
|
1852
|
-
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49" />
|
|
1853
|
-
</svg>
|
|
1854
|
-
</button>
|
|
1855
|
-
<button class="bar-btn" id="refresh-btn" title="Refresh app">
|
|
1856
|
-
<svg
|
|
1857
|
-
width="16"
|
|
1858
|
-
height="16"
|
|
1859
|
-
viewBox="0 0 24 24"
|
|
1860
|
-
fill="none"
|
|
1861
|
-
stroke="currentColor"
|
|
1862
|
-
stroke-width="2"
|
|
1863
|
-
stroke-linecap="round"
|
|
1864
|
-
stroke-linejoin="round"
|
|
1865
|
-
>
|
|
1866
|
-
<polyline points="23 4 23 10 17 10" />
|
|
1867
|
-
<polyline points="1 20 1 14 7 14" />
|
|
1868
|
-
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" />
|
|
1869
|
-
</svg>
|
|
1870
|
-
</button>
|
|
1871
1853
|
<div class="theme-wrap" id="theme-wrap">
|
|
1872
1854
|
<button class="bar-btn" id="theme-toggle" title="Switch theme">
|
|
1873
1855
|
<svg
|
|
@@ -1928,6 +1910,23 @@
|
|
|
1928
1910
|
</div>
|
|
1929
1911
|
</div>
|
|
1930
1912
|
</div>
|
|
1913
|
+
<button class="bar-btn" id="palette-trigger" title="Tools (Ctrl+K)">
|
|
1914
|
+
<svg
|
|
1915
|
+
width="16"
|
|
1916
|
+
height="16"
|
|
1917
|
+
viewBox="0 0 24 24"
|
|
1918
|
+
fill="none"
|
|
1919
|
+
stroke="currentColor"
|
|
1920
|
+
stroke-width="2"
|
|
1921
|
+
stroke-linecap="round"
|
|
1922
|
+
stroke-linejoin="round"
|
|
1923
|
+
>
|
|
1924
|
+
<rect x="3" y="3" width="7" height="7" rx="1" />
|
|
1925
|
+
<rect x="14" y="3" width="7" height="7" rx="1" />
|
|
1926
|
+
<rect x="3" y="14" width="7" height="7" rx="1" />
|
|
1927
|
+
<rect x="14" y="14" width="7" height="7" rx="1" />
|
|
1928
|
+
</svg>
|
|
1929
|
+
</button>
|
|
1931
1930
|
<button id="stop-btn" title="Stop session">
|
|
1932
1931
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
|
1933
1932
|
<rect x="6" y="6" width="12" height="12" rx="2" /></svg
|
|
@@ -1946,7 +1945,16 @@
|
|
|
1946
1945
|
</div>
|
|
1947
1946
|
|
|
1948
1947
|
<!-- Terminals Wrapper (panes created dynamically) -->
|
|
1949
|
-
<div id="terminals-wrapper"
|
|
1948
|
+
<div id="terminals-wrapper">
|
|
1949
|
+
<div class="search-bar" id="search-bar">
|
|
1950
|
+
<input type="text" id="search-input" placeholder="Search…" autocomplete="off" />
|
|
1951
|
+
<span class="search-count" id="search-count"></span>
|
|
1952
|
+
<button id="search-regex" title="Regex">.*</button>
|
|
1953
|
+
<button id="search-prev" title="Previous">▲</button>
|
|
1954
|
+
<button id="search-next" title="Next">▼</button>
|
|
1955
|
+
<button id="search-close" title="Close">✕</button>
|
|
1956
|
+
</div>
|
|
1957
|
+
</div>
|
|
1950
1958
|
|
|
1951
1959
|
<div id="copy-toast">Copied!</div>
|
|
1952
1960
|
|
|
@@ -1965,7 +1973,7 @@
|
|
|
1965
1973
|
<div class="key-row">
|
|
1966
1974
|
<button class="key-btn modifier" id="ctrl-btn" title="Toggle Ctrl modifier">Ctrl</button>
|
|
1967
1975
|
<button class="key-btn modifier" id="shift-btn" title="Toggle Shift modifier">Shift</button>
|
|
1968
|
-
<button class="key-btn special" data-key="
|
|
1976
|
+
<button class="key-btn special" data-key="tab" title="Autocomplete">Tab</button>
|
|
1969
1977
|
<button class="key-btn special key-danger" data-key="" title="Interrupt process">
|
|
1970
1978
|
^C
|
|
1971
1979
|
</button>
|
|
@@ -2212,9 +2220,34 @@
|
|
|
2212
2220
|
</div>
|
|
2213
2221
|
</div>
|
|
2214
2222
|
|
|
2223
|
+
<!-- Command Palette / Tool Panel -->
|
|
2224
|
+
<div id="palette-backdrop" class="palette-backdrop"></div>
|
|
2225
|
+
<div id="palette-panel" class="palette-panel">
|
|
2226
|
+
<div class="palette-header">
|
|
2227
|
+
<span>Tools</span>
|
|
2228
|
+
<button class="palette-close" id="palette-close">✕</button>
|
|
2229
|
+
</div>
|
|
2230
|
+
<div class="palette-body" id="palette-body"></div>
|
|
2231
|
+
</div>
|
|
2232
|
+
|
|
2233
|
+
<!-- Theme Sub-Panel -->
|
|
2234
|
+
<div class="theme-subpanel" id="theme-subpanel">
|
|
2235
|
+
<div class="theme-subpanel-header">
|
|
2236
|
+
<span>Theme</span>
|
|
2237
|
+
<button class="theme-subpanel-close" id="theme-subpanel-close">✕</button>
|
|
2238
|
+
</div>
|
|
2239
|
+
<div class="theme-subpanel-list" id="theme-subpanel-list"></div>
|
|
2240
|
+
</div>
|
|
2241
|
+
|
|
2215
2242
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js"></script>
|
|
2216
2243
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.10.0/lib/addon-fit.min.js"></script>
|
|
2217
2244
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0.11.0/lib/addon-web-links.min.js"></script>
|
|
2245
|
+
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-search@0.15.0/lib/addon-search.min.js"></script>
|
|
2246
|
+
<script src="/js/shared.js"></script>
|
|
2247
|
+
<script src="/js/themes.js"></script>
|
|
2248
|
+
<script src="/js/terminal-themes.js"></script>
|
|
2249
|
+
<script src="/js/keybar.js"></script>
|
|
2250
|
+
<script src="/js/search.js"></script>
|
|
2218
2251
|
<script>
|
|
2219
2252
|
// ===== Constants =====
|
|
2220
2253
|
const SESSION_COLORS = [
|
|
@@ -2235,6 +2268,33 @@
|
|
|
2235
2268
|
|
|
2236
2269
|
let splitSecondId = null;
|
|
2237
2270
|
|
|
2271
|
+
// ===== Notification State =====
|
|
2272
|
+
let notificationsEnabled = localStorage.getItem('termbeam-notifications') !== 'false';
|
|
2273
|
+
|
|
2274
|
+
function updateNotifyToggle() {
|
|
2275
|
+
// notify-toggle button removed from top bar; function kept for palette use
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
function sendCommandNotification(sessionName) {
|
|
2279
|
+
if (Notification.permission !== 'granted') return;
|
|
2280
|
+
try {
|
|
2281
|
+
new Notification('Command finished in ' + sessionName, {
|
|
2282
|
+
icon: '/icons/icon-192.png',
|
|
2283
|
+
tag: 'termbeam-cmd',
|
|
2284
|
+
});
|
|
2285
|
+
} catch {}
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
function resetSilenceTimer(ms) {
|
|
2289
|
+
if (ms.silenceTimer) clearTimeout(ms.silenceTimer);
|
|
2290
|
+
ms.silenceTimer = setTimeout(() => {
|
|
2291
|
+
ms.silenceTimer = null;
|
|
2292
|
+
if (document.hidden && notificationsEnabled) {
|
|
2293
|
+
sendCommandNotification(ms.name || ms.id);
|
|
2294
|
+
}
|
|
2295
|
+
}, 3000);
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2238
2298
|
// Clipboard copy fallback for non-secure contexts (HTTP over LAN)
|
|
2239
2299
|
function copyFallback(text) {
|
|
2240
2300
|
const ta = document.createElement('textarea');
|
|
@@ -2249,6 +2309,14 @@
|
|
|
2249
2309
|
document.body.removeChild(ta);
|
|
2250
2310
|
}
|
|
2251
2311
|
|
|
2312
|
+
// Hook into shared theme system to update xterm terminal themes
|
|
2313
|
+
window.onThemeApplied = function (theme) {
|
|
2314
|
+
const termTheme = TERM_THEMES[theme] || darkTermTheme;
|
|
2315
|
+
for (const [, ms] of managed) {
|
|
2316
|
+
ms.term.options.theme = termTheme;
|
|
2317
|
+
}
|
|
2318
|
+
};
|
|
2319
|
+
|
|
2252
2320
|
// ===== DOM Refs =====
|
|
2253
2321
|
const statusDot = document.getElementById('status-dot');
|
|
2254
2322
|
const statusText = document.getElementById('status-text');
|
|
@@ -2257,345 +2325,6 @@
|
|
|
2257
2325
|
const tabListEl = document.getElementById('tab-list');
|
|
2258
2326
|
const terminalsWrapper = document.getElementById('terminals-wrapper');
|
|
2259
2327
|
|
|
2260
|
-
// ===== Terminal Themes =====
|
|
2261
|
-
const darkTermTheme = {
|
|
2262
|
-
background: '#1e1e1e',
|
|
2263
|
-
foreground: '#d4d4d4',
|
|
2264
|
-
cursor: '#aeafad',
|
|
2265
|
-
cursorAccent: '#1e1e1e',
|
|
2266
|
-
selectionBackground: 'rgba(38, 79, 120, 0.5)',
|
|
2267
|
-
black: '#000000',
|
|
2268
|
-
red: '#cd3131',
|
|
2269
|
-
green: '#0dbc79',
|
|
2270
|
-
yellow: '#e5e510',
|
|
2271
|
-
blue: '#2472c8',
|
|
2272
|
-
magenta: '#bc3fbc',
|
|
2273
|
-
cyan: '#11a8cd',
|
|
2274
|
-
white: '#e5e5e5',
|
|
2275
|
-
brightBlack: '#666666',
|
|
2276
|
-
brightRed: '#f14c4c',
|
|
2277
|
-
brightGreen: '#23d18b',
|
|
2278
|
-
brightYellow: '#f5f543',
|
|
2279
|
-
brightBlue: '#3b8eea',
|
|
2280
|
-
brightMagenta: '#d670d6',
|
|
2281
|
-
brightCyan: '#29b8db',
|
|
2282
|
-
brightWhite: '#e5e5e5',
|
|
2283
|
-
};
|
|
2284
|
-
const lightTermTheme = {
|
|
2285
|
-
background: '#ffffff',
|
|
2286
|
-
foreground: '#1e1e1e',
|
|
2287
|
-
cursor: '#000000',
|
|
2288
|
-
cursorAccent: '#ffffff',
|
|
2289
|
-
selectionBackground: 'rgba(0, 120, 215, 0.3)',
|
|
2290
|
-
black: '#000000',
|
|
2291
|
-
red: '#cd3131',
|
|
2292
|
-
green: '#00bc7c',
|
|
2293
|
-
yellow: '#949800',
|
|
2294
|
-
blue: '#0451a5',
|
|
2295
|
-
magenta: '#bc05bc',
|
|
2296
|
-
cyan: '#0598bc',
|
|
2297
|
-
white: '#555555',
|
|
2298
|
-
brightBlack: '#666666',
|
|
2299
|
-
brightRed: '#cd3131',
|
|
2300
|
-
brightGreen: '#14ce14',
|
|
2301
|
-
brightYellow: '#b5ba00',
|
|
2302
|
-
brightBlue: '#0451a5',
|
|
2303
|
-
brightMagenta: '#bc05bc',
|
|
2304
|
-
brightCyan: '#0598bc',
|
|
2305
|
-
brightWhite: '#a5a5a5',
|
|
2306
|
-
};
|
|
2307
|
-
const monokaiTermTheme = {
|
|
2308
|
-
background: '#272822',
|
|
2309
|
-
foreground: '#f8f8f2',
|
|
2310
|
-
cursor: '#f8f8f0',
|
|
2311
|
-
cursorAccent: '#272822',
|
|
2312
|
-
selectionBackground: 'rgba(166, 226, 46, 0.3)',
|
|
2313
|
-
black: '#272822',
|
|
2314
|
-
red: '#f92672',
|
|
2315
|
-
green: '#a6e22e',
|
|
2316
|
-
yellow: '#f4bf75',
|
|
2317
|
-
blue: '#66d9e8',
|
|
2318
|
-
magenta: '#ae81ff',
|
|
2319
|
-
cyan: '#a1efe4',
|
|
2320
|
-
white: '#f8f8f2',
|
|
2321
|
-
brightBlack: '#75715e',
|
|
2322
|
-
brightRed: '#f92672',
|
|
2323
|
-
brightGreen: '#a6e22e',
|
|
2324
|
-
brightYellow: '#f4bf75',
|
|
2325
|
-
brightBlue: '#66d9e8',
|
|
2326
|
-
brightMagenta: '#ae81ff',
|
|
2327
|
-
brightCyan: '#a1efe4',
|
|
2328
|
-
brightWhite: '#f9f8f5',
|
|
2329
|
-
};
|
|
2330
|
-
const solarizedDarkTermTheme = {
|
|
2331
|
-
background: '#002b36',
|
|
2332
|
-
foreground: '#839496',
|
|
2333
|
-
cursor: '#839496',
|
|
2334
|
-
cursorAccent: '#002b36',
|
|
2335
|
-
selectionBackground: 'rgba(7, 54, 66, 0.8)',
|
|
2336
|
-
black: '#073642',
|
|
2337
|
-
red: '#dc322f',
|
|
2338
|
-
green: '#859900',
|
|
2339
|
-
yellow: '#b58900',
|
|
2340
|
-
blue: '#268bd2',
|
|
2341
|
-
magenta: '#d33682',
|
|
2342
|
-
cyan: '#2aa198',
|
|
2343
|
-
white: '#eee8d5',
|
|
2344
|
-
brightBlack: '#002b36',
|
|
2345
|
-
brightRed: '#cb4b16',
|
|
2346
|
-
brightGreen: '#586e75',
|
|
2347
|
-
brightYellow: '#657b83',
|
|
2348
|
-
brightBlue: '#839496',
|
|
2349
|
-
brightMagenta: '#6c71c4',
|
|
2350
|
-
brightCyan: '#93a1a1',
|
|
2351
|
-
brightWhite: '#fdf6e3',
|
|
2352
|
-
};
|
|
2353
|
-
const solarizedLightTermTheme = {
|
|
2354
|
-
background: '#fdf6e3',
|
|
2355
|
-
foreground: '#657b83',
|
|
2356
|
-
cursor: '#586e75',
|
|
2357
|
-
cursorAccent: '#fdf6e3',
|
|
2358
|
-
selectionBackground: 'rgba(238, 232, 213, 0.8)',
|
|
2359
|
-
black: '#073642',
|
|
2360
|
-
red: '#dc322f',
|
|
2361
|
-
green: '#859900',
|
|
2362
|
-
yellow: '#b58900',
|
|
2363
|
-
blue: '#268bd2',
|
|
2364
|
-
magenta: '#d33682',
|
|
2365
|
-
cyan: '#2aa198',
|
|
2366
|
-
white: '#eee8d5',
|
|
2367
|
-
brightBlack: '#002b36',
|
|
2368
|
-
brightRed: '#cb4b16',
|
|
2369
|
-
brightGreen: '#586e75',
|
|
2370
|
-
brightYellow: '#657b83',
|
|
2371
|
-
brightBlue: '#839496',
|
|
2372
|
-
brightMagenta: '#6c71c4',
|
|
2373
|
-
brightCyan: '#93a1a1',
|
|
2374
|
-
brightWhite: '#fdf6e3',
|
|
2375
|
-
};
|
|
2376
|
-
const nordTermTheme = {
|
|
2377
|
-
background: '#2e3440',
|
|
2378
|
-
foreground: '#d8dee9',
|
|
2379
|
-
cursor: '#d8dee9',
|
|
2380
|
-
cursorAccent: '#2e3440',
|
|
2381
|
-
selectionBackground: 'rgba(67, 76, 94, 0.5)',
|
|
2382
|
-
black: '#3b4252',
|
|
2383
|
-
red: '#bf616a',
|
|
2384
|
-
green: '#a3be8c',
|
|
2385
|
-
yellow: '#ebcb8b',
|
|
2386
|
-
blue: '#81a1c1',
|
|
2387
|
-
magenta: '#b48ead',
|
|
2388
|
-
cyan: '#88c0d0',
|
|
2389
|
-
white: '#e5e9f0',
|
|
2390
|
-
brightBlack: '#4c566a',
|
|
2391
|
-
brightRed: '#bf616a',
|
|
2392
|
-
brightGreen: '#a3be8c',
|
|
2393
|
-
brightYellow: '#ebcb8b',
|
|
2394
|
-
brightBlue: '#81a1c1',
|
|
2395
|
-
brightMagenta: '#b48ead',
|
|
2396
|
-
brightCyan: '#8fbcbb',
|
|
2397
|
-
brightWhite: '#eceff4',
|
|
2398
|
-
};
|
|
2399
|
-
const draculaTermTheme = {
|
|
2400
|
-
background: '#282a36',
|
|
2401
|
-
foreground: '#f8f8f2',
|
|
2402
|
-
cursor: '#f8f8f2',
|
|
2403
|
-
cursorAccent: '#282a36',
|
|
2404
|
-
selectionBackground: 'rgba(68, 71, 90, 0.7)',
|
|
2405
|
-
black: '#21222c',
|
|
2406
|
-
red: '#ff5555',
|
|
2407
|
-
green: '#50fa7b',
|
|
2408
|
-
yellow: '#f1fa8c',
|
|
2409
|
-
blue: '#bd93f9',
|
|
2410
|
-
magenta: '#ff79c6',
|
|
2411
|
-
cyan: '#8be9fd',
|
|
2412
|
-
white: '#f8f8f2',
|
|
2413
|
-
brightBlack: '#6272a4',
|
|
2414
|
-
brightRed: '#ff6e6e',
|
|
2415
|
-
brightGreen: '#69ff94',
|
|
2416
|
-
brightYellow: '#ffffa5',
|
|
2417
|
-
brightBlue: '#d6acff',
|
|
2418
|
-
brightMagenta: '#ff92df',
|
|
2419
|
-
brightCyan: '#a4ffff',
|
|
2420
|
-
brightWhite: '#ffffff',
|
|
2421
|
-
};
|
|
2422
|
-
const githubDarkTermTheme = {
|
|
2423
|
-
background: '#0d1117',
|
|
2424
|
-
foreground: '#c9d1d9',
|
|
2425
|
-
cursor: '#c9d1d9',
|
|
2426
|
-
cursorAccent: '#0d1117',
|
|
2427
|
-
selectionBackground: 'rgba(56, 139, 253, 0.3)',
|
|
2428
|
-
black: '#484f58',
|
|
2429
|
-
red: '#ff7b72',
|
|
2430
|
-
green: '#3fb950',
|
|
2431
|
-
yellow: '#d29922',
|
|
2432
|
-
blue: '#58a6ff',
|
|
2433
|
-
magenta: '#bc8cff',
|
|
2434
|
-
cyan: '#39c5cf',
|
|
2435
|
-
white: '#c9d1d9',
|
|
2436
|
-
brightBlack: '#6e7681',
|
|
2437
|
-
brightRed: '#ffa198',
|
|
2438
|
-
brightGreen: '#56d364',
|
|
2439
|
-
brightYellow: '#e3b341',
|
|
2440
|
-
brightBlue: '#79c0ff',
|
|
2441
|
-
brightMagenta: '#d2a8ff',
|
|
2442
|
-
brightCyan: '#56d4dd',
|
|
2443
|
-
brightWhite: '#f0f6fc',
|
|
2444
|
-
};
|
|
2445
|
-
const oneDarkTermTheme = {
|
|
2446
|
-
background: '#282c34',
|
|
2447
|
-
foreground: '#abb2bf',
|
|
2448
|
-
cursor: '#528bff',
|
|
2449
|
-
cursorAccent: '#282c34',
|
|
2450
|
-
selectionBackground: 'rgba(62, 68, 82, 0.7)',
|
|
2451
|
-
black: '#3f4451',
|
|
2452
|
-
red: '#e06c75',
|
|
2453
|
-
green: '#98c379',
|
|
2454
|
-
yellow: '#e5c07b',
|
|
2455
|
-
blue: '#61afef',
|
|
2456
|
-
magenta: '#c678dd',
|
|
2457
|
-
cyan: '#56b6c2',
|
|
2458
|
-
white: '#d7dae0',
|
|
2459
|
-
brightBlack: '#4f5666',
|
|
2460
|
-
brightRed: '#e06c75',
|
|
2461
|
-
brightGreen: '#98c379',
|
|
2462
|
-
brightYellow: '#e5c07b',
|
|
2463
|
-
brightBlue: '#61afef',
|
|
2464
|
-
brightMagenta: '#c678dd',
|
|
2465
|
-
brightCyan: '#56b6c2',
|
|
2466
|
-
brightWhite: '#ffffff',
|
|
2467
|
-
};
|
|
2468
|
-
const catppuccinTermTheme = {
|
|
2469
|
-
background: '#1e1e2e',
|
|
2470
|
-
foreground: '#cdd6f4',
|
|
2471
|
-
cursor: '#f5e0dc',
|
|
2472
|
-
cursorAccent: '#1e1e2e',
|
|
2473
|
-
selectionBackground: 'rgba(88, 91, 112, 0.5)',
|
|
2474
|
-
black: '#45475a',
|
|
2475
|
-
red: '#f38ba8',
|
|
2476
|
-
green: '#a6e3a1',
|
|
2477
|
-
yellow: '#f9e2af',
|
|
2478
|
-
blue: '#89b4fa',
|
|
2479
|
-
magenta: '#f5c2e7',
|
|
2480
|
-
cyan: '#94e2d5',
|
|
2481
|
-
white: '#bac2de',
|
|
2482
|
-
brightBlack: '#585b70',
|
|
2483
|
-
brightRed: '#f38ba8',
|
|
2484
|
-
brightGreen: '#a6e3a1',
|
|
2485
|
-
brightYellow: '#f9e2af',
|
|
2486
|
-
brightBlue: '#89b4fa',
|
|
2487
|
-
brightMagenta: '#f5c2e7',
|
|
2488
|
-
brightCyan: '#94e2d5',
|
|
2489
|
-
brightWhite: '#a6adc8',
|
|
2490
|
-
};
|
|
2491
|
-
const gruvboxTermTheme = {
|
|
2492
|
-
background: '#282828',
|
|
2493
|
-
foreground: '#ebdbb2',
|
|
2494
|
-
cursor: '#ebdbb2',
|
|
2495
|
-
cursorAccent: '#282828',
|
|
2496
|
-
selectionBackground: 'rgba(80, 73, 69, 0.7)',
|
|
2497
|
-
black: '#282828',
|
|
2498
|
-
red: '#cc241d',
|
|
2499
|
-
green: '#98971a',
|
|
2500
|
-
yellow: '#d79921',
|
|
2501
|
-
blue: '#458588',
|
|
2502
|
-
magenta: '#b16286',
|
|
2503
|
-
cyan: '#689d6a',
|
|
2504
|
-
white: '#a89984',
|
|
2505
|
-
brightBlack: '#928374',
|
|
2506
|
-
brightRed: '#fb4934',
|
|
2507
|
-
brightGreen: '#b8bb26',
|
|
2508
|
-
brightYellow: '#fabd2f',
|
|
2509
|
-
brightBlue: '#83a598',
|
|
2510
|
-
brightMagenta: '#d3869b',
|
|
2511
|
-
brightCyan: '#8ec07c',
|
|
2512
|
-
brightWhite: '#ebdbb2',
|
|
2513
|
-
};
|
|
2514
|
-
const nightOwlTermTheme = {
|
|
2515
|
-
background: '#011627',
|
|
2516
|
-
foreground: '#d6deeb',
|
|
2517
|
-
cursor: '#80a4c2',
|
|
2518
|
-
cursorAccent: '#011627',
|
|
2519
|
-
selectionBackground: 'rgba(29, 59, 83, 0.7)',
|
|
2520
|
-
black: '#010e1a',
|
|
2521
|
-
red: '#ef5350',
|
|
2522
|
-
green: '#22da6e',
|
|
2523
|
-
yellow: '#addb67',
|
|
2524
|
-
blue: '#82aaff',
|
|
2525
|
-
magenta: '#c792ea',
|
|
2526
|
-
cyan: '#21c7a8',
|
|
2527
|
-
white: '#d6deeb',
|
|
2528
|
-
brightBlack: '#575656',
|
|
2529
|
-
brightRed: '#ef5350',
|
|
2530
|
-
brightGreen: '#22da6e',
|
|
2531
|
-
brightYellow: '#ffeb95',
|
|
2532
|
-
brightBlue: '#82aaff',
|
|
2533
|
-
brightMagenta: '#c792ea',
|
|
2534
|
-
brightCyan: '#7fdbca',
|
|
2535
|
-
brightWhite: '#ffffff',
|
|
2536
|
-
};
|
|
2537
|
-
const TERM_THEMES = {
|
|
2538
|
-
dark: darkTermTheme,
|
|
2539
|
-
light: lightTermTheme,
|
|
2540
|
-
monokai: monokaiTermTheme,
|
|
2541
|
-
'solarized-dark': solarizedDarkTermTheme,
|
|
2542
|
-
'solarized-light': solarizedLightTermTheme,
|
|
2543
|
-
nord: nordTermTheme,
|
|
2544
|
-
dracula: draculaTermTheme,
|
|
2545
|
-
'github-dark': githubDarkTermTheme,
|
|
2546
|
-
'one-dark': oneDarkTermTheme,
|
|
2547
|
-
catppuccin: catppuccinTermTheme,
|
|
2548
|
-
gruvbox: gruvboxTermTheme,
|
|
2549
|
-
'night-owl': nightOwlTermTheme,
|
|
2550
|
-
};
|
|
2551
|
-
|
|
2552
|
-
// ===== Theme =====
|
|
2553
|
-
const THEMES = [
|
|
2554
|
-
{ id: 'dark', name: 'Dark', bg: '#1e1e1e' },
|
|
2555
|
-
{ id: 'light', name: 'Light', bg: '#f3f3f3' },
|
|
2556
|
-
{ id: 'monokai', name: 'Monokai', bg: '#272822' },
|
|
2557
|
-
{ id: 'solarized-dark', name: 'Solarized Dark', bg: '#002b36' },
|
|
2558
|
-
{ id: 'solarized-light', name: 'Solarized Light', bg: '#fdf6e3' },
|
|
2559
|
-
{ id: 'nord', name: 'Nord', bg: '#2e3440' },
|
|
2560
|
-
{ id: 'dracula', name: 'Dracula', bg: '#282a36' },
|
|
2561
|
-
{ id: 'github-dark', name: 'GitHub Dark', bg: '#0d1117' },
|
|
2562
|
-
{ id: 'one-dark', name: 'One Dark', bg: '#282c34' },
|
|
2563
|
-
{ id: 'catppuccin', name: 'Catppuccin', bg: '#1e1e2e' },
|
|
2564
|
-
{ id: 'gruvbox', name: 'Gruvbox', bg: '#282828' },
|
|
2565
|
-
{ id: 'night-owl', name: 'Night Owl', bg: '#011627' },
|
|
2566
|
-
];
|
|
2567
|
-
function getTheme() {
|
|
2568
|
-
return localStorage.getItem('termbeam-theme') || 'dark';
|
|
2569
|
-
}
|
|
2570
|
-
function applyTheme(theme) {
|
|
2571
|
-
document.documentElement.setAttribute('data-theme', theme);
|
|
2572
|
-
const t = THEMES.find((x) => x.id === theme) || THEMES[0];
|
|
2573
|
-
document.querySelector('meta[name="theme-color"]').content = t.bg;
|
|
2574
|
-
localStorage.setItem('termbeam-theme', theme);
|
|
2575
|
-
document.querySelectorAll('.theme-option').forEach((el) => {
|
|
2576
|
-
el.classList.toggle('active', el.dataset.themeOption === theme);
|
|
2577
|
-
});
|
|
2578
|
-
const termTheme = TERM_THEMES[theme] || darkTermTheme;
|
|
2579
|
-
for (const [, ms] of managed) {
|
|
2580
|
-
ms.term.options.theme = termTheme;
|
|
2581
|
-
}
|
|
2582
|
-
}
|
|
2583
|
-
applyTheme(getTheme());
|
|
2584
|
-
document.getElementById('theme-toggle').addEventListener('click', (e) => {
|
|
2585
|
-
e.stopPropagation();
|
|
2586
|
-
document.getElementById('theme-picker').classList.toggle('open');
|
|
2587
|
-
});
|
|
2588
|
-
document.addEventListener('click', () => {
|
|
2589
|
-
document.getElementById('theme-picker').classList.remove('open');
|
|
2590
|
-
});
|
|
2591
|
-
document.querySelectorAll('.theme-option').forEach((el) => {
|
|
2592
|
-
el.addEventListener('click', (e) => {
|
|
2593
|
-
e.stopPropagation();
|
|
2594
|
-
applyTheme(el.dataset.themeOption);
|
|
2595
|
-
document.getElementById('theme-picker').classList.remove('open');
|
|
2596
|
-
});
|
|
2597
|
-
});
|
|
2598
|
-
|
|
2599
2328
|
// ===== Font Loading (non-blocking) =====
|
|
2600
2329
|
const nerdFont = new FontFace(
|
|
2601
2330
|
'NerdFont',
|
|
@@ -2614,11 +2343,6 @@
|
|
|
2614
2343
|
init();
|
|
2615
2344
|
|
|
2616
2345
|
// ===== Helpers =====
|
|
2617
|
-
function esc(str) {
|
|
2618
|
-
const d = document.createElement('div');
|
|
2619
|
-
d.textContent = str;
|
|
2620
|
-
return d.innerHTML;
|
|
2621
|
-
}
|
|
2622
2346
|
function escAttr(str) {
|
|
2623
2347
|
return String(str)
|
|
2624
2348
|
.replace(/&/g, '&')
|
|
@@ -2731,12 +2455,6 @@
|
|
|
2731
2455
|
loadShellsForModal();
|
|
2732
2456
|
startPolling();
|
|
2733
2457
|
|
|
2734
|
-
// Zoom
|
|
2735
|
-
document.getElementById('zoom-in').addEventListener('click', () => applyZoom(fontSize + 2));
|
|
2736
|
-
document
|
|
2737
|
-
.getElementById('zoom-out')
|
|
2738
|
-
.addEventListener('click', () => applyZoom(fontSize - 2));
|
|
2739
|
-
|
|
2740
2458
|
// Pinch-to-zoom
|
|
2741
2459
|
(function setupPinchZoom() {
|
|
2742
2460
|
const wrapper = document.getElementById('terminals-wrapper');
|
|
@@ -2864,9 +2582,6 @@
|
|
|
2864
2582
|
);
|
|
2865
2583
|
}
|
|
2866
2584
|
|
|
2867
|
-
// Split toggle
|
|
2868
|
-
document.getElementById('split-toggle').addEventListener('click', toggleSplit);
|
|
2869
|
-
|
|
2870
2585
|
// Scroll to bottom when returning from idle / tab switch
|
|
2871
2586
|
document.addEventListener('visibilitychange', () => {
|
|
2872
2587
|
if (!document.hidden && activeId) {
|
|
@@ -2914,7 +2629,7 @@
|
|
|
2914
2629
|
fetch('/api/version')
|
|
2915
2630
|
.then((r) => r.json())
|
|
2916
2631
|
.then((d) => {
|
|
2917
|
-
|
|
2632
|
+
window._termbeamVersion = 'v' + d.version;
|
|
2918
2633
|
document.getElementById('side-panel-version').textContent = 'v' + d.version;
|
|
2919
2634
|
})
|
|
2920
2635
|
.catch(() => {});
|
|
@@ -2923,6 +2638,7 @@
|
|
|
2923
2638
|
// ===== Session Management =====
|
|
2924
2639
|
function addSession(data) {
|
|
2925
2640
|
if (managed.has(data.id)) return;
|
|
2641
|
+
managed.set(data.id, null); // reserve slot to prevent race condition
|
|
2926
2642
|
|
|
2927
2643
|
const term = new window.Terminal({
|
|
2928
2644
|
cursorBlink: true,
|
|
@@ -2941,8 +2657,10 @@
|
|
|
2941
2657
|
|
|
2942
2658
|
const fitAddon = new window.FitAddon.FitAddon();
|
|
2943
2659
|
const webLinksAddon = new window.WebLinksAddon.WebLinksAddon();
|
|
2660
|
+
const searchAddon = new window.SearchAddon.SearchAddon();
|
|
2944
2661
|
term.loadAddon(fitAddon);
|
|
2945
2662
|
term.loadAddon(webLinksAddon);
|
|
2663
|
+
term.loadAddon(searchAddon);
|
|
2946
2664
|
|
|
2947
2665
|
const container = document.createElement('div');
|
|
2948
2666
|
container.className = 'terminal-pane';
|
|
@@ -3090,8 +2808,10 @@
|
|
|
3090
2808
|
shell: data.shell,
|
|
3091
2809
|
cwd: data.cwd,
|
|
3092
2810
|
pid: data.pid,
|
|
2811
|
+
git: data.git || null,
|
|
3093
2812
|
term,
|
|
3094
2813
|
fitAddon,
|
|
2814
|
+
searchAddon,
|
|
3095
2815
|
container,
|
|
3096
2816
|
coalescedWrite,
|
|
3097
2817
|
scrollBtn,
|
|
@@ -3100,6 +2820,7 @@
|
|
|
3100
2820
|
reconnectTimer: null,
|
|
3101
2821
|
reconnectDelay: 3000,
|
|
3102
2822
|
lastActivity: data.lastActivity || Date.now(),
|
|
2823
|
+
silenceTimer: null,
|
|
3103
2824
|
};
|
|
3104
2825
|
|
|
3105
2826
|
managed.set(data.id, ms);
|
|
@@ -3211,6 +2932,7 @@
|
|
|
3211
2932
|
if (msg.type === 'output') {
|
|
3212
2933
|
ms.coalescedWrite(msg.data);
|
|
3213
2934
|
ms.lastActivity = Date.now();
|
|
2935
|
+
resetSilenceTimer(ms);
|
|
3214
2936
|
markUnreadOutput();
|
|
3215
2937
|
if (ms.id !== activeId && !ms.hasUnread) {
|
|
3216
2938
|
ms.hasUnread = true;
|
|
@@ -3331,6 +3053,10 @@
|
|
|
3331
3053
|
clearTimeout(ms.reconnectTimer);
|
|
3332
3054
|
ms.reconnectTimer = null;
|
|
3333
3055
|
}
|
|
3056
|
+
if (ms.silenceTimer) {
|
|
3057
|
+
clearTimeout(ms.silenceTimer);
|
|
3058
|
+
ms.silenceTimer = null;
|
|
3059
|
+
}
|
|
3334
3060
|
if (ms.ws)
|
|
3335
3061
|
try {
|
|
3336
3062
|
ms.ws.close();
|
|
@@ -3563,6 +3289,26 @@
|
|
|
3563
3289
|
'" title="Close session">×</button>' +
|
|
3564
3290
|
'</div>' +
|
|
3565
3291
|
(activity ? '<div class="side-panel-card-meta">' + activity + ' ago</div>' : '') +
|
|
3292
|
+
(ms.git
|
|
3293
|
+
? '<div class="side-panel-card-git">' +
|
|
3294
|
+
'<span class="git-badge"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg> ' +
|
|
3295
|
+
esc(ms.git.branch || 'detached') +
|
|
3296
|
+
'</span>' +
|
|
3297
|
+
(ms.git.provider
|
|
3298
|
+
? '<span class="git-badge">' + esc(ms.git.provider) + '</span>'
|
|
3299
|
+
: '') +
|
|
3300
|
+
(ms.git.repoName
|
|
3301
|
+
? '<span class="git-badge">' + esc(ms.git.repoName) + '</span>'
|
|
3302
|
+
: '') +
|
|
3303
|
+
(ms.git.status
|
|
3304
|
+
? '<span class="git-badge ' +
|
|
3305
|
+
(ms.git.status.clean ? 'git-status-clean' : 'git-status-dirty') +
|
|
3306
|
+
'">' +
|
|
3307
|
+
(ms.git.status.clean ? '✓ clean' : esc(ms.git.status.summary)) +
|
|
3308
|
+
'</span>'
|
|
3309
|
+
: '') +
|
|
3310
|
+
'</div>'
|
|
3311
|
+
: '') +
|
|
3566
3312
|
previewContent +
|
|
3567
3313
|
'</div>'
|
|
3568
3314
|
);
|
|
@@ -3717,7 +3463,6 @@
|
|
|
3717
3463
|
// ===== Split View =====
|
|
3718
3464
|
function toggleSplit() {
|
|
3719
3465
|
splitMode = !splitMode;
|
|
3720
|
-
document.getElementById('split-toggle').classList.toggle('active', splitMode);
|
|
3721
3466
|
|
|
3722
3467
|
if (splitMode) {
|
|
3723
3468
|
// Find a second session to show
|
|
@@ -3751,178 +3496,6 @@
|
|
|
3751
3496
|
renderTabs();
|
|
3752
3497
|
}
|
|
3753
3498
|
|
|
3754
|
-
// ===== Key Bar =====
|
|
3755
|
-
// Modifier state (shared with terminal onData)
|
|
3756
|
-
let ctrlActive = false;
|
|
3757
|
-
let shiftActive = false;
|
|
3758
|
-
function clearModifiers() {
|
|
3759
|
-
ctrlActive = false;
|
|
3760
|
-
shiftActive = false;
|
|
3761
|
-
const ctrlBtn = document.getElementById('ctrl-btn');
|
|
3762
|
-
const shiftBtn = document.getElementById('shift-btn');
|
|
3763
|
-
if (ctrlBtn) ctrlBtn.classList.remove('active');
|
|
3764
|
-
if (shiftBtn) shiftBtn.classList.remove('active');
|
|
3765
|
-
}
|
|
3766
|
-
|
|
3767
|
-
function setupKeyBar() {
|
|
3768
|
-
const keyBar = document.getElementById('key-bar');
|
|
3769
|
-
const ctrlBtn = document.getElementById('ctrl-btn');
|
|
3770
|
-
const shiftBtn = document.getElementById('shift-btn');
|
|
3771
|
-
let repeatTimer = null;
|
|
3772
|
-
let repeatInterval = null;
|
|
3773
|
-
|
|
3774
|
-
function toggleModifier(which) {
|
|
3775
|
-
if (which === 'ctrl') {
|
|
3776
|
-
ctrlActive = !ctrlActive;
|
|
3777
|
-
ctrlBtn.classList.toggle('active', ctrlActive);
|
|
3778
|
-
} else {
|
|
3779
|
-
shiftActive = !shiftActive;
|
|
3780
|
-
shiftBtn.classList.toggle('active', shiftActive);
|
|
3781
|
-
}
|
|
3782
|
-
}
|
|
3783
|
-
|
|
3784
|
-
function applyModifiers(key) {
|
|
3785
|
-
if (!ctrlActive && !shiftActive) return key;
|
|
3786
|
-
// Modifier param: Shift=2, Ctrl=5, Ctrl+Shift=6
|
|
3787
|
-
const mod = ctrlActive && shiftActive ? 6 : ctrlActive ? 5 : 2;
|
|
3788
|
-
// Arrow keys: \x1b[X → \x1b[1;{mod}X
|
|
3789
|
-
const csiMatch = key.match(/^\x1b\[([ABCD])$/);
|
|
3790
|
-
if (csiMatch) return '\x1b[1;' + mod + csiMatch[1];
|
|
3791
|
-
// Home/End: \x1bOH/\x1bOF → \x1b[1;{mod}H/F
|
|
3792
|
-
const ssMatch = key.match(/^\x1bO([HF])$/);
|
|
3793
|
-
if (ssMatch) return '\x1b[1;' + mod + ssMatch[1];
|
|
3794
|
-
// Tab with Shift → reverse tab
|
|
3795
|
-
if (key === '\x09' && shiftActive && !ctrlActive) return '\x1b[Z';
|
|
3796
|
-
return key;
|
|
3797
|
-
}
|
|
3798
|
-
|
|
3799
|
-
function flashBtn(btn) {
|
|
3800
|
-
btn.classList.add('flash');
|
|
3801
|
-
setTimeout(() => btn.classList.remove('flash'), 120);
|
|
3802
|
-
}
|
|
3803
|
-
|
|
3804
|
-
function sendKey(btn) {
|
|
3805
|
-
if (!btn || !btn.dataset.key) return;
|
|
3806
|
-
flashBtn(btn);
|
|
3807
|
-
let data = btn.dataset.key === 'enter' ? '\r' : btn.dataset.key;
|
|
3808
|
-
data = applyModifiers(data);
|
|
3809
|
-
const ms = managed.get(activeId);
|
|
3810
|
-
if (ms && ms.ws && ms.ws.readyState === 1) {
|
|
3811
|
-
ms.ws.send(JSON.stringify({ type: 'input', data }));
|
|
3812
|
-
}
|
|
3813
|
-
clearModifiers();
|
|
3814
|
-
}
|
|
3815
|
-
|
|
3816
|
-
function stopRepeat() {
|
|
3817
|
-
clearTimeout(repeatTimer);
|
|
3818
|
-
clearInterval(repeatInterval);
|
|
3819
|
-
repeatTimer = null;
|
|
3820
|
-
repeatInterval = null;
|
|
3821
|
-
}
|
|
3822
|
-
|
|
3823
|
-
function startRepeat(btn) {
|
|
3824
|
-
stopRepeat();
|
|
3825
|
-
sendKey(btn);
|
|
3826
|
-
repeatTimer = setTimeout(() => {
|
|
3827
|
-
repeatInterval = setInterval(() => sendKey(btn), 80);
|
|
3828
|
-
}, 400);
|
|
3829
|
-
}
|
|
3830
|
-
|
|
3831
|
-
ctrlBtn.addEventListener('click', (e) => {
|
|
3832
|
-
e.preventDefault();
|
|
3833
|
-
e.stopPropagation();
|
|
3834
|
-
toggleModifier('ctrl');
|
|
3835
|
-
});
|
|
3836
|
-
shiftBtn.addEventListener('click', (e) => {
|
|
3837
|
-
e.preventDefault();
|
|
3838
|
-
e.stopPropagation();
|
|
3839
|
-
toggleModifier('shift');
|
|
3840
|
-
});
|
|
3841
|
-
ctrlBtn.addEventListener('mousedown', (e) => e.preventDefault());
|
|
3842
|
-
shiftBtn.addEventListener('mousedown', (e) => e.preventDefault());
|
|
3843
|
-
ctrlBtn.addEventListener(
|
|
3844
|
-
'touchstart',
|
|
3845
|
-
(e) => {
|
|
3846
|
-
e.preventDefault();
|
|
3847
|
-
keyBarTouched = true;
|
|
3848
|
-
toggleModifier('ctrl');
|
|
3849
|
-
},
|
|
3850
|
-
{ passive: false },
|
|
3851
|
-
);
|
|
3852
|
-
shiftBtn.addEventListener(
|
|
3853
|
-
'touchstart',
|
|
3854
|
-
(e) => {
|
|
3855
|
-
e.preventDefault();
|
|
3856
|
-
keyBarTouched = true;
|
|
3857
|
-
toggleModifier('shift');
|
|
3858
|
-
},
|
|
3859
|
-
{ passive: false },
|
|
3860
|
-
);
|
|
3861
|
-
|
|
3862
|
-
let keyBarTouched = false;
|
|
3863
|
-
keyBar.addEventListener('mousedown', (e) => {
|
|
3864
|
-
if (keyBarTouched) {
|
|
3865
|
-
keyBarTouched = false;
|
|
3866
|
-
return;
|
|
3867
|
-
}
|
|
3868
|
-
const btn = e.target.closest('.key-btn');
|
|
3869
|
-
if (btn && btn.dataset.key) {
|
|
3870
|
-
e.preventDefault();
|
|
3871
|
-
startRepeat(btn);
|
|
3872
|
-
}
|
|
3873
|
-
});
|
|
3874
|
-
keyBar.addEventListener('mouseup', stopRepeat);
|
|
3875
|
-
keyBar.addEventListener('mouseleave', stopRepeat);
|
|
3876
|
-
|
|
3877
|
-
// Touch handling: allow native scroll when swiping, fire key only on tap
|
|
3878
|
-
const SWIPE_THRESHOLD = 10;
|
|
3879
|
-
let touchStartX = 0;
|
|
3880
|
-
let touchBtn = null;
|
|
3881
|
-
let touchMoved = false;
|
|
3882
|
-
|
|
3883
|
-
keyBar.addEventListener(
|
|
3884
|
-
'touchstart',
|
|
3885
|
-
(e) => {
|
|
3886
|
-
keyBarTouched = true;
|
|
3887
|
-
touchBtn = e.target.closest('.key-btn');
|
|
3888
|
-
touchMoved = false;
|
|
3889
|
-
touchStartX = e.touches[0].clientX;
|
|
3890
|
-
// Don't preventDefault — let the browser handle scroll
|
|
3891
|
-
},
|
|
3892
|
-
{ passive: true },
|
|
3893
|
-
);
|
|
3894
|
-
keyBar.addEventListener(
|
|
3895
|
-
'touchmove',
|
|
3896
|
-
(e) => {
|
|
3897
|
-
if (Math.abs(e.touches[0].clientX - touchStartX) > SWIPE_THRESHOLD) {
|
|
3898
|
-
touchMoved = true;
|
|
3899
|
-
stopRepeat();
|
|
3900
|
-
}
|
|
3901
|
-
},
|
|
3902
|
-
{ passive: true },
|
|
3903
|
-
);
|
|
3904
|
-
keyBar.addEventListener('touchend', (e) => {
|
|
3905
|
-
if (!touchMoved && touchBtn && touchBtn.dataset.key) {
|
|
3906
|
-
e.preventDefault();
|
|
3907
|
-
sendKey(touchBtn);
|
|
3908
|
-
}
|
|
3909
|
-
stopRepeat();
|
|
3910
|
-
touchBtn = null;
|
|
3911
|
-
});
|
|
3912
|
-
keyBar.addEventListener('touchcancel', () => {
|
|
3913
|
-
stopRepeat();
|
|
3914
|
-
touchBtn = null;
|
|
3915
|
-
});
|
|
3916
|
-
|
|
3917
|
-
keyBar.addEventListener('click', (e) => {
|
|
3918
|
-
const btn = e.target.closest('.key-btn');
|
|
3919
|
-
if (btn) {
|
|
3920
|
-
const ms = managed.get(activeId);
|
|
3921
|
-
if (ms) ms.term.focus();
|
|
3922
|
-
}
|
|
3923
|
-
});
|
|
3924
|
-
}
|
|
3925
|
-
|
|
3926
3499
|
// ===== Paste =====
|
|
3927
3500
|
function setupPaste() {
|
|
3928
3501
|
const pasteOverlay = document.getElementById('paste-overlay');
|
|
@@ -4337,6 +3910,18 @@
|
|
|
4337
3910
|
if (cmd) body.initialCommand = cmd;
|
|
4338
3911
|
if (color) body.color = color;
|
|
4339
3912
|
|
|
3913
|
+
// Include current terminal dimensions so the PTY spawns at the right
|
|
3914
|
+
// size — prevents oh-my-posh and other slow prompts from rendering
|
|
3915
|
+
// at the default 120×30 size and triggering a duplicate on SIGWINCH.
|
|
3916
|
+
const activeMs = managed.get(activeId);
|
|
3917
|
+
if (activeMs && activeMs.fitAddon) {
|
|
3918
|
+
const dims = activeMs.fitAddon.proposeDimensions();
|
|
3919
|
+
if (dims) {
|
|
3920
|
+
body.cols = dims.cols;
|
|
3921
|
+
body.rows = dims.rows;
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
|
|
4340
3925
|
try {
|
|
4341
3926
|
const res = await fetch('/api/sessions', {
|
|
4342
3927
|
method: 'POST',
|
|
@@ -4378,7 +3963,9 @@
|
|
|
4378
3963
|
const ms = managed.get(s.id);
|
|
4379
3964
|
ms.name = s.name;
|
|
4380
3965
|
ms.color = s.color;
|
|
3966
|
+
ms.cwd = s.cwd;
|
|
4381
3967
|
ms.lastActivity = s.lastActivity;
|
|
3968
|
+
ms.git = s.git || null;
|
|
4382
3969
|
}
|
|
4383
3970
|
}
|
|
4384
3971
|
|
|
@@ -4391,6 +3978,10 @@
|
|
|
4391
3978
|
clearTimeout(ms.reconnectTimer);
|
|
4392
3979
|
ms.reconnectTimer = null;
|
|
4393
3980
|
}
|
|
3981
|
+
if (ms.silenceTimer) {
|
|
3982
|
+
clearTimeout(ms.silenceTimer);
|
|
3983
|
+
ms.silenceTimer = null;
|
|
3984
|
+
}
|
|
4394
3985
|
if (ms.ws)
|
|
4395
3986
|
try {
|
|
4396
3987
|
ms.ws.close();
|
|
@@ -4524,12 +4115,11 @@
|
|
|
4524
4115
|
});
|
|
4525
4116
|
}
|
|
4526
4117
|
|
|
4527
|
-
|
|
4118
|
+
async function shareLink() {
|
|
4528
4119
|
const urlPromise = fetch('/api/share-token')
|
|
4529
4120
|
.then((r) => (r.ok ? r.json() : null))
|
|
4530
4121
|
.then((data) => (data && data.url) || location.href)
|
|
4531
4122
|
.catch(() => location.href);
|
|
4532
|
-
// ClipboardItem with a promise preserves user activation across the fetch
|
|
4533
4123
|
if (navigator.clipboard && typeof ClipboardItem !== 'undefined') {
|
|
4534
4124
|
try {
|
|
4535
4125
|
const blobPromise = urlPromise.then((u) => new Blob([u], { type: 'text/plain' }));
|
|
@@ -4538,7 +4128,6 @@
|
|
|
4538
4128
|
return;
|
|
4539
4129
|
} catch {}
|
|
4540
4130
|
}
|
|
4541
|
-
// Fallback: resolve URL first, then try legacy methods
|
|
4542
4131
|
const url = await urlPromise;
|
|
4543
4132
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
4544
4133
|
try {
|
|
@@ -4552,10 +4141,9 @@
|
|
|
4552
4141
|
} else {
|
|
4553
4142
|
showShareUrlPrompt(url);
|
|
4554
4143
|
}
|
|
4555
|
-
}
|
|
4144
|
+
}
|
|
4556
4145
|
|
|
4557
|
-
|
|
4558
|
-
document.getElementById('refresh-btn').addEventListener('click', async () => {
|
|
4146
|
+
async function refreshApp() {
|
|
4559
4147
|
if ('caches' in window) {
|
|
4560
4148
|
const keys = await caches.keys();
|
|
4561
4149
|
await Promise.all(keys.map((k) => caches.delete(k)));
|
|
@@ -4565,12 +4153,282 @@
|
|
|
4565
4153
|
if (reg) await reg.update();
|
|
4566
4154
|
}
|
|
4567
4155
|
location.reload();
|
|
4568
|
-
}
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
// ===== Command Palette =====
|
|
4159
|
+
(function setupPalette() {
|
|
4160
|
+
const paletteActions = [
|
|
4161
|
+
{
|
|
4162
|
+
icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>',
|
|
4163
|
+
label: 'New tab',
|
|
4164
|
+
category: 'Session',
|
|
4165
|
+
action: () => openNewSessionModal(),
|
|
4166
|
+
},
|
|
4167
|
+
{
|
|
4168
|
+
icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',
|
|
4169
|
+
label: 'Close tab',
|
|
4170
|
+
category: 'Session',
|
|
4171
|
+
action: () => {
|
|
4172
|
+
if (!activeId) return;
|
|
4173
|
+
const ms = managed.get(activeId);
|
|
4174
|
+
const name = (ms && ms.name) || activeId.slice(0, 8);
|
|
4175
|
+
if (confirm('Close session "' + name + '"?')) {
|
|
4176
|
+
removeSession(activeId);
|
|
4177
|
+
}
|
|
4178
|
+
},
|
|
4179
|
+
},
|
|
4180
|
+
{
|
|
4181
|
+
icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"/></svg>',
|
|
4182
|
+
label: 'Rename session',
|
|
4183
|
+
category: 'Session',
|
|
4184
|
+
action: () => {
|
|
4185
|
+
if (!activeId) return;
|
|
4186
|
+
const ms = managed.get(activeId);
|
|
4187
|
+
if (!ms) return;
|
|
4188
|
+
const name = prompt('Rename session:', ms.name || '');
|
|
4189
|
+
if (name !== null && name.trim()) {
|
|
4190
|
+
ms.name = name.trim();
|
|
4191
|
+
renderTabs();
|
|
4192
|
+
updateStatusBar();
|
|
4193
|
+
}
|
|
4194
|
+
},
|
|
4195
|
+
},
|
|
4196
|
+
{
|
|
4197
|
+
icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="12" y1="3" x2="12" y2="21"/></svg>',
|
|
4198
|
+
label: 'Split view',
|
|
4199
|
+
category: 'Session',
|
|
4200
|
+
action: () => toggleSplit(),
|
|
4201
|
+
},
|
|
4202
|
+
{
|
|
4203
|
+
icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" stroke="none"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>',
|
|
4204
|
+
label: 'Stop session',
|
|
4205
|
+
category: 'Session',
|
|
4206
|
+
action: () => {
|
|
4207
|
+
if (!activeId) return;
|
|
4208
|
+
if (!confirm('Stop this session? The process will be killed.')) return;
|
|
4209
|
+
removeSession(activeId);
|
|
4210
|
+
},
|
|
4211
|
+
},
|
|
4212
|
+
{
|
|
4213
|
+
icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>',
|
|
4214
|
+
label: 'Find in terminal',
|
|
4215
|
+
category: 'Search',
|
|
4216
|
+
action: () => openSearchBar(),
|
|
4217
|
+
},
|
|
4218
|
+
{
|
|
4219
|
+
icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/></svg>',
|
|
4220
|
+
label: 'Increase font size',
|
|
4221
|
+
category: 'View',
|
|
4222
|
+
action: () => applyZoom(fontSize + 1),
|
|
4223
|
+
},
|
|
4224
|
+
{
|
|
4225
|
+
icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="10"/><line x1="8" y1="12" x2="16" y2="12"/></svg>',
|
|
4226
|
+
label: 'Decrease font size',
|
|
4227
|
+
category: 'View',
|
|
4228
|
+
action: () => applyZoom(fontSize - 1),
|
|
4229
|
+
},
|
|
4230
|
+
{
|
|
4231
|
+
icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="13.5" cy="6.5" r=".5" fill="currentColor"/><circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/><circle cx="6.5" cy="12.5" r=".5" fill="currentColor"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"/></svg>',
|
|
4232
|
+
get label() {
|
|
4233
|
+
const current = THEMES.find((x) => x.id === getTheme()) || THEMES[0];
|
|
4234
|
+
return 'Theme (' + current.name + ')';
|
|
4235
|
+
},
|
|
4236
|
+
category: 'View',
|
|
4237
|
+
action: () => {
|
|
4238
|
+
openThemeSubpanel();
|
|
4239
|
+
},
|
|
4240
|
+
},
|
|
4241
|
+
{
|
|
4242
|
+
icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>',
|
|
4243
|
+
label: 'Preview port',
|
|
4244
|
+
category: 'View',
|
|
4245
|
+
action: () => openPreviewModal(),
|
|
4246
|
+
},
|
|
4247
|
+
{
|
|
4248
|
+
icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>',
|
|
4249
|
+
label: 'Copy link',
|
|
4250
|
+
category: 'Share',
|
|
4251
|
+
action: shareLink,
|
|
4252
|
+
},
|
|
4253
|
+
{
|
|
4254
|
+
icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>',
|
|
4255
|
+
get label() {
|
|
4256
|
+
return notificationsEnabled ? 'Notifications (on)' : 'Notifications (off)';
|
|
4257
|
+
},
|
|
4258
|
+
category: 'Notifications',
|
|
4259
|
+
keepOpen: true,
|
|
4260
|
+
action: () => {
|
|
4261
|
+
notificationsEnabled = !notificationsEnabled;
|
|
4262
|
+
localStorage.setItem('termbeam-notifications', notificationsEnabled);
|
|
4263
|
+
if (
|
|
4264
|
+
notificationsEnabled &&
|
|
4265
|
+
'Notification' in window &&
|
|
4266
|
+
Notification.permission === 'default'
|
|
4267
|
+
) {
|
|
4268
|
+
Notification.requestPermission();
|
|
4269
|
+
}
|
|
4270
|
+
showToast(notificationsEnabled ? 'Notifications on' : 'Notifications off');
|
|
4271
|
+
renderPalette();
|
|
4272
|
+
},
|
|
4273
|
+
},
|
|
4274
|
+
{
|
|
4275
|
+
icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>',
|
|
4276
|
+
label: 'Refresh',
|
|
4277
|
+
category: 'System',
|
|
4278
|
+
action: () => refreshApp(),
|
|
4279
|
+
},
|
|
4280
|
+
{
|
|
4281
|
+
icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 4H8l-7 8 7 8h13a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z"/><line x1="18" y1="9" x2="12" y2="15"/><line x1="12" y1="9" x2="18" y2="15"/></svg>',
|
|
4282
|
+
label: 'Clear terminal',
|
|
4283
|
+
category: 'System',
|
|
4284
|
+
action: () => {
|
|
4285
|
+
if (!activeId) return;
|
|
4286
|
+
const ms = managed.get(activeId);
|
|
4287
|
+
if (ms) ms.term.clear();
|
|
4288
|
+
},
|
|
4289
|
+
},
|
|
4290
|
+
{
|
|
4291
|
+
icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>',
|
|
4292
|
+
label: 'About',
|
|
4293
|
+
category: 'System',
|
|
4294
|
+
action: () => {
|
|
4295
|
+
const ver = window._termbeamVersion || 'TermBeam';
|
|
4296
|
+
const overlay = document.createElement('div');
|
|
4297
|
+
overlay.style.cssText =
|
|
4298
|
+
'position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:300;display:flex;align-items:center;justify-content:center;';
|
|
4299
|
+
const box = document.createElement('div');
|
|
4300
|
+
box.style.cssText =
|
|
4301
|
+
'background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:24px;max-width:90vw;width:320px;text-align:center;';
|
|
4302
|
+
box.innerHTML =
|
|
4303
|
+
'<div style="font-size:24px;margin-bottom:8px;">⚡</div>' +
|
|
4304
|
+
'<div style="font-size:16px;font-weight:600;color:var(--text);margin-bottom:4px;">TermBeam</div>' +
|
|
4305
|
+
'<div style="font-size:13px;color:var(--text-secondary);margin-bottom:16px;">' +
|
|
4306
|
+
esc(ver) +
|
|
4307
|
+
'</div>' +
|
|
4308
|
+
'<div style="font-size:12px;color:var(--text-secondary);margin-bottom:16px;">Terminal in your browser, optimized for mobile.</div>' +
|
|
4309
|
+
'<div style="display:flex;gap:16px;justify-content:center;margin-bottom:16px;">' +
|
|
4310
|
+
'<a href="https://github.com/dorlugasigal/TermBeam" target="_blank" rel="noopener" style="color:var(--accent);font-size:12px;text-decoration:none;">GitHub</a>' +
|
|
4311
|
+
'<a href="https://dorlugasigal.github.io/TermBeam/" target="_blank" rel="noopener" style="color:var(--accent);font-size:12px;text-decoration:none;">Docs</a>' +
|
|
4312
|
+
'<a href="https://termbeam.pages.dev" target="_blank" rel="noopener" style="color:var(--accent);font-size:12px;text-decoration:none;">Website</a>' +
|
|
4313
|
+
'</div>';
|
|
4314
|
+
const btn = document.createElement('button');
|
|
4315
|
+
btn.textContent = 'Close';
|
|
4316
|
+
btn.style.cssText =
|
|
4317
|
+
'padding:6px 20px;border-radius:6px;border:none;background:var(--accent);color:#fff;font-size:13px;font-weight:600;cursor:pointer;';
|
|
4318
|
+
btn.onclick = () => overlay.remove();
|
|
4319
|
+
box.appendChild(btn);
|
|
4320
|
+
overlay.appendChild(box);
|
|
4321
|
+
overlay.addEventListener('click', (e) => {
|
|
4322
|
+
if (e.target === overlay) overlay.remove();
|
|
4323
|
+
});
|
|
4324
|
+
document.body.appendChild(overlay);
|
|
4325
|
+
},
|
|
4326
|
+
},
|
|
4327
|
+
];
|
|
4328
|
+
|
|
4329
|
+
const backdrop = document.getElementById('palette-backdrop');
|
|
4330
|
+
const panel = document.getElementById('palette-panel');
|
|
4331
|
+
const body = document.getElementById('palette-body');
|
|
4332
|
+
|
|
4333
|
+
function renderPalette() {
|
|
4334
|
+
const grouped = {};
|
|
4335
|
+
paletteActions.forEach((a) => {
|
|
4336
|
+
if (!grouped[a.category]) grouped[a.category] = [];
|
|
4337
|
+
grouped[a.category].push(a);
|
|
4338
|
+
});
|
|
4339
|
+
body.innerHTML = '';
|
|
4340
|
+
Object.keys(grouped).forEach((cat) => {
|
|
4341
|
+
const header = document.createElement('div');
|
|
4342
|
+
header.className = 'palette-category';
|
|
4343
|
+
header.textContent = cat;
|
|
4344
|
+
body.appendChild(header);
|
|
4345
|
+
grouped[cat].forEach((a) => {
|
|
4346
|
+
const btn = document.createElement('button');
|
|
4347
|
+
btn.className = 'palette-action';
|
|
4348
|
+
btn.innerHTML =
|
|
4349
|
+
'<span class="palette-action-icon">' + a.icon + '</span>' + esc(a.label);
|
|
4350
|
+
btn.addEventListener('click', () => {
|
|
4351
|
+
if (!a.keepOpen) closePalette();
|
|
4352
|
+
a.action();
|
|
4353
|
+
});
|
|
4354
|
+
body.appendChild(btn);
|
|
4355
|
+
});
|
|
4356
|
+
});
|
|
4357
|
+
}
|
|
4358
|
+
|
|
4359
|
+
function openPalette() {
|
|
4360
|
+
backdrop.classList.add('open');
|
|
4361
|
+
panel.classList.add('open');
|
|
4362
|
+
}
|
|
4363
|
+
|
|
4364
|
+
function closePalette() {
|
|
4365
|
+
backdrop.classList.remove('open');
|
|
4366
|
+
panel.classList.remove('open');
|
|
4367
|
+
}
|
|
4368
|
+
|
|
4369
|
+
function togglePalette() {
|
|
4370
|
+
if (panel.classList.contains('open')) closePalette();
|
|
4371
|
+
else openPalette();
|
|
4372
|
+
}
|
|
4373
|
+
|
|
4374
|
+
backdrop.addEventListener('click', closePalette);
|
|
4375
|
+
document.getElementById('palette-close').addEventListener('click', closePalette);
|
|
4376
|
+
document.getElementById('palette-trigger').addEventListener('click', togglePalette);
|
|
4377
|
+
|
|
4378
|
+
document.addEventListener('keydown', (e) => {
|
|
4379
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
|
4380
|
+
e.preventDefault();
|
|
4381
|
+
togglePalette();
|
|
4382
|
+
}
|
|
4383
|
+
if (e.key === 'Escape' && panel.classList.contains('open')) {
|
|
4384
|
+
closePalette();
|
|
4385
|
+
}
|
|
4386
|
+
});
|
|
4387
|
+
|
|
4388
|
+
renderPalette();
|
|
4389
|
+
})();
|
|
4390
|
+
|
|
4391
|
+
// ===== Theme Sub-Panel =====
|
|
4392
|
+
(function setupThemeSubpanel() {
|
|
4393
|
+
const subpanel = document.getElementById('theme-subpanel');
|
|
4394
|
+
const list = document.getElementById('theme-subpanel-list');
|
|
4395
|
+
document.getElementById('theme-subpanel-close').addEventListener('click', () => {
|
|
4396
|
+
subpanel.classList.remove('open');
|
|
4397
|
+
});
|
|
4398
|
+
function renderThemeList() {
|
|
4399
|
+
const cur = getTheme();
|
|
4400
|
+
list.innerHTML = THEMES.map(
|
|
4401
|
+
(t) =>
|
|
4402
|
+
'<button class="theme-subpanel-item' +
|
|
4403
|
+
(t.id === cur ? ' active' : '') +
|
|
4404
|
+
'" data-tid="' +
|
|
4405
|
+
t.id +
|
|
4406
|
+
'"><span class="theme-subpanel-swatch" style="background:' +
|
|
4407
|
+
t.bg +
|
|
4408
|
+
'"></span>' +
|
|
4409
|
+
esc(t.name) +
|
|
4410
|
+
'</button>',
|
|
4411
|
+
).join('');
|
|
4412
|
+
list.querySelectorAll('.theme-subpanel-item').forEach((btn) => {
|
|
4413
|
+
btn.addEventListener('click', () => {
|
|
4414
|
+
applyTheme(btn.dataset.tid);
|
|
4415
|
+
renderThemeList();
|
|
4416
|
+
});
|
|
4417
|
+
});
|
|
4418
|
+
}
|
|
4419
|
+
window.openThemeSubpanel = function () {
|
|
4420
|
+
renderThemeList();
|
|
4421
|
+
subpanel.classList.add('open');
|
|
4422
|
+
};
|
|
4423
|
+
document.addEventListener('keydown', (e) => {
|
|
4424
|
+
if (e.key === 'Escape' && subpanel.classList.contains('open')) {
|
|
4425
|
+
subpanel.classList.remove('open');
|
|
4426
|
+
}
|
|
4427
|
+
});
|
|
4428
|
+
})();
|
|
4569
4429
|
|
|
4570
4430
|
// ===== Service Worker =====
|
|
4571
|
-
|
|
4572
|
-
navigator.serviceWorker.register('/sw.js').catch(() => {});
|
|
4573
|
-
}
|
|
4431
|
+
registerServiceWorker();
|
|
4574
4432
|
</script>
|
|
4575
4433
|
</body>
|
|
4576
4434
|
</html>
|