From 38f3fc251e23f65196cdf230c71e711e3c9c9d71 Mon Sep 17 00:00:00 2001 From: Leonabcd123 <156839416+Leonabcd123@users.noreply.github.com> Date: Sun, 14 Dec 2025 15:29:05 +0200 Subject: [PATCH] refactor: Remove JQuery from account-settings (@Leonabcd123, @fehmer) (#7219) ### Description Also modifies `swapElements` to accept `ElementWithUtils`. --------- Co-authored-by: Christian Fehmer Co-authored-by: Christian Fehmer --- frontend/src/ts/elements/account-button.ts | 10 +- .../src/ts/elements/settings/theme-picker.ts | 17 +-- frontend/src/ts/pages/account-settings.ts | 140 +++++++++--------- frontend/src/ts/utils/misc.ts | 30 ++-- 4 files changed, 90 insertions(+), 107 deletions(-) diff --git a/frontend/src/ts/elements/account-button.ts b/frontend/src/ts/elements/account-button.ts index ed29c0b75..5d394ce75 100644 --- a/frontend/src/ts/elements/account-button.ts +++ b/frontend/src/ts/elements/account-button.ts @@ -65,15 +65,11 @@ export function update(): void { accountButtonAndMenuEl .qs(".menu .items .goToProfile") ?.setAttribute("href", `/profile/${name}`); - void Misc.swapElements( - loginButtonEl.native, - accountButtonAndMenuEl.native, - 250, - ); + void Misc.swapElements(loginButtonEl, accountButtonAndMenuEl, 250); } else { void Misc.swapElements( - accountButtonAndMenuEl.native, - loginButtonEl.native, + accountButtonAndMenuEl, + loginButtonEl, 250, async () => { updateName(""); diff --git a/frontend/src/ts/elements/settings/theme-picker.ts b/frontend/src/ts/elements/settings/theme-picker.ts index 7b667bd93..38a414c98 100644 --- a/frontend/src/ts/elements/settings/theme-picker.ts +++ b/frontend/src/ts/elements/settings/theme-picker.ts @@ -13,6 +13,7 @@ import * as ActivePage from "../../states/active-page"; import { CustomThemeColors, ThemeName } from "@monkeytype/schemas/configs"; import { captureException } from "../../sentry"; import { ThemesListSorted } from "../../constants/themes"; +import { qs } from "../../utils/dom"; function updateActiveButton(): void { let activeThemeName: string = Config.theme; @@ -310,22 +311,14 @@ export function updateActiveTab(): void { if (Config.customTheme) { void Misc.swapElements( - document.querySelector( - '.pageSettings [tabContent="preset"]', - ) as HTMLElement, - document.querySelector( - '.pageSettings [tabContent="custom"]', - ) as HTMLElement, + qs('.pageSettings [tabContent="preset"]'), + qs('.pageSettings [tabContent="custom"]'), 250, ); } else { void Misc.swapElements( - document.querySelector( - '.pageSettings [tabContent="custom"]', - ) as HTMLElement, - document.querySelector( - '.pageSettings [tabContent="preset"]', - ) as HTMLElement, + qs('.pageSettings [tabContent="custom"]'), + qs('.pageSettings [tabContent="preset"]'), 250, ); } diff --git a/frontend/src/ts/pages/account-settings.ts b/frontend/src/ts/pages/account-settings.ts index 4d9ab4502..4e558d1cf 100644 --- a/frontend/src/ts/pages/account-settings.ts +++ b/frontend/src/ts/pages/account-settings.ts @@ -12,9 +12,9 @@ import * as BlockedUserTable from "../elements/account-settings/blocked-user-tab import * as Notifications from "../elements/notifications"; import { z } from "zod"; import * as AuthEvent from "../observables/auth-event"; -import { qsr } from "../utils/dom"; +import { qs, qsr, onDocumentReady } from "../utils/dom"; -const pageElement = $(".page.pageAccountSettings"); +const pageElement = qsr(".page.pageAccountSettings"); const StateSchema = z.object({ tab: z.enum([ @@ -34,9 +34,9 @@ const state: State = { }; function updateAuthenticationSections(): void { - pageElement.find(".section.passwordAuthSettings button").addClass("hidden"); - pageElement.find(".section.googleAuthSettings button").addClass("hidden"); - pageElement.find(".section.githubAuthSettings button").addClass("hidden"); + pageElement.qs(".section.passwordAuthSettings button")?.addClass("hidden"); + pageElement.qs(".section.googleAuthSettings button")?.addClass("hidden"); + pageElement.qs(".section.githubAuthSettings button")?.addClass("hidden"); const user = getAuthenticatedUser(); if (user === null) return; @@ -53,134 +53,128 @@ function updateAuthenticationSections(): void { if (passwordProvider) { pageElement - .find(".section.passwordAuthSettings #emailPasswordAuth") - .removeClass("hidden"); + .qs(".section.passwordAuthSettings #emailPasswordAuth") + ?.removeClass("hidden"); pageElement - .find(".section.passwordAuthSettings #passPasswordAuth") - .removeClass("hidden"); + .qs(".section.passwordAuthSettings #passPasswordAuth") + ?.removeClass("hidden"); if (googleProvider || githubProvider) { pageElement - .find(".section.passwordAuthSettings #removePasswordAuth") - .removeClass("hidden"); + .qs(".section.passwordAuthSettings #removePasswordAuth") + ?.removeClass("hidden"); } } else { pageElement - .find(".section.passwordAuthSettings #addPasswordAuth") - .removeClass("hidden"); + .qs(".section.passwordAuthSettings #addPasswordAuth") + ?.removeClass("hidden"); } if (googleProvider) { pageElement - .find(".section.googleAuthSettings #removeGoogleAuth") - .removeClass("hidden"); + .qs(".section.googleAuthSettings #removeGoogleAuth") + ?.removeClass("hidden"); if (passwordProvider || githubProvider) { pageElement - .find(".section.googleAuthSettings #removeGoogleAuth") - .removeClass("disabled"); + .qs(".section.googleAuthSettings #removeGoogleAuth") + ?.removeClass("disabled"); } else { pageElement - .find(".section.googleAuthSettings #removeGoogleAuth") - .addClass("disabled"); + .qs(".section.googleAuthSettings #removeGoogleAuth") + ?.addClass("disabled"); } } else { pageElement - .find(".section.googleAuthSettings #addGoogleAuth") - .removeClass("hidden"); + .qs(".section.googleAuthSettings #addGoogleAuth") + ?.removeClass("hidden"); } if (githubProvider) { pageElement - .find(".section.githubAuthSettings #removeGithubAuth") - .removeClass("hidden"); + .qs(".section.githubAuthSettings #removeGithubAuth") + ?.removeClass("hidden"); if (passwordProvider || googleProvider) { pageElement - .find(".section.githubAuthSettings #removeGithubAuth") - .removeClass("disabled"); + .qs(".section.githubAuthSettings #removeGithubAuth") + ?.removeClass("disabled"); } else { pageElement - .find(".section.githubAuthSettings #removeGithubAuth") - .addClass("disabled"); + .qs(".section.githubAuthSettings #removeGithubAuth") + ?.addClass("disabled"); } } else { pageElement - .find(".section.githubAuthSettings #addGithubAuth") - .removeClass("hidden"); + .qs(".section.githubAuthSettings #addGithubAuth") + ?.removeClass("hidden"); } } function updateIntegrationSections(): void { //no code and no discord if (!isAuthenticated()) { - pageElement.find(".section.discordIntegration").addClass("hidden"); + pageElement.qs(".section.discordIntegration")?.addClass("hidden"); } else { if (!getSnapshot()) return; - pageElement.find(".section.discordIntegration").removeClass("hidden"); + pageElement.qs(".section.discordIntegration")?.removeClass("hidden"); if (getSnapshot()?.discordId === undefined) { //show button pageElement - .find(".section.discordIntegration .buttons") - .removeClass("hidden"); - pageElement.find(".section.discordIntegration .info").addClass("hidden"); + .qs(".section.discordIntegration .buttons") + ?.removeClass("hidden"); + pageElement.qs(".section.discordIntegration .info")?.addClass("hidden"); } else { pageElement - .find(".section.discordIntegration .buttons") - .addClass("hidden"); + .qs(".section.discordIntegration .buttons") + ?.addClass("hidden"); pageElement - .find(".section.discordIntegration .info") - .removeClass("hidden"); + .qs(".section.discordIntegration .info") + ?.removeClass("hidden"); } } } function updateTabs(): void { void swapElements( - pageElement.find(".tab.active")[0] as HTMLElement, - pageElement.find(`.tab[data-tab="${state.tab}"]`)[0] as HTMLElement, + pageElement.qs(".tab.active"), + pageElement.qs(`.tab[data-tab="${state.tab}"]`), 250, async () => { // }, async () => { - pageElement.find(".tab").removeClass("active"); - pageElement.find(`.tab[data-tab="${state.tab}"]`).addClass("active"); + pageElement.qs(".tab")?.removeClass("active"); + pageElement.qs(`.tab[data-tab="${state.tab}"]`)?.addClass("active"); }, ); - pageElement.find("button").removeClass("active"); - pageElement.find(`button[data-tab="${state.tab}"]`).addClass("active"); + pageElement.qs("button")?.removeClass("active"); + pageElement.qs(`button[data-tab="${state.tab}"]`)?.addClass("active"); } function updateAccountSections(): void { + pageElement.qs(".section.optOutOfLeaderboards .optedOut")?.addClass("hidden"); pageElement - .find(".section.optOutOfLeaderboards .optedOut") - .addClass("hidden"); + .qs(".section.optOutOfLeaderboards .buttons") + ?.removeClass("hidden"); + pageElement.qs(".section.setStreakHourOffset .info")?.addClass("hidden"); pageElement - .find(".section.optOutOfLeaderboards .buttons") - .removeClass("hidden"); - pageElement.find(".section.setStreakHourOffset .info").addClass("hidden"); - pageElement - .find(".section.setStreakHourOffset .buttons") - .removeClass("hidden"); + .qs(".section.setStreakHourOffset .buttons") + ?.removeClass("hidden"); const snapshot = getSnapshot(); if (snapshot?.lbOptOut === true) { pageElement - .find(".section.optOutOfLeaderboards .optedOut") - .removeClass("hidden"); + .qs(".section.optOutOfLeaderboards .optedOut") + ?.removeClass("hidden"); pageElement - .find(".section.optOutOfLeaderboards .buttons") - .addClass("hidden"); + .qs(".section.optOutOfLeaderboards .buttons") + ?.addClass("hidden"); } if (snapshot?.streakHourOffset !== undefined) { - pageElement - .find(".section.setStreakHourOffset .info") - .removeClass("hidden"); + pageElement.qs(".section.setStreakHourOffset .info")?.removeClass("hidden"); const sign = snapshot?.streakHourOffset > 0 ? "+" : ""; pageElement - .find(".section.setStreakHourOffset .info span") - .text(sign + snapshot?.streakHourOffset); - pageElement - .find(".section.setStreakHourOffset .buttons") - .addClass("hidden"); + .qs(".section.setStreakHourOffset .info span") + ?.setText(sign + snapshot?.streakHourOffset); + pageElement.qs(".section.setStreakHourOffset .buttons")?.addClass("hidden"); } } @@ -195,15 +189,17 @@ export function updateUI(): void { page.setUrlParams(state); } -$(".page.pageAccountSettings").on("click", ".tabs button", (event) => { - state.tab = $(event.target).data("tab") as State["tab"]; +qs(".page.pageAccountSettings")?.onChild("click", ".tabs button", (event) => { + state.tab = (event.target as HTMLElement).getAttribute( + "data-tab", + ) as State["tab"]; updateTabs(); page.setUrlParams(state); }); -$( +qs( ".page.pageAccountSettings .section.discordIntegration .getLinkAndGoToOauth", -).on("click", () => { +)?.on("click", () => { Loader.show(); void Ape.users.getDiscordOAuth().then((response) => { if (response.status === 200) { @@ -217,7 +213,7 @@ $( }); }); -$(".page.pageAccountSettings #setStreakHourOffset").on("click", () => { +qs(".page.pageAccountSettings #setStreakHourOffset")?.on("click", () => { StreakHourOffsetModal.show(); }); @@ -230,7 +226,7 @@ AuthEvent.subscribe((event) => { export const page = new PageWithUrlParams({ id: "accountSettings", display: "Account Settings", - element: qsr(".page.pageAccountSettings"), + element: pageElement, path: "/account-settings", urlParamsSchema: UrlParameterSchema, afterHide: async (): Promise => { @@ -241,11 +237,11 @@ export const page = new PageWithUrlParams({ state.tab = options.urlParams.tab; } Skeleton.append("pageAccountSettings", "main"); - pageElement.find(`.tab[data-tab="${state.tab}"]`).addClass("active"); + pageElement.qs(`.tab[data-tab="${state.tab}"]`)?.addClass("active"); updateUI(); }, }); -$(() => { +onDocumentReady(() => { Skeleton.save("pageAccountSettings"); }); diff --git a/frontend/src/ts/utils/misc.ts b/frontend/src/ts/utils/misc.ts index e3e17c6a7..7ddeab6e5 100644 --- a/frontend/src/ts/utils/misc.ts +++ b/frontend/src/ts/utils/misc.ts @@ -7,6 +7,7 @@ import { Result } from "@monkeytype/schemas/results"; import { RankAndCount } from "@monkeytype/schemas/users"; import { roundTo2 } from "@monkeytype/util/numbers"; import { animate, AnimationParams } from "animejs"; +import { ElementWithUtils } from "./dom"; export function whorf(speed: number, wordlen: number): number { return Math.min( @@ -187,8 +188,8 @@ type LastIndex = { export const trailingComposeChars = /[\u02B0-\u02FF`´^¨~]+$|⎄.*$/; export async function swapElements( - el1: HTMLElement, - el2: HTMLElement, + el1: ElementWithUtils | null, + el2: ElementWithUtils | null, totalDuration: number, callback = async function (): Promise { return Promise.resolve(); @@ -203,38 +204,35 @@ export async function swapElements( totalDuration = applyReducedMotion(totalDuration); if ( - (el1.classList.contains("hidden") && !el2.classList.contains("hidden")) || - (!el1.classList.contains("hidden") && el2.classList.contains("hidden")) + (el1.hasClass("hidden") && !el2.hasClass("hidden")) || + (!el1.hasClass("hidden") && el2.hasClass("hidden")) ) { //one of them is hidden and the other is visible - if (el1.classList.contains("hidden")) { + if (el1.hasClass("hidden")) { await middleCallback(); await callback(); return false; } - el1.classList.remove("hidden"); - await promiseAnimate(el1, { + el1.show(); + await el1.promiseAnimate({ opacity: [1, 0], duration: totalDuration / 2, }); - el1.classList.add("hidden"); + el1.hide(); await middleCallback(); - el2.classList.remove("hidden"); - await promiseAnimate(el2, { + el2.show(); + await el2.promiseAnimate({ opacity: [0, 1], duration: totalDuration / 2, }); await callback(); - } else if ( - el1.classList.contains("hidden") && - el2.classList.contains("hidden") - ) { + } else if (el1.hasClass("hidden") && el2.hasClass("hidden")) { //both are hidden, only fade in the second await middleCallback(); - el2.classList.remove("hidden"); - await promiseAnimate(el2, { + el2.show(); + await el2.promiseAnimate({ opacity: [0, 1], duration: totalDuration / 2, });