From ca1452dde197388f9024d8369c5f43c8e57f6e98 Mon Sep 17 00:00:00 2001 From: ZDDC Date: Tue, 16 Jun 2026 08:52:30 -0500 Subject: [PATCH] chore(embedded): cut v0.0.27-beta --- zddc/internal/apps/embedded/archive.html | 2 +- zddc/internal/apps/embedded/browse.html | 2 +- zddc/internal/apps/embedded/classifier.html | 113 +++++++++++++++++-- zddc/internal/apps/embedded/index.html | 2 +- zddc/internal/apps/embedded/transmittal.html | 2 +- zddc/internal/apps/embedded/versions.txt | 14 +-- zddc/internal/handler/tables.html | 2 +- 7 files changed, 117 insertions(+), 20 deletions(-) diff --git a/zddc/internal/apps/embedded/archive.html b/zddc/internal/apps/embedded/archive.html index 8803098..9bca4f7 100644 --- a/zddc/internal/apps/embedded/archive.html +++ b/zddc/internal/apps/embedded/archive.html @@ -2717,7 +2717,7 @@ td[data-field="trackingNumber"] {
ZDDC Archive - v0.0.27-beta · 2026-06-16 13:31:21 · be5b396 + v0.0.27-beta · 2026-06-16 13:52:23 · 1bb5d1a
diff --git a/zddc/internal/apps/embedded/browse.html b/zddc/internal/apps/embedded/browse.html index e13f3da..12b24e3 100644 --- a/zddc/internal/apps/embedded/browse.html +++ b/zddc/internal/apps/embedded/browse.html @@ -2824,7 +2824,7 @@ li.CodeMirror-hint-active {
ZDDC Browse - v0.0.27-beta · 2026-06-16 13:31:22 · be5b396 + v0.0.27-beta · 2026-06-16 13:52:24 · 1bb5d1a
diff --git a/zddc/internal/apps/embedded/classifier.html b/zddc/internal/apps/embedded/classifier.html index 2acb434..9018797 100644 --- a/zddc/internal/apps/embedded/classifier.html +++ b/zddc/internal/apps/embedded/classifier.html @@ -1331,10 +1331,13 @@ body.is-elevated::after { font-size: 0.8rem; } -/* Resize Handle */ +/* Resize Handle — sits just to the RIGHT of the split (past the 1px + border-right), overhanging the right pane's edge, so its grab area + hover + highlight never cover the folder tree's vertical scrollbar (which lives on the + left pane's right edge). */ .resize-handle { position: absolute; - right: 0; + right: -6px; top: 0; bottom: 0; width: 5px; @@ -1385,6 +1388,7 @@ body.is-elevated::after { } .tree-toolbar__label { color: var(--text-muted); font-size: 0.8rem; font-weight: 600; } .classify-filters .filter-count { color: var(--text-muted); font-size: 0.85em; } +.export-list-btn { margin-left: auto; } /* push the export action to the toolbar's right edge */ /* Live filter box above a file tree. */ .tree-filter { @@ -2474,7 +2478,7 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o
ZDDC Classifier - v0.0.27-beta · 2026-06-16 13:31:21 · be5b396 + v0.0.27-beta · 2026-06-16 13:52:23 · 1bb5d1a
@@ -2533,6 +2537,8 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o Empty + @@ -6049,6 +6055,7 @@ X.B(E,Y);return E}return J}()) showAssignedCheckbox: document.getElementById('showAssignedCheckbox'), showExcludedCheckbox: document.getElementById('showExcludedCheckbox'), showEmptyCheckbox: document.getElementById('showEmptyCheckbox'), + exportListBtn: document.getElementById('exportListBtn'), exportDatasetBtn: document.getElementById('exportDatasetBtn'), importDatasetBtn: document.getElementById('importDatasetBtn'), importDatasetInput: document.getElementById('importDatasetInput'), @@ -6266,6 +6273,11 @@ X.B(E,Y);return E}return J}()) [app.dom.showUnassignedCheckbox, app.dom.showPartialCheckbox, app.dom.showAssignedCheckbox, app.dom.showExcludedCheckbox, app.dom.showEmptyCheckbox] .forEach(function (cb) { if (cb) cb.addEventListener('change', pushClassifyFilters); }); + // Export the filtered file list (path + file TSV) for the Excel round-trip. + if (app.dom.exportListBtn) app.dom.exportListBtn.addEventListener('click', function () { + if (app.modules.tree && app.modules.tree.exportFilteredList) app.modules.tree.exportFilteredList(); + }); + // Collapse tree button app.dom.collapseTreeBtn.addEventListener('click', handleCollapseTree); @@ -8061,8 +8073,17 @@ X.B(E,Y);return E}return J}()) return addTrackingPath(null, parseFolderLevels(tn + '_' + rev)); } function assignFromRow(keys, row) { + if (!keys || !keys.length) return; var leaf = leafForRow(row); - if (!leaf || !keys || !keys.length) return; + if (!leaf) { + // No tracking number on the row yet — still CLAIM these files for it + // (e.g. a pasted full path on a row whose tracking is still blank). The + // binding is recorded in row.bound; when a tracking/rev later lands, + // restampRow places the claimed files onto the new leaf. + keys.forEach(function (k) { row.placed[k] = true; (row.bound || (row.bound = Object.create(null)))[k] = true; }); + notify(); + return; + } place(keys, leaf, 'tracking'); keys.forEach(function (k) { row.placed[k] = true; @@ -8096,12 +8117,17 @@ X.B(E,Y);return E}return J}()) if (!keys.length) return; var leaf = leafForRow(row); if (!leaf) return; - var old = Object.create(null); + var old = Object.create(null), toPlace = []; keys.forEach(function (k) { var a = state.assignments[k]; if (a && a.trackingNodeId) { if (a.trackingNodeId !== leaf) old[a.trackingNodeId] = true; a.trackingNodeId = leaf; } + else if (row.bound && row.bound[k]) toPlace.push(k); // claimed by path, not yet placed → place now else delete row.placed[k]; // user un-placed it elsewhere — don't resurrect }); + if (toPlace.length) { + place(toPlace, leaf, 'tracking'); + if (row.title && row.title.trim()) toPlace.forEach(function (k) { var aa = state.assignments[k]; if (aa && !aa.titleOverride) setTitleOverride(k, row.title); }); + } clearHashConflicts(); Object.keys(old).forEach(pruneEmptyTrackingChain); notify(); @@ -8166,6 +8192,7 @@ X.B(E,Y);return E}return J}()) }); return { rows: rows, skipped: skipped }; } + function baseName(s) { return String(s == null ? '' : s).split(/[\/\\]/).pop(); } function normTok(s) { return String(s == null ? '' : s).toUpperCase().replace(/[^A-Z0-9]/g, ''); } function dropExt(s) { return String(s == null ? '' : s).replace(/\.[^.\/\\]+$/, ''); } function nameKey(s) { return dropExt(s).toLowerCase().replace(/[^a-z0-9]+/g, ''); } @@ -8200,9 +8227,15 @@ X.B(E,Y);return E}return J}()) var out = []; (files || []).forEach(function (f) { var full = zddc.joinExtension(f.originalFilename, f.extension); + var key = srcKeyForFile(f); var best = null; named.forEach(function (r) { - var s = nameScore(r.currentName, full); + // A pasted FULL PATH equal to this file's key → an exact, direct + // bind (the strongest signal — wins over any name score). + if (r.currentName === key) { best = { row: r, confidence: 1, via: 'path' }; return; } + // Otherwise score on the name; a path that didn't match exactly is + // reduced to its basename so the fuzzy name match still applies. + var s = nameScore(baseName(r.currentName), full); if (s > 0 && (!best || s > best.confidence)) best = { row: r, confidence: s, via: 'name' }; }); if (!best) { // fallback: tracking number in the filename @@ -10335,6 +10368,68 @@ X.B(E,Y);return E}return J}()) }); } + // ── Export the filtered file list to TSV (path + file) ────────────────── + // Every file passing the CURRENT tree filters (name search + the Show + // toggles), across the WHOLE tree — expand/collapse is display-only, so a + // collapsed folder's files are included just the same. `path` is the file's + // root-relative key (paste it into "Current name" to bind that exact file); + // `file` is the bare filename (paste it for a name to match/drop later). + function filteredFileObjects() { + var c = window.app.modules.classify; + var vis = anyFilter() ? computeVisible() : null; + var out = []; + (function walk(nodes) { + (nodes || []).forEach(function (n) { + (n.files || []).forEach(function (f) { + var show = vis ? !!vis.files[c.srcKeyForFile(f)] : classifyAllows(f); + if (show) out.push(f); + }); + walk(n.children); + }); + })(window.app.folderTree || []); + return out; + } + function buildExportTsv() { + var c = window.app.modules.classify; + var files = filteredFileObjects().slice().sort(function (a, b) { + return cmpName(c.srcKeyForFile(a), c.srcKeyForFile(b)); + }); + var lines = ['path\tfile']; + files.forEach(function (f) { + lines.push(c.srcKeyForFile(f) + '\t' + window.zddc.joinExtension(f.originalFilename, f.extension)); + }); + return { tsv: lines.join('\n'), count: files.length }; + } + function exportFilteredList() { + var built = buildExportTsv(); + if (!built.count) { window.zddc.toast('No files to export — nothing passes the current filters.', 'info'); return; } + copyOrDownload(built.tsv, built.count); + } + function copyOrDownload(text, count) { + function ok() { window.zddc.toast('Copied ' + count + ' file' + (count === 1 ? '' : 's') + ' (path + file) — paste into Excel.', 'success'); } + function download() { + try { + var blob = new Blob([text], { type: 'text/tab-separated-values' }); + var url = URL.createObjectURL(blob); + var a = document.createElement('a'); a.href = url; a.download = 'classifier-files.tsv'; + document.body.appendChild(a); a.click(); a.remove(); + setTimeout(function () { URL.revokeObjectURL(url); }, 10000); + window.zddc.toast('Clipboard unavailable — downloaded classifier-files.tsv instead.', 'info'); + } catch (e) { window.zddc.toast('Could not copy or download the list — ' + (e.message || e), 'error'); } + } + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(text).then(ok, download); + return; + } + try { + var ta = document.createElement('textarea'); + ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0'; + document.body.appendChild(ta); ta.focus(); ta.select(); + var done = document.execCommand('copy'); ta.remove(); + done ? ok() : download(); + } catch (e) { download(); } + } + /** * Render the folder tree */ @@ -11110,7 +11205,9 @@ X.B(E,Y);return E}return J}()) selectAll, revealFile, setShowFilters, - setNameFilter + setNameFilter, + exportFilteredList, + _buildExportTsv: buildExportTsv }; })(); @@ -12019,7 +12116,7 @@ X.B(E,Y);return E}return J}()) } function openPasteDialog(prefill) { var c = C(); - var m = scratchModal('Paste rows from Excel', 'Fixed columns, tab-separated as Excel copies: Tracking number · Rev (Status) · Title · Current name. A header row is skipped. The current name is matched against your files — exact matches are assigned automatically.'); + var m = scratchModal('Paste rows from Excel', 'Fixed columns, tab-separated as Excel copies: Tracking number · Rev (Status) · Title · Current name. A header row is skipped. Current name accepts a bare filename (matched against your files — exact name matches are assigned automatically) OR a full path from “⬆ Export list” (binds that exact file directly on paste).'); var ta = document.createElement('textarea'); ta.className = 'scratch-paste__ta'; ta.rows = 6; ta.spellcheck = false; ta.placeholder = 'ACME-AR-DWG-0001\tA (IFR)\tFloor plan\tIMG_4471.pdf'; diff --git a/zddc/internal/apps/embedded/index.html b/zddc/internal/apps/embedded/index.html index 0e9a93d..cddafb9 100644 --- a/zddc/internal/apps/embedded/index.html +++ b/zddc/internal/apps/embedded/index.html @@ -1793,7 +1793,7 @@ body {
ZDDC - v0.0.27-beta · 2026-06-16 13:31:21 · be5b396 + v0.0.27-beta · 2026-06-16 13:52:23 · 1bb5d1a
diff --git a/zddc/internal/apps/embedded/transmittal.html b/zddc/internal/apps/embedded/transmittal.html index 608bc1b..21adf15 100644 --- a/zddc/internal/apps/embedded/transmittal.html +++ b/zddc/internal/apps/embedded/transmittal.html @@ -2770,7 +2770,7 @@ dialog.modal--narrow {
ZDDC Transmittal - v0.0.27-beta · 2026-06-16 13:31:21 · be5b396 + v0.0.27-beta · 2026-06-16 13:52:23 · 1bb5d1a
JavaScript not available