PageSourceSearch

https://consult.videowhisper.com/wp-content/themes/fanspaysite/js/menu.js?ver=1784169573

js videowhisper.com collected 2026-09-24 18:20:42 UTC 18,676 bytes, 499 lines download raw bytes

1document.addEventListener("DOMContentLoaded", function() {
2  var isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
3  var body = document.body;
4
5  // Determine hover setting from body classes
6  var hoverSetting = 'auto';
7  if (body.classList.contains('submenu-hover-yes')) hoverSetting = 'yes';
8  else if (body.classList.contains('submenu-hover-no')) hoverSetting = 'no';
9
10  // Resolve effective hover mode
11  var useHover;
12  if (hoverSetting === 'yes') useHover = true;
13  else if (hoverSetting === 'no') useHover = false;
14  else useHover = !isTouchDevice;
15
16  // Determine submenu display mode
17  var submenuDisplayMode = 'auto';
18  if (body.classList.contains('submenu-display-burger')) submenuDisplayMode = 'burger';
19
20  // Burger submenu mode forces click-only
21  if (submenuDisplayMode === 'burger') useHover = false;
22
23  // Add body class so CSS can target resolved no-hover state
24  if (!useHover) {
25    body.classList.add('fps-no-hover');
26  }
27
28  // Burger menu mode
29  var burgerMenuMode = 'auto';
30  if (body.classList.contains('burger-menu-yes')) burgerMenuMode = 'yes';
31  else if (body.classList.contains('burger-menu-no')) burgerMenuMode = 'no';
32
33  // Touch + burger (auto or yes): add CSS class for responsive burger
34  if (burgerMenuMode !== 'no' && isTouchDevice) {
35    body.classList.add('fps-burger-touch');
36  }
37
38  // Default Fomantic dropdown options
39  var dropdownOpts = {
40    on: useHover ? 'hover' : 'click',
41    showOnFocus: true,
42    allowTab: true,
43    forceSelection: false,
44    action: function(text, value, element) { element.click(); },
45    onChange: function(value, text, $choice) {}
46  };
47
48  // --- Helper: inject parent link as first submenu item (idempotent) ---
49  function injectParentLink(el) {
50    var parentUrl = el.getAttribute('data-parent-url');
51    var parentTitle = el.getAttribute('data-parent-title');
52    var parentIcon = el.getAttribute('data-parent-icon');
53    if (!parentUrl || !parentTitle) return;
54    var submenu = el.querySelector('.fps-submenu');
55    if (!submenu) return;
56    if (submenu.querySelector('.fps-parent-link')) return;
57    var a = document.createElement('a');
58    a.href = parentUrl;
59    a.className = 'item fps-parent-link';
60    a.setAttribute('role', 'menuitem');
61    a.setAttribute('title', parentTitle);
62    a.innerHTML = '<i class="' + parentIcon + ' menu-item-icon"></i> <span class="menu-item-title"><strong>' + parentTitle + '</strong></span>';
63    submenu.insertBefore(a, submenu.firstChild);
64  }
65
66  // When hover is disabled globally, inject parent links and remove hrefs
67  if (!useHover) {
68    document.querySelectorAll('.fps-has-submenu').forEach(function(el) {
69      injectParentLink(el);
70    });
71    document.querySelectorAll('.fpsDropdown > a').forEach(function(link) {
72      link.removeAttribute('href');
73      link.style.cursor = 'pointer';
74      makeSubmenuToggle(link, false);
75    });
76  }
77
78  // Initialize Fomantic dropdowns globally
79  jQuery('.fpsDropdown').dropdown(dropdownOpts);
80  syncDropdownExpandedState(document);
81
82  if (window.MutationObserver) {
83    document.querySelectorAll('.fpsDropdown').forEach(function(dropdown) {
84      var observer = new MutationObserver(function() {
85        syncDropdownExpandedState(dropdown);
86      });
87      observer.observe(dropdown, { attributes: true, attributeFilter: ['class'] });
88    });
89  }
90
91  // Main menu mode detection
92  var mainMenuMode = 'auto';
93  if (body.classList.contains('menu-display-big_icon')) mainMenuMode = 'big_icon';
94  else if (body.classList.contains('menu-display-small_icon_text')) mainMenuMode = 'small_icon_text';
95  if (mainMenuMode === 'auto' && isTouchDevice) {
96    body.classList.add('menu-display-touch');
97  }
98
99  // Narrow sidebar: watch for open dropdowns and temporarily allow overflow so
100  // flyout submenus can extend beyond the sidebar boundary. CSS (overflow-x: hidden)
101  // prevents the horizontal scrollbar when no dropdown is open.
102  var isNarrowSidebar = mainMenuMode === 'big_icon' ||
103    (mainMenuMode === 'auto' && (isTouchDevice || window.innerWidth <= 768));
104  if (isNarrowSidebar && window.MutationObserver) {
105    var $sidebar = jQuery('.site-sidebar');
106    var sidebarObserver = new MutationObserver(function() {
107      var anyActive = !!document.querySelector('.site-sidebar .fpsDropdown.active');
108      // overflow:visible lets the flyout menu escape the sidebar;
109      // '' removes the inline style so CSS (overflow-y:auto; overflow-x:hidden) takes over.
110      $sidebar.css('overflow', anyActive ? 'visible' : '');
111    });
112    document.querySelectorAll('.site-sidebar .fpsDropdown').forEach(function(el) {
113      sidebarObserver.observe(el, { attributes: true, attributeFilter: ['class'] });
114    });
115  }
116
117  // === Focus trap + Escape key for overlay menus ===
118  var FOCUSABLE = 'a[href], button:not([disabled]), [role="button"][tabindex="0"], [tabindex]:not
118([tabindex="-1"])';
119
120  function isFocusableVisible(el) {
121    if (!el || el.disabled || el.getAttribute('aria-hidden') === 'true') {
122      return false;
123    }
124
125    var style = window.getComputedStyle(el);
126    if (style.visibility === 'hidden' || style.display === 'none') {
127      return false;
128    }
129
130    return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
131  }
132
133  function getFocusable(container) {
134    return Array.prototype.slice.call(container.querySelectorAll(FOCUSABLE)).filter(isFocusableVisible);
135  }
136
137  function makeSubmenuToggle(link, expanded) {
138    if (!link) return;
139    link.setAttribute('role', 'button');
140    link.setAttribute('tabindex', '0');
141    link.setAttribute('aria-expanded', expanded ? 'true' : 'false');
142  }
143
144  function restoreSubmenuToggle(link) {
145    if (!link) return;
146    link.removeAttribute('role');
147    link.removeAttribute('aria-expanded');
148  }
149
150  function syncDropdownExpandedState(root) {
151    var dropdowns = root.classList && root.classList.contains('fpsDropdown')
152      ? [root]
153      : root.querySelectorAll('.fpsDropdown');
154
155    Array.prototype.forEach.call(dropdowns, function(dropdown) {
156      var toggle = dropdown.querySelector(':scope > a[role="button"]');
157      if (!toggle) return;
158      toggle.setAttribute('aria-expanded', dropdown.classList.contains('active') ? 'true' : 'false');
159    });
160  }
161
162  function activateOnKeyboard(handler) {
163    return function(e) {
164      if (e.key !== 'Enter' && e.key !== ' ') {
165        return;
166      }
167
168      e.preventDefault();
169      handler(e);
170    };
171  }
172
173  function trapFocus(container, returnFocus) {
174    var focusable = getFocusable(container);
175    if (focusable.length) focusable[0].focus();
176
177    function keyHandler(e) {
178      if (e.key === 'Escape') {
179        closeOverlayMenu(container, returnFocus);
180        document.removeEventListener('keydown', keyHandler);
181        return;
182      }
183      if (e.key === 'Tab') {
184        var focusableNow = getFocusable(container);
185        if (focusableNow.length === 0) return;
186        var first = focusableNow[0];
187        var last = focusableNow[focusableNow.length - 1];
188        if (e.shiftKey && document.activeElement === first) {
189          e.preventDefault();
190          last.focus();
191        } else if (!e.shiftKey && document.activeElement === last) {
192          e.preventDefault();
193          first.focus();
194        }
195      }
196    }
197    document.addEventListener('keydown', keyHandler);
198    container._fpsTrapHandler = keyHandler;
199  }
200
201  function closeOverlayMenu(container, returnFocus) {
202    if (!container) return;
203
204    if (container.id === 'topbar-expanded-menu') {
205      container.style.display = 'none';
206      container.setAttribute('aria-hidden', 'true');
207      restoreMenu(container);
208    } else if (container.id === 'sidebar-menu-panel') {
209      container.classList.remove('fps-open');
210      container.style.display = '';
211      restoreMenu(container);
212    } else {
213      container.classList.remove('fps-open');
214      container.style.display = 'none';
215    }
216
217    if (returnFocus) {
218      returnFocus.setAttribute('aria-expanded', 'false');
219      returnFocus.focus();
220    }
221  }
222
223  function releaseFocus(container) {
224    if (container._fpsTrapHandler) {
225      document.removeEventListener('keydown', container._fpsTrapHandler);
226      delete container._fpsTrapHandler;
227    }
228  }
229
230  // === Burger overlay helpers ===
231
232  // Prepare a container for burger/overlay mode (always click-to-open)
233  function prepareBurgerMenu(container) {
234    // Destroy and re-init Fomantic dropdowns for click mode
235    var $dd = jQuery(container).find('.fpsDropdown');
236    $dd.dropdown('destroy');
237    $dd.dropdown({
238      on: 'click',
239      showOnFocus: true,
240      allowTab: true,
241      forceSelection: false,
242      action: function(text, value, element) { element.click(); }
243    });
244
245    // Save and remove hrefs from all parent links with submenus
246    container.querySelectorAll('.fps-has-submenu > a').forEach(function(link) {
247      if (link.hasAttribute('href') && !link.hasAttribute('data-burger-href')) {
248        link.setAttribute('data-burger-href', link.getAttribute('href'));
249        link.removeAttribute('href');
250        link.style.cursor = 'pointer';
251      }
252      makeSubmenuToggle(link, false);
253    });
254
255    // Inject parent links into submenus
256    container.querySelectorAll('.fps-has-submenu').forEach(function(el) {
257      injectParentLink(el);
258    });
259  }
260
261  // Restore a container from burger mode to normal sidebar mode
262  function restoreMenu(container) {
263    // Destroy and re-init Fomantic dropdowns with original mode
264    var $dd = jQuery(container).find('.fpsDropdown');
265    $dd.dropdown('destroy');
266    $dd.dropdown(dropdownOpts);
267
268    // Restore all hrefs saved during burger mode
269    container.querySelectorAll('[data-burger-href]').forEach(function(link) {
270      link.setAttribute('href', link.getAttribute('data-burger-href'));
271      link.removeAttribute('data-burger-href');
272      link.style.cursor = '';
273      restoreSubmenuToggle(link);
274    });
275
276    // Re-remove hrefs for top-level dropdowns if global no-hover mode
277    if (!useHover) {
278      container.querySelectorAll('.fpsDropdown > a').forEach(function(link) {
279        link.removeAttribute('href');
280        link.style.cursor = 'pointer';
281        makeSubmenuToggle(link, false);
282      });
283    }
284
285    // Close any open nested submenus
286    container.querySelectorAll('.fps-submenu.fps-nested-open').forEach(function(sub) {
287      sub.classList.remove('fps-nested-open');
288    });
289  }
290
291  // Delegated click handler for nested submenus (non-Fomantic) in burger mode
292  function addNestedSubmenuClickHandler(container, activeCheck) {
293    container.addEventListener('click', function(e) {
294      if (activeCheck && !activeCheck()) return;
295
296      var link = e.target.closest('.fps-has-submenu:not(.fpsDropdown) > a');
297      if (!link || !container.contains(link)) return;
298
299      var parent = link.closest('.fps-has-submenu');
300      var submenu = parent.querySelector('.fps-submenu');
301      if (!submenu) return;
302
303      e.preventDefault();
304      e.stopPropagation();
305      var isOpen = submenu.classList.toggle('fps-nested-open');
306      makeSubmenuToggle(link, isOpen);
307    });
308
309    container.addEventListener('keydown', function(e) {
310      var link = e.target.closest('.fps-has-submenu:not(.fpsDropdown) > a, .fps-has-submenu > a[role="button"]');
311      if (!link || !container.contains(link)) return;
312      activateOnKeyboard(function(event) {
313        link.click();
314      })(e);
315    });
316  }
317
318  // === Top bar hamburger toggle ===
319  var hamburger = document.getElementById('topbar-hamburger');
320  var expandedMenu = document.getElementById('topbar-expanded-menu');
321  if (hamburger && expandedMenu) {
322    expandedMenu.setAttribute('aria-hidden', 'true');
323
324    // Expanded menu always uses burger/click mode
325    addNestedSubmenuClickHandler(expandedMenu);
326
327    hamburger.addEventListener('click', function() {
328      if (expandedMenu.style.display === 'none' || expandedMenu.style.display === '') {
329        expandedMenu.style.display = 'block';
330        expandedMenu.setAttribute('aria-hidden', 'false');
331        hamburger.setAttribute('aria-expanded', 'true');
332        prepareBurgerMenu(expandedMenu);
333        trapFocus(expandedMenu, hamburger);
334      } else {
335        releaseFocus(expandedMenu);
336        closeOverlayMenu(expandedMenu, hamburger);
337      }
338    });
339  }
340
341  // === Sidebar hamburger toggle (left-sidebar layout) ===
342  var sidebarHamburger = document.getElementById('sidebar-hamburger');
343  var sidebarMenuPanel = document.getElementById('sidebar-menu-panel');
344  if (sidebarHamburger && sidebarMenuPanel) {
345    // Nested click handler only active when panel is open as overlay
346    addNestedSubmenuClickHandler(sidebarMenuPanel, function() {
347      return sidebarMenuPanel.classList.contains('fps-open');
348    });
349
350    sidebarHamburger.addEventListener('click', function() {
351      if (sidebarMenuPanel.classList.contains('fps-open')) {
352        releaseFocus(sidebarMenuPanel);
353        closeOverlayMenu(sidebarMenuPanel, sidebarHamburger);
354      } else {
355        sidebarMenuPanel.classList.add('fps-open');
356        sidebarHamburger.setAttribute('aria-expanded', 'true');
357        prepareBurgerMenu(sidebarMenuPanel);
358        trapFocus(sidebarMenuPanel, sidebarHamburger);
359      }
360    });
361
362    var sidebarPanelClose = document.getElementById('sidebar-panel-close');
363    if (sidebarPanelClose) {
364      sidebarPanelClose.addEventListener('click', function() {
365        releaseFocus(sidebarMenuPanel);
366        closeOverlayMenu(sidebarMenuPanel, sidebarHamburger);
367      });
368    }
369  }
370
371  // === Top bar submenu toggle on click (no-hover / touch / burger submenu mode) ===
372  var useTopbarClick = !useHover || submenuDisplayMode === 'burger';
373
374  if (useTopbarClick) {
375    document.querySelectorAll('.topbar-item.has-children').forEach(function(item) {
376      var link = item.querySelector('.topbar-icon-link');
377      var submenu = item.querySelector('.topbar-submenu');
378      if (!link || !submenu) return;
379
380      link.removeAttribute('href');
381      link.style.cursor = 'pointer';
382      makeSubmenuToggle(link, false);
383
384      function toggleTopbarSubmenu(e) {
385        e.preventDefault();
386        e.stopPropagation();
387        document.querySelectorAll('.topbar-submenu.fps-open').forEach(function(other) {
388          if (other !== submenu) {
389            other.classList.remove('fps-open');
390            other.style.display = 'none';
391            var otherToggle = other.closest('.topbar-item');
392            if (otherToggle) {
393              var otherLink = otherToggle.querySelector('.topbar-icon-link[role="button"]');
394              if (otherLink) otherLink.setAttribute('aria-expanded', 'false');
395            }
396          }
397        });
398        if (submenu.classList.contains('fps-open')) {
399          submenu.classList.remove('fps-open');
400          submenu.style.display = 'none';
401          link.setAttribute('aria-expanded', 'false');
402        } else {
403          submenu.classList.add('fps-open');
404          submenu.style.display = 'block';
405          link.setAttribute('aria-expanded', 'true');
406        }
407      }
408
409      link.addEventListener('click', toggleTopbarSubmenu);
410      link.addEventListener('keydown', activateOnKeyboard(toggleTopbarSubmenu));
411    });
412
413    // Nested submenu toggle on click (top bar)
414    document.querySelectorAll('.topbar-nested-item.has-children').forEach(function(item) {
415      var toggle = item.querySelector('.topbar-nested-toggle');
416      var nested = item.querySelector('.topbar-submenu-nested');
417      if (!toggle || !nested) return;
418
419      toggle.removeAttribute('href');
420      toggle.style.cursor = 'pointer';
421      makeSubmenuToggle(toggle, false);
422
423      function toggleNestedSubmenu(e) {
424        e.preventDefault();
425        e.stopPropagation();
426        item.parentElement.querySelectorAll('.topbar-submenu-nested.fps-open').forEach(function(other) {
427          if (other !== nested) {
428            other.classList.remove('fps-open');
429            other.style.display = 'none';
430            var otherItem = other.closest('.topbar-nested-item');
431            if (otherItem) {
432              var otherToggle = otherItem.querySelector('.topbar-nested-toggle[role="button"]');
433              if (otherToggle) otherToggle.setAttribute('aria-expanded', 'false');
434            }
435          }
436        });
437        if (nested.classList.contains('fps-open')) {
438          nested.classList.remove('fps-open');
439          nested.style.display = 'none';
440          toggle.setAttribute('aria-expanded', 'false');
441        } else {
442          nested.classList.add('fps-open');
443          nested.style.display = 'block';
444          toggle.setAttribute('aria-expanded', 'true');
445        }
446      }
447
448      toggle.addEventListener('click', toggleNestedSubmenu);
449      toggle.addEventListener('keydown', activateOnKeyboard(toggleNestedSubmenu));
450    });
451
452    // Close submenus when clicking outside
453    document.addEventListener('click', function(e) {
454      if (!e.target.closest('.topbar-item')) {
455        document.querySelectorAll('.topbar-submenu.fps-open, .topbar-submenu-nested.fps-open').forEach(function(sub) {
456          sub.classList.remove('fps-open');
457          sub.style.display = 'none';
458        });
459        document.querySelectorAll('.topbar-icon-link[role="button"], .topbar-nested-toggle[role="button"]').forEach(function(toggle) {
460          toggle.setAttribute('aria-expanded', 'false');
461        });
462      }
463    });
464  }
465
466  // === Burger submenu mode: inject close buttons ===
467  if (submenuDisplayMode === 'burger') {
468    document.querySelectorAll('.fps-submenu').forEach(function(submenu) {
469      if (submenu.querySelector('.fps-burger-close')) return;
470      var closeBtn = document.createElement('button');
471      closeBtn.className = 'fps-burger-close';
472      closeBtn.setAttribute('aria-label', 'Close submenu');
473      closeBtn.innerHTML = '<i class="fas fa-times"></i>';
474      closeBtn.addEventListener('click', function(e) {
475        e.preventDefault();
476        e.stopPropagation();
477        var dropdown = submenu.closest('.fpsDropdown');
478        if (dropdown) jQuery(dropdown).dropdown('hide');
479      });
480      submenu.insertBefore(closeBtn, submenu.firstChild);
481    });
482
483    document.querySelectorAll('.topbar-submenu').forEach(function(submenu) {
484      if (submenu.querySelector('.fps-burger-close')) return;
485      var closeBtn = document.createElement('button');
486      closeBtn.className = 'fps-burger-close';
487      closeBtn.setAttribute('aria-label', 'Close submenu');
488      closeBtn.innerHTML = '<i class="fas fa-times"></i>';
489      closeBtn.addEventListener('click', function(e) {
490        e.preventDefault();
491        e.stopPropagation();
492        submenu.classList.remove('fps-open');
493        submenu.style.display = 'none';
494      });
495      submenu.insertBefore(closeBtn, submenu.firstChild);
496    });
497  }
498
499});

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.