1(function () { 2 'use strict'; 3 4 document.addEventListener('DOMContentLoaded', function () { 5 var blocks = document.querySelectorAll('.archive-listing-block'); 6 if (!blocks.length) return; 7 8 blocks.forEach(function (block) { 9 initBlock(block); 10 }); 11 12 bindOriginMemory(); 13 applyCardTitleTooltips(document); 14 }); 15 16 /* Card titles are line-clamped; when one actually truncates, surface the 17 * full headline as a native hover tooltip on its link. Re-run after every 18 * AJAX re-render. */ 19 function applyCardTitleTooltips(scope) { 20 (scope || document).querySelectorAll('.archive-card .post__title').forEach(function (heading) { 21 var link = heading.querySelector('.post__link') || heading; 22 if (heading.scrollHeight > heading.clientHeight + 2) { 23 link.setAttribute('title', (link.textContent || '').trim()); 24 } else { 25 link.removeAttribute('title'); 26 } 27 }); 28 } 29 30 /* ââ Origin memory âââââââââââââââââââââââââââââââââââââââââââââââââ 31 * Every card/aside navigation off the listing records the exact 32 * listing URL (live search/filter/page query included) plus the page 33 * title in sessionStorage; assets/js/single-article.js reads it on the 34 * story page and upgrades the breadcrumb so "back" returns to the 35 * precise listing state. Plain navigation â never preventDefault. 36 */ 37 function bindOriginMemory() { 38 var ORIGIN_KEY = 'nam_alb_origin'; 39 40 function listingLabel() { 41 var h1 = document.querySelector('h1.entry-title') || document.querySelector('.entry-header .entry-title'); 42 var label = h1 ? h1.textContent.trim() : ''; 43 if (!label) { 44 label = (document.title || '').split(/\s[-|\u2013\u2014]\s/)[0].trim(); 45 } 46 return label || 'the archive'; 47 } 48 49 document.addEventListener('click', function (e) { 50 var link = e.target.closest('a'); 51 if (!link || !link.closest('.archive-listing-block')) return; 52 53 var card = link.closest('article.archive-card'); 54 var isCardLink = !!card && (link.classList.contains('post__link') || link.classList.contains('archive-card__media-link')); 55 var isAsideLink = link.classList.contains('aside__title'); 56 if (!isCardLink && !isAsideLink) return; 57 58 try { 59 sessionStorage.setItem(ORIGIN_KEY, JSON.stringify({ 60 url: window.location.href, 61 label: listingLabel(), 62 ts: Date.now() 63 })); 64 } catch (err) { /* storage unavailable */ } 65 }); 66 } 67 68 function initBlock(block) { 69 var configAttr = block.getAttribute('data-alb-config'); 70 if (!configAttr) return; 71 72 var config; 73 try { 74 config = JSON.parse(configAttr); 75 } catch (e) { 76 return; 77 } 78 79 var state = { 80 postTypes: [], 81 taxonomies: {}, 82 search: '', 83 paged: 1 84 }; 85 86 var resultsEl = block.querySelector('.archive-listing__results'); 87 var paginationEl = block.querySelector('.archive-listing__pagination'); 88 var filtersEl = block.querySelector('.archive-listing__filters'); 89 var controlsEl = block.querySelector('.archive-listing__controls'); 90 var searchInput = block.querySelector('[data-archive-search]'); 91 // The columns' list (grid + pagination) â NOT the lead wrapper, which 92 // also carries .archive__list for the shared card styling. 93 var listEl = block.querySelector('.archive-listing__columns .archive__list'); 94 var basePath = window.location.pathname.replace(/\/page\/\d+\/?$/, '/'); 95 96 initStateFromUrl(); 97 98 // Normalize /page/N/ URLs to query-string format so pushUrl() works 99 // consistently and the aside panel slug is derived correctly. 100 if (/\/page\/\d+\/?$/.test(window.location.pathname)) { 101 var normalizeParams = buildUrlParams(); 102 var normalizeQs = normalizeParams.toString();
103 var cleanUrl = basePath + (normalizeQs ? '?' + normalizeQs : ''); 104 window.history.replaceState(null, '', cleanUrl); 105 106 // Fix the browser tab title if it still shows the 404 title. 107 if (document.title.toLowerCase().indexOf('not found') !== -1) { 108 var pageHeading = document.querySelector('.entry-title'); 109 if (pageHeading) { 110 document.title = document.title.replace(/page not found/i, pageHeading.textContent.trim()); 111 } 112 } 113 } 114 115 syncSearchUI(); 116 bindSearch(); 117 bindPillClicks(); 118 bindShowMore(); 119 bindDropdownFilters(); 120 bindPaginationClicks(); 121 window.addEventListener('popstate', function () { 122 initStateFromUrl(); 123 syncSearchUI(); 124 syncPillUI(); 125 syncDropdownUI(); 126 fetchResults(); 127 }); 128 129 function initStateFromUrl() { 130 var params = new URLSearchParams(window.location.search); 131 132 state.paged = parseInt(params.get('paged'), 10) || 1; 133 if (state.paged <= 1) { 134 var pathMatch = window.location.pathname.match(/\/page\/(\d+)\/?$/); 135 if (pathMatch) { 136 state.paged = parseInt(pathMatch[1], 10) || 1; 137 } 138 } 139 state.search = params.get('alb_search') || ''; 140 141 state.postTypes = []; 142 if (params.has('alb_post_type')) { 143 state.postTypes = params.get('alb_post_type').split(',').filter(Boolean); 144 } 145 146 state.taxonomies = {}; 147 if (config.filterTaxonomies) { 148 config.filterTaxonomies.forEach(function (tax) { 149 var key = 'alb_' + tax; 150 if (params.has(key)) { 151 state.taxonomies[tax] = params.get(key).split(',').filter(Boolean); 152 } 153 }); 154 } 155 } 156 157 function buildUrlParams() { 158 var params = new URLSearchParams(); 159 160 if (state.search) { 161 params.set('alb_search', state.search); 162 } 163 164 if (state.postTypes.length > 0) { 165 params.set('alb_post_type', state.postTypes.join(',')); 166 } 167 168 Object.keys(state.taxonomies).forEach(function (tax) { 169 if (state.taxonomies[tax].length > 0) { 170 params.set('alb_' + tax, state.taxonomies[tax].join(',')); 171 } 172 }); 173 174 if (state.paged > 1) { 175 params.set('paged', state.paged); 176 } 177 178 return params; 179 } 180 181 function debounce(fn, delay) { 182 var timer; 183 return function () { 184 var args = arguments; 185 clearTimeout(timer); 186 timer = setTimeout(function () { 187 fn.apply(null, args); 188 }, delay); 189 }; 190 } 191 192 function pushUrl() { 193 var params = buildUrlParams(); 194 var qs = params.toString(); 195 var url = basePath + (qs ? '?' + qs : ''); 196 window.history.pushState(null, '', url); 197 } 198 199 function syncSearchUI() { 200 if (!searchInput) return; 201 searchInput.value = state.search; 202 } 203 204 function runSearch(value) { 205 state.search = value.trim(); 206 state.paged = 1; 207 pushUrl(); 208 fetchResults(); 209 } 210 211 function bindSearch() { 212 if (!controlsEl || !searchInput) return; 213 214 var debouncedSearch = debounce(function () { 215 runSearch(searchInput.value); 216 }, 350); 217 218 controlsEl.addEventListener('submit', function (e) { 219 e.preventDefault(); 220 runSearch(searchInput.value); 221 }); 222 223 searchInput.addEventListener('input', debouncedSearch); 224 } 225 226 function syncPillUI() { 227 if (!filtersEl) return; 228 229 var ptPills = filtersEl.querySelectorAll('[data-filter-type="post_type"]'); 230 ptPills.forEach(function (btn) { 231 var item = btn.closest('.archive-listing__facet-item'); 232 if (!item) return; 233 var val = btn.getAttribute('data-value'); 234 item.classList.toggle('is-active', state.postTypes.indexOf(val) !== -1); 235 }); 236 237 var taxPills = filtersEl.querySelectorAll('button[data-filter-type="taxonomy"]'); 238 taxPills.forEach(function (btn) { 239 var item = btn.closest('.archive-listing__facet-item'); 240 if (!item) return; 241 var tax = btn.getAttribute('data-taxonomy'); 242 var val = btn.getAttribute('data-value'); 243 var active = state.taxonomies[tax] && state.taxonomies[tax].indexOf(val) !== -1; 244 item.classList.toggle('is-active', active); 245 }); 246 } 247 248 function bindPillClicks() { 249 if (!filtersEl) return; 250 251 filtersEl.addEventListener('click', function (e) { 252 var pill = e.target.closest('.archive-listing__facet-pill'); 253 if (!pill) return; 254 255 var filterType = pill.getAttribute('data-filter-type'); 256 var value = pill.getAttribute('data-value'); 257 258 if (filterType === 'post_type') { 259 var idx = state.postTypes.indexOf(value); 260 if (idx === -1) { 261 state.postTypes.push(value); 262 } else { 263 state.postTypes.splice(idx, 1); 264 } 265 } else if (filterType === 'taxonomy') { 266 var tax = pill.getAttribute('data-taxonomy'); 267 if (!state.taxonomies[tax]) { 268 state.taxonomies[tax] = []; 269 } 270 var tidx = state.taxonomies[tax].indexOf(value); 271 if (tidx === -1) { 272 state.taxonomies[tax].push(value); 273 } else { 274 state.taxonomies[tax].splice(tidx, 1); 275 } 276 if (state.taxonomies[tax].length === 0) { 277 delete state.taxonomies[tax]; 278 } 279 } 280 281 state.paged = 1; 282 syncPillUI(); 283 pushUrl(); 284 fetchResults(); 285 }); 286 } 287 288 function bindShowMore() { 289 if (!filtersEl) return; 290 291 filtersEl.addEventListener('click', function (e) { 292 var btn = e.target.closest('.archive-listing__show-more'); 293 if (!btn) return; 294 295 var group = btn.closest('.archive-listing__facet-group'); 296 if (!group) return; 297 298 var hidden = group.querySelectorAll('[data-overflow="true"]'); 299 var isExpanded = btn.getAttribute('data-expanded') === 'true'; 300
301 hidden.forEach(function (el) { 302 el.style.display = isExpanded ? 'none' : ''; 303 }); 304 305 btn.setAttribute('data-expanded', isExpanded ? 'false' : 'true'); 306 btn.textContent = isExpanded ? 'Show more' : 'Show less'; 307 }); 308 } 309 310 function bindDropdownFilters() { 311 if (!filtersEl) return; 312 313 var groups = filtersEl.querySelectorAll('[data-facet-dropdown]'); 314 groups.forEach(function (group) { 315 var tax = group.getAttribute('data-taxonomy'); 316 var wrap = group.querySelector('.archive-listing__dropdown-wrap'); 317 var searchInput = group.querySelector('.archive-listing__dropdown-search'); 318 var listEl = group.querySelector('.archive-listing__dropdown-list'); 319 var tagsEl = group.querySelector('.archive-listing__dropdown-tags'); 320 if (!wrap || !searchInput || !listEl || !tagsEl) return; 321 322 var closeTimer = null; 323 324 searchInput.addEventListener('focus', function () { 325 clearTimeout(closeTimer); 326 wrap.classList.add('is-open'); 327 }); 328 329 searchInput.addEventListener('blur', function () { 330 closeTimer = setTimeout(function () { 331 wrap.classList.remove('is-open'); 332 }, 200); 333 }); 334 335 searchInput.addEventListener('input', function () { 336 var q = searchInput.value.toLowerCase(); 337 var opts = listEl.querySelectorAll('.archive-listing__dropdown-option'); 338 opts.forEach(function (opt) { 339 var label = opt.querySelector('.archive-listing__dropdown-option-label'); 340 var text = label ? label.textContent.toLowerCase() : ''; 341 opt.classList.toggle('is-hidden', q !== '' && text.indexOf(q) === -1); 342 }); 343 }); 344 345 listEl.addEventListener('change', function (e) { 346 var cb = e.target; 347 if (cb.type !== 'checkbox') return; 348 349 var value = cb.value; 350 var label = cb.getAttribute('data-label'); 351 var opt = cb.closest('.archive-listing__dropdown-option'); 352 353 if (!state.taxonomies[tax]) { 354 state.taxonomies[tax] = []; 355 } 356 357 if (cb.checked) { 358 if (state.taxonomies[tax].indexOf(value) === -1) { 359 state.taxonomies[tax].push(value); 360 } 361 if (opt) opt.classList.add('is-checked'); 362 var tag = document.createElement('span'); 363 tag.className = 'archive-listing__dropdown-tag'; 364 tag.setAttribute('data-value', value); 365 var tagColor = cb.getAttribute('data-color'); 366 if (tagColor) tag.style.setProperty('--alb-facet-color', tagColor); 367 tag.innerHTML = label + '<button type="button" class="archive-listing__dropdown-tag-remove" aria-label="Remove ' + label + '">×</button>'; 368 tagsEl.appendChild(tag); 369 } else { 370 var idx = state.taxonomies[tax].indexOf(value); 371 if (idx !== -1) state.taxonomies[tax].splice(idx, 1); 372 if (state.taxonomies[tax].length === 0) delete state.taxonomies[tax]; 373 if (opt) opt.classList.remove('is-checked'); 374 var existing = tagsEl.querySelector('[data-value="' + value + '"]'); 375 if (existing) existing.remove(); 376 } 377 378 state.paged = 1; 379 syncPillUI(); 380 pushUrl(); 381 fetchResults(); 382 }); 383 384 tagsEl.addEventListener('click', function (e) { 385 var removeBtn = e.target.closest('.archive-listing__dropdown-tag-remove'); 386 if (!removeBtn) return; 387 388 var tag = removeBtn.closest('.archive-listing__dropdown-tag'); 389 if (!tag) return; 390 391 var value = tag.getAttribute('data-value'); 392 tag.remove(); 393 394 var cb = listEl.querySelector('input[type="checkbox"][value="' + value + '"]'); 395 if (cb) { 396 cb.checked = false; 397 var opt = cb.closest('.archive-listing__dropdown-option'); 398 if (opt) opt.classList.remove('is-checked'); 399 } 400 401 if (state.taxonomies[tax]) { 402 var idx = state.taxonomies[tax].indexOf(value); 403 if (idx !== -1) state.taxonomies[tax].splice(idx, 1); 404 if (state.taxonomies[tax].length === 0) delete state.taxonomies[tax]; 405 } 406 407 state.paged = 1; 408 syncPillUI(); 409 pushUrl(); 410 fetchResults(); 411 }); 412 }); 413 } 414 415 function syncDropdownUI() { 416 if (!filtersEl) return; 417 418 var groups = filtersEl.querySelectorAll('[data-facet-dropdown]'); 419 groups.forEach(function (group) { 420 var tax = group.getAttribute('data-taxonomy'); 421 var listEl = group.querySelector('.archive-listing__dropdown-list'); 422 var tagsEl = group.querySelector('.archive-listing__dropdown-tags'); 423 if (!listEl || !tagsEl) return; 424 425 var activeTerms = state.taxonomies[tax] || []; 426 427 listEl.querySelectorAll('input[type="checkbox"]').forEach(function (cb) { 428 var isActive = activeTerms.indexOf(cb.value) !== -1; 429 cb.checked = isActive; 430 var opt = cb.closest('.archive-listing__dropdown-option'); 431 if (opt) opt.classList.toggle('is-checked', isActive); 432 }); 433 434 tagsEl.innerHTML = '';
435 activeTerms.forEach(function (val) { 436 var cb = listEl.querySelector('input[type="checkbox"][value="' + val + '"]'); 437 var label = cb ? cb.getAttribute('data-label') : val; 438 var tag = document.createElement('span'); 439 tag.className = 'archive-listing__dropdown-tag'; 440 tag.setAttribute('data-value', val); 441 var tagColor = cb ? cb.getAttribute('data-color') : ''; 442 if (tagColor) tag.style.setProperty('--alb-facet-color', tagColor); 443 tag.innerHTML = label + '<button type="button" class="archive-listing__dropdown-tag-remove" aria-label="Remove ' + label + '">×</button>'; 444 tagsEl.appendChild(tag); 445 }); 446 }); 447 } 448 449 function bindPaginationClicks() { 450 block.addEventListener('click', function (e) { 451 var link = e.target.closest('.archive-listing__pagination a.pagination-button'); 452 if (!link) return; 453 454 e.preventDefault(); 455 456 var dataPage = link.getAttribute('data-page'); 457 if (dataPage) { 458 state.paged = parseInt(dataPage, 10) || 1; 459 } else { 460 var href = link.getAttribute('href'); 461 if (!href) return; 462 var url = new URL(href, window.location.origin); 463 var pageParam = url.searchParams.get('paged'); 464 if (pageParam) { 465 state.paged = parseInt(pageParam, 10) || 1; 466 } else { 467 var match = href.match(/\/page\/(\d+)/); 468 state.paged = match ? parseInt(match[1], 10) : 1; 469 } 470 } 471 472 pushUrl(); 473 fetchResults(); 474 475 block.scrollIntoView({ behavior: 'smooth', block: 'start' }); 476 }); 477 } 478 479 function fetchResults() { 480 if (!resultsEl) return; 481 482 resultsEl.classList.add('is-loading'); 483 484 var formData = new FormData(); 485 formData.append('action', 'archive_listing_filter'); 486 formData.append('nonce', config.nonce); 487 formData.append('posts_per_page', config.postsPerPage); 488 formData.append('orderby', config.orderby); 489 formData.append('order', config.order); 490 formData.append('paged', state.paged); 491 formData.append('search', state.search); 492 formData.append('show_aside', config.showAside ? '1' : '0'); 493 // Lead flags (re-asserted server-side): view_lead marks the 494 // unfiltered VIEW whose page 1 carries the full-width lead card â 495 // the server derives the page offset windows from it on every 496 // page. with_lead marks page 1 itself (the lead render request). 497 formData.append('view_lead', isLeadView() ? '1' : '0'); 498 formData.append('with_lead', isLeadState() ? '1' : '0'); 499 formData.append('page_slug', basePath.split('/').filter(Boolean).pop() || ''); 500 501 var activeTypes = state.postTypes.length > 0 ? state.postTypes : config.postTypes; 502 activeTypes.forEach(function (pt) { 503 formData.append('post_types[]', pt); 504 }); 505 506 Object.keys(state.taxonomies).forEach(function (tax) { 507 state.taxonomies[tax].forEach(function (term) { 508 formData.append('taxonomies[' + tax + '][]', term); 509 }); 510 }); 511 512 // Forward the editor-defined base scope so the AJAX query stays scoped. 513 function appendScopeMap(key, map) { 514 if (!map || typeof map !== 'object') return; 515 Object.keys(map).forEach(function (tax) { 516 var terms = map[tax]; 517 if (!Array.isArray(terms)) return; 518 terms.forEach(function (slug) { 519 formData.append(key + '[' + tax + '][]', slug); 520 }); 521 }); 522 } 523 appendScopeMap('restrict_map', config.restrictMap); 524 appendScopeMap('exclude_map', config.excludeMap); 525 if (Array.isArray(config.excludePostTypes)) { 526 config.excludePostTypes.forEach(function (pt) { 527 formData.append('exclude_post_types[]', pt); 528 }); 529 } 530 531 fetch(config.ajaxUrl, { 532 method: 'POST', 533 body: formData, 534 credentials: 'same-origin' 535 }) 536 .then(function (response) { 537 return response.json(); 538 }) 539 .then(function (data) { 540 if (data.success) { 541 resultsEl.innerHTML = data.data.html; 542 // Full-width lead slot: filled on page 1 of the unfiltered 543 // view, cleared (and hidden) in every other state. 544 var leadEl = block.querySelector('[data-alb-lead]'); 545 if (leadEl) { 546 var leadHtml = data.data.lead || ''; 547 leadEl.innerHTML = leadHtml; 548 leadEl.hidden = leadHtml === ''; 549 } 550 if (paginationEl) { 551 paginationEl.innerHTML = data.data.pagination; 552 } 553 updateAside(data.data.aside || ''); 554 applyCardTitleTooltips(block); 555 } 556 resultsEl.classList.remove('is-loading'); 557 }) 558 .catch(function () { 559 resultsEl.classList.remove('is-loading'); 560 }); 561 } 562 563 function isLeadView() { 564 return !state.search && 565 state.postTypes.length === 0 && 566 Object.keys(state.taxonomies).length === 0; 567 } 568 569 function isLeadState() { 570 return state.paged === 1 && isLeadView(); 571 } 572 573 function updateAside(html) { 574 var asideEl = block.querySelector('.archive-listing__aside-panel'); 575 if (!config.showAside) return; 576 577 if (html) { 578 if (asideEl) { 579 asideEl.outerHTML = html; 580 } else if (listEl) { 581 listEl.insertAdjacentHTML('afterend', html); 582 } 583 } else if (asideEl) { 584 asideEl.remove(); 585 } 586 587 initAsideScroll(); 588 } 589 590 function initAsideScroll() { 591 // The aside is now a static, user-scrollable list (no marquee). 592 // Kept as a hook so ajax refreshes don't error. 593 } 594 595 initAsideScroll(); 596 } 597 598 // Standalone marquee init for [data-aside-scroll] elements outside of a 599 // configured archive-listing-block (e.g., the related-posts aside on single.php). 600 document.addEventListener('DOMContentLoaded', function () { 601 var tracks = document.querySelectorAll('[data-aside-scroll]'); 602 if (!tracks.length) return; 603 604 tracks.forEach(function (track) { 605 var owningBlock = track.closest('.archive-listing-block[data-alb-config]'); 606 if (owningBlock) return; 607 608 initStandaloneAsideScroll(track); 609 }); 610 }); 611 612 function initStandaloneAsideScroll(track) { 613 // Static aside: nothing to initialize (marquee removed per MI feedback). 614 } 615})();
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.