Merge branch 'master' into tapeMode-newline

This commit is contained in:
Nad Alaba 2024-09-18 20:52:16 +03:00
commit c34d109026
No known key found for this signature in database
GPG key ID: E02710B967DA8951
58 changed files with 6674 additions and 317 deletions

View file

@ -28,6 +28,7 @@ import { LeaderboardRank } from "@monkeytype/contracts/schemas/leaderboards";
import { randomUUID } from "node:crypto";
import _ from "lodash";
import { MonkeyMail, UserStreak } from "@monkeytype/contracts/schemas/users";
import { isFirebaseError } from "../../../src/utils/error";
const mockApp = request(app);
const configuration = Configuration.getCachedConfiguration();
@ -355,10 +356,16 @@ describe("user controller test", () => {
it("should fail with too many firebase requests", async () => {
//GIVEN
adminGenerateVerificationLinkMock.mockRejectedValue({
code: "auth/internal-error",
message: "TOO_MANY_ATTEMPTS_TRY_LATER",
} as FirebaseError);
const mockFirebaseError = {
code: "auth/too-many-requests",
codePrefix: "auth",
errorInfo: {
code: "auth/too-many-requests",
message: "Too many requests",
},
};
adminGenerateVerificationLinkMock.mockRejectedValue(mockFirebaseError);
expect(isFirebaseError(mockFirebaseError)).toBe(true);
//WHEN
const { body } = await mockApp
@ -371,9 +378,16 @@ describe("user controller test", () => {
});
it("should fail with firebase user not found", async () => {
//GIVEN
adminGenerateVerificationLinkMock.mockRejectedValue({
const mockFirebaseError = {
code: "auth/user-not-found",
} as FirebaseError);
codePrefix: "auth",
errorInfo: {
code: "auth/user-not-found",
message: "User not found",
},
};
adminGenerateVerificationLinkMock.mockRejectedValue(mockFirebaseError);
expect(isFirebaseError(mockFirebaseError)).toBe(true);
//WHEN
const { body } = await mockApp
@ -387,11 +401,13 @@ describe("user controller test", () => {
'Stack: {"decodedTokenEmail":"newuser@mail.com","userInfoEmail":"newuser@mail.com"}'
);
});
it("should fail with firebase errir", async () => {
it("should fail with unknown error", async () => {
//GIVEN
adminGenerateVerificationLinkMock.mockRejectedValue({
message: "Internal error encountered.",
} as FirebaseError);
const mockFirebaseError = {
message: "Internal server error",
};
adminGenerateVerificationLinkMock.mockRejectedValue(mockFirebaseError);
expect(isFirebaseError(mockFirebaseError)).toBe(false);
//WHEN
const { body } = await mockApp
@ -401,7 +417,7 @@ describe("user controller test", () => {
//THEN
expect(body.message).toEqual(
"Firebase failed to generate an email verification link. Please try again later."
"Firebase failed to generate an email verification link: Internal server error"
);
});
});
@ -1039,9 +1055,16 @@ describe("user controller test", () => {
});
it("should fail for duplicate email", async () => {
//GIVEN
authUpdateEmailMock.mockRejectedValue({
const mockFirebaseError = {
code: "auth/email-already-exists",
} as FirebaseError);
codePrefix: "auth",
errorInfo: {
code: "auth/email-already-exists",
message: "Email already exists",
},
};
authUpdateEmailMock.mockRejectedValue(mockFirebaseError);
expect(isFirebaseError(mockFirebaseError)).toBe(true);
//WHEN
const { body } = await mockApp
@ -1062,9 +1085,16 @@ describe("user controller test", () => {
it("should fail for invalid email", async () => {
//GIVEN
authUpdateEmailMock.mockRejectedValue({
const mockFirebaseError = {
code: "auth/invalid-email",
} as FirebaseError);
codePrefix: "auth",
errorInfo: {
code: "auth/invalid-email",
message: "Invalid email",
},
};
authUpdateEmailMock.mockRejectedValue(mockFirebaseError);
expect(isFirebaseError(mockFirebaseError)).toBe(true);
//WHEN
const { body } = await mockApp
@ -1082,9 +1112,16 @@ describe("user controller test", () => {
});
it("should fail for too many requests", async () => {
//GIVEN
authUpdateEmailMock.mockRejectedValue({
const mockFirebaseError = {
code: "auth/too-many-requests",
} as FirebaseError);
codePrefix: "auth",
errorInfo: {
code: "auth/too-many-requests",
message: "Too many requests",
},
};
authUpdateEmailMock.mockRejectedValue(mockFirebaseError);
expect(isFirebaseError(mockFirebaseError)).toBe(true);
//WHEN
const { body } = await mockApp
@ -1102,9 +1139,16 @@ describe("user controller test", () => {
});
it("should fail for unknown user", async () => {
//GIVEN
authUpdateEmailMock.mockRejectedValue({
const mockFirebaseError = {
code: "auth/user-not-found",
} as FirebaseError);
codePrefix: "auth",
errorInfo: {
code: "auth/user-not-found",
message: "User not found",
},
};
authUpdateEmailMock.mockRejectedValue(mockFirebaseError);
expect(isFirebaseError(mockFirebaseError)).toBe(true);
//WHEN
const { body } = await mockApp
@ -1126,7 +1170,12 @@ describe("user controller test", () => {
//GIVEN
authUpdateEmailMock.mockRejectedValue({
code: "auth/invalid-user-token",
} as FirebaseError);
codePrefix: "auth",
errorInfo: {
code: "auth/invalid-user-token",
message: "Invalid user token",
},
});
//WHEN
const { body } = await mockApp

View file

@ -11,7 +11,7 @@ import {
ToggleBanRequest,
ToggleBanResponse,
} from "@monkeytype/contracts/admin";
import MonkeyError from "../../utils/error";
import MonkeyError, { getErrorMessage } from "../../utils/error";
import { Configuration } from "@monkeytype/contracts/schemas/configuration";
import { addImportantLog } from "../../dal/logs";
@ -117,7 +117,10 @@ export async function handleReports(
if (e instanceof MonkeyError) {
throw new MonkeyError(e.status, e.message);
} else {
throw new MonkeyError(500, "Error handling reports: " + e.message);
throw new MonkeyError(
500,
"Error handling reports: " + getErrorMessage(e)
);
}
}
}

View file

@ -247,6 +247,7 @@ async function updateUser(uid: string): Promise<void> {
if (lbPersonalBests[mode.mode][mode.mode2] === undefined)
lbPersonalBests[mode.mode][mode.mode2] = {};
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
lbPersonalBests[mode.mode][mode.mode2][mode.language] = entry;
}

View file

@ -1,6 +1,9 @@
import _ from "lodash";
import * as UserDAL from "../../dal/user";
import MonkeyError from "../../utils/error";
import MonkeyError, {
getErrorMessage,
isFirebaseError,
} from "../../utils/error";
import { MonkeyResponse } from "../../utils/monkey-response";
import * as DiscordUtils from "../../utils/discord";
import {
@ -12,7 +15,6 @@ import {
sanitizeString,
} from "../../utils/misc";
import GeorgeQueue from "../../queues/george-queue";
import { type FirebaseError } from "firebase-admin";
import { deleteAllApeKeys } from "../../dal/ape-keys";
import { deleteAllPresets } from "../../dal/preset";
import { deleteAll as deleteAllResults } from "../../dal/result";
@ -183,31 +185,48 @@ export async function sendVerificationEmail(
: "https://monkeytype.com",
});
} catch (e) {
const firebaseError = e as FirebaseError;
if (
firebaseError.code === "auth/internal-error" &&
firebaseError.message.includes("TOO_MANY_ATTEMPTS_TRY_LATER")
) {
// for some reason this error is not handled with a custom auth/ code, so we have to do it manually
throw new MonkeyError(429, "Too many requests. Please try again later");
}
if (firebaseError.code === "auth/user-not-found") {
throw new MonkeyError(
500,
"Auth user not found when the user was found in the database. Contact support with this error message and your email",
JSON.stringify({
decodedTokenEmail: email,
userInfoEmail: userInfo.email,
stack: e.stack as unknown,
}),
userInfo.uid
);
}
if (firebaseError.message.includes("Internal error encountered.")) {
throw new MonkeyError(
500,
"Firebase failed to generate an email verification link. Please try again later."
);
if (isFirebaseError(e)) {
if (e.errorInfo.code === "auth/user-not-found") {
throw new MonkeyError(
500,
"Auth user not found when the user was found in the database. Contact support with this error message and your email",
JSON.stringify({
decodedTokenEmail: email,
userInfoEmail: userInfo.email,
}),
userInfo.uid
);
} else if (e.errorInfo.code === "auth/too-many-requests") {
throw new MonkeyError(429, "Too many requests. Please try again later");
} else {
throw new MonkeyError(
500,
"Firebase failed to generate an email verification link: " +
e.errorInfo.message
);
}
} else {
const message = getErrorMessage(e);
if (message === undefined) {
throw new MonkeyError(
500,
"Firebase failed to generate an email verification link. Unknown error occured"
);
} else {
if (message.toLowerCase().includes("too_many_attempts")) {
throw new MonkeyError(
429,
"Too many requests. Please try again later"
);
} else {
throw new MonkeyError(
500,
"Firebase failed to generate an email verification link: " +
message,
(e as Error).stack
);
}
}
}
}
await emailQueue.sendVerificationEmail(email, userInfo.name, link);
@ -394,24 +413,26 @@ export async function updateEmail(
await AuthUtil.updateUserEmail(uid, newEmail);
await UserDAL.updateEmail(uid, newEmail);
} catch (e) {
if (e.code === "auth/email-already-exists") {
throw new MonkeyError(
409,
"The email address is already in use by another account"
);
} else if (e.code === "auth/invalid-email") {
throw new MonkeyError(400, "Invalid email address");
} else if (e.code === "auth/too-many-requests") {
throw new MonkeyError(429, "Too many requests. Please try again later");
} else if (e.code === "auth/user-not-found") {
throw new MonkeyError(
404,
"User not found in the auth system",
"update email",
uid
);
} else if (e.code === "auth/invalid-user-token") {
throw new MonkeyError(401, "Invalid user token", "update email", uid);
if (isFirebaseError(e)) {
if (e.code === "auth/email-already-exists") {
throw new MonkeyError(
409,
"The email address is already in use by another account"
);
} else if (e.code === "auth/invalid-email") {
throw new MonkeyError(400, "Invalid email address");
} else if (e.code === "auth/too-many-requests") {
throw new MonkeyError(429, "Too many requests. Please try again later");
} else if (e.code === "auth/user-not-found") {
throw new MonkeyError(
404,
"User not found in the auth system",
"update email",
uid
);
} else if (e.code === "auth/invalid-user-token") {
throw new MonkeyError(401, "Invalid user token", "update email", uid);
}
} else {
throw e;
}
@ -475,6 +496,7 @@ export async function getUser(
try {
userInfo = await UserDAL.getUser(uid, "get user");
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (e.status === 404) {
//if the user is in the auth system but not in the db, its possible that the user was created by bypassing captcha
//since there is no data in the database anyway, we can just delete the user from the auth system
@ -488,6 +510,7 @@ export async function getUser(
uid
);
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (e.code === "auth/user-not-found") {
throw new MonkeyError(
404,

View file

@ -54,6 +54,7 @@ export async function get(
return preset;
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (e.error === 175) {
//QueryPlanKilled, collection was removed during the query
return false;
@ -84,6 +85,7 @@ export async function getRank(
entry: entry !== null ? entry : undefined,
};
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (e.error === 175) {
//QueryPlanKilled, collection was removed during the query
return false;

View file

@ -735,6 +735,7 @@ export async function getPersonalBests(
]);
if (mode2 !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return user.personalBests?.[mode]?.[mode2] as PersonalBest;
}

View file

@ -7,6 +7,7 @@ import { BASE_CONFIGURATION } from "../constants/base-configuration";
import { Configuration } from "@monkeytype/contracts/schemas/configuration";
import { addLog } from "../dal/logs";
import { PartialConfiguration } from "@monkeytype/contracts/configuration";
import { getErrorMessage } from "../utils/error";
const CONFIG_UPDATE_INTERVAL = 10 * 60 * 1000; // 10 Minutes
@ -86,9 +87,10 @@ export async function getLiveConfiguration(): Promise<Configuration> {
}); // Seed the base configuration.
}
} catch (error) {
const errorMessage = getErrorMessage(error) ?? "Unknown error";
void addLog(
"fetch_configuration_failure",
`Could not fetch configuration: ${error.message}`
`Could not fetch configuration: ${errorMessage}`
);
}
@ -104,9 +106,10 @@ async function pushConfiguration(configuration: Configuration): Promise<void> {
await db.collection("configuration").replaceOne({}, configuration);
serverConfigurationUpdated = true;
} catch (error) {
const errorMessage = getErrorMessage(error) ?? "Unknown error";
void addLog(
"push_configuration_failure",
`Could not push configuration: ${error.message}`
`Could not push configuration: ${errorMessage}`
);
}
}
@ -124,9 +127,10 @@ export async function patchConfiguration(
await getLiveConfiguration();
} catch (error) {
const errorMessage = getErrorMessage(error) ?? "Unknown error";
void addLog(
"patch_configuration_failure",
`Could not patch configuration: ${error.message}`
`Could not patch configuration: ${errorMessage}`
);
return false;

View file

@ -6,7 +6,7 @@ import {
type MongoClientOptions,
type WithId,
} from "mongodb";
import MonkeyError from "../utils/error";
import MonkeyError, { getErrorMessage } from "../utils/error";
import Logger from "../utils/logger";
let db: Db;
@ -58,7 +58,7 @@ export async function connect(): Promise<void> {
await mongoClient.connect();
db = mongoClient.db(DB_NAME);
} catch (error) {
Logger.error(error.message as string);
Logger.error(getErrorMessage(error) ?? "Unknown error");
Logger.error(
"Failed to connect to database. Exiting with exit status code 1."
);

View file

@ -7,6 +7,7 @@ import mustache from "mustache";
import { recordEmail } from "../utils/prometheus";
import type { EmailTaskContexts, EmailType } from "../queues/email-queue";
import { isDevEnvironment } from "../utils/misc";
import { getErrorMessage } from "../utils/error";
type EmailMetadata = {
subject: string;
@ -72,7 +73,7 @@ export async function init(): Promise<void> {
Logger.success("Email client configuration verified");
} catch (error) {
transportInitialized = false;
Logger.error(error.message as string);
Logger.error(getErrorMessage(error) ?? "Unknown error");
Logger.error("Failed to verify email client configuration.");
}
}
@ -112,7 +113,7 @@ export async function sendEmail(
recordEmail(templateName, "fail");
return {
success: false,
message: e.message as string,
message: getErrorMessage(e) ?? "Unknown error",
};
}

View file

@ -4,6 +4,7 @@ import { join } from "path";
import IORedis from "ioredis";
import Logger from "../utils/logger";
import { isDevEnvironment } from "../utils/misc";
import { getErrorMessage } from "../utils/error";
let connection: IORedis.Redis;
let connected = false;
@ -53,7 +54,7 @@ export async function connect(): Promise<void> {
connected = true;
} catch (error) {
Logger.error(error.message as string);
Logger.error(getErrorMessage(error) ?? "Unknown error");
if (isDevEnvironment()) {
await connection.quit();
Logger.warning(

View file

@ -19,6 +19,7 @@ import {
RequestAuthenticationOptions,
} from "@monkeytype/contracts/schemas/api";
import { Configuration } from "@monkeytype/contracts/schemas/configuration";
import { getMetadata, TsRestRequestWithCtx } from "./utility";
const DEFAULT_OPTIONS: RequestAuthenticationOptions = {
isGithubWebhook: false,
@ -28,11 +29,6 @@ const DEFAULT_OPTIONS: RequestAuthenticationOptions = {
isPublicOnDev: false,
};
export type TsRestRequestWithCtx = {
ctx: Readonly<MonkeyTypes.Context>;
} & TsRestRequest &
ExpressRequest;
/**
* Authenticate request based on the auth settings of the route.
* By default a Bearer token with user authentication is required.
@ -48,7 +44,7 @@ export function authenticateTsRestRequest<
): Promise<void> => {
const options = {
...DEFAULT_OPTIONS,
...((req.tsRestRoute["metadata"]?.["authenticationOptions"] ??
...((getMetadata(req)["authenticationOptions"] ??
{}) as EndpointMetadata),
};
@ -188,6 +184,7 @@ async function authenticateWithBearerToken(
email: decodedToken.email ?? "",
};
} catch (error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const errorCode = error?.errorInfo?.code as string | undefined;
if (errorCode?.includes("auth/id-token-expired")) {

View file

@ -1,5 +1,4 @@
import type { Response, NextFunction } from "express";
import { TsRestRequestWithCtx } from "./auth";
import { TsRestRequestHandler } from "@ts-rest/express";
import { EndpointMetadata } from "@monkeytype/contracts/schemas/api";
import MonkeyError from "../utils/error";
@ -8,6 +7,7 @@ import {
ConfigurationPath,
RequireConfiguration,
} from "@monkeytype/contracts/require-configuration/index";
import { getMetadata, TsRestRequestWithCtx } from "./utility";
export function verifyRequiredConfiguration<
T extends AppRouter | AppRoute
@ -17,9 +17,7 @@ export function verifyRequiredConfiguration<
_res: Response,
next: NextFunction
): Promise<void> => {
const requiredConfigurations = getRequireConfigurations(
req.tsRestRoute["metadata"] as EndpointMetadata | undefined
);
const requiredConfigurations = getRequireConfigurations(getMetadata(req));
if (requiredConfigurations === undefined) {
next();

View file

@ -1,7 +1,7 @@
import * as db from "../init/db";
import { v4 as uuidv4 } from "uuid";
import Logger from "../utils/logger";
import MonkeyError from "../utils/error";
import MonkeyError, { getErrorMessage } from "../utils/error";
import { incrementBadAuth } from "./rate-limit";
import type { NextFunction, Response } from "express";
import { isCustomCode } from "../constants/monkey-status-codes";
@ -92,7 +92,7 @@ async function errorHandlingMiddleware(
});
} catch (e) {
Logger.error("Logging to db failed.");
Logger.error(e.message as string);
Logger.error(getErrorMessage(e) ?? "Unknown error");
console.error(e);
}
} else {
@ -107,7 +107,7 @@ async function errorHandlingMiddleware(
return;
} catch (e) {
Logger.error("Error handling middleware failed.");
Logger.error(e.message as string);
Logger.error(getErrorMessage(e) ?? "Unknown error");
console.error(e);
}

View file

@ -4,13 +4,13 @@ import type { Response, NextFunction } from "express";
import { getPartialUser } from "../dal/user";
import { isAdmin } from "../dal/admin-uids";
import { TsRestRequestHandler } from "@ts-rest/express";
import { TsRestRequestWithCtx } from "./auth";
import {
EndpointMetadata,
RequestAuthenticationOptions,
PermissionId,
} from "@monkeytype/contracts/schemas/api";
import { isDevEnvironment } from "../utils/misc";
import { getMetadata, TsRestRequestWithCtx } from "./utility";
type RequestPermissionCheck = {
type: "request";
@ -77,9 +77,7 @@ export function verifyPermissions<
_res: Response,
next: NextFunction
): Promise<void> => {
const metadata = req.tsRestRoute["metadata"] as
| EndpointMetadata
| undefined;
const metadata = getMetadata(req);
const requiredPermissionIds = getRequiredPermissionIds(metadata);
if (
requiredPermissionIds === undefined ||

View file

@ -8,8 +8,6 @@ import {
type Options,
} from "express-rate-limit";
import { isDevEnvironment } from "../utils/misc";
import { EndpointMetadata } from "@monkeytype/contracts/schemas/api";
import { TsRestRequestWithCtx } from "./auth";
import { TsRestRequestHandler } from "@ts-rest/express";
import {
limits,
@ -18,6 +16,7 @@ import {
Window,
} from "@monkeytype/contracts/rate-limit/index";
import statuses from "../constants/monkey-status-codes";
import { getMetadata, TsRestRequestWithCtx } from "./utility";
export const REQUEST_MULTIPLIER = isDevEnvironment() ? 100 : 1;
@ -99,8 +98,7 @@ export function rateLimitRequest<
res: Response,
next: NextFunction
): Promise<void> => {
const rateLimit = (req.tsRestRoute["metadata"] as EndpointMetadata)
?.rateLimit;
const rateLimit = getMetadata(req).rateLimit;
if (rateLimit === undefined) {
next();
return;

View file

@ -3,7 +3,12 @@ import type { Request, Response, NextFunction, RequestHandler } from "express";
import { recordClientVersion as prometheusRecordClientVersion } from "../utils/prometheus";
import { isDevEnvironment } from "../utils/misc";
import MonkeyError from "../utils/error";
import { TsRestRequestWithCtx } from "./auth";
import { EndpointMetadata } from "@monkeytype/contracts/schemas/api";
export type TsRestRequestWithCtx = {
ctx: Readonly<MonkeyTypes.Context>;
} & TsRestRequest &
ExpressRequest;
/**
* record the client version from the `x-client-version` or ` client-version` header to prometheus
@ -35,3 +40,8 @@ export function onlyAvailableOnDev(): MonkeyTypes.RequestHandler {
}
};
}
export function getMetadata(req: TsRestRequestWithCtx): EndpointMetadata {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return (req.tsRestRoute["metadata"] ?? {}) as EndpointMetadata;
}

View file

@ -14,6 +14,7 @@ import * as EmailClient from "./init/email-client";
import { init as initFirebaseAdmin } from "./init/firebase-admin";
import { createIndicies as leaderboardDbSetup } from "./dal/leaderboards";
import { createIndicies as blocklistDbSetup } from "./dal/blocklist";
import { getErrorMessage } from "./utils/error";
async function bootServer(port: number): Promise<Server> {
try {
@ -74,7 +75,8 @@ async function bootServer(port: number): Promise<Server> {
recordServerVersion(version);
} catch (error) {
Logger.error("Failed to boot server");
Logger.error(error.message as string);
const message = getErrorMessage(error);
Logger.error(message ?? "Unknown error");
console.error(error);
return process.exit(1);
}

View file

@ -89,7 +89,8 @@ export class WeeklyXpLeaderboard {
const currentEntryTimeTypedSeconds =
currentEntry !== null
? (JSON.parse(currentEntry)?.timeTypedSeconds as number | undefined)
? (JSON.parse(currentEntry) as { timeTypedSeconds: number | undefined })
?.timeTypedSeconds
: undefined;
const totalTimeTypedSeconds =

View file

@ -1,10 +1,9 @@
type ObjectId = import("mongodb").ObjectId;
type ExpressRequest = import("express").Request;
/* eslint-disable @typescript-eslint/no-explicit-any */
type TsRestRequest = import("@ts-rest/express").TsRestRequest<any>;
/* eslint-enable @typescript-eslint/no-explicit-any */
type AppRoute = import("@ts-rest/core").AppRoute;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type TsRestRequest = import("@ts-rest/express").TsRestRequest<any>;
type AppRouter = import("@ts-rest/core").AppRouter;
declare namespace MonkeyTypes {
export type DecodedToken = {

View file

@ -9,6 +9,7 @@ import { type DecodedIdToken, UserRecord } from "firebase-admin/auth";
import { isDevEnvironment } from "./misc";
import emailQueue from "../queues/email-queue";
import * as UserDAL from "../dal/user";
import { isFirebaseError } from "./error";
const tokenCache = new LRUCache<string, DecodedIdToken>({
max: 20000,
@ -105,7 +106,8 @@ export async function sendForgotPasswordEmail(email: string): Promise<void> {
await emailQueue.sendForgotPasswordEmail(email, name, link);
} catch (err) {
if (err.errorInfo?.code !== "auth/user-not-found") {
if (isFirebaseError(err) && err.errorInfo.code !== "auth/user-not-found") {
// eslint-disable-next-line @typescript-eslint/only-throw-error
throw err;
}
}

View file

@ -1,6 +1,53 @@
import { v4 as uuidv4 } from "uuid";
import { isDevEnvironment } from "./misc";
import { MonkeyServerErrorType } from "@monkeytype/contracts/schemas/api";
import { FirebaseError } from "firebase-admin";
type FirebaseErrorParent = {
code: string;
errorInfo: FirebaseError;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function isFirebaseError(err: any): err is FirebaseErrorParent {
return (
typeof err === "object" &&
"code" in err &&
"errorInfo" in err &&
"codePrefix" in err &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
typeof err.errorInfo === "object" &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
"code" in err.errorInfo &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
"message" in err.errorInfo
);
}
export function getErrorMessage(error: unknown): string | undefined {
let message = "";
if (error instanceof Error) {
message = error.message;
} else if (
error !== null &&
typeof error === "object" &&
"message" in error &&
(typeof error.message === "string" || typeof error.message === "number")
) {
message = `${error.message}`;
} else if (typeof error === "string") {
message = error;
} else if (typeof error === "number") {
message = `${error}`;
}
if (message === "") {
return undefined;
}
return message;
}
class MonkeyError extends Error implements MonkeyServerErrorType {
status: number;

View file

@ -180,12 +180,13 @@ export function updateLeaderboardPersonalBests(
if (!shouldUpdateLeaderboardPersonalBests(result)) {
return null;
}
const mode = result.mode;
const mode2 = result.mode2;
const lbPb = lbPersonalBests ?? {};
const mode = result.mode as keyof typeof lbPb;
const mode2 = result.mode2 as unknown as keyof (typeof lbPb)[typeof mode];
lbPb[mode] ??= {};
lbPb[mode][mode2] ??= {};
const bestForEveryLanguage = {};
const bestForEveryLanguage: Record<string, PersonalBest> = {};
(userPersonalBests[mode][mode2] as PersonalBest[]).forEach(
(pb: PersonalBest) => {
const language = pb.language;
@ -198,12 +199,14 @@ export function updateLeaderboardPersonalBests(
}
);
_.each(bestForEveryLanguage, (pb: PersonalBest, language: string) => {
const languageDoesNotExist = lbPb[mode][mode2][language] === undefined;
const languageIsEmpty = _.isEmpty(lbPb[mode][mode2][language]);
const languageDoesNotExist = lbPb[mode][mode2]?.[language] === undefined;
const languageIsEmpty = _.isEmpty(lbPb[mode][mode2]?.[language]);
if (
languageDoesNotExist ||
languageIsEmpty ||
lbPb[mode][mode2][language].wpm < pb.wpm
(languageDoesNotExist ||
languageIsEmpty ||
(lbPb[mode][mode2]?.[language]?.wpm ?? 0) < pb.wpm) &&
lbPb[mode][mode2] !== undefined
) {
lbPb[mode][mode2][language] = pb;
}

View file

@ -215,6 +215,8 @@ export function recordAuthTime(
time: number,
req: Request
): void {
// for some reason route is not in the types
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const reqPath = req.baseUrl + req.route.path;
let normalizedPath = "/";
@ -234,6 +236,8 @@ const requestCountry = new Counter({
});
export function recordRequestCountry(country: string, req: Request): void {
// for some reason route is not in the types
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const reqPath = req.baseUrl + req.route.path;
let normalizedPath = "/";

View file

@ -1,3 +1,3 @@
#!bin/sh
#!/bin/sh
./applyConfig.sh &
node server.js

View file

@ -58,7 +58,7 @@
"sass": "1.70.0",
"subset-font": "2.3.0",
"typescript": "5.5.4",
"vite": "5.1.7",
"vite": "5.2.14",
"vite-bundle-visualizer": "1.0.1",
"vite-plugin-checker": "0.7.2",
"vite-plugin-filter-replace": "0.1.13",

View file

@ -644,32 +644,9 @@
color: var(--sub-color);
// grid-column: 1/3;
margin-bottom: 1rem;
--unreached-color: var(--sub-color);
--speed-0-color: var(--colorful-error-color);
--speed-1-color: color-mix(
in srgb,
var(--colorful-error-color),
var(--text-color)
);
--speed-2-color: var(--text-color);
--speed-3-color: color-mix(in srgb, var(--main-color), var(--text-color));
--speed-4-color: var(--main-color);
&.withSubColor {
--unreached-color: var(--sub-alt-color);
--speed-2-color: var(--sub-color);
--speed-3-color: color-mix(
in srgb,
var(--sub-color),
var(--text-color)
);
}
.textButton {
padding: 0 0.25em;
}
#copyWordsListButton,
#playpauseReplayButton {
margin-left: 0.5em;
@ -682,7 +659,6 @@
font-size: 0.75rem;
color: var(--sub-color);
width: min-content;
.boxes {
// display: flex;
display: grid;
@ -697,22 +673,11 @@
display: grid;
place-content: center center;
}
.box0 {
.box:nth-child(1) {
border-radius: var(--roundness) 0 0 var(--roundness);
background-color: var(--speed-0-color);
}
.box1 {
background-color: var(--speed-1-color);
}
.box2 {
background-color: var(--speed-2-color);
}
.box3 {
background-color: var(--speed-3-color);
}
.box4 {
.box:nth-child(5) {
border-radius: 0 var(--roundness) var(--roundness) 0;
background-color: var(--speed-4-color);
}
}
}
@ -727,27 +692,9 @@
flex-wrap: wrap;
width: 100%;
align-content: flex-start;
.unreached {
color: var(--unreached-color);
}
.word {
position: relative;
margin: 0.18rem 0.6rem 0.15rem 0;
&[speed="0"] {
color: var(--speed-0-color);
}
&[speed="1"] {
color: var(--speed-1-color);
}
&[speed="2"] {
color: var(--speed-2-color);
}
&[speed="3"] {
color: var(--speed-3-color);
}
&[speed="4"] {
color: var(--speed-4-color);
}
& letter.corrected {
color: var(--text-color);
border-bottom: 2px dotted var(--main-color);

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { debounce } from "throttle-debounce";
// import * as Numbers from "../utils/numbers";
import * as ConfigEvent from "../observables/config-event";

View file

@ -1,3 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { envConfig } from "../constants/env-config";
const siteKey = envConfig.recaptchaSiteKey;
@ -13,7 +16,6 @@ export function render(
}
//@ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
const widgetId = grecaptcha.render(element, {
sitekey: siteKey,
callback,

View file

@ -95,7 +95,7 @@ class ChartWithUpdateColors<
id: DatasetIds extends never ? never : "x" | DatasetIds
): DatasetIds extends never ? never : CartesianScaleOptions {
//@ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
return this.options.scales[id];
}
}
@ -1119,6 +1119,7 @@ async function updateColors<
//@ts-expect-error
chart.data.datasets[0].borderColor = (ctx): string => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const isPb = ctx.raw?.isPb as boolean;
const color = isPb ? textcolor : maincolor;
return color;

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
export function init(): void {
$("head").append(`<script>
!function(e){var s=new XMLHttpRequest;s.open("GET","https://api.enthusiastgaming.net/scripts/cdn.enthusiast.gg/script/eg-aps/release/eg-aps-bootstrap-v2.0.0.bundle.js?site=monkeytype.com",!0),s.onreadystatechange=function(){var t;4==s.readyState&&(200<=s.status&&s.status<300||304==s.status)&&((t=e.createElement("script")).type="text/javascript",t.text=s.responseText,e.head.appendChild(t))},s.send(null)}(document);

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
//@ts-nocheck

View file

@ -46,6 +46,7 @@ window.onerror = function (message, url, line, column, error): void {
window.onunhandledrejection = function (e): void {
if (Misc.isDevEnvironment()) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const message = (e.reason.message ?? e.reason) as string;
Notifications.add(`${message}`, -1, {
customTitle: "DEV: Unhandled rejection",
@ -54,6 +55,7 @@ window.onunhandledrejection = function (e): void {
console.error(e);
}
void log("error", {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
error: (e.reason.stack ?? "") as string,
});
};

View file

@ -426,7 +426,7 @@ function reset(): void {
$(".pageSettings .section[data-config-name='fontFamily'] .buttons").empty();
for (const select of document.querySelectorAll(".pageSettings select")) {
//@ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
select?.slim?.destroy?.();
}
}

View file

@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import * as Notifications from "../elements/notifications";
import * as AdController from "../controllers/ad-controller";
import * as Skeleton from "../utils/skeleton";
@ -38,7 +40,6 @@ export async function show(): Promise<void> {
.removeClass("hidden")
.animate({ opacity: 1 }, 125, () => {
//@ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
window.dataLayer.push({ event: "EG_Video" });
});
}

View file

@ -325,7 +325,9 @@ $("#replayWords").on("click", "letter", (event) => {
const replayWords = document.querySelector("#replayWords");
const words = [...(replayWords?.children ?? [])];
targetWordPos = words.indexOf(event.target.parentNode as HTMLElement);
targetWordPos = words.indexOf(
(event.target as HTMLElement).parentNode as HTMLElement
);
const letters = [...(words[targetWordPos] as HTMLElement).children];
targetCurPos = letters.indexOf(event.target as HTMLElement);

View file

@ -12,6 +12,7 @@ import * as Misc from "../utils/misc";
import * as Strings from "../utils/strings";
import * as JSONData from "../utils/json-data";
import * as Numbers from "../utils/numbers";
import { blendTwoHexColors } from "../utils/colors";
import { get as getTypingSpeedUnit } from "../utils/typing-speed-units";
import * as SlowTimer from "../states/slow-timer";
import * as CompositionState from "../states/composition";
@ -1444,14 +1445,6 @@ export async function applyBurstHeatmap(): Promise<void> {
if (Config.burstHeatmap) {
$("#resultWordsHistory .heatmapLegend").removeClass("hidden");
const themeColors = await ThemeColors.getAll();
if (themeColors.main === themeColors.text) {
$("#resultWordsHistory").addClass("withSubColor");
} else {
$("#resultWordsHistory").removeClass("withSubColor");
}
let burstlist = [...TestInput.burstHistory];
burstlist = burstlist.filter((x) => x !== Infinity);
@ -1462,6 +1455,28 @@ export async function applyBurstHeatmap(): Promise<void> {
burstlist[index] = Math.round(typingSpeedUnit.fromWpm(burst));
});
const themeColors = await ThemeColors.getAll();
let colors = [
themeColors.colorfulError,
blendTwoHexColors(themeColors.colorfulError, themeColors.text, 0.5),
themeColors.text,
blendTwoHexColors(themeColors.main, themeColors.text, 0.5),
themeColors.main,
];
let unreachedColor = themeColors.sub;
if (themeColors.main === themeColors.text) {
colors = [
themeColors.colorfulError,
blendTwoHexColors(themeColors.colorfulError, themeColors.text, 0.5),
themeColors.sub,
blendTwoHexColors(themeColors.sub, themeColors.text, 0.5),
themeColors.main,
];
unreachedColor = themeColors.subAlt;
}
const burstlistSorted = burstlist.sort((a, b) => a - b);
const burstlistLength = burstlist.length;
@ -1511,7 +1526,7 @@ export async function applyBurstHeatmap(): Promise<void> {
$("#resultWordsHistory .words .word").each((_, word) => {
const wordBurstAttr = $(word).attr("burst");
if (wordBurstAttr === undefined) {
$(word).addClass("unreached");
$(word).css("color", unreachedColor);
} else {
let wordBurstVal = parseInt(wordBurstAttr);
wordBurstVal = Math.round(
@ -1519,11 +1534,16 @@ export async function applyBurstHeatmap(): Promise<void> {
);
steps.forEach((step) => {
if (wordBurstVal >= step.val) {
$(word).addClass("heatmapInherit").attr("speed", step.colorId);
$(word).addClass("heatmapInherit");
$(word).css("color", colors[step.colorId] as string);
}
});
}
});
$("#resultWordsHistory .heatmapLegend .boxes .box").each((index, box) => {
$(box).css("background", colors[index] as string);
});
} else {
$("#resultWordsHistory .heatmapLegend").addClass("hidden");
$("#resultWordsHistory .words .word").removeClass("heatmapInherit");

View file

@ -286,6 +286,7 @@ export async function getSection(language: string): Promise<Section> {
sectionReq.onload = (): void => {
if (sectionReq.readyState === 4) {
if (sectionReq.status === 200) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
let sectionText = JSON.parse(sectionReq.responseText).query.pages[
pageid.toString()
].extract as string;

View file

@ -198,7 +198,7 @@
},
{
"name": "slovenian",
"languages": ["slovenian"]
"languages": ["slovenian", "slovenian_1k", "slovenian_5k"]
},
{
"name": "croatian",

View file

@ -125,6 +125,8 @@
,"slovak_1k"
,"slovak_10k"
,"slovenian"
,"slovenian_1k"
,"slovenian_5k"
,"croatian"
,"dutch"
,"dutch_1k"

View file

@ -1,5 +1,6 @@
{
"name": "slovenian",
"bcp47": "sl-SI",
"words": [
"hitro",
"potem",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2046,5 +2046,16 @@
"row4": ["qQ", ",<", ".>", "xX", "zZ", "vV", "fF", "pP", "kK", "/?"],
"row5": [" "]
}
},
"inqwerted": {
"keymapShowTopRow": false,
"type": "ansi",
"keys": {
"row1": ["`~", "1!", "2@", "3#", "4$", "5%", "6^", "7&", "8*", "9(", "0)", "-_", "=+"],
"row2": ["tT", "rR", "eE", "wW", "qQ", "pP", "oO", "iI", "uU", "yY", "[{", "]}", "\\|"],
"row3": ["gG", "fF", "dD", "sS", "aA", ";:", "lL", "kK", "jJ", "hH", "'\""],
"row4": ["bB", "vV", "cC", "xX", "zZ", "/?", ".>", ",<", "mM", "nN"],
"row5": [" "]
}
}
}

View file

@ -146,7 +146,7 @@
"length": 201
},
{
"text": "static public void Main() {\n\t\tstring[] @for = {\"C#\", \"PHP\", \"Java\", \"Python\"};\n\t\tforeach (string @as in @for) {\n\t\t\tConsole.WriteLine(\"Element of Array: {0}\", @as); \n\t\t\t} \n\t\t}",
"text": "public static void Main() {\n\t\tstring[] @for = {\"C#\", \"PHP\", \"Java\", \"Python\"};\n\t\tforeach (string @as in @for) {\n\t\t\tConsole.WriteLine(\"Element of Array: {0}\", @as); \n\t\t\t} \n\t\t}",
"id": 24,
"source": "Geeks For Geeks - C# | Verbatim String Literal",
"length": 174

View file

@ -170,7 +170,7 @@
"id": 27
},
{
"text": "function fisherYates(originalArray) {\n\tconst array = originalArray.slice(0)\n\n\tfor (let i = array.length - 1; i > 0; i -= 1) {\n\t\tconst randomIndex = Math.floor(Math.random() * (i + 1))\n\t\t;[array[i], array[randomIndex]] = [array[randomIndex], array[i]]\n\t}\n\n\treturn array\n}",
"text": "function fisherYates(originalArray) {\n\tconst array = originalArray.slice(0)\n\n\tfor (let i = array.length - 1; i > 0; i -= 1) {\n\t\tconst randomIndex = Math.floor(Math.random() * (i + 1));\n\t\t[array[i], array[randomIndex]] = [array[randomIndex], array[i]]\n\t}\n\n\treturn array\n}",
"source": "trekhleb/javascript-algorithms - Fisher-Yates sourcecode",
"length": 270,
"id": 28

View file

@ -6387,7 +6387,7 @@
},
{
"text": "The wonder is, not that the field of stars is so vast, but that man has measured it.",
"source": "Civilization V",
"source": "Anatole France",
"id": 1115,
"length": 84
},
@ -6758,10 +6758,10 @@
"length": 166
},
{
"text": "This is what I offer each of you today: one tiny vial of liquid luck to the student who in the hour that remains manages to brew an acceptable draft of living death. The recipe for which can be found on page 10 of your books.",
"text": "This is what I offer each of you today: one tiny vial of liquid luck to the student who in the hour that remains manages to brew an acceptable draught of living death. The recipe for which can be found on page 10 of your books.",
"source": "Harry Potter and the Half-Blood Prince",
"id": 1181,
"length": 225
"length": 227
},
{
"text": "The moon will never lie to anyone. Be like the moon. No one hates the moon or wants to kill it. The moon does not take antidepressants and never gets sent to prison. The moon never shot a guy in the face and ran away. The moon has been around a long time and has never tried to rip anyone off. The moon does not care who you want to touch or what color you are. The moon treats everyone the same. The moon never tries to get in on the guest list or use your name to impress others. Be like the moon. When others insult or belittle in an attempt to elevate themselves, the moon sits passively and watches, never lowering itself to anything that weak. The moon is beautiful and bright. It needs no makeup to look beautiful. The moon never shoves clouds out of its way so it can be seen. The moon needs not fame or money to be powerful. The moon never asks you to go to war to defend it. Be like the moon.",
@ -6908,10 +6908,10 @@
"length": 149
},
{
"text": "In our world, every storm has an end. Every night, brings a new morning. What's important is to trust those you love, and never give up. We must all keep hope alive.",
"text": "In our world, every storm has an end. Every night brings a new morning. What's important is to trust those you love, and never give up. We must all keep hope alive.",
"source": "Chrono Trigger",
"id": 1207,
"length": 165
"length": 164
},
{
"text": "When the light of life has gone, no change for the meter. Then the king of spivs will come, selling blood by the litre. When nothing's sacred anymore, when the demon's knocking on your door, you'll still be staring down at the floor.",
@ -9596,10 +9596,10 @@
"length": 241
},
{
"text": "I must have left my house at eight because I always do. My train, I'm certain, left the station when it was due. I must have read the morning paper going into town, and having gotten through the editorial no doubt I must have frowned.",
"source": "The Day Before You Came",
"text": "Must have left my house at eight, because I always do. My train, I'm certain, left the station just when it was due. I must have read the morning paper going into town, and having gotten through the editorial no doubt I must have frowned.",
"source": "ABBA - The Day Before You Came",
"id": 1690,
"length": 234
"length": 238
},
{
"text": "This used to be my playground, this used to be my childhood dream, this used to be the place I ran to whenever I was in need of a friend. Why did it have to end?",
@ -10749,7 +10749,7 @@
},
{
"text": "Ocean man, take me by the hand, lead me to the land that you understand. Ocean man, the voyage to the corner of the globe is a real trip. Ocean man, the crust of a tan man imbibed by the sand. Soaking up the thirst of the land.",
"source": "Ocean Man",
"source": "Ween - Ocean Man",
"id": 1893,
"length": 227
},
@ -12632,10 +12632,10 @@
"length": 607
},
{
"text": "You were the chosen one! It was said that you would destroy the Sith, not join them. You were to bring balance to the Force, not leave it in darkness.",
"text": "You were the chosen one! It was said that you would destroy the Sith, not join them. Bring balance to the Force, not leave it in darkness.",
"source": "Star Wars Episode III - Revenge of the Sith",
"id": 2226,
"length": 150
"length": 138
},
{
"text": "Well, when I came home from school my head started to get really hot. So I drank some cold water, but it didn't do nothing. So I laid in the bathtub for a while, but then I realized that it was my hair that was making my head hot. So I went into my kitchen and I shaved it all off. I don't want anyone to see.",
@ -13947,10 +13947,10 @@
"length": 172
},
{
"text": "Every single night I endure the fight of little wings of white-flamed butterflies in my brain. These ideas of mine percolate the mind, trickle down the spine, swarm the bell, swellin' to a blaze. That's when the pain comes in like a second skeleton trying to fit beneath the skin. I can't fit the feelings in. Every single night is a fight with my brain.",
"text": "Every single night I endure the flight of little wings of white-flamed butterflies in my brain. These ideas of mine percolate the mind, trickle down the spine, swarm the bell, swellin' to a blaze. That's when the pain comes in like a second skeleton trying to fit beneath the skin. I can't fit the feelings in. Every single night is a fight with my brain.",
"source": "Every Single Night",
"id": 2462,
"length": 354
"length": 355
},
{
"text": "I am sitting in a room, different from the one you are in now. I am recording the sound of my speaking voice, and I am going to play it back into the room again and again, until the resonant frequencies of the room reinforce themselves, so that any semblance of my speech, with perhaps the exception of rhythm, is destroyed. What you will hear then are the natural resonant frequencies of the room articulated by speech. I regard this activity not so much as a demonstration of a physical fact, but more as a way to smooth out any irregularities my speech might have.",
@ -14062,7 +14062,7 @@
},
{
"text": "Our attempts to succeed in life will be largely determined by our confidence. This rests on the way we see ourselves, what we believe about ourselves, what we believe we are capable of.",
"source": "The Born Identity",
"source": "The Bourne Identity",
"id": 2484,
"length": 185
},
@ -15188,12 +15188,6 @@
"id": 2691,
"length": 243
},
{
"text": "She said let's change our luck, this night is all we've got. Drive fast until we crash... This dead-end life... Sweet dreams that won't come true, I'd leave it all for you. Brick walls are closing in, let's make... A run tonight.",
"source": "Skyway Avenue",
"id": 2693,
"length": 229
},
{
"text": "Renting a car in Palau has its advantages and disadvantages. Taxis and tour companies can take you on guided tours of the big island, but they are often expensive and less convenient than a rental car. Having your own car will allow you to go off the beaten path and to take your time. It is also a great idea if you are planning to spend a night or two at one of the little beach resorts up north.",
"source": "Federated States of Micronesia and Palau",
@ -18716,12 +18710,6 @@
"id": 3333,
"length": 200
},
{
"text": "As the United States assumed greater status as a power in world politics, Americans came to believe that the nation's actions on the world stage should be guided by American political and moral principles.",
"source": "American Government and Politics Today: The Essentials",
"id": 3334,
"length": 205
},
{
"text": "Seems like everybody's got a price. I wonder how they sleep at night when the sale comes first and the truth comes second.",
"source": "Price Tag",
@ -23115,12 +23103,6 @@
"id": 4125,
"length": 538
},
{
"text": "I need a love reaction. Come on now, baby, give me just one look.",
"source": "Dancing in the Dark",
"id": 4127,
"length": 65
},
{
"text": "Okay, stand outa the way. Sometimes when I go to exertin' myself I use up all the air nearby and grown men faint from suffocation. Stand back. There's liable to be crackin' cement and flying steel. Get the women and kids someplace safe. Stand back...",
"source": "One Flew Over the Cuckoo's Nest",
@ -23199,12 +23181,6 @@
"id": 4140,
"length": 414
},
{
"text": "Everyday I get up and pray to Jah and he increases the number of clocks by exactly one. Everybody's coming home for lunch these days. Last night there were skinheads on my lawn. Take the skinheads bowling, take them bowling.",
"source": "Take the Skinheads Bowling",
"id": 4141,
"length": 224
},
{
"text": "You, my friend, are a victim of disorganized thinking. You are under the unfortunate impression that just because you run away you have no courage; you're confusing courage with wisdom.",
"source": "The Wizard of Oz",
@ -25132,7 +25108,7 @@
"length": 340
},
{
"text": "I been doin' this a long time. I ain't never said nothin' to no cop. I feel old... I been out there since I was 13. I ain't never screwed up a count, never stole off a package, never did some stuff that I wasn't told to do. I've been straight up. But what come back? You think if I get jammed up on some stuff, they be like, \"All right, yeah. Bodie been there. Bodie hang tough. We got to his pay lawyer. We got a bail.\" They want me to stand with them, right? Where they at when they supposed to be standing by us? I mean, when it goes bad and there's hell to pay, where they at? This game is rigged, man...",
"text": "I been doin' this a long time. I ain't never said nothin' to no cop. I feel old... I been out there since I was 13. I ain't never screwed up a count, never stole off a package, never did some stuff that I wasn't told to do. I've been straight up. But what come back? You think if I get jammed up on some stuff, they be like, \"All right, yeah. Bodie been there. Bodie hang tough. We got to pay his lawyer. We got a bail.\" They want me to stand with them, right? Where they at when they supposed to be standing by us? I mean, when it goes bad and there's hell to pay, where they at? This game is rigged, man...",
"source": "The Wire",
"id": 4498,
"length": 608
@ -34449,7 +34425,7 @@
"id": 6386
},
{
"text": "You know, it's funny; when you look at someone through rose colored glasses, all the red flags just look like flags.",
"text": "You know, it's funny; when you look at someone through rose-colored glasses, all the red flags just look like flags.",
"source": "Bojack Horseman",
"length": 116,
"approvedBy": "Smithster",
@ -35268,16 +35244,16 @@
"id": 6512
},
{
"text": "I thought I felt your shape but I was wrong. Really all I felt was falsely strong.",
"text": "I thought I felt your shape, but I was wrong. Really all I felt, was falsely strong.",
"source": "I Felt Your Shape",
"length": 82,
"length": 84,
"approvedBy": "Smithster",
"id": 6513
},
{
"text": "Looking from up here, it's as if each flame were a small dream for each person. They look like a bonfire of dreams, don't they?... But there's not flame for me here. I'm just a temporary visitor, taking comfort from the flame.",
"text": "Looking from up here, it's as if each flame were a small dream for each person. They look like a bonfire of dreams, don't they?... But there's no flame for me here. I'm just a temporary visitor, taking comfort from the flame.",
"source": "Berserk",
"length": 226,
"length": 225,
"approvedBy": "Smithster",
"id": 6514
},
@ -37338,12 +37314,6 @@
"length": 231,
"id": 6864
},
{
"text": "I am... not interested, little sun. Try again when you have become a man.",
"source": "Final Fantasy XIV",
"length": 73,
"id": 6865
},
{
"text": "Stay strong. Keep the faith. At duty's end, we will meet again. We will. We will.",
"source": "Final Fantasy XIV",
@ -38299,9 +38269,9 @@
"id": 7030
},
{
"text": "Darkness cannot drive out darkness: only light can do that. Hate cannot drive out hate: only love can do that.",
"text": "Darkness cannot drive out darkness - only light can do that. Hate cannot drive out hate - only love can do that.",
"source": "Martin Luther King Jr.",
"length": 110,
"length": 112,
"id": 7031
},
{
@ -38352,12 +38322,6 @@
"length": 108,
"id": 7039
},
{
"text": "Don't ever underestimate the power of volunteering. Noah's ark was built by volunteers, the Titanic was built by professionals.",
"source": "Anonymous",
"length": 127,
"id": 7040
},
{
"text": "You aren't a \"broken wreck\", Joshua. You're just scared, mostly because you care for people so much it breaks your heart… and you're lying to yourself about it. That's how I see it, and I know I'm right.",
"source": "Legend of Heroes: Trails in the Sky SC",

View file

@ -1537,12 +1537,6 @@
"length": 404,
"id": 264
},
{
"text": "J'ai envie de te dire oui mais je ne sais pas comment faire alors aide moi. je pense que ne nous sommes pas en mesure de répondre à ceci dans la mesure où la question posée est compliquée à répondre et je pense que pour y répondre nous devons déjà connaître comment l'analyser afin de pouvoir mieux l'appréhender et la terminer à tout jamais. Le principe même de la réflexion est important pour bien comprendre la question et son enjeu et donc pour moi un manque d'analyse sur une question est end.",
"source": "fakes - aqf",
"length": 498,
"id": 265
},
{
"text": "Vous savez, moi je ne crois pas qu'il y ait de bonne ou de mauvaise situation. Moi, si je devais résumer ma vie aujourd'hui avec vous, je dirais que c'est d'abord des rencontres. Des gens qui m'ont tendu la main, peut-être à un moment où je ne pouvais pas, où j'étais seul chez moi. Et c'est assez curieux de se dire que les hasards, les rencontres, forgent une destinée... Parce que quand on a le goût de la chose, quand on a le goût de la chose bien faite, le beau geste, parfois on ne trouve pas..",
"source": "je ne sais pas - Otis citation astérix et obélix mission cléopatre",
@ -2221,12 +2215,6 @@
"length": 343,
"id": 378
},
{
"text": "La educación es el pasaporte para el futuro, el mañana pertenece a aquellos que se preparan para él en el día de hoy. Oséase, el hábito del estudio es el más demandado en cuánto a éxito.",
"source": "Malcom X - La educación",
"length": 186,
"id": 379
},
{
"text": "La fatalité fond sur nous. Le temps s'étire, les jours se suivent pesamment, en nous berçant de l'illusion que le désastre redouté va être différé. Puis, brusquement, les jours sombres que nous avons tous prédits sont sur nous, et l'heure est passée où nous aurions pu éviter le sort funeste. Jusqu'à quel âge faut-il vivre pour tirer des leçons de ses expériences ?",
"source": "Robin Hobb - Les aventuriers de la mer",
@ -2893,12 +2881,6 @@
"length": 496,
"id": 492
},
{
"text": "Je ne veux pas travailler Je ne veux pas déjeuner Je veux seulement l'oublier Et puis je fume Déjà j'ai connu le parfum de l'amour Un millions de roses n'embaumeraient pas autant.",
"source": "Pink Martini - Sympathique",
"length": 179,
"id": 493
},
{
"text": "L'été craonnait, doux mais ferme, réchauffait ce bronze impeccablement lové sur lui-même : trois spires de vipères à tenter l'orfèvre, moins les saphirs classiques des yeux, car, heureusement pour moi, cette vipère, elle dormait.",
"source": "Hervé Bazin - Vipère au Poing",
@ -3079,12 +3061,6 @@
"length": 221,
"id": 523
},
{
"text": "Parler avec ces pétasses, songeait Bruno en retraversant le camping, c'est comme pisser dans un urinoir rempli de mégots ; ou encore c'est comme chier dans une chiotte remplie de serviettes hygiéniques : les choses ne rentrent pas, et elles se mettent à puer.",
"source": "Michel Houellebecq - Les Particules élémentaires",
"length": 259,
"id": 524
},
{
"text": "Le sang est composé d'une partie liquide, le plasma, et d'une partie solide, les globules rouges, les globules blancs et les plaquettes. Le sang est essentiel à la vie des cellules et donc de notre corps.",
"source": "Elisabeth Andréani, etc. - De quoi est fait le sang ?",
@ -4004,9 +3980,9 @@
"id": 678
},
{
"text": "Celui qui sait tout ne sait rien. Celui qui veut apprendre tout ne pourra jamais le faire. Celui qui veut apprendre en commençant par le début sans se soucir de tout fera des grandes choses.",
"text": "Celui qui sait tout ne sait rien. Celui qui veut apprendre tout ne pourra jamais le faire. Celui qui veut apprendre en commençant par le début sans se soucier de tout fera des grandes choses.",
"source": "Sarah Lemaitre",
"length": 190,
"length": 191,
"id": 679
},
{

View file

@ -3314,9 +3314,9 @@
"id": 577
},
{
"text": "Eines Tages wirdst du erwachen, und es wird keine Zeit dafür geben, zu machen, was du schon immer machen wolltest. Mach es lieber jetzt.",
"text": "Eines Tages wirst du erwachen, und es wird keine Zeit dafür geben, zu machen, was du schon immer machen wolltest. Mach es lieber jetzt.",
"source": "Paulo Coelho",
"length": 136,
"length": 135,
"id": 578
}
]

View file

@ -1141,18 +1141,6 @@
"length": 63,
"id": 197
},
{
"text": "Nabi saja seorang pemimpin, tapi tidak sarjana kok.",
"source": "Megawati Sukarnoputri",
"length": 51,
"id": 198
},
{
"text": "Yo ndak tahu kok tanya saya.",
"source": "Joko Widodo",
"length": 28,
"id": 199
},
{
"text": "Inggris kita linggis Amerika kita setrika.",
"source": "Ir. H. Soekarno",

View file

@ -218,7 +218,7 @@
"id": 36
},
{
"text": "La vita è terribile. È lei che ci governa, non noi che la governiamo.",
"text": "La vita è terribile. E lei che ci governa, non noi che la governiamo.",
"source": "Oscar Wilde",
"length": 69,
"id": 37
@ -476,7 +476,7 @@
"id": 88
},
{
"text": "È un vero peccato che impariamo le lezioni della vita solo quando non ci servono più.",
"text": "E un vero peccato che impariamo le lezioni della vita solo quando non ci servono più.",
"source": "Oscar Wilde",
"length": 85,
"id": 89

View file

@ -782,7 +782,7 @@
"length": 38
},
{
"text": "Det er ingen klubber som har levert flere spillere Til europeiske nattklubber enn Start.",
"text": "Det er ingen klubber som har levert flere spillere til europeiske nattklubber enn Start.",
"source": "Svein Mathisen",
"id": 130,
"length": 88

View file

@ -320,7 +320,7 @@
"id": 52
},
{
"text": "Nu există alt diavol decât cel pe care îl avem în propriul nostru inimă.",
"text": "Nu există alt diavol decât cel pe care îl avem în propria noastră inimă.",
"source": "Hans Christian Andersen",
"length": 72,
"id": 53
@ -410,9 +410,9 @@
"id": 67
},
{
"text": "De multe ori, viitor ul ne arată cât de valoroasă a fost ceva.",
"text": "De multe ori, viitorul ne arată cât de valoros a fost ceva.",
"source": "Murakami Haruki",
"length": 62,
"length": 59,
"id": 68
},
{

View file

@ -4264,8 +4264,8 @@
{
"id": 744,
"source": "Юдковский Элиезер - Гарри Поттер и методы рационального мышления",
"text": "- Мальчикам не должно быть позволено любить девочек без разрешения! -заявила Гермиона Грейнджер. - Это правило замечательно во многих отношениях, но особенно, когда дело доходит до приклеивания к потолку людей!",
"length": 210
"text": "- Мальчикам не должно быть позволено любить девочек без разрешения! - заявила Гермиона Грейнджер. - Это правило замечательно во многих отношениях, но особенно, когда дело доходит до приклеивания к потолку людей!",
"length": 211
},
{
"id": 745,

View file

@ -446,7 +446,7 @@
"id": 78
},
{
"text": "El trabajo del pensamiento se parece a la perforación de un pozo: el agua es turbia al principio, más luego se clarifica.",
"text": "El trabajo del pensamiento se parece a la perforación de un pozo: el agua es turbia al principio, mas luego se clarifica.",
"source": "Proverbio chino",
"length": 121,
"id": 79
@ -944,7 +944,7 @@
"id": 164
},
{
"text": "No me arrepiento de este amor, aunque me cueste el corazón. Amar es un milagro, y yo te ame como nunca jamas me imaginé.",
"text": "No me arrepiento de este amor, aunque me cueste el corazón. Amar es un milagro, y yo te amé como nunca jamás me imaginé.",
"source": "Attaque 77 - No Me Arrepiento De Este Amor",
"length": 120,
"id": 165

View file

@ -101,11 +101,10 @@ body::before {
--c-dot--error: var(--colorful-error-color);
}
#wordsWrapper .word:has(~ .active) letter {
animation: toDust 200ms ease-out 0ms 1 forwards;
#words .word:has(~ .active) letter {
animation: toDust 200ms ease-out 0ms 1 forwards !important;
}
#wordsWrapper .word:has(~ .active) letter::after {
#words .word:has(~ .active) letter::after {
animation: fadeIn 100ms ease-in 100ms 1 forwards;
}

View file

@ -79,6 +79,6 @@
"eslint"
]
},
"version": "24.37.0",
"version": "24.38.0",
"packageManager": "pnpm@9.6.0"
}

View file

@ -79,10 +79,7 @@ module.exports = {
"@typescript-eslint/no-redundant-type-constituents": "off",
"@typescript-eslint/restrict-plus-operands": "off",
// TODO: enable at some point
"@typescript-eslint/no-unsafe-member-access": "off", //~105
//
"@typescript-eslint/no-unsafe-member-access": "error",
"@typescript-eslint/no-unsafe-call": "error",
"@typescript-eslint/no-unsafe-argument": "error",
"@typescript-eslint/no-unsafe-assignment": "error",

View file

@ -422,14 +422,14 @@ importers:
specifier: 5.5.4
version: 5.5.4
vite:
specifier: 5.1.7
version: 5.1.7(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3)
specifier: 5.2.14
version: 5.2.14(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3)
vite-bundle-visualizer:
specifier: 1.0.1
version: 1.0.1(rollup@2.79.1)
vite-plugin-checker:
specifier: 0.7.2
version: 0.7.2(eslint@8.57.0)(optionator@0.9.4)(typescript@5.5.4)(vite@5.1.7(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3))
version: 0.7.2(eslint@8.57.0)(optionator@0.9.4)(typescript@5.5.4)(vite@5.2.14(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3))
vite-plugin-filter-replace:
specifier: 0.1.13
version: 0.1.13
@ -438,10 +438,10 @@ importers:
version: 1.1.2
vite-plugin-inspect:
specifier: 0.8.3
version: 0.8.3(rollup@2.79.1)(vite@5.1.7(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3))
version: 0.8.3(rollup@2.79.1)(vite@5.2.14(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3))
vite-plugin-pwa:
specifier: 0.20.0
version: 0.20.0(vite@5.1.7(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3))(workbox-build@7.1.1)(workbox-window@7.1.0)
version: 0.20.0(vite@5.2.14(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3))(workbox-build@7.1.1)(workbox-window@7.1.0)
vitest:
specifier: 2.0.5
version: 2.0.5(@types/node@20.14.11)(happy-dom@13.4.1)(sass@1.70.0)(terser@5.31.3)
@ -1263,6 +1263,12 @@ packages:
cpu: [ppc64]
os: [aix]
'@esbuild/aix-ppc64@0.20.2':
resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [aix]
'@esbuild/aix-ppc64@0.21.5':
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
engines: {node: '>=12'}
@ -1281,6 +1287,12 @@ packages:
cpu: [arm64]
os: [android]
'@esbuild/android-arm64@0.20.2':
resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm64@0.21.5':
resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
engines: {node: '>=12'}
@ -1299,6 +1311,12 @@ packages:
cpu: [arm]
os: [android]
'@esbuild/android-arm@0.20.2':
resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
'@esbuild/android-arm@0.21.5':
resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
engines: {node: '>=12'}
@ -1317,6 +1335,12 @@ packages:
cpu: [x64]
os: [android]
'@esbuild/android-x64@0.20.2':
resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
'@esbuild/android-x64@0.21.5':
resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
engines: {node: '>=12'}
@ -1335,6 +1359,12 @@ packages:
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-arm64@0.20.2':
resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-arm64@0.21.5':
resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
engines: {node: '>=12'}
@ -1353,6 +1383,12 @@ packages:
cpu: [x64]
os: [darwin]
'@esbuild/darwin-x64@0.20.2':
resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
'@esbuild/darwin-x64@0.21.5':
resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
engines: {node: '>=12'}
@ -1371,6 +1407,12 @@ packages:
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-arm64@0.20.2':
resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-arm64@0.21.5':
resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
engines: {node: '>=12'}
@ -1389,6 +1431,12 @@ packages:
cpu: [x64]
os: [freebsd]
'@esbuild/freebsd-x64@0.20.2':
resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
'@esbuild/freebsd-x64@0.21.5':
resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
engines: {node: '>=12'}
@ -1407,6 +1455,12 @@ packages:
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm64@0.20.2':
resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm64@0.21.5':
resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
engines: {node: '>=12'}
@ -1425,6 +1479,12 @@ packages:
cpu: [arm]
os: [linux]
'@esbuild/linux-arm@0.20.2':
resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
'@esbuild/linux-arm@0.21.5':
resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
engines: {node: '>=12'}
@ -1443,6 +1503,12 @@ packages:
cpu: [ia32]
os: [linux]
'@esbuild/linux-ia32@0.20.2':
resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-ia32@0.21.5':
resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
engines: {node: '>=12'}
@ -1461,6 +1527,12 @@ packages:
cpu: [loong64]
os: [linux]
'@esbuild/linux-loong64@0.20.2':
resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-loong64@0.21.5':
resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
engines: {node: '>=12'}
@ -1479,6 +1551,12 @@ packages:
cpu: [mips64el]
os: [linux]
'@esbuild/linux-mips64el@0.20.2':
resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-mips64el@0.21.5':
resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
engines: {node: '>=12'}
@ -1497,6 +1575,12 @@ packages:
cpu: [ppc64]
os: [linux]
'@esbuild/linux-ppc64@0.20.2':
resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-ppc64@0.21.5':
resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
engines: {node: '>=12'}
@ -1515,6 +1599,12 @@ packages:
cpu: [riscv64]
os: [linux]
'@esbuild/linux-riscv64@0.20.2':
resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-riscv64@0.21.5':
resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
engines: {node: '>=12'}
@ -1533,6 +1623,12 @@ packages:
cpu: [s390x]
os: [linux]
'@esbuild/linux-s390x@0.20.2':
resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-s390x@0.21.5':
resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
engines: {node: '>=12'}
@ -1551,6 +1647,12 @@ packages:
cpu: [x64]
os: [linux]
'@esbuild/linux-x64@0.20.2':
resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
'@esbuild/linux-x64@0.21.5':
resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
engines: {node: '>=12'}
@ -1569,6 +1671,12 @@ packages:
cpu: [x64]
os: [netbsd]
'@esbuild/netbsd-x64@0.20.2':
resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
'@esbuild/netbsd-x64@0.21.5':
resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
engines: {node: '>=12'}
@ -1593,6 +1701,12 @@ packages:
cpu: [x64]
os: [openbsd]
'@esbuild/openbsd-x64@0.20.2':
resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
'@esbuild/openbsd-x64@0.21.5':
resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
engines: {node: '>=12'}
@ -1611,6 +1725,12 @@ packages:
cpu: [x64]
os: [sunos]
'@esbuild/sunos-x64@0.20.2':
resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
'@esbuild/sunos-x64@0.21.5':
resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
engines: {node: '>=12'}
@ -1629,6 +1749,12 @@ packages:
cpu: [arm64]
os: [win32]
'@esbuild/win32-arm64@0.20.2':
resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-arm64@0.21.5':
resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
engines: {node: '>=12'}
@ -1647,6 +1773,12 @@ packages:
cpu: [ia32]
os: [win32]
'@esbuild/win32-ia32@0.20.2':
resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-ia32@0.21.5':
resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
engines: {node: '>=12'}
@ -1665,6 +1797,12 @@ packages:
cpu: [x64]
os: [win32]
'@esbuild/win32-x64@0.20.2':
resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
'@esbuild/win32-x64@0.21.5':
resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
engines: {node: '>=12'}
@ -4363,6 +4501,11 @@ packages:
engines: {node: '>=12'}
hasBin: true
esbuild@0.20.2:
resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==}
engines: {node: '>=12'}
hasBin: true
esbuild@0.21.5:
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
engines: {node: '>=12'}
@ -4892,7 +5035,7 @@ packages:
resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==}
engines: {node: '>= 4.0'}
os: [darwin]
deprecated: The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2
deprecated: Upgrade to fsevents v2 to mitigate potential security issues
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
@ -9109,6 +9252,34 @@ packages:
terser:
optional: true
vite@5.2.14:
resolution: {integrity: sha512-TFQLuwWLPms+NBNlh0D9LZQ+HXW471COABxw/9TEUBrjuHMo9BrYBPrN/SYAwIuVL+rLerycxiLT41t4f5MZpA==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
'@types/node': ^18.0.0 || >=20.0.0
less: '*'
lightningcss: ^1.21.0
sass: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
peerDependenciesMeta:
'@types/node':
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
vitest-mongodb@1.0.0:
resolution: {integrity: sha512-IG39uQ4JpJf62rx9H0FUYwluXVQI5/Am6yrD9dE92SwJnFsJwpN4AynZkBbDuedvqFzG2GWK6mzzwU3vq28N0w==}
@ -10435,6 +10606,9 @@ snapshots:
'@esbuild/aix-ppc64@0.19.12':
optional: true
'@esbuild/aix-ppc64@0.20.2':
optional: true
'@esbuild/aix-ppc64@0.21.5':
optional: true
@ -10444,6 +10618,9 @@ snapshots:
'@esbuild/android-arm64@0.19.12':
optional: true
'@esbuild/android-arm64@0.20.2':
optional: true
'@esbuild/android-arm64@0.21.5':
optional: true
@ -10453,6 +10630,9 @@ snapshots:
'@esbuild/android-arm@0.19.12':
optional: true
'@esbuild/android-arm@0.20.2':
optional: true
'@esbuild/android-arm@0.21.5':
optional: true
@ -10462,6 +10642,9 @@ snapshots:
'@esbuild/android-x64@0.19.12':
optional: true
'@esbuild/android-x64@0.20.2':
optional: true
'@esbuild/android-x64@0.21.5':
optional: true
@ -10471,6 +10654,9 @@ snapshots:
'@esbuild/darwin-arm64@0.19.12':
optional: true
'@esbuild/darwin-arm64@0.20.2':
optional: true
'@esbuild/darwin-arm64@0.21.5':
optional: true
@ -10480,6 +10666,9 @@ snapshots:
'@esbuild/darwin-x64@0.19.12':
optional: true
'@esbuild/darwin-x64@0.20.2':
optional: true
'@esbuild/darwin-x64@0.21.5':
optional: true
@ -10489,6 +10678,9 @@ snapshots:
'@esbuild/freebsd-arm64@0.19.12':
optional: true
'@esbuild/freebsd-arm64@0.20.2':
optional: true
'@esbuild/freebsd-arm64@0.21.5':
optional: true
@ -10498,6 +10690,9 @@ snapshots:
'@esbuild/freebsd-x64@0.19.12':
optional: true
'@esbuild/freebsd-x64@0.20.2':
optional: true
'@esbuild/freebsd-x64@0.21.5':
optional: true
@ -10507,6 +10702,9 @@ snapshots:
'@esbuild/linux-arm64@0.19.12':
optional: true
'@esbuild/linux-arm64@0.20.2':
optional: true
'@esbuild/linux-arm64@0.21.5':
optional: true
@ -10516,6 +10714,9 @@ snapshots:
'@esbuild/linux-arm@0.19.12':
optional: true
'@esbuild/linux-arm@0.20.2':
optional: true
'@esbuild/linux-arm@0.21.5':
optional: true
@ -10525,6 +10726,9 @@ snapshots:
'@esbuild/linux-ia32@0.19.12':
optional: true
'@esbuild/linux-ia32@0.20.2':
optional: true
'@esbuild/linux-ia32@0.21.5':
optional: true
@ -10534,6 +10738,9 @@ snapshots:
'@esbuild/linux-loong64@0.19.12':
optional: true
'@esbuild/linux-loong64@0.20.2':
optional: true
'@esbuild/linux-loong64@0.21.5':
optional: true
@ -10543,6 +10750,9 @@ snapshots:
'@esbuild/linux-mips64el@0.19.12':
optional: true
'@esbuild/linux-mips64el@0.20.2':
optional: true
'@esbuild/linux-mips64el@0.21.5':
optional: true
@ -10552,6 +10762,9 @@ snapshots:
'@esbuild/linux-ppc64@0.19.12':
optional: true
'@esbuild/linux-ppc64@0.20.2':
optional: true
'@esbuild/linux-ppc64@0.21.5':
optional: true
@ -10561,6 +10774,9 @@ snapshots:
'@esbuild/linux-riscv64@0.19.12':
optional: true
'@esbuild/linux-riscv64@0.20.2':
optional: true
'@esbuild/linux-riscv64@0.21.5':
optional: true
@ -10570,6 +10786,9 @@ snapshots:
'@esbuild/linux-s390x@0.19.12':
optional: true
'@esbuild/linux-s390x@0.20.2':
optional: true
'@esbuild/linux-s390x@0.21.5':
optional: true
@ -10579,6 +10798,9 @@ snapshots:
'@esbuild/linux-x64@0.19.12':
optional: true
'@esbuild/linux-x64@0.20.2':
optional: true
'@esbuild/linux-x64@0.21.5':
optional: true
@ -10588,6 +10810,9 @@ snapshots:
'@esbuild/netbsd-x64@0.19.12':
optional: true
'@esbuild/netbsd-x64@0.20.2':
optional: true
'@esbuild/netbsd-x64@0.21.5':
optional: true
@ -10600,6 +10825,9 @@ snapshots:
'@esbuild/openbsd-x64@0.19.12':
optional: true
'@esbuild/openbsd-x64@0.20.2':
optional: true
'@esbuild/openbsd-x64@0.21.5':
optional: true
@ -10609,6 +10837,9 @@ snapshots:
'@esbuild/sunos-x64@0.19.12':
optional: true
'@esbuild/sunos-x64@0.20.2':
optional: true
'@esbuild/sunos-x64@0.21.5':
optional: true
@ -10618,6 +10849,9 @@ snapshots:
'@esbuild/win32-arm64@0.19.12':
optional: true
'@esbuild/win32-arm64@0.20.2':
optional: true
'@esbuild/win32-arm64@0.21.5':
optional: true
@ -10627,6 +10861,9 @@ snapshots:
'@esbuild/win32-ia32@0.19.12':
optional: true
'@esbuild/win32-ia32@0.20.2':
optional: true
'@esbuild/win32-ia32@0.21.5':
optional: true
@ -10636,6 +10873,9 @@ snapshots:
'@esbuild/win32-x64@0.19.12':
optional: true
'@esbuild/win32-x64@0.20.2':
optional: true
'@esbuild/win32-x64@0.21.5':
optional: true
@ -13907,6 +14147,32 @@ snapshots:
'@esbuild/win32-ia32': 0.19.12
'@esbuild/win32-x64': 0.19.12
esbuild@0.20.2:
optionalDependencies:
'@esbuild/aix-ppc64': 0.20.2
'@esbuild/android-arm': 0.20.2
'@esbuild/android-arm64': 0.20.2
'@esbuild/android-x64': 0.20.2
'@esbuild/darwin-arm64': 0.20.2
'@esbuild/darwin-x64': 0.20.2
'@esbuild/freebsd-arm64': 0.20.2
'@esbuild/freebsd-x64': 0.20.2
'@esbuild/linux-arm': 0.20.2
'@esbuild/linux-arm64': 0.20.2
'@esbuild/linux-ia32': 0.20.2
'@esbuild/linux-loong64': 0.20.2
'@esbuild/linux-mips64el': 0.20.2
'@esbuild/linux-ppc64': 0.20.2
'@esbuild/linux-riscv64': 0.20.2
'@esbuild/linux-s390x': 0.20.2
'@esbuild/linux-x64': 0.20.2
'@esbuild/netbsd-x64': 0.20.2
'@esbuild/openbsd-x64': 0.20.2
'@esbuild/sunos-x64': 0.20.2
'@esbuild/win32-arm64': 0.20.2
'@esbuild/win32-ia32': 0.20.2
'@esbuild/win32-x64': 0.20.2
esbuild@0.21.5:
optionalDependencies:
'@esbuild/aix-ppc64': 0.21.5
@ -19599,7 +19865,7 @@ snapshots:
debug: 4.3.6(supports-color@5.5.0)
pathe: 1.1.2
tinyrainbow: 1.2.0
vite: 5.1.7(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3)
vite: 5.2.14(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3)
transitivePeerDependencies:
- '@types/node'
- less
@ -19616,7 +19882,7 @@ snapshots:
debug: 4.3.6(supports-color@5.5.0)
pathe: 1.1.2
tinyrainbow: 1.2.0
vite: 5.1.7(@types/node@20.5.1)(sass@1.70.0)(terser@5.31.3)
vite: 5.2.14(@types/node@20.5.1)(sass@1.70.0)(terser@5.31.3)
transitivePeerDependencies:
- '@types/node'
- less
@ -19627,7 +19893,7 @@ snapshots:
- supports-color
- terser
vite-plugin-checker@0.7.2(eslint@8.57.0)(optionator@0.9.4)(typescript@5.5.4)(vite@5.1.7(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3)):
vite-plugin-checker@0.7.2(eslint@8.57.0)(optionator@0.9.4)(typescript@5.5.4)(vite@5.2.14(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3)):
dependencies:
'@babel/code-frame': 7.24.7
ansi-escapes: 4.3.2
@ -19639,7 +19905,7 @@ snapshots:
npm-run-path: 4.0.1
strip-ansi: 6.0.1
tiny-invariant: 1.3.3
vite: 5.1.7(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3)
vite: 5.2.14(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3)
vscode-languageclient: 7.0.0
vscode-languageserver: 7.0.0
vscode-languageserver-textdocument: 1.0.11
@ -19653,7 +19919,7 @@ snapshots:
vite-plugin-html-inject@1.1.2: {}
vite-plugin-inspect@0.8.3(rollup@2.79.1)(vite@5.1.7(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3)):
vite-plugin-inspect@0.8.3(rollup@2.79.1)(vite@5.2.14(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3)):
dependencies:
'@antfu/utils': 0.7.10
'@rollup/pluginutils': 5.1.0(rollup@2.79.1)
@ -19664,17 +19930,17 @@ snapshots:
perfect-debounce: 1.0.0
picocolors: 1.0.1
sirv: 2.0.4
vite: 5.1.7(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3)
vite: 5.2.14(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3)
transitivePeerDependencies:
- rollup
- supports-color
vite-plugin-pwa@0.20.0(vite@5.1.7(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3))(workbox-build@7.1.1)(workbox-window@7.1.0):
vite-plugin-pwa@0.20.0(vite@5.2.14(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3))(workbox-build@7.1.1)(workbox-window@7.1.0):
dependencies:
debug: 4.3.6(supports-color@5.5.0)
fast-glob: 3.3.2
pretty-bytes: 6.1.1
vite: 5.1.7(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3)
vite: 5.2.14(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3)
workbox-build: 7.1.1
workbox-window: 7.1.0
transitivePeerDependencies:
@ -19702,6 +19968,28 @@ snapshots:
sass: 1.70.0
terser: 5.31.3
vite@5.2.14(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3):
dependencies:
esbuild: 0.20.2
postcss: 8.4.40
rollup: 4.19.1
optionalDependencies:
'@types/node': 20.14.11
fsevents: 2.3.3
sass: 1.70.0
terser: 5.31.3
vite@5.2.14(@types/node@20.5.1)(sass@1.70.0)(terser@5.31.3):
dependencies:
esbuild: 0.20.2
postcss: 8.4.40
rollup: 4.19.1
optionalDependencies:
'@types/node': 20.5.1
fsevents: 2.3.3
sass: 1.70.0
terser: 5.31.3
vitest-mongodb@1.0.0:
dependencies:
debug: 4.3.6(supports-color@5.5.0)