diff options
Diffstat (limited to 'modules/ext/critical-ext-bundle.js')
| -rw-r--r-- | modules/ext/critical-ext-bundle.js | 150 |
1 files changed, 100 insertions, 50 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); |