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
 
(32 intermediate revisions by the same user not shown)
Line 1: Line 1:
/* Any JavaScript here will be loaded for all users on every page load. */// Load DataTables library
/* Any JavaScript here will be loaded for all users on every page load. */


mw.hook('wikipage.content').add(function ($content) {
mw.hook('wikipage.content').add(function ($content) {
Line 7: Line 7:
});
});


// Auto-embed X/Twitter status links into live tweets (no extension required)
(function () {
  'use strict';


  // Options you can tweak
mw.hook('wikipage.content').add(function () {
  var TW_OPTIONS = {
    // theme: 'dark',            // uncomment for dark
    // conversation: 'none',    // hide reply thread
    // dnt: true,                // "Do Not Track"
    // align: 'center'          // center the embed
  };


   function loadXWidgets (cb) {
   /* ===========================
    // If the widget API is ready, go
    WAIT FOR TABLE (IMPORTANT)
    if (window.twttr && twttr.widgets) { cb(); return; }
    =========================== */


    // Already loading?
  function waitForTable(callback) {
     if (document.querySelector('script[src*="platform.twitter.com/widgets.js"]')) {
     const table = document.getElementById("leaderboardTable");
       // Poll until ready
    if (table) {
      var tries = 0, t = setInterval(function () {
       callback(table);
        if (window.twttr && twttr.widgets) { clearInterval(t); cb(); }
    } else {
        else if (++tries > 40) { clearInterval(t); }
      setTimeout(() => waitForTable(callback), 50);
      }, 250);
      return;
     }
     }
  }
  waitForTable(function(table) {


     // Inject the widgets.js script
     /* ===========================
     var s = document.createElement('script');
      HELPERS
     s.async = true;
      =========================== */
     s.src = 'https://platform.twitter.com/widgets.js';
 
     s.onload = function () { cb(); };
    function parseTime(t) {
     document.head.appendChild(s);
      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);
 
      });


  // Find eligible links and embed them
       rows.forEach(r => table.appendChild(r));
  function embedLinks(root) {
    var scope = root || document;
    var selector = [
       'a[href^="https://x.com/"][href*="/status/"]',
      'a[href^="http://x.com/"][href*="/status/"]',
      'a[href^="https://twitter.com/"][href*="/status/"]',
      'a[href^="http://twitter.com/"][href*="/status/"]'
    ].join(',');


    var anchors = scope.querySelectorAll(selector);
      let currentDungeon = "";
    if (!anchors.length) return;
      let position = 0;
      let rank = 0;
      let prevTime = null;


    loadXWidgets(function () {
       rows.forEach(row => {
       anchors.forEach(function (a) {
        if (a.dataset.tweetEmbedded === '1') return; // avoid duplicates
        a.dataset.tweetEmbedded = '1';


         // Extract the numeric tweet ID
         if (row.cells.length < 5) return;
        var match = a.href.match(/\/status\/(\d+)/);
        if (!match) return;
        var id = match[1];


         // Create a container after the link
         const dungeon = row.cells[1].innerText.trim();
        var container = document.createElement('div');
         const time = parseTime(row.cells[4].innerText);
         container.className = 'tweet-embed';
        a.insertAdjacentElement('afterend', container);


        // Ask widgets API to render
         if (dungeon !== currentDungeon) {
         if (window.twttr && twttr.widgets && twttr.widgets.createTweet) {
           currentDungeon = dungeon;
           twttr.widgets.createTweet(id, container, TW_OPTIONS).catch(function(){});
          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);
        }


      // Ensure any dynamically added blockquotes also render
        /* ===========================
      if (window.twttr && twttr.widgets && twttr.widgets.load) {
          WORLD RECORDS (WORKING)
        twttr.widgets.load(scope);
          =========================== */
      }
 
    });
        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);
          }
        }


  // Run on page load and whenever VE/LQT/etc. inject content
       });
  if (window.mw && mw.hook) {
    mw.hook('wikipage.content').add(function ($content) {
       embedLinks($content && $content[0]);
    });
  } else {
    document.addEventListener('DOMContentLoaded', function () { embedLinks(document); });
  }
})();


/* ===========================
  Dungeon Leaderboard Script
  =========================== */


document.addEventListener("DOMContentLoaded", function () {
      /* REMOVE OLD */
      const existing = document.getElementById("topSection");
      if (existing) existing.remove();


    const table = document.getElementById("leaderboardTable");
    if (!table) return; // Only run on leaderboard page


    const dungeonFilter = document.getElementById("filterDungeon");
      /* FLEX CONTAINER */
    const typeFilter = document.getElementById("filterType");
      const top = document.createElement("div");
    const addBtn = document.getElementById("addRunBtn");
      top.id = "topSection";
      top.style.display = "flex";
      top.style.gap = "30px";
      top.style.marginBottom = "25px";


    function parseTime(t) {
        if (!t || !t.includes(":")) return 999999;
        const parts = t.split(":");
        return parseInt(parts[0]) * 60 + parseInt(parts[1]);
    }


    function sortAndRank() {
      /* ===========================
        const rows = Array.from(table.rows).slice(1);
        COMPETITION TABLE
        =========================== */


        rows.sort((a, b) => {
      const compBox = document.createElement("div");
            return parseTime(a.cells[6].innerText) - parseTime(b.cells[6].innerText);
      compBox.style.flex = "1";
        });


        rows.forEach(row => table.appendChild(row));
      const compTitle = document.createElement("h2");
      compTitle.innerText = "🏆 Competition Leaderboard";
      compBox.appendChild(compTitle);


        rows.forEach((row, index) => {
      const compTable = document.createElement("table");
            const rankCell = row.cells[0];
      compTable.className = "wikitable";
            if (index === 0) rankCell.innerHTML = "🥇 1";
      compTable.style.width = "100%";
            else if (index === 1) rankCell.innerHTML = "🥈 2";
            else if (index === 2) rankCell.innerHTML = "🥉 3";
            else rankCell.innerText = index + 1;
        });
    }


    function filterTable() {
      compTable.innerHTML = `
         const selectedDungeon = dungeonFilter.value;
         <tr>
         const selectedType = typeFilter.value;
          <th>Rank</th>
          <th>Player</th>
          <th>Points</th>
         </tr>
      `;


        Array.from(table.rows).slice(1).forEach(row => {
      const leaderboard = Object.entries(playerPoints)
            const dungeon = row.cells[1].innerText.trim();
        .map(([player, points]) => ({player, points}))
            const type = row.cells[2].innerText.trim();
        .sort((a,b)=>b.points-a.points);


            const show =
      let prevPoints = null;
                (selectedDungeon === "All" || dungeon === selectedDungeon) &&
      let rank = 0;
                (selectedType === "All" || type === selectedType);
      let position = 0;


            row.style.display = show ? "" : "none";
      leaderboard.forEach(entry => {
        });
    }


    dungeonFilter.addEventListener("change", filterTable);
        position++;
    typeFilter.addEventListener("change", filterTable);
        if (entry.points !== prevPoints) rank = position;
        prevPoints = entry.points;


    addBtn.addEventListener("click", function () {
        const medal =
          rank===1?"🥇 1":
          rank===2?"🥈 2":
          rank===3?"🥉 3":
          rank;


         const overlay = document.createElement("div");
         const row = document.createElement("tr");
         overlay.style = `
         row.innerHTML = `
            position:fixed;
          <td>${medal}</td>
            top:0;left:0;
          <td>${entry.player}</td>
            width:100%;height:100%;
          <td>${entry.points}</td>
            background:rgba(0,0,0,0.6);
            display:flex;
            align-items:center;
            justify-content:center;
            z-index:9999;
         `;
         `;
        compTable.appendChild(row);


        const modal = document.createElement("div");
      });
        modal.style = `
 
            background:white;
      compBox.appendChild(compTable);
            padding:20px;
 
            width:420px;
 
            border-radius:8px;
      /* ===========================
        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>
         `;
         `;


         modal.innerHTML = `
         wrTable.appendChild(row);
            <h3>Submit Dungeon Run</h3>


            <label>Dungeon:</label><br>
      });
            <select id="runDungeon">
                <option>Wolf's Den</option>
                <option>Wolf's Den II</option>
                <option>Wolf's Den III</option>
                <option>The Red Nest</option>
                <option>For The Eggs</option>
                <option>Scarlet Revenge</option>
                <option>Scarlet Revenge II</option>
                <option>Promised One</option>
                <option>Promised One II</option>
                <option>Promised One III</option>
                <option>Lost and Found</option>
                <option>The Hostage</option>
                <option>Cactus On Fire</option>
                <option>Deep Echoes</option>
                <option>Deep Echoes II</option>
                <option>Deep Echoes III</option>
            </select><br><br>


            <label>Type:</label><br>
      wrBox.appendChild(wrTable);
            <select id="runType">
                <option value="Solo">Solo</option>
                <option value="Party">Party</option>
            </select><br><br>


            <div id="partySizeSection" style="display:none;">
                <label>Party Size:</label><br>
                <select id="runPartySize">
                    <option>1</option>
                    <option>2</option>
                    <option>3</option>
                    <option>4</option>
                </select><br><br>
            </div>


            <div id="weaponSection">
      /* COMBINE */
                <label>Weapon (Solo Only):</label><br>
      top.appendChild(compBox);
                <select id="runWeapon">
      top.appendChild(wrBox);
                    <option>Bow</option>
                    <option>Great Sword</option>
                    <option>Sword & Shield</option>
                </select><br><br>
            </div>


            <label>Player / Party:</label><br>
      table.parentNode.insertBefore(top, table);
            <input type="text" id="runPlayer"><br><br>


            <label>Time (MM:SS):</label><br>
      /* FILTER BELOW */
            <input type="text" id="runTime"><br><br>
      table.parentNode.insertBefore(filterWrapper, table);
    }


            <label>Date:</label><br>
            <input type="text" id="runDate"><br><br>


            <label>Screenshot:</label><br>
    /* ===========================
            <small>Upload first via <a href="/wiki/Special:Upload" target="_blank">Upload File</a></small><br>
      FILTER
            <input type="text" id="runScreenshot"><br><br>
      =========================== */


            <button id="submitRun">Generate Entry</button>
    function filterTable() {
            <button id="cancelRun">Cancel</button>
        `;


        overlay.appendChild(modal);
      const dungeon = dungeonSelect.value;
        document.body.appendChild(overlay);
      const player = playerInput.value.toLowerCase();


        const runType = document.getElementById("runType");
      const rows = Array.from(table.querySelectorAll("tr")).slice(1);
        const partySizeSection = document.getElementById("partySizeSection");
        const weaponSection = document.getElementById("weaponSection");


        runType.addEventListener("change", function() {
      rows.forEach(row => {
            if (this.value === "Party") {
                partySizeSection.style.display = "block";
                weaponSection.style.display = "none";
            } else {
                partySizeSection.style.display = "none";
                weaponSection.style.display = "block";
            }
        });


         document.getElementById("cancelRun").onclick = () => {
         if (row.cells.length < 5) return;
            document.body.removeChild(overlay);
        };


         document.getElementById("submitRun").onclick = function () {
         const d = row.cells[1].innerText.trim();
        const p = row.cells[2].innerText.toLowerCase();


            const dungeon = document.getElementById("runDungeon").value;
        row.style.display =
            const type = runType.value;
          (dungeon === "All" || d === dungeon) &&
            const partySize = type === "Solo" ? "1" : document.getElementById("runPartySize").value;
          p.includes(player)
            const weapon = type === "Solo" ? document.getElementById("runWeapon").value : "—";
             ? ""
            const player = document.getElementById("runPlayer").value;
             : "none";
             const time = document.getElementById("runTime").value;
      });
             const date = document.getElementById("runDate").value;
    }
            const screenshot = document.getElementById("runScreenshot").value;


            if (!player || !time || !date || !screenshot) {
    dungeonSelect.addEventListener("change", filterTable);
                alert("All required fields must be completed.");
    playerInput.addEventListener("input", filterTable);
                return;
            }


            const row = `
|-
|
| ${dungeon}
| ${type}
| ${partySize}
| ${player}
| ${weapon}
| ${time}
| ${date}
| [[File:${screenshot}|120px]]
`;


            alert("Copy this row and paste at bottom of table:\\n\\n" + row);
    /* ===========================
            document.body.removeChild(overlay);
      INIT
        };
      =========================== */
    });


     sortAndRank();
     sortAndRank();
    buildTopSection();
  });
});
});