MediaWiki:Common.js
MediaWiki interface page
More actions
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* 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();
});