diff options
Diffstat (limited to 'modules/ext')
| -rw-r--r-- | modules/ext/critical-ext-bundle.js | 150 | ||||
| -rw-r--r-- | modules/ext/filemgr.js | 150 | ||||
| -rw-r--r-- | modules/ext/webdav.js | 51 |
3 files changed, 238 insertions, 113 deletions
diff --git a/modules/ext/critical-ext-bundle.js b/modules/ext/critical-ext-bundle.js index 0c92381..8e968fb 100644 --- a/modules/ext/critical-ext-bundle.js +++ b/modules/ext/critical-ext-bundle.js @@ -1473,6 +1473,20 @@ class FileManagerModule { downloadBtn.onclick = () => this._actionDownload(); actionBar.appendChild(downloadBtn); + // 2026-10-26 (phone test): share the whole selection — the preview toolbar could only ever + // share the one previewed file, so three selected files produced one link. + const shareSelBtn = document.createElement('button'); + shareSelBtn.textContent = '🔗 Share'; + shareSelBtn.title = 'Share or copy the selected files'; + shareSelBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--border-color);color:var(--text-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; + shareSelBtn.onclick = () => { + let t = this._getSelectedPaths ? this._getSelectedPaths() : []; + if (!t.length && this._selectedFile) t = [this._selectedFile]; + if (!t.length) { this._updateStatus('🔗 Nothing selected — tap ☐ Select then tap files'); return; } + this._sharePaths(t); + }; + actionBar.appendChild(shareSelBtn); + const editBtn = document.createElement('button'); editBtn.textContent = '✏ Edit'; editBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--accent-color);color:var(--accent-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; @@ -1908,15 +1922,77 @@ class FileManagerModule { // 2026-10-21: preview pane had no fullscreen and no share action. _previewShell(path, innerHtml) { - return '<div class="fm-preview-toolbar" style="display:flex;gap:10px;justify-content:flex-end;' + + // 2026-10-26 (phone test): the pane had no close control at all — the only ✕ belonged to the + // whole file manager and wiped _multiSelect on the way out, so "select 3 files → Download" + // ended in "No file selected to download". + const btn = (cls, label, title) => + '<button class="' + cls + '" title="' + title + '" aria-label="' + title + '" style="background:var(--bg-tertiary);' + + 'border:1px solid var(--border-color);color:var(--text-color);padding:0 10px;border-radius:4px;' + + 'cursor:pointer;font-size:1em;min-height:44px;min-width:44px;touch-action:manipulation;">' + label + '</button>'; + return '<div class="fm-preview-toolbar" style="display:flex;gap:10px;justify-content:space-between;align-items:center;' + 'padding:6px 8px;border-bottom:1px solid var(--border-color);flex-shrink:0">' + - '<button class="fm-btn-fs" title="Fullscreen preview" aria-label="Fullscreen preview" style="background:var(--bg-tertiary);' + - 'border:1px solid var(--border-color);color:var(--text-color);padding:0 10px;border-radius:4px;' + - 'cursor:pointer;font-size:1em;min-height:44px;min-width:44px;touch-action:manipulation;">⛶</button>' + - '<button class="fm-btn-share" title="Copy share link" aria-label="Copy share link" style="background:var(--bg-tertiary);' + - 'border:1px solid var(--border-color);color:var(--text-color);padding:0 10px;border-radius:4px;' + - 'cursor:pointer;font-size:1em;min-height:44px;min-width:44px;touch-action:manipulation;">🔗</button>' + - '</div><div class="fm-preview-body" style="flex:1;min-height:0;overflow:auto">' + innerHtml + '</div>'; + btn('fm-btn-close', '✕', 'Close preview') + + '<div style="display:flex;gap:10px;align-items:center">' + + btn('fm-btn-fs', '⛶', 'Fullscreen preview') + + btn('fm-btn-copy', '📋', 'Copy link') + + btn('fm-btn-share', '🔗', 'Share link') + + '</div></div><div class="fm-preview-body" style="flex:1;min-height:0;overflow:auto">' + innerHtml + '</div>'; + } + + // 2026-10-26: one entry point for both the preview toolbar (1 path) and the action bar (N paths). + _sharePaths(paths, mode) { + if (!paths || !paths.length) { this._updateStatus('🔗 Nothing selected — tap ☐ Select then tap files'); return; } + const urls = (paths || []).map(p => this._previewShareUrl(p)).filter(Boolean); + if (!urls.length) { this._updateStatus('🔗 Share unavailable — no space resolved'); return; } + const n = urls.length, text = urls.join('\n'); + const copy = () => { + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(text) + .then(() => this._updateStatus('📋 Copied ' + n + (n === 1 ? ' link' : ' links'))) + .catch(() => this._inlineLinkBox(text, n)); + } else { this._inlineLinkBox(text, n); } + }; + if (mode === 'copy') { copy(); return; } + if (navigator.share) { + const payload = n === 1 ? { url: urls[0] } : { title: n + ' newOS4 files', text }; + navigator.share(payload) + .then(() => this._updateStatus('🔗 Shared ' + n + (n === 1 ? ' link' : ' links'))) + .catch(copy); + return; + } + copy(); + } + + // Visible, selectable fallback: prompt() cannot appear in iOS standalone PWAs or most WebViews. + _inlineLinkBox(text, n) { + const pane = document.getElementById('filemgr-preview'); + const bar = pane ? pane.querySelector('.fm-preview-toolbar') : null; + let box = document.querySelector('.fm-share-input'); + if (!box) { + box = document.createElement(n > 1 ? 'textarea' : 'input'); + box.className = 'fm-share-input'; + box.readOnly = true; + box.setAttribute('aria-label', n > 1 ? 'Share links' : 'Share link'); + box.style.cssText = 'flex:1;min-width:0;min-height:44px;padding:6px 10px;font-size:1em;box-sizing:border-box;' + + 'border:1px solid var(--border-color);border-radius:4px;background:var(--bg-primary);color:var(--text-color)'; + if (bar) { + bar.insertBefore(box, bar.firstChild); + } else { + // 2026-10-26: sharing from the action bar happens with no preview open — the fallback + // box needs its own row above the buttons, otherwise the URL is only in the status line. + const anchor = document.getElementById('filemgr-action-bar'); + const host = anchor && anchor.parentNode ? anchor.parentNode + : document.getElementById('filemgr-main-container'); + if (!host) { this._updateStatus('📋 ' + text); return; } + const row = document.createElement('div'); + row.className = 'fm-share-row'; + row.style.cssText = 'display:flex;gap:8px;padding:6px 8px;flex-shrink:0'; + row.appendChild(box); + host.insertBefore(row, anchor || null); + } + } + if (box) { box.value = text; box.focus(); box.select(); } + this._updateStatus('📋 Select and copy ' + n + (n === 1 ? ' link' : ' links')); } _previewShareUrl(path) { @@ -1958,49 +2034,19 @@ class FileManagerModule { const fsBtn = pane.querySelector('.fm-btn-fs'); const shBtn = pane.querySelector('.fm-btn-share'); if (fsBtn) fsBtn.onclick = () => { - const body = pane.querySelector('.fm-preview-body') || pane; - if (document.fullscreenElement) { document.exitFullscreen(); return; } - if (body.requestFullscreen) body.requestFullscreen().catch(() => this._updateStatus('⛶ Fullscreen blocked by this browser')); - else if (body.webkitRequestFullscreen) body.webkitRequestFullscreen(); + // 2026-10-26 (phone test): fullscreening .fm-preview-body hid the toolbar, so the only way + // out was the OS back gesture. Fullscreen the pane instead — ✕ and ⛶ stay on screen. + if (document.fullscreenElement) { document.exitFullscreen().catch(() => {}); return; } + const target = pane; + if (target.requestFullscreen) target.requestFullscreen().catch(() => this._updateStatus('⛶ Fullscreen blocked by this browser')); + else if (target.webkitRequestFullscreen) target.webkitRequestFullscreen(); else this._updateStatus('⛶ Fullscreen not supported here'); }; - if (shBtn) shBtn.onclick = () => { - const url = this._previewShareUrl(path); - if (!url) { this._updateStatus('🔗 Share unavailable — no space resolved'); return; } - // T3: prompt() is suppressed in iOS standalone PWAs and most Android WebViews, so the - // last resort is a real input the user can select — never a dialog that cannot appear. - const inlineFallback = () => { - const bar = pane.querySelector('.fm-preview-toolbar'); - let inp = pane.querySelector('.fm-share-input'); - if (!inp && bar) { - inp = document.createElement('input'); - inp.className = 'fm-share-input'; - inp.readOnly = true; - inp.setAttribute('aria-label', 'Share link'); - inp.style.cssText = 'flex:1;min-width:0;min-height:44px;padding:0 10px;font-size:1em;' + - 'border:1px solid var(--border-color);border-radius:4px;' + - 'background:var(--bg-primary);color:var(--text-color)'; - inp.value = url; - bar.insertBefore(inp, bar.firstChild); - } - if (inp) { inp.focus(); inp.select(); } - this._updateStatus('🔗 Select and copy'); - }; - const copyThen = () => { - if (navigator.clipboard && navigator.clipboard.writeText) { - navigator.clipboard.writeText(url) - .then(() => this._updateStatus('🔗 Link copied: ' + url)) - .catch(inlineFallback); - } else { inlineFallback(); } - }; - if (navigator.share) { - navigator.share({ title: 'newOS4 file', url }) - .then(() => this._updateStatus('🔗 Shared')) - .catch(copyThen); - return; - } - copyThen(); - }; + const closeBtn = pane.querySelector('.fm-btn-close'); + if (closeBtn) closeBtn.onclick = () => { this._clearPreview(); this._updateStatus('Preview closed — selection kept'); }; + const copyBtn = pane.querySelector('.fm-btn-copy'); + if (copyBtn) copyBtn.onclick = () => this._sharePaths([path], 'copy'); + if (shBtn) shBtn.onclick = () => this._sharePaths([path]); } _renderPreviewError(msg) { @@ -2012,7 +2058,11 @@ class FileManagerModule { _clearPreview() { const preview = document.getElementById('filemgr-preview'); + if (document.fullscreenElement) { + document.exitFullscreen().catch(() => {}); + } if (preview) preview.innerHTML = ''; + this._updateSelectionStatus(); // selection survives closing the preview } _updateStatus(msg) { @@ -2033,7 +2083,7 @@ class FileManagerModule { // T2: honour the multi-selection the way every sibling action does (_getSelectedPaths). let targets = this._getSelectedPaths ? this._getSelectedPaths() : []; if (!targets || !targets.length) targets = this._selectedFile ? [this._selectedFile] : []; - if (!targets.length) { this._updateStatus('No file selected to download'); return; } + if (!targets.length) { this._updateStatus('⬇ Nothing selected — tap ☐ Select then tap files, or tap one file first'); return; } const saved = [], skipped = []; for (const p of targets) { const content = this.terminal.fs.readFile(p); diff --git a/modules/ext/filemgr.js b/modules/ext/filemgr.js index 120e07e..7412578 100644 --- a/modules/ext/filemgr.js +++ b/modules/ext/filemgr.js @@ -540,6 +540,20 @@ class FileManagerModule { downloadBtn.onclick = () => this._actionDownload(); actionBar.appendChild(downloadBtn); + // 2026-10-26 (phone test): share the whole selection — the preview toolbar could only ever + // share the one previewed file, so three selected files produced one link. + const shareSelBtn = document.createElement('button'); + shareSelBtn.textContent = '🔗 Share'; + shareSelBtn.title = 'Share or copy the selected files'; + shareSelBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--border-color);color:var(--text-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; + shareSelBtn.onclick = () => { + let t = this._getSelectedPaths ? this._getSelectedPaths() : []; + if (!t.length && this._selectedFile) t = [this._selectedFile]; + if (!t.length) { this._updateStatus('🔗 Nothing selected — tap ☐ Select then tap files'); return; } + this._sharePaths(t); + }; + actionBar.appendChild(shareSelBtn); + const editBtn = document.createElement('button'); editBtn.textContent = '✏ Edit'; editBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--accent-color);color:var(--accent-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; @@ -975,15 +989,77 @@ class FileManagerModule { // 2026-10-21: preview pane had no fullscreen and no share action. _previewShell(path, innerHtml) { - return '<div class="fm-preview-toolbar" style="display:flex;gap:10px;justify-content:flex-end;' + + // 2026-10-26 (phone test): the pane had no close control at all — the only ✕ belonged to the + // whole file manager and wiped _multiSelect on the way out, so "select 3 files → Download" + // ended in "No file selected to download". + const btn = (cls, label, title) => + '<button class="' + cls + '" title="' + title + '" aria-label="' + title + '" style="background:var(--bg-tertiary);' + + 'border:1px solid var(--border-color);color:var(--text-color);padding:0 10px;border-radius:4px;' + + 'cursor:pointer;font-size:1em;min-height:44px;min-width:44px;touch-action:manipulation;">' + label + '</button>'; + return '<div class="fm-preview-toolbar" style="display:flex;gap:10px;justify-content:space-between;align-items:center;' + 'padding:6px 8px;border-bottom:1px solid var(--border-color);flex-shrink:0">' + - '<button class="fm-btn-fs" title="Fullscreen preview" aria-label="Fullscreen preview" style="background:var(--bg-tertiary);' + - 'border:1px solid var(--border-color);color:var(--text-color);padding:0 10px;border-radius:4px;' + - 'cursor:pointer;font-size:1em;min-height:44px;min-width:44px;touch-action:manipulation;">⛶</button>' + - '<button class="fm-btn-share" title="Copy share link" aria-label="Copy share link" style="background:var(--bg-tertiary);' + - 'border:1px solid var(--border-color);color:var(--text-color);padding:0 10px;border-radius:4px;' + - 'cursor:pointer;font-size:1em;min-height:44px;min-width:44px;touch-action:manipulation;">🔗</button>' + - '</div><div class="fm-preview-body" style="flex:1;min-height:0;overflow:auto">' + innerHtml + '</div>'; + btn('fm-btn-close', '✕', 'Close preview') + + '<div style="display:flex;gap:10px;align-items:center">' + + btn('fm-btn-fs', '⛶', 'Fullscreen preview') + + btn('fm-btn-copy', '📋', 'Copy link') + + btn('fm-btn-share', '🔗', 'Share link') + + '</div></div><div class="fm-preview-body" style="flex:1;min-height:0;overflow:auto">' + innerHtml + '</div>'; + } + + // 2026-10-26: one entry point for both the preview toolbar (1 path) and the action bar (N paths). + _sharePaths(paths, mode) { + if (!paths || !paths.length) { this._updateStatus('🔗 Nothing selected — tap ☐ Select then tap files'); return; } + const urls = (paths || []).map(p => this._previewShareUrl(p)).filter(Boolean); + if (!urls.length) { this._updateStatus('🔗 Share unavailable — no space resolved'); return; } + const n = urls.length, text = urls.join('\n'); + const copy = () => { + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(text) + .then(() => this._updateStatus('📋 Copied ' + n + (n === 1 ? ' link' : ' links'))) + .catch(() => this._inlineLinkBox(text, n)); + } else { this._inlineLinkBox(text, n); } + }; + if (mode === 'copy') { copy(); return; } + if (navigator.share) { + const payload = n === 1 ? { url: urls[0] } : { title: n + ' newOS4 files', text }; + navigator.share(payload) + .then(() => this._updateStatus('🔗 Shared ' + n + (n === 1 ? ' link' : ' links'))) + .catch(copy); + return; + } + copy(); + } + + // Visible, selectable fallback: prompt() cannot appear in iOS standalone PWAs or most WebViews. + _inlineLinkBox(text, n) { + const pane = document.getElementById('filemgr-preview'); + const bar = pane ? pane.querySelector('.fm-preview-toolbar') : null; + let box = document.querySelector('.fm-share-input'); + if (!box) { + box = document.createElement(n > 1 ? 'textarea' : 'input'); + box.className = 'fm-share-input'; + box.readOnly = true; + box.setAttribute('aria-label', n > 1 ? 'Share links' : 'Share link'); + box.style.cssText = 'flex:1;min-width:0;min-height:44px;padding:6px 10px;font-size:1em;box-sizing:border-box;' + + 'border:1px solid var(--border-color);border-radius:4px;background:var(--bg-primary);color:var(--text-color)'; + if (bar) { + bar.insertBefore(box, bar.firstChild); + } else { + // 2026-10-26: sharing from the action bar happens with no preview open — the fallback + // box needs its own row above the buttons, otherwise the URL is only in the status line. + const anchor = document.getElementById('filemgr-action-bar'); + const host = anchor && anchor.parentNode ? anchor.parentNode + : document.getElementById('filemgr-main-container'); + if (!host) { this._updateStatus('📋 ' + text); return; } + const row = document.createElement('div'); + row.className = 'fm-share-row'; + row.style.cssText = 'display:flex;gap:8px;padding:6px 8px;flex-shrink:0'; + row.appendChild(box); + host.insertBefore(row, anchor || null); + } + } + if (box) { box.value = text; box.focus(); box.select(); } + this._updateStatus('📋 Select and copy ' + n + (n === 1 ? ' link' : ' links')); } _previewShareUrl(path) { @@ -1025,49 +1101,19 @@ class FileManagerModule { const fsBtn = pane.querySelector('.fm-btn-fs'); const shBtn = pane.querySelector('.fm-btn-share'); if (fsBtn) fsBtn.onclick = () => { - const body = pane.querySelector('.fm-preview-body') || pane; - if (document.fullscreenElement) { document.exitFullscreen(); return; } - if (body.requestFullscreen) body.requestFullscreen().catch(() => this._updateStatus('⛶ Fullscreen blocked by this browser')); - else if (body.webkitRequestFullscreen) body.webkitRequestFullscreen(); + // 2026-10-26 (phone test): fullscreening .fm-preview-body hid the toolbar, so the only way + // out was the OS back gesture. Fullscreen the pane instead — ✕ and ⛶ stay on screen. + if (document.fullscreenElement) { document.exitFullscreen().catch(() => {}); return; } + const target = pane; + if (target.requestFullscreen) target.requestFullscreen().catch(() => this._updateStatus('⛶ Fullscreen blocked by this browser')); + else if (target.webkitRequestFullscreen) target.webkitRequestFullscreen(); else this._updateStatus('⛶ Fullscreen not supported here'); }; - if (shBtn) shBtn.onclick = () => { - const url = this._previewShareUrl(path); - if (!url) { this._updateStatus('🔗 Share unavailable — no space resolved'); return; } - // T3: prompt() is suppressed in iOS standalone PWAs and most Android WebViews, so the - // last resort is a real input the user can select — never a dialog that cannot appear. - const inlineFallback = () => { - const bar = pane.querySelector('.fm-preview-toolbar'); - let inp = pane.querySelector('.fm-share-input'); - if (!inp && bar) { - inp = document.createElement('input'); - inp.className = 'fm-share-input'; - inp.readOnly = true; - inp.setAttribute('aria-label', 'Share link'); - inp.style.cssText = 'flex:1;min-width:0;min-height:44px;padding:0 10px;font-size:1em;' + - 'border:1px solid var(--border-color);border-radius:4px;' + - 'background:var(--bg-primary);color:var(--text-color)'; - inp.value = url; - bar.insertBefore(inp, bar.firstChild); - } - if (inp) { inp.focus(); inp.select(); } - this._updateStatus('🔗 Select and copy'); - }; - const copyThen = () => { - if (navigator.clipboard && navigator.clipboard.writeText) { - navigator.clipboard.writeText(url) - .then(() => this._updateStatus('🔗 Link copied: ' + url)) - .catch(inlineFallback); - } else { inlineFallback(); } - }; - if (navigator.share) { - navigator.share({ title: 'newOS4 file', url }) - .then(() => this._updateStatus('🔗 Shared')) - .catch(copyThen); - return; - } - copyThen(); - }; + const closeBtn = pane.querySelector('.fm-btn-close'); + if (closeBtn) closeBtn.onclick = () => { this._clearPreview(); this._updateStatus('Preview closed — selection kept'); }; + const copyBtn = pane.querySelector('.fm-btn-copy'); + if (copyBtn) copyBtn.onclick = () => this._sharePaths([path], 'copy'); + if (shBtn) shBtn.onclick = () => this._sharePaths([path]); } _renderPreviewError(msg) { @@ -1079,7 +1125,11 @@ class FileManagerModule { _clearPreview() { const preview = document.getElementById('filemgr-preview'); + if (document.fullscreenElement) { + document.exitFullscreen().catch(() => {}); + } if (preview) preview.innerHTML = ''; + this._updateSelectionStatus(); // selection survives closing the preview } _updateStatus(msg) { @@ -1100,7 +1150,7 @@ class FileManagerModule { // T2: honour the multi-selection the way every sibling action does (_getSelectedPaths). let targets = this._getSelectedPaths ? this._getSelectedPaths() : []; if (!targets || !targets.length) targets = this._selectedFile ? [this._selectedFile] : []; - if (!targets.length) { this._updateStatus('No file selected to download'); return; } + if (!targets.length) { this._updateStatus('⬇ Nothing selected — tap ☐ Select then tap files, or tap one file first'); return; } const saved = [], skipped = []; for (const p of targets) { const content = this.terminal.fs.readFile(p); diff --git a/modules/ext/webdav.js b/modules/ext/webdav.js index ed312a5..88f1f59 100644 --- a/modules/ext/webdav.js +++ b/modules/ext/webdav.js @@ -1962,19 +1962,44 @@ class WebDAVModule { document.getElementById('bulk-move')?.addEventListener('click', function() { const names = Object.entries(self._selected).filter(function(e) { return e[1]; }).map(function(e) { return e[0]; }); if (!names.length) return; - let target = prompt('Move ' + names.length + ' item(s) to directory:', '/'); - if (!target) return; - target = target.replace(/\/+$/, ''); - names.forEach(function(name) { - const oldFull = (path.replace(/\/+$/, '') || '') + '/' + name; - const newFull = target + '/' + name; - self._webdavRequest('MOVE', oldFull, { headers: { 'Destination': self._absoluteUrl(newFull) } }) - .then(function(r) { - if (r.ok || r.status === 201 || r.status === 204) self.terminal.print('Moved: ' + name, 'success'); - else self.terminal.print('Move failed: ' + name + ' (HTTP ' + r.status + ')', 'error'); - }); - }); - setTimeout(function() { self.openWebdavBrowser(path); }, 500); + // 2026-10-26 (phone test): prompt() is suppressed in iOS standalone PWAs and most + // Android WebViews — it returns null, so "Move" looked like a missing feature. + // Ask for the destination inline in the bulk bar instead. + const doMove = function(rawTarget) { + const target = String(rawTarget || '').trim().replace(/\/+$/, ''); + if (!target) { self.terminal.print('Move cancelled — no destination', 'error'); return; } + names.forEach(function(name) { + const oldFull = (path.replace(/\/+$/, '') || '') + '/' + name; + const newFull = target + '/' + name; + self._webdavRequest('MOVE', oldFull, { headers: { 'Destination': self._absoluteUrl(newFull) } }) + .then(function(r) { + if (r.ok || r.status === 201 || r.status === 204) self.terminal.print('Moved: ' + name + ' → ' + newFull, 'success'); + else self.terminal.print('Move failed: ' + name + ' (HTTP ' + r.status + ')', 'error'); + }) + .catch(function(e) { self.terminal.print('Move failed: ' + name + ' — ' + (e && e.message ? e.message : e), 'error'); }); + }); + setTimeout(function() { self.openWebdavBrowser(target); }, 600); + }; + const bar = document.getElementById('wdav-bulk-bar'); + let row = document.getElementById('wdav-move-row'); + if (row) { row.remove(); row = null; } + if (!bar) { doMove(path || '/'); return; } + row = document.createElement('div'); + row.id = 'wdav-move-row'; + row.style.cssText = 'display:flex;gap:8px;align-items:center;width:100%;padding-top:8px;flex-wrap:wrap'; + row.innerHTML = '<span style="font-size:0.9em;color:var(--text-color)">Move ' + names.length + + ' to:</span><input id="wdav-move-dest" type="text" aria-label="Destination directory" ' + + 'style="flex:1;min-width:120px;min-height:44px;padding:6px 10px;font-size:1em;border-radius:4px;' + + 'border:1px solid var(--border);background:var(--bg-primary);color:var(--text-color)">' + + '<button class="btn" id="wdav-move-go" style="min-height:44px;padding:0 14px">Move here</button>' + + '<button class="btn" id="wdav-move-cancel" style="min-height:44px;padding:0 14px">Cancel</button>'; + bar.appendChild(row); + const dest = row.querySelector('#wdav-move-dest'); + dest.value = (path || '/').replace(/\/+$/, '') || '/'; + row.querySelector('#wdav-move-go').onclick = function() { const v = dest.value; row.remove(); doMove(v); }; + row.querySelector('#wdav-move-cancel').onclick = function() { row.remove(); }; + dest.focus(); + dest.select(); }); document.getElementById('bulk-download')?.addEventListener('click', function() { const names = Object.entries(self._selected).filter(function(e) { return e[1]; }).map(function(e) { return e[0]; }); |