pinokiod 3.181.0 → 3.183.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/kernel/util.js +15 -2
- package/package.json +1 -1
- package/server/index.js +111 -12
- package/server/public/common.js +433 -240
- package/server/public/files-app/app.css +64 -0
- package/server/public/files-app/app.js +87 -0
- package/server/public/install.js +8 -1
- package/server/public/layout.js +11 -1
- package/server/public/sound/beep.mp3 +0 -0
- package/server/public/sound/bell.mp3 +0 -0
- package/server/public/sound/bright-ring.mp3 +0 -0
- package/server/public/sound/clap.mp3 +0 -0
- package/server/public/sound/deep-ring.mp3 +0 -0
- package/server/public/sound/gasp.mp3 +0 -0
- package/server/public/sound/hehe.mp3 +0 -0
- package/server/public/sound/levelup.mp3 +0 -0
- package/server/public/sound/light-pop.mp3 +0 -0
- package/server/public/sound/light-ring.mp3 +0 -0
- package/server/public/sound/meow.mp3 +0 -0
- package/server/public/sound/piano.mp3 +0 -0
- package/server/public/sound/pop.mp3 +0 -0
- package/server/public/sound/uhoh.mp3 +0 -0
- package/server/public/sound/whistle.mp3 +0 -0
- package/server/public/style.css +173 -2
- package/server/public/tab-idle-notifier.js +697 -4
- package/server/public/terminal-settings.js +1131 -0
- package/server/public/urldropdown.css +28 -1
- package/server/views/{terminals.ejs → agents.ejs} +98 -30
- package/server/views/app.ejs +1 -0
- package/server/views/bootstrap.ejs +8 -0
- package/server/views/connect/x.ejs +1 -0
- package/server/views/connect.ejs +2 -1
- package/server/views/container.ejs +1 -0
- package/server/views/d.ejs +172 -18
- package/server/views/download.ejs +1 -0
- package/server/views/editor.ejs +8 -0
- package/server/views/explore.ejs +1 -0
- package/server/views/file_browser.ejs +4 -0
- package/server/views/form.ejs +1 -0
- package/server/views/frame.ejs +1 -0
- package/server/views/github.ejs +1 -0
- package/server/views/help.ejs +1 -0
- package/server/views/index.ejs +2 -1
- package/server/views/init/index.ejs +10 -1
- package/server/views/install.ejs +8 -0
- package/server/views/mini.ejs +1 -0
- package/server/views/net.ejs +2 -1
- package/server/views/network.ejs +2 -1
- package/server/views/pro.ejs +8 -0
- package/server/views/prototype/index.ejs +9 -0
- package/server/views/review.ejs +1 -0
- package/server/views/screenshots.ejs +2 -2
- package/server/views/settings.ejs +2 -2
- package/server/views/setup.ejs +1 -0
- package/server/views/setup_home.ejs +1 -0
- package/server/views/shell.ejs +8 -0
- package/server/views/terminal.ejs +8 -0
- package/server/views/tools.ejs +2 -2
|
@@ -0,0 +1,1131 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const STORAGE_KEY = 'pinokio.xterm.preferences';
|
|
5
|
+
const CUSTOM_FONT_VALUE = '__custom__';
|
|
6
|
+
const FONT_OPTIONS = [
|
|
7
|
+
{ label: 'Default (Theme)', value: '' },
|
|
8
|
+
{ label: 'Monospace (generic)', value: 'monospace' },
|
|
9
|
+
{ label: 'UI Monospace', value: 'ui-monospace' },
|
|
10
|
+
{ label: 'Courier New', value: '"Courier New", Courier, monospace' },
|
|
11
|
+
{ label: 'Lucida Console', value: '"Lucida Console", "Lucida Sans Typewriter", monospace' },
|
|
12
|
+
{ label: 'Consolas', value: 'Consolas, "Liberation Mono", "Courier New", monospace' },
|
|
13
|
+
{ label: 'Menlo', value: 'Menlo, Monaco, "Courier New", monospace' },
|
|
14
|
+
{ label: 'Monaco', value: 'Monaco, "Courier New", monospace' },
|
|
15
|
+
{ label: 'IBM Plex Mono', value: '"IBM Plex Mono", "Courier New", monospace' },
|
|
16
|
+
{ label: 'Source Code Pro', value: '"Source Code Pro", "Courier New", monospace' },
|
|
17
|
+
{ label: 'Fira Code', value: '"Fira Code", "Courier New", monospace' },
|
|
18
|
+
{ label: 'JetBrains Mono', value: '"JetBrains Mono", "Courier New", monospace' },
|
|
19
|
+
{ label: 'Cascadia Mono', value: '"Cascadia Mono", "Courier New", monospace' },
|
|
20
|
+
{ label: 'Iosevka', value: 'Iosevka, "Courier New", monospace' },
|
|
21
|
+
{ label: 'Anonymous Pro', value: '"Anonymous Pro", "Courier New", monospace' },
|
|
22
|
+
{ label: 'Roboto Mono', value: '"Roboto Mono", "Courier New", monospace' },
|
|
23
|
+
{ label: 'Inconsolata', value: 'Inconsolata, "Courier New", monospace' },
|
|
24
|
+
{ label: 'Hack', value: 'Hack, "Courier New", monospace' },
|
|
25
|
+
{ label: 'Noto Sans Mono', value: '"Noto Sans Mono", "Courier New", monospace' },
|
|
26
|
+
{ label: 'PT Mono', value: '"PT Mono", "Courier New", monospace' },
|
|
27
|
+
{ label: 'Space Mono', value: '"Space Mono", "Courier New", monospace' },
|
|
28
|
+
{ label: 'Custom...', value: CUSTOM_FONT_VALUE }
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const THEME_OPTIONS = [
|
|
32
|
+
{ key: 'foreground', label: 'Foreground' },
|
|
33
|
+
{ key: 'background', label: 'Background' },
|
|
34
|
+
{ key: 'cursor', label: 'Cursor' },
|
|
35
|
+
{ key: 'cursorAccent', label: 'Cursor Accent' },
|
|
36
|
+
{ key: 'selectionBackground', label: 'Selection Background' },
|
|
37
|
+
{ key: 'selectionForeground', label: 'Selection Text' },
|
|
38
|
+
{ key: 'selectionInactiveBackground', label: 'Selection (Inactive)' },
|
|
39
|
+
{ key: 'black', label: 'ANSI 0 Black' },
|
|
40
|
+
{ key: 'red', label: 'ANSI 1 Red' },
|
|
41
|
+
{ key: 'green', label: 'ANSI 2 Green' },
|
|
42
|
+
{ key: 'yellow', label: 'ANSI 3 Yellow' },
|
|
43
|
+
{ key: 'blue', label: 'ANSI 4 Blue' },
|
|
44
|
+
{ key: 'magenta', label: 'ANSI 5 Magenta' },
|
|
45
|
+
{ key: 'cyan', label: 'ANSI 6 Cyan' },
|
|
46
|
+
{ key: 'white', label: 'ANSI 7 White' },
|
|
47
|
+
{ key: 'brightBlack', label: 'ANSI 8 Bright Black' },
|
|
48
|
+
{ key: 'brightRed', label: 'ANSI 9 Bright Red' },
|
|
49
|
+
{ key: 'brightGreen', label: 'ANSI 10 Bright Green' },
|
|
50
|
+
{ key: 'brightYellow', label: 'ANSI 11 Bright Yellow' },
|
|
51
|
+
{ key: 'brightBlue', label: 'ANSI 12 Bright Blue' },
|
|
52
|
+
{ key: 'brightMagenta', label: 'ANSI 13 Bright Magenta' },
|
|
53
|
+
{ key: 'brightCyan', label: 'ANSI 14 Bright Cyan' },
|
|
54
|
+
{ key: 'brightWhite', label: 'ANSI 15 Bright White' }
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const THEME_KEYS = THEME_OPTIONS.map((option) => option.key);
|
|
58
|
+
const HEX_COLOR_REGEX = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
59
|
+
const THEME_KEY_ALIASES = {
|
|
60
|
+
selection: 'selectionBackground'
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function isFiniteNumber(value) {
|
|
64
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
class TerminalSettings {
|
|
68
|
+
constructor() {
|
|
69
|
+
this.preferences = this.loadPreferences();
|
|
70
|
+
this.terminals = new Set();
|
|
71
|
+
this.menus = new Set();
|
|
72
|
+
this.styleElement = null;
|
|
73
|
+
this.currentFontFamily = typeof this.preferences.fontFamily === 'string' ? this.preferences.fontFamily.trim() : '';
|
|
74
|
+
if (typeof document !== 'undefined') {
|
|
75
|
+
const ready = document.readyState;
|
|
76
|
+
const inspect = () => {
|
|
77
|
+
if (this.currentFontFamily && !this.isMonospaceFamily(this.currentFontFamily)) {
|
|
78
|
+
this.warnNonMonospace(this.currentFontFamily);
|
|
79
|
+
delete this.preferences.fontFamily;
|
|
80
|
+
this.currentFontFamily = '';
|
|
81
|
+
}
|
|
82
|
+
this.updateGlobalStylesFromPreferences();
|
|
83
|
+
};
|
|
84
|
+
if (ready === 'complete' || ready === 'interactive') {
|
|
85
|
+
inspect();
|
|
86
|
+
} else {
|
|
87
|
+
document.addEventListener('DOMContentLoaded', inspect, { once: true });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
loadPreferences() {
|
|
93
|
+
if (typeof window === 'undefined' || !window.localStorage) {
|
|
94
|
+
return {};
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const stored = window.localStorage.getItem(STORAGE_KEY);
|
|
98
|
+
if (!stored) {
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
101
|
+
const parsed = JSON.parse(stored);
|
|
102
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
103
|
+
return {};
|
|
104
|
+
}
|
|
105
|
+
if ('fontSize' in parsed) {
|
|
106
|
+
const numeric = parseInt(parsed.fontSize, 10);
|
|
107
|
+
parsed.fontSize = Number.isNaN(numeric) ? undefined : numeric;
|
|
108
|
+
if (!isFiniteNumber(parsed.fontSize)) {
|
|
109
|
+
delete parsed.fontSize;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if ('fontFamily' in parsed && typeof parsed.fontFamily !== 'string') {
|
|
113
|
+
delete parsed.fontFamily;
|
|
114
|
+
}
|
|
115
|
+
if ('theme' in parsed) {
|
|
116
|
+
parsed.theme = this.sanitizeTheme(parsed.theme);
|
|
117
|
+
if (!parsed.theme || !Object.keys(parsed.theme).length) {
|
|
118
|
+
delete parsed.theme;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return parsed;
|
|
122
|
+
} catch (_) {
|
|
123
|
+
return {};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
savePreferences() {
|
|
128
|
+
if (typeof window === 'undefined' || !window.localStorage) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(this.preferences));
|
|
133
|
+
} catch (_) {
|
|
134
|
+
/* ignore */
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
hasPreferences() {
|
|
139
|
+
return Boolean(this.preferences.fontFamily)
|
|
140
|
+
|| isFiniteNumber(this.preferences.fontSize)
|
|
141
|
+
|| this.hasThemePreferences();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
applyToConfig(config) {
|
|
145
|
+
const updated = Object.assign({}, config || {});
|
|
146
|
+
if (isFiniteNumber(this.preferences.fontSize)) {
|
|
147
|
+
updated.fontSize = this.preferences.fontSize;
|
|
148
|
+
}
|
|
149
|
+
if (typeof this.preferences.fontFamily === 'string' && this.preferences.fontFamily.trim()) {
|
|
150
|
+
updated.fontFamily = this.preferences.fontFamily;
|
|
151
|
+
}
|
|
152
|
+
if (this.hasThemePreferences()) {
|
|
153
|
+
const themeBase = Object.assign({}, updated.theme || {});
|
|
154
|
+
const themePrefs = this.getThemePreferences();
|
|
155
|
+
updated.theme = Object.assign(themeBase, themePrefs);
|
|
156
|
+
}
|
|
157
|
+
return updated;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
safeGetOption(term, option) {
|
|
161
|
+
if (!term || typeof term.getOption !== 'function') {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
return term.getOption(option);
|
|
166
|
+
} catch (_) {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
register(term, meta) {
|
|
172
|
+
if (!term || typeof term !== 'object') {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (!term._pinokioBaseOptions) {
|
|
176
|
+
const base = meta && meta.baseConfig ? meta.baseConfig : {};
|
|
177
|
+
const baseFontSize = isFiniteNumber(base.fontSize)
|
|
178
|
+
? base.fontSize
|
|
179
|
+
: this.safeGetOption(term, 'fontSize');
|
|
180
|
+
const baseFamilyRaw = typeof base.fontFamily === 'string' && base.fontFamily.trim()
|
|
181
|
+
? base.fontFamily.trim()
|
|
182
|
+
: this.safeGetOption(term, 'fontFamily');
|
|
183
|
+
const baseFontFamily = baseFamilyRaw || 'monospace';
|
|
184
|
+
const baseThemeRaw = base && base.theme ? base.theme : this.safeGetOption(term, 'theme');
|
|
185
|
+
const baseTheme = this.sanitizeTheme(baseThemeRaw, true);
|
|
186
|
+
term._pinokioBaseOptions = {
|
|
187
|
+
fontSize: isFiniteNumber(baseFontSize) ? baseFontSize : 12,
|
|
188
|
+
fontFamily: baseFontFamily,
|
|
189
|
+
theme: baseTheme
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
this.terminals.add(term);
|
|
193
|
+
this.applyPreferences(term);
|
|
194
|
+
if (!term._pinokioPatchedDispose && typeof term.dispose === 'function') {
|
|
195
|
+
const dispose = term.dispose.bind(term);
|
|
196
|
+
term.dispose = (...args) => {
|
|
197
|
+
this.terminals.delete(term);
|
|
198
|
+
return dispose(...args);
|
|
199
|
+
};
|
|
200
|
+
term._pinokioPatchedDispose = true;
|
|
201
|
+
}
|
|
202
|
+
this.initRunnerMenus();
|
|
203
|
+
this.syncMenus();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
getPrimaryTerminal() {
|
|
207
|
+
const iterator = this.terminals.values();
|
|
208
|
+
const first = iterator.next();
|
|
209
|
+
return first && !first.done ? first.value : null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
getResolvedOption(option) {
|
|
213
|
+
if (option === 'fontSize' && isFiniteNumber(this.preferences.fontSize)) {
|
|
214
|
+
return this.preferences.fontSize;
|
|
215
|
+
}
|
|
216
|
+
if (option === 'fontFamily' && typeof this.preferences.fontFamily === 'string' && this.preferences.fontFamily.trim()) {
|
|
217
|
+
return this.preferences.fontFamily;
|
|
218
|
+
}
|
|
219
|
+
const term = this.getPrimaryTerminal();
|
|
220
|
+
if (!term) {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
const current = this.safeGetOption(term, option);
|
|
224
|
+
if (current !== undefined && current !== null && String(current).trim() !== '') {
|
|
225
|
+
return current;
|
|
226
|
+
}
|
|
227
|
+
const base = term._pinokioBaseOptions || {};
|
|
228
|
+
return base[option];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
applyPreferences(term) {
|
|
232
|
+
if (!term) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const base = term._pinokioBaseOptions || {};
|
|
236
|
+
const sizePref = this.preferences.fontSize;
|
|
237
|
+
const familyPref = typeof this.preferences.fontFamily === 'string' ? this.preferences.fontFamily.trim() : '';
|
|
238
|
+
|
|
239
|
+
const resolvedSize = isFiniteNumber(sizePref)
|
|
240
|
+
? sizePref
|
|
241
|
+
: (isFiniteNumber(base.fontSize) ? base.fontSize : undefined);
|
|
242
|
+
const resolvedFamily = familyPref || base.fontFamily || 'monospace';
|
|
243
|
+
const resolvedTheme = this.resolveTheme(base.theme);
|
|
244
|
+
|
|
245
|
+
let needsRefresh = false;
|
|
246
|
+
if (resolvedSize !== undefined) {
|
|
247
|
+
this.applyNumericOption(term, 'fontSize', resolvedSize);
|
|
248
|
+
needsRefresh = true;
|
|
249
|
+
}
|
|
250
|
+
if (resolvedFamily) {
|
|
251
|
+
this.applyStringOption(term, 'fontFamily', resolvedFamily);
|
|
252
|
+
needsRefresh = true;
|
|
253
|
+
}
|
|
254
|
+
const themeApplied = resolvedTheme ? this.applyThemeOption(term, resolvedTheme) : false;
|
|
255
|
+
if (themeApplied) {
|
|
256
|
+
needsRefresh = true;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (needsRefresh) {
|
|
260
|
+
this.refreshTerm(term, {
|
|
261
|
+
fontSize: resolvedSize,
|
|
262
|
+
fontFamily: resolvedFamily
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
applyNumericOption(term, key, value) {
|
|
268
|
+
if (typeof term.setOption === 'function') {
|
|
269
|
+
try {
|
|
270
|
+
term.setOption(key, value);
|
|
271
|
+
} catch (_) {}
|
|
272
|
+
}
|
|
273
|
+
if (term.element && term.element.style) {
|
|
274
|
+
const cssValue = `${value}px`;
|
|
275
|
+
if (key === 'fontSize') {
|
|
276
|
+
term.element.style.setProperty('--font-size', cssValue);
|
|
277
|
+
term.element.style.fontSize = cssValue;
|
|
278
|
+
} else {
|
|
279
|
+
term.element.style.setProperty(`--${key}`, cssValue);
|
|
280
|
+
term.element.style[key] = cssValue;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
applyStringOption(term, key, value) {
|
|
286
|
+
if (typeof term.setOption === 'function') {
|
|
287
|
+
try {
|
|
288
|
+
term.setOption(key, value);
|
|
289
|
+
} catch (_) {}
|
|
290
|
+
}
|
|
291
|
+
if (term.element && term.element.style) {
|
|
292
|
+
term.element.style.setProperty(`--${key}`, value);
|
|
293
|
+
if (key === 'fontFamily') {
|
|
294
|
+
term.element.style.fontFamily = value;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
applyAll() {
|
|
300
|
+
this.updateGlobalStylesFromPreferences();
|
|
301
|
+
this.terminals.forEach((term) => this.applyPreferences(term));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
updateFontFamily(value) {
|
|
305
|
+
const trimmed = typeof value === 'string' ? value.trim() : '';
|
|
306
|
+
if (trimmed) {
|
|
307
|
+
this.preferences.fontFamily = trimmed;
|
|
308
|
+
this.currentFontFamily = trimmed;
|
|
309
|
+
} else {
|
|
310
|
+
delete this.preferences.fontFamily;
|
|
311
|
+
this.currentFontFamily = '';
|
|
312
|
+
}
|
|
313
|
+
this.savePreferences();
|
|
314
|
+
this.applyAll();
|
|
315
|
+
this.syncMenus();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
updateFontSize(value) {
|
|
319
|
+
if (value === null || value === undefined || value === '') {
|
|
320
|
+
delete this.preferences.fontSize;
|
|
321
|
+
} else {
|
|
322
|
+
const numeric = parseInt(value, 10);
|
|
323
|
+
if (Number.isNaN(numeric)) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const clamped = Math.min(Math.max(numeric, 8), 72);
|
|
327
|
+
this.preferences.fontSize = clamped;
|
|
328
|
+
}
|
|
329
|
+
this.savePreferences();
|
|
330
|
+
this.applyAll();
|
|
331
|
+
this.syncMenus();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
resetPreferences() {
|
|
335
|
+
delete this.preferences.fontFamily;
|
|
336
|
+
delete this.preferences.fontSize;
|
|
337
|
+
delete this.preferences.theme;
|
|
338
|
+
this.currentFontFamily = '';
|
|
339
|
+
this.savePreferences();
|
|
340
|
+
this.applyAll();
|
|
341
|
+
this.syncMenus();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
refreshTerm(term, overrides) {
|
|
345
|
+
if (!term || typeof term.refresh !== 'function') {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
const rows = typeof term.rows === 'number' && term.rows > 0 ? term.rows - 1 : 0;
|
|
350
|
+
term.refresh(0, rows);
|
|
351
|
+
const core = term._core || term._coreService || term.core;
|
|
352
|
+
if (core && core._renderService && typeof core._renderService.onResize === 'function') {
|
|
353
|
+
core._renderService.onResize(term.cols, term.rows);
|
|
354
|
+
}
|
|
355
|
+
if (core && core._charSizeService && typeof core._charSizeService.measure === 'function') {
|
|
356
|
+
core._charSizeService.measure();
|
|
357
|
+
}
|
|
358
|
+
if (core && core._renderService && typeof core._renderService.clear === 'function') {
|
|
359
|
+
core._renderService.clear();
|
|
360
|
+
}
|
|
361
|
+
if (core && core._viewport && typeof core._viewport._refresh === 'function') {
|
|
362
|
+
core._viewport._refresh();
|
|
363
|
+
}
|
|
364
|
+
if (core && core._viewport && typeof core._viewport._syncScrollArea === 'function') {
|
|
365
|
+
core._viewport._syncScrollArea();
|
|
366
|
+
}
|
|
367
|
+
} catch (_) {
|
|
368
|
+
/* ignore */
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
ensureStyleElement() {
|
|
373
|
+
if (typeof document === 'undefined') {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
if (this.styleElement && this.styleElement.isConnected) {
|
|
377
|
+
return this.styleElement;
|
|
378
|
+
}
|
|
379
|
+
const style = document.createElement('style');
|
|
380
|
+
style.id = 'pinokio-terminal-overrides';
|
|
381
|
+
document.head.appendChild(style);
|
|
382
|
+
this.styleElement = style;
|
|
383
|
+
return style;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
removeStyleElement() {
|
|
387
|
+
if (this.styleElement && this.styleElement.parentNode) {
|
|
388
|
+
this.styleElement.parentNode.removeChild(this.styleElement);
|
|
389
|
+
}
|
|
390
|
+
this.styleElement = null;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
updateGlobalStylesFromPreferences() {
|
|
394
|
+
if (typeof document === 'undefined') {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const family = typeof this.preferences.fontFamily === 'string' ? this.preferences.fontFamily.trim() : '';
|
|
398
|
+
const size = isFiniteNumber(this.preferences.fontSize) ? this.preferences.fontSize : null;
|
|
399
|
+
if (!family && !size) {
|
|
400
|
+
this.removeStyleElement();
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const style = this.ensureStyleElement();
|
|
404
|
+
if (!style) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const selectors = [
|
|
408
|
+
'.xterm',
|
|
409
|
+
'.xterm .xterm-rows',
|
|
410
|
+
'.xterm .xterm-rows span',
|
|
411
|
+
'.xterm .xterm-text-layer',
|
|
412
|
+
'.xterm .xterm-text-layer canvas',
|
|
413
|
+
'.xterm .xterm-cursor-layer',
|
|
414
|
+
'.xterm .xterm-char-measure-element'
|
|
415
|
+
];
|
|
416
|
+
const rules = [];
|
|
417
|
+
if (family) {
|
|
418
|
+
rules.push(`${selectors.join(', ')} { font-family: ${family} !important; }`);
|
|
419
|
+
}
|
|
420
|
+
if (size) {
|
|
421
|
+
rules.push(`${selectors.join(', ')} { font-size: ${size}px !important; }`);
|
|
422
|
+
}
|
|
423
|
+
style.textContent = rules.join('\n');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
sanitizeTheme(raw, allowUnknown) {
|
|
427
|
+
if (!raw || typeof raw !== 'object') {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
const sanitized = {};
|
|
431
|
+
const keys = allowUnknown ? Object.keys(raw) : THEME_KEYS;
|
|
432
|
+
keys.forEach((originalKey) => {
|
|
433
|
+
let key = originalKey;
|
|
434
|
+
if (!THEME_KEYS.includes(key) && THEME_KEY_ALIASES[key]) {
|
|
435
|
+
key = THEME_KEY_ALIASES[key];
|
|
436
|
+
}
|
|
437
|
+
if (!allowUnknown && !THEME_KEYS.includes(key)) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const value = raw[originalKey];
|
|
441
|
+
if (typeof value === 'string') {
|
|
442
|
+
const trimmed = value.trim();
|
|
443
|
+
if (trimmed) {
|
|
444
|
+
sanitized[key] = trimmed;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
return Object.keys(sanitized).length ? sanitized : null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
getThemePreferences() {
|
|
452
|
+
if (!this.preferences.theme || typeof this.preferences.theme !== 'object') {
|
|
453
|
+
return {};
|
|
454
|
+
}
|
|
455
|
+
return this.sanitizeTheme(this.preferences.theme) || {};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
hasThemePreferences() {
|
|
459
|
+
return Object.keys(this.getThemePreferences()).length > 0;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
resolveTheme(baseTheme) {
|
|
463
|
+
const baseSanitized = this.sanitizeTheme(baseTheme, true) || {};
|
|
464
|
+
const prefs = this.getThemePreferences();
|
|
465
|
+
if (!Object.keys(baseSanitized).length && !Object.keys(prefs).length) {
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
return Object.assign({}, baseSanitized, prefs);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
getResolvedTheme() {
|
|
472
|
+
const term = this.getPrimaryTerminal();
|
|
473
|
+
let baseTheme = null;
|
|
474
|
+
if (term && term._pinokioBaseOptions && term._pinokioBaseOptions.theme) {
|
|
475
|
+
baseTheme = term._pinokioBaseOptions.theme;
|
|
476
|
+
} else if (term) {
|
|
477
|
+
baseTheme = this.safeGetOption(term, 'theme');
|
|
478
|
+
}
|
|
479
|
+
return this.resolveTheme(baseTheme) || {};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
colorToPicker(value) {
|
|
483
|
+
if (typeof value !== 'string') {
|
|
484
|
+
return '';
|
|
485
|
+
}
|
|
486
|
+
const trimmed = value.trim();
|
|
487
|
+
if (!HEX_COLOR_REGEX.test(trimmed)) {
|
|
488
|
+
return '';
|
|
489
|
+
}
|
|
490
|
+
if (trimmed.length === 4) {
|
|
491
|
+
const r = trimmed[1];
|
|
492
|
+
const g = trimmed[2];
|
|
493
|
+
const b = trimmed[3];
|
|
494
|
+
return `#${r}${r}${g}${g}${b}${b}`.toLowerCase();
|
|
495
|
+
}
|
|
496
|
+
return trimmed.slice(0, 7).toLowerCase();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
isValidThemeColor(value) {
|
|
500
|
+
if (typeof value !== 'string') {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
const trimmed = value.trim();
|
|
504
|
+
if (!trimmed) {
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
if (typeof window !== 'undefined' && window.CSS && typeof window.CSS.supports === 'function') {
|
|
508
|
+
try {
|
|
509
|
+
if (window.CSS.supports('color', trimmed)) {
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
} catch (_) {}
|
|
513
|
+
}
|
|
514
|
+
return HEX_COLOR_REGEX.test(trimmed);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
applyThemeOption(term, theme) {
|
|
518
|
+
if (!term) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const nextTheme = Object.assign({}, theme);
|
|
522
|
+
let applied = false;
|
|
523
|
+
if (typeof term.setOption === 'function') {
|
|
524
|
+
try {
|
|
525
|
+
term.setOption('theme', nextTheme);
|
|
526
|
+
applied = true;
|
|
527
|
+
} catch (_) {}
|
|
528
|
+
} else if (term.options) {
|
|
529
|
+
term.options.theme = nextTheme;
|
|
530
|
+
applied = true;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const element = term.element;
|
|
534
|
+
if (element && element.style) {
|
|
535
|
+
if (nextTheme.background) {
|
|
536
|
+
element.style.backgroundColor = nextTheme.background;
|
|
537
|
+
} else {
|
|
538
|
+
element.style.backgroundColor = '';
|
|
539
|
+
}
|
|
540
|
+
if (nextTheme.foreground) {
|
|
541
|
+
element.style.color = nextTheme.foreground;
|
|
542
|
+
} else {
|
|
543
|
+
element.style.color = '';
|
|
544
|
+
}
|
|
545
|
+
const viewport = element.querySelector('.xterm-viewport');
|
|
546
|
+
if (viewport && viewport.style && nextTheme.background) {
|
|
547
|
+
viewport.style.backgroundColor = nextTheme.background;
|
|
548
|
+
} else if (viewport && viewport.style) {
|
|
549
|
+
viewport.style.backgroundColor = '';
|
|
550
|
+
}
|
|
551
|
+
const rows = element.querySelector('.xterm-rows');
|
|
552
|
+
if (rows && rows.style && nextTheme.foreground) {
|
|
553
|
+
rows.style.color = nextTheme.foreground;
|
|
554
|
+
} else if (rows && rows.style) {
|
|
555
|
+
rows.style.color = '';
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return applied;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
updateThemeValue(key, value) {
|
|
563
|
+
if (!THEME_KEYS.includes(key)) {
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
const trimmed = typeof value === 'string' ? value.trim() : '';
|
|
567
|
+
if (trimmed && !this.isValidThemeColor(trimmed)) {
|
|
568
|
+
this.syncMenus();
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (!trimmed) {
|
|
572
|
+
if (this.preferences.theme && typeof this.preferences.theme === 'object') {
|
|
573
|
+
delete this.preferences.theme[key];
|
|
574
|
+
if (!Object.keys(this.preferences.theme).length) {
|
|
575
|
+
delete this.preferences.theme;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
} else {
|
|
579
|
+
if (!this.preferences.theme || typeof this.preferences.theme !== 'object') {
|
|
580
|
+
this.preferences.theme = {};
|
|
581
|
+
}
|
|
582
|
+
this.preferences.theme[key] = trimmed;
|
|
583
|
+
}
|
|
584
|
+
this.savePreferences();
|
|
585
|
+
this.applyAll();
|
|
586
|
+
this.syncMenus();
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
isMonospaceFamily() {
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
warnNonMonospace() {}
|
|
594
|
+
|
|
595
|
+
initRunnerMenus() {
|
|
596
|
+
if (typeof document === 'undefined') {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
const terminalContainer = document.querySelector('#terminal, #terminal2, [data-terminal-root]');
|
|
600
|
+
if (!terminalContainer) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
const runners = document.querySelectorAll('.runner');
|
|
604
|
+
if (!runners.length) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
runners.forEach((runner) => {
|
|
608
|
+
if (runner.dataset.terminalConfigAttached === 'true') {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
runner.dataset.terminalConfigAttached = 'true';
|
|
612
|
+
const menu = this.createMenu(runner);
|
|
613
|
+
if (menu) {
|
|
614
|
+
this.menus.add(menu);
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
createMenu(runner) {
|
|
620
|
+
if (!runner) {
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
const wrapper = document.createElement('div');
|
|
624
|
+
wrapper.className = 'terminal-config';
|
|
625
|
+
|
|
626
|
+
const button = document.createElement('button');
|
|
627
|
+
button.type = 'button';
|
|
628
|
+
button.className = 'btn terminal-config-button';
|
|
629
|
+
button.innerHTML = '<i class="fa-solid fa-sliders"></i> Configure';
|
|
630
|
+
button.setAttribute('aria-haspopup', 'true');
|
|
631
|
+
button.setAttribute('aria-expanded', 'false');
|
|
632
|
+
|
|
633
|
+
const menu = document.createElement('div');
|
|
634
|
+
menu.className = 'terminal-config-menu';
|
|
635
|
+
menu.hidden = true;
|
|
636
|
+
menu.style.display = 'none';
|
|
637
|
+
menu.setAttribute('aria-hidden', 'true');
|
|
638
|
+
|
|
639
|
+
const title = document.createElement('div');
|
|
640
|
+
title.className = 'terminal-config-title';
|
|
641
|
+
title.textContent = 'Terminal appearance';
|
|
642
|
+
|
|
643
|
+
const note = document.createElement('div');
|
|
644
|
+
note.className = 'terminal-config-note';
|
|
645
|
+
|
|
646
|
+
const fontGroup = document.createElement('div');
|
|
647
|
+
fontGroup.className = 'terminal-config-group';
|
|
648
|
+
|
|
649
|
+
const fontLabel = document.createElement('label');
|
|
650
|
+
fontLabel.className = 'terminal-config-label';
|
|
651
|
+
fontLabel.textContent = 'Font family';
|
|
652
|
+
|
|
653
|
+
const fontSelect = document.createElement('select');
|
|
654
|
+
fontSelect.className = 'terminal-config-select';
|
|
655
|
+
FONT_OPTIONS.forEach((option) => {
|
|
656
|
+
const opt = document.createElement('option');
|
|
657
|
+
opt.value = option.value;
|
|
658
|
+
opt.textContent = option.label;
|
|
659
|
+
fontSelect.appendChild(opt);
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
const customInput = document.createElement('input');
|
|
663
|
+
customInput.type = 'text';
|
|
664
|
+
customInput.placeholder = 'Enter custom font stack';
|
|
665
|
+
customInput.className = 'terminal-config-input terminal-config-input-custom';
|
|
666
|
+
customInput.hidden = true;
|
|
667
|
+
|
|
668
|
+
fontGroup.appendChild(fontLabel);
|
|
669
|
+
fontGroup.appendChild(fontSelect);
|
|
670
|
+
fontGroup.appendChild(customInput);
|
|
671
|
+
|
|
672
|
+
const sizeGroup = document.createElement('div');
|
|
673
|
+
sizeGroup.className = 'terminal-config-group';
|
|
674
|
+
|
|
675
|
+
const sizeLabel = document.createElement('label');
|
|
676
|
+
sizeLabel.className = 'terminal-config-label';
|
|
677
|
+
sizeLabel.textContent = 'Font size';
|
|
678
|
+
|
|
679
|
+
const sizeInput = document.createElement('input');
|
|
680
|
+
sizeInput.type = 'number';
|
|
681
|
+
sizeInput.min = '8';
|
|
682
|
+
sizeInput.max = '72';
|
|
683
|
+
sizeInput.step = '1';
|
|
684
|
+
sizeInput.className = 'terminal-config-input';
|
|
685
|
+
|
|
686
|
+
sizeGroup.appendChild(sizeLabel);
|
|
687
|
+
sizeGroup.appendChild(sizeInput);
|
|
688
|
+
|
|
689
|
+
const themeSection = document.createElement('div');
|
|
690
|
+
themeSection.className = 'terminal-config-section terminal-config-theme';
|
|
691
|
+
|
|
692
|
+
const themeTitle = document.createElement('div');
|
|
693
|
+
themeTitle.className = 'terminal-config-subtitle';
|
|
694
|
+
themeTitle.textContent = 'Theme colors';
|
|
695
|
+
|
|
696
|
+
const themeHelp = document.createElement('div');
|
|
697
|
+
themeHelp.className = 'terminal-config-help';
|
|
698
|
+
themeHelp.textContent = 'Override background, foreground, cursor, selection, and ANSI palette colors.';
|
|
699
|
+
|
|
700
|
+
const themeGrid = document.createElement('div');
|
|
701
|
+
themeGrid.className = 'terminal-config-theme-grid';
|
|
702
|
+
|
|
703
|
+
const themeInputs = new Map();
|
|
704
|
+
THEME_OPTIONS.forEach((option) => {
|
|
705
|
+
const row = document.createElement('div');
|
|
706
|
+
row.className = 'terminal-config-theme-row';
|
|
707
|
+
|
|
708
|
+
const label = document.createElement('label');
|
|
709
|
+
label.className = 'terminal-config-label terminal-config-theme-label';
|
|
710
|
+
label.textContent = option.label;
|
|
711
|
+
|
|
712
|
+
const colorInput = document.createElement('input');
|
|
713
|
+
colorInput.type = 'color';
|
|
714
|
+
colorInput.className = 'terminal-config-color-input';
|
|
715
|
+
colorInput.value = '#000000';
|
|
716
|
+
colorInput.dataset.themeKey = option.key;
|
|
717
|
+
|
|
718
|
+
const textInput = document.createElement('input');
|
|
719
|
+
textInput.type = 'text';
|
|
720
|
+
textInput.className = 'terminal-config-input terminal-config-theme-text';
|
|
721
|
+
textInput.placeholder = '#000000';
|
|
722
|
+
textInput.dataset.themeKey = option.key;
|
|
723
|
+
|
|
724
|
+
const clearButton = document.createElement('button');
|
|
725
|
+
clearButton.type = 'button';
|
|
726
|
+
clearButton.className = 'btn2 terminal-config-theme-clear';
|
|
727
|
+
clearButton.innerHTML = '<i class="fa-solid fa-xmark"></i>';
|
|
728
|
+
clearButton.title = 'Remove override';
|
|
729
|
+
|
|
730
|
+
row.appendChild(label);
|
|
731
|
+
row.appendChild(colorInput);
|
|
732
|
+
row.appendChild(textInput);
|
|
733
|
+
row.appendChild(clearButton);
|
|
734
|
+
themeGrid.appendChild(row);
|
|
735
|
+
|
|
736
|
+
themeInputs.set(option.key, {
|
|
737
|
+
row,
|
|
738
|
+
label,
|
|
739
|
+
colorInput,
|
|
740
|
+
textInput,
|
|
741
|
+
clearButton
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
themeSection.appendChild(themeTitle);
|
|
746
|
+
themeSection.appendChild(themeHelp);
|
|
747
|
+
themeSection.appendChild(themeGrid);
|
|
748
|
+
|
|
749
|
+
const actions = document.createElement('div');
|
|
750
|
+
actions.className = 'terminal-config-actions';
|
|
751
|
+
|
|
752
|
+
const resetButton = document.createElement('button');
|
|
753
|
+
resetButton.type = 'button';
|
|
754
|
+
resetButton.className = 'btn2 terminal-config-reset';
|
|
755
|
+
resetButton.innerHTML = '<i class="fa-solid fa-arrow-rotate-left"></i> Reset';
|
|
756
|
+
|
|
757
|
+
actions.appendChild(resetButton);
|
|
758
|
+
|
|
759
|
+
menu.appendChild(title);
|
|
760
|
+
menu.appendChild(note);
|
|
761
|
+
menu.appendChild(fontGroup);
|
|
762
|
+
menu.appendChild(sizeGroup);
|
|
763
|
+
menu.appendChild(themeSection);
|
|
764
|
+
menu.appendChild(actions);
|
|
765
|
+
|
|
766
|
+
wrapper.appendChild(button);
|
|
767
|
+
wrapper.appendChild(menu);
|
|
768
|
+
runner.appendChild(wrapper);
|
|
769
|
+
|
|
770
|
+
const menuRecord = {
|
|
771
|
+
runner,
|
|
772
|
+
wrapper,
|
|
773
|
+
button,
|
|
774
|
+
menu,
|
|
775
|
+
fontSelect,
|
|
776
|
+
customInput,
|
|
777
|
+
sizeInput,
|
|
778
|
+
resetButton,
|
|
779
|
+
note,
|
|
780
|
+
themeSection,
|
|
781
|
+
themeInputs,
|
|
782
|
+
placeholder: null,
|
|
783
|
+
isPortal: false,
|
|
784
|
+
close: null
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
this.attachMenuHandlers(menuRecord);
|
|
788
|
+
this.syncMenu(menuRecord);
|
|
789
|
+
return menuRecord;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
attachMenuHandlers(menuRecord) {
|
|
793
|
+
const { button, menu, wrapper, fontSelect, customInput, sizeInput, resetButton, themeInputs } = menuRecord;
|
|
794
|
+
if (!button || !menu) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
const settings = this;
|
|
798
|
+
let outsideClickHandler = null;
|
|
799
|
+
let escapeHandler = null;
|
|
800
|
+
let scrollHandler = null;
|
|
801
|
+
let resizeHandler = null;
|
|
802
|
+
|
|
803
|
+
const viewportPadding = 12;
|
|
804
|
+
const verticalGap = 8;
|
|
805
|
+
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
806
|
+
|
|
807
|
+
const positionMenu = () => {
|
|
808
|
+
if (menu.hidden) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
const rect = button.getBoundingClientRect();
|
|
812
|
+
const width = menu.offsetWidth;
|
|
813
|
+
const height = menu.offsetHeight;
|
|
814
|
+
const maxLeft = window.innerWidth - width - viewportPadding;
|
|
815
|
+
const maxTop = window.innerHeight - height - viewportPadding;
|
|
816
|
+
const idealLeft = rect.right - width;
|
|
817
|
+
const idealTop = rect.bottom + verticalGap;
|
|
818
|
+
const left = clamp(idealLeft, viewportPadding, Math.max(viewportPadding, maxLeft));
|
|
819
|
+
const top = clamp(idealTop, viewportPadding, Math.max(viewportPadding, maxTop));
|
|
820
|
+
menu.style.left = `${Math.round(left)}px`;
|
|
821
|
+
menu.style.top = `${Math.round(top)}px`;
|
|
822
|
+
menu.style.zIndex = '1000000';
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
const closeMenu = () => {
|
|
826
|
+
if (menu.hidden) {
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
menu.hidden = true;
|
|
830
|
+
menu.style.display = 'none';
|
|
831
|
+
menu.style.position = 'absolute';
|
|
832
|
+
menu.style.left = '';
|
|
833
|
+
menu.style.top = '';
|
|
834
|
+
menu.style.right = '';
|
|
835
|
+
menu.style.bottom = '';
|
|
836
|
+
menu.style.zIndex = '';
|
|
837
|
+
menu.setAttribute('aria-hidden', 'true');
|
|
838
|
+
wrapper.classList.remove('terminal-config-open');
|
|
839
|
+
button.setAttribute('aria-expanded', 'false');
|
|
840
|
+
if (outsideClickHandler) {
|
|
841
|
+
document.removeEventListener('mousedown', outsideClickHandler, true);
|
|
842
|
+
outsideClickHandler = null;
|
|
843
|
+
}
|
|
844
|
+
if (escapeHandler) {
|
|
845
|
+
document.removeEventListener('keydown', escapeHandler, true);
|
|
846
|
+
escapeHandler = null;
|
|
847
|
+
}
|
|
848
|
+
if (scrollHandler) {
|
|
849
|
+
window.removeEventListener('scroll', scrollHandler, true);
|
|
850
|
+
scrollHandler = null;
|
|
851
|
+
}
|
|
852
|
+
if (resizeHandler) {
|
|
853
|
+
window.removeEventListener('resize', resizeHandler, true);
|
|
854
|
+
resizeHandler = null;
|
|
855
|
+
}
|
|
856
|
+
if (menuRecord.isPortal) {
|
|
857
|
+
if (menuRecord.placeholder && menuRecord.placeholder.parentNode) {
|
|
858
|
+
menuRecord.placeholder.parentNode.replaceChild(menu, menuRecord.placeholder);
|
|
859
|
+
} else {
|
|
860
|
+
wrapper.appendChild(menu);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
menuRecord.placeholder = null;
|
|
864
|
+
menuRecord.isPortal = false;
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
const openMenu = () => {
|
|
868
|
+
if (!menu.hidden) {
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
if (!menuRecord.placeholder) {
|
|
872
|
+
menuRecord.placeholder = document.createComment('terminal-config-menu');
|
|
873
|
+
}
|
|
874
|
+
if (!menuRecord.isPortal) {
|
|
875
|
+
wrapper.insertBefore(menuRecord.placeholder, menu);
|
|
876
|
+
document.body.appendChild(menu);
|
|
877
|
+
menuRecord.isPortal = true;
|
|
878
|
+
}
|
|
879
|
+
menu.hidden = false;
|
|
880
|
+
menu.style.display = 'block';
|
|
881
|
+
menu.style.position = 'fixed';
|
|
882
|
+
menu.style.right = 'auto';
|
|
883
|
+
menu.style.bottom = 'auto';
|
|
884
|
+
menu.removeAttribute('aria-hidden');
|
|
885
|
+
wrapper.classList.add('terminal-config-open');
|
|
886
|
+
button.setAttribute('aria-expanded', 'true');
|
|
887
|
+
positionMenu();
|
|
888
|
+
outsideClickHandler = function (event) {
|
|
889
|
+
if (!menu.contains(event.target) && !button.contains(event.target)) {
|
|
890
|
+
closeMenu();
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
escapeHandler = function (event) {
|
|
894
|
+
if (event.key === 'Escape') {
|
|
895
|
+
event.preventDefault();
|
|
896
|
+
closeMenu();
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
document.addEventListener('mousedown', outsideClickHandler, true);
|
|
900
|
+
document.addEventListener('keydown', escapeHandler, true);
|
|
901
|
+
scrollHandler = () => positionMenu();
|
|
902
|
+
resizeHandler = () => positionMenu();
|
|
903
|
+
window.addEventListener('scroll', scrollHandler, true);
|
|
904
|
+
window.addEventListener('resize', resizeHandler, true);
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
button.addEventListener('click', (event) => {
|
|
908
|
+
event.preventDefault();
|
|
909
|
+
if (menu.hidden) {
|
|
910
|
+
openMenu();
|
|
911
|
+
} else {
|
|
912
|
+
closeMenu();
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
fontSelect.addEventListener('change', () => {
|
|
917
|
+
if (fontSelect.value === CUSTOM_FONT_VALUE) {
|
|
918
|
+
customInput.hidden = false;
|
|
919
|
+
customInput.focus();
|
|
920
|
+
if (customInput.value.trim()) {
|
|
921
|
+
settings.updateFontFamily(customInput.value);
|
|
922
|
+
} else {
|
|
923
|
+
settings.updateFontFamily(null);
|
|
924
|
+
}
|
|
925
|
+
} else {
|
|
926
|
+
customInput.hidden = true;
|
|
927
|
+
settings.updateFontFamily(fontSelect.value);
|
|
928
|
+
}
|
|
929
|
+
positionMenu();
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
const handleCustomInput = () => {
|
|
933
|
+
const value = customInput.value.trim();
|
|
934
|
+
if (!value) {
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
settings.updateFontFamily(value);
|
|
938
|
+
positionMenu();
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
customInput.addEventListener('change', handleCustomInput);
|
|
942
|
+
customInput.addEventListener('blur', handleCustomInput);
|
|
943
|
+
customInput.addEventListener('keydown', (event) => {
|
|
944
|
+
if (event.key === 'Enter') {
|
|
945
|
+
event.preventDefault();
|
|
946
|
+
handleCustomInput();
|
|
947
|
+
closeMenu();
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
const handleSizeChange = () => {
|
|
952
|
+
const raw = sizeInput.value.trim();
|
|
953
|
+
if (!raw) {
|
|
954
|
+
settings.updateFontSize(null);
|
|
955
|
+
positionMenu();
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
settings.updateFontSize(raw);
|
|
959
|
+
positionMenu();
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
sizeInput.addEventListener('change', handleSizeChange);
|
|
963
|
+
sizeInput.addEventListener('blur', handleSizeChange);
|
|
964
|
+
|
|
965
|
+
if (themeInputs && themeInputs.size) {
|
|
966
|
+
const applyThemeValue = (key, value) => {
|
|
967
|
+
settings.updateThemeValue(key, value);
|
|
968
|
+
positionMenu();
|
|
969
|
+
};
|
|
970
|
+
themeInputs.forEach((controls, key) => {
|
|
971
|
+
const { colorInput, textInput, clearButton } = controls;
|
|
972
|
+
if (colorInput) {
|
|
973
|
+
const handleColor = () => {
|
|
974
|
+
const value = colorInput.value ? colorInput.value.trim() : '';
|
|
975
|
+
if (textInput) {
|
|
976
|
+
textInput.value = value;
|
|
977
|
+
}
|
|
978
|
+
applyThemeValue(key, value);
|
|
979
|
+
};
|
|
980
|
+
colorInput.addEventListener('input', handleColor);
|
|
981
|
+
colorInput.addEventListener('change', handleColor);
|
|
982
|
+
}
|
|
983
|
+
if (textInput) {
|
|
984
|
+
const handleTextInput = () => {
|
|
985
|
+
const value = textInput.value.trim();
|
|
986
|
+
if (!value) {
|
|
987
|
+
applyThemeValue(key, null);
|
|
988
|
+
} else if (settings.isValidThemeColor(value)) {
|
|
989
|
+
applyThemeValue(key, value);
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
const commitText = () => {
|
|
993
|
+
applyThemeValue(key, textInput.value.trim());
|
|
994
|
+
};
|
|
995
|
+
textInput.addEventListener('input', handleTextInput);
|
|
996
|
+
textInput.addEventListener('change', commitText);
|
|
997
|
+
textInput.addEventListener('blur', commitText);
|
|
998
|
+
textInput.addEventListener('keydown', (event) => {
|
|
999
|
+
if (event.key === 'Enter') {
|
|
1000
|
+
event.preventDefault();
|
|
1001
|
+
commitText();
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
if (clearButton) {
|
|
1006
|
+
clearButton.addEventListener('click', (event) => {
|
|
1007
|
+
event.preventDefault();
|
|
1008
|
+
if (colorInput) {
|
|
1009
|
+
colorInput.value = '#000000';
|
|
1010
|
+
}
|
|
1011
|
+
if (textInput) {
|
|
1012
|
+
textInput.value = '';
|
|
1013
|
+
}
|
|
1014
|
+
applyThemeValue(key, null);
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
resetButton.addEventListener('click', (event) => {
|
|
1021
|
+
event.preventDefault();
|
|
1022
|
+
settings.resetPreferences();
|
|
1023
|
+
closeMenu();
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
menuRecord.close = closeMenu;
|
|
1027
|
+
menuRecord.positionMenu = positionMenu;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
syncMenus() {
|
|
1031
|
+
this.menus.forEach((menu) => this.syncMenu(menu));
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
syncMenu(menuRecord) {
|
|
1035
|
+
if (!menuRecord) {
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
const { fontSelect, customInput, sizeInput, note, resetButton, themeInputs } = menuRecord;
|
|
1039
|
+
const prefFamily = typeof this.preferences.fontFamily === 'string' ? this.preferences.fontFamily.trim() : '';
|
|
1040
|
+
const prefSize = this.preferences.fontSize;
|
|
1041
|
+
const resolvedFamily = this.getResolvedOption('fontFamily');
|
|
1042
|
+
const resolvedSize = this.getResolvedOption('fontSize');
|
|
1043
|
+
|
|
1044
|
+
const knownOption = FONT_OPTIONS.find((option) => option.value === prefFamily);
|
|
1045
|
+
if (prefFamily && !knownOption) {
|
|
1046
|
+
fontSelect.value = CUSTOM_FONT_VALUE;
|
|
1047
|
+
customInput.hidden = false;
|
|
1048
|
+
customInput.value = prefFamily;
|
|
1049
|
+
} else {
|
|
1050
|
+
fontSelect.value = prefFamily && knownOption ? prefFamily : '';
|
|
1051
|
+
customInput.hidden = fontSelect.value !== CUSTOM_FONT_VALUE;
|
|
1052
|
+
if (customInput.hidden) {
|
|
1053
|
+
customInput.value = '';
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
if (prefFamily || (fontSelect.value && fontSelect.value !== CUSTOM_FONT_VALUE)) {
|
|
1058
|
+
customInput.placeholder = prefFamily || resolvedFamily || 'Enter custom font stack';
|
|
1059
|
+
} else {
|
|
1060
|
+
customInput.placeholder = resolvedFamily || 'Enter custom font stack';
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (isFiniteNumber(prefSize)) {
|
|
1064
|
+
sizeInput.value = String(prefSize);
|
|
1065
|
+
} else {
|
|
1066
|
+
sizeInput.value = '';
|
|
1067
|
+
}
|
|
1068
|
+
sizeInput.placeholder = resolvedSize ? String(resolvedSize) : '';
|
|
1069
|
+
|
|
1070
|
+
const themePrefs = this.getThemePreferences();
|
|
1071
|
+
const resolvedTheme = this.getResolvedTheme() || {};
|
|
1072
|
+
const themeOverrideCount = Object.keys(themePrefs).length;
|
|
1073
|
+
|
|
1074
|
+
if (themeInputs && themeInputs.size) {
|
|
1075
|
+
themeInputs.forEach((controls, key) => {
|
|
1076
|
+
const prefValue = themePrefs[key] || '';
|
|
1077
|
+
const effectiveValue = resolvedTheme[key] || '';
|
|
1078
|
+
if (controls.textInput) {
|
|
1079
|
+
controls.textInput.value = prefValue;
|
|
1080
|
+
controls.textInput.placeholder = prefValue ? '' : effectiveValue;
|
|
1081
|
+
controls.textInput.title = prefValue ? `Override: ${prefValue}` : (effectiveValue ? `Inherited: ${effectiveValue}` : 'No color override');
|
|
1082
|
+
}
|
|
1083
|
+
if (controls.colorInput) {
|
|
1084
|
+
const pickerValue = prefValue
|
|
1085
|
+
? this.colorToPicker(prefValue)
|
|
1086
|
+
: this.colorToPicker(effectiveValue);
|
|
1087
|
+
if (pickerValue) {
|
|
1088
|
+
controls.colorInput.value = pickerValue;
|
|
1089
|
+
delete controls.colorInput.dataset.invalid;
|
|
1090
|
+
} else {
|
|
1091
|
+
controls.colorInput.value = '#000000';
|
|
1092
|
+
controls.colorInput.dataset.invalid = 'true';
|
|
1093
|
+
}
|
|
1094
|
+
controls.colorInput.title = prefValue
|
|
1095
|
+
? `Override: ${prefValue}`
|
|
1096
|
+
: (effectiveValue ? `Inherited: ${effectiveValue}` : 'No color override');
|
|
1097
|
+
}
|
|
1098
|
+
if (controls.clearButton) {
|
|
1099
|
+
controls.clearButton.disabled = !prefValue;
|
|
1100
|
+
controls.clearButton.title = prefValue ? 'Remove override' : 'No override to remove';
|
|
1101
|
+
}
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
if (note) {
|
|
1106
|
+
const familyText = resolvedFamily ? resolvedFamily : 'Default';
|
|
1107
|
+
const sizeText = resolvedSize ? `${resolvedSize}px` : 'Auto';
|
|
1108
|
+
const themeText = themeOverrideCount
|
|
1109
|
+
? `Theme overrides: ${themeOverrideCount}`
|
|
1110
|
+
: 'Theme: Default';
|
|
1111
|
+
note.textContent = `Current font: ${familyText} | ${sizeText} | ${themeText}`;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if (resetButton) {
|
|
1115
|
+
resetButton.disabled = !this.hasPreferences();
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
const settings = new TerminalSettings();
|
|
1121
|
+
window.PinokioTerminalSettings = settings;
|
|
1122
|
+
|
|
1123
|
+
if (typeof document !== 'undefined') {
|
|
1124
|
+
const readyState = document.readyState;
|
|
1125
|
+
if (readyState === 'complete' || readyState === 'interactive') {
|
|
1126
|
+
settings.initRunnerMenus();
|
|
1127
|
+
} else {
|
|
1128
|
+
document.addEventListener('DOMContentLoaded', () => settings.initRunnerMenus(), { once: true });
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
})();
|