pinokiod 3.86.0 → 3.87.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/Dockerfile +61 -0
- package/docker-entrypoint.sh +75 -0
- package/kernel/api/hf/index.js +1 -1
- package/kernel/api/index.js +1 -1
- package/kernel/api/shell/index.js +6 -0
- package/kernel/api/terminal/index.js +166 -0
- package/kernel/bin/conda.js +3 -2
- package/kernel/bin/index.js +53 -2
- package/kernel/bin/setup.js +32 -0
- package/kernel/bin/vs.js +11 -2
- package/kernel/index.js +42 -2
- package/kernel/info.js +36 -0
- package/kernel/peer.js +42 -15
- package/kernel/router/index.js +23 -15
- package/kernel/router/localhost_static_router.js +0 -3
- package/kernel/router/pinokio_domain_router.js +333 -0
- package/kernel/shells.js +21 -1
- package/kernel/util.js +2 -2
- package/package.json +2 -1
- package/script/install-mode.js +33 -0
- package/script/pinokio.json +7 -0
- package/server/index.js +513 -173
- package/server/public/Socket.js +48 -0
- package/server/public/common.js +1441 -276
- package/server/public/fseditor.js +71 -12
- package/server/public/install.js +1 -1
- package/server/public/layout.js +740 -0
- package/server/public/modalinput.js +0 -1
- package/server/public/style.css +97 -105
- package/server/public/tab-idle-notifier.js +629 -0
- package/server/public/terminal_input_tracker.js +63 -0
- package/server/public/urldropdown.css +319 -53
- package/server/public/urldropdown.js +615 -159
- package/server/public/window_storage.js +97 -28
- package/server/socket.js +40 -9
- package/server/views/500.ejs +2 -2
- package/server/views/app.ejs +3136 -1367
- package/server/views/bookmarklet.ejs +1 -1
- package/server/views/bootstrap.ejs +1 -1
- package/server/views/columns.ejs +2 -13
- package/server/views/connect.ejs +3 -4
- package/server/views/container.ejs +1 -2
- package/server/views/d.ejs +223 -53
- package/server/views/editor.ejs +1 -1
- package/server/views/file_explorer.ejs +1 -1
- package/server/views/index.ejs +12 -11
- package/server/views/index2.ejs +4 -4
- package/server/views/init/index.ejs +4 -5
- package/server/views/install.ejs +1 -1
- package/server/views/layout.ejs +105 -0
- package/server/views/net.ejs +39 -7
- package/server/views/network.ejs +20 -6
- package/server/views/network2.ejs +1 -1
- package/server/views/old_network.ejs +2 -2
- package/server/views/partials/dynamic.ejs +3 -5
- package/server/views/partials/menu.ejs +3 -5
- package/server/views/partials/running.ejs +1 -1
- package/server/views/pro.ejs +1 -1
- package/server/views/prototype/index.ejs +1 -1
- package/server/views/review.ejs +11 -23
- package/server/views/rows.ejs +2 -13
- package/server/views/screenshots.ejs +293 -138
- package/server/views/settings.ejs +3 -4
- package/server/views/setup.ejs +1 -2
- package/server/views/shell.ejs +277 -26
- package/server/views/terminal.ejs +322 -49
- package/server/views/tools.ejs +448 -4
|
@@ -4,13 +4,64 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
function initUrlDropdown(config = {}) {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
if (window.PinokioUrlDropdown && typeof window.PinokioUrlDropdown.destroy === 'function') {
|
|
8
|
+
try {
|
|
9
|
+
window.PinokioUrlDropdown.destroy();
|
|
10
|
+
} catch (error) {
|
|
11
|
+
console.error('Failed to dispose existing URL dropdown', error);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let urlInput = document.querySelector('.urlbar input[type="url"]');
|
|
16
|
+
let dropdown = document.getElementById('url-dropdown');
|
|
9
17
|
const mobileButton = document.getElementById('mobile-link-button');
|
|
10
|
-
|
|
18
|
+
const mobileButtonHandler = () => showMobileModal();
|
|
19
|
+
|
|
20
|
+
const fallbackElements = {
|
|
21
|
+
form: null,
|
|
22
|
+
dropdown: null
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const ensureFallbackInput = () => {
|
|
26
|
+
if (fallbackElements.form) {
|
|
27
|
+
const existingInput = fallbackElements.form.querySelector('input[type="url"]');
|
|
28
|
+
if (existingInput) return existingInput;
|
|
29
|
+
}
|
|
30
|
+
if (!document.body) return null;
|
|
31
|
+
const form = document.createElement('form');
|
|
32
|
+
form.className = 'urlbar pinokio-url-fallback';
|
|
33
|
+
form.id = 'pinokio-url-fallback-form';
|
|
34
|
+
form.style.display = 'none';
|
|
35
|
+
const input = document.createElement('input');
|
|
36
|
+
input.type = 'url';
|
|
37
|
+
form.appendChild(input);
|
|
38
|
+
document.body.appendChild(form);
|
|
39
|
+
fallbackElements.form = form;
|
|
40
|
+
return input;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const ensureFallbackDropdown = () => {
|
|
44
|
+
if (fallbackElements.dropdown) return fallbackElements.dropdown;
|
|
45
|
+
if (!document.body) return null;
|
|
46
|
+
const el = document.createElement('div');
|
|
47
|
+
el.id = 'url-dropdown';
|
|
48
|
+
el.className = 'url-dropdown';
|
|
49
|
+
el.style.display = 'none';
|
|
50
|
+
document.body.appendChild(el);
|
|
51
|
+
fallbackElements.dropdown = el;
|
|
52
|
+
return el;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (!urlInput) {
|
|
56
|
+
urlInput = ensureFallbackInput();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!dropdown) {
|
|
60
|
+
dropdown = ensureFallbackDropdown();
|
|
61
|
+
}
|
|
62
|
+
|
|
11
63
|
if (!urlInput || !dropdown) {
|
|
12
|
-
console.warn('URL dropdown elements not found');
|
|
13
|
-
return;
|
|
64
|
+
console.warn('URL dropdown elements not found; process picker modal will be limited.');
|
|
14
65
|
}
|
|
15
66
|
|
|
16
67
|
// Configuration options
|
|
@@ -21,11 +72,62 @@ function initUrlDropdown(config = {}) {
|
|
|
21
72
|
...config
|
|
22
73
|
};
|
|
23
74
|
|
|
75
|
+
const toArray = (value) => {
|
|
76
|
+
if (!value) return [];
|
|
77
|
+
if (Array.isArray(value)) {
|
|
78
|
+
return value.filter(Boolean);
|
|
79
|
+
}
|
|
80
|
+
return [value];
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const getProcessUrls = (process) => {
|
|
84
|
+
const urls = (process && process.urls) || {};
|
|
85
|
+
const httpUrl = urls.http || (process && process.ip ? `http://${process.ip}` : null);
|
|
86
|
+
const httpsUrls = toArray(urls.https || (process && process.protocol === 'https' && process.url ? process.url : null))
|
|
87
|
+
.map((value) => {
|
|
88
|
+
if (typeof value !== 'string') return null;
|
|
89
|
+
const trimmed = value.trim();
|
|
90
|
+
if (!trimmed) return null;
|
|
91
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
92
|
+
return trimmed.replace(/^http:/i, 'https:');
|
|
93
|
+
}
|
|
94
|
+
return `https://${trimmed}`;
|
|
95
|
+
})
|
|
96
|
+
.filter(Boolean);
|
|
97
|
+
return { httpUrl, httpsUrls };
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const getProcessDisplayUrl = (process) => {
|
|
101
|
+
if (process && typeof process.url === 'string' && process.url.trim().length > 0) {
|
|
102
|
+
return process.url;
|
|
103
|
+
}
|
|
104
|
+
const { httpUrl, httpsUrls } = getProcessUrls(process);
|
|
105
|
+
if (httpsUrls.length > 0) {
|
|
106
|
+
return httpsUrls[0];
|
|
107
|
+
}
|
|
108
|
+
return httpUrl;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const getProcessFilterValues = (process) => {
|
|
112
|
+
const urls = new Set();
|
|
113
|
+
const display = getProcessDisplayUrl(process);
|
|
114
|
+
if (display) {
|
|
115
|
+
urls.add(display);
|
|
116
|
+
}
|
|
117
|
+
const { httpUrl, httpsUrls } = getProcessUrls(process);
|
|
118
|
+
if (httpUrl) {
|
|
119
|
+
urls.add(httpUrl);
|
|
120
|
+
}
|
|
121
|
+
httpsUrls.forEach((httpsUrl) => urls.add(httpsUrl));
|
|
122
|
+
return Array.from(urls);
|
|
123
|
+
};
|
|
124
|
+
|
|
24
125
|
let isDropdownVisible = false;
|
|
25
126
|
let allProcesses = []; // Store all processes for filtering
|
|
26
127
|
let filteredProcesses = []; // Store currently filtered processes
|
|
27
128
|
let createLauncherModal = null;
|
|
28
129
|
let pendingCreateDetail = null;
|
|
130
|
+
let mobileModalKeydownHandler = null;
|
|
29
131
|
const EMPTY_STATE_DESCRIPTION = 'enter a prompt to create a launcher';
|
|
30
132
|
|
|
31
133
|
// Initialize input field state based on clear behavior
|
|
@@ -39,17 +141,19 @@ function initUrlDropdown(config = {}) {
|
|
|
39
141
|
});
|
|
40
142
|
|
|
41
143
|
// Event listeners
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
144
|
+
if (urlInput) {
|
|
145
|
+
urlInput.addEventListener('focus', function() {
|
|
146
|
+
// Auto-select text for restore behavior to make filtering easier
|
|
147
|
+
if (options.clearBehavior === 'restore' && urlInput.value) {
|
|
148
|
+
// Use setTimeout to ensure the focus event completes first
|
|
149
|
+
setTimeout(() => {
|
|
150
|
+
urlInput.select();
|
|
151
|
+
}, 0);
|
|
152
|
+
}
|
|
153
|
+
showDropdown();
|
|
154
|
+
});
|
|
155
|
+
urlInput.addEventListener('input', handleInputChange);
|
|
156
|
+
}
|
|
53
157
|
|
|
54
158
|
// Hide dropdown when clicking outside
|
|
55
159
|
document.addEventListener('click', function(e) {
|
|
@@ -84,6 +188,7 @@ function initUrlDropdown(config = {}) {
|
|
|
84
188
|
|
|
85
189
|
|
|
86
190
|
function initializeInputValue() {
|
|
191
|
+
if (!urlInput) return;
|
|
87
192
|
if (options.clearBehavior === 'empty') {
|
|
88
193
|
urlInput.value = '';
|
|
89
194
|
} else if (options.clearBehavior === 'restore') {
|
|
@@ -95,6 +200,7 @@ function initUrlDropdown(config = {}) {
|
|
|
95
200
|
}
|
|
96
201
|
|
|
97
202
|
function showDropdown() {
|
|
203
|
+
if (!dropdown || !urlInput) return;
|
|
98
204
|
if (isDropdownVisible && allProcesses.length > 0) {
|
|
99
205
|
// If dropdown is already visible and we have data, show all initially
|
|
100
206
|
showAllProcesses();
|
|
@@ -138,6 +244,7 @@ function initUrlDropdown(config = {}) {
|
|
|
138
244
|
}
|
|
139
245
|
|
|
140
246
|
function handleInputChange() {
|
|
247
|
+
if (!urlInput) return;
|
|
141
248
|
if (!isDropdownVisible) return;
|
|
142
249
|
|
|
143
250
|
const query = urlInput.value.toLowerCase().trim();
|
|
@@ -152,11 +259,12 @@ function initUrlDropdown(config = {}) {
|
|
|
152
259
|
} else {
|
|
153
260
|
// Filter processes based on name and URL
|
|
154
261
|
filteredProcesses = allProcesses.filter(process => {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
262
|
+
const name = (process.name || '').toLowerCase();
|
|
263
|
+
if (name.includes(query)) {
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
const urls = getProcessFilterValues(process);
|
|
267
|
+
return urls.some((value) => (value || '').toLowerCase().includes(query));
|
|
160
268
|
});
|
|
161
269
|
}
|
|
162
270
|
|
|
@@ -165,7 +273,9 @@ function initUrlDropdown(config = {}) {
|
|
|
165
273
|
|
|
166
274
|
function hideDropdown() {
|
|
167
275
|
isDropdownVisible = false;
|
|
168
|
-
dropdown
|
|
276
|
+
if (dropdown) {
|
|
277
|
+
dropdown.style.display = 'none';
|
|
278
|
+
}
|
|
169
279
|
}
|
|
170
280
|
|
|
171
281
|
function createHostBadge(host) {
|
|
@@ -258,8 +368,6 @@ function initUrlDropdown(config = {}) {
|
|
|
258
368
|
platformIcon = 'fa-solid fa-desktop';
|
|
259
369
|
break;
|
|
260
370
|
}
|
|
261
|
-
|
|
262
|
-
console.log({ isLocal, host })
|
|
263
371
|
const hostName = isLocal ? `${host.name} (This Machine)` : `${host.name} (Peer)`;
|
|
264
372
|
|
|
265
373
|
return `
|
|
@@ -274,15 +382,63 @@ function initUrlDropdown(config = {}) {
|
|
|
274
382
|
}
|
|
275
383
|
|
|
276
384
|
function populateDropdown(processes) {
|
|
385
|
+
const currentUrl = window.location.href;
|
|
386
|
+
const currentTitle = document.title || 'Current tab';
|
|
387
|
+
|
|
388
|
+
let html = '';
|
|
389
|
+
if (currentUrl) {
|
|
390
|
+
html += `
|
|
391
|
+
<div class="url-dropdown-host-header current-tab">
|
|
392
|
+
<span class="host-name">Current tab</span>
|
|
393
|
+
</div>
|
|
394
|
+
<div class="url-dropdown-item" data-url="${escapeHtml(currentUrl)}" data-host-type="current">
|
|
395
|
+
<div class="url-dropdown-name">
|
|
396
|
+
<span>
|
|
397
|
+
<i class="fa-solid fa-clone"></i>
|
|
398
|
+
${escapeHtml(currentTitle)}
|
|
399
|
+
</span>
|
|
400
|
+
</div>
|
|
401
|
+
<div class="url-dropdown-url">${escapeHtml(currentUrl)}</div>
|
|
402
|
+
</div>
|
|
403
|
+
`;
|
|
404
|
+
}
|
|
405
|
+
|
|
277
406
|
if (processes.length === 0) {
|
|
278
|
-
|
|
407
|
+
html += createEmptyStateHtml(getEmptyStateMessage(urlInput));
|
|
408
|
+
dropdown.innerHTML = html;
|
|
409
|
+
attachCreateButtonHandler(dropdown, urlInput);
|
|
410
|
+
dropdown.querySelectorAll('.url-dropdown-item:not(.non-selectable)').forEach(item => {
|
|
411
|
+
item.addEventListener('click', function() {
|
|
412
|
+
const url = this.getAttribute('data-url');
|
|
413
|
+
const type = this.getAttribute('data-host-type');
|
|
414
|
+
urlInput.value = url;
|
|
415
|
+
urlInput.setAttribute("data-host-type", type || 'current');
|
|
416
|
+
hideDropdown();
|
|
417
|
+
|
|
418
|
+
if (type === "local") {
|
|
419
|
+
let redirect_uri = "/container?url=" + url;
|
|
420
|
+
location.href = redirect_uri;
|
|
421
|
+
} else {
|
|
422
|
+
if (!type || type === 'current') {
|
|
423
|
+
location.href = url;
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
let u = new URL(url);
|
|
427
|
+
if (String(u.port) === "42000") {
|
|
428
|
+
window.open(url, "_blank", 'self');
|
|
429
|
+
} else {
|
|
430
|
+
let redirect_uri = "/container?url=" + url;
|
|
431
|
+
location.href = redirect_uri;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
});
|
|
279
436
|
return;
|
|
280
437
|
}
|
|
281
438
|
|
|
282
439
|
// Group processes by host
|
|
283
440
|
const groupedProcesses = groupProcessesByHost(processes);
|
|
284
|
-
|
|
285
|
-
let html = '';
|
|
441
|
+
|
|
286
442
|
Object.keys(groupedProcesses).forEach(hostKey => {
|
|
287
443
|
const hostData = groupedProcesses[hostKey];
|
|
288
444
|
const hostInfo = hostData.host;
|
|
@@ -312,7 +468,10 @@ function initUrlDropdown(config = {}) {
|
|
|
312
468
|
`;
|
|
313
469
|
} else {
|
|
314
470
|
// Normal selectable item
|
|
315
|
-
const url =
|
|
471
|
+
const url = getProcessDisplayUrl(process);
|
|
472
|
+
if (!url) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
316
475
|
html += `
|
|
317
476
|
<div class="url-dropdown-item" data-url="${url}" data-host-type="${process.host.local ? "local" : "remote"}">
|
|
318
477
|
<div class="url-dropdown-name">
|
|
@@ -334,13 +493,15 @@ function initUrlDropdown(config = {}) {
|
|
|
334
493
|
const url = this.getAttribute('data-url');
|
|
335
494
|
const type = this.getAttribute('data-host-type');
|
|
336
495
|
urlInput.value = url;
|
|
337
|
-
urlInput.setAttribute("data-host-type", type);
|
|
496
|
+
urlInput.setAttribute("data-host-type", type || 'remote');
|
|
338
497
|
hideDropdown();
|
|
339
498
|
|
|
340
499
|
// Navigate directly instead of dispatching submit event
|
|
341
500
|
if (type === "local") {
|
|
342
501
|
let redirect_uri = "/container?url=" + url;
|
|
343
502
|
location.href = redirect_uri;
|
|
503
|
+
} else if (type === 'current') {
|
|
504
|
+
location.href = url;
|
|
344
505
|
} else {
|
|
345
506
|
let u = new URL(url);
|
|
346
507
|
if (String(u.port) === "42000") {
|
|
@@ -370,98 +531,345 @@ function initUrlDropdown(config = {}) {
|
|
|
370
531
|
return div.innerHTML;
|
|
371
532
|
}
|
|
372
533
|
|
|
534
|
+
function getModalOverlay() {
|
|
535
|
+
return document.getElementById('url-modal-overlay');
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function getModalRefs() {
|
|
539
|
+
const overlay = getModalOverlay();
|
|
540
|
+
return overlay ? overlay._modalRefs : null;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function resolveModal(refs, value) {
|
|
544
|
+
if (!refs || typeof refs.resolve !== 'function') {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const resolver = refs.resolve;
|
|
548
|
+
refs.resolve = null;
|
|
549
|
+
refs.returnSelection = false;
|
|
550
|
+
try {
|
|
551
|
+
resolver(value);
|
|
552
|
+
} catch (err) {
|
|
553
|
+
console.error('Failed to resolve URL modal selection', err);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function buildPaneUrl(url, type) {
|
|
558
|
+
if (!url || typeof url !== 'string') return url;
|
|
559
|
+
|
|
560
|
+
const ensureContainer = () => {
|
|
561
|
+
if (url.startsWith('/container?url=')) return url;
|
|
562
|
+
return `/container?url=${encodeURIComponent(url)}`;
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
switch (type) {
|
|
566
|
+
case 'current':
|
|
567
|
+
return url;
|
|
568
|
+
case 'local':
|
|
569
|
+
return ensureContainer();
|
|
570
|
+
case 'remote':
|
|
571
|
+
try {
|
|
572
|
+
const parsed = new URL(url);
|
|
573
|
+
if (String(parsed.port) === '42000') {
|
|
574
|
+
return url;
|
|
575
|
+
}
|
|
576
|
+
} catch (_) {
|
|
577
|
+
// If URL constructor fails, fall back to container redirect
|
|
578
|
+
}
|
|
579
|
+
return ensureContainer();
|
|
580
|
+
default:
|
|
581
|
+
return ensureContainer();
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function handleModalSelection(url, type) {
|
|
586
|
+
const refs = getModalRefs();
|
|
587
|
+
if (!refs) return;
|
|
588
|
+
|
|
589
|
+
const paneUrl = buildPaneUrl(url, type);
|
|
590
|
+
|
|
591
|
+
if (refs.input) {
|
|
592
|
+
refs.input.value = paneUrl;
|
|
593
|
+
if (typeof refs.updateConfirmState === 'function') {
|
|
594
|
+
refs.updateConfirmState();
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (!refs.returnSelection && urlInput) {
|
|
599
|
+
urlInput.value = paneUrl;
|
|
600
|
+
urlInput.setAttribute('data-host-type', type || 'remote');
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (refs.returnSelection) {
|
|
604
|
+
resolveModal(refs, paneUrl);
|
|
605
|
+
closeMobileModal({ suppressResolve: true });
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
closeMobileModal();
|
|
610
|
+
|
|
611
|
+
if (!type || type === 'current') {
|
|
612
|
+
location.href = paneUrl;
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (type === 'local' || type === 'remote') {
|
|
617
|
+
if (paneUrl.startsWith('/container?url=')) {
|
|
618
|
+
location.href = paneUrl;
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
try {
|
|
622
|
+
const parsed = new URL(paneUrl);
|
|
623
|
+
if (String(parsed.port) === '42000') {
|
|
624
|
+
window.open(paneUrl, '_blank', 'self');
|
|
625
|
+
} else {
|
|
626
|
+
location.href = `/container?url=${encodeURIComponent(paneUrl)}`;
|
|
627
|
+
}
|
|
628
|
+
} catch (error) {
|
|
629
|
+
console.error('Failed to open URL, redirecting directly', error);
|
|
630
|
+
location.href = paneUrl;
|
|
631
|
+
}
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
location.href = paneUrl;
|
|
636
|
+
}
|
|
637
|
+
|
|
373
638
|
// Mobile modal functionality
|
|
374
639
|
function createMobileModal() {
|
|
375
640
|
const overlay = document.createElement('div');
|
|
376
|
-
overlay.className = 'url-modal-overlay';
|
|
641
|
+
overlay.className = 'modal-overlay url-modal-overlay';
|
|
377
642
|
overlay.id = 'url-modal-overlay';
|
|
378
|
-
|
|
643
|
+
|
|
379
644
|
const content = document.createElement('div');
|
|
380
645
|
content.className = 'url-modal-content';
|
|
381
|
-
|
|
382
|
-
|
|
646
|
+
content.setAttribute('role', 'dialog');
|
|
647
|
+
content.setAttribute('aria-modal', 'true');
|
|
648
|
+
|
|
649
|
+
const closeButton = document.createElement('button');
|
|
650
|
+
closeButton.type = 'button';
|
|
383
651
|
closeButton.className = 'url-modal-close';
|
|
652
|
+
closeButton.setAttribute('aria-label', 'Close');
|
|
384
653
|
closeButton.innerHTML = '×';
|
|
385
|
-
|
|
386
|
-
|
|
654
|
+
|
|
655
|
+
const heading = document.createElement('h3');
|
|
656
|
+
heading.textContent = 'Open a URL';
|
|
657
|
+
heading.id = 'url-modal-title';
|
|
658
|
+
|
|
659
|
+
const description = document.createElement('p');
|
|
660
|
+
description.className = 'url-modal-description';
|
|
661
|
+
description.id = 'url-modal-description';
|
|
662
|
+
description.textContent = 'Enter a local URL or choose from running processes.';
|
|
663
|
+
|
|
664
|
+
content.setAttribute('aria-labelledby', heading.id);
|
|
665
|
+
content.setAttribute('aria-describedby', description.id);
|
|
666
|
+
|
|
387
667
|
const modalInput = document.createElement('input');
|
|
388
668
|
modalInput.type = 'url';
|
|
389
669
|
modalInput.className = 'url-modal-input';
|
|
390
|
-
modalInput.placeholder = '
|
|
391
|
-
|
|
670
|
+
modalInput.placeholder = 'Example: http://localhost:7860';
|
|
671
|
+
|
|
392
672
|
const modalDropdown = document.createElement('div');
|
|
393
673
|
modalDropdown.className = 'url-dropdown';
|
|
394
674
|
modalDropdown.id = 'url-modal-dropdown';
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
675
|
+
|
|
676
|
+
const actions = document.createElement('div');
|
|
677
|
+
actions.className = 'url-modal-actions';
|
|
678
|
+
|
|
679
|
+
const cancelButton = document.createElement('button');
|
|
680
|
+
cancelButton.type = 'button';
|
|
681
|
+
cancelButton.className = 'url-modal-button cancel';
|
|
682
|
+
cancelButton.textContent = 'Cancel';
|
|
683
|
+
|
|
684
|
+
const confirmButton = document.createElement('button');
|
|
685
|
+
confirmButton.type = 'button';
|
|
686
|
+
confirmButton.className = 'url-modal-button confirm';
|
|
687
|
+
confirmButton.textContent = 'Open';
|
|
688
|
+
confirmButton.disabled = true;
|
|
689
|
+
|
|
690
|
+
actions.append(cancelButton, confirmButton);
|
|
691
|
+
|
|
692
|
+
content.append(closeButton, heading, description, modalInput, modalDropdown, actions);
|
|
693
|
+
overlay.append(content);
|
|
694
|
+
|
|
695
|
+
const updateConfirmState = () => {
|
|
696
|
+
confirmButton.disabled = !modalInput.value.trim();
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
modalInput.addEventListener('focus', () => {
|
|
700
|
+
if (options.clearBehavior === 'restore' && modalInput.value) {
|
|
701
|
+
setTimeout(() => modalInput.select(), 0);
|
|
702
|
+
}
|
|
703
|
+
updateConfirmState();
|
|
704
|
+
showModalDropdown(modalDropdown);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
modalInput.addEventListener('input', () => {
|
|
708
|
+
handleModalInputChange(modalInput, modalDropdown);
|
|
709
|
+
updateConfirmState();
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
modalInput.addEventListener('keydown', (event) => {
|
|
713
|
+
if (event.key === 'Enter') {
|
|
714
|
+
event.preventDefault();
|
|
715
|
+
submitMobileModal();
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
cancelButton.addEventListener('click', closeMobileModal);
|
|
720
|
+
confirmButton.addEventListener('click', submitMobileModal);
|
|
721
|
+
closeButton.addEventListener('click', closeMobileModal);
|
|
722
|
+
|
|
723
|
+
overlay.addEventListener('click', (event) => {
|
|
724
|
+
if (event.target === overlay) {
|
|
725
|
+
closeMobileModal();
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
overlay._modalRefs = {
|
|
730
|
+
input: modalInput,
|
|
731
|
+
dropdown: modalDropdown,
|
|
732
|
+
confirmButton,
|
|
733
|
+
cancelButton,
|
|
734
|
+
closeButton,
|
|
735
|
+
heading,
|
|
736
|
+
description,
|
|
737
|
+
updateConfirmState,
|
|
738
|
+
defaults: {
|
|
739
|
+
title: heading.textContent,
|
|
740
|
+
description: description.textContent,
|
|
741
|
+
confirmLabel: confirmButton.textContent
|
|
742
|
+
},
|
|
743
|
+
context: 'default',
|
|
744
|
+
returnSelection: false,
|
|
745
|
+
includeCurrent: true,
|
|
746
|
+
resolve: null
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
return overlay;
|
|
407
750
|
}
|
|
408
|
-
|
|
409
|
-
function showMobileModal() {
|
|
410
|
-
let
|
|
411
|
-
if (!
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
751
|
+
|
|
752
|
+
function showMobileModal(customOptions = {}) {
|
|
753
|
+
let overlay = document.getElementById('url-modal-overlay');
|
|
754
|
+
if (!overlay) {
|
|
755
|
+
overlay = createMobileModal();
|
|
756
|
+
document.body.appendChild(overlay);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const refs = overlay._modalRefs || {};
|
|
760
|
+
const modalInput = refs.input;
|
|
761
|
+
const updateConfirmState = refs.updateConfirmState;
|
|
762
|
+
if (!modalInput || !updateConfirmState) return undefined;
|
|
763
|
+
|
|
764
|
+
const defaults = refs.defaults || {};
|
|
765
|
+
const title = customOptions.title || defaults.title || 'Open a URL';
|
|
766
|
+
const descriptionText = customOptions.description || defaults.description || 'Enter a local URL or choose from running processes.';
|
|
767
|
+
const confirmLabel = customOptions.confirmLabel || defaults.confirmLabel || 'Open';
|
|
768
|
+
const includeCurrent = customOptions.includeCurrent !== false;
|
|
769
|
+
const initialValue = customOptions.initialValue !== undefined
|
|
770
|
+
? customOptions.initialValue
|
|
771
|
+
: (options.clearBehavior === 'restore'
|
|
772
|
+
? ((urlInput && urlInput.value) || options.defaultValue || '')
|
|
773
|
+
: '');
|
|
774
|
+
|
|
775
|
+
refs.heading.textContent = title;
|
|
776
|
+
refs.description.textContent = descriptionText;
|
|
777
|
+
refs.confirmButton.textContent = confirmLabel;
|
|
778
|
+
refs.includeCurrent = includeCurrent;
|
|
779
|
+
refs.context = customOptions.context || 'default';
|
|
780
|
+
refs.returnSelection = Boolean(customOptions.awaitSelection);
|
|
781
|
+
refs.resolve = null;
|
|
782
|
+
|
|
783
|
+
modalInput.value = initialValue;
|
|
784
|
+
updateConfirmState();
|
|
785
|
+
|
|
786
|
+
requestAnimationFrame(() => {
|
|
787
|
+
overlay.classList.add('is-visible');
|
|
788
|
+
requestAnimationFrame(() => modalInput.focus());
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
if (!mobileModalKeydownHandler) {
|
|
792
|
+
mobileModalKeydownHandler = (event) => {
|
|
793
|
+
if (event.key === 'Escape') {
|
|
794
|
+
event.preventDefault();
|
|
430
795
|
closeMobileModal();
|
|
431
796
|
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
urlInput.closest('form').dispatchEvent(new Event('submit'));
|
|
441
|
-
closeMobileModal();
|
|
442
|
-
}
|
|
443
|
-
}
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
document.addEventListener('keydown', mobileModalKeydownHandler, true);
|
|
801
|
+
|
|
802
|
+
if (refs.returnSelection) {
|
|
803
|
+
return new Promise((resolve) => {
|
|
804
|
+
refs.resolve = resolve;
|
|
444
805
|
});
|
|
445
806
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
807
|
+
|
|
808
|
+
return undefined;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
function closeMobileModal(options = {}) {
|
|
812
|
+
const overlay = getModalOverlay();
|
|
813
|
+
if (!overlay) return;
|
|
814
|
+
overlay.classList.remove('is-visible');
|
|
815
|
+
const refs = overlay._modalRefs;
|
|
816
|
+
|
|
817
|
+
if (refs?.dropdown) {
|
|
818
|
+
refs.dropdown.style.display = 'none';
|
|
819
|
+
}
|
|
820
|
+
if (refs?.confirmButton) {
|
|
821
|
+
refs.confirmButton.disabled = true;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (refs) {
|
|
825
|
+
if (options.resolveValue !== undefined) {
|
|
826
|
+
resolveModal(refs, options.resolveValue);
|
|
827
|
+
} else if (refs.returnSelection && options.suppressResolve !== true) {
|
|
828
|
+
resolveModal(refs, null);
|
|
829
|
+
} else if (!refs.returnSelection || options.keepMode) {
|
|
830
|
+
// Preserve resolver when explicitly requested
|
|
831
|
+
} else {
|
|
832
|
+
refs.resolve = null;
|
|
833
|
+
refs.returnSelection = false;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
if (!options.keepMode) {
|
|
837
|
+
refs.context = 'default';
|
|
838
|
+
refs.includeCurrent = true;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (mobileModalKeydownHandler) {
|
|
843
|
+
document.removeEventListener('keydown', mobileModalKeydownHandler, true);
|
|
844
|
+
mobileModalKeydownHandler = null;
|
|
455
845
|
}
|
|
456
|
-
|
|
457
|
-
setTimeout(() => modalInput.focus(), 100);
|
|
458
846
|
}
|
|
459
|
-
|
|
460
|
-
function
|
|
461
|
-
const
|
|
462
|
-
if (
|
|
463
|
-
|
|
847
|
+
|
|
848
|
+
function submitMobileModal() {
|
|
849
|
+
const refs = getModalRefs();
|
|
850
|
+
if (!refs || !refs.input) return;
|
|
851
|
+
const input = refs.input;
|
|
852
|
+
const value = input.value.trim();
|
|
853
|
+
if (!value) return;
|
|
854
|
+
|
|
855
|
+
if (refs.returnSelection) {
|
|
856
|
+
const paneUrl = buildPaneUrl(value, 'remote');
|
|
857
|
+
resolveModal(refs, paneUrl);
|
|
858
|
+
closeMobileModal({ suppressResolve: true });
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
if (urlInput) {
|
|
863
|
+
const paneUrl = buildPaneUrl(value, 'remote');
|
|
864
|
+
urlInput.value = paneUrl;
|
|
865
|
+
const form = urlInput.closest('form');
|
|
866
|
+
if (form) {
|
|
867
|
+
form.dispatchEvent(new Event('submit'));
|
|
868
|
+
} else {
|
|
869
|
+
location.href = paneUrl;
|
|
870
|
+
}
|
|
464
871
|
}
|
|
872
|
+
closeMobileModal();
|
|
465
873
|
}
|
|
466
874
|
|
|
467
875
|
function showModalDropdown(modalDropdown) {
|
|
@@ -499,10 +907,12 @@ function initUrlDropdown(config = {}) {
|
|
|
499
907
|
filtered = allProcesses;
|
|
500
908
|
} else if (query) {
|
|
501
909
|
filtered = allProcesses.filter(process => {
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
910
|
+
const name = (process.name || '').toLowerCase();
|
|
911
|
+
if (name.includes(query)) {
|
|
912
|
+
return true;
|
|
913
|
+
}
|
|
914
|
+
const urls = getProcessFilterValues(process);
|
|
915
|
+
return urls.some((value) => (value || '').toLowerCase().includes(query));
|
|
506
916
|
});
|
|
507
917
|
}
|
|
508
918
|
|
|
@@ -511,33 +921,58 @@ function initUrlDropdown(config = {}) {
|
|
|
511
921
|
|
|
512
922
|
function populateModalDropdown(processes, modalDropdown) {
|
|
513
923
|
const modalInput = modalDropdown.parentElement.querySelector('.url-modal-input');
|
|
514
|
-
|
|
924
|
+
const currentUrl = window.location.href;
|
|
925
|
+
const currentTitle = document.title || 'Current tab';
|
|
926
|
+
const overlayRefs = getModalRefs();
|
|
927
|
+
const includeCurrent = overlayRefs?.includeCurrent !== false;
|
|
928
|
+
|
|
929
|
+
let html = '';
|
|
930
|
+
|
|
931
|
+
if (includeCurrent && currentUrl) {
|
|
932
|
+
html += `
|
|
933
|
+
<div class="url-dropdown-host-header current-tab">
|
|
934
|
+
<span class="host-name">Current tab</span>
|
|
935
|
+
</div>
|
|
936
|
+
<div class="url-dropdown-item" data-url="${escapeHtml(currentUrl)}" data-host-type="current">
|
|
937
|
+
<div class="url-dropdown-name">
|
|
938
|
+
<i class="fa-solid fa-clone"></i>
|
|
939
|
+
<span>${escapeHtml(currentTitle)}</span>
|
|
940
|
+
</div>
|
|
941
|
+
<div class="url-dropdown-url">${escapeHtml(currentUrl)}</div>
|
|
942
|
+
</div>
|
|
943
|
+
`;
|
|
944
|
+
}
|
|
945
|
+
|
|
515
946
|
if (processes.length === 0) {
|
|
516
|
-
|
|
947
|
+
html += createEmptyStateHtml(getEmptyStateMessage(modalInput));
|
|
948
|
+
modalDropdown.innerHTML = html;
|
|
949
|
+
attachCreateButtonHandler(modalDropdown, modalInput);
|
|
950
|
+
|
|
951
|
+
modalDropdown.querySelectorAll('.url-dropdown-item:not(.non-selectable)').forEach(item => {
|
|
952
|
+
item.addEventListener('click', function() {
|
|
953
|
+
const url = this.getAttribute('data-url');
|
|
954
|
+
const type = this.getAttribute('data-host-type');
|
|
955
|
+
handleModalSelection(url, type);
|
|
956
|
+
});
|
|
957
|
+
});
|
|
517
958
|
return;
|
|
518
959
|
}
|
|
519
960
|
|
|
520
|
-
// Group processes by host
|
|
521
961
|
const groupedProcesses = groupProcessesByHost(processes);
|
|
522
|
-
|
|
523
|
-
let html = '';
|
|
524
962
|
Object.keys(groupedProcesses).forEach(hostKey => {
|
|
525
963
|
const hostData = groupedProcesses[hostKey];
|
|
526
964
|
const hostInfo = hostData.host;
|
|
527
|
-
const
|
|
965
|
+
const hostProcesses = hostData.processes;
|
|
528
966
|
const isLocal = hostData.isLocal;
|
|
529
|
-
|
|
530
|
-
// Add host header
|
|
967
|
+
|
|
531
968
|
html += createHostHeader(hostInfo, isLocal);
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
'<div class="status-circle online"></div>' :
|
|
969
|
+
|
|
970
|
+
hostProcesses.forEach(process => {
|
|
971
|
+
const onlineIndicator = process.online ?
|
|
972
|
+
'<div class="status-circle online"></div>' :
|
|
537
973
|
'<div class="status-circle offline"></div>';
|
|
538
|
-
|
|
974
|
+
|
|
539
975
|
if (process.ip === null || process.ip === undefined) {
|
|
540
|
-
// Non-selectable item with "turn on peer network" button
|
|
541
976
|
const networkUrl = `http://${process.host.ip}:42000/network`;
|
|
542
977
|
html += `
|
|
543
978
|
<div class="url-dropdown-item non-selectable">
|
|
@@ -549,12 +984,16 @@ function initUrlDropdown(config = {}) {
|
|
|
549
984
|
</div>
|
|
550
985
|
`;
|
|
551
986
|
} else {
|
|
552
|
-
|
|
553
|
-
|
|
987
|
+
const url = getProcessDisplayUrl(process);
|
|
988
|
+
if (!url) {
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
554
991
|
html += `
|
|
555
992
|
<div class="url-dropdown-item" data-url="${url}" data-host-type="${process.host.local ? "local" : "remote"}">
|
|
556
|
-
|
|
557
|
-
|
|
993
|
+
<div class="url-dropdown-name">
|
|
994
|
+
${onlineIndicator}
|
|
995
|
+
${escapeHtml(process.name)}
|
|
996
|
+
</div>
|
|
558
997
|
<div class="url-dropdown-url">${escapeHtml(url)}</div>
|
|
559
998
|
</div>
|
|
560
999
|
`;
|
|
@@ -568,28 +1007,10 @@ function initUrlDropdown(config = {}) {
|
|
|
568
1007
|
item.addEventListener('click', function() {
|
|
569
1008
|
const url = this.getAttribute('data-url');
|
|
570
1009
|
const type = this.getAttribute('data-host-type');
|
|
571
|
-
|
|
572
|
-
urlInput.value = url;
|
|
573
|
-
urlInput.setAttribute("data-host-type", type);
|
|
574
|
-
closeMobileModal();
|
|
575
|
-
|
|
576
|
-
// Navigate directly instead of dispatching submit event
|
|
577
|
-
if (type === "local") {
|
|
578
|
-
let redirect_uri = "/container?url=" + url;
|
|
579
|
-
location.href = redirect_uri;
|
|
580
|
-
} else {
|
|
581
|
-
let u = new URL(url);
|
|
582
|
-
if (String(u.port) === "42000") {
|
|
583
|
-
window.open(url, "_blank", 'self');
|
|
584
|
-
} else {
|
|
585
|
-
let redirect_uri = "/container?url=" + url;
|
|
586
|
-
location.href = redirect_uri;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
1010
|
+
handleModalSelection(url, type);
|
|
589
1011
|
});
|
|
590
1012
|
});
|
|
591
1013
|
|
|
592
|
-
// Add click handlers to peer network buttons in modal
|
|
593
1014
|
modalDropdown.querySelectorAll('.peer-network-button').forEach(button => {
|
|
594
1015
|
button.addEventListener('click', function(e) {
|
|
595
1016
|
e.stopPropagation();
|
|
@@ -601,16 +1022,15 @@ function initUrlDropdown(config = {}) {
|
|
|
601
1022
|
|
|
602
1023
|
// Set up mobile button click handler
|
|
603
1024
|
if (mobileButton) {
|
|
604
|
-
mobileButton.addEventListener('click',
|
|
1025
|
+
mobileButton.addEventListener('click', mobileButtonHandler);
|
|
605
1026
|
}
|
|
606
1027
|
|
|
607
|
-
|
|
608
|
-
return {
|
|
1028
|
+
const api = {
|
|
609
1029
|
show: showDropdown,
|
|
610
1030
|
hide: hideDropdown,
|
|
611
1031
|
showAll: showAllProcesses,
|
|
612
|
-
showMobileModal
|
|
613
|
-
closeMobileModal
|
|
1032
|
+
showMobileModal,
|
|
1033
|
+
closeMobileModal,
|
|
614
1034
|
refresh: function() {
|
|
615
1035
|
allProcesses = []; // Clear cache to force refetch
|
|
616
1036
|
if (isDropdownVisible) {
|
|
@@ -618,18 +1038,43 @@ function initUrlDropdown(config = {}) {
|
|
|
618
1038
|
}
|
|
619
1039
|
},
|
|
620
1040
|
filter: handleInputChange,
|
|
1041
|
+
openSplitModal: function(modalOptions = {}) {
|
|
1042
|
+
return showMobileModal({
|
|
1043
|
+
title: modalOptions.title || 'Split View',
|
|
1044
|
+
description: modalOptions.description || 'Choose a running process or use the current tab URL for the new pane.',
|
|
1045
|
+
confirmLabel: modalOptions.confirmLabel || 'Split',
|
|
1046
|
+
includeCurrent: modalOptions.includeCurrent !== false,
|
|
1047
|
+
awaitSelection: true,
|
|
1048
|
+
context: 'split'
|
|
1049
|
+
});
|
|
1050
|
+
},
|
|
621
1051
|
destroy: function() {
|
|
622
|
-
|
|
623
|
-
|
|
1052
|
+
if (urlInput) {
|
|
1053
|
+
urlInput.removeEventListener('input', handleInputChange);
|
|
1054
|
+
}
|
|
624
1055
|
if (mobileButton) {
|
|
625
|
-
mobileButton.removeEventListener('click',
|
|
1056
|
+
mobileButton.removeEventListener('click', mobileButtonHandler);
|
|
626
1057
|
}
|
|
627
1058
|
hideDropdown();
|
|
628
|
-
closeMobileModal();
|
|
1059
|
+
closeMobileModal({ suppressResolve: true });
|
|
629
1060
|
allProcesses = [];
|
|
630
1061
|
filteredProcesses = [];
|
|
1062
|
+
if (fallbackElements.form && fallbackElements.form.parentElement) {
|
|
1063
|
+
fallbackElements.form.parentElement.removeChild(fallbackElements.form);
|
|
1064
|
+
}
|
|
1065
|
+
if (fallbackElements.dropdown && fallbackElements.dropdown.parentElement) {
|
|
1066
|
+
fallbackElements.dropdown.parentElement.removeChild(fallbackElements.dropdown);
|
|
1067
|
+
}
|
|
1068
|
+
fallbackElements.form = null;
|
|
1069
|
+
fallbackElements.dropdown = null;
|
|
1070
|
+
if (window.PinokioUrlDropdown === api) {
|
|
1071
|
+
window.PinokioUrlDropdown = null;
|
|
1072
|
+
}
|
|
631
1073
|
}
|
|
632
1074
|
};
|
|
1075
|
+
|
|
1076
|
+
window.PinokioUrlDropdown = api;
|
|
1077
|
+
return api;
|
|
633
1078
|
function showEmptyState(container, inputElement) {
|
|
634
1079
|
container.innerHTML = createEmptyStateHtml(getEmptyStateMessage(inputElement));
|
|
635
1080
|
attachCreateButtonHandler(container, inputElement);
|
|
@@ -691,22 +1136,27 @@ function initUrlDropdown(config = {}) {
|
|
|
691
1136
|
pendingCreateDetail = detail;
|
|
692
1137
|
|
|
693
1138
|
modal.error.textContent = '';
|
|
694
|
-
modal.overlay.style.display = 'flex';
|
|
695
|
-
|
|
696
1139
|
// const defaultName = generateFolderName(detail.prompt);
|
|
697
|
-
modal.input.value =
|
|
1140
|
+
modal.input.value = '';
|
|
698
1141
|
modal.description.textContent = detail.prompt
|
|
699
1142
|
? `Prompt: ${detail.prompt}`
|
|
700
1143
|
: 'Enter a prompt in the search bar to describe your launcher.';
|
|
701
|
-
|
|
702
|
-
|
|
1144
|
+
|
|
1145
|
+
requestAnimationFrame(() => {
|
|
1146
|
+
modal.overlay.classList.add('is-visible');
|
|
1147
|
+
requestAnimationFrame(() => {
|
|
1148
|
+
modal.input.focus();
|
|
1149
|
+
modal.input.select();
|
|
1150
|
+
});
|
|
1151
|
+
});
|
|
1152
|
+
|
|
703
1153
|
document.addEventListener('keydown', handleCreateModalEscape, true);
|
|
704
1154
|
}
|
|
705
1155
|
|
|
706
1156
|
function hideCreateLauncherModal() {
|
|
707
1157
|
const modal = createLauncherModal;
|
|
708
1158
|
if (!modal) return;
|
|
709
|
-
modal.overlay.
|
|
1159
|
+
modal.overlay.classList.remove('is-visible');
|
|
710
1160
|
pendingCreateDetail = null;
|
|
711
1161
|
document.removeEventListener('keydown', handleCreateModalEscape, true);
|
|
712
1162
|
}
|
|
@@ -746,19 +1196,25 @@ function initUrlDropdown(config = {}) {
|
|
|
746
1196
|
}
|
|
747
1197
|
|
|
748
1198
|
const overlay = document.createElement('div');
|
|
749
|
-
overlay.className = 'create-launcher-modal-overlay';
|
|
750
|
-
overlay.style.display = 'none';
|
|
1199
|
+
overlay.className = 'modal-overlay create-launcher-modal-overlay';
|
|
751
1200
|
|
|
752
1201
|
const modalContent = document.createElement('div');
|
|
753
1202
|
modalContent.className = 'create-launcher-modal';
|
|
1203
|
+
modalContent.setAttribute('role', 'dialog');
|
|
1204
|
+
modalContent.setAttribute('aria-modal', 'true');
|
|
754
1205
|
|
|
755
1206
|
const title = document.createElement('h3');
|
|
1207
|
+
title.id = 'quick-create-launcher-title';
|
|
756
1208
|
title.textContent = 'Create';
|
|
757
1209
|
|
|
758
1210
|
const description = document.createElement('p');
|
|
759
1211
|
description.className = 'create-launcher-modal-description';
|
|
1212
|
+
description.id = 'quick-create-launcher-description';
|
|
760
1213
|
description.textContent = 'Enter a prompt in the search bar to describe your launcher.';
|
|
761
1214
|
|
|
1215
|
+
modalContent.setAttribute('aria-labelledby', title.id);
|
|
1216
|
+
modalContent.setAttribute('aria-describedby', description.id);
|
|
1217
|
+
|
|
762
1218
|
const label = document.createElement('label');
|
|
763
1219
|
label.className = 'create-launcher-modal-label';
|
|
764
1220
|
label.textContent = 'Folder name';
|
|
@@ -782,7 +1238,7 @@ function initUrlDropdown(config = {}) {
|
|
|
782
1238
|
const confirmButton = document.createElement('button');
|
|
783
1239
|
confirmButton.type = 'button';
|
|
784
1240
|
confirmButton.className = 'create-launcher-modal-button confirm';
|
|
785
|
-
confirmButton.textContent = '
|
|
1241
|
+
confirmButton.textContent = 'Create';
|
|
786
1242
|
|
|
787
1243
|
actions.appendChild(cancelButton);
|
|
788
1244
|
actions.appendChild(confirmButton);
|