PageSourceSearch

https://netaccess.ca/wp-content/plugins/jf-mega-menu-patched/assets/front.js?ver=3.0.1

js netaccess.ca collected 2026-10-10 00:44:52 UTC 14,350 bytes, 310 lines download raw bytes

1/* JF Mega Menu — front.js v3.0.2
2 *
3 * v3.0.2 — Mobile CTA fix:
4 *  - buildMobCTAs() now appends the user's custom CSS class (cta.class)
5 *    to the mobile-specific structural classes, matching desktop behavior.
6 *    Previously the custom class was ignored on mobile.
7 *
8 * Security hardening v3.0.1 (Fix H5 — client side):
9 *  - safeHref():   validates URL protocol against a fixed allow-list before
10 *                  any .href assignment. Blocks javascript:, data:, vbscript:.
11 *  - safeTarget(): restricts target attribute to standard values.
12 *  Defense-in-depth — server already sanitizes via esc_url_raw / safe_target,
13 *  but values coming from data-sub JSON pass through the DOM and could be
14 *  tampered with by other scripts. Validating here makes the failure mode safe.
15 */
16(function(){
17'use strict';
18var DATA=(typeof jfmmData!=='undefined')?jfmmData:{logoUrl:'',logoAlt:'',ctas:[]};
19var DELAY=100;
20
21/* ─── URL & target sanitizers (Fix H5) ──────────────────────────────
22   Allow standard web protocols + relative URLs + fragments + tel:/mailto:.
23   Anything else (javascript:, data:, vbscript:, file:, etc.) becomes '#'. */
24function safeHref(u){
25  u=(u==null?'':String(u)).trim();
26  if(!u)return'#';
27  /* Relative URL, fragment, query, dot-relative */
28  if(/^(\/|\?|#|\.)/.test(u))return u;
29  /* Protocol-relative */
30  if(/^\/\//.test(u))return u;
31  /* Allow-list of protocols */
32  if(/^(https?|mailto|tel):/i.test(u))return u;
33  return'#';
34}
35function safeTarget(t){
36  t=String(t||'_self');
37  return(t==='_blank'||t==='_self'||t==='_parent'||t==='_top')?t:'_self';
38}
39
40/* Handle both: DOMContentLoaded not yet fired, or already fired (inline shortcode) */
41function jfmmInit(){initDesktop();initMobile();}
42if(document.readyState==='loading'){
43  document.addEventListener('DOMContentLoaded',jfmmInit);
44} else {
45  jfmmInit();
46}
47
48function initDesktop(){
49  var nav=document.querySelector('#jfmm-nav, .jfmm-nav-wrap');
50  if(!nav)return;
51  var items=nav.querySelectorAll('li.jfmm-item.jfmm-has-drop');
52  if(!items.length)return;
53  items.forEach(function(item){
54    var drop=item.querySelector(':scope > .jfmm-drop');
55    if(!drop)return;
56    /* Already moved to body — skip */
57    if(item._jfmmDrop)return;
58    drop._jfmmItem=item; item._jfmmDrop=drop;
59    document.body.appendChild(drop);
60  });
61  items.forEach(function(item){
62    var drop=item._jfmmDrop; if(!drop)return;
63    var timer=null;
64    item.addEventListener('mouseenter',function(){clearTimeout(timer);closeAll();openDrop(item,drop);});
65    item.addEventListener('mouseleave',function(){timer=setTimeout(function(){closeDrop(item,drop);},DELAY);});
66    drop.addEventListener('mouseenter',function(){clearTimeout(timer);});
67    drop.addEventListener('mouseleave',function(){timer=setTimeout(function(){closeDrop(item,drop);},DELAY);});
68    if(drop.classList.contains('jfmm-tpl-sidebar'))initSidebarHover(drop);
69  });
70  document.addEventListener('keydown',function(e){if(e.key==='Escape')closeAll();});
71  document.addEventListener('click',function(e){
72    if(!e.target.closest('li.jfmm-item')&&!e.target.closest('.jfmm-drop'))closeAll();
73  });
74  window.addEventListener('scroll',function(){repositionOpen();},{passive:true});
75  window.addEventListener('resize',function(){closeAll();});
76}
77
78function openDrop(item,drop){
79  item.classList.add('jfmm-open');
80  drop.classList.add('jfmm-drop--open');
81  var btn=item.querySelector('button.jfmm-toggle');
82  if(btn)btn.setAttribute('aria-expanded','true');
83  positionDrop(item,drop);
84  if(drop.classList.contains('jfmm-tpl-sidebar')){
85    var first=drop.querySelector('li.jfmm-l2.has-sub');
86    if(first)activateL2(drop,first);
87  }
88}
89function closeDrop(item,drop){
90  item.classList.remove('jfmm-open');
91  if(drop)drop.classList.remove('jfmm-drop--open');
92  var btn=item.querySelector('button.jfmm-toggle');
93  if(btn)btn.setAttribute('aria-expanded','false');
94}
95function closeAll(){
96  document.querySelectorAll('li.jfmm-item.jfmm-open').forEach(function(it){closeDrop(it,it._jfmmDrop);});
97  document.querySelectorAll('.jfmm-drop.jfmm-drop--open').forEach(function(d){d.classList.remove('jfmm-drop--open');});
98}
99function repositionOpen(){
100  document.querySelectorAll('.jfmm-drop.jfmm-drop--open').forEach(function(d){
101    if(d._jfmmItem)positionDrop(d._jfmmItem,d);
102  });
103}
104function positionDrop(item,drop){
105  var vw=document.documentElement.clientWidth;
106  var dropW=parseInt(getComputedStyle(document.documentElement).getPropertyValue('--jfmm-width'))||1000;
107  dropW=Math.min(dropW,vw*0.96);
108  var rect=item.getBoundingClientRect();
109  var top=rect.bottom+window.scrollY+6;
110  var left=Math.max(10,(vw-dropW)/2);
111  drop.style.position='absolute';
112  drop.style.top=top+'px';
113  drop.style.left=left+'px';
114  drop.style.right='auto';
115  drop.style.transform='none';
116  drop.style.width=dropW+'px';
117}
118function initSidebarHover(drop){
119  drop.querySelectorAll('li.jfmm-l2').forEach(function(li){
120    li.addEventListener('mouseenter',function(){activateL2(drop,li);});
121  });
122}
123function activateL2(drop,li){
124  drop.querySelectorAll('li.jfmm-l2').forEach(function(el){el.classList.remove('active');});
125  li.classList.add('active');
126  var btn=li.querySelector('button.jfmm-l2-btn'); if(!btn)return;
127  var subs=[];try{subs=JSON.parse(btn.getAttribute('data-sub')||'[]');}catch(e){}
128  var right=drop.querySelector('div.jfmm-right'); if(!right)return;
129  right.innerHTML='';
130  if(!subs.length){right.innerHTML='<span class="jfmm-empty">No sub-items.</span>';return;}
131  subs.forEach(function(s){
132    var a=document.createElement('a');
133    a.className='jfmm-l3-r';
134    a.href=safeHref(s.url);                            /* Fix H5 */
135    a.target=safeTarget(s.target);                     /* Fix H5 */
136    if(a.target==='_blank')a.setAttribute('rel','noopener');
137    var sp=document.createElement('span');sp.textContent=s.title||'';
138    a.appendChild(sp);right.appendChild(a);
139  });
140}
141
142/* ── MOBILE ── */
143function initMobile(){
144  var nav=document.querySelector('#jfmm-nav, .jfmm-nav-wrap');
145  if(!nav)return;
146  /* Prevent double init — check if burger already added */
147  if(nav.querySelector('.jfmm-burger'))return;
148  if(document.querySelector('.jfmm-mob-overlay'))return;
149  var burger=document.createElement('button');
150  burger.type='button';burger.className='jfmm-burger';
151  burger.setAttribute('aria-label','Open menu');
152  burger.innerHTML='<span></span><span></span><span></span>';
153  nav.appendChild(burger);
154  var ov=buildOverlay(nav);
155  document.body.appendChild(ov);
156  burger.addEventListener('click',function(){openMob(ov);});
157  var cb=ov.querySelector('.jfmm-mob-close');
158  if(cb)cb.addEventListener('click',function(){closeMob(ov);});
159  var bd=ov.querySelector('.jfmm-mob-backdrop');
160  if(bd)bd.addEventListener('click',function(){closeMob(ov);});
161  document.addEventListener('keydown',function(e){if(e.key==='Escape')closeMob(ov);});
162}
163
164function buildOverlay(nav){
165  var ov=document.createElement('div');ov.className='jfmm-mob-overlay';
166  /* logoUrl already passed through esc_url_raw on server. Run it through
167     safeHref here too in case future code paths inject it elsewhere. */
168  var safeLogo=safeHref(DATA.logoUrl);
169  var logo=(DATA.logoUrl&&safeLogo!=='#')
170    ? '<img src="'+esc(safeLogo)+'" alt="'+esc(DATA.logoAlt)+'">'
171    : '<span class="jfmm-mob-logo-txt">'+esc(DATA.logoAlt)+'</span>';
172  ov.innerHTML='<div class="jfmm-mob-backdrop"></div>'
173    +'<div class="jfmm-mob-panel">'
174    +'<div class="jfmm-mob-hdr"><div class="jfmm-mob-logo">'+logo+'</div>'
175    +'<button type="button" class="jfmm-mob-close" aria-label="Close">&#x2715;</button></div>'
176    +'<nav class="jfmm-mob-nav" id="jfmm-mob-list"></nav>'
177    +'<div class="jfmm-mob-ctas" id="jfmm-mob-ctas"></div>'
178    +'</div>';
179  buildMobNav(nav,ov.querySelector('#jfmm-mob-list'));
180  buildMobCTAs(ov.querySelector('#jfmm-mob-ctas'));
181  return ov;
182}
183
184function buildMobCTAs(container){
185  if(!container||!DATA.ctas||!DATA.ctas.length){if(container)container.style.display='none';return;}
186  DATA.ctas.forEach(function(cta,idx){
187    var a=document.createElement('a');
188    a.href=safeHref(cta.url);                          /* Fix H5 */
189    /* Mobile-specific structural classes (layout, padding, border)
190       + user's custom class from admin (theming, colors, etc.).
191       This matches desktop, where the user's class is applied as well. */
192    var customClass=String(cta.class||'').trim();
193    a.className='jfmm-mob-cta jfmm-mob-cta-'+(idx+1)+(customClass?' '+customClass:'');
194    a.textContent=cta.label||'';
195    if(cta.id)a.id=cta.id;
196    container.appendChild(a);
197  });
198}
199
200function buildMobNav(desktopNav,mobNav){
201  var l0=desktopNav.querySelectorAll('ul.jfmm-nav > li.jfmm-item');
202  l0.forEach(function(item){
203    var drop=item._jfmmDrop||null;
204    var toggle=item.querySelector('button.jfmm-toggle');
205    var linkEl=item.querySelector(':scope > a');
206    var title='';
207    if(toggle){var c=toggle.cloneNode(true);var arr=c.querySelector('.jfmm-arr');if(arr)arr.parentNode.removeChild(arr);title=(c.textContent||'').trim();}
208    else if(linkEl){title=(linkEl.textContent||'').trim();}
209    var href=linkEl?linkEl.getAttribute('href'):'#';
210    var wrap=document.createElement('div');wrap.className='jfmm-mob-item';
211    if(!drop){
212      var a=document.createElement('a');a.className='jfmm-mob-toggle-link';
213      a.href=safeHref(href);                           /* Fix H5 */
214      a.textContent=title;wrap.appendChild(a);
215    } else {
216      var btn=document.createElement('button');btn.type='button';btn.className='jfmm-mob-toggle';
217      btn.innerHTML='<span>'+esc(title)+'</span><span class="jfmm-mob-arr"></span>';
218      var sub=document.createElement('div');sub.className='jfmm-mob-sub';
219      var l2els=drop.querySelectorAll('li.jfmm-l2');
220      var cols=drop.querySelectorAll('.jfmm-col');
221      if(l2els.length)l2els.forEach(function(l2){appendMobL2Sidebar(sub,l2);});
222      else if(cols.length)cols.forEach(function(col){appendMobL2Col(sub,col);});
223      btn.addEventListener('click',function(){
224        var open=btn.classList.contains('open');
225        mobNav.querySelectorAll('.jfmm-mob-toggle.open').forEach(function(b){
226          b.classList.remove('open');
227          var s=b.parentElement&&b.parentElement.querySelector('.jfmm-mob-sub');
228          if(s)s.classList.remove('open');
229        });
230        if(!open){btn.classList.add('open');sub.classList.add('open');}
231      });
232      wrap.appendChild(btn);wrap.appendChild(sub);
233    }
234    mobNav.appendChild(wrap);
235  });
236}
237
238function appendMobL2Sidebar(sub,l2el){
239  var btn2=l2el.querySelector('button.jfmm-l2-btn');
240  var link2=l2el.querySelector('a.jfmm-l2-link');
241  var titleEl=l2el.querySelector('span:last-of-type');
242  var title2=titleEl?titleEl.textContent.trim():((btn2||link2||{textContent:''}).textContent.trim());
243  var href2=link2?link2.getAttribute('href'):'#';
244  var iconEl=l2el.querySelector('.jfmm-icon');
245  var hasSub=!!(btn2&&btn2.getAttribute('data-sub'));
246  var subs=[];if(hasSub){try{subs=JSON.parse(btn2.getAttribute('data-sub'));}catch(e){}}
247  var el2=document.createElement(hasSub?'button':'a');
248  el2.className='jfmm-mob-l2';
249  if(!hasSub)el2.href=safeHref(href2);                /* Fix H5 */
250  else el2.type='button';
251  el2.innerHTML='<span class="jfmm-mob-l2-inner">'+(iconEl?iconEl.outerHTML:'')+'<span>'+esc(title2)+'</span></span>'+(hasSub?'<span class="jfmm-mob-l2-arr"></span>':'');
252  sub.appendChild(el2);
253  if(hasSub&&subs.length){
254    var l3=document.createElement('div');l3.className='jfmm-mob-l3-list';
255    subs.forEach(function(s){
256      var a=document.createElement('a');
257      a.className='jfmm-mob-l3';
258      a.href=safeHref(s.url);                          /* Fix H5 */
259      a.target=safeTarget(s.target);                   /* Fix H5 */
260      if(a.target==='_blank')a.setAttribute('rel','noopener');
261      a.textContent=s.title||'';
262      l3.appendChild(a);
263    });
264    el2.addEventListener('click',function(){
265      var open=el2.classList.contains('open');
266      sub.querySelectorAll('.jfmm-mob-l2.open').forEach(function(b){b.classList.remove('open');var n=b.nextElementSibling;if(n&&n.classList.contains('jfmm-mob-l3-list'))n.classList.remove('open');});
267      if(!open){el2.classList.add('open');l3.classList.add('open');}
268    });
269    sub.appendChild(l3);
270  }
271}
272
273function appendMobL2Col(sub,col){
274  var hdr=col.querySelector('.jfmm-col-hdr');if(!hdr)return;
275  var titleEl=hdr.querySelector('.jfmm-col-title');
276  var title2=titleEl?titleEl.textContent.trim():'';
277  var href2=(titleEl&&titleEl.tagName==='A')?titleEl.getAttribute('href'):'#';
278  var iconEl=hdr.querySelector('.jfmm-icon');
279  var l3s=col.querySelectorAll('a.jfmm-l3');
280  var hasSub=l3s.length>0;
281  var el2=document.createElement(hasSub?'button':'a');
282  el2.className='jfmm-mob-l2';
283  if(!hasSub)el2.href=safeHref(href2);                /* Fix H5 */
284  else el2.type='button';
285  el2.innerHTML='<span class="jfmm-mob-l2-inner">'+(iconEl?iconEl.outerHTML:'')+'<span>'+esc(title2)+'</span></span>'+(hasSub?'<span class="jfmm-mob-l2-arr"></span>':'');
286  sub.appendChild(el2);
287  if(hasSub){
288    var l3=document.createElement('div');l3.className='jfmm-mob-l3-list';
289    l3s.forEach(function(a){
290      var b=document.createElement('a');
291      b.className='jfmm-mob-l3';
292      b.href=safeHref(a.getAttribute('href'));         /* Fix H5 */
293      b.target=safeTarget(a.target);                   /* Fix H5 */
294      if(b.target==='_blank')b.setAttribute('rel','noopener');
295      b.textContent=a.textContent.trim();
296      l3.appendChild(b);
297    });
298    el2.addEventListener('click',function(){
299      var open=el2.classList.contains('open');
300      sub.querySelectorAll('.jfmm-mob-l2.open').forEach(function(b){b.classList.remove('open');var n=b.nextElementSibling;if(n&&n.classList.contains('jfmm-mob-l3-list'))n.classList.remove('open');});
301      if(!open){el2.classList.add('open');l3.classList.add('open');}
302    });
303    sub.appendChild(l3);
304  }
305}
306
307function openMob(ov){ov.classList.add('open');document.body.classList.add('jfmm-mob-open');document.body.style.overflow='hidden';}
308function closeMob(ov){ov.classList.remove('open');document.body.classList.remove('jfmm-mob-open');document.body.style.overflow='';}
309function esc(s){return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');}
310})();

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.