diff --git a/backend/src/utils/error.ts b/backend/src/utils/error.ts index 733b137422c3..7b0a55fdd77e 100644 --- a/backend/src/utils/error.ts +++ b/backend/src/utils/error.ts @@ -8,18 +8,16 @@ type FirebaseErrorParent = { errorInfo: FirebaseError; }; -// oxlint-disable-next-line no-explicit-any -export function isFirebaseError(err: any): err is FirebaseErrorParent { +export function isFirebaseError(err: unknown): err is FirebaseErrorParent { return ( + err !== null && typeof err === "object" && "code" in err && "errorInfo" in err && "codePrefix" in err && - // oxlint-disable-next-line no-unsafe-member-access typeof err.errorInfo === "object" && - // oxlint-disable-next-line no-unsafe-member-access + err.errorInfo !== null && "code" in err.errorInfo && - // oxlint-disable-next-line no-unsafe-member-access "message" in err.errorInfo ); } diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index ff0eb0954a0f..d040161513aa 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -13,7 +13,7 @@ ## Getting Started -When contributing to Monkeytype, it's good to know our best practices, tips, and tricks. First, Monkeytype is written in ~~JavaScript~~ TypeScript, HTML, and CSS (in order of language usage within the project); thus, we assume you are comfortable with these languages or have basic knowledge of them. Our backend is in NodeJS and we use MongoDB to store our user data. Firebase is used for authentication. Redis is used to store ephemeral data (daily leaderboards, jobs via BullMQ, OAuth state parameters). Furthermore, we use Prettier to format our code. +When contributing to Monkeytype, it's good to know our best practices, tips, and tricks. First, Monkeytype is written in ~~JavaScript~~ TypeScript, HTML, and CSS (in order of language usage within the project); thus, we assume you are comfortable with these languages or have basic knowledge of them. Our backend is in NodeJS and we use MongoDB to store our user data. Firebase is used for authentication. Redis is used to store ephemeral data (daily leaderboards, jobs via BullMQ, OAuth state parameters). Furthermore, we use Oxc (Oxfmt and Oxlint) to format and lint our code. ## How to Contribute diff --git a/docs/CONTRIBUTING_ADVANCED.md b/docs/CONTRIBUTING_ADVANCED.md index 6291fc31ab73..d0cf0dd7899d 100644 --- a/docs/CONTRIBUTING_ADVANCED.md +++ b/docs/CONTRIBUTING_ADVANCED.md @@ -148,7 +148,7 @@ If you are on a UNIX system and you get a spawn error, run npm with `sudo`. ## Standards and Guidelines -Code formatting is enforced by [Prettier](https://prettier.io/docs/en/install.html), which automatically runs every time you make a commit. +Code formatting and linting is enforced by [Oxc (Oxfmt and Oxlint)](https://github.com/oxc-project/oxc), which automatically runs every time you make a commit. For guidelines on commit messages, adding themes, languages, or quotes, please refer to [CONTRIBUTING.md](./CONTRIBUTING.md). Following these guidelines will increase the chances of getting your change accepted. diff --git a/frontend/scripts/check-assets.ts b/frontend/scripts/check-assets.ts index dff3c69a10c3..91857bf62739 100644 --- a/frontend/scripts/check-assets.ts +++ b/frontend/scripts/check-assets.ts @@ -183,7 +183,7 @@ async function validateQuotes(): Promise { //check schema const schema = QuoteDataSchema.extend({ language: LanguageSchema - //icelandic only exists as icelandic_1k, language in quote file is stipped of its size + //icelandic only exists as icelandic_1k, language in quote file is stripped of its size .or(z.literal("icelandic")), }); problems.addValidation(quotefilename, schema.safeParse(quoteData)); @@ -198,14 +198,21 @@ async function validateQuotes(): Promise { } //check quote length - quoteData.quotes - .filter((quote) => quote.text.length !== quote.length) - .forEach((quote) => + quoteData.quotes.forEach((quote) => { + if (quote.text.length !== quote.length) { problems.add( quotefilename, `ID ${quote.id}: expected length ${quote.text.length}`, - ), - ); + ); + } + + if (quote.text.length < 60) { + problems.add( + quotefilename, + `ID ${quote.id}: length too short (under 60 characters)`, + ); + } + }); //check groups let last = -1; diff --git a/frontend/src/styles/nav.scss b/frontend/src/styles/nav.scss index 91c58ceb8ceb..e3c018f55bfe 100644 --- a/frontend/src/styles/nav.scss +++ b/frontend/src/styles/nav.scss @@ -447,7 +447,7 @@ header { .textButton, button { - color: transparent; + color: transparent !important; } .avatar, .levelAndBar { diff --git a/frontend/src/styles/test.scss b/frontend/src/styles/test.scss index c935deffed67..fea6c859bb21 100644 --- a/frontend/src/styles/test.scss +++ b/frontend/src/styles/test.scss @@ -351,7 +351,7 @@ &.blind { .word { & letter.extra { - display: none; + display: none !important; } & letter.incorrect { color: var(--correct-letter-color); @@ -533,45 +533,69 @@ } } - &.typed-effect-dots:not(.withLigatures) { + &.typed-effect-dots { /* transform already typed letters into appropriately colored dots */ - .word letter { - position: relative; - &::after { - content: ""; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 1em; - aspect-ratio: 1; - border-radius: 50%; - opacity: 0; + &:not(.withLigatures) .word, + &.withLigatures .word.broken-ligatures { + letter { + position: relative; + display: inline-block; + &::after { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 1em; + aspect-ratio: 1; + border-radius: 50%; + opacity: 0; + } } } - .typed letter { - color: var(--bg-color); - animation: typedEffectToDust 200ms ease-out 0ms 1 forwards !important; - &::after { - animation: typedEffectFadeIn 100ms ease-in 100ms 1 forwards; - background: var(--c-dot); + // unify dot spacing + &.withLigatures .word.broken-ligatures { + letter { + width: 0.4em; + } + } + .word.broken-ligatures:not(.needs-wrap) { + white-space: nowrap; + } + + &:not(.withLigatures) .word.typed, + &.withLigatures .word.broken-ligatures.typed { + letter { + color: var(--bg-color); + animation: typedEffectToDust 200ms ease-out 0ms 1 forwards !important; + &::after { + animation: typedEffectFadeIn 100ms ease-in 100ms 1 forwards; + background: var(--c-dot); + } } } - &:not(.blind) { + + &:not(.withLigatures):not(.blind) { .word letter.incorrect::after { background: var(--c-dot--error); } } + &.withLigatures:not(.blind) .word.broken-ligatures letter.incorrect::after { + background: var(--c-dot--error); + } @media (prefers-reduced-motion) { - .typed letter { - animation: none !important; - transform: scale(0.4); - color: transparent; - &::after { + &:not(.withLigatures) .word.typed, + &.withLigatures .word.broken-ligatures.typed { + letter { animation: none !important; - opacity: 1; + transform: scale(0.4); + color: transparent; + &::after { + animation: none !important; + opacity: 1; + } } } } diff --git a/frontend/src/ts/auth.tsx b/frontend/src/ts/auth.tsx index 5adf922c78a0..32560c567596 100644 --- a/frontend/src/ts/auth.tsx +++ b/frontend/src/ts/auth.tsx @@ -27,7 +27,6 @@ import { showPopup } from "./modals/simple-modals-base"; import * as AuthEvent from "./observables/auth-event"; import * as Sentry from "./sentry"; import { showLoaderBar, hideLoaderBar } from "./signals/loader-bar"; -import * as ConnectionState from "./states/connection"; import { addBanner } from "./stores/banners"; import * as Misc from "./utils/misc"; @@ -244,12 +243,6 @@ async function addAuthProvider( providerName: string, provider: AuthProvider, ): Promise { - if (!ConnectionState.get()) { - Notifications.add("You are offline", 0, { - duration: 2, - }); - return; - } if (!isAuthAvailable()) { Notifications.add("Authentication uninitialized", -1, { duration: 3, diff --git a/frontend/src/ts/commandline/util.ts b/frontend/src/ts/commandline/util.ts index b3794344ed41..714233901f33 100644 --- a/frontend/src/ts/commandline/util.ts +++ b/frontend/src/ts/commandline/util.ts @@ -10,7 +10,7 @@ import { } from "./commandline-metadata"; import { Command } from "./types"; import * as ConfigSchemas from "@monkeytype/schemas/configs"; -import { z, ZodSchema } from "zod"; +import { z, ZodSchema, ZodFirstPartySchemaTypes } from "zod"; function getOptions(schema: T): undefined | z.infer[] { if (schema instanceof z.ZodLiteral) { @@ -28,7 +28,6 @@ function getOptions(schema: T): undefined | z.infer[] { } export function buildCommandForConfigKey< - // oxlint-disable-next-line no-unnecessary-type-parameters K extends keyof CommandlineConfigMetadataObject, >(key: K): Command { const configMeta = configMetadata[key]; @@ -69,7 +68,7 @@ function _buildCommandForConfigKey< const inputCommand = buildInputCommand({ key: "secondKey" in inputProps ? inputProps.secondKey : key, - isPartOfSubgruop: "subgroup" in commandMeta, + isPartOfSubgroup: "subgroup" in commandMeta, inputProps: inputProps as InputProps, configMeta: configMeta as unknown as ConfigMetadata< keyof ConfigSchemas.Config @@ -120,8 +119,7 @@ function buildCommandWithSubgroup( if (values === undefined) { throw new Error( - //@ts-expect-error todo - `Unsupported schema type for key "${key}": ${schema._def.typeName}`, + `Unsupported schema type for key "${key}": ${(schema as ZodFirstPartySchemaTypes)._def.typeName}`, ); } const list = values.map((value) => @@ -201,13 +199,13 @@ function buildSubgroupCommand( function buildInputCommand({ key, - isPartOfSubgruop, + isPartOfSubgroup, inputProps, configMeta, schema, }: { key: K; - isPartOfSubgruop: boolean; + isPartOfSubgroup: boolean; inputProps?: InputProps; configMeta: ConfigMetadata; schema?: ZodSchema; @@ -216,7 +214,7 @@ function buildInputCommand({ const displayString = inputProps?.display ?? - (isPartOfSubgruop + (isPartOfSubgroup ? "custom..." : `${capitalizeFirstLetter(configMeta.displayString ?? key)}...`); diff --git a/frontend/src/ts/components/common/AnimatedModal.tsx b/frontend/src/ts/components/common/AnimatedModal.tsx index 2ef8637bf83c..e00bbfe5ceb4 100644 --- a/frontend/src/ts/components/common/AnimatedModal.tsx +++ b/frontend/src/ts/components/common/AnimatedModal.tsx @@ -76,7 +76,7 @@ export function AnimatedModal(props: AnimatedModalProps): JSXElement { await props.beforeShow?.(); // Open the dialog - dialogEl()?.removeClass("hidden"); + dialogEl()?.show(); if (props.mode === "dialog") { dialogEl()?.native.show(); } else { @@ -188,13 +188,13 @@ export function AnimatedModal(props: AnimatedModalProps): JSXElement { duration: wrapperDuration, onComplete: async () => { dialogEl()?.native.close(); - dialogEl()?.addClass("hidden"); + dialogEl()?.hide(); await handleAfterHide(); }, }); } else { dialogEl()?.native.close(); - dialogEl()?.addClass("hidden"); + dialogEl()?.hide(); await handleAfterHide(); } } else if (animMode === "modalOnly") { @@ -204,7 +204,7 @@ export function AnimatedModal(props: AnimatedModalProps): JSXElement { duration: modalAnimDuration, onComplete: async () => { dialogEl()?.native.close(); - dialogEl()?.addClass("hidden"); + dialogEl()?.hide(); await handleAfterHide(); }, }); diff --git a/frontend/src/ts/components/common/ChartJs.tsx b/frontend/src/ts/components/common/ChartJs.tsx index dae20fcd8af7..3cdf5bdf2a06 100644 --- a/frontend/src/ts/components/common/ChartJs.tsx +++ b/frontend/src/ts/components/common/ChartJs.tsx @@ -31,8 +31,9 @@ export function ChartJs>( let chart: ChartWithUpdateColors | undefined; onMount(() => { - //oxlint-disable-next-line no-non-null-assertion - chart = new ChartWithUpdateColors(canvasEl()!.native, { + const canvas = canvasEl(); + if (canvas === undefined) return; + chart = new ChartWithUpdateColors(canvas.native, { type: props.type, data: props.data, options: props.options, diff --git a/frontend/src/ts/components/pages/AboutPage.tsx b/frontend/src/ts/components/pages/AboutPage.tsx index 64d77fe1d9bf..de2a4c73ea6b 100644 --- a/frontend/src/ts/components/pages/AboutPage.tsx +++ b/frontend/src/ts/components/pages/AboutPage.tsx @@ -12,6 +12,7 @@ import { getConfig } from "../../signals/config"; import { getActivePage } from "../../signals/core"; import { showModal } from "../../stores/modals"; import { qsr } from "../../utils/dom"; +import { getNumberWithMagnitude } from "../../utils/numbers"; import AsyncContent from "../common/AsyncContent"; import { Button } from "../common/Button"; import { ChartJs } from "../common/ChartJs"; @@ -45,6 +46,14 @@ export function AboutPage(): JSXElement { enabled: isOpen(), })); + const numberOfHistogramRecords = (data?: { y: number }[]) => { + if (data === undefined) return ""; + const sum = getNumberWithMagnitude( + data.reduce((sum, it) => (sum += it.y), 0), + ); + return `${sum.roundedTo2} ${sum.orderOfMagnitude}`; + }; + return (
@@ -91,69 +100,81 @@ export function AboutPage(): JSXElement { errorMessage="Failed to get global speed stats for histogram" > {(data) => ( - + { + return ( + (context.raw as { topPercentage?: string }) + .topPercentage ?? "" + ); + }, + }, + }, }, - }, - }} - /> + }} + /> +
+ distribution of time 60 leaderboard results (wpm)
+ {numberOfHistogramRecords(data?.data)} total results +
+ )} -
- distribution of time 60 leaderboard results (wpm) -

