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


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


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


Line 29: Line 25:


   const wrapper = document.createElement("div");
   const wrapper = document.createElement("div");
   wrapper.style.marginBottom = "15px";
   wrapper.style.marginBottom = "20px";


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


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


   const dungeons = [
   [
     "All",
     "All",
     "Wolf's Den","Wolf's Den II","Wolf's Den III",
     "Wolf's Den","Wolf's Den II","Wolf's Den III",
Line 52: Line 44:
     "King's Hoard","King's Hoard II",
     "King's Hoard","King's Hoard II",
     "Hall of Daggers"
     "Hall of Daggers"
   ];
   ].forEach(name => {
 
  dungeons.forEach(name => {
     const option = document.createElement("option");
     const option = document.createElement("option");
     option.value = name;
     option.value = name;
Line 61: Line 51:
   });
   });


  wrapper.appendChild(document.createTextNode("Dungeon: "));
   wrapper.appendChild(dungeonSelect);
   wrapper.appendChild(dungeonSelect);
  const playerLabel = document.createElement("strong");
  playerLabel.innerText = "Player: ";
  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 = "15px";
   playerInput.style.padding = "4px";
   wrapper.appendChild(playerInput);
   wrapper.appendChild(playerInput);


Line 79: Line 65:


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


     rows.sort((a,b) => {
     rows.sort((a,b) => {
 
       const dA = a.cells[1].innerText.trim();
       const dungeonA = a.cells[1].innerText.trim();
       const dB = b.cells[1].innerText.trim();
       const dungeonB = b.cells[1].innerText.trim();
       if (dA !== dB) return dA.localeCompare(dB);
 
       if (dungeonA < dungeonB) return -1;
      if (dungeonA > dungeonB) return 1;
 
       return parseTime(a.cells[4].innerText) - parseTime(b.cells[4].innerText);
       return parseTime(a.cells[4].innerText) - parseTime(b.cells[4].innerText);
     });
     });


Line 97: Line 77:


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


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


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


       rankCell.innerText = dungeonRank;
       row.cells[0].innerText = rank;
 
     });
     });
   }
   }




   /* ===========================
   /* ===========================
     WORLD RECORDS TABLE
     BUILD WR + COMPETITION
     =========================== */
     =========================== */


   function buildWorldRecordsTable() {
   function buildTopSection() {


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


    const playerPoints = {};
     const records = {};
     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 => {
     rows.forEach(row => {


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


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


     });
     });


    const existing = document.getElementById("worldRecordsTable");
    if (existing) existing.remove();


     const container = document.createElement("div");
     /* ---------- container ---------- */
    container.id = "worldRecordsTable";
    container.style.marginBottom = "20px";


     const title = document.createElement("h2");
     const existing = document.getElementById("topSection");
     title.innerText = "🏆 Dungeon World Records";
     if (existing) existing.remove();
    container.appendChild(title);


     const tableEl = document.createElement("table");
     const top = document.createElement("div");
     tableEl.className = "wikitable";
     top.id = "topSection";
    tableEl.style.width = "60%";


     tableEl.innerHTML = `
     top.style.display = "flex";
      <tr>
    top.style.gap = "30px";
        <th>Dungeon</th>
    top.style.alignItems = "flex-start";
        <th>Time</th>
     top.style.marginBottom = "25px";
        <th>Player(s)</th>
      </tr>
     `;


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


      const row = document.createElement("tr");
    /* ---------- COMPETITION ---------- */


      row.innerHTML = `
    const compBox = document.createElement("div");
        <td>${dungeon}</td>
    compBox.style.flex = "1";
        <td>${data.time}</td>
        <td>${data.players.join(", ")}</td>
      `;


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


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


     container.appendChild(tableEl);
     compTable.innerHTML = `
      <tr>
        <th>Rank</th>
        <th>Player</th>
        <th>Points</th>
      </tr>
    `;


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


  }
    let prev = null, rank = 0, pos = 0;


    leaderboard.forEach(e=>{
      pos++;
      if (e.points !== prev) rank = pos;
      prev = e.points;


  /* ===========================
      const r = document.createElement("tr");
    COMPETITION LEADERBOARD
    =========================== */


  function buildCompetitionLeaderboard() {
      let medal = rank===1?"🥇 1":rank===2?"🥈 2":rank===3?"🥉 3":rank;


    const rows = Array.from(table.querySelectorAll("tr")).slice(1);
      r.innerHTML = `
 
        <td>${medal}</td>
    const playerPoints = {};
        <td>${e.player}</td>
 
        <td>${e.points}</td>
    function getPoints(rank) {
       `;
      const scoring = {
       compTable.appendChild(r);
        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 rank = parseInt(row.cells[0].innerText);
    compBox.appendChild(compTable);
      const player = row.cells[2].innerText.trim();


      if (!player || isNaN(rank)) return;


      const points = getPoints(rank);
    /* ---------- WORLD RECORDS ---------- */


      playerPoints[player] = (playerPoints[player] || 0) + points;
    const wrBox = document.createElement("div");
    wrBox.style.flex = "1";


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


     const leaderboard = Object.entries(playerPoints)
     const wrTable = document.createElement("table");
      .map(([player, points]) => ({ player, points }))
    wrTable.className = "wikitable";
      .sort((a, b) => b.points - a.points);
    wrTable.style.width = "100%";


     const existing = document.getElementById("competitionLeaderboard");
     wrTable.innerHTML = `
    if (existing) existing.remove();
 
    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");
    tableEl.className = "wikitable";
    tableEl.style.width = "50%";
 
    tableEl.innerHTML = `
       <tr>
       <tr>
         <th>Rank</th>
         <th>Dungeon</th>
         <th>Player</th>
         <th>Time</th>
         <th>Points</th>
         <th>Player(s)</th>
       </tr>
       </tr>
     `;
     `;


     let prevPoints = null;
     Object.entries(records).forEach(([d, data])=>{
    let rank = 0;
       const r = document.createElement("tr");
    let position = 0;
       r.innerHTML = `
 
        <td>${d}</td>
    leaderboard.forEach((entry) => {
         <td>${data.time}</td>
 
        <td>${data.players.join(", ")}</td>
       position++;
       `;
 
       wrTable.appendChild(r);
       if (entry.points !== prevPoints) {
    });
         rank = position;
      }
 
      prevPoints = entry.points;
 
      let displayRank;
      if (rank === 1) displayRank = "🥇 1";
       else if (rank === 2) displayRank = "🥈 2";
       else if (rank === 3) displayRank = "🥉 3";
      else displayRank = rank;


      const row = document.createElement("tr");
    wrBox.appendChild(wrTable);


      row.innerHTML = `
        <td>${displayRank}</td>
        <td>${entry.player}</td>
        <td>${entry.points}</td>
      `;


      tableEl.appendChild(row);
    /* ---------- combine ---------- */


     });
     top.appendChild(compBox);
    top.appendChild(wrBox);


     compContainer.appendChild(tableEl);
     table.parentNode.insertBefore(top, table);


    table.parentNode.insertBefore(compContainer, table);
     // filters under both
 
     // Insert filters UNDER leaderboard
     table.parentNode.insertBefore(wrapper, table);
     table.parentNode.insertBefore(wrapper, table);
   }
   }


