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


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


   const wrapper = document.createElement("div");
   function waitForTable(callback) {
  wrapper.style.marginBottom = "15px";
    const table = document.getElementById("leaderboardTable");
    if (table) {
      callback(table);
    } else {
      setTimeout(() => waitForTable(callback), 50);
    }
  }


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


   const dungeonLabel = document.createElement("strong");
   waitForTable(function(table) {
  dungeonLabel.innerText = "Dungeon: ";
  wrapper.appendChild(dungeonLabel);


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


  const dungeons = [
    function parseTime(t) {
    "All",
      if (!t || !t.includes(":")) return 999999;
    "Wolf's Den","Wolf's Den II","Wolf's Den III",
      const [m, s] = t.trim().split(":").map(Number);
    "The Red Nest","For The Eggs",
      return m * 60 + s;
    "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 => {
    function getPoints(rank) {
    const option = document.createElement("option");
      const scoring = {
    option.value = name;
        1:25,2:20,3:17,4:15,5:13,
    option.textContent = name;
        6:11,7:10,8:9,9:8,10:7,
     dungeonSelect.appendChild(option);
        11:6,12:5,13:4,14:3,15:2
  });
      };
      return scoring[rank] || 1;
     }


  wrapper.appendChild(dungeonSelect);


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


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


    const filterTitle = document.createElement("h3");
    filterTitle.innerText = "🔍 Filter Runs";
    filterWrapper.appendChild(filterTitle);


  /* ===========================
    const dungeonSelect = document.createElement("select");
    SORT + RANK
    dungeonSelect.style.marginRight = "10px";
    =========================== */


  function sortAndRank() {
    [
      "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 rows = Array.from(table.querySelectorAll("tr")).slice(1);
     filterWrapper.appendChild(document.createTextNode("Dungeon: "));
    filterWrapper.appendChild(dungeonSelect);


     rows.sort((a,b) => {
     const playerInput = document.createElement("input");
    playerInput.placeholder = "Search player...";
    playerInput.style.marginLeft = "10px";
    filterWrapper.appendChild(playerInput);


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


      if (dungeonA < dungeonB) return -1;
    /* ===========================
      if (dungeonA > dungeonB) return 1;
      SORT + RANK
      =========================== */


      return parseTime(a.cells[4].innerText) - parseTime(b.cells[4].innerText);
    function sortAndRank() {


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


    rows.forEach(row => table.appendChild(row));
      rows.sort((a,b) => {


    let currentDungeon = "";
        if (a.cells.length < 5 || b.cells.length < 5) return 0;
    let dungeonRank = 0;
    let prevTime = null;
    let position = 0;


    rows.forEach(row => {
        const dA = a.cells[1].innerText.trim();
        const dB = b.cells[1].innerText.trim();


      const dungeon = row.cells[1].innerText.trim();
        if (dA !== dB) return dA.localeCompare(dB);
      const time = parseTime(row.cells[4].innerText);
      const rankCell = row.cells[0];


      if (dungeon !== currentDungeon) {
        return parseTime(a.cells[4].innerText) - parseTime(b.cells[4].innerText);
        currentDungeon = dungeon;
        dungeonRank = 1;
        position = 1;
        prevTime = time;
      } else {
        position++;


        if (time !== prevTime) {
      });
          dungeonRank = position;
        }


        prevTime = time;
      rows.forEach(r => table.appendChild(r));
      }


       rankCell.innerText = dungeonRank;
       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) {
    WORLD RECORDS TABLE
          currentDungeon = dungeon;
    =========================== */
          position = 1;
          rank = 1;
          prevTime = time;
        } else {
          position++;
          if (time !== prevTime) rank = position;
          prevTime = time;
        }


  function buildWorldRecordsTable() {
        row.cells[0].innerText = rank;


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


    const records = {};


     rows.forEach(row => {
     /* ===========================
      BUILD TOP SECTION
      =========================== */


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


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


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


    const existing = document.getElementById("worldRecordsTable");
      rows.forEach(row => {
    if (existing) existing.remove();


    const container = document.createElement("div");
        if (row.cells.length < 5) return;
    container.id = "worldRecordsTable";
    container.style.marginBottom = "20px";


    const title = document.createElement("h2");
        const dungeon = row.cells[1].innerText.trim();
    title.innerText = "🏆 Dungeon World Records";
        const player = row.cells[2].innerText.trim();
    container.appendChild(title);
        const time = row.cells[4].innerText.trim();
        const rank = parseInt(row.cells[0].innerText);


    const tableEl = document.createElement("table");
        // Only require valid time (CRITICAL FIX)
    tableEl.className = "wikitable";
        if (!time || !time.includes(":")) return;
    tableEl.style.width = "60%";


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


    Object.entries(records).forEach(([dungeon, data]) => {
        if (!isNaN(rank)) {
          playerPoints[player] = (playerPoints[player] || 0) + getPoints(rank);
        }


      const row = document.createElement("tr");
        /* ===========================
          WORLD RECORDS (WORKING)
          =========================== */


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


      tableEl.appendChild(row);
        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);
          }
        }


    });
      });


    container.appendChild(tableEl);


    table.parentNode.insertBefore(container, table);
      /* 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 LEADERBOARD
    =========================== */
  function buildCompetitionLeaderboard() {


    const rows = Array.from(table.querySelectorAll("tr")).slice(1);
      /* ===========================
        COMPETITION TABLE
        =========================== */


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


    function getPoints(rank) {
      const compTitle = document.createElement("h2");
       const scoring = {
       compTitle.innerText = "🏆 Competition Leaderboard";
        1: 25, 2: 20, 3: 17, 4: 15, 5: 13,
       compBox.appendChild(compTitle);
        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 compTable = document.createElement("table");
      compTable.className = "wikitable";
      compTable.style.width = "100%";


       const rank = parseInt(row.cells[0].innerText);
       compTable.innerHTML = `
       const player = row.cells[2].innerText.trim();
        <tr>
          <th>Rank</th>
          <th>Player</th>
          <th>Points</th>
        </tr>
       `;


       if (!player || isNaN(rank)) return;
       const leaderboard = Object.entries(playerPoints)
        .map(([player, points]) => ({player, points}))
        .sort((a,b)=>b.points-a.points);


       const points = getPoints(rank);
       let prevPoints = null;
      let rank = 0;
      let position = 0;


       playerPoints[player] = (playerPoints[player] || 0) + points;
       leaderboard.forEach(entry => {


    });
        position++;
        if (entry.points !== prevPoints) rank = position;
        prevPoints = entry.points;


    const leaderboard = Object.entries(playerPoints)
        const medal =
      .map(([player, points]) => ({ player, points }))
          rank===1?"🥇 1":
      .sort((a, b) => b.points - a.points);
          rank===2?"🥈 2":
          rank===3?"🥉 3":
          rank;


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


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


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


    const tableEl = document.createElement("table");
    tableEl.className = "wikitable";
    tableEl.style.width = "50%";


    tableEl.innerHTML = `
       /* ===========================
       <tr>
        WORLD RECORD TABLE
        <th>Rank</th>
        =========================== */
        <th>Player</th>
        <th>Points</th>
      </tr>
    `;


    let prevPoints = null;
      const wrBox = document.createElement("div");
    let rank = 0;
      wrBox.style.flex = "1";
    let position = 0;


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


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


       if (entry.points !== prevPoints) {
       wrTable.innerHTML = `
         rank = position;
        <tr>
       }
          <th>Dungeon</th>
          <th>Time</th>
          <th>Player(s)</th>
         </tr>
       `;


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


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


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


       tableEl.appendChild(row);
       });


    });
      wrBox.appendChild(wrTable);


    compContainer.appendChild(tableEl);


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


    // Insert filters UNDER leaderboard
      table.parentNode.insertBefore(top, table);
    table.parentNode.insertBefore(wrapper, table);


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




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


  function filterTable() {
    function filterTable() {


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


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


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


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


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


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


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


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


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


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


   sortAndRank();
   });
  buildWorldRecordsTable();
  buildCompetitionLeaderboard();


});
});