refactor: remove JQuery from pages directory (@Leonabcd123) (#7237)

- [x] 404
- [x] about
- [x] account-settings
- [x] account
- [x] friends
- [x] leaderboards
- [x] loading
- [x] login
- [x] page
- [x] profile-search
- [x] profile
- [x] settings
- [x] test

Before merging make sure to load every changed page to check for the qsa
notification.

---------

Co-authored-by: Christian Fehmer <fehmer@users.noreply.github.com>
This commit is contained in:
Leonabcd123 2025-12-21 17:24:07 +02:00 committed by GitHub
parent fd177c9ed4
commit 5d169e933a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 560 additions and 480 deletions

View file

@ -10,11 +10,11 @@ import * as Skeleton from "../utils/skeleton";
import { TypingStats, SpeedHistogram } from "@monkeytype/schemas/public";
import { getNumberWithMagnitude, numberWithSpaces } from "../utils/numbers";
import { tryCatch } from "@monkeytype/util/trycatch";
import { qsr } from "../utils/dom";
import { qs, qsr, onWindowLoad } from "../utils/dom";
function reset(): void {
$(".pageAbout .contributors").empty();
$(".pageAbout .supporters").empty();
qs(".pageAbout .contributors")?.empty();
qs(".pageAbout .supporters")?.empty();
ChartController.globalSpeedHistogram.getDataset("count").data = [];
void ChartController.globalSpeedHistogram.updateColors();
@ -43,11 +43,11 @@ function updateStatsAndHistogram(): void {
end: secondsRounded * 1000,
});
$(".pageAbout #totalTimeTypingStat .val").text(
qs(".pageAbout #totalTimeTypingStat .val")?.setText(
timeTypingDuration.years?.toString() ?? "",
);
$(".pageAbout #totalTimeTypingStat .valSmall").text("years");
$(".pageAbout #totalTimeTypingStat").attr(
qs(".pageAbout #totalTimeTypingStat .valSmall")?.setText("years");
qs(".pageAbout #totalTimeTypingStat")?.setAttribute(
"aria-label",
numberWithSpaces(Math.round(secondsRounded / 3600)) + " hours",
);
@ -56,15 +56,15 @@ function updateStatsAndHistogram(): void {
typingStatsResponseData.testsStarted,
);
$(".pageAbout #totalStartedTestsStat .val").text(
qs(".pageAbout #totalStartedTestsStat .val")?.setText(
startedWithMagnitude.rounded < 10
? startedWithMagnitude.roundedTo2
: startedWithMagnitude.rounded,
? startedWithMagnitude.roundedTo2.toString()
: startedWithMagnitude.rounded.toString(),
);
$(".pageAbout #totalStartedTestsStat .valSmall").text(
qs(".pageAbout #totalStartedTestsStat .valSmall")?.setText(
startedWithMagnitude.orderOfMagnitude,
);
$(".pageAbout #totalStartedTestsStat").attr(
qs(".pageAbout #totalStartedTestsStat")?.setAttribute(
"aria-label",
numberWithSpaces(typingStatsResponseData.testsStarted) + " tests",
);
@ -73,15 +73,15 @@ function updateStatsAndHistogram(): void {
typingStatsResponseData.testsCompleted,
);
$(".pageAbout #totalCompletedTestsStat .val").text(
qs(".pageAbout #totalCompletedTestsStat .val")?.setText(
completedWIthMagnitude.rounded < 10
? completedWIthMagnitude.roundedTo2
: completedWIthMagnitude.rounded,
? completedWIthMagnitude.roundedTo2.toString()
: completedWIthMagnitude.rounded.toString(),
);
$(".pageAbout #totalCompletedTestsStat .valSmall").text(
qs(".pageAbout #totalCompletedTestsStat .valSmall")?.setText(
completedWIthMagnitude.orderOfMagnitude,
);
$(".pageAbout #totalCompletedTestsStat").attr(
qs(".pageAbout #totalCompletedTestsStat")?.setAttribute(
"aria-label",
numberWithSpaces(typingStatsResponseData.testsCompleted) + " tests",
);
@ -212,6 +212,6 @@ export const page = new Page({
},
});
$(() => {
onWindowLoad(() => {
Skeleton.save("pageAbout");
});

View file

@ -35,7 +35,7 @@ import { SnapshotResult } from "../constants/default-snapshot";
import Ape from "../ape";
import { AccountChart } from "@monkeytype/schemas/configs";
import { SortedTableWithLimit } from "../utils/sorted-table";
import { qsr } from "../utils/dom";
import { qs, qsa, qsr, onWindowLoad, ElementWithUtils } from "../utils/dom";
let filterDebug = false;
//toggle filterdebug
@ -62,9 +62,9 @@ function loadMoreLines(lineIndex?: number): void {
visibleTableLines = newVisibleLines;
if (visibleTableLines >= filteredResults.length) {
$(".pageAccount .loadMoreButton").addClass("hidden");
qs(".pageAccount .loadMoreButton")?.hide();
} else {
$(".pageAccount .loadMoreButton").removeClass("hidden");
qs(".pageAccount .loadMoreButton")?.show();
}
historyTable.setLimit(newVisibleLines);
@ -284,7 +284,7 @@ async function fillContent(): Promise<void> {
const typingSpeedUnit = getTypingSpeedUnit(Config.typingSpeedUnit);
filteredResults = [];
$(".pageAccount .history table tbody").empty();
qs(".pageAccount .history table tbody")?.empty();
DB.getSnapshot()?.results?.forEach((result) => {
// totalSeconds += tt;
@ -673,7 +673,7 @@ async function fillContent(): Promise<void> {
historyTable.setData(filteredResults);
$(".pageAccount .group.history table thead tr td:nth-child(2)").text(
qs(".pageAccount .group.history table thead tr td:nth-child(2)")?.setText(
Config.typingSpeedUnit,
);
@ -854,91 +854,95 @@ async function fillContent(): Promise<void> {
}
if (chartData === undefined || chartData.length === 0) {
$(".pageAccount .group.noDataError").removeClass("hidden");
$(".pageAccount .group.chart").addClass("hidden");
$(".pageAccount .group.dailyActivityChart").addClass("hidden");
$(".pageAccount .group.histogramChart").addClass("hidden");
$(".pageAccount .group.aboveHistory").addClass("hidden");
$(".pageAccount .group.history").addClass("hidden");
$(".pageAccount .triplegroup.stats").addClass("hidden");
$(".pageAccount .group.estimatedWordsTyped").addClass("hidden");
qs(".pageAccount .group.noDataError")?.show();
qs(".pageAccount .group.chart")?.hide();
qs(".pageAccount .group.dailyActivityChart")?.hide();
qs(".pageAccount .group.histogramChart")?.hide();
qs(".pageAccount .group.aboveHistory")?.hide();
qs(".pageAccount .group.history")?.hide();
qs(".pageAccount .triplegroup.stats")?.hide();
qs(".pageAccount .group.estimatedWordsTyped")?.hide();
} else {
$(".pageAccount .group.noDataError").addClass("hidden");
$(".pageAccount .group.chart").removeClass("hidden");
$(".pageAccount .group.dailyActivityChart").removeClass("hidden");
$(".pageAccount .group.histogramChart").removeClass("hidden");
$(".pageAccount .group.aboveHistory").removeClass("hidden");
$(".pageAccount .group.history").removeClass("hidden");
$(".pageAccount .triplegroup.stats").removeClass("hidden");
$(".pageAccount .group.estimatedWordsTyped").removeClass("hidden");
qs(".pageAccount .group.noDataError")?.hide();
qs(".pageAccount .group.chart")?.show();
qs(".pageAccount .group.dailyActivityChart")?.show();
qs(".pageAccount .group.histogramChart")?.show();
qs(".pageAccount .group.aboveHistory")?.show();
qs(".pageAccount .group.history")?.show();
qs(".pageAccount .triplegroup.stats")?.show();
qs(".pageAccount .group.estimatedWordsTyped")?.show();
}
$(".pageAccount .timeTotalFiltered .val").text(
qs(".pageAccount .timeTotalFiltered .val")?.setText(
DateTime.secondsToString(Math.round(totalSecondsFiltered), true, true),
);
const speedUnit = Config.typingSpeedUnit;
$(".pageAccount .highestWpm .title").text(`highest ${speedUnit}`);
$(".pageAccount .highestWpm .val").text(Format.typingSpeed(topWpm));
qs(".pageAccount .highestWpm .title")?.setText(`highest ${speedUnit}`);
qs(".pageAccount .highestWpm .val")?.setText(Format.typingSpeed(topWpm));
$(".pageAccount .averageWpm .title").text(`average ${speedUnit}`);
$(".pageAccount .averageWpm .val").text(
qs(".pageAccount .averageWpm .title")?.setText(`average ${speedUnit}`);
qs(".pageAccount .averageWpm .val")?.setText(
Format.typingSpeed(totalWpm / testCount),
);
$(".pageAccount .averageWpm10 .title").text(
qs(".pageAccount .averageWpm10 .title")?.setText(
`average ${speedUnit} (last 10 tests)`,
);
$(".pageAccount .averageWpm10 .val").text(
qs(".pageAccount .averageWpm10 .val")?.setText(
Format.typingSpeed(wpmLast10total / last10),
);
$(".pageAccount .highestRaw .title").text(`highest raw ${speedUnit}`);
$(".pageAccount .highestRaw .val").text(Format.typingSpeed(rawWpm.max));
qs(".pageAccount .highestRaw .title")?.setText(`highest raw ${speedUnit}`);
qs(".pageAccount .highestRaw .val")?.setText(Format.typingSpeed(rawWpm.max));
$(".pageAccount .averageRaw .title").text(`average raw ${speedUnit}`);
$(".pageAccount .averageRaw .val").text(
qs(".pageAccount .averageRaw .title")?.setText(`average raw ${speedUnit}`);
qs(".pageAccount .averageRaw .val")?.setText(
Format.typingSpeed(rawWpm.total / rawWpm.count),
);
$(".pageAccount .averageRaw10 .title").text(
qs(".pageAccount .averageRaw10 .title")?.setText(
`average raw ${speedUnit} (last 10 tests)`,
);
$(".pageAccount .averageRaw10 .val").text(
qs(".pageAccount .averageRaw10 .val")?.setText(
Format.typingSpeed(rawWpm.last10Total / rawWpm.last10Count),
);
$(".pageAccount .highestWpm .mode").html(topMode);
$(".pageAccount .testsTaken .val").text(testCount);
qs(".pageAccount .highestWpm .mode")?.setHtml(topMode);
qs(".pageAccount .testsTaken .val")?.setText(testCount.toString());
$(".pageAccount .highestAcc .val").text(Format.accuracy(topAcc));
$(".pageAccount .avgAcc .val").text(Format.accuracy(totalAcc / testCount));
$(".pageAccount .avgAcc10 .val").text(Format.accuracy(totalAcc10 / last10));
qs(".pageAccount .highestAcc .val")?.setText(Format.accuracy(topAcc));
qs(".pageAccount .avgAcc .val")?.setText(
Format.accuracy(totalAcc / testCount),
);
qs(".pageAccount .avgAcc10 .val")?.setText(
Format.accuracy(totalAcc10 / last10),
);
if (totalCons === 0 || totalCons === undefined) {
$(".pageAccount .avgCons .val").text("-");
$(".pageAccount .avgCons10 .val").text("-");
qs(".pageAccount .avgCons .val")?.setText("-");
qs(".pageAccount .avgCons10 .val")?.setText("-");
} else {
$(".pageAccount .highestCons .val").text(Format.percentage(topCons));
qs(".pageAccount .highestCons .val")?.setText(Format.percentage(topCons));
$(".pageAccount .avgCons .val").text(
qs(".pageAccount .avgCons .val")?.setText(
Format.percentage(totalCons / consCount),
);
$(".pageAccount .avgCons10 .val").text(
qs(".pageAccount .avgCons10 .val")?.setText(
Format.percentage(totalCons10 / Math.min(last10, consCount)),
);
}
$(".pageAccount .testsStarted .val").text(`${testCount + testRestarts}`);
$(".pageAccount .testsCompleted .val").text(
qs(".pageAccount .testsStarted .val")?.setText(`${testCount + testRestarts}`);
qs(".pageAccount .testsCompleted .val")?.setText(
`${testCount}(${Math.floor(
(testCount / (testCount + testRestarts)) * 100,
)}%)`,
);
$(".pageAccount .testsCompleted .avgres").text(`
qs(".pageAccount .testsCompleted .avgres")?.setText(`
${(testRestarts / testCount).toFixed(1)} restarts per completed test
`);
@ -949,13 +953,15 @@ async function fillContent(): Promise<void> {
const wpmChange = trend[1][1] - trend[0][1];
const wpmChangePerHour = wpmChange * (3600 / totalSecondsFiltered);
const plus = wpmChangePerHour > 0 ? "+" : "";
$(".pageAccount .group.chart .below .text").text(
qs(".pageAccount .group.chart .below .text")?.setText(
`Speed change per hour spent typing: ${
plus + Format.typingSpeed(wpmChangePerHour, { showDecimalPlaces: true })
} ${Config.typingSpeedUnit}`,
);
}
$(".pageAccount .estimatedWordsTyped .val").text(totalEstimatedWords);
qs(".pageAccount .estimatedWordsTyped .val")?.setText(
totalEstimatedWords.toString(),
);
if (chartData.length || accChartData.length) {
ChartController.updateAccountChartButtons();
@ -967,7 +973,7 @@ async function fillContent(): Promise<void> {
ChartController.accountActivity.update();
ChartController.accountHistogram.update();
Focus.set(false);
$(".page.pageAccount").css("height", "unset"); //weird safari fix
qs(".page.pageAccount")?.setStyle({ height: "unset" }); //weird safari fix
setTimeout(() => {
Profile.updateNameFontSize("account");
}, 0);
@ -1012,56 +1018,56 @@ export function updateTagsForResult(resultId: string, tagIds: string[]): void {
}
}
const el = $(
const el = qs(
`.pageAccount .resultEditTagsButton[data-result-id='${resultId}']`,
);
el.attr("data-tags", JSON.stringify(tagIds));
el?.setAttribute("data-tags", JSON.stringify(tagIds));
if (tagIds.length > 0) {
el.attr("aria-label", tagNames.join(", "));
el.addClass("active");
el?.setAttribute("aria-label", tagNames.join(", "));
el?.addClass("active");
if (tagIds.length > 1) {
el.html(`<i class="fas fa-fw fa-tags"></i>`);
el?.setHtml(`<i class="fas fa-fw fa-tags"></i>`);
} else {
el.html(`<i class="fas fa-fw fa-tag"></i>`);
el?.setHtml(`<i class="fas fa-fw fa-tag"></i>`);
}
} else {
el.attr("aria-label", "no tags");
el.removeClass("active");
el.html(`<i class="fas fa-fw fa-tag"></i>`);
el?.setAttribute("aria-label", "no tags");
el?.removeClass("active");
el?.setHtml(`<i class="fas fa-fw fa-tag"></i>`);
}
}
$(".pageAccount button.toggleResultsOnChart").on("click", () => {
qs(".pageAccount button.toggleResultsOnChart")?.on("click", () => {
const newValue = [...Config.accountChart] as AccountChart;
newValue[0] = newValue[0] === "on" ? "off" : "on";
setConfig("accountChart", newValue);
});
$(".pageAccount button.toggleAccuracyOnChart").on("click", () => {
qs(".pageAccount button.toggleAccuracyOnChart")?.on("click", () => {
const newValue = [...Config.accountChart] as AccountChart;
newValue[1] = newValue[1] === "on" ? "off" : "on";
setConfig("accountChart", newValue);
});
$(".pageAccount button.toggleAverage10OnChart").on("click", () => {
qs(".pageAccount button.toggleAverage10OnChart")?.on("click", () => {
const newValue = [...Config.accountChart] as AccountChart;
newValue[2] = newValue[2] === "on" ? "off" : "on";
setConfig("accountChart", newValue);
});
$(".pageAccount button.toggleAverage100OnChart").on("click", () => {
qs(".pageAccount button.toggleAverage100OnChart")?.on("click", () => {
const newValue = [...Config.accountChart] as AccountChart;
newValue[3] = newValue[3] === "on" ? "off" : "on";
setConfig("accountChart", newValue);
});
$(".pageAccount .loadMoreButton").on("click", () => {
qs(".pageAccount .loadMoreButton")?.on("click", () => {
loadMoreLines();
});
$(".pageAccount #accountHistoryChart").on("click", () => {
qs(".pageAccount #accountHistoryChart")?.on("click", () => {
const index: number = ChartController.accountHistoryActiveIndex;
loadMoreLines(index);
if (window === undefined) return;
@ -1070,98 +1076,109 @@ $(".pageAccount #accountHistoryChart").on("click", () => {
if (resultId === undefined) {
throw new Error("Cannot find result for index " + index);
}
const element = $(`.resultRow[data-id="${resultId}"`);
$(".resultRow").removeClass("active");
const element = qs(`.resultRow[data-id="${resultId}"`);
qsa(".resultRow").removeClass("active");
element[0]?.scrollIntoView({
element?.scrollIntoView({
block: "center",
});
element.addClass("active");
element?.addClass("active");
});
$(".pageAccount").on("click", ".miniResultChartButton", async (event) => {
const target = $(event.currentTarget);
const resultId: string = target.parents("tr").data("id") as string;
if (target.hasClass("loading")) return;
if (target.hasClass("disabled")) return;
const result = filteredResults.find((it) => it._id === resultId);
if (result === undefined) return;
let chartData = result.chartData as ChartData;
if (chartData === undefined) {
//need to load full result
target.addClass("loading");
target.attr("aria-label", null);
target.html('<i class="fas fa-fw fa-spin fa-circle-notch"></i>');
Loader.show();
const response = await Ape.results.getById({
params: { resultId: result._id },
});
Loader.hide();
target.html('<i class="fas fa-fw fa-chart-line"></i>');
target.removeClass("loading");
if (response.status !== 200) {
Notifications.add("Error fetching result", -1, { response });
return;
}
chartData = response.body.data.chartData as ChartData;
//update local cache
result.chartData = chartData;
const dbResult = DB.getSnapshot()?.results?.find(
(it) => it._id === result._id,
);
if (dbResult !== undefined) {
dbResult["chartData"] = result.chartData;
}
if (response.body.data.chartData === "toolong") {
target.attr(
"aria-label",
"Graph history is not available for long tests",
);
target.attr("data-baloon-pos", "up");
target.addClass("disabled");
Notifications.add("Graph history is not available for long tests", 0);
return;
}
}
target.attr("aria-label", "View graph");
MiniResultChartModal.show(chartData);
});
$(".pageAccount .group.topFilters, .pageAccount .filterButtons").on(
qs(".pageAccount")?.onChild(
"click",
"button",
() => {
setTimeout(() => {
void update();
}, 0);
".miniResultChartButton",
async (event) => {
const target = new ElementWithUtils(event.childTarget as HTMLElement);
const resultId: string = target
.closestParent("tr")
?.getAttribute("data-id") as string;
if (target.hasClass("loading")) return;
if (target.hasClass("disabled")) return;
const result = filteredResults.find((it) => it._id === resultId);
if (result === undefined) return;
let chartData = result.chartData as ChartData;
if (chartData === undefined) {
//need to load full result
target?.addClass("loading");
target?.removeAttribute("aria-label");
target?.setHtml('<i class="fas fa-fw fa-spin fa-circle-notch"></i>');
Loader.show();
const response = await Ape.results.getById({
params: { resultId: result._id },
});
Loader.hide();
target?.setHtml('<i class="fas fa-fw fa-chart-line"></i>');
target?.removeClass("loading");
if (response.status !== 200) {
Notifications.add("Error fetching result", -1, { response });
return;
}
chartData = response.body.data.chartData as ChartData;
//update local cache
result.chartData = chartData;
const dbResult = DB.getSnapshot()?.results?.find(
(it) => it._id === result._id,
);
if (dbResult !== undefined) {
dbResult["chartData"] = result.chartData;
}
if (response.body.data.chartData === "toolong") {
target?.setAttribute(
"aria-label",
"Graph history is not available for long tests",
);
target?.setAttribute("data-balloon-pos", "up");
target.addClass("disabled");
Notifications.add("Graph history is not available for long tests", 0);
return;
}
}
target?.setAttribute("aria-label", "View graph");
MiniResultChartModal.show(chartData);
},
);
$(".pageAccount .group.presetFilterButtons").on(
const filterButtons = qsa(
".pageAccount .group.topFilters, .pageAccount .filterButtons",
);
filterButtons.forEach((filterButton) => {
filterButton.onChild("click", "button", () => {
setTimeout(() => {
void update();
}, 0);
});
});
qs(".pageAccount .group.presetFilterButtons")?.onChild(
"click",
".filterBtns .filterPresets .select-filter-preset",
async (e) => {
await ResultFilters.setFilterPreset($(e.target).data("id") as string);
const target = e.childTarget as HTMLElement;
await ResultFilters.setFilterPreset(
target.getAttribute("data-id") as string,
);
void update();
},
);
$(".pageAccount .content .group.aboveHistory .exportCSV").on("click", () => {
qs(".pageAccount .content .group.aboveHistory .exportCSV")?.on("click", () => {
void Misc.downloadResultsCSV(filteredResults);
});
$(".pageAccount .profile").on("click", ".details .copyLink", () => {
qs(".pageAccount .profile")?.onChild("click", ".details .copyLink", () => {
const snapshot = DB.getSnapshot();
if (!snapshot) return;
const { name } = snapshot;
@ -1177,7 +1194,7 @@ $(".pageAccount .profile").on("click", ".details .copyLink", () => {
);
});
$(".pageAccount button.loadMoreResults").on("click", async () => {
qs(".pageAccount button.loadMoreResults")?.on("click", async () => {
const offset = DB.getSnapshot()?.results?.length ?? 0;
Loader.show();
@ -1256,9 +1273,9 @@ export const page = new Page<undefined>({
await update().then(() => {
void updateChartColors();
$(".pageAccount .content .accountVerificatinNotice").remove();
qs(".pageAccount .content .accountVerificatinNotice")?.remove();
if (getAuthenticatedUser()?.emailVerified === false) {
$(".pageAccount .content").prepend(
qs(".pageAccount .content")?.prependHtml(
`<div class="accountVerificatinNotice"><i class="fas icon fa-exclamation-triangle"></i><p>Your email address is still not verified</p><button class="sendVerificationEmail">resend verification email</button></div>`,
);
}
@ -1267,6 +1284,6 @@ export const page = new Page<undefined>({
},
});
$(() => {
onWindowLoad(() => {
Skeleton.save("pageAccount");
});

View file

@ -30,7 +30,7 @@ import { Friend, UserNameSchema } from "@monkeytype/schemas/users";
import * as Loader from "../elements/loader";
import { LocalStorageWithSchema } from "../utils/local-storage-with-schema";
import { remoteValidation } from "../utils/remote-validation";
import { qsr } from "../utils/dom";
import { qs, qsr, onWindowLoad } from "../utils/dom";
let friendsTable: SortedTable<Friend> | undefined = undefined;
@ -155,12 +155,12 @@ async function fetchPendingConnections(): Promise<void> {
}
function updatePendingConnections(): void {
$(".pageFriends .pendingRequests").addClass("hidden");
qs(".pageFriends .pendingRequests")?.hide();
if (pendingRequests === undefined || pendingRequests.length === 0) {
$(".pageFriends .pendingRequests").addClass("hidden");
qs(".pageFriends .pendingRequests")?.hide();
} else {
$(".pageFriends .pendingRequests").removeClass("hidden");
qs(".pageFriends .pendingRequests")?.show();
const html = pendingRequests
.map(
@ -193,7 +193,7 @@ function updatePendingConnections(): void {
)
.join("\n");
$(".pageFriends .pendingRequests tbody").html(html);
qs(".pageFriends .pendingRequests tbody")?.setHtml(html);
}
}
@ -208,17 +208,17 @@ async function fetchFriends(): Promise<void> {
}
function updateFriends(): void {
$(".pageFriends .friends .nodata").addClass("hidden");
$(".pageFriends .friends table").addClass("hidden");
qs(".pageFriends .friends .nodata")?.hide();
qs(".pageFriends .friends table")?.hide();
$(".pageFriends .friends .error").addClass("hidden");
qs(".pageFriends .friends .error")?.hide();
if (friendsList === undefined || friendsList.length === 0) {
$(".pageFriends .friends table").addClass("hidden");
$(".pageFriends .friends .nodata").removeClass("hidden");
qs(".pageFriends .friends table")?.hide();
qs(".pageFriends .friends .nodata")?.show();
} else {
$(".pageFriends .friends table").removeClass("hidden");
$(".pageFriends .friends .nodata").addClass("hidden");
qs(".pageFriends .friends table")?.show();
qs(".pageFriends .friends .nodata")?.hide();
if (friendsTable === undefined) {
friendsTable = new SortedTable<Friend>({
@ -386,19 +386,20 @@ function formatStreak(length?: number, prefix?: string): string {
: "-";
}
$(".pageFriends button.friendAdd").on("click", () => {
qs(".pageFriends button.friendAdd")?.on("click", () => {
addFriendModal.show(undefined, {});
});
// need to set the listener for action buttons on the table because the table content is getting replaced
$(".pageFriends .pendingRequests table").on("click", async (e) => {
const action = Array.from(e.target.classList).find((it) =>
qs(".pageFriends .pendingRequests table")?.on("click", async (e) => {
const target = e.target as HTMLElement;
const action = Array.from(target.classList).find((it) =>
["accepted", "rejected", "blocked"].includes(it),
) as "accepted" | "rejected" | "blocked";
if (action === undefined) return;
const row = e.target.closest("tr") as HTMLElement;
const row = target.closest("tr") as HTMLElement;
const id = row.dataset["id"];
if (id === undefined) {
throw new Error("Cannot find id of target.");
@ -462,14 +463,15 @@ $(".pageFriends .pendingRequests table").on("click", async (e) => {
}
});
// need to set the listener for action buttons on the table because the table content is getting replaced
$(".pageFriends .friends table").on("click", async (e) => {
const action = Array.from(e.target.classList).find((it) =>
qs(".pageFriends .friends table")?.on("click", async (e) => {
const target = e.target as HTMLElement;
const action = Array.from(target.classList).find((it) =>
["remove"].includes(it),
);
if (action === undefined) return;
const row = e.target.closest("tr") as HTMLElement;
const row = target.closest("tr") as HTMLElement;
const connectionId = row.dataset["connectionId"];
if (connectionId === undefined) {
throw new Error("Cannot find id of target.");
@ -551,7 +553,7 @@ export const page = new Page<undefined>({
},
});
$(() => {
onWindowLoad(() => {
Skeleton.save("pageFriends");
});

View file

@ -44,7 +44,7 @@ import { isSafeNumber } from "@monkeytype/util/numbers";
import { Mode, Mode2, ModeSchema } from "@monkeytype/schemas/shared";
import * as ServerConfiguration from "../ape/server-configuration";
import { getAvatarElement } from "../utils/discord-avatar";
import { qsr } from "../utils/dom";
import { qs, qsa, qsr, onWindowLoad } from "../utils/dom";
const LeaderboardTypeSchema = z.enum(["allTime", "weekly", "daily"]);
type LeaderboardType = z.infer<typeof LeaderboardTypeSchema>;
@ -169,32 +169,30 @@ function updateTitle(): void {
: "";
state.title = `${type} ${language} ${mode} ${friend}Leaderboard`;
$(".page.pageLeaderboards .bigtitle >.text").text(state.title);
qs(".page.pageLeaderboards .bigtitle >.text")?.setText(state.title);
$(".page.pageLeaderboards .bigtitle .subtext").addClass("hidden");
$(".page.pageLeaderboards .bigtitle button").addClass("hidden");
$(".page.pageLeaderboards .bigtitle .subtext .divider").addClass("hidden");
qs(".page.pageLeaderboards .bigtitle .subtext")?.hide();
qsa(".page.pageLeaderboards .bigtitle button")?.hide();
qs(".page.pageLeaderboards .bigtitle .subtext .divider")?.hide();
if (state.type === "daily") {
$(".page.pageLeaderboards .bigtitle .subtext").removeClass("hidden");
$(
qs(".page.pageLeaderboards .bigtitle .subtext")?.show();
qs(
".page.pageLeaderboards .bigtitle button[data-action='toggleYesterday']",
).removeClass("hidden");
$(".page.pageLeaderboards .bigtitle .subtext .divider").removeClass(
"hidden",
);
)?.show();
qs(".page.pageLeaderboards .bigtitle .subtext .divider")?.show();
if (state.yesterday) {
$(
qs(
".page.pageLeaderboards .bigtitle button[data-action='toggleYesterday']",
).html(`
)?.setHtml(`
<i class="fas fa-forward"></i>
show today
`);
} else {
$(
qs(
".page.pageLeaderboards .bigtitle button[data-action='toggleYesterday']",
).html(`
)?.setHtml(`
<i class="fas fa-backward"></i>
show yesterday
`);
@ -211,23 +209,23 @@ function updateTitle(): void {
utcToLocalDate(endOfDay(timestamp)),
);
} else if (state.type === "weekly") {
$(".page.pageLeaderboards .bigtitle .subtext").removeClass("hidden");
$(
qs(".page.pageLeaderboards .bigtitle .subtext")?.show();
qs(
".page.pageLeaderboards .bigtitle button[data-action='toggleLastWeek']",
).removeClass("hidden");
$(".page.pageLeaderboards .bigtitle .subtext .divider").removeClass(
"hidden",
);
)?.show();
qs(".page.pageLeaderboards .bigtitle .subtext .divider")?.show();
if (state.lastWeek) {
$(".page.pageLeaderboards .bigtitle button[data-action='toggleLastWeek']")
.html(`
qs(
".page.pageLeaderboards .bigtitle button[data-action='toggleLastWeek']",
)?.setHtml(`
<i class="fas fa-forward"></i>
show this week
`);
} else {
$(".page.pageLeaderboards .bigtitle button[data-action='toggleLastWeek']")
.html(`
qs(
".page.pageLeaderboards .bigtitle button[data-action='toggleLastWeek']",
)?.setHtml(`
<i class="fas fa-backward"></i>
show last week
`);
@ -391,43 +389,43 @@ async function requestData(update = false): Promise<void> {
}
function updateJumpButtons(): void {
const el = $(".page.pageLeaderboards .titleAndButtons .jumpButtons");
el.find("button").removeClass("active");
const el = qsa(".page.pageLeaderboards .titleAndButtons .jumpButtons");
el?.qsa("button")?.removeClass("active");
const totalPages = Math.ceil(state.count / state.pageSize);
if (totalPages <= 1) {
el.find("button").addClass("disabled");
el?.qsa("button")?.disable();
} else {
el.find("button").removeClass("disabled");
el?.qsa("button")?.enable();
}
if (state.page === 0) {
el.find("button[data-action='previousPage']").addClass("disabled");
el.find("button[data-action='firstPage']").addClass("disabled");
el?.qs("button[data-action='previousPage']")?.disable();
el?.qs("button[data-action='firstPage']")?.disable();
} else {
el.find("button[data-action='previousPage']").removeClass("disabled");
el.find("button[data-action='firstPage']").removeClass("disabled");
el?.qs("button[data-action='previousPage']")?.enable();
el?.qs("button[data-action='firstPage']")?.enable();
}
if (isAuthenticated()) {
const userButton = el.find("button[data-action='userPage']");
const userButton = el?.qs("button[data-action='userPage']");
if (!state.userData) {
userButton.addClass("disabled");
userButton?.disable();
} else {
const userPage = Math.floor((state.userData.rank - 1) / state.pageSize);
if (state.page === userPage) {
userButton.addClass("disabled");
userButton?.disable();
} else {
userButton.removeClass("disabled");
userButton?.enable();
}
}
}
if (state.page >= totalPages - 1) {
el.find("button[data-action='nextPage']").addClass("disabled");
el?.qs("button[data-action='nextPage']")?.disable();
} else {
el.find("button[data-action='nextPage']").removeClass("disabled");
el?.qs("button[data-action='nextPage']")?.enable();
}
}
@ -554,43 +552,41 @@ function buildWeeklyTableRow(
}
function fillTable(): void {
const table = $(".page.pageLeaderboards table tbody");
table.empty();
const table = qs(".page.pageLeaderboards table tbody");
table?.empty();
if (state.friendsOnly) {
table.parent().addClass("friendsOnly");
table?.getParent()?.addClass("friendsOnly");
} else {
table.parent().removeClass("friendsOnly");
table?.getParent()?.removeClass("friendsOnly");
}
$(".page.pageLeaderboards table thead").addClass("hidden");
qsa(".page.pageLeaderboards table thead")?.hide();
if (state.type === "allTime" || state.type === "daily") {
$(".page.pageLeaderboards table thead.allTimeAndDaily").removeClass(
"hidden",
);
qs(".page.pageLeaderboards table thead.allTimeAndDaily")?.show();
} else if (state.type === "weekly") {
$(".page.pageLeaderboards table thead.weekly").removeClass("hidden");
qs(".page.pageLeaderboards table thead.weekly")?.show();
}
if (state.data === null || state.data.length === 0) {
table.append(`<tr><td colspan="7" class="empty">No data</td></tr>`);
$(".page.pageLeaderboards table").removeClass("hidden");
table?.appendHtml(`<tr><td colspan="7" class="empty">No data</td></tr>`);
qs(".page.pageLeaderboards table")?.show();
return;
}
if (state.type === "allTime" || state.type === "daily") {
for (const entry of state.data) {
const me = getAuthenticatedUser()?.uid === entry.uid;
table.append(buildTableRow(entry, me));
table?.append(buildTableRow(entry, me));
}
} else if (state.type === "weekly") {
for (const entry of state.data) {
const me = getAuthenticatedUser()?.uid === entry.uid;
table.append(buildWeeklyTableRow(entry, me));
table?.append(buildWeeklyTableRow(entry, me));
}
}
$(".page.pageLeaderboards table").removeClass("hidden");
qs(".page.pageLeaderboards table")?.show();
}
function getLbMemoryDifference(): number | null {
@ -612,14 +608,14 @@ function getLbMemoryDifference(): number | null {
function fillUser(): void {
if (isAuthenticated() && DB.getSnapshot()?.lbOptOut === true) {
$(".page.pageLeaderboards .bigUser").html(
qs(".page.pageLeaderboards .bigUser")?.setHtml(
'<div class="warning">You have opted out of the leaderboards.</div>',
);
return;
}
if (isAuthenticated() && DB.getSnapshot()?.banned === true) {
$(".page.pageLeaderboards .bigUser").html(
qs(".page.pageLeaderboards .bigUser")?.setHtml(
'<div class="warning">Your account is banned</div>',
);
return;
@ -633,7 +629,7 @@ function fillUser(): void {
!isDevEnvironment() &&
(DB.getSnapshot()?.typingStats?.timeTyping ?? 0) < minTimeTyping
) {
$(".page.pageLeaderboards .bigUser").html(
qs(".page.pageLeaderboards .bigUser")?.setHtml(
`<div class="warning">Your account must have ${formatDuration(
intervalToDuration({ start: 0, end: minTimeTyping * 1000 }),
)} typed to be placed on the leaderboard.</div>`,
@ -651,14 +647,14 @@ function fillUser(): void {
})})`;
}
$(".page.pageLeaderboards .bigUser").html(
qs(".page.pageLeaderboards .bigUser")?.setHtml(
`<div class="warning">${str}</div>`,
);
return;
}
if (isAuthenticated() && state.userData === null) {
$(".page.pageLeaderboards .bigUser").html(
qs(".page.pageLeaderboards .bigUser")?.setHtml(
`<div class="warning">Not qualified</div>`,
);
return;
@ -670,10 +666,8 @@ function fillUser(): void {
if (state.type === "allTime" || state.type === "daily") {
if (!state.userData || !state.count) {
$(".page.pageLeaderboards .bigUser").addClass("hidden");
$(".page.pageLeaderboards .tableAndUser > .divider").removeClass(
"hidden",
);
qs(".page.pageLeaderboards .bigUser")?.hide();
qs(".page.pageLeaderboards .tableAndUser > .divider")?.show();
return;
}
@ -759,10 +753,10 @@ function fillUser(): void {
</div>
`;
$(".page.pageLeaderboards .bigUser").html(html);
qs(".page.pageLeaderboards .bigUser")?.setHtml(html);
} else if (state.type === "weekly") {
if (!state.userData || !state.count) {
$(".page.pageLeaderboards .bigUser").addClass("hidden");
qs(".page.pageLeaderboards .bigUser")?.hide();
return;
}
@ -843,43 +837,43 @@ function fillUser(): void {
</div>
`;
$(".page.pageLeaderboards .bigUser").html(html);
qs(".page.pageLeaderboards .bigUser")?.setHtml(html);
}
$(".page.pageLeaderboards .bigUser").removeClass("hidden");
$(".page.pageLeaderboards .tableAndUser > .divider").addClass("hidden");
qs(".page.pageLeaderboards .bigUser")?.show();
qs(".page.pageLeaderboards .tableAndUser > .divider")?.hide();
}
function updateContent(): void {
$(".page.pageLeaderboards .loading").addClass("hidden");
$(".page.pageLeaderboards .updating").addClass("invisible");
$(".page.pageLeaderboards .error").addClass("hidden");
qsa(".page.pageLeaderboards .loading").hide();
qsa(".page.pageLeaderboards .updating").addClass("invisible");
qs(".page.pageLeaderboards .error")?.hide();
if (state.error !== undefined) {
$(".page.pageLeaderboards .error").removeClass("hidden");
$(".page.pageLeaderboards .error p").text(state.error);
qs(".page.pageLeaderboards .error")?.show();
qs(".page.pageLeaderboards .error p")?.setText(state.error);
enableButtons();
return;
}
if (state.updating) {
disableButtons();
$(".page.pageLeaderboards .updating").removeClass("invisible");
qsa(".page.pageLeaderboards .updating").removeClass("invisible");
return;
} else if (state.loading) {
disableButtons();
$(".page.pageLeaderboards .bigUser").addClass("hidden");
$(".page.pageLeaderboards .titleAndButtons").addClass("hidden");
$(".page.pageLeaderboards .loading").removeClass("hidden");
$(".page.pageLeaderboards table").addClass("hidden");
qs(".page.pageLeaderboards .bigUser")?.hide();
qsa(".page.pageLeaderboards .titleAndButtons")?.hide();
qsa(".page.pageLeaderboards .loading").show();
qs(".page.pageLeaderboards table")?.hide();
return;
} else {
enableButtons();
}
if (isAuthenticated()) {
$(".page.pageLeaderboards .needAuth").removeClass("hidden");
qsa(".page.pageLeaderboards .needAuth")?.show();
} else {
$(".page.pageLeaderboards .needAuth").addClass("hidden");
qsa(".page.pageLeaderboards .needAuth")?.hide();
}
if (state.data === null) {
@ -887,7 +881,7 @@ function updateContent(): void {
return;
}
$(".page.pageLeaderboards .titleAndButtons").removeClass("hidden");
qsa(".page.pageLeaderboards .titleAndButtons")?.show();
updateJumpButtons();
updateTimerVisibility();
fillTable();
@ -914,58 +908,56 @@ function updateSideButtons(): void {
}
function updateTypeButtons(): void {
const el = $(".page.pageLeaderboards .buttonGroup.typeButtons");
el.find("button").removeClass("active");
el.find(`button[data-type=${state.type}]`).addClass("active");
const el = qs(".page.pageLeaderboards .buttonGroup.typeButtons");
el?.qsa("button")?.removeClass("active");
el?.qs(`button[data-type=${state.type}]`)?.addClass("active");
}
function updateFriendsButtons(): void {
const friendsOnlyGroup = $(
const friendsOnlyGroup = qs(
".page.pageLeaderboards .buttonGroup.friendsOnlyButtons",
);
if (
isAuthenticated() &&
(ServerConfiguration.get()?.connections.enabled ?? false)
) {
friendsOnlyGroup.removeClass("hidden");
friendsOnlyGroup?.show();
} else {
friendsOnlyGroup.addClass("hidden");
friendsOnlyGroup?.hide();
state.friendsOnly = false;
return;
}
const everyoneButton = $(
const everyoneButton = qs(
".page.pageLeaderboards .buttonGroup.friendsOnlyButtons .everyone",
);
const friendsOnlyButton = $(
const friendsOnlyButton = qs(
".page.pageLeaderboards .buttonGroup.friendsOnlyButtons .friendsOnly",
);
if (state.friendsOnly) {
friendsOnlyButton.addClass("active");
everyoneButton.removeClass("active");
friendsOnlyButton?.addClass("active");
everyoneButton?.removeClass("active");
} else {
friendsOnlyButton.removeClass("active");
everyoneButton.addClass("active");
friendsOnlyButton?.removeClass("active");
everyoneButton?.addClass("active");
}
}
function updateModeButtons(): void {
if (state.type !== "allTime" && state.type !== "daily") {
$(".page.pageLeaderboards .buttonGroup.modeButtons").addClass("hidden");
qs(".page.pageLeaderboards .buttonGroup.modeButtons")?.hide();
return;
}
$(".page.pageLeaderboards .buttonGroup.modeButtons").removeClass("hidden");
qs(".page.pageLeaderboards .buttonGroup.modeButtons")?.show();
const el = $(".page.pageLeaderboards .buttonGroup.modeButtons");
el.find("button").removeClass("active");
el.find(
`button[data-mode=${state.mode}][data-mode2=${state.mode2}]`,
).addClass("active");
const el = qs(".page.pageLeaderboards .buttonGroup.modeButtons");
el?.qsa("button")?.removeClass("active");
el?.qs(
`button[data-mode="${state.mode}"][data-mode2="${state.mode2}"]`,
)?.addClass("active");
//hide all mode buttons
$(`.page.pageLeaderboards .buttonGroup.modeButtons button`).addClass(
"hidden",
);
qsa(`.page.pageLeaderboards .buttonGroup.modeButtons button`)?.hide();
//show all valid ones
for (const mode of Object.keys(validLeaderboards[state.type]) as Mode[]) {
@ -973,37 +965,33 @@ function updateModeButtons(): void {
// oxlint-disable-next-line no-non-null-assertion
validLeaderboards[state.type][mode]!,
)) {
$(
qs(
`.page.pageLeaderboards .buttonGroup.modeButtons button[data-mode="${mode}"][data-mode2="${mode2}"]`,
).removeClass("hidden");
)?.show();
}
}
}
function updateLanguageButtons(): void {
if (state.type !== "daily") {
$(".page.pageLeaderboards .buttonGroup.languageButtons").addClass("hidden");
qs(".page.pageLeaderboards .buttonGroup.languageButtons")?.hide();
return;
}
$(".page.pageLeaderboards .buttonGroup.languageButtons").removeClass(
"hidden",
);
qs(".page.pageLeaderboards .buttonGroup.languageButtons")?.show();
const el = $(".page.pageLeaderboards .buttonGroup.languageButtons");
el.find("button").removeClass("active");
el.find(`button[data-language=${state.language}]`).addClass("active");
const el = qs(".page.pageLeaderboards .buttonGroup.languageButtons");
el?.qsa("button")?.removeClass("active");
el?.qs(`button[data-language=${state.language}]`)?.addClass("active");
//hide all languages
$(`.page.pageLeaderboards .buttonGroup.languageButtons button`).addClass(
"hidden",
);
qsa(`.page.pageLeaderboards .buttonGroup.languageButtons button`)?.hide();
//show all valid ones
for (const lang of validLeaderboards[state.type][state.mode]?.[state.mode2] ??
[]) {
$(
qs(
`.page.pageLeaderboards .buttonGroup.languageButtons button[data-language="${lang}"]`,
).removeClass("hidden");
)?.show();
}
}
@ -1013,7 +1001,7 @@ function updateTimerElement(): void {
if (state.type === "daily") {
const diff = differenceInSeconds(new Date(), endOfDay(new UTCDateMini()));
$(".page.pageLeaderboards .titleAndButtons .timer").text(
qs(".page.pageLeaderboards .titleAndButtons .timer")?.setText(
"Next reset in: " + DateTime.secondsToString(diff, true),
);
} else if (state.type === "allTime") {
@ -1021,13 +1009,13 @@ function updateTimerElement(): void {
const minutesToNextUpdate = 14 - (date.getMinutes() % 15);
const secondsToNextUpdate = 60 - date.getSeconds();
const totalSeconds = minutesToNextUpdate * 60 + secondsToNextUpdate;
$(".page.pageLeaderboards .titleAndButtons .timer").text(
qs(".page.pageLeaderboards .titleAndButtons .timer")?.setText(
"Next update in: " + DateTime.secondsToString(totalSeconds, true),
);
} else if (state.type === "weekly") {
const nextWeekTimestamp = endOfWeek(new UTCDateMini(), { weekStartsOn: 1 });
const totalSeconds = differenceInSeconds(new Date(), nextWeekTimestamp);
$(".page.pageLeaderboards .titleAndButtons .timer").text(
qs(".page.pageLeaderboards .titleAndButtons .timer")?.setText(
"Next reset in: " +
DateTime.secondsToString(totalSeconds, true, true, ":", true, true),
);
@ -1045,11 +1033,11 @@ function updateTimerVisibility(): void {
}
if (visible) {
$(".page.pageLeaderboards .titleAndButtons .timer").removeClass(
qs(".page.pageLeaderboards .titleAndButtons .timer")?.removeClass(
"invisible",
);
} else {
$(".page.pageLeaderboards .titleAndButtons .timer").addClass("invisible");
qs(".page.pageLeaderboards .titleAndButtons .timer")?.addClass("invisible");
}
}
@ -1063,7 +1051,7 @@ function startTimer(): void {
function stopTimer(): void {
clearInterval(updateTimer);
updateTimer = undefined;
$(".page.pageLeaderboards .titleAndButtons .timer").text("-");
qs(".page.pageLeaderboards .titleAndButtons .timer")?.setText("-");
}
function convertRuleOption(rule: string): string[] {
@ -1183,7 +1171,7 @@ async function appendModeAndLanguageButtons(): Promise<void> {
</button>`,
);
});
$(".modeButtons").html(
qs(".modeButtons")?.setHtml(
`<div class="divider"></div>` + mode2Buttons.join("\n"),
);
@ -1203,17 +1191,17 @@ async function appendModeAndLanguageButtons(): Promise<void> {
${lang}
</button>`,
);
$(".languageButtons").html(
qs(".languageButtons")?.setHtml(
`<div class="divider"></div>` + languageButtons.join("\n"),
);
}
function disableButtons(): void {
$(".page.pageLeaderboards button").prop("disabled", true);
qsa(".page.pageLeaderboards button")?.disable();
}
function enableButtons(): void {
$(".page.pageLeaderboards button").prop("disabled", false);
qsa(".page.pageLeaderboards button")?.enable();
}
export function goToPage(pageId: number): void {
@ -1373,9 +1361,9 @@ function updateTimeText(
" - \n" +
format(localEnd, localDateFormat);
const text = $(".page.pageLeaderboards .bigtitle .subtext > .text");
text.text(`${dateString}`);
text.attr("aria-label", localDateString);
const text = qs(".page.pageLeaderboards .bigtitle .subtext > .text");
text?.setText(`${dateString}`);
text?.setAttribute("aria-label", localDateString);
}
function formatRank(rank: number | undefined): string {
@ -1385,23 +1373,26 @@ function formatRank(rank: number | undefined): string {
return rank.toString();
}
$(".page.pageLeaderboards .jumpButtons button").on("click", function () {
const action = $(this).data("action") as Action;
qsa(".page.pageLeaderboards .jumpButtons button")?.on("click", function () {
const action = this.getAttribute("data-action") as Action;
if (action !== "goToPage") {
handleJumpButton(action);
}
});
$(".page.pageLeaderboards .bigtitle button").on("click", function () {
const action = $(this).data("action") as string;
qsa(".page.pageLeaderboards .bigtitle button")?.on("click", function () {
const action = this.getAttribute("data-action") as string;
handleYesterdayLastWeekButton(action);
});
$(".page.pageLeaderboards .buttonGroup.typeButtons").on(
qs(".page.pageLeaderboards .buttonGroup.typeButtons")?.onChild(
"click",
"button",
function () {
const type = $(this).data("type") as "allTime" | "weekly" | "daily";
const type = this.getAttribute("data-type") as
| "allTime"
| "weekly"
| "daily";
if (state.type === type) return;
state.type = type;
if (state.type === "daily") {
@ -1422,16 +1413,17 @@ $(".page.pageLeaderboards .buttonGroup.typeButtons").on(
},
);
$(".page.pageLeaderboards .buttonGroup.modeButtons").on(
qs(".page.pageLeaderboards .buttonGroup.modeButtons")?.onChild(
"click",
"button",
function () {
const mode = $(this).attr("data-mode") as Mode;
const mode2 = $(this).attr("data-mode2");
const mode = this.getAttribute("data-mode") as Mode;
const mode2 = this.getAttribute("data-mode2");
if (
mode !== undefined &&
mode2 !== undefined &&
mode2 !== null &&
(state.type === "allTime" || state.type === "daily")
) {
if (state.mode === mode && state.mode2 === mode2) return;
@ -1451,11 +1443,11 @@ $(".page.pageLeaderboards .buttonGroup.modeButtons").on(
},
);
$(".page.pageLeaderboards .buttonGroup.languageButtons").on(
qs(".page.pageLeaderboards .buttonGroup.languageButtons")?.onChild(
"click",
"button",
function () {
const language = $(this).attr("data-language") as Language;
const language = this.getAttribute("data-language") as Language;
if (language !== undefined && state.type === "daily") {
if (state.language === language) return;
@ -1474,7 +1466,7 @@ $(".page.pageLeaderboards .buttonGroup.languageButtons").on(
},
);
$(".page.pageLeaderboards .buttonGroup.friendsOnlyButtons").on(
qs(".page.pageLeaderboards .buttonGroup.friendsOnlyButtons")?.onChild(
"click",
"button",
() => {
@ -1517,7 +1509,7 @@ export const page = new PageWithUrlParams({
},
});
$(async () => {
onWindowLoad(async () => {
Skeleton.save("pageLeaderboards");
});

View file

@ -1,47 +1,46 @@
import Page from "./page";
import * as Skeleton from "../utils/skeleton";
import { promiseAnimate } from "../utils/misc";
import { qsr } from "../utils/dom";
import { qs, qsr } from "../utils/dom";
const pageEl = $(".page.pageLoading");
const barEl = pageEl.find(".bar");
const errorEl = pageEl.find(".error");
const spinnerEl = pageEl.find(".spinner");
const textEl = pageEl.find(".text");
const pageEl = qs(".page.pageLoading");
const barEl = pageEl?.qs(".bar");
const errorEl = pageEl?.qs(".error");
const spinnerEl = pageEl?.qs(".spinner");
const textEl = pageEl?.qs(".text");
export async function updateBar(
percentage: number,
duration: number,
): Promise<void> {
await promiseAnimate(barEl[0]?.querySelector(".fill") as HTMLElement, {
await barEl?.qs(".fill")?.promiseAnimate({
width: percentage + "%",
duration,
});
}
export function updateText(text: string): void {
textEl.removeClass("hidden").html(text);
textEl?.show()?.setHtml(text);
}
export function showSpinner(): void {
barEl.addClass("hidden");
errorEl.addClass("hidden");
spinnerEl.removeClass("hidden");
textEl.addClass("hidden");
barEl?.hide();
errorEl?.hide();
spinnerEl?.show();
textEl?.hide();
}
export function showError(): void {
barEl.addClass("hidden");
spinnerEl.addClass("hidden");
errorEl.removeClass("hidden");
textEl.addClass("hidden");
barEl?.hide();
spinnerEl?.hide();
errorEl?.show();
textEl?.hide();
}
export async function showBar(): Promise<void> {
barEl.removeClass("hidden");
errorEl.addClass("hidden");
spinnerEl.addClass("hidden");
textEl.addClass("hidden");
barEl?.show();
errorEl?.hide();
spinnerEl?.hide();
textEl?.hide();
}
export const page = new Page({

View file

@ -11,7 +11,7 @@ import { ValidatedHtmlInputElement } from "../elements/input-validation";
import { isDevEnvironment } from "../utils/misc";
import { z } from "zod";
import { remoteValidation } from "../utils/remote-validation";
import { qsr } from "../utils/dom";
import { qs, qsa, qsr, onWindowLoad } from "../utils/dom";
let registerForm: {
name?: string;
@ -20,29 +20,29 @@ let registerForm: {
} = {};
export function enableSignUpButton(): void {
$(".page.pageLogin .register.side button").prop("disabled", false);
qs(".page.pageLogin .register.side button")?.enable();
}
export function disableSignUpButton(): void {
$(".page.pageLogin .register.side button").prop("disabled", true);
qs(".page.pageLogin .register.side button")?.disable();
}
export function enableInputs(): void {
$(".pageLogin input").prop("disabled", false);
$(".pageLogin button").prop("disabled", false);
qsa(".pageLogin input")?.enable();
qsa(".pageLogin button")?.enable();
}
export function disableInputs(): void {
$(".pageLogin input").prop("disabled", true);
$(".pageLogin button").prop("disabled", true);
qsa(".pageLogin input")?.disable();
qsa(".pageLogin button")?.disable();
}
export function showPreloader(): void {
$(".pageLogin .preloader").removeClass("hidden");
qs(".pageLogin .preloader")?.show();
}
export function hidePreloader(): void {
$(".pageLogin .preloader").addClass("hidden");
qs(".pageLogin .preloader")?.hide();
}
function isFormComplete(): boolean {
@ -217,13 +217,16 @@ export const page = new Page({
beforeShow: async (): Promise<void> => {
Skeleton.append("pageLogin", "main");
registerForm = {};
$(".pageLogin input").val("");
$(".pageLogin .register .indicator").addClass("hidden");
const inputs = qsa<HTMLInputElement>(".pageLogin input");
inputs.forEach((input) => {
input.setValue("");
});
qsa(".pageLogin .register .indicator")?.hide();
enableInputs();
disableSignUpButton();
},
});
$(() => {
onWindowLoad(() => {
Skeleton.save("pageLogin");
});

View file

@ -5,17 +5,17 @@ import { ValidatedHtmlInputElement } from "../elements/input-validation";
import { UserNameSchema, UserProfile } from "@monkeytype/schemas/users";
import { remoteValidation } from "../utils/remote-validation";
import * as NavigationEvent from "../observables/navigation-event";
import { qsr } from "../utils/dom";
import { qs, qsr, onWindowLoad } from "../utils/dom";
let nameInputEl: ValidatedHtmlInputElement | null = null;
let lastProfile: UserProfile | null = null;
function enableButton(): void {
$('.page.pageProfileSearch button[type="submit"]').prop("disabled", false);
qs('.page.pageProfileSearch button[type="submit"]')?.enable();
}
function disableButton(): void {
$('.page.pageProfileSearch button[type="submit"]').prop("disabled", true);
qs('.page.pageProfileSearch button[type="submit"]')?.disable();
}
export const page = new Page({
@ -57,11 +57,11 @@ export const page = new Page({
disableButton();
},
afterShow: async (): Promise<void> => {
$(".page.pageProfileSearch input").trigger("focus");
qs(".page.pageProfileSearch input")?.dispatch("focus");
},
});
$(".page.pageProfileSearch form").on("submit", (e) => {
qs(".page.pageProfileSearch form")?.on("submit", (e) => {
e.preventDefault();
if (lastProfile === null) return;
NavigationEvent.dispatch(`/profile/${lastProfile.name}`, {
@ -69,6 +69,6 @@ $(".page.pageProfileSearch form").on("submit", (e) => {
});
});
$(() => {
onWindowLoad(() => {
Skeleton.save("pageProfileSearch");
});

View file

@ -12,14 +12,14 @@ import * as TestActivity from "../elements/test-activity";
import { TestActivityCalendar } from "../elements/test-activity-calendar";
import { getFirstDayOfTheWeek } from "../utils/date-and-time";
import { addFriend } from "./friends";
import { qsr } from "../utils/dom";
import { qs, qsr } from "../utils/dom";
const firstDayOfTheWeek = getFirstDayOfTheWeek();
function reset(): void {
$(".page.pageProfile .error").addClass("hidden");
$(".page.pageProfile .preloader").removeClass("hidden");
$(".page.pageProfile .profile").html(`
qs(".page.pageProfile .error")?.hide();
qs(".page.pageProfile .preloader")?.show();
qs(".page.pageProfile .profile")?.setHtml(`
<div class="details none">
<div class="avatarAndName">
<div class="avatar"></div>
@ -182,7 +182,7 @@ type UpdateOptions = {
async function update(options: UpdateOptions): Promise<void> {
const getParamExists = checkIfGetParameterExists("isUid");
if (options.data) {
$(".page.pageProfile .preloader").addClass("hidden");
qs(".page.pageProfile .preloader")?.hide();
await Profile.update("profile", options.data);
PbTables.update(
// this cast is fine because pb tables can handle the partial data inside user profiles
@ -195,15 +195,15 @@ async function update(options: UpdateOptions): Promise<void> {
query: { isUid: getParamExists },
});
$(".page.pageProfile .preloader").addClass("hidden");
qs(".page.pageProfile .preloader")?.hide();
if (response.status === 404) {
const message = getParamExists
? "User not found"
: `User ${options.uidOrName} not found`;
$(".page.pageProfile .preloader").addClass("hidden");
$(".page.pageProfile .error").removeClass("hidden");
$(".page.pageProfile .error .message").text(message);
qs(".page.pageProfile .preloader")?.hide();
qs(".page.pageProfile .error")?.show();
qs(".page.pageProfile .error .message")?.setText(message);
} else if (response.status === 200) {
const profile = response.body.data;
window.history.replaceState(null, "", `/profile/${profile.name}`);
@ -228,7 +228,7 @@ async function update(options: UpdateOptions): Promise<void> {
TestActivity.clear(testActivity);
}
} else {
// $(".page.pageProfile .failedToLoad").removeClass("hidden");
// qs(".page.pageProfile .failedToLoad")?.show();
Notifications.add("Failed to load profile: " + response.body.message, -1);
return;
}
@ -237,27 +237,33 @@ async function update(options: UpdateOptions): Promise<void> {
}
}
$(".page.pageProfile").on("click", ".profile .userReportButton", () => {
const uid = $(".page.pageProfile .profile").attr("uid") ?? "";
const name = $(".page.pageProfile .profile").attr("name") ?? "";
qs(".page.pageProfile")?.onChild("click", ".profile .userReportButton", () => {
const uid = qs(".page.pageProfile .profile")?.getAttribute("uid") ?? "";
const name = qs(".page.pageProfile .profile")?.getAttribute("name") ?? "";
const lbOptOut =
($(".page.pageProfile .profile").attr("lbOptOut") ?? "false") === "true";
(qs(".page.pageProfile .profile")?.getAttribute("lbOptOut") ?? "false") ===
"true";
void UserReportModal.show({ uid, name, lbOptOut });
});
$(".page.pageProfile").on("click", ".profile .addFriendButton", async () => {
const friendName = $(".page.pageProfile .profile").attr("name") ?? "";
qs(".page.pageProfile")?.onChild(
"click",
".profile .addFriendButton",
async () => {
const friendName =
qs(".page.pageProfile .profile")?.getAttribute("name") ?? "";
const result = await addFriend(friendName);
const result = await addFriend(friendName);
if (result === true) {
Notifications.add(`Request sent to ${friendName}`);
$(".profile .details .addFriendButton").addClass("disabled");
} else {
Notifications.add(result, -1);
}
});
if (result === true) {
Notifications.add(`Request sent to ${friendName}`);
qs(".profile .details .addFriendButton")?.disable();
} else {
Notifications.add(result, -1);
}
},
);
export const page = new Page<undefined | UserProfile>({
id: "profile",
@ -271,18 +277,18 @@ export const page = new Page<undefined | UserProfile>({
Skeleton.append("pageProfile", "main");
const uidOrName = options?.params?.["uidOrName"] ?? "";
if (uidOrName) {
$(".page.pageProfile .preloader").removeClass("hidden");
$(".page.pageProfile .search").addClass("hidden");
$(".page.pageProfile .content").removeClass("hidden");
qs(".page.pageProfile .preloader")?.show();
qs(".page.pageProfile .search")?.hide();
qs(".page.pageProfile .content")?.show();
reset();
void update({
uidOrName,
data: options?.data,
});
} else {
$(".page.pageProfile .preloader").addClass("hidden");
$(".page.pageProfile .search").removeClass("hidden");
$(".page.pageProfile .content").addClass("hidden");
qs(".page.pageProfile .preloader")?.hide();
qs(".page.pageProfile .search")?.show();
qs(".page.pageProfile .content")?.hide();
}
},
});

View file

@ -43,7 +43,7 @@ import * as CustomBackgroundPicker from "../elements/settings/custom-background-
import * as CustomFontPicker from "../elements/settings/custom-font-picker";
import * as AuthEvent from "../observables/auth-event";
import * as FpsLimitSection from "../elements/settings/fps-limit-section";
import { qs, qsr } from "../utils/dom";
import { qs, qsa, qsr, onWindowLoad } from "../utils/dom";
let settingsInitialized = false;
@ -83,37 +83,25 @@ async function initGroups(): Promise<void> {
groups["keymapMode"] = new SettingsGroup("keymapMode", "button", {
updateCallback: () => {
if (Config.keymapMode === "off") {
$(".pageSettings .section[data-config-name='keymapStyle']").addClass(
"hidden",
);
$(".pageSettings .section[data-config-name='keymapLayout']").addClass(
"hidden",
);
$(
qs(".pageSettings .section[data-config-name='keymapStyle']")?.hide();
qs(".pageSettings .section[data-config-name='keymapLayout']")?.hide();
qs(
".pageSettings .section[data-config-name='keymapLegendStyle']",
).addClass("hidden");
$(
)?.hide();
qs(
".pageSettings .section[data-config-name='keymapShowTopRow']",
).addClass("hidden");
$(".pageSettings .section[data-config-name='keymapSize']").addClass(
"hidden",
);
)?.hide();
qs(".pageSettings .section[data-config-name='keymapSize']")?.hide();
} else {
$(".pageSettings .section[data-config-name='keymapStyle']").removeClass(
"hidden",
);
$(
".pageSettings .section[data-config-name='keymapLayout']",
).removeClass("hidden");
$(
qs(".pageSettings .section[data-config-name='keymapStyle']")?.show();
qs(".pageSettings .section[data-config-name='keymapLayout']")?.show();
qs(
".pageSettings .section[data-config-name='keymapLegendStyle']",
).removeClass("hidden");
$(
)?.show();
qs(
".pageSettings .section[data-config-name='keymapShowTopRow']",
).removeClass("hidden");
$(".pageSettings .section[data-config-name='keymapSize']").removeClass(
"hidden",
);
)?.show();
qs(".pageSettings .section[data-config-name='keymapSize']")?.show();
}
},
});
@ -226,19 +214,21 @@ async function initGroups(): Promise<void> {
groups["timerColor"] = new SettingsGroup("timerColor", "button");
groups["fontFamily"] = new SettingsGroup("fontFamily", "button", {
updateCallback: () => {
const customButton = $(
const customButton = qs(
".pageSettings .section[data-config-name='fontFamily'] .buttons button[data-config-value='custom']",
);
if (
$(
qsa(
".pageSettings .section[data-config-name='fontFamily'] .buttons .active",
).length === 0
) {
customButton.addClass("active");
customButton.text(`Custom (${Config.fontFamily.replace(/_/g, " ")})`);
customButton?.addClass("active");
customButton?.setText(
`Custom (${Config.fontFamily.replace(/_/g, " ")})`,
);
} else {
customButton.text("Custom");
customButton?.setText("Custom");
}
},
});
@ -482,11 +472,11 @@ async function fillSettingsPage(): Promise<void> {
// export let settingsFillPromise = fillSettingsPage();
export function hideAccountSection(): void {
$(`.pageSettings .section.needsAccount`).addClass("hidden");
qsa(`.pageSettings .section.needsAccount`)?.hide();
}
function showAccountSection(): void {
$(`.pageSettings .section.needsAccount`).removeClass("hidden");
qsa(`.pageSettings .section.needsAccount`)?.show();
refreshTagsSettingsSection();
refreshPresetsSettingsSection();
}
@ -519,13 +509,13 @@ function setActiveFunboxButton(): void {
function refreshTagsSettingsSection(): void {
if (isAuthenticated() && DB.getSnapshot()) {
const tagsEl = $(".pageSettings .section.tags .tagsList").empty();
const tagsEl = qs(".pageSettings .section.tags .tagsList")?.empty();
DB.getSnapshot()?.tags?.forEach((tag) => {
// let tagPbString = "No PB found";
// if (tag.pb !== undefined && tag.pb > 0) {
// tagPbString = `PB: ${tag.pb}`;
// }
tagsEl.append(`
tagsEl?.appendHtml(`
<div class="buttons tag" data-id="${tag._id}" data-name="${
tag.name
@ -548,17 +538,19 @@ function refreshTagsSettingsSection(): void {
`);
});
$(".pageSettings .section.tags").removeClass("hidden");
qs(".pageSettings .section.tags")?.show();
} else {
$(".pageSettings .section.tags").addClass("hidden");
qs(".pageSettings .section.tags")?.hide();
}
}
function refreshPresetsSettingsSection(): void {
if (isAuthenticated() && DB.getSnapshot()) {
const presetsEl = $(".pageSettings .section.presets .presetsList").empty();
const presetsEl = qs(
".pageSettings .section.presets .presetsList",
)?.empty();
DB.getSnapshot()?.presets?.forEach((preset: SnapshotPreset) => {
presetsEl.append(`
presetsEl?.appendHtml(`
<div class="buttons preset" data-id="${preset._id}" data-name="${preset.name}" data-display="${preset.display}">
<button class="presetButton">${preset.display}</button>
<button class="editButton">
@ -571,9 +563,9 @@ function refreshPresetsSettingsSection(): void {
`);
});
$(".pageSettings .section.presets").removeClass("hidden");
qs(".pageSettings .section.presets")?.show();
} else {
$(".pageSettings .section.presets").addClass("hidden");
qs(".pageSettings .section.presets")?.hide();
}
}
@ -581,16 +573,16 @@ export async function updateFilterSectionVisibility(): Promise<void> {
const hasBackgroundUrl =
Config.customBackground !== "" ||
(await FileStorage.hasFile("LocalBackgroundFile"));
const isImageVisible = $(".customBackground img").is(":visible");
const isImageVisible = qs(".customBackground img")?.isVisible();
if (hasBackgroundUrl && isImageVisible) {
$(
qs(
".pageSettings .section[data-config-name='customBackgroundFilter']",
).removeClass("hidden");
)?.show();
} else {
$(
qs(
".pageSettings .section[data-config-name='customBackgroundFilter']",
).addClass("hidden");
)?.hide();
}
}
@ -604,9 +596,9 @@ export async function update(
}
if (Config.showKeyTips) {
$(".pageSettings .tip").removeClass("hidden");
qs(".pageSettings .tip")?.show();
} else {
$(".pageSettings .tip").addClass("hidden");
qs(".pageSettings .tip")?.hide();
}
for (const group of Object.values(groups)) {
@ -674,13 +666,13 @@ export async function update(
);
if (Config.autoSwitchTheme) {
$(
qs(
".pageSettings .section[data-config-name='autoSwitchThemeInputs']",
).removeClass("hidden");
)?.show();
} else {
$(
qs(
".pageSettings .section[data-config-name='autoSwitchThemeInputs']",
).addClass("hidden");
)?.hide();
}
setInputValue(
@ -728,7 +720,7 @@ export async function update(
: "ctrl";
const commandKey = Config.quickRestart === "esc" ? "tab" : "esc";
$(".pageSettings .tip").html(`
qs(".pageSettings .tip")?.setHtml(`
tip: You can also change all these settings quickly using the
command line (<key>${commandKey}</key> or <key>${modifierKey}</key> + <key>shift</key> + <key>p</key>)`);
@ -788,44 +780,45 @@ function toggleSettingsGroup(groupName: string): void {
}
//funbox
$(".pageSettings .section[data-config-name='funbox'] .buttons").on(
qs(".pageSettings .section[data-config-name='funbox'] .buttons")?.onChild(
"click",
"button",
(e) => {
const funbox = $(e.currentTarget).attr("data-config-value") as FunboxName;
const target = e.childTarget as HTMLElement;
const funbox = target?.getAttribute("data-config-value") as FunboxName;
Funbox.toggleFunbox(funbox);
setActiveFunboxButton();
},
);
//tags
$(".pageSettings .section.tags").on(
qs(".pageSettings .section.tags")?.onChild(
"click",
".tagsList .tag .tagButton",
(e) => {
const target = e.currentTarget as HTMLElement;
const tagid = $(target).parent(".tag").attr("data-id") as string;
const target = e.childTarget as HTMLElement;
const tagid = target.parentElement?.getAttribute("data-id") as string;
TagController.toggle(tagid);
$(target).toggleClass("active");
target.classList.toggle("active");
},
);
$(".pageSettings .section.presets").on(
qs(".pageSettings .section.presets")?.onChild(
"click",
".presetsList .preset .presetButton",
async (e) => {
const target = e.currentTarget as HTMLElement;
const presetid = $(target).parent(".preset").attr("data-id") as string;
const target = e.childTarget as HTMLElement;
const presetid = target.parentElement?.getAttribute("data-id") as string;
await PresetController.apply(presetid);
void update();
},
);
$("#importSettingsButton").on("click", () => {
qs("#importSettingsButton")?.on("click", () => {
ImportExportSettingsModal.show("import");
});
$("#exportSettingsButton").on("click", () => {
qs("#exportSettingsButton")?.on("click", () => {
const configJSON = JSON.stringify(Config);
navigator.clipboard.writeText(configJSON).then(
function () {
@ -837,19 +830,20 @@ $("#exportSettingsButton").on("click", () => {
);
});
$(".pageSettings .sectionGroupTitle").on("click", (e) => {
toggleSettingsGroup($(e.currentTarget).attr("group") as string);
qsa(".pageSettings .sectionGroupTitle")?.on("click", (e) => {
const target = e.currentTarget as HTMLElement;
toggleSettingsGroup(target.getAttribute("group") as string);
});
$(
qs(
".pageSettings .section[data-config-name='keymapSize'] .inputAndButton button.save",
).on("click", () => {
)?.on("click", () => {
const didConfigSave = setConfig(
"keymapSize",
parseFloat(
$(
qs<HTMLInputElement>(
".pageSettings .section[data-config-name='keymapSize'] .inputAndButton input",
).val() as string,
)?.getValue() as string,
),
);
if (didConfigSave) {
@ -859,15 +853,15 @@ $(
}
});
$(
qs(
".pageSettings .section[data-config-name='keymapSize'] .inputAndButton input",
).on("focusout", () => {
)?.on("focusout", () => {
const didConfigSave = setConfig(
"keymapSize",
parseFloat(
$(
qs<HTMLInputElement>(
".pageSettings .section[data-config-name='keymapSize'] .inputAndButton input",
).val() as string,
)?.getValue() as string,
),
);
if (didConfigSave) {
@ -877,16 +871,16 @@ $(
}
});
$(
qs(
".pageSettings .section[data-config-name='keymapSize'] .inputAndButton input",
).on("keypress", (e) => {
)?.on("keypress", (e) => {
if (e.key === "Enter") {
const didConfigSave = setConfig(
"keymapSize",
parseFloat(
$(
qs<HTMLInputElement>(
".pageSettings .section[data-config-name='keymapSize'] .inputAndButton input",
).val() as string,
)?.getValue() as string,
),
);
if (didConfigSave) {
@ -897,11 +891,12 @@ $(
}
});
$(".pageSettings .quickNav .links a").on("click", (e) => {
const settingsGroup = e.target.innerText;
const isClosed = $(`.pageSettings .settingsGroup.${settingsGroup}`).hasClass(
"slideup",
);
qsa(".pageSettings .quickNav .links a")?.on("click", (e) => {
const target = e.currentTarget as HTMLElement;
const settingsGroup = target.innerText;
const isClosed = qs(
`.pageSettings .settingsGroup.${settingsGroup}`,
)?.hasClass("slideup");
if (isClosed) {
toggleSettingsGroup(settingsGroup);
}
@ -972,8 +967,9 @@ function handleHighlightSection(highlight: Highlight | undefined): void {
}
}
$(".pageSettings .section .groupTitle button").on("click", (e) => {
const section = e.target.parentElement?.parentElement;
qsa(".pageSettings .section .groupTitle button")?.on("click", (e) => {
const target = e.currentTarget as HTMLElement;
const section = target.parentElement?.parentElement;
const configName = (section?.dataset?.["configName"] ??
section?.dataset?.["sectionId"]) as Highlight | undefined;
if (configName === undefined) {
@ -997,13 +993,13 @@ ConfigEvent.subscribe(({ key, newValue }) => {
if (key === "fullConfigChange") setEventDisabled(true);
if (key === "fullConfigChangeFinished") setEventDisabled(false);
if (key === "themeLight") {
$(
qs(
`.pageSettings .section[data-config-name='autoSwitchThemeInputs'] select.light option[value="${newValue}"]`,
).attr("selected", "true");
)?.setAttribute("selected", "true");
} else if (key === "themeDark") {
$(
qs(
`.pageSettings .section[data-config-name='autoSwitchThemeInputs'] select.dark option[value="${newValue}"]`,
).attr("selected", "true");
)?.setAttribute("selected", "true");
}
//make sure the page doesnt update a billion times when applying a preset/config at once
if (configEventDisabled) return;
@ -1044,6 +1040,6 @@ export const page = new PageWithUrlParams({
},
});
$(async () => {
onWindowLoad(async () => {
Skeleton.save("pageSettings");
});

View file

@ -207,6 +207,20 @@ export class ElementWithUtils<T extends HTMLElement = HTMLElement> {
return this;
}
/**
* Check if element is visible
*/
isVisible(): boolean {
return this.native.offsetWidth > 0 || this.native.offsetHeight > 0;
}
scrollIntoView(options: ScrollIntoViewOptions): this {
this.native.scrollIntoView(options);
return this;
}
/**
* Add a class to the element
*/
@ -538,6 +552,25 @@ export class ElementWithUtils<T extends HTMLElement = HTMLElement> {
return null;
}
/**
* Get the first parent that matches a selector
*/
closestParent(selector: string): ElementWithUtils | null {
const closestParent = this.native.parentElement?.closest(
selector,
) as HTMLElement;
return closestParent !== null ? new ElementWithUtils(closestParent) : null;
}
/**
* Check if element matches a selector
*/
matches(selector: string): boolean {
return this.native.matches(selector);
}
/**
* Replace this element with another element
*/
@ -710,6 +743,38 @@ export class ElementsWithUtils<
return this;
}
/**
* Query all elements in the array for a child element matching the selector
*/
qs<U extends HTMLElement>(selector: string): ElementsWithUtils<U> {
const allElements: ElementWithUtils<U>[] = [];
for (const item of this) {
const found = item.native.querySelector<U>(selector);
if (found) allElements.push(new ElementWithUtils<U>(found));
}
return new ElementsWithUtils<U>(...allElements);
}
/**
* Query all elements in the array for all child elements matching the selector
*/
qsa<U extends HTMLElement = HTMLElement>(
selector: string,
): ElementsWithUtils<U> {
const allElements: ElementWithUtils<U>[] = [];
for (const item of this) {
const elements = Array.from(item.native.querySelectorAll<U>(selector));
for (const el of elements) {
if (el !== null) allElements.push(new ElementWithUtils<U>(el));
}
}
return new ElementsWithUtils<U>(...allElements);
}
/**
* Attach an event listener to all elements in the array
*/