MediaWiki:ShowFirstTabs.js

From CoraTO Wiki - Official Wiki
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
(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 findScopedElementById(scopeRoot, id) {
    if (!id) return null;
    const element = document.getElementById(id);
    if (!element) return null;
    if (scopeRoot && scopeRoot !== document && !scopeRoot.contains(element)) return null;
    return element;
  }

  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();
  }
})();