From b8e25a5d53462e86f77ba0e9f37f6ba1ade5d0ee Mon Sep 17 00:00:00 2001 From: Miodec Date: Thu, 15 Jan 2026 11:59:56 +0100 Subject: [PATCH 1/4] chore: explicitly define formatter fehmer was complaining --- monkeytype.code-workspace | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/monkeytype.code-workspace b/monkeytype.code-workspace index 370b75eb3b5b..f9d0d01b958a 100644 --- a/monkeytype.code-workspace +++ b/monkeytype.code-workspace @@ -45,6 +45,27 @@ "vitest.maximumConfigs": 10, "oxc.typeAware": true, "typescript.format.enable": false, + "[json]": { + "editor.defaultFormatter": "oxc.oxc-vscode", + }, + "[html]": { + "editor.defaultFormatter": "oxc.oxc-vscode", + }, + "[scss]": { + "editor.defaultFormatter": "oxc.oxc-vscode", + }, + "[javascript]": { + "editor.defaultFormatter": "oxc.oxc-vscode", + }, + "[typescript]": { + "editor.defaultFormatter": "oxc.oxc-vscode", + }, + "[typescriptreact]": { + "editor.defaultFormatter": "oxc.oxc-vscode", + }, + "[javascriptreact]": { + "editor.defaultFormatter": "oxc.oxc-vscode", + }, }, "launch": { From ea3a545f714667dac299f3365736aa9c1a42fcf5 Mon Sep 17 00:00:00 2001 From: Leonabcd123 <156839416+Leonabcd123@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:15:32 +0200 Subject: [PATCH 2/4] fix(tags): Update tags after deletion (@Leonabcd123) (#7338) ### Description Fixes the following bug: - Create tag - Go to account page - Add that tag to some result - Delete the tag - Go back to account page - Notice that it still thinks that the test has a tag --------- Co-authored-by: Jack --- frontend/src/ts/db.ts | 9 +++++++++ frontend/src/ts/modals/edit-tag.ts | 3 +++ 2 files changed, 12 insertions(+) diff --git a/frontend/src/ts/db.ts b/frontend/src/ts/db.ts index 99980595f261..f71418abdb90 100644 --- a/frontend/src/ts/db.ts +++ b/frontend/src/ts/db.ts @@ -875,6 +875,15 @@ export async function saveLocalTagPB( return; } +export function deleteLocalTag(tagId: string): void { + getSnapshot()?.results?.forEach((result) => { + const tagIndex = result.tags.indexOf(tagId); + if (tagIndex > -1) { + result.tags.splice(tagIndex, 1); + } + }); +} + export async function updateLocalTagPB( tagId: string, mode: M, diff --git a/frontend/src/ts/modals/edit-tag.ts b/frontend/src/ts/modals/edit-tag.ts index a56f4e0836c0..a9782c7c2b37 100644 --- a/frontend/src/ts/modals/edit-tag.ts +++ b/frontend/src/ts/modals/edit-tag.ts @@ -131,7 +131,10 @@ const actionModals: Record = { snapshot.tags = snapshot.tags.filter((it) => it._id !== tagId); } + DB.deleteLocalTag(tagId); + void Settings.update(); + return { status: 1, message: `Tag removed` }; }, }), From a6cd53caf08b5ef76b68d8b8191823950e213bcc Mon Sep 17 00:00:00 2001 From: Francis Eugene Casibu Date: Thu, 15 Jan 2026 22:58:36 +0800 Subject: [PATCH 3/4] refactor(modals): remove jquery in ts/modals (@fcasibu) (#7292) ### Description Refactor `frontend/ts/modals/*` to replace jQuery (some still TODO). I've also modified `dom.ts` to add `setValue` for `ElementsWithUtils` instance. Still trying to grep the whole flow/codebase, so usage of `qsr` or `qs` is uncertain (Probably use `qsr` always? Can find non-existing elements that way or developer error). There are a lot of atomic commits in this PR, feel free to squash. While doing the refactor, also stumbled upon things that I have questions for (didn't made any changes to them), which I thought can be improved: - `custom-generator.ts:146`: The user is able to write minLength > maxLength. Is this behavior correct? - `custom-test-duration.ts:99`: It seems like `parseInput`, always expects a non nullable string, and we seem to always expect that `#customTestDurationModal input` to always exist (so I used `qsr`), which makes the conditions `val !== null`, `!isNaN(val)` seem to be unnecessary (val is never null or NaN, tbh also isFinite, and val >= 0 is the only valid condition) - `custom-text.ts:169`: These elements does not seem exist `.randomWordsCheckbox`, `.replaceNewlineWithSpace`, `.typographyCheck`, and `.delimiterCheck` (last two is just in a challenge file). Are they good to remove? - `edit-preset.ts:146`: Noticed that we're not updaitng the DOM, since for the most part, in the logic, we mostly use `state.checkboxes`, so no problem happens, but I think it is also a good idea to update the DOM to match current state? Either calling `updateUI()` here or just changing the checked value inline. - `quote-rate.ts:89`: `getQuoteStats` expects a `quote` but we're not really passing anything to it, so this essentially does nothing, since it returns immediately on `!quote` ### Checks - [x] Adding/modifying Typescript code? - [x] I have used `qs`, `qsa` or `qsr` instead of JQuery selectors. - [x] Check if any open issues are related to this PR; if so, be sure to tag them below. - [x] Make sure the PR title follows the Conventional Commits standard. (https://www.conventionalcommits.org for more info) - [x] Make sure to include your GitHub username prefixed with @ inside parentheses at the end of the PR title. Related #7186 #7186 --------- Co-authored-by: Miodec --- frontend/src/ts/modals/custom-generator.ts | 30 ++- .../src/ts/modals/custom-test-duration.ts | 9 +- frontend/src/ts/modals/custom-text.ts | 201 +++++++++--------- frontend/src/ts/modals/dev-options.ts | 4 +- frontend/src/ts/modals/edit-preset.ts | 177 +++++++-------- frontend/src/ts/modals/edit-profile.ts | 18 +- frontend/src/ts/modals/edit-result-tags.ts | 9 +- frontend/src/ts/modals/google-sign-up.ts | 13 +- .../src/ts/modals/last-signed-out-result.ts | 7 +- frontend/src/ts/modals/mobile-test-config.ts | 54 ++--- frontend/src/ts/modals/practise-words.ts | 23 +- frontend/src/ts/modals/quote-approve.ts | 132 ++++++------ frontend/src/ts/modals/quote-rate.ts | 47 ++-- frontend/src/ts/modals/quote-report.ts | 20 +- frontend/src/ts/modals/quote-search.ts | 54 ++--- frontend/src/ts/modals/quote-submit.ts | 44 ++-- frontend/src/ts/modals/save-custom-text.ts | 27 +-- frontend/src/ts/modals/saved-texts.ts | 82 ++++--- frontend/src/ts/modals/share-custom-theme.ts | 7 +- frontend/src/ts/modals/share-test-settings.ts | 20 +- frontend/src/ts/modals/simple-modals.ts | 73 ++++--- frontend/src/ts/modals/user-report.ts | 10 +- frontend/src/ts/modals/word-filter.ts | 88 +++++--- frontend/src/ts/utils/dom.ts | 14 +- 24 files changed, 639 insertions(+), 524 deletions(-) diff --git a/frontend/src/ts/modals/custom-generator.ts b/frontend/src/ts/modals/custom-generator.ts index c89951c42f0f..e4f2c3fa4dc7 100644 --- a/frontend/src/ts/modals/custom-generator.ts +++ b/frontend/src/ts/modals/custom-generator.ts @@ -92,10 +92,14 @@ export async function show(showOptions?: ShowOptions): Promise { } function applyPreset(): void { - const presetName = $("#customGeneratorModal .presetInput").val() as string; + const modalEl = modal.getModal(); + const presetName = modalEl.qs(".presetInput")?.getValue(); + if (presetName !== undefined && presetName !== "" && presets[presetName]) { const preset = presets[presetName]; - $("#customGeneratorModal .characterInput").val(preset.characters.join(" ")); + modalEl + .qsr(".characterInput") + .setValue(preset.characters.join(" ")); } } @@ -106,17 +110,25 @@ function hide(hideOptions?: HideOptions): void { } function generateWords(): string[] { - const characterInput = $( - "#customGeneratorModal .characterInput", - ).val() as string; + const modalEl = modal.getModal(); + const characterInput = modalEl + .qs(".characterInput") + ?.getValue(); + const minLength = - parseInt($("#customGeneratorModal .minLengthInput").val() as string) || 2; + parseInt( + modalEl.qs(".minLengthInput")?.getValue() as string, + ) || 2; const maxLength = - parseInt($("#customGeneratorModal .maxLengthInput").val() as string) || 5; + parseInt( + modalEl.qs(".maxLengthInput")?.getValue() as string, + ) || 5; const wordCount = - parseInt($("#customGeneratorModal .wordCountInput").val() as string) || 100; + parseInt( + modalEl.qs(".wordCountInput")?.getValue() as string, + ) || 100; - if (!characterInput || characterInput.trim() === "") { + if (characterInput === undefined || characterInput.trim() === "") { Notifications.add("Character set cannot be empty", 0); return []; } diff --git a/frontend/src/ts/modals/custom-test-duration.ts b/frontend/src/ts/modals/custom-test-duration.ts index 53ca3aebba52..76688e0b4364 100644 --- a/frontend/src/ts/modals/custom-test-duration.ts +++ b/frontend/src/ts/modals/custom-test-duration.ts @@ -54,7 +54,8 @@ function format(duration: number): string { } function previewDuration(): void { - const input = $("#customTestDurationModal input").val() as string; + const modalEl = modal.getModal(); + const input = modalEl.qsr("input").getValue() as string; const duration = parseInput(input); let formattedDuration = ""; @@ -66,7 +67,7 @@ function previewDuration(): void { formattedDuration = format(duration); } - $("#customTestDurationModal .preview").text(formattedDuration); + modalEl.qs(".preview")?.setText(formattedDuration); } export function show(showOptions?: ShowOptions): void { @@ -87,7 +88,9 @@ function hide(clearChain = false): void { } function apply(): void { - const val = parseInput($("#customTestDurationModal input").val() as string); + const val = parseInput( + modal.getModal().qsr("input").getValue() as string, + ); if (val !== null && !isNaN(val) && val >= 0 && isFinite(val)) { setConfig("time", val); diff --git a/frontend/src/ts/modals/custom-text.ts b/frontend/src/ts/modals/custom-text.ts index d46fb91ceb0a..c3bce7eb4490 100644 --- a/frontend/src/ts/modals/custom-text.ts +++ b/frontend/src/ts/modals/custom-text.ts @@ -1,4 +1,3 @@ -import { ElementWithUtils } from "../utils/dom"; import * as CustomText from "../test/custom-text"; import * as CustomTextState from "../states/custom-text-name"; import * as ManualRestart from "../test/manual-restart-tracker"; @@ -14,8 +13,7 @@ import * as SavedTextsPopup from "./saved-texts"; import * as SaveCustomTextPopup from "./save-custom-text"; import AnimatedModal, { ShowOptions } from "../utils/animated-modal"; import { CustomTextMode } from "@monkeytype/schemas/util"; - -const popup = "#customTextModal .modal"; +import { qs, ElementWithUtils } from "../utils/dom"; type State = { textarea: string; @@ -55,114 +53,127 @@ const state: State = { }; function updateUI(): void { - $(`${popup} .inputs .group[data-id="mode"] button`).removeClass("active"); - $( - `${popup} .inputs .group[data-id="mode"] button[value="${state.customTextMode}"]`, - ).addClass("active"); - - $(`${popup} .inputs .group[data-id="limit"] input.words`).addClass("hidden"); - $(`${popup} .inputs .group[data-id="limit"] input.sections`).addClass( - "hidden", - ); - - $(`${popup} .inputs .group[data-id="limit"] input.words`).val( - state.customTextLimits.word, - ); - $(`${popup} .inputs .group[data-id="limit"] input.time`).val( - state.customTextLimits.time, - ); - $(`${popup} .inputs .group[data-id="limit"] input.sections`).val( - state.customTextLimits.section, - ); + const modalEl = modal.getModal(); + modalEl.qsa(`.inputs .group[data-id="mode"] button`)?.removeClass("active"); + modalEl + .qs( + `.inputs .group[data-id="mode"] button[value="${state.customTextMode}"]`, + ) + ?.addClass("active"); + + modalEl.qs(`.inputs .group[data-id="limit"] input.words`)?.hide(); + modalEl.qs(`.inputs .group[data-id="limit"] input.sections`)?.hide(); + + modalEl + .qs(`.inputs .group[data-id="limit"] input.words`) + ?.setValue(state.customTextLimits.word); + modalEl + .qs(`.inputs .group[data-id="limit"] input.time`) + ?.setValue(state.customTextLimits.time); + modalEl + .qs(`.inputs .group[data-id="limit"] input.sections`) + ?.setValue(state.customTextLimits.section); if (state.customTextLimits.word !== "") { - $(`${popup} .inputs .group[data-id="limit"] input.words`).removeClass( - "hidden", - ); + modalEl.qs(`.inputs .group[data-id="limit"] input.words`)?.show(); } if (state.customTextLimits.section !== "") { - $(`${popup} .inputs .group[data-id="limit"] input.sections`).removeClass( - "hidden", - ); + modalEl.qs(`.inputs .group[data-id="limit"] input.sections`)?.show(); } if (state.customTextPipeDelimiter) { - $(`${popup} .inputs .group[data-id="limit"] input.sections`).removeClass( - "hidden", - ); - $(`${popup} .inputs .group[data-id="limit"] input.words`).addClass( - "hidden", - ); + modalEl.qs(`.inputs .group[data-id="limit"] input.sections`)?.show(); + modalEl.qs(`.inputs .group[data-id="limit"] input.words`)?.hide(); } else { - $(`${popup} .inputs .group[data-id="limit"] input.words`).removeClass( - "hidden", - ); - $(`${popup} .inputs .group[data-id="limit"] input.sections`).addClass( - "hidden", - ); + modalEl.qs(`.inputs .group[data-id="limit"] input.words`)?.show(); + modalEl.qs(`.inputs .group[data-id="limit"] input.sections`)?.hide(); } if (state.customTextMode === "simple") { - $(`${popup} .inputs .group[data-id="limit"]`).addClass("disabled"); - $(`${popup} .inputs .group[data-id="limit"] input`).val(""); - $(`${popup} .inputs .group[data-id="limit"] input`).prop("disabled", true); + modalEl.qs(`.inputs .group[data-id="limit"]`)?.addClass("disabled"); + modalEl + .qsa(`.inputs .group[data-id="limit"] input`) + ?.setValue(""); + modalEl.qsa(`.inputs .group[data-id="limit"] input`)?.disable(); } else { - $(`${popup} .inputs .group[data-id="limit"]`).removeClass("disabled"); - $(`${popup} .inputs .group[data-id="limit"] input`).prop("disabled", false); + modalEl.qs(`.inputs .group[data-id="limit"]`)?.removeClass("disabled"); + modalEl.qsa(`.inputs .group[data-id="limit"] input`)?.enable(); } - $(`${popup} .inputs .group[data-id="fancy"] button`).removeClass("active"); - $( - `${popup} .inputs .group[data-id="fancy"] button[value="${state.removeFancyTypographyEnabled}"]`, - ).addClass("active"); - - $(`${popup} .inputs .group[data-id="control"] button`).removeClass("active"); - $( - `${popup} .inputs .group[data-id="control"] button[value="${state.replaceControlCharactersEnabled}"]`, - ).addClass("active"); - - $(`${popup} .inputs .group[data-id="zeroWidth"] button`).removeClass( - "active", - ); - $( - `${popup} .inputs .group[data-id="zeroWidth"] button[value="${state.removeZeroWidthCharactersEnabled}"]`, - ).addClass("active"); - - $(`${popup} .inputs .group[data-id="delimiter"] button`).removeClass( - "active", - ); - $( - `${popup} .inputs .group[data-id="delimiter"] button[value="${state.customTextPipeDelimiter}"]`, - ).addClass("active"); - - $(`${popup} .inputs .group[data-id="newlines"] button`).removeClass("active"); - $( - `${popup} .inputs .group[data-id="newlines"] button[value="${state.replaceNewlines}"]`, - ).addClass("active"); - - $(`${popup} textarea`).val(state.textarea); + modalEl.qsa(`.inputs .group[data-id="fancy"] button`)?.removeClass("active"); + modalEl + .qs( + `.inputs .group[data-id="fancy"] button[value="${state.removeFancyTypographyEnabled}"]`, + ) + ?.addClass("active"); + + modalEl + .qsa(`.inputs .group[data-id="control"] button`) + ?.removeClass("active"); + modalEl + .qs( + `.inputs .group[data-id="control"] button[value="${state.replaceControlCharactersEnabled}"]`, + ) + ?.addClass("active"); + + modalEl + .qsa(`.inputs .group[data-id="zeroWidth"] button`) + ?.removeClass("active"); + modalEl + .qs( + `.inputs .group[data-id="zeroWidth"] button[value="${state.removeZeroWidthCharactersEnabled}"]`, + ) + ?.addClass("active"); + + modalEl + .qsa(`.inputs .group[data-id="delimiter"] button`) + ?.removeClass("active"); + modalEl + .qs( + `.inputs .group[data-id="delimiter"] button[value="${state.customTextPipeDelimiter}"]`, + ) + ?.addClass("active"); + + modalEl + .qsa(`.inputs .group[data-id="newlines"] button`) + ?.removeClass("active"); + modalEl + .qs( + `.inputs .group[data-id="newlines"] button[value="${state.replaceNewlines}"]`, + ) + ?.addClass("active"); + + modalEl.qs(`textarea`)?.setValue(state.textarea); if (state.longCustomTextWarning) { - $(`${popup} .longCustomTextWarning`).removeClass("hidden"); - $(`${popup} .randomWordsCheckbox input`).prop("checked", false); - $(`${popup} .delimiterCheck input`).prop("checked", false); - $(`${popup} .typographyCheck`).prop("checked", true); - $(`${popup} .replaceNewlineWithSpace input`).prop("checked", false); - $(`${popup} .inputs`).addClass("disabled"); + modalEl.qs(`.longCustomTextWarning`)?.show(); + modalEl + .qs(`.randomWordsCheckbox input`) + ?.setChecked(false); + modalEl.qs(`.delimiterCheck input`)?.setChecked(false); + modalEl.qs(`.typographyCheck`)?.setChecked(true); + modalEl + .qs(`.replaceNewlineWithSpace input`) + ?.setChecked(false); + modalEl.qs(`.inputs`)?.addClass("disabled"); } else { - $(`${popup} .longCustomTextWarning`).addClass("hidden"); - $(`${popup} .inputs`).removeClass("disabled"); + modalEl.qs(`.longCustomTextWarning`)?.hide(); + modalEl.qs(`.inputs`)?.removeClass("disabled"); } if (state.challengeWarning) { - $(`${popup} .challengeWarning`).removeClass("hidden"); - $(`${popup} .randomWordsCheckbox input`).prop("checked", false); - $(`${popup} .delimiterCheck input`).prop("checked", false); - $(`${popup} .typographyCheck`).prop("checked", true); - $(`${popup} .replaceNewlineWithSpace input`).prop("checked", false); - $(`${popup} .inputs`).addClass("disabled"); + modalEl.qs(`.challengeWarning`)?.show(); + modalEl + .qs(`.randomWordsCheckbox input`) + ?.setChecked(false); + modalEl.qs(`.delimiterCheck input`)?.setChecked(false); + modalEl.qs(`.typographyCheck`)?.setChecked(true); + modalEl + .qs(`.replaceNewlineWithSpace input`) + ?.setChecked(false); + modalEl.qs(`.inputs`)?.addClass("disabled"); } else { - $(`${popup} .challengeWarning`).addClass("hidden"); - $(`${popup} .inputs`).removeClass("disabled"); + modalEl.qs(`.challengeWarning`)?.hide(); + modalEl.qs(`.inputs`)?.removeClass("disabled"); } } @@ -219,7 +230,7 @@ async function beforeAnimation( async function afterAnimation(): Promise { if (!state.challengeWarning && !state.longCustomTextWarning) { - $(`${popup} textarea`).trigger("focus"); + modal.getModal().qs(`textarea`)?.focus(); } } @@ -239,7 +250,7 @@ function hide(): void { } function handleFileOpen(): void { - const file = ($(`#fileInput`)[0] as HTMLInputElement).files?.[0]; + const file = qs("#fileInput")?.native.files?.[0]; if (file) { if (file.type !== "text/plain") { Notifications.add("File is not a text file", -1, { @@ -255,7 +266,7 @@ function handleFileOpen(): void { const content = readerEvent.target?.result as string; state.textarea = content; updateUI(); - $(`#fileInput`).val(""); + qs(`#fileInput`)?.setValue(""); }; reader.onerror = (): void => { Notifications.add("Failed to read file", -1, { @@ -522,7 +533,7 @@ async function setup(modalEl: ElementWithUtils): Promise { return; } if (e.code === "Enter" && e.ctrlKey) { - $(`${popup} .button.apply`).trigger("click"); + modal.getModal().qs(`.button.apply`)?.dispatch("click"); } if ( CustomTextState.isCustomTextLong() && diff --git a/frontend/src/ts/modals/dev-options.ts b/frontend/src/ts/modals/dev-options.ts index f0e25f664625..894ce59b3c98 100644 --- a/frontend/src/ts/modals/dev-options.ts +++ b/frontend/src/ts/modals/dev-options.ts @@ -10,7 +10,7 @@ import { toggleUserFakeChartData } from "../test/result"; import { toggleCaretDebug } from "../utils/caret"; import { getInputElement } from "../input/input-element"; import { disableSlowTimerFail } from "../test/test-timer"; -import { ElementWithUtils } from "../utils/dom"; +import { ElementWithUtils, qsr } from "../utils/dom"; let mediaQueryDebugLevel = 0; @@ -102,7 +102,7 @@ const modal = new AnimatedModal({ }); export function appendButton(): void { - $("body").prepend( + qsr("body").prependHtml( `
diff --git a/frontend/src/ts/modals/edit-preset.ts b/frontend/src/ts/modals/edit-preset.ts index 45b616614334..e4ea8a6b66a7 100644 --- a/frontend/src/ts/modals/edit-preset.ts +++ b/frontend/src/ts/modals/edit-preset.ts @@ -21,7 +21,7 @@ import { import { getDefaultConfig } from "../constants/default-config"; import { SnapshotPreset } from "../constants/default-snapshot"; import { ValidatedHtmlInputElement } from "../elements/input-validation"; -import { ElementWithUtils, qsr } from "../utils/dom"; +import { ElementWithUtils } from "../utils/dom"; import { configMetadata } from "../config-metadata"; const state = { @@ -44,42 +44,38 @@ export function show(action: string, id?: string, name?: string): void { void modal.show({ focusFirstInput: true, - beforeAnimation: async () => { - $("#editPresetModal .modal .text").addClass("hidden"); + beforeAnimation: async (modalEl) => { + modalEl.qsr(".text").hide(); addCheckBoxes(); presetNameEl ??= new ValidatedHtmlInputElement( - qsr("#editPresetModal .modal input[type=text]"), + modalEl.qsr("input[type=text]"), { schema: PresetNameSchema, }, ); if (action === "add") { - $("#editPresetModal .modal").attr("data-action", "add"); - $("#editPresetModal .modal .popupTitle").html("Add new preset"); - $("#editPresetModal .modal .submit").html(`add`); + modalEl.setAttribute("data-action", "add"); + modalEl.qsr(".popupTitle").setHtml("Add new preset"); + modalEl.qsr(".submit").setHtml("add"); presetNameEl.setValue(null); - presetNameEl.getParent()?.removeClass("hidden"); - $("#editPresetModal .modal input").removeClass("hidden"); - $( - "#editPresetModal .modal label.changePresetToCurrentCheckbox", - ).addClass("hidden"); - $("#editPresetModal .modal .inputs").removeClass("hidden"); - $("#editPresetModal .modal .presetType").removeClass("hidden"); - $("#editPresetModal .modal .presetNameTitle").removeClass("hidden"); + presetNameEl.getParent()?.show(); + modalEl.qsa("input").show(); + modalEl.qsr("label.changePresetToCurrentCheckbox").hide(); + modalEl.qsr(".inputs").show(); + modalEl.qsr(".presetType").show(); + modalEl.qsr(".presetNameTitle").show(); state.presetType = "full"; } else if (action === "edit" && id !== undefined && name !== undefined) { - $("#editPresetModal .modal").attr("data-action", "edit"); - $("#editPresetModal .modal").attr("data-preset-id", id); - $("#editPresetModal .modal .popupTitle").html("Edit preset"); - $("#editPresetModal .modal .submit").html(`save`); + modalEl.setAttribute("data-action", "edit"); + modalEl.setAttribute("data-preset-id", id); + modalEl.qsr(".popupTitle").setHtml("Edit preset"); + modalEl.qsr(".submit").setHtml(`save`); presetNameEl?.setValue(name); - presetNameEl?.getParent()?.removeClass("hidden"); + presetNameEl?.getParent()?.show(); - $("#editPresetModal .modal input").removeClass("hidden"); - $( - "#editPresetModal .modal label.changePresetToCurrentCheckbox", - ).removeClass("hidden"); - $("#editPresetModal .modal .presetNameTitle").removeClass("hidden"); + modalEl.qsa("input").show(); + modalEl.qsr("label.changePresetToCurrentCheckbox").show(); + modalEl.qsr(".presetNameTitle").show(); state.setPresetToCurrent = false; await updateEditPresetUI(); } else if ( @@ -87,22 +83,20 @@ export function show(action: string, id?: string, name?: string): void { id !== undefined && name !== undefined ) { - $("#editPresetModal .modal").attr("data-action", "remove"); - $("#editPresetModal .modal").attr("data-preset-id", id); - $("#editPresetModal .modal .popupTitle").html("Delete preset"); - $("#editPresetModal .modal .submit").html("delete"); - $("#editPresetModal .modal input").addClass("hidden"); - $( - "#editPresetModal .modal label.changePresetToCurrentCheckbox", - ).addClass("hidden"); - $("#editPresetModal .modal .text").removeClass("hidden"); - $("#editPresetModal .modal .deletePrompt").text( - `Are you sure you want to delete the preset ${name}?`, - ); - $("#editPresetModal .modal .inputs").addClass("hidden"); - $("#editPresetModal .modal .presetType").addClass("hidden"); - $("#editPresetModal .modal .presetNameTitle").addClass("hidden"); - presetNameEl?.getParent()?.addClass("hidden"); + modalEl.setAttribute("data-action", "remove"); + modalEl.setAttribute("data-preset-id", id); + modalEl.qsr(".popupTitle").setHtml("Delete preset"); + modalEl.qsr(".submit").setHtml("delete"); + modalEl.qsa("input").hide(); + modalEl.qsr("label.changePresetToCurrentCheckbox").hide(); + modalEl.qsr(".text").show(); + modalEl + .qsr(".deletePrompt") + .setText(`Are you sure you want to delete the preset ${name}?`); + modalEl.qsr(".inputs").hide(); + modalEl.qsr(".presetType").hide(); + modalEl.qsr(".presetNameTitle").hide(); + presetNameEl?.getParent()?.hide(); } updateUI(); }, @@ -137,43 +131,40 @@ async function initializeEditState(id: string): Promise { } function addCheckboxListeners(): void { + const modalEl = modal.getModal(); ConfigGroupNameSchema.options.forEach((settingGroup: ConfigGroupName) => { - const checkboxInput = $( - `#editPresetModal .modal .checkboxList .checkboxTitlePair[data-id="${settingGroup}"] input`, + const checkboxInput = modalEl.qsr( + `.checkboxList .checkboxTitlePair[data-id="${settingGroup}"] input`, ); - checkboxInput.on("change", (e) => { - state.checkboxes.set( - settingGroup, - checkboxInput.prop("checked") as boolean, - ); + + checkboxInput.on("change", async () => { + state.checkboxes.set(settingGroup, checkboxInput.isChecked() as boolean); }); }); - const presetToCurrentCheckbox = $( - `#editPresetModal .modal .changePresetToCurrentCheckbox input`, + const presetToCurrentCheckbox = modalEl.qsr( + `.changePresetToCurrentCheckbox input`, ); presetToCurrentCheckbox.on("change", async () => { - state.setPresetToCurrent = presetToCurrentCheckbox.prop( - "checked", - ) as boolean; + state.setPresetToCurrent = presetToCurrentCheckbox.isChecked() as boolean; await updateEditPresetUI(); }); } function addCheckBoxes(): void { + const modalEl = modal.getModal(); function camelCaseToSpaced(input: string): string { return input.replace(/([a-z])([A-Z])/g, "$1 $2"); } - const settingGroupListEl = $( - "#editPresetModal .modal .inputs .checkboxList", - ).empty(); + const settingGroupListEl = modalEl.qsr(".inputs .checkboxList").empty(); + ConfigGroupNameSchema.options.forEach((currSettingGroup) => { const currSettingGroupTitle = camelCaseToSpaced(currSettingGroup); const settingGroupCheckbox: string = ``; - settingGroupListEl.append(settingGroupCheckbox); + settingGroupListEl.appendHtml(settingGroupCheckbox); }); for (const key of state.checkboxes.keys()) { state.checkboxes.set(key, true); @@ -182,35 +173,48 @@ function addCheckBoxes(): void { } function updateUI(): void { + const modalEl = modal.getModal(); ConfigGroupNameSchema.options.forEach((settingGroup: ConfigGroupName) => { - $( - `#editPresetModal .modal .checkboxList .checkboxTitlePair[data-id="${settingGroup}"] input`, - ).prop("checked", state.checkboxes.get(settingGroup)); + if (state.checkboxes.get(settingGroup)) { + modalEl + .qsr( + `.checkboxList .checkboxTitlePair[data-id="${settingGroup}"] input`, + ) + .setChecked(true); + } else { + modalEl + .qsr( + `.checkboxList .checkboxTitlePair[data-id="${settingGroup}"] input`, + ) + .setChecked(false); + } }); - $(`#editPresetModal .modal .presetType button`).removeClass("active"); - $( - `#editPresetModal .modal .presetType button[value="${state.presetType}"]`, - ).addClass("active"); - $(`#editPresetModal .modal .partialPresetGroups`).removeClass("hidden"); + + modalEl.qsa(".presetType button").removeClass("active"); + modalEl + .qsr(`.presetType button[value="${state.presetType}"]`) + .addClass("active"); + modalEl.qsr(`.partialPresetGroups`).show(); if (state.presetType === "full") { - $(`#editPresetModal .modal .partialPresetGroups`).addClass("hidden"); + modalEl.qsr(".partialPresetGroups").hide(); } } async function updateEditPresetUI(): Promise { - $("#editPresetModal .modal label.changePresetToCurrentCheckbox input").prop( - "checked", - state.setPresetToCurrent, - ); + const modalEl = modal.getModal(); if (state.setPresetToCurrent) { - const presetId = $("#editPresetModal .modal").attr( - "data-preset-id", - ) as string; + modalEl + .qsr("label.changePresetToCurrentCheckbox input") + .setChecked(true); + const presetId = modalEl.getAttribute("data-preset-id") as string; await initializeEditState(presetId); - $("#editPresetModal .modal .inputs").removeClass("hidden"); - $("#editPresetModal .modal .presetType").removeClass("hidden"); + modalEl.qsr(".inputs").show(); + modalEl.qsr(".presetType").show(); } else { - $("#editPresetModal .modal .inputs").addClass("hidden"); - $("#editPresetModal .modal .presetType").addClass("hidden"); + modalEl + .qsr("label.changePresetToCurrentCheckbox input") + .setChecked(false); + modalEl.qsr(".inputs").hide(); + modalEl.qsr(".presetType").hide(); } } @@ -219,20 +223,21 @@ function hide(): void { } async function apply(): Promise { - const action = $("#editPresetModal .modal").attr("data-action"); - const propPresetName = $("#editPresetModal .modal input").val() as string; + const modalEl = modal.getModal(); + const action = modalEl.getAttribute("data-action"); + const propPresetName = modalEl + .qsr(".group input[title='presets']") + .getValue() as string; const presetName = propPresetName.replaceAll(" ", "_"); - const presetId = $("#editPresetModal .modal").attr( - "data-preset-id", - ) as string; + const presetId = modalEl.getAttribute("data-preset-id") as string; - const updateConfig = $("#editPresetModal .modal label input").prop( - "checked", - ) as boolean; + const updateConfig = modalEl + .qsr("label.changePresetToCurrentCheckbox input") + .isChecked(); const snapshotPresets = DB.getSnapshot()?.presets ?? []; - if (action === undefined) { + if (action === null || action === "") { return; } diff --git a/frontend/src/ts/modals/edit-profile.ts b/frontend/src/ts/modals/edit-profile.ts index c51438a0f740..d6e1ed0a3f82 100644 --- a/frontend/src/ts/modals/edit-profile.ts +++ b/frontend/src/ts/modals/edit-profile.ts @@ -101,13 +101,17 @@ function hydrateInputs(): void { `, ); - $(".badgeSelectionItem").on("click", ({ currentTarget }) => { - const selectionId = $(currentTarget).attr("selection-id") as string; - currentSelectedBadgeId = parseInt(selectionId, 10); - - badgeIdsSelect?.qsa(".badgeSelectionItem")?.removeClass("selected"); - $(currentTarget).addClass("selected"); - }); + badgeIdsSelect + ?.qsa(".badgeSelectionItem") + ?.on("click", ({ currentTarget }) => { + const selectionId = (currentTarget as HTMLElement).getAttribute( + "selection-id", + ) as string; + currentSelectedBadgeId = parseInt(selectionId, 10); + + badgeIdsSelect?.qsa(".badgeSelectionItem")?.removeClass("selected"); + (currentTarget as HTMLElement).classList.add("selected"); + }); indicators.forEach((it) => it.hide()); } diff --git a/frontend/src/ts/modals/edit-result-tags.ts b/frontend/src/ts/modals/edit-result-tags.ts index 3698c141fb53..1e375e08580c 100644 --- a/frontend/src/ts/modals/edit-result-tags.ts +++ b/frontend/src/ts/modals/edit-result-tags.ts @@ -90,12 +90,13 @@ function appendButtons(): void { } function updateActiveButtons(): void { - for (const button of $("#editResultTagsModal .modal .buttons button")) { - const tagid: string = $(button).attr("data-tag-id") ?? ""; + const buttons = modal.getModal().qsa(".buttons button"); + for (const button of buttons) { + const tagid: string = button.getAttribute("data-tag-id") ?? ""; if (state.tags.includes(tagid)) { - $(button).addClass("active"); + button.addClass("active"); } else { - $(button).removeClass("active"); + button.removeClass("active"); } } } diff --git a/frontend/src/ts/modals/google-sign-up.ts b/frontend/src/ts/modals/google-sign-up.ts index dfb9e53e2fed..9b96bb06c48e 100644 --- a/frontend/src/ts/modals/google-sign-up.ts +++ b/frontend/src/ts/modals/google-sign-up.ts @@ -25,7 +25,7 @@ function show(credential: UserCredential): void { void modal.show({ mode: "dialog", focusFirstInput: true, - beforeAnimation: async () => { + beforeAnimation: async (modalEl) => { signedInUser = credential; if (!CaptchaController.isCaptchaAvailable()) { @@ -37,7 +37,7 @@ function show(credential: UserCredential): void { } CaptchaController.reset("googleSignUpModal"); CaptchaController.render( - $("#googleSignUpModal .captcha")[0] as HTMLElement, + modalEl.qsr(".captcha").native, "googleSignUpModal", ); enableInput(); @@ -93,7 +93,10 @@ async function apply(): Promise { disableButton(); Loader.show(); - const name = $("#googleSignUpModal input").val() as string; + const name = modal + .getModal() + .qsr("input") + .getValue() as string; try { if (name.length === 0) throw new Error("Name cannot be empty"); const response = await Ape.users.create({ body: { name, captcha } }); @@ -135,11 +138,11 @@ async function apply(): Promise { } function enableButton(): void { - $("#googleSignUpModal button").prop("disabled", false); + modal.getModal().qsr("button").enable(); } function disableButton(): void { - $("#googleSignUpModal button").prop("disabled", true); + modal.getModal().qsr("button").disable(); } const nameInputEl = qsr("#googleSignUpModal input"); diff --git a/frontend/src/ts/modals/last-signed-out-result.ts b/frontend/src/ts/modals/last-signed-out-result.ts index 11bb845145c7..194f184a193b 100644 --- a/frontend/src/ts/modals/last-signed-out-result.ts +++ b/frontend/src/ts/modals/last-signed-out-result.ts @@ -85,10 +85,13 @@ function fillGroup( text: string | number, html = false, ): void { + const el = modal.getModal().qs(`.group.${groupClass} .val`); + if (!el) return; + if (html) { - $(modal.getModal()).find(`.group.${groupClass} .val`).html(`${text}`); + el.setHtml(`${text}`); } else { - $(modal.getModal()).find(`.group.${groupClass} .val`).text(text); + el.setText(`${text}`); } } diff --git a/frontend/src/ts/modals/mobile-test-config.ts b/frontend/src/ts/modals/mobile-test-config.ts index 59f393580650..88d88c3cc99d 100644 --- a/frontend/src/ts/modals/mobile-test-config.ts +++ b/frontend/src/ts/modals/mobile-test-config.ts @@ -1,4 +1,3 @@ -import { ElementWithUtils } from "../utils/dom"; import * as TestLogic from "../test/test-logic"; import Config, { setConfig, setQuoteLengthAll } from "../config"; import * as ManualRestart from "../test/manual-restart-tracker"; @@ -11,58 +10,59 @@ import { QuoteLength, QuoteLengthConfig } from "@monkeytype/schemas/configs"; import { Mode } from "@monkeytype/schemas/shared"; import { areUnsortedArraysEqual } from "../utils/arrays"; import * as ShareTestSettingsPopup from "./share-test-settings"; +import { ElementWithUtils } from "../utils/dom"; function update(): void { - const el = $("#mobileTestConfigModal"); - el.find("button").removeClass("active"); + const el = modal.getModal(); + el.qsa("button").removeClass("active"); - el.find(`.modeGroup button[data-mode='${Config.mode}']`).addClass("active"); - el.find(".timeGroup").addClass("hidden"); - el.find(".wordsGroup").addClass("hidden"); - el.find(".quoteGroup").addClass("hidden"); - el.find(".customGroup").addClass("hidden"); - el.find(`.${Config.mode}Group`).removeClass("hidden"); + el.qs(`.modeGroup button[data-mode='${Config.mode}']`)?.addClass("active"); + el.qs(".timeGroup")?.hide(); + el.qs(".wordsGroup")?.hide(); + el.qs(".quoteGroup")?.hide(); + el.qs(".customGroup")?.hide(); + el.qs(`.${Config.mode}Group`)?.show(); if (Config.punctuation) { - el.find(".punctuation").addClass("active"); + el.qs(".punctuation")?.addClass("active"); } else { - el.find(".punctuation").removeClass("active"); + el.qs(".punctuation")?.removeClass("active"); } if (Config.numbers) { - el.find(".numbers").addClass("active"); + el.qs(".numbers")?.addClass("active"); } else { - el.find(".numbers").removeClass("active"); + el.qs(".numbers")?.removeClass("active"); } if (Config.mode === "time") { - el.find(`.timeGroup button[data-time='${Config.time}']`).addClass("active"); - el.find(".punctuation").removeClass("disabled"); - el.find(".numbers").removeClass("disabled"); + el.qs(`.timeGroup button[data-time='${Config.time}']`)?.addClass("active"); + el.qs(".punctuation")?.enable(); + el.qs(".numbers")?.enable(); } else if (Config.mode === "words") { - el.find(`.wordsGroup button[data-words='${Config.words}']`).addClass( + el.qs(`.wordsGroup button[data-words='${Config.words}']`)?.addClass( "active", ); - el.find(".punctuation").removeClass("disabled"); - el.find(".numbers").removeClass("disabled"); + el.qs(".punctuation")?.enable(); + el.qs(".numbers")?.enable(); } else if (Config.mode === "quote") { if (areUnsortedArraysEqual(Config.quoteLength, [0, 1, 2, 3])) { - el.find(`.quoteGroup button[data-quoteLength='all']`).addClass("active"); + el.qs(`.quoteGroup button[data-quoteLength='all']`)?.addClass("active"); } else { for (const ql of Config.quoteLength) { - el.find(`.quoteGroup button[data-quoteLength='${ql}']`).addClass( + el.qs(`.quoteGroup button[data-quoteLength='${ql}']`)?.addClass( "active", ); } } - el.find(".punctuation").addClass("disabled"); - el.find(".numbers").addClass("disabled"); + el.qs(".punctuation")?.disable(); + el.qs(".numbers")?.disable(); } else if (Config.mode === "zen") { - el.find(".punctuation").addClass("disabled"); - el.find(".numbers").addClass("disabled"); + el.qs(".punctuation")?.disable(); + el.qs(".numbers")?.disable(); } else if (Config.mode === "custom") { - el.find(".punctuation").removeClass("disabled"); - el.find(".numbers").removeClass("disabled"); + el.qs(".punctuation")?.enable(); + el.qs(".numbers")?.enable(); } } diff --git a/frontend/src/ts/modals/practise-words.ts b/frontend/src/ts/modals/practise-words.ts index e7c0b08c4d56..bf05f050fa96 100644 --- a/frontend/src/ts/modals/practise-words.ts +++ b/frontend/src/ts/modals/practise-words.ts @@ -13,23 +13,22 @@ const state: State = { slow: false, }; -const practiseModal = "#practiseWordsModal .modal"; - function updateUI(): void { - $(`${practiseModal} .group[data-id="missed"] button`).removeClass("active"); - $( - `${practiseModal} .group[data-id="missed"] button[value="${state.missed}"]`, - ).addClass("active"); + const modalEl = modal.getModal(); + modalEl.qsa(`.group[data-id="missed"] button`).removeClass("active"); + modalEl + .qs(`.group[data-id="missed"] button[value="${state.missed}"]`) + ?.addClass("active"); - $(`${practiseModal} .group[data-id="slow"] button`).removeClass("active"); - $( - `${practiseModal} .group[data-id="slow"] button[value="${state.slow}"]`, - ).addClass("active"); + modalEl.qsa(`.group[data-id="slow"] button`).removeClass("active"); + modalEl + .qs(`.group[data-id="slow"] button[value="${state.slow}"]`) + ?.addClass("active"); if (state.missed === "off" && !state.slow) { - $(`${practiseModal} .start`).prop("disabled", true); + modalEl.qs(`.start`)?.disable(); } else { - $(`${practiseModal} .start`).prop("disabled", false); + modalEl.qs(`.start`)?.enable(); } } diff --git a/frontend/src/ts/modals/quote-approve.ts b/frontend/src/ts/modals/quote-approve.ts index 6953e1b53f46..a1b990e54160 100644 --- a/frontend/src/ts/modals/quote-approve.ts +++ b/frontend/src/ts/modals/quote-approve.ts @@ -1,4 +1,3 @@ -import { ElementWithUtils } from "../utils/dom"; import Ape from "../ape"; import * as Loader from "../elements/loader"; import * as Notifications from "../elements/notifications"; @@ -6,13 +5,16 @@ import { format } from "date-fns/format"; import AnimatedModal, { ShowOptions } from "../utils/animated-modal"; import { Quote } from "@monkeytype/schemas/quotes"; import { escapeHTML } from "../utils/misc"; +import { createElementWithUtils, ElementWithUtils } from "../utils/dom"; let quotes: Quote[] = []; function updateList(): void { - $("#quoteApproveModal .quotes").empty(); + const modalEl = modal.getModal(); + modalEl.qsr(".quotes").empty(); quotes.forEach((quote, index) => { - const quoteEl = $(` + const quoteEl = createElementWithUtils("div"); + quoteEl.setHtml(`
{ - $(`#quoteApproveModal .quote[data-id=${index}] .undo`).prop( - "disabled", - false, - ); - $(`#quoteApproveModal .quote[data-id=${index}] .approve`).addClass( - "hidden", - ); - $(`#quoteApproveModal .quote[data-id=${index}] .edit`).removeClass( - "hidden", - ); + + modalEl.qsr(".quotes").append(quoteEl); + quoteEl.qsr(".source").on("input", () => { + modalEl.qsr(`.quote[data-id="${index}"] .undo`).enable(); + modalEl.qsr(`.quote[data-id="${index}"] .approve`).hide(); + modalEl.qsr(`.quote[data-id="${index}"] .edit`).show(); }); - quoteEl.find(".text").on("input", () => { - $(`#quoteApproveModal .quote[data-id=${index}] .undo`).prop( - "disabled", - false, - ); - $(`#quoteApproveModal .quote[data-id=${index}] .approve`).addClass( - "hidden", - ); - $(`#quoteApproveModal .quote[data-id=${index}] .edit`).removeClass( - "hidden", - ); + quoteEl.qsr(".text").on("input", () => { + modalEl.qsr(`.quote[data-id="${index}"] .undo`).enable(); + modalEl.qsr(`.quote[data-id="${index}"] .approve`).hide(); + modalEl.qsr(`.quote[data-id="${index}"] .edit`).show(); updateQuoteLength(index); }); - quoteEl.find(".undo").on("click", () => { + quoteEl.qsr(".undo").on("click", () => { undoQuote(index); }); - quoteEl.find(".approve").on("click", () => { + quoteEl.qsr(".approve").on("click", () => { void approveQuote(index, quote._id); }); - quoteEl.find(".refuse").on("click", () => { + quoteEl.qsr(".refuse").on("click", () => { void refuseQuote(index, quote._id); }); - quoteEl.find(".edit").on("click", () => { + quoteEl.qsr(".edit").on("click", () => { void editQuote(index, quote._id); }); }); } function updateQuoteLength(index: number): void { + const modalEl = modal.getModal(); const len = ( - $(`#quoteApproveModal .quote[data-id=${index}] .text`).val() as string + modalEl + .qsr(`.quote[data-id="${index}"] .text`) + .getValue() as string )?.length; - $(`#quoteApproveModal .quote[data-id=${index}] .length`).html( - `${len}`, - ); + modalEl + .qsr(`.quote[data-id="${index}"] .length`) + .setHtml(`${len}`); if (len < 60) { - $(`#quoteApproveModal .quote[data-id=${index}] .length`).addClass("red"); + modalEl.qsr(`.quote[data-id="${index}"] .length`).addClass("red"); } else { - $(`#quoteApproveModal .quote[data-id=${index}] .length`).removeClass("red"); + modalEl.qsr(`.quote[data-id="${index}"] .length`).removeClass("red"); } } @@ -124,33 +116,32 @@ export async function show(showOptions?: ShowOptions): Promise { // } function resetButtons(index: number): void { - const quote = $(`#quoteApproveModal .quotes .quote[data-id=${index}]`); - quote.find("button").prop("disabled", false); - if (quote.find(".edit").hasClass("hidden")) { - quote.find(".undo").prop("disabled", true); + const quote = modal.getModal().qsr(`.quotes .quote[data-id="${index}"]`); + quote.qsa("button").enable(); + if (quote.qsr(".edit").hasClass("hidden")) { + quote.qsr(".undo").disable(); } } function undoQuote(index: number): void { - $(`#quoteApproveModal .quote[data-id=${index}] .text`).val( - quotes[index]?.text ?? "", - ); - $(`#quoteApproveModal .quote[data-id=${index}] .source`).val( - quotes[index]?.source ?? "", - ); - $(`#quoteApproveModal .quote[data-id=${index}] .undo`).prop("disabled", true); - $(`#quoteApproveModal .quote[data-id=${index}] .approve`).removeClass( - "hidden", - ); - $(`#quoteApproveModal .quote[data-id=${index}] .edit`).addClass("hidden"); + const modalEl = modal.getModal(); + modalEl + .qsr(`.quote[data-id="${index}"] .text`) + .setValue(quotes[index]?.text ?? ""); + modalEl + .qsr(`.quote[data-id="${index}"] .source`) + .setValue(quotes[index]?.source ?? ""); + modalEl.qsr(`.quote[data-id="${index}"] .undo`).disable(); + modalEl.qsr(`.quote[data-id="${index}"] .approve`).show(); + modalEl.qsr(`.quote[data-id="${index}"] .edit`).hide(); updateQuoteLength(index); } async function approveQuote(index: number, dbid: string): Promise { if (!confirm("Are you sure?")) return; - const quote = $(`#quoteApproveModal .quotes .quote[data-id=${index}]`); - quote.find("button").prop("disabled", true); - quote.find("textarea, input").prop("disabled", true); + const quote = modal.getModal().qsr(`.quotes .quote[data-id="${index}"]`); + quote.qsa("button").disable(); + quote.qsa("textarea, input").disable(); Loader.show(); const response = await Ape.quotes.approveSubmission({ @@ -160,7 +151,7 @@ async function approveQuote(index: number, dbid: string): Promise { if (response.status !== 200) { resetButtons(index); - quote.find("textarea, input").prop("disabled", false); + quote.qsa("textarea, input").enable(); Notifications.add("Failed to approve quote", -1, { response }); return; } @@ -172,9 +163,9 @@ async function approveQuote(index: number, dbid: string): Promise { async function refuseQuote(index: number, dbid: string): Promise { if (!confirm("Are you sure?")) return; - const quote = $(`#quoteApproveModal .quotes .quote[data-id=${index}]`); - quote.find("button").prop("disabled", true); - quote.find("textarea, input").prop("disabled", true); + const quote = modal.getModal().qsr(`.quotes .quote[data-id="${index}"]`); + quote.qsa("button").disable(); + quote.qsa("textarea, input").disable(); Loader.show(); const response = await Ape.quotes.rejectSubmission({ @@ -184,7 +175,7 @@ async function refuseQuote(index: number, dbid: string): Promise { if (response.status !== 200) { resetButtons(index); - quote.find("textarea, input").prop("disabled", false); + quote.qsa("textarea, input").enable(); Notifications.add("Failed to refuse quote", -1, { response }); return; } @@ -196,15 +187,16 @@ async function refuseQuote(index: number, dbid: string): Promise { async function editQuote(index: number, dbid: string): Promise { if (!confirm("Are you sure?")) return; - const editText = $( - `#quoteApproveModal .quote[data-id=${index}] .text`, - ).val() as string; - const editSource = $( - `#quoteApproveModal .quote[data-id=${index}] .source`, - ).val() as string; - const quote = $(`#quoteApproveModal .quotes .quote[data-id=${index}]`); - quote.find("button").prop("disabled", true); - quote.find("textarea, input").prop("disabled", true); + const modalEl = modal.getModal(); + const editText = modalEl + .qsr(`.quote[data-id="${index}"] .text`) + .getValue() as string; + const editSource = modalEl + .qsr(`.quote[data-id="${index}"] .source`) + .getValue() as string; + const quote = modalEl.qsr(`.quotes .quote[data-id="${index}"]`); + quote.qsa("button").disable(); + quote.qsa("textarea, input").disable(); Loader.show(); const response = await Ape.quotes.approveSubmission({ @@ -218,7 +210,7 @@ async function editQuote(index: number, dbid: string): Promise { if (response.status !== 200) { resetButtons(index); - quote.find("textarea, input").prop("disabled", false); + quote.qsa("textarea, input").enable(); Notifications.add("Failed to approve quote", -1, { response }); return; } @@ -233,7 +225,7 @@ async function editQuote(index: number, dbid: string): Promise { async function setup(modalEl: ElementWithUtils): Promise { modalEl.qs("button.refreshList")?.on("click", () => { - $("#quoteApproveModal .quotes").empty(); + modalEl.qsr(".quotes").empty(); void getQuotes(); }); } diff --git a/frontend/src/ts/modals/quote-rate.ts b/frontend/src/ts/modals/quote-rate.ts index fd05f192f1aa..f3201e388d67 100644 --- a/frontend/src/ts/modals/quote-rate.ts +++ b/frontend/src/ts/modals/quote-rate.ts @@ -6,7 +6,7 @@ import * as Loader from "../elements/loader"; import * as Notifications from "../elements/notifications"; import AnimatedModal, { ShowOptions } from "../utils/animated-modal"; import { isSafeNumber } from "@monkeytype/util/numbers"; -import { ElementWithUtils } from "../utils/dom"; +import { qs, ElementWithUtils } from "../utils/dom"; let rating = 0; @@ -26,12 +26,13 @@ export function clearQuoteStats(): void { } function reset(): void { - $(`#quoteRateModal .quote .text`).text("-"); - $(`#quoteRateModal .quote .source .val`).text("-"); - $(`#quoteRateModal .quote .id .val`).text("-"); - $(`#quoteRateModal .quote .length .val`).text("-"); - $("#quoteRateModal .ratingCount .val").text("-"); - $("#quoteRateModal .ratingAverage .val").text("-"); + const modalEl = modal.getModal(); + modalEl.qsr(`.quote .text`).setText("-"); + modalEl.qsr(`.quote .source .val`).setText("-"); + modalEl.qsr(`.quote .id .val`).setText("-"); + modalEl.qsr(`.quote .length .val`).setText("-"); + modalEl.qsr(".ratingCount .val").setText("-"); + modalEl.qsr(".ratingAverage .val").setText("-"); } function getRatingAverage(quoteStats: QuoteStats): number { @@ -78,19 +79,24 @@ export async function getQuoteStats( } function refreshStars(force?: number): void { + const modalEl = modal.getModal(); const limit = force ?? rating; - $(`#quoteRateModal .star`).removeClass("active"); + modalEl.qsa(`.star`).removeClass("active"); for (let i = 1; i <= limit; i++) { - $(`#quoteRateModal .star[data-rating=${i}]`).addClass("active"); + modalEl.qsr(`.star[data-rating="${i}"]`).addClass("active"); } } async function updateRatingStats(): Promise { if (!quoteStats) await getQuoteStats(); - $("#quoteRateModal .ratingCount .val").text(quoteStats?.ratings ?? "0"); - $("#quoteRateModal .ratingAverage .val").text( - quoteStats?.average?.toFixed(1) ?? "-", - ); + const modalEl = modal.getModal(); + const ratings = quoteStats?.ratings; + modalEl + .qsr(".ratingCount .val") + .setText(ratings === undefined ? "0" : ratings.toString()); + modalEl + .qsr(".ratingAverage .val") + .setText(quoteStats?.average?.toFixed(1) ?? "-"); } function updateData(): void { @@ -105,10 +111,11 @@ function updateData(): void { } else if (currentQuote.group === 3) { lengthDesc = "thicc"; } - $(`#quoteRateModal .quote .text`).text(currentQuote.text); - $(`#quoteRateModal .quote .source .val`).text(currentQuote.source); - $(`#quoteRateModal .quote .id .val`).text(currentQuote.id); - $(`#quoteRateModal .quote .length .val`).text(lengthDesc as string); + const modalEl = modal.getModal(); + modalEl.qsr(`.quote .text`).setText(currentQuote.text); + modalEl.qsr(`.quote .source .val`).setText(currentQuote.source); + modalEl.qsr(`.quote .id .val`).setText(`${currentQuote.id}`); + modalEl.qsr(`.quote .length .val`).setText(lengthDesc as string); void updateRatingStats(); } @@ -202,11 +209,11 @@ async function submit(): Promise { DB.setSnapshot(snapshot); quoteStats.average = getRatingAverage(quoteStats); - $(".pageTest #result #rateQuoteButton .rating").text( + qs(".pageTest #result #rateQuoteButton .rating")?.setText( quoteStats.average?.toFixed(1), ); - $(".pageTest #result #rateQuoteButton .icon").removeClass("far"); - $(".pageTest #result #rateQuoteButton .icon").addClass("fas"); + qs(".pageTest #result #rateQuoteButton .icon")?.removeClass("far"); + qs(".pageTest #result #rateQuoteButton .icon")?.addClass("fas"); } async function setup(modalEl: ElementWithUtils): Promise { diff --git a/frontend/src/ts/modals/quote-report.ts b/frontend/src/ts/modals/quote-report.ts index 988fbb5273c5..871fbf070501 100644 --- a/frontend/src/ts/modals/quote-report.ts +++ b/frontend/src/ts/modals/quote-report.ts @@ -36,9 +36,9 @@ export async function show( void modal.show({ mode: "dialog", ...showOptions, - beforeAnimation: async () => { + beforeAnimation: async (modalEl) => { CaptchaController.render( - document.querySelector("#quoteReportModal .g-recaptcha") as HTMLElement, + modalEl.qsr(".g-recaptcha").native, "quoteReportModal", ); @@ -50,9 +50,9 @@ export async function show( return quote.id === quoteId; }); - $("#quoteReportModal .quote").text(state.quoteToReport?.text as string); - $("#quoteReportModal .reason").val("Grammatical error"); - $("#quoteReportModal .comment").val(""); + modalEl.qsr(".quote").setText(state.quoteToReport?.text as string); + modalEl.qsr(".reason").setValue("Grammatical error"); + modalEl.qsr(".comment").setValue(""); state.reasonSelect = new SlimSelect({ select: "#quoteReportModal .reason", @@ -61,7 +61,7 @@ export async function show( }, }); - new CharacterCounter(qsr("#quoteReportModal .comment"), 250); + new CharacterCounter(modalEl.qsr(".comment"), 250); }, }); } @@ -81,8 +81,12 @@ async function submitReport(): Promise { const quoteId = state.quoteToReport?.id.toString(); const quoteLanguage = removeLanguageSize(Config.language); - const reason = $("#quoteReportModal .reason").val() as QuoteReportReason; - const comment = $("#quoteReportModal .comment").val() as string; + const reason = qsr( + "#quoteReportModal .reason", + ).getValue() as QuoteReportReason; + const comment = qsr( + "#quoteReportModal .comment", + ).getValue() as string; const captcha = captchaResponse; if (quoteId === undefined || quoteId === "") { diff --git a/frontend/src/ts/modals/quote-search.ts b/frontend/src/ts/modals/quote-search.ts index 7701ee93fdc9..528a499dd98d 100644 --- a/frontend/src/ts/modals/quote-search.ts +++ b/frontend/src/ts/modals/quote-search.ts @@ -22,7 +22,7 @@ import AnimatedModal, { ShowOptions } from "../utils/animated-modal"; import * as TestLogic from "../test/test-logic"; import { createErrorMessage } from "../utils/misc"; import { highlightMatches } from "../utils/strings"; -import { ElementWithUtils } from "../utils/dom"; +import { qsr, ElementWithUtils } from "../utils/dom"; const searchServiceCache: Record> = {}; @@ -53,8 +53,13 @@ function getSearchService( function applyQuoteLengthFilter(quotes: Quote[]): Quote[] { if (!modal.isOpen()) return []; - const quoteLengthDropdown = $("#quoteSearchModal .quoteLengthFilter"); - const quoteLengthFilterValue = quoteLengthDropdown.val() as string[]; + const quoteLengthDropdown = modal + .getModal() + .qs(".quoteLengthFilter"); + const selectedOptions = quoteLengthDropdown + ? Array.from(quoteLengthDropdown.native.selectedOptions) + : []; + const quoteLengthFilterValue = selectedOptions.map((el) => el.value); if (quoteLengthFilterValue.length === 0) { usingCustomLength = true; @@ -70,7 +75,7 @@ function applyQuoteLengthFilter(quotes: Quote[]): Quote[] { if (customFilterIndex !== -1) { if (QuoteFilterPopup.removeCustom) { QuoteFilterPopup.setRemoveCustom(false); - const selectElement = quoteLengthDropdown.get(0) as + const selectElement = quoteLengthDropdown?.native as | HTMLSelectElement | null | undefined; @@ -277,38 +282,39 @@ async function updateResults(searchText: string): Promise { applyQuoteFavFilter(searchText === "" ? quotes : matches), ); - const resultsList = $("#quoteSearchResults"); + const resultsList = qsr("#quoteSearchResults"); resultsList.empty(); const totalPages = Math.ceil(quotesToShow.length / pageSize); if (currentPageNumber >= totalPages) { - $("#quoteSearchPageNavigator .nextPage").prop("disabled", true); + qsr("#quoteSearchPageNavigator .nextPage").disable(); } else { - $("#quoteSearchPageNavigator .nextPage").prop("disabled", false); + qsr("#quoteSearchPageNavigator .nextPage").enable(); } if (currentPageNumber <= 1) { - $("#quoteSearchPageNavigator .prevPage").prop("disabled", true); + qsr("#quoteSearchPageNavigator .prevPage").disable(); } else { - $("#quoteSearchPageNavigator .prevPage").prop("disabled", false); + qsr("#quoteSearchPageNavigator .prevPage").enable(); } if (quotesToShow.length === 0) { - $("#quoteSearchModal .pageInfo").html("No search results"); + modal.getModal().qsr(".pageInfo").setHtml("No search results"); return; } const startIndex = (currentPageNumber - 1) * pageSize; const endIndex = Math.min(currentPageNumber * pageSize, quotesToShow.length); - $("#quoteSearchModal .pageInfo").html( - `${startIndex + 1} - ${endIndex} of ${quotesToShow.length}`, - ); + modal + .getModal() + .qsr(".pageInfo") + .setHtml(`${startIndex + 1} - ${endIndex} of ${quotesToShow.length}`); quotesToShow.slice(startIndex, endIndex).forEach((quote) => { const quoteSearchResult = buildQuoteSearchResult(quote, matchedQueryTerms); - resultsList.append(quoteSearchResult); + resultsList.appendHtml(quoteSearchResult); }); const searchResults = modal.getModal().qsa(".searchResult"); @@ -360,13 +366,13 @@ export async function show(showOptions?: ShowOptions): Promise { void modal.show({ ...showOptions, focusFirstInput: true, - beforeAnimation: async () => { + beforeAnimation: async (modalEl) => { if (!isAuthenticated()) { - $("#quoteSearchModal .goToQuoteSubmit").addClass("hidden"); - $("#quoteSearchModal .toggleFavorites").addClass("hidden"); + modalEl.qsr(".goToQuoteSubmit").hide(); + modalEl.qsr(".toggleFavorites").hide(); } else { - $("#quoteSearchModal .goToQuoteSubmit").removeClass("hidden"); - $("#quoteSearchModal .toggleFavorites").removeClass("hidden"); + modalEl.qsr(".goToQuoteSubmit").show(); + modalEl.qsr(".toggleFavorites").show(); } const quoteMod = DB.getSnapshot()?.quoteMod; @@ -375,9 +381,9 @@ export async function show(showOptions?: ShowOptions): Promise { (quoteMod === true || (quoteMod as string) !== ""); if (isQuoteMod) { - $("#quoteSearchModal .goToQuoteApprove").removeClass("hidden"); + modalEl.qsr(".goToQuoteApprove").show(); } else { - $("#quoteSearchModal .goToQuoteApprove").addClass("hidden"); + modalEl.qsr(".goToQuoteApprove").hide(); } lengthSelect = new SlimSelect({ @@ -465,9 +471,9 @@ async function toggleFavoriteForQuote(quoteId: string): Promise { const alreadyFavorited = QuotesController.isQuoteFavorite(quote); - const $button = $( - `#quoteSearchModal .searchResult[data-quote-id=${quoteId}] .textButton.favorite i`, - ); + const $button = modal + .getModal() + .qsr(`.searchResult[data-quote-id=${quoteId}] .textButton.favorite i`); const dbSnapshot = DB.getSnapshot(); if (!dbSnapshot) return; diff --git a/frontend/src/ts/modals/quote-submit.ts b/frontend/src/ts/modals/quote-submit.ts index c4111f049959..52418a27ae2f 100644 --- a/frontend/src/ts/modals/quote-submit.ts +++ b/frontend/src/ts/modals/quote-submit.ts @@ -1,4 +1,4 @@ -import { ElementWithUtils, qsr } from "../utils/dom"; +import { ElementWithUtils } from "../utils/dom"; import Ape from "../ape"; import * as Loader from "../elements/loader"; import * as Notifications from "../elements/notifications"; @@ -17,9 +17,12 @@ async function initDropdown(): Promise { for (const group of LanguageGroupNames) { if (group === "swiss_german") continue; - $("#quoteSubmitModal .newQuoteLanguage").append( - ``, - ); + modal + .getModal() + .qsr(".newQuoteLanguage") + .appendHtml( + ``, + ); } dropdownReady = true; } @@ -27,9 +30,16 @@ async function initDropdown(): Promise { let select: SlimSelect | undefined = undefined; async function submitQuote(): Promise { - const text = $("#quoteSubmitModal .newQuoteText").val() as string; - const source = $("#quoteSubmitModal .newQuoteSource").val() as string; - const language = $("#quoteSubmitModal .newQuoteLanguage").val() as Language; + const modalEl = modal.getModal(); + const text = modalEl + .qsr(".newQuoteText") + .getValue() as string; + const source = modalEl + .qsr(".newQuoteSource") + .getValue() as string; + const language = modalEl + .qsr(".newQuoteLanguage") + .getValue() as Language; const captcha = CaptchaController.getResponse("submitQuote"); if (!text || !source || !language) { @@ -49,8 +59,8 @@ async function submitQuote(): Promise { } Notifications.add("Quote submitted.", 1); - $("#quoteSubmitModal .newQuoteText").val(""); - $("#quoteSubmitModal .newQuoteSource").val(""); + modalEl.qsr(".newQuoteText").setValue(""); + modalEl.qsr(".newQuoteSource").setValue(""); CaptchaController.reset("submitQuote"); } @@ -67,9 +77,9 @@ export async function show(showOptions: ShowOptions): Promise { ...showOptions, mode: "dialog", focusFirstInput: true, - afterAnimation: async () => { + afterAnimation: async (modalEl) => { CaptchaController.render( - document.querySelector("#quoteSubmitModal .g-recaptcha") as HTMLElement, + modalEl.qsr(".g-recaptcha").native, "submitQuote", ); await initDropdown(); @@ -78,13 +88,13 @@ export async function show(showOptions: ShowOptions): Promise { select: "#quoteSubmitModal .newQuoteLanguage", }); - $("#quoteSubmitModal .newQuoteLanguage").val( - Strings.removeLanguageSize(Config.language), - ); - $("#quoteSubmitModal .newQuoteLanguage").trigger("change"); - $("#quoteSubmitModal input").val(""); + modalEl + .qsr(".newQuoteLanguage") + .setValue(Strings.removeLanguageSize(Config.language)); + modalEl.qsr(".newQuoteLanguage").dispatch("change"); + modalEl.qsr("input").setValue(""); - new CharacterCounter(qsr("#quoteSubmitModal .newQuoteText"), 250); + new CharacterCounter(modalEl.qsr(".newQuoteText"), 250); }, }); } diff --git a/frontend/src/ts/modals/save-custom-text.ts b/frontend/src/ts/modals/save-custom-text.ts index 34921566c31d..80ca7934c135 100644 --- a/frontend/src/ts/modals/save-custom-text.ts +++ b/frontend/src/ts/modals/save-custom-text.ts @@ -31,17 +31,19 @@ const validatedInput = new ValidatedHtmlInputElement( "Name can only contain letters, numbers, spaces, underscores and hyphens", }), isValid: async (value) => { - const checkbox = $("#saveCustomTextModal .isLongText").prop( - "checked", - ) as boolean; + const checkbox = modal + .getModal() + .qsr(".isLongText") + .isChecked() as boolean; const names = CustomText.getCustomTextNames(checkbox); return !names.includes(value) ? true : "Duplicate name"; }, callback: (result) => { + const modalEl = modal.getModal(); if (result.status === "success") { - $("#saveCustomTextModal button.save").prop("disabled", false); + modalEl.qsr("button.save").enable(); } else { - $("#saveCustomTextModal button.save").prop("disabled", true); + modalEl.qsr("button.save").disable(); } }, }, @@ -53,18 +55,19 @@ export async function show(options: ShowOptions): Promise { ...options, beforeAnimation: async (modalEl, modalChainData) => { state.textToSave = modalChainData?.text ?? []; - $("#saveCustomTextModal .textName").val(""); - $("#saveCustomTextModal .isLongText").prop("checked", false); - $("#saveCustomTextModal button.save").prop("disabled", true); + modalEl.qsr(".textName").setValue(""); + modalEl.qsr(".isLongText").setChecked(false); + modalEl.qsr("button.save").disable(); }, }); } function save(): boolean { - const name = $("#saveCustomTextModal .textName").val() as string; - const checkbox = $("#saveCustomTextModal .isLongText").prop( - "checked", - ) as boolean; + const modalEl = modal.getModal(); + const name = modalEl.qsr(".textName").getValue() as string; + const checkbox = modalEl + .qsr(".isLongText") + .isChecked() as boolean; if (!name) { Notifications.add("Custom text needs a name", 0); diff --git a/frontend/src/ts/modals/saved-texts.ts b/frontend/src/ts/modals/saved-texts.ts index 24869352f7af..051b3fb816ec 100644 --- a/frontend/src/ts/modals/saved-texts.ts +++ b/frontend/src/ts/modals/saved-texts.ts @@ -9,8 +9,9 @@ import { showPopup } from "./simple-modals"; import * as Notifications from "../elements/notifications"; async function fill(): Promise { + const modalEl = modal.getModal(); const names = CustomText.getCustomTextNames(); - const listEl = $(`#savedTextsModal .list`).empty(); + const listEl = modalEl.qsr(".list").empty(); let list = ""; if (names.length === 0) { list += "
No saved custom texts found
"; @@ -24,10 +25,10 @@ async function fill(): Promise {
`; } } - listEl.html(list); + listEl.setHtml(list); const longNames = CustomText.getCustomTextNames(true); - const longListEl = $(`#savedTextsModal .listLong`).empty(); + const longListEl = modalEl.qsr(".listLong").empty(); let longList = ""; if (longNames.length === 0) { longList += "
No saved long custom texts found
"; @@ -44,13 +45,13 @@ async function fill(): Promise {
`; } } - longListEl.html(longList); + longListEl.setHtml(longList); - $("#savedTextsModal .list .savedText .button.delete").on("click", (e) => { - const name = $(e.target).closest(".savedText").data("name") as - | string - | undefined; - if (name === undefined) { + modalEl.qs(".list .savedText .button.delete")?.on("click", (e) => { + const name = (e.target as HTMLElement) + .closest(".savedText") + ?.getAttribute("data-name"); + if (name === null || name === undefined) { Notifications.add("Failed to show delete modal: no name found", -1); return; } @@ -59,54 +60,47 @@ async function fill(): Promise { }); }); - $("#savedTextsModal .listLong .savedLongText .button.delete").on( - "click", - (e) => { - const name = $(e.target).closest(".savedLongText").data("name") as - | string - | undefined; - if (name === undefined) { - Notifications.add("Failed to show delete modal: no name found", -1); - return; - } - showPopup("deleteCustomTextLong", [name], { - modalChain: modal as AnimatedModal, - }); - }, - ); + modalEl.qs(".listLong .savedLongText .button.delete")?.on("click", (e) => { + const name = (e.target as HTMLElement) + .closest(".savedLongText") + ?.getAttribute("data-name"); + if (name === null || name === undefined) { + Notifications.add("Failed to show delete modal: no name found", -1); + return; + } + showPopup("deleteCustomTextLong", [name], { + modalChain: modal as AnimatedModal, + }); + }); - $("#savedTextsModal .listLong .savedLongText .button.resetProgress").on( - "click", - (e) => { - const name = $(e.target).closest(".savedLongText").data("name") as - | string - | undefined; - if (name === undefined) { + modalEl + .qs(".listLong .savedLongText .button.resetProgress") + ?.on("click", (e) => { + const name = (e.target as HTMLElement) + .closest(".savedLongText") + ?.getAttribute("data-name"); + if (name === null || name === undefined) { Notifications.add("Failed to show delete modal: no name found", -1); return; } showPopup("resetProgressCustomTextLong", [name], { modalChain: modal as AnimatedModal, }); - }, - ); + }); - $("#savedTextsModal .list .savedText .button.name").on("click", (e) => { - const name = $(e.target).text(); + modalEl.qs(".list .savedText .button.name")?.on("click", (e) => { + const name = (e.target as HTMLElement).textContent; CustomTextState.setCustomTextName(name, false); const text = getSavedText(name, false); hide({ modalChainData: { text, long: false } }); }); - $("#savedTextsModal .listLong .savedLongText .button.name").on( - "click", - (e) => { - const name = $(e.target).text(); - CustomTextState.setCustomTextName(name, true); - const text = getSavedText(name, true); - hide({ modalChainData: { text, long: true } }); - }, - ); + modalEl.qs(".listLong .savedLongText .button.name")?.on("click", (e) => { + const name = (e.target as HTMLElement).textContent; + CustomTextState.setCustomTextName(name, true); + const text = getSavedText(name, true); + hide({ modalChainData: { text, long: true } }); + }); } export async function show(options: ShowOptions): Promise { diff --git a/frontend/src/ts/modals/share-custom-theme.ts b/frontend/src/ts/modals/share-custom-theme.ts index 05626d110870..e50185823474 100644 --- a/frontend/src/ts/modals/share-custom-theme.ts +++ b/frontend/src/ts/modals/share-custom-theme.ts @@ -2,6 +2,7 @@ import * as ThemeController from "../controllers/theme-controller"; import Config from "../config"; import * as Notifications from "../elements/notifications"; import AnimatedModal from "../utils/animated-modal"; +import { qsr } from "../utils/dom"; type State = { includeBackground: boolean; @@ -29,9 +30,9 @@ async function generateUrl(): Promise { } = { c: ThemeController.colorVars.map( (color) => - $(`.pageSettings .tabContent.customTheme #${color}[type='color']`).attr( - "value", - ) as string, + qsr( + `.pageSettings .tabContent.customTheme #${color}[type='color']`, + ).getValue() as string, ), }; diff --git a/frontend/src/ts/modals/share-test-settings.ts b/frontend/src/ts/modals/share-test-settings.ts index 458bcc9e330d..101d54245a29 100644 --- a/frontend/src/ts/modals/share-test-settings.ts +++ b/frontend/src/ts/modals/share-test-settings.ts @@ -1,4 +1,3 @@ -import { ElementWithUtils } from "../utils/dom"; import Config from "../config"; import { currentQuote } from "../test/test-words"; import { getMode2 } from "../utils/misc"; @@ -7,12 +6,14 @@ import { compressToURI } from "lz-ts"; import AnimatedModal, { ShowOptions } from "../utils/animated-modal"; import { Difficulty, FunboxName } from "@monkeytype/schemas/configs"; import { Mode, Mode2 } from "@monkeytype/schemas/shared"; +import { ElementWithUtils } from "../utils/dom"; import { CustomTextSettings } from "@monkeytype/schemas/results"; function getCheckboxValue(checkbox: string): boolean { - return $(`#shareTestSettingsModal label.${checkbox} input`).prop( - "checked", - ) as boolean; + return modal + .getModal() + .qsr(`label.${checkbox} input`) + .isChecked() as boolean; } type SharedTestSettings = [ @@ -56,16 +57,17 @@ function updateURL(): void { } function updateShareModal(url: string): void { - const $modal = $(`#shareTestSettingsModal`); - $modal.find("textarea.url").val(url); - $modal.find(".tooLongWarning").toggleClass("hidden", url.length <= 2000); + const modalEl = modal.getModal(); + modalEl.qsr("textarea.url").setValue(url); + modalEl.qsr(".tooLongWarning").toggleClass("hidden", url.length <= 2000); } function updateSubgroups(): void { + const modalEl = modal.getModal(); if (getCheckboxValue("mode")) { - $(`#shareTestSettingsModal .subgroup`).removeClass("hidden"); + modalEl.qsa(".subgroup").show(); } else { - $(`#shareTestSettingsModal .subgroup`).addClass("hidden"); + modalEl.qsa(".subgroup").hide(); } } diff --git a/frontend/src/ts/modals/simple-modals.ts b/frontend/src/ts/modals/simple-modals.ts index 1bf579504415..ea85b6f22f0a 100644 --- a/frontend/src/ts/modals/simple-modals.ts +++ b/frontend/src/ts/modals/simple-modals.ts @@ -46,6 +46,7 @@ import { goToPage } from "../pages/leaderboards"; import FileStorage from "../utils/file-storage"; import { z } from "zod"; import { remoteValidation } from "../utils/remote-validation"; +import { qs, qsr } from "../utils/dom"; type PopupKey = | "updateEmail" @@ -1141,9 +1142,9 @@ list.updateCustomTheme = new SimpleModal({ if (updateColors === "true") { for (const color of ThemeController.colorVars) { newColors.push( - $( + qsr( `.pageSettings .tabContent.customTheme #${color}[type='color']`, - ).attr("value") as string, + ).getValue() as string, ); } } else { @@ -1336,89 +1337,105 @@ export function showPopup( } //todo: move these event handlers to their respective files (either global event files or popup files) -$(".pageAccountSettings").on("click", "#unlinkDiscordButton", () => { +qs(".pageAccountSettings")?.onChild("click", "#unlinkDiscordButton", () => { showPopup("unlinkDiscord"); }); -$(".pageAccountSettings").on("click", "#removeGoogleAuth", () => { +qs(".pageAccountSettings")?.onChild("click", "#removeGoogleAuth", () => { showPopup("removeGoogleAuth"); }); -$(".pageAccountSettings").on("click", "#removeGithubAuth", () => { +qs(".pageAccountSettings")?.onChild("click", "#removeGithubAuth", () => { showPopup("removeGithubAuth"); }); -$(".pageAccountSettings").on("click", "#removePasswordAuth", () => { +qs(".pageAccountSettings")?.onChild("click", "#removePasswordAuth", () => { showPopup("removePasswordAuth"); }); -$("#resetSettingsButton").on("click", () => { +qs("#resetSettingsButton")?.on("click", () => { showPopup("resetSettings"); }); -$(".pageAccountSettings").on("click", "#revokeAllTokens", () => { +qs(".pageAccountSettings")?.onChild("click", "#revokeAllTokens", () => { showPopup("revokeAllTokens"); }); -$(".pageAccountSettings").on("click", "#resetPersonalBestsButton", () => { - showPopup("resetPersonalBests"); -}); +qs(".pageAccountSettings")?.onChild( + "click", + "#resetPersonalBestsButton", + () => { + showPopup("resetPersonalBests"); + }, +); -$(".pageAccountSettings").on("click", "#updateAccountName", () => { +qs(".pageAccountSettings")?.onChild("click", "#updateAccountName", () => { showPopup("updateName"); }); -$("#bannerCenter").on("click", ".banner .text .openNameChange", () => { +qs("#bannerCenter")?.onChild("click", ".banner .text .openNameChange", () => { showPopup("updateName"); }); -$(".pageAccountSettings").on("click", "#addPasswordAuth", () => { +qs(".pageAccountSettings")?.onChild("click", "#addPasswordAuth", () => { showPopup("addPasswordAuth"); }); -$(".pageAccountSettings").on("click", "#emailPasswordAuth", () => { +qs(".pageAccountSettings")?.onChild("click", "#emailPasswordAuth", () => { showPopup("updateEmail"); }); -$(".pageAccountSettings").on("click", "#passPasswordAuth", () => { +qs(".pageAccountSettings")?.onChild("click", "#passPasswordAuth", () => { showPopup("updatePassword"); }); -$(".pageAccountSettings").on("click", "#deleteAccount", () => { +qs(".pageAccountSettings")?.onChild("click", "#deleteAccount", () => { showPopup("deleteAccount"); }); -$(".pageAccountSettings").on("click", "#resetAccount", () => { +qs(".pageAccountSettings")?.onChild("click", "#resetAccount", () => { showPopup("resetAccount"); }); -$(".pageAccountSettings").on("click", "#optOutOfLeaderboardsButton", () => { - showPopup("optOutOfLeaderboards"); -}); +qs(".pageAccountSettings")?.onChild( + "click", + "#optOutOfLeaderboardsButton", + () => { + showPopup("optOutOfLeaderboards"); + }, +); -$(".pageSettings").on( +qs(".pageSettings")?.onChild( "click", ".section.themes .customTheme .delButton", (e) => { - const $parentElement = $(e.currentTarget).parent(".customTheme.button"); - const customThemeId = $parentElement.attr("customThemeId") as string; + const $parentElement = (e.childTarget as HTMLElement | null)?.closest( + ".customTheme.button", + ); + const customThemeId = $parentElement?.getAttribute( + "customThemeId", + ) as string; showPopup("deleteCustomTheme", [customThemeId]); }, ); -$(".pageSettings").on( +qs(".pageSettings")?.onChild( "click", ".section.themes .customTheme .editButton", (e) => { - const $parentElement = $(e.currentTarget).parent(".customTheme.button"); - const customThemeId = $parentElement.attr("customThemeId") as string; + const $parentElement = (e.childTarget as HTMLElement | null)?.closest( + ".customTheme.button", + ); + const customThemeId = $parentElement?.getAttribute( + "customThemeId", + ) as string; showPopup("updateCustomTheme", [customThemeId], { focusFirstInput: "focusAndSelect", }); }, ); -$(".pageSettings").on( +qs(".pageSettings")?.onChild( "click", ".section[data-config-name='fontFamily'] button[data-config-value='custom']", () => { diff --git a/frontend/src/ts/modals/user-report.ts b/frontend/src/ts/modals/user-report.ts index 034ad33d9dc9..41b1f44f555a 100644 --- a/frontend/src/ts/modals/user-report.ts +++ b/frontend/src/ts/modals/user-report.ts @@ -68,7 +68,7 @@ export async function show(options: ShowOptions): Promise { }, }); - new CharacterCounter(qsr("#userReportModal .comment"), 250); + new CharacterCounter(modal.getModal().qsr(".comment"), 250); } async function hide(): Promise { @@ -82,8 +82,12 @@ async function submitReport(): Promise { return; } - const reason = $("#userReportModal .reason").val() as ReportUserReason; - const comment = $("#userReportModal .comment").val() as string; + const reason = qsr( + "#userReportModal .reason", + ).getValue() as ReportUserReason; + const comment = qsr( + "#userReportModal .comment", + ).getValue() as string; const captcha = captchaResponse; if (!reason) { diff --git a/frontend/src/ts/modals/word-filter.ts b/frontend/src/ts/modals/word-filter.ts index fa676c5a5e77..baa4b65d46ce 100644 --- a/frontend/src/ts/modals/word-filter.ts +++ b/frontend/src/ts/modals/word-filter.ts @@ -12,6 +12,7 @@ import { tryCatch } from "@monkeytype/util/trycatch"; import { LanguageList } from "../constants/languages"; import { Language } from "@monkeytype/schemas/languages"; import { LayoutObject } from "@monkeytype/schemas/layouts"; +import { qs, qsr } from "../utils/dom"; type FilterPreset = { display: string; @@ -26,7 +27,9 @@ type FilterPreset = { } ); -const exactMatchCheckbox = $("#wordFilterModal #exactMatchOnly"); +const exactMatchCheckbox = qs( + "#wordFilterModal #exactMatchOnly", +); const presets: Record = { homeKeys: { @@ -82,28 +85,29 @@ const presets: Record = { }; async function initSelectOptions(): Promise { - $("#wordFilterModal .languageInput").empty(); - $("#wordFilterModal .layoutInput").empty(); - $("wordFilterModal .presetInput").empty(); + const modalEl = modal.getModal(); + modalEl.qsr(".languageInput").empty(); + modalEl.qsr(".layoutInput").empty(); + modalEl.qsr(".presetInput").empty(); LanguageList.forEach((language) => { const prettyLang = language.replace(/_/gi, " "); - $("#wordFilterModal .languageInput").append(` + modalEl.qsr(".languageInput").appendHtml(` `); }); for (const layout of LayoutsList) { const prettyLayout = layout.replace(/_/gi, " "); - $("#wordFilterModal .layoutInput").append(` + modalEl.qsr(".layoutInput").appendHtml(` `); } for (const [presetId, preset] of Object.entries(presets)) { - $("#wordFilterModal .presetInput").append( - ``, - ); + modalEl + .qsr(".presetInput") + .appendHtml(``); } } @@ -133,7 +137,7 @@ export async function show(showOptions?: ShowOptions): Promise { contentLocation: modal.getModal().native, }, }); - $("#wordFilterModal .loadingIndicator").removeClass("hidden"); + modalEl.qs(".loadingIndicator")?.show(); enableButtons(); }, }); @@ -146,8 +150,11 @@ function hide(hideOptions?: HideOptions): void { } async function filter(language: Language): Promise { - const exactMatchOnly = exactMatchCheckbox.is(":checked"); - let filterin = $("#wordFilterModal .wordIncludeInput").val() as string; + const modalEl = modal.getModal(); + const exactMatchOnly = exactMatchCheckbox?.isChecked() as boolean; + let filterin = modalEl + .qsr(".wordIncludeInput") + .getValue() as string; filterin = Misc.escapeRegExp(filterin?.trim()); filterin = filterin.replace(/\s+/gi, "|"); let regincl; @@ -158,7 +165,9 @@ async function filter(language: Language): Promise { regincl = new RegExp(filterin, "i"); } - let filterout = $("#wordFilterModal .wordExcludeInput").val() as string; + let filterout = modalEl + .qsr(".wordExcludeInput") + .getValue() as string; filterout = Misc.escapeRegExp(filterout.trim()); filterout = filterout.replace(/\s+/gi, "|"); const regexcl = new RegExp(filterout, "i"); @@ -175,8 +184,12 @@ async function filter(language: Language): Promise { return []; } - const maxLengthInput = $("#wordFilterModal .wordMaxInput").val() as string; - const minLengthInput = $("#wordFilterModal .wordMinInput").val() as string; + const maxLengthInput = modalEl + .qsr(".wordMaxInput") + .getValue() as string; + const minLengthInput = modalEl + .qsr(".wordMinInput") + .getValue() as string; let maxLength; let minLength; if (maxLengthInput === "") { @@ -204,7 +217,10 @@ async function filter(language: Language): Promise { } async function apply(set: boolean): Promise { - const language = $("#wordFilterModal .languageInput").val() as Language; + const language = modal + .getModal() + .qsr(".languageInput") + .getValue() as Language; const filteredWords = await filter(language); if (filteredWords.length === 0) { @@ -226,16 +242,18 @@ async function apply(set: boolean): Promise { } function setExactMatchInput(disable: boolean): void { - const wordExcludeInputEl = $("#wordFilterModal #wordExcludeInput"); + const wordExcludeInputEl = modal + .getModal() + .qsr("#wordExcludeInput"); if (disable) { - $("#wordFilterModal #wordExcludeInput").val(""); - wordExcludeInputEl.attr("disabled", "disabled"); + wordExcludeInputEl.setValue(""); + wordExcludeInputEl.disable(); } else { - wordExcludeInputEl.removeAttr("disabled"); + wordExcludeInputEl.enable(); } - exactMatchCheckbox.prop("checked", disable); + exactMatchCheckbox?.setChecked(disable); } function disableButtons(): void { @@ -249,9 +267,15 @@ function enableButtons(): void { async function setup(): Promise { await initSelectOptions(); - $("#wordFilterModal button.generateButton").on("click", async () => { - const presetName = $("#wordFilterModal .presetInput").val() as string; - const layoutName = $("#wordFilterModal .layoutInput").val() as string; + const modalEl = modal.getModal(); + + modalEl.qsr("button.generateButton").on("click", async () => { + const presetName = modalEl + .qsr(".presetInput") + .getValue() as string; + const layoutName = modalEl + .qsr(".layoutInput") + .getValue() as string; const presetToApply = presets[presetName]; @@ -262,7 +286,7 @@ async function setup(): Promise { const layout = await JSONData.getLayout(layoutName); - $("#wordIncludeInput").val( + qsr("#wordIncludeInput").setValue( presetToApply .getIncludeString(layout) .map((x) => x[0]) @@ -274,7 +298,7 @@ async function setup(): Promise { } else { setExactMatchInput(false); if (presetToApply.getExcludeString !== undefined) { - $("#wordExcludeInput").val( + qsr("#wordExcludeInput").setValue( presetToApply .getExcludeString(layout) .map((x) => x[0]) @@ -284,20 +308,20 @@ async function setup(): Promise { } }); - exactMatchCheckbox.on("change", () => { - setExactMatchInput(exactMatchCheckbox.is(":checked")); + exactMatchCheckbox?.on("change", () => { + setExactMatchInput(exactMatchCheckbox.isChecked() as boolean); }); - $("#wordFilterModal button.addButton").on("click", () => { - $("#wordFilterModal .loadingIndicator").removeClass("hidden"); + modalEl.qsr("button.addButton").on("click", () => { + modalEl.qs(".loadingIndicator")?.show(); disableButtons(); setTimeout(() => { void apply(false); }, 0); }); - $("#wordFilterModal button.setButton").on("click", () => { - $("#wordFilterModal .loadingIndicator").removeClass("hidden"); + modalEl.qsr("button.setButton").on("click", () => { + modalEl.qs(".loadingIndicator")?.show(); disableButtons(); setTimeout(() => { void apply(true); diff --git a/frontend/src/ts/utils/dom.ts b/frontend/src/ts/utils/dom.ts index a4ceb1e14278..f9774b6e69c5 100644 --- a/frontend/src/ts/utils/dom.ts +++ b/frontend/src/ts/utils/dom.ts @@ -311,8 +311,8 @@ export class ElementWithUtils { /** * Toggle a class on the element */ - toggleClass(className: string): this { - this.native.classList.toggle(className); + toggleClass(className: string, force?: boolean): this { + this.native.classList.toggle(className, force); return this; } @@ -926,6 +926,16 @@ export class ElementsWithUtils< return this; } + /** + * Set value of all input elements in the array + */ + setValue(this: ElementsWithUtils, value: string): this { + for (const item of this) { + item.setValue(value); + } + return this as unknown as this; + } + /** * Query all elements in the array for a child element matching the selector */ From 9896c1816ec4fe69b5463b9c1bde10e3e360709b Mon Sep 17 00:00:00 2001 From: Biplav Barua <90949688+biplavbarua@users.noreply.github.com> Date: Thu, 15 Jan 2026 20:46:50 +0530 Subject: [PATCH 4/4] refactor: move event handlers from simple-modals to respective files (@biplavbarua) (#7333) This PR addresses the `TODO` in `simple-modals.ts` by moving event handlers to their respective page controller files (`account-settings.ts` and `settings.ts`). **Key Changes:** 1. Moved `.pageAccountSettings` handlers to `account-settings.ts`. 2. Moved `.pageSettings` handlers to `settings.ts`. 3. Extracted `PopupKey`, `list`, and `showPopup` to `simple-modals-base.ts` to resolve circular dependencies introduced by importing page controllers in `simple-modals.ts` (which now import `showPopup` from base). **Testing:** - Verified no circular dependencies with `madge`. - Verified type safety with `tsc`. - Verified build success. --------- Co-authored-by: Jack --- frontend/src/ts/modals/simple-modals-base.ts | 67 ++++++++ frontend/src/ts/modals/simple-modals.ts | 171 +------------------ frontend/src/ts/pages/account-settings.ts | 61 +++++++ frontend/src/ts/pages/settings.ts | 43 +++++ 4 files changed, 175 insertions(+), 167 deletions(-) create mode 100644 frontend/src/ts/modals/simple-modals-base.ts diff --git a/frontend/src/ts/modals/simple-modals-base.ts b/frontend/src/ts/modals/simple-modals-base.ts new file mode 100644 index 000000000000..d6004fa3f685 --- /dev/null +++ b/frontend/src/ts/modals/simple-modals-base.ts @@ -0,0 +1,67 @@ +import * as Notifications from "../elements/notifications"; +import { ShowOptions } from "../utils/animated-modal"; +import { SimpleModal } from "../utils/simple-modal"; + +export type PopupKey = + | "updateEmail" + | "updateName" + | "updatePassword" + | "removeGoogleAuth" + | "removeGithubAuth" + | "removePasswordAuth" + | "addPasswordAuth" + | "deleteAccount" + | "resetAccount" + | "optOutOfLeaderboards" + | "applyCustomFont" + | "resetPersonalBests" + | "resetSettings" + | "revokeAllTokens" + | "unlinkDiscord" + | "editApeKey" + | "deleteCustomText" + | "deleteCustomTextLong" + | "resetProgressCustomTextLong" + | "updateCustomTheme" + | "deleteCustomTheme" + | "devGenerateData" + | "lbGoToPage"; + +export const list: Record = { + updateEmail: undefined, + updateName: undefined, + updatePassword: undefined, + removeGoogleAuth: undefined, + removeGithubAuth: undefined, + removePasswordAuth: undefined, + addPasswordAuth: undefined, + deleteAccount: undefined, + resetAccount: undefined, + optOutOfLeaderboards: undefined, + applyCustomFont: undefined, + resetPersonalBests: undefined, + resetSettings: undefined, + revokeAllTokens: undefined, + unlinkDiscord: undefined, + editApeKey: undefined, + deleteCustomText: undefined, + deleteCustomTextLong: undefined, + resetProgressCustomTextLong: undefined, + updateCustomTheme: undefined, + deleteCustomTheme: undefined, + devGenerateData: undefined, + lbGoToPage: undefined, +}; + +export function showPopup( + key: PopupKey, + showParams = [] as string[], + showOptions: ShowOptions = {}, +): void { + const popup = list[key]; + if (popup === undefined) { + Notifications.add("Failed to show popup - popup is not defined", -1); + return; + } + popup.show(showParams, showOptions); +} diff --git a/frontend/src/ts/modals/simple-modals.ts b/frontend/src/ts/modals/simple-modals.ts index ea85b6f22f0a..a0b2baca2d54 100644 --- a/frontend/src/ts/modals/simple-modals.ts +++ b/frontend/src/ts/modals/simple-modals.ts @@ -35,7 +35,7 @@ import { SimpleModal, TextInput, } from "../utils/simple-modal"; -import { ShowOptions } from "../utils/animated-modal"; + import { GenerateDataRequest } from "@monkeytype/contracts/dev"; import { PasswordSchema, @@ -47,57 +47,10 @@ import FileStorage from "../utils/file-storage"; import { z } from "zod"; import { remoteValidation } from "../utils/remote-validation"; import { qs, qsr } from "../utils/dom"; +import { list, PopupKey, showPopup } from "./simple-modals-base"; -type PopupKey = - | "updateEmail" - | "updateName" - | "updatePassword" - | "removeGoogleAuth" - | "removeGithubAuth" - | "removePasswordAuth" - | "addPasswordAuth" - | "deleteAccount" - | "resetAccount" - | "optOutOfLeaderboards" - | "applyCustomFont" - | "resetPersonalBests" - | "resetSettings" - | "revokeAllTokens" - | "unlinkDiscord" - | "editApeKey" - | "deleteCustomText" - | "deleteCustomTextLong" - | "resetProgressCustomTextLong" - | "updateCustomTheme" - | "deleteCustomTheme" - | "devGenerateData" - | "lbGoToPage"; - -const list: Record = { - updateEmail: undefined, - updateName: undefined, - updatePassword: undefined, - removeGoogleAuth: undefined, - removeGithubAuth: undefined, - removePasswordAuth: undefined, - addPasswordAuth: undefined, - deleteAccount: undefined, - resetAccount: undefined, - optOutOfLeaderboards: undefined, - applyCustomFont: undefined, - resetPersonalBests: undefined, - resetSettings: undefined, - revokeAllTokens: undefined, - unlinkDiscord: undefined, - editApeKey: undefined, - deleteCustomText: undefined, - deleteCustomTextLong: undefined, - resetProgressCustomTextLong: undefined, - updateCustomTheme: undefined, - deleteCustomTheme: undefined, - devGenerateData: undefined, - lbGoToPage: undefined, -}; +export { list, showPopup }; +export type { PopupKey }; type AuthMethod = "password" | "github.com" | "google.com"; @@ -1323,122 +1276,6 @@ list.lbGoToPage = new SimpleModal({ }, }); -export function showPopup( - key: PopupKey, - showParams = [] as string[], - showOptions: ShowOptions = {}, -): void { - const popup = list[key]; - if (popup === undefined) { - Notifications.add("Failed to show popup - popup is not defined", -1); - return; - } - popup.show(showParams, showOptions); -} - -//todo: move these event handlers to their respective files (either global event files or popup files) -qs(".pageAccountSettings")?.onChild("click", "#unlinkDiscordButton", () => { - showPopup("unlinkDiscord"); -}); - -qs(".pageAccountSettings")?.onChild("click", "#removeGoogleAuth", () => { - showPopup("removeGoogleAuth"); -}); - -qs(".pageAccountSettings")?.onChild("click", "#removeGithubAuth", () => { - showPopup("removeGithubAuth"); -}); - -qs(".pageAccountSettings")?.onChild("click", "#removePasswordAuth", () => { - showPopup("removePasswordAuth"); -}); - -qs("#resetSettingsButton")?.on("click", () => { - showPopup("resetSettings"); -}); - -qs(".pageAccountSettings")?.onChild("click", "#revokeAllTokens", () => { - showPopup("revokeAllTokens"); -}); - -qs(".pageAccountSettings")?.onChild( - "click", - "#resetPersonalBestsButton", - () => { - showPopup("resetPersonalBests"); - }, -); - -qs(".pageAccountSettings")?.onChild("click", "#updateAccountName", () => { - showPopup("updateName"); -}); - qs("#bannerCenter")?.onChild("click", ".banner .text .openNameChange", () => { showPopup("updateName"); }); - -qs(".pageAccountSettings")?.onChild("click", "#addPasswordAuth", () => { - showPopup("addPasswordAuth"); -}); - -qs(".pageAccountSettings")?.onChild("click", "#emailPasswordAuth", () => { - showPopup("updateEmail"); -}); - -qs(".pageAccountSettings")?.onChild("click", "#passPasswordAuth", () => { - showPopup("updatePassword"); -}); - -qs(".pageAccountSettings")?.onChild("click", "#deleteAccount", () => { - showPopup("deleteAccount"); -}); - -qs(".pageAccountSettings")?.onChild("click", "#resetAccount", () => { - showPopup("resetAccount"); -}); - -qs(".pageAccountSettings")?.onChild( - "click", - "#optOutOfLeaderboardsButton", - () => { - showPopup("optOutOfLeaderboards"); - }, -); - -qs(".pageSettings")?.onChild( - "click", - ".section.themes .customTheme .delButton", - (e) => { - const $parentElement = (e.childTarget as HTMLElement | null)?.closest( - ".customTheme.button", - ); - const customThemeId = $parentElement?.getAttribute( - "customThemeId", - ) as string; - showPopup("deleteCustomTheme", [customThemeId]); - }, -); - -qs(".pageSettings")?.onChild( - "click", - ".section.themes .customTheme .editButton", - (e) => { - const $parentElement = (e.childTarget as HTMLElement | null)?.closest( - ".customTheme.button", - ); - const customThemeId = $parentElement?.getAttribute( - "customThemeId", - ) as string; - showPopup("updateCustomTheme", [customThemeId], { - focusFirstInput: "focusAndSelect", - }); - }, -); - -qs(".pageSettings")?.onChild( - "click", - ".section[data-config-name='fontFamily'] button[data-config-value='custom']", - () => { - showPopup("applyCustomFont"); - }, -); diff --git a/frontend/src/ts/pages/account-settings.ts b/frontend/src/ts/pages/account-settings.ts index 38fea33f86ec..69b4a3e7ec2a 100644 --- a/frontend/src/ts/pages/account-settings.ts +++ b/frontend/src/ts/pages/account-settings.ts @@ -13,6 +13,7 @@ import * as Notifications from "../elements/notifications"; import { z } from "zod"; import * as AuthEvent from "../observables/auth-event"; import { qs, qsa, qsr, onDOMReady } from "../utils/dom"; +import { showPopup } from "../modals/simple-modals-base"; const pageElement = qsr(".page.pageAccountSettings"); @@ -185,6 +186,66 @@ qs(".page.pageAccountSettings #setStreakHourOffset")?.on("click", () => { StreakHourOffsetModal.show(); }); +qs(".pageAccountSettings")?.onChild("click", "#unlinkDiscordButton", () => { + showPopup("unlinkDiscord"); +}); + +qs(".pageAccountSettings")?.onChild("click", "#removeGoogleAuth", () => { + showPopup("removeGoogleAuth"); +}); + +qs(".pageAccountSettings")?.onChild("click", "#removeGithubAuth", () => { + showPopup("removeGithubAuth"); +}); + +qs(".pageAccountSettings")?.onChild("click", "#removePasswordAuth", () => { + showPopup("removePasswordAuth"); +}); + +qs(".pageAccountSettings")?.onChild("click", "#addPasswordAuth", () => { + showPopup("addPasswordAuth"); +}); + +qs(".pageAccountSettings")?.onChild("click", "#emailPasswordAuth", () => { + showPopup("updateEmail"); +}); + +qs(".pageAccountSettings")?.onChild("click", "#passPasswordAuth", () => { + showPopup("updatePassword"); +}); + +qs(".pageAccountSettings")?.onChild("click", "#deleteAccount", () => { + showPopup("deleteAccount"); +}); + +qs(".pageAccountSettings")?.onChild("click", "#resetAccount", () => { + showPopup("resetAccount"); +}); + +qs(".pageAccountSettings")?.onChild( + "click", + "#optOutOfLeaderboardsButton", + () => { + showPopup("optOutOfLeaderboards"); + }, +); + +qs(".pageAccountSettings")?.onChild("click", "#revokeAllTokens", () => { + showPopup("revokeAllTokens"); +}); + +qs(".pageAccountSettings")?.onChild( + "click", + "#resetPersonalBestsButton", + () => { + showPopup("resetPersonalBests"); + }, +); + +qs(".pageAccountSettings")?.onChild("click", "#updateAccountName", () => { + showPopup("updateName"); +}); + AuthEvent.subscribe((event) => { if (event.type === "authConfigUpdated") { updateUI(); diff --git a/frontend/src/ts/pages/settings.ts b/frontend/src/ts/pages/settings.ts index 201a3b52f10c..9fab875178a8 100644 --- a/frontend/src/ts/pages/settings.ts +++ b/frontend/src/ts/pages/settings.ts @@ -44,6 +44,7 @@ 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, qsa, qsr, onDOMReady } from "../utils/dom"; +import { showPopup } from "../modals/simple-modals-base"; let settingsInitialized = false; @@ -979,6 +980,48 @@ qsa(".pageSettings .section .groupTitle button")?.on("click", (e) => { }); }); +qs(".pageSettings")?.onChild( + "click", + ".section.themes .customTheme .delButton", + (e) => { + const parentElement = (e.childTarget as HTMLElement | null)?.closest( + ".customTheme.button", + ); + const customThemeId = parentElement?.getAttribute( + "customThemeId", + ) as string; + showPopup("deleteCustomTheme", [customThemeId]); + }, +); + +qs(".pageSettings")?.onChild( + "click", + ".section.themes .customTheme .editButton", + (e) => { + const parentElement = (e.childTarget as HTMLElement | null)?.closest( + ".customTheme.button", + ); + const customThemeId = parentElement?.getAttribute( + "customThemeId", + ) as string; + showPopup("updateCustomTheme", [customThemeId], { + focusFirstInput: "focusAndSelect", + }); + }, +); + +qs(".pageSettings")?.onChild( + "click", + ".section[data-config-name='fontFamily'] button[data-config-value='custom']", + () => { + showPopup("applyCustomFont"); + }, +); + +qs(".pageSettings #resetSettingsButton")?.on("click", () => { + showPopup("resetSettings"); +}); + ConfigEvent.subscribe(({ key, newValue }) => { if (key === "fullConfigChange") setEventDisabled(true); if (key === "fullConfigChangeFinished") setEventDisabled(false);