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
 
(31 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; }
    =========================== */
 
   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;
    }


     // Already loading?
     function getPoints(rank) {
    if (document.querySelector('script[src*="platform.twitter.com/widgets.js"]')) {
       const scoring = {
       // Poll until ready
        1:25,2:20,3:17,4:15,5:13,
      var tries = 0, t = setInterval(function () {
         6:11,7:10,8:9,9:8,10:7,
         if (window.twttr && twttr.widgets) { clearInterval(t); cb(); }
         11:6,12:5,13:4,14:3,15:2
         else if (++tries > 40) { clearInterval(t); }
       };
       }, 250);
       return scoring[rank] || 1;
       return;
     }
     }


    // Inject the widgets.js script
    var s = document.createElement('script');
    s.async = true;
    s.src = 'https://platform.twitter.com/widgets.js';
    s.onload = function () { cb(); };
    document.head.appendChild(s);
  }


  // Find eligible links and embed them
    /* ===========================
  function embedLinks(root) {
      FILTER UI
     var scope = root || document;
      =========================== */
     var selector = [
 
      'a[href^="https://x.com/"][href*="/status/"]',
    const filterWrapper = document.createElement("div");
       'a[href^="http://x.com/"][href*="/status/"]',
    filterWrapper.style.marginBottom = "20px";
       'a[href^="https://twitter.com/"][href*="/status/"]',
 
       'a[href^="http://twitter.com/"][href*="/status/"]'
     const filterTitle = document.createElement("h3");
    ].join(',');
     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));


    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);
          =========================== */
      }
    });
  }


  // Run on page load and whenever VE/LQT/etc. inject content
        const seconds = parseTime(time);
  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
  =========================== */


