|
|
| Line 1: |
Line 1: |
| $( function () {
| | /* ============================== |
| // sidebar-chunk only applies to desktop-small, but the toggles are hidden at
| | VortanMU - Mega flyout (hover + colunas) |
| // other resolutions regardless and the css overrides any visible effects.
| | Cole no FINAL do MediaWiki:Timeless.js |
| var $dropdowns = $( '#personal, #p-variants-desktop, .sidebar-chunk' );
| | ============================== */ |
| | (function () { |
| | function onReady(fn) { |
| | if (document.readyState === "loading") { |
| | document.addEventListener("DOMContentLoaded", fn); |
| | } else { |
| | fn(); |
| | } |
| | } |
|
| |
|
| /**
| | function clamp(n, min, max) { |
| * Desktop menu click-toggling
| | return Math.max(min, Math.min(max, n)); |
| *
| | } |
| * We're not even checking if it's desktop because the classes in play have no effect
| |
| * on mobile regardless... this may break things at some point, though.
| |
| */
| |
|
| |
|
| /**
| | function calcColumns(portletId, itemCount) { |
| * Close all dropdowns
| | // Regra principal: ~6 itens por coluna |
| */
| | // Mapas é exceção: aceita mais por coluna pra não virar 5+ colunas |
| function closeOpen() {
| | var perCol = 6; |
| $dropdowns.removeClass( 'dropdown-active' );
| | if (portletId === "p-Mapas") perCol = 10; |
| }
| |
|
| |
|
| /**
| | var cols = Math.ceil(itemCount / perCol); |
| * Click behaviour
| | return clamp(cols, 1, 4); |
| */
| | } |
| $dropdowns.on( 'click', function ( e ) {
| |
| // Check if it's already open so we don't open it again
| |
| // eslint-disable-next-line no-jquery/no-class-state
| |
| if ( $( this ).hasClass( 'dropdown-active' ) ) {
| |
| if ( $( e.target ).closest( $( 'h2, #p-variants-desktop h3' ) ).length > 0 ) {
| |
| // treat reclick on the header as a toggle
| |
| closeOpen();
| |
| }
| |
| // Clicked inside an open menu; don't do anything
| |
| } else {
| |
| closeOpen();
| |
| e.stopPropagation(); // stop hiding it!
| |
| $( this ).addClass( 'dropdown-active' );
| |
| }
| |
| } );
| |
| $( document ).on( 'click', function ( e ) {
| |
| if ( $( e.target ).closest( $dropdowns ).length > 0 ) {
| |
| // Clicked inside an open menu; don't close anything
| |
| } else {
| |
| closeOpen();
| |
| }
| |
| } );
| |
| } );
| |
| | |
| mw.hook( 'wikipage.content' ).add( function ( $content ) {
| |
| // Gotta wrap them for this to work; maybe later the parser etc will do this for us?!
| |
| $content.find( 'div > table:not( table table )' ).wrap( '<div class="content-table-wrapper"><div class="content-table"></div></div>' );
| |
| $content.find( '.content-table-wrapper' ).prepend( '<div class="content-table-left"></div><div class="content-table-right"></div>' );
| |
| | |
| /**
| |
| * Set up borders for experimental overflowing table scrolling
| |
| *
| |
| * I have no idea what I'm doing.
| |
| *
| |
| * @param {jQuery} $table
| |
| */
| |
| function setScrollClass( $table ) {
| |
| var $tableWrapper = $table.parent(),
| |
| // wtf browser rtl implementations
| |
| scroll = Math.abs( $tableWrapper.scrollLeft() );
| |
| | |
| $tableWrapper.parent()
| |
| // 1 instead of 0 because of weird rtl rounding errors or something
| |
| .toggleClass( 'scroll-left', scroll > 1 )
| |
| .toggleClass( 'scroll-right', $table.outerWidth() - $tableWrapper.innerWidth() - scroll > 1 );
| |
| }
| |
| | |
| $content.find( '.content-table' ).on( 'scroll', function () {
| |
| setScrollClass( $( this ).children( 'table' ).first() );
| |
| | |
| if ( $content.attr( 'dir' ) === 'rtl' ) {
| |
| $( this ).find( 'caption' ).css( 'margin-right', Math.abs( $( this ).scrollLeft() ) + 'px' );
| |
| } else {
| |
| $( this ).find( 'caption' ).css( 'margin-left', $( this ).scrollLeft() + 'px' );
| |
| }
| |
| } );
| |
| | |
| /**
| |
| * Mark overflowed tables for scrolling
| |
| */
| |
| function unOverflowTables() {
| |
| $content.find( '.content-table > table' ).each( function () {
| |
| var $table = $( this ),
| |
| $wrapper = $table.parent().parent();
| |
| if ( $table.outerWidth() > $wrapper.outerWidth() ) {
| |
| $wrapper.addClass( 'overflowed' );
| |
| setScrollClass( $table );
| |
| } else {
| |
| $wrapper.removeClass( 'overflowed scroll-left scroll-right fixed-scrollbar-container' );
| |
| }
| |
| } );
| |
| | |
| // Set up sticky captions
| |
| $content.find( '.content-table > table > caption' ).each( function () {
| |
| var $container, tableHeight,
| |
| $table = $( this ).parent(),
| |
| $wrapper = $table.parent().parent();
| |
| | |
| if ( $table.outerWidth() > $wrapper.outerWidth() ) {
| |
| $container = $( this ).parents( '.content-table-wrapper' );
| |
| $( this ).width( $content.width() );
| |
| tableHeight = $container.innerHeight() - $( this ).outerHeight();
| |
| | |
| $container.find( '.content-table-left' ).height( tableHeight );
| |
| $container.find( '.content-table-right' ).height( tableHeight );
| |
| }
| |
| } );
| |
| }
| |
| | |
| unOverflowTables();
| |
| $( window ).on( 'resize', unOverflowTables );
| |
| | |
| /**
| |
| * Sticky scrollbars maybe?!
| |
| */
| |
| $content.find( '.content-table' ).each( function () {
| |
| var $table, $tableWrapper, $spoof, $scrollbar;
| |
| | |
| $tableWrapper = $( this );
| |
| $table = $tableWrapper.children( 'table' ).first();
| |
| | |
| // Assemble our silly crap and add to page
| |
| $scrollbar = $( '<div>' ).addClass( 'content-table-scrollbar inactive' ).width( $content.width() );
| |
| $spoof = $( '<div>' ).addClass( 'content-table-spoof' ).width( $table.outerWidth() );
| |
| $tableWrapper.parent().prepend( $scrollbar.prepend( $spoof ) );
| |
| } );
| |
| | |
| /**
| |
| * Scoll table when scrolling scrollbar and visa-versa lololol wut
| |
| */
| |
| $content.find( '.content-table' ).on( 'scroll', function () {
| |
| // Only do this here if we're not already mirroring the spoof
| |
| var $mirror = $( this ).siblings( '.inactive' ).first();
| |
| | |
| $mirror.scrollLeft( $( this ).scrollLeft() );
| |
| } );
| |
| $content.find( '.content-table-scrollbar' ).on( 'scroll', function () {
| |
| var $mirror = $( this ).siblings( '.content-table' ).first();
| |
| | |
| // Only do this here if we're not already mirroring the table
| |
| // eslint-disable-next-line no-jquery/no-class-state
| |
| if ( !$( this ).hasClass( 'inactive' ) ) {
| |
| $mirror.scrollLeft( $( this ).scrollLeft() );
| |
| }
| |
| } );
| |
| | |
| /**
| |
| * Set active when actually over the table it applies to...
| |
| */
| |
| function determineActiveSpoofScrollbars() {
| |
| $content.find( '.overflowed .content-table' ).each( function () {
| |
| var $scrollbar = $( this ).siblings( '.content-table-scrollbar' ).first();
| |
| | |
| // Skip caption
| |
| var captionHeight = $( this ).find( 'caption' ).outerHeight() || 0;
| |
| if ( captionHeight ) {
| |
| // Pad slightly for reasons
| |
| captionHeight += 8;
| |
| }
| |
| | |
| var tableTop = $( this ).offset().top,
| |
| tableBottom = tableTop + $( this ).outerHeight(),
| |
| viewBottom = window.scrollY + document.documentElement.clientHeight,
| |
| active = tableTop + captionHeight < viewBottom && tableBottom > viewBottom;
| |
| $scrollbar.toggleClass( 'inactive', !active );
| |
| } );
| |
| }
| |
| | |
| determineActiveSpoofScrollbars();
| |
| $( window ).on( 'scroll resize', determineActiveSpoofScrollbars );
| |
| | |
| | |
| function showContent(id) {
| |
| const conteudos = document.querySelectorAll('.nav-content');
| |
| conteudos.forEach((div) => {
| |
| div.classList.remove('show-content');
| |
| });
| |
| document.getElementById(id).classList.add('show-content');
| |
| }
| |
| | |
| /**
| |
| * Make sure tablespoofs remain correctly-sized?
| |
| */
| |
| $( window ).on( 'resize', function () {
| |
| $content.find( '.content-table-scrollbar' ).each( function () {
| |
| var width = $( this ).siblings().first().find( 'table' ).first().width();
| |
| $( this ).find( '.content-table-spoof' ).first().width( width );
| |
| $( this ).width( $content.width() );
| |
| } );
| |
| } );
| |
| } );
| |
| /* Popout menus (header) */
| |
| | |
| /* eslint-disable no-jquery/no-fade */
| |
| | |
| $( function () {
| |
| var toggleTime = 200;
| |
| | |
| // Open the various menus
| |
| $( '#user-tools h2' ).on( 'click', function () {
| |
| if ( $( window ).width() < 851 ) {
| |
| $( '#personal-inner, #menus-cover' ).fadeToggle( toggleTime );
| |
| }
| |
| } );
| |
| $( '#site-navigation h2' ).on( 'click', function () {
| |
| if ( $( window ).width() < 851 ) {
| |
| $( '#site-navigation .sidebar-inner, #menus-cover' ).fadeToggle( toggleTime );
| |
| }
| |
| } );
| |
| $( '#site-tools h2' ).on( 'click', function () {
| |
| if ( $( window ).width() < 851 ) {
| |
| $( '#site-tools .sidebar-inner, #menus-cover' ).fadeToggle( toggleTime );
| |
| }
| |
| } );
| |
| $( '#ca-more' ).on( 'click', function () {
| |
| $( '#page-tools .sidebar-inner' ).css( 'top', $( '#ca-more' ).offset().top + 25 );
| |
| if ( $( window ).width() < 851 ) {
| |
| $( '#page-tools .sidebar-inner, #menus-cover' ).fadeToggle( toggleTime );
| |
| }
| |
| } );
| |
| $( '#ca-languages' ).on( 'click', function () {
| |
| $( '#other-languages .sidebar-inner' ).css( 'top', $( '#ca-languages' ).offset().top + 25 );
| |
| if ( $( window ).width() < 851 ) {
| |
| $( '#other-languages .sidebar-inner, #menus-cover' ).fadeToggle( toggleTime );
| |
| }
| |
| } );
| |
| | |
| // Close menus on click outside
| |
| $( document ).on( 'click touchstart', function ( e ) {
| |
| if ( $( e.target ).closest( '#menus-cover' ).length > 0 ) {
| |
| $( '#personal-inner' ).fadeOut( toggleTime );
| |
| $( '.sidebar-inner' ).fadeOut( toggleTime );
| |
| $( '#menus-cover' ).fadeOut( toggleTime );
| |
| }
| |
| } );
| |
| } );
| |
| | |
| // Melhoria do comportamento dos submenus multi-coluna
| |
| $(document).ready(function() {
| |
| | |
| // Função para aplicar classes de coluna dinamicamente se necessário
| |
| function adjustSubmenuColumns() {
| |
| $('.submenu').each(function() {
| |
| const $submenu = $(this);
| |
| const itemCount = $submenu.find('li').length;
| |
| | |
| // Remove classes existentes
| |
| $submenu.removeClass('submenu-2-columns submenu-3-columns submenu-4-columns');
| |
| | |
| // Aplica classe baseada na quantidade de itens
| |
| if (itemCount > 20) {
| |
| $submenu.addClass('submenu-4-columns');
| |
| } else if (itemCount > 12) {
| |
| $submenu.addClass('submenu-3-columns');
| |
| } else if (itemCount > 6) {
| |
| $submenu.addClass('submenu-2-columns');
| |
| }
| |
| });
| |
| }
| |
| | |
| // Aplica as classes na inicialização
| |
| adjustSubmenuColumns();
| |
| | |
| // Comportamento móvel para submenus
| |
| if ($(window).width() <= 850) {
| |
| $('li.has-submenu > a').on('click', function(e) {
| |
| e.preventDefault();
| |
| var $parent = $(this).parent();
| |
| | |
| if ($parent.hasClass('active')) {
| |
| $parent.removeClass('active');
| |
| } else {
| |
| // Fecha outros menus abertos
| |
| $('li.has-submenu.active').removeClass('active');
| |
| $parent.addClass('active');
| |
| }
| |
| });
| |
| }
| |
| | |
| // Força aplicação de colunas em navegadores específicos
| |
| function forceColumnLayout() {
| |
| // Verifica se as colunas não estão sendo aplicadas corretamente
| |
| $('.submenu.submenu-2-columns').each(function() {
| |
| const $this = $(this);
| |
| if ($this.is(':visible')) {
| |
| const width = $this.width();
| |
| const items = $this.find('li');
| |
| | |
| // Se parece que não está em colunas, força o CSS
| |
| if (items.length > 6 && width < 400) {
| |
| $this.css({
| |
| 'column-count': '2',
| |
| 'column-gap': '20px',
| |
| 'min-width': '450px'
| |
| });
| |
| }
| |
| }
| |
| });
| |
| | |
| $('.submenu.submenu-3-columns').each(function() {
| |
| const $this = $(this);
| |
| if ($this.is(':visible')) {
| |
| const width = $this.width();
| |
| const items = $this.find('li');
| |
| | |
| if (items.length > 12 && width < 550) {
| |
| $this.css({
| |
| 'column-count': '3',
| |
| 'column-gap': '20px',
| |
| 'min-width': '600px'
| |
| });
| |
| }
| |
| }
| |
| });
| |
| | |
| $('.submenu.submenu-4-columns').each(function() {
| |
| const $this = $(this);
| |
| if ($this.is(':visible')) {
| |
| const width = $this.width();
| |
| const items = $this.find('li');
| |
| | |
| if (items.length > 20 && width < 700) {
| |
| $this.css({
| |
| 'column-count': '4',
| |
| 'column-gap': '20px',
| |
| 'min-width': '750px'
| |
| });
| |
| }
| |
| }
| |
| });
| |
| }
| |
| | |
| // Aplica verificação quando submenus são mostrados
| |
| $('li.has-submenu').on('mouseenter', function() {
| |
| setTimeout(forceColumnLayout, 50);
| |
| });
| |
| | |
| // Reaplica classes quando a janela é redimensionada
| |
| $(window).on('resize', function() {
| |
| adjustSubmenuColumns();
| |
| setTimeout(forceColumnLayout, 100);
| |
| });
| |
| | |
| // Garante que o comportamento seja aplicado em conteúdo carregado dinamicamente
| |
| if (typeof MutationObserver !== 'undefined') {
| |
| const observer = new MutationObserver(function(mutations) {
| |
| mutations.forEach(function(mutation) {
| |
| if (mutation.addedNodes.length) {
| |
| // Verifica se algum dos nós adicionados contém submenus
| |
| $(mutation.addedNodes).find('.submenu').each(function() {
| |
| adjustSubmenuColumns();
| |
| });
| |
| }
| |
| });
| |
| });
| |
| | |
| observer.observe(document.body, { childList: true, subtree: true });
| |
| }
| |
| | |
| // Função de debug para verificar se as colunas estão funcionando
| |
| window.debugSubmenus = function() {
| |
| $('.submenu').each(function() {
| |
| const $this = $(this);
| |
| const itemCount = $this.find('li').length;
| |
| const isVisible = $this.is(':visible');
| |
| const columnCount = $this.css('column-count');
| |
| const width = $this.width();
| |
| | |
| console.log('Submenu:', {
| |
| items: itemCount,
| |
| visible: isVisible,
| |
| columnCount: columnCount,
| |
| width: width,
| |
| classes: $this.attr('class')
| |
| });
| |
| });
| |
| };
| |
| });
| |
| // Adiciona efeitos de interação
| |
| document.addEventListener('DOMContentLoaded', function() {
| |
| const guideItems = document.querySelectorAll('.mw-guide-item');
| |
| | |
| guideItems.forEach(item => {
| |
| item.addEventListener('mouseenter', function() {
| |
| this.style.transform = 'translateY(-3px)';
| |
| });
| |
|
| |
|
| item.addEventListener('mouseleave', function() {
| | onReady(function () { |
| this.style.transform = 'translateY(0)';
| | var nav = document.getElementById("site-navigation"); |
| });
| |
| | |
| item.addEventListener('click', function(e) {
| |
| // Adiciona efeito de clique
| |
| this.style.transform = 'translateY(1px)';
| |
| setTimeout(() => {
| |
| this.style.transform = 'translateY(-3px)';
| |
| }, 100);
| |
| });
| |
| });
| |
| });
| |
| // JavaScript para Cards com Imagem - compatível com sistema existente
| |
| $(document).ready(function() {
| |
| | |
| // Função para gerenciar cards com imagem (reutiliza lógica existente)
| |
| function initImageCards() {
| |
| const imageCards = $('.mw-image-card');
| |
| | |
| // Aplica os mesmos efeitos dos guias existentes
| |
| imageCards.each(function() {
| |
| const $card = $(this);
| |
| | |
| // Efeitos hover melhorados
| |
| $card.on('mouseenter', function() {
| |
| $(this).css({
| |
| 'transform': 'translateY(-8px) scale(1.02)',
| |
| 'transition': 'all 0.3s ease'
| |
| });
| |
| });
| |
| | |
| $card.on('mouseleave', function() {
| |
| $(this).css({
| |
| 'transform': 'translateY(0) scale(1)',
| |
| 'transition': 'all 0.3s ease'
| |
| });
| |
| });
| |
| | |
| // Efeito de clique
| |
| $card.on('click', function() {
| |
| $(this).css('transform', 'translateY(-2px) scale(0.98)');
| |
| setTimeout(() => {
| |
| $(this).css('transform', 'translateY(-8px) scale(1.02)');
| |
| }, 150);
| |
| });
| |
| });
| |
| }
| |
| | |
| // Função para gerenciar carregamento de imagens
| |
| function handleImageLoading() {
| |
| $('.mw-card-image img').each(function() {
| |
| const $img = $(this);
| |
| const $container = $img.parent();
| |
| | |
| // Adiciona classe de loading
| |
| $container.addClass('loading');
| |
| | |
| // Quando a imagem carregar
| |
| $img.on('load', function() {
| |
| $container.removeClass('loading');
| |
| });
| |
| | |
| // Se a imagem falhar ao carregar
| |
| $img.on('error', function() {
| |
| $container.removeClass('loading');
| |
| // Mantém o gradiente de fundo como fallback
| |
| $img.hide();
| |
| });
| |
| | |
| // Se a imagem já estiver carregada (cache)
| |
| if (this.complete) {
| |
| $container.removeClass('loading');
| |
| }
| |
| });
| |
| }
| |
| | |
| // Função de responsividade para cards com imagem
| |
| function adjustImageGrid() {
| |
| const imageGrid = $('.mw-image-grid');
| |
| const width = $(window).innerWidth;
| |
| | |
| if (width < 480) {
| |
| imageGrid.css('grid-template-columns', '1fr');
| |
| } else if (width < 768) {
| |
| imageGrid.css('grid-template-columns', 'repeat(auto-fit, minmax(250px, 1fr))');
| |
| } else {
| |
| imageGrid.css('grid-template-columns', 'repeat(auto-fit, minmax(300px, 1fr))');
| |
| }
| |
| }
| |
| | |
| // Função para lazy loading de imagens (opcional)
| |
| function lazyLoadImages() {
| |
| if ('IntersectionObserver' in window) {
| |
| const imageObserver = new IntersectionObserver((entries, observer) => {
| |
| entries.forEach(entry => {
| |
| if (entry.isIntersecting) {
| |
| const img = entry.target;
| |
| const dataSrc = img.getAttribute('data-src');
| |
| if (dataSrc) {
| |
| img.src = dataSrc;
| |
| img.removeAttribute('data-src');
| |
| imageObserver.unobserve(img);
| |
| }
| |
| }
| |
| });
| |
| });
| |
| | |
| $('.mw-card-image img[data-src]').each(function() {
| |
| imageObserver.observe(this);
| |
| });
| |
| }
| |
| }
| |
| | |
| // Integração com sistema existente de guias
| |
| function integrateWithExistingSystem() {
| |
| // Estende a funcionalidade existente para incluir cards de imagem
| |
| if (typeof window.adjustGuideGrid === 'function') {
| |
| const originalAdjustGuideGrid = window.adjustGuideGrid;
| |
| window.adjustGuideGrid = function() {
| |
| originalAdjustGuideGrid();
| |
| adjustImageGrid();
| |
| };
| |
| } else {
| |
| window.adjustGuideGrid = adjustImageGrid;
| |
| }
| |
| | |
| // Adiciona cards de imagem ao sistema de debug existente
| |
| if (typeof window.debugSubmenus === 'function') {
| |
| const originalDebug = window.debugSubmenus;
| |
| window.debugImageCards = function() {
| |
| $('.mw-image-card').each(function() {
| |
| const $card = $(this);
| |
| const text = $card.find('.mw-card-text').text();
| |
| const hasImage = $card.find('img').length > 0;
| |
| const imageLoaded = hasImage ? $card.find('img')[0].complete : false;
| |
| | |
| console.log('Image Card:', {
| |
| text: text,
| |
| hasImage: hasImage,
| |
| imageLoaded: imageLoaded,
| |
| dimensions: {
| |
| width: $card.width(),
| |
| height: $card.height()
| |
| }
| |
| });
| |
| });
| |
| | |
| // Chama debug original se existir
| |
| if (originalDebug) originalDebug();
| |
| };
| |
| }
| |
| }
| |
| | |
| // Inicialização
| |
| initImageCards();
| |
| handleImageLoading();
| |
| adjustImageGrid();
| |
| lazyLoadImages();
| |
| integrateWithExistingSystem();
| |
| | |
| // Event listeners
| |
| $(window).on('resize', function() {
| |
| adjustImageGrid();
| |
| });
| |
| | |
| // Observa mudanças no DOM para novos cards adicionados dinamicamente
| |
| if (typeof MutationObserver !== 'undefined') {
| |
| const observer = new MutationObserver(function(mutations) {
| |
| mutations.forEach(function(mutation) {
| |
| if (mutation.addedNodes.length) {
| |
| $(mutation.addedNodes).find('.mw-image-card').each(function() {
| |
| initImageCards();
| |
| handleImageLoading();
| |
| });
| |
| }
| |
| });
| |
| });
| |
| | |
| observer.observe(document.body, { childList: true, subtree: true });
| |
| }
| |
| });
| |
| | |
| // Função global para adicionar novos cards dinamicamente
| |
| window.addImageCard = function(container, link, imageSrc, text, bgClass) {
| |
| const cardHtml = `
| |
| <a href="${link}" class="mw-image-card">
| |
| <div class="mw-card-image ${bgClass || ''}">
| |
| <img src="${imageSrc}" alt="${text}" />
| |
| </div>
| |
| <div class="mw-card-text">${text}</div>
| |
| </a>
| |
| `;
| |
| | |
| $(container).find('.mw-image-grid').append(cardHtml);
| |
| | |
| // Reinicializa funcionalidades para o novo card
| |
| const $newCard = $(container).find('.mw-image-card').last();
| |
| $newCard.trigger('initImageCard');
| |
| };
| |
| | |
| // Função para tornar todas as tabelas responsivas
| |
| function makeTablesResponsive() {
| |
| // Seleciona todas as tabelas da página
| |
| const tables = document.querySelectorAll('table');
| |
| | |
| tables.forEach(table => {
| |
| // Verifica se a tabela tem cabeçalhos (th)
| |
| let headers = Array.from(table.querySelectorAll('th')).map(th => th.textContent.trim());
| |
| | |
| // Se não houver cabeçalhos, tenta usar a primeira linha como cabeçalho
| |
| if (headers.length === 0) {
| |
| const firstRow = table.querySelector('tr');
| |
| if (firstRow) {
| |
| const cells = firstRow.querySelectorAll('td');
| |
| headers.push(...Array.from(cells).map(cell => cell.textContent.trim()));
| |
| }
| |
| }
| |
| | |
| // Para tabelas com cabeçalhos em múltiplas linhas, tentamos capturar todos
| |
| if (table.querySelectorAll('thead tr').length > 1) {
| |
| // Para tabelas com cabeçalhos complexos, usamos a última linha de cabeçalhos
| |
| const headerRows = table.querySelectorAll('thead tr');
| |
| const lastHeaderRow = headerRows[headerRows.length - 1];
| |
| headers = Array.from(lastHeaderRow.querySelectorAll('th')).map(th => th.textContent.trim());
| |
| }
| |
| | |
| // Para cada linha da tabela, adiciona atributos data-label às células
| |
| const rows = table.querySelectorAll('tr');
| |
| | |
| // Começa a partir da primeira linha após os cabeçalhos
| |
| // Se estamos em uma tabela sem thead ou com th, começamos nas linhas de tbody
| |
| // Se tabela tiver thead, ignoramos as linhas de cabeçalho
| |
| let startRow = 0;
| |
| if (table.querySelector('thead')) {
| |
| startRow = table.querySelectorAll('thead tr').length;
| |
| } else if (headers.length > 0 && table.querySelectorAll('th').length > 0) {
| |
| // Se não tem thead mas tem th, assumimos primeira linha como cabeçalho
| |
| startRow = 1;
| |
| }
| |
| | |
| for (let i = startRow; i < rows.length; i++) {
| |
| const cells = rows[i].querySelectorAll('td');
| |
| cells.forEach((cell, index) => {
| |
| if (index < headers.length && headers[index]) {
| |
| cell.setAttribute('data-label', headers[index]);
| |
| }
| |
| });
| |
| }
| |
| });
| |
| }
| |
| | |
| // Executa quando o DOM estiver pronto
| |
| if (document.readyState === 'loading') {
| |
| document.addEventListener('DOMContentLoaded', makeTablesResponsive);
| |
| } else {
| |
| makeTablesResponsive();
| |
| }
| |
| | |
| // Verifica novamente após o carregamento da página (para conteúdo carregado dinamicamente)
| |
| window.addEventListener('load', makeTablesResponsive);
| |
| | |
| // Para lidar com conteúdo adicionado dinamicamente (como em edições AJAX)
| |
| // Observe mutações no DOM
| |
| if (typeof MutationObserver !== 'undefined') {
| |
| const observer = new MutationObserver(function(mutations) {
| |
| mutations.forEach(function(mutation) {
| |
| if (mutation.addedNodes.length) {
| |
| makeTablesResponsive();
| |
| }
| |
| });
| |
| });
| |
| | |
| // Observe mudanças no corpo do documento
| |
| observer.observe(document.body, { childList: true, subtree: true });
| |
| }
| |
| mw.loader.state({
| |
| "skins.timeless.js": "ready"
| |
| });
| |
| /*
| |
| VortanMU - Timeless sidebar behavior (v3) | |
| - Hover to open (desktop) + click to toggle (touch/keyboard)
| |
| - Keeps original Timeless structure (portlets)
| |
| - Avoids conflicts with any previously-injected mega-menu nodes
| |
| | |
| Install: paste into MediaWiki:Timeless.js (replace your current sidebar script).
| |
| */
| |
| (function () { | |
| 'use strict';
| |
| if (window.VM_TIMELESS_SIDEBAR_V3) return;
| |
| window.VM_TIMELESS_SIDEBAR_V3 = true;
| |
| | |
| function enhanceSidebar() {
| |
| var nav = document.getElementById('mw-site-navigation'); | |
| if (!nav) return; | | if (!nav) return; |
|
| |
|
| nav.classList.add('vm-nav'); | | var portlets = nav.querySelectorAll(".mw-portlet"); |
| | portlets.forEach(function (portlet) { |
| | var h3 = portlet.querySelector("h3"); |
| | var body = portlet.querySelector(".mw-portlet-body"); |
| | if (!h3 || !body) return; |
|
| |
|
| // Remove any old injected mega-menu nodes (common source of “applies then reverts”)
| | var ul = body.querySelector("ul"); |
| Array.prototype.forEach.call(nav.querySelectorAll('ul.submenu'), function (el) {
| | if (!ul) return; |
| el.parentNode && el.parentNode.removeChild(el); | |
| });
| |
|
| |
|
| var portlets = Array.prototype.slice.call(
| | var lis = Array.from(ul.querySelectorAll("li")); |
| nav.querySelectorAll('.mw-portlet[role="navigation"]')
| | if (!lis.length) return; |
| );
| |
|
| |
|
| var hoverCapable = !!(window.matchMedia && window.matchMedia('(hover:hover) and (pointer:fine)').matches);
| | // Marca como “mega” |
| | h3.classList.add("has-mega"); |
|
| |
|
| function setExpanded(p, expanded) {
| | // Cria o submenu (se ainda não existe) |
| var h = p.querySelector('h3.vm-portlet__header') || p.querySelector('h3'); | | var submenu = portlet.querySelector("ul.submenu"); |
| if (h) h.setAttribute('aria-expanded', expanded ? 'true' : 'false'); | | if (!submenu) { |
| }
| | submenu = document.createElement("ul"); |
| | submenu.className = "submenu"; |
| | portlet.appendChild(submenu); |
| | } |
|
| |
|
| function closePortlet(p) {
| | // Reconstrói os itens do flyout (mantém sempre consistente) |
| p.classList.remove('is-open');
| | submenu.innerHTML = ""; |
| setExpanded(p, false);
| | lis.forEach(function (li) { |
| }
| | var a = li.querySelector("a"); |
| | if (!a) return; |
| | var li2 = document.createElement("li"); |
| | li2.appendChild(a.cloneNode(true)); |
| | submenu.appendChild(li2); |
| | }); |
|
| |
|
| function openPortlet(p) {
| | // Define quantidade de colunas por regra |
| // close others (keeps sidebar clean), but never close Wiki tools | | var count = submenu.querySelectorAll("li").length; |
| portlets.forEach(function (other) { | | var cols = calcColumns(portlet.id || "", count); |
| if (other !== p && other.id !== 'p-tb') closePortlet(other);
| |
| });
| |
| p.classList.add('is-open');
| |
| setExpanded(p, true); | |
| }
| |
|
| |
|
| portlets.forEach(function (p) {
| | submenu.classList.remove( |
| var h3 = p.querySelector('h3'); | | "submenu-1-columns", |
| var body = p.querySelector('.mw-portlet-body'); | | "submenu-2-columns", |
| if (!h3 || !body) return;
| | "submenu-3-columns", |
| | "submenu-4-columns" |
| | ); |
| | submenu.classList.add("submenu-" + cols + "-columns"); |
|
| |
|
| h3.classList.add('vm-portlet__header'); | | // Esconde UL original (menu vira “botão” + flyout) |
| body.classList.add('vm-portlet__body'); | | ul.style.display = "none"; |
| | body.style.display = "none"; |
|
| |
|
| // If something hid the original <ul> inline, bring it back | | // Hover abre/fecha com pequeno delay (pra dar tempo de mover o mouse) |
| var ul = body.querySelector('ul');
| | var hideTimer = null; |
| if (ul && ul.style && ul.style.display === 'none') ul.style.display = ''; | |
|
| |
|
| // Wiki tools: always visible | | function show() { |
| if (p.id === 'p-tb') {
| | if (hideTimer) clearTimeout(hideTimer); |
| p.classList.add('is-open'); | | submenu.style.display = "grid"; |
| h3.setAttribute('aria-expanded', 'true');
| |
| return; | |
| } | | } |
|
| |
|
| // default closed | | function hide() { |
| closePortlet(p);
| | hideTimer = setTimeout(function () { |
| | submenu.style.display = "none"; |
| | }, 140); |
| | } |
|
| |
|
| // accessibility + click toggle | | // Hover desktop |
| h3.setAttribute('role', 'button'); | | h3.addEventListener("mouseenter", show); |
| h3.setAttribute('tabindex', '0'); | | portlet.addEventListener("mouseenter", show); |
| h3.setAttribute('aria-expanded', 'false'); | | portlet.addEventListener("mouseleave", hide); |
| | submenu.addEventListener("mouseenter", show); |
| | submenu.addEventListener("mouseleave", hide); |
|
| |
|
| function toggle() { | | // Clique (fallback) |
| if (p.classList.contains('is-open')) closePortlet(p);
| | h3.addEventListener("click", function (e) { |
| else openPortlet(p);
| | // em desktop isso não atrapalha, e em touch ajuda |
| }
| |
| | |
| h3.addEventListener('click', function (e) { | |
| e.preventDefault(); | | e.preventDefault(); |
| toggle(); | | var isOpen = submenu.style.display === "grid"; |
| | submenu.style.display = isOpen ? "none" : "grid"; |
| }); | | }); |
|
| |
| h3.addEventListener('keydown', function (e) {
| |
| if (e.key === 'Enter' || e.key === ' ') {
| |
| e.preventDefault();
| |
| toggle();
| |
| }
| |
| });
| |
|
| |
| // Hover open (desktop)
| |
| if (hoverCapable) {
| |
| var t;
| |
| p.addEventListener('mouseenter', function () {
| |
| if (t) clearTimeout(t);
| |
| openPortlet(p);
| |
| });
| |
| p.addEventListener('mouseleave', function () {
| |
| if (t) clearTimeout(t);
| |
| t = setTimeout(function () { closePortlet(p); }, 180);
| |
| });
| |
| }
| |
| }); | | }); |
| } | | }); |
| | |
| // Run on normal loads + after purge/navigation
| |
| if (window.mw && mw.hook) {
| |
| mw.hook('wikipage.content').add(enhanceSidebar);
| |
| } else if (document.readyState === 'loading') {
| |
| document.addEventListener('DOMContentLoaded', enhanceSidebar);
| |
| } else {
| |
| enhanceSidebar();
| |
| }
| |
| })(); | | })(); |