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
 
(5 intermediate revisions by the same user not shown)
Line 10: Line 10:
mw.hook('wikipage.content').add(function () {
mw.hook('wikipage.content').add(function () {


   function parseTime(t) {
  /* ===========================
     if (!t || !t.includes(":")) return 999999;
    WAIT FOR TABLE (IMPORTANT)
     const [m, s] = t.trim().split(":").map(Number);
    =========================== */
     return m * 60 + s;
 
   function waitForTable(callback) {
     const table = document.getElementById("leaderboardTable");
    if (table) {
      callback(table);
     } else {
      setTimeout(() => waitForTable(callback), 50);
     }
   }
   }


   const table = document.getElementById("leaderboardTable");
 
  if (!table) return;
   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
      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";


  const wrapper = document.createElement("div");
    [
  wrapper.style.marginBottom = "20px";
      "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 label = document.createElement("h3");
    filterWrapper.appendChild(document.createTextNode("Dungeon: "));
  label.innerText = "🔍 Filter Runs";
    filterWrapper.appendChild(dungeonSelect);
  wrapper.appendChild(label);


  const dungeonSelect = document.createElement("select");
    const playerInput = document.createElement("input");
  dungeonSelect.style.marginRight = "20px";
    playerInput.placeholder = "Search player...";
    playerInput.style.marginLeft = "10px";
    filterWrapper.appendChild(playerInput);


  [
    "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 option = document.createElement("option");
    option.value = name;
    option.textContent = name;
    dungeonSelect.appendChild(option);
  });


  wrapper.appendChild(document.createTextNode("Dungeon: "));
    /* ===========================
  wrapper.appendChild(dungeonSelect);
      SORT + RANK
      =========================== */


  const playerInput = document.createElement("input");
    function sortAndRank() {
  playerInput.placeholder = "Search player...";
  playerInput.style.marginLeft = "15px";
  wrapper.appendChild(playerInput);


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


  /* ===========================
      rows.sort((a,b) => {
    SORT + RANK
    =========================== */


  function sortAndRank() {
        if (a.cells.length < 5 || b.cells.length < 5) return 0;
    const rows = Array.from(table.querySelectorAll("tr")).slice(1);


    rows.sort((a,b) => {
        const dA = a.cells[1].innerText.trim();
      const dA = a.cells[1].innerText.trim();
        const dB = b.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(row => table.appendChild(row));
        if (dA !== dB) return dA.localeCompare(dB);


    let currentDungeon = "";
        return parseTime(a.cells[4].innerText) - parseTime(b.cells[4].innerText);
    let position = 0;
    let rank = 0;
    let prevTime = null;


    rows.forEach(row => {
       });
       const dungeon = row.cells[1].innerText.trim();
      const time = parseTime(row.cells[4].innerText);


       if (dungeon !== currentDungeon) {
       rows.forEach(r => table.appendChild(r));
        currentDungeon = dungeon;
        position = 1;
        rank = 1;
        prevTime = time;
      } else {
        position++;
        if (time !== prevTime) rank = position;
        prevTime = time;
      }


       row.cells[0].innerText = rank;
       let currentDungeon = "";
    });
      let position = 0;
  }
      let rank = 0;
      let prevTime = null;


      rows.forEach(row => {


  /* ===========================
        if (row.cells.length < 5) return;
    BUILD WR + COMPETITION
    =========================== */


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


    const rows = Array.from(table.querySelectorAll("tr")).slice(1);
        if (dungeon !== currentDungeon) {
          currentDungeon = dungeon;
          position = 1;
          rank = 1;
          prevTime = time;
        } else {
          position++;
          if (time !== prevTime) rank = position;
          prevTime = time;
        }


    const playerPoints = {};
        row.cells[0].innerText = rank;
    const records = {};


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


    rows.forEach(row => {


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


      if (!player || isNaN(rank)) return;
    function buildTopSection() {


       // points
       const rows = Array.from(table.querySelectorAll("tr")).slice(1);
      playerPoints[player] = (playerPoints[player] || 0) + getPoints(rank);


       // WR
       const playerPoints = {};
      if (!records[dungeon]) {
       const records = {};
        records[dungeon] = { time, players: [player] };
       } else if (time === records[dungeon].time) {
        records[dungeon].players.push(player);
      }


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


    /* ---------- container ---------- */
        /* ===========================
          POINTS
          =========================== */


    const existing = document.getElementById("topSection");
        if (!isNaN(rank)) {
    if (existing) existing.remove();
          playerPoints[player] = (playerPoints[player] || 0) + getPoints(rank);
        }


    const top = document.createElement("div");
        /* ===========================
    top.id = "topSection";
          WORLD RECORDS (WORKING)
          =========================== */


    top.style.display = "flex";
        const seconds = parseTime(time);
    top.style.gap = "30px";
    top.style.alignItems = "flex-start";
    top.style.marginBottom = "25px";


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


    /* ---------- COMPETITION ---------- */
      });


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


    const compTitle = document.createElement("h2");
      /* REMOVE OLD */
    compTitle.innerText = "🏆 Competition Leaderboard";
      const existing = document.getElementById("topSection");
    compBox.appendChild(compTitle);
      if (existing) existing.remove();


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


    compTable.innerHTML = `
      /* FLEX CONTAINER */
       <tr>
      const top = document.createElement("div");
        <th>Rank</th>
      top.id = "topSection";
        <th>Player</th>
       top.style.display = "flex";
        <th>Points</th>
      top.style.gap = "30px";
       </tr>
       top.style.marginBottom = "25px";
    `;


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


    let prev = null, rank = 0, pos = 0;
      /* ===========================
        COMPETITION TABLE
        =========================== */


    leaderboard.forEach(e=>{
      const compBox = document.createElement("div");
      pos++;
       compBox.style.flex = "1";
       if (e.points !== prev) rank = pos;
      prev = e.points;


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


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


       r.innerHTML = `
       compTable.innerHTML = `
         <td>${medal}</td>
         <tr>
        <td>${e.player}</td>
          <th>Rank</th>
        <td>${e.points}</td>
          <th>Player</th>
          <th>Points</th>
        </tr>
       `;
       `;
      compTable.appendChild(r);
    });


    compBox.appendChild(compTable);
      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 RECORDS ---------- */


    const wrBox = document.createElement("div");
      /* ===========================
    wrBox.style.flex = "1";
        WORLD RECORD TABLE
        =========================== */


    const wrTitle = document.createElement("h2");
      const wrBox = document.createElement("div");
    wrTitle.innerText = "🏆 Dungeon World Records";
      wrBox.style.flex = "1";
    wrBox.appendChild(wrTitle);


    const wrTable = document.createElement("table");
      const wrTitle = document.createElement("h2");
    wrTable.className = "wikitable";
      wrTitle.innerText = "🏁 Competition Dungeon Records";
    wrTable.style.width = "100%";
      wrBox.appendChild(wrTitle);


    wrTable.innerHTML = `
      const wrTable = document.createElement("table");
      <tr>
       wrTable.className = "wikitable";
        <th>Dungeon</th>
      wrTable.style.width = "100%";
        <th>Time</th>
        <th>Player(s)</th>
       </tr>
    `;


    Object.entries(records).forEach(([d, data])=>{
       wrTable.innerHTML = `
      const r = document.createElement("tr");
         <tr>
       r.innerHTML = `
          <th>Dungeon</th>
         <td>${d}</td>
          <th>Time</th>
        <td>${data.time}</td>
          <th>Player(s)</th>
        <td>${data.players.join(", ")}</td>
        </tr>
       `;
       `;
      wrTable.appendChild(r);
    });


    wrBox.appendChild(wrTable);
      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);
    }




     /* ---------- combine ---------- */
     /* ===========================
      FILTER
      =========================== */


     top.appendChild(compBox);
     function filterTable() {
    top.appendChild(wrBox);


    table.parentNode.insertBefore(top, table);
      const dungeon = dungeonSelect.value;
      const player = playerInput.value.toLowerCase();


    // filters under both
      const rows = Array.from(table.querySelectorAll("tr")).slice(1);
    table.parentNode.insertBefore(wrapper, table);
  }


      rows.forEach(row => {


  /* ===========================
        if (row.cells.length < 5) return;
    FILTER
    =========================== */


  function filterTable() {
        const d = row.cells[1].innerText.trim();
    const d = dungeonSelect.value;
        const p = row.cells[2].innerText.toLowerCase();
    const p = playerInput.value.toLowerCase();


    Array.from(table.querySelectorAll("tr")).slice(1).forEach(row=>{
        row.style.display =
      const dungeon = row.cells[1].innerText.trim();
          (dungeon === "All" || d === dungeon) &&
       const player = row.cells[2].innerText.toLowerCase();
          p.includes(player)
            ? ""
            : "none";
       });
    }


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


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


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


  /* ===========================
    sortAndRank();
    INIT
    buildTopSection();
    =========================== */


   sortAndRank();
   });
  buildTopSection();


});
});

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

  });

});