Compare commits

..

No commits in common. "8875d490f544fbc749e61b4ed9a71edc7ebe2d0a" and "c05fc376f2e6b35e0214985ccbd3fb384d0c6dc7" have entirely different histories.

12 changed files with 21 additions and 698 deletions

View file

@ -42,8 +42,6 @@ concat_files \
# without an external HTTP dependency.
concat_files \
"../shared/vendor/jszip.min.js" \
"../shared/vendor/docx-preview.min.js" \
"../shared/vendor/xlsx.full.min.js" \
"../shared/vendor/utif.min.js" \
"../shared/vendor/js-yaml.min.js" \
"../shared/vendor/codemirror-yaml.min.js" \

View file

@ -589,14 +589,7 @@ body {
.md-shell {
display: grid;
grid-template-rows: 1fr;
/* minmax(0, ) on BOTH tracks is load-bearing: a bare `1fr` is
`minmax(auto, 1fr)`, whose `auto` floor is the editor's min-content
width (Toast UI's toolbar). That floor stops the content track from
shrinking, so the whole shell overflows #previewBody as the window
narrows instead of the editor getting narrower. minmax(0, 1fr) drops
the floor so the editor reflows down to nothing. JS overrides the
column widths on drag it preserves the minmax(0, ) form. */
grid-template-columns: minmax(0, 280px) minmax(0, 1fr);
grid-template-columns: 280px 1fr; /* JS overrides on resize */
grid-template-areas: "sidebar content";
height: 100%;
min-height: 0;
@ -613,7 +606,6 @@ body {
grid-area: sidebar;
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
overflow: hidden;
border-right: 1px solid var(--border);

View file

@ -354,7 +354,7 @@
container.innerHTML = '';
var shell = document.createElement('div');
shell.className = 'md-shell';
shell.style.gridTemplateColumns = 'minmax(0, ' + lastSidebarWidth + 'px) minmax(0, 1fr)';
shell.style.gridTemplateColumns = lastSidebarWidth + 'px 1fr';
container.appendChild(shell);
// ── Sidebar (col 1): front matter (top) + TOC (bottom) ──────────────
@ -595,7 +595,7 @@
var w = startW + dx;
w = Math.max(SIDEBAR_MIN_WIDTH, Math.min(SIDEBAR_MAX_WIDTH, w));
lastSidebarWidth = w;
shell.style.gridTemplateColumns = 'minmax(0, ' + w + 'px) minmax(0, 1fr)';
shell.style.gridTemplateColumns = w + 'px 1fr';
e.preventDefault();
}
function onUp() {
@ -620,7 +620,7 @@
var w = Math.max(SIDEBAR_MIN_WIDTH,
Math.min(SIDEBAR_MAX_WIDTH, lastSidebarWidth + step));
lastSidebarWidth = w;
shell.style.gridTemplateColumns = 'minmax(0, ' + w + 'px) minmax(0, 1fr)';
shell.style.gridTemplateColumns = w + 'px 1fr';
});
})();

View file

