fix: the editor shows an empty page, not the last file, while a file loads; text in UTF-16 and with BOM; + menu groups open and shut smoothly; the assistant's errors in plain words

This commit is contained in:
redtea committed 2026-10-16 09:28:00 +03:00
1 parent b272aed8fd
commit 352b016384
5 files changed
+178 -70

No files matched your search

+37 -19
View File
@@ -474,23 +474,23 @@
return b;
};
const FILES = '<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="M3 7a2 2 0 0 1 2-2h4l2 2.5h8a2 2 0 0 1 2 2V17a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>';
// A group: «Терминал ›» or «Файловый менеджер ›», opening to here, the
// other machines and an SSH host.
const group = (icon, label, kind, sshKind, mode) => {
const g = item(addMenu, '<span class="ico">' + icon + '</span><span><b>' + label + '</b></span><span class="chev">›</span>', () => {
if (g.nextSibling && g.nextSibling.classList && g.nextSibling.classList.contains('mlist')) { g.nextSibling.remove(); placeMenu(anchor); return; }
addMenu.querySelectorAll('.mlist').forEach(x => x.remove());
const list = document.createElement('div');
list.className = 'mlist';
g.after(list);
if (here) item(list, '<span class="ico">+</span><span><b>Здесь</b><small>' + esc(here.name) + ' · ' + esc(here.user + '@' + here.host) + '</small></span>',
() => { closeMenu(); openPane(kind, here); });
const box = document.createElement('div');
box.innerHTML = '<div class="muted" style="padding:8px 10px;font-size:13px">загрузка…</div>';
list.appendChild(box);
item(list, '<span class="ico">' + KEYI + '</span><span><b>SSH-хост…</b><small>машина откроет SSH к другому хосту</small></span>',
() => { closeMenu(); openSSH(here, mode); });
// Both groups keep their contents mounted so they can animate in both directions.
const group = (icon, label, kind, mode) => {
let loaded = false;
const g = item(addMenu, '<span class="ico">' + icon + '</span><span><b>' + label + '</b></span><span class="chev" aria-hidden="true">›</span>', () => {
const opening = g.getAttribute('aria-expanded') !== 'true';
addMenu.querySelectorAll('.item[aria-expanded="true"]').forEach(b => {
b.setAttribute('aria-expanded', 'false');
b.nextSibling.classList.remove('open');
b.nextSibling.inert = true;
});
if (!opening) { placeMenu(anchor); return; }
g.setAttribute('aria-expanded', 'true');
list.classList.add('open');
list.inert = false;
placeMenu(anchor);
if (loaded) return;
loaded = true;
(agentsCache ? Promise.resolve(agentsCache) : fetch('/api/panel/agents/presence', { cache: 'no-store' }).then(r => r.json()))
.then(all => {
agentsCache = all;
@@ -501,15 +501,33 @@
() => { closeMenu(); openPane(kind, { id: a.id, name: a.name, host: a.hostname, user: a.user }); }, 'machine');
b.disabled = !a.online;
});
placeMenu(anchor);
if (list.isConnected) placeMenu(anchor);
})
.catch(() => { box.innerHTML = '<div class="muted" style="padding:8px 10px">не загрузилось</div>'; });
});
g.setAttribute('aria-expanded', 'false');
const list = document.createElement('div');
list.className = 'mlist';
list.inert = true;
const inner = document.createElement('div');
inner.className = 'mlist-inner';
const content = document.createElement('div');
content.className = 'mlist-content';
inner.appendChild(content);
list.appendChild(inner);
g.after(list);
if (here) item(content, '<span class="ico">+</span><span><b>Здесь</b><small>' + esc(here.name) + ' · ' + esc(here.user + '@' + here.host) + '</small></span>',
() => { closeMenu(); openPane(kind, here); });
const box = document.createElement('div');
box.innerHTML = '<div class="muted" style="padding:8px 10px;font-size:13px">загрузка…</div>';
content.appendChild(box);
item(content, '<span class="ico">' + KEYI + '</span><span><b>SSH-хост…</b><small>машина откроет SSH к другому хосту</small></span>',
() => { closeMenu(); openSSH(here, mode); });
if (!paneTypes[kind]) g.disabled = true;
return g;
};
group('&gt;_', 'Терминал', 'term', 'ssh', 'term');
group(FILES, 'Файловый менеджер', 'files', 'sshfiles', 'files');
group('&gt;_', 'Терминал', 'term', 'term');
group(FILES, 'Файловый менеджер', 'files', 'files');
addMenu.hidden = false;
placeMenu(anchor);
setTimeout(() => document.addEventListener('pointerdown', outside, true), 0);