mw.hook('wikipage.content').add(function () {
        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);
          }
        }


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


    var dungeonFilter = document.getElementById("filterDungeon");
    var typeFilter = document.getElementById("filterType");
    var addBtn = document.getElementById("addRunBtn");


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


    function parseTime(t) {
        if (!t || t.indexOf(":") === -1) return 999999;
        var parts = t.split(":");
        return parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10);
    }


    /* ---------- Sorting & Ranking ---------- */
      /* FLEX CONTAINER */
      const top = document.createElement("div");
      top.id = "topSection";
      top.style.display = "flex";
      top.style.gap = "30px";
      top.style.marginBottom = "25px";


    function sortAndRank() {


        var rows = Array.prototype.slice.call(table.rows, 1);
      /* ===========================
        COMPETITION TABLE
        =========================== */


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


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


        rows.forEach(function (row, index) {
      const compTable = document.createElement("table");
            var rankCell = row.cells[0];
      compTable.className = "wikitable";
      compTable.style.width = "100%";


            if (index === 0) rankCell.innerHTML = "🥇 1";
      compTable.innerHTML = `
            else if (index === 1) rankCell.innerHTML = "🥈 2";
        <tr>
            else if (index === 2) rankCell.innerHTML = "🥉 3";
          <th>Rank</th>
            else rankCell.innerText = index + 1;
          <th>Player</th>
         });
          <th>Points</th>
    }
         </tr>
      `;


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


    function filterTable() {
      let prevPoints = null;
      let rank = 0;
      let position = 0;


        var selectedDungeon = dungeonFilter ? dungeonFilter.value : "All";
      leaderboard.forEach(entry => {
        var selectedType = typeFilter ? typeFilter.value : "All";


         Array.prototype.slice.call(table.rows, 1).forEach(function (row) {
         position++;
        if (entry.points !== prevPoints) rank = position;
        prevPoints = entry.points;


            var dungeon = row.cells[1].innerText.trim();
        const medal =
            var type = row.cells[2].innerText.trim();
          rank===1?"🥇 1":
          rank===2?"🥈 2":
          rank===3?"🥉 3":
          rank;


            var show =
        const row = document.createElement("tr");
                (selectedDungeon === "All" || dungeon === selectedDungeon) &&
        row.innerHTML = `
                (selectedType === "All" || type === selectedType);
          <td>${medal}</td>
          <td>${entry.player}</td>
          <td>${entry.points}</td>
        `;
        compTable.appendChild(row);


            row.style.display = show ? "" : "none";
      });
        });
    }


    if (dungeonFilter)
      compBox.appendChild(compTable);
        dungeonFilter.addEventListener("change", filterTable);


    if (typeFilter)
        typeFilter.addEventListener("change", filterTable);


    /* ---------- Submit Modal ---------- */
      /* ===========================
        WORLD RECORD TABLE
        =========================== */


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


        addBtn.addEventListener("click", function () {
      const wrTitle = document.createElement("h2");
      wrTitle.innerText = "🏁 Competition Dungeon Records";
      wrBox.appendChild(wrTitle);


            var overlay = document.createElement("div");
      const wrTable = document.createElement("table");
            overlay.style.position = "fixed";
      wrTable.className = "wikitable";
            overlay.style.top = "0";
      wrTable.style.width = "100%";
            overlay.style.left = "0";
            overlay.style.width = "100%";
            overlay.style.height = "100%";
            overlay.style.background = "rgba(0,0,0,0.6)";
            overlay.style.display = "flex";
            overlay.style.alignItems = "center";
            overlay.style.justifyContent = "center";
            overlay.style.zIndex = "9999";


            var modal = document.createElement("div");
      wrTable.innerHTML = `
            modal.style.background = "#fff";
        <tr>
            modal.style.padding = "20px";
          <th>Dungeon</th>
            modal.style.width = "420px";
          <th>Time</th>
            modal.style.borderRadius = "8px";
          <th>Player(s)</th>
        </tr>
      `;


            modal.innerHTML = `
      Object.entries(records).forEach(([d, data]) => {
                <h3>Submit Dungeon Run</h3>


                <label>Dungeon:</label><br>
        const row = document.createElement("tr");
                <input type="text" id="runDungeon"><br><br>


                <label>Type:</label><br>
        row.innerHTML = `
                <select id="runType">
          <td>${d}</td>
                    <option value="Solo">Solo</option>
          <td>${data.time}</td>
                    <option value="Party">Party</option>
          <td>${data.players.join(", ")}</td>
                </select><br><br>
        `;


                <div id="partySizeSection" style="display:none;">
        wrTable.appendChild(row);
                    <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">
      });
                    <label>Weapon (Solo Only):</label><br>
                    <select id="runWeapon">
                        <option>Bow</option>
                        <option>Great Sword</option>
                        <option>Sword & Shield</option>
                    </select><br><br>
                </div>


                <label>Player / Party:</label><br>
      wrBox.appendChild(wrTable);
                <input type="text" id="runPlayer"><br><br>


                <label>Time (MM:SS):</label><br>
                <input type="text" id="runTime"><br><br>


                <label>Date:</label><br>
      /* COMBINE */
                <input type="text" id="runDate"><br><br>
      top.appendChild(compBox);
      top.appendChild(wrBox);


                <label>Screenshot:</label><br>
      table.parentNode.insertBefore(top, table);
                <small>Upload first via <a href="/wiki/Special:Upload" target="_blank">Upload File</a></small><br>
                <input type="text" id="runScreenshot"><br><br>


                <button id="submitRun">Generate Entry</button>
      /* FILTER BELOW */
                <button id="cancelRun">Cancel</button>
      table.parentNode.insertBefore(filterWrapper, table);
            `;
    }


            overlay.appendChild(modal);
            document.body.appendChild(overlay);


            var runType = document.getElementById("runType");
    /* ===========================
            var partySizeSection = document.getElementById("partySizeSection");
      FILTER
            var weaponSection = document.getElementById("weaponSection");
      =========================== */


            runType.addEventListener("change", function () {
    function filterTable() {
                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 = function () {
      const dungeon = dungeonSelect.value;
                document.body.removeChild(overlay);
      const player = playerInput.value.toLowerCase();
            };


            document.getElementById("submitRun").onclick = function () {
      const rows = Array.from(table.querySelectorAll("tr")).slice(1);


                var dungeon = document.getElementById("runDungeon").value;
      rows.forEach(row => {
                var type = runType.value;
                var partySize = type === "Solo" ? "1" :
                    document.getElementById("runPartySize").value;
                var weapon = type === "Solo" ?
                    document.getElementById("runWeapon").value : "—";
                var player = document.getElementById("runPlayer").value;
                var time = document.getElementById("runTime").value;
                var date = document.getElementById("runDate").value;
                var screenshot = document.getElementById("runScreenshot").value;


                if (!player || !time || !date || !screenshot) {
        if (row.cells.length < 5) return;
                    alert("All required fields must be completed.");
                    return;
                }


                var row = `
        const d = row.cells[1].innerText.trim();
|-
        const p = row.cells[2].innerText.toLowerCase();
|
| ${dungeon}
| ${type}
| ${partySize}
| ${player}
| ${weapon}
| ${time}
| ${date}
| [[File:${screenshot}|120px]]
`;


                alert("Copy this row and paste at bottom of table:\n\n" + row);
        row.style.display =
                document.body.removeChild(overlay);
          (dungeon === "All" || d === dungeon) &&
             };
          p.includes(player)
        });
             ? ""
            : "none";
      });
     }
     }


     /* ---------- Init ---------- */
    dungeonSelect.addEventListener("change", filterTable);
    playerInput.addEventListener("input", filterTable);
 
 
     /* ===========================
      INIT
      =========================== */


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