1/* Newman Core â Header runtime 2 * - Reads gallery dropdown data from <script id="nmc-gallery-dropdown-data"> 3 * - Locates the GALLERY <li> in the primary nav and injects a sub-menu 4 * - Adds keyboard support (Escape to close, focus-trap-free Tab) 5 * 6 * Page-cache safe: no AJAX, all data is server-rendered as JSON inline. 7 */ 8(function () { 9 'use strict'; 10 11 /* If the theme didn't call wp_body_open(), our strip never rendered at 12 * the top of <body>. The PHP also emits a <template id="nmc-top-strip- 13 * template"> in wp_footer; if that's present and an actual strip is not, 14 * we move the template's content to the start of <body>. */ 15 function ensureTopStrip() { 16 if (document.querySelector('.nmc-top-strip')) return; // already there 17 var tpl = document.getElementById('nmc-top-strip-template'); 18 if (!tpl || !tpl.content) return; 19 var node = tpl.content.firstElementChild ? tpl.content.firstElementChild.cloneNode(true) : null; 20 if (!node) return; 21 document.body.insertBefore(node, document.body.firstChild); 22 } 23 24 /* Measure the actual rendered strip height and shift the theme's mobile 25 * toolbar (#eiiMenuToolbar) and desktop header below it. Fixed CSS 26 * heights are unreliable across iOS Safari, Android Chrome, and 27 * different DPRs â measuring the live DOM box is the only way to 28 * guarantee no overlap. Re-runs on resize and on orientation change. */ 29 function reflowFixedAreas() { 30 var strip = document.querySelector('.nmc-top-strip'); 31 if (!strip) return; 32 var stripH = Math.ceil(strip.getBoundingClientRect().height); 33 if (!stripH) return; 34 var adminBarOffset = 0; 35 var ab = document.getElementById('wpadminbar'); 36 if (ab && getComputedStyle(ab).display !== 'none') { 37 adminBarOffset = Math.ceil(ab.getBoundingClientRect().height); 38 } 39 // Use setProperty with !important â the Etna theme has rules like 40 // `#eiiMenuToolbar { top: 40px !important }` and `header { top: 40px !important }` 41 // that win over normal inline styles. Important inline beats them. 42 strip.style.setProperty('top', adminBarOffset + 'px', 'important'); 43 var headerTopPx = (adminBarOffset + stripH) + 'px'; 44 var desktopHeader = document.querySelector('header.eii-show-for-large'); 45 if (desktopHeader) desktopHeader.style.setProperty('top', headerTopPx, 'important'); 46 var mobileToolbar = document.getElementById('eiiMenuToolbar'); 47 if (mobileToolbar) mobileToolbar.style.setProperty('top', headerTopPx, 'important'); 48 } 49 50 function init() { 51 ensureTopStrip(); 52 reflowFixedAreas(); 53 // Re-measure when fonts load (font swap can change strip height) 54 if (document.fonts && document.fonts.ready) { 55 document.fonts.ready.then(reflowFixedAreas); 56 } 57 window.addEventListener('resize', reflowFixedAreas, { passive: true }); 58 window.addEventListener('orientationchange', reflowFixedAreas); 59 60 var dataEl = document.getElementById('nmc-gallery-dropdown-data'); 61 if (!dataEl) return; 62 63 var items; 64 try { items = JSON.parse(dataEl.textContent || '[]'); } 65 catch (e) { return; } 66 if (!Array.isArray(items) || !items.length) return; 67 68 // Find the GALLERY menu item: an <li> in .eii-nav-primary whose anchor 69 // text matches /^gallery$/i. Fall back to any <a> with href ending in /gallery/. 70 var anchors = document.querySelectorAll('.eii-nav-primary li > a, .eii-nav-primary a'); 71 var galleryLi = null; 72 for (var i = 0; i < anchors.length; i++) { 73 var a = anchors[i]; 74 var txt = (a.textContent || '').trim(); 75 if (/^gallery$/i.test(txt) || /\/gallery\/?$/.test(a.getAttribute('href') || '')) { 76 galleryLi = a.closest('li'); 77 if (galleryLi) break; 78 } 79 } 80 if (!galleryLi) return; 81 82 // Don't double-inject if reload or HMR-ish situation. 83 if (galleryLi.querySelector('.nmc-gallery-dropdown__menu')) return; 84 85 galleryLi.classList.add('nmc-gallery-dropdown'); 86 87 var ul = document.createElement('ul'); 88 ul.className = 'nmc-gallery-dropdown__menu'; 89 ul.setAttribute('role', 'menu'); 90 91 items.forEach(function (item) { 92 var li = document.createElement('li'); 93 if (item.is_tail) li.classList.add('--tail'); 94 li.setAttribute('role', 'none'); 95 var a = document.createElement('a'); 96 a.setAttribute('role', 'menuitem'); 97 a.href = item.href; 98 a.textContent = item.label; 99 li.appendChild(a); 100 ul.appendChild(li); 101 }); 102 103 galleryLi.appendChild(ul); 104 105 // Click toggle for touch devices that don't support :hover well. 106 // We only intercept the toggle if the gallery anchor is clicked while 107 // the menu is closed â second click goes through to /gallery/. 108 var galleryAnchor = galleryLi.querySelector(':scope > a'); 109 if (galleryAnchor) { 110 galleryAnchor.addEventListener('click', function (e) { 111 // Only on touch / when not hover-capable. 112 if (!window.matchMedia('(hover: none)').matches) return; 113 if (!galleryLi.classList.contains('is-open')) { 114 e.preventDefault(); 115 galleryLi.classList.add('is-open'); 116 } 117 }); 118 } 119 120 // Click outside to close (touch). 121 document.addEventListener('click', function (e) { 122 if (!galleryLi.contains(e.target)) galleryLi.classList.remove('is-open'); 123 }); 124
125 // Escape closes when focus is inside. 126 galleryLi.addEventListener('keydown', function (e) { 127 if (e.key === 'Escape') { 128 galleryLi.classList.remove('is-open'); 129 if (galleryAnchor) galleryAnchor.focus(); 130 } 131 }); 132 } 133 134 if (document.readyState === 'loading') { 135 document.addEventListener('DOMContentLoaded', init); 136 } else { 137 init(); 138 } 139})();
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.