// preview.js — file preview popup. Reuses shared/preview-lib.js for // TIFF, ZIP listing, and image-rendering helpers; native iframe for // PDF and HTML;
for text; download button for everything else.
//
// Lifecycle: a single popup window is reused across multiple file
// clicks (state.previewWindow). Subsequent clicks rewrite its
// contents instead of spawning a new window — same UX as the archive
// tool.
(function () {
'use strict';
var state = window.app.state;
var loader = window.app.modules.loader;
var preview = window.zddc && window.zddc.preview;
if (!preview) {
// shared/preview-lib.js wasn't concatenated in. Bail loudly so
// the bug shows up in console rather than mysteriously failing.
console.error('[browse] zddc.preview not loaded — preview popup disabled.');
}
function escapeHtml(s) {
return String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
}
var MIME = {
'pdf': 'application/pdf',
'html': 'text/html', 'htm': 'text/html',
'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'png': 'image/png',
'gif': 'image/gif', 'webp': 'image/webp', 'svg': 'image/svg+xml',
'tif': 'image/tiff', 'tiff': 'image/tiff',
'zip': 'application/zip',
'txt': 'text/plain', 'md': 'text/markdown', 'json': 'application/json',
'xml': 'application/xml', 'csv': 'text/csv', 'log': 'text/plain',
'js': 'text/javascript', 'css': 'text/css'
};
// Pull bytes for a file node. Three sources:
// - server URL (zddc-server-backed file, including downloads
// of archived files served at real paths)
// - FS-API handle (local folder)
// - JSZip entry (file inside an expanded zip; reads from
// parent's cached JSZip instance)
async function getArrayBuffer(node) {
if (node.zipParentId != null) {
var owner = state.nodes.get(node.zipParentId);
if (!owner || !owner.zipFile) {
throw new Error('parent zip not loaded');
}
return await owner.zipFile.file(node.zipPath).async('arraybuffer');
}
if (state.source === 'server' && node.url) {
var resp = await fetch(node.url);
if (!resp.ok) throw new Error('HTTP ' + resp.status);
return await resp.arrayBuffer();
}
if (node.handle) {
var f = await node.handle.getFile();
return await f.arrayBuffer();
}
throw new Error('no source for file');
}
function getMime(ext) {
return MIME[ext] || 'application/octet-stream';
}
// Build a blob URL for the file's bytes. For server-mode regular
// files (not in a zip), prefer the live URL — relative links and
// server-side interception (e.g. .archive resolution) work then.
async function getBlobUrl(node) {
if (state.source === 'server' && node.url && node.zipParentId == null) {
return { url: node.url, fromServer: true };
}
var buf = await getArrayBuffer(node);
var blob = new Blob([buf], { type: getMime(node.ext) });
return { url: URL.createObjectURL(blob), fromServer: false };
}
function popupShell(node, primaryUrl) {
var safeName = escapeHtml(node.name);
var safeHref = escapeHtml(primaryUrl);
var ext = (node.ext || '').toLowerCase();
// Inline PDF and HTML previews load in iframes. HTML uses
// sandbox="allow-same-origin allow-popups
// allow-popups-to-escape-sandbox" — same posture as archive's
// preview: links navigate, scripts blocked, popups allowed.
var contentHtml;
if (ext === 'pdf') {
contentHtml = '';
} else if (ext === 'html' || ext === 'htm') {
contentHtml = '';
} else if (preview && preview.isImage(ext) && !preview.isTiff(ext)) {
contentHtml = '
';
} else {
contentHtml = 'Loading preview…';
}
return ''
+ '' + safeName + ' — preview '
+ ''
+ contentHtml
+ '