MediaWiki:Common.js: Difference between revisions
MediaWiki interface page
More actions
No edit summary |
No edit summary |
||
| Line 7: | Line 7: | ||
}); | }); | ||
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 | const [m, s] = t.trim().split(":").map(Number); | ||
return | return m * 60 + s; | ||
} | } | ||
| Line 29: | Line 25: | ||
const wrapper = document.createElement("div"); | const wrapper = document.createElement("div"); | ||
wrapper.style.marginBottom = " | 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 dungeonSelect = document.createElement("select"); | const dungeonSelect = document.createElement("select"); | ||
dungeonSelect.style.marginRight = "20px"; | dungeonSelect.style.marginRight = "20px"; | ||
[ | |||
"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 => { | ||
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 playerInput = document.createElement("input"); | const playerInput = document.createElement("input"); | ||
playerInput.placeholder = "Search player..."; | |||
playerInput.placeholder = "Search player | playerInput.style.marginLeft = "15px"; | ||
playerInput.style. | |||
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 | const dB = b.cells[1].innerText.trim(); | ||
const | if (dA !== dB) return dA.localeCompare(dB); | ||
if ( | |||
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 | let position = 0; | ||
let rank = 0; | |||
let prevTime = null; | let prevTime = null; | ||
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); | ||
if (dungeon !== currentDungeon) { | if (dungeon !== currentDungeon) { | ||
currentDungeon = dungeon; | currentDungeon = dungeon; | ||
position = 1; | position = 1; | ||
rank = 1; | |||
prevTime = time; | prevTime = time; | ||
} else { | } else { | ||
position++; | position++; | ||
if (time !== prevTime) rank = position; | |||
if (time !== prevTime) | |||
prevTime = time; | prevTime = time; | ||
} | } | ||
row.cells[0].innerText = rank; | |||
}); | }); | ||
} | } | ||
/* =========================== | /* =========================== | ||
BUILD WR + COMPETITION | |||
=========================== */ | =========================== */ | ||
function | 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 | 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] }; | ||
} else if (time === records[dungeon].time) { | |||
records[dungeon].players.push(player); | |||
} else | |||
} | } | ||
}); | }); | ||
/* ---------- container ---------- */ | |||
const | const existing = document.getElementById("topSection"); | ||
if (existing) existing.remove(); | |||
const | 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 | const wrTable = document.createElement("table"); | ||
wrTable.className = "wikitable"; | |||
wrTable.style.width = "100%"; | |||
wrTable.innerHTML = ` | |||
<tr> | <tr> | ||
<th> | <th>Dungeon</th> | ||
<th> | <th>Time</th> | ||
<th> | <th>Player(s)</th> | ||
</tr> | </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); | table.parentNode.insertBefore(wrapper, table); | ||
} | } | ||
| Line 308: | Line 255: | ||
function filterTable() { | 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 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 = | ||
( | (d==="All"||dungeon===d) && player.includes(p) ? "" : "none"; | ||
}); | }); | ||
} | } | ||
| Line 338: | Line 276: | ||
sortAndRank(); | sortAndRank(); | ||
buildTopSection(); | |||
}); | }); | ||
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();
});