From 5d169e933a43f96c07a4d526ebaaa6427c678c62 Mon Sep 17 00:00:00 2001 From: Leonabcd123 <156839416+Leonabcd123@users.noreply.github.com> Date: Sun, 21 Dec 2025 17:24:07 +0200 Subject: [PATCH] refactor: remove JQuery from pages directory (@Leonabcd123) (#7237) - [x] 404 - [x] about - [x] account-settings - [x] account - [x] friends - [x] leaderboards - [x] loading - [x] login - [x] page - [x] profile-search - [x] profile - [x] settings - [x] test Before merging make sure to load every changed page to check for the qsa notification. --------- Co-authored-by: Christian Fehmer --- frontend/src/ts/pages/about.ts | 34 +-- frontend/src/ts/pages/account.ts | 299 +++++++++++++----------- frontend/src/ts/pages/friends.ts | 42 ++-- frontend/src/ts/pages/leaderboards.ts | 276 +++++++++++----------- frontend/src/ts/pages/loading.ts | 41 ++-- frontend/src/ts/pages/login.ts | 27 ++- frontend/src/ts/pages/profile-search.ts | 12 +- frontend/src/ts/pages/profile.ts | 66 +++--- frontend/src/ts/pages/settings.ts | 178 +++++++------- frontend/src/ts/utils/dom.ts | 65 ++++++ 10 files changed, 560 insertions(+), 480 deletions(-) diff --git a/frontend/src/ts/pages/about.ts b/frontend/src/ts/pages/about.ts index a82da0c53..d0321c388 100644 --- a/frontend/src/ts/pages/about.ts +++ b/frontend/src/ts/pages/about.ts @@ -10,11 +10,11 @@ import * as Skeleton from "../utils/skeleton"; import { TypingStats, SpeedHistogram } from "@monkeytype/schemas/public"; import { getNumberWithMagnitude, numberWithSpaces } from "../utils/numbers"; import { tryCatch } from "@monkeytype/util/trycatch"; -import { qsr } from "../utils/dom"; +import { qs, qsr, onWindowLoad } from "../utils/dom"; function reset(): void { - $(".pageAbout .contributors").empty(); - $(".pageAbout .supporters").empty(); + qs(".pageAbout .contributors")?.empty(); + qs(".pageAbout .supporters")?.empty(); ChartController.globalSpeedHistogram.getDataset("count").data = []; void ChartController.globalSpeedHistogram.updateColors(); @@ -43,11 +43,11 @@ function updateStatsAndHistogram(): void { end: secondsRounded * 1000, }); - $(".pageAbout #totalTimeTypingStat .val").text( + qs(".pageAbout #totalTimeTypingStat .val")?.setText( timeTypingDuration.years?.toString() ?? "", ); - $(".pageAbout #totalTimeTypingStat .valSmall").text("years"); - $(".pageAbout #totalTimeTypingStat").attr( + qs(".pageAbout #totalTimeTypingStat .valSmall")?.setText("years"); + qs(".pageAbout #totalTimeTypingStat")?.setAttribute( "aria-label", numberWithSpaces(Math.round(secondsRounded / 3600)) + " hours", ); @@ -56,15 +56,15 @@ function updateStatsAndHistogram(): void { typingStatsResponseData.testsStarted, ); - $(".pageAbout #totalStartedTestsStat .val").text( + qs(".pageAbout #totalStartedTestsStat .val")?.setText( startedWithMagnitude.rounded < 10 - ? startedWithMagnitude.roundedTo2 - : startedWithMagnitude.rounded, + ? startedWithMagnitude.roundedTo2.toString() + : startedWithMagnitude.rounded.toString(), ); - $(".pageAbout #totalStartedTestsStat .valSmall").text( + qs(".pageAbout #totalStartedTestsStat .valSmall")?.setText( startedWithMagnitude.orderOfMagnitude, ); - $(".pageAbout #totalStartedTestsStat").attr( + qs(".pageAbout #totalStartedTestsStat")?.setAttribute( "aria-label", numberWithSpaces(typingStatsResponseData.testsStarted) + " tests", ); @@ -73,15 +73,15 @@ function updateStatsAndHistogram(): void { typingStatsResponseData.testsCompleted, ); - $(".pageAbout #totalCompletedTestsStat .val").text( + qs(".pageAbout #totalCompletedTestsStat .val")?.setText( completedWIthMagnitude.rounded < 10 - ? completedWIthMagnitude.roundedTo2 - : completedWIthMagnitude.rounded, + ? completedWIthMagnitude.roundedTo2.toString() + : completedWIthMagnitude.rounded.toString(), ); - $(".pageAbout #totalCompletedTestsStat .valSmall").text( + qs(".pageAbout #totalCompletedTestsStat .valSmall")?.setText( completedWIthMagnitude.orderOfMagnitude, ); - $(".pageAbout #totalCompletedTestsStat").attr( + qs(".pageAbout #totalCompletedTestsStat")?.setAttribute( "aria-label", numberWithSpaces(typingStatsResponseData.testsCompleted) + " tests", ); @@ -212,6 +212,6 @@ export const page = new Page({ }, }); -$(() => { +onWindowLoad(() => { Skeleton.save("pageAbout"); }); diff --git a/frontend/src/ts/pages/account.ts b/frontend/src/ts/pages/account.ts index 326193880..f57c3ade6 100644 --- a/frontend/src/ts/pages/account.ts +++ b/frontend/src/ts/pages/account.ts @@ -35,7 +35,7 @@ import { SnapshotResult } from "../constants/default-snapshot"; import Ape from "../ape"; import { AccountChart } from "@monkeytype/schemas/configs"; import { SortedTableWithLimit } from "../utils/sorted-table"; -import { qsr } from "../utils/dom"; +import { qs, qsa, qsr, onWindowLoad, ElementWithUtils } from "../utils/dom"; let filterDebug = false; //toggle filterdebug @@ -62,9 +62,9 @@ function loadMoreLines(lineIndex?: number): void { visibleTableLines = newVisibleLines; if (visibleTableLines >= filteredResults.length) { - $(".pageAccount .loadMoreButton").addClass("hidden"); + qs(".pageAccount .loadMoreButton")?.hide(); } else { - $(".pageAccount .loadMoreButton").removeClass("hidden"); + qs(".pageAccount .loadMoreButton")?.show(); } historyTable.setLimit(newVisibleLines); @@ -284,7 +284,7 @@ async function fillContent(): Promise { const typingSpeedUnit = getTypingSpeedUnit(Config.typingSpeedUnit); filteredResults = []; - $(".pageAccount .history table tbody").empty(); + qs(".pageAccount .history table tbody")?.empty(); DB.getSnapshot()?.results?.forEach((result) => { // totalSeconds += tt; @@ -673,7 +673,7 @@ async function fillContent(): Promise { historyTable.setData(filteredResults); - $(".pageAccount .group.history table thead tr td:nth-child(2)").text( + qs(".pageAccount .group.history table thead tr td:nth-child(2)")?.setText( Config.typingSpeedUnit, ); @@ -854,91 +854,95 @@ async function fillContent(): Promise { } if (chartData === undefined || chartData.length === 0) { - $(".pageAccount .group.noDataError").removeClass("hidden"); - $(".pageAccount .group.chart").addClass("hidden"); - $(".pageAccount .group.dailyActivityChart").addClass("hidden"); - $(".pageAccount .group.histogramChart").addClass("hidden"); - $(".pageAccount .group.aboveHistory").addClass("hidden"); - $(".pageAccount .group.history").addClass("hidden"); - $(".pageAccount .triplegroup.stats").addClass("hidden"); - $(".pageAccount .group.estimatedWordsTyped").addClass("hidden"); + qs(".pageAccount .group.noDataError")?.show(); + qs(".pageAccount .group.chart")?.hide(); + qs(".pageAccount .group.dailyActivityChart")?.hide(); + qs(".pageAccount .group.histogramChart")?.hide(); + qs(".pageAccount .group.aboveHistory")?.hide(); + qs(".pageAccount .group.history")?.hide(); + qs(".pageAccount .triplegroup.stats")?.hide(); + qs(".pageAccount .group.estimatedWordsTyped")?.hide(); } else { - $(".pageAccount .group.noDataError").addClass("hidden"); - $(".pageAccount .group.chart").removeClass("hidden"); - $(".pageAccount .group.dailyActivityChart").removeClass("hidden"); - $(".pageAccount .group.histogramChart").removeClass("hidden"); - $(".pageAccount .group.aboveHistory").removeClass("hidden"); - $(".pageAccount .group.history").removeClass("hidden"); - $(".pageAccount .triplegroup.stats").removeClass("hidden"); - $(".pageAccount .group.estimatedWordsTyped").removeClass("hidden"); + qs(".pageAccount .group.noDataError")?.hide(); + qs(".pageAccount .group.chart")?.show(); + qs(".pageAccount .group.dailyActivityChart")?.show(); + qs(".pageAccount .group.histogramChart")?.show(); + qs(".pageAccount .group.aboveHistory")?.show(); + qs(".pageAccount .group.history")?.show(); + qs(".pageAccount .triplegroup.stats")?.show(); + qs(".pageAccount .group.estimatedWordsTyped")?.show(); } - $(".pageAccount .timeTotalFiltered .val").text( + qs(".pageAccount .timeTotalFiltered .val")?.setText( DateTime.secondsToString(Math.round(totalSecondsFiltered), true, true), ); const speedUnit = Config.typingSpeedUnit; - $(".pageAccount .highestWpm .title").text(`highest ${speedUnit}`); - $(".pageAccount .highestWpm .val").text(Format.typingSpeed(topWpm)); + qs(".pageAccount .highestWpm .title")?.setText(`highest ${speedUnit}`); + qs(".pageAccount .highestWpm .val")?.setText(Format.typingSpeed(topWpm)); - $(".pageAccount .averageWpm .title").text(`average ${speedUnit}`); - $(".pageAccount .averageWpm .val").text( + qs(".pageAccount .averageWpm .title")?.setText(`average ${speedUnit}`); + qs(".pageAccount .averageWpm .val")?.setText( Format.typingSpeed(totalWpm / testCount), ); - $(".pageAccount .averageWpm10 .title").text( + qs(".pageAccount .averageWpm10 .title")?.setText( `average ${speedUnit} (last 10 tests)`, ); - $(".pageAccount .averageWpm10 .val").text( + qs(".pageAccount .averageWpm10 .val")?.setText( Format.typingSpeed(wpmLast10total / last10), ); - $(".pageAccount .highestRaw .title").text(`highest raw ${speedUnit}`); - $(".pageAccount .highestRaw .val").text(Format.typingSpeed(rawWpm.max)); + qs(".pageAccount .highestRaw .title")?.setText(`highest raw ${speedUnit}`); + qs(".pageAccount .highestRaw .val")?.setText(Format.typingSpeed(rawWpm.max)); - $(".pageAccount .averageRaw .title").text(`average raw ${speedUnit}`); - $(".pageAccount .averageRaw .val").text( + qs(".pageAccount .averageRaw .title")?.setText(`average raw ${speedUnit}`); + qs(".pageAccount .averageRaw .val")?.setText( Format.typingSpeed(rawWpm.total / rawWpm.count), ); - $(".pageAccount .averageRaw10 .title").text( + qs(".pageAccount .averageRaw10 .title")?.setText( `average raw ${speedUnit} (last 10 tests)`, ); - $(".pageAccount .averageRaw10 .val").text( + qs(".pageAccount .averageRaw10 .val")?.setText( Format.typingSpeed(rawWpm.last10Total / rawWpm.last10Count), ); - $(".pageAccount .highestWpm .mode").html(topMode); - $(".pageAccount .testsTaken .val").text(testCount); + qs(".pageAccount .highestWpm .mode")?.setHtml(topMode); + qs(".pageAccount .testsTaken .val")?.setText(testCount.toString()); - $(".pageAccount .highestAcc .val").text(Format.accuracy(topAcc)); - $(".pageAccount .avgAcc .val").text(Format.accuracy(totalAcc / testCount)); - $(".pageAccount .avgAcc10 .val").text(Format.accuracy(totalAcc10 / last10)); + qs(".pageAccount .highestAcc .val")?.setText(Format.accuracy(topAcc)); + qs(".pageAccount .avgAcc .val")?.setText( + Format.accuracy(totalAcc / testCount), + ); + qs(".pageAccount .avgAcc10 .val")?.setText( + Format.accuracy(totalAcc10 / last10), + ); if (totalCons === 0 || totalCons === undefined) { - $(".pageAccount .avgCons .val").text("-"); - $(".pageAccount .avgCons10 .val").text("-"); + qs(".pageAccount .avgCons .val")?.setText("-"); + qs(".pageAccount .avgCons10 .val")?.setText("-"); } else { - $(".pageAccount .highestCons .val").text(Format.percentage(topCons)); + qs(".pageAccount .highestCons .val")?.setText(Format.percentage(topCons)); - $(".pageAccount .avgCons .val").text( + qs(".pageAccount .avgCons .val")?.setText( Format.percentage(totalCons / consCount), ); - $(".pageAccount .avgCons10 .val").text( + qs(".pageAccount .avgCons10 .val")?.setText( Format.percentage(totalCons10 / Math.min(last10, consCount)), ); } - $(".pageAccount .testsStarted .val").text(`${testCount + testRestarts}`); - $(".pageAccount .testsCompleted .val").text( + qs(".pageAccount .testsStarted .val")?.setText(`${testCount + testRestarts}`); + qs(".pageAccount .testsCompleted .val")?.setText( `${testCount}(${Math.floor( (testCount / (testCount + testRestarts)) * 100, )}%)`, ); - $(".pageAccount .testsCompleted .avgres").text(` + qs(".pageAccount .testsCompleted .avgres")?.setText(` ${(testRestarts / testCount).toFixed(1)} restarts per completed test `); @@ -949,13 +953,15 @@ async function fillContent(): Promise { const wpmChange = trend[1][1] - trend[0][1]; const wpmChangePerHour = wpmChange * (3600 / totalSecondsFiltered); const plus = wpmChangePerHour > 0 ? "+" : ""; - $(".pageAccount .group.chart .below .text").text( + qs(".pageAccount .group.chart .below .text")?.setText( `Speed change per hour spent typing: ${ plus + Format.typingSpeed(wpmChangePerHour, { showDecimalPlaces: true }) } ${Config.typingSpeedUnit}`, ); } - $(".pageAccount .estimatedWordsTyped .val").text(totalEstimatedWords); + qs(".pageAccount .estimatedWordsTyped .val")?.setText( + totalEstimatedWords.toString(), + ); if (chartData.length || accChartData.length) { ChartController.updateAccountChartButtons(); @@ -967,7 +973,7 @@ async function fillContent(): Promise { ChartController.accountActivity.update(); ChartController.accountHistogram.update(); Focus.set(false); - $(".page.pageAccount").css("height", "unset"); //weird safari fix + qs(".page.pageAccount")?.setStyle({ height: "unset" }); //weird safari fix setTimeout(() => { Profile.updateNameFontSize("account"); }, 0); @@ -1012,56 +1018,56 @@ export function updateTagsForResult(resultId: string, tagIds: string[]): void { } } - const el = $( + const el = qs( `.pageAccount .resultEditTagsButton[data-result-id='${resultId}']`, ); - el.attr("data-tags", JSON.stringify(tagIds)); + el?.setAttribute("data-tags", JSON.stringify(tagIds)); if (tagIds.length > 0) { - el.attr("aria-label", tagNames.join(", ")); - el.addClass("active"); + el?.setAttribute("aria-label", tagNames.join(", ")); + el?.addClass("active"); if (tagIds.length > 1) { - el.html(``); + el?.setHtml(``); } else { - el.html(``); + el?.setHtml(``); } } else { - el.attr("aria-label", "no tags"); - el.removeClass("active"); - el.html(``); + el?.setAttribute("aria-label", "no tags"); + el?.removeClass("active"); + el?.setHtml(``); } } -$(".pageAccount button.toggleResultsOnChart").on("click", () => { +qs(".pageAccount button.toggleResultsOnChart")?.on("click", () => { const newValue = [...Config.accountChart] as AccountChart; newValue[0] = newValue[0] === "on" ? "off" : "on"; setConfig("accountChart", newValue); }); -$(".pageAccount button.toggleAccuracyOnChart").on("click", () => { +qs(".pageAccount button.toggleAccuracyOnChart")?.on("click", () => { const newValue = [...Config.accountChart] as AccountChart; newValue[1] = newValue[1] === "on" ? "off" : "on"; setConfig("accountChart", newValue); }); -$(".pageAccount button.toggleAverage10OnChart").on("click", () => { +qs(".pageAccount button.toggleAverage10OnChart")?.on("click", () => { const newValue = [...Config.accountChart] as AccountChart; newValue[2] = newValue[2] === "on" ? "off" : "on"; setConfig("accountChart", newValue); }); -$(".pageAccount button.toggleAverage100OnChart").on("click", () => { +qs(".pageAccount button.toggleAverage100OnChart")?.on("click", () => { const newValue = [...Config.accountChart] as AccountChart; newValue[3] = newValue[3] === "on" ? "off" : "on"; setConfig("accountChart", newValue); }); -$(".pageAccount .loadMoreButton").on("click", () => { +qs(".pageAccount .loadMoreButton")?.on("click", () => { loadMoreLines(); }); -$(".pageAccount #accountHistoryChart").on("click", () => { +qs(".pageAccount #accountHistoryChart")?.on("click", () => { const index: number = ChartController.accountHistoryActiveIndex; loadMoreLines(index); if (window === undefined) return; @@ -1070,98 +1076,109 @@ $(".pageAccount #accountHistoryChart").on("click", () => { if (resultId === undefined) { throw new Error("Cannot find result for index " + index); } - const element = $(`.resultRow[data-id="${resultId}"`); - $(".resultRow").removeClass("active"); + const element = qs(`.resultRow[data-id="${resultId}"`); + qsa(".resultRow").removeClass("active"); - element[0]?.scrollIntoView({ + element?.scrollIntoView({ block: "center", }); - element.addClass("active"); + element?.addClass("active"); }); -$(".pageAccount").on("click", ".miniResultChartButton", async (event) => { - const target = $(event.currentTarget); - const resultId: string = target.parents("tr").data("id") as string; - if (target.hasClass("loading")) return; - if (target.hasClass("disabled")) return; - - const result = filteredResults.find((it) => it._id === resultId); - if (result === undefined) return; - - let chartData = result.chartData as ChartData; - - if (chartData === undefined) { - //need to load full result - target.addClass("loading"); - target.attr("aria-label", null); - target.html(''); - Loader.show(); - - const response = await Ape.results.getById({ - params: { resultId: result._id }, - }); - Loader.hide(); - - target.html(''); - target.removeClass("loading"); - - if (response.status !== 200) { - Notifications.add("Error fetching result", -1, { response }); - return; - } - - chartData = response.body.data.chartData as ChartData; - - //update local cache - result.chartData = chartData; - const dbResult = DB.getSnapshot()?.results?.find( - (it) => it._id === result._id, - ); - if (dbResult !== undefined) { - dbResult["chartData"] = result.chartData; - } - - if (response.body.data.chartData === "toolong") { - target.attr( - "aria-label", - "Graph history is not available for long tests", - ); - target.attr("data-baloon-pos", "up"); - target.addClass("disabled"); - - Notifications.add("Graph history is not available for long tests", 0); - return; - } - } - target.attr("aria-label", "View graph"); - MiniResultChartModal.show(chartData); -}); - -$(".pageAccount .group.topFilters, .pageAccount .filterButtons").on( +qs(".pageAccount")?.onChild( "click", - "button", - () => { - setTimeout(() => { - void update(); - }, 0); + ".miniResultChartButton", + async (event) => { + const target = new ElementWithUtils(event.childTarget as HTMLElement); + const resultId: string = target + .closestParent("tr") + ?.getAttribute("data-id") as string; + if (target.hasClass("loading")) return; + if (target.hasClass("disabled")) return; + + const result = filteredResults.find((it) => it._id === resultId); + if (result === undefined) return; + + let chartData = result.chartData as ChartData; + + if (chartData === undefined) { + //need to load full result + target?.addClass("loading"); + target?.removeAttribute("aria-label"); + target?.setHtml(''); + Loader.show(); + + const response = await Ape.results.getById({ + params: { resultId: result._id }, + }); + Loader.hide(); + + target?.setHtml(''); + target?.removeClass("loading"); + + if (response.status !== 200) { + Notifications.add("Error fetching result", -1, { response }); + return; + } + + chartData = response.body.data.chartData as ChartData; + + //update local cache + result.chartData = chartData; + const dbResult = DB.getSnapshot()?.results?.find( + (it) => it._id === result._id, + ); + if (dbResult !== undefined) { + dbResult["chartData"] = result.chartData; + } + + if (response.body.data.chartData === "toolong") { + target?.setAttribute( + "aria-label", + "Graph history is not available for long tests", + ); + target?.setAttribute("data-balloon-pos", "up"); + target.addClass("disabled"); + + Notifications.add("Graph history is not available for long tests", 0); + return; + } + } + target?.setAttribute("aria-label", "View graph"); + MiniResultChartModal.show(chartData); }, ); -$(".pageAccount .group.presetFilterButtons").on( +const filterButtons = qsa( + ".pageAccount .group.topFilters, .pageAccount .filterButtons", +); + +filterButtons.forEach((filterButton) => { + filterButton.onChild("click", "button", () => { + setTimeout(() => { + void update(); + }, 0); + }); +}); + +qs(".pageAccount .group.presetFilterButtons")?.onChild( "click", ".filterBtns .filterPresets .select-filter-preset", async (e) => { - await ResultFilters.setFilterPreset($(e.target).data("id") as string); + const target = e.childTarget as HTMLElement; + await ResultFilters.setFilterPreset( + target.getAttribute("data-id") as string, + ); void update(); }, ); -$(".pageAccount .content .group.aboveHistory .exportCSV").on("click", () => { +qs(".pageAccount .content .group.aboveHistory .exportCSV")?.on("click", () => { void Misc.downloadResultsCSV(filteredResults); }); -$(".pageAccount .profile").on("click", ".details .copyLink", () => { +qs(".pageAccount .profile")?.onChild("click", ".details .copyLink", () => { const snapshot = DB.getSnapshot(); if (!snapshot) return; const { name } = snapshot; @@ -1177,7 +1194,7 @@ $(".pageAccount .profile").on("click", ".details .copyLink", () => { ); }); -$(".pageAccount button.loadMoreResults").on("click", async () => { +qs(".pageAccount button.loadMoreResults")?.on("click", async () => { const offset = DB.getSnapshot()?.results?.length ?? 0; Loader.show(); @@ -1256,9 +1273,9 @@ export const page = new Page({ await update().then(() => { void updateChartColors(); - $(".pageAccount .content .accountVerificatinNotice").remove(); + qs(".pageAccount .content .accountVerificatinNotice")?.remove(); if (getAuthenticatedUser()?.emailVerified === false) { - $(".pageAccount .content").prepend( + qs(".pageAccount .content")?.prependHtml( `

Your email address is still not verified

`, ); } @@ -1267,6 +1284,6 @@ export const page = new Page({ }, }); -$(() => { +onWindowLoad(() => { Skeleton.save("pageAccount"); }); diff --git a/frontend/src/ts/pages/friends.ts b/frontend/src/ts/pages/friends.ts index e8b5c0c64..e192fee44 100644 --- a/frontend/src/ts/pages/friends.ts +++ b/frontend/src/ts/pages/friends.ts @@ -30,7 +30,7 @@ import { Friend, UserNameSchema } from "@monkeytype/schemas/users"; import * as Loader from "../elements/loader"; import { LocalStorageWithSchema } from "../utils/local-storage-with-schema"; import { remoteValidation } from "../utils/remote-validation"; -import { qsr } from "../utils/dom"; +import { qs, qsr, onWindowLoad } from "../utils/dom"; let friendsTable: SortedTable | undefined = undefined; @@ -155,12 +155,12 @@ async function fetchPendingConnections(): Promise { } function updatePendingConnections(): void { - $(".pageFriends .pendingRequests").addClass("hidden"); + qs(".pageFriends .pendingRequests")?.hide(); if (pendingRequests === undefined || pendingRequests.length === 0) { - $(".pageFriends .pendingRequests").addClass("hidden"); + qs(".pageFriends .pendingRequests")?.hide(); } else { - $(".pageFriends .pendingRequests").removeClass("hidden"); + qs(".pageFriends .pendingRequests")?.show(); const html = pendingRequests .map( @@ -193,7 +193,7 @@ function updatePendingConnections(): void { ) .join("\n"); - $(".pageFriends .pendingRequests tbody").html(html); + qs(".pageFriends .pendingRequests tbody")?.setHtml(html); } } @@ -208,17 +208,17 @@ async function fetchFriends(): Promise { } function updateFriends(): void { - $(".pageFriends .friends .nodata").addClass("hidden"); - $(".pageFriends .friends table").addClass("hidden"); + qs(".pageFriends .friends .nodata")?.hide(); + qs(".pageFriends .friends table")?.hide(); - $(".pageFriends .friends .error").addClass("hidden"); + qs(".pageFriends .friends .error")?.hide(); if (friendsList === undefined || friendsList.length === 0) { - $(".pageFriends .friends table").addClass("hidden"); - $(".pageFriends .friends .nodata").removeClass("hidden"); + qs(".pageFriends .friends table")?.hide(); + qs(".pageFriends .friends .nodata")?.show(); } else { - $(".pageFriends .friends table").removeClass("hidden"); - $(".pageFriends .friends .nodata").addClass("hidden"); + qs(".pageFriends .friends table")?.show(); + qs(".pageFriends .friends .nodata")?.hide(); if (friendsTable === undefined) { friendsTable = new SortedTable({ @@ -386,19 +386,20 @@ function formatStreak(length?: number, prefix?: string): string { : "-"; } -$(".pageFriends button.friendAdd").on("click", () => { +qs(".pageFriends button.friendAdd")?.on("click", () => { addFriendModal.show(undefined, {}); }); // need to set the listener for action buttons on the table because the table content is getting replaced -$(".pageFriends .pendingRequests table").on("click", async (e) => { - const action = Array.from(e.target.classList).find((it) => +qs(".pageFriends .pendingRequests table")?.on("click", async (e) => { + const target = e.target as HTMLElement; + const action = Array.from(target.classList).find((it) => ["accepted", "rejected", "blocked"].includes(it), ) as "accepted" | "rejected" | "blocked"; if (action === undefined) return; - const row = e.target.closest("tr") as HTMLElement; + const row = target.closest("tr") as HTMLElement; const id = row.dataset["id"]; if (id === undefined) { throw new Error("Cannot find id of target."); @@ -462,14 +463,15 @@ $(".pageFriends .pendingRequests table").on("click", async (e) => { } }); // need to set the listener for action buttons on the table because the table content is getting replaced -$(".pageFriends .friends table").on("click", async (e) => { - const action = Array.from(e.target.classList).find((it) => +qs(".pageFriends .friends table")?.on("click", async (e) => { + const target = e.target as HTMLElement; + const action = Array.from(target.classList).find((it) => ["remove"].includes(it), ); if (action === undefined) return; - const row = e.target.closest("tr") as HTMLElement; + const row = target.closest("tr") as HTMLElement; const connectionId = row.dataset["connectionId"]; if (connectionId === undefined) { throw new Error("Cannot find id of target."); @@ -551,7 +553,7 @@ export const page = new Page({ }, }); -$(() => { +onWindowLoad(() => { Skeleton.save("pageFriends"); }); diff --git a/frontend/src/ts/pages/leaderboards.ts b/frontend/src/ts/pages/leaderboards.ts index bed08ec8e..bb07897e9 100644 --- a/frontend/src/ts/pages/leaderboards.ts +++ b/frontend/src/ts/pages/leaderboards.ts @@ -44,7 +44,7 @@ import { isSafeNumber } from "@monkeytype/util/numbers"; import { Mode, Mode2, ModeSchema } from "@monkeytype/schemas/shared"; import * as ServerConfiguration from "../ape/server-configuration"; import { getAvatarElement } from "../utils/discord-avatar"; -import { qsr } from "../utils/dom"; +import { qs, qsa, qsr, onWindowLoad } from "../utils/dom"; const LeaderboardTypeSchema = z.enum(["allTime", "weekly", "daily"]); type LeaderboardType = z.infer; @@ -169,32 +169,30 @@ function updateTitle(): void { : ""; state.title = `${type} ${language} ${mode} ${friend}Leaderboard`; - $(".page.pageLeaderboards .bigtitle >.text").text(state.title); + qs(".page.pageLeaderboards .bigtitle >.text")?.setText(state.title); - $(".page.pageLeaderboards .bigtitle .subtext").addClass("hidden"); - $(".page.pageLeaderboards .bigtitle button").addClass("hidden"); - $(".page.pageLeaderboards .bigtitle .subtext .divider").addClass("hidden"); + qs(".page.pageLeaderboards .bigtitle .subtext")?.hide(); + qsa(".page.pageLeaderboards .bigtitle button")?.hide(); + qs(".page.pageLeaderboards .bigtitle .subtext .divider")?.hide(); if (state.type === "daily") { - $(".page.pageLeaderboards .bigtitle .subtext").removeClass("hidden"); - $( + qs(".page.pageLeaderboards .bigtitle .subtext")?.show(); + qs( ".page.pageLeaderboards .bigtitle button[data-action='toggleYesterday']", - ).removeClass("hidden"); - $(".page.pageLeaderboards .bigtitle .subtext .divider").removeClass( - "hidden", - ); + )?.show(); + qs(".page.pageLeaderboards .bigtitle .subtext .divider")?.show(); if (state.yesterday) { - $( + qs( ".page.pageLeaderboards .bigtitle button[data-action='toggleYesterday']", - ).html(` + )?.setHtml(` show today `); } else { - $( + qs( ".page.pageLeaderboards .bigtitle button[data-action='toggleYesterday']", - ).html(` + )?.setHtml(` show yesterday `); @@ -211,23 +209,23 @@ function updateTitle(): void { utcToLocalDate(endOfDay(timestamp)), ); } else if (state.type === "weekly") { - $(".page.pageLeaderboards .bigtitle .subtext").removeClass("hidden"); - $( + qs(".page.pageLeaderboards .bigtitle .subtext")?.show(); + qs( ".page.pageLeaderboards .bigtitle button[data-action='toggleLastWeek']", - ).removeClass("hidden"); - $(".page.pageLeaderboards .bigtitle .subtext .divider").removeClass( - "hidden", - ); + )?.show(); + qs(".page.pageLeaderboards .bigtitle .subtext .divider")?.show(); if (state.lastWeek) { - $(".page.pageLeaderboards .bigtitle button[data-action='toggleLastWeek']") - .html(` + qs( + ".page.pageLeaderboards .bigtitle button[data-action='toggleLastWeek']", + )?.setHtml(` show this week `); } else { - $(".page.pageLeaderboards .bigtitle button[data-action='toggleLastWeek']") - .html(` + qs( + ".page.pageLeaderboards .bigtitle button[data-action='toggleLastWeek']", + )?.setHtml(` show last week `); @@ -391,43 +389,43 @@ async function requestData(update = false): Promise { } function updateJumpButtons(): void { - const el = $(".page.pageLeaderboards .titleAndButtons .jumpButtons"); - el.find("button").removeClass("active"); + const el = qsa(".page.pageLeaderboards .titleAndButtons .jumpButtons"); + el?.qsa("button")?.removeClass("active"); const totalPages = Math.ceil(state.count / state.pageSize); if (totalPages <= 1) { - el.find("button").addClass("disabled"); + el?.qsa("button")?.disable(); } else { - el.find("button").removeClass("disabled"); + el?.qsa("button")?.enable(); } if (state.page === 0) { - el.find("button[data-action='previousPage']").addClass("disabled"); - el.find("button[data-action='firstPage']").addClass("disabled"); + el?.qs("button[data-action='previousPage']")?.disable(); + el?.qs("button[data-action='firstPage']")?.disable(); } else { - el.find("button[data-action='previousPage']").removeClass("disabled"); - el.find("button[data-action='firstPage']").removeClass("disabled"); + el?.qs("button[data-action='previousPage']")?.enable(); + el?.qs("button[data-action='firstPage']")?.enable(); } if (isAuthenticated()) { - const userButton = el.find("button[data-action='userPage']"); + const userButton = el?.qs("button[data-action='userPage']"); if (!state.userData) { - userButton.addClass("disabled"); + userButton?.disable(); } else { const userPage = Math.floor((state.userData.rank - 1) / state.pageSize); if (state.page === userPage) { - userButton.addClass("disabled"); + userButton?.disable(); } else { - userButton.removeClass("disabled"); + userButton?.enable(); } } } if (state.page >= totalPages - 1) { - el.find("button[data-action='nextPage']").addClass("disabled"); + el?.qs("button[data-action='nextPage']")?.disable(); } else { - el.find("button[data-action='nextPage']").removeClass("disabled"); + el?.qs("button[data-action='nextPage']")?.enable(); } } @@ -554,43 +552,41 @@ function buildWeeklyTableRow( } function fillTable(): void { - const table = $(".page.pageLeaderboards table tbody"); - table.empty(); + const table = qs(".page.pageLeaderboards table tbody"); + table?.empty(); if (state.friendsOnly) { - table.parent().addClass("friendsOnly"); + table?.getParent()?.addClass("friendsOnly"); } else { - table.parent().removeClass("friendsOnly"); + table?.getParent()?.removeClass("friendsOnly"); } - $(".page.pageLeaderboards table thead").addClass("hidden"); + qsa(".page.pageLeaderboards table thead")?.hide(); if (state.type === "allTime" || state.type === "daily") { - $(".page.pageLeaderboards table thead.allTimeAndDaily").removeClass( - "hidden", - ); + qs(".page.pageLeaderboards table thead.allTimeAndDaily")?.show(); } else if (state.type === "weekly") { - $(".page.pageLeaderboards table thead.weekly").removeClass("hidden"); + qs(".page.pageLeaderboards table thead.weekly")?.show(); } if (state.data === null || state.data.length === 0) { - table.append(`No data`); - $(".page.pageLeaderboards table").removeClass("hidden"); + table?.appendHtml(`No data`); + qs(".page.pageLeaderboards table")?.show(); return; } if (state.type === "allTime" || state.type === "daily") { for (const entry of state.data) { const me = getAuthenticatedUser()?.uid === entry.uid; - table.append(buildTableRow(entry, me)); + table?.append(buildTableRow(entry, me)); } } else if (state.type === "weekly") { for (const entry of state.data) { const me = getAuthenticatedUser()?.uid === entry.uid; - table.append(buildWeeklyTableRow(entry, me)); + table?.append(buildWeeklyTableRow(entry, me)); } } - $(".page.pageLeaderboards table").removeClass("hidden"); + qs(".page.pageLeaderboards table")?.show(); } function getLbMemoryDifference(): number | null { @@ -612,14 +608,14 @@ function getLbMemoryDifference(): number | null { function fillUser(): void { if (isAuthenticated() && DB.getSnapshot()?.lbOptOut === true) { - $(".page.pageLeaderboards .bigUser").html( + qs(".page.pageLeaderboards .bigUser")?.setHtml( '
You have opted out of the leaderboards.
', ); return; } if (isAuthenticated() && DB.getSnapshot()?.banned === true) { - $(".page.pageLeaderboards .bigUser").html( + qs(".page.pageLeaderboards .bigUser")?.setHtml( '
Your account is banned
', ); return; @@ -633,7 +629,7 @@ function fillUser(): void { !isDevEnvironment() && (DB.getSnapshot()?.typingStats?.timeTyping ?? 0) < minTimeTyping ) { - $(".page.pageLeaderboards .bigUser").html( + qs(".page.pageLeaderboards .bigUser")?.setHtml( `
Your account must have ${formatDuration( intervalToDuration({ start: 0, end: minTimeTyping * 1000 }), )} typed to be placed on the leaderboard.
`, @@ -651,14 +647,14 @@ function fillUser(): void { })})`; } - $(".page.pageLeaderboards .bigUser").html( + qs(".page.pageLeaderboards .bigUser")?.setHtml( `
${str}
`, ); return; } if (isAuthenticated() && state.userData === null) { - $(".page.pageLeaderboards .bigUser").html( + qs(".page.pageLeaderboards .bigUser")?.setHtml( `
Not qualified
`, ); return; @@ -670,10 +666,8 @@ function fillUser(): void { if (state.type === "allTime" || state.type === "daily") { if (!state.userData || !state.count) { - $(".page.pageLeaderboards .bigUser").addClass("hidden"); - $(".page.pageLeaderboards .tableAndUser > .divider").removeClass( - "hidden", - ); + qs(".page.pageLeaderboards .bigUser")?.hide(); + qs(".page.pageLeaderboards .tableAndUser > .divider")?.show(); return; } @@ -759,10 +753,10 @@ function fillUser(): void { `; - $(".page.pageLeaderboards .bigUser").html(html); + qs(".page.pageLeaderboards .bigUser")?.setHtml(html); } else if (state.type === "weekly") { if (!state.userData || !state.count) { - $(".page.pageLeaderboards .bigUser").addClass("hidden"); + qs(".page.pageLeaderboards .bigUser")?.hide(); return; } @@ -843,43 +837,43 @@ function fillUser(): void { `; - $(".page.pageLeaderboards .bigUser").html(html); + qs(".page.pageLeaderboards .bigUser")?.setHtml(html); } - $(".page.pageLeaderboards .bigUser").removeClass("hidden"); - $(".page.pageLeaderboards .tableAndUser > .divider").addClass("hidden"); + qs(".page.pageLeaderboards .bigUser")?.show(); + qs(".page.pageLeaderboards .tableAndUser > .divider")?.hide(); } function updateContent(): void { - $(".page.pageLeaderboards .loading").addClass("hidden"); - $(".page.pageLeaderboards .updating").addClass("invisible"); - $(".page.pageLeaderboards .error").addClass("hidden"); + qsa(".page.pageLeaderboards .loading").hide(); + qsa(".page.pageLeaderboards .updating").addClass("invisible"); + qs(".page.pageLeaderboards .error")?.hide(); if (state.error !== undefined) { - $(".page.pageLeaderboards .error").removeClass("hidden"); - $(".page.pageLeaderboards .error p").text(state.error); + qs(".page.pageLeaderboards .error")?.show(); + qs(".page.pageLeaderboards .error p")?.setText(state.error); enableButtons(); return; } if (state.updating) { disableButtons(); - $(".page.pageLeaderboards .updating").removeClass("invisible"); + qsa(".page.pageLeaderboards .updating").removeClass("invisible"); return; } else if (state.loading) { disableButtons(); - $(".page.pageLeaderboards .bigUser").addClass("hidden"); - $(".page.pageLeaderboards .titleAndButtons").addClass("hidden"); - $(".page.pageLeaderboards .loading").removeClass("hidden"); - $(".page.pageLeaderboards table").addClass("hidden"); + qs(".page.pageLeaderboards .bigUser")?.hide(); + qsa(".page.pageLeaderboards .titleAndButtons")?.hide(); + qsa(".page.pageLeaderboards .loading").show(); + qs(".page.pageLeaderboards table")?.hide(); return; } else { enableButtons(); } if (isAuthenticated()) { - $(".page.pageLeaderboards .needAuth").removeClass("hidden"); + qsa(".page.pageLeaderboards .needAuth")?.show(); } else { - $(".page.pageLeaderboards .needAuth").addClass("hidden"); + qsa(".page.pageLeaderboards .needAuth")?.hide(); } if (state.data === null) { @@ -887,7 +881,7 @@ function updateContent(): void { return; } - $(".page.pageLeaderboards .titleAndButtons").removeClass("hidden"); + qsa(".page.pageLeaderboards .titleAndButtons")?.show(); updateJumpButtons(); updateTimerVisibility(); fillTable(); @@ -914,58 +908,56 @@ function updateSideButtons(): void { } function updateTypeButtons(): void { - const el = $(".page.pageLeaderboards .buttonGroup.typeButtons"); - el.find("button").removeClass("active"); - el.find(`button[data-type=${state.type}]`).addClass("active"); + const el = qs(".page.pageLeaderboards .buttonGroup.typeButtons"); + el?.qsa("button")?.removeClass("active"); + el?.qs(`button[data-type=${state.type}]`)?.addClass("active"); } function updateFriendsButtons(): void { - const friendsOnlyGroup = $( + const friendsOnlyGroup = qs( ".page.pageLeaderboards .buttonGroup.friendsOnlyButtons", ); if ( isAuthenticated() && (ServerConfiguration.get()?.connections.enabled ?? false) ) { - friendsOnlyGroup.removeClass("hidden"); + friendsOnlyGroup?.show(); } else { - friendsOnlyGroup.addClass("hidden"); + friendsOnlyGroup?.hide(); state.friendsOnly = false; return; } - const everyoneButton = $( + const everyoneButton = qs( ".page.pageLeaderboards .buttonGroup.friendsOnlyButtons .everyone", ); - const friendsOnlyButton = $( + const friendsOnlyButton = qs( ".page.pageLeaderboards .buttonGroup.friendsOnlyButtons .friendsOnly", ); if (state.friendsOnly) { - friendsOnlyButton.addClass("active"); - everyoneButton.removeClass("active"); + friendsOnlyButton?.addClass("active"); + everyoneButton?.removeClass("active"); } else { - friendsOnlyButton.removeClass("active"); - everyoneButton.addClass("active"); + friendsOnlyButton?.removeClass("active"); + everyoneButton?.addClass("active"); } } function updateModeButtons(): void { if (state.type !== "allTime" && state.type !== "daily") { - $(".page.pageLeaderboards .buttonGroup.modeButtons").addClass("hidden"); + qs(".page.pageLeaderboards .buttonGroup.modeButtons")?.hide(); return; } - $(".page.pageLeaderboards .buttonGroup.modeButtons").removeClass("hidden"); + qs(".page.pageLeaderboards .buttonGroup.modeButtons")?.show(); - const el = $(".page.pageLeaderboards .buttonGroup.modeButtons"); - el.find("button").removeClass("active"); - el.find( - `button[data-mode=${state.mode}][data-mode2=${state.mode2}]`, - ).addClass("active"); + const el = qs(".page.pageLeaderboards .buttonGroup.modeButtons"); + el?.qsa("button")?.removeClass("active"); + el?.qs( + `button[data-mode="${state.mode}"][data-mode2="${state.mode2}"]`, + )?.addClass("active"); //hide all mode buttons - $(`.page.pageLeaderboards .buttonGroup.modeButtons button`).addClass( - "hidden", - ); + qsa(`.page.pageLeaderboards .buttonGroup.modeButtons button`)?.hide(); //show all valid ones for (const mode of Object.keys(validLeaderboards[state.type]) as Mode[]) { @@ -973,37 +965,33 @@ function updateModeButtons(): void { // oxlint-disable-next-line no-non-null-assertion validLeaderboards[state.type][mode]!, )) { - $( + qs( `.page.pageLeaderboards .buttonGroup.modeButtons button[data-mode="${mode}"][data-mode2="${mode2}"]`, - ).removeClass("hidden"); + )?.show(); } } } function updateLanguageButtons(): void { if (state.type !== "daily") { - $(".page.pageLeaderboards .buttonGroup.languageButtons").addClass("hidden"); + qs(".page.pageLeaderboards .buttonGroup.languageButtons")?.hide(); return; } - $(".page.pageLeaderboards .buttonGroup.languageButtons").removeClass( - "hidden", - ); + qs(".page.pageLeaderboards .buttonGroup.languageButtons")?.show(); - const el = $(".page.pageLeaderboards .buttonGroup.languageButtons"); - el.find("button").removeClass("active"); - el.find(`button[data-language=${state.language}]`).addClass("active"); + const el = qs(".page.pageLeaderboards .buttonGroup.languageButtons"); + el?.qsa("button")?.removeClass("active"); + el?.qs(`button[data-language=${state.language}]`)?.addClass("active"); //hide all languages - $(`.page.pageLeaderboards .buttonGroup.languageButtons button`).addClass( - "hidden", - ); + qsa(`.page.pageLeaderboards .buttonGroup.languageButtons button`)?.hide(); //show all valid ones for (const lang of validLeaderboards[state.type][state.mode]?.[state.mode2] ?? []) { - $( + qs( `.page.pageLeaderboards .buttonGroup.languageButtons button[data-language="${lang}"]`, - ).removeClass("hidden"); + )?.show(); } } @@ -1013,7 +1001,7 @@ function updateTimerElement(): void { if (state.type === "daily") { const diff = differenceInSeconds(new Date(), endOfDay(new UTCDateMini())); - $(".page.pageLeaderboards .titleAndButtons .timer").text( + qs(".page.pageLeaderboards .titleAndButtons .timer")?.setText( "Next reset in: " + DateTime.secondsToString(diff, true), ); } else if (state.type === "allTime") { @@ -1021,13 +1009,13 @@ function updateTimerElement(): void { const minutesToNextUpdate = 14 - (date.getMinutes() % 15); const secondsToNextUpdate = 60 - date.getSeconds(); const totalSeconds = minutesToNextUpdate * 60 + secondsToNextUpdate; - $(".page.pageLeaderboards .titleAndButtons .timer").text( + qs(".page.pageLeaderboards .titleAndButtons .timer")?.setText( "Next update in: " + DateTime.secondsToString(totalSeconds, true), ); } else if (state.type === "weekly") { const nextWeekTimestamp = endOfWeek(new UTCDateMini(), { weekStartsOn: 1 }); const totalSeconds = differenceInSeconds(new Date(), nextWeekTimestamp); - $(".page.pageLeaderboards .titleAndButtons .timer").text( + qs(".page.pageLeaderboards .titleAndButtons .timer")?.setText( "Next reset in: " + DateTime.secondsToString(totalSeconds, true, true, ":", true, true), ); @@ -1045,11 +1033,11 @@ function updateTimerVisibility(): void { } if (visible) { - $(".page.pageLeaderboards .titleAndButtons .timer").removeClass( + qs(".page.pageLeaderboards .titleAndButtons .timer")?.removeClass( "invisible", ); } else { - $(".page.pageLeaderboards .titleAndButtons .timer").addClass("invisible"); + qs(".page.pageLeaderboards .titleAndButtons .timer")?.addClass("invisible"); } } @@ -1063,7 +1051,7 @@ function startTimer(): void { function stopTimer(): void { clearInterval(updateTimer); updateTimer = undefined; - $(".page.pageLeaderboards .titleAndButtons .timer").text("-"); + qs(".page.pageLeaderboards .titleAndButtons .timer")?.setText("-"); } function convertRuleOption(rule: string): string[] { @@ -1183,7 +1171,7 @@ async function appendModeAndLanguageButtons(): Promise { `, ); }); - $(".modeButtons").html( + qs(".modeButtons")?.setHtml( `
` + mode2Buttons.join("\n"), ); @@ -1203,17 +1191,17 @@ async function appendModeAndLanguageButtons(): Promise { ${lang} `, ); - $(".languageButtons").html( + qs(".languageButtons")?.setHtml( `
` + languageButtons.join("\n"), ); } function disableButtons(): void { - $(".page.pageLeaderboards button").prop("disabled", true); + qsa(".page.pageLeaderboards button")?.disable(); } function enableButtons(): void { - $(".page.pageLeaderboards button").prop("disabled", false); + qsa(".page.pageLeaderboards button")?.enable(); } export function goToPage(pageId: number): void { @@ -1373,9 +1361,9 @@ function updateTimeText( " - \n" + format(localEnd, localDateFormat); - const text = $(".page.pageLeaderboards .bigtitle .subtext > .text"); - text.text(`${dateString}`); - text.attr("aria-label", localDateString); + const text = qs(".page.pageLeaderboards .bigtitle .subtext > .text"); + text?.setText(`${dateString}`); + text?.setAttribute("aria-label", localDateString); } function formatRank(rank: number | undefined): string { @@ -1385,23 +1373,26 @@ function formatRank(rank: number | undefined): string { return rank.toString(); } -$(".page.pageLeaderboards .jumpButtons button").on("click", function () { - const action = $(this).data("action") as Action; +qsa(".page.pageLeaderboards .jumpButtons button")?.on("click", function () { + const action = this.getAttribute("data-action") as Action; if (action !== "goToPage") { handleJumpButton(action); } }); -$(".page.pageLeaderboards .bigtitle button").on("click", function () { - const action = $(this).data("action") as string; +qsa(".page.pageLeaderboards .bigtitle button")?.on("click", function () { + const action = this.getAttribute("data-action") as string; handleYesterdayLastWeekButton(action); }); -$(".page.pageLeaderboards .buttonGroup.typeButtons").on( +qs(".page.pageLeaderboards .buttonGroup.typeButtons")?.onChild( "click", "button", function () { - const type = $(this).data("type") as "allTime" | "weekly" | "daily"; + const type = this.getAttribute("data-type") as + | "allTime" + | "weekly" + | "daily"; if (state.type === type) return; state.type = type; if (state.type === "daily") { @@ -1422,16 +1413,17 @@ $(".page.pageLeaderboards .buttonGroup.typeButtons").on( }, ); -$(".page.pageLeaderboards .buttonGroup.modeButtons").on( +qs(".page.pageLeaderboards .buttonGroup.modeButtons")?.onChild( "click", "button", function () { - const mode = $(this).attr("data-mode") as Mode; - const mode2 = $(this).attr("data-mode2"); + const mode = this.getAttribute("data-mode") as Mode; + const mode2 = this.getAttribute("data-mode2"); if ( mode !== undefined && mode2 !== undefined && + mode2 !== null && (state.type === "allTime" || state.type === "daily") ) { if (state.mode === mode && state.mode2 === mode2) return; @@ -1451,11 +1443,11 @@ $(".page.pageLeaderboards .buttonGroup.modeButtons").on( }, ); -$(".page.pageLeaderboards .buttonGroup.languageButtons").on( +qs(".page.pageLeaderboards .buttonGroup.languageButtons")?.onChild( "click", "button", function () { - const language = $(this).attr("data-language") as Language; + const language = this.getAttribute("data-language") as Language; if (language !== undefined && state.type === "daily") { if (state.language === language) return; @@ -1474,7 +1466,7 @@ $(".page.pageLeaderboards .buttonGroup.languageButtons").on( }, ); -$(".page.pageLeaderboards .buttonGroup.friendsOnlyButtons").on( +qs(".page.pageLeaderboards .buttonGroup.friendsOnlyButtons")?.onChild( "click", "button", () => { @@ -1517,7 +1509,7 @@ export const page = new PageWithUrlParams({ }, }); -$(async () => { +onWindowLoad(async () => { Skeleton.save("pageLeaderboards"); }); diff --git a/frontend/src/ts/pages/loading.ts b/frontend/src/ts/pages/loading.ts index 4e7437fc5..1bc238310 100644 --- a/frontend/src/ts/pages/loading.ts +++ b/frontend/src/ts/pages/loading.ts @@ -1,47 +1,46 @@ import Page from "./page"; import * as Skeleton from "../utils/skeleton"; -import { promiseAnimate } from "../utils/misc"; -import { qsr } from "../utils/dom"; +import { qs, qsr } from "../utils/dom"; -const pageEl = $(".page.pageLoading"); -const barEl = pageEl.find(".bar"); -const errorEl = pageEl.find(".error"); -const spinnerEl = pageEl.find(".spinner"); -const textEl = pageEl.find(".text"); +const pageEl = qs(".page.pageLoading"); +const barEl = pageEl?.qs(".bar"); +const errorEl = pageEl?.qs(".error"); +const spinnerEl = pageEl?.qs(".spinner"); +const textEl = pageEl?.qs(".text"); export async function updateBar( percentage: number, duration: number, ): Promise { - await promiseAnimate(barEl[0]?.querySelector(".fill") as HTMLElement, { + await barEl?.qs(".fill")?.promiseAnimate({ width: percentage + "%", duration, }); } export function updateText(text: string): void { - textEl.removeClass("hidden").html(text); + textEl?.show()?.setHtml(text); } export function showSpinner(): void { - barEl.addClass("hidden"); - errorEl.addClass("hidden"); - spinnerEl.removeClass("hidden"); - textEl.addClass("hidden"); + barEl?.hide(); + errorEl?.hide(); + spinnerEl?.show(); + textEl?.hide(); } export function showError(): void { - barEl.addClass("hidden"); - spinnerEl.addClass("hidden"); - errorEl.removeClass("hidden"); - textEl.addClass("hidden"); + barEl?.hide(); + spinnerEl?.hide(); + errorEl?.show(); + textEl?.hide(); } export async function showBar(): Promise { - barEl.removeClass("hidden"); - errorEl.addClass("hidden"); - spinnerEl.addClass("hidden"); - textEl.addClass("hidden"); + barEl?.show(); + errorEl?.hide(); + spinnerEl?.hide(); + textEl?.hide(); } export const page = new Page({ diff --git a/frontend/src/ts/pages/login.ts b/frontend/src/ts/pages/login.ts index b7cd52294..2e96baf20 100644 --- a/frontend/src/ts/pages/login.ts +++ b/frontend/src/ts/pages/login.ts @@ -11,7 +11,7 @@ import { ValidatedHtmlInputElement } from "../elements/input-validation"; import { isDevEnvironment } from "../utils/misc"; import { z } from "zod"; import { remoteValidation } from "../utils/remote-validation"; -import { qsr } from "../utils/dom"; +import { qs, qsa, qsr, onWindowLoad } from "../utils/dom"; let registerForm: { name?: string; @@ -20,29 +20,29 @@ let registerForm: { } = {}; export function enableSignUpButton(): void { - $(".page.pageLogin .register.side button").prop("disabled", false); + qs(".page.pageLogin .register.side button")?.enable(); } export function disableSignUpButton(): void { - $(".page.pageLogin .register.side button").prop("disabled", true); + qs(".page.pageLogin .register.side button")?.disable(); } export function enableInputs(): void { - $(".pageLogin input").prop("disabled", false); - $(".pageLogin button").prop("disabled", false); + qsa(".pageLogin input")?.enable(); + qsa(".pageLogin button")?.enable(); } export function disableInputs(): void { - $(".pageLogin input").prop("disabled", true); - $(".pageLogin button").prop("disabled", true); + qsa(".pageLogin input")?.disable(); + qsa(".pageLogin button")?.disable(); } export function showPreloader(): void { - $(".pageLogin .preloader").removeClass("hidden"); + qs(".pageLogin .preloader")?.show(); } export function hidePreloader(): void { - $(".pageLogin .preloader").addClass("hidden"); + qs(".pageLogin .preloader")?.hide(); } function isFormComplete(): boolean { @@ -217,13 +217,16 @@ export const page = new Page({ beforeShow: async (): Promise => { Skeleton.append("pageLogin", "main"); registerForm = {}; - $(".pageLogin input").val(""); - $(".pageLogin .register .indicator").addClass("hidden"); + const inputs = qsa(".pageLogin input"); + inputs.forEach((input) => { + input.setValue(""); + }); + qsa(".pageLogin .register .indicator")?.hide(); enableInputs(); disableSignUpButton(); }, }); -$(() => { +onWindowLoad(() => { Skeleton.save("pageLogin"); }); diff --git a/frontend/src/ts/pages/profile-search.ts b/frontend/src/ts/pages/profile-search.ts index 1bf6dbd90..e5ca472ef 100644 --- a/frontend/src/ts/pages/profile-search.ts +++ b/frontend/src/ts/pages/profile-search.ts @@ -5,17 +5,17 @@ import { ValidatedHtmlInputElement } from "../elements/input-validation"; import { UserNameSchema, UserProfile } from "@monkeytype/schemas/users"; import { remoteValidation } from "../utils/remote-validation"; import * as NavigationEvent from "../observables/navigation-event"; -import { qsr } from "../utils/dom"; +import { qs, qsr, onWindowLoad } from "../utils/dom"; let nameInputEl: ValidatedHtmlInputElement | null = null; let lastProfile: UserProfile | null = null; function enableButton(): void { - $('.page.pageProfileSearch button[type="submit"]').prop("disabled", false); + qs('.page.pageProfileSearch button[type="submit"]')?.enable(); } function disableButton(): void { - $('.page.pageProfileSearch button[type="submit"]').prop("disabled", true); + qs('.page.pageProfileSearch button[type="submit"]')?.disable(); } export const page = new Page({ @@ -57,11 +57,11 @@ export const page = new Page({ disableButton(); }, afterShow: async (): Promise => { - $(".page.pageProfileSearch input").trigger("focus"); + qs(".page.pageProfileSearch input")?.dispatch("focus"); }, }); -$(".page.pageProfileSearch form").on("submit", (e) => { +qs(".page.pageProfileSearch form")?.on("submit", (e) => { e.preventDefault(); if (lastProfile === null) return; NavigationEvent.dispatch(`/profile/${lastProfile.name}`, { @@ -69,6 +69,6 @@ $(".page.pageProfileSearch form").on("submit", (e) => { }); }); -$(() => { +onWindowLoad(() => { Skeleton.save("pageProfileSearch"); }); diff --git a/frontend/src/ts/pages/profile.ts b/frontend/src/ts/pages/profile.ts index 098a5a5af..1ff4681e7 100644 --- a/frontend/src/ts/pages/profile.ts +++ b/frontend/src/ts/pages/profile.ts @@ -12,14 +12,14 @@ import * as TestActivity from "../elements/test-activity"; import { TestActivityCalendar } from "../elements/test-activity-calendar"; import { getFirstDayOfTheWeek } from "../utils/date-and-time"; import { addFriend } from "./friends"; -import { qsr } from "../utils/dom"; +import { qs, qsr } from "../utils/dom"; const firstDayOfTheWeek = getFirstDayOfTheWeek(); function reset(): void { - $(".page.pageProfile .error").addClass("hidden"); - $(".page.pageProfile .preloader").removeClass("hidden"); - $(".page.pageProfile .profile").html(` + qs(".page.pageProfile .error")?.hide(); + qs(".page.pageProfile .preloader")?.show(); + qs(".page.pageProfile .profile")?.setHtml(`
@@ -182,7 +182,7 @@ type UpdateOptions = { async function update(options: UpdateOptions): Promise { const getParamExists = checkIfGetParameterExists("isUid"); if (options.data) { - $(".page.pageProfile .preloader").addClass("hidden"); + qs(".page.pageProfile .preloader")?.hide(); await Profile.update("profile", options.data); PbTables.update( // this cast is fine because pb tables can handle the partial data inside user profiles @@ -195,15 +195,15 @@ async function update(options: UpdateOptions): Promise { query: { isUid: getParamExists }, }); - $(".page.pageProfile .preloader").addClass("hidden"); + qs(".page.pageProfile .preloader")?.hide(); if (response.status === 404) { const message = getParamExists ? "User not found" : `User ${options.uidOrName} not found`; - $(".page.pageProfile .preloader").addClass("hidden"); - $(".page.pageProfile .error").removeClass("hidden"); - $(".page.pageProfile .error .message").text(message); + qs(".page.pageProfile .preloader")?.hide(); + qs(".page.pageProfile .error")?.show(); + qs(".page.pageProfile .error .message")?.setText(message); } else if (response.status === 200) { const profile = response.body.data; window.history.replaceState(null, "", `/profile/${profile.name}`); @@ -228,7 +228,7 @@ async function update(options: UpdateOptions): Promise { TestActivity.clear(testActivity); } } else { - // $(".page.pageProfile .failedToLoad").removeClass("hidden"); + // qs(".page.pageProfile .failedToLoad")?.show(); Notifications.add("Failed to load profile: " + response.body.message, -1); return; } @@ -237,27 +237,33 @@ async function update(options: UpdateOptions): Promise { } } -$(".page.pageProfile").on("click", ".profile .userReportButton", () => { - const uid = $(".page.pageProfile .profile").attr("uid") ?? ""; - const name = $(".page.pageProfile .profile").attr("name") ?? ""; +qs(".page.pageProfile")?.onChild("click", ".profile .userReportButton", () => { + const uid = qs(".page.pageProfile .profile")?.getAttribute("uid") ?? ""; + const name = qs(".page.pageProfile .profile")?.getAttribute("name") ?? ""; const lbOptOut = - ($(".page.pageProfile .profile").attr("lbOptOut") ?? "false") === "true"; + (qs(".page.pageProfile .profile")?.getAttribute("lbOptOut") ?? "false") === + "true"; void UserReportModal.show({ uid, name, lbOptOut }); }); -$(".page.pageProfile").on("click", ".profile .addFriendButton", async () => { - const friendName = $(".page.pageProfile .profile").attr("name") ?? ""; +qs(".page.pageProfile")?.onChild( + "click", + ".profile .addFriendButton", + async () => { + const friendName = + qs(".page.pageProfile .profile")?.getAttribute("name") ?? ""; - const result = await addFriend(friendName); + const result = await addFriend(friendName); - if (result === true) { - Notifications.add(`Request sent to ${friendName}`); - $(".profile .details .addFriendButton").addClass("disabled"); - } else { - Notifications.add(result, -1); - } -}); + if (result === true) { + Notifications.add(`Request sent to ${friendName}`); + qs(".profile .details .addFriendButton")?.disable(); + } else { + Notifications.add(result, -1); + } + }, +); export const page = new Page({ id: "profile", @@ -271,18 +277,18 @@ export const page = new Page({ Skeleton.append("pageProfile", "main"); const uidOrName = options?.params?.["uidOrName"] ?? ""; if (uidOrName) { - $(".page.pageProfile .preloader").removeClass("hidden"); - $(".page.pageProfile .search").addClass("hidden"); - $(".page.pageProfile .content").removeClass("hidden"); + qs(".page.pageProfile .preloader")?.show(); + qs(".page.pageProfile .search")?.hide(); + qs(".page.pageProfile .content")?.show(); reset(); void update({ uidOrName, data: options?.data, }); } else { - $(".page.pageProfile .preloader").addClass("hidden"); - $(".page.pageProfile .search").removeClass("hidden"); - $(".page.pageProfile .content").addClass("hidden"); + qs(".page.pageProfile .preloader")?.hide(); + qs(".page.pageProfile .search")?.show(); + qs(".page.pageProfile .content")?.hide(); } }, }); diff --git a/frontend/src/ts/pages/settings.ts b/frontend/src/ts/pages/settings.ts index 885550626..a95237020 100644 --- a/frontend/src/ts/pages/settings.ts +++ b/frontend/src/ts/pages/settings.ts @@ -43,7 +43,7 @@ import * as CustomBackgroundPicker from "../elements/settings/custom-background- import * as CustomFontPicker from "../elements/settings/custom-font-picker"; import * as AuthEvent from "../observables/auth-event"; import * as FpsLimitSection from "../elements/settings/fps-limit-section"; -import { qs, qsr } from "../utils/dom"; +import { qs, qsa, qsr, onWindowLoad } from "../utils/dom"; let settingsInitialized = false; @@ -83,37 +83,25 @@ async function initGroups(): Promise { groups["keymapMode"] = new SettingsGroup("keymapMode", "button", { updateCallback: () => { if (Config.keymapMode === "off") { - $(".pageSettings .section[data-config-name='keymapStyle']").addClass( - "hidden", - ); - $(".pageSettings .section[data-config-name='keymapLayout']").addClass( - "hidden", - ); - $( + qs(".pageSettings .section[data-config-name='keymapStyle']")?.hide(); + qs(".pageSettings .section[data-config-name='keymapLayout']")?.hide(); + qs( ".pageSettings .section[data-config-name='keymapLegendStyle']", - ).addClass("hidden"); - $( + )?.hide(); + qs( ".pageSettings .section[data-config-name='keymapShowTopRow']", - ).addClass("hidden"); - $(".pageSettings .section[data-config-name='keymapSize']").addClass( - "hidden", - ); + )?.hide(); + qs(".pageSettings .section[data-config-name='keymapSize']")?.hide(); } else { - $(".pageSettings .section[data-config-name='keymapStyle']").removeClass( - "hidden", - ); - $( - ".pageSettings .section[data-config-name='keymapLayout']", - ).removeClass("hidden"); - $( + qs(".pageSettings .section[data-config-name='keymapStyle']")?.show(); + qs(".pageSettings .section[data-config-name='keymapLayout']")?.show(); + qs( ".pageSettings .section[data-config-name='keymapLegendStyle']", - ).removeClass("hidden"); - $( + )?.show(); + qs( ".pageSettings .section[data-config-name='keymapShowTopRow']", - ).removeClass("hidden"); - $(".pageSettings .section[data-config-name='keymapSize']").removeClass( - "hidden", - ); + )?.show(); + qs(".pageSettings .section[data-config-name='keymapSize']")?.show(); } }, }); @@ -226,19 +214,21 @@ async function initGroups(): Promise { groups["timerColor"] = new SettingsGroup("timerColor", "button"); groups["fontFamily"] = new SettingsGroup("fontFamily", "button", { updateCallback: () => { - const customButton = $( + const customButton = qs( ".pageSettings .section[data-config-name='fontFamily'] .buttons button[data-config-value='custom']", ); if ( - $( + qsa( ".pageSettings .section[data-config-name='fontFamily'] .buttons .active", ).length === 0 ) { - customButton.addClass("active"); - customButton.text(`Custom (${Config.fontFamily.replace(/_/g, " ")})`); + customButton?.addClass("active"); + customButton?.setText( + `Custom (${Config.fontFamily.replace(/_/g, " ")})`, + ); } else { - customButton.text("Custom"); + customButton?.setText("Custom"); } }, }); @@ -482,11 +472,11 @@ async function fillSettingsPage(): Promise { // export let settingsFillPromise = fillSettingsPage(); export function hideAccountSection(): void { - $(`.pageSettings .section.needsAccount`).addClass("hidden"); + qsa(`.pageSettings .section.needsAccount`)?.hide(); } function showAccountSection(): void { - $(`.pageSettings .section.needsAccount`).removeClass("hidden"); + qsa(`.pageSettings .section.needsAccount`)?.show(); refreshTagsSettingsSection(); refreshPresetsSettingsSection(); } @@ -519,13 +509,13 @@ function setActiveFunboxButton(): void { function refreshTagsSettingsSection(): void { if (isAuthenticated() && DB.getSnapshot()) { - const tagsEl = $(".pageSettings .section.tags .tagsList").empty(); + const tagsEl = qs(".pageSettings .section.tags .tagsList")?.empty(); DB.getSnapshot()?.tags?.forEach((tag) => { // let tagPbString = "No PB found"; // if (tag.pb !== undefined && tag.pb > 0) { // tagPbString = `PB: ${tag.pb}`; // } - tagsEl.append(` + tagsEl?.appendHtml(`
{ - presetsEl.append(` + presetsEl?.appendHtml(`