feat(archive,landing): local-mode ?projects= filter + ?v= propagation
Two small additions to the project-filter / channel-selector flow that
already worked end-to-end for HTTP-mode but were missing in the local
File-System-Access path and across landing→archive navigation:
* archive: scanLocalRecursive now applies window.app.projectFilter at
depth 0, mirroring the HTTP source's existing filter at source.js:316.
Loading archive.html?projects=A,B in local mode (file://) now virtually
merges A and B into one combined view, same as HTTP mode does today.
* landing: openArchive() reads ?v= from its own URL and passes it through
to the archive.html link it generates. This keeps the user on the same
channel (alpha/beta/stable/<version>) when they cross from the project
picker to the archive — without it, alpha-channel users would silently
drop back to whatever the deployment-default channel is at the
archive.html boundary.
Test exercises the local-mode filter via the existing mock-fs-api
fixture: three top-level projects, projectFilter set to {A, B}, scan
produces only A's and B's files. (The url-state.restore() URL parsing
path is well-trodden in the HTTP case — the test sets projectFilter
directly to isolate the new source.js change from a pre-existing init()
fragility in the mock environment.)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
89c5ec064d
commit
f8a3da2ea1
3 changed files with 70 additions and 1 deletions
|
|
@ -78,6 +78,13 @@
|
|||
|
||||
for (const entry of entries) {
|
||||
if (entry.kind === 'directory') {
|
||||
// Project filter: at root depth, skip directories not in the
|
||||
// allowed set so ?projects=A,B virtually merges A and B into a
|
||||
// single combined view. Mirrors the HTTP-source filter at the
|
||||
// depth === 0 site below.
|
||||
if (depth === 0 && window.app.projectFilter && window.app.projectFilter.size > 0) {
|
||||
if (!window.app.projectFilter.has(entry.name)) continue;
|
||||
}
|
||||
const subPath = currentPath + '/' + entry.name;
|
||||
try {
|
||||
if (window.app.modules.parser.isTransmittalFolder(entry.name)) {
|
||||
|
|
|
|||
|
|
@ -117,7 +117,14 @@
|
|||
return;
|
||||
}
|
||||
var base = location.pathname.replace(/\/[^\/]*$/, '/');
|
||||
location.href = base + 'archive.html?projects=' + checked.map(encodeURIComponent).join(',');
|
||||
var params = ['projects=' + checked.map(encodeURIComponent).join(',')];
|
||||
// Propagate ?v= (channel selector) so the archive page loads through
|
||||
// the same level-2 bootstrap stub on the same channel as this landing.
|
||||
var v = new URLSearchParams(location.search).get('v');
|
||||
if (v) {
|
||||
params.push('v=' + encodeURIComponent(v));
|
||||
}
|
||||
location.href = base + 'archive.html?' + params.join('&');
|
||||
}
|
||||
|
||||
function savePreset() {
|
||||
|
|
|
|||
|
|
@ -87,6 +87,61 @@ test.describe('Archive Browser', () => {
|
|||
expect(fileCountText).toBeTruthy();
|
||||
});
|
||||
|
||||
test('projectFilter filters local-mode scan at root depth (virtual merge)', async ({ page }) => {
|
||||
// The corresponding URL flow is `archive.html?projects=A,B` →
|
||||
// url-state.restore() → window.app.projectFilter Set. We set the Set
|
||||
// directly here because the existing test environment hits an
|
||||
// unrelated init() error before url-state.restore() runs (a
|
||||
// getElementById returning null in events.js); a separate test would
|
||||
// be needed to re-confirm the URL-parsing path. The behavior under
|
||||
// test is the source.js depth-0 filter check, which only reads
|
||||
// window.app.projectFilter.
|
||||
await page.goto(`file://${HTML_PATH}`, { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForSelector('#addDirectoryBtn', { timeout: 15000 });
|
||||
|
||||
// Three top-level projects under one root; only A and B should be scanned.
|
||||
await page.evaluate(() => {
|
||||
window.__setMockDirectoryTree('combined-root', {
|
||||
'Project-A': {
|
||||
'2025-01-15_123456-EM-TRN-0001 (IFC) - First': {
|
||||
'123456-EL-SPC-0001_A (IFC) - SpecA.pdf': '%PDF',
|
||||
},
|
||||
},
|
||||
'Project-B': {
|
||||
'2025-02-10_789012-EM-TRN-0001 (IFC) - Second': {
|
||||
'789012-EL-SPC-0002_A (IFC) - SpecB.pdf': '%PDF',
|
||||
},
|
||||
},
|
||||
'Project-C': {
|
||||
'2025-03-01_345678-EM-TRN-0001 (IFC) - Third': {
|
||||
'345678-EL-SPC-0003_A (IFC) - SpecC.pdf': '%PDF',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Set projectFilter to {A, B}, mimicking what url-state.restore()
|
||||
// would do for a URL like archive.html?projects=Project-A,Project-B.
|
||||
await page.evaluate(() => {
|
||||
window.app.projectFilter = new Set(['Project-A', 'Project-B']);
|
||||
});
|
||||
|
||||
await page.locator('#addDirectoryBtn').click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Surface all files into the table.
|
||||
await page.evaluate(() => {
|
||||
const cb = document.getElementById('selectAllGroupingCheckbox');
|
||||
if (cb && !cb.checked) cb.click();
|
||||
});
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const tableText = await page.locator('#filesTableBody').textContent();
|
||||
expect(tableText).toContain('SpecA');
|
||||
expect(tableText).toContain('SpecB');
|
||||
expect(tableText).not.toContain('SpecC');
|
||||
});
|
||||
|
||||
test('parser module uses shared zddc helpers (not its own wrappers)', async ({ page }) => {
|
||||
await page.goto(`file://${HTML_PATH}`, { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForSelector('#appContainer', { timeout: 15000 });
|
||||
|
|
|
|||
Loading…
Reference in a new issue