Preview uploaded documents in an in-page modal instead of a new tab
Chrome blocks top-level navigation to data: URLs (anti-phishing), so the earlier target="_blank" preview links silently opened a blank tab for every uploaded document — real bug, caught by live testing. Replaces that with a same-tab modal: clicking a document name opens it in an overlay (<iframe> for PDFs, <img> for images — data: URLs work fine for embedding, just not top-level navigation), with its own download button and a close control. The small ⬇ icon next to each chip still downloads directly, unaffected since the `download` attribute uses a different code path than navigation.
This commit is contained in:
parent
92101e3077
commit
dd24d7217a
@ -360,6 +360,41 @@
|
|||||||
.contract-template-form .field-checkbox input { width: auto; }
|
.contract-template-form .field-checkbox input { width: auto; }
|
||||||
.signature-pad-wrap { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
|
.signature-pad-wrap { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
|
||||||
.signature-pad-canvas { border: 1px solid var(--border); border-radius: 8px; background: #fff; touch-action: none; cursor: crosshair; }
|
.signature-pad-canvas { border: 1px solid var(--border); border-radius: 8px; background: #fff; touch-action: none; cursor: crosshair; }
|
||||||
|
|
||||||
|
/* --- Dokument-Vorschau (Modal) --- */
|
||||||
|
.doc-preview-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
z-index: 200;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
.doc-preview-box {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border-radius: 14px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 720px;
|
||||||
|
max-height: 90vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.doc-preview-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 14px 18px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.doc-preview-header strong { font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.doc-preview-body { flex: 1; overflow: auto; background: var(--bg); display: flex; align-items: center; justify-content: center; min-height: 300px; }
|
||||||
|
.doc-preview-body img { max-width: 100%; display: block; }
|
||||||
|
.doc-preview-body iframe { width: 100%; height: 75vh; border: none; }
|
||||||
#trashCalendarEl { font-size: 13px; }
|
#trashCalendarEl { font-size: 13px; }
|
||||||
#trashCalendarEl .fc { font-family: inherit; }
|
#trashCalendarEl .fc { font-family: inherit; }
|
||||||
#trashCalendarEl .fc-toolbar-title { font-size: 16px; font-weight: 700; color: var(--text); }
|
#trashCalendarEl .fc-toolbar-title { font-size: 16px; font-weight: 700; color: var(--text); }
|
||||||
@ -1032,6 +1067,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ================= DOKUMENT-VORSCHAU (Modal) ================= -->
|
||||||
|
<div id="docPreviewOverlay" class="doc-preview-overlay" style="display:none" onclick="if (event.target === this) closeDocumentPreview()">
|
||||||
|
<div class="doc-preview-box">
|
||||||
|
<div class="doc-preview-header">
|
||||||
|
<strong id="docPreviewTitle"></strong>
|
||||||
|
<div style="display:flex; gap:10px; align-items:center;">
|
||||||
|
<a id="docPreviewDownload" class="btn-secondary" download>Herunterladen</a>
|
||||||
|
<button type="button" class="btn-secondary" onclick="closeDocumentPreview()">Schließen ✕</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="doc-preview-body" id="docPreviewBody"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const API_BASE_URL = window.WG_API_BASE_URL ||
|
const API_BASE_URL = window.WG_API_BASE_URL ||
|
||||||
(location.protocol === 'file:' ? 'http://localhost:3000/v1' : '/v1');
|
(location.protocol === 'file:' ? 'http://localhost:3000/v1' : '/v1');
|
||||||
@ -2854,25 +2903,30 @@
|
|||||||
return `Dokument ${index + 1}.${ext}`;
|
return `Dokument ${index + 1}.${ext}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Klick auf den Namen öffnet eine Vorschau im neuen Tab (Browser können
|
function docFileExt(url) {
|
||||||
// PDFs/Bilder nativ anzeigen); der Pfeil daneben lädt bei Bedarf separat
|
return url.includes('application/pdf') ? 'pdf' : (/^data:image\/(\w+)/.exec(url)?.[1] || 'jpg');
|
||||||
// herunter. Für alle hochgeladenen Dokumente (Verträge, Ausweis, Schufa) einheitlich.
|
}
|
||||||
|
|
||||||
|
// Klick auf den Namen öffnet eine kleine Vorschau im selben Tab (Modal) —
|
||||||
|
// ein Link mit target="_blank" auf eine data:-URL würde Chrome als
|
||||||
|
// Top-Level-Navigation blockieren (Phishing-Schutz), als eingebettetes
|
||||||
|
// <img>/<iframe> im Modal ist das kein Problem. Der Pfeil daneben lädt bei
|
||||||
|
// Bedarf separat herunter. Für alle hochgeladenen Dokumente (Verträge,
|
||||||
|
// Ausweis, Schufa) einheitlich.
|
||||||
function buildDocumentChip(label, url) {
|
function buildDocumentChip(label, url) {
|
||||||
const chip = el('span', 'invite-status-chip');
|
const chip = el('span', 'invite-status-chip');
|
||||||
chip.style.cssText = 'background:var(--bg); color:var(--text); text-transform:none; display:inline-flex; align-items:center; gap:7px;';
|
chip.style.cssText = 'background:var(--bg); color:var(--text); text-transform:none; display:inline-flex; align-items:center; gap:7px;';
|
||||||
|
|
||||||
const previewLink = document.createElement('a');
|
const previewBtn = document.createElement('button');
|
||||||
previewLink.href = url;
|
previewBtn.type = 'button';
|
||||||
previewLink.target = '_blank';
|
previewBtn.textContent = label;
|
||||||
previewLink.rel = 'noopener';
|
previewBtn.style.cssText = 'background:none; border:none; padding:0; margin:0; font:inherit; color:var(--text); text-decoration:underline; cursor:pointer;';
|
||||||
previewLink.textContent = label;
|
previewBtn.onclick = () => openDocumentPreview(label, url);
|
||||||
previewLink.style.color = 'var(--text)';
|
chip.appendChild(previewBtn);
|
||||||
chip.appendChild(previewLink);
|
|
||||||
|
|
||||||
const ext = url.includes('application/pdf') ? 'pdf' : (/^data:image\/(\w+)/.exec(url)?.[1] || 'jpg');
|
|
||||||
const downloadLink = document.createElement('a');
|
const downloadLink = document.createElement('a');
|
||||||
downloadLink.href = url;
|
downloadLink.href = url;
|
||||||
downloadLink.download = `${label.replace(/[^\w.-]+/g, '_')}.${ext}`;
|
downloadLink.download = `${label.replace(/[^\w.-]+/g, '_')}.${docFileExt(url)}`;
|
||||||
downloadLink.title = 'Herunterladen';
|
downloadLink.title = 'Herunterladen';
|
||||||
downloadLink.textContent = '⬇';
|
downloadLink.textContent = '⬇';
|
||||||
downloadLink.style.cssText = 'color:var(--text-muted); text-decoration:none; font-size:12px;';
|
downloadLink.style.cssText = 'color:var(--text-muted); text-decoration:none; font-size:12px;';
|
||||||
@ -2881,6 +2935,31 @@
|
|||||||
return chip;
|
return chip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openDocumentPreview(label, url) {
|
||||||
|
document.getElementById('docPreviewTitle').textContent = label;
|
||||||
|
const body = document.getElementById('docPreviewBody');
|
||||||
|
body.innerHTML = '';
|
||||||
|
if (url.startsWith('data:application/pdf')) {
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.src = url;
|
||||||
|
body.appendChild(iframe);
|
||||||
|
} else {
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = url;
|
||||||
|
img.alt = label;
|
||||||
|
body.appendChild(img);
|
||||||
|
}
|
||||||
|
const downloadLink = document.getElementById('docPreviewDownload');
|
||||||
|
downloadLink.href = url;
|
||||||
|
downloadLink.download = `${label.replace(/[^\w.-]+/g, '_')}.${docFileExt(url)}`;
|
||||||
|
document.getElementById('docPreviewOverlay').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDocumentPreview() {
|
||||||
|
document.getElementById('docPreviewOverlay').style.display = 'none';
|
||||||
|
document.getElementById('docPreviewBody').innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
function buildProfileCompletenessBadge(user) {
|
function buildProfileCompletenessBadge(user) {
|
||||||
const missing = [];
|
const missing = [];
|
||||||
if (!user.phoneNumber) missing.push('Telefonnummer');
|
if (!user.phoneNumber) missing.push('Telefonnummer');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user