MediaWiki:ShowFirstTabs.js: Difference between revisions

From CoraTO Wiki - Official Wiki
Jump to navigation Jump to search
No edit summary
Line 1: Line 1:
/**
(function () {
* MediaWiki Tab Navigation Module
  'use strict';
* Extracted from Common copy.js for modularity and customization
 
* Handles tab switching functionality with smooth scrolling support
  function getTabTargetId(tabElement) {
* Enhanced to work with both MediaWiki tabs and nested tabs
    if (!tabElement) return null;
*/
    const dataTab = tabElement.getAttribute('data-tab');
    if (dataTab) return dataTab;
    const ariaControls = tabElement.getAttribute('aria-controls');
    if (ariaControls) return ariaControls;
    const href = tabElement.getAttribute('href');
    if (href && href.charAt(0) === '#') return href.slice(1);
    return null;
  }


(function() {
  function escapeForAttributeSelector(value) {
  'use strict';
    if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(value);
    return String(value).replace(/[\\\"\n\r\f]/g, '\\$&');
  }


   const CONFIG = {
   function findScopedElementById(scopeRoot, id) {
     SELECTORS: {
    if (!id) return null;
       TAB_CONTENT: '.tab-content',
    const scope = scopeRoot && scopeRoot !== document ? scopeRoot : document;
       NAV_TAB: '.nav-tab, .nav-tab-fgod',
     if (scope !== document) {
      NESTED_TAB: '.nested-tab',
       const escaped = escapeForAttributeSelector(id);
      NESTED_CONTENT: '.nested-content'
       return scope.querySelector('[id="' + escaped + '"]');
    },
    CLASSES: {
      ACTIVE: 'active'
    },
    DELAYS: {
      SCROLL_DELAY: 100
     }
     }
   };
    return document.getElementById(id);
   }


   function safeGetElementById(id) {
   function setActiveState(element, isActive) {
     try { return document.getElementById(id); } catch (e) { return null; }
     if (!element) return;
    element.classList.toggle('active', isActive);
    if (element.hasAttribute('aria-selected')) {
      element.setAttribute('aria-selected', isActive ? 'true' : 'false');
    }
    if (element.hasAttribute('aria-hidden')) {
      element.setAttribute('aria-hidden', isActive ? 'false' : 'true');
    }
    if (element.hasAttribute('tabindex')) {
      element.setAttribute('tabindex', isActive ? '0' : '-1');
    }
   }
   }
   function safeQuerySelectorAll(selector, context = document) {
 
     try { return context.querySelectorAll(selector); } catch (e) { return []; }
   function getScopeForTablist(tablistElement) {
     if (!tablistElement) return document;
    if (tablistElement.classList.contains('nav-tabs')) {
      return tablistElement.closest('.tabs-container') || tablistElement.parentElement || document;
    }
    return tablistElement.closest('.nested-content, .tab-content') || document;
   }
   }
   function safeQuerySelector(selector, context = document) {
 
     try { return context.querySelector(selector); } catch (e) { return null; }
   function showNavTab(tabElement) {
    const tablist = tabElement.closest('.nav-tabs');
    if (!tablist) return;
 
    const scope = getScopeForTablist(tablist);
    const tabs = Array.from(tablist.querySelectorAll('.nav-tab[data-tab]'));
    const tabMappings = tabs
      .map((tab) => ({ tab, id: getTabTargetId(tab) }))
      .filter((m) => !!m.id);
 
    const targetId = getTabTargetId(tabElement);
    if (!targetId) return;
 
     for (const mapping of tabMappings) {
      setActiveState(mapping.tab, mapping.tab === tabElement);
      const panel = findScopedElementById(scope, mapping.id);
      if (panel && panel.classList.contains('tab-content')) {
        setActiveState(panel, mapping.id === targetId);
      }
    }
 
    const targetPanel = findScopedElementById(scope, targetId);
    if (targetPanel && targetPanel.classList.contains('tab-content')) {
      setActiveState(targetPanel, true);
      ensureNestedChain(targetPanel);
    }
   }
   }


   const TabNavigation = {
   function showNestedTab(tabElement) {
     showTab: function(tabId, tabElement) {
     const tablist = tabElement.closest('.nested-tabs');
      if (!tabId) return;
    if (!tablist) return;


      const allNested = document.querySelectorAll(CONFIG.SELECTORS.NESTED_CONTENT);
    const scope = getScopeForTablist(tablist);
       allNested.forEach(c => c.classList.remove(CONFIG.CLASSES.ACTIVE));
    const tabs = Array.from(tablist.querySelectorAll('.nested-tab[data-tab]'));
    const tabMappings = tabs
       .map((tab) => ({ tab, id: getTabTargetId(tab) }))
      .filter((m) => !!m.id);


      const contents = safeQuerySelectorAll(CONFIG.SELECTORS.TAB_CONTENT);
    const targetId = getTabTargetId(tabElement);
      contents.forEach(c => c.classList.remove(CONFIG.CLASSES.ACTIVE));
    if (!targetId) return;


       const tabs = safeQuerySelectorAll(CONFIG.SELECTORS.NAV_TAB);
    for (const mapping of tabMappings) {
       tabs.forEach(t => t.classList.remove(CONFIG.CLASSES.ACTIVE));
      setActiveState(mapping.tab, mapping.tab === tabElement);
       const panel = findScopedElementById(scope, mapping.id);
       if (panel && panel.classList.contains('nested-content')) {
        setActiveState(panel, mapping.id === targetId);
      }
    }


      const target = safeGetElementById(tabId);
    const targetPanel = findScopedElementById(scope, targetId);
       if (target) {
    if (targetPanel && targetPanel.classList.contains('nested-content')) {
        target.classList.add(CONFIG.CLASSES.ACTIVE);
      setActiveState(targetPanel, true);
       ensureNestedChain(targetPanel);
    }
  }
 
  function findDirectNestedTablist(containerElement) {
    if (!containerElement) return null;
    const tablists = Array.from(containerElement.querySelectorAll('.nested-tabs'));
    if (tablists.length === 0) return null;
 
    const containerIsNested = containerElement.classList.contains('nested-content');


        setTimeout(() => {
    for (const tablist of tablists) {
          const first = target.querySelector(CONFIG.SELECTORS.NESTED_TAB + '[data-tab]');
      const closestNestedContent = tablist.closest('.nested-content');
          if (first) {
      if (containerIsNested) {
            const nid = first.getAttribute('data-tab');
        if (closestNestedContent === containerElement) return tablist;
            if (nid) TabNavigation.showNestedTab(nid, first);
      } else {
          }
         if (!closestNestedContent) return tablist;
         }, 50);
       }
       }
    }


      if (tabElement) tabElement.classList.add(CONFIG.CLASSES.ACTIVE);
    return null;
    },
  }


    showNestedTab: function(tabId, tabElement) {
  function ensureNestedChain(activeContainer) {
      if (!tabId) return;
    let currentContainer = activeContainer;
      const container = tabElement
        ? (tabElement.closest('.nested-container, .token-card')
            || tabElement.closest('.tokens-section')
            || tabElement.closest('.tab-content')
            || document)
        : document;


       const nestedContents = container.querySelectorAll(CONFIG.SELECTORS.NESTED_CONTENT);
    while (currentContainer) {
       nestedContents.forEach(c => c.classList.remove(CONFIG.CLASSES.ACTIVE));
       const nestedTablist = findDirectNestedTablist(currentContainer);
       if (!nestedTablist) return;


       const nestedTabs = container.querySelectorAll(CONFIG.SELECTORS.NESTED_TAB);
       const nestedTabs = Array.from(nestedTablist.querySelectorAll('.nested-tab[data-tab]'));
       nestedTabs.forEach(t => t.classList.remove(CONFIG.CLASSES.ACTIVE));
       if (nestedTabs.length === 0) return;


       let target = null;
       let tabToActivate = nestedTabs.find((t) => t.classList.contains('active'));
      if (container && container.querySelector) target = container.querySelector('#' + tabId);
       if (!tabToActivate) tabToActivate = nestedTabs[0];
       if (!target) target = safeGetElementById(tabId);
      if (target) target.classList.add(CONFIG.CLASSES.ACTIVE);


       if (tabElement) tabElement.classList.add(CONFIG.CLASSES.ACTIVE);
       const targetId = getTabTargetId(tabToActivate);
    },
      if (!targetId) return;


     handleTabClick: function(event) {
      showNestedTab(tabToActivate);
      const tab = event.target.closest('.nav-tab[data-tab], .nav-tab-fgod[data-tab]');
 
      if (!tab) return;
      const nextContainer = findScopedElementById(currentContainer, targetId);
      event.preventDefault();
      if (!nextContainer || !nextContainer.classList.contains('nested-content')) return;
      const targetId = tab.getAttribute('data-tab');
 
       if (targetId) TabNavigation.showTab(targetId, tab);
      currentContainer = nextContainer;
     },
     }
  }
 
  function handleSpecialTrigger(event) {
    const trigger = event.target.closest('[data-tab-trigger][data-tab-button]');
    if (!trigger) return false;
 
    event.preventDefault();
 
    const buttonId = trigger.getAttribute('data-tab-button');
    const tabId = trigger.getAttribute('data-tab-trigger');
 
    let tabButton = null;
    if (buttonId) tabButton = document.getElementById(buttonId);
    if (!tabButton && tabId) tabButton = document.querySelector('.nav-tab[data-tab="' + tabId + '"], .nested-tab[data-tab="' + tabId + '"]');
 
    if (!tabButton) return true;
 
    if (tabButton.classList.contains('nav-tab')) showNavTab(tabButton);
    else if (tabButton.classList.contains('nested-tab')) showNestedTab(tabButton);
 
    const href = trigger.getAttribute('href');
    if (href && href.charAt(0) === '#') {
      const anchor = document.querySelector(href);
       if (anchor) {
        setTimeout(() => anchor.scrollIntoView({ behavior: 'smooth', block: 'start' }), 100);
      }
    }
 
     return true;
  }
 
  function onDocumentClick(event) {
    if (handleSpecialTrigger(event)) return;


     handleNestedTabClick: function(event) {
     const navTab = event.target.closest('.nav-tab[data-tab]');
      const nestedTab = event.target.closest('.nested-tab[data-tab]');
    if (navTab) {
      if (!nestedTab) return;
       event.preventDefault();
       event.preventDefault();
       const targetId = nestedTab.getAttribute('data-tab');
       showNavTab(navTab);
       if (targetId) TabNavigation.showNestedTab(targetId, nestedTab);
       return;
     },
     }


     handleSpecialTabNavigation: function(event) {
     const nestedTab = event.target.closest('.nested-tab[data-tab]');
      const trigger = event.target.closest('[data-tab-trigger]');
    if (nestedTab) {
      if (!trigger) return;
       event.preventDefault();
       event.preventDefault();
       const tabData = trigger.getAttribute('data-tab-trigger');
       showNestedTab(nestedTab);
      const buttonId = trigger.getAttribute('data-tab-button');
    }
      const href = trigger.getAttribute('href');
  }
      const scrollToAttr = trigger.getAttribute('data-scroll-to');
      let scrollTargetSelector = null;
      if (scrollToAttr && scrollToAttr.trim()) scrollTargetSelector = '#' + scrollToAttr.replace(/^#/, '');
      else if (href && href.charAt(0) === '#') scrollTargetSelector = href;
      if (tabData && buttonId) {
        const btn = safeGetElementById(buttonId);
        if (btn) {
          TabNavigation.showTab(tabData, btn);
          if (scrollTargetSelector) {
            const target = safeQuerySelector(scrollTargetSelector);
            if (target) setTimeout(() => target.scrollIntoView({ behavior: 'smooth' }), CONFIG.DELAYS.SCROLL_DELAY);
          }
        }
      }
    },


    init: function() {
  function normalizeInitialState() {
      document.addEventListener('click', this.handleTabClick, false);
    const navTablists = Array.from(document.querySelectorAll('.nav-tabs'));
       document.addEventListener('click', this.handleNestedTabClick, false);
    for (const tablist of navTablists) {
      document.addEventListener('click', this.handleSpecialTabNavigation, false);
       const activeTab = tablist.querySelector('.nav-tab.active[data-tab]') || tablist.querySelector('.nav-tab[data-tab]');
       window.showTab = this.showTab;
       if (activeTab) showNavTab(activeTab);
     }
     }
   };
 
    const activeTabPanels = Array.from(document.querySelectorAll('.tab-content.active'));
    for (const panel of activeTabPanels) ensureNestedChain(panel);
 
    const activeNestedPanels = Array.from(document.querySelectorAll('.nested-content.active'));
    for (const panel of activeNestedPanels) ensureNestedChain(panel);
   }
 
  function init() {
    document.addEventListener('click', onDocumentClick, false);
    normalizeInitialState();
  }


   if (document.readyState === 'loading') {
   if (document.readyState === 'loading') {
     document.addEventListener('DOMContentLoaded', () => TabNavigation.init());
     document.addEventListener('DOMContentLoaded', init);
   } else {
   } else {
     TabNavigation.init();
     init();
   }
   }
})();
})();

Revision as of 05:18, 30 December 2025

(function () {
  'use strict';

  function getTabTargetId(tabElement) {
    if (!tabElement) return null;
    const dataTab = tabElement.getAttribute('data-tab');
    if (dataTab) return dataTab;
    const ariaControls = tabElement.getAttribute('aria-controls');
    if (ariaControls) return ariaControls;
    const href = tabElement.getAttribute('href');
    if (href && href.charAt(0) === '#') return href.slice(1);
    return null;
  }

  function escapeForAttributeSelector(value) {
    if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(value);
    return String(value).replace(/[\\\"\n\r\f]/g, '\\$&');
  }

  function findScopedElementById(scopeRoot, id) {
    if (!id) return null;
    const scope = scopeRoot && scopeRoot !== document ? scopeRoot : document;
    if (scope !== document) {
      const escaped = escapeForAttributeSelector(id);
      return scope.querySelector('[id="' + escaped + '"]');
    }
    return document.getElementById(id);
  }

  function setActiveState(element, isActive) {
    if (!element) return;
    element.classList.toggle('active', isActive);
    if (element.hasAttribute('aria-selected')) {
      element.setAttribute('aria-selected', isActive ? 'true' : 'false');
    }
    if (element.hasAttribute('aria-hidden')) {
      element.setAttribute('aria-hidden', isActive ? 'false' : 'true');
    }
    if (element.hasAttribute('tabindex')) {
      element.setAttribute('tabindex', isActive ? '0' : '-1');
    }
  }

  function getScopeForTablist(tablistElement) {
    if (!tablistElement) return document;
    if (tablistElement.classList.contains('nav-tabs')) {
      return tablistElement.closest('.tabs-container') || tablistElement.parentElement || document;
    }
    return tablistElement.closest('.nested-content, .tab-content') || document;
  }

  function showNavTab(tabElement) {
    const tablist = tabElement.closest('.nav-tabs');
    if (!tablist) return;

    const scope = getScopeForTablist(tablist);
    const tabs = Array.from(tablist.querySelectorAll('.nav-tab[data-tab]'));
    const tabMappings = tabs
      .map((tab) => ({ tab, id: getTabTargetId(tab) }))
      .filter((m) => !!m.id);

    const targetId = getTabTargetId(tabElement);
    if (!targetId) return;

    for (const mapping of tabMappings) {
      setActiveState(mapping.tab, mapping.tab === tabElement);
      const panel = findScopedElementById(scope, mapping.id);
      if (panel && panel.classList.contains('tab-content')) {
        setActiveState(panel, mapping.id === targetId);
      }
    }

    const targetPanel = findScopedElementById(scope, targetId);
    if (targetPanel && targetPanel.classList.contains('tab-content')) {
      setActiveState(targetPanel, true);
      ensureNestedChain(targetPanel);
    }
  }

  function showNestedTab(tabElement) {
    const tablist = tabElement.closest('.nested-tabs');
    if (!tablist) return;

    const scope = getScopeForTablist(tablist);
    const tabs = Array.from(tablist.querySelectorAll('.nested-tab[data-tab]'));
    const tabMappings = tabs
      .map((tab) => ({ tab, id: getTabTargetId(tab) }))
      .filter((m) => !!m.id);

    const targetId = getTabTargetId(tabElement);
    if (!targetId) return;

    for (const mapping of tabMappings) {
      setActiveState(mapping.tab, mapping.tab === tabElement);
      const panel = findScopedElementById(scope, mapping.id);
      if (panel && panel.classList.contains('nested-content')) {
        setActiveState(panel, mapping.id === targetId);
      }
    }

    const targetPanel = findScopedElementById(scope, targetId);
    if (targetPanel && targetPanel.classList.contains('nested-content')) {
      setActiveState(targetPanel, true);
      ensureNestedChain(targetPanel);
    }
  }

  function findDirectNestedTablist(containerElement) {
    if (!containerElement) return null;
    const tablists = Array.from(containerElement.querySelectorAll('.nested-tabs'));
    if (tablists.length === 0) return null;

    const containerIsNested = containerElement.classList.contains('nested-content');

    for (const tablist of tablists) {
      const closestNestedContent = tablist.closest('.nested-content');
      if (containerIsNested) {
        if (closestNestedContent === containerElement) return tablist;
      } else {
        if (!closestNestedContent) return tablist;
      }
    }

    return null;
  }

  function ensureNestedChain(activeContainer) {
    let currentContainer = activeContainer;

    while (currentContainer) {
      const nestedTablist = findDirectNestedTablist(currentContainer);
      if (!nestedTablist) return;

      const nestedTabs = Array.from(nestedTablist.querySelectorAll('.nested-tab[data-tab]'));
      if (nestedTabs.length === 0) return;

      let tabToActivate = nestedTabs.find((t) => t.classList.contains('active'));
      if (!tabToActivate) tabToActivate = nestedTabs[0];

      const targetId = getTabTargetId(tabToActivate);
      if (!targetId) return;

      showNestedTab(tabToActivate);

      const nextContainer = findScopedElementById(currentContainer, targetId);
      if (!nextContainer || !nextContainer.classList.contains('nested-content')) return;

      currentContainer = nextContainer;
    }
  }

  function handleSpecialTrigger(event) {
    const trigger = event.target.closest('[data-tab-trigger][data-tab-button]');
    if (!trigger) return false;

    event.preventDefault();

    const buttonId = trigger.getAttribute('data-tab-button');
    const tabId = trigger.getAttribute('data-tab-trigger');

    let tabButton = null;
    if (buttonId) tabButton = document.getElementById(buttonId);
    if (!tabButton && tabId) tabButton = document.querySelector('.nav-tab[data-tab="' + tabId + '"], .nested-tab[data-tab="' + tabId + '"]');

    if (!tabButton) return true;

    if (tabButton.classList.contains('nav-tab')) showNavTab(tabButton);
    else if (tabButton.classList.contains('nested-tab')) showNestedTab(tabButton);

    const href = trigger.getAttribute('href');
    if (href && href.charAt(0) === '#') {
      const anchor = document.querySelector(href);
      if (anchor) {
        setTimeout(() => anchor.scrollIntoView({ behavior: 'smooth', block: 'start' }), 100);
      }
    }

    return true;
  }

  function onDocumentClick(event) {
    if (handleSpecialTrigger(event)) return;

    const navTab = event.target.closest('.nav-tab[data-tab]');
    if (navTab) {
      event.preventDefault();
      showNavTab(navTab);
      return;
    }

    const nestedTab = event.target.closest('.nested-tab[data-tab]');
    if (nestedTab) {
      event.preventDefault();
      showNestedTab(nestedTab);
    }
  }

  function normalizeInitialState() {
    const navTablists = Array.from(document.querySelectorAll('.nav-tabs'));
    for (const tablist of navTablists) {
      const activeTab = tablist.querySelector('.nav-tab.active[data-tab]') || tablist.querySelector('.nav-tab[data-tab]');
      if (activeTab) showNavTab(activeTab);
    }

    const activeTabPanels = Array.from(document.querySelectorAll('.tab-content.active'));
    for (const panel of activeTabPanels) ensureNestedChain(panel);

    const activeNestedPanels = Array.from(document.querySelectorAll('.nested-content.active'));
    for (const panel of activeNestedPanels) ensureNestedChain(panel);
  }

  function init() {
    document.addEventListener('click', onDocumentClick, false);
    normalizeInitialState();
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
})();