diff --git a/internal/server/web/templates/files.html b/internal/server/web/templates/files.html index 5e45c31..dc5a8c4 100644 --- a/internal/server/web/templates/files.html +++ b/internal/server/web/templates/files.html @@ -164,7 +164,7 @@ .hx-b i { margin-right:3px; } .hx-b i:nth-child(8) { margin-right:8px; } } - .toast { position:fixed; bottom:18px; left:50%; transform:translateX(-50%); + .toast { z-index:50; position:fixed; bottom:18px; left:50%; transform:translateX(-50%); background:#2a2f3a; border:1px solid var(--border); border-radius:8px; padding:10px 16px; font-size:14px; opacity:0; transition:opacity .2s; pointer-events:none; } .toast.show { opacity:1; } @@ -287,7 +287,17 @@ const selected = new Set(); const $ = id => document.getElementById(id); - const toast = (msg, err) => { const t=$('toast'); t.textContent=msg; t.className='toast show'+(err?' err':''); setTimeout(()=>t.className='toast',2200); }; + // An open dialog sits in the page's top layer, above any z-index: the toast + // goes inside it then, so "сохранено" or an error shows over the editor. + let toastTimer = null; + const toast = (msg, err) => { + const t = $('toast'), host = document.querySelector('dialog[open]') || document.body; + if (t.parentNode !== host) host.appendChild(t); + t.textContent = msg; + t.className = 'toast show' + (err ? ' err' : ''); + clearTimeout(toastTimer); + toastTimer = setTimeout(() => t.className = 'toast', err ? 3500 : 2200); + }; const b64ToBytes = b64 => { const bin=atob(b64); const u=new Uint8Array(bin.length); for(let i=0;i (dir.endsWith('/') ? dir : dir+'/') + name; const parent = p => { if(p==='/'||p==='') return '/'; const i=p.replace(/\/+$/,'').lastIndexOf('/'); return i<=0 ? '/' : p.slice(0,i); }; diff --git a/internal/server/web/templates/terminal.html b/internal/server/web/templates/terminal.html index 3167d92..2800ae3 100644 --- a/internal/server/web/templates/terminal.html +++ b/internal/server/web/templates/terminal.html @@ -28,10 +28,11 @@ .keys button.on { background:var(--accent); border-color:var(--accent); color:#fff; } .keys button.lock { background:var(--accent); border-color:#fff; color:#fff; } /* Copy and paste on touch screens: a long press selects, this menu acts. */ - .selmenu { position:fixed; left:50%; transform:translateX(-50%); z-index:20; display:flex; gap:4px; padding:4px; + .selmenu { position:fixed; left:50%; transform:translateX(-50%); z-index:20; display:flex; gap:7px; padding:5px; background:var(--panel); border:1px solid var(--border); border-radius:8px; box-shadow:0 6px 24px rgba(0,0,0,.5); } .selmenu[hidden] { display:none; } - .selmenu button { height:36px; padding:0 12px; font-size:14px; touch-action:manipulation; -webkit-tap-highlight-color:transparent; } + .selmenu button { width:42px; height:42px; padding:0; display:inline-flex; align-items:center; justify-content:center; + touch-action:manipulation; -webkit-tap-highlight-color:transparent; } body.touch #terminal { -webkit-user-select:none; user-select:none; -webkit-touch-callout:none; } @@ -51,10 +52,18 @@