@@ -0,0 +1,193 @@
( function ( ) {
'use strict' ;
// ── Track setIntervals so navigation can clear them ───────────────────────
const _origSetInterval = window . setInterval . bind ( window ) ;
const _origClearInterval = window . clearInterval . bind ( window ) ;
let _intervals = [ ] ;
window . setInterval = function ( fn , ms , ... args ) {
const id = _origSetInterval ( fn , ms , ... args ) ;
_intervals . push ( id ) ;
return id ;
} ;
function clearTrackedIntervals ( ) {
_intervals . forEach ( id => _origClearInterval ( id ) ) ;
_intervals = [ ] ;
}
// ── Cleanup state left behind by the outgoing page ────────────────────────
function cleanupPage ( ) {
clearTrackedIntervals ( ) ;
// DataTables 2.x
if ( window . DataTable ) {
try { DataTable . tables ( { api : true } ) . destroy ( ) ; } catch ( _ ) { }
}
// Dropzone
if ( window . Dropzone ) {
try { [ ... Dropzone . instances ] . forEach ( dz => dz . destroy ( ) ) ; } catch ( _ ) { }
}
// Bootstrap modals – hide any open one and remove leftover backdrops
document . querySelectorAll ( '.modal.show' ) . forEach ( el => {
try { bootstrap . Modal . getInstance ( el ) ? . hide ( ) ; } catch ( _ ) { }
} ) ;
document . querySelectorAll ( '.modal-backdrop' ) . forEach ( el => el . remove ( ) ) ;
document . body . classList . remove ( 'modal-open' ) ;
document . body . style . removeProperty ( 'padding-right' ) ;
}
// ── Per-page inline <style> management ───────────────────────────────────
const STYLE _ID = 'ajax-page-style' ;
function setPageStyles ( cssText ) {
let tag = document . getElementById ( STYLE _ID ) ;
if ( ! cssText ) { tag ? . remove ( ) ; return ; }
if ( ! tag ) {
tag = document . createElement ( 'style' ) ;
tag . id = STYLE _ID ;
document . head . appendChild ( tag ) ;
}
tag . textContent = cssText ;
}
// ── Load external <script src> tags not already on the page ──────────────
const _loadedSrcs = new Set (
[ ... document . querySelectorAll ( 'script[src]' ) ] . map ( s => s . src )
) ;
function loadScript ( src ) {
const abs = new URL ( src , location . href ) . href ;
if ( _loadedSrcs . has ( abs ) ) return Promise . resolve ( ) ;
_loadedSrcs . add ( abs ) ;
return new Promise ( ( resolve , reject ) => {
const s = document . createElement ( 'script' ) ;
s . src = src ;
s . onload = resolve ;
s . onerror = reject ;
document . head . appendChild ( s ) ;
} ) ;
}
// ── Load external <link rel="stylesheet"> tags not already on the page ───
const _loadedHrefs = new Set (
[ ... document . querySelectorAll ( 'link[rel="stylesheet"]' ) ] . map ( l => l . href )
) ;
function loadStylesheet ( href ) {
const abs = new URL ( href , location . href ) . href ;
if ( _loadedHrefs . has ( abs ) ) return Promise . resolve ( ) ;
_loadedHrefs . add ( abs ) ;
return new Promise ( ( resolve , reject ) => {
const l = document . createElement ( 'link' ) ;
l . rel = 'stylesheet' ;
l . href = href ;
l . onload = resolve ;
l . onerror = reject ;
document . head . appendChild ( l ) ;
} ) ;
}
// ── Re-execute inline scripts from the fetched document ──────────────────
function runInlineScripts ( doc ) {
doc . querySelectorAll ( 'body script:not([src])' ) . forEach ( old => {
const code = old . textContent ;
// Wrap in an IIFE so top-level `let`/`const` are function-scoped and
// don't collide with bindings from a previous visit to the same page.
// Then re-expose every named function declaration to `window` so that
// inline onclick="foo()" handlers continue to resolve them globally.
const funcNames = [
... code . matchAll ( /^(?:async\s+)?function\s+(\w+)\s*\(/gm ) ,
] . map ( m => m [ 1 ] ) ;
const expose = funcNames
. map ( n => ` try{window. ${ n } = ${ n } ;}catch(_){} ` )
. join ( '\n' ) ;
const s = document . createElement ( 'script' ) ;
s . textContent = ` (function(){ \n ${ code } \n ${ expose } \n })(); ` ;
document . head . appendChild ( s ) ;
s . remove ( ) ;
} ) ;
}
// ── Update which nav link is highlighted as active ────────────────────────
function setActiveNav ( filename ) {
document . querySelectorAll ( '.nav-link-custom, .bottom-nav a' ) . forEach ( el => {
const hf = ( el . getAttribute ( 'href' ) || '' ) . split ( '/' ) . pop ( ) ;
el . classList . toggle ( 'active' , hf === filename ) ;
} ) ;
}
// ── Core AJAX navigation ──────────────────────────────────────────────────
let _navigating = false ;
async function navigateTo ( href ) {
if ( _navigating ) return ;
_navigating = true ;
const mainInner = document . querySelector ( '.main-inner' ) ;
if ( ! mainInner ) { window . location . href = href ; return ; }
// Dim content while loading
mainInner . style . transition = 'opacity 0.12s' ;
mainInner . style . opacity = '0.35' ;
try {
const r = await fetch ( href , { redirect : 'manual' } ) ;
if ( r . type === 'opaqueredirect' ) { window . location . reload ( ) ; return ; }
if ( ! r . ok ) { window . location . href = href ; return ; }
const text = await r . text ( ) ;
const doc = new DOMParser ( ) . parseFromString ( text , 'text/html' ) ;
// Load any external libs/styles the new page needs that aren't already loaded.
// Scripts must load sequentially (order matters: e.g. jQuery before DataTables).
// Stylesheets have no dependency order so can load in parallel.
for ( const s of doc . querySelectorAll ( 'script[src]' ) ) {
await loadScript ( s . getAttribute ( 'src' ) ) ;
}
await Promise . all (
[ ... doc . querySelectorAll ( 'link[rel="stylesheet"]' ) ] . map ( l => loadStylesheet ( l . getAttribute ( 'href' ) ) )
) ;
// Swap page-specific inline styles
const styles = [ ... doc . head . querySelectorAll ( 'style' ) ] . map ( s => s . textContent ) . join ( '\n' ) ;
setPageStyles ( styles || null ) ;
// Cleanup outgoing page, then inject new content
cleanupPage ( ) ;
mainInner . innerHTML = doc . querySelector ( '.main-inner' ) ? . innerHTML ? ? '' ;
document . title = doc . title ;
const filename = href . split ( '/' ) . pop ( ) ;
setActiveNav ( filename ) ;
// Execute the new page's inline scripts in order
runInlineScripts ( doc ) ;
} catch {
// Fallback to hard navigation on any error
window . location . href = href ;
} finally {
mainInner . style . opacity = '1' ;
_navigating = false ;
}
}
// ── Intercept all local .html link clicks ────────────────────────────────
document . addEventListener ( 'click' , e => {
const link = e . target . closest ( 'a[href]' ) ;
if ( ! link ) return ;
const href = link . getAttribute ( 'href' ) ;
if ( ! href || href . startsWith ( '#' ) || href . includes ( '://' ) || ! href . endsWith ( '.html' ) ) return ;
e . preventDefault ( ) ;
navigateTo ( href ) ;
} , true ) ;
// History API intentionally omitted — no pushState/popstate.
} ) ( ) ;