PageSourceSearch

https://www.psurgery.com/wp-content/plugins/newman-core/assets/js/header.js?ver=0.8.3

js psurgery.com collected 2026-10-07 17:22:58 UTC 5,683 bytes, 139 lines download raw bytes

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.