1/* Arabian Post AJAX navigation v47.6-header-instant - no header fade/slide during AJAX swaps */ 2(function (window, document, $) { 3 'use strict'; 4 5 /* 6 * The runtime is intentionally loaded early from header.php and may also be 7 * printed later by WordPress for standalone templates. If the early copy 8 * completed successfully, the later copy must not bind click/popstate/ 9 * prefetch handlers a second time. If the early copy failed before ready, 10 * the later copy is still allowed to initialise. 11 */ 12 if ( 13 window.AP_AJAX_NAV && 14 window.AP_AJAX_NAV.ready === true && 15 window.AP_AJAX_NAV.runtimeVersion === '47.6-header-instant' && 16 typeof window.AP_AJAX_NAV.navigate === 'function' 17 ) { 18 return; 19 } 20 21 var config = window.AP_AJAX_NAV || {}; 22 23 /* 24 * Rocket Loader-safe jQuery binding. 25 * 26 * This file can now be loaded early from header.php while Cloudflare keeps 27 * jQuery itself deferred. Never permanently capture an undefined jQuery. 28 * Refresh the local reference whenever a theme-only jQuery feature runs. 29 */ 30 function getJQuery() { 31 if (!$ && window.jQuery) { 32 $ = window.jQuery; 33 } 34 return $ || null; 35 } 36 var contentSelector = config.contentSelector || '.eltd-content'; 37 var menuSelector = config.menuSelector || '#menu-empty'; 38 var requestController = null; 39 var navigationGeneration = 0; 40 var currentUrl = window.location.href; 41 42 function sameOrigin(url) { 43 return url.origin === window.location.origin; 44 } 45 46 function normalisePath(url) { 47 var path = url.pathname.replace(/\/+$/, ''); 48 return path || '/'; 49 } 50 51 function escapeSelector(value) { 52 if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(value); 53 return String(value).replace(/([ #;?%&,.+*~\\':"!^$[\]()=>|\/@])/g, '\\$1'); 54 } 55 56 function shouldHandleLink(link, event) { 57 if (!link || !link.href || link.target === '_blank' || link.hasAttribute('download')) return false; 58 if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return false; 59 if (link.closest('[data-no-ajax], .no-ajax, .woocommerce-cart-form, .woocommerce-checkout')) return false; 60 if (link.classList.contains('no_link') || /^\s*(?:javascript:|mailto:|tel:)/i.test(link.getAttribute('href') || '')) return false; 61 62 var rawHref = (link.getAttribute('href') || '').trim(); 63 if (!rawHref || rawHref === '#') return false; 64 65 var url; 66 try { url = new URL(link.href, window.location.href); } catch (e) { return false; } 67 68 if (!sameOrigin(url) || !/^https?:$/.test(url.protocol)) return false; 69 if (url.pathname.indexOf('/wp-admin') === 0 || url.pathname.indexOf('/wp-login.php') === 0) return false; 70 if (/\.(?:jpg|jpeg|png|gif|webp|svg|pdf|zip|xml|json|mp4|mp3|docx?|xlsx?)$/i.test(url.pathname)) return false; 71 if (url.searchParams.has('add-to-cart') || url.searchParams.has('wc-ajax')) return false; 72 if (url.hash && normalisePath(url) === normalisePath(new URL(window.location.href)) && url.search === window.location.search) return false; 73 74 return true; 75 } 76 77 var progressResetTimer = null; 78 var progressCreepTimer = null; 79 var currentProgress = 0; 80 81 function stopProgressCreep() { 82 if (progressCreepTimer) { 83 window.clearTimeout(progressCreepTimer); 84 progressCreepTimer = null; 85 } 86 } 87 88 function scheduleProgressCreep() { 89 stopProgressCreep(); 90 91 function creep() { 92 if (!document.documentElement.classList.contains('ap-ajax-loading')) { 93 progressCreepTimer = null; 94 return; 95 } 96 97 /* Keep slow requests visibly alive without pretending to be complete. */ 98 if (currentProgress < 91) { 99 var remaining = 91 - currentProgress; 100 var step = Math.max(0.45, Math.min(2.45, remaining * 0.082)); 101 setProgress(currentProgress + step); 102 } 103 104 progressCreepTimer = window.setTimeout(creep, 520); 105 } 106 107 progressCreepTimer = window.setTimeout(creep, 560); 108 } 109 110 function setProgress(value, immediate) { 111 var bar = document.getElementById('progress-bar'); 112 if (!bar) return; 113 114 var progress = Math.max(0, Math.min(100, Number(value) || 0)); 115 currentProgress = Math.max(currentProgress, progress); 116 if (progress === 0 || immediate) currentProgress = progress; 117 if (progressResetTimer) { 118 window.clearTimeout(progressResetTimer); 119 progressResetTimer = null; 120 } 121 122 bar.style.transition = immediate ? 'none' : ''; 123 bar.style.width = progress + '%'; 124 bar.setAttribute('aria-valuenow', String(Math.round(progress))); 125 126 if (immediate) { 127 /* Force the initial 0% state to render before the first animated step. */ 128 void bar.offsetWidth; 129 bar.style.transition = ''; 130 } 131 } 132 133 var headerInstantReleaseRaf = 0; 134 135 function setLoading(isLoading) { 136 var root = document.documentElement; 137 138 if (isLoading) { 139 if (headerInstantReleaseRaf) { 140 window.cancelAnimationFrame(headerInstantReleaseRaf); 141 headerInstantReleaseRaf = 0; 142 } 143 144 root.classList.add('ap-ajax-header-instant'); 145 root.classList.add('ap-ajax-loading'); 146 document.body.setAttribute('aria-busy', 'true'); 147 148 setProgress(0, true); 149 window.requestAnimationFrame(function () { 150 setProgress(6); 151 scheduleProgressCreep(); 152 }); 153 return; 154 } 155 156 stopProgressCreep(); 157 root.classList.remove('ap-ajax-loading'); 158 document.body.setAttribute('aria-busy', 'false'); 159 setProgress(100); 160 161 headerInstantReleaseRaf = window.requestAnimationFrame(function () { 162 headerInstantReleaseRaf = window.requestAnimationFrame(function () { 163 root.classList.remove('ap-ajax-header-instant'); 164 headerInstantReleaseRaf = 0; 165 }); 166 }); 167 168 progressResetTimer = window.setTimeout(function () { 169 var bar = document.getElementById('progress-bar'); 170 if (bar) { 171 bar.classList.add('ap-progress-resetting'); 172 bar.style.width = '0%'; 173 currentProgress = 0; 174 window.setTimeout(function () { 175 bar.classList.remove('ap-progress-resetting'); 176 }, 40); 177 } 178 progressResetTimer = null; 179 }, 320); 180 } 181 182 var contentVersionStoragePrefix = 'apEditorialLandingVersion:'; 183 var initialDocumentContentVersion = config.contentVersion ? String(config.contentVersion) : ''; 184 var initialLandingVersions = config.landingVersions && typeof config.landingVersions === 'object' ? 185 config.landingVersions : 186 {}; 187 var versionCheckThrottleMs = Math.max(0, Number(config.versionCheckThrottleMs) || 8000); 188 var editorialFreshBudgetMs = Math.max(300, Number(config.editorialFreshBudgetMs) || 1400); 189 var editorialVersionPollMs = Math.max(10000, Number(config.editorialVersionPollMs) || 30000); 190 var cachedVersionPayload = null; 191 var cachedVersionPayloadAt = 0; 192 var dirtyLandingVersions = {}; 193 var versionCheckPromise = null; 194 var versionPollTimer = null; 195 196 function landingKeyForUrl(value) { 197 var requested; 198 try { 199 requested = new URL(value, window.location.href); 200 } catch (e) { 201 return ''; 202 } 203 204 var targets = Array.isArray(config.freshLandingUrls) && config.freshLandingUrls.length ? 205 config.freshLandingUrls : 206 [config.homeUrl || '/']; 207 208 for (var i = 0; i < targets.length; i += 1) { 209 var target;
210 try { 211 target = new URL(targets[i], window.location.origin); 212 } catch (e) { 213 continue; 214 } 215 216 if ( 217 requested.origin === target.origin && 218 normalisePath(requested) === normalisePath(target) && 219 requested.search === target.search 220 ) { 221 return requested.origin + normalisePath(target); 222 } 223 } 224 225 return ''; 226 } 227 228 function landingPathForUrl(value) { 229 var requested; 230 try { 231 requested = new URL(value, window.location.href); 232 } catch (e) { 233 return ''; 234 } 235 236 var targets = Array.isArray(config.freshLandingUrls) && config.freshLandingUrls.length ? 237 config.freshLandingUrls : 238 [config.homeUrl || '/']; 239 240 for (var i = 0; i < targets.length; i += 1) { 241 var target; 242 try { 243 target = new URL(targets[i], window.location.origin); 244 } catch (e) { 245 continue; 246 } 247 248 if ( 249 requested.origin === target.origin && 250 normalisePath(requested) === normalisePath(target) && 251 requested.search === target.search 252 ) { 253 return normalisePath(target); 254 } 255 } 256 257 return ''; 258 } 259 260 function isFreshLandingTarget(value) { 261 return Boolean(landingPathForUrl(value)); 262 } 263 264 function initialVersionForLanding(value) { 265 var path = landingPathForUrl(value); 266 if (path && initialLandingVersions[path]) { 267 return String(initialLandingVersions[path]); 268 } 269 return initialDocumentContentVersion || ''; 270 } 271 272 function versionForLandingPayload(payload, value) { 273 if (!payload || typeof payload !== 'object') return ''; 274 275 var path = landingPathForUrl(value); 276 if ( 277 path && 278 payload.versions && 279 typeof payload.versions === 'object' && 280 payload.versions[path] 281 ) { 282 return String(payload.versions[path]); 283 } 284 285 return payload.version ? String(payload.version) : ''; 286 } 287 288 function getRememberedLandingVersion(value) { 289 var key = landingKeyForUrl(value); 290 if (!key) return ''; 291 292 try { 293 return window.sessionStorage.getItem(contentVersionStoragePrefix + key) || ''; 294 } catch (e) { 295 return ''; 296 } 297 } 298 299 function rememberLandingVersion(value, version) { 300 var key = landingKeyForUrl(value); 301 if (!key || !version) return; 302 303 try { 304 window.sessionStorage.setItem(contentVersionStoragePrefix + key, String(version)); 305 } catch (e) {} 306 } 307 308 function buildAjaxRequestUrl(value, forceFresh) { 309 var requestUrl = new URL(value, window.location.href); 310 311 if (forceFresh) { 312 requestUrl.searchParams.set('_ap_editorial_fresh', String(Date.now())); 313 } 314 315 return requestUrl.href; 316 } 317 318 function fetchEditorialContentVersion(signal) { 319 var now = Date.now(); 320 321 if (!config.contentVersionUrl) { 322 return Promise.resolve({ 323 version: initialDocumentContentVersion || '', 324 versions: initialLandingVersions 325 }); 326 } 327 328 var versionUrl; 329 try { 330 versionUrl = new URL(config.contentVersionUrl, window.location.href); 331 versionUrl.searchParams.set('_apv', String(now)); 332 } catch (e) { 333 return Promise.resolve({ 334 version: initialDocumentContentVersion || '', 335 versions: initialLandingVersions 336 }); 337 } 338 339 return fetch(versionUrl.href, { 340 method: 'GET', 341 credentials: 'same-origin', 342 cache: 'no-store', 343 headers: { 344 'Accept': 'application/json', 345 'Cache-Control': 'no-cache, no-store, max-age=0', 346 'Pragma': 'no-cache' 347 }, 348 signal: signal 349 }).then(function (response) { 350 if (!response.ok) throw new Error('Content version HTTP ' + response.status); 351 return response.json(); 352 }).then(function (payload) { 353 if (!payload || typeof payload !== 'object') { 354 throw new Error('Invalid editorial version payload'); 355 } 356 cachedVersionPayload = payload; 357 cachedVersionPayloadAt = Date.now(); 358 return payload; 359 }); 360 } 361 362 function markEditorialChanges(payload) { 363 if (!payload || typeof payload !== 'object') return; 364 365 var targets = Array.isArray(config.freshLandingUrls) ? config.freshLandingUrls : [];
366 targets.forEach(function (targetUrl) { 367 var path = landingPathForUrl(targetUrl); 368 if (!path) return; 369 370 var incoming = versionForLandingPayload(payload, targetUrl); 371 if (!incoming) return; 372 373 var remembered = getRememberedLandingVersion(targetUrl) || initialVersionForLanding(targetUrl); 374 375 if (remembered && incoming !== remembered) { 376 dirtyLandingVersions[path] = incoming; 377 } else if (remembered && incoming === remembered) { 378 delete dirtyLandingVersions[path]; 379 } 380 }); 381 } 382 383 function runEditorialVersionCheck() { 384 if (versionCheckPromise) return versionCheckPromise; 385 386 var controller = new AbortController(); 387 versionCheckPromise = fetchEditorialContentVersion(controller.signal) 388 .then(function (payload) { 389 markEditorialChanges(payload); 390 return payload; 391 }) 392 .catch(function () { 393 return cachedVersionPayload || null; 394 }) 395 .then(function (payload) { 396 versionCheckPromise = null; 397 return payload; 398 }); 399 400 return versionCheckPromise; 401 } 402 403 function scheduleEditorialVersionCheck(delay) { 404 if (versionPollTimer) window.clearTimeout(versionPollTimer); 405 406 versionPollTimer = window.setTimeout(function poll() { 407 runEditorialVersionCheck().then(function () { 408 scheduleEditorialVersionCheck(editorialVersionPollMs); 409 }); 410 }, Math.max(0, Number(delay) || 0)); 411 } 412 413 function landingDirtyVersion(value) { 414 var path = landingPathForUrl(value); 415 return path && dirtyLandingVersions[path] ? String(dirtyLandingVersions[path]) : ''; 416 } 417 418 function markLandingFresh(value, version) { 419 var path = landingPathForUrl(value); 420 if (!path || !version) return; 421 422 rememberLandingVersion(value, version); 423 if (dirtyLandingVersions[path] === String(version)) { 424 delete dirtyLandingVersions[path]; 425 } 426 } 427 428 function fetchHtmlWithProgress(url, signal, attempt, forceFresh) { 429 /* 430 * Do not impose a client-side timeout. A genuinely slow server request is 431 * allowed to remain alive while the progress bar creeps forward. If the 432 * server/cache actually fails, retry once and only then hand the URL back 433 * to the browser as a normal navigation so the user is never stranded. 434 */ 435 attempt = attempt || 0; 436 setProgress(attempt ? Math.max(currentProgress, 58) : 12); 437 438 var freshEditorialRequest = Boolean(forceFresh && isFreshLandingTarget(url)); 439 var requestUrl = buildAjaxRequestUrl(url, freshEditorialRequest); 440 441 return fetch(requestUrl, { 442 method: 'GET', 443 credentials: 'same-origin', 444 headers: (function () { 445 var headers = { 446 'Accept': 'text/html,application/xhtml+xml' 447 }; 448 449 if (freshEditorialRequest) { 450 headers['Cache-Control'] = 'no-cache, no-store, max-age=0'; 451 headers['Pragma'] = 'no-cache'; 452 headers['X-AP-Editorial-Fresh'] = '1'; 453 } 454 455 return headers; 456 })(), 457 cache: freshEditorialRequest ? 'no-store' : (attempt ? 'reload' : 'default'), 458 signal: signal 459 }).then(function (response) { 460 if (!response.ok) { 461 var error = new Error('HTTP ' + response.status); 462 error.status = response.status; 463 throw error; 464 } 465 setProgress(Math.max(currentProgress, 54)); 466 return response.text(); 467 }).then(function (html) { 468 if (!html || html.length < 128) throw new Error('Empty or incomplete HTML response'); 469 setProgress(Math.max(currentProgress, 68)); 470 return html; 471 }).catch(function (error) { 472 if (error && error.name === 'AbortError') throw error; 473 474 var status = Number(error && error.status || 0); 475 var transient = !status || status === 408 || status === 425 || status === 429 || status >= 500; 476 477 if (attempt === 0 && transient && !signal.aborted) { 478 setProgress(Math.max(currentProgress, 62)); 479 return new Promise(function (resolve) { 480 window.setTimeout(resolve, 220); 481 }).then(function () { 482 if (signal.aborted) { 483 var aborted = new Error('Navigation aborted'); 484 aborted.name = 'AbortError'; 485 throw aborted; 486 } 487 return fetchHtmlWithProgress(url, signal, 1, forceFresh); 488 }); 489 } 490 491 throw error; 492 }); 493 } 494 495 function fetchFreshHtmlSilently(url, signal) { 496 /* 497 * Fresh editorial request used only when a landing is already known dirty. 498 * It deliberately does NOT touch the progress bar. The visible bar belongs 499 * to the single normal navigation lifecycle, so users never see a second 500 * loading phase. 501 */ 502 var requestUrl = buildAjaxRequestUrl(url, true); 503 504 return fetch(requestUrl, { 505 method: 'GET', 506 credentials: 'same-origin', 507 headers: { 508 'Accept': 'text/html,application/xhtml+xml', 509 'Cache-Control': 'no-cache, no-store, max-age=0', 510 'Pragma': 'no-cache',
511 'X-AP-Editorial-Fresh': '1' 512 }, 513 cache: 'no-store', 514 signal: signal 515 }).then(function (response) { 516 if (!response.ok) { 517 var error = new Error('HTTP ' + response.status); 518 error.status = response.status; 519 throw error; 520 } 521 return response.text(); 522 }).then(function (html) { 523 if (!html || html.length < 128) { 524 throw new Error('Empty or incomplete fresh HTML response'); 525 } 526 return html; 527 }); 528 } 529 530 function absoluteAssetUrl(value, base) { 531 try { 532 var url = new URL(value, base || window.location.href); 533 url.hash = ''; 534 return url.href; 535 } catch (e) { 536 return value || ''; 537 } 538 } 539 540 function assetSet(root, selector, attribute, base) { 541 return Array.prototype.map.call(root.querySelectorAll(selector), function (node) { 542 return absoluteAssetUrl(node.getAttribute(attribute), base); 543 }).filter(Boolean); 544 } 545 546 function copyAttributes(source, target) { 547 Array.prototype.forEach.call(source.attributes || [], function (attribute) { 548 target.setAttribute(attribute.name, attribute.value); 549 }); 550 } 551 552 function loadStylesheetFromNode(incomingLink, base) { 553 return new Promise(function (resolve) { 554 var link = document.createElement('link'); 555 copyAttributes(incomingLink, link); 556 link.href = absoluteAssetUrl(incomingLink.getAttribute('href'), base); 557 link.setAttribute('data-ap-ajax-style', 'true'); 558 link.onload = resolve; 559 link.onerror = resolve; 560 document.head.appendChild(link); 561 }); 562 } 563 564 function styleTextSignature(text) { 565 var value = String(text || '').replace(/\s+/g, ' ').trim(); 566 var hash = 2166136261; 567 for (var i = 0; i < value.length; i += 1) { 568 hash ^= value.charCodeAt(i); 569 hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24); 570 } 571 return 'ap-style-' + (hash >>> 0).toString(36); 572 } 573 574 function syncInlineStyles(parsed) { 575 var incomingView = parsed.querySelector(contentSelector); 576 577 /* 578 * Replace the complete managed destination-style layer on every AJAX page. 579 * Earlier versions only appended anonymous style blocks, so styles from the 580 * previous template remained later in the cascade and could keep controlling 581 * the persistent header. The original server-rendered styles remain intact; 582 * this managed layer is rebuilt in destination order and appended last. 583 */ 584 Array.prototype.forEach.call( 585 document.querySelectorAll('style[data-ap-destination-style="true"]'), 586 function (style) { style.parentNode.removeChild(style); } 587 ); 588 589 var incomingStyles = Array.prototype.filter.call(parsed.querySelectorAll('style'), function (style) { 590 return !incomingView || !incomingView.contains(style); 591 }); 592 593 incomingStyles.forEach(function (incomingStyle, index) { 594 var text = incomingStyle.textContent || ''; 595 if (!text.trim()) return; 596 597 var style = document.createElement('style'); 598 Array.prototype.forEach.call(incomingStyle.attributes || [], function (attribute) { 599 /* Avoid duplicate DOM ids while preserving media/type/nonce attributes. */ 600 if (attribute.name.toLowerCase() !== 'id') { 601 style.setAttribute(attribute.name, attribute.value); 602 } 603 }); 604 style.setAttribute('data-ap-destination-style', 'true'); 605 style.setAttribute('data-ap-destination-style-order', String(index)); 606 if (incomingStyle.id) style.setAttribute('data-ap-source-style-id', incomingStyle.id); 607 style.textContent = text; 608 document.head.appendChild(style); 609 }); 610 } 611 612 function copyShellElementState(parsed, selector, options) { 613 var incoming = parsed.querySelector(selector); 614 var current = document.querySelector(selector); 615 if (!incoming || !current) return; 616 617 options = options || {}; 618 619 if (options.classes !== false) current.className = incoming.className; 620 621 if (options.style) { 622 if (incoming.hasAttribute('style')) current.setAttribute('style', incoming.getAttribute('style')); 623 else current.removeAttribute('style'); 624 } 625 626 Array.prototype.forEach.call(options.attributes || [], function (name) { 627 if (incoming.hasAttribute(name)) current.setAttribute(name, incoming.getAttribute(name) || ''); 628 else current.removeAttribute(name); 629 }); 630 } 631 632 function syncPersistentShellState(parsed) { 633 /* 634 * Keep the same header DOM nodes, but update the destination's classes and
635 * top-level inline state. WordPress templates often change these selectors, 636 * and their CSS depends on the destination classes even though the markup is 637 * intentionally persistent. 638 */ 639 copyShellElementState(parsed, '.eltd-wrapper', { classes: true }); 640 copyShellElementState(parsed, '.eltd-wrapper-inner', { classes: true }); 641 copyShellElementState(parsed, '#eltd-content-wrapper', { classes: true, style: true }); 642 copyShellElementState(parsed, '.eltd-page-header', { classes: true, style: true }); 643 copyShellElementState(parsed, '.eltd-mobile-header', { classes: true, style: true }); 644 copyShellElementState(parsed, '.eltd-sticky-header', { classes: true, style: true }); 645 copyShellElementState(parsed, 'footer', { classes: true, style: true }); 646 } 647 648 function ensureRequiredAssets(parsed) { 649 isolateHomeToSingleStyles(parsed); 650 651 var incomingBase = parsed.baseURI || window.location.href; 652 var currentStyles = assetSet(document, 'link[rel="stylesheet"][href]', 'href'); 653 var incomingLinks = Array.prototype.slice.call(parsed.querySelectorAll('link[rel="stylesheet"][href]')); 654 655 var missingLinks = incomingLinks.filter(function (link) { 656 var url = absoluteAssetUrl(link.getAttribute('href'), incomingBase); 657 if (!url || currentStyles.indexOf(url) !== -1) return false; 658 currentStyles.push(url); 659 return true; 660 }); 661 662 /* WordPress templates and widgets frequently print essential CSS as inline 663 style blocks rather than linked files. Carry those blocks forward before 664 swapping content, while leaving existing global/header CSS in place. */ 665 syncInlineStyles(parsed); 666 667 /* Never inject all fetched scripts: doing so would reinitialise the preserved 668 header. Only missing destination stylesheets are loaded here. */ 669 return Promise.all(missingLinks.map(function (link) { 670 return loadStylesheetFromNode(link, incomingBase); 671 })); 672 } 673 674 function elementSignature(root, selector) { 675 var element = root.querySelector(selector); 676 if (!element) return 'missing'; 677 678 /* Page-specific classes frequently differ between archives and single posts. 679 They do not mean that the shared header/footer DOM is incompatible. */ 680 return element.tagName + '|' + String(element.id || ''); 681 } 682 683 function isHomeContext(body) { 684 if (!body) return false; 685 return body.classList.contains('home') || 686 body.classList.contains('blog') || 687 body.classList.contains('front-page') || 688 body.classList.contains('page-template-default-home'); 689 } 690 691 function linkedStylesheetSet(root, base) { 692 return assetSet(root, 'link[rel="stylesheet"][href]', 'href', base).sort(); 693 } 694 695 function isSinglePostContext(body) { 696 if (!body) return false; 697 return body.classList.contains('single-post') || 698 body.classList.contains('single') || 699 body.classList.contains('singular'); 700 } 701 702 function isPersistentGlobalStylesheet(link) { 703 var id = String(link.id || '').toLowerCase(); 704 var href = String(link.getAttribute('href') || '').toLowerCase(); 705 var token = id + ' ' + href; 706 707 return /(?:modules(?:-responsive)?\.min\.css|plugins\.min\.css|style_dynamic(?:_responsive)?\.css|\/style\.css(?:\?|$)|ap-ajax-navigation\.css|wp-block-library|global-styles|classic-theme-styles|dashicons|admin-bar|font-?awesome|elegant-icons|ionicons|google-font|fonts\.googleapis|revslider|rs6\.css)/i.test(token); 708 } 709 710 function isPersistentGlobalInlineStyle(style) { 711 var id = String(style.id || '').toLowerCase(); 712 return /(?:global-styles|wp-block-library|classic-theme-styles|admin-bar|emoji|custom-background|eltd-style-dynamic)/i.test(id); 713 } 714 715 /* 716 * A direct homepage request can contain homepage-only linked and inline CSS. 717 * Because the header document is intentionally preserved, those original 718 * source-page rules otherwise remain active after the body becomes a single 719 * post. Suspend only source styles that are absent from the fetched single 720 * page, while never touching the theme's global CSS. Suspended rules are 721 * restored automatically when a later destination contains them again. 722 */ 723 function isolateHomeToSingleStyles(parsed) { 724 var homeToSingle = isHomeContext(document.body) && isSinglePostContext(parsed.body); 725 var incomingBase = parsed.baseURI || window.location.href; 726 var incomingHrefs = new Set(assetSet(parsed, 'link[rel="stylesheet"][href]', 'href', incomingBase)); 727 728 Array.prototype.forEach.call(document.querySelectorAll('link[rel="stylesheet"][href]'), function (link) { 729 var href = absoluteAssetUrl(link.getAttribute('href'), window.location.href); 730 var required = incomingHrefs.has(href); 731 732 if (required && link.hasAttribute('data-ap-home-style-suspended')) { 733 link.disabled = false;
734 link.removeAttribute('data-ap-home-style-suspended'); 735 return; 736 } 737 738 if (homeToSingle && !required && !isPersistentGlobalStylesheet(link)) { 739 link.disabled = true; 740 link.setAttribute('data-ap-home-style-suspended', 'true'); 741 } 742 }); 743 744 var incomingView = parsed.querySelector(contentSelector); 745 var incomingSignatures = new Set(); 746 Array.prototype.forEach.call(parsed.querySelectorAll('style'), function (style) { 747 if (incomingView && incomingView.contains(style)) return; 748 incomingSignatures.add(styleTextSignature(style.textContent || '')); 749 }); 750 751 var currentView = document.querySelector(contentSelector); 752 Array.prototype.forEach.call(document.querySelectorAll('style'), function (style) { 753 if (style.getAttribute('data-ap-destination-style') === 'true') return; 754 if (currentView && currentView.contains(style)) return; 755 756 var signature = styleTextSignature(style.textContent || ''); 757 var required = incomingSignatures.has(signature); 758 759 if (required && style.hasAttribute('data-ap-home-style-suspended')) { 760 var oldMedia = style.getAttribute('data-ap-old-media'); 761 if (oldMedia !== null && oldMedia !== '') style.setAttribute('media', oldMedia); 762 else style.removeAttribute('media'); 763 style.removeAttribute('data-ap-old-media'); 764 style.removeAttribute('data-ap-home-style-suspended'); 765 return; 766 } 767 768 if (homeToSingle && !required && !isPersistentGlobalInlineStyle(style)) { 769 if (!style.hasAttribute('data-ap-home-style-suspended')) { 770 style.setAttribute('data-ap-old-media', style.getAttribute('media') || ''); 771 } 772 style.setAttribute('media', 'not all'); 773 style.setAttribute('data-ap-home-style-suspended', 'true'); 774 } 775 }); 776 } 777 778 function stylingContextIsCompatible(parsed) { 779 /* In persistent-shell mode the header must never be discarded. Missing 780 destination styles are loaded before content replacement, including 781 home-to-inner and inner-to-home transitions. */ 782 if (config.preserveHeaderShell || config.neverHardReload) return true; 783 784 if (isHomeContext(document.body) !== isHomeContext(parsed.body)) return false; 785 786 var currentStyles = linkedStylesheetSet(document, window.location.href); 787 var incomingStyles = linkedStylesheetSet(parsed, parsed.baseURI || window.location.href); 788 789 /* Missing destination styles are safe to load; a radically different core 790 stylesheet set is not. Compare only theme/plugin CSS basenames. */ 791 function coreNames(list) { 792 return list.map(function (url) { 793 try { return new URL(url).pathname.split('/').pop().replace(/\?.*$/, ''); } 794 catch (e) { return url; } 795 }).filter(function (name) { 796 return /(?:modules|plugins|style_dynamic|responsive|woocommerce|js_composer|visual-composer)/i.test(name); 797 }).sort().join('|'); 798 } 799 800 return coreNames(currentStyles) === coreNames(incomingStyles); 801 } 802 803 function structureIsCompatible(parsed) { 804 var incoming = parsed.querySelector(contentSelector); 805 var current = document.querySelector(contentSelector); 806 if (!incoming || !current || incoming.tagName !== current.tagName) return false; 807 808 /* Different WordPress templates may enqueue different page CSS. Missing 809 destination styles are loaded by ensureRequiredAssets(), while the live 810 header shell remains untouched. Do not reject navigation solely because 811 the current and destination stylesheet lists differ. */ 812 813 /* Header, mobile header, footer, side area and wrapper are deliberately not 814 replaced. If any of those structures differ, use a normal page load. */ 815 var structuralSelectors = [ 816 '.eltd-wrapper', 817 '.eltd-wrapper-inner', 818 '#eltd-content-wrapper', 819 '.eltd-page-header', 820 'footer' 821 ]; 822 823 if (!(config.preserveHeaderShell || config.neverHardReload) && 824 !structuralSelectors.every(function (selector) { 825 return elementSignature(document, selector) === elementSignature(parsed, selector); 826 })) { 827 if (config.debug && window.console) { 828 console.warn('[AP AJAX] Shared shell mismatch; using normal navigation.'); 829 } 830 return false;
831 } 832 833 /* 834 * Custom archive/rewrite routes may render a different mobile-menu copy or 835 * omit the optional side-menu container. Those nodes are outside 836 * contentSelector and are not replaced, so they must not force a full reload. 837 */ 838 839 /* Do not AJAX into or out of deliberately standalone/special templates. */ 840 var specialBodyClasses = [ 841 'page-template-landing-page', 842 'page-template-landing-page-php', 843 'woocommerce-checkout', 844 'woocommerce-cart' 845 ]; 846 if (!(config.preserveHeaderShell || config.neverHardReload) && specialBodyClasses.some(function (className) { 847 return document.body.classList.contains(className) || (parsed.body && parsed.body.classList.contains(className)); 848 })) return false; 849 850 return true; 851 } 852 853 function isRepeatablePageLoader(src) { 854 var absolute = absoluteAssetUrl(src || ''); 855 856 /* 857 * These scripts are page-local renderers rather than one-time application 858 * bundles. They must execute again after AJAX inserts fresh ad placeholders. 859 * Keep this allow-list deliberately narrow so theme/plugin scripts are never 860 * duplicated or reinitialised accidentally. 861 */ 862 return /\/www\/delivery\/asyncjs\.php(?:[?#]|$)/i.test(absolute) || 863 /\/js\/[a-f0-9]{16,}\.js(?:[?#]|$)/i.test(absolute); 864 } 865 866 function executeScripts(container) { 867 var scripts = Array.prototype.slice.call(container.querySelectorAll('script')); 868 scripts.forEach(function (oldScript) { 869 var src = oldScript.getAttribute('src'); 870 if (src) { 871 var absolute = absoluteAssetUrl(src); 872 var repeatablePageLoader = isRepeatablePageLoader(absolute); 873 var alreadyLoaded = Array.prototype.some.call(document.querySelectorAll('script[src]'), function (script) { 874 return absoluteAssetUrl(script.getAttribute('src')) === absolute; 875 }); 876 if (alreadyLoaded && !repeatablePageLoader) { 877 oldScript.remove(); 878 return; 879 } 880 } 881 882 var newScript = document.createElement('script'); 883 Array.prototype.slice.call(oldScript.attributes).forEach(function (attr) { 884 newScript.setAttribute(attr.name, attr.value); 885 }); 886 if (!src) newScript.text = oldScript.textContent; 887 oldScript.parentNode.replaceChild(newScript, oldScript); 888 }); 889 } 890 891 function syncOneMenu(incomingMenu, currentMenu) { 892 if (!incomingMenu || !currentMenu) return; 893 894 var currentItems = currentMenu.querySelectorAll('[id^="nav-menu-item-"]'); 895 Array.prototype.forEach.call(currentItems, function (item) { 896 var incomingItem = incomingMenu.querySelector('#' + escapeSelector(item.id)); 897 if (!incomingItem) return; 898 item.className = incomingItem.className; 899 900 var currentLink = item.firstElementChild && item.firstElementChild.tagName === 'A' ? item.firstElementChild : null; 901 var incomingLink = incomingItem.firstElementChild && incomingItem.firstElementChild.tagName === 'A' ? incomingItem.firstElementChild : null; 902 if (currentLink && incomingLink) currentLink.className = incomingLink.className; 903 }); 904 } 905 906 function syncMenuState(parsed) { 907 var incomingPrimary = parsed.querySelector(menuSelector); 908 var currentPrimary = document.querySelector(menuSelector); 909 syncOneMenu(incomingPrimary, currentPrimary); 910 911 /* The theme can render desktop, sticky and mobile copies of the same menu. */ 912 var currentMenus = document.querySelectorAll('nav ul, .eltd-mobile-nav ul'); 913 Array.prototype.forEach.call(currentMenus, function (currentMenu) { 914 if (currentMenu === currentPrimary || !currentMenu.id) return; 915 syncOneMenu(parsed.getElementById(currentMenu.id), currentMenu); 916 }); 917 } 918 919 function syncHead(parsed) { 920 document.title = parsed.title || document.title; 921 922 ['canonical', 'prev', 'next'].forEach(function (rel) { 923 var incoming = parsed.querySelector('link[rel="' + rel + '"]'); 924 var current = document.querySelector('link[rel="' + rel + '"]'); 925 if (incoming && current) current.href = incoming.href; 926 else if (incoming && !current) document.head.appendChild(incoming.cloneNode(true)); 927 else if (!incoming && current && rel !== 'canonical') current.remove(); 928 }); 929 930 ['description', 'robots'].forEach(function (name) { 931 var incoming = parsed.querySelector('meta[name="' + name + '"]'); 932 var current = document.querySelector('meta[name="' + name + '"]'); 933 if (incoming && current) current.setAttribute('content', incoming.getAttribute('content') || ''); 934 else if (incoming && !current) document.head.appendChild(incoming.cloneNode(true)); 935 else if (!incoming && current && name !== 'robots') current.remove(); 936 }); 937 } 938 939 function recalcUncoveringFooter() { 940 var footer = document.querySelector('footer'); 941 var wrapper = document.getElementById('eltd-content-wrapper'); 942 if (!footer) return; 943 944 if (document.body.classList.contains('eltd-uncovering-footer') && wrapper && !document.documentElement.classList.contains('touch')) { 945 var inner = footer.querySelector('.eltd-footer-inner'); 946 var height = inner ? inner.getBoundingClientRect().height : footer.getBoundingClientRect().height; 947 wrapper.style.marginBottom = height + 'px'; 948 footer.style.height = height + 'px'; 949 footer.style.visibility = 'visible'; 950 wrapper.style.borderBottom = '2px solid #dddcdc'; 951 wrapper.style.backgroundColor = window.getComputedStyle(document.body).backgroundColor; 952 } else { 953 footer.style.height = ''; 954 footer.style.visibility = ''; 955 if (wrapper) { 956 wrapper.style.marginBottom = ''; 957 wrapper.style.borderBottom = ''; 958 wrapper.style.backgroundColor = ''; 959 } 960 } 961 } 962 963 964 965 function initialisePreservedHeaderMenus() { 966 $ = getJQuery(); 967 if (!$) return; 968 969 var itemSelector = '.eltd-page-header .eltd-drop-down > ul > li.eltd-menu-has-sub, ' + 970 '.eltd-page-header .eltd-drop-down > ul > li.menu-item-has-children'; 971 972 /* 973 * The header is deliberately preserved during AJAX navigation. Some cached 974 * pages leave visibility:hidden and overflow:hidden inline on the preserved 975 * submenu. Those inline values override the theme's hover CSS even when the 976 * normal eltd-drop-down-start class is added. Remove only those stale values 977 * and provide namespaced hover handling for both main and sticky menus. 978 */ 979 function normalise($item) { 980 var $second = $item.children('.eltd-menu-second'); 981 if (!$second.length) return $second; 982 983 var node = $second.get(0); 984 node.style.removeProperty('visibility'); 985 node.style.removeProperty('overflow'); 986 987 if (!$second.data('ap-natural-height')) { 988 var inner = $second.children('.eltd-menu-inner').get(0); 989 var natural = inner ? inner.scrollHeight : node.scrollHeight; 990 if (natural > 0) $second.data('ap-natural-height', natural); 991 } 992 993 return $second; 994 } 995 996 $(itemSelector).each(function () { 997 normalise($(this)); 998 }); 999 1000 $(document) 1001 .off('.apPreservedMenu') 1002 .on('mouseenter.apPreservedMenu focusin.apPreservedMenu', itemSelector, function () { 1003 var $item = $(this); 1004 var $second = normalise($item); 1005 if (!$second.length) return; 1006 1007 var height = $second.data('ap-natural-height'); 1008 if (!height) { 1009 var inner = $second.children('.eltd-menu-inner').get(0); 1010 height = inner ? inner.scrollHeight : $second.get(0).scrollHeight; 1011 if (height > 0) $second.data('ap-natural-height', height); 1012 } 1013 1014 $second.addClass('eltd-drop-down-start').css({ 1015 height: height ? height + 'px' : 'auto', 1016 opacity: '1', 1017 visibility: 'visible', 1018 overflow: 'visible' 1019 }); 1020 }) 1021 .on('mouseleave.apPreservedMenu focusout.apPreservedMenu', itemSelector, function (event) { 1022 if (event.type === 'focusout' && event.relatedTarget && this.contains(event.relatedTarget)) return; 1023 1024 var $second = $(this).children('.eltd-menu-second'); 1025 if (!$second.length) return; 1026 1027 $second.removeClass('eltd-drop-down-start').css({ 1028 height: '0px', 1029 opacity: '0' 1030 }); 1031 $second.get(0).style.removeProperty('visibility'); 1032 $second.get(0).style.removeProperty('overflow'); 1033 }); 1034 } 1035 1036 function closeOpenPreservedSubmenus() { 1037 $ = getJQuery(); 1038 if (!$) return; 1039 1040 var $openMenus = $('.eltd-page-header .eltd-menu-second').filter(function () { 1041 var node = this; 1042 var style = window.getComputedStyle(node); 1043 return node.classList.contains('eltd-drop-down-start') || 1044 parseFloat(style.opacity || '0') > 0 || 1045 node.getBoundingClientRect().height > 1; 1046 }); 1047 1048 if (!$openMenus.length) return; 1049 1050 $openMenus.each(function () { 1051 var $second = $(this); 1052 $second.removeClass('eltd-drop-down-start').css({ 1053 height: '0px', 1054 opacity: '0' 1055 }); 1056 this.style.removeProperty('visibility'); 1057 this.style.removeProperty('overflow'); 1058 }); 1059 } 1060 1061 function reinitialiseFeatures() { 1062 /* 1063 * Header-critical AJAX may execute before Rocket Loader releases jQuery. 1064 * The page swap itself does not require jQuery, so never turn that into a 1065 * hard-navigation fallback. Run Newsroom/jQuery enhancement hooks only 1066 * when jQuery is actually available. 1067 */ 1068 $ = getJQuery(); 1069 1070 try { 1071 if ($ && window.eltd) { 1072 window.eltd.body = $(document.body); 1073 window.eltd.windowWidth = $(window).width(); 1074 window.eltd.windowHeight = $(window).height(); 1075 } 1076 1077 var common = window.eltd && window.eltd.modules && window.eltd.modules.common; 1078 if (common) { 1079 ['eltdFluidVideo', 'eltdPrettyPhoto', 'eltdPostGallerySlider', 'eltdPreloadBackgroun
1079ds', 1080 'eltdInitAudioPlayer', 'eltdInitSelfHostedVideoPlayer', 'eltdSelfHostedVideoSize', 'eltdInitParallax'] 1081 .forEach(function (name) { 1082 if (typeof common[name] === 'function') { 1083 try { common[name](); } catch (e) {} 1084 } 1085 }); 1086 } 1087 1088 if (typeof window.vc_js === 'function') { 1089 try { window.vc_js(); } catch (e) {} 1090 } 1091 1092 if (window.Waypoint && typeof window.Waypoint.refreshAll === 'function') { 1093 try { window.Waypoint.refreshAll(); } catch (e) {} 1094 } 1095 1096 if ($) { 1097 try { $(document.body).trigger('post-load'); } catch (e) {} 1098 try { $(window).trigger('resize').trigger('scroll'); } catch (e) {} 1099 } else { 1100 /* 1101 * Native wake-up is enough for the custom sticky/sidebar runtimes. 1102 * A short delayed pass catches jQuery once Rocket Loader releases it. 1103 */ 1104 try { window.dispatchEvent(new Event('resize')); } catch (e) {} 1105 try { window.dispatchEvent(new Event('scroll')); } catch (e) {} 1106 1107 window.setTimeout(function () { 1108 $ = getJQuery(); 1109 1110 if ($) { 1111 try { $(document.body).trigger('post-load'); } catch (e) {} 1112 try { $(window).trigger('resize').trigger('scroll'); } catch (e) {} 1113 try { initialisePreservedHeaderMenus(); } catch (e) {} 1114 } 1115 }, 120); 1116 } 1117 } finally { 1118 window.requestAnimationFrame(recalcUncoveringFooter); 1119 window.setTimeout(recalcUncoveringFooter, 120); 1120 window.setTimeout(recalcUncoveringFooter, 420); 1121 } 1122 } 1123 1124 function syncBodyClasses(parsedBody) { 1125 if (!parsedBody) return; 1126 1127 /* 1128 * WordPress template classes must follow the destination so home, single, 1129 * category, archive and custom templates retain their CSS. Runtime shell 1130 * classes are preserved because they belong to the already-mounted header. 1131 */ 1132 var preserve = [ 1133 'admin-bar', 'logged-in', 'customize-support', 1134 'eltd-side-menu-opened', 'eltd-mobile-nav-opened', 1135 'ap-ajax-loading' 1136 ]; 1137 var runtime = preserve.filter(function (name) { 1138 return document.body.classList.contains(name); 1139 }); 1140 1141 document.body.className = parsedBody.className || ''; 1142 runtime.forEach(function (name) { document.body.classList.add(name); }); 1143 } 1144 1145 function syncBodyAttributes(parsedBody) { 1146 if (!parsedBody) return; 1147 1148 /* Keep accessibility/schema attributes in step with the destination while 1149 never replacing the body or any persistent header descendants. */ 1150 ['itemscope', 'itemtype', 'itemid', 'dir'].forEach(function (name) { 1151 if (parsedBody.hasAttribute(name)) { 1152 document.body.setAttribute(name, parsedBody.getAttribute(name) || ''); 1153 } else if (name !== 'dir') { 1154 document.body.removeAttribute(name); 1155 } 1156 }); 1157 } 1158 1159 function markPersistentShell() { 1160 var header = document.querySelector('.eltd-page-header'); 1161 var mobileHeader = document.querySelector('.eltd-mobile-header'); 1162 var content = document.querySelector(contentSelector); 1163 if (header) header.setAttribute('data-ap-persistent-header', 'true'); 1164 if (mobileHeader) mobileHeader.setAttribute('data-ap-persistent-header', 'true'); 1165 if (content) content.setAttribute('data-ap-ajax-root', 'true'); 1166 } 1167 1168 /* 1169 * Close WP Responsive Menu through the plugin's own click handler. 1170 * No plugin classes, styles, overlay states or body states are changed here. 1171 */ 1172 function requestPluginMobileMenuClose() { 1173 var panel = document.getElementById('mg-wprm-wrap'); 1174 var bar = document.getElementById('wprmenu_bar'); 1175 var overlay = document.querySelector('.wprm-overlay'); 1176 var hamburger = bar && bar.querySelector('.hamburger'); 1177 1178 var isOpen = Boolean( 1179 (panel && panel.classList.contains('cbp-spmenu-open')) || 1180 (bar && bar.classList.contains('active')) || 1181 (overlay && overlay.classList.contains('active')) || 1182 (hamburger && hamburger.classList.contains('is-active')) 1183 ); 1184 1185 if (!isOpen || !bar) return; 1186 1187 /* Let the successful page replacement settle before the plugin closes. */ 1188 window.requestAnimationFrame(function () { 1189 window.requestAnimationFrame(function () { 1190 try { 1191 bar.dispatchEvent(new MouseEvent('click', { 1192 bubbles: true, 1193 cancelable: true, 1194 view: window 1195 })); 1196 } catch (error) { 1197 /* Older mobile browsers still support the native click method. */ 1198 if (typeof bar.click === 'function') bar.click(); 1199 } 1200 }); 1201 }); 1202 } 1203 1204 function updateDocument(parsed, url, pushState, closeSubmenuAfterLoad, closeMobileMenuAfterLoad) {
1205 if (!structureIsCompatible(parsed)) throw new Error('AJAX structure mismatch'); 1206 1207 var incoming = parsed.querySelector(contentSelector); 1208 var current = document.querySelector(contentSelector); 1209 var imported = document.importNode(incoming, true); 1210 1211 current.replaceWith(imported); 1212 syncHead(parsed); 1213 syncMenuState(parsed); 1214 1215 syncBodyClasses(parsed.body); 1216 syncBodyAttributes(parsed.body); 1217 syncPersistentShellState(parsed); 1218 markPersistentShell(); 1219 1220 executeScripts(imported); 1221 1222 if (pushState) window.history.pushState({ apAjax: true }, '', url); 1223 currentUrl = url; 1224 window.scrollTo(0, 0); 1225 1226 if (closeSubmenuAfterLoad) closeOpenPreservedSubmenus(); 1227 if (closeMobileMenuAfterLoad) requestPluginMobileMenuClose(); 1228 reinitialiseFeatures(); 1229 1230 var loadedEvent; 1231 try { 1232 loadedEvent = new CustomEvent('ap:ajax:loaded', { detail: { url: url } }); 1233 } catch (e) { 1234 loadedEvent = document.createEvent('CustomEvent'); 1235 loadedEvent.initCustomEvent('ap:ajax:loaded', false, false, { url: url }); 1236 } 1237 document.dispatchEvent(loadedEvent); 1238 1239 $ = getJQuery(); 1240 if ($) { 1241 try { 1242 $(document).trigger('ap_ajax_loaded', [url]); 1243 } catch (e) {} 1244 } 1245 } 1246 1247 function isArchiveContext(body) { 1248 if (!body) return false; 1249 return body.classList.contains('category') || 1250 body.classList.contains('archive') || 1251 body.classList.contains('search') || 1252 body.classList.contains('blog'); 1253 } 1254 1255 function hasPagination(root) { 1256 if (!root) return false; 1257 return !!root.querySelector( 1258 '.eltd-pagination, .eltd-blog-pagination, .pagination, .page-numbers, ' + 1259 'nav.navigation.pagination, .wp-pagenavi, [class*="pagination"]' 1260 ); 1261 } 1262 1263 function validateFetchedDocument(parsed, url) { 1264 var incoming = parsed.querySelector(contentSelector); 1265 if (!incoming) throw new Error('Destination content wrapper missing'); 1266 1267 /* A cached archive response must retain its server-rendered pagination. 1268 Do not reject a genuinely one-page archive, but reject suspiciously 1269 truncated responses when the current archive visibly has pagination. */ 1270 if (isArchiveContext(parsed.body) && isArchiveContext(document.body)) { 1271 var currentContent = document.querySelector(contentSelector); 1272 if (hasPagination(currentContent) && !hasPagination(incoming)) { 1273 throw new Error('Archive response is missing pagination'); 1274 } 1275 } 1276 1277 /* Single posts must contain a recognisable post/article body. This catches 1278 cache/plugin fragment responses before the current category is replaced. */ 1279 if (isSinglePostContext(parsed.body)) { 1280 var hasPost = incoming.querySelector( 1281 'article, .eltd-blog-single, .single-post, .eltd-post-content, .eltd-post-text' 1282 ); 1283 if (!hasPost) throw new Error('Single-post response is incomplete'); 1284 } 1285 1286 return parsed; 1287 } 1288 1289 function fetchNavigationHtml(url, signal) { 1290 if (!isFreshLandingTarget(url)) { 1291 return fetchHtmlWithProgress(url, signal, 0, false); 1292 } 1293 1294 var dirtyVersion = landingDirtyVersion(url); 1295 1296 /* 1297 * No detected change = normal fast AJAX immediately. 1298 * The version detector runs independently in the background. 1299 */ 1300 if (!dirtyVersion) { 1301 return fetchHtmlWithProgress(url, signal, 0, false); 1302 } 1303 1304 /* 1305 * A real change was already detected. 1306 * 1307 * Start normal cached AJAX and fresh HTML at the SAME time. Only the normal 1308 * AJAX request drives the progress bar. The fresh request is silent, so the 1309 * user sees one continuous loading effect instead of two phases. 1310 * 1311 * If fresh HTML arrives inside the configured budget it wins. Otherwise we 1312 * use the normal cached response and keep this route dirty for a later retry. 1313 */ 1314 var freshController = new AbortController(); 1315 var fallbackTimer = null; 1316 1317 function abortFreshFromParent() { 1318 if (!freshController.signal.aborted) freshController.abort(); 1319 } 1320 1321 if (signal.aborted) { 1322 abortFreshFromParent(); 1323 } else { 1324 signal.addEventListener('abort', abortFreshFromParent, { once: true }); 1325 } 1326 1327 var normalPromise = fetchHtmlWithProgress(url, signal, 0, false); 1328 1329 var freshPromise = fetchFreshHtmlSilently(url, freshController.signal) 1330 .then(function (freshHtml) { 1331 return { type: 'fresh', html: freshHtml }; 1332 }) 1333 .catch(function () { 1334 if (signal.aborted) { 1335 var parentAbort = new Error('Navigation aborted'); 1336 parentAbort.name = 'AbortError'; 1337 throw parentAbort; 1338 } 1339 return null; 1340 }); 1341 1342 var budgetPromise = new Promise(function (resolve) { 1343 fallbackTimer = window.setTimeout(function () { 1344 resolve({ type: 'budget' }); 1345 }, editorialFreshBudgetMs); 1346 }); 1347 1348 return Promise.race([freshPromise, budgetPromise]).then(function (winner) { 1349 if (winner && winner.type === 'fresh') { 1350 if (fallbackTimer) { 1351 window.clearTimeout(fallbackTimer); 1352 fallbackTimer = null; 1353 } 1354 signal.removeEventListener('abort', abortFreshFromParent); 1355 markLandingFresh(url, dirtyVersion); 1356 return winner.html; 1357 } 1358 1359 abortFreshFromParent(); 1360 signal.removeEventListener('abort', abortFreshFromParent); 1361 return normalPromise; 1362 }); 1363 } 1364 1365 1366 var apIntentCache = new Map(); 1367 var AP_INTENT_TTL = 30000; 1368 var AP_INTENT_MAX = 6; 1369 var apPrefetchTimer = null; 1370 1371 function pruneIntentCache() { 1372 var now = Date.now(); 1373 apIntentCache.forEach(function (entry, key) { 1374 if (!entry || entry.expiresAt <= now) apIntentCache.delete(key); 1375 }); 1376 while (apIntentCache.size > AP_INTENT_MAX) { 1377 var firstKey = apIntentCache.keys().next().value;
1378 if (!firstKey) break; 1379 apIntentCache.delete(firstKey); 1380 } 1381 } 1382 1383 function eligiblePrefetchUrl(value) { 1384 var url; 1385 try { url = new URL(value, window.location.href); } catch (e) { return ''; } 1386 if (!sameOrigin(url) || !/^https?:$/.test(url.protocol)) return ''; 1387 if (isFreshLandingTarget(url.href)) return ''; 1388 if (url.pathname.indexOf('/wp-admin') === 0 || url.pathname.indexOf('/wp-login.php') === 0) return ''; 1389 if (/\.(?:jpg|jpeg|png|gif|webp|svg|pdf|zip|xml|json|mp4|mp3|docx?|xlsx?)$/i.test(url.pathname)) return ''; 1390 return url.href; 1391 } 1392 1393 function prefetchDestination(value) { 1394 var url = eligiblePrefetchUrl(value); 1395 if (!url) return; 1396 pruneIntentCache(); 1397 var existing = apIntentCache.get(url); 1398 if (existing && existing.expiresAt > Date.now()) return; 1399 1400 var entry = { html: '', promise: null, expiresAt: Date.now() + AP_INTENT_TTL }; 1401 entry.promise = fetch(url, { 1402 method: 'GET', 1403 credentials: 'same-origin', 1404 headers: { 'Accept': 'text/html,application/xhtml+xml' }, 1405 cache: 'default' 1406 }).then(function (r) { 1407 if (!r.ok) throw new Error('Prefetch HTTP ' + r.status); 1408 return r.text(); 1409 }).then(function (html) { 1410 if (!html || html.length < 128) throw new Error('Incomplete prefetch'); 1411 entry.html = html; 1412 entry.promise = null; 1413 entry.expiresAt = Date.now() + AP_INTENT_TTL; 1414 return html; 1415 }).catch(function () { 1416 apIntentCache.delete(url); 1417 return ''; 1418 }); 1419 1420 apIntentCache.set(url, entry); 1421 pruneIntentCache(); 1422 } 1423 1424 function rememberNavigationHtml(url, html) { 1425 if (!html || isFreshLandingTarget(url)) return; 1426 1427 var eligible = eligiblePrefetchUrl(url); 1428 if (!eligible) return; 1429 1430 apIntentCache.set(eligible, { 1431 html: html, 1432 promise: null, 1433 expiresAt: Date.now() + AP_INTENT_TTL 1434 }); 1435 1436 pruneIntentCache(); 1437 } 1438 1439 function cachedNavigationHtml(url, signal) { 1440 pruneIntentCache(); 1441 var entry = apIntentCache.get(url); 1442 if (!entry || entry.expiresAt <= Date.now()) return null; 1443 if (entry.html) return Promise.resolve(entry.html); 1444 if (entry.promise) { 1445 return entry.promise.then(function (html) { 1446 if (signal.aborted) { 1447 var aborted = new Error('Navigation aborted'); 1448 aborted.name = 'AbortError'; 1449 throw aborted; 1450 } 1451 return html || null; 1452 }); 1453 } 1454 return null; 1455 } 1456 1457 1458 /* 1459 * Keep the progress animation visibly continuous on very fast/prefetched pages. 1460 * The DOM must not swap while the CSS width transition is still visually 1461 * sitting around the middle of the bar. 1462 */ 1463 function waitForProgressPaint(target, delay) { 1464 setProgress(target); 1465 1466 return new Promise(function (resolve) { 1467 window.requestAnimationFrame(function () { 1468 window.setTimeout(resolve, Math.max(0, Number(delay) || 0)); 1469 }); 1470 }); 1471 } 1472 1473 function navigate(url, pushState, closeSubmenuAfterLoad, closeMobileMenuAfterLoad) { 1474 if (requestController) requestController.abort(); 1475 1476 var controller = new AbortController(); 1477 var generation = ++navigationGeneration; 1478 requestController = controller; 1479 setLoading(true); 1480 1481 var cachedHtmlPromise = isFreshLandingTarget(url) 1482 ? null 1483 : cachedNavigationHtml(url, controller.signal); 1484 1485 var htmlPromise = cachedHtmlPromise || fetchNavigationHtml(url, controller.signal); 1486 1487 htmlPromise 1488 .then(function (html) { 1489 rememberNavigationHtml(url, html); 1490 setProgress(72); 1491 var parsed = new DOMParser().parseFromString(html, 'text/html'); 1492 validateFetchedDocument(parsed, url); 1493 if (!structureIsCompatible(parsed)) throw new Error('AJAX structure mismatch'); 1494 setProgress(78); 1495 return ensureRequiredAssets(parsed).then(function () { 1496 /* 1497 * Everything needed for the destination is ready. Give the progress 1498 * bar one painted beat to visibly reach the high 90s, then swap only 1499 * .eltd-content. This preserves the persistent header/footer shell. 1500 */ 1501 return waitForProgressPaint(94, 45).then(function () { 1502 updateDocument( 1503 parsed, 1504 url, 1505 pushState, 1506 closeSubmenuAfterLoad, 1507 closeMobileMenuAfterLoad 1508 ); 1509 setProgress(98); 1510 }); 1511 }); 1512 }) 1513 .catch(function (error) { 1514 if (error.name === 'AbortError') return; 1515 1516 if (window.console && config.debug) { 1517 console.error('[AP AJAX] Navigation failed; using browser fallback.', error); 1518 } 1519 1520 var failedEvent; 1521 try { 1522 failedEvent = new CustomEvent('ap:ajax:error', { 1523 detail: { url: url, error: String(error && error.message || error), fallback: true } 1524 }); 1525 } catch (e) { 1526 failedEvent = document.createEvent('CustomEvent'); 1527 failedEvent.initCustomEvent('ap:ajax:error', false, false, { 1528 url: url, 1529 error: String(error && error.message || error), 1530 fallback: true 1531 }); 1532 } 1533 document.dispatchEvent(failedEvent); 1534 1535 /* Keep the bar visibly active until the browser takes over. */ 1536 setProgress(Math.max(currentProgress, 94)); 1537 window.setTimeout(function () { 1538 window.location.assign(url); 1539 }, 90); 1540 return; 1541 }) 1542 .then(function () { 1543 /* 1544 * Only the navigation that currently owns the progress UI may finish 1545 * it. An older aborted request must never clear/reset a newer request's 1546 * bar or loading state. 1547 */ 1548 if (generation !== navigationGeneration || requestController !== controller) { 1549 return; 1550 } 1551 1552 requestController = null;
1553 setLoading(false); 1554 }); 1555 } 1556 1557 /* Public entry point used by the tiny bootstrap in header.php. */ 1558 window.AP_AJAX_NAV = Object.assign({}, window.AP_AJAX_NAV || {}, { 1559 ready: true, 1560 runtimeVersion: '47.6-header-instant', 1561 navigate: function (url, pushState, closeSubmenuAfterLoad, closeMobileMenuAfterLoad) { 1562 navigate(url, pushState !== false, !!closeSubmenuAfterLoad, !!closeMobileMenuAfterLoad); 1563 } 1564 }); 1565 1566 1567 document.addEventListener('pointerover', function (event) { 1568 var target = event.target; 1569 if (!target || typeof target.closest !== 'function') return; 1570 var link = target.closest('a[href]'); 1571 if (!link) return; 1572 if (apPrefetchTimer) window.clearTimeout(apPrefetchTimer); 1573 apPrefetchTimer = window.setTimeout(function () { 1574 prefetchDestination(link.href); 1575 }, 45); 1576 }, { passive: true, capture: true }); 1577 1578 document.addEventListener('touchstart', function (event) { 1579 var target = event.target; 1580 if (!target || typeof target.closest !== 'function') return; 1581 var link = target.closest('a[href]'); 1582 if (link) prefetchDestination(link.href); 1583 }, { passive: true, capture: true }); 1584 1585 1586 document.addEventListener('pointerdown', function (event) { 1587 var target = event.target; 1588 if (!target || typeof target.closest !== 'function') return; 1589 var link = target.closest('a[href]'); 1590 if (!link) return; 1591 prefetchDestination(link.href); 1592 }, { 1593 passive: true, 1594 capture: true 1595 }); 1596 1597 document.addEventListener('click', function (event) { 1598 var target = event.target; 1599 if (!target || typeof target.closest !== 'function') return; 1600 var link = target.closest('a'); 1601 if (!shouldHandleLink(link, event)) return; 1602 var cameFromSubmenu = !!link.closest('.eltd-page-header .eltd-menu-second'); 1603 var cameFromPluginMobileMenu = !!link.closest('#mg-wprm-wrap'); 1604 event.preventDefault(); 1605 event.stopImmediatePropagation(); 1606 document.documentElement.setAttribute('data-ap-last-navigation', 'ajax'); 1607 navigate(link.href, true, cameFromSubmenu, cameFromPluginMobileMenu); 1608 }, true); 1609 1610 document.addEventListener('submit', function (event) { 1611 var form = event.target; 1612 if (!form || String(form.method).toLowerCase() !== 'get') return; 1613 if (form.closest('[data-no-ajax], .no-ajax')) return; 1614 if (!(form.matches('[role="search"], .searchform, .woocommerce-product-search') || form.querySelector('input[name="s"]'))) return; 1615 1616 var action = new URL(form.action || window.location.href, window.location.href); 1617 if (!sameOrigin(action)) return; 1618 1619 event.preventDefault(); 1620 var params = new URLSearchParams(new FormData(form)); 1621 action.search = params.toString(); 1622 navigate(action.toString(), true); 1623 }, true); 1624 1625 window.addEventListener('popstate', function () { 1626 if (window.location.href === currentUrl) return; 1627 navigate(window.location.href, false); 1628 }); 1629 1630 markPersistentShell(); 1631 initialisePreservedHeaderMenus(); 1632 1633 /* Background-only editorial detector: immediate first check, then every 30s. */ 1634 scheduleEditorialVersionCheck(0); 1635 1636 document.addEventListener('visibilitychange', function () { 1637 if (!document.hidden && (Date.now() - cachedVersionPayloadAt) >= editorialVersionPollMs) { 1638 runEditorialVersionCheck(); 1639 } 1640 }); 1641 1642 window.addEventListener('focus', function () { 1643 if ((Date.now() - cachedVersionPayloadAt) >= editorialVersionPollMs) { 1644 runEditorialVersionCheck(); 1645 } 1646 }); 1647 1648 window.addEventListener('load', function () { 1649 recalcUncoveringFooter(); 1650 initialisePreservedHeaderMenus(); 1651 }); 1652})(window, document, window.jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.