Anhänge (Unterlagen + Mailanhänge): PDF im neuen Tab öffnen statt Download
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -103,6 +103,21 @@ if (!fs.existsSync(emailAnhaengeDir)) {
|
|||||||
fs.mkdirSync(emailAnhaengeDir, { recursive: true });
|
fs.mkdirSync(emailAnhaengeDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Serve a stored file with `Content-Disposition: inline` so the browser opens it
|
||||||
|
// (PDF/image) in a tab instead of downloading. Falls back to the file extension
|
||||||
|
// when no MIME type is stored.
|
||||||
|
function serveInline(res, filePath, filename, mime) {
|
||||||
|
const type = mime || {
|
||||||
|
'.pdf': 'application/pdf', '.png': 'image/png', '.jpg': 'image/jpeg',
|
||||||
|
'.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp',
|
||||||
|
}[path.extname(filename || filePath).toLowerCase()];
|
||||||
|
if (type) res.type(type);
|
||||||
|
const safe = String(filename || 'datei').replace(/["\r\n]/g, '');
|
||||||
|
res.setHeader('Content-Disposition',
|
||||||
|
`inline; filename="${safe}"; filename*=UTF-8''${encodeURIComponent(safe)}`);
|
||||||
|
res.sendFile(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
// --- E-mail display + reply helpers --------------------------------------
|
// --- E-mail display + reply helpers --------------------------------------
|
||||||
// A stored message is rendered as HTML when we have an HTML body (or the plain
|
// A stored message is rendered as HTML when we have an HTML body (or the plain
|
||||||
// body is actually HTML markup — some senders put HTML in the text/plain part).
|
// body is actually HTML markup — some senders put HTML in the text/plain part).
|
||||||
@@ -1475,6 +1490,8 @@ initializeDatabase().then(() => {
|
|||||||
if (!a) return res.status(404).send('Anhang nicht gefunden');
|
if (!a) return res.status(404).send('Anhang nicht gefunden');
|
||||||
const p = path.join(emailAnhaengeDir, a.pfad);
|
const p = path.join(emailAnhaengeDir, a.pfad);
|
||||||
if (!fs.existsSync(p)) return res.status(404).send('Datei nicht gefunden');
|
if (!fs.existsSync(p)) return res.status(404).send('Datei nicht gefunden');
|
||||||
|
// inline=1 opens viewable files (PDF/image) in the browser tab instead of forcing a download.
|
||||||
|
if (req.query.inline === '1') return serveInline(res, p, a.name || a.pfad, a.mime);
|
||||||
res.download(p, a.name || a.pfad);
|
res.download(p, a.name || a.pfad);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error downloading e-mail attachment:', error);
|
console.error('Error downloading e-mail attachment:', error);
|
||||||
@@ -1943,6 +1960,8 @@ initializeDatabase().then(() => {
|
|||||||
if (!anhang) return res.status(404).send('Anhang nicht gefunden');
|
if (!anhang) return res.status(404).send('Anhang nicht gefunden');
|
||||||
const filePath = path.join(anhaengeDir, anhang.pfad);
|
const filePath = path.join(anhaengeDir, anhang.pfad);
|
||||||
if (!fs.existsSync(filePath)) return res.status(404).send('Datei nicht gefunden');
|
if (!fs.existsSync(filePath)) return res.status(404).send('Datei nicht gefunden');
|
||||||
|
// inline=1 opens viewable files (PDF/image) in the browser tab instead of forcing a download.
|
||||||
|
if (req.query.inline === '1') return serveInline(res, filePath, anhang.dateiname, anhang.mime);
|
||||||
res.download(filePath, anhang.dateiname);
|
res.download(filePath, anhang.dateiname);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error downloading attachment:', error);
|
console.error('Error downloading attachment:', error);
|
||||||
|
|||||||
+5
-4
@@ -154,15 +154,16 @@
|
|||||||
<ul class="space-y-2">
|
<ul class="space-y-2">
|
||||||
<% anhaenge.forEach(a => { %>
|
<% anhaenge.forEach(a => { %>
|
||||||
<li class="flex items-center justify-between gap-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 px-4 py-3">
|
<li class="flex items-center justify-between gap-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 px-4 py-3">
|
||||||
<div class="flex items-center gap-3 min-w-0">
|
<a href="/anhaenge/<%= a.id %>/download?inline=1" target="_blank" rel="noopener"
|
||||||
|
class="flex items-center gap-3 min-w-0 group" title="Im neuen Tab öffnen">
|
||||||
<svg class="w-6 h-6 text-red-500 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-6 h-6 text-red-500 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<p class="text-sm font-medium text-gray-800 dark:text-gray-100 truncate"><%= a.name || a.dateiname %></p>
|
<p class="text-sm font-medium text-gray-800 dark:text-gray-100 truncate group-hover:text-blue-600 dark:group-hover:text-blue-400 group-hover:underline"><%= a.name || a.dateiname %></p>
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 truncate"><%= a.dateiname %></p>
|
<p class="text-xs text-gray-500 dark:text-gray-400 truncate"><%= a.dateiname %></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
<div class="flex items-center gap-3 shrink-0">
|
<div class="flex items-center gap-3 shrink-0">
|
||||||
<a href="/anhaenge/<%= a.id %>/download"
|
<a href="/anhaenge/<%= a.id %>/download"
|
||||||
class="inline-flex items-center gap-1 text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 hover:underline">
|
class="inline-flex items-center gap-1 text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 hover:underline">
|
||||||
@@ -355,7 +356,7 @@
|
|||||||
<% if (e.anhaenge && e.anhaenge.length) { %>
|
<% if (e.anhaenge && e.anhaenge.length) { %>
|
||||||
<div class="mt-2 flex flex-wrap gap-2">
|
<div class="mt-2 flex flex-wrap gap-2">
|
||||||
<% e.anhaenge.forEach(function(a){ %>
|
<% e.anhaenge.forEach(function(a){ %>
|
||||||
<a href="/email-anhaenge/<%= a.id %>/download" class="inline-flex items-center gap-1 text-xs px-2 py-1 rounded border border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-400 hover:underline">
|
<a href="/email-anhaenge/<%= a.id %>/download?inline=1" target="_blank" rel="noopener" class="inline-flex items-center gap-1 text-xs px-2 py-1 rounded border border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-400 hover:underline">
|
||||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"></path></svg>
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"></path></svg>
|
||||||
<%= a.name || 'Anhang' %>
|
<%= a.name || 'Anhang' %>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user