1/** 2 * Main theme JavaScript file 3 */ 4 5(function($) { 6 'use strict'; 7 8 // Mobile menu toggle 9 function initMobileMenu() { 10 const menuToggle = document.querySelector('.menu-toggle'); 11 const mainNav = document.querySelector('.main-navigation'); 12 13 if (menuToggle && mainNav) { 14 menuToggle.addEventListener('click', function() { 15 mainNav.classList.toggle('toggled'); 16 if (mainNav.classList.contains('toggled')) { 17 menuToggle.setAttribute('aria-expanded', 'true'); 18 } else { 19 menuToggle.setAttribute('aria-expanded', 'false'); 20 } 21 }); 22 } 23 } 24 25 // Navegação por teclado para menu 26 function initKeyboardNavigation() { 27 // Adiciona suporte para teclas Enter e Espaço nos toggles de submenu 28 document.addEventListener('keydown', function(e) { 29 const target = e.target; 30 31 // Verifica se é um toggle de submenu 32 if (target.classList.contains('submenu-toggle')) { 33 if (e.key === 'Enter' || e.key === ' ') { 34 e.preventDefault(); 35 target.click(); 36 } 37 } 38 39 // Verifica se é um link de menu com submenu 40 if (target.classList.contains('menu-link') && target.hasAttribute('aria-haspopup')) { 41 if (e.key === 'Enter' || e.key === ' ') { 42 e.preventDefault(); 43 const toggle = target.querySelector('.submenu-toggle'); 44 if (toggle) { 45 toggle.click(); 46 } 47 } 48 } 49 50 // Navegação com setas 51 if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { 52 const menuItems = Array.from(document.querySelectorAll('.new-menu a, .submenu-toggle')); 53 const currentIndex = menuItems.indexOf(target); 54 55 if (currentIndex !== -1) { 56 e.preventDefault(); 57 let nextIndex; 58 59 if (e.key === 'ArrowDown') { 60 nextIndex = (currentIndex + 1) % menuItems.length; 61 } else { 62 nextIndex = (currentIndex - 1 + menuItems.length) % menuItems.length; 63 } 64 65 menuItems[nextIndex].focus(); 66 } 67 } 68 69 // Fechar submenu com Escape 70 if (e.key === 'Escape') { 71 const openSubmenu = document.querySelector('.menu-item-active'); 72 if (openSubmenu) { 73 closeSubmenuAndChildren(openSubmenu); 74 const menuLink = openSubmenu.querySelector('.menu-link'); 75 if (menuLink) { 76 menuLink.focus(); 77 } 78 } 79 } 80 }); 81 } 82 83 84 // Função para fechar um submenu e todos os seus filhos 85 function closeSubmenuAndChildren(element) { 86 if (!element) return; 87 88 // Remove a classe ativa do item 89 element.classList.remove('menu-item-active'); 90 91 // Fecha o submenu direto 92 const submenu = element.querySelector('.sub-menu') || element.querySelector('.children'); 93 if (submenu) { 94 submenu.classList.remove('show'); 95 } 96 97 // Remove a rotação da seta (seletor corrigido) 98 const toggle = element.querySelector('.submenu-toggle'); 99 if (toggle) { 100 toggle.classList.remove('rotated'); 101 } 102 103 // Fecha todos os itens filhos recursivamente 104 const children = element.querySelectorAll('.menu-item-has-children, .page_item_has_children'); 105 children.forEach(child => closeSubmenuAndChildren(child)); 106 } 107 108 // Função para verificar se um elemento tem submenu aberto 109 function hasOpenSubmenu(element) { 110 if (!element) return false; 111 const submenu = element.querySelector('.sub-menu.show') || element.querySelector('.children.show'); 112 return submenu !== null; 113 } 114 115 // Função para fechar todos os submenus 116 // 117 // :not([data-menu-sti] *) â exclui o menu-sti-uenf (Sprint 2): este script 118 // implementa o comportamento de "persiana" do menu legado e não sabe 119 // distinguir os dois sistemas, já que ambos podem gerar as mesmas classes 120 // .menu-item-has-children/.page_item_has_children (saÃda padrão do 121 // WordPress). Sem essa exclusão, o clique no link de um item com filhos 122 // dentro do menu-sti-uenf era interceptado (preventDefault) por este 123 // handler, impedindo a navegação normal do link. 124 function closeAllSubmenus() { 125 document.querySelectorAll('.menu-item-has-children:not([data-menu-sti] *), .page_item_has_children:not([data-menu-sti] *)').forEach(item => { 126 item.classList.remove('menu-item-active'); 127 // Usando seletores separados para evitar problemas de sintaxe 128 const submenu = item.querySelector('.sub-menu') || item.querySelector('.children'); 129 if (submenu) submenu.classList.remove('show'); 130 const toggle = item.querySelector('.submenu-toggle'); 131 if (toggle) { 132 toggle.classList.remove('rotated'); 133 // Garante que a seta aponte para baixo 134 toggle.classList.remove('fa-chevron-right'); 135 toggle.classList.add('fa-chevron-down'); 136 } 137 }); 138 } 139 140 // Função para implementar comportamento accordion (fecha outros itens do mesmo nÃvel) 141 function closeOtherMenuItems(currentItem, container) { 142 const selector = container === 'top-level' ? 143 '.new-menu > .menu-item-has-children, .new-menu > .page_item_has_children' : 144 '.menu-item-has-children, .page_item_has_children'; 145 146 const items = container === 'top-level' ? 147 document.querySelectorAll(selector) : 148 Array.from(currentItem.parentElement.children); 149
150 items.forEach(item => { 151 if (item !== currentItem && (item.classList.contains('menu-item-has-children') || item.classList.contains('page_item_has_children'))) { 152 closeSubmenuAndChildren(item); 153 } 154 }); 155 } 156 157 // Função para verificar se o estilo clássico está ativo 158 function checkClassicMenuStyle() { 159 // Verifica se há CSS inline que indica estilo clássico 160 const styles = document.querySelectorAll('style'); 161 let isClassic = false; 162 163 styles.forEach(style => { 164 if (style.textContent.includes('body .new-menu') && 165 style.textContent.includes('transition: none !important')) { 166 isClassic = true; 167 } 168 }); 169 170 // Adiciona classe ao body para controle via CSS 171 if (isClassic) { 172 document.body.classList.add('menu-classic-style'); 173 } else { 174 document.body.classList.remove('menu-classic-style'); 175 } 176 177 return isClassic; 178 } 179 180 // Submenu active state 181 function initSubmenuActive() { 182 // Verifica o estilo do menu 183 const isClassicStyle = checkClassicMenuStyle(); 184 // Remove Ãcones duplicados que possam ter sido adicionados pelo WordPress 185 document.querySelectorAll('.menu-item-has-children a .submenu-toggle, .page_item_has_children a .submenu-toggle').forEach(icon => { 186 icon.remove(); 187 }); 188 189 // Adiciona o Ãcone de seta para itens com submenu 190 // :not([data-menu-sti] *) â exclui o menu-sti-uenf (Sprint 2), que já 191 // tem seu próprio botão de disclosure (menu-sti-uenf__toggle) fora do 192 // <a>; ver nota completa em closeAllSubmenus() acima. 193 document.querySelectorAll('.menu-item-has-children > a:not([data-menu-sti] *), .page_item_has_children > a:not([data-menu-sti] *)').forEach(link => { 194 // Verifica se já tem um Ãcone 195 if (!link.querySelector('.submenu-toggle')) { 196 const icon = document.createElement('i'); 197 icon.className = 'fas fa-chevron-down submenu-toggle'; 198 link.appendChild(icon); 199 } 200 }); 201 202 // Fecha todos os menus ao carregar a página 203 closeAllSubmenus(); 204 205 const menuItems = document.querySelectorAll('.menu-item-has-children > a:not([data-menu-sti] *), .page_item_has_children > a:not([data-menu-sti] *)'); 206 207 menuItems.forEach(item => { 208 // Adiciona evento de clique 209 item.addEventListener('click', function(e) { 210 const submenu = this.nextElementSibling; 211 const parentLi = this.parentElement; 212 const isSubmenu = parentLi.parentElement.classList.contains('sub-menu') || 213 parentLi.parentElement.classList.contains('children'); 214 215 // Verifica se o clique foi no link ou na seta 216 const isClickOnToggle = e.target.closest('.submenu-toggle') !== null; 217 const isClickOnLink = e.target === this || e.target.tagName === 'A'; 218 219 // Só processa se foi clique válido 220 if (isClickOnLink || isClickOnToggle) { 221 // Verifica se o item já está ativo 222 const isActive = parentLi.classList.contains('menu-item-active'); 223 224 if (!isSubmenu) { 225 // Para itens de primeiro nÃvel: FECHA TUDO PRIMEIRO (abordagem radical) 226 document.querySelectorAll('.menu-item-active').forEach(activeItem => { 227 activeItem.classList.remove('menu-item-active'); 228 }); 229 document.querySelectorAll('.sub-menu.show, .children.show').forEach(openSubmenu => { 230 openSubmenu.classList.remove('show'); 231 }); 232 document.querySelectorAll('.submenu-toggle.rotated').forEach(rotatedToggle => { 233 rotatedToggle.classList.remove('rotated'); 234 }); 235 236 // Se não estava ativo, abre este item 237 if (!isActive) { 238 parentLi.classList.add('menu-item-active'); 239 if (submenu) submenu.classList.add('show'); 240 const toggle = this.querySelector('.submenu-toggle'); 241 if (toggle) toggle.classList.add('rotated'); 242 } 243 } else { 244 // Para submenus, fecha apenas irmãos do mesmo nÃvel 245 const parentMenu = parentLi.parentElement; 246 const siblings = parentMenu.querySelectorAll(':scope > .menu-item-has-children, :scope > .page_item_has_children');
247 siblings.forEach(item => { 248 if (item !== parentLi) { 249 closeSubmenuAndChildren(item); 250 } 251 }); 252 253 // Toggle do submenu 254 if (!isActive) { 255 parentLi.classList.add('menu-item-active'); 256 if (submenu) submenu.classList.add('show'); 257 const toggle = this.querySelector('.submenu-toggle'); 258 if (toggle) toggle.classList.add('rotated'); 259 } else { 260 closeSubmenuAndChildren(parentLi); 261 } 262 } 263 } 264 265 e.preventDefault(); 266 e.stopPropagation(); 267 }); 268 }); 269 } 270 271 // Smooth scroll to anchors 272 function initSmoothScroll() { 273 $('a[href*="#"]:not([href="#"]):not(.skip-link)').click(function() { 274 if (location.pathname.replace(/^\//, '') === this.pathname.replace(/^\//, '') && location.hostname === this.hostname && location.search === this.search) { 275 let target = $(this.hash); 276 target = target.length ? target : $('[name=' + this.hash.slice(1) + ']'); 277 if (target.length) { 278 $('html, body').animate({ 279 scrollTop: target.offset().top - 100 280 }, 1000); 281 return false; 282 } 283 } 284 }); 285 } 286 287 // Sticky header 288 function initStickyHeader() { 289 const header = $('.site-header'); 290 const headerHeight = header.outerHeight(); 291 let lastScroll = 0; 292 293 $(window).scroll(function() { 294 const currentScroll = $(this).scrollTop(); 295 296 if (currentScroll > headerHeight) { 297 if (currentScroll > lastScroll) { 298 header.addClass('header-hidden'); 299 } else { 300 header.removeClass('header-hidden').addClass('header-sticky'); 301 } 302 } else { 303 header.removeClass('header-sticky header-hidden'); 304 } 305 306 lastScroll = currentScroll; 307 }); 308 } 309 310 // Back to top: removido daqui (Sprint 3, Bug B07). Esta função criava um 311 // SEGUNDO botão .back-to-top via jQuery com a classe .show â que nenhum 312 // CSS do tema conhece (o CSS usa .visible) â duplicando o botão real do 313 // footer.php controlado por js/back-to-top.js (vanilla). Um único botão, 314 // um único controlador. 315 316 // Form validation 317 function initFormValidation() { 318 $('form').each(function() { 319 $(this).on('submit', function(e) { 320 let isValid = true; 321 $(this).find('input[required], textarea[required]').each(function() { 322 if (!$(this).val()) { 323 isValid = false; 324 $(this).addClass('error'); 325 } else { 326 $(this).removeClass('error'); 327 } 328 }); 329 330 if (!isValid) { 331 e.preventDefault(); 332 } 333 }); 334 }); 335 } 336 337 // Responsive tables 338 function initResponsiveTables() { 339 $('table').wrap('<div class="table-responsive"></div>'); 340 } 341 342 // Initialize all functions when DOM is ready 343 $(document).ready(function() { 344 initMobileMenu(); 345 initKeyboardNavigation(); // Inicializa navegação por teclado 346 initSmoothScroll(); 347 initStickyHeader(); 348 // initBackToTop() removido â ver Bug B07/Sprint 3 (botão duplicado) 349 initFormValidation(); 350 initResponsiveTables(); 351 initSubmenuActive(); // Inicializa o gerenciamento de submenus ativos 352 }); 353 354 // Handle images loading 355 $(window).on('load', function() { 356 $('.entry-content img').each(function() { 357 $(this).addClass('loaded'); 358 }); 359 }); 360 361})(jQuery);
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.