MediaWiki:Common.js: Difference between revisions
MediaWiki interface page
More actions
No edit summary |
No edit summary |
||
| (10 intermediate revisions by the same user not shown) | |||
| Line 8: | Line 8: | ||
mw.hook('wikipage.content').add(function () { | |||
/* =========================== | |||
WAIT FOR TABLE (IMPORTANT) | |||
=========================== */ | |||
function | 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; | |||
} | |||
const | 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); | |||
}); | |||
rows.forEach(r => table.appendChild(r)); | |||
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) { | |||
currentDungeon = dungeon; | |||
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); | |||
} | } | ||
/* =========================== | |||
WORLD RECORDS (WORKING) | |||
=========================== */ | |||
const seconds = parseTime(time); | |||
if ( | 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); | |||
} | |||
} | |||
}); | |||
/* 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 TABLE | |||
=========================== */ | |||
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 prevPoints = null; | |||
let rank = 0; | |||
let position = 0; | |||
leaderboard.forEach(entry => { | |||
position++; | |||
if (entry.points !== prevPoints) rank = position; | |||
prevPoints = entry.points; | |||
const medal = | |||
rank===1?"🥇 1": | |||
rank===2?"🥈 2": | |||
rank===3?"🥉 3": | |||
rank; | |||
const row = document.createElement("tr"); | |||
row.innerHTML = ` | |||
<td>${medal}</td> | |||
<td>${entry.player}</td> | |||
<td>${entry.points}</td> | |||
`; | |||
compTable.appendChild(row); | |||
}); | |||
compBox.appendChild(compTable); | |||
/* =========================== | |||
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> | |||
`; | |||
wrTable.appendChild(row); | |||
}); | |||
wrBox.appendChild(wrTable); | |||
/* COMBINE */ | |||
top.appendChild(compBox); | |||
top.appendChild(wrBox); | |||
table.parentNode.insertBefore(top, table); | |||
/* FILTER BELOW */ | |||
table.parentNode.insertBefore(filterWrapper, table); | |||
} | |||
/* =========================== | |||
FILTER | |||
=========================== */ | |||
function filterTable() { | |||
const dungeon = dungeonSelect.value; | |||
const player = playerInput.value.toLowerCase(); | |||
const rows = Array.from(table.querySelectorAll("tr")).slice(1); | |||
rows.forEach(row => { | rows.forEach(row => { | ||
if (row.cells.length < 5) return; | |||
const d = row.cells[1].innerText.trim(); | |||
const p = row.cells[2].innerText.toLowerCase(); | |||
row.style.display = | |||
(dungeon === "All" || d === dungeon) && | |||
p.includes(player) | |||
? "" | |||
: "none"; | |||
}); | |||
} | |||
dungeonSelect.addEventListener("change", filterTable); | |||
playerInput.addEventListener("input", filterTable); | |||
/* =========================== | |||
INIT | |||
=========================== */ | |||
sortAndRank(); | |||
buildTopSection(); | |||
}); | |||
}); | }); | ||