// 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; // Walk a `?file=` path segment-by-segment from the current root. // Each non-leaf segment is matched against the parent's children // by name; if found and it's a folder, expand+load it (so its // children populate state.nodes) and recurse into them. The leaf // segment becomes the selected/previewed entry. Silently no-ops // when any segment doesn't resolve — deep links aren't a hard // contract, just an affordance. async function openDeepLink(path) { var segs = path.split('/').filter(Boolean); if (segs.length === 0) return; var tree = window.app.modules.tree; var prev = window.app.modules.preview; // Lookup helper: find a node by name within a given parent's // immediate children. Top-level walk uses state.rootIds. function findChild(parentIds, name) { for (var i = 0; i < parentIds.length; i++) { var n = window.app.state.nodes.get(parentIds[i]); if (n && n.name === name) return n; } return null; } var ids = window.app.state.rootIds; for (var i = 0; i < segs.length; i++) { var node = findChild(ids, segs[i]); if (!node) return; // segment not present in this listing if (i === segs.length - 1) { // Leaf — select + preview. window.app.state.selectedId = node.id; window.app.state.lastPreviewedNodeId = node.id; tree.render(); if (prev && !node.isDir) prev.showFilePreview(node); return; } // Intermediate — must be a folder we can expand into. if (!(node.isDir || node.isZip)) return; if (!node.loaded) { await tree.toggleFolder(node.id); // loads + sets expanded } else if (!node.expanded) { node.expanded = true; } ids = node.childIds; } } async function bootstrap() { events.init(); // Honor ?file= deep links: external clients (the profile // page's "edit your .zddc files" list, future bookmarks, etc.) // can link directly to "open browse at , with this entry // selected and previewed". Single-segment names (?file=foo.md) // match in the current directory; multi-segment paths // (?file=a/b/foo.md) walk into a/ then b/ then open foo.md, // loading intermediate directories on the way. // // When the LEAF (or any intermediate segment) is hidden // (.zddc, .form.yaml, …), flip showHidden ON BEFORE the // initial listing fetch so dotfiles appear in the tree. var qs = new URLSearchParams(location.search); var deepFile = qs.get('file'); // Explicit ?hidden=1 in the URL: restore the show-hidden toggle // on reload (the URL is the persistence layer for this flag — // see events.js syncURLToSelection). if (qs.get('hidden') === '1') state.showHidden = true; if (deepFile) { var segs = deepFile.split('/').filter(Boolean); for (var si = 0; si < segs.length; si++) { var c = segs[si].charAt(0); if (c === '.' || c === '_') { state.showHidden = true; break; } } } // 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) { tree.setRoot(detected.entries); events.showBrowseRoot(); tree.render(); if (events.prefetchScopeAccess) events.prefetchScopeAccess(); events.statusInfo('Loaded ' + detected.entries.length + ' item' + (detected.entries.length === 1 ? '' : 's') + ' from ' + detected.path); // The initial events.init() applied view mode before the // cascade headers were available (no fetch yet). Now that // state.scopeDefaultTool is set from the detection // response, re-resolve so an /incoming URL auto-activates // grid mode. if (events.applyResolvedViewMode) events.applyResolvedViewMode(); // Final step of the deep link: walk the path segment by // segment, expanding + loading intermediate directories // before opening the leaf. Single-segment names use the // same code path with one iteration. if (deepFile) { await openDeepLink(deepFile); } } // 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 += '/'; var popQS = new URLSearchParams(location.search); if (popQS.get('hidden') === '1') window.app.state.showHidden = true; else window.app.state.showHidden = false; // Join the shared nav token: rapid back/forward (or back/forward // while an in-tool rescope is mid-flight) must not apply a stale // listing on top of a newer one. var seq = events.beginNav ? events.beginNav() : 0; try { var es = await loader.fetchServerChildren(path); if (events.isCurrentNav && !events.isCurrentNav(seq)) return; window.app.state.currentPath = path; window.app.state.selectedId = null; window.app.state.lastPreviewedNodeId = null; tree.setRoot(es); tree.render(); if (events.prefetchScopeAccess) events.prefetchScopeAccess(); // Route through clearPreview so a live editor is disposed // (not leaked) when back/forward swaps scope. var pmod = window.app.modules.preview; if (pmod && pmod.clearPreview) pmod.clearPreview(); else { var previewBody = document.getElementById('previewBody'); if (previewBody) previewBody.innerHTML = ''; } var previewTitle = document.getElementById('previewTitle'); if (previewTitle) previewTitle.textContent = 'No file selected'; // Reapply view mode for the new URL (incoming/ → grid, etc). if (events.applyResolvedViewMode) events.applyResolvedViewMode(); // Re-walk ?file= so back/forward restores selection + // expansion, not just scope. var popFile = popQS.get('file'); if (popFile) await openDeepLink(popFile); } catch (_e) { /* swallow — leave the tree as-is */ } }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', bootstrap); } else { bootstrap(); } })();