PageSourceSearch

https://uenf.br/reitoria/wp-content/themes/uenf_sti_theme/js/main.js

js uenf.br collected 2026-09-24 09:18:30 UTC 15,257 bytes, 361 lines download raw bytes

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.