// app.js — bootstrap. Runs after every other module's IIFE has // registered its functions on window.app.modules. (function () { 'use strict'; var state = window.app.state; var loader = window.app.modules.loader; var tree = window.app.modules.tree; var events = window.app.modules.events; // Canonical folders that should appear at the root of a project // view even if they don't yet exist on disk. Matches the four // stage cards on the project landing page. zddc-server returns an // empty listing for these paths (see commit 3fc3717), so // navigating into a virtual folder works without 404. var CANONICAL_PROJECT_FOLDERS = ['archive', 'working', 'staging', 'reviewing']; // Decide whether `path` looks like a project root — i.e. exactly // one path segment after the leading slash. /Project-1/ → yes; // / → no; /Project-1/working/ → no. function isProjectRoot(path) { if (!path || path === '/') return false; var trimmed = path.replace(/^\/+|\/+$/g, ''); if (!trimmed) return false; return trimmed.indexOf('/') < 0; } // Merge virtual entries for any canonical folders absent from the // server's listing. Each virtual entry is shaped like a normal // directory entry so the tree renderer treats it the same way. function withVirtualCanonicals(entries, path) { if (!isProjectRoot(path)) return entries; var present = Object.create(null); entries.forEach(function (e) { if (e.isDir) present[e.name] = true; }); var augmented = entries.slice(); CANONICAL_PROJECT_FOLDERS.forEach(function (name) { if (!present[name]) { augmented.push({ name: name, isDir: true, size: 0, modTime: null, ext: '', url: path.replace(/\/$/, '') + '/' + name + '/', virtual: true }); } }); return augmented; } // Expose for events.js's client-side rescope on dblclick. window.app.modules.augmentRoot = withVirtualCanonicals; async function bootstrap() { events.init(); // Try server auto-detect. If this page is served by zddc-server // (or any server with a Caddy-shaped JSON listing), load the // current directory automatically. Otherwise show the empty // state with the "Select Directory" button. var detected = await loader.autoDetectServerMode(); if (detected) { var entries = withVirtualCanonicals(detected.entries, detected.path); tree.setRoot(entries); events.showBrowseRoot(); tree.render(); events.statusInfo('Loaded ' + detected.entries.length + ' item' + (detected.entries.length === 1 ? '' : 's') + ' from ' + detected.path); } // Else: empty state stays visible; user can click Select Directory. // Browser back / forward: client-side rescope when the URL // changes via popstate. We can't tell server-vs-fs mode from // popstate alone, so only honor it in server mode. window.addEventListener('popstate', async function () { if (window.app.state.source !== 'server') return; var path = location.pathname; if (!path.endsWith('/')) path += '/'; try { var es = await loader.fetchServerChildren(path); window.app.state.currentPath = path; window.app.state.selectedId = null; window.app.state.lastPreviewedNodeId = null; tree.setRoot(withVirtualCanonicals(es, path)); tree.render(); var previewBody = document.getElementById('previewBody'); if (previewBody) previewBody.innerHTML = ''; var previewTitle = document.getElementById('previewTitle'); if (previewTitle) previewTitle.textContent = 'No file selected'; } catch (_e) { /* swallow — leave the tree as-is */ } }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', bootstrap); } else { bootstrap(); } })();