From be106b8f1c7f72781a36b2d191cfc749abb0060f Mon Sep 17 00:00:00 2001 From: Miodec Date: Wed, 15 Oct 2025 12:48:52 +0200 Subject: [PATCH 1/3] fix: local typing stats not updated on result save closes #7016 --- frontend/src/ts/db.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/src/ts/db.ts b/frontend/src/ts/db.ts index dff0d111fa20..06895c87d6de 100644 --- a/frontend/src/ts/db.ts +++ b/frontend/src/ts/db.ts @@ -951,16 +951,16 @@ export function saveLocalResult(data: SaveLocalResultData): void { startedTests: 0, completedTests: 0, }; + } - const time = - data.result.testDuration + - data.result.incompleteTestSeconds - - data.result.afkDuration; + const time = + data.result.testDuration + + data.result.incompleteTestSeconds - + data.result.afkDuration; - snapshot.typingStats.timeTyping += time; - snapshot.typingStats.startedTests += data.result.restartCount + 1; - snapshot.typingStats.completedTests += 1; - } + snapshot.typingStats.timeTyping += time; + snapshot.typingStats.startedTests += data.result.restartCount + 1; + snapshot.typingStats.completedTests += 1; if (data.isPb) { saveLocalPB( From 015252120fde771944006072d945923175bfcaee Mon Sep 17 00:00:00 2001 From: Miodec Date: Wed, 15 Oct 2025 12:54:43 +0200 Subject: [PATCH 2/3] impr(british english): replace double quotes with single quotes closes #7015 --- .../__tests__/test/british-english.spec.ts | 25 +++++++++++++++++++ frontend/src/ts/test/british-english.ts | 5 ++++ 2 files changed, 30 insertions(+) diff --git a/frontend/__tests__/test/british-english.spec.ts b/frontend/__tests__/test/british-english.spec.ts index c8ec165df6bb..edf4ab6a144d 100644 --- a/frontend/__tests__/test/british-english.spec.ts +++ b/frontend/__tests__/test/british-english.spec.ts @@ -38,5 +38,30 @@ describe("british-english", () => { "armour-flavouring" ); }); + + it("should convert double quotes to single quotes", async () => { + await expect(replace('"hello"', "")).resolves.toEqual("'hello'"); + await expect(replace('"test"', "")).resolves.toEqual("'test'"); + await expect(replace('"Hello World"', "")).resolves.toEqual( + "'Hello World'" + ); + }); + + it("should convert double quotes and replace words", async () => { + await expect(replace('"color"', "")).resolves.toEqual("'colour'"); + await expect(replace('"math"', "")).resolves.toEqual("'maths'"); + await expect(replace('"Color"', "")).resolves.toEqual("'Colour'"); + }); + + it("should handle multiple double quotes in a word", async () => { + await expect( + replace('He said "hello" and "goodbye"', "") + ).resolves.toEqual("He said 'hello' and 'goodbye'"); + }); + + it("should not affect words without double quotes", async () => { + await expect(replace("'hello'", "")).resolves.toEqual("'hello'"); + await expect(replace("test", "")).resolves.toEqual("test"); + }); }); }); diff --git a/frontend/src/ts/test/british-english.ts b/frontend/src/ts/test/british-english.ts index b926a8cce084..8b2703155568 100644 --- a/frontend/src/ts/test/british-english.ts +++ b/frontend/src/ts/test/british-english.ts @@ -658,6 +658,11 @@ export async function replace( word: string, previousWord: string ): Promise { + // Convert American-style double quotes to British-style single quotes + if (word.includes('"')) { + word = word.replace(/"/g, "'"); + } + if (word.includes("-")) { //this handles hyphenated words (for example "cream-colored") to make sure //we don't have to add every possible combination to the list From d9d375d05a14af3eeccacc51d95b3bb476b4b07b Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Wed, 15 Oct 2025 13:07:57 +0200 Subject: [PATCH 3/3] fix: discord avatars not loading (@fehmer) (#6999) fixes #6998 Seems it was fixed by discord in the meantime. Still I think this is a better approach because it only takes one rest call to the discord cdn instead of two for each avatar. --------- Co-authored-by: Miodec --- frontend/src/styles/core.scss | 12 +++- frontend/src/ts/index.ts | 9 +-- frontend/src/ts/utils/discord-avatar.ts | 89 ++++++++++--------------- frontend/src/ts/utils/misc.ts | 7 ++ 4 files changed, 54 insertions(+), 63 deletions(-) diff --git a/frontend/src/styles/core.scss b/frontend/src/styles/core.scss index ec0f9aaac13d..455732ea3930 100644 --- a/frontend/src/styles/core.scss +++ b/frontend/src/styles/core.scss @@ -350,14 +350,20 @@ key { line-height: 1em; font-size: var(--size); border-radius: 100%; - background-position: center center; - background-size: contain; - background-repeat: no-repeat; grid-column: 1/2; grid-row: 1/2; place-self: center center; display: grid; place-content: center center; + overflow: hidden; + display: inline-block; + z-index: 1; //place above the loading spinner + img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + } } .loading { diff --git a/frontend/src/ts/index.ts b/frontend/src/ts/index.ts index 78f78a230130..67d4fc521b22 100644 --- a/frontend/src/ts/index.ts +++ b/frontend/src/ts/index.ts @@ -42,7 +42,7 @@ import "./states/connection"; import "./test/tts"; import "./elements/fps-counter"; import "./controllers/profile-search-controller"; -import { isDevEnvironment } from "./utils/misc"; +import { isDevEnvironment, addToGlobal } from "./utils/misc"; import * as VersionButton from "./elements/version-button"; import * as Focus from "./test/focus"; import { getDevOptionsModal } from "./utils/async-modules"; @@ -71,13 +71,6 @@ Object.defineProperty(window, "Math", { enumerable: true, }); -function addToGlobal(items: Record): void { - for (const [name, item] of Object.entries(items)) { - //@ts-expect-error dev - window[name] = item; - } -} - void loadFromLocalStorage(); void VersionButton.update(); Focus.set(true, true); diff --git a/frontend/src/ts/utils/discord-avatar.ts b/frontend/src/ts/utils/discord-avatar.ts index 9d1d108065d8..3748846fc5fe 100644 --- a/frontend/src/ts/utils/discord-avatar.ts +++ b/frontend/src/ts/utils/discord-avatar.ts @@ -1,26 +1,41 @@ -const cachedAvatarUrlByAvatarId: Map = new Map(); - type Options = { size?: number; userIcon?: string }; -function buildElement( - url: string | null, - options?: { loading?: boolean } & Options -): HTMLElement { +const knownBadUrls = new Set(); + +function buildElement(url: string | null, options?: Options): HTMLElement { const avatar = document.createElement("div"); avatar.classList.add("avatar"); - if (url === null) { - if (options?.loading) { - avatar.innerHTML = `
`; - } else { - avatar.innerHTML = `
`; - } + + if (url === null || knownBadUrls.has(url)) { + avatar.innerHTML = `
`; } else { - avatar.innerHTML = `
`; + const loading = document.createElement("div"); + loading.className = "loading"; + loading.innerHTML = ''; + + const imageContainer = document.createElement("div"); + imageContainer.className = "discordImage"; + + const img = document.createElement("img"); + img.src = `${url}?size=${options?.size ?? 32}`; + + // Add event listeners directly to the img element + img.addEventListener("load", async () => { + loading.remove(); + }); + + img.addEventListener("error", () => { + knownBadUrls.add(url); + avatar.replaceWith(buildElement(null, options)); + }); + + imageContainer.appendChild(img); + avatar.appendChild(loading); + avatar.appendChild(imageContainer); } + return avatar; } @@ -43,40 +58,10 @@ export function getAvatarElement( return buildElement(null, options); } - const cachedUrl = cachedAvatarUrlByAvatarId.get(discordAvatar); - - if (cachedUrl !== undefined) { - return buildElement(cachedUrl, options); - } else { - const element = buildElement(null, { loading: true }); - - void getDiscordAvatarUrl({ discordId, discordAvatar }).then((url) => { - cachedAvatarUrlByAvatarId.set(discordAvatar, url); - element.replaceWith(buildElement(url, options)); - }); - - return element; - } -} - -async function getDiscordAvatarUrl({ - discordId, - discordAvatar, -}: { - discordId: string; - discordAvatar: string; -}): Promise { - // An invalid request to this URL will return a 404. - try { - const avatarUrl = `https://cdn.discordapp.com/avatars/${discordId}/${discordAvatar}.png`; - - const response = await fetch(avatarUrl, { method: "HEAD" }); - if (!response.ok) { - return null; - } - - return avatarUrl; - } catch (error) {} + const element = buildElement( + `https://cdn.discordapp.com/avatars/${discordId}/${discordAvatar}.png`, + options + ); - return null; + return element; } diff --git a/frontend/src/ts/utils/misc.ts b/frontend/src/ts/utils/misc.ts index 5920e7694d2a..5a41ca8a126d 100644 --- a/frontend/src/ts/utils/misc.ts +++ b/frontend/src/ts/utils/misc.ts @@ -761,4 +761,11 @@ export function scrollToCenterOrTop(el: HTMLElement | null): void { }); } +export function addToGlobal(items: Record): void { + for (const [name, item] of Object.entries(items)) { + //@ts-expect-error dev + window[name] = item; + } +} + // DO NOT ALTER GLOBAL OBJECTSONSTRUCTOR, IT WILL BREAK RESULT HASHES