MediaWiki:Common.js: Difference between revisions

From UO Renaissance Wiki
Jump to navigation Jump to search
Add Patch Notes Archive submenu to Compendium flyout
Position scroll to top button next to main container and above navbar
 
(6 intermediate revisions by the same user not shown)
Line 107: Line 107:
       { label: 'Era 4: Veteran & Modern (2016-2020)', href: '/wiki/index.php/Patch_Notes_Archive#Era_4:_Veteran_Shard_.26_Modernization_.28Nov_2016_-_June_2020.29' }
       { label: 'Era 4: Veteran & Modern (2016-2020)', href: '/wiki/index.php/Patch_Notes_Archive#Era_4:_Veteran_Shard_.26_Modernization_.28Nov_2016_-_June_2020.29' }
     ]);
     ]);
    // 9. Unique Shard Systems Submenu
    var uniqueSystemsItems = [
      { label: "The Zookeeper's Quest", href: "/wiki/index.php/The_Zookeeper's_Quest" },
      { label: "Ancient MIB's", href: "/wiki/index.php/Ancient_MIB's" }
    ];
    function normalizeSystemUrl(url) {
      if (!url) return '';
      try {
        return decodeURIComponent(url).replace(/\/index\.php\//, '/index.php/').replace(/\/+$/, '').toLowerCase();
      } catch(e) {
        return url.toLowerCase();
      }
    }
    function isExcludedOrDuplicate(item, list) {
      if (!item || !item.href) return true;
      var nHref = normalizeSystemUrl(item.href);
      var nLbl = (item.label || '').trim().toLowerCase();
      // Exclude the parent page itself
      if (nHref.indexOf('unique_systems') !== -1 || nLbl === 'unique shard systems' || nLbl === 'unique systems') {
        return true;
      }
      return list.some(function(existing) {
        return normalizeSystemUrl(existing.href) === nHref ||
              (existing.label && existing.label.trim().toLowerCase() === nLbl);
      });
    }
    // Automatically incorporate any additional unique systems stored in localStorage
    try {
      var storedUnique = localStorage.getItem('uor_unique_shard_systems');
      if (storedUnique) {
        var parsedUnique = JSON.parse(storedUnique);
        if (Array.isArray(parsedUnique)) {
          var filteredStorage = [];
          parsedUnique.forEach(function(item) {
            if (!isExcludedOrDuplicate(item, uniqueSystemsItems)) {
              uniqueSystemsItems.push(item);
              filteredStorage.push(item);
            }
          });
          // Update localStorage to strip any invalid/duplicate entries
          localStorage.setItem('uor_unique_shard_systems', JSON.stringify(filteredStorage));
        }
      }
    } catch (e) {}
    // Check if the DOM already has sub-items (e.g. from MediaWiki:Sidebar)
    var uniqueLi = compendiumPortal.querySelector('li[id*="Unique"], li#n-Unique-Shard-Systems') ||
                  compendiumPortal.querySelector('a[href*="/wiki/index.php/Unique_Systems"]');
    if (uniqueLi) {
      if (uniqueLi.tagName.toLowerCase() === 'a') uniqueLi = uniqueLi.closest('li');
      if (uniqueLi) {
        var domSubLinks = uniqueLi.querySelectorAll('ul li a');
        domSubLinks.forEach(function(sl) {
          var lbl = sl.textContent.trim();
          var hrf = sl.getAttribute('href');
          var candidate = { label: lbl, href: hrf };
          if (!isExcludedOrDuplicate(candidate, uniqueSystemsItems)) {
            uniqueSystemsItems.push(candidate);
          }
        });
      }
    }
    attachSidebarSubmenu('li[id*="Unique"], li#n-Unique-Shard-Systems', '/wiki/index.php/Unique_Systems', uniqueSystemsItems);
    // Sync any custom unique system links dynamically from the Unique Systems hub page
    if (window.location.pathname.indexOf('/Unique_Systems') !== -1) {
      try {
        var pageLinks = document.querySelectorAll('.uor-unique-system-link, [data-shard-system]');
        if (pageLinks.length > 0) {
          var collected = [];
          pageLinks.forEach(function(pl) {
            var plLabel = pl.getAttribute('data-shard-system') || pl.textContent.trim();
            var plHref = pl.getAttribute('href');
            var candidate = { label: plLabel, href: plHref };
            if (!isExcludedOrDuplicate(candidate, collected) && !isExcludedOrDuplicate(candidate, uniqueSystemsItems)) {
              collected.push(candidate);
            }
          });
          if (collected.length > 0) {
            localStorage.setItem('uor_unique_shard_systems', JSON.stringify(collected));
          }
        }
      } catch (e) {}
    }


     document.addEventListener('click', function(e) {
     document.addEventListener('click', function(e) {
Line 116: Line 205:
       }
       }
     });
     });
  }
})();
/* Floating Scroll to Top Arrow Button */
(function() {
  function initScrollToTop() {
    var existing = document.getElementById('uor-scroll-top');
    if (existing) existing.remove();
    var btn = document.createElement('a');
    btn.id = 'uor-scroll-top';
    btn.className = 'uor-scroll-top-btn';
    btn.href = '#top';
    btn.title = 'Scroll to Top';
    btn.setAttribute('aria-label', 'Scroll to top of page');
    btn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M12 4l-8 8h5v8h6v-8h5z"/></svg>';
    btn.addEventListener('click', function(e) {
      e.preventDefault();
      window.scrollTo({
        top: 0,
        behavior: 'smooth'
      });
    });
    document.body.appendChild(btn);
    function repositionButton() {
      var mainBox = document.getElementById('content') || document.querySelector('.mw-body');
      if (mainBox) {
        var rect = mainBox.getBoundingClientRect();
        var spaceRight = window.innerWidth - rect.right;
        // If there's at least 54px room to the right of the container, stick right beside it
        if (spaceRight >= 54) {
          btn.style.left = Math.round(rect.right + 10) + 'px';
          btn.style.right = 'auto';
        } else {
          // Narrow viewport / mobile: dock to right screen edge
          btn.style.left = 'auto';
          btn.style.right = '16px';
        }
      } else {
        btn.style.left = 'auto';
        btn.style.right = '16px';
      }
    }
    var ticking = false;
    function updateState() {
      var scrollY = window.pageYOffset || document.documentElement.scrollTop || 0;
      if (scrollY > 220) {
        btn.classList.add('is-visible');
      } else {
        btn.classList.remove('is-visible');
      }
      repositionButton();
      ticking = false;
    }
    window.addEventListener('scroll', function() {
      if (!ticking) {
        window.requestAnimationFrame(updateState);
        ticking = true;
      }
    }, { passive: true });
    window.addEventListener('resize', function() {
      repositionButton();
    }, { passive: true });
    // Initial check
    updateState();
    setTimeout(repositionButton, 200);
    setTimeout(repositionButton, 1000);
  }
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initScrollToTop);
  } else {
    initScrollToTop();
   }
   }
})();
})();

