feat(filemanager): 👁 Open + ⬇ Download pro Datei in App + Diagnostic
Stefan's UX-Wunsch: Datei direkt oeffnen ohne Umweg ueber Download. Plus: in der App fehlte komplett der Per-Row-Download-Button (nur via Checkbox + Bulk-Download). Beides jetzt gefixt. App (SettingsScreen.tsx): - Neue per-Row-Buttons: 👁 Open + ⬇ Download + 🕒 Versionen + 🗑 Loeschen - Open-Pfad nutzt requestId-Praefix 'open-' im file_response-Handler → Datei wird nach CachesDirectory geschrieben (kein Storage-Bloat) → FileOpener-Native-Module (Intent.ACTION_VIEW mit MIME) oeffnet mit dem System-Picker → User waehlt PDF-Viewer / Galerie / Player - guessMimeFromName-Helper fuer den Intent damit Android die passende App findet - Download-Pfad unveraendert ('single-' Praefix), schreibt nach DownloadDirectory mit Suffix-Inkrement bei Namens-Konflikt Diagnostic (server.js + index.html): - Neue Route /api/files-view (gleicher Code-Pfad wie files-download, aber Content-Disposition:inline + echter MIME-Type statt octet-stream) - Browser zeigt PDF / Bilder / Text im neuen Tab statt forcierten Download - 👁-Button in jeder File-Row neben ⬇/🕒/🗑 - Fallback fuer unbekannte MIMEs: octet-stream → Browser bietet Download Bei beiden Pfaden bleibt der Cache nutzbar: nach App-Open kann der User die Datei im jeweiligen Viewer behalten; im Browser bleibt sie im Tab.
This commit is contained in:
@@ -4038,6 +4038,7 @@
|
||||
<div style="color:#E0E0F0;font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${badge}<strong>${escapeHtml(f.name)}</strong></div>
|
||||
<div style="color:#555570;font-size:10px;">${fmtSize(f.size)} · ${fmtDate(f.mtime)}</div>
|
||||
</div>
|
||||
<button class="btn secondary" onclick="openFileInline('${encodeURIComponent(f.path)}')" style="padding:2px 8px;font-size:10px;" title="Öffnen">👁</button>
|
||||
<button class="btn secondary" onclick="downloadFile('${encodeURIComponent(f.path)}')" style="padding:2px 8px;font-size:10px;" title="Herunterladen">⬇</button>
|
||||
<button class="btn secondary" onclick="showVersions('${escapeHtml(f.name)}')" style="padding:2px 8px;font-size:10px;" title="Versionen">🕒</button>
|
||||
<button class="btn secondary" onclick="deleteFile('${pathEsc}','${escapeHtml(f.name)}')" style="padding:2px 8px;font-size:10px;color:#FF6B6B;border-color:#FF6B6B;" title="Loeschen">🗑</button>
|
||||
@@ -4174,6 +4175,12 @@
|
||||
window.location.href = '/api/files-download?path=' + encPath;
|
||||
}
|
||||
|
||||
function openFileInline(encPath) {
|
||||
// Inline-View — Browser zeigt PDF / Bild / Text im neuen Tab,
|
||||
// bei unbekanntem MIME landet's als Download-Fallback.
|
||||
window.open('/api/files-view?path=' + encPath, '_blank', 'noopener');
|
||||
}
|
||||
|
||||
async function deleteFile(p, name) {
|
||||
if (!confirm(`Datei "${name}" wirklich löschen?\n\nIn allen Chat-Bubbles wird sie als gelöscht markiert.`)) return;
|
||||
try {
|
||||
|
||||
+22
-3
@@ -1622,7 +1622,10 @@ const server = http.createServer((req, res) => {
|
||||
res.end(JSON.stringify({ ok: false, error: err.message }));
|
||||
}
|
||||
return;
|
||||
} else if (req.url.startsWith("/api/files-download?") && req.method === "GET") {
|
||||
} else if ((req.url.startsWith("/api/files-download?") || req.url.startsWith("/api/files-view?")) && req.method === "GET") {
|
||||
// /api/files-download → mit Content-Disposition:attachment (Browser downloaded)
|
||||
// /api/files-view → mit Disposition:inline (Browser zeigt PDF/Bilder im Tab)
|
||||
const isInline = req.url.startsWith("/api/files-view?");
|
||||
const u = new URL("http://x" + req.url);
|
||||
const p = u.searchParams.get("path") || "";
|
||||
const safe = path.resolve(p);
|
||||
@@ -1633,10 +1636,26 @@ const server = http.createServer((req, res) => {
|
||||
}
|
||||
const stat = fs.statSync(safe);
|
||||
const fname = path.basename(safe);
|
||||
// Beim View-Modus echten MIME-Type setzen damit Browser inline rendert.
|
||||
// Bei Download-Modus weiter octet-stream + attachment-Disposition.
|
||||
const ext = path.extname(fname).toLowerCase();
|
||||
const mimeMap = {
|
||||
".pdf": "application/pdf",
|
||||
".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png",
|
||||
".gif": "image/gif", ".webp": "image/webp", ".svg": "image/svg+xml",
|
||||
".mp3": "audio/mpeg", ".wav": "audio/wav", ".ogg": "audio/ogg",
|
||||
".mp4": "video/mp4", ".webm": "video/webm",
|
||||
".txt": "text/plain; charset=utf-8", ".md": "text/markdown; charset=utf-8",
|
||||
".html": "text/html; charset=utf-8", ".htm": "text/html; charset=utf-8",
|
||||
".json": "application/json; charset=utf-8", ".csv": "text/csv; charset=utf-8",
|
||||
".zip": "application/zip",
|
||||
};
|
||||
const mime = isInline ? (mimeMap[ext] || "application/octet-stream")
|
||||
: "application/octet-stream";
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "application/octet-stream",
|
||||
"Content-Type": mime,
|
||||
"Content-Length": stat.size,
|
||||
"Content-Disposition": `attachment; filename="${fname}"`,
|
||||
"Content-Disposition": `${isInline ? "inline" : "attachment"}; filename="${fname}"`,
|
||||
});
|
||||
fs.createReadStream(safe).pipe(res);
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user