Line 308: Line 255:


   function filterTable() {
   function filterTable() {
    const d = dungeonSelect.value;
    const p = playerInput.value.toLowerCase();


     const selectedDungeon = dungeonSelect.value;
     Array.from(table.querySelectorAll("tr")).slice(1).forEach(row=>{
    const playerSearch = playerInput.value.toLowerCase();
 
    const rows = Array.from(table.querySelectorAll("tr")).slice(1);
 
    rows.forEach(row => {
 
       const dungeon = row.cells[1].innerText.trim();
       const dungeon = row.cells[1].innerText.trim();
       const player = row.cells[2].innerText.toLowerCase();
       const player = row.cells[2].innerText.toLowerCase();


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


Line 338: Line 276:


   sortAndRank();
   sortAndRank();
   buildWorldRecordsTable();
   buildTopSection();
  buildCompetitionLeaderboard();


});
});

Revision as of 14:55, 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 () {

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

  const table = document.getElementById("leaderboardTable");
  if (!table) return;


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

  const wrapper = document.createElement("div");
  wrapper.style.marginBottom = "20px";

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

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

  wrapper.appendChild(document.createTextNode("Dungeon: "));
  wrapper.appendChild(dungeonSelect);

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


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

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

    rows.sort((a,b) => {
      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(row => table.appendChild(row));

    let currentDungeon = "";
    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) {
        currentDungeon = dungeon;
        position = 1;
        rank = 1;
        prevTime = time;
      } else {
        position++;
        if (time !== prevTime) rank = position;
        prevTime = time;
      }

      row.cells[0].innerText = rank;
    });
  }


  /* ===========================
     BUILD WR + COMPETITION
     =========================== */

  function buildTopSection() {

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

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

      if (!player || isNaN(rank)) return;

      // points
      playerPoints[player] = (playerPoints[player] || 0) + getPoints(rank);

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

    });


    /* ---------- container ---------- */

    const existing = document.getElementById("topSection");
    if (existing) existing.remove();

    const top = document.createElement("div");
    top.id = "topSection";

    top.style.display = "flex";
    top.style.gap = "30px";
    top.style.alignItems = "flex-start";
    top.style.marginBottom = "25px";


    /* ---------- COMPETITION ---------- */

    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 prev = null, rank = 0, pos = 0;

    leaderboard.forEach(e=>{
      pos++;
      if (e.points !== prev) rank = pos;
      prev = e.points;

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

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

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

    compBox.appendChild(compTable);


    /* ---------- WORLD RECORDS ---------- */

    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 r = document.createElement("tr");
      r.innerHTML = `
        <td>${d}</td>
        <td>${data.time}</td>
        <td>${data.players.join(", ")}</td>
      `;
      wrTable.appendChild(r);
    });

    wrBox.appendChild(wrTable);


    /* ---------- combine ---------- */

    top.appendChild(compBox);
    top.appendChild(wrBox);

    table.parentNode.insertBefore(top, table);

    // filters under both
    table.parentNode.insertBefore(wrapper, table);
  }


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

  function filterTable() {
    const d = dungeonSelect.value;
    const p = playerInput.value.toLowerCase();

    Array.from(table.querySelectorAll("tr")).slice(1).forEach(row=>{
      const dungeon = row.cells[1].innerText.trim();
      const player = row.cells[2].innerText.toLowerCase();

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

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


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

  sortAndRank();
  buildTopSection();

});