Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

MediaWiki:Common.js: Difference between revisions

MediaWiki interface page
No edit summary
No edit summary
 
(14 intermediate revisions by the same user not shown)
Line 1: Line 1:
/* Any JavaScript here will be loaded for all users on every page load. */// Load DataTables library
/* Any JavaScript here will be loaded for all users on every page load. */


mw.hook('wikipage.content').add(function ($content) {
mw.hook('wikipage.content').add(function ($content) {
Line 7: Line 7:
});
});


// Auto-embed X/Twitter status links into live tweets (no extension required)
(function () {
  'use strict';


   // Options you can tweak
mw.hook('wikipage.content').add(function () {
   var TW_OPTIONS = {
 
     // theme: 'dark',            // uncomment for dark
   /* ===========================
     // conversation: 'none',     // hide reply thread
    WAIT FOR TABLE (IMPORTANT)
    // dnt: true,               // "Do Not Track"
    =========================== */
     // align: 'center'          // center the embed
 
   };
   function waitForTable(callback) {
     const table = document.getElementById("leaderboardTable");
     if (table) {
      callback(table);
     } else {
      setTimeout(() => waitForTable(callback), 50);
     }
   }
 


   function loadXWidgets (cb) {
   waitForTable(function(table) {
    // If the widget API is ready, go
    if (window.twttr && twttr.widgets) { cb(); return; }


     // Already loading?
     /* ===========================
    if (document.querySelector('script[src*="platform.twitter.com/widgets.js"]')) {
      HELPERS
      // Poll until ready
      =========================== */
      var tries = 0, t = setInterval(function () {
 
        if (window.twttr && twttr.widgets) { clearInterval(t); cb(); }
    function parseTime(t) {
        else if (++tries > 40) { clearInterval(t); }
      if (!t || !t.includes(":")) return 999999;
      }, 250);
      const [m, s] = t.trim().split(":").map(Number);
       return;
       return m * 60 + s;
     }
     }


     // Inject the widgets.js script
    function getPoints(rank) {
     var s = document.createElement('script');
      const scoring = {
     s.async = true;
        1:25,2:20,3:17,4:15,5:13,
     s.src = 'https://platform.twitter.com/widgets.js';
        6:11,7:10,8:9,9:8,10:7,
     s.onload = function () { cb(); };
        11:6,12:5,13:4,14:3,15:2
     document.head.appendChild(s);
      };
  }
      return scoring[rank] || 1;
    }
 
 
     /* ===========================
      FILTER UI
      =========================== */
 
    const filterWrapper = document.createElement("div");
     filterWrapper.style.marginBottom = "20px";
 
    const filterTitle = document.createElement("h3");
     filterTitle.innerText = "🔍 Filter Runs";
     filterWrapper.appendChild(filterTitle);
 
    const dungeonSelect = document.createElement("select");
    dungeonSelect.style.marginRight = "10px";
 
     [
      "All",
      "Wolf's Den","Wolf's Den II","Wolf's Den III",
      "The Red Nest","For The Eggs",
      "Scarlet Revenge","Scarlet Revenge II",
      "Promised One","Promised One II","Promised One III",
      "Lost and Found","The Hostage","Cactus On Fire",
      "Deep Echoes","Deep Echoes II","Deep Echoes III",
      "King's Hoard","King's Hoard II",
      "Hall of Daggers"
    ].forEach(name => {
      const opt = document.createElement("option");
      opt.value = name;
      opt.textContent = name;
      dungeonSelect.appendChild(opt);
    });
 
     filterWrapper.appendChild(document.createTextNode("Dungeon: "));
    filterWrapper.appendChild(dungeonSelect);
 
    const playerInput = document.createElement("input");
    playerInput.placeholder = "Search player...";
    playerInput.style.marginLeft = "10px";
    filterWrapper.appendChild(playerInput);
 
 
    /* ===========================
      SORT + RANK
      =========================== */
 
    function sortAndRank() {
 
      const rows = Array.from(table.querySelectorAll("tr")).slice(1);
 
      rows.sort((a,b) => {
 
        if (a.cells.length < 5 || b.cells.length < 5) return 0;
 
        const dA = a.cells[1].innerText.trim();
        const dB = b.cells[1].innerText.trim();
 
        if (dA !== dB) return dA.localeCompare(dB);
 
        return parseTime(a.cells[4].innerText) - parseTime(b.cells[4].innerText);
 
      });


  // Find eligible links and embed them
       rows.forEach(r => table.appendChild(r));
  function embedLinks(root) {
    var scope = root || document;
    var selector = [
       'a[href^="https://x.com/"][href*="/status/"]',
      'a[href^="http://x.com/"][href*="/status/"]',
      'a[href^="https://twitter.com/"][href*="/status/"]',
      'a[href^="http://twitter.com/"][href*="/status/"]'
    ].join(',');


    var anchors = scope.querySelectorAll(selector);
      let currentDungeon = "";
    if (!anchors.length) return;
      let position = 0;
      let rank = 0;
      let prevTime = null;


    loadXWidgets(function () {
       rows.forEach(row => {
       anchors.forEach(function (a) {
        if (a.dataset.tweetEmbedded === '1') return; // avoid duplicates
        a.dataset.tweetEmbedded = '1';


         // Extract the numeric tweet ID
         if (row.cells.length < 5) return;
        var match = a.href.match(/\/status\/(\d+)/);
        if (!match) return;
        var id = match[1];


         // Create a container after the link
         const dungeon = row.cells[1].innerText.trim();
        var container = document.createElement('div');
         const time = parseTime(row.cells[4].innerText);
         container.className = 'tweet-embed';
        a.insertAdjacentElement('afterend', container);


        // Ask widgets API to render
         if (dungeon !== currentDungeon) {
         if (window.twttr && twttr.widgets && twttr.widgets.createTweet) {
           currentDungeon = dungeon;
           twttr.widgets.createTweet(id, container, TW_OPTIONS).catch(function(){});
          position = 1;
          rank = 1;
          prevTime = time;
        } else {
          position++;
          if (time !== prevTime) rank = position;
          prevTime = time;
         }
         }
        row.cells[0].innerText = rank;
       });
       });
    }


      // Ensure any dynamically added blockquotes also render
      if (window.twttr && twttr.widgets && twttr.widgets.load) {
        twttr.widgets.load(scope);
      }
    });
  }


  // Run on page load and whenever VE/LQT/etc. inject content
    /* ===========================
  if (window.mw && mw.hook) {
      BUILD TOP SECTION
    mw.hook('wikipage.content').add(function ($content) {
      =========================== */
       embedLinks($content && $content[0]);
 
    });
    function buildTopSection() {
  } else {
 
    document.addEventListener('DOMContentLoaded', function () { embedLinks(document); });
      const rows = Array.from(table.querySelectorAll("tr")).slice(1);
  }
 
})();
       const playerPoints = {};
      const records = {};
 
      rows.forEach(row => {


        if (row.cells.length < 5) return;


/* ===========================
        const dungeon = row.cells[1].innerText.trim();
  Dungeon Speedrun Leaderboard
        const player = row.cells[2].innerText.trim();
  Per-Dungeon Ranking + Filters
        const time = row.cells[4].innerText.trim();
  =========================== */
        const rank = parseInt(row.cells[0].innerText);


mw.hook('wikipage.content').add(function () {
        // Only require valid time (CRITICAL FIX)
        if (!time || !time.includes(":")) return;


  // ===== SOLO TABLE =====
        /* ===========================
  const table = document.getElementById("leaderboardTable");
          POINTS
  if (table) {
          =========================== */
    const container = document.getElementById("filterContainer");
    if (container) {


      const wrapper = document.createElement("div");
        if (!isNaN(rank)) {
      wrapper.style.marginBottom = "15px";
          playerPoints[player] = (playerPoints[player] || 0) + getPoints(rank);
        }


      const dungeonLabel = document.createElement("strong");
        /* ===========================
      dungeonLabel.innerText = "Dungeon: ";
          WORLD RECORDS (WORKING)
      wrapper.appendChild(dungeonLabel);
          =========================== */


      const dungeonSelect = document.createElement("select");
        const seconds = parseTime(time);
      dungeonSelect.style.marginRight = "20px";


      const dungeons = [
        if (!records[dungeon]) {
        "All",
          records[dungeon] = {
        "Wolf's Den", "Wolf's Den II", "Wolf's Den III",
            time: time,
         "The Red Nest", "For The Eggs",
            seconds: seconds,
        "Scarlet Revenge", "Scarlet Revenge II",
            players: [player]
        "Promised One", "Promised One II", "Promised One III",
          };
        "Lost and Found", "The Hostage", "Cactus On Fire",
         } else {
        "Deep Echoes", "Deep Echoes II", "Deep Echoes III", "King's Hoard" , "King's Hoard II",
          if (seconds < records[dungeon].seconds) {
        "Hall of Daggers",
            records[dungeon] = {
      ];
              time: time,
              seconds: seconds,
              players: [player]
            };
          } else if (seconds === records[dungeon].seconds) {
            records[dungeon].players.push(player);
          }
        }


      dungeons.forEach(name => {
        const option = document.createElement("option");
        option.value = name;
        option.textContent = name;
        dungeonSelect.appendChild(option);
       });
       });


      wrapper.appendChild(dungeonSelect);


       const playerLabel = document.createElement("strong");
      /* REMOVE OLD */
       playerLabel.innerText = "Player: ";
       const existing = document.getElementById("topSection");
      wrapper.appendChild(playerLabel);
       if (existing) existing.remove();


      const playerInput = document.createElement("input");
      playerInput.type = "text";
      playerInput.placeholder = "Search player name...";
      playerInput.style.padding = "4px";
      wrapper.appendChild(playerInput);


       container.appendChild(wrapper);
       /* FLEX CONTAINER */
      const top = document.createElement("div");
      top.id = "topSection";
      top.style.display = "flex";
      top.style.gap = "30px";
      top.style.marginBottom = "25px";


      function parseTime(t) {
        if (!t || !t.includes(":")) return 999999;
        const parts = t.trim().split(":");
        return parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10);
      }


       function sortAndRank() {
       /* ===========================
        const rows = Array.from(table.querySelectorAll("tr")).slice(1);
        COMPETITION TABLE
        rows.sort((a, b) => {
        =========================== */
          const dungeonA = a.cells[1].innerText.trim();
          const dungeonB = b.cells[1].innerText.trim();
          if (dungeonA < dungeonB) return -1;
          if (dungeonA > dungeonB) return 1;
          return parseTime(a.cells[4].innerText) - parseTime(b.cells[4].innerText);
        });
        rows.forEach(row => table.appendChild(row));


        let currentDungeon = "";
      const compBox = document.createElement("div");
        let dungeonRank = 0;
      compBox.style.flex = "1";
        rows.forEach(row => {
          if (row.style.display === "none") return;
          const dungeon = row.cells[1].innerText.trim();
          const rankCell = row.cells[0];
          if (dungeon !== currentDungeon) { currentDungeon = dungeon; dungeonRank = 1; }
          else { dungeonRank++; }
          if (dungeonRank === 1) rankCell.innerHTML = "🥇 1";
          else if (dungeonRank === 2) rankCell.innerHTML = "🥈 2";
          else if (dungeonRank === 3) rankCell.innerHTML = "🥉 3";
          else rankCell.innerText = dungeonRank;
        });
      }


       function filterTable() {
       const compTitle = document.createElement("h2");
        const selectedDungeon = dungeonSelect.value;
      compTitle.innerText = "🏆 Competition Leaderboard";
        const playerSearch = playerInput.value.toLowerCase();
      compBox.appendChild(compTitle);
        const rows = Array.from(table.querySelectorAll("tr")).slice(1);
        rows.forEach(row => {
          const dungeon = row.cells[1].innerText.trim();
          const player = row.cells[2].innerText.toLowerCase();
          row.style.display =
            (selectedDungeon === "All" || dungeon === selectedDungeon) &&
            player.includes(playerSearch) ? "" : "none";
        });
        sortAndRank();
      }


       dungeonSelect.addEventListener("change", filterTable);
       const compTable = document.createElement("table");
       playerInput.addEventListener("input", filterTable);
       compTable.className = "wikitable";
       sortAndRank();
      compTable.style.width = "100%";
    }
 
  }
      compTable.innerHTML = `
        <tr>
          <th>Rank</th>
          <th>Player</th>
          <th>Points</th>
        </tr>
      `;
 
      const leaderboard = Object.entries(playerPoints)
        .map(([player, points]) => ({player, points}))
        .sort((a,b)=>b.points-a.points);
 
      let prevPoints = null;
      let rank = 0;
      let position = 0;
 
       leaderboard.forEach(entry => {
 
        position++;
        if (entry.points !== prevPoints) rank = position;
        prevPoints = entry.points;
 
        const medal =
          rank===1?"🥇 1":
          rank===2?"🥈 2":
          rank===3?"🥉 3":
          rank;
 
        const row = document.createElement("tr");
        row.innerHTML = `
          <td>${medal}</td>
          <td>${entry.player}</td>
          <td>${entry.points}</td>
        `;
        compTable.appendChild(row);
 
      });
 
      compBox.appendChild(compTable);
 
 
      /* ===========================
        WORLD RECORD TABLE
        =========================== */
 
      const wrBox = document.createElement("div");
      wrBox.style.flex = "1";
 
      const wrTitle = document.createElement("h2");
      wrTitle.innerText = "🏁 Competition Dungeon Records";
      wrBox.appendChild(wrTitle);
 
      const wrTable = document.createElement("table");
      wrTable.className = "wikitable";
      wrTable.style.width = "100%";


  // ===== PARTY TABLE =====
      wrTable.innerHTML = `
  const partyTable = document.getElementById("partyLeaderboardTable");
        <tr>
  if (partyTable) {
          <th>Dungeon</th>
    const partyContainer = document.getElementById("partyFilterContainer");
          <th>Time</th>
    if (partyContainer) {
          <th>Player(s)</th>
        </tr>
      `;


       const wrapper = document.createElement("div");
       Object.entries(records).forEach(([d, data]) => {
      wrapper.style.marginBottom = "15px";


      const dungeonLabel = document.createElement("strong");
        const row = document.createElement("tr");
      dungeonLabel.innerText = "Dungeon: ";
      wrapper.appendChild(dungeonLabel);


      const dungeonSelect = document.createElement("select");
        row.innerHTML = `
      dungeonSelect.style.marginRight = "20px";
          <td>${d}</td>
          <td>${data.time}</td>
          <td>${data.players.join(", ")}</td>
        `;


      const dungeons = [
         wrTable.appendChild(row);
         "All",
        "Wolf's Den", "Wolf's Den II", "Wolf's Den III",
        "The Red Nest", "For The Eggs",
        "Scarlet Revenge", "Scarlet Revenge II",
        "Promised One", "Promised One II", "Promised One III",
        "Lost and Found", "The Hostage", "Cactus On Fire",
        "Deep Echoes", "Deep Echoes II", "Deep Echoes III", "King's Hoard", "King's Hoard II",
        "Hall of Daggers",
      ];


      dungeons.forEach(name => {
        const option = document.createElement("option");
        option.value = name;
        option.textContent = name;
        dungeonSelect.appendChild(option);
       });
       });


       wrapper.appendChild(dungeonSelect);
       wrBox.appendChild(wrTable);
 
 
      /* COMBINE */
      top.appendChild(compBox);
      top.appendChild(wrBox);
 
      table.parentNode.insertBefore(top, table);
 
      /* FILTER BELOW */
      table.parentNode.insertBefore(filterWrapper, table);
    }
 


      const playerLabel = document.createElement("strong");
    /* ===========================
      playerLabel.innerText = "Players: ";
      FILTER
      wrapper.appendChild(playerLabel);
      =========================== */


      const playerInput = document.createElement("input");
    function filterTable() {
      playerInput.type = "text";
      playerInput.placeholder = "Search player name...";
      playerInput.style.padding = "4px";
      wrapper.appendChild(playerInput);


       partyContainer.appendChild(wrapper);
       const dungeon = dungeonSelect.value;
      const player = playerInput.value.toLowerCase();


       function parseTime(t) {
       const rows = Array.from(table.querySelectorAll("tr")).slice(1);
        if (!t || !t.includes(":")) return 999999;
        const parts = t.trim().split(":");
        return parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10);
      }


       function sortAndRankParty() {
       rows.forEach(row => {
        const rows = Array.from(partyTable.querySelectorAll("tr")).slice(1);
        rows.sort((a, b) => {
          const dungeonA = a.cells[1].innerText.trim();
          const dungeonB = b.cells[1].innerText.trim();
          if (dungeonA < dungeonB) return -1;
          if (dungeonA > dungeonB) return 1;
          // Time is now column 5 (0-indexed) because of the extra Party Size column
          return parseTime(a.cells[5].innerText) - parseTime(b.cells[5].innerText);
        });
        rows.forEach(row => partyTable.appendChild(row));


         let currentDungeon = "";
         if (row.cells.length < 5) return;
        let dungeonRank = 0;
        rows.forEach(row => {
          if (row.style.display === "none") return;
          const dungeon = row.cells[1].innerText.trim();
          const rankCell = row.cells[0];
          if (dungeon !== currentDungeon) { currentDungeon = dungeon; dungeonRank = 1; }
          else { dungeonRank++; }
          if (dungeonRank === 1) rankCell.innerHTML = "🥇 1";
          else if (dungeonRank === 2) rankCell.innerHTML = "🥈 2";
          else if (dungeonRank === 3) rankCell.innerHTML = "🥉 3";
          else rankCell.innerText = dungeonRank;
        });
      }


      function filterPartyTable() {
         const d = row.cells[1].innerText.trim();
        const selectedDungeon = dungeonSelect.value;
        const p = row.cells[2].innerText.toLowerCase();
         const playerSearch = playerInput.value.toLowerCase();
        const rows = Array.from(partyTable.querySelectorAll("tr")).slice(1);
        rows.forEach(row => {
          const dungeon = row.cells[1].innerText.trim();
          // Players column is now index 3
          const player = row.cells[3].innerText.toLowerCase();
          row.style.display =
            (selectedDungeon === "All" || dungeon === selectedDungeon) &&
            player.includes(playerSearch) ? "" : "none";
        });
        sortAndRankParty();
      }


      dungeonSelect.addEventListener("change", filterPartyTable);
        row.style.display =
      playerInput.addEventListener("input", filterPartyTable);
          (dungeon === "All" || d === dungeon) &&
       sortAndRankParty();
          p.includes(player)
            ? ""
            : "none";
       });
     }
     }
  }


});
    dungeonSelect.addEventListener("change", filterTable);
    playerInput.addEventListener("input", filterTable);
 
 
    /* ===========================
      INIT
      =========================== */
 
    sortAndRank();
    buildTopSection();


/* Open YouTube links in new tab */
mw.hook('wikipage.content').add(function ($content) {
  $content.find('a[href*="youtube.com"], a[href*="youtu.be"]').each(function () {
    this.setAttribute("target", "_blank");
    this.setAttribute("rel", "noopener noreferrer");
   });
   });
});
});