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
Line 11: Line 11:


   /* ===========================
   /* ===========================
     HELPERS
     WAIT FOR TABLE (CRITICAL FIX)
     =========================== */
     =========================== */


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


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


  waitForTable(function(table) {


  /* ===========================
    /* ===========================
    MAIN TABLE
      HELPERS
    =========================== */
      =========================== */


  const table = document.getElementById("leaderboardTable");
    function parseTime(t) {
  if (!table) return;
      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";
      FILTER UI
      =========================== */


  const title = document.createElement("h3");
    const filterWrapper = document.createElement("div");
  title.innerText = "🔍 Filter Runs";
    filterWrapper.style.marginBottom = "20px";
  filterWrapper.appendChild(title);


  const dungeonSelect = document.createElement("select");
    const filterTitle = document.createElement("h3");
  dungeonSelect.style.marginRight = "10px";
    filterTitle.innerText = "🔍 Filter Runs";
    filterWrapper.appendChild(filterTitle);


  [
     const dungeonSelect = document.createElement("select");
    "All",
     dungeonSelect.style.marginRight = "10px";
    "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);
      "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 playerInput = document.createElement("input");
    filterWrapper.appendChild(document.createTextNode("Dungeon: "));
  playerInput.placeholder = "Search player...";
    filterWrapper.appendChild(dungeonSelect);
  playerInput.style.marginLeft = "10px";
  filterWrapper.appendChild(playerInput);


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


  /* ===========================
    SORT + RANK
    =========================== */


  function sortAndRank() {
     /* ===========================
 
      SORT + RANK
     const rows = Array.from(table.querySelectorAll("tr")).slice(1);
      =========================== */
 
    rows.sort((a,b) => {


      if (a.cells.length < 5 || b.cells.length < 5) return 0;
    function sortAndRank() {


       const dA = a.cells[1].innerText.trim();
       const rows = Array.from(table.querySelectorAll("tr")).slice(1);
      const dB = b.cells[1].innerText.trim();


       if (dA !== dB) return dA.localeCompare(dB);
       rows.sort((a,b) => {


      return parseTime(a.cells[4].innerText) - parseTime(b.cells[4].innerText);
        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();


    rows.forEach(r => table.appendChild(r));
        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 => {
      });


       if (row.cells.length < 5) return;
       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;
      let rank = 0;
      let prevTime = null;


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


      row.cells[0].innerText = rank;
        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 = {};
      BUILD TOP SECTION
      =========================== */


     rows.forEach(row => {
     function buildTopSection() {


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


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


       if (!dungeon || !player || !time) return;
       rows.forEach(row => {


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


      if (!isNaN(rank)) {
        const dungeon = row.cells[1]?.innerText?.trim();
         playerPoints[player] = (playerPoints[player] || 0) + getPoints(rank);
        const player = row.cells[2]?.innerText?.trim();
      }
         const time = row.cells[4]?.innerText?.trim();
        const rank = parseInt(row.cells[0]?.innerText);


      /* ===========================
        if (!dungeon || !player || !time) return;
        WORLD RECORDS (FIXED)
        =========================== */


      const newTime = parseTime(time);
        /* POINTS */
        if (!isNaN(rank)) {
          playerPoints[player] = (playerPoints[player] || 0) + getPoints(rank);
        }


      if (!records[dungeon]) {
        /* WORLD RECORDS */
         records[dungeon] = {
         const newTime = parseTime(time);
          time: time,
          seconds: newTime,
          players: [player]
        };
      } else {


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


       }
       });


    });


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


    /* 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";


    /* 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");
      COMPETITION TABLE
      compBox.style.flex = "1";
      =========================== */


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


    const compTitle = document.createElement("h2");
      const compTable = document.createElement("table");
    compTitle.innerText = "🏆 Competition Leaderboard";
      compTable.className = "wikitable";
    compBox.appendChild(compTitle);
      compTable.style.width = "100%";


    const compTable = document.createElement("table");
      compTable.innerHTML = `
    compTable.className = "wikitable";
        <tr>
    compTable.style.width = "100%";
          <th>Rank</th>
          <th>Player</th>
          <th>Points</th>
        </tr>
      `;


    compTable.innerHTML = `
      const leaderboard = Object.entries(playerPoints)
      <tr>
         .map(([player, points]) => ({player, points}))
         <th>Rank</th>
         .sort((a,b)=>b.points-a.points);
         <th>Player</th>
        <th>Points</th>
      </tr>
    `;


    const leaderboard = Object.entries(playerPoints)
      let prevPoints = null;
       .map(([player, points]) => ({player, points}))
       let rank = 0;
       .sort((a,b)=>b.points-a.points);
       let position = 0;


    let prevPoints = null;
      leaderboard.forEach(entry => {
    let rank = 0;
    let position = 0;


    leaderboard.forEach(entry => {
        position++;
 
        if (entry.points !== prevPoints) rank = position;
      position++;
        prevPoints = entry.points;
      if (entry.points !== prevPoints) rank = position;
      prevPoints = entry.points;


      const medal =
        const medal =
        rank===1?"🥇 1":
          rank===1?"🥇 1":
        rank===2?"🥈 2":
          rank===2?"🥈 2":
        rank===3?"🥉 3":
          rank===3?"🥉 3":
        rank;
          rank;


      const row = document.createElement("tr");
        const row = document.createElement("tr");
      row.innerHTML = `
        row.innerHTML = `
        <td>${medal}</td>
          <td>${medal}</td>
        <td>${entry.player}</td>
          <td>${entry.player}</td>
        <td>${entry.points}</td>
          <td>${entry.points}</td>
      `;
        `;
      compTable.appendChild(row);
        compTable.appendChild(row);


    });
      });


    compBox.appendChild(compTable);
      compBox.appendChild(compTable);




    /* ===========================
      /* ===========================
      WORLD RECORD TABLE
        WORLD RECORD TABLE
      =========================== */
        =========================== */


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


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


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


    wrTable.innerHTML = `
      wrTable.innerHTML = `
      <tr>
        <tr>
        <th>Dungeon</th>
          <th>Dungeon</th>
        <th>Time</th>
          <th>Time</th>
        <th>Player(s)</th>
          <th>Player(s)</th>
      </tr>
        </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);
       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);


    wrBox.appendChild(wrTable);


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


    /* COMBINE */
      table.parentNode.insertBefore(top, table);
    top.appendChild(compBox);
    top.appendChild(wrBox);


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


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


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


  /* ===========================
    function filterTable() {
    FILTER
    =========================== */


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


    const dungeon = dungeonSelect.value;
      const rows = Array.from(table.querySelectorAll("tr")).slice(1);
    const player = playerInput.value.toLowerCase();


    const rows = Array.from(table.querySelectorAll("tr")).slice(1);
      rows.forEach(row => {


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


      if (row.cells.length < 5) return;
        const d = row.cells[1].innerText.trim();
        const p = row.cells[2].innerText.toLowerCase();


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


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


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


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


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


   sortAndRank();
   });
  buildTopSection();


});
});

Revision as of 15:14, 18 March 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 (CRITICAL FIX)
     =========================== */

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

        if (!dungeon || !player || !time) return;

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

        /* WORLD RECORDS */
        const newTime = parseTime(time);

        if (!records[dungeon]) {
          records[dungeon] = {
            time: time,
            seconds: newTime,
            players: [player]
          };
        } else {
          if (newTime < records[dungeon].seconds) {
            records[dungeon] = {
              time: time,
              seconds: newTime,
              players: [player]
            };
          } else if (newTime === 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 = "🏆 Dungeon World 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();

  });

});