Ensure the server is running, CORS is not blocking the request, and Caddy\'s file browsing is enabled.
';
}
el.classList.remove('hidden');
}
// Show a warning banner listing projects in the URL filter that the user cannot access
function showProjectWarning(missingProjects) {
var el = document.getElementById('projectWarningBanner');
if (!el || missingProjects.length === 0) return;
var list = missingProjects.map(function(p) { return escapeHtml(p); }).join(', ');
el.querySelector('.project-warning-text').innerHTML =
'This link includes projects you don\'t have access to: ' + list + '';
el.classList.remove('hidden');
}
function dismissProjectWarning() {
var el = document.getElementById('projectWarningBanner');
if (el) el.classList.add('hidden');
}
// Show unsupported browser message
function showUnsupportedBrowserMessage() {
const app = document.getElementById('app');
app.innerHTML = `
Browser Not Supported
This application requires a Chromium-based browser (Chrome, Edge, Brave) with File System Access API support.
Please use one of these browsers to access the Archive Browser.
`;
}
// Show empty state
function showEmptyState() {
document.getElementById('noDirectoryMessage').classList.remove('hidden');
document.querySelector('.main-container').style.display = 'none';
// Keep header visible
document.querySelector('.app-header').style.display = '';
var refreshBtn = document.getElementById('refreshHeaderBtn');
if (refreshBtn) { refreshBtn.classList.add('hidden'); }
}
// Hide empty state
function hideEmptyState() {
document.getElementById('noDirectoryMessage').classList.add('hidden');
document.querySelector('.main-container').style.display = '';
var refreshBtn = document.getElementById('refreshHeaderBtn');
if (refreshBtn) { refreshBtn.classList.remove('hidden'); }
}
// Update UI based on current state
function updateUI() {
renderFolderTypeBar();
renderFolderLists();
window.app.modules.table.updateFileTable();
updateStatusBar();
}
// Render folder lists (rebuilds DOM)
function renderFolderLists() {
renderGroupingFolders();
renderTransmittalFolders();
}
// Check if a folder path is under a hidden folder type
// Returns true if any path segment is a known folder type that is NOT currently enabled
function isUnderHiddenFolderType(path) {
const parts = path.toLowerCase().split('/');
return parts.some(part =>
window.app.FOLDER_TYPE_NAMES.includes(part) && !window.app.enabledFolderTypes.has(part)
);
}
// Get filtered grouping folders (single source of truth for filtering logic)
function getFilteredGroupingFolders() {
const filter = window.app.groupingFilter;
return window.app.groupingFolders.filter(folder => {
if (isUnderHiddenFolderType(folder.path)) {
return false;
}
if (!filter) return true;
const terms = parseSearchTerms(filter);
return matchesSearchTerms(folder.name.toLowerCase(), terms);
});
}
// Render grouping folders as a flat list of party names (depth 1 only)
function renderGroupingFolders() {
const container = document.getElementById('groupingFoldersList');
// Get filtered grouping folders (uses shared filtering logic)
const filteredFolders = getFilteredGroupingFolders();
// Only show top-level party folders (the shallowest depth among all grouping folders)
const allDepths = window.app.groupingFolders.map(f => f.path.split('/').length);
const minDepth = allDepths.length > 0 ? Math.min(...allDepths) : 1;
const partyFolders = filteredFolders.filter(f => f.path.split('/').length === minDepth);
// Sort alphabetically
partyFolders.sort((a, b) => a.path.localeCompare(b.path));
// Build set of paths for quick lookup
const partyPaths = new Set(partyFolders.map(f => f.path));
// If "Select All" mode is active, auto-select all visible party folders
if (window.app.selectAllGroupingFolders) {
window.app.selectedGroupingFolders.clear();
partyFolders.forEach(f => window.app.selectedGroupingFolders.add(f.path));
} else {
// Remove selections for folders that are no longer visible
for (const selectedPath of window.app.selectedGroupingFolders) {
if (!partyPaths.has(selectedPath)) {
window.app.selectedGroupingFolders.delete(selectedPath);
}
}
}
// Sync checkbox state
const checkbox = document.getElementById('selectAllGroupingCheckbox');
if (checkbox) checkbox.checked = window.app.selectAllGroupingFolders;
if (partyFolders.length === 0 && window.app.groupingFilter) {
container.innerHTML = '
`).join('');
updateFolderSelectionState('groupingFoldersList');
}
// Render the global folder type toggle bar
function renderFolderTypeBar() {
const bar = document.getElementById('folderTypeBar');
if (!bar) return;
const FOLDER_TYPE_LABELS = { mdl: 'MDL', incoming: 'Incoming', issued: 'Issued', received: 'Received' };
bar.innerHTML = window.app.FOLDER_TYPE_NAMES.map(type => {
const active = window.app.enabledFolderTypes.has(type);
const label = FOLDER_TYPE_LABELS[type] || (type.charAt(0).toUpperCase() + type.slice(1));
return ``;
}).join('');
}
// Toggle a folder type on/off globally
function toggleFolderType(type) {
if (window.app.enabledFolderTypes.has(type)) {
window.app.enabledFolderTypes.delete(type);
} else {
window.app.enabledFolderTypes.add(type);
}
renderFolderTypeBar();
renderGroupingFolders();
renderTransmittalFolders();
window.app.modules.filtering.applyFilters();
window.app.modules.urlState.push();
}
// Returns true if an outstanding file's actualPath is under a selected grouping folder
// that is itself visible (not hidden by folder type toggles).
function outstandingFileIsVisible(file) {
const selectedGrouping = window.app.selectedGroupingFolders;
if (selectedGrouping.size === 0) return false;
// The actualPath must not be under a hidden folder type
if (isUnderHiddenFolderType(file.actualPath)) return false;
// The actualPath must be at or under one of the selected grouping folder paths
return Array.from(selectedGrouping).some(function(gPath) {
return file.actualPath === gPath || file.actualPath.startsWith(gPath + '/');
});
}
// Returns true if any outstanding (non-transmittal) files exist under the currently
// selected and visible grouping folders.
function hasVisibleOutstandingFiles() {
return window.app.files.some(function(f) {
if (f.folderPath !== '__outstanding__') return false;
return outstandingFileIsVisible(f);
});
}
// Returns true if a transmittal folder is under a selected party and an enabled folder type.
// Handles both HTTP paths (party at depth 0) and local paths (party at depth 1+ due to root dir prefix).
function transmittalIsUnderVisibleParty(folder) {
const parts = folder.path.split('/');
// Find which segment is the party (the one that matches a selected grouping folder path prefix).
// The party path is the selected grouping folder path, so check prefix matches.
for (const partyPath of window.app.selectedGroupingFolders) {
const partyParts = partyPath.split('/');
const partyDepth = partyParts.length; // e.g. 1 for HTTP ("ACME"), 2 for local ("RootDir/ACME")
// Check that folder path starts with partyPath
if (!folder.path.startsWith(partyPath + '/') && folder.path !== partyPath) continue;
// The segment immediately after partyPath is either a folder type or the transmittal itself
const remainder = folder.path.substring(partyPath.length + 1); // e.g. "Issued/2025-01-01_..." or "2025-01-01_..."
const remainderParts = remainder.split('/');
if (remainderParts.length >= 2) {
// There's a folder type segment before the transmittal
const folderType = remainderParts[0].toLowerCase();
if (window.app.FOLDER_TYPE_NAMES.includes(folderType)) {
// Must be an enabled type
return window.app.enabledFolderTypes.has(folderType);
}
// Unknown folder type — treat as visible
return true;
}
// Transmittal is directly under the party (no folder type level) — always show
return true;
}
// Party not selected
return false;
}
// Render transmittal folders (rebuilds DOM)
function renderTransmittalFolders() {
const container = document.getElementById('transmittalFoldersList');
const filter = window.app.transmittalFilter;
// Filter transmittal folders based on grouping selection and name filter
const filteredFolders = window.app.transmittalFolders.filter(folder => {
// Outstanding virtual transmittal: include if there are visible outstanding files
if (folder.path === '__outstanding__') {
if (!hasVisibleOutstandingFiles()) return false;
// Apply name filter to "Outstanding" label too
if (filter && filter.trim()) {
const terms = parseSearchTerms(filter.trim());
if (!matchesSearchTerms('outstanding', terms)) return false;
}
return true;
}
// Check name filter
let matchesFilter = true;
if (filter && filter.trim()) {
const terms = parseSearchTerms(filter.trim());
const folderText = folder.name.toLowerCase();
matchesFilter = matchesSearchTerms(folderText, terms);
}
// If no grouping folders exist at all, show all transmittal folders (flat structure)
if (window.app.groupingFolders.length === 0) {
return matchesFilter;
}
// If grouping folders exist but none are selected, show nothing
if (window.app.selectedGroupingFolders.size === 0) {
return false;
}
// Check party + folder type visibility
return matchesFilter && transmittalIsUnderVisibleParty(folder);
});
// Sort regular transmittal folders by date (newest first); Outstanding handled separately
const regularFolders = filteredFolders.filter(f => f.path !== '__outstanding__');
regularFolders.sort((a, b) => b.name.localeCompare(a.name));
const showOutstanding = filteredFolders.some(f => f.path === '__outstanding__');
// Build set of visible folder paths (for Select All and deselection logic)
const filteredPaths = new Set(filteredFolders.map(f => f.path));
// If "Select All" mode is active, auto-select all visible transmittal folders
if (window.app.selectAllTransmittals) {
window.app.selectedTransmittalFolders.clear();
filteredFolders.forEach(f => window.app.selectedTransmittalFolders.add(f.path));
} else {
// Remove selections for folders that are now filtered out
for (const selectedPath of window.app.selectedTransmittalFolders) {
if (!filteredPaths.has(selectedPath)) {
window.app.selectedTransmittalFolders.delete(selectedPath);
}
}
}
// Sync checkbox state
const checkbox = document.getElementById('selectAllTransmittalsCheckbox');
if (checkbox) checkbox.checked = window.app.selectAllTransmittals;
// Group regular folders by date
const foldersByDate = new Map();
regularFolders.forEach(folder => {
const match = folder.name.match(/^(\d{4}-\d{2}-\d{2})/);
const date = match ? match[1] : 'Unknown';
if (!foldersByDate.has(date)) {
foldersByDate.set(date, []);
}
foldersByDate.get(date).push(folder);
});
// Build HTML
let html = '';
// Outstanding virtual transmittal — pinned at top
if (showOutstanding) {
const isSelected = window.app.selectedTransmittalFolders.has('__outstanding__');
html += `
⋯ Outstanding
`;
}
// Regular date-grouped folders
for (const [date, folders] of foldersByDate) {
const isCollapsed = window.app.collapsedDateGroups.has(date);
const folderCount = folders.length;
html += `