termbeam 1.3.0 → 1.5.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 +10 -1
- package/package.json +2 -2
- package/public/index.html +326 -33
- package/public/terminal.html +1493 -113
- package/src/auth.js +96 -12
- package/src/routes.js +6 -2
- package/src/sessions.js +15 -3
- package/src/websocket.js +31 -4
package/public/terminal.html
CHANGED
|
@@ -63,6 +63,216 @@
|
|
|
63
63
|
--key-special-bg: #adb5bd;
|
|
64
64
|
--overlay-bg: rgba(0, 0, 0, 0.5);
|
|
65
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
|
+
}
|
|
66
276
|
@font-face {
|
|
67
277
|
font-family: 'NerdFont';
|
|
68
278
|
src: url('https://cdn.jsdelivr.net/gh/ryanoasis/nerd-fonts@latest/patched-fonts/JetBrainsMono/Ligatures/Regular/JetBrainsMonoNerdFont-Regular.ttf')
|
|
@@ -248,6 +458,23 @@
|
|
|
248
458
|
.tab-close:hover {
|
|
249
459
|
color: var(--danger);
|
|
250
460
|
}
|
|
461
|
+
.tab-unread {
|
|
462
|
+
width: 7px;
|
|
463
|
+
height: 7px;
|
|
464
|
+
border-radius: 50%;
|
|
465
|
+
background: var(--accent);
|
|
466
|
+
flex-shrink: 0;
|
|
467
|
+
animation: tab-pulse 1.5s ease-in-out infinite;
|
|
468
|
+
}
|
|
469
|
+
@keyframes tab-pulse {
|
|
470
|
+
0%,
|
|
471
|
+
100% {
|
|
472
|
+
opacity: 1;
|
|
473
|
+
}
|
|
474
|
+
50% {
|
|
475
|
+
opacity: 0.4;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
251
478
|
/* ===== Tab Preview ===== */
|
|
252
479
|
#tab-preview {
|
|
253
480
|
display: none;
|
|
@@ -382,6 +609,52 @@
|
|
|
382
609
|
font-size: 14px;
|
|
383
610
|
font-weight: 600;
|
|
384
611
|
}
|
|
612
|
+
.theme-wrap {
|
|
613
|
+
position: relative;
|
|
614
|
+
display: none;
|
|
615
|
+
align-items: center;
|
|
616
|
+
}
|
|
617
|
+
.theme-picker {
|
|
618
|
+
display: none;
|
|
619
|
+
position: absolute;
|
|
620
|
+
top: calc(100% + 4px);
|
|
621
|
+
right: 0;
|
|
622
|
+
background: var(--surface);
|
|
623
|
+
border: 1px solid var(--border);
|
|
624
|
+
border-radius: 8px;
|
|
625
|
+
min-width: 160px;
|
|
626
|
+
padding: 4px 0;
|
|
627
|
+
z-index: 200;
|
|
628
|
+
box-shadow: 0 4px 12px var(--shadow);
|
|
629
|
+
}
|
|
630
|
+
.theme-picker.open {
|
|
631
|
+
display: block;
|
|
632
|
+
}
|
|
633
|
+
.theme-option {
|
|
634
|
+
display: flex;
|
|
635
|
+
align-items: center;
|
|
636
|
+
gap: 8px;
|
|
637
|
+
padding: 7px 12px;
|
|
638
|
+
cursor: pointer;
|
|
639
|
+
font-size: 13px;
|
|
640
|
+
color: var(--text);
|
|
641
|
+
transition: background 0.1s;
|
|
642
|
+
white-space: nowrap;
|
|
643
|
+
}
|
|
644
|
+
.theme-option:hover {
|
|
645
|
+
background: var(--border);
|
|
646
|
+
}
|
|
647
|
+
.theme-option.active {
|
|
648
|
+
color: var(--accent);
|
|
649
|
+
}
|
|
650
|
+
.theme-swatch {
|
|
651
|
+
width: 14px;
|
|
652
|
+
height: 14px;
|
|
653
|
+
border-radius: 50%;
|
|
654
|
+
display: inline-block;
|
|
655
|
+
flex-shrink: 0;
|
|
656
|
+
border: 1px solid rgba(128, 128, 128, 0.3);
|
|
657
|
+
}
|
|
385
658
|
#stop-btn {
|
|
386
659
|
background: none;
|
|
387
660
|
border: none;
|
|
@@ -389,7 +662,7 @@
|
|
|
389
662
|
height: 30px;
|
|
390
663
|
border-radius: 8px;
|
|
391
664
|
cursor: pointer;
|
|
392
|
-
display:
|
|
665
|
+
display: none;
|
|
393
666
|
align-items: center;
|
|
394
667
|
justify-content: center;
|
|
395
668
|
gap: 4px;
|
|
@@ -412,6 +685,75 @@
|
|
|
412
685
|
transform: scale(0.9);
|
|
413
686
|
}
|
|
414
687
|
|
|
688
|
+
/* ===== Notification Toggle ===== */
|
|
689
|
+
.notify-btn.active {
|
|
690
|
+
color: var(--accent) !important;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/* ===== Search Bar ===== */
|
|
694
|
+
.search-bar {
|
|
695
|
+
display: none;
|
|
696
|
+
position: absolute;
|
|
697
|
+
top: 4px;
|
|
698
|
+
right: 12px;
|
|
699
|
+
z-index: 100;
|
|
700
|
+
background: var(--surface);
|
|
701
|
+
border: 1px solid var(--border);
|
|
702
|
+
border-radius: 8px;
|
|
703
|
+
padding: 4px 6px;
|
|
704
|
+
gap: 4px;
|
|
705
|
+
align-items: center;
|
|
706
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
707
|
+
font-size: 13px;
|
|
708
|
+
color: var(--text);
|
|
709
|
+
}
|
|
710
|
+
.search-bar.visible {
|
|
711
|
+
display: flex;
|
|
712
|
+
}
|
|
713
|
+
.search-bar input {
|
|
714
|
+
background: var(--bg);
|
|
715
|
+
border: 1px solid var(--border);
|
|
716
|
+
border-radius: 4px;
|
|
717
|
+
color: var(--text);
|
|
718
|
+
padding: 3px 6px;
|
|
719
|
+
font-size: 13px;
|
|
720
|
+
font-family: inherit;
|
|
721
|
+
width: 160px;
|
|
722
|
+
outline: none;
|
|
723
|
+
}
|
|
724
|
+
.search-bar input:focus {
|
|
725
|
+
border-color: var(--accent);
|
|
726
|
+
}
|
|
727
|
+
.search-bar .search-count {
|
|
728
|
+
color: var(--text-secondary);
|
|
729
|
+
font-size: 11px;
|
|
730
|
+
min-width: 40px;
|
|
731
|
+
text-align: center;
|
|
732
|
+
white-space: nowrap;
|
|
733
|
+
}
|
|
734
|
+
.search-bar button {
|
|
735
|
+
background: none;
|
|
736
|
+
border: 1px solid transparent;
|
|
737
|
+
color: var(--text-dim);
|
|
738
|
+
width: 24px;
|
|
739
|
+
height: 24px;
|
|
740
|
+
border-radius: 4px;
|
|
741
|
+
cursor: pointer;
|
|
742
|
+
display: flex;
|
|
743
|
+
align-items: center;
|
|
744
|
+
justify-content: center;
|
|
745
|
+
font-size: 13px;
|
|
746
|
+
padding: 0;
|
|
747
|
+
}
|
|
748
|
+
.search-bar button:hover {
|
|
749
|
+
background: var(--border);
|
|
750
|
+
color: var(--text);
|
|
751
|
+
}
|
|
752
|
+
.search-bar button.active {
|
|
753
|
+
color: var(--accent);
|
|
754
|
+
border-color: var(--accent);
|
|
755
|
+
}
|
|
756
|
+
|
|
415
757
|
/* ===== Terminals Wrapper ===== */
|
|
416
758
|
#terminals-wrapper {
|
|
417
759
|
position: absolute;
|
|
@@ -467,6 +809,9 @@
|
|
|
467
809
|
[data-theme='light'] #key-bar {
|
|
468
810
|
background: #d1d3d9;
|
|
469
811
|
}
|
|
812
|
+
[data-theme='solarized-light'] #key-bar {
|
|
813
|
+
background: #d1d3d9;
|
|
814
|
+
}
|
|
470
815
|
.key-row {
|
|
471
816
|
display: flex;
|
|
472
817
|
align-items: center;
|
|
@@ -502,6 +847,10 @@
|
|
|
502
847
|
color: #000;
|
|
503
848
|
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
|
|
504
849
|
}
|
|
850
|
+
[data-theme='solarized-light'] .key-btn {
|
|
851
|
+
color: #000;
|
|
852
|
+
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
|
|
853
|
+
}
|
|
505
854
|
.key-btn:active {
|
|
506
855
|
background: #6e6e72;
|
|
507
856
|
transform: scale(0.95);
|
|
@@ -510,6 +859,9 @@
|
|
|
510
859
|
[data-theme='light'] .key-btn:active {
|
|
511
860
|
background: #c8c8cc;
|
|
512
861
|
}
|
|
862
|
+
[data-theme='solarized-light'] .key-btn:active {
|
|
863
|
+
background: #c8c8cc;
|
|
864
|
+
}
|
|
513
865
|
.key-btn.flash {
|
|
514
866
|
background: #fff !important;
|
|
515
867
|
color: #000 !important;
|
|
@@ -519,6 +871,10 @@
|
|
|
519
871
|
background: #333 !important;
|
|
520
872
|
color: #fff !important;
|
|
521
873
|
}
|
|
874
|
+
[data-theme='solarized-light'] .key-btn.flash {
|
|
875
|
+
background: #333 !important;
|
|
876
|
+
color: #fff !important;
|
|
877
|
+
}
|
|
522
878
|
.key-btn.modifier,
|
|
523
879
|
.key-btn.special {
|
|
524
880
|
background: var(--key-special-bg);
|
|
@@ -530,6 +886,11 @@
|
|
|
530
886
|
background: var(--key-special-bg);
|
|
531
887
|
color: #000;
|
|
532
888
|
}
|
|
889
|
+
[data-theme='solarized-light'] .key-btn.modifier,
|
|
890
|
+
[data-theme='solarized-light'] .key-btn.special {
|
|
891
|
+
background: var(--key-special-bg);
|
|
892
|
+
color: #000;
|
|
893
|
+
}
|
|
533
894
|
.key-btn.modifier.active {
|
|
534
895
|
background: var(--accent);
|
|
535
896
|
color: #fff;
|
|
@@ -563,6 +924,10 @@
|
|
|
563
924
|
background: #fee2e2;
|
|
564
925
|
color: #dc2626;
|
|
565
926
|
}
|
|
927
|
+
[data-theme='solarized-light'] .key-btn.key-danger {
|
|
928
|
+
background: #fee2e2;
|
|
929
|
+
color: #dc2626;
|
|
930
|
+
}
|
|
566
931
|
.key-btn.key-danger:active {
|
|
567
932
|
background: var(--danger);
|
|
568
933
|
color: #fff;
|
|
@@ -1372,17 +1737,12 @@
|
|
|
1372
1737
|
#tab-list {
|
|
1373
1738
|
display: none;
|
|
1374
1739
|
}
|
|
1375
|
-
|
|
1376
|
-
display: none;
|
|
1377
|
-
}
|
|
1378
|
-
#version-text {
|
|
1379
|
-
display: none;
|
|
1380
|
-
}
|
|
1740
|
+
|
|
1381
1741
|
#back-btn {
|
|
1382
1742
|
display: none;
|
|
1383
1743
|
}
|
|
1384
|
-
#theme-
|
|
1385
|
-
display:
|
|
1744
|
+
#theme-wrap {
|
|
1745
|
+
display: none;
|
|
1386
1746
|
}
|
|
1387
1747
|
#stop-btn {
|
|
1388
1748
|
padding: 0 8px;
|
|
@@ -1399,6 +1759,195 @@
|
|
|
1399
1759
|
width: auto;
|
|
1400
1760
|
}
|
|
1401
1761
|
}
|
|
1762
|
+
|
|
1763
|
+
/* ===== Command Palette / Tool Panel ===== */
|
|
1764
|
+
.palette-backdrop {
|
|
1765
|
+
position: fixed;
|
|
1766
|
+
inset: 0;
|
|
1767
|
+
background: rgba(0, 0, 0, 0.4);
|
|
1768
|
+
z-index: 250;
|
|
1769
|
+
opacity: 0;
|
|
1770
|
+
pointer-events: none;
|
|
1771
|
+
transition: opacity 0.3s;
|
|
1772
|
+
}
|
|
1773
|
+
.palette-backdrop.open {
|
|
1774
|
+
opacity: 1;
|
|
1775
|
+
pointer-events: auto;
|
|
1776
|
+
}
|
|
1777
|
+
.palette-panel {
|
|
1778
|
+
position: fixed;
|
|
1779
|
+
top: 0;
|
|
1780
|
+
right: 0;
|
|
1781
|
+
width: 280px;
|
|
1782
|
+
max-width: 85vw;
|
|
1783
|
+
height: 100%;
|
|
1784
|
+
background: var(--surface);
|
|
1785
|
+
border-left: 1px solid var(--border);
|
|
1786
|
+
z-index: 260;
|
|
1787
|
+
transform: translateX(100%);
|
|
1788
|
+
transition: transform 0.3s ease;
|
|
1789
|
+
display: flex;
|
|
1790
|
+
flex-direction: column;
|
|
1791
|
+
overflow-y: auto;
|
|
1792
|
+
-webkit-overflow-scrolling: touch;
|
|
1793
|
+
}
|
|
1794
|
+
.palette-panel.open {
|
|
1795
|
+
transform: translateX(0);
|
|
1796
|
+
}
|
|
1797
|
+
.palette-header {
|
|
1798
|
+
display: flex;
|
|
1799
|
+
align-items: center;
|
|
1800
|
+
justify-content: space-between;
|
|
1801
|
+
padding: 14px 16px;
|
|
1802
|
+
border-bottom: 1px solid var(--border);
|
|
1803
|
+
font-weight: 600;
|
|
1804
|
+
font-size: 15px;
|
|
1805
|
+
color: var(--text);
|
|
1806
|
+
}
|
|
1807
|
+
.palette-close {
|
|
1808
|
+
background: none;
|
|
1809
|
+
border: none;
|
|
1810
|
+
color: var(--text-secondary);
|
|
1811
|
+
font-size: 18px;
|
|
1812
|
+
cursor: pointer;
|
|
1813
|
+
padding: 4px 8px;
|
|
1814
|
+
border-radius: 6px;
|
|
1815
|
+
}
|
|
1816
|
+
.palette-close:hover {
|
|
1817
|
+
background: var(--hover-bg, rgba(255, 255, 255, 0.08));
|
|
1818
|
+
color: var(--text);
|
|
1819
|
+
}
|
|
1820
|
+
.palette-body {
|
|
1821
|
+
padding: 8px 0;
|
|
1822
|
+
flex: 1;
|
|
1823
|
+
}
|
|
1824
|
+
.palette-category {
|
|
1825
|
+
padding: 8px 16px 4px;
|
|
1826
|
+
font-size: 11px;
|
|
1827
|
+
font-weight: 600;
|
|
1828
|
+
text-transform: uppercase;
|
|
1829
|
+
letter-spacing: 0.5px;
|
|
1830
|
+
color: var(--text-muted, var(--text-secondary));
|
|
1831
|
+
}
|
|
1832
|
+
.palette-action {
|
|
1833
|
+
display: flex;
|
|
1834
|
+
align-items: center;
|
|
1835
|
+
gap: 10px;
|
|
1836
|
+
width: 100%;
|
|
1837
|
+
padding: 10px 16px;
|
|
1838
|
+
background: none;
|
|
1839
|
+
border: none;
|
|
1840
|
+
color: var(--text);
|
|
1841
|
+
font-size: 13px;
|
|
1842
|
+
cursor: pointer;
|
|
1843
|
+
text-align: left;
|
|
1844
|
+
transition: background 0.15s;
|
|
1845
|
+
}
|
|
1846
|
+
.palette-action:hover {
|
|
1847
|
+
background: rgba(255, 255, 255, 0.06);
|
|
1848
|
+
}
|
|
1849
|
+
.palette-action:active {
|
|
1850
|
+
background: rgba(255, 255, 255, 0.1);
|
|
1851
|
+
}
|
|
1852
|
+
[data-theme='light'] .palette-action:hover,
|
|
1853
|
+
[data-theme='solarized-light'] .palette-action:hover {
|
|
1854
|
+
background: rgba(0, 0, 0, 0.06);
|
|
1855
|
+
}
|
|
1856
|
+
[data-theme='light'] .palette-action:active,
|
|
1857
|
+
[data-theme='solarized-light'] .palette-action:active {
|
|
1858
|
+
background: rgba(0, 0, 0, 0.1);
|
|
1859
|
+
}
|
|
1860
|
+
.palette-action-icon {
|
|
1861
|
+
width: 20px;
|
|
1862
|
+
height: 20px;
|
|
1863
|
+
display: flex;
|
|
1864
|
+
align-items: center;
|
|
1865
|
+
justify-content: center;
|
|
1866
|
+
flex-shrink: 0;
|
|
1867
|
+
color: var(--text-secondary);
|
|
1868
|
+
}
|
|
1869
|
+
.palette-action-icon svg {
|
|
1870
|
+
width: 16px;
|
|
1871
|
+
}
|
|
1872
|
+
/* ===== Theme Sub-Panel ===== */
|
|
1873
|
+
.theme-subpanel {
|
|
1874
|
+
display: none;
|
|
1875
|
+
position: fixed;
|
|
1876
|
+
top: 50%;
|
|
1877
|
+
left: 50%;
|
|
1878
|
+
transform: translate(-50%, -50%);
|
|
1879
|
+
width: 240px;
|
|
1880
|
+
max-height: 70vh;
|
|
1881
|
+
background: var(--surface);
|
|
1882
|
+
border: 1px solid var(--border);
|
|
1883
|
+
border-radius: 12px;
|
|
1884
|
+
z-index: 270;
|
|
1885
|
+
overflow-y: auto;
|
|
1886
|
+
-webkit-overflow-scrolling: touch;
|
|
1887
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
1888
|
+
}
|
|
1889
|
+
.theme-subpanel.open {
|
|
1890
|
+
display: block;
|
|
1891
|
+
}
|
|
1892
|
+
.theme-subpanel-header {
|
|
1893
|
+
display: flex;
|
|
1894
|
+
align-items: center;
|
|
1895
|
+
justify-content: space-between;
|
|
1896
|
+
padding: 12px 14px;
|
|
1897
|
+
border-bottom: 1px solid var(--border);
|
|
1898
|
+
font-weight: 600;
|
|
1899
|
+
font-size: 13px;
|
|
1900
|
+
color: var(--text);
|
|
1901
|
+
}
|
|
1902
|
+
.theme-subpanel-close {
|
|
1903
|
+
background: none;
|
|
1904
|
+
border: none;
|
|
1905
|
+
color: var(--text-secondary);
|
|
1906
|
+
font-size: 16px;
|
|
1907
|
+
cursor: pointer;
|
|
1908
|
+
padding: 2px 6px;
|
|
1909
|
+
border-radius: 6px;
|
|
1910
|
+
}
|
|
1911
|
+
.theme-subpanel-close:hover {
|
|
1912
|
+
background: var(--hover-bg, rgba(255, 255, 255, 0.08));
|
|
1913
|
+
color: var(--text);
|
|
1914
|
+
}
|
|
1915
|
+
.theme-subpanel-list {
|
|
1916
|
+
padding: 6px 0;
|
|
1917
|
+
}
|
|
1918
|
+
.theme-subpanel-item {
|
|
1919
|
+
display: flex;
|
|
1920
|
+
align-items: center;
|
|
1921
|
+
gap: 10px;
|
|
1922
|
+
width: 100%;
|
|
1923
|
+
padding: 9px 14px;
|
|
1924
|
+
background: none;
|
|
1925
|
+
border: none;
|
|
1926
|
+
color: var(--text);
|
|
1927
|
+
font-size: 13px;
|
|
1928
|
+
cursor: pointer;
|
|
1929
|
+
text-align: left;
|
|
1930
|
+
transition: background 0.15s;
|
|
1931
|
+
}
|
|
1932
|
+
.theme-subpanel-item:hover {
|
|
1933
|
+
background: rgba(255, 255, 255, 0.06);
|
|
1934
|
+
}
|
|
1935
|
+
[data-theme='light'] .theme-subpanel-item:hover,
|
|
1936
|
+
[data-theme='solarized-light'] .theme-subpanel-item:hover {
|
|
1937
|
+
background: rgba(0, 0, 0, 0.06);
|
|
1938
|
+
}
|
|
1939
|
+
.theme-subpanel-item.active {
|
|
1940
|
+
color: var(--accent);
|
|
1941
|
+
}
|
|
1942
|
+
.theme-subpanel-swatch {
|
|
1943
|
+
width: 14px;
|
|
1944
|
+
height: 14px;
|
|
1945
|
+
border-radius: 50%;
|
|
1946
|
+
flex-shrink: 0;
|
|
1947
|
+
border: 1px solid rgba(128, 128, 128, 0.3);
|
|
1948
|
+
}
|
|
1949
|
+
height: 16px;
|
|
1950
|
+
}
|
|
1402
1951
|
</style>
|
|
1403
1952
|
</head>
|
|
1404
1953
|
<body>
|
|
@@ -1496,7 +2045,6 @@
|
|
|
1496
2045
|
</div>
|
|
1497
2046
|
<div id="tab-list"></div>
|
|
1498
2047
|
<div class="right">
|
|
1499
|
-
<span id="version-text" style="font-size: 11px; color: var(--text-muted)"></span>
|
|
1500
2048
|
<button class="tab-bar-btn" id="tab-new-btn" title="New session">
|
|
1501
2049
|
<svg
|
|
1502
2050
|
width="14"
|
|
@@ -1511,34 +2059,67 @@
|
|
|
1511
2059
|
<line x1="5" y1="12" x2="19" y2="12" /></svg
|
|
1512
2060
|
><span class="new-btn-label">New</span>
|
|
1513
2061
|
</button>
|
|
1514
|
-
<
|
|
1515
|
-
<
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
2062
|
+
<div class="theme-wrap" id="theme-wrap">
|
|
2063
|
+
<button class="bar-btn" id="theme-toggle" title="Switch theme">
|
|
2064
|
+
<svg
|
|
2065
|
+
width="16"
|
|
2066
|
+
height="16"
|
|
2067
|
+
viewBox="0 0 24 24"
|
|
2068
|
+
fill="none"
|
|
2069
|
+
stroke="currentColor"
|
|
2070
|
+
stroke-width="2"
|
|
2071
|
+
stroke-linecap="round"
|
|
2072
|
+
stroke-linejoin="round"
|
|
2073
|
+
>
|
|
2074
|
+
<circle cx="13.5" cy="6.5" r=".5" fill="currentColor" />
|
|
2075
|
+
<circle cx="17.5" cy="10.5" r=".5" fill="currentColor" />
|
|
2076
|
+
<circle cx="8.5" cy="7.5" r=".5" fill="currentColor" />
|
|
2077
|
+
<circle cx="6.5" cy="12.5" r=".5" fill="currentColor" />
|
|
2078
|
+
<path
|
|
2079
|
+
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"
|
|
2080
|
+
/>
|
|
2081
|
+
</svg>
|
|
2082
|
+
</button>
|
|
2083
|
+
<div class="theme-picker" id="theme-picker">
|
|
2084
|
+
<div class="theme-option" data-theme-option="dark">
|
|
2085
|
+
<span class="theme-swatch" style="background: #1e1e1e"></span>Dark
|
|
2086
|
+
</div>
|
|
2087
|
+
<div class="theme-option" data-theme-option="light">
|
|
2088
|
+
<span class="theme-swatch" style="background: #ffffff"></span>Light
|
|
2089
|
+
</div>
|
|
2090
|
+
<div class="theme-option" data-theme-option="monokai">
|
|
2091
|
+
<span class="theme-swatch" style="background: #272822"></span>Monokai
|
|
2092
|
+
</div>
|
|
2093
|
+
<div class="theme-option" data-theme-option="solarized-dark">
|
|
2094
|
+
<span class="theme-swatch" style="background: #002b36"></span>Solarized Dark
|
|
2095
|
+
</div>
|
|
2096
|
+
<div class="theme-option" data-theme-option="solarized-light">
|
|
2097
|
+
<span class="theme-swatch" style="background: #fdf6e3"></span>Solarized Light
|
|
2098
|
+
</div>
|
|
2099
|
+
<div class="theme-option" data-theme-option="nord">
|
|
2100
|
+
<span class="theme-swatch" style="background: #2e3440"></span>Nord
|
|
2101
|
+
</div>
|
|
2102
|
+
<div class="theme-option" data-theme-option="dracula">
|
|
2103
|
+
<span class="theme-swatch" style="background: #282a36"></span>Dracula
|
|
2104
|
+
</div>
|
|
2105
|
+
<div class="theme-option" data-theme-option="github-dark">
|
|
2106
|
+
<span class="theme-swatch" style="background: #0d1117"></span>GitHub Dark
|
|
2107
|
+
</div>
|
|
2108
|
+
<div class="theme-option" data-theme-option="one-dark">
|
|
2109
|
+
<span class="theme-swatch" style="background: #282c34"></span>One Dark
|
|
2110
|
+
</div>
|
|
2111
|
+
<div class="theme-option" data-theme-option="catppuccin">
|
|
2112
|
+
<span class="theme-swatch" style="background: #1e1e2e"></span>Catppuccin
|
|
2113
|
+
</div>
|
|
2114
|
+
<div class="theme-option" data-theme-option="gruvbox">
|
|
2115
|
+
<span class="theme-swatch" style="background: #282828"></span>Gruvbox
|
|
2116
|
+
</div>
|
|
2117
|
+
<div class="theme-option" data-theme-option="night-owl">
|
|
2118
|
+
<span class="theme-swatch" style="background: #011627"></span>Night Owl
|
|
2119
|
+
</div>
|
|
2120
|
+
</div>
|
|
1532
2121
|
</div>
|
|
1533
|
-
<button
|
|
1534
|
-
class="bar-btn"
|
|
1535
|
-
id="preview-btn"
|
|
1536
|
-
title="Preview local port"
|
|
1537
|
-
onclick="openPreviewModal()"
|
|
1538
|
-
>
|
|
1539
|
-
🌐
|
|
1540
|
-
</button>
|
|
1541
|
-
<button class="bar-btn" id="share-btn" title="Share link">
|
|
2122
|
+
<button class="bar-btn" id="palette-trigger" title="Tools (Ctrl+K)">
|
|
1542
2123
|
<svg
|
|
1543
2124
|
width="16"
|
|
1544
2125
|
height="16"
|
|
@@ -1549,49 +2130,10 @@
|
|
|
1549
2130
|
stroke-linecap="round"
|
|
1550
2131
|
stroke-linejoin="round"
|
|
1551
2132
|
>
|
|
1552
|
-
<
|
|
1553
|
-
<
|
|
1554
|
-
<
|
|
1555
|
-
<
|
|
1556
|
-
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49" />
|
|
1557
|
-
</svg>
|
|
1558
|
-
</button>
|
|
1559
|
-
<button class="bar-btn" id="refresh-btn" title="Refresh app">
|
|
1560
|
-
<svg
|
|
1561
|
-
width="16"
|
|
1562
|
-
height="16"
|
|
1563
|
-
viewBox="0 0 24 24"
|
|
1564
|
-
fill="none"
|
|
1565
|
-
stroke="currentColor"
|
|
1566
|
-
stroke-width="2"
|
|
1567
|
-
stroke-linecap="round"
|
|
1568
|
-
stroke-linejoin="round"
|
|
1569
|
-
>
|
|
1570
|
-
<polyline points="23 4 23 10 17 10" />
|
|
1571
|
-
<polyline points="1 20 1 14 7 14" />
|
|
1572
|
-
<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" />
|
|
1573
|
-
</svg>
|
|
1574
|
-
</button>
|
|
1575
|
-
<button class="bar-btn" id="theme-toggle" title="Toggle theme">
|
|
1576
|
-
<svg
|
|
1577
|
-
width="16"
|
|
1578
|
-
height="16"
|
|
1579
|
-
viewBox="0 0 24 24"
|
|
1580
|
-
fill="none"
|
|
1581
|
-
stroke="currentColor"
|
|
1582
|
-
stroke-width="2"
|
|
1583
|
-
stroke-linecap="round"
|
|
1584
|
-
stroke-linejoin="round"
|
|
1585
|
-
>
|
|
1586
|
-
<circle cx="12" cy="12" r="5" />
|
|
1587
|
-
<line x1="12" y1="1" x2="12" y2="3" />
|
|
1588
|
-
<line x1="12" y1="21" x2="12" y2="23" />
|
|
1589
|
-
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
|
1590
|
-
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
|
1591
|
-
<line x1="1" y1="12" x2="3" y2="12" />
|
|
1592
|
-
<line x1="21" y1="12" x2="23" y2="12" />
|
|
1593
|
-
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
|
1594
|
-
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
|
2133
|
+
<rect x="3" y="3" width="7" height="7" rx="1" />
|
|
2134
|
+
<rect x="14" y="3" width="7" height="7" rx="1" />
|
|
2135
|
+
<rect x="3" y="14" width="7" height="7" rx="1" />
|
|
2136
|
+
<rect x="14" y="14" width="7" height="7" rx="1" />
|
|
1595
2137
|
</svg>
|
|
1596
2138
|
</button>
|
|
1597
2139
|
<button id="stop-btn" title="Stop session">
|
|
@@ -1612,7 +2154,16 @@
|
|
|
1612
2154
|
</div>
|
|
1613
2155
|
|
|
1614
2156
|
<!-- Terminals Wrapper (panes created dynamically) -->
|
|
1615
|
-
<div id="terminals-wrapper"
|
|
2157
|
+
<div id="terminals-wrapper">
|
|
2158
|
+
<div class="search-bar" id="search-bar">
|
|
2159
|
+
<input type="text" id="search-input" placeholder="Search…" autocomplete="off" />
|
|
2160
|
+
<span class="search-count" id="search-count"></span>
|
|
2161
|
+
<button id="search-regex" title="Regex">.*</button>
|
|
2162
|
+
<button id="search-prev" title="Previous">▲</button>
|
|
2163
|
+
<button id="search-next" title="Next">▼</button>
|
|
2164
|
+
<button id="search-close" title="Close">✕</button>
|
|
2165
|
+
</div>
|
|
2166
|
+
</div>
|
|
1616
2167
|
|
|
1617
2168
|
<div id="copy-toast">Copied!</div>
|
|
1618
2169
|
|
|
@@ -1631,7 +2182,7 @@
|
|
|
1631
2182
|
<div class="key-row">
|
|
1632
2183
|
<button class="key-btn modifier" id="ctrl-btn" title="Toggle Ctrl modifier">Ctrl</button>
|
|
1633
2184
|
<button class="key-btn modifier" id="shift-btn" title="Toggle Shift modifier">Shift</button>
|
|
1634
|
-
<button class="key-btn special" data-key="
|
|
2185
|
+
<button class="key-btn special" data-key="tab" title="Autocomplete">Tab</button>
|
|
1635
2186
|
<button class="key-btn special key-danger" data-key="" title="Interrupt process">
|
|
1636
2187
|
^C
|
|
1637
2188
|
</button>
|
|
@@ -1878,9 +2429,29 @@
|
|
|
1878
2429
|
</div>
|
|
1879
2430
|
</div>
|
|
1880
2431
|
|
|
2432
|
+
<!-- Command Palette / Tool Panel -->
|
|
2433
|
+
<div id="palette-backdrop" class="palette-backdrop"></div>
|
|
2434
|
+
<div id="palette-panel" class="palette-panel">
|
|
2435
|
+
<div class="palette-header">
|
|
2436
|
+
<span>Tools</span>
|
|
2437
|
+
<button class="palette-close" id="palette-close">✕</button>
|
|
2438
|
+
</div>
|
|
2439
|
+
<div class="palette-body" id="palette-body"></div>
|
|
2440
|
+
</div>
|
|
2441
|
+
|
|
2442
|
+
<!-- Theme Sub-Panel -->
|
|
2443
|
+
<div class="theme-subpanel" id="theme-subpanel">
|
|
2444
|
+
<div class="theme-subpanel-header">
|
|
2445
|
+
<span>Theme</span>
|
|
2446
|
+
<button class="theme-subpanel-close" id="theme-subpanel-close">✕</button>
|
|
2447
|
+
</div>
|
|
2448
|
+
<div class="theme-subpanel-list" id="theme-subpanel-list"></div>
|
|
2449
|
+
</div>
|
|
2450
|
+
|
|
1881
2451
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js"></script>
|
|
1882
2452
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.10.0/lib/addon-fit.min.js"></script>
|
|
1883
2453
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0.11.0/lib/addon-web-links.min.js"></script>
|
|
2454
|
+
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-search@0.15.0/lib/addon-search.min.js"></script>
|
|
1884
2455
|
<script>
|
|
1885
2456
|
// ===== Constants =====
|
|
1886
2457
|
const SESSION_COLORS = [
|
|
@@ -1901,6 +2472,33 @@
|
|
|
1901
2472
|
|
|
1902
2473
|
let splitSecondId = null;
|
|
1903
2474
|
|
|
2475
|
+
// ===== Notification State =====
|
|
2476
|
+
let notificationsEnabled = localStorage.getItem('termbeam-notifications') !== 'false';
|
|
2477
|
+
|
|
2478
|
+
function updateNotifyToggle() {
|
|
2479
|
+
// notify-toggle button removed from top bar; function kept for palette use
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
function sendCommandNotification(sessionName) {
|
|
2483
|
+
if (Notification.permission !== 'granted') return;
|
|
2484
|
+
try {
|
|
2485
|
+
new Notification('Command finished in ' + sessionName, {
|
|
2486
|
+
icon: '/icons/icon-192.png',
|
|
2487
|
+
tag: 'termbeam-cmd',
|
|
2488
|
+
});
|
|
2489
|
+
} catch {}
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
function resetSilenceTimer(ms) {
|
|
2493
|
+
if (ms.silenceTimer) clearTimeout(ms.silenceTimer);
|
|
2494
|
+
ms.silenceTimer = setTimeout(() => {
|
|
2495
|
+
ms.silenceTimer = null;
|
|
2496
|
+
if (document.hidden && notificationsEnabled) {
|
|
2497
|
+
sendCommandNotification(ms.name || ms.id);
|
|
2498
|
+
}
|
|
2499
|
+
}, 3000);
|
|
2500
|
+
}
|
|
2501
|
+
|
|
1904
2502
|
// Clipboard copy fallback for non-secure contexts (HTTP over LAN)
|
|
1905
2503
|
function copyFallback(text) {
|
|
1906
2504
|
const ta = document.createElement('textarea');
|
|
@@ -1970,29 +2568,384 @@
|
|
|
1970
2568
|
brightCyan: '#0598bc',
|
|
1971
2569
|
brightWhite: '#a5a5a5',
|
|
1972
2570
|
};
|
|
2571
|
+
const monokaiTermTheme = {
|
|
2572
|
+
background: '#272822',
|
|
2573
|
+
foreground: '#f8f8f2',
|
|
2574
|
+
cursor: '#f8f8f0',
|
|
2575
|
+
cursorAccent: '#272822',
|
|
2576
|
+
selectionBackground: 'rgba(166, 226, 46, 0.3)',
|
|
2577
|
+
black: '#272822',
|
|
2578
|
+
red: '#f92672',
|
|
2579
|
+
green: '#a6e22e',
|
|
2580
|
+
yellow: '#f4bf75',
|
|
2581
|
+
blue: '#66d9e8',
|
|
2582
|
+
magenta: '#ae81ff',
|
|
2583
|
+
cyan: '#a1efe4',
|
|
2584
|
+
white: '#f8f8f2',
|
|
2585
|
+
brightBlack: '#75715e',
|
|
2586
|
+
brightRed: '#f92672',
|
|
2587
|
+
brightGreen: '#a6e22e',
|
|
2588
|
+
brightYellow: '#f4bf75',
|
|
2589
|
+
brightBlue: '#66d9e8',
|
|
2590
|
+
brightMagenta: '#ae81ff',
|
|
2591
|
+
brightCyan: '#a1efe4',
|
|
2592
|
+
brightWhite: '#f9f8f5',
|
|
2593
|
+
};
|
|
2594
|
+
const solarizedDarkTermTheme = {
|
|
2595
|
+
background: '#002b36',
|
|
2596
|
+
foreground: '#839496',
|
|
2597
|
+
cursor: '#839496',
|
|
2598
|
+
cursorAccent: '#002b36',
|
|
2599
|
+
selectionBackground: 'rgba(7, 54, 66, 0.8)',
|
|
2600
|
+
black: '#073642',
|
|
2601
|
+
red: '#dc322f',
|
|
2602
|
+
green: '#859900',
|
|
2603
|
+
yellow: '#b58900',
|
|
2604
|
+
blue: '#268bd2',
|
|
2605
|
+
magenta: '#d33682',
|
|
2606
|
+
cyan: '#2aa198',
|
|
2607
|
+
white: '#eee8d5',
|
|
2608
|
+
brightBlack: '#002b36',
|
|
2609
|
+
brightRed: '#cb4b16',
|
|
2610
|
+
brightGreen: '#586e75',
|
|
2611
|
+
brightYellow: '#657b83',
|
|
2612
|
+
brightBlue: '#839496',
|
|
2613
|
+
brightMagenta: '#6c71c4',
|
|
2614
|
+
brightCyan: '#93a1a1',
|
|
2615
|
+
brightWhite: '#fdf6e3',
|
|
2616
|
+
};
|
|
2617
|
+
const solarizedLightTermTheme = {
|
|
2618
|
+
background: '#fdf6e3',
|
|
2619
|
+
foreground: '#657b83',
|
|
2620
|
+
cursor: '#586e75',
|
|
2621
|
+
cursorAccent: '#fdf6e3',
|
|
2622
|
+
selectionBackground: 'rgba(238, 232, 213, 0.8)',
|
|
2623
|
+
black: '#073642',
|
|
2624
|
+
red: '#dc322f',
|
|
2625
|
+
green: '#859900',
|
|
2626
|
+
yellow: '#b58900',
|
|
2627
|
+
blue: '#268bd2',
|
|
2628
|
+
magenta: '#d33682',
|
|
2629
|
+
cyan: '#2aa198',
|
|
2630
|
+
white: '#eee8d5',
|
|
2631
|
+
brightBlack: '#002b36',
|
|
2632
|
+
brightRed: '#cb4b16',
|
|
2633
|
+
brightGreen: '#586e75',
|
|
2634
|
+
brightYellow: '#657b83',
|
|
2635
|
+
brightBlue: '#839496',
|
|
2636
|
+
brightMagenta: '#6c71c4',
|
|
2637
|
+
brightCyan: '#93a1a1',
|
|
2638
|
+
brightWhite: '#fdf6e3',
|
|
2639
|
+
};
|
|
2640
|
+
const nordTermTheme = {
|
|
2641
|
+
background: '#2e3440',
|
|
2642
|
+
foreground: '#d8dee9',
|
|
2643
|
+
cursor: '#d8dee9',
|
|
2644
|
+
cursorAccent: '#2e3440',
|
|
2645
|
+
selectionBackground: 'rgba(67, 76, 94, 0.5)',
|
|
2646
|
+
black: '#3b4252',
|
|
2647
|
+
red: '#bf616a',
|
|
2648
|
+
green: '#a3be8c',
|
|
2649
|
+
yellow: '#ebcb8b',
|
|
2650
|
+
blue: '#81a1c1',
|
|
2651
|
+
magenta: '#b48ead',
|
|
2652
|
+
cyan: '#88c0d0',
|
|
2653
|
+
white: '#e5e9f0',
|
|
2654
|
+
brightBlack: '#4c566a',
|
|
2655
|
+
brightRed: '#bf616a',
|
|
2656
|
+
brightGreen: '#a3be8c',
|
|
2657
|
+
brightYellow: '#ebcb8b',
|
|
2658
|
+
brightBlue: '#81a1c1',
|
|
2659
|
+
brightMagenta: '#b48ead',
|
|
2660
|
+
brightCyan: '#8fbcbb',
|
|
2661
|
+
brightWhite: '#eceff4',
|
|
2662
|
+
};
|
|
2663
|
+
const draculaTermTheme = {
|
|
2664
|
+
background: '#282a36',
|
|
2665
|
+
foreground: '#f8f8f2',
|
|
2666
|
+
cursor: '#f8f8f2',
|
|
2667
|
+
cursorAccent: '#282a36',
|
|
2668
|
+
selectionBackground: 'rgba(68, 71, 90, 0.7)',
|
|
2669
|
+
black: '#21222c',
|
|
2670
|
+
red: '#ff5555',
|
|
2671
|
+
green: '#50fa7b',
|
|
2672
|
+
yellow: '#f1fa8c',
|
|
2673
|
+
blue: '#bd93f9',
|
|
2674
|
+
magenta: '#ff79c6',
|
|
2675
|
+
cyan: '#8be9fd',
|
|
2676
|
+
white: '#f8f8f2',
|
|
2677
|
+
brightBlack: '#6272a4',
|
|
2678
|
+
brightRed: '#ff6e6e',
|
|
2679
|
+
brightGreen: '#69ff94',
|
|
2680
|
+
brightYellow: '#ffffa5',
|
|
2681
|
+
brightBlue: '#d6acff',
|
|
2682
|
+
brightMagenta: '#ff92df',
|
|
2683
|
+
brightCyan: '#a4ffff',
|
|
2684
|
+
brightWhite: '#ffffff',
|
|
2685
|
+
};
|
|
2686
|
+
const githubDarkTermTheme = {
|
|
2687
|
+
background: '#0d1117',
|
|
2688
|
+
foreground: '#c9d1d9',
|
|
2689
|
+
cursor: '#c9d1d9',
|
|
2690
|
+
cursorAccent: '#0d1117',
|
|
2691
|
+
selectionBackground: 'rgba(56, 139, 253, 0.3)',
|
|
2692
|
+
black: '#484f58',
|
|
2693
|
+
red: '#ff7b72',
|
|
2694
|
+
green: '#3fb950',
|
|
2695
|
+
yellow: '#d29922',
|
|
2696
|
+
blue: '#58a6ff',
|
|
2697
|
+
magenta: '#bc8cff',
|
|
2698
|
+
cyan: '#39c5cf',
|
|
2699
|
+
white: '#c9d1d9',
|
|
2700
|
+
brightBlack: '#6e7681',
|
|
2701
|
+
brightRed: '#ffa198',
|
|
2702
|
+
brightGreen: '#56d364',
|
|
2703
|
+
brightYellow: '#e3b341',
|
|
2704
|
+
brightBlue: '#79c0ff',
|
|
2705
|
+
brightMagenta: '#d2a8ff',
|
|
2706
|
+
brightCyan: '#56d4dd',
|
|
2707
|
+
brightWhite: '#f0f6fc',
|
|
2708
|
+
};
|
|
2709
|
+
const oneDarkTermTheme = {
|
|
2710
|
+
background: '#282c34',
|
|
2711
|
+
foreground: '#abb2bf',
|
|
2712
|
+
cursor: '#528bff',
|
|
2713
|
+
cursorAccent: '#282c34',
|
|
2714
|
+
selectionBackground: 'rgba(62, 68, 82, 0.7)',
|
|
2715
|
+
black: '#3f4451',
|
|
2716
|
+
red: '#e06c75',
|
|
2717
|
+
green: '#98c379',
|
|
2718
|
+
yellow: '#e5c07b',
|
|
2719
|
+
blue: '#61afef',
|
|
2720
|
+
magenta: '#c678dd',
|
|
2721
|
+
cyan: '#56b6c2',
|
|
2722
|
+
white: '#d7dae0',
|
|
2723
|
+
brightBlack: '#4f5666',
|
|
2724
|
+
brightRed: '#e06c75',
|
|
2725
|
+
brightGreen: '#98c379',
|
|
2726
|
+
brightYellow: '#e5c07b',
|
|
2727
|
+
brightBlue: '#61afef',
|
|
2728
|
+
brightMagenta: '#c678dd',
|
|
2729
|
+
brightCyan: '#56b6c2',
|
|
2730
|
+
brightWhite: '#ffffff',
|
|
2731
|
+
};
|
|
2732
|
+
const catppuccinTermTheme = {
|
|
2733
|
+
background: '#1e1e2e',
|
|
2734
|
+
foreground: '#cdd6f4',
|
|
2735
|
+
cursor: '#f5e0dc',
|
|
2736
|
+
cursorAccent: '#1e1e2e',
|
|
2737
|
+
selectionBackground: 'rgba(88, 91, 112, 0.5)',
|
|
2738
|
+
black: '#45475a',
|
|
2739
|
+
red: '#f38ba8',
|
|
2740
|
+
green: '#a6e3a1',
|
|
2741
|
+
yellow: '#f9e2af',
|
|
2742
|
+
blue: '#89b4fa',
|
|
2743
|
+
magenta: '#f5c2e7',
|
|
2744
|
+
cyan: '#94e2d5',
|
|
2745
|
+
white: '#bac2de',
|
|
2746
|
+
brightBlack: '#585b70',
|
|
2747
|
+
brightRed: '#f38ba8',
|
|
2748
|
+
brightGreen: '#a6e3a1',
|
|
2749
|
+
brightYellow: '#f9e2af',
|
|
2750
|
+
brightBlue: '#89b4fa',
|
|
2751
|
+
brightMagenta: '#f5c2e7',
|
|
2752
|
+
brightCyan: '#94e2d5',
|
|
2753
|
+
brightWhite: '#a6adc8',
|
|
2754
|
+
};
|
|
2755
|
+
const gruvboxTermTheme = {
|
|
2756
|
+
background: '#282828',
|
|
2757
|
+
foreground: '#ebdbb2',
|
|
2758
|
+
cursor: '#ebdbb2',
|
|
2759
|
+
cursorAccent: '#282828',
|
|
2760
|
+
selectionBackground: 'rgba(80, 73, 69, 0.7)',
|
|
2761
|
+
black: '#282828',
|
|
2762
|
+
red: '#cc241d',
|
|
2763
|
+
green: '#98971a',
|
|
2764
|
+
yellow: '#d79921',
|
|
2765
|
+
blue: '#458588',
|
|
2766
|
+
magenta: '#b16286',
|
|
2767
|
+
cyan: '#689d6a',
|
|
2768
|
+
white: '#a89984',
|
|
2769
|
+
brightBlack: '#928374',
|
|
2770
|
+
brightRed: '#fb4934',
|
|
2771
|
+
brightGreen: '#b8bb26',
|
|
2772
|
+
brightYellow: '#fabd2f',
|
|
2773
|
+
brightBlue: '#83a598',
|
|
2774
|
+
brightMagenta: '#d3869b',
|
|
2775
|
+
brightCyan: '#8ec07c',
|
|
2776
|
+
brightWhite: '#ebdbb2',
|
|
2777
|
+
};
|
|
2778
|
+
const nightOwlTermTheme = {
|
|
2779
|
+
background: '#011627',
|
|
2780
|
+
foreground: '#d6deeb',
|
|
2781
|
+
cursor: '#80a4c2',
|
|
2782
|
+
cursorAccent: '#011627',
|
|
2783
|
+
selectionBackground: 'rgba(29, 59, 83, 0.7)',
|
|
2784
|
+
black: '#010e1a',
|
|
2785
|
+
red: '#ef5350',
|
|
2786
|
+
green: '#22da6e',
|
|
2787
|
+
yellow: '#addb67',
|
|
2788
|
+
blue: '#82aaff',
|
|
2789
|
+
magenta: '#c792ea',
|
|
2790
|
+
cyan: '#21c7a8',
|
|
2791
|
+
white: '#d6deeb',
|
|
2792
|
+
brightBlack: '#575656',
|
|
2793
|
+
brightRed: '#ef5350',
|
|
2794
|
+
brightGreen: '#22da6e',
|
|
2795
|
+
brightYellow: '#ffeb95',
|
|
2796
|
+
brightBlue: '#82aaff',
|
|
2797
|
+
brightMagenta: '#c792ea',
|
|
2798
|
+
brightCyan: '#7fdbca',
|
|
2799
|
+
brightWhite: '#ffffff',
|
|
2800
|
+
};
|
|
2801
|
+
const TERM_THEMES = {
|
|
2802
|
+
dark: darkTermTheme,
|
|
2803
|
+
light: lightTermTheme,
|
|
2804
|
+
monokai: monokaiTermTheme,
|
|
2805
|
+
'solarized-dark': solarizedDarkTermTheme,
|
|
2806
|
+
'solarized-light': solarizedLightTermTheme,
|
|
2807
|
+
nord: nordTermTheme,
|
|
2808
|
+
dracula: draculaTermTheme,
|
|
2809
|
+
'github-dark': githubDarkTermTheme,
|
|
2810
|
+
'one-dark': oneDarkTermTheme,
|
|
2811
|
+
catppuccin: catppuccinTermTheme,
|
|
2812
|
+
gruvbox: gruvboxTermTheme,
|
|
2813
|
+
'night-owl': nightOwlTermTheme,
|
|
2814
|
+
};
|
|
1973
2815
|
|
|
1974
2816
|
// ===== Theme =====
|
|
2817
|
+
const THEMES = [
|
|
2818
|
+
{ id: 'dark', name: 'Dark', bg: '#1e1e1e' },
|
|
2819
|
+
{ id: 'light', name: 'Light', bg: '#f3f3f3' },
|
|
2820
|
+
{ id: 'monokai', name: 'Monokai', bg: '#272822' },
|
|
2821
|
+
{ id: 'solarized-dark', name: 'Solarized Dark', bg: '#002b36' },
|
|
2822
|
+
{ id: 'solarized-light', name: 'Solarized Light', bg: '#fdf6e3' },
|
|
2823
|
+
{ id: 'nord', name: 'Nord', bg: '#2e3440' },
|
|
2824
|
+
{ id: 'dracula', name: 'Dracula', bg: '#282a36' },
|
|
2825
|
+
{ id: 'github-dark', name: 'GitHub Dark', bg: '#0d1117' },
|
|
2826
|
+
{ id: 'one-dark', name: 'One Dark', bg: '#282c34' },
|
|
2827
|
+
{ id: 'catppuccin', name: 'Catppuccin', bg: '#1e1e2e' },
|
|
2828
|
+
{ id: 'gruvbox', name: 'Gruvbox', bg: '#282828' },
|
|
2829
|
+
{ id: 'night-owl', name: 'Night Owl', bg: '#011627' },
|
|
2830
|
+
];
|
|
1975
2831
|
function getTheme() {
|
|
1976
2832
|
return localStorage.getItem('termbeam-theme') || 'dark';
|
|
1977
2833
|
}
|
|
1978
2834
|
function applyTheme(theme) {
|
|
1979
2835
|
document.documentElement.setAttribute('data-theme', theme);
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
const btn = document.getElementById('theme-toggle');
|
|
1983
|
-
if (btn)
|
|
1984
|
-
btn.innerHTML =
|
|
1985
|
-
theme === 'light'
|
|
1986
|
-
? '<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 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>'
|
|
1987
|
-
: '<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="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>';
|
|
2836
|
+
const t = THEMES.find((x) => x.id === theme) || THEMES[0];
|
|
2837
|
+
document.querySelector('meta[name="theme-color"]').content = t.bg;
|
|
1988
2838
|
localStorage.setItem('termbeam-theme', theme);
|
|
2839
|
+
document.querySelectorAll('.theme-option').forEach((el) => {
|
|
2840
|
+
el.classList.toggle('active', el.dataset.themeOption === theme);
|
|
2841
|
+
});
|
|
2842
|
+
const termTheme = TERM_THEMES[theme] || darkTermTheme;
|
|
1989
2843
|
for (const [, ms] of managed) {
|
|
1990
|
-
ms.term.options.theme =
|
|
2844
|
+
ms.term.options.theme = termTheme;
|
|
1991
2845
|
}
|
|
1992
2846
|
}
|
|
1993
2847
|
applyTheme(getTheme());
|
|
1994
|
-
document.getElementById('theme-toggle').addEventListener('click', () => {
|
|
1995
|
-
|
|
2848
|
+
document.getElementById('theme-toggle').addEventListener('click', (e) => {
|
|
2849
|
+
e.stopPropagation();
|
|
2850
|
+
document.getElementById('theme-picker').classList.toggle('open');
|
|
2851
|
+
});
|
|
2852
|
+
document.addEventListener('click', () => {
|
|
2853
|
+
document.getElementById('theme-picker').classList.remove('open');
|
|
2854
|
+
});
|
|
2855
|
+
document.querySelectorAll('.theme-option').forEach((el) => {
|
|
2856
|
+
el.addEventListener('click', (e) => {
|
|
2857
|
+
e.stopPropagation();
|
|
2858
|
+
applyTheme(el.dataset.themeOption);
|
|
2859
|
+
});
|
|
2860
|
+
});
|
|
2861
|
+
|
|
2862
|
+
// ===== Terminal Search =====
|
|
2863
|
+
const searchBar = document.getElementById('search-bar');
|
|
2864
|
+
const searchInput = document.getElementById('search-input');
|
|
2865
|
+
const searchCount = document.getElementById('search-count');
|
|
2866
|
+
const searchRegexBtn = document.getElementById('search-regex');
|
|
2867
|
+
let searchRegex = false;
|
|
2868
|
+
let searchResultIndex = 0;
|
|
2869
|
+
let searchResultTotal = 0;
|
|
2870
|
+
|
|
2871
|
+
function getActiveSearchAddon() {
|
|
2872
|
+
if (!activeId) return null;
|
|
2873
|
+
const ms = managed.get(activeId);
|
|
2874
|
+
return ms ? ms.searchAddon : null;
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
function updateSearchCount(idx, total) {
|
|
2878
|
+
searchResultIndex = idx;
|
|
2879
|
+
searchResultTotal = total;
|
|
2880
|
+
searchCount.textContent = total > 0 ? idx + 1 + ' of ' + total : 'No results';
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2883
|
+
function doSearch(direction) {
|
|
2884
|
+
const addon = getActiveSearchAddon();
|
|
2885
|
+
if (!addon) return;
|
|
2886
|
+
const query = searchInput.value;
|
|
2887
|
+
if (!query) {
|
|
2888
|
+
searchCount.textContent = '';
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
const opts = {
|
|
2892
|
+
regex: searchRegex,
|
|
2893
|
+
caseSensitive: false,
|
|
2894
|
+
incremental: direction === 'next',
|
|
2895
|
+
};
|
|
2896
|
+
let result;
|
|
2897
|
+
if (direction === 'prev') {
|
|
2898
|
+
result = addon.findPrevious(query, opts);
|
|
2899
|
+
} else {
|
|
2900
|
+
result = addon.findNext(query, opts);
|
|
2901
|
+
}
|
|
2902
|
+
// SearchAddon returns boolean; no match count API in v0.15
|
|
2903
|
+
searchCount.textContent = result ? 'Found' : 'No results';
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
function openSearchBar() {
|
|
2907
|
+
searchBar.classList.add('visible');
|
|
2908
|
+
searchInput.focus();
|
|
2909
|
+
searchInput.select();
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
function closeSearchBar() {
|
|
2913
|
+
searchBar.classList.remove('visible');
|
|
2914
|
+
searchCount.textContent = '';
|
|
2915
|
+
searchInput.value = '';
|
|
2916
|
+
const addon = getActiveSearchAddon();
|
|
2917
|
+
if (addon) addon.clearDecorations();
|
|
2918
|
+
// Re-focus terminal
|
|
2919
|
+
if (activeId) {
|
|
2920
|
+
const ms = managed.get(activeId);
|
|
2921
|
+
if (ms) ms.term.focus();
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
searchInput.addEventListener('input', () => doSearch('next'));
|
|
2926
|
+
searchInput.addEventListener('keydown', (e) => {
|
|
2927
|
+
if (e.key === 'Escape') {
|
|
2928
|
+
closeSearchBar();
|
|
2929
|
+
e.preventDefault();
|
|
2930
|
+
} else if (e.key === 'Enter') {
|
|
2931
|
+
e.preventDefault();
|
|
2932
|
+
doSearch(e.shiftKey ? 'prev' : 'next');
|
|
2933
|
+
}
|
|
2934
|
+
});
|
|
2935
|
+
document.getElementById('search-next').addEventListener('click', () => doSearch('next'));
|
|
2936
|
+
document.getElementById('search-prev').addEventListener('click', () => doSearch('prev'));
|
|
2937
|
+
document.getElementById('search-close').addEventListener('click', closeSearchBar);
|
|
2938
|
+
document.getElementById('search-regex').addEventListener('click', () => {
|
|
2939
|
+
searchRegex = !searchRegex;
|
|
2940
|
+
searchRegexBtn.classList.toggle('active', searchRegex);
|
|
2941
|
+
if (searchInput.value) doSearch('next');
|
|
2942
|
+
});
|
|
2943
|
+
|
|
2944
|
+
document.addEventListener('keydown', (e) => {
|
|
2945
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
|
|
2946
|
+
e.preventDefault();
|
|
2947
|
+
openSearchBar();
|
|
2948
|
+
}
|
|
1996
2949
|
});
|
|
1997
2950
|
|
|
1998
2951
|
// ===== Font Loading (non-blocking) =====
|
|
@@ -2049,7 +3002,7 @@
|
|
|
2049
3002
|
|
|
2050
3003
|
// ===== Zoom =====
|
|
2051
3004
|
const MIN_FONT = 2,
|
|
2052
|
-
MAX_FONT =
|
|
3005
|
+
MAX_FONT = 32;
|
|
2053
3006
|
function defaultFontSize() {
|
|
2054
3007
|
const w = window.innerWidth;
|
|
2055
3008
|
if (w <= 480) return 12;
|
|
@@ -2130,11 +3083,61 @@
|
|
|
2130
3083
|
loadShellsForModal();
|
|
2131
3084
|
startPolling();
|
|
2132
3085
|
|
|
2133
|
-
//
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
3086
|
+
// Pinch-to-zoom
|
|
3087
|
+
(function setupPinchZoom() {
|
|
3088
|
+
const wrapper = document.getElementById('terminals-wrapper');
|
|
3089
|
+
let pinchStartDist = 0;
|
|
3090
|
+
let pinchStartFont = 0;
|
|
3091
|
+
let pinchActive = false;
|
|
3092
|
+
let pinchZoomTimer = null;
|
|
3093
|
+
|
|
3094
|
+
function touchDist(t) {
|
|
3095
|
+
const dx = t[0].clientX - t[1].clientX;
|
|
3096
|
+
const dy = t[0].clientY - t[1].clientY;
|
|
3097
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
wrapper.addEventListener(
|
|
3101
|
+
'touchstart',
|
|
3102
|
+
function (e) {
|
|
3103
|
+
if (e.touches.length === 2) {
|
|
3104
|
+
pinchActive = true;
|
|
3105
|
+
pinchStartDist = touchDist(e.touches);
|
|
3106
|
+
pinchStartFont = fontSize;
|
|
3107
|
+
wrapper.style.touchAction = 'none';
|
|
3108
|
+
}
|
|
3109
|
+
},
|
|
3110
|
+
{ passive: true },
|
|
3111
|
+
);
|
|
3112
|
+
|
|
3113
|
+
wrapper.addEventListener(
|
|
3114
|
+
'touchmove',
|
|
3115
|
+
function (e) {
|
|
3116
|
+
if (!pinchActive || e.touches.length !== 2) return;
|
|
3117
|
+
e.preventDefault();
|
|
3118
|
+
const dist = touchDist(e.touches);
|
|
3119
|
+
const scale = dist / pinchStartDist;
|
|
3120
|
+
const newSize = Math.round(pinchStartFont * scale);
|
|
3121
|
+
if (newSize !== fontSize) {
|
|
3122
|
+
if (pinchZoomTimer) clearTimeout(pinchZoomTimer);
|
|
3123
|
+
pinchZoomTimer = setTimeout(function () {
|
|
3124
|
+
applyZoom(newSize);
|
|
3125
|
+
}, 50);
|
|
3126
|
+
}
|
|
3127
|
+
},
|
|
3128
|
+
{ passive: false },
|
|
3129
|
+
);
|
|
3130
|
+
|
|
3131
|
+
wrapper.addEventListener('touchend', function () {
|
|
3132
|
+
pinchActive = false;
|
|
3133
|
+
wrapper.style.touchAction = '';
|
|
3134
|
+
});
|
|
3135
|
+
|
|
3136
|
+
wrapper.addEventListener('touchcancel', function () {
|
|
3137
|
+
pinchActive = false;
|
|
3138
|
+
wrapper.style.touchAction = '';
|
|
3139
|
+
});
|
|
3140
|
+
})();
|
|
2138
3141
|
|
|
2139
3142
|
// Resize
|
|
2140
3143
|
function doResize() {
|
|
@@ -2207,12 +3210,10 @@
|
|
|
2207
3210
|
);
|
|
2208
3211
|
}
|
|
2209
3212
|
|
|
2210
|
-
// Split toggle
|
|
2211
|
-
document.getElementById('split-toggle').addEventListener('click', toggleSplit);
|
|
2212
|
-
|
|
2213
3213
|
// Scroll to bottom when returning from idle / tab switch
|
|
2214
3214
|
document.addEventListener('visibilitychange', () => {
|
|
2215
3215
|
if (!document.hidden && activeId) {
|
|
3216
|
+
clearUnreadIndicator();
|
|
2216
3217
|
const ms = managed.get(activeId);
|
|
2217
3218
|
if (ms) {
|
|
2218
3219
|
ms.term.scrollToBottom();
|
|
@@ -2256,7 +3257,7 @@
|
|
|
2256
3257
|
fetch('/api/version')
|
|
2257
3258
|
.then((r) => r.json())
|
|
2258
3259
|
.then((d) => {
|
|
2259
|
-
|
|
3260
|
+
window._termbeamVersion = 'v' + d.version;
|
|
2260
3261
|
document.getElementById('side-panel-version').textContent = 'v' + d.version;
|
|
2261
3262
|
})
|
|
2262
3263
|
.catch(() => {});
|
|
@@ -2265,6 +3266,7 @@
|
|
|
2265
3266
|
// ===== Session Management =====
|
|
2266
3267
|
function addSession(data) {
|
|
2267
3268
|
if (managed.has(data.id)) return;
|
|
3269
|
+
managed.set(data.id, null); // reserve slot to prevent race condition
|
|
2268
3270
|
|
|
2269
3271
|
const term = new window.Terminal({
|
|
2270
3272
|
cursorBlink: true,
|
|
@@ -2275,7 +3277,7 @@
|
|
|
2275
3277
|
fontWeightBold: 'bold',
|
|
2276
3278
|
letterSpacing: 0,
|
|
2277
3279
|
lineHeight: 1.1,
|
|
2278
|
-
theme: getTheme()
|
|
3280
|
+
theme: TERM_THEMES[getTheme()] || darkTermTheme,
|
|
2279
3281
|
allowProposedApi: true,
|
|
2280
3282
|
scrollback: 10000,
|
|
2281
3283
|
scrollOnOutput: false,
|
|
@@ -2283,8 +3285,10 @@
|
|
|
2283
3285
|
|
|
2284
3286
|
const fitAddon = new window.FitAddon.FitAddon();
|
|
2285
3287
|
const webLinksAddon = new window.WebLinksAddon.WebLinksAddon();
|
|
3288
|
+
const searchAddon = new window.SearchAddon.SearchAddon();
|
|
2286
3289
|
term.loadAddon(fitAddon);
|
|
2287
3290
|
term.loadAddon(webLinksAddon);
|
|
3291
|
+
term.loadAddon(searchAddon);
|
|
2288
3292
|
|
|
2289
3293
|
const container = document.createElement('div');
|
|
2290
3294
|
container.className = 'terminal-pane';
|
|
@@ -2434,6 +3438,7 @@
|
|
|
2434
3438
|
pid: data.pid,
|
|
2435
3439
|
term,
|
|
2436
3440
|
fitAddon,
|
|
3441
|
+
searchAddon,
|
|
2437
3442
|
container,
|
|
2438
3443
|
coalescedWrite,
|
|
2439
3444
|
scrollBtn,
|
|
@@ -2442,6 +3447,7 @@
|
|
|
2442
3447
|
reconnectTimer: null,
|
|
2443
3448
|
reconnectDelay: 3000,
|
|
2444
3449
|
lastActivity: data.lastActivity || Date.now(),
|
|
3450
|
+
silenceTimer: null,
|
|
2445
3451
|
};
|
|
2446
3452
|
|
|
2447
3453
|
managed.set(data.id, ms);
|
|
@@ -2472,6 +3478,53 @@
|
|
|
2472
3478
|
return ms;
|
|
2473
3479
|
}
|
|
2474
3480
|
|
|
3481
|
+
// Tab title activity indicator for unread output
|
|
3482
|
+
const originalTitle = document.title;
|
|
3483
|
+
let hasUnread = false;
|
|
3484
|
+
|
|
3485
|
+
// Shared AudioContext — must be created/resumed on user gesture for mobile
|
|
3486
|
+
let notifAudioCtx = null;
|
|
3487
|
+
function ensureAudioContext() {
|
|
3488
|
+
if (!notifAudioCtx) {
|
|
3489
|
+
notifAudioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
3490
|
+
}
|
|
3491
|
+
if (notifAudioCtx.state === 'suspended') notifAudioCtx.resume();
|
|
3492
|
+
return notifAudioCtx;
|
|
3493
|
+
}
|
|
3494
|
+
document.addEventListener('click', ensureAudioContext, { once: true });
|
|
3495
|
+
document.addEventListener('touchstart', ensureAudioContext, { once: true });
|
|
3496
|
+
|
|
3497
|
+
function playNotificationSound() {
|
|
3498
|
+
try {
|
|
3499
|
+
const ctx = ensureAudioContext();
|
|
3500
|
+
const osc = ctx.createOscillator();
|
|
3501
|
+
const gain = ctx.createGain();
|
|
3502
|
+
osc.connect(gain);
|
|
3503
|
+
gain.connect(ctx.destination);
|
|
3504
|
+
osc.frequency.value = 440;
|
|
3505
|
+
osc.type = 'sine';
|
|
3506
|
+
gain.gain.setValueAtTime(0.15, ctx.currentTime);
|
|
3507
|
+
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.15);
|
|
3508
|
+
osc.start(ctx.currentTime);
|
|
3509
|
+
osc.stop(ctx.currentTime + 0.15);
|
|
3510
|
+
} catch {
|
|
3511
|
+
// Audio not available
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3515
|
+
function markUnreadOutput() {
|
|
3516
|
+
if (!document.hidden || hasUnread) return;
|
|
3517
|
+
hasUnread = true;
|
|
3518
|
+
playNotificationSound();
|
|
3519
|
+
document.title = '(\u25CF) ' + originalTitle;
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3522
|
+
function clearUnreadIndicator() {
|
|
3523
|
+
if (!hasUnread) return;
|
|
3524
|
+
hasUnread = false;
|
|
3525
|
+
document.title = originalTitle;
|
|
3526
|
+
}
|
|
3527
|
+
|
|
2475
3528
|
function connectSession(ms) {
|
|
2476
3529
|
if (ms.reconnectTimer) {
|
|
2477
3530
|
clearTimeout(ms.reconnectTimer);
|
|
@@ -2506,6 +3559,13 @@
|
|
|
2506
3559
|
if (msg.type === 'output') {
|
|
2507
3560
|
ms.coalescedWrite(msg.data);
|
|
2508
3561
|
ms.lastActivity = Date.now();
|
|
3562
|
+
resetSilenceTimer(ms);
|
|
3563
|
+
markUnreadOutput();
|
|
3564
|
+
if (ms.id !== activeId && !ms.hasUnread) {
|
|
3565
|
+
ms.hasUnread = true;
|
|
3566
|
+
playNotificationSound();
|
|
3567
|
+
renderTabs();
|
|
3568
|
+
}
|
|
2509
3569
|
} else if (msg.type === 'attached') {
|
|
2510
3570
|
if (ms.container.classList.contains('visible')) {
|
|
2511
3571
|
sendResize(ms);
|
|
@@ -2567,6 +3627,8 @@
|
|
|
2567
3627
|
function activateSession(id) {
|
|
2568
3628
|
if (!managed.has(id)) return;
|
|
2569
3629
|
activeId = id;
|
|
3630
|
+
const ms = managed.get(id);
|
|
3631
|
+
if (ms) ms.hasUnread = false;
|
|
2570
3632
|
reconnectOverlay.classList.remove('visible');
|
|
2571
3633
|
|
|
2572
3634
|
// Show/hide panes
|
|
@@ -2618,6 +3680,10 @@
|
|
|
2618
3680
|
clearTimeout(ms.reconnectTimer);
|
|
2619
3681
|
ms.reconnectTimer = null;
|
|
2620
3682
|
}
|
|
3683
|
+
if (ms.silenceTimer) {
|
|
3684
|
+
clearTimeout(ms.silenceTimer);
|
|
3685
|
+
ms.silenceTimer = null;
|
|
3686
|
+
}
|
|
2621
3687
|
if (ms.ws)
|
|
2622
3688
|
try {
|
|
2623
3689
|
ms.ws.close();
|
|
@@ -2681,6 +3747,7 @@
|
|
|
2681
3747
|
esc(ms.name) +
|
|
2682
3748
|
'</span>' +
|
|
2683
3749
|
(activity ? '<span class="tab-activity">' + activity + '</span>' : '') +
|
|
3750
|
+
(ms.hasUnread && !isActive ? '<span class="tab-unread"></span>' : '') +
|
|
2684
3751
|
'<span class="tab-status" style="background:' +
|
|
2685
3752
|
safeColor(statusColor) +
|
|
2686
3753
|
'"></span>' +
|
|
@@ -2770,6 +3837,14 @@
|
|
|
2770
3837
|
activateSession(tab.dataset.id);
|
|
2771
3838
|
});
|
|
2772
3839
|
|
|
3840
|
+
// Middle-click to close tab
|
|
3841
|
+
tab.addEventListener('auxclick', (e) => {
|
|
3842
|
+
if (e.button === 1) {
|
|
3843
|
+
e.preventDefault();
|
|
3844
|
+
if (confirm('Close this session?')) removeSession(tab.dataset.id);
|
|
3845
|
+
}
|
|
3846
|
+
});
|
|
3847
|
+
|
|
2773
3848
|
// Desktop: hover preview (non-active tabs only)
|
|
2774
3849
|
tab.addEventListener('mouseenter', () => {
|
|
2775
3850
|
if (tab.dataset.id === activeId) return;
|
|
@@ -2832,6 +3907,7 @@
|
|
|
2832
3907
|
'<span class="side-panel-card-name">' +
|
|
2833
3908
|
esc(ms.name) +
|
|
2834
3909
|
'</span>' +
|
|
3910
|
+
(ms.hasUnread && !isActive ? '<span class="tab-unread"></span>' : '') +
|
|
2835
3911
|
'<span class="side-panel-card-status" style="background:' +
|
|
2836
3912
|
safeColor(statusColor) +
|
|
2837
3913
|
'"></span>' +
|
|
@@ -2994,7 +4070,6 @@
|
|
|
2994
4070
|
// ===== Split View =====
|
|
2995
4071
|
function toggleSplit() {
|
|
2996
4072
|
splitMode = !splitMode;
|
|
2997
|
-
document.getElementById('split-toggle').classList.toggle('active', splitMode);
|
|
2998
4073
|
|
|
2999
4074
|
if (splitMode) {
|
|
3000
4075
|
// Find a second session to show
|
|
@@ -3081,7 +4156,12 @@
|
|
|
3081
4156
|
function sendKey(btn) {
|
|
3082
4157
|
if (!btn || !btn.dataset.key) return;
|
|
3083
4158
|
flashBtn(btn);
|
|
3084
|
-
let data =
|
|
4159
|
+
let data =
|
|
4160
|
+
btn.dataset.key === 'enter'
|
|
4161
|
+
? '\r'
|
|
4162
|
+
: btn.dataset.key === 'tab'
|
|
4163
|
+
? '\x09'
|
|
4164
|
+
: btn.dataset.key;
|
|
3085
4165
|
data = applyModifiers(data);
|
|
3086
4166
|
const ms = managed.get(activeId);
|
|
3087
4167
|
if (ms && ms.ws && ms.ws.readyState === 1) {
|
|
@@ -3614,6 +4694,18 @@
|
|
|
3614
4694
|
if (cmd) body.initialCommand = cmd;
|
|
3615
4695
|
if (color) body.color = color;
|
|
3616
4696
|
|
|
4697
|
+
// Include current terminal dimensions so the PTY spawns at the right
|
|
4698
|
+
// size — prevents oh-my-posh and other slow prompts from rendering
|
|
4699
|
+
// at the default 120×30 size and triggering a duplicate on SIGWINCH.
|
|
4700
|
+
const activeMs = managed.get(activeId);
|
|
4701
|
+
if (activeMs && activeMs.fitAddon) {
|
|
4702
|
+
const dims = activeMs.fitAddon.proposeDimensions();
|
|
4703
|
+
if (dims) {
|
|
4704
|
+
body.cols = dims.cols;
|
|
4705
|
+
body.rows = dims.rows;
|
|
4706
|
+
}
|
|
4707
|
+
}
|
|
4708
|
+
|
|
3617
4709
|
try {
|
|
3618
4710
|
const res = await fetch('/api/sessions', {
|
|
3619
4711
|
method: 'POST',
|
|
@@ -3668,6 +4760,10 @@
|
|
|
3668
4760
|
clearTimeout(ms.reconnectTimer);
|
|
3669
4761
|
ms.reconnectTimer = null;
|
|
3670
4762
|
}
|
|
4763
|
+
if (ms.silenceTimer) {
|
|
4764
|
+
clearTimeout(ms.silenceTimer);
|
|
4765
|
+
ms.silenceTimer = null;
|
|
4766
|
+
}
|
|
3671
4767
|
if (ms.ws)
|
|
3672
4768
|
try {
|
|
3673
4769
|
ms.ws.close();
|
|
@@ -3801,12 +4897,11 @@
|
|
|
3801
4897
|
});
|
|
3802
4898
|
}
|
|
3803
4899
|
|
|
3804
|
-
|
|
4900
|
+
async function shareLink() {
|
|
3805
4901
|
const urlPromise = fetch('/api/share-token')
|
|
3806
4902
|
.then((r) => (r.ok ? r.json() : null))
|
|
3807
4903
|
.then((data) => (data && data.url) || location.href)
|
|
3808
4904
|
.catch(() => location.href);
|
|
3809
|
-
// ClipboardItem with a promise preserves user activation across the fetch
|
|
3810
4905
|
if (navigator.clipboard && typeof ClipboardItem !== 'undefined') {
|
|
3811
4906
|
try {
|
|
3812
4907
|
const blobPromise = urlPromise.then((u) => new Blob([u], { type: 'text/plain' }));
|
|
@@ -3815,7 +4910,6 @@
|
|
|
3815
4910
|
return;
|
|
3816
4911
|
} catch {}
|
|
3817
4912
|
}
|
|
3818
|
-
// Fallback: resolve URL first, then try legacy methods
|
|
3819
4913
|
const url = await urlPromise;
|
|
3820
4914
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
3821
4915
|
try {
|
|
@@ -3829,10 +4923,9 @@
|
|
|
3829
4923
|
} else {
|
|
3830
4924
|
showShareUrlPrompt(url);
|
|
3831
4925
|
}
|
|
3832
|
-
}
|
|
4926
|
+
}
|
|
3833
4927
|
|
|
3834
|
-
|
|
3835
|
-
document.getElementById('refresh-btn').addEventListener('click', async () => {
|
|
4928
|
+
async function refreshApp() {
|
|
3836
4929
|
if ('caches' in window) {
|
|
3837
4930
|
const keys = await caches.keys();
|
|
3838
4931
|
await Promise.all(keys.map((k) => caches.delete(k)));
|
|
@@ -3842,7 +4935,294 @@
|
|
|
3842
4935
|
if (reg) await reg.update();
|
|
3843
4936
|
}
|
|
3844
4937
|
location.reload();
|
|
3845
|
-
}
|
|
4938
|
+
}
|
|
4939
|
+
|
|
4940
|
+
// ===== Command Palette =====
|
|
4941
|
+
(function setupPalette() {
|
|
4942
|
+
const paletteActions = [
|
|
4943
|
+
{
|
|
4944
|
+
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>',
|
|
4945
|
+
label: 'New tab',
|
|
4946
|
+
category: 'Session',
|
|
4947
|
+
action: () => openNewSessionModal(),
|
|
4948
|
+
},
|
|
4949
|
+
{
|
|
4950
|
+
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>',
|
|
4951
|
+
label: 'Close tab',
|
|
4952
|
+
category: 'Session',
|
|
4953
|
+
action: () => {
|
|
4954
|
+
if (!activeId) return;
|
|
4955
|
+
const ms = managed.get(activeId);
|
|
4956
|
+
const name = (ms && ms.name) || activeId.slice(0, 8);
|
|
4957
|
+
if (confirm('Close session "' + name + '"?')) {
|
|
4958
|
+
removeSession(activeId);
|
|
4959
|
+
}
|
|
4960
|
+
},
|
|
4961
|
+
},
|
|
4962
|
+
{
|
|
4963
|
+
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>',
|
|
4964
|
+
label: 'Rename session',
|
|
4965
|
+
category: 'Session',
|
|
4966
|
+
action: () => {
|
|
4967
|
+
if (!activeId) return;
|
|
4968
|
+
const ms = managed.get(activeId);
|
|
4969
|
+
if (!ms) return;
|
|
4970
|
+
const name = prompt('Rename session:', ms.name || '');
|
|
4971
|
+
if (name !== null && name.trim()) {
|
|
4972
|
+
ms.name = name.trim();
|
|
4973
|
+
renderTabs();
|
|
4974
|
+
updateStatusBar();
|
|
4975
|
+
}
|
|
4976
|
+
},
|
|
4977
|
+
},
|
|
4978
|
+
{
|
|
4979
|
+
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>',
|
|
4980
|
+
label: 'Split view',
|
|
4981
|
+
category: 'Session',
|
|
4982
|
+
action: () => toggleSplit(),
|
|
4983
|
+
},
|
|
4984
|
+
{
|
|
4985
|
+
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>',
|
|
4986
|
+
label: 'Stop session',
|
|
4987
|
+
category: 'Session',
|
|
4988
|
+
action: () => {
|
|
4989
|
+
if (!activeId) return;
|
|
4990
|
+
if (!confirm('Stop this session? The process will be killed.')) return;
|
|
4991
|
+
removeSession(activeId);
|
|
4992
|
+
},
|
|
4993
|
+
},
|
|
4994
|
+
{
|
|
4995
|
+
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>',
|
|
4996
|
+
label: 'Find in terminal',
|
|
4997
|
+
category: 'Search',
|
|
4998
|
+
action: () => openSearchBar(),
|
|
4999
|
+
},
|
|
5000
|
+
{
|
|
5001
|
+
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>',
|
|
5002
|
+
label: 'Increase font size',
|
|
5003
|
+
category: 'View',
|
|
5004
|
+
action: () => applyZoom(fontSize + 1),
|
|
5005
|
+
},
|
|
5006
|
+
{
|
|
5007
|
+
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>',
|
|
5008
|
+
label: 'Decrease font size',
|
|
5009
|
+
category: 'View',
|
|
5010
|
+
action: () => applyZoom(fontSize - 1),
|
|
5011
|
+
},
|
|
5012
|
+
{
|
|
5013
|
+
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>',
|
|
5014
|
+
get label() {
|
|
5015
|
+
const current = THEMES.find((x) => x.id === getTheme()) || THEMES[0];
|
|
5016
|
+
return 'Theme (' + current.name + ')';
|
|
5017
|
+
},
|
|
5018
|
+
category: 'View',
|
|
5019
|
+
action: () => {
|
|
5020
|
+
openThemeSubpanel();
|
|
5021
|
+
},
|
|
5022
|
+
},
|
|
5023
|
+
{
|
|
5024
|
+
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>',
|
|
5025
|
+
label: 'Preview port',
|
|
5026
|
+
category: 'View',
|
|
5027
|
+
action: () => openPreviewModal(),
|
|
5028
|
+
},
|
|
5029
|
+
{
|
|
5030
|
+
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>',
|
|
5031
|
+
label: 'Copy link',
|
|
5032
|
+
category: 'Share',
|
|
5033
|
+
action: () => {
|
|
5034
|
+
const url = location.href;
|
|
5035
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
5036
|
+
navigator.clipboard.writeText(url).then(
|
|
5037
|
+
() => showToast('Link copied!'),
|
|
5038
|
+
() => {
|
|
5039
|
+
if (copyToClipboardFallback(url)) showToast('Link copied!');
|
|
5040
|
+
else showShareUrlPrompt(url);
|
|
5041
|
+
},
|
|
5042
|
+
);
|
|
5043
|
+
} else if (copyToClipboardFallback(url)) {
|
|
5044
|
+
showToast('Link copied!');
|
|
5045
|
+
} else {
|
|
5046
|
+
showShareUrlPrompt(url);
|
|
5047
|
+
}
|
|
5048
|
+
},
|
|
5049
|
+
},
|
|
5050
|
+
{
|
|
5051
|
+
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>',
|
|
5052
|
+
get label() {
|
|
5053
|
+
return notificationsEnabled ? 'Notifications (on)' : 'Notifications (off)';
|
|
5054
|
+
},
|
|
5055
|
+
category: 'Notifications',
|
|
5056
|
+
keepOpen: true,
|
|
5057
|
+
action: () => {
|
|
5058
|
+
notificationsEnabled = !notificationsEnabled;
|
|
5059
|
+
localStorage.setItem('termbeam-notifications', notificationsEnabled);
|
|
5060
|
+
if (
|
|
5061
|
+
notificationsEnabled &&
|
|
5062
|
+
'Notification' in window &&
|
|
5063
|
+
Notification.permission === 'default'
|
|
5064
|
+
) {
|
|
5065
|
+
Notification.requestPermission();
|
|
5066
|
+
}
|
|
5067
|
+
showToast(notificationsEnabled ? 'Notifications on' : 'Notifications off');
|
|
5068
|
+
renderPalette();
|
|
5069
|
+
},
|
|
5070
|
+
},
|
|
5071
|
+
{
|
|
5072
|
+
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>',
|
|
5073
|
+
label: 'Refresh',
|
|
5074
|
+
category: 'System',
|
|
5075
|
+
action: () => refreshApp(),
|
|
5076
|
+
},
|
|
5077
|
+
{
|
|
5078
|
+
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>',
|
|
5079
|
+
label: 'Clear terminal',
|
|
5080
|
+
category: 'System',
|
|
5081
|
+
action: () => {
|
|
5082
|
+
if (!activeId) return;
|
|
5083
|
+
const ms = managed.get(activeId);
|
|
5084
|
+
if (ms) ms.term.clear();
|
|
5085
|
+
},
|
|
5086
|
+
},
|
|
5087
|
+
{
|
|
5088
|
+
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>',
|
|
5089
|
+
label: 'About',
|
|
5090
|
+
category: 'System',
|
|
5091
|
+
action: () => {
|
|
5092
|
+
const ver = window._termbeamVersion || 'TermBeam';
|
|
5093
|
+
const overlay = document.createElement('div');
|
|
5094
|
+
overlay.style.cssText =
|
|
5095
|
+
'position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:300;display:flex;align-items:center;justify-content:center;';
|
|
5096
|
+
const box = document.createElement('div');
|
|
5097
|
+
box.style.cssText =
|
|
5098
|
+
'background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:24px;max-width:90vw;width:320px;text-align:center;';
|
|
5099
|
+
box.innerHTML =
|
|
5100
|
+
'<div style="font-size:24px;margin-bottom:8px;">⚡</div>' +
|
|
5101
|
+
'<div style="font-size:16px;font-weight:600;color:var(--text);margin-bottom:4px;">TermBeam</div>' +
|
|
5102
|
+
'<div style="font-size:13px;color:var(--text-secondary);margin-bottom:16px;">' +
|
|
5103
|
+
esc(ver) +
|
|
5104
|
+
'</div>' +
|
|
5105
|
+
'<div style="font-size:12px;color:var(--text-secondary);margin-bottom:16px;">Terminal in your browser, optimized for mobile.</div>' +
|
|
5106
|
+
'<div style="display:flex;gap:16px;justify-content:center;margin-bottom:16px;">' +
|
|
5107
|
+
'<a href="https://github.com/dorlugasigal/TermBeam" target="_blank" rel="noopener" style="color:var(--accent);font-size:12px;text-decoration:none;">GitHub</a>' +
|
|
5108
|
+
'<a href="https://dorlugasigal.github.io/TermBeam/" target="_blank" rel="noopener" style="color:var(--accent);font-size:12px;text-decoration:none;">Docs</a>' +
|
|
5109
|
+
'<a href="https://termbeam.pages.dev" target="_blank" rel="noopener" style="color:var(--accent);font-size:12px;text-decoration:none;">Website</a>' +
|
|
5110
|
+
'</div>';
|
|
5111
|
+
const btn = document.createElement('button');
|
|
5112
|
+
btn.textContent = 'Close';
|
|
5113
|
+
btn.style.cssText =
|
|
5114
|
+
'padding:6px 20px;border-radius:6px;border:none;background:var(--accent);color:#fff;font-size:13px;font-weight:600;cursor:pointer;';
|
|
5115
|
+
btn.onclick = () => overlay.remove();
|
|
5116
|
+
box.appendChild(btn);
|
|
5117
|
+
overlay.appendChild(box);
|
|
5118
|
+
overlay.addEventListener('click', (e) => {
|
|
5119
|
+
if (e.target === overlay) overlay.remove();
|
|
5120
|
+
});
|
|
5121
|
+
document.body.appendChild(overlay);
|
|
5122
|
+
},
|
|
5123
|
+
},
|
|
5124
|
+
];
|
|
5125
|
+
|
|
5126
|
+
const backdrop = document.getElementById('palette-backdrop');
|
|
5127
|
+
const panel = document.getElementById('palette-panel');
|
|
5128
|
+
const body = document.getElementById('palette-body');
|
|
5129
|
+
|
|
5130
|
+
function renderPalette() {
|
|
5131
|
+
const grouped = {};
|
|
5132
|
+
paletteActions.forEach((a) => {
|
|
5133
|
+
if (!grouped[a.category]) grouped[a.category] = [];
|
|
5134
|
+
grouped[a.category].push(a);
|
|
5135
|
+
});
|
|
5136
|
+
body.innerHTML = '';
|
|
5137
|
+
Object.keys(grouped).forEach((cat) => {
|
|
5138
|
+
const header = document.createElement('div');
|
|
5139
|
+
header.className = 'palette-category';
|
|
5140
|
+
header.textContent = cat;
|
|
5141
|
+
body.appendChild(header);
|
|
5142
|
+
grouped[cat].forEach((a) => {
|
|
5143
|
+
const btn = document.createElement('button');
|
|
5144
|
+
btn.className = 'palette-action';
|
|
5145
|
+
btn.innerHTML =
|
|
5146
|
+
'<span class="palette-action-icon">' + a.icon + '</span>' + esc(a.label);
|
|
5147
|
+
btn.addEventListener('click', () => {
|
|
5148
|
+
if (!a.keepOpen) closePalette();
|
|
5149
|
+
a.action();
|
|
5150
|
+
});
|
|
5151
|
+
body.appendChild(btn);
|
|
5152
|
+
});
|
|
5153
|
+
});
|
|
5154
|
+
}
|
|
5155
|
+
|
|
5156
|
+
function openPalette() {
|
|
5157
|
+
backdrop.classList.add('open');
|
|
5158
|
+
panel.classList.add('open');
|
|
5159
|
+
}
|
|
5160
|
+
|
|
5161
|
+
function closePalette() {
|
|
5162
|
+
backdrop.classList.remove('open');
|
|
5163
|
+
panel.classList.remove('open');
|
|
5164
|
+
}
|
|
5165
|
+
|
|
5166
|
+
function togglePalette() {
|
|
5167
|
+
if (panel.classList.contains('open')) closePalette();
|
|
5168
|
+
else openPalette();
|
|
5169
|
+
}
|
|
5170
|
+
|
|
5171
|
+
backdrop.addEventListener('click', closePalette);
|
|
5172
|
+
document.getElementById('palette-close').addEventListener('click', closePalette);
|
|
5173
|
+
document.getElementById('palette-trigger').addEventListener('click', togglePalette);
|
|
5174
|
+
|
|
5175
|
+
document.addEventListener('keydown', (e) => {
|
|
5176
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
|
5177
|
+
e.preventDefault();
|
|
5178
|
+
togglePalette();
|
|
5179
|
+
}
|
|
5180
|
+
if (e.key === 'Escape' && panel.classList.contains('open')) {
|
|
5181
|
+
closePalette();
|
|
5182
|
+
}
|
|
5183
|
+
});
|
|
5184
|
+
|
|
5185
|
+
renderPalette();
|
|
5186
|
+
})();
|
|
5187
|
+
|
|
5188
|
+
// ===== Theme Sub-Panel =====
|
|
5189
|
+
(function setupThemeSubpanel() {
|
|
5190
|
+
const subpanel = document.getElementById('theme-subpanel');
|
|
5191
|
+
const list = document.getElementById('theme-subpanel-list');
|
|
5192
|
+
document.getElementById('theme-subpanel-close').addEventListener('click', () => {
|
|
5193
|
+
subpanel.classList.remove('open');
|
|
5194
|
+
});
|
|
5195
|
+
function renderThemeList() {
|
|
5196
|
+
const cur = getTheme();
|
|
5197
|
+
list.innerHTML = THEMES.map(
|
|
5198
|
+
(t) =>
|
|
5199
|
+
'<button class="theme-subpanel-item' +
|
|
5200
|
+
(t.id === cur ? ' active' : '') +
|
|
5201
|
+
'" data-tid="' +
|
|
5202
|
+
t.id +
|
|
5203
|
+
'"><span class="theme-subpanel-swatch" style="background:' +
|
|
5204
|
+
t.bg +
|
|
5205
|
+
'"></span>' +
|
|
5206
|
+
esc(t.name) +
|
|
5207
|
+
'</button>',
|
|
5208
|
+
).join('');
|
|
5209
|
+
list.querySelectorAll('.theme-subpanel-item').forEach((btn) => {
|
|
5210
|
+
btn.addEventListener('click', () => {
|
|
5211
|
+
applyTheme(btn.dataset.tid);
|
|
5212
|
+
renderThemeList();
|
|
5213
|
+
});
|
|
5214
|
+
});
|
|
5215
|
+
}
|
|
5216
|
+
window.openThemeSubpanel = function () {
|
|
5217
|
+
renderThemeList();
|
|
5218
|
+
subpanel.classList.add('open');
|
|
5219
|
+
};
|
|
5220
|
+
document.addEventListener('keydown', (e) => {
|
|
5221
|
+
if (e.key === 'Escape' && subpanel.classList.contains('open')) {
|
|
5222
|
+
subpanel.classList.remove('open');
|
|
5223
|
+
}
|
|
5224
|
+
});
|
|
5225
|
+
})();
|
|
3846
5226
|
|
|
3847
5227
|
// ===== Service Worker =====
|
|
3848
5228
|
if ('serviceWorker' in navigator) {
|