@@ -266,12 +283,11 @@ function fillPSAs(): void {
function fillNotifications(): void {
if (state.notifications.length === 0) {
- $("#alertsPopup .notificationHistory .list").html(
+ notificationHistoryListEl.setHtml(
`
Nothing to show
`,
);
} else {
- $("#alertsPopup .notificationHistory .list").empty();
-
+ notificationHistoryListEl.empty();
for (const n of state.notifications) {
const { message, level, customTitle } = n;
let title = "Notice";
@@ -288,7 +304,7 @@ function fillNotifications(): void {
title = customTitle;
}
- $("#alertsPopup .notificationHistory .list").prepend(`
+ notificationHistoryListEl.prependHtml(`
${title}
@@ -303,24 +319,24 @@ function fillNotifications(): void {
export function setNotificationBubbleVisible(tf: boolean): void {
if (tf) {
- $("header nav .showAlerts .notificationBubble").removeClass("hidden");
+ qs("header nav .showAlerts .notificationBubble")?.show();
} else {
- $("header nav .showAlerts .notificationBubble").addClass("hidden");
+ qs("header nav .showAlerts .notificationBubble")?.hide();
}
}
function updateInboxSize(): void {
const remainingItems = accountAlerts.length - mailToDelete.length;
- $("#alertsPopup .accountAlerts .title .right").text(
- `${remainingItems}/${maxMail}`,
- );
+ alertsPopupEl
+ .qs(".accountAlerts .title .right")
+ ?.setText(`${remainingItems}/${maxMail}`);
}
function deleteAlert(id: string): void {
mailToDelete.push(id);
- $(`#alertsPopup .accountAlerts .list .item[data-id="${id}"]`).remove();
- if ($("#alertsPopup .accountAlerts .list .item").length === 0) {
- $("#alertsPopup .accountAlerts .list").html(`
+ alertsPopupEl.qs(`.accountAlerts .list .item[data-id="${id}"]`)?.remove();
+ if (alertsPopupEl.qsa(".accountAlerts .list .item").length === 0) {
+ accountAlertsListEl.setHtml(`
Nothing to show
@@ -332,34 +348,35 @@ function deleteAlert(id: string): void {
function markReadAlert(id: string): void {
mailToMarkRead.push(id);
- const item = $(`#alertsPopup .accountAlerts .list .item[data-id="${id}"]`);
+ const item = alertsPopupEl.qsr(`.accountAlerts .list .item[data-id="${id}"]`);
updateClaimDeleteAllButton();
- item.find(".indicator").removeClass("main");
- item.find(".buttons").empty();
+ item.qs(".indicator")?.removeClass("main");
+ item.qs(".buttons")?.empty();
item
- .find(".buttons")
- .append(
+ .qs(".buttons")
+ ?.appendHtml(
`
`,
);
- animate(item.find(".rewards")[0] as HTMLElement, {
+ const itemToAnimate = item.qsr(".rewards");
+ animate(itemToAnimate.native, {
opacity: 0,
height: 0,
marginTop: 0,
duration: 250,
onComplete: () => {
- item.find(".rewards").remove();
+ itemToAnimate.remove();
},
});
}
function updateClaimDeleteAllButton(): void {
- const claimAllButton = $("#alertsPopup .accountAlerts .claimAll");
- const deleteAllButton = $("#alertsPopup .accountAlerts .deleteAll");
+ const claimAllButton = alertsPopupEl.qs(".accountAlerts .claimAll");
+ const deleteAllButton = alertsPopupEl.qs(".accountAlerts .deleteAll");
- claimAllButton.addClass("hidden");
- deleteAllButton.addClass("hidden");
+ claimAllButton?.hide();
+ deleteAllButton?.hide();
if (accountAlerts.length > 0) {
let rewardsCount = 0;
for (const ie of accountAlerts) {
@@ -369,17 +386,17 @@ function updateClaimDeleteAllButton(): void {
}
if (rewardsCount > 0) {
- claimAllButton.removeClass("hidden");
+ claimAllButton?.show();
} else {
- deleteAllButton.removeClass("hidden");
+ deleteAllButton?.show();
}
}
if (mailToDelete.length === accountAlerts.length) {
- deleteAllButton.addClass("hidden");
+ deleteAllButton?.hide();
}
}
-$("header nav .showAlerts").on("click", () => {
+qs("header nav .showAlerts")?.on("click", () => {
void show();
});
@@ -404,7 +421,7 @@ AuthEvent.subscribe((event) => {
accountAlerts = [];
mailToMarkRead = [];
mailToDelete = [];
- $("#alertsPopup .accountAlerts .list").empty();
+ accountAlertsListEl.empty();
}
});
@@ -429,7 +446,7 @@ const modal = new AnimatedModal({
hide();
},
setup: async (): Promise
=> {
- $("#alertsPopup .accountAlerts").on("click", ".claimAll", () => {
+ alertsPopupEl.qs(".accountAlerts")?.onChild("click", ".claimAll", () => {
for (const ie of accountAlerts) {
if (!ie.read && !mailToMarkRead.includes(ie.id)) {
markReadAlert(ie.id);
@@ -437,7 +454,7 @@ const modal = new AnimatedModal({
}
});
- $("#alertsPopup .accountAlerts").on("click", ".deleteAll", () => {
+ alertsPopupEl.qs(".accountAlerts")?.onChild("click", ".deleteAll", () => {
for (const ie of accountAlerts) {
if (!mailToDelete.includes(ie.id)) {
deleteAlert(ie.id);
@@ -445,30 +462,38 @@ const modal = new AnimatedModal({
}
});
- $("#alertsPopup .mobileClose").on("click", () => {
+ alertsPopupEl.qs(".mobileClose")?.on("click", () => {
hide();
});
- $("#alertsPopup .accountAlerts .list").on(
- "click",
- ".item .buttons .deleteAlert",
- (e) => {
- const id = $(e.currentTarget)
- .closest(".item")
- .attr("data-id") as string;
+ alertsPopupEl
+ .qs(".accountAlerts .list")
+ ?.onChild("click", ".item .buttons .deleteAlert", (e) => {
+ const id = (e.target as HTMLElement | null)
+ ?.closest(".item")
+ ?.getAttribute("data-id")
+ ?.toString();
+
+ if (id === undefined) {
+ throw new Error("Alert ID is undefined");
+ }
+
deleteAlert(id);
- },
- );
+ });
+
+ alertsPopupEl
+ .qs(".accountAlerts .list")
+ ?.onChild("click", ".item .buttons .markReadAlert", (e) => {
+ const id = (e.target as HTMLElement | null)
+ ?.closest(".item")
+ ?.getAttribute("data-id")
+ ?.toString();
+
+ if (id === undefined) {
+ throw new Error("Alert ID is undefined");
+ }
- $("#alertsPopup .accountAlerts .list").on(
- "click",
- ".item .buttons .markReadAlert",
- (e) => {
- const id = $(e.currentTarget)
- .closest(".item")
- .attr("data-id") as string;
markReadAlert(id);
- },
- );
+ });
},
});
diff --git a/frontend/src/ts/elements/character-counter.ts b/frontend/src/ts/elements/character-counter.ts
index b118cd59a372..550fff6cfade 100644
--- a/frontend/src/ts/elements/character-counter.ts
+++ b/frontend/src/ts/elements/character-counter.ts
@@ -1,27 +1,40 @@
+import { createElementWithUtils, ElementWithUtils } from "../utils/dom";
+
export class CharacterCounter {
- private textareaElement: JQuery;
- private parentElement: JQuery;
- private counterElement: JQuery;
+ private textareaElement: ElementWithUtils;
+ private parentElement: ElementWithUtils;
+ private counterElement: ElementWithUtils;
private maxLength: number;
- constructor(textareaElement: JQuery, maxLength: number) {
+ constructor(
+ textareaElement: ElementWithUtils,
+ maxLength: number,
+ ) {
this.textareaElement = textareaElement;
this.maxLength = maxLength;
- this.textareaElement.attr("maxlength", this.maxLength.toString());
+ this.textareaElement.setAttribute("maxlength", this.maxLength.toString());
- // Wrap the textarea element in a div if not already wrapped
- if (!this.textareaElement.parent().hasClass("textareaWithCounter")) {
- $(this.textareaElement).wrap(`
`);
+ const textAreaParent = this.textareaElement.getParent();
+ if (!textAreaParent) {
+ // Wrap the textarea element in a div if not already wrapped
+ const wrapper = this.textareaElement?.wrapWith(
+ `
`,
+ );
+ this.parentElement = wrapper;
+ } else {
+ this.parentElement = textAreaParent;
}
- this.parentElement = $(this.textareaElement).parent(".textareaWithCounter");
- // Create the counter element if it doesn't exist
- if (this.parentElement.find(".char-counter").length === 0) {
- this.counterElement = $(` `);
- this.parentElement.append(this.counterElement);
+ const counterElement = this.parentElement.qs(".char-counter");
+ if (counterElement !== null) {
+ this.counterElement = counterElement;
} else {
- this.counterElement = this.parentElement.find(".char-counter");
+ const counterElement = createElementWithUtils("span", {
+ classList: ["char-counter"],
+ });
+ this.parentElement.append(counterElement);
+ this.counterElement = counterElement;
}
this.updateCounter();
@@ -30,9 +43,9 @@ export class CharacterCounter {
private updateCounter(): void {
const maxLength = this.maxLength;
- const currentLength = (this.textareaElement.val() as string).length;
+ const currentLength = (this.textareaElement.getValue() ?? "").length;
const remaining = maxLength - currentLength;
- this.counterElement.text(`${currentLength}/${maxLength}`);
+ this.counterElement.setText(`${currentLength}/${maxLength}`);
const remainingPercentage = (remaining / this.maxLength) * 100;
diff --git a/frontend/src/ts/elements/custom-background-filter.ts b/frontend/src/ts/elements/custom-background-filter.ts
index 822b04da7991..fc764871ba32 100644
--- a/frontend/src/ts/elements/custom-background-filter.ts
+++ b/frontend/src/ts/elements/custom-background-filter.ts
@@ -2,6 +2,11 @@ import { CustomBackgroundFilter } from "@monkeytype/schemas/configs";
import * as UpdateConfig from "../config";
import * as ConfigEvent from "../observables/config-event";
import { debounce } from "throttle-debounce";
+import { qs, qsr } from "../utils/dom";
+
+const section = qsr(
+ ".pageSettings .section[data-config-name='customBackgroundFilter']",
+);
const filters = {
blur: {
@@ -45,37 +50,31 @@ export function apply(): void {
top: `-${filters.blur.value * 2}rem`,
position: "absolute",
};
- $(".customBackground img").css(css);
+ qs(".customBackground img")?.setStyle(css);
}
function syncSliders(): void {
- $(".section[data-config-name='customBackgroundFilter'] .blur input").val(
- filters.blur.value,
- );
- $(
- ".section[data-config-name='customBackgroundFilter'] .brightness input",
- ).val(filters.brightness.value);
- $(".section[data-config-name='customBackgroundFilter'] .saturate input").val(
- filters.saturate.value,
- );
- $(".section[data-config-name='customBackgroundFilter'] .opacity input").val(
- filters.opacity.value,
- );
+ section
+ .qs(".blur input")
+ ?.setValue(filters.blur.value.toString());
+ section
+ .qs(".brightness input")
+ ?.setValue(filters.brightness.value.toString());
+ section
+ .qs(".saturate input")
+ ?.setValue(filters.saturate.value.toString());
+ section
+ .qs(".opacity input")
+ ?.setValue(filters.opacity.value.toString());
}
function updateNumbers(): void {
- $(".section[data-config-name='customBackgroundFilter'] .blur .value").html(
- filters.blur.value.toFixed(1),
- );
- $(
- ".section[data-config-name='customBackgroundFilter'] .brightness .value",
- ).html(filters.brightness.value.toFixed(1));
- $(
- ".section[data-config-name='customBackgroundFilter'] .saturate .value",
- ).html(filters.saturate.value.toFixed(1));
- $(".section[data-config-name='customBackgroundFilter'] .opacity .value").html(
- filters.opacity.value.toFixed(1),
- );
+ section.qs(".blur .value")?.setHtml(filters.blur.value.toFixed(1));
+ section
+ .qs(".brightness .value")
+ ?.setHtml(filters.brightness.value.toFixed(1));
+ section.qs(".saturate .value")?.setHtml(filters.saturate.value.toFixed(1));
+ section.qs(".opacity .value")?.setHtml(filters.opacity.value.toFixed(1));
}
export function updateUI(): void {
@@ -91,64 +90,41 @@ function loadConfig(config: CustomBackgroundFilter): void {
updateUI();
}
-$(".section[data-config-name='customBackgroundFilter'] .blur input").on(
- "input",
- () => {
- filters.blur.value = parseFloat(
- $(
- ".section[data-config-name='customBackgroundFilter'] .blur input",
- ).val() as string,
- );
- updateNumbers();
- apply();
- },
-);
+section.qs(".blur input")?.on("input", () => {
+ filters.blur.value = parseFloat(
+ section.qs(".blur input")?.getValue() ?? "0",
+ );
+ updateNumbers();
+ apply();
+});
-$(".section[data-config-name='customBackgroundFilter'] .brightness input").on(
- "input",
- () => {
- filters.brightness.value = parseFloat(
- $(
- ".section[data-config-name='customBackgroundFilter'] .brightness input",
- ).val() as string,
- );
- updateNumbers();
- apply();
- },
-);
+section.qs(".brightness input")?.on("input", () => {
+ filters.brightness.value = parseFloat(
+ section.qs(".brightness input")?.getValue() ?? "1",
+ );
+ updateNumbers();
+ apply();
+});
-$(".section[data-config-name='customBackgroundFilter'] .saturate input").on(
- "input",
- () => {
- filters.saturate.value = parseFloat(
- $(
- ".section[data-config-name='customBackgroundFilter'] .saturate input",
- ).val() as string,
- );
- updateNumbers();
- apply();
- },
-);
+section.qs(".saturate input")?.on("input", () => {
+ filters.saturate.value = parseFloat(
+ section.qs(".saturate input")?.getValue() ?? "1",
+ );
+ updateNumbers();
+ apply();
+});
-$(".section[data-config-name='customBackgroundFilter'] .opacity input").on(
- "input",
- () => {
- filters.opacity.value = parseFloat(
- $(
- ".section[data-config-name='customBackgroundFilter'] .opacity input",
- ).val() as string,
- );
- updateNumbers();
- apply();
- },
-);
+section.qs(".opacity input")?.on("input", () => {
+ filters.opacity.value = parseFloat(
+ section.qs(".opacity input")?.getValue() ?? "1",
+ );
+ updateNumbers();
+ apply();
+});
-$(".section[data-config-name='customBackgroundFilter'] input").on(
- "input",
- () => {
- debouncedSave();
- },
-);
+section.qsa("input")?.on("input", () => {
+ debouncedSave();
+});
const debouncedSave = debounce(2000, async () => {
const arr = Object.keys(filters).map(
diff --git a/frontend/src/ts/elements/input-indicator.ts b/frontend/src/ts/elements/input-indicator.ts
index a5c970dcf591..56b65fc3e6a1 100644
--- a/frontend/src/ts/elements/input-indicator.ts
+++ b/frontend/src/ts/elements/input-indicator.ts
@@ -1,3 +1,5 @@
+import { ElementWithUtils } from "../utils/dom";
+
type InputIndicatorOption = {
icon: string;
spinIcon?: true;
@@ -6,18 +8,20 @@ type InputIndicatorOption = {
};
export class InputIndicator {
- private inputElement: JQuery | HTMLInputElement;
- private parentElement: JQuery;
+ private inputElement: ElementWithUtils;
+ private parentElement: ElementWithUtils;
private options: Record;
private currentStatus: keyof typeof this.options | null;
constructor(
- inputElement: JQuery | HTMLInputElement,
+ inputElement: ElementWithUtils,
options: Record,
) {
this.inputElement = inputElement;
- $(this.inputElement).wrap(`
`);
- this.parentElement = $(this.inputElement).parent(".inputAndIndicator");
+ const wrapper = this.inputElement.wrapWith(
+ `
`,
+ );
+ this.parentElement = wrapper;
this.options = options;
this.currentStatus = null;
@@ -46,13 +50,13 @@ export class InputIndicator {
indicator += ` `;
- this.parentElement.append(indicator);
+ this.parentElement.appendHtml(indicator);
}
hide(): void {
- this.parentElement.find(".statusIndicator div").addClass("hidden");
+ this.parentElement.qsa(".statusIndicator div")?.hide();
this.currentStatus = null;
- $(this.inputElement).css("padding-right", "0.5em");
+ this.inputElement.setStyle({ paddingRight: "0.5em" });
}
show(optionId: keyof typeof this.options, messageOverride?: string): void {
@@ -60,21 +64,21 @@ export class InputIndicator {
this.currentStatus = optionId;
- const indicator = this.parentElement.find(`[data-option-id="${optionId}"]`);
+ const indicator = this.parentElement.qs(`[data-option-id="${optionId}"]`);
- indicator.removeClass("hidden");
+ indicator?.show();
if (messageOverride !== undefined && messageOverride !== "") {
if (messageOverride.length > 20) {
- indicator.attr("data-balloon-length", "large");
+ indicator?.setAttribute("data-balloon-length", "large");
} else {
- indicator.removeAttr("data-balloon-length");
+ indicator?.removeAttribute("data-balloon-length");
}
- indicator.attr("aria-label", messageOverride);
+ indicator?.setAttribute("aria-label", messageOverride);
}
- $(this.inputElement).css("padding-right", "2.1em");
- this.parentElement.attr("data-indicator-status", optionId);
+ this.inputElement.setStyle({ paddingRight: "2.1em" });
+ this.parentElement.setAttribute("data-indicator-status", optionId);
}
get(): keyof typeof this.options | null {
diff --git a/frontend/src/ts/elements/input-validation.ts b/frontend/src/ts/elements/input-validation.ts
index 33caf8d9c3da..0e755f9c44b3 100644
--- a/frontend/src/ts/elements/input-validation.ts
+++ b/frontend/src/ts/elements/input-validation.ts
@@ -8,6 +8,7 @@ import {
} from "@monkeytype/schemas/configs";
import Config, * as UpdateConfig from "../config";
import * as Notifications from "../elements/notifications";
+import { ElementWithUtils } from "../utils/dom";
export type ValidationResult = {
status: "checking" | "success" | "failed" | "warning";
@@ -122,13 +123,13 @@ export function createInputEventHandler
(
if (callIsValid === undefined) {
callback({ status: "success" });
//call original handler if defined
- originalInput.oninput?.(e);
+ originalInput.oninput?.(e as InputEvent);
return;
}
await callIsValid(originalInput, currentValue, checkValue as T);
//call original handler if defined
- originalInput.oninput?.(e);
+ originalInput.oninput?.(e as InputEvent);
};
}
@@ -142,17 +143,25 @@ export type ValidationOptions = (T extends string
callback?: (result: ValidationResult) => void;
};
-export class ValidatedHtmlInputElement {
- public native: HTMLInputElement;
+export class ValidatedHtmlInputElement<
+ T = string,
+> extends ElementWithUtils {
private indicator: InputIndicator;
private currentStatus: ValidationResult = {
status: "checking",
};
- constructor(inputElement: HTMLInputElement, options: ValidationOptions) {
- this.native = inputElement;
+ constructor(
+ inputElement: HTMLInputElement | ElementWithUtils,
+ options: ValidationOptions,
+ ) {
+ super(
+ inputElement instanceof ElementWithUtils
+ ? inputElement.native
+ : inputElement,
+ );
- this.indicator = new InputIndicator(inputElement, {
+ this.indicator = new InputIndicator(this, {
success: {
icon: "fa-check",
level: 1,
@@ -188,33 +197,32 @@ export class ValidatedHtmlInputElement {
"inputValueConvert" in options ? options.inputValueConvert : undefined,
);
- inputElement.addEventListener("input", handler);
+ this.on("input", handler);
}
getValidationResult(): ValidationResult {
return this.currentStatus;
}
- setValue(val: string | null): this {
- this.native.value = val ?? "";
+
+ override setValue(val: string | null): this {
if (val === null) {
this.indicator.hide();
this.currentStatus = { status: "checking" };
} else {
- this.native.dispatchEvent(new Event("input"));
+ super.setValue(val);
+ this.dispatch("input");
}
return this;
}
- getValue(): string {
- return this.native.value;
- }
+
triggerValidation(): void {
- this.native.dispatchEvent(new Event("input"));
+ this.dispatch("input");
}
}
export type ConfigInputOptions = {
- input: HTMLInputElement | null;
+ input: ElementWithUtils;
configName: K;
validation?: (T extends string
? Omit, "schema">
@@ -242,10 +250,6 @@ export function handleConfigInput({
configName,
validation,
}: ConfigInputOptions): void {
- if (input === null) {
- throw new Error(`Failed to find input element for ${configName}`);
- }
-
const inputValueConvert =
validation !== undefined && "inputValueConvert" in validation
? validation.inputValueConvert
@@ -269,23 +273,23 @@ export function handleConfigInput({
let shakeTimeout: null | NodeJS.Timeout;
const handleStore = (): void => {
- if (input.value === "" && (validation?.resetIfEmpty ?? true)) {
+ if (input.getValue() === "" && (validation?.resetIfEmpty ?? true)) {
//use last config value, clear validation
- input.value = new String(Config[configName]).toString();
- input.dispatchEvent(new Event("input"));
+ input.setValue(new String(Config[configName]).toString());
+ input.dispatch("input");
}
if (status === "failed") {
- input.parentElement?.classList.add("hasError");
+ input.getParent()?.addClass("hasError");
if (shakeTimeout !== null) {
clearTimeout(shakeTimeout);
}
shakeTimeout = setTimeout(() => {
- input.parentElement?.classList.remove("hasError");
+ input.getParent()?.removeClass("hasError");
}, 500);
return;
}
- const value = (inputValueConvert?.(input.value) ??
- input.value) as ConfigType[T];
+ const value = (inputValueConvert?.(input.getValue() ?? "") ??
+ input.getValue()) as ConfigType[T];
if (Config[configName] === value) {
return;
@@ -299,10 +303,10 @@ export function handleConfigInput({
}
};
- input.addEventListener("keypress", (e) => {
+ input.on("keypress", (e) => {
if (e.key === "Enter") {
handleStore();
}
});
- input.addEventListener("focusout", (e) => handleStore());
+ input.on("focusout", (e) => handleStore());
}
diff --git a/frontend/src/ts/elements/keymap.ts b/frontend/src/ts/elements/keymap.ts
index afbe14c94e79..555bcbfdcd6f 100644
--- a/frontend/src/ts/elements/keymap.ts
+++ b/frontend/src/ts/elements/keymap.ts
@@ -16,9 +16,11 @@ import { getActiveFunboxNames } from "../test/funbox/list";
import { areSortedArraysEqual } from "../utils/arrays";
import { LayoutObject } from "@monkeytype/schemas/layouts";
import { animate } from "animejs";
+import { ElementsWithUtils, qsr } from "../utils/dom";
import { requestDebouncedAnimationFrame } from "../utils/debounced-animation-frame";
export const keyDataDelimiter = "\uE000";
+const keymap = qsr("#keymap");
const stenoKeys: LayoutObject = {
keymapShowTopRow: true,
@@ -59,16 +61,18 @@ const stenoKeys: LayoutObject = {
},
};
-function findKeyElements(char: string): JQuery {
+function findKeyElements(char: string): ElementsWithUtils | null {
+ if (char === "\n") return null;
+
if (char === " ") {
- return $("#keymap .keySpace");
+ return keymap.qsa(".keySpace");
}
if (char === '"') {
- return $(`#keymap .keymapKey[data-key*='${char}']`);
+ return keymap.qsa(`.keymapKey[data-key*='${char}']`);
}
- return $(`#keymap .keymapKey[data-key*="${char}"]`);
+ return keymap.qsa(`.keymapKey[data-key*="${char}"]`);
}
function highlightKey(currentKey: string): void {
@@ -85,6 +89,8 @@ function highlightKey(currentKey: string): void {
}
const $target = findKeyElements(currentKey);
+ if ($target === null || $target.length === 0) return;
+
$target.addClass("activeKey");
} catch (e) {
if (e instanceof Error) {
@@ -97,10 +103,8 @@ function highlightKey(currentKey: string): void {
async function flashKey(key: string, correct?: boolean): Promise {
if (key === undefined) return;
requestDebouncedAnimationFrame(`keymap.flashKey.${key}`, async () => {
- const $target = findKeyElements(key);
-
- const elements = $target.toArray();
- if (elements.length === 0) return;
+ const elements = findKeyElements(key);
+ if (elements === null || elements.length === 0) return;
const themecolors = await ThemeColors.getAll();
@@ -125,7 +129,7 @@ async function flashKey(key: string, correct?: boolean): Promise {
};
}
- animate(elements, {
+ animate(elements.native, {
color: [startingStyle.color, themecolors.sub],
backgroundColor: [startingStyle.backgroundColor, themecolors.subAlt],
borderColor: [startingStyle.borderColor, themecolors.sub],
@@ -137,11 +141,11 @@ async function flashKey(key: string, correct?: boolean): Promise {
}
export function hide(): void {
- $("#keymap").addClass("hidden");
+ keymap.addClass("hidden");
}
export function show(): void {
- $("#keymap").removeClass("hidden");
+ keymap.removeClass("hidden");
}
function buildRow(options: {
@@ -459,16 +463,18 @@ export async function refresh(): Promise {
});
}
- $("#keymap").html(keymapElement);
-
- $("#keymap").removeClass("staggered");
- $("#keymap").removeClass("matrix");
- $("#keymap").removeClass("split");
- $("#keymap").removeClass("split_matrix");
- $("#keymap").removeClass("alice");
- $("#keymap").removeClass("steno");
- $("#keymap").removeClass("steno_matrix");
- $("#keymap").addClass(Config.keymapStyle);
+ keymap.setHtml(keymapElement);
+
+ keymap.removeClass([
+ "staggered",
+ "matrix",
+ "split",
+ "split_matrix",
+ "alice",
+ "steno",
+ "steno_matrix",
+ ]);
+ keymap.addClass(Config.keymapStyle);
} catch (e) {
if (e instanceof Error) {
console.log(
@@ -591,12 +597,12 @@ let ignoreConfigEvent = false;
ConfigEvent.subscribe((eventKey) => {
const handleMode = (): void => {
- $(".activeKey").removeClass("activeKey");
- $(".keymapKey").attr("style", "");
+ keymap.qsa(".activeKey").removeClass("activeKey");
+ keymap.qsa(".keymapKey").setAttribute("style", "");
Config.keymapMode === "off" ? hide() : show();
};
const handleSize = (): void => {
- $("#keymap").css("zoom", Config.keymapSize);
+ keymap.setStyle({ zoom: Config.keymapSize.toString() });
};
const handleLegendStyle = (): void => {
let style = Config.keymapLegendStyle;
@@ -604,26 +610,28 @@ ConfigEvent.subscribe((eventKey) => {
// Remove existing styles
const keymapLegendStyles = ["lowercase", "uppercase", "blank", "dynamic"];
keymapLegendStyles.forEach((name) => {
- $(".keymapLegendStyle").removeClass(name);
+ keymap.qsa(".keymapLegendStyle").removeClass(name);
});
style = style || "lowercase";
// Mutate the keymap in the DOM, if it exists.
// 1. Remove everything
- $(".keymapKey > .letter").css("display", "");
- $(".keymapKey > .letter").css("text-transform", "");
+ keymap.qsa(".keymapKey > .letter").setStyle({ display: "" });
+ keymap.qsa(".keymapKey > .letter").setStyle({ textTransform: "" });
// 2. Append special styles onto the DOM elements
if (style === "uppercase") {
- $(".keymapKey > .letter").css("text-transform", "capitalize");
+ keymap
+ .qsa(".keymapKey > .letter")
+ .setStyle({ textTransform: "capitalize" });
}
if (style === "blank") {
- $(".keymapKey > .letter").css("display", "none");
+ keymap.qsa(".keymapKey > .letter").setStyle({ display: "none" });
}
// Update and save to cookie for persistence
- $(".keymapLegendStyle").addClass(style);
+ keymap.qsa(".keymapLegendStyle").addClass(style);
};
if (eventKey === "fullConfigChange") {
@@ -667,7 +675,7 @@ KeymapEvent.subscribe((mode, key, correct) => {
}
});
-$(document).on("keydown", (e) => {
+document.addEventListener("keydown", (e) => {
if (
Config.keymapLegendStyle === "dynamic" &&
(e.code === "ShiftLeft" ||
@@ -679,7 +687,7 @@ $(document).on("keydown", (e) => {
}
});
-$(document).on("keyup", (e) => {
+document.addEventListener("keyup", (e) => {
if (
Config.keymapLegendStyle === "dynamic" &&
(e.code === "ShiftLeft" ||
diff --git a/frontend/src/ts/elements/loader.ts b/frontend/src/ts/elements/loader.ts
index 16a400d36cd1..929fcdcb5a51 100644
--- a/frontend/src/ts/elements/loader.ts
+++ b/frontend/src/ts/elements/loader.ts
@@ -1,7 +1,8 @@
import { animate, JSAnimation } from "animejs";
import { requestDebouncedAnimationFrame } from "../utils/debounced-animation-frame";
+import { qsr } from "../utils/dom";
-const element = document.querySelector("#backgroundLoader") as HTMLElement;
+const element = qsr("#backgroundLoader");
let showAnim: JSAnimation | null = null;
export function show(instant = false): void {
@@ -11,7 +12,7 @@ export function show(instant = false): void {
duration: 125,
delay: instant ? 0 : 125,
onBegin: () => {
- element.classList.remove("hidden");
+ element.removeClass("hidden");
},
});
});
@@ -24,7 +25,7 @@ export function hide(): void {
opacity: 0,
duration: 125,
onComplete: () => {
- element.classList.add("hidden");
+ element.addClass("hidden");
},
});
});
diff --git a/frontend/src/ts/elements/modes-notice.ts b/frontend/src/ts/elements/modes-notice.ts
index d526d8151a39..0d22d57aeb42 100644
--- a/frontend/src/ts/elements/modes-notice.ts
+++ b/frontend/src/ts/elements/modes-notice.ts
@@ -11,6 +11,7 @@ import { getLanguageDisplayString } from "../utils/strings";
import Format from "../utils/format";
import { getActiveFunboxes, getActiveFunboxNames } from "../test/funbox/list";
import { escapeHTML, getMode2 } from "../utils/misc";
+import { qsr } from "../utils/dom";
ConfigEvent.subscribe((eventKey) => {
const configKeys: ConfigEvent.ConfigEventKey[] = [
@@ -37,32 +38,34 @@ ConfigEvent.subscribe((eventKey) => {
}
});
+const testModesNotice = qsr(".pageTest #testModesNotice");
+
export async function update(): Promise {
- $(".pageTest #testModesNotice").empty();
+ testModesNotice.empty();
if (TestState.isRepeated && Config.mode !== "quote") {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` repeated
`,
);
}
if (!TestState.savingEnabled) {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` saving disabled
`,
);
}
if (TestWords.hasTab) {
if (Config.quickRestart === "esc") {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` shift + tab to open commandline
`,
);
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` shift + esc to restart
`,
);
}
if (Config.quickRestart === "tab") {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` shift + tab to restart
`,
);
}
@@ -72,7 +75,7 @@ export async function update(): Promise {
(TestWords.hasNewline || Config.funbox.includes("58008")) &&
Config.quickRestart === "enter"
) {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` shift + enter to restart
`,
);
}
@@ -80,7 +83,7 @@ export async function update(): Promise {
const customTextName = CustomTextState.getCustomTextName();
const isLong = CustomTextState.isCustomTextLong();
if (Config.mode === "custom" && customTextName !== "" && isLong) {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` ${escapeHTML(
customTextName,
)} (shift + enter to save progress)
`,
@@ -88,13 +91,13 @@ export async function update(): Promise {
}
if (TestState.activeChallenge) {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` ${TestState.activeChallenge.display}
`,
);
}
if (Config.mode === "zen") {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` shift + enter to finish zen
`,
);
}
@@ -102,7 +105,7 @@ export async function update(): Promise {
const usingPolyglot = getActiveFunboxNames().includes("polyglot");
if (Config.mode !== "zen" && !usingPolyglot) {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` ${getLanguageDisplayString(
Config.language,
Config.mode === "quote",
@@ -118,29 +121,29 @@ export async function update(): Promise {
})
.join(", ");
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` ${languages} `,
);
}
if (Config.difficulty === "expert") {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` expert `,
);
} else if (Config.difficulty === "master") {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` master `,
);
}
if (Config.blindMode) {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` blind `,
);
}
if (Config.lazyMode) {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` lazy `,
);
}
@@ -154,7 +157,7 @@ export async function update(): Promise {
suffix: ` ${Config.typingSpeedUnit}`,
});
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` ${
Config.paceCaret === "average"
? "average"
@@ -186,7 +189,7 @@ export async function update(): Promise {
const text = `${avgWPMText} ${avgAccText}`.trim();
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` avg: ${text} `,
);
}
@@ -217,13 +220,13 @@ export async function update(): Promise {
})} ${pb?.acc}% acc`;
}
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` ${str} `,
);
}
if (Config.minWpm !== "off") {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` min ${Format.typingSpeed(
Config.minWpmCustomSpeed,
{ showDecimalPlaces: false, suffix: ` ${Config.typingSpeedUnit}` },
@@ -232,13 +235,13 @@ export async function update(): Promise {
}
if (Config.minAcc !== "off") {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` min ${Config.minAccCustom}% acc `,
);
}
if (Config.minBurst !== "off") {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` min ${Format.typingSpeed(
Config.minBurstCustomSpeed,
{ showDecimalPlaces: false },
@@ -249,7 +252,7 @@ export async function update(): Promise {
}
if (Config.funbox.length > 0) {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` ${Config.funbox
.map((it) => it.replace(/_/g, " "))
.join(", ")} `,
@@ -257,24 +260,24 @@ export async function update(): Promise {
}
if (Config.confidenceMode === "on") {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` confidence `,
);
}
if (Config.confidenceMode === "max") {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` max confidence `,
);
}
if (Config.stopOnError !== "off") {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` stop on ${Config.stopOnError} `,
);
}
if (Config.layout !== "default") {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` emulating ${Config.layout.replace(
/_/g,
" ",
@@ -283,7 +286,7 @@ export async function update(): Promise {
}
if (Config.oppositeShiftMode !== "off") {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` opposite shift${
Config.oppositeShiftMode === "keymap" ? " (keymap)" : ""
} `,
@@ -299,7 +302,7 @@ export async function update(): Promise {
});
if (tagsString !== "") {
- $(".pageTest #testModesNotice").append(
+ testModesNotice.appendHtml(
` ${tagsString.substring(
0,
tagsString.length - 2,
diff --git a/frontend/src/ts/elements/monkey-power.ts b/frontend/src/ts/elements/monkey-power.ts
index 270675037ff4..c5529b4c628e 100644
--- a/frontend/src/ts/elements/monkey-power.ts
+++ b/frontend/src/ts/elements/monkey-power.ts
@@ -3,9 +3,10 @@ import * as SlowTimer from "../states/slow-timer";
import Config from "../config";
import { isSafeNumber } from "@monkeytype/util/numbers";
import { requestDebouncedAnimationFrame } from "../utils/debounced-animation-frame";
+import { ElementWithUtils, qsr } from "../utils/dom";
-const html = document.querySelector("html") as HTMLElement;
-const body = document.body;
+const html = qsr("html");
+const body = qsr("body");
type Particle = {
x: number;
@@ -18,7 +19,7 @@ type Particle = {
type CTX = {
particles: Particle[];
- caret?: HTMLElement;
+ caret?: ElementWithUtils;
canvas?: HTMLCanvasElement;
context2d?: CanvasRenderingContext2D;
rendering: boolean;
@@ -122,7 +123,7 @@ function updateParticle(particle: Particle): void {
}
export function init(): void {
- ctx.caret = document.querySelector("#caret") as HTMLElement;
+ ctx.caret = qsr("#caret");
ctx.canvas = createCanvas();
ctx.context2d = ctx.canvas.getContext("2d") as CanvasRenderingContext2D;
}
@@ -172,13 +173,16 @@ export function reset(immediate = false): void {
delete ctx.resetTimeOut;
clearTimeout(ctx.resetTimeOut);
- body.style.transition = "all .25s, transform 0.8s";
- body.style.transform = `translate(0,0)`;
+ body.setStyle({
+ transition: "all .25s, transform 0.8s",
+ transform: "translate(0,0)",
+ });
setTimeout(
() => {
- body.style.transition = "all .25s, transform .05s";
- html.style.overflow = "inherit";
- html.style.overflowY = "scroll";
+ body.setStyle({
+ transition: "all .25s, transform .05s",
+ });
+ html.setStyle({ overflow: "inherit", overflowY: "scroll" });
},
immediate ? 0 : 1000,
);
@@ -209,21 +213,21 @@ export async function addPower(good = true, extra = false): Promise {
// Shake
if (["3", "4"].includes(Config.monkeyPowerLevel)) {
- html.style.overflow = "hidden";
+ html.setStyle({ overflow: "hidden" });
const shake = [
Math.round(shakeAmount - Math.random() * shakeAmount),
Math.round(shakeAmount - Math.random() * shakeAmount),
];
- body.style.transform = `translate(${shake[0]}px, ${shake[1]}px)`;
+ body.setStyle({ transform: `translate(${shake[0]}px, ${shake[1]}px)` });
if (isSafeNumber(ctx.resetTimeOut)) clearTimeout(ctx.resetTimeOut);
ctx.resetTimeOut = setTimeout(reset, 2000) as unknown as number;
}
// Sparks
- const offset = ctx.caret?.getBoundingClientRect();
+ const offset = ctx.caret?.native.getBoundingClientRect();
const coords = [
offset?.left ?? 0,
- (offset?.top ?? 0) + (ctx.caret?.offsetHeight ?? 0) / 2,
+ (offset?.top ?? 0) + (ctx.caret?.native.offsetHeight ?? 0) / 2,
];
for (
diff --git a/frontend/src/ts/elements/no-css.ts b/frontend/src/ts/elements/no-css.ts
index 003180775bc7..42d41933cb4d 100644
--- a/frontend/src/ts/elements/no-css.ts
+++ b/frontend/src/ts/elements/no-css.ts
@@ -1,6 +1,7 @@
import { envConfig } from "virtual:env-config";
+import { qs } from "../utils/dom";
-$("#nocss .requestedStylesheets").html(
+qs("#nocss .requestedStylesheets")?.setHtml(
"Requested stylesheets: " +
(
[
@@ -12,7 +13,7 @@ $("#nocss .requestedStylesheets").html(
.join(" "),
);
-$("#nocss .requestedJs").html(
+qs("#nocss .requestedJs")?.setHtml(
"Requested Javascript files: " +
([...document.querySelectorAll("script")] as HTMLScriptElement[])
.map((l) => l.src)
@@ -23,7 +24,7 @@ $("#nocss .requestedJs").html(
);
if (window.navigator.userAgent.toLowerCase().includes("mac")) {
- $("#nocss .keys").html(`
+ qs("#nocss .keys")?.setHtml(`
`);
} else {
- $("#nocss .keys").html(`
+ qs("#nocss .keys")?.setHtml(`