@ -176,24 +176,6 @@
return;
}
// Office docs (.docx via docx-preview, .xlsx/.xls via SheetJS) →
// shared/preview-lib renderers. .doc/.ppt etc. fall through to the
// download fallback below.
if (preview && preview.isOffice(ext)) {
try {
var officeBuf = await getArrayBuffer(node);
container.innerHTML = '';
if (ext === 'docx') {
await preview.renderDocx(document, container, officeBuf, { fileName: node.name });
} else {
await preview.renderXlsx(document, container, officeBuf, { fileName: node.name });
}
} catch (e) {
renderError(container, 'Failed to render ' + ext.toUpperCase() + ': ' + (e.message || e));
}
return;
}
if (preview && preview.isText(ext)) {
try {
var txtBuf = await getArrayBuffer(node);
@ -291,13 +273,6 @@
} else if (preview && preview.isZip(ext)) {
var zb = await getArrayBuffer(node);
await preview.renderZipListing(win.document, c, zb, { fileName: node.name });
} else if (preview && preview.isOffice(ext)) {
var ob = await getArrayBuffer(node);
if (ext === 'docx') {
await preview.renderDocx(win.document, c, ob, { fileName: node.name });
} else {
await preview.renderXlsx(win.document, c, ob, { fileName: node.name });
}
} else if (preview && preview.isText(ext)) {
var txb = await getArrayBuffer(node);
var text = new TextDecoder('utf-8', { fatal: false }).decode(txb);

View file

@ -4,23 +4,15 @@
* Cross-tool helpers for previewing file types that need a decoder:
* - TIFF (UTIF.js) multi-page, browser-PDF-viewer-style toolbar
* - ZIP listing (JSZip) sortable file-list view
* - DOCX (docx-preview) Word-styled pages in a scroll container
* - XLSX/XLS (SheetJS) sheet-to-HTML table with a sheet tab bar
*
* Renderers operate on any document (parent window or popup window), so the
* same code works for tools whose preview opens in a popup (classifier,
* archive, transmittal) and tools that render inline (browse).
*
* The DOCX/XLSX renderers expect their vendor lib bundled by the calling
* tool's build.sh (docx-preview.min.js window.docx, xlsx.full.min.js
* window.XLSX); they degrade to a friendly message if it isn't present.
*
* Public API on window.zddc.preview:
* loadLibrary(url) Promise<void>
* renderTiff(doc, container, arrayBuffer, opts) Promise<void>
* renderZipListing(doc, container, arrayBuffer, opts) Promise<void>
* renderDocx(doc, container, arrayBuffer, opts) Promise<void>
* renderXlsx(doc, container, arrayBuffer, opts) Promise<void>
* TIFF_EXTENSIONS, IMAGE_EXTENSIONS, TEXT_EXTENSIONS, OFFICE_EXTENSIONS
* isTiff(ext), isImage(ext), isText(ext), isZip(ext), isOffice(ext)
*
@ -533,111 +525,6 @@
});
}
// ── DOCX (docx-preview) ────────────────────────────────────────────────
//
// docx-preview renders Word-styled pages with an intrinsic page width, so
// we wrap it in a scroll container: a wide document scrolls WITHIN the
// preview pane rather than pushing the page wider. docx-preview is bundled
// by the tools that opt in (each build.sh concatenates
// shared/vendor/docx-preview.min.js → window.docx).
var DOCX_CSS =
'.zddc-docx{height:100%;min-width:0;overflow:auto;'
+ 'background:var(--bg-secondary,#eee);padding:1rem;box-sizing:border-box;}'
+ '.zddc-docx .docx-wrapper{background:transparent;padding:0;}';
function renderDocx(doc, container, arrayBuffer, opts) {
opts = opts || {};
injectStyles(doc, 'zddc-docx-styles', DOCX_CSS);
if (!window.docx || typeof window.docx.renderAsync !== 'function') {
container.innerHTML = '<div class="preview-empty">DOCX preview unavailable '
+ '(renderer not bundled in this tool).</div>';
return Promise.resolve();
}
container.innerHTML = '';
var scroll = doc.createElement('div');
scroll.className = 'zddc-docx';
container.appendChild(scroll);
return Promise.resolve(
window.docx.renderAsync(arrayBuffer, scroll, null, { inWrapper: true })
).catch(function (err) {
container.innerHTML = '<div class="preview-empty" style="color:var(--danger,#c00)">'
+ 'Failed to render DOCX: ' + escapeHtml(err.message || err) + '</div>';
});
}
// ── XLSX / XLS (SheetJS) ───────────────────────────────────────────────
//
// Reads the workbook and renders the active sheet to an HTML table; a tab
// bar switches sheets when there's more than one. SheetJS is bundled by
// the tools that opt in (shared/vendor/xlsx.full.min.js → window.XLSX).
var XLSX_CSS =
'.zddc-xlsx{display:flex;flex-direction:column;height:100%;min-width:0;'
+ 'min-height:0;overflow:hidden;}'
+ '.zddc-xlsx__tabs{display:flex;flex-wrap:wrap;gap:0.25rem;padding:0.4rem;'
+ 'border-bottom:1px solid var(--border,#ccc);flex:0 0 auto;}'
+ '.zddc-xlsx__tab{padding:0.2rem 0.6rem;border:1px solid var(--border,#ccc);'
+ 'border-radius:4px;background:var(--bg,#fff);color:var(--text,#222);'
+ 'cursor:pointer;font-size:0.85rem;}'
+ '.zddc-xlsx__tab.is-active{background:var(--primary,#2563eb);color:#fff;'
+ 'border-color:var(--primary,#2563eb);}'
+ '.zddc-xlsx__body{flex:1 1 auto;min-width:0;min-height:0;overflow:auto;}'
+ '.zddc-xlsx__body table{border-collapse:collapse;font-size:0.85rem;'
+ 'color:var(--text,#222);}'
+ '.zddc-xlsx__body td,.zddc-xlsx__body th{border:1px solid var(--border,#ddd);'
+ 'padding:0.2rem 0.45rem;white-space:nowrap;}';
function renderXlsx(doc, container, arrayBuffer, opts) {
opts = opts || {};
injectStyles(doc, 'zddc-xlsx-styles', XLSX_CSS);
if (!window.XLSX || typeof window.XLSX.read !== 'function') {
container.innerHTML = '<div class="preview-empty">Spreadsheet preview unavailable '
+ '(renderer not bundled in this tool).</div>';
return Promise.resolve();
}
try {
var wb = window.XLSX.read(arrayBuffer, { type: 'array' });
container.innerHTML = '';
var rootEl = doc.createElement('div');
rootEl.className = 'zddc-xlsx';
var body = doc.createElement('div');
body.className = 'zddc-xlsx__body';
function showSheet(name) {
var sheet = wb.Sheets[name];
body.innerHTML = sheet
? window.XLSX.utils.sheet_to_html(sheet, { editable: false })
: '';
}
if (wb.SheetNames.length > 1) {
var tabs = doc.createElement('div');
tabs.className = 'zddc-xlsx__tabs';
wb.SheetNames.forEach(function (name, i) {
var btn = doc.createElement('button');
btn.className = 'zddc-xlsx__tab' + (i === 0 ? ' is-active' : '');
btn.textContent = name;
btn.addEventListener('click', function () {
var all = tabs.querySelectorAll('.zddc-xlsx__tab');
for (var j = 0; j < all.length; j++) all[j].classList.remove('is-active');
btn.classList.add('is-active');
showSheet(name);
});
tabs.appendChild(btn);
});
rootEl.appendChild(tabs);
}
rootEl.appendChild(body);
container.appendChild(rootEl);
showSheet(wb.SheetNames[0]);
} catch (err) {
container.innerHTML = '<div class="preview-empty" style="color:var(--danger,#c00)">'
+ 'Failed to render spreadsheet: ' + escapeHtml(err.message || err) + '</div>';
}
return Promise.resolve();
}
// ── Public API ───────────────────────────────────────────────────────────
if (!root.zddc) root.zddc = {};
@ -654,8 +541,6 @@
loadLibrary: loadLibrary,
renderTiff: renderTiff,
renderZipListing: renderZipListing,
renderDocx: renderDocx,
renderXlsx: renderXlsx,
formatSize: formatSize,
formatDate: formatDate
};

