// kernel/lifecycle.js — the MountContext a feature module receives. // // Part of the feature-module architecture (docs/frontend-modularization.md §2.4). // A feature exports mount(ctx)/unmount(ctx); the SPA shell drives them and hands // in this context. In this first form the content helpers delegate to the SPA // shell (so rendering is byte-identical to the legacy handlers), plus a teardown // ledger: any listener registered via ctx.on() or subscription via ctx.sub() is // auto-released on unmount, so a feature can't leak document listeners or live // streams across navigations. Shared-service injection (ctx.services) lands with // the Phase 2 DI container. (function () { const LP = (window.LP = window.LP || {}); class MountContext { constructor(shell, route) { this.shell = shell; // the LibrePortalSPAClean instance this.route = route || {}; this.path = this.route.path || (location.pathname + location.search); // Injected DI container — the cross-cutting services (tasks/live/auth/ // data/notify/theme/modal/router). Lazy getters onto the live singletons. this.services = (window.LP && window.LP.services) || {}; this._ac = new AbortController(); // backs ctx.on() — abort kills all this._unsubs = []; // backs ctx.sub() — SSE/bus/timer handles } // Lazy-load the feature's controller scripts in array order (idempotent // across re-mounts). Sequential, not parallel, so a feature can list a // base file before files that augment it (e.g. a class definition before // prototype-extension modules) — order in the array is honoured. async loadScripts(list) { for (const src of (list || [])) { await this.shell.loadScript(src); } } // Fetch an HTML fragment (same path the legacy handlers used). loadFragment(url) { return this.shell.fetchContent(url); } // Inject fragment HTML into #main-content + set the document title + // refresh nav highlighting — identical to the legacy loadContent(). setContent(html, title) { return this.shell.loadContent(html, title); } // SPA navigation (for redirects from within a feature). addToHistory mirrors // navigate()'s second arg so a feature can do a no-history redirect exactly // like the legacy handlers (e.g. handleAppDetail's navigate('/apps', false)). nav(path, addToHistory = true) { return this.shell.navigate(path, addToHistory); } // addEventListener bound to this mount's AbortController — auto-removed on // teardown. target = window | document | a bus | an element. on(target, event, fn, opts) { target.addEventListener(event, fn, Object.assign({}, opts, { signal: this._ac.signal })); } // Track an arbitrary unsubscribe fn (SSE handle, bus off(), clearInterval // wrapper) so it's called on teardown. sub(unsub) { if (typeof unsub === 'function') this._unsubs.push(unsub); return unsub; } // Release everything opened during this mount. Called by the shell after // the feature's own unmount(). teardown() { try { this._ac.abort(); } catch (_) {} this._unsubs.forEach(u => { try { u(); } catch (_) {} }); this._unsubs.length = 0; } } LP.kernel = LP.kernel || {}; LP.kernel.MountContext = MountContext; })();