diff --git a/classifier/css/layout.css b/classifier/css/layout.css index 188d48e..9fed191 100644 --- a/classifier/css/layout.css +++ b/classifier/css/layout.css @@ -614,6 +614,8 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o } .tcell__name { font-weight: 600; } .trev__inner .tcell__name { color: var(--primary); } +.tcell__preview { text-decoration: none; cursor: pointer; } +.tcell__preview:hover { text-decoration: underline; } .ttable__cell:hover .tnode__actions, .ttable__rev:hover .tnode__actions { opacity: 1; } .ttable .drop-hover { outline: 2px solid var(--primary); outline-offset: -2px; } .ttable__file { padding: 0.1rem 0.4rem; } diff --git a/classifier/js/target-tree.js b/classifier/js/target-tree.js index 5958a6c..47d15c0 100644 --- a/classifier/js/target-tree.js +++ b/classifier/js/target-tree.js @@ -253,9 +253,18 @@ } function revCellContent(node, placedMap) { var inner = el('div', 'tcell__inner trev__inner'); - inner.appendChild(el('span', 'tcell__name', node.name)); - var n = (placedMap[node.id] || []).length; - if (n) inner.appendChild(el('span', 'tnode__badge', String(n))); + // The revision name doubles as a preview link for its placed file (the + // common case is one file per revision). No count bubble. + var files = placedMap[node.id] || []; + if (files.length) { + var link = el('a', 'tcell__name tcell__preview', node.name); + link.href = '#'; + link.dataset.previewKey = C().srcKeyForFile(files[0]); + link.title = 'Preview ' + files[0].originalFilename + (files[0].extension ? '.' + files[0].extension : ''); + inner.appendChild(link); + } else { + inner.appendChild(el('span', 'tcell__name', node.name)); + } inner.appendChild(nodeActions([ { act: 'rename', label: '✎', title: 'Rename revision' }, { act: 'del', label: '🗑', title: 'Delete' }, @@ -446,6 +455,16 @@ } // Click a placed-file row (anywhere but its editable name) → preview it. function previewFromTarget(e) { + // Preview link on a revision cell (its placed file). + var pl = e.target.closest('[data-preview-key]'); + if (pl) { + e.preventDefault(); + var pf = fileByKey(pl.dataset.previewKey); + if (pf && window.app.modules.preview && window.app.modules.preview.previewFile) { + window.app.modules.preview.previewFile(pf); + } + return true; + } if (e.target.closest('.tfile__name')) return false; var tf = e.target.closest('.tfile'); if (!tf || !tf.dataset.key) return false; diff --git a/tests/classify.spec.js b/tests/classify.spec.js index d14d43a..024fc0f 100644 --- a/tests/classify.spec.js +++ b/tests/classify.spec.js @@ -984,3 +984,26 @@ test('By-tracking table merges shared ancestors and aligns revisions', async ({ // The revisions live in one aligned column; the date revision stays intact. expect(r.revs).toEqual(['2025-11-17 (IFI)', 'A (IFR)', '0 (IFU)']); }); + +test('revision cell links to preview its file and shows no count bubble', async ({ page }) => { + await page.click('#modeClassifyBtn'); + const r = await page.evaluate(() => { + const c = window.app.modules.classify, tt = window.app.modules.targetTree; + c.reset(); + const f = { originalFilename: 'foundation', extension: 'pdf', folderPath: 'Root' }; + window.app.folderTree = [{ name: 'Root', path: 'Root', files: [f], children: [] }]; + const leaf = c.addTrackingPath(null, c.parseFolderLevels('ACME-MECH-0001_A (IFR)')); + c.place([c.srcKeyForFile(f)], leaf, 'tracking'); + tt.render(); + const rev = document.querySelector('#trackingTree .ttable__rev'); + const link = rev.querySelector('.tcell__preview[data-preview-key]'); + return { + hasPreview: !!link, + previewKey: link && link.dataset.previewKey, + hasBadge: !!rev.querySelector('.tnode__badge'), + }; + }); + expect(r.hasPreview).toBe(true); // revision name is a preview link + expect(r.previewKey).toBe('foundation.pdf'); + expect(r.hasBadge).toBe(false); // no count bubble +});