diff --git a/frontend/js/leaderboard/compare.js b/frontend/js/leaderboard/compare.js index c5f51b48..902689e9 100644 --- a/frontend/js/leaderboard/compare.js +++ b/frontend/js/leaderboard/compare.js @@ -231,19 +231,33 @@ function updateFloatingCompareBar() { bar.style.display = "flex"; - const namesStr = window.selectedUsers.map((u) => u.name).join(", "); const count = window.selectedUsers.length; + // Static markup (no user-controlled data) is safe via innerHTML. bar.innerHTML = ` -
- Comparing (${count}/3): ${namesStr} -
+
`; + + // Names are user-controlled, so build this part with safe DOM nodes + // (textContent) instead of interpolating into innerHTML. + const listDiv = bar.querySelector(".selected-list"); + listDiv.appendChild(document.createTextNode("Comparing (")); + const tagSpan = document.createElement("span"); + tagSpan.className = "selected-tag"; + tagSpan.textContent = `${count}/3`; + listDiv.appendChild(tagSpan); + listDiv.appendChild(document.createTextNode("): ")); + window.selectedUsers.forEach((u, idx) => { + listDiv.appendChild(document.createTextNode(u.name)); + if (idx < window.selectedUsers.length - 1) { + listDiv.appendChild(document.createTextNode(", ")); + } + }); } /** @@ -498,22 +512,34 @@ function populateComparisonTable() { }); } - let html = ""; + table.innerHTML = ""; + + const thead = document.createElement("thead"); + const headerRow = document.createElement("tr"); headers.forEach((h) => { - html += `${h}`; + const th = document.createElement("th"); + // Header may include user-controlled names (textContent only). + th.textContent = h; + headerRow.appendChild(th); }); - html += ""; + thead.appendChild(headerRow); + const tbody = document.createElement("tbody"); metrics.forEach((m) => { - html += `${m.label}`; + const row = document.createElement("tr"); + const labelCell = document.createElement("td"); + labelCell.textContent = m.label; + row.appendChild(labelCell); m.values.forEach((v) => { - html += `${v}`; + const cell = document.createElement("td"); + cell.textContent = v; + row.appendChild(cell); }); - html += ""; + tbody.appendChild(row); }); - html += ""; - table.innerHTML = html; + table.appendChild(thead); + table.appendChild(tbody); } /** diff --git a/frontend/js/leaderboard/sync-changes.js b/frontend/js/leaderboard/sync-changes.js index 50549b7f..3766f8c6 100644 --- a/frontend/js/leaderboard/sync-changes.js +++ b/frontend/js/leaderboard/sync-changes.js @@ -47,24 +47,63 @@ document.addEventListener("DOMContentLoaded", () => { btn.classList.add("pulse"); - let html = ""; + const fragment = document.createDocumentFragment(); + if (changes.rank_changes) { changes.rank_changes.forEach((rc) => { const arrow = rc.rank_delta > 0 ? "↑" : "↓"; const color = rc.rank_delta > 0 ? "var(--green)" : "var(--red)"; - html += `
${arrow} ${rc.username} moved #${rc.old_rank} → #${rc.new_rank}
`; + + const line = document.createElement("div"); + line.className = "changes-content-line"; + + const arrowSpan = document.createElement("span"); + arrowSpan.style.color = color; + arrowSpan.textContent = arrow; + line.appendChild(arrowSpan); + line.appendChild(document.createTextNode(" ")); + + const detailSpan = document.createElement("span"); + const nameStrong = document.createElement("strong"); + nameStrong.style.color = "#fff"; + // User-controlled display name (textContent only). + nameStrong.textContent = rc.username; + detailSpan.appendChild(nameStrong); + detailSpan.appendChild( + document.createTextNode(` moved #${rc.old_rank} → `), + ); + const newRankStrong = document.createElement("strong"); + newRankStrong.style.color = "var(--green)"; + newRankStrong.textContent = `#${rc.new_rank}`; + detailSpan.appendChild(newRankStrong); + + line.appendChild(detailSpan); + fragment.appendChild(line); }); } if (changes.new_users && changes.new_users.length > 0) { - html += `
${changes.new_users.length} new user(s) joined
`; + const line = document.createElement("div"); + line.className = "changes-content-line"; + const span = document.createElement("span"); + span.style.color = "#fff"; + span.textContent = `${changes.new_users.length} new user(s) joined`; + line.appendChild(span); + fragment.appendChild(line); } if (changes.total_new_solves && changes.total_new_solves > 0) { - html += `
${changes.total_new_solves} new problems solved across ${changes.users_with_new_solves} users
`; + const line = document.createElement("div"); + line.className = "changes-content-line"; + const span = document.createElement("span"); + span.style.color = "#fff"; + span.textContent = `${changes.total_new_solves} new problems solved across ${changes.users_with_new_solves} users`; + line.appendChild(span); + fragment.appendChild(line); } - body.innerHTML = html; + body.innerHTML = ""; + body.appendChild(fragment); } loadChanges(); diff --git a/frontend/registration.html b/frontend/registration.html index 8bee62fa..7d4d77f3 100644 --- a/frontend/registration.html +++ b/frontend/registration.html @@ -60,6 +60,7 @@

Join the Leaderboard

name="name" class="form-input" placeholder="Enter your full name" + maxlength="100" required />