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
 
(13 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. */
/* Any JavaScript here will be loaded for all users on every page load. */


Line 9: Line 8:




/* ===========================
mw.hook('wikipage.content').add(function () {
  Dungeon Speedrun Leaderboard
  =========================== */


mw.hook('wikipage.content').add(function () {
  /* ===========================
    WAIT FOR TABLE (IMPORTANT)
    =========================== */


   function parseTime(t) {
   function waitForTable(callback) {
     if (!t || !t.includes(":")) return 999999;
     const table = document.getElementById("leaderboardTable");
     const parts = t.trim().split(":");
     if (table) {
     return parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10);
      callback(table);
     } else {
      setTimeout(() => waitForTable(callback), 50);
    }
   }
   }




   /* ===========================
   waitForTable(function(table) {
    SOLO TABLE
 
    =========================== */
    /* ===========================
      HELPERS
      =========================== */
 
    function parseTime(t) {
      if (!t || !t.includes(":")) return 999999;
      const [m, s] = t.trim().split(":").map(Number);
      return m * 60 + s;
    }
 
    function getPoints(rank) {
      const scoring = {
        1:25,2:20,3:17,4:15,5:13,
        6:11,7:10,8:9,9:8,10:7,
        11:6,12:5,13:4,14:3,15:2
      };
      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 table = document.getElementById("leaderboardTable");
    const playerInput = document.createElement("input");
    playerInput.placeholder = "Search player...";
    playerInput.style.marginLeft = "10px";
    filterWrapper.appendChild(playerInput);


  if (table) {


     const container = document.getElementById("filterContainer");
     /* ===========================
      SORT + RANK
      =========================== */


     if (container) {
     function sortAndRank() {


       const wrapper = document.createElement("div");
       const rows = Array.from(table.querySelectorAll("tr")).slice(1);
      wrapper.style.marginBottom = "15px";


       const dungeonLabel = document.createElement("strong");
       rows.sort((a,b) => {
      dungeonLabel.innerText = "Dungeon: ";
      wrapper.appendChild(dungeonLabel);


      const dungeonSelect = document.createElement("select");
        if (a.cells.length < 5 || b.cells.length < 5) return 0;
      dungeonSelect.style.marginRight = "20px";


      const dungeons = [
        const dA = a.cells[1].innerText.trim();
         "All",
         const dB = b.cells[1].innerText.trim();
        "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 => {
        if (dA !== dB) return dA.localeCompare(dB);
        const option = document.createElement("option");
        option.value = name;
        option.textContent = name;
        dungeonSelect.appendChild(option);
      });


      wrapper.appendChild(dungeonSelect);
        return parseTime(a.cells[4].innerText) - parseTime(b.cells[4].innerText);


       const playerLabel = document.createElement("strong");
       });
      playerLabel.innerText = "Player: ";
      wrapper.appendChild(playerLabel);


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


       container.appendChild(wrapper);
       let currentDungeon = "";
      let position = 0;
      let rank = 0;
      let prevTime = null;


      rows.forEach(row => {


      function sortAndRank() {
        if (row.cells.length < 5) return;


         const rows = Array.from(table.querySelectorAll("tr")).slice(1);
         const dungeon = row.cells[1].innerText.trim();
        const time = parseTime(row.cells[4].innerText);


         rows.sort((a,b) => {
         if (dungeon !== currentDungeon) {
          currentDungeon = dungeon;
          position = 1;
          rank = 1;
          prevTime = time;
        } else {
          position++;
          if (time !== prevTime) rank = position;
          prevTime = time;
        }


          const dungeonA = a.cells[1].innerText.trim();
        row.cells[0].innerText = rank;
          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);


        });
    /* ===========================
      BUILD TOP SECTION
      =========================== */


        rows.forEach(row => table.appendChild(row));
    function buildTopSection() {


        let currentDungeon = "";
      const rows = Array.from(table.querySelectorAll("tr")).slice(1);
        let dungeonRank = 0;


        rows.forEach(row => {
      const playerPoints = {};
      const records = {};


          const dungeon = row.cells[1].innerText.trim();
      rows.forEach(row => {
          const rankCell = row.cells[0];


          if (dungeon !== currentDungeon) {
        if (row.cells.length < 5) return;
            currentDungeon = dungeon;
            dungeonRank = 1;
          } else {
            dungeonRank++;
          }


          if (dungeonRank === 1) rankCell.innerHTML = "🥇 1";
        const dungeon = row.cells[1].innerText.trim();
          else if (dungeonRank === 2) rankCell.innerHTML = "🥈 2";
        const player = row.cells[2].innerText.trim();
          else if (dungeonRank === 3) rankCell.innerHTML = "🥉 3";
        const time = row.cells[4].innerText.trim();
          else rankCell.innerText = dungeonRank;
        const rank = parseInt(row.cells[0].innerText);


         });
         // Only require valid time (CRITICAL FIX)
        if (!time || !time.includes(":")) return;


      }
        /* ===========================
          POINTS
          =========================== */


        if (!isNaN(rank)) {
          playerPoints[player] = (playerPoints[player] || 0) + getPoints(rank);
        }


      function filterTable() {
        /* ===========================
          WORLD RECORDS (WORKING)
          =========================== */


         const selectedDungeon = dungeonSelect.value;
         const seconds = parseTime(time);
        const playerSearch = playerInput.value.toLowerCase();


         const rows = Array.from(table.querySelectorAll("tr")).slice(1);
         if (!records[dungeon]) {
          records[dungeon] = {
            time: time,
            seconds: seconds,
            players: [player]
          };
        } else {
          if (seconds < records[dungeon].seconds) {
            records[dungeon] = {
              time: time,
              seconds: seconds,
              players: [player]
            };
          } else if (seconds === records[dungeon].seconds) {
            records[dungeon].players.push(player);
          }
        }


        rows.forEach(row => {
      });


          const dungeon = row.cells[1].innerText.trim();
          const player = row.cells[2].innerText.toLowerCase();


          row.style.display =
      /* REMOVE OLD */
            (selectedDungeon === "All" || dungeon === selectedDungeon) &&
      const existing = document.getElementById("topSection");
            player.includes(playerSearch)
      if (existing) existing.remove();
              ? ""
              : "none";


        });


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


      dungeonSelect.addEventListener("change", filterTable);
      playerInput.addEventListener("input", filterTable);


       sortAndRank();
       /* ===========================
    }
        COMPETITION TABLE
  }
        =========================== */


      const compBox = document.createElement("div");
      compBox.style.flex = "1";


  /* ===========================
      const compTitle = document.createElement("h2");
    PARTY TABLE
      compTitle.innerText = "🏆 Competition Leaderboard";
    =========================== */
      compBox.appendChild(compTitle);


  const partyTable = document.getElementById("partyLeaderboardTable");
      const compTable = document.createElement("table");
      compTable.className = "wikitable";
      compTable.style.width = "100%";


  if (partyTable) {
      compTable.innerHTML = `
        <tr>
          <th>Rank</th>
          <th>Player</th>
          <th>Points</th>
        </tr>
      `;


    const partyContainer = document.getElementById("partyFilterContainer");
      const leaderboard = Object.entries(playerPoints)
        .map(([player, points]) => ({player, points}))
        .sort((a,b)=>b.points-a.points);


    if (partyContainer) {
      let prevPoints = null;
      let rank = 0;
      let position = 0;


       const wrapper = document.createElement("div");
       leaderboard.forEach(entry => {
      wrapper.style.marginBottom = "15px";


      const dungeonLabel = document.createElement("strong");
        position++;
      dungeonLabel.innerText = "Dungeon: ";
        if (entry.points !== prevPoints) rank = position;
      wrapper.appendChild(dungeonLabel);
        prevPoints = entry.points;


      const dungeonSelect = document.createElement("select");
        const medal =
      dungeonSelect.style.marginRight = "20px";
          rank===1?"🥇 1":
          rank===2?"🥈 2":
          rank===3?"🥉 3":
          rank;


      const dungeons = [
        const row = document.createElement("tr");
        "All",
         row.innerHTML = `
         "Wolf's Den","Wolf's Den II","Wolf's Den III",
          <td>${medal}</td>
        "The Red Nest","For The Eggs",
          <td>${entry.player}</td>
        "Scarlet Revenge","Scarlet Revenge II",
          <td>${entry.points}</td>
        "Promised One","Promised One II","Promised One III",
         `;
        "Lost and Found","The Hostage","Cactus On Fire",
         compTable.appendChild(row);
        "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);
       compBox.appendChild(compTable);


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


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


       partyContainer.appendChild(wrapper);
       const wrBox = document.createElement("div");
      wrBox.style.flex = "1";


      const wrTitle = document.createElement("h2");
      wrTitle.innerText = "🏁 Competition Dungeon Records";
      wrBox.appendChild(wrTitle);


       function sortAndRankParty() {
       const wrTable = document.createElement("table");
      wrTable.className = "wikitable";
      wrTable.style.width = "100%";


         const rows = Array.from(partyTable.querySelectorAll("tr")).slice(1);
      wrTable.innerHTML = `
         <tr>
          <th>Dungeon</th>
          <th>Time</th>
          <th>Player(s)</th>
        </tr>
      `;


        rows.sort((a,b) => {
      Object.entries(records).forEach(([d, data]) => {


          const dungeonA = a.cells[1].innerText.trim();
        const row = document.createElement("tr");
          const dungeonB = b.cells[1].innerText.trim();


           if (dungeonA < dungeonB) return -1;
        row.innerHTML = `
           if (dungeonA > dungeonB) return 1;
           <td>${d}</td>
          <td>${data.time}</td>
           <td>${data.players.join(", ")}</td>
        `;


          return parseTime(a.cells[5].innerText) - parseTime(b.cells[5].innerText);
        wrTable.appendChild(row);


        });
      });


        rows.forEach(row => partyTable.appendChild(row));
      wrBox.appendChild(wrTable);


        let currentDungeon = "";
        let dungeonRank = 0;


        rows.forEach(row => {
      /* COMBINE */
      top.appendChild(compBox);
      top.appendChild(wrBox);


          const dungeon = row.cells[1].innerText.trim();
      table.parentNode.insertBefore(top, table);
          const rankCell = row.cells[0];


          if (dungeon !== currentDungeon) {
      /* FILTER BELOW */
            currentDungeon = dungeon;
      table.parentNode.insertBefore(filterWrapper, table);
            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;


        });
    /* ===========================
      FILTER
      =========================== */


      }
    function filterTable() {


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


       function filterPartyTable() {
       const rows = Array.from(table.querySelectorAll("tr")).slice(1);


        const selectedDungeon = dungeonSelect.value;
      rows.forEach(row => {
        const playerSearch = playerInput.value.toLowerCase();


         const rows = Array.from(partyTable.querySelectorAll("tr")).slice(1);
         if (row.cells.length < 5) return;


         rows.forEach(row => {
         const d = row.cells[1].innerText.trim();
        const p = row.cells[2].innerText.toLowerCase();


          const dungeon = row.cells[1].innerText.trim();
        row.style.display =
           const player = row.cells[3].innerText.toLowerCase();
          (dungeon === "All" || d === dungeon) &&
           p.includes(player)
            ? ""
            : "none";
      });
    }


          row.style.display =
    dungeonSelect.addEventListener("change", filterTable);
            (selectedDungeon === "All" || dungeon === selectedDungeon) &&
    playerInput.addEventListener("input", filterTable);
            player.includes(playerSearch)
              ? ""
              : "none";


        });


      }
    /* ===========================
      INIT
      =========================== */


      dungeonSelect.addEventListener("change", filterPartyTable);
    sortAndRank();
      playerInput.addEventListener("input", filterPartyTable);
    buildTopSection();


      sortAndRankParty();
  });
    }
  }


});
});

Latest revision as of 18:07, 14 May 2026

/* Any JavaScript here will be loaded for all users on every page load. */

mw.hook('wikipage.content').add(function ($content) {
  $content.find('.spoiler').on('click', function () {
    $(this).toggleClass('revealed');
  });
});


mw.hook('wikipage.content').add(function () {

  /* ===========================
     WAIT FOR TABLE (IMPORTANT)
     =========================== */

  function waitForTable(callback) {
    const table = document.getElementById("leaderboardTable");
    if (table) {
      callback(table);
    } else {
      setTimeout(() => waitForTable(callback), 50);
    }
  }


  waitForTable(function(table) {

    /* ===========================
       HELPERS
       =========================== */

    function parseTime(t) {
      if (!t || !t.includes(":")) return 999999;
      const [m, s] = t.trim().split(":").map(Number);
      return m * 60 + s;
    }

    function getPoints(rank) {
      const scoring = {
        1:25,2:20,3:17,4:15,5:13,
        6:11,7:10,8:9,9:8,10:7,
        11:6,12:5,13:4,14:3,15:2
      };
      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);

      });

      rows.forEach(r => table.appendChild(r));

      let currentDungeon = "";
      let position = 0;
      let rank = 0;
      let prevTime = null;

      rows.forEach(row => {

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

        const dungeon = row.cells[1].innerText.trim();
        const time = parseTime(row.cells[4].innerText);

        if (dungeon !== currentDungeon) {
          currentDungeon = dungeon;
          position = 1;
          rank = 1;
          prevTime = time;
        } else {
          position++;
          if (time !== prevTime) rank = position;
          prevTime = time;
        }

        row.cells[0].innerText = rank;

      });
    }


    /* ===========================
       BUILD TOP SECTION
       =========================== */

    function buildTopSection() {

      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();
        const player = row.cells[2].innerText.trim();
        const time = row.cells[4].innerText.trim();
        const rank = parseInt(row.cells[0].innerText);

        // Only require valid time (CRITICAL FIX)
        if (!time || !time.includes(":")) return;

        /* ===========================
           POINTS
           =========================== */

        if (!isNaN(rank)) {
          playerPoints[player] = (playerPoints[player] || 0) + getPoints(rank);
        }

        /* ===========================
           WORLD RECORDS (WORKING)
           =========================== */

        const seconds = parseTime(time);

        if (!records[dungeon]) {
          records[dungeon] = {
            time: time,
            seconds: seconds,
            players: [player]
          };
        } else {
          if (seconds < records[dungeon].seconds) {
            records[dungeon] = {
              time: time,
              seconds: seconds,
              players: [player]
            };
          } else if (seconds === records[dungeon].seconds) {
            records[dungeon].players.push(player);
          }
        }

      });


      /* REMOVE OLD */
      const existing = document.getElementById("topSection");
      if (existing) existing.remove();


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


      /* ===========================
         COMPETITION TABLE
         =========================== */

      const compBox = document.createElement("div");
      compBox.style.flex = "1";

      const compTitle = document.createElement("h2");
      compTitle.innerText = "🏆 Competition Leaderboard";
      compBox.appendChild(compTitle);

      const compTable = document.createElement("table");
      compTable.className = "wikitable";
      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%";

      wrTable.innerHTML = `
        <tr>
          <th>Dungeon</th>
          <th>Time</th>
          <th>Player(s)</th>
        </tr>
      `;

      Object.entries(records).forEach(([d, data]) => {

        const row = document.createElement("tr");

        row.innerHTML = `
          <td>${d}</td>
          <td>${data.time}</td>
          <td>${data.players.join(", ")}</td>
        `;

        wrTable.appendChild(row);

      });

      wrBox.appendChild(wrTable);


      /* COMBINE */
      top.appendChild(compBox);
      top.appendChild(wrBox);

      table.parentNode.insertBefore(top, table);

      /* FILTER BELOW */
      table.parentNode.insertBefore(filterWrapper, table);
    }


    /* ===========================
       FILTER
       =========================== */

    function filterTable() {

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

      const rows = Array.from(table.querySelectorAll("tr")).slice(1);

      rows.forEach(row => {

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

        const d = row.cells[1].innerText.trim();
        const p = row.cells[2].innerText.toLowerCase();

        row.style.display =
          (dungeon === "All" || d === dungeon) &&
          p.includes(player)
            ? ""
            : "none";
      });
    }

    dungeonSelect.addEventListener("change", filterTable);
    playerInput.addEventListener("input", filterTable);


    /* ===========================
       INIT
       =========================== */

    sortAndRank();
    buildTopSection();

  });

});