diff --git a/frontend/src/ts/constants/languages.ts b/frontend/src/ts/constants/languages.ts index 783e7e9c251a..678919b856e6 100644 --- a/frontend/src/ts/constants/languages.ts +++ b/frontend/src/ts/constants/languages.ts @@ -119,7 +119,7 @@ export const LanguageGroups: Record = { "greeklish_25k", ], turkish: ["turkish", "turkish_1k", "turkish_5k"], - irish: ["irish"], + irish: ["irish", "irish_1k"], galician: ["galician"], thai: [ "thai", diff --git a/frontend/src/ts/controllers/challenge-controller.ts b/frontend/src/ts/controllers/challenge-controller.ts index 2c396ab848b3..85f9541a2047 100644 --- a/frontend/src/ts/controllers/challenge-controller.ts +++ b/frontend/src/ts/controllers/challenge-controller.ts @@ -221,8 +221,8 @@ export async function setup(challengeName: string): Promise { Notifications.add(message, -1); ManualRestart.set(); setTimeout(() => { - qs("header .config")?.removeClass("hidden"); - qs(".page.pageTest")?.removeClass("hidden"); + qs("header .config")?.show(); + qs(".page.pageTest")?.show(); }, 250); return false; } @@ -236,8 +236,8 @@ export async function setup(challengeName: string): Promise { Notifications.add("Challenge not found", 0); ManualRestart.set(); setTimeout(() => { - qs("header .config")?.removeClass("hidden"); - qs(".page.pageTest")?.removeClass("hidden"); + qs("header .config")?.show(); + qs(".page.pageTest")?.show(); }, 250); return false; } @@ -378,8 +378,8 @@ export async function setup(challengeName: string): Promise { } ManualRestart.set(); notitext = challenge.message; - qs("header .config")?.removeClass("hidden"); - qs(".page.pageTest")?.removeClass("hidden"); + qs("header .config")?.show(); + qs(".page.pageTest")?.show(); if (notitext === undefined) { Notifications.add(`Challenge '${challenge.display}' loaded.`, 0); diff --git a/frontend/src/ts/controllers/chart-controller.ts b/frontend/src/ts/controllers/chart-controller.ts index af7bb8e6fb78..faec3da680be 100644 --- a/frontend/src/ts/controllers/chart-controller.ts +++ b/frontend/src/ts/controllers/chart-controller.ts @@ -1356,10 +1356,7 @@ createDebouncedEffectOn(125, getTheme, (theme) => { ConfigEvent.subscribe(({ key, newValue }) => { if (key === "accountChart" && getActivePage() === "account") { - updateResults(); - updateAccuracy(); - updateAverage10(); - updateAverage100(); + updateAccountChartButtons(); accountHistory.update(); } if (key === "fontFamily") setDefaultFontFamily(newValue); diff --git a/frontend/src/ts/controllers/theme-controller.ts b/frontend/src/ts/controllers/theme-controller.ts index 321fe9dc908a..7be5e0330251 100644 --- a/frontend/src/ts/controllers/theme-controller.ts +++ b/frontend/src/ts/controllers/theme-controller.ts @@ -259,7 +259,7 @@ export async function applyCustomBackground(): Promise { // hide the filter section initially and always qs( ".pageSettings .section[data-config-name='customBackgroundFilter']", - )?.addClass("hidden"); + )?.hide(); if (backgroundUrl === "") { qs("#words")?.removeClass("noErrorBorder"); @@ -282,7 +282,7 @@ export async function applyCustomBackground(): Promise { // show the filter section only if the image loads successfully qs( ".pageSettings .section[data-config-name='customBackgroundFilter']", - )?.removeClass("hidden"); + )?.show(); }; container?.replaceChildren(img); diff --git a/frontend/src/ts/db.ts b/frontend/src/ts/db.ts index 679d01326628..02f1d9da4e8f 100644 --- a/frontend/src/ts/db.ts +++ b/frontend/src/ts/db.ts @@ -1,7 +1,6 @@ import Ape from "./ape"; import * as Notifications from "./elements/notifications"; import { isAuthenticated, getAuthenticatedUser } from "./firebase"; -import * as ConnectionState from "./states/connection"; import { lastElementFromArray } from "./utils/arrays"; import * as Dates from "date-fns"; import { @@ -42,8 +41,6 @@ export class SnapshotInitError extends Error { constructor(message: string, responseCode: number) { super(message); this.name = "SnapshotInitError"; - // TODO INVESTIGATE - // oxlint-disable-next-line this.responseCode = responseCode; } } @@ -265,10 +262,6 @@ export async function getUserResults(offset?: number): Promise { return false; } - if (!ConnectionState.get()) { - return false; - } - const response = await Ape.results.get({ query: { offset } }); if (response.status !== 200) { @@ -1061,10 +1054,6 @@ export async function getTestActivityCalendar( } if (dbSnapshot.testActivityByYear === undefined) { - if (!ConnectionState.get()) { - return undefined; - } - showLoaderBar(); const response = await Ape.users.getTestActivity(); if (response.status !== 200) { diff --git a/frontend/src/ts/elements/account-button.ts b/frontend/src/ts/elements/account-button.ts index 5d394ce75908..f6cf7da1d4e4 100644 --- a/frontend/src/ts/elements/account-button.ts +++ b/frontend/src/ts/elements/account-button.ts @@ -15,8 +15,8 @@ const accountButtonAndMenuEl = nav.qsr(".accountButtonAndMenu"); const loginButtonEl = nav.qsr(".textButton.view-login"); export function hide(): void { - accountButtonAndMenuEl.addClass("hidden"); - loginButtonEl.addClass("hidden"); + accountButtonAndMenuEl.hide(); + loginButtonEl.hide(); } export function loading(state: boolean): void { diff --git a/frontend/src/ts/elements/account-settings/ape-key-table.ts b/frontend/src/ts/elements/account-settings/ape-key-table.ts index 2e57f3085f81..c13b5ed5f499 100644 --- a/frontend/src/ts/elements/account-settings/ape-key-table.ts +++ b/frontend/src/ts/elements/account-settings/ape-key-table.ts @@ -18,7 +18,6 @@ const editApeKey = new SimpleModal({ }, ], buttonText: "edit", - onlineOnly: true, execFn: async (_thisPopup, input) => { const response = await Ape.apeKeys.save({ params: { apeKeyId: _thisPopup.parameters[0] ?? "" }, @@ -48,7 +47,6 @@ const deleteApeKeyModal = new SimpleModal({ title: "Delete Ape key", text: "Are you sure?", buttonText: "delete", - onlineOnly: true, execFn: async (_thisPopup) => { const response = await Ape.apeKeys.delete({ params: { apeKeyId: _thisPopup.parameters[0] ?? "" }, @@ -112,10 +110,10 @@ const viewApeKey = new SimpleModal({ modalEl.qs("textarea")?.setStyle({ height: "110px", }); - modalEl.qs(".submitButton")?.addClass("hidden"); + modalEl.qs(".submitButton")?.hide(); setTimeout(() => { _thisPopup.canClose = true; - modalEl.qs(".submitButton")?.removeClass("hidden"); + modalEl.qs(".submitButton")?.show(); }, 5000); }, }); @@ -131,7 +129,6 @@ const generateApeKey = new SimpleModal({ }, ], buttonText: "generate", - onlineOnly: true, execFn: async (thisPopup, name) => { const response = await Ape.apeKeys.add({ body: { name, enabled: false } }); if (response.status !== 200) { diff --git a/frontend/src/ts/elements/account/result-filters.ts b/frontend/src/ts/elements/account/result-filters.ts index fffecacb1e48..8a7e702dc80c 100644 --- a/frontend/src/ts/elements/account/result-filters.ts +++ b/frontend/src/ts/elements/account/result-filters.ts @@ -166,7 +166,7 @@ export async function setFilterPreset(id: string): Promise { // make current filter presest button active qsa( - `.pageAccount .group.presetFilterButtons .filterBtns .filterPresets .select-filter-preset[data-id=${id}]`, + `.pageAccount .group.presetFilterButtons .filterBtns .filterPresets .select-filter-preset[data-id="${id}"]`, ).addClass("active"); } @@ -528,19 +528,6 @@ for (const el of qsa(` `)) { el.onChild("click", "button", (e) => { const childTarget = e.childTarget as HTMLElement; - const group = (e.target as HTMLElement).parentElement?.getAttribute( - "group", - ) as ResultFiltersGroup | null; - if (group === null) { - throw new Error("Cannot find group of target."); - } - - const filter = childTarget.getAttribute("filter") as ResultFiltersGroupItem< - typeof group - > | null; - if (filter === null) { - throw new Error("Cannot find filter of target."); - } if (childTarget.classList.contains("allFilters")) { Misc.typedKeys(getFilters()).forEach((group) => { @@ -564,14 +551,30 @@ for (const el of qsa(` setAllFilters(group, false); } }); - } else if ((e.target as HTMLElement).tagName === "BUTTON") { - if (e.shiftKey) { - setAllFilters(group, false); - filters[group][filter] = - true as ResultFilters[typeof group][typeof filter]; - } else { - toggle(group, filter); - // filters[group][filter] = !filters[group][filter]; + } else { + const group = (e.target as HTMLElement).parentElement?.getAttribute( + "group", + ) as ResultFiltersGroup | null; + if (group === null) { + throw new Error("Cannot find group of target."); + } + + const filter = childTarget.getAttribute( + "filter", + ) as ResultFiltersGroupItem | null; + if (filter === null) { + throw new Error("Cannot find filter of target."); + } + + if ((e.target as HTMLElement).tagName === "BUTTON") { + if (e.shiftKey) { + setAllFilters(group, false); + filters[group][filter] = + true as ResultFilters[typeof group][typeof filter]; + } else { + toggle(group, filter); + // filters[group][filter] = !filters[group][filter]; + } } } updateActive(); @@ -897,14 +900,14 @@ function tagDropdownUpdate(snapshot: Snapshot): void { ); if (snapshot.tags.length === 0) { - tagsSection?.addClass("hidden"); + tagsSection?.hide(); if (groupSelects["tags"]) { groupSelects["tags"].destroy(); delete groupSelects["tags"]; } setFilter("tags", "none", true); } else { - tagsSection?.removeClass("hidden"); + tagsSection?.show(); updateTagsDropdownOptions(); diff --git a/frontend/src/ts/elements/alerts.ts b/frontend/src/ts/elements/alerts.ts index c047aa91afc8..0fe861804130 100644 --- a/frontend/src/ts/elements/alerts.ts +++ b/frontend/src/ts/elements/alerts.ts @@ -5,7 +5,6 @@ import * as DB from "../db"; import * as NotificationEvent from "../observables/notification-event"; import * as BadgeController from "../controllers/badge-controller"; import * as Notifications from "../elements/notifications"; -import * as ConnectionState from "../states/connection"; import { applyReducedMotion, createErrorMessage, @@ -157,15 +156,6 @@ async function show(): Promise { } async function getAccountAlerts(): Promise { - if (!ConnectionState.get()) { - accountAlertsListEl.setHtml(` -
- You are offline -
- `); - return; - } - const inboxResponse = await Ape.users.getInbox(); if (inboxResponse.status === 503) { diff --git a/frontend/src/ts/elements/keymap.ts b/frontend/src/ts/elements/keymap.ts index 19e0add7bf91..fe907b6adb88 100644 --- a/frontend/src/ts/elements/keymap.ts +++ b/frontend/src/ts/elements/keymap.ts @@ -148,11 +148,11 @@ async function flashKey(key: string, correct?: boolean): Promise { } export function hide(): void { - keymap.addClass("hidden"); + keymap.hide(); } export function show(): void { - keymap.removeClass("hidden"); + keymap.show(); } function buildRow(options: { diff --git a/frontend/src/ts/elements/notifications.ts b/frontend/src/ts/elements/notifications.ts index 31e14ec8f06c..97c253d44c7e 100644 --- a/frontend/src/ts/elements/notifications.ts +++ b/frontend/src/ts/elements/notifications.ts @@ -167,7 +167,7 @@ function updateClearAllButton(): void { padding: [0, "0.5em"], duration: 125, onBegin: () => { - clearAllButton?.removeClass("hidden"); + clearAllButton?.show(); }, }); } else if (visibleStickyNotifications < 1) { @@ -176,7 +176,7 @@ function updateClearAllButton(): void { padding: ["0.5em", 0], duration: 125, onComplete: () => { - clearAllButton?.addClass("hidden"); + clearAllButton?.hide(); }, }); } diff --git a/frontend/src/ts/elements/profile.ts b/frontend/src/ts/elements/profile.ts index 5d9ba62570f8..bac7915cd4ee 100644 --- a/frontend/src/ts/elements/profile.ts +++ b/frontend/src/ts/elements/profile.ts @@ -81,12 +81,12 @@ export async function update( if (where === "profile") { profileElement ?.qs(".lbOptOutReminder") - ?.removeClass("hidden") + ?.show() ?.setText( "Note: This account has opted out of the leaderboards, meaning their results aren't verified by the anticheat system and may not be legitimate.", ); } else { - profileElement?.qs(".lbOptOutReminder")?.addClass("hidden"); + profileElement?.qs(".lbOptOutReminder")?.hide(); } } @@ -288,15 +288,15 @@ export async function update( //lbs if (banned) { - profileElement?.qs(".leaderboardsPositions")?.addClass("hidden"); + profileElement?.qs(".leaderboardsPositions")?.hide(); } else { - profileElement?.qs(".leaderboardsPositions")?.removeClass("hidden"); + profileElement?.qs(".leaderboardsPositions")?.show(); const t15 = profile.allTimeLbs?.time?.["15"]?.["english"] ?? null; const t60 = profile.allTimeLbs?.time?.["60"]?.["english"] ?? null; if (t15 === null && t60 === null) { - profileElement?.qs(".leaderboardsPositions")?.addClass("hidden"); + profileElement?.qs(".leaderboardsPositions")?.hide(); } else { if (t15 !== null) { profileElement @@ -320,39 +320,39 @@ export async function update( } if (profile.uid === getAuthenticatedUser()?.uid) { - profileElement?.qs(".userReportButton")?.addClass("hidden"); + profileElement?.qs(".userReportButton")?.hide(); } else { - profileElement?.qs(".userReportButton")?.removeClass("hidden"); + profileElement?.qs(".userReportButton")?.show(); } const bioAndKey = bio || keyboard; if (!bio) { - details?.qs(".bio")?.addClass("hidden"); + details?.qs(".bio")?.hide(); } else { - details?.qs(".bio")?.removeClass("hidden"); + details?.qs(".bio")?.show(); } if (!keyboard) { - details?.qs(".keyboard")?.addClass("hidden"); + details?.qs(".keyboard")?.hide(); } else { - details?.qs(".keyboard")?.removeClass("hidden"); + details?.qs(".keyboard")?.show(); } if (!bioAndKey) { - details?.qs(".bioAndKeyboard")?.addClass("hidden"); - details?.qs(".sep2")?.addClass("hidden"); + details?.qs(".bioAndKeyboard")?.hide(); + details?.qs(".sep2")?.hide(); } else { - details?.qs(".bioAndKeyboard")?.removeClass("hidden"); - details?.qs(".sep2")?.removeClass("hidden"); + details?.qs(".bioAndKeyboard")?.show(); + details?.qs(".sep2")?.show(); } if (!socials) { - details?.qs(".socials")?.addClass("hidden"); - details?.qs(".sep3")?.addClass("hidden"); + details?.qs(".socials")?.hide(); + details?.qs(".sep3")?.hide(); } else { - details?.qs(".socials")?.removeClass("hidden"); - details?.qs(".sep3")?.removeClass("hidden"); + details?.qs(".socials")?.show(); + details?.qs(".sep3")?.show(); } details?.removeClass("none"); diff --git a/frontend/src/ts/elements/result-batches.ts b/frontend/src/ts/elements/result-batches.ts index d72645da9991..7595a6cfee78 100644 --- a/frontend/src/ts/elements/result-batches.ts +++ b/frontend/src/ts/elements/result-batches.ts @@ -6,11 +6,11 @@ import { getTheme } from "../signals/theme"; import { qs } from "../utils/dom"; export function hide(): void { - qs(".pageAccount .resultBatches")?.addClass("hidden"); + qs(".pageAccount .resultBatches")?.hide(); } export function show(): void { - qs(".pageAccount .resultBatches")?.removeClass("hidden"); + qs(".pageAccount .resultBatches")?.show(); } export async function update(): Promise { diff --git a/frontend/src/ts/elements/settings/theme-picker.ts b/frontend/src/ts/elements/settings/theme-picker.ts index f76df63246da..3124c5757e70 100644 --- a/frontend/src/ts/elements/settings/theme-picker.ts +++ b/frontend/src/ts/elements/settings/theme-picker.ts @@ -150,13 +150,13 @@ export async function fillCustomButtons(): Promise { if (!isAuthenticated()) { saveButton?.setText("save"); - addButton?.addClass("hidden"); + addButton?.hide(); customThemesEl?.setStyle({ marginBottom: "0" }); return; } saveButton?.setText("save as new"); - addButton?.removeClass("hidden"); + addButton?.show(); const customThemes = DB.getSnapshot()?.customThemes ?? []; diff --git a/frontend/src/ts/hooks/useVisibilityAnimation.ts b/frontend/src/ts/hooks/useVisibilityAnimation.ts index 1aa7b7d8efa0..cff17b5f67af 100644 --- a/frontend/src/ts/hooks/useVisibilityAnimation.ts +++ b/frontend/src/ts/hooks/useVisibilityAnimation.ts @@ -25,7 +25,7 @@ export function useVisibilityAnimation(options: { duration: applyReducedMotion(125), ...options.showAnimationOptions, onBegin: (self) => { - el.removeClass("hidden"); + el.show(); options.showAnimationOptions?.onBegin?.(self); }, }); @@ -36,7 +36,7 @@ export function useVisibilityAnimation(options: { duration: applyReducedMotion(125), ...options.hideAnimationOptions, onComplete: (self) => { - el.addClass("hidden"); + el.hide(); options.hideAnimationOptions?.onComplete?.(self); }, }); diff --git a/frontend/src/ts/modals/edit-preset.ts b/frontend/src/ts/modals/edit-preset.ts index 5663006b3a0e..bc9566367d85 100644 --- a/frontend/src/ts/modals/edit-preset.ts +++ b/frontend/src/ts/modals/edit-preset.ts @@ -5,7 +5,6 @@ import * as Config from "../config"; import { showLoaderBar, hideLoaderBar } from "../signals/loader-bar"; import * as Settings from "../pages/settings"; import * as Notifications from "../elements/notifications"; -import * as ConnectionState from "../states/connection"; import AnimatedModal from "../utils/animated-modal"; import { PresetNameSchema, @@ -36,13 +35,6 @@ const state = { let presetNameEl: ValidatedHtmlInputElement | null = null; export function show(action: string, id?: string, name?: string): void { - if (!ConnectionState.get()) { - Notifications.add("You are offline", 0, { - duration: 2, - }); - return; - } - void modal.show({ focusFirstInput: true, beforeAnimation: async (modalEl) => { @@ -71,7 +63,7 @@ export function show(action: string, id?: string, name?: string): void { modalEl.setAttribute("data-preset-id", id); modalEl.qsr(".popupTitle").setHtml("Edit preset"); modalEl.qsr(".submit").setHtml(`save`); - presetNameEl?.setValue(name); + presetNameEl?.setValue(name.replaceAll(" ", "_")); presetNameEl?.getParent()?.show(); modalEl.qsa("input").show(); @@ -226,10 +218,9 @@ function hide(): void { async function apply(): Promise { const modalEl = modal.getModal(); const action = modalEl.getAttribute("data-action"); - const propPresetName = modalEl + const presetName = modalEl .qsr(".group input[title='presets']") .getValue() as string; - const presetName = propPresetName.replaceAll(" ", "_"); const presetId = modalEl.getAttribute("data-preset-id") as string; const updateConfig = modalEl @@ -289,11 +280,7 @@ async function apply(): Promise { }); if (response.status !== 200 || response.body.data === null) { - Notifications.add( - "Failed to add preset: " + - response.body.message.replace(presetName, propPresetName), - -1, - ); + Notifications.add("Failed to add preset: " + response.body.message, -1); } else { Notifications.add("Preset added", 1, { duration: 2, @@ -304,7 +291,7 @@ async function apply(): Promise { ...(state.presetType === "partial" && { settingGroups: activeSettingGroups, }), - display: propPresetName, + display: presetName.replaceAll("_", " "), _id: response.body.data.presetId, } as SnapshotPreset); } @@ -336,7 +323,7 @@ async function apply(): Promise { Notifications.add("Preset updated", 1); preset.name = presetName; - preset.display = presetName.replace(/_/g, " "); + preset.display = presetName.replaceAll("_", " "); if (updateConfig) { preset.config = configChanges; if (state.presetType === "partial") { diff --git a/frontend/src/ts/modals/edit-profile.ts b/frontend/src/ts/modals/edit-profile.ts index 88aafaae0407..d373940b525e 100644 --- a/frontend/src/ts/modals/edit-profile.ts +++ b/frontend/src/ts/modals/edit-profile.ts @@ -4,7 +4,6 @@ import * as DB from "../db"; import { showLoaderBar, hideLoaderBar } from "../signals/loader-bar"; import * as Notifications from "../elements/notifications"; -import * as ConnectionState from "../states/connection"; import AnimatedModal from "../utils/animated-modal"; import * as Profile from "../elements/profile"; import { CharacterCounter } from "../elements/character-counter"; @@ -19,13 +18,6 @@ import { InputIndicator } from "../elements/input-indicator"; import { ElementWithUtils, qsr } from "../utils/dom"; export function show(): void { - if (!ConnectionState.get()) { - Notifications.add("You are offline", 0, { - duration: 2, - }); - return; - } - void modal.show({ beforeAnimation: async () => { hydrateInputs(); diff --git a/frontend/src/ts/modals/edit-result-tags.ts b/frontend/src/ts/modals/edit-result-tags.ts index 89650c41b061..18c9dbad304e 100644 --- a/frontend/src/ts/modals/edit-result-tags.ts +++ b/frontend/src/ts/modals/edit-result-tags.ts @@ -4,7 +4,6 @@ import * as DB from "../db"; import { showLoaderBar, hideLoaderBar } from "../signals/loader-bar"; import * as Notifications from "../elements/notifications"; import * as AccountPage from "../pages/account"; -import * as ConnectionState from "../states/connection"; import { areUnsortedArraysEqual } from "../utils/arrays"; import * as TestResult from "../test/result"; import AnimatedModal from "../utils/animated-modal"; @@ -28,12 +27,6 @@ export function show( tags: string[], source: "accountPage" | "resultPage", ): void { - if (!ConnectionState.get()) { - Notifications.add("You are offline", 0, { - duration: 2, - }); - return; - } if (resultId === "") { Notifications.add( "Failed to show edit result tags modal: result id is empty", diff --git a/frontend/src/ts/modals/edit-tag.ts b/frontend/src/ts/modals/edit-tag.ts index a9782c7c2b37..ce02be7dd4dd 100644 --- a/frontend/src/ts/modals/edit-tag.ts +++ b/frontend/src/ts/modals/edit-tag.ts @@ -30,7 +30,6 @@ const actionModals: Record = { validation: { isValid: tagNameValidation, debounceDelay: 0 }, }, ], - onlineOnly: true, buttonText: "add", execFn: async (_thisPopup, propTagName) => { const tagName = cleanTagName(propTagName); @@ -73,7 +72,6 @@ const actionModals: Record = { validation: { isValid: tagNameValidation, debounceDelay: 0 }, }, ], - onlineOnly: true, buttonText: "save", beforeInitFn: (_thisPopup) => { (_thisPopup.inputs[0] as TextInput).initVal = _thisPopup.parameters[0]; @@ -109,7 +107,6 @@ const actionModals: Record = { remove: new SimpleModal({ id: "removeTag", title: "Delete tag", - onlineOnly: true, buttonText: "delete", beforeInitFn: (_thisPopup) => { _thisPopup.text = `Are you sure you want to delete tag ${_thisPopup.parameters[0]} ?`; @@ -141,7 +138,6 @@ const actionModals: Record = { clearPb: new SimpleModal({ id: "clearTagPb", title: "Clear personal bests", - onlineOnly: true, buttonText: "clear", beforeInitFn: (_thisPopup) => { _thisPopup.text = `Are you sure you want to clear personal bests for tag ${_thisPopup.parameters[0]} ?`; diff --git a/frontend/src/ts/modals/new-filter-preset.ts b/frontend/src/ts/modals/new-filter-preset.ts index 828c2e3d9bb5..aa669242e192 100644 --- a/frontend/src/ts/modals/new-filter-preset.ts +++ b/frontend/src/ts/modals/new-filter-preset.ts @@ -20,7 +20,6 @@ const newFilterPresetModal = new SimpleModal({ }, ], buttonText: "add", - onlineOnly: true, execFn: async (_thisPopup, name) => { const status = await createFilterPreset(name); diff --git a/frontend/src/ts/modals/quote-filter.ts b/frontend/src/ts/modals/quote-filter.ts index d83fccce564f..1e4834c7ddc0 100644 --- a/frontend/src/ts/modals/quote-filter.ts +++ b/frontend/src/ts/modals/quote-filter.ts @@ -15,7 +15,7 @@ function refresh(): void { export const quoteFilterModal = new SimpleModal({ id: "quoteFilter", - title: "Enter minimum and maximum values", + title: "Enter minimum and maximum number of words", inputs: [ { placeholder: "1", @@ -41,7 +41,7 @@ export const quoteFilterModal = new SimpleModal({ maxFilterLength = maxNum; refresh(); - let message: string = "saved custom filter"; + let message: string = "Saved custom filter"; return { status: 1, message }; }, afterClickAway: () => { diff --git a/frontend/src/ts/modals/simple-modals.ts b/frontend/src/ts/modals/simple-modals.ts index b9742fda1503..7f31d2e76e5b 100644 --- a/frontend/src/ts/modals/simple-modals.ts +++ b/frontend/src/ts/modals/simple-modals.ts @@ -209,7 +209,6 @@ list.updateEmail = new SimpleModal({ }, ], buttonText: "update", - onlineOnly: true, execFn: async ( _thisPopup, password, @@ -273,7 +272,6 @@ list.removeGoogleAuth = new SimpleModal({ initVal: "", }, ], - onlineOnly: true, buttonText: "remove", execFn: async (_thisPopup, password): Promise => { const reauth = await reauthenticate({ @@ -327,7 +325,6 @@ list.removeGithubAuth = new SimpleModal({ initVal: "", }, ], - onlineOnly: true, buttonText: "remove", execFn: async (_thisPopup, password): Promise => { const reauth = await reauthenticate({ @@ -380,7 +377,6 @@ list.removePasswordAuth = new SimpleModal({ label: `I understand I will lose access to my Monkeytype account if my Google/GitHub account is lost or disabled.`, }, ], - onlineOnly: true, buttonText: "reauthenticate to remove", execFn: async (_thisPopup): Promise => { const reauth = await reauthenticate({ @@ -443,7 +439,6 @@ list.updateName = new SimpleModal({ }, ], buttonText: "update", - onlineOnly: true, execFn: async (_thisPopup, password, newName): Promise => { const reauth = await reauthenticate({ password }); if (reauth.status !== 1) { @@ -517,7 +512,6 @@ list.updatePassword = new SimpleModal({ }, ], buttonText: "update", - onlineOnly: true, execFn: async ( _thisPopup, previousPass, @@ -601,7 +595,6 @@ list.addPasswordAuth = new SimpleModal({ }, ], buttonText: "reauthenticate to add", - onlineOnly: true, execFn: async ( _thisPopup, email, @@ -680,7 +673,6 @@ list.deleteAccount = new SimpleModal({ ], text: "This is the last time you can change your mind. After pressing the button everything is gone.", buttonText: "delete", - onlineOnly: true, execFn: async (_thisPopup, password): Promise => { const reauth = await reauthenticate({ password }); if (reauth.status !== 1) { @@ -729,7 +721,6 @@ list.resetAccount = new SimpleModal({ ], text: "This is the last time you can change your mind. After pressing the button everything is gone.", buttonText: "reset", - onlineOnly: true, execFn: async (_thisPopup, password): Promise => { const reauth = await reauthenticate({ password }); if (reauth.status !== 1) { @@ -781,7 +772,6 @@ list.optOutOfLeaderboards = new SimpleModal({ ], text: "Are you sure you want to opt out of leaderboards?", buttonText: "opt out", - onlineOnly: true, execFn: async (_thisPopup, password): Promise => { const reauth = await reauthenticate({ password }); if (reauth.status !== 1) { @@ -843,7 +833,6 @@ list.resetPersonalBests = new SimpleModal({ }, ], buttonText: "reset", - onlineOnly: true, execFn: async (_thisPopup, password): Promise => { const reauth = await reauthenticate({ password }); if (reauth.status !== 1) { @@ -897,7 +886,6 @@ list.resetSettings = new SimpleModal({ title: "Reset settings", text: "Are you sure you want to reset all your settings?", buttonText: "reset", - onlineOnly: true, execFn: async (): Promise => { await resetConfig(); await FileStorage.deleteFile("LocalBackgroundFile"); @@ -920,7 +908,6 @@ list.revokeAllTokens = new SimpleModal({ ], text: "Are you sure you want to do this? This will log you out of all devices.", buttonText: "revoke all", - onlineOnly: true, execFn: async (_thisPopup, password): Promise => { const reauth = await reauthenticate({ password }); if (reauth.status !== 1) { @@ -962,7 +949,6 @@ list.unlinkDiscord = new SimpleModal({ title: "Unlink Discord", text: "Are you sure you want to unlink your Discord account?", buttonText: "unlink", - onlineOnly: true, execFn: async (): Promise => { const snap = DB.getSnapshot(); if (!snap) { @@ -1070,7 +1056,6 @@ list.updateCustomTheme = new SimpleModal({ }, ], buttonText: "update", - onlineOnly: true, execFn: async (_thisPopup, name, updateColors): Promise => { const snapshot = DB.getSnapshot(); if (!snapshot) { @@ -1131,7 +1116,6 @@ list.deleteCustomTheme = new SimpleModal({ title: "Delete custom theme", text: "Are you sure?", buttonText: "delete", - onlineOnly: true, execFn: async (_thisPopup): Promise => { await DB.deleteCustomTheme(_thisPopup.parameters[0] as string); void ThemePicker.fillCustomButtons(); diff --git a/frontend/src/ts/modals/streak-hour-offset.ts b/frontend/src/ts/modals/streak-hour-offset.ts index 81c13a0f7393..2f7a3a6ed3d3 100644 --- a/frontend/src/ts/modals/streak-hour-offset.ts +++ b/frontend/src/ts/modals/streak-hour-offset.ts @@ -4,7 +4,6 @@ import * as Notifications from "../elements/notifications"; import { showLoaderBar, hideLoaderBar } from "../signals/loader-bar"; // import * as Settings from "../pages/settings"; -import * as ConnectionState from "../states/connection"; import { getSnapshot, setSnapshot } from "../db"; import AnimatedModal from "../utils/animated-modal"; import { Snapshot } from "../constants/default-snapshot"; @@ -14,13 +13,6 @@ let state = { }; export function show(): void { - if (!ConnectionState.get()) { - Notifications.add("You are offline", 0, { - duration: 2, - }); - return; - } - void modal.show({ focusFirstInput: "focusAndSelect", beforeAnimation: async (modalEl) => { diff --git a/frontend/src/ts/pages/account-settings.ts b/frontend/src/ts/pages/account-settings.ts index 323f29f8999b..eb66a67db7e6 100644 --- a/frontend/src/ts/pages/account-settings.ts +++ b/frontend/src/ts/pages/account-settings.ts @@ -36,9 +36,9 @@ const state: State = { }; function updateAuthenticationSections(): void { - pageElement.qsa(".section.passwordAuthSettings button")?.addClass("hidden"); - pageElement.qsa(".section.googleAuthSettings button")?.addClass("hidden"); - pageElement.qsa(".section.githubAuthSettings button")?.addClass("hidden"); + pageElement.qsa(".section.passwordAuthSettings button")?.hide(); + pageElement.qsa(".section.googleAuthSettings button")?.hide(); + pageElement.qsa(".section.githubAuthSettings button")?.hide(); const user = getAuthenticatedUser(); if (user === null) return; diff --git a/frontend/src/ts/pages/account.ts b/frontend/src/ts/pages/account.ts index 6bf43f9a4b7c..7486d48b8a2c 100644 --- a/frontend/src/ts/pages/account.ts +++ b/frontend/src/ts/pages/account.ts @@ -15,7 +15,6 @@ import * as Numbers from "@monkeytype/util/numbers"; import { get as getTypingSpeedUnit } from "../utils/typing-speed-units"; import * as Profile from "../elements/profile"; import { format } from "date-fns/format"; -import * as ConnectionState from "../states/connection"; import * as Skeleton from "../utils/skeleton"; import type { ScaleChartOptions, LinearScaleOptions } from "chart.js"; import * as ConfigEvent from "../observables/config-event"; @@ -972,12 +971,6 @@ async function fillContent(): Promise { export async function downloadResults(offset?: number): Promise { const results = await DB.getUserResults(offset); - if (!results && !ConnectionState.get()) { - Notifications.add("Could not get results - you are offline", -1, { - duration: 5, - }); - return; - } TodayTracker.addAllFromToday(); if (results) { @@ -1197,12 +1190,6 @@ qs(".pageAccount button.loadMoreResults")?.on("click", async () => { }); qs(".pageAccount")?.onChild("click", ".sendVerificationEmail", async () => { - if (!ConnectionState.get()) { - Notifications.add("You are offline", 0, { - duration: 2, - }); - return; - } qs(".sendVerificationEmail")?.disable(); await sendVerificationEmail(); qs(".sendVerificationEmail")?.enable(); diff --git a/frontend/src/ts/pages/friends.ts b/frontend/src/ts/pages/friends.ts index 0d3157f26899..2597b894d1a2 100644 --- a/frontend/src/ts/pages/friends.ts +++ b/frontend/src/ts/pages/friends.ts @@ -86,7 +86,6 @@ const addFriendModal = new SimpleModal({ }, ], buttonText: "request", - onlineOnly: true, execFn: async (_thisPopup, receiverName) => { const result = await addFriend(receiverName); diff --git a/frontend/src/ts/pages/settings.ts b/frontend/src/ts/pages/settings.ts index 6a4e63faa9af..a2f787e0bb3d 100644 --- a/frontend/src/ts/pages/settings.ts +++ b/frontend/src/ts/pages/settings.ts @@ -885,8 +885,11 @@ qs( }); qsa(".pageSettings .quickNav .links a")?.on("click", (e) => { - const target = e.currentTarget as HTMLElement; - const settingsGroup = target.innerText; + const target = e.currentTarget as HTMLAnchorElement; + const href = target.getAttribute("href") ?? ""; + if (!href.startsWith("#group_")) return; + const settingsGroup = href.slice("#group_".length); + if (settingsGroup === "") return; const isClosed = qs( `.pageSettings .settingsGroup.${settingsGroup}`, )?.hasClass("slideup"); diff --git a/frontend/src/ts/popups/video-ad-popup.ts b/frontend/src/ts/popups/video-ad-popup.ts index 15bdf401f9fd..03c17ea64967 100644 --- a/frontend/src/ts/popups/video-ad-popup.ts +++ b/frontend/src/ts/popups/video-ad-popup.ts @@ -1,5 +1,3 @@ -/* oxlint-disable no-unsafe-call */ -/* oxlint-disable no-unsafe-member-access */ import * as Notifications from "../elements/notifications"; import * as AdController from "../controllers/ad-controller"; import * as Skeleton from "../utils/skeleton"; @@ -44,7 +42,8 @@ export async function show(): Promise { el.show(); }, onComplete: () => { - //@ts-expect-error 3rd party ad code + // @ts-expect-error 3rd party ad code + // oxlint-disable-next-line no-unsafe-call no-unsafe-member-access window.dataLayer.push({ event: "EG_Video" }); }, }); diff --git a/frontend/src/ts/queries/public.ts b/frontend/src/ts/queries/public.ts index 96ec3a3d3adc..258c4286e3ef 100644 --- a/frontend/src/ts/queries/public.ts +++ b/frontend/src/ts/queries/public.ts @@ -85,8 +85,14 @@ async function fetchSpeedHistogram(): Promise< const data = response.body.data; - const histogramChartDataBucketed: { x: number; y: number }[] = []; + const histogramChartDataBucketed: { + x: number; + y: number; + topPercentage?: string; + }[] = []; const labels: string[] = []; + const sum = Object.values(data).reduce((sum, it) => (sum += it), 0); + let topPercentage = 100; const keys = Object.keys(data).sort( (a, b) => parseInt(a, 10) - parseInt(b, 10), @@ -94,9 +100,12 @@ async function fetchSpeedHistogram(): Promise< for (const [i, key] of keys.entries()) { const nextKey = keys[i + 1]; const bucket = parseInt(key, 10); + topPercentage -= ((data[bucket] as number) / sum) * 100; histogramChartDataBucketed.push({ x: bucket, y: data[bucket] as number, + topPercentage: + topPercentage > 0.01 ? `top ${topPercentage.toFixed(2)}%` : undefined, }); labels.push(`${bucket} - ${bucket + 9}`); if (nextKey !== undefined && bucket + 10 !== parseInt(nextKey, 10)) { diff --git a/frontend/src/ts/ready.ts b/frontend/src/ts/ready.ts index 0f20178de55b..58c72ad9bfa8 100644 --- a/frontend/src/ts/ready.ts +++ b/frontend/src/ts/ready.ts @@ -1,7 +1,6 @@ import * as Misc from "./utils/misc"; import * as MonkeyPower from "./elements/monkey-power"; import * as MerchBanner from "./elements/merch-banner"; -import * as ConnectionState from "./states/connection"; import * as AccountButton from "./elements/account-button"; //@ts-expect-error no types for this package import Konami from "konami"; @@ -28,19 +27,19 @@ onDOMReady(async () => { opacity: [0, 1], duration: Misc.applyReducedMotion(250), }); - if (ConnectionState.get()) { - void ServerConfiguration.sync().then(() => { - if (!ServerConfiguration.get()?.users.signUp) { - AccountButton.hide(); - qs(".register")?.addClass("hidden"); - qs(".login")?.addClass("hidden"); - qs(".disabledNotification")?.removeClass("hidden"); - } - if (!ServerConfiguration.get()?.connections.enabled) { - qs(".accountButtonAndMenu .goToFriends")?.addClass("hidden"); - } - }); - } + + void ServerConfiguration.sync().then(() => { + if (!ServerConfiguration.get()?.users.signUp) { + AccountButton.hide(); + qs(".register")?.hide(); + qs(".login")?.hide(); + qs(".disabledNotification")?.show(); + } + if (!ServerConfiguration.get()?.connections.enabled) { + qs(".accountButtonAndMenu .goToFriends")?.hide(); + } + }); + MonkeyPower.init(); // untyped, need to ignore diff --git a/frontend/src/ts/test/break-ligatures.ts b/frontend/src/ts/test/break-ligatures.ts new file mode 100644 index 000000000000..8ce3ea569677 --- /dev/null +++ b/frontend/src/ts/test/break-ligatures.ts @@ -0,0 +1,58 @@ +import Config from "../config"; +import { ElementWithUtils } from "../utils/dom"; + +function canBreak(wordEl: ElementWithUtils): boolean { + if (Config.typedEffect !== "dots") return false; + if (wordEl.hasClass("broken-ligatures")) return false; + + return wordEl.getParent()?.hasClass("withLigatures") ?? false; +} + +function applyIfNeeded(wordEl: ElementWithUtils): void { + if (!canBreak(wordEl)) return; + + const letters = wordEl.qsa("letter"); + const firstTop = Math.floor(letters[0]?.getOffsetTop() ?? 0); + const isWrapped = letters.some( + (l) => Math.floor(l.getOffsetTop()) !== firstTop, + ); + + if (!isWrapped) { + const { width } = wordEl.screenBounds(); + wordEl.setStyle({ width: `${width}px` }); + wordEl.removeClass("needs-wrap"); + } else { + wordEl.setStyle({ width: "" }); + wordEl.addClass("needs-wrap"); + } + wordEl.addClass("broken-ligatures"); +} + +function reset(wordEl: ElementWithUtils): void { + if (!wordEl.hasClass("broken-ligatures")) return; + wordEl.removeClass("broken-ligatures"); + wordEl.removeClass("needs-wrap"); + wordEl.setStyle({ width: "" }); +} + +export function set( + wordEl: ElementWithUtils, + areLigaturesBroken: boolean, +): void { + areLigaturesBroken ? applyIfNeeded(wordEl) : reset(wordEl); +} + +export function update(key: string, wordsEl: ElementWithUtils): void { + const words = wordsEl.qsa(".word.typed"); + + const shouldReset = + !wordsEl.hasClass("withLigatures") || + Config.typedEffect !== "dots" || + key === "fontFamily" || + key === "fontSize"; + + if (shouldReset) { + words.forEach(reset); + } + words.forEach(applyIfNeeded); +} diff --git a/frontend/src/ts/test/caps-warning.ts b/frontend/src/ts/test/caps-warning.ts index 97a329059923..df67b4501b40 100644 --- a/frontend/src/ts/test/caps-warning.ts +++ b/frontend/src/ts/test/caps-warning.ts @@ -9,14 +9,14 @@ export let capsState = false; function show(): void { if (!visible) { - el.removeClass("hidden"); + el.show(); visible = true; } } function hide(): void { if (visible) { - el.addClass("hidden"); + el.hide(); visible = false; } } diff --git a/frontend/src/ts/test/test-config.ts b/frontend/src/ts/test/test-config.ts index d15bbf5091e9..f44381278845 100644 --- a/frontend/src/ts/test/test-config.ts +++ b/frontend/src/ts/test/test-config.ts @@ -67,7 +67,6 @@ export async function instantUpdate(): Promise { qs("#testConfig .customText")?.show(); } - updateActiveExtraButtons("quoteLength", Config.quoteLength); updateActiveExtraButtons("numbers", Config.numbers); updateActiveExtraButtons("punctuation", Config.punctuation); } @@ -76,18 +75,14 @@ async function update(previous: Mode, current: Mode): Promise { if (previous === current) return; updateActiveModeButtons(current); - let m2; - - if (Config.mode === "time") { - m2 = Config.time; - } else if (Config.mode === "words") { - m2 = Config.words; - } else if (Config.mode === "quote") { - m2 = Config.quoteLength; + if (current === "time") { + updateActiveExtraButtons("time", Config.time); + } else if (current === "words") { + updateActiveExtraButtons("words", Config.words); + } else if (current === "quote") { + updateActiveExtraButtons("quoteLength", Config.quoteLength); } - if (m2 !== undefined) updateActiveExtraButtons(Config.mode, m2); - const submenu = { time: "time", words: "wordCount", @@ -251,6 +246,8 @@ async function update(previous: Mode, current: Mode): Promise { duration: animTime / 2, ease: easing.out, }); + + currentEl?.setStyle({ width: "" }); } function updateActiveModeButtons(mode: Mode): void { diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index bce35a8353a6..1159082b0075 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -1011,14 +1011,6 @@ export async function finish(difficultyFailed = false): Promise { duration: 1, }); dontSave = true; - } else if (afkDetected) { - Notifications.add("Test invalid - AFK detected", 0); - TestStats.setInvalid(); - dontSave = true; - } else if (TestState.isRepeated) { - Notifications.add("Test invalid - repeated", 0); - TestStats.setInvalid(); - dontSave = true; } else if ( completedEvent.testDuration < 1 || (Config.mode === "time" && mode2Number < 15 && mode2Number > 0) || @@ -1042,6 +1034,14 @@ export async function finish(difficultyFailed = false): Promise { TestStats.setInvalid(); tooShort = true; dontSave = true; + } else if (afkDetected) { + Notifications.add("Test invalid - AFK detected", 0); + TestStats.setInvalid(); + dontSave = true; + } else if (TestState.isRepeated) { + Notifications.add("Test invalid - repeated", 0); + TestStats.setInvalid(); + dontSave = true; } else if ( completedEvent.wpm < 0 || (completedEvent.wpm > 350 && @@ -1202,21 +1202,6 @@ async function saveResult( return null; } - if (!ConnectionState.get()) { - Notifications.add("Result not saved: offline", -1, { - duration: 2, - customTitle: "Notice", - important: true, - }); - AccountButton.loading(false); - retrySaving.canRetry = true; - qs("#retrySavingResultButton")?.show(); - if (!isRetrying) { - retrySaving.completedEvent = completedEvent; - } - return null; - } - const result = structuredClone(completedEvent); if (result.testDuration > 122) { diff --git a/frontend/src/ts/test/test-screenshot.ts b/frontend/src/ts/test/test-screenshot.ts index c8cf55ec4bc1..45232db81786 100644 --- a/frontend/src/ts/test/test-screenshot.ts +++ b/frontend/src/ts/test/test-screenshot.ts @@ -36,7 +36,7 @@ function revert(): void { if (revertCookie) qs("#cookiesModal")?.show(); if (revealReplay) qs("#resultReplay")?.show(); if (!isAuthenticated()) { - qs(".pageTest .loginTip")?.removeClass("hidden"); + qs(".pageTest .loginTip")?.show(); } qs("html")?.setStyle({ scrollBehavior: "smooth" }); for (const fb of getActiveFunboxesWithFunction("applyGlobalCSS")) { diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index 0153061d91b7..2c8d2e818065 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -48,6 +48,7 @@ import * as SlowTimer from "../states/slow-timer"; import * as TestConfig from "./test-config"; import * as CompositionDisplay from "../elements/composition-display"; import * as AdController from "../controllers/ad-controller"; +import * as Ligatures from "./break-ligatures"; import * as LayoutfluidFunboxTimer from "../test/funbox/layoutfluid-funbox-timer"; import * as Keymap from "../elements/keymap"; import * as ThemeController from "../controllers/theme-controller"; @@ -141,6 +142,7 @@ export function updateActiveElement( if (previousActiveWord !== null) { if (direction === "forward") { previousActiveWord.addClass("typed"); + Ligatures.set(previousActiveWord, true); } else if (direction === "back") { // } @@ -157,6 +159,7 @@ export function updateActiveElement( newActiveWord.addClass("active"); newActiveWord.removeClass("error"); newActiveWord.removeClass("typed"); + Ligatures.set(newActiveWord, false); activeWordTop = newActiveWord.getOffsetTop(); activeWordHeight = newActiveWord.getOffsetHeight(); @@ -603,7 +606,7 @@ export function updateWordsWrapperHeight(force = false): void { const activeWordEl = getActiveWordElement(); if (!activeWordEl) return; - wordsWrapperEl.removeClass("hidden"); + wordsWrapperEl.show(); const wordComputedStyle = window.getComputedStyle(activeWordEl.native); const wordMargin = @@ -1462,7 +1465,7 @@ export async function toggleResultWords(noAnimation = false): Promise { export async function applyBurstHeatmap(): Promise { if (Config.burstHeatmap) { - qsa("#resultWordsHistory .heatmapLegend")?.removeClass("hidden"); + qsa("#resultWordsHistory .heatmapLegend")?.show(); let burstlist = [...TestInput.burstHistory]; @@ -1566,7 +1569,7 @@ export async function applyBurstHeatmap(): Promise { }); } } else { - qs("#resultWordsHistory .heatmapLegend")?.addClass("hidden"); + qs("#resultWordsHistory .heatmapLegend")?.hide(); qsa("#resultWordsHistory .words .word")?.removeClass("heatmapInherit"); qsa("#resultWordsHistory .words .word")?.setStyle({ color: "" }); @@ -1685,9 +1688,9 @@ function updateLiveStatsColor(value: TimerColor): void { function showHideTestRestartButton(showHide: boolean): void { if (showHide) { - qs(".pageTest #restartTestButton")?.removeClass("hidden"); + qs(".pageTest #restartTestButton")?.show(); } else { - qs(".pageTest #restartTestButton")?.addClass("hidden"); + qs(".pageTest #restartTestButton")?.hide(); } } @@ -1879,8 +1882,8 @@ export function onTestStart(): void { } export function onTestRestart(source: "testPage" | "resultPage"): void { - qs("#result")?.addClass("hidden"); - qs("#typingTest")?.setStyle({ opacity: "0" }).removeClass("hidden"); + qs("#result")?.hide(); + qs("#typingTest")?.setStyle({ opacity: "0" }).show(); getInputElement().style.left = "0"; TestConfig.show(); Focus.set(false); @@ -2073,11 +2076,15 @@ ConfigEvent.subscribe(({ key, newValue }) => { "colorfulMode", "showAllLines", "fontSize", + "fontFamily", "maxLineWidth", "tapeMargin", ].includes(key) ) { - updateWordWrapperClasses(); + if (key !== "fontFamily") updateWordWrapperClasses(); + if (["typedEffect", "fontFamily", "fontSize"].includes(key)) { + Ligatures.update(key, wordsEl); + } } if (["tapeMode", "tapeMargin"].includes(key)) { updateLiveStatsMargin(); diff --git a/frontend/src/ts/utils/async-modules.ts b/frontend/src/ts/utils/async-modules.ts index 8837399b590f..d9f8064b083c 100644 --- a/frontend/src/ts/utils/async-modules.ts +++ b/frontend/src/ts/utils/async-modules.ts @@ -10,7 +10,6 @@ export async function getDevOptionsModal(): Promise< > { try { showLoaderBar(); - // oxlint-disable-next-line import/no-unresolved const module = await import("../modals/dev-options.js"); hideLoaderBar(); return module; diff --git a/frontend/src/ts/utils/dom.ts b/frontend/src/ts/utils/dom.ts index 29f9c59d0b9e..2e6c6d583e38 100644 --- a/frontend/src/ts/utils/dom.ts +++ b/frontend/src/ts/utils/dom.ts @@ -637,8 +637,8 @@ export class ElementWithUtils { } /** - * Get value of input or textarea - * @returns The value of the element, or undefined if the element is not an input or textarea. + * Get value of input, textarea or select + * @returns The value of the element, or undefined if the element is not an input, textarea or select. */ getValue(this: ElementWithUtils): string | undefined { if (this.hasValue()) { diff --git a/frontend/src/ts/utils/misc.ts b/frontend/src/ts/utils/misc.ts index b5f321a51450..c780b792f6d2 100644 --- a/frontend/src/ts/utils/misc.ts +++ b/frontend/src/ts/utils/misc.ts @@ -176,8 +176,6 @@ type LastIndex = { lastIndexOfRegex(regex: RegExp): number; } & string; -// TODO INVESTIGATE IF THIS IS NEEDED -// oxlint-disable-next-line no-extend-native (String.prototype as LastIndex).lastIndexOfRegex = function ( regex: RegExp, ): number { @@ -596,13 +594,11 @@ export function promiseWithResolvers(): { ): Promise { return currentPromise.then(onfulfilled, onrejected); }, - // oxlint-disable-next-line promise-function-async async catch( onrejected?: ((reason: unknown) => TResult | PromiseLike) | null, ): Promise { return currentPromise.catch(onrejected); }, - // oxlint-disable-next-line promise-function-async async finally(onfinally?: (() => void) | null): Promise { return currentPromise.finally(onfinally); }, diff --git a/frontend/src/ts/utils/results.ts b/frontend/src/ts/utils/results.ts index 2b4806f67538..83cf45e3194f 100644 --- a/frontend/src/ts/utils/results.ts +++ b/frontend/src/ts/utils/results.ts @@ -20,7 +20,7 @@ export async function syncNotSignedInLastResult(uid: string): Promise { //TODO - this type cast was not needed before because we were using JSON cloning // but now with the stronger types it shows that we are forcing completed event - // into a snapshot result - might not cuase issues but worth investigating + // into a snapshot result - might not cause issues but worth investigating const result = structuredClone( notSignedInLastResult, ) as unknown as SnapshotResult; diff --git a/frontend/src/ts/utils/simple-modal.ts b/frontend/src/ts/utils/simple-modal.ts index 10318d5385bb..71c1f00e8968 100644 --- a/frontend/src/ts/utils/simple-modal.ts +++ b/frontend/src/ts/utils/simple-modal.ts @@ -4,7 +4,6 @@ import { format as dateFormat } from "date-fns/format"; import { showLoaderBar, hideLoaderBar } from "../signals/loader-bar"; import * as Notifications from "../elements/notifications"; -import * as ConnectionState from "../states/connection"; import { IsValidResponse, ValidatedHtmlInputElement, @@ -112,7 +111,6 @@ type SimpleModalOptions = { beforeInitFn?: (thisPopup: SimpleModal) => void; beforeShowFn?: (thisPopup: SimpleModal) => void; canClose?: boolean; - onlineOnly?: boolean; hideCallsExec?: boolean; showLabels?: boolean; afterClickAway?: () => void; @@ -133,7 +131,6 @@ export class SimpleModal { beforeInitFn: ((thisPopup: SimpleModal) => void) | undefined; beforeShowFn: ((thisPopup: SimpleModal) => void) | undefined; canClose: boolean; - onlineOnly: boolean; hideCallsExec: boolean; showLabels: boolean; afterClickAway: (() => void) | undefined; @@ -152,7 +149,6 @@ export class SimpleModal { this.beforeInitFn = options.beforeInitFn; this.beforeShowFn = options.beforeShowFn; this.canClose = options.canClose ?? true; - this.onlineOnly = options.onlineOnly ?? false; this.hideCallsExec = options.hideCallsExec ?? false; this.showLabels = options.showLabels ?? false; this.afterClickAway = options.afterClickAway; @@ -408,10 +404,6 @@ export class SimpleModal { } show(parameters: string[] = [], showOptions: ShowOptions): void { - if (this.onlineOnly && !ConnectionState.get()) { - Notifications.add("You are offline", 0, { duration: 2 }); - return; - } activePopup = this; this.parameters = parameters; void modal.show({ diff --git a/frontend/static/languages/irish.json b/frontend/static/languages/irish.json index 2f66b4ed3e8b..e25d6bc30a98 100644 --- a/frontend/static/languages/irish.json +++ b/frontend/static/languages/irish.json @@ -1,5 +1,6 @@ { "name": "irish", + "bcp47": "ga-IE", "words": [ "an", "bí", diff --git a/frontend/static/languages/irish_1k.json b/frontend/static/languages/irish_1k.json new file mode 100644 index 000000000000..c1ea11f4ba8e --- /dev/null +++ b/frontend/static/languages/irish_1k.json @@ -0,0 +1,1006 @@ +{ + "name": "irish_1k", + "bcp47": "ga-IE", + "words": [ + "an", + "a", + "ar", + "agus", + "na", + "go", + "i", + "ag", + "le", + "is", + "sé", + "tá", + "ó", + "sa", + "sin", + "seo", + "é", + "de", + "ach", + "mar", + "leis", + "in", + "atá", + "ní", + "uirlis", + "do", + "raibh", + "faoi", + "nach", + "ina", + "féin", + "ann", + "gur", + "eile", + "as", + "chun", + "den", + "don", + "siad", + "bheith", + "dá", + "ná", + "aon", + "ba", + "mé", + "san", + "acu", + "nuair", + "iad", + "chur", + "á", + "amach", + "duine", + "daoine", + "teanga", + "air", + "ón", + "gach", + "bhailiú", + "gan", + "chomh", + "aige", + "í", + "chuid", + "déanamh", + "maith", + "dúirt", + "lá", + "nua", + "sna", + "átha", + "idir", + "mó", + "bhliain", + "faoin", + "dul", + "bith", + "mo", + "chéad", + "lucht", + "cé", + "mac", + "chaile", + "féidir", + "isteach", + "chuir", + "leo", + "deireadh", + "orthu", + "láthair", + "liom", + "cur", + "thabhairt", + "scéal", + "áit", + "bliain", + "dó", + "lena", + "am", + "thug", + "obair", + "tú", + "leor", + "linn", + "má", + "rá", + "cuid", + "bheadh", + "fad", + "bliana", + "níor", + "tar", + "éis", + "dhá", + "tháinig", + "ceann", + "áigh", + "beag", + "salann", + "dé", + "agam", + "baile", + "leith", + "teacht", + "leabhar", + "bheo", + "deir", + "ansin", + "rialtas", + "rinne", + "uair", + "nár", + "thar", + "againn", + "téarma", + "céanna", + "saol", + "siúl", + "taobh", + "measc", + "anseo", + "dóibh", + "bhaile", + "mhór", + "fuinneog", + "fós", + "freisin", + "roimh", + "réir", + "fear", + "fada", + "léir", + "fiú", + "cuireadh", + "cad", + "caite", + "mhaith", + "ea", + "síos", + "anuas", + "náisiúnta", + "riamh", + "dár", + "oibre", + "iarraidh", + "roinn", + "bíonn", + "cinn", + "ríonn", + "fáil", + "oíche", + "fearr", + "céin", + "stát", + "beidh", + "déanta", + "bharr", + "éigin", + "áirithe", + "thart", + "uí", + "cinnte", + "tír", + "maidir", + "chuig", + "súil", + "dara", + "mí", + "gcónaí", + "scoil", + "nós", + "áfach", + "bhain", + "daingean", + "cúrsaí", + "gnách", + "bróg", + "clár", + "oiread", + "orm", + "ndóigh", + "leithéid", + "háirithe", + "chóir", + "ár", + "chuaigh", + "muid", + "fuair", + "siar", + "eolas", + "sásta", + "fios", + "ábhar", + "oideachais", + "cheana", + "thaobh", + "dúinn", + "baint", + "gurb", + "aici", + "blianta", + "mbíonn", + "dom", + "rath", + "os", + "phobal", + "bhaint", + "siúd", + "tharla", + "gá", + "fud", + "lán", + "cúpla", + "méid", + "déanaí", + "uile", + "uirthi", + "minic", + "mórán", + "teach", + "cheann", + "cuideachta", + "úsáid", + "rith", + "mhí", + "léi", + "raidió", + "ais", + "leat", + "phobail", + "feadh", + "dhiaidh", + "éagsúla", + "beo", + "dóigh", + "ainm", + "óg", + "dhuine", + "curtha", + "agat", + "arsa", + "bhíodh", + "chontae", + "ionad", + "shampla", + "seans", + "bord", + "suas", + "caint", + "tseachtain", + "inniu", + "dealaigh", + "tabhairt", + "lár", + "chaith", + "chugainn", + "muintir", + "airgid", + "líon", + "thosaigh", + "bhaineann", + "rialtais", + "úd", + "thuadh", + "cosúil", + "son", + "iomlán", + "aontaithe", + "pobal", + "di", + "thiar", + "cúig", + "chuige", + "amuigh", + "roimhe", + "laghad", + "lae", + "díreach", + "seachas", + "éirí", + "cheist", + "scannán", + "chéanna", + "eolais", + "ceol", + "fómhair", + "amhras", + "hata", + "deis", + "dúbailte", + "fóill", + "labhairt", + "istigh", + "láidir", + "domhain", + "tugadh", + "leagan", + "scéalta", + "chaitheamh", + "dlí", + "teaghlach", + "déag", + "taithí", + "óga", + "bua", + "fosta", + "stair", + "ard", + "léinn", + "chúrsaí", + "feiceáil", + "bun", + "ollscoil", + "tamall", + "focal", + "gcás", + "mise", + "chathair", + "dála", + "ceist", + "rogha", + "athrú", + "rinneadh", + "léim", + "léiriú", + "béarla", + "cearnóg", + "iarracht", + "tosaigh", + "mhuintir", + "mura", + "cuma", + "teoranta", + "toisc", + "mhic", + "ainneoin", + "sinn", + "brionglóid", + "saothar", + "mbéal", + "móra", + "seisean", + "comhairle", + "oifig", + "fáilte", + "fáth", + "gurbh", + "ab", + "fhoireann", + "lorg", + "luath", + "timpeall", + "léamh", + "feasta", + "ceithre", + "tráth", + "pobail", + "tuairim", + "tacaíocht", + "airgead", + "coláiste", + "bheas", + "iontach", + "bheag", + "shaol", + "domhan", + "beirt", + "ceoil", + "díobh", + "chlár", + "áireamh", + "plé", + "tuaisceart", + "sráid", + "caithfidh", + "freastal", + "poiblí", + "mná", + "páirtí", + "leabhair", + "glacadh", + "uachtarán", + "iontu", + "coinníoll", + "dócha", + "cuireann", + "orainn", + "tharlóidh", + "té", + "breá", + "gnó", + "limistéar", + "iarratais", + "faoina", + "scoileanna", + "chluiche", + "sláinte", + "bean", + "post", + "um", + "contae", + "ama", + "teilifíse", + "páistí", + "beaga", + "deacair", + "leibhéal", + "oileán", + "cat", + "fíor", + "dráma", + "duit", + "ré", + "cá", + "cead", + "córas", + "lámh", + "cuairt", + "bhealach", + "breis", + "chuireann", + "speisialta", + "nuacht", + "seachtain", + "chláir", + "uaidh", + "chor", + "cionn", + "choláiste", + "ceisteanna", + "meán", + "loch", + "deirtear", + "bhord", + "céard", + "féile", + "forbairt", + "trácht", + "amhlaidh", + "ort", + "bhean", + "réiteach", + "dtiús", + "tí", + "scéim", + "cluiche", + "carraig", + "idirnáisiúnta", + "duais", + "orlach", + "airde", + "cathrach", + "bunaithe", + "áitiúil", + "thugann", + "deich", + "abhaile", + "cois", + "tréimhse", + "bhéal", + "ghlac", + "foireann", + "aithne", + "moladh", + "fheiceáil", + "bhuaigh", + "úr", + "céad", + "aimsir", + "dún", + "chill", + "thír", + "anuraidh", + "naomh", + "imirt", + "éirigh", + "spéis", + "aisling", + "eaglais", + "tugann", + "chonradh", + "scoile", + "stáisiún", + "ionann", + "múinteoirí", + "alt", + "cheantar", + "haois", + "dheireadh", + "suim", + "labhair", + "fir", + "eagraíocht", + "rún", + "ball", + "seasamh", + "taispeántas", + "sula", + "fianna", + "boladh", + "rugadh", + "coiste", + "inis", + "bunaidh", + "aird", + "seoladh", + "dhuán", + "tagtha", + "ceannais", + "bóthar", + "bhonn", + "bog", + "aontas", + "tugtha", + "theas", + "bhfeidhm", + "saoire", + "iúl", + "bunúsach", + "chuala", + "ealaíne", + "uisce", + "ócáid", + "polaitíochta", + "fiche", + "comórtas", + "tuilleadh", + "páirteach", + "costas", + "barr", + "seirbhísí", + "chineál", + "óna", + "bonn", + "riachtanach", + "leath", + "fanacht", + "easpa", + "lú", + "dáta", + "bhunú", + "laethanta", + "cumann", + "bheirt", + "tríú", + "seiceáil", + "fhorbairt", + "brú", + "craobh", + "chonaic", + "taighde", + "deo", + "brath", + "meáin", + "maidin", + "bíodh", + "phost", + "conradh", + "iarthar", + "áitiúla", + "milliún", + "gorm", + "foilsíodh", + "tógadh", + "fadhbanna", + "dáil", + "thóg", + "páirt", + "iriseoir", + "imeachtaí", + "talamh", + "míle", + "cainte", + "leanúint", + "seacht", + "stiúrthóir", + "gairid", + "tionchar", + "thuas", + "slán", + "leas", + "iúil", + "thuig", + "fhéile", + "athair", + "teilifís", + "phointe", + "foirne", + "mhéid", + "fágtha", + "aois", + "léiríonn", + "deimhin", + "teideal", + "rialta", + "oifigiúil", + "imithe", + "gclár", + "neart", + "rúnaí", + "ábalta", + "chás", + "éadan", + "beagnach", + "nuachtán", + "fás", + "conas", + "bealach", + "cill", + "tuiscint", + "breise", + "thógáil", + "fonn", + "deireanach", + "dtír", + "thall", + "cuirfear", + "uathu", + "scoth", + "deara", + "cluichí", + "árann", + "bhéarla", + "scrúdú", + "tuairisc", + "thuilleadh", + "inti", + "féachaint", + "toradh", + "míosa", + "comhlacht", + "thagann", + "poll", + "eagarthóir", + "mian", + "scór", + "chúis", + "thíos", + "réimse", + "mheán", + "fhéidir", + "triúr", + "réidh", + "chuma", + "eagrán", + "smaoineamh", + "meas", + "forbartha", + "dorcha", + "cogadh", + "sóisialta", + "saor", + "seirbhís", + "gcoláiste", + "tithe", + "luain", + "dearcadh", + "mheas", + "ghlacadh", + "aoine", + "turas", + "cúis", + "chloiste", + "moltaí", + "imeachta", + "tábhachtach", + "céim", + "chaoi", + "chóras", + "mhaithe", + "cás", + "bhféadfadh", + "grúpa", + "pé", + "tagairt", + "fhearr", + "dearmad", + "shaothar", + "níorbh", + "cruinniú", + "suíochán", + "cultúir", + "caithfear", + "feachtas", + "cúrsa", + "choinneáil", + "comhair", + "seomra", + "theanga", + "reatha", + "cinneadh", + "scríofa", + "bhfuair", + "dia", + "staidéar", + "scéil", + "aer", + "nuachtáin", + "éisteacht", + "tuarascáil", + "rí", + "peile", + "baineann", + "measa", + "fhianna", + "mhol", + "léirigh", + "dobhair", + "ceapadh", + "ainmfhocail", + "leanas", + "údar", + "ros", + "áiteanna", + "soiléir", + "cónaí", + "sular", + "rás", + "gaelach", + "ráite", + "coimisiún", + "teachta", + "scríbhneoir", + "sagart", + "ról", + "imní", + "lean", + "breathnú", + "vóta", + "ndearna", + "bhrí", + "dhó", + "baill", + "aice", + "stádas", + "focail", + "oscailt", + "dúchais", + "méadú", + "traidisiúnta", + "neamhspleách", + "domhanda", + "cearta", + "mhó", + "aontais", + "díol", + "daonna", + "tíortha", + "oideachas", + "trasna", + "inné", + "táthar", + "ráiteas", + "croí", + "dheas", + "tapa", + "bainte", + "urlabhraí", + "tábhacht", + "chuirfeadh", + "cathaoirleach", + "sraith", + "íoc", + "éileamh", + "bhunaigh", + "romhainn", + "mbéarla", + "arm", + "cúram", + "rua", + "fine", + "fuar", + "caitheamh", + "tráthnóna", + "polasaí", + "óige", + "suí", + "pictiúr", + "ardú", + "bia", + "amhrán", + "baol", + "titim", + "gceann", + "tobann", + "aonair", + "inár", + "dubh", + "gcluiche", + "amharclann", + "chaill", + "tagann", + "bán", + "huaire", + "muirí", + "litríocht", + "fhear", + "cruthú", + "leaba", + "éigean", + "tom", + "líne", + "eagla", + "cineál", + "luach", + "chroí", + "charráin", + "bhuail", + "dtiocfadh", + "aitheantas", + "casadh", + "péire", + "teagmháil", + "phlé", + "cuir", + "cáil", + "tacaíochta", + "meastar", + "talún", + "gcuirfí", + "tuaiscirt", + "ranganna", + "mílte", + "faighte", + "airgeadais", + "radharc", + "oscailte", + "háite", + "gabh", + "ríoga", + "gabháil", + "geall", + "formhór", + "reáchtáil", + "fadhb", + "scannáin", + "inar", + "dteach", + "doras", + "teangacha", + "athuair", + "bí", + "phoiblí", + "filíochta", + "dúradh", + "cheol", + "bpáirc", + "inneall", + "file", + "feidhmiú", + "lonnaithe", + "coiscéim", + "bocht", + "thit", + "deiridh", + "acht", + "díth", + "tábhachtaí", + "hamháin", + "mhalairt", + "deacrachtaí", + "mhac", + "éiríonn", + "cheoil", + "shíl", + "cóir", + "bainisteoir", + "tuarastal", + "trua", + "polaitiúla", + "gloine", + "coitianta", + "bháis", + "thuaisceart", + "gcontae", + "dochar", + "chois", + "oireachtais", + "choinne", + "suite", + "sámháin", + "saghas", + "théann", + "luí", + "gcoitinne", + "chosaint", + "chomórtas", + "álainn", + "gradam", + "thiocfadh", + "spóirt", + "institiúidí", + "táim", + "sórt", + "samhradh", + "laistigh", + "chloig", + "nárbh", + "táimid", + "bhuel", + "ráth", + "ceantar", + "agallamh", + "polaiteoir", + "nithe", + "mbíodh", + "cloisteáil", + "comhdháil", + "fhadhb", + "déanann", + "caighdeán", + "sholáthar", + "ionadh", + "chaint", + "taoiseach", + "soláthar", + "pháistí", + "oifigeach", + "leathanach", + "cúl", + "ionsaí", + "aidhm", + "uaireanta", + "freagra", + "chinn", + "fúthu", + "fhágáil", + "thoradh", + "roinne", + "olltoghchán", + "mhóir", + "ghrúpa", + "cheannach", + "bhun", + "mháthair", + "ghnáth", + "cumas", + "cheap", + "sos", + "locht", + "iris", + "girseach", + "thús", + "rannóg", + "crua", + "iontas", + "cuimhin", + "litir", + "plean", + "chumas", + "tuaithe", + "theach", + "teastáil", + "spreagadh", + "gceannas", + "cosc", + "comhlachtaí", + "fhoghlaim", + "cuí", + "stráice", + "súile", + "deiseanna", + "mic", + "dóchas", + "cibé", + "glan", + "dán", + "chraobh", + "toghchán", + "chúrsa", + "cille", + "fírinne", + "ceantair", + "beatha", + "áras", + "tuigtear", + "tosú", + "siopa", + "saoirse", + "srón" + ] +} diff --git a/frontend/static/languages/kinyarwanda.json b/frontend/static/languages/kinyarwanda.json index 5df4ca814739..cc177a0e2029 100644 --- a/frontend/static/languages/kinyarwanda.json +++ b/frontend/static/languages/kinyarwanda.json @@ -276,6 +276,101 @@ "intare", "inzovu", "imvubu", - "ingagi" + "ingagi", + "ubaka", + "ikoranabuhanga", + "iterambere", + "uburezi", + "ubuhinzi", + "ubucuruzi", + "ubukerarugendo", + "umuco", + "amateka", + "ubuhanzi", + "imikino", + "imyidagaduro", + "itumanaho", + "ubwikorezi", + "inganda", + "ingufu", + "ibidukikije", + "ubutabera", + "amategeko", + "uburenganzira", + "uburinganire", + "urubyiruko", + "abagore", + "abana", + "abageze", + "zabukuru", + "ubumuga", + "ubukene", + "icyorezo", + "inkingo", + "isuku", + "imiturire", + "amashanyarazi", + "itange", + "inyubako", + "imihanda", + "ikiraro", + "icyambu", + "ikibuga", + "itangazamaruru", + "imbuga", + "nkoranyambaga", + "internet", + "mudasobwa", + "porogaramu", + "itangwa", + "ubukwe", + "ikiriyo", + "umuganura", + "intwari", + "ubumwe", + "ubwiyunge", + "amahoro", + "urukundo", + "ubumuntu", + "ubwitange", + "umurava", + "ubunyangamugayo", + "ubushishozi", + "ubuvugizi", + "ubwisungane", + "ubufatanye", + "umuganda", + "gacaca", + "abunzi", + "itonesha", + "ruswa", + "akarengane", + "ubujura", + "ubwicanyi", + "intambara", + "itsembabwoko", + "amahano", + "icyaha", + "igifungo", + "urukiko", + "umunyamategeko", + "umupolisi", + "umusirikare", + "umuturage", + "abayobozi", + "akarere", + "umurenge", + "akagari", + "umudugudu", + "amahanga", + "afurika", + "isi", + "umuvuduko", + "intego", + "ubushobozi", + "ubushakashatsi", + "ubuhanga", + "umutungo", + "ishoramari" ] } diff --git a/frontend/static/layouts/miligram.json b/frontend/static/layouts/miligram.json new file mode 100644 index 000000000000..b1158c0ba255 --- /dev/null +++ b/frontend/static/layouts/miligram.json @@ -0,0 +1,63 @@ +{ + "keymapShowTopRow": true, + "type": "iso", + "keys": { + "row1": [ + ["`", "~"], + ["1", "!"], + ["2", "\""], + ["3", "#"], + ["4", "$"], + ["5", "%"], + ["6", "&"], + ["7", "'"], + ["8", "("], + ["9", ")"], + ["0", "~"], + ["^", "~"], + ["]", "}"] + ], + "row2": [ + ["b", "B"], + ["w", "W"], + ["l", "L"], + ["u", "U"], + ["[", "{"], + ["]", "}"], + ["o", "O"], + ["f", "F"], + ["g", "G"], + ["y", "Y"], + ["k", "K"], + ["=", "+"] + ], + "row3": [ + ["d", "D"], + ["a", "A"], + ["t", "T"], + ["s", "S"], + [";", "+"], + [":", "*"], + ["e", "E"], + ["i", "I"], + ["r", "R"], + ["n", "N"], + ["q", "Q"], + ["'", "\""] + ], + "row4": [ + ["z", "Z"], + ["x", "X"], + ["c", "C"], + ["v", "V"], + [",", "<"], + [".", ">"], + ["h", "H"], + ["m", "M"], + ["j", "J"], + ["p", "P"], + ["@", "`"] + ], + "row5": [[" "]] + } +} diff --git a/frontend/static/layouts/vitrimak.json b/frontend/static/layouts/vitrimak.json new file mode 100644 index 000000000000..86f64e41536a --- /dev/null +++ b/frontend/static/layouts/vitrimak.json @@ -0,0 +1,62 @@ +{ + "keymapShowTopRow": false, + "type": "ansi", + "keys": { + "row1": [ + ["`", "~"], + ["1", "!"], + ["2", "@"], + ["3", "#"], + ["4", "$"], + ["5", "%"], + ["6", "^"], + ["7", "&"], + ["8", "*"], + ["9", "("], + ["0", ")"], + ["-", "_"], + ["=", "+"] + ], + "row2": [ + ["t", "T"], + ["k", "K"], + ["v", "V"], + ["u", "U"], + ["m", "M"], + ["i", "I"], + ["a", "A"], + ["j", "J"], + ["b", "B"], + ["r", "R"], + ["[", "{"], + ["]", "}"], + ["\\", "|"] + ], + "row3": [ + ["w", "W"], + ["x", "X"], + ["/", "?"], + ["f", "F"], + ["p", "P"], + ["d", "D"], + ["g", "G"], + ["q", "Q"], + [",", "<"], + ["s", "S"], + [";", ":"] + ], + "row4": [ + ["h", "H"], + [".", ">"], + ["'", "\""], + ["c", "C"], + ["o", "O"], + ["l", "L"], + ["n", "N"], + ["z", "Z"], + ["y", "Y"], + ["e", "E"] + ], + "row5": [[" "]] + } +} diff --git a/frontend/static/quotes/code_java.json b/frontend/static/quotes/code_java.json index b46678b1cf4f..20291f168ce7 100644 --- a/frontend/static/quotes/code_java.json +++ b/frontend/static/quotes/code_java.json @@ -846,6 +846,96 @@ "source": "Micrometer Metrics Integration", "id": 140, "length": 220 + }, + { + "text": "public static List> findMaximalCliques(List> adjacency) {\n\tif (adjacency == null) {\n\t\tthrow new IllegalArgumentException(\"Adjacency list must not be null\");\n\t}\n\n\tint n = adjacency.size();\n\tList> graph = new ArrayList<>(n);\n\tfor (int u = 0; u < n; u++) {\n\t\tSet neighbors = adjacency.get(u);\n\t\tif (neighbors == null) {\n\t\t\tthrow new IllegalArgumentException(\"Adjacency list must not contain null sets\");\n\t\t}\n\t\tSet copy = new HashSet<>();\n\t\tfor (int v : neighbors) {\n\t\t\tif (v < 0 || v >= n) {\n\t\t\t\tthrow new IllegalArgumentException(\"Neighbor index out of bounds: \" + v);\n\t\t\t}\n\t\t\tif (v != u) {\n\t\t\t\tcopy.add(v);\n\t\t\t}\n\t\t}\n\t\tgraph.add(copy);\n\t}\n\n\tSet r = new HashSet<>();\n\tSet p = new HashSet<>();\n\tSet x = new HashSet<>();\n\tfor (int v = 0; v < n; v++) {\n\t\tp.add(v);\n\t}\n\n\tList> cliques = new ArrayList<>();\n\tbronKerboschPivot(r, p, x, graph, cliques);\n\treturn cliques;\n}", + "source": "Graph Algorithms - Maximal Cliques", + "id": 141, + "length": 959 + }, + { + "text": "private static void bronKerboschPivot(Set r, Set p, Set x, List> graph, List> cliques) {\n\tif (p.isEmpty() && x.isEmpty()) {\n\t\tcliques.add(new HashSet<>(r));\n\t\treturn;\n\t}\n\n\tint pivot = choosePivot(p, x, graph);\n\tSet candidates = new HashSet<>(p);\n\tif (pivot != -1) {\n\t\tcandidates.removeAll(graph.get(pivot));\n\t}\n\n\tfor (Integer v : candidates) {\n\t\tr.add(v);\n\t\tSet newP = intersection(p, graph.get(v));\n\t\tSet newX = intersection(x, graph.get(v));\n\t\tbronKerboschPivot(r, newP, newX, graph, cliques);\n\t\tr.remove(v);\n\t\tp.remove(v);\n\t\tx.add(v);\n\t}\n}", + "source": "Bron-Kerbosch Algorithm with Pivoting", + "id": 142, + "length": 622 + }, + { + "text": "private static int choosePivot(Set p, Set x, List> graph) {\n\tint pivot = -1;\n\tint maxDegree = -1;\n\tSet union = new HashSet<>(p);\n\tunion.addAll(x);\n\tfor (Integer v : union) {\n\t\tint degree = graph.get(v).size();\n\t\tif (degree > maxDegree) {\n\t\t\tmaxDegree = degree;\n\t\t\tpivot = v;\n\t\t}\n\t}\n\treturn pivot;\n}", + "source": "Bron-Kerbosch Algorithm - Pivot Selection", + "id": 143, + "length": 338 + }, + { + "text": "private static Set intersection(Set base, Set neighbors) {\n\tSet result = new HashSet<>();\n\tfor (Integer v : base) {\n\t\tif (neighbors.contains(v)) {\n\t\t\tresult.add(v);\n\t\t}\n\t}\n\treturn result;\n}", + "source": "Set Intersection Utility", + "id": 144, + "length": 225 + }, + { + "text": "public int solve(int start, int target) {\n\tint numNodes = graph.getNumNodes();\n\tint[][] dp = new int[maxResource + 1][numNodes];\n\tfor (int i = 0; i <= maxResource; i++) {\n\t\tArrays.fill(dp[i], Integer.MAX_VALUE);\n\t}\n\tdp[0][start] = 0;\n\tfor (int r = 0; r <= maxResource; r++) {\n\t\tfor (int u = 0; u < numNodes; u++) {\n\t\t\tif (dp[r][u] == Integer.MAX_VALUE) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfor (Graph.Edge edge : graph.getEdges(u)) {\n\t\t\t\tint v = edge.to();\n\t\t\t\tint cost = edge.cost();\n\t\t\t\tint resource = edge.resource();\n\t\t\t\tif (r + resource <= maxResource) {\n\t\t\t\t\tdp[r + resource][v] = Math.min(dp[r + resource][v], dp[r][u] + cost);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tint minCost = Integer.MAX_VALUE;\n\tfor (int r = 0; r <= maxResource; r++) {\n\t\tminCost = Math.min(minCost, dp[r][target]);\n\t}\n\treturn minCost == Integer.MAX_VALUE ? -1 : minCost;\n}", + "source": "Constrained Shortest Path Problem (CSP)", + "id": 145, + "length": 819 + }, + { + "text": "public static int maxFlow(int[][] capacity, int source, int sink) {\n\tif (capacity == null || capacity.length == 0) {\n\t\tthrow new IllegalArgumentException(\"Capacity matrix must not be null or empty\");\n\t}\n\tfinal int n = capacity.length;\n\tfor (int i = 0; i < n; i++) {\n\t\tif (capacity[i] == null || capacity[i].length != n) {\n\t\t\tthrow new IllegalArgumentException(\"Capacity matrix must be square\");\n\t\t}\n\t\tfor (int j = 0; j < n; j++) {\n\t\t\tif (capacity[i][j] < 0) {\n\t\t\t\tthrow new IllegalArgumentException(\"Capacities must be non-negative\");\n\t\t\t}\n\t\t}\n\t}\n\tif (source < 0 || sink < 0 || source >= n || sink >= n) {\n\t\tthrow new IllegalArgumentException(\"Source and sink must be valid vertex indices\");\n\t}\n\tif (source == sink) {\n\t\treturn 0;\n\t}\n\tint[][] residual = new int[n][n];\n\tfor (int i = 0; i < n; i++) {\n\t\tresidual[i] = Arrays.copyOf(capacity[i], n);\n\t}\n\tint[] level = new int[n];\n\tint flow = 0;\n\twhile (bfsBuildLevelGraph(residual, source, sink, level)) {\n\t\tint[] next = new int[n];\n\t\tint pushed;\n\t\tdo {\n\t\t\tpushed = dfsBlocking(residual, level, next, source, sink, Integer.MAX_VALUE);\n\t\t\tflow += pushed;\n\t\t} while (pushed > 0);\n\t}\n\treturn flow;\n}", + "source": "Dinic's Algorithm - Maximum Flow", + "id": 146, + "length": 1142 + }, + { + "text": "private static boolean bfsBuildLevelGraph(int[][] residual, int source, int sink, int[] level) {\n\tArrays.fill(level, -1);\n\tlevel[source] = 0;\n\tQueue q = new ArrayDeque<>();\n\tq.add(source);\n\twhile (!q.isEmpty()) {\n\t\tint u = q.poll();\n\t\tfor (int v = 0; v < residual.length; v++) {\n\t\t\tif (residual[u][v] > 0 && level[v] == -1) {\n\t\t\t\tlevel[v] = level[u] + 1;\n\t\t\t\tif (v == sink) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tq.add(v);\n\t\t\t}\n\t\t}\n\t}\n\treturn level[sink] != -1;\n}", + "source": "Dinic's Algorithm - BFS Level Graph Construction", + "id": 147, + "length": 463 + }, + { + "text": "private static int dfsBlocking(int[][] residual, int[] level, int[] next, int u, int sink, int f) {\n\tif (u == sink) {\n\t\treturn f;\n\t}\n\tfinal int n = residual.length;\n\tfor (int v = next[u]; v < n; v++, next[u] = v) {\n\t\tif (residual[u][v] <= 0) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (level[v] != level[u] + 1) {\n\t\t\tcontinue;\n\t\t}\n\t\tint pushed = dfsBlocking(residual, level, next, v, sink, Math.min(f, residual[u][v]));\n\t\tif (pushed > 0) {\n\t\t\tresidual[u][v] -= pushed;\n\t\t\tresidual[v][u] += pushed;\n\t\t\treturn pushed;\n\t\t}\n\t}\n\treturn 0;\n}", + "source": "Dinic's Algorithm - DFS Blocking Flow", + "id": 148, + "length": 516 + }, + { + "text": "public static boolean isAlphabetical(String s) {\n\ts = s.toLowerCase();\n\tfor (int i = 0; i < s.length() - 1; ++i) {\n\t\tif (!Character.isLetter(s.charAt(i)) || s.charAt(i) > s.charAt(i + 1)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn !s.isEmpty() && Character.isLetter(s.charAt(s.length() - 1));\n}", + "source": "String Validation - Alphabetical Check", + "id": 149, + "length": 285 + }, + { + "text": "public List findEulerianCircuit() {\n\tif (!hasEulerianCircuit()) {\n\t\treturn Collections.emptyList();\n\t}\n\tMap> tempGraph = new HashMap<>();\n\tfor (Map.Entry> entry : graph.entrySet()) {\n\t\ttempGraph.put(entry.getKey(), new LinkedList<>(entry.getValue()));\n\t}\n\tStack currentPath = new Stack<>();\n\tLinkedList circuit = new LinkedList<>();\n\n\tint startVertex = -1;\n\tfor (Map.Entry> entry : tempGraph.entrySet()) {\n\t\tif (!entry.getValue().isEmpty()) {\n\t\t\tstartVertex = entry.getKey();\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (startVertex == -1) {\n\t\tif (graph.isEmpty()) {\n\t\t\treturn Collections.emptyList();\n\t\t}\n\t\treturn Collections.singletonList(graph.keySet().iterator().next());\n\t}\n\tcurrentPath.push(startVertex);\n\twhile (!currentPath.isEmpty()) {\n\t\tint currentVertex = currentPath.peek();\n\n\t\tif (tempGraph.containsKey(currentVertex) && !tempGraph.get(currentVertex).isEmpty()) {\n\t\t\tint nextVertex = tempGraph.get(currentVertex).pollFirst();\n\t\t\ttempGraph.get(nextVertex).remove(Integer.valueOf(currentVertex));\n\t\t\tcurrentPath.push(nextVertex);\n\t\t} else {\n\t\t\tcircuit.addFirst(currentVertex);\n\t\t\tcurrentPath.pop();\n\t\t}\n\t}\n\treturn circuit;\n}", + "source": "Graph Theory - Eulerian Circuit", + "id": 150, + "length": 1226 + }, + { + "text": "private boolean isCoherentlyConnected() {\n\tif (graph.isEmpty()) {\n\t\treturn true;\n\t}\n\tSet visited = new HashSet<>();\n\tint startNode = -1;\n\tfor (Map.Entry> entry : graph.entrySet()) {\n\t\tif (!entry.getValue().isEmpty()) {\n\t\t\tstartNode = entry.getKey();\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (startNode == -1) {\n\t\treturn true;\n\t}\n\tdfs(startNode, visited);\n\tfor (Map.Entry> entry : graph.entrySet()) {\n\t\tif (!entry.getValue().isEmpty() && !visited.contains(entry.getKey())) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}", + "source": "Graph Connectivity - Coherent Connection Check", + "id": 151, + "length": 560 + }, + { + "text": "@WebEndpointTest\nvoid allEvents(WebTestClient client) {\n\tclient.get()\n\t\t.uri((builder) -> builder.path(\"/actuator/auditevents\").build())\n\t\t.exchange()\n\t\t.expectStatus()\n\t\t.isOk()\n\t\t.expectBody()\n\t\t.jsonPath(\"events.[*].principal\")\n\t\t.isEqualTo(new JSONArray().appendElement(\"admin\").appendElement(\"admin\").appendElement(\"user\"));\n}", + "source": "Spring Boot - WebTestClient Fluent API", + "id": 152, + "length": 331 + }, + { + "text": "@Configuration(proxyBeanMethods = false)\nstatic class TestConfiguration {\n\t@Bean\n\tAuditEventRepository auditEventsRepository() {\n\t\tAuditEventRepository repository = new InMemoryAuditEventRepository(3);\n\t\trepository.add(createEvent(\"2016-11-01T11:00:00Z\", \"admin\", \"login\"));\n\t\trepository.add(createEvent(\"2016-11-01T12:00:00Z\", \"admin\", \"logout\"));\n\t\trepository.add(createEvent(\"2016-11-01T12:00:00Z\", \"user\", \"login\"));\n\t\treturn repository;\n\t}\n\t@Bean\n\tAuditEventsEndpoint auditEventsEndpoint(AuditEventRepository auditEventRepository) {\n\t\treturn new AuditEventsEndpoint(auditEventRepository);\n\t}\n\tprivate AuditEvent createEvent(String instant, String principal, String type) {\n\t\treturn new AuditEvent(Instant.parse(instant), principal, type, Collections.emptyMap());\n\t}\n}", + "source": "Spring Boot - Test Configuration Class", + "id": 153, + "length": 772 + }, + { + "text": "@Test\nvoid getWhenContextTypeIsNullShouldThrowException() {\n\tassertThatIllegalArgumentException()\n\t\t.isThrownBy(() -> ApplicationContextAssertProvider.get(TestAssertProviderApplicationContextClass.class,\n\t\t\t\tApplicationContext.class, this.mockContextSupplier))\n\t\t.withMessageContaining(\"'type' must be an interface\");\n}", + "source": "Spring Boot - Exception Assertion Testing", + "id": 154, + "length": 319 + }, + { + "text": "@Test\nvoid getWhenContextFailsShouldReturnProxyThatThrowsExceptions() {\n\tApplicationContextAssertProvider context = get(this.startupFailureSupplier);\n\tassertThat((Object) context).isNotNull();\n\tassertThatIllegalStateException().isThrownBy(() -> context.getBean(\"foo\"))\n\t\t.withCause(this.startupFailure)\n\t\t.withMessageContaining(\"failed to start\");\n}", + "source": "Spring Boot - Context Failure Testing", + "id": 155, + "length": 369 } ] } diff --git a/frontend/static/quotes/english.json b/frontend/static/quotes/english.json index ccacc9cf33c4..d266e8963cf4 100644 --- a/frontend/static/quotes/english.json +++ b/frontend/static/quotes/english.json @@ -39189,6 +39189,18 @@ "source": "Everything Everywhere All At Once", "id": 7744, "length": 150 + }, + { + "text": "The only way to learn is by playing. The only way to win is by learning. And the only way to begin is by beginning.", + "source": "Sam Reich - Game Changer", + "id": 7745, + "length": 115 + }, + { + "text": "I will travel far beyond the path of reason. Take me back to Eden.", + "source": "Sleep Token", + "id": 7746, + "length": 66 } ] } diff --git a/frontend/static/quotes/esperanto.json b/frontend/static/quotes/esperanto.json index 753d7f0e40c2..957f57ab905d 100644 --- a/frontend/static/quotes/esperanto.json +++ b/frontend/static/quotes/esperanto.json @@ -9,19 +9,19 @@ "quotes": [ { "text": "La nun proponatan broŝuron la leganto kredeble prenos en la manojn kun malkonfido, kun antaŭe preta penso, ke al li estos proponata ia neefektivigebla utopio.", - "source": "La unua libro, L.Zamenof", + "source": "La unua libro, L. L. Zamenhof", "length": 158, "id": 1 }, { "text": "Mi devas tial antaŭ ĉio peti la leganton, ke li formetu tiun ĉi antaŭjuĝon kaj ke li pripensu serioze kaj kritike la proponatan aferon.", - "source": "La unua libro, L.Zamenof", + "source": "La unua libro, L. L. Zamenhof", "length": 135, "id": 2 }, { "text": "La demando pri lingvo internacia okupadis min jam longe; sed sentante min nek pli talenta, nek pli energia, ol la aŭtoroj de ĉiuj senfrukte pereintaj provoj, mi longan tempon limigadis min nur per revado kaj nevola meditado super tiu ĉi afero. Sed kelke da feliĉaj ideoj, kiuj aperis kiel frukto de tiu ĉi nevola meditado, kuraĝigis min por plua laborado kaj igis min ekprovi, ĉu ne prosperos al mi sisteme venki ĉiujn barojn por la kreo kaj enkonduko en uzadon de racia lingvo internacia.", - "source": "La unua libro, L.Zamenof", + "source": "La unua libro, L. L. Zamenhof", "length": 489, "id": 3 }, @@ -45,7 +45,7 @@ }, { "text": "Ke la lingvo estu eksterordinare facila, tiel ke oni povu ellerni ĝin ludante.", - "source": "La unua libro, L.Zamenof", + "source": "La unua libro, L. L. Zamenhof", "length": 78, "id": 7 }, @@ -90,6 +90,102 @@ "source": "La ŝtormo, John S. Dinwoodie", "length": 91, "id": 14 + }, + { + "text": "Sur la velŝipo Magdalena la matenmanĝo estas jena: bifsteko kun botelo plena. Hej! Hej! La vivo dura.", + "source": "La velŝipo Magdalena, Kajto", + "length": 101, + "id": 15 + }, + { + "text": "Ni hisu la velojn, ĉar blovas la vent'. Finfine ni foriru en ĝusta moment'. Ni kuraĝas forveli, esper' en la kor'. La ŝip' estas preta, ni iru nun for.", + "source": "Ni hisu la velojn, Kajto", + "length": 151, + "id": 16 + }, + { + "text": "La ord' en la vortaro plaĉas al mi. La sistematiko plenigas min per ĝuo. Tamen el la vortoj kiujn mi trovis en ĝi la plej bela estas tohuvabohuo.", + "source": "Tohuvabohuo, Kajto", + "length": 145, + "id": 17 + }, + { + "text": "En la mondon venis nova sento, tra la mondo iras forta voko; per flugiloj de facila vento nun de loko flugu ĝi al loko.", + "source": "La Espero, L. L. Zamenhof", + "length": 119, + "id": 18 + }, + { + "text": "Iun matenon vekiĝis mi. Ho belulin', belulin', belulin', ĝis, ĝis. Iun matenon vekiĝis mi, kaj tie estis malamik'.", + "source": "Ho belulin' ĝis", + "length": 114, + "id": 19 + }, + { + "text": "La glasojn dissplitu, fendiĝu la vazoj! Tranĉilojn difektu, kurbigu la forkojn! Ĉar Bilbon ĉagrenas tiaj okazoj - frakasu botelojn, brulumu la korkojn!", + "source": "La Hobito, J. R. R. Tolkien", + "length": 151, + "id": 20 + }, + { + "text": "Transmonten en nebulo frida, al kavenar' profunde sida, ni devas for je la tagaŭror', cele al pala or' sorĉita.", + "source": "La Hobito, J. R. R. Tolkien", + "length": 111, + "id": 21 + }, + { + "text": "Kio sen radikoj pli altas ol salikoj, supren-supren krestas sed neniam kreskas?", + "source": "La Hobito, J. R. R. Tolkien", + "length": 79, + "id": 22 + }, + { + "text": "Tridek ĉevaloj ruĝmonte sin trovas, unue krampas, poste stamfas, poste senmovas.", + "source": "La Hobito, J. R. R. Tolkien", + "length": 80, + "id": 23 + }, + { + "text": "Senvoĉe kiraĉas, sen-ale veturas, sendente ĝi maĉas, senbuŝe murmuras.", + "source": "La Hobito, J. R. R. Tolkien", + "length": 70, + "id": 24 + }, + { + "text": "Okulo sur blua vizaĝo vidis okulon sur verda vizaĝo. \"Similas l' okulon, l' okulo,\" diris l' una okulo, \"sed malaltaloke, ne altaloke\".", + "source": "La Hobito, J. R. R. Tolkien", + "length": 135, + "id": 25 + }, + { + "text": "Nevidebla, nepalpebla, neaŭdebla, neflarebla, malantaŭ steloj, submontete, plenigas truojn ĝi komplete, antaŭiras kaj postfinas, vivon, ridojn, mortdestinas.", + "source": "La Hobito, J. R. R. Tolkien", + "length": 157, + "id": 26 + }, + { + "text": "Sen kovro, sen ĉarniro la skatolo. Tamen enestas ĝin valora oro.", + "source": "La Hobito, J. R. R. Tolkien", + "length": 64, + "id": 27 + }, + { + "text": "Senspire vivkarna, mortece malvarma; nek soifas, nek trinkas, kirasite ne tintas.", + "source": "La Hobito, J. R. R. Tolkien", + "length": 81, + "id": 28 + }, + { + "text": "Senkruro sur unukruro restis, dukruroj sur trikruroj sidis, kvarkruroj kelkon ricevis.", + "source": "La Hobito, J. R. R. Tolkien", + "length": 86, + "id": 29 + }, + { + "text": "Ĉi tiu voras ĉiujn estojn: arbojn, florojn, birdojn, bestojn; maĉas feron, ŝtalon mordas; durajn rokojn piste tordas; reĝojn, urbojn, mortsvenigas, altajn montojn ebenigas.", + "source": "La Hobito, J. R. R. Tolkien", + "length": 172, + "id": 30 } ] } diff --git a/frontend/static/quotes/esperanto_x_sistemo.json b/frontend/static/quotes/esperanto_x_sistemo.json index f4d4caa5d0ef..0ede28f68ca8 100644 --- a/frontend/static/quotes/esperanto_x_sistemo.json +++ b/frontend/static/quotes/esperanto_x_sistemo.json @@ -9,19 +9,19 @@ "quotes": [ { "text": "La nun proponatan brosxuron la leganto kredeble prenos en la manojn kun malkonfido, kun antauxe preta penso, ke al li estos proponata ia neefektivigebla utopio.", - "source": "La unua libro, L.Zamenof", + "source": "La unua libro, L. L. Zamenhof", "length": 160, "id": 1 }, { "text": "Mi devas tial antaux cxio peti la leganton, ke li formetu tiun cxi antauxjugxon kaj ke li pripensu serioze kaj kritike la proponatan aferon.", - "source": "La unua libro, L.Zamenof", + "source": "La unua libro, L. L. Zamenhof", "length": 140, "id": 2 }, { "text": "La demando pri lingvo internacia okupadis min jam longe; sed sentante min nek pli talenta, nek pli energia, ol la auxtoroj de cxiuj senfrukte pereintaj provoj, mi longan tempon limigadis min nur per revado kaj nevola meditado super tiu cxi afero. Sed kelke da felicxaj ideoj, kiuj aperis kiel frukto de tiu cxi nevola meditado, kuragxigis min por plua laborado kaj igis min ekprovi, cxu ne prosperos al mi sisteme venki cxiujn barojn por la kreo kaj enkonduko en uzadon de racia lingvo internacia.", - "source": "La unua libro, L.Zamenof", + "source": "La unua libro, L. L. Zamenhof", "length": 497, "id": 3 }, @@ -45,7 +45,7 @@ }, { "text": "Ke la lingvo estu eksterordinare facila, tiel ke oni povu ellerni gxin ludante.", - "source": "La unua libro, L.Zamenof", + "source": "La unua libro, L. L. Zamenhof", "length": 79, "id": 7 }, @@ -90,6 +90,102 @@ "source": "La ŝtormo, John S. Dinwoodie", "length": 93, "id": 14 + }, + { + "text": "Sur la velsxipo Magdalena la matenmangxo estas jena: bifsteko kun botelo plena. Hej! Hej! La vivo dura.", + "source": "La velŝipo Magdalena, Kajto", + "length": 103, + "id": 15 + }, + { + "text": "Ni hisu la velojn, cxar blovas la vent'. Finfine ni foriru en gxusta moment'. Ni kuragxas forveli, esper' en la kor'. La sxip' estas preta, ni iru nun for.", + "source": "Ni hisu la velojn, Kajto", + "length": 155, + "id": 16 + }, + { + "text": "La ord' en la vortaro placxas al mi. La sistematiko plenigas min per gxuo. Tamen el la vortoj kiujn mi trovis en gxi la plej bela estas tohuvabohuo.", + "source": "Tohuvabohuo, Kajto", + "length": 148, + "id": 17 + }, + { + "text": "En la mondon venis nova sento, tra la mondo iras forta voko; per flugiloj de facila vento nun de loko flugu gxi al loko.", + "source": "La Espero, L. L. Zamenhof", + "length": 120, + "id": 18 + }, + { + "text": "Iun matenon vekigxis mi. Ho belulin', belulin', belulin', gxis, gxis. Iun matenon vekigxis mi, kaj tie estis malamik'.", + "source": "Ho belulin' ĝis", + "length": 118, + "id": 19 + }, + { + "text": "La glasojn dissplitu, fendigxu la vazoj! Trancxilojn difektu, kurbigu la forkojn! Cxar Bilbon cxagrenas tiaj okazoj - frakasu botelojn, brulumu la korkojn!", + "source": "La Hobito, J. R. R. Tolkien", + "length": 155, + "id": 20 + }, + { + "text": "Transmonten en nebulo frida, al kavenar' profunde sida, ni devas for je la tagauxror', cele al pala or' sorcxita.", + "source": "La Hobito, J. R. R. Tolkien", + "length": 113, + "id": 21 + }, + { + "text": "Kio sen radikoj pli altas ol salikoj, supren-supren krestas sed neniam kreskas?", + "source": "La Hobito, J. R. R. Tolkien", + "length": 79, + "id": 22 + }, + { + "text": "Tridek cxevaloj rugxmonte sin trovas, unue krampas, poste stamfas, poste senmovas.", + "source": "La Hobito, J. R. R. Tolkien", + "length": 82, + "id": 23 + }, + { + "text": "Senvocxe kiracxas, sen-ale veturas, sendente gxi macxas, senbusxe murmuras.", + "source": "La Hobito, J. R. R. Tolkien", + "length": 75, + "id": 24 + }, + { + "text": "Okulo sur blua vizagxo vidis okulon sur verda vizagxo. \"Similas l' okulon, l' okulo,\" diris l' una okulo, \"sed malaltaloke, ne altaloke\".", + "source": "La Hobito, J. R. R. Tolkien", + "length": 137, + "id": 25 + }, + { + "text": "Nevidebla, nepalpebla, neauxdebla, neflarebla, malantaux steloj, submontete, plenigas truojn gxi komplete, antauxiras kaj postfinas, vivon, ridojn, mortdestinas.", + "source": "La Hobito, J. R. R. Tolkien", + "length": 161, + "id": 26 + }, + { + "text": "Sen kovro, sen cxarniro la skatolo. Tamen enestas gxin valora oro.", + "source": "La Hobito, J. R. R. Tolkien", + "length": 66, + "id": 27 + }, + { + "text": "Senspire vivkarna, mortece malvarma; nek soifas, nek trinkas, kirasite ne tintas.", + "source": "La Hobito, J. R. R. Tolkien", + "length": 81, + "id": 28 + }, + { + "text": "Senkruro sur unukruro restis, dukruroj sur trikruroj sidis, kvarkruroj kelkon ricevis.", + "source": "La Hobito, J. R. R. Tolkien", + "length": 86, + "id": 29 + }, + { + "text": "Cxi tiu voras cxiujn estojn: arbojn, florojn, birdojn, bestojn; macxas feron, sxtalon mordas; durajn rokojn piste tordas; regxojn, urbojn, mortsvenigas, altajn montojn ebenigas.", + "source": "La Hobito, J. R. R. Tolkien", + "length": 177, + "id": 30 } ] } diff --git a/frontend/static/sound/click1/click1_1.wav b/frontend/static/sound/click1/click1_1.wav index 81b143d9d2fe..0aa332124fe8 100644 Binary files a/frontend/static/sound/click1/click1_1.wav and b/frontend/static/sound/click1/click1_1.wav differ diff --git a/frontend/static/sound/click1/click1_2.wav b/frontend/static/sound/click1/click1_2.wav index 7f1a263cf8d1..3539954d1bb8 100644 Binary files a/frontend/static/sound/click1/click1_2.wav and b/frontend/static/sound/click1/click1_2.wav differ diff --git a/frontend/static/sound/click1/click1_3.wav b/frontend/static/sound/click1/click1_3.wav index 5c96eb1a49c9..be6989c54e99 100644 Binary files a/frontend/static/sound/click1/click1_3.wav and b/frontend/static/sound/click1/click1_3.wav differ diff --git a/frontend/static/sound/click14/click14_1.wav b/frontend/static/sound/click14/click14_1.wav index 932a7db0e724..c25c19467298 100644 Binary files a/frontend/static/sound/click14/click14_1.wav and b/frontend/static/sound/click14/click14_1.wav differ diff --git a/frontend/static/sound/click14/click14_2.wav b/frontend/static/sound/click14/click14_2.wav index 1e3078caa061..3d89c5926ad4 100644 Binary files a/frontend/static/sound/click14/click14_2.wav and b/frontend/static/sound/click14/click14_2.wav differ diff --git a/frontend/static/sound/click14/click14_3.wav b/frontend/static/sound/click14/click14_3.wav index 3819f45cef49..f0b2a3ac90fe 100644 Binary files a/frontend/static/sound/click14/click14_3.wav and b/frontend/static/sound/click14/click14_3.wav differ diff --git a/frontend/static/sound/click14/click14_4.wav b/frontend/static/sound/click14/click14_4.wav index 15801257fcf9..e6fcba84c1d5 100644 Binary files a/frontend/static/sound/click14/click14_4.wav and b/frontend/static/sound/click14/click14_4.wav differ diff --git a/frontend/static/sound/click14/click14_5.wav b/frontend/static/sound/click14/click14_5.wav index 0fca1268df15..41c1986d30ad 100644 Binary files a/frontend/static/sound/click14/click14_5.wav and b/frontend/static/sound/click14/click14_5.wav differ diff --git a/frontend/static/sound/click14/click14_6.wav b/frontend/static/sound/click14/click14_6.wav index 64cc42a2dcd9..855f0400c808 100644 Binary files a/frontend/static/sound/click14/click14_6.wav and b/frontend/static/sound/click14/click14_6.wav differ diff --git a/frontend/static/sound/click14/click14_7.wav b/frontend/static/sound/click14/click14_7.wav index 4e743d211dc3..ad420aa4bab9 100644 Binary files a/frontend/static/sound/click14/click14_7.wav and b/frontend/static/sound/click14/click14_7.wav differ diff --git a/frontend/static/sound/click14/click14_8.wav b/frontend/static/sound/click14/click14_8.wav index a157f37af86e..e6713966200d 100644 Binary files a/frontend/static/sound/click14/click14_8.wav and b/frontend/static/sound/click14/click14_8.wav differ diff --git a/frontend/static/sound/click15/click15_1.wav b/frontend/static/sound/click15/click15_1.wav index 561d74a50e69..a6db198b1290 100644 Binary files a/frontend/static/sound/click15/click15_1.wav and b/frontend/static/sound/click15/click15_1.wav differ diff --git a/frontend/static/sound/click15/click15_2.wav b/frontend/static/sound/click15/click15_2.wav index d7db2b4f59a3..a34dd1ae06de 100644 Binary files a/frontend/static/sound/click15/click15_2.wav and b/frontend/static/sound/click15/click15_2.wav differ diff --git a/frontend/static/sound/click15/click15_3.wav b/frontend/static/sound/click15/click15_3.wav index 00f97c52e74d..fe0bde489a05 100644 Binary files a/frontend/static/sound/click15/click15_3.wav and b/frontend/static/sound/click15/click15_3.wav differ diff --git a/frontend/static/sound/click15/click15_4.wav b/frontend/static/sound/click15/click15_4.wav index 46aed9fb7e22..75a09d0517ff 100644 Binary files a/frontend/static/sound/click15/click15_4.wav and b/frontend/static/sound/click15/click15_4.wav differ diff --git a/frontend/static/sound/click15/click15_5.wav b/frontend/static/sound/click15/click15_5.wav index 35d2b48cdf06..dca02d035290 100644 Binary files a/frontend/static/sound/click15/click15_5.wav and b/frontend/static/sound/click15/click15_5.wav differ diff --git a/frontend/static/sound/click16/click16_1.wav b/frontend/static/sound/click16/click16_1.wav index 7acf355c3ce6..ecac6605c78d 100644 Binary files a/frontend/static/sound/click16/click16_1.wav and b/frontend/static/sound/click16/click16_1.wav differ diff --git a/frontend/static/sound/click16/click16_10.wav b/frontend/static/sound/click16/click16_10.wav index 3d3c1e496afc..23aed8d0ae55 100644 Binary files a/frontend/static/sound/click16/click16_10.wav and b/frontend/static/sound/click16/click16_10.wav differ diff --git a/frontend/static/sound/click16/click16_11.wav b/frontend/static/sound/click16/click16_11.wav index 95ce4f84c6dd..7bc0996cd505 100644 Binary files a/frontend/static/sound/click16/click16_11.wav and b/frontend/static/sound/click16/click16_11.wav differ diff --git a/frontend/static/sound/click16/click16_2.wav b/frontend/static/sound/click16/click16_2.wav index e93765a20e7f..dbbafd4e370c 100644 Binary files a/frontend/static/sound/click16/click16_2.wav and b/frontend/static/sound/click16/click16_2.wav differ diff --git a/frontend/static/sound/click16/click16_3.wav b/frontend/static/sound/click16/click16_3.wav index a39720fbd1d8..06f3f6582a9d 100644 Binary files a/frontend/static/sound/click16/click16_3.wav and b/frontend/static/sound/click16/click16_3.wav differ diff --git a/frontend/static/sound/click16/click16_4.wav b/frontend/static/sound/click16/click16_4.wav index b5cfed02fc0d..9143e9b57029 100644 Binary files a/frontend/static/sound/click16/click16_4.wav and b/frontend/static/sound/click16/click16_4.wav differ diff --git a/frontend/static/sound/click16/click16_5.wav b/frontend/static/sound/click16/click16_5.wav index 171b1d7993b4..71df277a5d18 100644 Binary files a/frontend/static/sound/click16/click16_5.wav and b/frontend/static/sound/click16/click16_5.wav differ diff --git a/frontend/static/sound/click16/click16_6.wav b/frontend/static/sound/click16/click16_6.wav index 62eeb100b807..4076a647942e 100644 Binary files a/frontend/static/sound/click16/click16_6.wav and b/frontend/static/sound/click16/click16_6.wav differ diff --git a/frontend/static/sound/click16/click16_7.wav b/frontend/static/sound/click16/click16_7.wav index 9ee853933e34..44919e80e80f 100644 Binary files a/frontend/static/sound/click16/click16_7.wav and b/frontend/static/sound/click16/click16_7.wav differ diff --git a/frontend/static/sound/click16/click16_8.wav b/frontend/static/sound/click16/click16_8.wav index e3cbc08a9623..54de28c7a0e5 100644 Binary files a/frontend/static/sound/click16/click16_8.wav and b/frontend/static/sound/click16/click16_8.wav differ diff --git a/frontend/static/sound/click16/click16_9.wav b/frontend/static/sound/click16/click16_9.wav index db7637ee5062..0678eac083cd 100644 Binary files a/frontend/static/sound/click16/click16_9.wav and b/frontend/static/sound/click16/click16_9.wav differ diff --git a/frontend/static/sound/click2/click2_1.wav b/frontend/static/sound/click2/click2_1.wav index cbf62ff5c48d..221a25137d32 100644 Binary files a/frontend/static/sound/click2/click2_1.wav and b/frontend/static/sound/click2/click2_1.wav differ diff --git a/frontend/static/sound/click2/click2_2.wav b/frontend/static/sound/click2/click2_2.wav index c602c662c271..45d929feccd6 100644 Binary files a/frontend/static/sound/click2/click2_2.wav and b/frontend/static/sound/click2/click2_2.wav differ diff --git a/frontend/static/sound/click2/click2_3.wav b/frontend/static/sound/click2/click2_3.wav index 20937d76091e..a08a2cf071c6 100644 Binary files a/frontend/static/sound/click2/click2_3.wav and b/frontend/static/sound/click2/click2_3.wav differ diff --git a/frontend/static/sound/click3/click3_1.wav b/frontend/static/sound/click3/click3_1.wav index 9e87093c3bcd..d9df93e87676 100644 Binary files a/frontend/static/sound/click3/click3_1.wav and b/frontend/static/sound/click3/click3_1.wav differ diff --git a/frontend/static/sound/click3/click3_2.wav b/frontend/static/sound/click3/click3_2.wav index d3804a7d3d9e..7217a1be7749 100644 Binary files a/frontend/static/sound/click3/click3_2.wav and b/frontend/static/sound/click3/click3_2.wav differ diff --git a/frontend/static/sound/click3/click3_3.wav b/frontend/static/sound/click3/click3_3.wav index 528c7f721340..e46c6b7a5963 100644 Binary files a/frontend/static/sound/click3/click3_3.wav and b/frontend/static/sound/click3/click3_3.wav differ diff --git a/frontend/static/sound/click4/click4_1.wav b/frontend/static/sound/click4/click4_1.wav index add537834d1f..b03acd4db372 100644 Binary files a/frontend/static/sound/click4/click4_1.wav and b/frontend/static/sound/click4/click4_1.wav differ diff --git a/frontend/static/sound/click4/click4_11.wav b/frontend/static/sound/click4/click4_11.wav index add537834d1f..b03acd4db372 100644 Binary files a/frontend/static/sound/click4/click4_11.wav and b/frontend/static/sound/click4/click4_11.wav differ diff --git a/frontend/static/sound/click4/click4_2.wav b/frontend/static/sound/click4/click4_2.wav index d64e2ce6e817..e8af2f633575 100644 Binary files a/frontend/static/sound/click4/click4_2.wav and b/frontend/static/sound/click4/click4_2.wav differ diff --git a/frontend/static/sound/click4/click4_22.wav b/frontend/static/sound/click4/click4_22.wav index d64e2ce6e817..e8af2f633575 100644 Binary files a/frontend/static/sound/click4/click4_22.wav and b/frontend/static/sound/click4/click4_22.wav differ diff --git a/frontend/static/sound/click4/click4_3.wav b/frontend/static/sound/click4/click4_3.wav index dff4adadbc65..604449d5df77 100644 Binary files a/frontend/static/sound/click4/click4_3.wav and b/frontend/static/sound/click4/click4_3.wav differ diff --git a/frontend/static/sound/click4/click4_33.wav b/frontend/static/sound/click4/click4_33.wav index dff4adadbc65..604449d5df77 100644 Binary files a/frontend/static/sound/click4/click4_33.wav and b/frontend/static/sound/click4/click4_33.wav differ diff --git a/frontend/static/sound/click4/click4_4.wav b/frontend/static/sound/click4/click4_4.wav index 9a5e4d43ff54..b2939d493ef3 100644 Binary files a/frontend/static/sound/click4/click4_4.wav and b/frontend/static/sound/click4/click4_4.wav differ diff --git a/frontend/static/sound/click4/click4_44.wav b/frontend/static/sound/click4/click4_44.wav index 9a5e4d43ff54..b2939d493ef3 100644 Binary files a/frontend/static/sound/click4/click4_44.wav and b/frontend/static/sound/click4/click4_44.wav differ diff --git a/frontend/static/sound/click4/click4_5.wav b/frontend/static/sound/click4/click4_5.wav index ca8bc9070cf1..145976c7c6c3 100644 Binary files a/frontend/static/sound/click4/click4_5.wav and b/frontend/static/sound/click4/click4_5.wav differ diff --git a/frontend/static/sound/click4/click4_55.wav b/frontend/static/sound/click4/click4_55.wav index ca8bc9070cf1..145976c7c6c3 100644 Binary files a/frontend/static/sound/click4/click4_55.wav and b/frontend/static/sound/click4/click4_55.wav differ diff --git a/frontend/static/sound/click4/click4_6.wav b/frontend/static/sound/click4/click4_6.wav index acdaf68e0d6f..ec0d5379ad15 100644 Binary files a/frontend/static/sound/click4/click4_6.wav and b/frontend/static/sound/click4/click4_6.wav differ diff --git a/frontend/static/sound/click4/click4_66.wav b/frontend/static/sound/click4/click4_66.wav index acdaf68e0d6f..ec0d5379ad15 100644 Binary files a/frontend/static/sound/click4/click4_66.wav and b/frontend/static/sound/click4/click4_66.wav differ diff --git a/frontend/static/sound/click5/click5_1.wav b/frontend/static/sound/click5/click5_1.wav index 91ba195243f5..5051ae1bec1f 100644 Binary files a/frontend/static/sound/click5/click5_1.wav and b/frontend/static/sound/click5/click5_1.wav differ diff --git a/frontend/static/sound/click5/click5_11.wav b/frontend/static/sound/click5/click5_11.wav index 91ba195243f5..5051ae1bec1f 100644 Binary files a/frontend/static/sound/click5/click5_11.wav and b/frontend/static/sound/click5/click5_11.wav differ diff --git a/frontend/static/sound/click5/click5_2.wav b/frontend/static/sound/click5/click5_2.wav index f442d0a45d90..a34e3025c280 100644 Binary files a/frontend/static/sound/click5/click5_2.wav and b/frontend/static/sound/click5/click5_2.wav differ diff --git a/frontend/static/sound/click5/click5_22.wav b/frontend/static/sound/click5/click5_22.wav index f442d0a45d90..a34e3025c280 100644 Binary files a/frontend/static/sound/click5/click5_22.wav and b/frontend/static/sound/click5/click5_22.wav differ diff --git a/frontend/static/sound/click5/click5_3.wav b/frontend/static/sound/click5/click5_3.wav index e2cf938d6cd2..e365207f495e 100644 Binary files a/frontend/static/sound/click5/click5_3.wav and b/frontend/static/sound/click5/click5_3.wav differ diff --git a/frontend/static/sound/click5/click5_33.wav b/frontend/static/sound/click5/click5_33.wav index e2cf938d6cd2..e365207f495e 100644 Binary files a/frontend/static/sound/click5/click5_33.wav and b/frontend/static/sound/click5/click5_33.wav differ diff --git a/frontend/static/sound/click5/click5_4.wav b/frontend/static/sound/click5/click5_4.wav index e105d33b9535..a6536ff9ee01 100644 Binary files a/frontend/static/sound/click5/click5_4.wav and b/frontend/static/sound/click5/click5_4.wav differ diff --git a/frontend/static/sound/click5/click5_44.wav b/frontend/static/sound/click5/click5_44.wav index e105d33b9535..a6536ff9ee01 100644 Binary files a/frontend/static/sound/click5/click5_44.wav and b/frontend/static/sound/click5/click5_44.wav differ diff --git a/frontend/static/sound/click5/click5_5.wav b/frontend/static/sound/click5/click5_5.wav index 9ce9944dfb1d..7182d01b7d71 100644 Binary files a/frontend/static/sound/click5/click5_5.wav and b/frontend/static/sound/click5/click5_5.wav differ diff --git a/frontend/static/sound/click5/click5_55.wav b/frontend/static/sound/click5/click5_55.wav index 9ce9944dfb1d..7182d01b7d71 100644 Binary files a/frontend/static/sound/click5/click5_55.wav and b/frontend/static/sound/click5/click5_55.wav differ diff --git a/frontend/static/sound/click5/click5_6.wav b/frontend/static/sound/click5/click5_6.wav index 4fce1efe14d8..60a2b3a13906 100644 Binary files a/frontend/static/sound/click5/click5_6.wav and b/frontend/static/sound/click5/click5_6.wav differ diff --git a/frontend/static/sound/click5/click5_66.wav b/frontend/static/sound/click5/click5_66.wav index 4fce1efe14d8..60a2b3a13906 100644 Binary files a/frontend/static/sound/click5/click5_66.wav and b/frontend/static/sound/click5/click5_66.wav differ diff --git a/frontend/static/sound/click6/click6_1.wav b/frontend/static/sound/click6/click6_1.wav index 31da545593a7..aacf41a62b3d 100644 Binary files a/frontend/static/sound/click6/click6_1.wav and b/frontend/static/sound/click6/click6_1.wav differ diff --git a/frontend/static/sound/click6/click6_11.wav b/frontend/static/sound/click6/click6_11.wav index 31da545593a7..aacf41a62b3d 100644 Binary files a/frontend/static/sound/click6/click6_11.wav and b/frontend/static/sound/click6/click6_11.wav differ diff --git a/frontend/static/sound/click6/click6_2.wav b/frontend/static/sound/click6/click6_2.wav index 31da545593a7..aacf41a62b3d 100644 Binary files a/frontend/static/sound/click6/click6_2.wav and b/frontend/static/sound/click6/click6_2.wav differ diff --git a/frontend/static/sound/click6/click6_22.wav b/frontend/static/sound/click6/click6_22.wav index 31da545593a7..aacf41a62b3d 100644 Binary files a/frontend/static/sound/click6/click6_22.wav and b/frontend/static/sound/click6/click6_22.wav differ diff --git a/frontend/static/sound/click6/click6_3.wav b/frontend/static/sound/click6/click6_3.wav index 31da545593a7..aacf41a62b3d 100644 Binary files a/frontend/static/sound/click6/click6_3.wav and b/frontend/static/sound/click6/click6_3.wav differ diff --git a/frontend/static/sound/click6/click6_33.wav b/frontend/static/sound/click6/click6_33.wav index 31da545593a7..aacf41a62b3d 100644 Binary files a/frontend/static/sound/click6/click6_33.wav and b/frontend/static/sound/click6/click6_33.wav differ diff --git a/frontend/static/sound/click7/click7_1.wav b/frontend/static/sound/click7/click7_1.wav index ed6f99cdde00..1521655b95b9 100644 Binary files a/frontend/static/sound/click7/click7_1.wav and b/frontend/static/sound/click7/click7_1.wav differ diff --git a/frontend/static/sound/click7/click7_11.wav b/frontend/static/sound/click7/click7_11.wav index ed6f99cdde00..1521655b95b9 100644 Binary files a/frontend/static/sound/click7/click7_11.wav and b/frontend/static/sound/click7/click7_11.wav differ diff --git a/frontend/static/sound/click7/click7_2.wav b/frontend/static/sound/click7/click7_2.wav index ed6f99cdde00..1521655b95b9 100644 Binary files a/frontend/static/sound/click7/click7_2.wav and b/frontend/static/sound/click7/click7_2.wav differ diff --git a/frontend/static/sound/click7/click7_22.wav b/frontend/static/sound/click7/click7_22.wav index ed6f99cdde00..1521655b95b9 100644 Binary files a/frontend/static/sound/click7/click7_22.wav and b/frontend/static/sound/click7/click7_22.wav differ diff --git a/frontend/static/sound/click7/click7_3.wav b/frontend/static/sound/click7/click7_3.wav index ed6f99cdde00..1521655b95b9 100644 Binary files a/frontend/static/sound/click7/click7_3.wav and b/frontend/static/sound/click7/click7_3.wav differ diff --git a/frontend/static/sound/click7/click7_33.wav b/frontend/static/sound/click7/click7_33.wav index ed6f99cdde00..1521655b95b9 100644 Binary files a/frontend/static/sound/click7/click7_33.wav and b/frontend/static/sound/click7/click7_33.wav differ diff --git a/frontend/static/sound/error1/error1_1.wav b/frontend/static/sound/error1/error1_1.wav index 00556e829093..3479db58a1ad 100644 Binary files a/frontend/static/sound/error1/error1_1.wav and b/frontend/static/sound/error1/error1_1.wav differ diff --git a/frontend/static/sound/error2/error2_1.wav b/frontend/static/sound/error2/error2_1.wav index bfe123845686..2414d2bd6cd8 100644 Binary files a/frontend/static/sound/error2/error2_1.wav and b/frontend/static/sound/error2/error2_1.wav differ diff --git a/frontend/static/sound/error3/error3_1.wav b/frontend/static/sound/error3/error3_1.wav index 382d7ee34f2b..00f11691be44 100644 Binary files a/frontend/static/sound/error3/error3_1.wav and b/frontend/static/sound/error3/error3_1.wav differ diff --git a/frontend/static/sound/error4/error4_1.wav b/frontend/static/sound/error4/error4_1.wav index 40ac203a30cb..fa554501e326 100644 Binary files a/frontend/static/sound/error4/error4_1.wav and b/frontend/static/sound/error4/error4_1.wav differ diff --git a/frontend/static/sound/error4/error4_2.wav b/frontend/static/sound/error4/error4_2.wav index 21c23792c419..d9025157da81 100644 Binary files a/frontend/static/sound/error4/error4_2.wav and b/frontend/static/sound/error4/error4_2.wav differ diff --git a/frontend/static/sound/fart-reverb.wav b/frontend/static/sound/fart-reverb.wav index c3288f449242..4727dc6acd0c 100644 Binary files a/frontend/static/sound/fart-reverb.wav and b/frontend/static/sound/fart-reverb.wav differ diff --git a/frontend/static/sound/timeWarning.wav b/frontend/static/sound/timeWarning.wav index 85c2727111a3..b5b8df5235e8 100644 Binary files a/frontend/static/sound/timeWarning.wav and b/frontend/static/sound/timeWarning.wav differ diff --git a/packages/release/src/index.js b/packages/release/src/index.js index f03c721068d5..db9b51604acc 100755 --- a/packages/release/src/index.js +++ b/packages/release/src/index.js @@ -1,5 +1,3 @@ -// idk why its failing to resolve -// oxlint-disable-next-line import/no-unresolved import { Octokit } from "@octokit/rest"; import { execSync } from "child_process"; import dotenv from "dotenv"; @@ -263,14 +261,20 @@ const main = async () => { const name = readlineSync.question( "Enter preview channel name (default: preview): ", ); - // oxlint-disable-next-line prefer-nullish-coalescing - const channelName = name.trim() || "preview"; + let channelName = name.trim(); + + if (channelName === "") { + channelName = "preview"; + } const expirationTime = readlineSync.question( "Enter expiration time (e.g., 2h, default: 1d): ", ); - // oxlint-disable-next-line prefer-nullish-coalescing - const expires = expirationTime.trim() || "1d"; + let expires = expirationTime.trim(); + + if (expires === "") { + expires = "1d"; + } console.log( `Deploying frontend preview to channel "${channelName}" with expiration "${expires}"...`, diff --git a/packages/schemas/src/languages.ts b/packages/schemas/src/languages.ts index 0df0c35aca3b..b22698584a5a 100644 --- a/packages/schemas/src/languages.ts +++ b/packages/schemas/src/languages.ts @@ -123,6 +123,7 @@ export const LanguageSchema = z.enum( "turkish_1k", "turkish_5k", "irish", + "irish_1k", "italian", "italian_1k", "italian_7k", diff --git a/packages/schemas/src/layouts.ts b/packages/schemas/src/layouts.ts index 50e637f0ae43..d738846cebe9 100644 --- a/packages/schemas/src/layouts.ts +++ b/packages/schemas/src/layouts.ts @@ -237,6 +237,8 @@ export const LayoutNameSchema = z.enum( "tamil99", "Gralmak", "GralmakS", + "vitrimak", + "miligram", ], { errorMap: customEnumErrorHandler("Must be a supported layout"), diff --git a/packages/schemas/src/users.ts b/packages/schemas/src/users.ts index 9247f7090f50..e592c1bea026 100644 --- a/packages/schemas/src/users.ts +++ b/packages/schemas/src/users.ts @@ -260,7 +260,7 @@ export const UserSchema = z.object({ uid: z.string(), //defined by firebase, no validation should be applied addedAt: z.number().int().nonnegative(), personalBests: PersonalBestsSchema, - lastReultHashes: z.array(z.string()).optional(), //todo: fix typo (its in the db too) + lastReultHashes: z.array(z.string()).optional(), //TODO: fix typo (it's in the db too) completedTests: z.number().int().nonnegative().optional(), startedTests: z.number().int().nonnegative().optional(), timeTyping: z diff --git a/packages/tsup-config/src/index.ts b/packages/tsup-config/src/index.ts index bd9739e859c8..5a07da31a725 100644 --- a/packages/tsup-config/src/index.ts +++ b/packages/tsup-config/src/index.ts @@ -2,9 +2,7 @@ import { defineConfig, Options } from "tsup"; export function extendConfig( customizer?: (options: Options) => Options, - // tsup uses MaybePromise which is not exported - // oxlint-disable-next-line no-explicit-any -): (options: Options) => any { +): (options: Options) => unknown { return (options) => { const overrideOptions = customizer?.(options); const config: Options = { diff --git a/packages/util/src/json.ts b/packages/util/src/json.ts index 8f2dae029ab0..45db6b250172 100644 --- a/packages/util/src/json.ts +++ b/packages/util/src/json.ts @@ -37,9 +37,7 @@ export function parseWithSchema( if (fallback === undefined) { throw new Error(`Invalid JSON: ` + error.message); } - // todo fix me - // oxlint-disable-next-line no-unsafe-return - return fallback as z.infer; + return fallback as unknown; } const safeParse = schema.safeParse(jsonParsed); @@ -66,9 +64,7 @@ export function parseWithSchema( .join(", ")}`, ); } - // todo fix me - // oxlint-disable-next-line no-unsafe-return - return fallback as z.infer; + return fallback as unknown; } return safeParseMigrated.data as T; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 198be40489a7..05e1f32de689 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -224,7 +224,7 @@ importers: version: 10.0.0 '@vitest/coverage-v8': specifier: 4.0.15 - version: 4.0.15(vitest@4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1)) + version: 4.0.15(vitest@4.0.15(@types/node@24.9.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1)) concurrently: specifier: 8.2.2 version: 8.2.2 @@ -531,7 +531,7 @@ importers: version: 2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1)) vitest: specifier: 4.0.15 - version: 4.0.15(@types/node@24.9.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1) + version: 4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1) packages/contracts: dependencies: @@ -4365,6 +4365,7 @@ packages: basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} + deprecated: Security vulnerability fixed in 5.2.0, please upgrade bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} @@ -6032,25 +6033,28 @@ packages: glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@11.0.3: resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} engines: {node: 20 || >=22} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@9.3.5: resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} engines: {node: '>=16 || 14 >=14.17'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-dirs@0.1.1: resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} @@ -13766,23 +13770,6 @@ snapshots: '@typescript-eslint/types': 8.52.0 eslint-visitor-keys: 4.2.1 - '@vitest/coverage-v8@4.0.15(vitest@4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1))': - dependencies: - '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.0.15 - ast-v8-to-istanbul: 0.3.8 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.2.0 - magicast: 0.5.1 - obug: 2.1.1 - std-env: 3.10.0 - tinyrainbow: 3.0.3 - vitest: 4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1) - transitivePeerDependencies: - - supports-color - '@vitest/coverage-v8@4.0.15(vitest@4.0.15(@types/node@20.5.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1))': dependencies: '@bcoe/v8-coverage': 1.0.2 @@ -13813,7 +13800,7 @@ snapshots: obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.15(@types/node@24.9.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1) + vitest: 4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -13834,14 +13821,6 @@ snapshots: optionalDependencies: vite: 7.1.12(@types/node@20.5.1)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1) - '@vitest/mocker@4.0.15(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1))': - dependencies: - '@vitest/spy': 4.0.15 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1) - '@vitest/pretty-format@4.0.15': dependencies: tinyrainbow: 3.0.3 @@ -20901,7 +20880,7 @@ snapshots: vitest@4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1): dependencies: '@vitest/expect': 4.0.15 - '@vitest/mocker': 4.0.15(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1)) + '@vitest/mocker': 4.0.15(vite@7.1.12(@types/node@20.5.1)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1)) '@vitest/pretty-format': 4.0.15 '@vitest/runner': 4.0.15 '@vitest/snapshot': 4.0.15 @@ -20977,45 +20956,6 @@ snapshots: - tsx - yaml - vitest@4.0.15(@types/node@24.9.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1): - dependencies: - '@vitest/expect': 4.0.15 - '@vitest/mocker': 4.0.15(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1)) - '@vitest/pretty-format': 4.0.15 - '@vitest/runner': 4.0.15 - '@vitest/snapshot': 4.0.15 - '@vitest/spy': 4.0.15 - '@vitest/utils': 4.0.15 - es-module-lexer: 1.7.0 - expect-type: 1.2.2 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vite: 7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.70.0)(terser@5.46.0)(tsx@4.16.2)(yaml@2.8.1) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 24.9.1 - happy-dom: 20.0.10 - jsdom: 27.4.0 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml - vlq@0.2.3: {} w3c-xmlserializer@5.0.0: diff --git a/vitest.config.ts b/vitest.config.ts index 563c205b2b6d..88a31c7e23bd 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -47,7 +47,6 @@ function convertTests( function copySolidPlugin(config: UserWorkspaceConfig): void { if (!config.plugins) return; config.plugins - //@ts-expect-error this is fine - .filter((it) => it["name"] === "solid") + .filter((it) => it?.["name"] === "solid") .forEach((it) => globalPlugins.push(it)); }