Files
SDS-CRM/public/js/core/attachment-preview.js
2026-03-23 02:09:14 +01:00

166 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** Download-URL (href der API) → dieselbe URL mit ?inline=1 für Anzeige */
export function hrefToInlineView(href) {
if (!href) return href;
const u = new URL(href, window.location.origin);
u.searchParams.set('inline', '1');
return u.pathname + u.search + u.hash;
}
const TEXT_PREVIEW_MAX = 512 * 1024;
/** @param {string} mime @param {string} fileName */
export function attachmentPreviewKind(mime, fileName) {
const m = (mime || '').toLowerCase().trim();
const ext = (fileName || '').split('.').pop()?.toLowerCase() || '';
if (m.startsWith('image/')) return 'image';
if (m === 'application/pdf' || ext === 'pdf') return 'pdf';
if (m.startsWith('video/')) return 'video';
if (m.startsWith('audio/')) return 'audio';
if (
m.startsWith('text/') ||
m === 'application/json' ||
m === 'application/xml' ||
['csv', 'json', 'xml', 'txt', 'log', 'md', 'svg'].includes(ext)
) {
return 'text';
}
return 'other';
}
let dialogEl;
let bodyEl;
let titleEl;
let downloadLink;
function setPageScrollLocked(locked) {
if (locked) {
document.documentElement.style.overflow = 'hidden';
document.body.style.overflow = 'hidden';
} else {
document.documentElement.style.overflow = '';
document.body.style.overflow = '';
}
}
function ensureDialog() {
if (dialogEl) return;
dialogEl = document.createElement('dialog');
dialogEl.className = 'attachment-preview-dialog';
dialogEl.setAttribute('aria-modal', 'true');
dialogEl.innerHTML = `
<div class="attachment-preview-inner">
<header class="attachment-preview-header">
<h3 class="attachment-preview-title"></h3>
<button type="button" class="attachment-preview-close" aria-label="Schließen">×</button>
</header>
<div class="attachment-preview-body"></div>
<footer class="attachment-preview-footer">
<a class="button secondary attachment-preview-download" href="#" download>Herunterladen</a>
</footer>
</div>`;
document.body.appendChild(dialogEl);
bodyEl = dialogEl.querySelector('.attachment-preview-body');
titleEl = dialogEl.querySelector('.attachment-preview-title');
downloadLink = dialogEl.querySelector('.attachment-preview-download');
dialogEl.querySelector('.attachment-preview-close').addEventListener('click', () => {
dialogEl.close();
});
dialogEl.addEventListener('click', (e) => {
if (e.target === dialogEl) dialogEl.close();
});
dialogEl.addEventListener('close', () => {
if (bodyEl) bodyEl.innerHTML = '';
setPageScrollLocked(false);
});
}
/**
* Klick-Delegation: Links mit .js-attachment-preview öffnen das Modal.
*/
export function bindAttachmentPreview(root = document.body) {
ensureDialog();
root.addEventListener('click', (e) => {
const a = e.target.closest('a.js-attachment-preview');
if (!a) return;
e.preventDefault();
openAttachmentPreview(a);
});
}
/**
* @param {HTMLAnchorElement} a
*/
export async function openAttachmentPreview(a) {
ensureDialog();
const name = a.getAttribute('data-name') || a.textContent?.trim() || 'Datei';
const mime = a.getAttribute('data-mime') || '';
const rawHref = a.getAttribute('href') || '';
const viewUrl = hrefToInlineView(rawHref);
const kind = attachmentPreviewKind(mime, name);
titleEl.textContent = name;
downloadLink.href = rawHref;
downloadLink.setAttribute('download', name);
bodyEl.className = 'attachment-preview-body attachment-preview-body--scroll';
bodyEl.innerHTML = '<p class="muted attachment-preview-loading">Lade Vorschau …</p>';
try {
if (kind === 'image') {
bodyEl.innerHTML = '';
const img = document.createElement('img');
img.className = 'attachment-preview-img';
img.alt = name;
img.src = viewUrl;
img.referrerPolicy = 'same-origin';
bodyEl.appendChild(img);
} else if (kind === 'pdf') {
bodyEl.className = 'attachment-preview-body attachment-preview-body--embed';
bodyEl.innerHTML = '';
const iframe = document.createElement('iframe');
iframe.className = 'attachment-preview-iframe';
iframe.title = name;
iframe.src = viewUrl;
bodyEl.appendChild(iframe);
} else if (kind === 'video') {
bodyEl.innerHTML = '';
const v = document.createElement('video');
v.className = 'attachment-preview-video';
v.controls = true;
v.playsInline = true;
v.src = viewUrl;
bodyEl.appendChild(v);
} else if (kind === 'audio') {
bodyEl.innerHTML = '';
const v = document.createElement('audio');
v.className = 'attachment-preview-audio';
v.controls = true;
v.src = viewUrl;
bodyEl.appendChild(v);
} else if (kind === 'text') {
const res = await fetch(viewUrl, { credentials: 'include' });
if (!res.ok) throw new Error('Laden fehlgeschlagen');
let text = await res.text();
if (text.length > TEXT_PREVIEW_MAX) {
text = `${text.slice(0, TEXT_PREVIEW_MAX)}\n\n… (gekürzt)`;
}
bodyEl.innerHTML = '';
const pre = document.createElement('pre');
pre.className = 'attachment-preview-text';
pre.textContent = text;
bodyEl.appendChild(pre);
} else {
bodyEl.innerHTML =
'<p class="muted">Für diesen Dateityp gibt es keine eingebaute Vorschau. Nutzen Sie „Herunterladen“.</p>';
}
} catch (err) {
bodyEl.className = 'attachment-preview-body attachment-preview-body--scroll';
bodyEl.innerHTML = `<p class="error">Vorschau konnte nicht geladen werden: ${err.message || err}</p>`;
}
if (typeof dialogEl.showModal === 'function') {
setPageScrollLocked(true);
dialogEl.showModal();
}
}