View file

@ -2582,7 +2582,7 @@ td[data-field="trackingNumber"] {
</svg>
<div class="header-title-group">
<span class="app-header__title">ZDDC Archive</span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b</span></span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-02 19:01:22 · af07fa4</span></span>
</div>
<button id="addDirectoryBtn" class="btn btn-primary">Use Local Directory</button>
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh Data"></button>
@ -5125,23 +5125,15 @@ X.B(E,Y);return E}return J}())
* Cross-tool helpers for previewing file types that need a decoder:
* - TIFF (UTIF.js) — multi-page, browser-PDF-viewer-style toolbar
* - ZIP listing (JSZip) — sortable file-list view
* - DOCX (docx-preview) — Word-styled pages in a scroll container
* - XLSX/XLS (SheetJS) — sheet-to-HTML table with a sheet tab bar
*
* Renderers operate on any document (parent window or popup window), so the
* same code works for tools whose preview opens in a popup (classifier,
* archive, transmittal) and tools that render inline (browse).
*
* The DOCX/XLSX renderers expect their vendor lib bundled by the calling
* tool's build.sh (docx-preview.min.js → window.docx, xlsx.full.min.js →
* window.XLSX); they degrade to a friendly message if it isn't present.
*
* Public API on window.zddc.preview:
* loadLibrary(url) → Promise<void>
* renderTiff(doc, container, arrayBuffer, opts) → Promise<void>
* renderZipListing(doc, container, arrayBuffer, opts) → Promise<void>
* renderDocx(doc, container, arrayBuffer, opts) → Promise<void>
* renderXlsx(doc, container, arrayBuffer, opts) → Promise<void>
* TIFF_EXTENSIONS, IMAGE_EXTENSIONS, TEXT_EXTENSIONS, OFFICE_EXTENSIONS
* isTiff(ext), isImage(ext), isText(ext), isZip(ext), isOffice(ext)
*
@ -5654,111 +5646,6 @@ X.B(E,Y);return E}return J}())
});
}
// ── DOCX (docx-preview) ────────────────────────────────────────────────
//
// docx-preview renders Word-styled pages with an intrinsic page width, so
// we wrap it in a scroll container: a wide document scrolls WITHIN the
// preview pane rather than pushing the page wider. docx-preview is bundled
// by the tools that opt in (each build.sh concatenates
// shared/vendor/docx-preview.min.js → window.docx).
var DOCX_CSS =
'.zddc-docx{height:100%;min-width:0;overflow:auto;'
+ 'background:var(--bg-secondary,#eee);padding:1rem;box-sizing:border-box;}'
+ '.zddc-docx .docx-wrapper{background:transparent;padding:0;}';
function renderDocx(doc, container, arrayBuffer, opts) {
opts = opts || {};
injectStyles(doc, 'zddc-docx-styles', DOCX_CSS);
if (!window.docx || typeof window.docx.renderAsync !== 'function') {
container.innerHTML = '<div class="preview-empty">DOCX preview unavailable '
+ '(renderer not bundled in this tool).</div>';
return Promise.resolve();
}
container.innerHTML = '';
var scroll = doc.createElement('div');
scroll.className = 'zddc-docx';
container.appendChild(scroll);
return Promise.resolve(
window.docx.renderAsync(arrayBuffer, scroll, null, { inWrapper: true })
).catch(function (err) {
container.innerHTML = '<div class="preview-empty" style="color:var(--danger,#c00)">'
+ 'Failed to render DOCX: ' + escapeHtml(err.message || err) + '</div>';
});
}
// ── XLSX / XLS (SheetJS) ───────────────────────────────────────────────
//
// Reads the workbook and renders the active sheet to an HTML table; a tab
// bar switches sheets when there's more than one. SheetJS is bundled by
// the tools that opt in (shared/vendor/xlsx.full.min.js → window.XLSX).
var XLSX_CSS =
'.zddc-xlsx{display:flex;flex-direction:column;height:100%;min-width:0;'
+ 'min-height:0;overflow:hidden;}'
+ '.zddc-xlsx__tabs{display:flex;flex-wrap:wrap;gap:0.25rem;padding:0.4rem;'
+ 'border-bottom:1px solid var(--border,#ccc);flex:0 0 auto;}'
+ '.zddc-xlsx__tab{padding:0.2rem 0.6rem;border:1px solid var(--border,#ccc);'
+ 'border-radius:4px;background:var(--bg,#fff);color:var(--text,#222);'
+ 'cursor:pointer;font-size:0.85rem;}'
+ '.zddc-xlsx__tab.is-active{background:var(--primary,#2563eb);color:#fff;'
+ 'border-color:var(--primary,#2563eb);}'
+ '.zddc-xlsx__body{flex:1 1 auto;min-width:0;min-height:0;overflow:auto;}'
+ '.zddc-xlsx__body table{border-collapse:collapse;font-size:0.85rem;'
+ 'color:var(--text,#222);}'
+ '.zddc-xlsx__body td,.zddc-xlsx__body th{border:1px solid var(--border,#ddd);'
+ 'padding:0.2rem 0.45rem;white-space:nowrap;}';
function renderXlsx(doc, container, arrayBuffer, opts) {
opts = opts || {};
injectStyles(doc, 'zddc-xlsx-styles', XLSX_CSS);
if (!window.XLSX || typeof window.XLSX.read !== 'function') {
container.innerHTML = '<div class="preview-empty">Spreadsheet preview unavailable '
+ '(renderer not bundled in this tool).</div>';
return Promise.resolve();
}
try {
var wb = window.XLSX.read(arrayBuffer, { type: 'array' });
container.innerHTML = '';
var rootEl = doc.createElement('div');
rootEl.className = 'zddc-xlsx';
var body = doc.createElement('div');
body.className = 'zddc-xlsx__body';
function showSheet(name) {
var sheet = wb.Sheets[name];
body.innerHTML = sheet
? window.XLSX.utils.sheet_to_html(sheet, { editable: false })
: '';
}
if (wb.SheetNames.length > 1) {
var tabs = doc.createElement('div');
tabs.className = 'zddc-xlsx__tabs';
wb.SheetNames.forEach(function (name, i) {
var btn = doc.createElement('button');
btn.className = 'zddc-xlsx__tab' + (i === 0 ? ' is-active' : '');
btn.textContent = name;
btn.addEventListener('click', function () {
var all = tabs.querySelectorAll('.zddc-xlsx__tab');
for (var j = 0; j < all.length; j++) all[j].classList.remove('is-active');
btn.classList.add('is-active');
showSheet(name);
});
tabs.appendChild(btn);
});
rootEl.appendChild(tabs);
}
rootEl.appendChild(body);
container.appendChild(rootEl);
showSheet(wb.SheetNames[0]);
} catch (err) {
container.innerHTML = '<div class="preview-empty" style="color:var(--danger,#c00)">'
+ 'Failed to render spreadsheet: ' + escapeHtml(err.message || err) + '</div>';
}
return Promise.resolve();
}
// ── Public API ───────────────────────────────────────────────────────────
if (!root.zddc) root.zddc = {};
@ -5775,8 +5662,6 @@ X.B(E,Y);return E}return J}())
loadLibrary: loadLibrary,
renderTiff: renderTiff,
renderZipListing: renderZipListing,
renderDocx: renderDocx,
renderXlsx: renderXlsx,
formatSize: formatSize,
formatDate: formatDate
};

