Bundles a stretch of in-progress work across the SPA tools so the
tree returns to a coherent shippable state ahead of cutting a new
zddc-server stable image:
- landing: substantial rework of the project picker (sortable/filterable
table, presets refactor, ?projects= filter, ?v= channel propagation,
loading/error states)
- archive: presets cleanup, source.js refactor, filtering/url-state
alignment with the landing page
- mdedit: file-system module split, resizer, file-tree improvements,
base/toc styling tweaks
- transmittal/classifier: small template touch-ups for shared chrome
- shared: build-lib.sh helpers, new favicon.svg
- bootstrap, build.sh: pick up the channel-aware install/track zip
generation
- tests: new landing.spec.js, expanded archive/mdedit/build-label specs
- docs: CLAUDE.md picks up the zddc-server section and freshens the
alpha-build exception note
- regenerated artifacts: install.zip, track-{alpha,beta,stable}.zip,
*_alpha.html — these are produced by `sh build.sh` and per project
convention are committed alongside the source changes
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
147 lines
5.3 KiB
JavaScript
147 lines
5.3 KiB
JavaScript
(function() {
|
|
'use strict';
|
|
// Project-picker dropdown for the archive browser.
|
|
//
|
|
// In multi-project mode (HTTP source against zddc-server, OR ?projects=
|
|
// present in the URL), this dropdown lets the user toggle which projects
|
|
// are scanned. Toggling a checkbox updates window.app.projectFilter, pushes
|
|
// the new ?projects= state to the URL, and triggers a re-scan.
|
|
//
|
|
// In single-project mode the dropdown is hidden — only one project is ever
|
|
// in scope, so picking is meaningless.
|
|
|
|
let isOpen = false;
|
|
|
|
// The set of project names currently shown in the dropdown.
|
|
function getKnownProjects() {
|
|
if (window.app.availableProjects && window.app.availableProjects.length > 0) {
|
|
return window.app.availableProjects.slice();
|
|
}
|
|
// Fall back to whatever is in the URL filter — useful when the server's
|
|
// ProjectInfo endpoint isn't reachable but ?projects= names the set.
|
|
return Array.from(window.app.projectFilter || []);
|
|
}
|
|
|
|
// Visibility-only filter: change visibleProjects, push URL state, re-render
|
|
// UI. No rescan — already-scanned data stays in memory. URL is updated via
|
|
// history.replaceState (same mechanism as every other UI control).
|
|
function applyVisibility(names) {
|
|
window.app.visibleProjects = new Set(names);
|
|
window.app.modules.urlState.push();
|
|
window.app.modules.app.updateUI();
|
|
window.app.modules.filtering.applyFilters();
|
|
renderDropdown();
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
var div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
function renderDropdown() {
|
|
var dropdown = document.getElementById('presetDropdown');
|
|
if (!dropdown) return;
|
|
|
|
var selected = new Set(window.app.visibleProjects || []);
|
|
var known = getKnownProjects().slice().sort();
|
|
|
|
var projectsHtml = known.map(name => {
|
|
var checked = selected.has(name) ? ' checked' : '';
|
|
var n = escapeHtml(name);
|
|
return '<div class="preset-project-item">'
|
|
+ '<label class="preset-project-label">'
|
|
+ '<input type="checkbox" class="preset-checkbox" data-name="' + n + '"' + checked + '>'
|
|
+ ' ' + n
|
|
+ '</label>'
|
|
+ '</div>';
|
|
}).join('');
|
|
if (!projectsHtml) {
|
|
projectsHtml = '<div class="preset-no-presets"><i>No projects available</i></div>';
|
|
}
|
|
|
|
dropdown.innerHTML =
|
|
'<div class="preset-section-bottom">'
|
|
+ '<div class="preset-section-label">Projects:</div>'
|
|
+ '<div class="preset-projects-list">' + projectsHtml + '</div>'
|
|
+ '</div>';
|
|
}
|
|
|
|
function toggleDropdown() {
|
|
var dropdown = document.getElementById('presetDropdown');
|
|
if (isOpen) { closeDropdown(); return; }
|
|
isOpen = true;
|
|
if (dropdown) dropdown.classList.remove('hidden');
|
|
renderDropdown();
|
|
}
|
|
|
|
function closeDropdown() {
|
|
isOpen = false;
|
|
var dropdown = document.getElementById('presetDropdown');
|
|
if (dropdown) dropdown.classList.add('hidden');
|
|
}
|
|
|
|
function setupDropdownDelegation() {
|
|
var dropdown = document.getElementById('presetDropdown');
|
|
if (!dropdown) return;
|
|
|
|
dropdown.addEventListener('click', function(e) {
|
|
e.stopPropagation();
|
|
var checkbox = e.target.closest('.preset-checkbox');
|
|
if (!checkbox) return;
|
|
var projectName = checkbox.getAttribute('data-name');
|
|
if (!projectName) return;
|
|
var sel = new Set(window.app.visibleProjects || []);
|
|
if (checkbox.checked) sel.add(projectName);
|
|
else sel.delete(projectName);
|
|
applyVisibility(Array.from(sel));
|
|
});
|
|
}
|
|
|
|
function setupOutsideClickHandler() {
|
|
document.addEventListener('click', function(e) {
|
|
var section = document.getElementById('presetSection');
|
|
var dropdown = document.getElementById('presetDropdown');
|
|
if (isOpen && section && dropdown && !section.contains(e.target)) {
|
|
closeDropdown();
|
|
}
|
|
});
|
|
}
|
|
|
|
function init() {
|
|
var section = document.getElementById('presetSection');
|
|
if (!section) return;
|
|
|
|
// Hide the dropdown entirely outside multi-project mode.
|
|
if (!window.app.isMultiProject) {
|
|
section.classList.add('hidden');
|
|
return;
|
|
}
|
|
section.classList.remove('hidden');
|
|
|
|
var btn = document.getElementById('presetBtn');
|
|
if (!btn || btn.dataset.presetInit) return;
|
|
btn.dataset.presetInit = '1';
|
|
btn.title = 'Project picker';
|
|
btn.textContent = '▾ Projects';
|
|
|
|
btn.addEventListener('click', function(e) {
|
|
e.stopPropagation();
|
|
toggleDropdown();
|
|
});
|
|
|
|
setupDropdownDelegation();
|
|
setupOutsideClickHandler();
|
|
}
|
|
|
|
window.app.modules.presets = {
|
|
init: init,
|
|
toggleDropdown: toggleDropdown,
|
|
closeDropdown: closeDropdown,
|
|
// No-op kept so existing callers (events.js after grouping-folder click)
|
|
// don't need to null-check; preset dirty state was removed with the
|
|
// saved-presets feature.
|
|
checkDirty: function() {}
|
|
};
|
|
|
|
})();
|