diff --git a/classifier/js/app.js b/classifier/js/app.js
index 6d45c4e..ea0760f 100644
--- a/classifier/js/app.js
+++ b/classifier/js/app.js
@@ -151,6 +151,7 @@
exportDatasetBtn: document.getElementById('exportDatasetBtn'),
importDatasetBtn: document.getElementById('importDatasetBtn'),
importDatasetInput: document.getElementById('importDatasetInput'),
+ resetDatasetBtn: document.getElementById('resetDatasetBtn'),
treeFilterInput: document.getElementById('treeFilterInput'),
trackingFilterInput: document.getElementById('trackingFilterInput'),
transmittalFilterInput: document.getElementById('transmittalFilterInput'),
@@ -305,6 +306,24 @@
reader.onerror = function () { window.zddc.toast('Import failed — could not read the file.', 'error'); };
reader.readAsText(file);
}
+ // Reset to a clean state: keep the scanned source tree (the raw input), but
+ // discard every classification — trees, assignments, excludes, overrides.
+ // Destructive and irreversible, so warn and steer the user to Export first.
+ function resetDataset() {
+ var c = app.modules.classify;
+ var n = Object.keys(c.serialize().assignments || {}).length;
+ if (!n && !c.getTrackingTree().length && !c.getTransmittalTree().length) {
+ window.zddc.toast('Nothing to reset — already at the raw input.', 'info');
+ return;
+ }
+ if (!confirm('Reset to a clean state?\n\nThis discards ALL classifications ('
+ + n + ' assigned file' + (n === 1 ? '' : 's') + ', plus the tracking and '
+ + 'transmittal trees) and returns to just the raw scanned input. Your actual '
+ + 'files are not touched.\n\nThis cannot be undone — Export first if you might '
+ + 'need this data.')) return;
+ c.reset();
+ window.zddc.toast('Reset to the raw scanned input.', 'success');
+ }
/**
* Set up event listeners
@@ -369,6 +388,7 @@
// Dataset export / import (round-trip the classification through a JSON file).
if (app.dom.exportDatasetBtn) app.dom.exportDatasetBtn.addEventListener('click', exportDataset);
if (app.dom.importDatasetBtn) app.dom.importDatasetBtn.addEventListener('click', function () { app.dom.importDatasetInput.click(); });
+ if (app.dom.resetDatasetBtn) app.dom.resetDatasetBtn.addEventListener('click', resetDataset);
if (app.dom.importDatasetInput) app.dom.importDatasetInput.addEventListener('change', function () {
if (this.files && this.files[0]) importDataset(this.files[0]);
this.value = ''; // allow re-importing the same file
diff --git a/classifier/js/tree.js b/classifier/js/tree.js
index d19dbae..561174d 100644
--- a/classifier/js/tree.js
+++ b/classifier/js/tree.js
@@ -143,6 +143,9 @@
*/
function render() {
const container = window.app.dom.folderTree;
+ // Preserve scroll across re-render — toggling a Show filter shouldn't
+ // jump the view back to the top.
+ const prevScroll = container.scrollTop;
wireClassifyInteractions();
container.innerHTML = '';
updateFilterCounts();
@@ -164,6 +167,7 @@
}
updateSelectedCount();
+ container.scrollTop = prevScroll;
}
/**
@@ -268,7 +272,7 @@
// expandable so its files can be revealed and dragged.
|| (classifyOn() && folder.files && folder.files.length > 0);
if (mightHaveChildren) {
- toggle.textContent = (folder.expanded || visible) ? '▼' : '▶';
+ toggle.textContent = (folder.expanded || filterActive()) ? '▼' : '▶';
toggle.addEventListener('click', (e) => {
e.stopPropagation();
const recursive = e.ctrlKey || e.metaKey;
@@ -346,8 +350,9 @@
div.appendChild(item);
- // Children — when expanded, or auto-expanded to reveal a filter match.
- if ((folder.expanded || visible) && folder.children && folder.children.length > 0) {
+ // Children — when expanded, or auto-expanded to reveal a NAME-search
+ // match. The Show toggles only hide/show; they never force-expand.
+ if ((folder.expanded || filterActive()) && folder.children && folder.children.length > 0) {
const childrenDiv = document.createElement('div');
childrenDiv.className = 'folder-children';
folder.children.forEach(child => {
@@ -359,8 +364,8 @@
}
// Classify mode: list this folder's own files (draggable leaves) when
- // expanded (or auto-expanded by a filter), so they can be dropped.
- if (classifyOn() && (folder.expanded || visible) && folder.files && folder.files.length > 0) {
+ // expanded (or auto-expanded by a name search), so they can be dropped.
+ if (classifyOn() && (folder.expanded || filterActive()) && folder.files && folder.files.length > 0) {
const filesDiv = document.createElement('div');
filesDiv.className = 'folder-children folder-files';
folder.files.forEach(function (file) {
diff --git a/classifier/template.html b/classifier/template.html
index e755d63..c44442f 100644
--- a/classifier/template.html
+++ b/classifier/template.html
@@ -165,6 +165,7 @@
+
diff --git a/tests/classify.spec.js b/tests/classify.spec.js
index d6446f5..0a91e39 100644
--- a/tests/classify.spec.js
+++ b/tests/classify.spec.js
@@ -760,3 +760,29 @@ test('Show Empty off hides folders that contain no files', async ({ page }) => {
expect(r.before).toEqual(['Docs', 'EmptyDir']); // both shown by default
expect(r.after).toEqual(['Docs']); // empty folder hidden when Show Empty off
});
+
+test('toggling a Show filter preserves collapse state (no force-expand)', async ({ page }) => {
+ await page.click('#modeClassifyBtn');
+ const r = await page.evaluate(() => {
+ window.app.folderTree = [{
+ name: 'Project', path: 'Project', expanded: false, scanState: 'done', files: [], children: [
+ { name: 'Sub', path: 'Project/Sub', expanded: false, scanState: 'done', children: [],
+ files: [{ originalFilename: 'a', extension: 'pdf', folderPath: 'Project/Sub' }] },
+ ],
+ }];
+ const tree = window.app.modules.tree;
+ tree.render();
+ const collapsed = Array.from(document.querySelectorAll('#folderTree .folder-item')).map((e) => e.dataset.path);
+ // A Show toggle must not expand the collapsed parent…
+ tree.setShowFilters({ unassigned: true, assigned: true, excluded: true, empty: false });
+ const afterToggle = Array.from(document.querySelectorAll('#folderTree .folder-item')).map((e) => e.dataset.path);
+ // …whereas a name search still reveals the match by auto-expanding.
+ tree.setShowFilters({ unassigned: true, assigned: true, excluded: true, empty: true });
+ tree.setNameFilter('a.pdf');
+ const afterSearch = Array.from(document.querySelectorAll('#folderTree .folder-item')).map((e) => e.dataset.path);
+ return { collapsed, afterToggle, afterSearch };
+ });
+ expect(r.collapsed).toEqual(['Project']); // child hidden — parent collapsed
+ expect(r.afterToggle).toEqual(['Project']); // Show toggle leaves it collapsed
+ expect(r.afterSearch).toEqual(['Project', 'Project/Sub']); // name search auto-expands to the match
+});