Compare commits
2 commits
c05fc376f2
...
8875d490f5
| Author | SHA1 | Date | |
|---|---|---|---|
| 8875d490f5 | |||
| 3e8737b7c9 |
12 changed files with 698 additions and 21 deletions
|
|
@ -42,6 +42,8 @@ concat_files \
|
||||||
# without an external HTTP dependency.
|
# without an external HTTP dependency.
|
||||||
concat_files \
|
concat_files \
|
||||||
"../shared/vendor/jszip.min.js" \
|
"../shared/vendor/jszip.min.js" \
|
||||||
|
"../shared/vendor/docx-preview.min.js" \
|
||||||
|
"../shared/vendor/xlsx.full.min.js" \
|
||||||
"../shared/vendor/utif.min.js" \
|
"../shared/vendor/utif.min.js" \
|
||||||
"../shared/vendor/js-yaml.min.js" \
|
"../shared/vendor/js-yaml.min.js" \
|
||||||
"../shared/vendor/codemirror-yaml.min.js" \
|
"../shared/vendor/codemirror-yaml.min.js" \
|
||||||
|
|
|
||||||
|
|
@ -589,7 +589,14 @@ body {
|
||||||
.md-shell {
|
.md-shell {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 1fr;
|
grid-template-rows: 1fr;
|
||||||
grid-template-columns: 280px 1fr; /* JS overrides on resize */
|
/* 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-areas: "sidebar content";
|
grid-template-areas: "sidebar content";
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|
@ -606,6 +613,7 @@ body {
|
||||||
grid-area: sidebar;
|
grid-area: sidebar;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-right: 1px solid var(--border);
|
border-right: 1px solid var(--border);
|
||||||
|
|
|
||||||
|
|
@ -354,7 +354,7 @@
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
var shell = document.createElement('div');
|
var shell = document.createElement('div');
|
||||||
shell.className = 'md-shell';
|
shell.className = 'md-shell';
|
||||||
shell.style.gridTemplateColumns = lastSidebarWidth + 'px 1fr';
|
shell.style.gridTemplateColumns = 'minmax(0, ' + lastSidebarWidth + 'px) minmax(0, 1fr)';
|
||||||
container.appendChild(shell);
|
container.appendChild(shell);
|
||||||
|
|
||||||
// ── Sidebar (col 1): front matter (top) + TOC (bottom) ──────────────
|
// ── Sidebar (col 1): front matter (top) + TOC (bottom) ──────────────
|
||||||
|
|
@ -595,7 +595,7 @@
|
||||||
var w = startW + dx;
|
var w = startW + dx;
|
||||||
w = Math.max(SIDEBAR_MIN_WIDTH, Math.min(SIDEBAR_MAX_WIDTH, w));
|
w = Math.max(SIDEBAR_MIN_WIDTH, Math.min(SIDEBAR_MAX_WIDTH, w));
|
||||||
lastSidebarWidth = w;
|
lastSidebarWidth = w;
|
||||||
shell.style.gridTemplateColumns = w + 'px 1fr';
|
shell.style.gridTemplateColumns = 'minmax(0, ' + w + 'px) minmax(0, 1fr)';
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
function onUp() {
|
function onUp() {
|
||||||
|
|
@ -620,7 +620,7 @@
|
||||||
var w = Math.max(SIDEBAR_MIN_WIDTH,
|
var w = Math.max(SIDEBAR_MIN_WIDTH,
|
||||||
Math.min(SIDEBAR_MAX_WIDTH, lastSidebarWidth + step));
|
Math.min(SIDEBAR_MAX_WIDTH, lastSidebarWidth + step));
|
||||||
lastSidebarWidth = w;
|
lastSidebarWidth = w;
|
||||||
shell.style.gridTemplateColumns = w + 'px 1fr';
|
shell.style.gridTemplateColumns = 'minmax(0, ' + w + 'px) minmax(0, 1fr)';
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,24 @@
|
||||||
return;
|
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)) {
|
if (preview && preview.isText(ext)) {
|
||||||
try {
|
try {
|
||||||
var txtBuf = await getArrayBuffer(node);
|
var txtBuf = await getArrayBuffer(node);
|
||||||
|
|
@ -273,6 +291,13 @@
|
||||||
} else if (preview && preview.isZip(ext)) {
|
} else if (preview && preview.isZip(ext)) {
|
||||||
var zb = await getArrayBuffer(node);
|
var zb = await getArrayBuffer(node);
|
||||||
await preview.renderZipListing(win.document, c, zb, { fileName: node.name });
|
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)) {
|
} else if (preview && preview.isText(ext)) {
|
||||||
var txb = await getArrayBuffer(node);
|
var txb = await getArrayBuffer(node);
|
||||||
var text = new TextDecoder('utf-8', { fatal: false }).decode(txb);
|
var text = new TextDecoder('utf-8', { fatal: false }).decode(txb);
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,23 @@
|
||||||
* Cross-tool helpers for previewing file types that need a decoder:
|
* Cross-tool helpers for previewing file types that need a decoder:
|
||||||
* - TIFF (UTIF.js) — multi-page, browser-PDF-viewer-style toolbar
|
* - TIFF (UTIF.js) — multi-page, browser-PDF-viewer-style toolbar
|
||||||
* - ZIP listing (JSZip) — sortable file-list view
|
* - 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
|
* Renderers operate on any document (parent window or popup window), so the
|
||||||
* same code works for tools whose preview opens in a popup (classifier,
|
* same code works for tools whose preview opens in a popup (classifier,
|
||||||
* archive, transmittal) and tools that render inline (browse).
|
* 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:
|
* Public API on window.zddc.preview:
|
||||||
* loadLibrary(url) → Promise<void>
|
* loadLibrary(url) → Promise<void>
|
||||||
* renderTiff(doc, container, arrayBuffer, opts) → Promise<void>
|
* renderTiff(doc, container, arrayBuffer, opts) → Promise<void>
|
||||||
* renderZipListing(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
|
* TIFF_EXTENSIONS, IMAGE_EXTENSIONS, TEXT_EXTENSIONS, OFFICE_EXTENSIONS
|
||||||
* isTiff(ext), isImage(ext), isText(ext), isZip(ext), isOffice(ext)
|
* isTiff(ext), isImage(ext), isText(ext), isZip(ext), isOffice(ext)
|
||||||
*
|
*
|
||||||
|
|
@ -525,6 +533,111 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── 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 ───────────────────────────────────────────────────────────
|
// ── Public API ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
if (!root.zddc) root.zddc = {};
|
if (!root.zddc) root.zddc = {};
|
||||||
|
|
@ -541,6 +654,8 @@
|
||||||
loadLibrary: loadLibrary,
|
loadLibrary: loadLibrary,
|
||||||
renderTiff: renderTiff,
|
renderTiff: renderTiff,
|
||||||
renderZipListing: renderZipListing,
|
renderZipListing: renderZipListing,
|
||||||
|
renderDocx: renderDocx,
|
||||||
|
renderXlsx: renderXlsx,
|
||||||
formatSize: formatSize,
|
formatSize: formatSize,
|
||||||
formatDate: formatDate
|
formatDate: formatDate
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2582,7 +2582,7 @@ td[data-field="trackingNumber"] {
|
||||||
</svg>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title">ZDDC Archive</span>
|
<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-02 19:01:22 · af07fa4</span></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>
|
||||||
</div>
|
</div>
|
||||||
<button id="addDirectoryBtn" class="btn btn-primary">Use Local Directory</button>
|
<button id="addDirectoryBtn" class="btn btn-primary">Use Local Directory</button>
|
||||||
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh Data">⟳</button>
|
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh Data">⟳</button>
|
||||||
|
|
@ -5125,15 +5125,23 @@ X.B(E,Y);return E}return J}())
|
||||||
* Cross-tool helpers for previewing file types that need a decoder:
|
* Cross-tool helpers for previewing file types that need a decoder:
|
||||||
* - TIFF (UTIF.js) — multi-page, browser-PDF-viewer-style toolbar
|
* - TIFF (UTIF.js) — multi-page, browser-PDF-viewer-style toolbar
|
||||||
* - ZIP listing (JSZip) — sortable file-list view
|
* - 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
|
* Renderers operate on any document (parent window or popup window), so the
|
||||||
* same code works for tools whose preview opens in a popup (classifier,
|
* same code works for tools whose preview opens in a popup (classifier,
|
||||||
* archive, transmittal) and tools that render inline (browse).
|
* 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:
|
* Public API on window.zddc.preview:
|
||||||
* loadLibrary(url) → Promise<void>
|
* loadLibrary(url) → Promise<void>
|
||||||
* renderTiff(doc, container, arrayBuffer, opts) → Promise<void>
|
* renderTiff(doc, container, arrayBuffer, opts) → Promise<void>
|
||||||
* renderZipListing(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
|
* TIFF_EXTENSIONS, IMAGE_EXTENSIONS, TEXT_EXTENSIONS, OFFICE_EXTENSIONS
|
||||||
* isTiff(ext), isImage(ext), isText(ext), isZip(ext), isOffice(ext)
|
* isTiff(ext), isImage(ext), isText(ext), isZip(ext), isOffice(ext)
|
||||||
*
|
*
|
||||||
|
|
@ -5646,6 +5654,111 @@ 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 ───────────────────────────────────────────────────────────
|
// ── Public API ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
if (!root.zddc) root.zddc = {};
|
if (!root.zddc) root.zddc = {};
|
||||||
|
|
@ -5662,6 +5775,8 @@ X.B(E,Y);return E}return J}())
|
||||||
loadLibrary: loadLibrary,
|
loadLibrary: loadLibrary,
|
||||||
renderTiff: renderTiff,
|
renderTiff: renderTiff,
|
||||||
renderZipListing: renderZipListing,
|
renderZipListing: renderZipListing,
|
||||||
|
renderDocx: renderDocx,
|
||||||
|
renderXlsx: renderXlsx,
|
||||||
formatSize: formatSize,
|
formatSize: formatSize,
|
||||||
formatDate: formatDate
|
formatDate: formatDate
|
||||||
};
|
};
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1793,7 +1793,7 @@ body.is-elevated::after {
|
||||||
</svg>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title">ZDDC Classifier</span>
|
<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-02 19:01:22 · af07fa4</span></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>
|
||||||
</div>
|
</div>
|
||||||
<button id="addDirectoryBtn" class="btn btn-primary">Use Local Directory</button>
|
<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>
|
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh and rescan directory" aria-label="Refresh" style="font-size:1.1rem;">⟳</button>
|
||||||
|
|
@ -4396,15 +4396,23 @@ X.B(E,Y);return E}return J}())
|
||||||
* Cross-tool helpers for previewing file types that need a decoder:
|
* Cross-tool helpers for previewing file types that need a decoder:
|
||||||
* - TIFF (UTIF.js) — multi-page, browser-PDF-viewer-style toolbar
|
* - TIFF (UTIF.js) — multi-page, browser-PDF-viewer-style toolbar
|
||||||
* - ZIP listing (JSZip) — sortable file-list view
|
* - 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
|
* Renderers operate on any document (parent window or popup window), so the
|
||||||
* same code works for tools whose preview opens in a popup (classifier,
|
* same code works for tools whose preview opens in a popup (classifier,
|
||||||
* archive, transmittal) and tools that render inline (browse).
|
* 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:
|
* Public API on window.zddc.preview:
|
||||||
* loadLibrary(url) → Promise<void>
|
* loadLibrary(url) → Promise<void>
|
||||||
* renderTiff(doc, container, arrayBuffer, opts) → Promise<void>
|
* renderTiff(doc, container, arrayBuffer, opts) → Promise<void>
|
||||||
* renderZipListing(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
|
* TIFF_EXTENSIONS, IMAGE_EXTENSIONS, TEXT_EXTENSIONS, OFFICE_EXTENSIONS
|
||||||
* isTiff(ext), isImage(ext), isText(ext), isZip(ext), isOffice(ext)
|
* isTiff(ext), isImage(ext), isText(ext), isZip(ext), isOffice(ext)
|
||||||
*
|
*
|
||||||
|
|
@ -4917,6 +4925,111 @@ 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 ───────────────────────────────────────────────────────────
|
// ── Public API ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
if (!root.zddc) root.zddc = {};
|
if (!root.zddc) root.zddc = {};
|
||||||
|
|
@ -4933,6 +5046,8 @@ X.B(E,Y);return E}return J}())
|
||||||
loadLibrary: loadLibrary,
|
loadLibrary: loadLibrary,
|
||||||
renderTiff: renderTiff,
|
renderTiff: renderTiff,
|
||||||
renderZipListing: renderZipListing,
|
renderZipListing: renderZipListing,
|
||||||
|
renderDocx: renderDocx,
|
||||||
|
renderXlsx: renderXlsx,
|
||||||
formatSize: formatSize,
|
formatSize: formatSize,
|
||||||
formatDate: formatDate
|
formatDate: formatDate
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1536,7 +1536,7 @@ body {
|
||||||
</svg>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title">ZDDC</span>
|
<span class="app-header__title">ZDDC</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>
|
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b</span></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
|
|
|
||||||
|
|
@ -2635,7 +2635,7 @@ dialog.modal--narrow {
|
||||||
</svg>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title">ZDDC Transmittal</span>
|
<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-02 19:01:22 · af07fa4</span></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>
|
||||||
</div>
|
</div>
|
||||||
<span id="no-js-notice" class="text-gray-400 text-xs italic">JavaScript not available</span>
|
<span id="no-js-notice" class="text-gray-400 text-xs italic">JavaScript not available</span>
|
||||||
<!-- Publish split-button (Transmittal-specific primary action;
|
<!-- Publish split-button (Transmittal-specific primary action;
|
||||||
|
|
@ -5452,15 +5452,23 @@ X.B(E,Y);return E}return J}())
|
||||||
* Cross-tool helpers for previewing file types that need a decoder:
|
* Cross-tool helpers for previewing file types that need a decoder:
|
||||||
* - TIFF (UTIF.js) — multi-page, browser-PDF-viewer-style toolbar
|
* - TIFF (UTIF.js) — multi-page, browser-PDF-viewer-style toolbar
|
||||||
* - ZIP listing (JSZip) — sortable file-list view
|
* - 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
|
* Renderers operate on any document (parent window or popup window), so the
|
||||||
* same code works for tools whose preview opens in a popup (classifier,
|
* same code works for tools whose preview opens in a popup (classifier,
|
||||||
* archive, transmittal) and tools that render inline (browse).
|
* 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:
|
* Public API on window.zddc.preview:
|
||||||
* loadLibrary(url) → Promise<void>
|
* loadLibrary(url) → Promise<void>
|
||||||
* renderTiff(doc, container, arrayBuffer, opts) → Promise<void>
|
* renderTiff(doc, container, arrayBuffer, opts) → Promise<void>
|
||||||
* renderZipListing(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
|
* TIFF_EXTENSIONS, IMAGE_EXTENSIONS, TEXT_EXTENSIONS, OFFICE_EXTENSIONS
|
||||||
* isTiff(ext), isImage(ext), isText(ext), isZip(ext), isOffice(ext)
|
* isTiff(ext), isImage(ext), isText(ext), isZip(ext), isOffice(ext)
|
||||||
*
|
*
|
||||||
|
|
@ -5973,6 +5981,111 @@ 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 ───────────────────────────────────────────────────────────
|
// ── Public API ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
if (!root.zddc) root.zddc = {};
|
if (!root.zddc) root.zddc = {};
|
||||||
|
|
@ -5989,6 +6102,8 @@ X.B(E,Y);return E}return J}())
|
||||||
loadLibrary: loadLibrary,
|
loadLibrary: loadLibrary,
|
||||||
renderTiff: renderTiff,
|
renderTiff: renderTiff,
|
||||||
renderZipListing: renderZipListing,
|
renderZipListing: renderZipListing,
|
||||||
|
renderDocx: renderDocx,
|
||||||
|
renderXlsx: renderXlsx,
|
||||||
formatSize: formatSize,
|
formatSize: formatSize,
|
||||||
formatDate: formatDate
|
formatDate: formatDate
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# Generated by build.sh — do not edit. One <app>=<build label> per line.
|
# Generated by build.sh — do not edit. One <app>=<build label> per line.
|
||||||
archive=v0.0.27-beta · 2026-06-02 19:01:22 · af07fa4
|
archive=v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b
|
||||||
transmittal=v0.0.27-beta · 2026-06-02 19:01:22 · af07fa4
|
transmittal=v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b
|
||||||
classifier=v0.0.27-beta · 2026-06-02 19:01:22 · af07fa4
|
classifier=v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b
|
||||||
landing=v0.0.27-beta · 2026-06-02 19:01:22 · af07fa4
|
landing=v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b
|
||||||
form=v0.0.27-beta · 2026-06-02 19:01:22 · af07fa4
|
form=v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b
|
||||||
tables=v0.0.27-beta · 2026-06-02 19:01:23 · af07fa4
|
tables=v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b
|
||||||
browse=v0.0.27-beta · 2026-06-02 19:01:23 · af07fa4
|
browse=v0.0.27-beta · 2026-06-03 13:55:32 · 3e8737b
|
||||||
|
|
|
||||||
|
|
@ -1534,7 +1534,7 @@ body.is-elevated::after {
|
||||||
</svg>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title" id="table-title">ZDDC Table</span>
|
<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-02 19:01:23 · af07fa4</span></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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue