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
 
(8 intermediate revisions by the same user not shown)
Line 7: Line 7:
});
});


/* ===========================
  Dungeon Speedrun Leaderboard
  =========================== */


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


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


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


  if (table) {


    const container = document.getElementById("filterContainer");
  waitForTable(function(table) {


     let wrapper; // <-- IMPORTANT (used later)
     /* ===========================
      HELPERS
      =========================== */


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


       wrapper = document.createElement("div");
    function getPoints(rank) {
       wrapper.style.marginBottom = "15px";
       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;
    }


      const label = document.createElement("h3");
      label.innerText = "🔍 Filter Runs";
      wrapper.appendChild(label);


      const dungeonLabel = document.createElement("strong");
    /* ===========================
      dungeonLabel.innerText = "Dungeon: ";
      FILTER UI
      wrapper.appendChild(dungeonLabel);
      =========================== */


      const dungeonSelect = document.createElement("select");
    const filterWrapper = document.createElement("div");
      dungeonSelect.style.marginRight = "20px";
    filterWrapper.style.marginBottom = "20px";


      const dungeons = [
    const filterTitle = document.createElement("h3");
        "All",
    filterTitle.innerText = "🔍 Filter Runs";
        "Wolf's Den","Wolf's Den II","Wolf's Den III",
    filterWrapper.appendChild(filterTitle);
        "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 dungeonSelect = document.createElement("select");
        const option = document.createElement("option");
    dungeonSelect.style.marginRight = "10px";
        option.value = name;
        option.textContent = name;
        dungeonSelect.appendChild(option);
      });


       wrapper.appendChild(dungeonSelect);
    [
       "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);
    });


      const playerLabel = document.createElement("strong");
    filterWrapper.appendChild(document.createTextNode("Dungeon: "));
      playerLabel.innerText = "Player: ";
    filterWrapper.appendChild(dungeonSelect);
      wrapper.appendChild(playerLabel);


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




      function sortAndRank() {
    /* ===========================
      SORT + RANK
      =========================== */


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


        rows.sort((a,b) => {
      const rows = Array.from(table.querySelectorAll("tr")).slice(1);


          const dungeonA = a.cells[1].innerText.trim();
      rows.sort((a,b) => {
          const dungeonB = b.cells[1].innerText.trim();


          if (dungeonA < dungeonB) return -1;
        if (a.cells.length < 5 || b.cells.length < 5) return 0;
          if (dungeonA > dungeonB) return 1;


          return parseTime(a.cells[4].innerText) - parseTime(b.cells[4].innerText);
        const dA = a.cells[1].innerText.trim();
        const dB = b.cells[1].innerText.trim();


         });
         if (dA !== dB) return dA.localeCompare(dB);


         rows.forEach(row => table.appendChild(row));
         return parseTime(a.cells[4].innerText) - parseTime(b.cells[4].innerText);


        let currentDungeon = "";
      });
        let dungeonRank = 0;
        let prevTime = null;
        let position = 0;


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


          const dungeon = row.cells[1].innerText.trim();
      let currentDungeon = "";
          const time = parseTime(row.cells[4].innerText);
      let position = 0;
          const rankCell = row.cells[0];
      let rank = 0;
      let prevTime = null;


          if (dungeon !== currentDungeon) {
      rows.forEach(row => {
            currentDungeon = dungeon;
            dungeonRank = 1;
            position = 1;
            prevTime = time;
          } else {
            position++;


            if (time !== prevTime) {
        if (row.cells.length < 5) return;
              dungeonRank = position;
            }


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


           rankCell.innerText = dungeonRank;
        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;


       }
       });
    }




      /* ===========================
    /* ===========================
        COMPETITION LEADERBOARD
      BUILD TOP SECTION
        =========================== */
      =========================== */


      function buildCompetitionLeaderboard() {
    function buildTopSection() {


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


        const playerPoints = {};
      const playerPoints = {};
      const records = {};


        function getPoints(rank) {
      rows.forEach(row => {
          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;
        }


         rows.forEach(row => {
         if (row.cells.length < 5) return;


          const rank = parseInt(row.cells[0].innerText);
        const dungeon = row.cells[1].innerText.trim();
          const player = row.cells[2].innerText.trim();
        const player = row.cells[2].innerText.trim();
        const time = row.cells[4].innerText.trim();
        const rank = parseInt(row.cells[0].innerText);


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


          const points = getPoints(rank);
        /* ===========================
          POINTS
          =========================== */


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


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


         const leaderboard = Object.entries(playerPoints)
         const seconds = parseTime(time);
          .map(([player, points]) => ({ player, points }))
          .sort((a, b) => b.points - a.points);


         const existing = document.getElementById("competitionLeaderboard");
         if (!records[dungeon]) {
        if (existing) existing.remove();
          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);
          }
        }


        const compContainer = document.createElement("div");
      });
        compContainer.id = "competitionLeaderboard";
        compContainer.style.marginBottom = "20px";


        const title = document.createElement("h2");
        title.innerText = "🏆 Competition Leaderboard";
        compContainer.appendChild(title);


        const tableEl = document.createElement("table");
      /* REMOVE OLD */
        tableEl.className = "wikitable";
      const existing = document.getElementById("topSection");
        tableEl.style.width = "50%";
      if (existing) existing.remove();


        tableEl.innerHTML = `
          <tr>
            <th>Rank</th>
            <th>Player</th>
            <th>Points</th>
          </tr>
        `;


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


        leaderboard.forEach((entry) => {


          position++;
      /* ===========================
        COMPETITION TABLE
        =========================== */


          if (entry.points !== prevPoints) {
      const compBox = document.createElement("div");
            rank = position;
      compBox.style.flex = "1";
          }


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


          let displayRank;
      const compTable = document.createElement("table");
          if (rank === 1) displayRank = "🥇 1";
      compTable.className = "wikitable";
          else if (rank === 2) displayRank = "🥈 2";
      compTable.style.width = "100%";
          else if (rank === 3) displayRank = "🥉 3";
          else displayRank = rank;


           const row = document.createElement("tr");
      compTable.innerHTML = `
        <tr>
           <th>Rank</th>
          <th>Player</th>
          <th>Points</th>
        </tr>
      `;


          row.innerHTML = `
      const leaderboard = Object.entries(playerPoints)
            <td>${displayRank}</td>
        .map(([player, points]) => ({player, points}))
            <td>${entry.player}</td>
        .sort((a,b)=>b.points-a.points);
            <td>${entry.points}</td>
          `;


          tableEl.appendChild(row);
      let prevPoints = null;
      let rank = 0;
      let position = 0;


        });
      leaderboard.forEach(entry => {


         compContainer.appendChild(tableEl);
         position++;
        if (entry.points !== prevPoints) rank = position;
        prevPoints = entry.points;


         // Insert leaderboard above table
         const medal =
        table.parentNode.insertBefore(compContainer, table);
          rank===1?"🥇 1":
          rank===2?"🥈 2":
          rank===3?"🥉 3":
          rank;


         // Insert filters UNDER leaderboard
         const row = document.createElement("tr");
         compContainer.insertAdjacentElement("afterend", wrapper);
        row.innerHTML = `
          <td>${medal}</td>
          <td>${entry.player}</td>
          <td>${entry.points}</td>
        `;
         compTable.appendChild(row);


       }
       });


      compBox.appendChild(compTable);


      function filterTable() {


        const selectedDungeon = dungeonSelect.value;
      /* ===========================
        const playerSearch = playerInput.value.toLowerCase();
        WORLD RECORD TABLE
        =========================== */


        const rows = Array.from(table.querySelectorAll("tr")).slice(1);
      const wrBox = document.createElement("div");
      wrBox.style.flex = "1";


        rows.forEach(row => {
      const wrTitle = document.createElement("h2");
      wrTitle.innerText = "🏁 Competition Dungeon Records";
      wrBox.appendChild(wrTitle);


          const dungeon = row.cells[1].innerText.trim();
      const wrTable = document.createElement("table");
          const player = row.cells[2].innerText.toLowerCase();
      wrTable.className = "wikitable";
      wrTable.style.width = "100%";


          row.style.display =
      wrTable.innerHTML = `
            (selectedDungeon === "All" || dungeon === selectedDungeon) &&
        <tr>
            player.includes(playerSearch)
          <th>Dungeon</th>
              ? ""
          <th>Time</th>
              : "none";
          <th>Player(s)</th>
        </tr>
      `;


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


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


      dungeonSelect.addEventListener("change", filterTable);
        row.innerHTML = `
      playerInput.addEventListener("input", filterTable);
          <td>${d}</td>
          <td>${data.time}</td>
          <td>${data.players.join(", ")}</td>
        `;


      sortAndRank();
        wrTable.appendChild(row);
      buildCompetitionLeaderboard();


    }
      });
  }
 
 
  /* ===========================
    PARTY TABLE (unchanged)
    =========================== */


  const partyTable = document.getElementById("partyLeaderboardTable");
      wrBox.appendChild(wrTable);


  if (partyTable) {


    function sortAndRankParty() {
      /* COMBINE */
      top.appendChild(compBox);
      top.appendChild(wrBox);


       const rows = Array.from(partyTable.querySelectorAll("tr")).slice(1);
       table.parentNode.insertBefore(top, table);


       rows.sort((a,b) => {
       /* FILTER BELOW */
      table.parentNode.insertBefore(filterWrapper, table);
    }


        const dungeonA = a.cells[1].innerText.trim();
        const dungeonB = b.cells[1].innerText.trim();


        if (dungeonA < dungeonB) return -1;
    /* ===========================
        if (dungeonA > dungeonB) return 1;
      FILTER
      =========================== */


        return parseTime(a.cells[5].innerText) - parseTime(b.cells[5].innerText);
    function filterTable() {


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


       rows.forEach(row => partyTable.appendChild(row));
       const rows = Array.from(table.querySelectorAll("tr")).slice(1);
 
      let currentDungeon = "";
      let dungeonRank = 0;
      let prevTime = null;
      let position = 0;


       rows.forEach(row => {
       rows.forEach(row => {


         const dungeon = row.cells[1].innerText.trim();
         if (row.cells.length < 5) return;
        const time = parseTime(row.cells[5].innerText);
        const rankCell = row.cells[0];


         if (dungeon !== currentDungeon) {
         const d = row.cells[1].innerText.trim();
          currentDungeon = dungeon;
        const p = row.cells[2].innerText.toLowerCase();
          dungeonRank = 1;
          position = 1;
          prevTime = time;
        } else {
          position++;


           if (time !== prevTime) {
        row.style.display =
             dungeonRank = position;
           (dungeon === "All" || d === dungeon) &&
          }
          p.includes(player)
            ? ""
             : "none";
      });
    }


          prevTime = time;
    dungeonSelect.addEventListener("change", filterTable);
        }
    playerInput.addEventListener("input", filterTable);


        rankCell.innerText = dungeonRank;


      });
    /* ===========================
      INIT
      =========================== */


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

  });

});