File diff suppressed because one or more lines are too long

View file

@ -1793,7 +1793,7 @@ body.is-elevated::after {
</svg>
<div class="header-title-group">
<span class="app-header__title">ZDDC Classifier</span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b</span></span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-02 19:01:22 · af07fa4</span></span>
</div>
<button id="addDirectoryBtn" class="btn btn-primary">Use Local Directory</button>
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh and rescan directory" aria-label="Refresh" style="font-size:1.1rem;"></button>
@ -4396,23 +4396,15 @@ X.B(E,Y);return E}return J}())
* Cross-tool helpers for previewing file types that need a decoder:
* - TIFF (UTIF.js) — multi-page, browser-PDF-viewer-style toolbar
* - ZIP listing (JSZip) — sortable file-list view
* - DOCX (docx-preview) — Word-styled pages in a scroll container
* - XLSX/XLS (SheetJS) — sheet-to-HTML table with a sheet tab bar
*
* Renderers operate on any document (parent window or popup window), so the
* same code works for tools whose preview opens in a popup (classifier,
* archive, transmittal) and tools that render inline (browse).
*
* The DOCX/XLSX renderers expect their vendor lib bundled by the calling
* tool's build.sh (docx-preview.min.js → window.docx, xlsx.full.min.js →
* window.XLSX); they degrade to a friendly message if it isn't present.
*
* Public API on window.zddc.preview:
* loadLibrary(url) → Promise<void>
* renderTiff(doc, container, arrayBuffer, opts) → Promise<void>
* renderZipListing(doc, container, arrayBuffer, opts) → Promise<void>
* renderDocx(doc, container, arrayBuffer, opts) → Promise<void>
* renderXlsx(doc, container, arrayBuffer, opts) → Promise<void>
* TIFF_EXTENSIONS, IMAGE_EXTENSIONS, TEXT_EXTENSIONS, OFFICE_EXTENSIONS
* isTiff(ext), isImage(ext), isText(ext), isZip(ext), isOffice(ext)
*
@ -4925,111 +4917,6 @@ X.B(E,Y);return E}return J}())
});
}
// ── DOCX (docx-preview) ────────────────────────────────────────────────
//
// docx-preview renders Word-styled pages with an intrinsic page width, so
// we wrap it in a scroll container: a wide document scrolls WITHIN the
// preview pane rather than pushing the page wider. docx-preview is bundled
// by the tools that opt in (each build.sh concatenates
// shared/vendor/docx-preview.min.js → window.docx).
var DOCX_CSS =
'.zddc-docx{height:100%;min-width:0;overflow:auto;'
+ 'background:var(--bg-secondary,#eee);padding:1rem;box-sizing:border-box;}'
+ '.zddc-docx .docx-wrapper{background:transparent;padding:0;}';
function renderDocx(doc, container, arrayBuffer, opts) {
opts = opts || {};
injectStyles(doc, 'zddc-docx-styles', DOCX_CSS);
if (!window.docx || typeof window.docx.renderAsync !== 'function') {
container.innerHTML = '<div class="preview-empty">DOCX preview unavailable '
+ '(renderer not bundled in this tool).</div>';
return Promise.resolve();
}
container.innerHTML = '';
var scroll = doc.createElement('div');
scroll.className = 'zddc-docx';
container.appendChild(scroll);
return Promise.resolve(
window.docx.renderAsync(arrayBuffer, scroll, null, { inWrapper: true })
).catch(function (err) {
container.innerHTML = '<div class="preview-empty" style="color:var(--danger,#c00)">'
+ 'Failed to render DOCX: ' + escapeHtml(err.message || err) + '</div>';
});
}
// ── XLSX / XLS (SheetJS) ───────────────────────────────────────────────
//
// Reads the workbook and renders the active sheet to an HTML table; a tab
// bar switches sheets when there's more than one. SheetJS is bundled by
// the tools that opt in (shared/vendor/xlsx.full.min.js → window.XLSX).
var XLSX_CSS =
'.zddc-xlsx{display:flex;flex-direction:column;height:100%;min-width:0;'
+ 'min-height:0;overflow:hidden;}'
+ '.zddc-xlsx__tabs{display:flex;flex-wrap:wrap;gap:0.25rem;padding:0.4rem;'
+ 'border-bottom:1px solid var(--border,#ccc);flex:0 0 auto;}'
+ '.zddc-xlsx__tab{padding:0.2rem 0.6rem;border:1px solid var(--border,#ccc);'
+ 'border-radius:4px;background:var(--bg,#fff);color:var(--text,#222);'
+ 'cursor:pointer;font-size:0.85rem;}'
+ '.zddc-xlsx__tab.is-active{background:var(--primary,#2563eb);color:#fff;'
+ 'border-color:var(--primary,#2563eb);}'
+ '.zddc-xlsx__body{flex:1 1 auto;min-width:0;min-height:0;overflow:auto;}'
+ '.zddc-xlsx__body table{border-collapse:collapse;font-size:0.85rem;'
+ 'color:var(--text,#222);}'
+ '.zddc-xlsx__body td,.zddc-xlsx__body th{border:1px solid var(--border,#ddd);'
+ 'padding:0.2rem 0.45rem;white-space:nowrap;}';
function renderXlsx(doc, container, arrayBuffer, opts) {
opts = opts || {};
injectStyles(doc, 'zddc-xlsx-styles', XLSX_CSS);
if (!window.XLSX || typeof window.XLSX.read !== 'function') {
container.innerHTML = '<div class="preview-empty">Spreadsheet preview unavailable '
+ '(renderer not bundled in this tool).</div>';
return Promise.resolve();
}
try {
var wb = window.XLSX.read(arrayBuffer, { type: 'array' });
container.innerHTML = '';
var rootEl = doc.createElement('div');
rootEl.className = 'zddc-xlsx';
var body = doc.createElement('div');
body.className = 'zddc-xlsx__body';
function showSheet(name) {
var sheet = wb.Sheets[name];
body.innerHTML = sheet
? window.XLSX.utils.sheet_to_html(sheet, { editable: false })
: '';
}
if (wb.SheetNames.length > 1) {
var tabs = doc.createElement('div');
tabs.className = 'zddc-xlsx__tabs';
wb.SheetNames.forEach(function (name, i) {
var btn = doc.createElement('button');
btn.className = 'zddc-xlsx__tab' + (i === 0 ? ' is-active' : '');
btn.textContent = name;
btn.addEventListener('click', function () {
var all = tabs.querySelectorAll('.zddc-xlsx__tab');
for (var j = 0; j < all.length; j++) all[j].classList.remove('is-active');
btn.classList.add('is-active');
showSheet(name);
});
tabs.appendChild(btn);
});
rootEl.appendChild(tabs);
}
rootEl.appendChild(body);
container.appendChild(rootEl);
showSheet(wb.SheetNames[0]);
} catch (err) {
container.innerHTML = '<div class="preview-empty" style="color:var(--danger,#c00)">'
+ 'Failed to render spreadsheet: ' + escapeHtml(err.message || err) + '</div>';
}
return Promise.resolve();
}
// ── Public API ───────────────────────────────────────────────────────────
if (!root.zddc) root.zddc = {};
@ -5046,8 +4933,6 @@ X.B(E,Y);return E}return J}())
loadLibrary: loadLibrary,
renderTiff: renderTiff,
renderZipListing: renderZipListing,
renderDocx: renderDocx,
renderXlsx: renderXlsx,
formatSize: formatSize,
formatDate: formatDate
};

