V0.1
This commit is contained in:
165
public/js/core/attachment-preview.js
Normal file
165
public/js/core/attachment-preview.js
Normal file
@@ -0,0 +1,165 @@
|
||||
/** 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user