Latest revision as of 18:28, 9 September 2026

/* UO Renaissance Wiki Common.js */
(function() {
  var compendiumPortal = document.getElementById('p-Compendium');
  if (compendiumPortal) {
    function attachSidebarSubmenu(liSelector, titleMatch, items) {
      var li = compendiumPortal.querySelector(liSelector) ||
               compendiumPortal.querySelector('a[href*="' + titleMatch + '"]');
      if (li && li.tagName.toLowerCase() === 'a') {
        li = li.closest('li');
      }
      if (!li || li.querySelector('.uor-sidebar-submenu')) return;

      li.classList.add('uor-sidebar-has-submenu');

      var link = li.querySelector(':scope > a');
      if (link && !link.querySelector('.uor-submenu-arrow')) {
        var arrow = document.createElement('span');
        arrow.className = 'uor-submenu-arrow';
        arrow.innerHTML = '&#9656;';
        arrow.title = 'View Sections';
        arrow.addEventListener('click', function(e) {
          e.preventDefault();
          e.stopPropagation();
          li.classList.toggle('is-open');
        });
        link.appendChild(arrow);
      }

      var submenu = document.createElement('ul');
      submenu.className = 'uor-sidebar-submenu';
      var html = '';
      for (var i = 0; i < items.length; i++) {
        html += '<li><a href="' + items[i].href + '">' + items[i].label + '</a></li>';
      }
      submenu.innerHTML = html;
      li.appendChild(submenu);
    }

    // 1. The World Submenu
    attachSidebarSubmenu('li#n-The-World', '/wiki/index.php/The_World', [
      { label: 'Towns', href: '/wiki/index.php/The_World#Towns' },
      { label: 'Dungeons', href: '/wiki/index.php/The_World#Dungeons' },
      { label: 'Points of Interest', href: '/wiki/index.php/The_World#Points_of_Interest' }
    ]);

    // 2. Skills Submenu
    attachSidebarSubmenu('li#n-Skills', '/wiki/index.php/Skills', [
      { label: 'Combat', href: '/wiki/index.php/Skills#Combat' },
      { label: 'Crafting', href: '/wiki/index.php/Skills#Crafting' },
      { label: 'Creatures', href: '/wiki/index.php/Skills#Creatures' },
      { label: 'Lore & Knowledge', href: '/wiki/index.php/Skills#Lore_&_Knowledge' },
      { label: 'Miscellaneous', href: '/wiki/index.php/Skills#Miscellaneous' },
      { label: 'Rogue', href: '/wiki/index.php/Skills#Rogue' },
      { label: 'Spells & Magic', href: '/wiki/index.php/Spells' }
    ]);

    // 3. Factions & Guilds Submenu
    attachSidebarSubmenu('li[id*="Factions"], li#n-Factions', '/wiki/index.php/Factions', [
      { label: 'Factions', href: '/wiki/index.php/Factions_%26_Guilds#Factions' },
      { label: 'Order & Chaos', href: '/wiki/index.php/Factions_%26_Guilds#Order_&_Chaos' },
      { label: 'Player run Guilds', href: '/wiki/index.php/Factions_%26_Guilds#Player_run_Guilds' }
    ]);

    // 4. Champion Spawns Submenu
    attachSidebarSubmenu('li#n-Champion-Spawns', '/wiki/index.php/Champion_Spawns', [
      { label: 'Overview & Altars', href: '/wiki/index.php/Champion_Spawns#Altars' },
      { label: 'The Six Champions', href: '/wiki/index.php/Champion_Spawns#Champions' },
      { label: 'The Harrower', href: '/wiki/index.php/Champion_Spawns#The_Harrower' },
      { label: 'Rewards & Titles', href: '/wiki/index.php/Champion_Spawns#Rewards' }
    ]);

    // 5. Bestiary Submenu
    attachSidebarSubmenu('li#n-Bestiary', '/wiki/index.php/Bestiary', [
      { label: 'Bosses & Champions', href: '/wiki/index.php/Bestiary#Bosses_and_Champions' },
      { label: 'Daemons & Elementals', href: '/wiki/index.php/Bestiary#Daemons_and_Elementals' },
      { label: 'Reptiles & Monstrosities', href: '/wiki/index.php/Bestiary#Reptiles_and_Monstrosities' },
      { label: 'Undead & Tomb Horrors', href: '/wiki/index.php/Bestiary#Undead_and_Horrors' },
      { label: 'Humanoids, Tribes & Beasts', href: '/wiki/index.php/Bestiary#Humanoids_and_Beasts' },
      { label: 'Holiday & Event Specials', href: '/wiki/index.php/Bestiary#Holiday_and_Events' }
    ]);

    // 6. Housing & Vendors Submenu
    attachSidebarSubmenu('li[id*="Housing"], li#n-Housing-Vendors', 'Housing', [
      { label: 'Ownership Rules', href: '/wiki/index.php/Housing_System#Home_Ownership_Fundamentals' },
      { label: 'House Catalog', href: '/wiki/index.php/Housing_System#Complete_House_Catalog' },
      { label: 'Security & Tactics', href: '/wiki/index.php/Housing_System#House_Security_&_Fortification' },
      { label: 'Classic Vendors', href: '/wiki/index.php/Housing_System#Classic_Player_Vendors' },
      { label: 'Enhanced Vendors', href: '/wiki/index.php/Housing_System#Enhanced_Vendor_System' },
      { label: 'Quick Reference', href: '/wiki/index.php/Housing_System#Housing_&_Vendor_Quick_Reference' }
    ]);

    // 7. Murder & Bounties Submenu
    attachSidebarSubmenu('li[id*="Murder"], li#n-Murder-Bounties', 'Murder', [
      { label: 'Murder Counts', href: '/wiki/index.php/The_Murder_System#Understanding_Murder_Counts' },
      { label: 'Stat Loss Formula', href: '/wiki/index.php/The_Murder_System#Stat_Loss_Formula_&_Penalties' },
      { label: 'Reporting & Bounties', href: '/wiki/index.php/The_Murder_System#Reporting_a_Murder_&_Placing_Bounties' },
      { label: 'The Bounty Board', href: '/wiki/index.php/The_Murder_System#The_Bounty_Board_System' },
      { label: 'Claiming Heads', href: '/wiki/index.php/The_Murder_System#Claiming_Bounties_&_Taking_Heads' },
      { label: 'Quick Reference', href: '/wiki/index.php/The_Murder_System#Murder_&_Bounty_Quick_Reference' }
    ]);

    // 8. Patch Notes Archive Submenu
    attachSidebarSubmenu('li#n-Patch-Notes-Archive', '/wiki/index.php/Patch_Notes_Archive', [
      { label: 'Era 1: Alpha & Beta (2012)', href: '/wiki/index.php/Patch_Notes_Archive#Era_1:_Alpha_.26_Beta_Foundations_.28May_2012_-_Sept_2012.29' },
      { label: 'Era 2: Launch & Maturation (2012-2014)', href: '/wiki/index.php/Patch_Notes_Archive#Era_2:_Shard_Launch_.26_Server_Maturation_.28Sept_2012_-_Sept_2014.29' },
      { label: 'Era 3: Golden Age (2014-2016)', href: '/wiki/index.php/Patch_Notes_Archive#Era_3:_The_Golden_Age_.26_Expansions_.28Oct_2014_-_Oct_2016.29' },
      { label: 'Era 4: Veteran & Modern (2016-2020)', href: '/wiki/index.php/Patch_Notes_Archive#Era_4:_Veteran_Shard_.26_Modernization_.28Nov_2016_-_June_2020.29' }
    ]);

    // 9. Unique Shard Systems Submenu
    var uniqueSystemsItems = [
      { label: "The Zookeeper's Quest", href: "/wiki/index.php/The_Zookeeper's_Quest" },
      { label: "Ancient MIB's", href: "/wiki/index.php/Ancient_MIB's" }
    ];

    function normalizeSystemUrl(url) {
      if (!url) return '';
      try {
        return decodeURIComponent(url).replace(/\/index\.php\//, '/index.php/').replace(/\/+$/, '').toLowerCase();
      } catch(e) {
        return url.toLowerCase();
      }
    }

    function isExcludedOrDuplicate(item, list) {
      if (!item || !item.href) return true;
      var nHref = normalizeSystemUrl(item.href);
      var nLbl = (item.label || '').trim().toLowerCase();
      // Exclude the parent page itself
      if (nHref.indexOf('unique_systems') !== -1 || nLbl === 'unique shard systems' || nLbl === 'unique systems') {
        return true;
      }
      return list.some(function(existing) {
        return normalizeSystemUrl(existing.href) === nHref ||
               (existing.label && existing.label.trim().toLowerCase() === nLbl);
      });
    }

    // Automatically incorporate any additional unique systems stored in localStorage
    try {
      var storedUnique = localStorage.getItem('uor_unique_shard_systems');
      if (storedUnique) {
        var parsedUnique = JSON.parse(storedUnique);
        if (Array.isArray(parsedUnique)) {
          var filteredStorage = [];
          parsedUnique.forEach(function(item) {
            if (!isExcludedOrDuplicate(item, uniqueSystemsItems)) {
              uniqueSystemsItems.push(item);
              filteredStorage.push(item);
            }
          });
          // Update localStorage to strip any invalid/duplicate entries
          localStorage.setItem('uor_unique_shard_systems', JSON.stringify(filteredStorage));
        }
      }
    } catch (e) {}

    // Check if the DOM already has sub-items (e.g. from MediaWiki:Sidebar)
    var uniqueLi = compendiumPortal.querySelector('li[id*="Unique"], li#n-Unique-Shard-Systems') ||
                   compendiumPortal.querySelector('a[href*="/wiki/index.php/Unique_Systems"]');
    if (uniqueLi) {
      if (uniqueLi.tagName.toLowerCase() === 'a') uniqueLi = uniqueLi.closest('li');
      if (uniqueLi) {
        var domSubLinks = uniqueLi.querySelectorAll('ul li a');
        domSubLinks.forEach(function(sl) {
          var lbl = sl.textContent.trim();
          var hrf = sl.getAttribute('href');
          var candidate = { label: lbl, href: hrf };
          if (!isExcludedOrDuplicate(candidate, uniqueSystemsItems)) {
            uniqueSystemsItems.push(candidate);
          }
        });
      }
    }

    attachSidebarSubmenu('li[id*="Unique"], li#n-Unique-Shard-Systems', '/wiki/index.php/Unique_Systems', uniqueSystemsItems);

    // Sync any custom unique system links dynamically from the Unique Systems hub page
    if (window.location.pathname.indexOf('/Unique_Systems') !== -1) {
      try {
        var pageLinks = document.querySelectorAll('.uor-unique-system-link, [data-shard-system]');
        if (pageLinks.length > 0) {
          var collected = [];
          pageLinks.forEach(function(pl) {
            var plLabel = pl.getAttribute('data-shard-system') || pl.textContent.trim();
            var plHref = pl.getAttribute('href');
            var candidate = { label: plLabel, href: plHref };
            if (!isExcludedOrDuplicate(candidate, collected) && !isExcludedOrDuplicate(candidate, uniqueSystemsItems)) {
              collected.push(candidate);
            }
          });
          if (collected.length > 0) {
            localStorage.setItem('uor_unique_shard_systems', JSON.stringify(collected));
          }
        }
      } catch (e) {}
    }

    document.addEventListener('click', function(e) {
      if (!e.target.closest('.uor-sidebar-has-submenu')) {
        var openMenus = document.querySelectorAll('.uor-sidebar-has-submenu.is-open');
        for (var i = 0; i < openMenus.length; i++) {
          openMenus[i].classList.remove('is-open');
        }
      }
    });
  }
})();

/* Floating Scroll to Top Arrow Button */
(function() {
  function initScrollToTop() {
    var existing = document.getElementById('uor-scroll-top');
    if (existing) existing.remove();

    var btn = document.createElement('a');
    btn.id = 'uor-scroll-top';
    btn.className = 'uor-scroll-top-btn';
    btn.href = '#top';
    btn.title = 'Scroll to Top';
    btn.setAttribute('aria-label', 'Scroll to top of page');
    btn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M12 4l-8 8h5v8h6v-8h5z"/></svg>';

    btn.addEventListener('click', function(e) {
      e.preventDefault();
      window.scrollTo({
        top: 0,
        behavior: 'smooth'
      });
    });

    document.body.appendChild(btn);

    function repositionButton() {
      var mainBox = document.getElementById('content') || document.querySelector('.mw-body');
      if (mainBox) {
        var rect = mainBox.getBoundingClientRect();
        var spaceRight = window.innerWidth - rect.right;
        // If there's at least 54px room to the right of the container, stick right beside it
        if (spaceRight >= 54) {
          btn.style.left = Math.round(rect.right + 10) + 'px';
          btn.style.right = 'auto';
        } else {
          // Narrow viewport / mobile: dock to right screen edge
          btn.style.left = 'auto';
          btn.style.right = '16px';
        }
      } else {
        btn.style.left = 'auto';
        btn.style.right = '16px';
      }
    }

    var ticking = false;
    function updateState() {
      var scrollY = window.pageYOffset || document.documentElement.scrollTop || 0;
      if (scrollY > 220) {
        btn.classList.add('is-visible');
      } else {
        btn.classList.remove('is-visible');
      }
      repositionButton();
      ticking = false;
    }

    window.addEventListener('scroll', function() {
      if (!ticking) {
        window.requestAnimationFrame(updateState);
        ticking = true;
      }
    }, { passive: true });

    window.addEventListener('resize', function() {
      repositionButton();
    }, { passive: true });

    // Initial check
    updateState();
    setTimeout(repositionButton, 200);
    setTimeout(repositionButton, 1000);
  }

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