From 8f4e28f86acf170bc55a0a97bad4c8e4adff28ad Mon Sep 17 00:00:00 2001 From: ZDDC Date: Tue, 16 Jun 2026 08:31:28 -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 | 317 +++++++++++++------ 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 | 122 ++++++- 7 files changed, 342 insertions(+), 119 deletions(-) diff --git a/zddc/internal/apps/embedded/archive.html b/zddc/internal/apps/embedded/archive.html index f13ef68..8803098 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 04:53:19 · 2b32ace + v0.0.27-beta · 2026-06-16 13:31:21 · be5b396
diff --git a/zddc/internal/apps/embedded/browse.html b/zddc/internal/apps/embedded/browse.html index 405fd16..e13f3da 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 04:53:19 · 2b32ace + v0.0.27-beta · 2026-06-16 13:31:22 · be5b396
diff --git a/zddc/internal/apps/embedded/classifier.html b/zddc/internal/apps/embedded/classifier.html index 4fd0992..2acb434 100644 --- a/zddc/internal/apps/embedded/classifier.html +++ b/zddc/internal/apps/embedded/classifier.html @@ -1183,6 +1183,16 @@ body.is-elevated::after { color: var(--text-muted); font-size: 0.68rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; } .seltable__table thead tr.seltable__filters th { top: 1.55rem; padding: 0.15rem 0.35rem; } +/* Sortable headers + multi-sort indicator. The title th is position:sticky + (a positioning context) so the drag-resizer can absolutely anchor to its edge. */ +.seltable__th--sortable { cursor: pointer; } +.seltable__th--sortable:hover { color: var(--text); } +.seltable__sortind { color: var(--primary); font-size: 0.7em; font-weight: 700; } +.seltable__resizer { + position: absolute; top: 0; right: 0; bottom: 0; width: 6px; + cursor: col-resize; user-select: none; touch-action: none; +} +.seltable__resizer:hover { background: var(--primary); opacity: 0.4; } .seltable__colfilter { width: 100%; min-width: 2rem; box-sizing: border-box; padding: 0.15rem 0.35rem; border: 1px solid var(--border); border-radius: var(--radius); @@ -1403,11 +1413,12 @@ body.is-elevated::after { .folder-item { display: flex; align-items: flex-start; /* toggle/icon sit on the name line; count drops below */ - padding: 0.5rem; + padding: 0.1rem 0.5rem; /* tight: the name + count are already stacked */ cursor: pointer; border-radius: var(--radius); user-select: none; transition: background-color 0.15s; + line-height: 1.25; } .folder-item:hover { @@ -1895,7 +1906,8 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o .dir-picker__err { color: var(--danger); font-size: 0.78rem; margin-left: 1.1rem; } /* ── By-tracking merged-cell table ──────────────────────────────────────── */ -#trackingTree { padding: 0; } /* table reaches the edges; cells carry padding */ +#trackingTree { padding: 0; min-height: 0; } /* seltable owns its own scroll + padding */ +#trackingTree .seltable { height: 100%; } .ttable { border-collapse: separate; border-spacing: 0; width: 100%; font-size: 0.82rem; } .ttable th, .ttable td { border-right: 1px solid var(--border); @@ -1950,10 +1962,8 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o .tfile__badge--ok { color: var(--success, #16a34a); } .tfile__badge--err { color: var(--danger); } -/* ── By-tracking flat editable grid (one row per file) ──────────────────── */ -.ttable--grid { width: auto; } -.ttable--grid td.tg-td { padding: 0.1rem 0.35rem; vertical-align: middle; } -.ttable--grid th.tg-th { white-space: nowrap; } /* .column-resizer (spreadsheet.css) sits in the sticky th */ +/* ── By-tracking flat editable grid (one row per file) — now on seltable, so + the cell classes (.tg-*) ride seltable's th/td; seltable owns layout. ── */ .tg-input { width: 100%; min-width: 4rem; box-sizing: border-box; padding: 0.12rem 0.3rem; border: 1px solid transparent; border-radius: var(--radius); @@ -1967,8 +1977,7 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o .tg-orig__link:hover { text-decoration: underline; } .tg-status, .tg-x { text-align: center; } .tg-x__btn { opacity: 0.5; } -.tg-row:hover .tg-x__btn { opacity: 1; } -.tg-row--err .tg-status { color: var(--danger); } +.seltable__row:hover .tg-x__btn { opacity: 1; } .tg-drop-hover { outline: 2px dashed var(--primary); outline-offset: -3px; background: var(--primary-light); } /* "Columns ▾" chooser menu */ @@ -2465,7 +2474,7 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o
ZDDC Classifier - v0.0.27-beta · 2026-06-16 04:53:19 · 2b32ace + v0.0.27-beta · 2026-06-16 13:31:21 · be5b396
@@ -8833,20 +8842,26 @@ X.B(E,Y);return E}return J}()) })(); /** - * ZDDC — shared selectable + autofilter table (used by the classifier catalog - * and the tables tool's "Add from archive"). + * ZDDC — shared selectable + autofilter table (used by the classifier worklist + + * By-tracking grid, and the tables tool's "Add from archive"). * - * A flat table with PER-COLUMN autofilters (one input per column, AND-combined, - * each an AND of space-separated terms) plus an optional programmatic global - * filter, and powerful selection for building complex sets quickly: + * A flat table with PER-COLUMN autofilters (one input per column, AND-combined), + * MULTI-COLUMN SORT (click a header to sort; shift/ctrl-click adds a secondary + * key), RESIZABLE columns (drag the header edge), an optional programmatic global + * filter, and powerful selection: * click replace selection + set anchor * ctrl/cmd-click toggle one row * shift-click range from the anchor (replaces the selection) * ctrl-shift-click ADD the anchor→row range to the existing selection * ctrl/cmd-Enter fire onActivate(selectedIds) — a bulk action * Esc clear - * Ranges run over the CURRENTLY FILTERED order. Selection is keyed by a stable - * rowId so it survives filtering and re-render. + * Ranges run over the CURRENTLY FILTERED + SORTED order. Selection is keyed by a + * stable rowId so it survives filtering, sorting, and re-render. Pass + * opts.persistKey to remember column widths + sort across reloads (localStorage). + * + * Column config: { key, title, cls?, get?(row), render?(row, td), filterable?, + * sortable?, sortValue?(row) }. sortable/filterable default true; set false for + * render-only columns (a remove button, a derived badge). */ (function () { 'use strict'; @@ -8865,6 +8880,12 @@ X.B(E,Y);return E}return J}()) if (text != null) e.textContent = text; return e; } + function loadPersist(key) { try { return JSON.parse(localStorage.getItem(key)) || {}; } catch (_) { return {}; } } + function savePersist(key, patch) { + var cur = loadPersist(key); + for (var k in patch) cur[k] = patch[k]; + try { localStorage.setItem(key, JSON.stringify(cur)); } catch (_) { /* private mode */ } + } function create(opts) { var container = opts.container; @@ -8876,6 +8897,12 @@ X.B(E,Y);return E}return J}()) var globalTerms = []; // programmatic global filter (tests/reveal) var colFilters = Object.create(null); // colKey -> terms[] (the per-column autofilters) + var persistKey = opts.persistKey || null; + var saved = persistKey ? loadPersist(persistKey) : {}; + var sortState = Array.isArray(saved.sort) ? saved.sort.slice() : []; // [{ key, dir }] + var colWidths = saved.widths || {}; // colKey -> px + var headEls = Object.create(null); // colKey -> { th, ind } + function rows() { return getRows() || []; } function colByKey(k) { for (var i = 0; i < columns.length; i++) { if (columns[i].key === k) return columns[i]; } return null; } function colVal(col, row) { return col.get ? col.get(row) : (row[col.key] == null ? '' : row[col.key]); } @@ -8890,6 +8917,59 @@ X.B(E,Y);return E}return J}()) } function filtered() { return rows().filter(rowMatches); } + // ── multi-column sort ────────────────────────────────────────────── + function sortVal(col, row) { return col.sortValue ? col.sortValue(row) : colVal(col, row); } + function sorted(list) { + if (!sortState.length) return list; + var arr = list.slice(); + arr.sort(function (a, b) { + for (var i = 0; i < sortState.length; i++) { + var s = sortState[i], col = colByKey(s.key); if (!col) continue; + var cmp = String(sortVal(col, a)).localeCompare(String(sortVal(col, b)), undefined, { numeric: true, sensitivity: 'base' }); + if (cmp) return s.dir === 'desc' ? -cmp : cmp; + } + return 0; + }); + return arr; + } + function visibleRows() { return sorted(filtered()); } + function sortIdx(key) { for (var i = 0; i < sortState.length; i++) { if (sortState[i].key === key) return i; } return -1; } + function toggleSort(key, add) { + var i = sortIdx(key); + if (add) { + if (i >= 0) { if (sortState[i].dir === 'asc') sortState[i].dir = 'desc'; else sortState.splice(i, 1); } + else sortState.push({ key: key, dir: 'asc' }); + } else { + if (i === 0 && sortState.length === 1) sortState = sortState[0].dir === 'asc' ? [{ key: key, dir: 'desc' }] : []; + else sortState = [{ key: key, dir: 'asc' }]; + } + if (persistKey) savePersist(persistKey, { sort: sortState }); + updateSortIndicators(); renderBody(); + } + function updateSortIndicators() { + columns.forEach(function (c) { + var h = headEls[c.key]; if (!h) return; + var i = sortIdx(c.key); + h.ind.textContent = i >= 0 ? ((sortState[i].dir === 'asc' ? ' ▲' : ' ▼') + (sortState.length > 1 ? (i + 1) : '')) : ''; + }); + } + // ── resizable columns ────────────────────────────────────────────── + function addResizer(th, key) { + var rz = elt('div', 'seltable__resizer'); + rz.addEventListener('mousedown', function (e) { + e.preventDefault(); e.stopPropagation(); + var startX = e.clientX, startW = th.offsetWidth; + function mm(ev) { var w = Math.max(40, startW + (ev.clientX - startX)); th.style.width = th.style.minWidth = th.style.maxWidth = w + 'px'; } + function mu() { + document.removeEventListener('mousemove', mm); document.removeEventListener('mouseup', mu); + colWidths[key] = Math.round(th.offsetWidth); + if (persistKey) savePersist(persistKey, { widths: colWidths }); + } + document.addEventListener('mousemove', mm); document.addEventListener('mouseup', mu); + }); + th.appendChild(rz); + } + function getSelection() { return Object.keys(selected); } function getFilteredRows() { return filtered(); } function fireSel() { if (opts.onSelectionChange) opts.onSelectionChange(getSelection()); } @@ -8929,7 +9009,23 @@ X.B(E,Y);return E}return J}()) var scroll = elt('div', 'seltable__scroll'); var table = elt('table', 'seltable__table'); var thead = elt('thead'), htr = elt('tr'); - columns.forEach(function (c) { htr.appendChild(elt('th', c.cls || null, c.title || c.key)); }); + headEls = Object.create(null); + columns.forEach(function (c) { + var th = elt('th', c.cls || null); + th.appendChild(document.createTextNode(c.title || c.key)); + var ind = elt('span', 'seltable__sortind'); th.appendChild(ind); + headEls[c.key] = { th: th, ind: ind }; + if (colWidths[c.key]) { th.style.width = th.style.minWidth = th.style.maxWidth = colWidths[c.key] + 'px'; } + if (c.sortable !== false) { + th.classList.add('seltable__th--sortable'); + th.addEventListener('click', function (e) { + if (e.target.classList.contains('seltable__resizer')) return; + toggleSort(c.key, e.shiftKey || e.ctrlKey || e.metaKey); + }); + } + addResizer(th, c.key); + htr.appendChild(th); + }); if (opts.rowExtra) htr.appendChild(elt('th', 'seltable__extrah', opts.extraTitle || '')); thead.appendChild(htr); // Per-column autofilter row. @@ -8939,6 +9035,7 @@ X.B(E,Y);return E}return J}()) if (c.filterable !== false) { var inp = elt('input', 'seltable__colfilter'); inp.type = 'search'; inp.placeholder = 'filter…'; inp.spellcheck = false; inp.setAttribute('data-no-select', ''); + if (colFilters[c.key]) inp.value = colFilters[c.key].join(' '); inp.addEventListener('input', function () { setColFilter(c.key, this.value); }); th.appendChild(inp); } @@ -8955,11 +9052,12 @@ X.B(E,Y);return E}return J}()) if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); if (opts.onActivate) opts.onActivate(getSelection()); } else if (e.key === 'Escape') { clearSel(); } }); + updateSortIndicators(); renderBody(); } function renderBody() { if (!bodyEl) return; - var fr = filtered(); + var fr = visibleRows(); bodyEl.textContent = ''; fr.forEach(function (row) { var id = rowId(row); @@ -9000,8 +9098,9 @@ X.B(E,Y);return E}return J}()) render: render, renderBody: renderBody, getSelection: getSelection, getFilteredRows: getFilteredRows, setFilter: setFilter, setColFilter: setColFilter, selectAllFiltered: selectAllFiltered, clear: clearSel, + sortBy: toggleSort, getSortState: function () { return sortState.slice(); }, clickRow: function (id, mods) { - var fr = filtered(); + var fr = visibleRows(); var row = fr.filter(function (r) { return String(rowId(r)) === String(id); })[0]; if (row) onRowClick(Object.assign({ shiftKey: false, ctrlKey: false, metaKey: false }, mods || {}), row, fr); }, @@ -11392,14 +11491,18 @@ X.B(E,Y);return E}return J}()) return rfHit(orig) || rfHit(C().deriveTarget(f).filename || ''); } - // ── By-tracking: flat editable grid (one row per file) ────────────────── - var GRID_COLS = [ - { id: 'status', title: '', cls: 'tg-status', fixed: true }, - { id: 'orig', title: 'Original name', cls: 'tg-orig' }, - { id: 'tn', title: 'Tracking number', cls: 'tg-tn' }, - { id: 'rev', title: 'Rev (status)', cls: 'tg-rev' }, - { id: 'title', title: 'Title', cls: 'tg-title' }, - { id: 'x', title: '', cls: 'tg-x', fixed: true }, + // ── By-tracking: flat editable grid (one row per file), on the shared + // seltable — so it gets multi-sort + per-column autofilters + resizable, + // persisted widths for free. Only `hidden` (the Columns ▾ chooser) is + // classifier-specific; widths + sort persist under the same key via + // seltable's own persistKey storage (merged, not clobbered). ─────────── + var GRID_COL_META = [ + { id: 'status', title: 'Status', fixed: true }, // fixed = never hidden by the chooser + { id: 'orig', title: 'Original name' }, + { id: 'tn', title: 'Tracking number' }, + { id: 'rev', title: 'Rev (status)' }, + { id: 'title', title: 'Title' }, + { id: 'x', title: '', fixed: true }, ]; var GRID_PREFS_KEY = 'zddc.classifier.trackingCols'; function gridPrefs() { try { return JSON.parse(localStorage.getItem(GRID_PREFS_KEY)) || {}; } catch (_) { return {}; } } @@ -11424,75 +11527,30 @@ X.B(E,Y);return E}return J}()) // different tab, so its "not placed in a transmittal" error doesn't count here. function nameErrors(d) { return (d.errors || []).filter(function (e) { return e.indexOf('transmittal') === -1; }); } - function renderTrackingGrid(container) { - container.textContent = ''; - var c = C(); - var files = c.trackingGridKeys().map(fileByKey).filter(Boolean) - .filter(function (f) { return !rfActive() || fileRowMatches(f); }); - if (!files.length) { - container.appendChild(el('div', 'target-empty', rfActive() ? 'No matches.' - : 'No files yet — drag files here from the left, then type each one’s tracking number, revision, and title. A file that’s already ZDDC-named fills in automatically.')); - return; - } - var prefs = gridPrefs(), hidden = prefs.hidden || {}, widths = prefs.widths || {}; - var cols = GRID_COLS.filter(function (col) { return !hidden[col.id]; }); - - var table = el('table', 'ttable ttable--grid'); - var thead = el('thead'), htr = el('tr'); - cols.forEach(function (col) { - var th = el('th', 'tg-th ' + col.cls, col.title); - th.dataset.col = col.id; - if (widths[col.id]) { th.style.width = th.style.minWidth = th.style.maxWidth = widths[col.id] + 'px'; } - htr.appendChild(th); - }); - thead.appendChild(htr); table.appendChild(thead); - - var tbody = el('tbody'); - files.forEach(function (f) { - var key = c.srcKeyForFile(f), d = c.deriveTarget(f); - var bad = nameErrors(d).length || c.hasHashConflict(key); - var tr = el('tr', 'tg-row' + (bad ? ' tg-row--err' : '')); - tr.dataset.key = key; - cols.forEach(function (col) { - var td = el('td', 'tg-td ' + col.cls); - buildGridCell(col.id, td, f, key); - tr.appendChild(td); - }); - tbody.appendChild(tr); - }); - table.appendChild(tbody); - container.appendChild(table); - if (window.app.modules.resize && window.app.modules.resize.init) window.app.modules.resize.init(table, persistColWidths); + // ── per-column cell renderers ────────────────────────────────────────── + function gridStatusCell(td, f) { + var c = C(), key = c.srcKeyForFile(f), d = c.deriveTarget(f), conflict = c.hasHashConflict(key); + var ne = nameErrors(d), ok = !ne.length && !conflict; + var badge = el('span', 'tfile__badge ' + (ok ? 'tfile__badge--ok' : 'tfile__badge--err'), conflict ? '≠' : (ne.length ? '⚠' : '✓')); + badge.title = (conflict ? 'Same tracking+revision as another file but DIFFERENT content. ' : '') + (ne.length ? ne.join('; ') : 'Complete'); + td.appendChild(badge); } - - function buildGridCell(colId, td, f, key) { - var c = C(), d = c.deriveTarget(f), conflict = c.hasHashConflict(key); - if (colId === 'status') { - var ne = nameErrors(d), ok = !ne.length && !conflict; - var badge = el('span', 'tfile__badge ' + (ok ? 'tfile__badge--ok' : 'tfile__badge--err'), conflict ? '≠' : (ne.length ? '⚠' : '✓')); - badge.title = (conflict ? 'Same tracking+revision as another file but DIFFERENT content. ' : '') + (ne.length ? ne.join('; ') : 'Complete'); - td.appendChild(badge); return; - } - if (colId === 'orig') { - var orig = f.originalFilename + (f.extension ? '.' + f.extension : ''); - var link = el('a', 'tg-orig__link', orig); - link.href = '#'; link.title = 'Preview ' + orig; - link.addEventListener('click', function (e) { e.preventDefault(); previewKey(key); }); - td.appendChild(link); return; - } - if (colId === 'x') { - var rm = el('button', 'tnode__act tg-x__btn', '✕'); - rm.title = 'Remove from the grid'; - rm.addEventListener('click', function () { c.removeFromTrackingGrid(key); }); - td.appendChild(rm); return; - } - // editable: tn / rev / title - var ident = currentIdent(f); + function gridOrigCell(td, f) { + var key = C().srcKeyForFile(f); + var orig = f.originalFilename + (f.extension ? '.' + f.extension : ''); + var link = el('a', 'tg-orig__link', orig); + link.href = '#'; link.title = 'Preview ' + orig; + link.addEventListener('click', function (e) { e.preventDefault(); previewKey(key); }); + td.appendChild(link); + } + function gridEditCell(td, colId, f) { + var c = C(), key = c.srcKeyForFile(f), ident = currentIdent(f); var value = colId === 'tn' ? ident.tracking : colId === 'rev' ? ident.rev : ident.title; var ph = colId === 'tn' ? 'ACME-…-0001' : colId === 'rev' ? 'A (IFR)' : 'title'; var warn = colId === 'tn' ? gridTnWarn(ident.tracking) : ''; var inp = el('input', 'tg-input' + (warn ? ' is-warn' : '')); inp.type = 'text'; inp.value = value || ''; inp.placeholder = ph; inp.spellcheck = false; + inp.setAttribute('data-no-select', ''); // a click in the input must not toggle row selection if (warn) inp.title = warn; inp.addEventListener('change', function () { var cur = currentIdent(f); // re-read so a prior edit isn't clobbered @@ -11503,11 +11561,73 @@ X.B(E,Y);return E}return J}()) }); td.appendChild(inp); } + function gridRemoveCell(td, f) { + var c = C(), key = c.srcKeyForFile(f); + var rm = el('button', 'tnode__act tg-x__btn', '✕'); + rm.title = 'Remove from the grid'; + rm.addEventListener('click', function () { c.removeFromTrackingGrid(key); }); + td.appendChild(rm); + } + // Build the seltable column array, dropping any the chooser has hidden. Each + // column's `get` feeds sort + filter; `render` paints the (editable) cell. + function trackingColumns() { + var c = C(), hidden = (gridPrefs().hidden || {}); + var defs = { + status: { key: 'status', title: 'Status', cls: 'tg-status', filterable: false, + get: function (f) { var d = c.deriveTarget(f); return c.hasHashConflict(c.srcKeyForFile(f)) ? 'conflict' : (nameErrors(d).length ? 'incomplete' : 'ok'); }, + render: function (f, td) { gridStatusCell(td, f); } }, + orig: { key: 'orig', title: 'Original name', cls: 'tg-orig', + get: function (f) { return f.originalFilename + (f.extension ? '.' + f.extension : ''); }, + render: function (f, td) { gridOrigCell(td, f); } }, + tn: { key: 'tn', title: 'Tracking number', cls: 'tg-tn', + get: function (f) { return currentIdent(f).tracking; }, + render: function (f, td) { gridEditCell(td, 'tn', f); } }, + rev: { key: 'rev', title: 'Rev (status)', cls: 'tg-rev', + get: function (f) { return currentIdent(f).rev; }, + render: function (f, td) { gridEditCell(td, 'rev', f); } }, + title: { key: 'title', title: 'Title', cls: 'tg-title', + get: function (f) { return currentIdent(f).title; }, + render: function (f, td) { gridEditCell(td, 'title', f); } }, + x: { key: 'x', title: '', cls: 'tg-x', sortable: false, filterable: false, + render: function (f, td) { gridRemoveCell(td, f); } }, + }; + return GRID_COL_META.filter(function (m) { return !hidden[m.id]; }).map(function (m) { return defs[m.id]; }); + } - function persistColWidths(table) { - var p = gridPrefs(); p.widths = p.widths || {}; - Array.prototype.forEach.call(table.querySelectorAll('thead th[data-col]'), function (th) { p.widths[th.dataset.col] = Math.round(th.offsetWidth); }); - saveGridPrefs(p); + var trackingGrid = null, trackingColSig = ''; + function colSig() { return trackingColumns().map(function (c) { return c.key; }).join(','); } + function ensureTrackingGrid(container) { + if (trackingGrid) return trackingGrid; + var c = C(); + trackingColSig = colSig(); + trackingGrid = window.app.modules.seltable.create({ + container: container, + rows: function () { return c.trackingGridKeys().map(fileByKey).filter(Boolean); }, + rowId: function (f) { return c.srcKeyForFile(f); }, + columns: trackingColumns(), + persistKey: GRID_PREFS_KEY, + }); + trackingGrid.render(); + return trackingGrid; + } + function renderTrackingGrid(container) { + // Empty ↔ populated transition: tear the seltable down for the prompt, + // re-create it (create-once) when files arrive — same as the worklist. + if (!C().trackingGridKeys().length) { + trackingGrid = null; + container.textContent = ''; + container.classList.remove('seltable'); + container.appendChild(el('div', 'target-empty', + 'No files yet — drag files here from the left, then type each one’s tracking number, revision, and title. A file that’s already ZDDC-named fills in automatically.')); + return; + } + // The Columns ▾ chooser changes which columns exist → rebuild on mismatch + // (self-correcting, whichever way `hidden` was changed). + if (trackingGrid && colSig() !== trackingColSig) trackingGrid = null; + ensureTrackingGrid(container); + // Mirror the name-filter box above the trees into the grid's global filter + // (setFilter re-renders the body, so the rows are always fresh on render). + trackingGrid.setFilter(rfTerms.join(' ')); } // Drag files onto the grid → add as rows; auto-fill any already ZDDC-named. @@ -11540,14 +11660,16 @@ X.B(E,Y);return E}return J}()) if (open) { open.remove(); return; } var hidden = (gridPrefs().hidden || {}); var menu = el('div', 'col-chooser'); - GRID_COLS.forEach(function (col) { + GRID_COL_META.forEach(function (col) { if (col.fixed) return; var lbl = el('label', 'col-chooser__item'); var cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = !hidden[col.id]; cb.addEventListener('change', function () { var p = gridPrefs(); p.hidden = p.hidden || {}; if (cb.checked) delete p.hidden[col.id]; else p.hidden[col.id] = true; - saveGridPrefs(p); render(); + saveGridPrefs(p); + trackingGrid = null; // column set changed → rebuild the seltable + render(); }); lbl.appendChild(cb); lbl.appendChild(document.createTextNode(' ' + col.title)); menu.appendChild(lbl); @@ -11672,7 +11794,8 @@ X.B(E,Y);return E}return J}()) { key: 'title', title: 'Title', cls: 'worklist-title', get: function (r) { return r.title || ''; }, render: function (r, td) { editCell(td, 'worklist-title__input', r.title, 'title', function (v) { c.setRowTitle(r.id, v); }); } }, { key: 'cur', title: 'Current name', cls: 'worklist-cur', get: function (r) { return r.currentName || ''; } }, - { key: 'src', title: 'Source', cls: 'worklist-src', get: function (r) { var s = r.source || {}; return [s.mdl ? 'mdl' : '', s.archive ? 'arch' : '', s.pasted ? 'pasted' : ''].filter(Boolean).join(' '); }, + { key: 'src', title: 'Source', cls: 'worklist-src', sortable: false, filterable: false, + get: function (r) { var s = r.source || {}; return [s.mdl ? 'mdl' : '', s.archive ? 'arch' : '', s.pasted ? 'pasted' : ''].filter(Boolean).join(' '); }, render: function (r, td) { renderSource(r, td); } }, { key: 'latest', title: 'Latest rev', get: function (r) { return latestRevOf(r.archiveRevisions); } }, { key: 'rev', title: 'Revision', cls: 'worklist-rev', get: function (r) { return r.revisionCell; }, diff --git a/zddc/internal/apps/embedded/index.html b/zddc/internal/apps/embedded/index.html index 3526e8b..0e9a93d 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 04:53:19 · 2b32ace + v0.0.27-beta · 2026-06-16 13:31:21 · be5b396
diff --git a/zddc/internal/apps/embedded/transmittal.html b/zddc/internal/apps/embedded/transmittal.html index 3ee875f..608bc1b 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 04:53:19 · 2b32ace + v0.0.27-beta · 2026-06-16 13:31:21 · be5b396
JavaScript not available