View file

@ -1536,7 +1536,7 @@ body {
</svg>
<div class="header-title-group">
<span class="app-header__title">ZDDC</span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b</span></span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-02 19:01:22 · af07fa4</span></span>
</div>
</div>
<div class="header-right">

View file

@ -2635,7 +2635,7 @@ dialog.modal--narrow {
</svg>
<div class="header-title-group">
<span class="app-header__title">ZDDC Transmittal</span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b</span></span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-02 19:01:22 · af07fa4</span></span>
</div>
<span id="no-js-notice" class="text-gray-400 text-xs italic">JavaScript not available</span>
<!-- Publish split-button (Transmittal-specific primary action;
@ -5452,23 +5452,15 @@ X.B(E,Y);return E}return J}())
* Cross-tool helpers for previewing file types that need a decoder:
* - TIFF (UTIF.js) — multi-page, browser-PDF-viewer-style toolbar
* - ZIP listing (JSZip) — sortable file-list view
* - DOCX (docx-preview) — Word-styled pages in a scroll container
* - XLSX/XLS (SheetJS) — sheet-to-HTML table with a sheet tab bar
*
* Renderers operate on any document (parent window or popup window), so the
* same code works for tools whose preview opens in a popup (classifier,
* archive, transmittal) and tools that render inline (browse).
*
* The DOCX/XLSX renderers expect their vendor lib bundled by the calling
* tool's build.sh (docx-preview.min.js → window.docx, xlsx.full.min.js →
* window.XLSX); they degrade to a friendly message if it isn't present.
*
* Public API on window.zddc.preview:
* loadLibrary(url) → Promise<void>
* renderTiff(doc, container, arrayBuffer, opts) → Promise<void>
* renderZipListing(doc, container, arrayBuffer, opts) → Promise<void>
* renderDocx(doc, container, arrayBuffer, opts) → Promise<void>
* renderXlsx(doc, container, arrayBuffer, opts) → Promise<void>
* TIFF_EXTENSIONS, IMAGE_EXTENSIONS, TEXT_EXTENSIONS, OFFICE_EXTENSIONS
* isTiff(ext), isImage(ext), isText(ext), isZip(ext), isOffice(ext)
*
@ -5981,111 +5973,6 @@ X.B(E,Y);return E}return J}())
});
}
// ── DOCX (docx-preview) ────────────────────────────────────────────────
//
// docx-preview renders Word-styled pages with an intrinsic page width, so
// we wrap it in a scroll container: a wide document scrolls WITHIN the
// preview pane rather than pushing the page wider. docx-preview is bundled
// by the tools that opt in (each build.sh concatenates
// shared/vendor/docx-preview.min.js → window.docx).
var DOCX_CSS =
'.zddc-docx{height:100%;min-width:0;overflow:auto;'
+ 'background:var(--bg-secondary,#eee);padding:1rem;box-sizing:border-box;}'
+ '.zddc-docx .docx-wrapper{background:transparent;padding:0;}';
function renderDocx(doc, container, arrayBuffer, opts) {
opts = opts || {};
injectStyles(doc, 'zddc-docx-styles', DOCX_CSS);
if (!window.docx || typeof window.docx.renderAsync !== 'function') {
container.innerHTML = '<div class="preview-empty">DOCX preview unavailable '
+ '(renderer not bundled in this tool).</div>';
return Promise.resolve();
}
container.innerHTML = '';
var scroll = doc.createElement('div');
scroll.className = 'zddc-docx';
container.appendChild(scroll);
return Promise.resolve(
window.docx.renderAsync(arrayBuffer, scroll, null, { inWrapper: true })
).catch(function (err) {
container.innerHTML = '<div class="preview-empty" style="color:var(--danger,#c00)">'
+ 'Failed to render DOCX: ' + escapeHtml(err.message || err) + '</div>';
});
}
// ── XLSX / XLS (SheetJS) ───────────────────────────────────────────────
//
// Reads the workbook and renders the active sheet to an HTML table; a tab
// bar switches sheets when there's more than one. SheetJS is bundled by
// the tools that opt in (shared/vendor/xlsx.full.min.js → window.XLSX).
var XLSX_CSS =
'.zddc-xlsx{display:flex;flex-direction:column;height:100%;min-width:0;'
+ 'min-height:0;overflow:hidden;}'
+ '.zddc-xlsx__tabs{display:flex;flex-wrap:wrap;gap:0.25rem;padding:0.4rem;'
+ 'border-bottom:1px solid var(--border,#ccc);flex:0 0 auto;}'
+ '.zddc-xlsx__tab{padding:0.2rem 0.6rem;border:1px solid var(--border,#ccc);'
+ 'border-radius:4px;background:var(--bg,#fff);color:var(--text,#222);'
+ 'cursor:pointer;font-size:0.85rem;}'
+ '.zddc-xlsx__tab.is-active{background:var(--primary,#2563eb);color:#fff;'
+ 'border-color:var(--primary,#2563eb);}'
+ '.zddc-xlsx__body{flex:1 1 auto;min-width:0;min-height:0;overflow:auto;}'
+ '.zddc-xlsx__body table{border-collapse:collapse;font-size:0.85rem;'
+ 'color:var(--text,#222);}'
+ '.zddc-xlsx__body td,.zddc-xlsx__body th{border:1px solid var(--border,#ddd);'
+ 'padding:0.2rem 0.45rem;white-space:nowrap;}';
function renderXlsx(doc, container, arrayBuffer, opts) {
opts = opts || {};
injectStyles(doc, 'zddc-xlsx-styles', XLSX_CSS);
if (!window.XLSX || typeof window.XLSX.read !== 'function') {
container.innerHTML = '<div class="preview-empty">Spreadsheet preview unavailable '
+ '(renderer not bundled in this tool).</div>';
return Promise.resolve();
}
try {
var wb = window.XLSX.read(arrayBuffer, { type: 'array' });
container.innerHTML = '';
var rootEl = doc.createElement('div');
rootEl.className = 'zddc-xlsx';
var body = doc.createElement('div');
body.className = 'zddc-xlsx__body';
function showSheet(name) {
var sheet = wb.Sheets[name];
body.innerHTML = sheet
? window.XLSX.utils.sheet_to_html(sheet, { editable: false })
: '';
}
if (wb.SheetNames.length > 1) {
var tabs = doc.createElement('div');
tabs.className = 'zddc-xlsx__tabs';
wb.SheetNames.forEach(function (name, i) {
var btn = doc.createElement('button');
btn.className = 'zddc-xlsx__tab' + (i === 0 ? ' is-active' : '');
btn.textContent = name;
btn.addEventListener('click', function () {
var all = tabs.querySelectorAll('.zddc-xlsx__tab');
for (var j = 0; j < all.length; j++) all[j].classList.remove('is-active');
btn.classList.add('is-active');
showSheet(name);
});
tabs.appendChild(btn);
});
rootEl.appendChild(tabs);
}
rootEl.appendChild(body);
container.appendChild(rootEl);
showSheet(wb.SheetNames[0]);
} catch (err) {
container.innerHTML = '<div class="preview-empty" style="color:var(--danger,#c00)">'
+ 'Failed to render spreadsheet: ' + escapeHtml(err.message || err) + '</div>';
}
return Promise.resolve();
}
// ── Public API ───────────────────────────────────────────────────────────
if (!root.zddc) root.zddc = {};
@ -6102,8 +5989,6 @@ X.B(E,Y);return E}return J}())
loadLibrary: loadLibrary,
renderTiff: renderTiff,
renderZipListing: renderZipListing,
renderDocx: renderDocx,
renderXlsx: renderXlsx,
formatSize: formatSize,
formatDate: formatDate
};

View file

@ -1,8 +1,8 @@
# Generated by build.sh — do not edit. One <app>=<build label> per line.
archive=v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b
transmittal=v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b
classifier=v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b
landing=v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b
form=v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b
tables=v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b
browse=v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b
archive=v0.0.27-beta · 2026-06-02 19:01:22 · af07fa4
transmittal=v0.0.27-beta · 2026-06-02 19:01:22 · af07fa4
classifier=v0.0.27-beta · 2026-06-02 19:01:22 · af07fa4
landing=v0.0.27-beta · 2026-06-02 19:01:22 · af07fa4
form=v0.0.27-beta · 2026-06-02 19:01:22 · af07fa4
tables=v0.0.27-beta · 2026-06-02 19:01:23 · af07fa4
browse=v0.0.27-beta · 2026-06-02 19:01:23 · af07fa4

View file

@ -1534,7 +1534,7 @@ body.is-elevated::after {
</svg>
<div class="header-title-group">
<span class="app-header__title" id="table-title">ZDDC Table</span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b</span></span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-02 19:01:23 · af07fa4</span></span>
</div>
</div>
<div class="header-right">