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">✕</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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');} 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.