From 13c26e6c054db84ac83e1073cdd74b87e7f1e87e Mon Sep 17 00:00:00 2001 From: Tristan Marion Date: Fri, 21 Mar 2025 14:14:30 +0100 Subject: [PATCH 01/10] feat: add new property in ResultFiltersSchema to contain the tags filter mode --- .../src/ts/constants/default-result-filters.ts | 1 + packages/contracts/src/schemas/users.ts | 15 ++++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/frontend/src/ts/constants/default-result-filters.ts b/frontend/src/ts/constants/default-result-filters.ts index 63560d5de88e..78ab058e844b 100644 --- a/frontend/src/ts/constants/default-result-filters.ts +++ b/frontend/src/ts/constants/default-result-filters.ts @@ -58,6 +58,7 @@ const object: ResultFilters = { tags: { none: true, }, + tagsFilterMode: "or", language: {}, funbox: { none: true, diff --git a/packages/contracts/src/schemas/users.ts b/packages/contracts/src/schemas/users.ts index d1f986fd48bf..c1d51fa1b266 100644 --- a/packages/contracts/src/schemas/users.ts +++ b/packages/contracts/src/schemas/users.ts @@ -1,16 +1,16 @@ import { z, ZodEffects, ZodOptional, ZodString } from "zod"; -import { IdSchema, LanguageSchema, StringNumberSchema } from "./util"; +import { doesNotContainProfanity } from "../validation/validation"; +import { CustomThemeColorsSchema } from "./configs"; import { - ModeSchema, + DefaultTimeModeSchema, + DefaultWordsModeSchema, + DifficultySchema, Mode2Schema, + ModeSchema, PersonalBestsSchema, - DefaultWordsModeSchema, - DefaultTimeModeSchema, QuoteLengthSchema, - DifficultySchema, } from "./shared"; -import { CustomThemeColorsSchema } from "./configs"; -import { doesNotContainProfanity } from "../validation/validation"; +import { IdSchema, LanguageSchema, StringNumberSchema } from "./util"; export const ResultFiltersSchema = z.object({ _id: IdSchema, @@ -51,6 +51,7 @@ export const ResultFiltersSchema = z.object({ }) .strict(), tags: z.record(z.string(), z.boolean()), + tagsFilterMode: z.enum(["and", "or"]).default("or"), language: z.record(LanguageSchema, z.boolean()), funbox: z.record(z.string(), z.boolean()), }); From 24ab797a95e4e1f0ac5032fe6b63ebfc0fe15343 Mon Sep 17 00:00:00 2001 From: Tristan Marion Date: Fri, 21 Mar 2025 14:17:21 +0100 Subject: [PATCH 02/10] feat: display filter mode next to tags title in filters --- frontend/src/html/pages/account.html | 9 +++++++++ frontend/src/styles/account.scss | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/frontend/src/html/pages/account.html b/frontend/src/html/pages/account.html index ef6d16b85962..ca942cd83cf1 100644 --- a/frontend/src/html/pages/account.html +++ b/frontend/src/html/pages/account.html @@ -496,6 +496,15 @@
tags + + + OR +
diff --git a/frontend/src/styles/account.scss b/frontend/src/styles/account.scss index 77892551cb86..7f83052d213b 100644 --- a/frontend/src/styles/account.scss +++ b/frontend/src/styles/account.scss @@ -375,6 +375,26 @@ margin-right: 0.5em; } } + + &.tags { + .title { + align-items: baseline; + + .tagsFilterModeToggle { + margin-left: 0.5rem; + cursor: pointer; + opacity: 0.5; + transition: 0.125s; + display: flex; + align-items: center; + font-size: 0.8em; + + &:hover { + opacity: 1; + } + } + } + } } &.presetFilterButtons { From 5ca9d9f7692c1a5e9a61c6f6cd662721df16c295 Mon Sep 17 00:00:00 2001 From: Tristan Marion Date: Fri, 21 Mar 2025 14:37:21 +0100 Subject: [PATCH 03/10] feat: toggle filter mode on click --- .../src/ts/elements/account/result-filters.ts | 48 ++++++++++++++----- packages/contracts/src/schemas/users.ts | 2 +- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/frontend/src/ts/elements/account/result-filters.ts b/frontend/src/ts/elements/account/result-filters.ts index 2ec5d1b0acb7..7e0ea0c8a8a3 100644 --- a/frontend/src/ts/elements/account/result-filters.ts +++ b/frontend/src/ts/elements/account/result-filters.ts @@ -1,23 +1,23 @@ -import * as Misc from "../../utils/misc"; -import * as Strings from "../../utils/strings"; -import * as JSONData from "../../utils/json-data"; -import * as DB from "../../db"; -import Config from "../../config"; -import * as Notifications from "../notifications"; -import Ape from "../../ape/index"; -import * as Loader from "../loader"; -import SlimSelect from "slim-select"; import { QuoteLength } from "@monkeytype/contracts/schemas/configs"; import { ResultFilters, - ResultFiltersSchema, ResultFiltersGroup, ResultFiltersGroupItem, + ResultFiltersSchema, } from "@monkeytype/contracts/schemas/users"; -import { LocalStorageWithSchema } from "../../utils/local-storage-with-schema"; -import defaultResultFilters from "../../constants/default-result-filters"; import { getAllFunboxes } from "@monkeytype/funbox"; +import SlimSelect from "slim-select"; +import Ape from "../../ape/index"; +import Config from "../../config"; +import defaultResultFilters from "../../constants/default-result-filters"; import { SnapshotUserTag } from "../../constants/default-snapshot"; +import * as DB from "../../db"; +import * as JSONData from "../../utils/json-data"; +import { LocalStorageWithSchema } from "../../utils/local-storage-with-schema"; +import * as Misc from "../../utils/misc"; +import * as Strings from "../../utils/strings"; +import * as Loader from "../loader"; +import * as Notifications from "../notifications"; export function mergeWithDefaultFilters( filters: Partial @@ -33,6 +33,8 @@ export function mergeWithDefaultFilters( merged[groupKey] = id; } else if (groupKey === "name") { merged[groupKey] = filters[groupKey] ?? defaultResultFilters[groupKey]; + } else if (groupKey === "tagsFilterMode") { + merged[groupKey] = filters[groupKey] ?? defaultResultFilters[groupKey]; } else { // @ts-expect-error i cant figure this out merged[groupKey] = { @@ -289,7 +291,7 @@ export function updateActive(): void { for (const group of Misc.typedKeys(getFilters())) { // id and name field do not correspond to any ui elements, no need to update - if (group === "_id" || group === "name") { + if (group === "_id" || group === "name" || group === "tagsFilterMode") { continue; } @@ -488,6 +490,19 @@ export function updateActive(): void { }, 0); } +function updateTagsFilterModeIcon(): void { + const toggleElement = $(".pageAccount .tagsFilterModeToggle"); + const modeTextElement = toggleElement.find(".mode-text"); + + if (filters.tagsFilterMode === "and") { + toggleElement.addClass("mode-and"); + modeTextElement.text("AND"); + } else { + toggleElement.removeClass("mode-and"); + modeTextElement.text("OR"); + } +} + function toggle( group: G, filter: ResultFiltersGroupItem @@ -925,6 +940,13 @@ $(".group.presetFilterButtons .filterBtns").on( } ); +$(document).on("click", ".pageAccount .tagsFilterModeToggle", () => { + filters.tagsFilterMode = filters.tagsFilterMode === "or" ? "and" : "or"; + save(); + updateTagsFilterModeIcon(); + selectChangeCallbackFn(); +}); + function verifyResultFiltersStructure(filterIn: ResultFilters): ResultFilters { const filter = Misc.deepClone(filterIn); Object.entries(defaultResultFilters).forEach((entry) => { diff --git a/packages/contracts/src/schemas/users.ts b/packages/contracts/src/schemas/users.ts index c1d51fa1b266..b102cd7417a0 100644 --- a/packages/contracts/src/schemas/users.ts +++ b/packages/contracts/src/schemas/users.ts @@ -51,7 +51,7 @@ export const ResultFiltersSchema = z.object({ }) .strict(), tags: z.record(z.string(), z.boolean()), - tagsFilterMode: z.enum(["and", "or"]).default("or"), + tagsFilterMode: z.enum(["and", "or"]), language: z.record(LanguageSchema, z.boolean()), funbox: z.record(z.string(), z.boolean()), }); From f5c5235175690ce98398708fe1f9c8323a4771d6 Mon Sep 17 00:00:00 2001 From: Tristan Marion Date: Fri, 21 Mar 2025 15:06:59 +0100 Subject: [PATCH 04/10] feat: implement AND mode for tags filtering --- .../src/ts/elements/account/result-filters.ts | 4 + frontend/src/ts/pages/account.ts | 113 +++++++++++------- 2 files changed, 77 insertions(+), 40 deletions(-) diff --git a/frontend/src/ts/elements/account/result-filters.ts b/frontend/src/ts/elements/account/result-filters.ts index 7e0ea0c8a8a3..0945249888a9 100644 --- a/frontend/src/ts/elements/account/result-filters.ts +++ b/frontend/src/ts/elements/account/result-filters.ts @@ -959,3 +959,7 @@ function verifyResultFiltersStructure(filterIn: ResultFilters): ResultFilters { }); return filter; } + +export function getTagsFilterMode(): "and" | "or" { + return filters.tagsFilterMode; +} diff --git a/frontend/src/ts/pages/account.ts b/frontend/src/ts/pages/account.ts index 0b63f5599345..8cf2fb2e1b3a 100644 --- a/frontend/src/ts/pages/account.ts +++ b/frontend/src/ts/pages/account.ts @@ -1,32 +1,3 @@ -import * as DB from "../db"; -import * as ResultFilters from "../elements/account/result-filters"; -import * as ThemeColors from "../elements/theme-colors"; -import * as ChartController from "../controllers/chart-controller"; -import Config, * as UpdateConfig from "../config"; -import * as MiniResultChartModal from "../modals/mini-result-chart"; -import * as PbTables from "../elements/account/pb-tables"; -import * as LoadingPage from "./loading"; -import * as Focus from "../test/focus"; -import * as TodayTracker from "../test/today-tracker"; -import * as Notifications from "../elements/notifications"; -import Page from "./page"; -import * as DateTime from "../utils/date-and-time"; -import * as Misc from "../utils/misc"; -import * as Arrays from "../utils/arrays"; -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"; -import * as ActivePage from "../states/active-page"; -import { Auth } from "../firebase"; -import * as Loader from "../elements/loader"; -import * as ResultBatches from "../elements/result-batches"; -import Format from "../utils/format"; -import * as TestActivity from "../elements/test-activity"; import { ChartData } from "@monkeytype/contracts/schemas/results"; import { Difficulty, @@ -35,9 +6,38 @@ import { Mode2Custom, } from "@monkeytype/contracts/schemas/shared"; import { ResultFiltersGroupItem } from "@monkeytype/contracts/schemas/users"; -import { findLineByLeastSquares } from "../utils/numbers"; +import * as Numbers from "@monkeytype/util/numbers"; +import type { LinearScaleOptions, ScaleChartOptions } from "chart.js"; +import { format } from "date-fns/format"; +import Config, * as UpdateConfig from "../config"; import defaultResultFilters from "../constants/default-result-filters"; import { SnapshotResult } from "../constants/default-snapshot"; +import * as ChartController from "../controllers/chart-controller"; +import * as DB from "../db"; +import * as PbTables from "../elements/account/pb-tables"; +import * as ResultFilters from "../elements/account/result-filters"; +import * as Loader from "../elements/loader"; +import * as Notifications from "../elements/notifications"; +import * as Profile from "../elements/profile"; +import * as ResultBatches from "../elements/result-batches"; +import * as TestActivity from "../elements/test-activity"; +import * as ThemeColors from "../elements/theme-colors"; +import { Auth } from "../firebase"; +import * as MiniResultChartModal from "../modals/mini-result-chart"; +import * as ConfigEvent from "../observables/config-event"; +import * as ActivePage from "../states/active-page"; +import * as ConnectionState from "../states/connection"; +import * as Focus from "../test/focus"; +import * as TodayTracker from "../test/today-tracker"; +import * as Arrays from "../utils/arrays"; +import * as DateTime from "../utils/date-and-time"; +import Format from "../utils/format"; +import * as Misc from "../utils/misc"; +import { findLineByLeastSquares } from "../utils/numbers"; +import * as Skeleton from "../utils/skeleton"; +import { get as getTypingSpeedUnit } from "../utils/typing-speed-units"; +import * as LoadingPage from "./loading"; +import Page from "./page"; let filterDebug = false; //toggle filterdebug @@ -450,18 +450,51 @@ async function fillContent(): Promise { if (validTags === undefined) return; - result.tags.forEach((tag) => { - //check if i even need to check tags anymore - if (!tagHide) return; - //check if tag is valid - if (validTags?.includes(tag)) { - //tag valid, check if filter is on - if (ResultFilters.getFilter("tags", tag)) tagHide = false; + if (ResultFilters.getTagsFilterMode() === "or") { + result.tags.forEach((tag) => { + //check if i even need to check tags anymore + if (!tagHide) return; + //check if tag is valid + if (validTags?.includes(tag)) { + //tag valid, check if filter is on + if (ResultFilters.getFilter("tags", tag)) tagHide = false; + } else { + //tag not found in valid tags, meaning probably deleted + if (ResultFilters.getFilter("tags", "none")) tagHide = false; + } + }); + } else if (ResultFilters.getTagsFilterMode() === "and") { + // AND mode - show results that match ALL selected tags + // First, identify all the enabled tags using validTags + const enabledTagIds: string[] = []; + + // Loop through all valid tags to find which ones are enabled in the filter + validTags.forEach((tagId) => { + if (tagId !== "none" && ResultFilters.getFilter("tags", tagId)) { + enabledTagIds.push(tagId); + } + }); + + if (enabledTagIds.length === 0) { + // No tag filters enabled, show everything + tagHide = false; } else { - //tag not found in valid tags, meaning probably deleted - if (ResultFilters.getFilter("tags", "none")) tagHide = false; + // Check if result has ALL the enabled tag filters + const resultHasAllTags = enabledTagIds.every((tagId) => + result.tags?.includes(tagId) + ); + + if (resultHasAllTags) { + tagHide = false; + } else if ( + ResultFilters.getFilter("tags", "none") && + result.tags.length === 0 + ) { + // Special case: "none" tag is enabled and result has no tags + tagHide = false; + } } - }); + } } if (tagHide) { From 851ddd184a947f9535a4d9e01e1e43d71dc85c7e Mon Sep 17 00:00:00 2001 From: Tristan Marion Date: Fri, 21 Mar 2025 15:21:56 +0100 Subject: [PATCH 05/10] test: fix failing tests --- .../__tests__/api/controllers/user.spec.ts | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/backend/__tests__/api/controllers/user.spec.ts b/backend/__tests__/api/controllers/user.spec.ts index 2e75511ebf0d..26823da0b161 100644 --- a/backend/__tests__/api/controllers/user.spec.ts +++ b/backend/__tests__/api/controllers/user.spec.ts @@ -1,37 +1,37 @@ +import { LeaderboardEntry } from "@monkeytype/contracts/schemas/leaderboards"; +import { PersonalBest } from "@monkeytype/contracts/schemas/shared"; +import { MonkeyMail, UserStreak } from "@monkeytype/contracts/schemas/users"; +import { FirebaseError } from "firebase-admin"; +import _ from "lodash"; +import { ObjectId } from "mongodb"; +import { randomUUID } from "node:crypto"; import request from "supertest"; -import app from "../../../src/app"; -import * as Configuration from "../../../src/init/configuration"; import { generateCurrentTestActivity } from "../../../src/api/controllers/user"; -import * as UserDal from "../../../src/dal/user"; -import * as AuthUtils from "../../../src/utils/auth"; -import * as BlocklistDal from "../../../src/dal/blocklist"; +import app from "../../../src/app"; import * as ApeKeys from "../../../src/dal/ape-keys"; -import * as PresetDal from "../../../src/dal/preset"; +import * as ApeKeysDal from "../../../src/dal/ape-keys"; +import * as BlocklistDal from "../../../src/dal/blocklist"; import * as ConfigDal from "../../../src/dal/config"; -import * as ResultDal from "../../../src/dal/result"; -import * as ReportDal from "../../../src/dal/report"; -import * as DailyLeaderboards from "../../../src/utils/daily-leaderboards"; import * as LeaderboardDal from "../../../src/dal/leaderboards"; +import * as LogDal from "../../../src/dal/logs"; +import * as PresetDal from "../../../src/dal/preset"; +import * as ReportDal from "../../../src/dal/report"; +import * as ResultDal from "../../../src/dal/result"; +import * as UserDal from "../../../src/dal/user"; +import * as Configuration from "../../../src/init/configuration"; +import * as FirebaseAdmin from "../../../src/init/firebase-admin"; import GeorgeQueue from "../../../src/queues/george-queue"; -import * as DiscordUtils from "../../../src/utils/discord"; +import * as WeeklyXpLeaderboard from "../../../src/services/weekly-xp-leaderboard"; +import * as AuthUtils from "../../../src/utils/auth"; import * as Captcha from "../../../src/utils/captcha"; -import * as FirebaseAdmin from "../../../src/init/firebase-admin"; -import { FirebaseError } from "firebase-admin"; -import * as ApeKeysDal from "../../../src/dal/ape-keys"; -import * as LogDal from "../../../src/dal/logs"; -import { ObjectId } from "mongodb"; -import { PersonalBest } from "@monkeytype/contracts/schemas/shared"; -import { pb } from "../../dal/leaderboards.spec"; +import * as DailyLeaderboards from "../../../src/utils/daily-leaderboards"; +import * as DiscordUtils from "../../../src/utils/discord"; +import MonkeyError, { isFirebaseError } from "../../../src/utils/error"; import { mockAuthenticateWithApeKey, mockBearerAuthentication, } from "../../__testData__/auth"; -import { randomUUID } from "node:crypto"; -import _ from "lodash"; -import { MonkeyMail, UserStreak } from "@monkeytype/contracts/schemas/users"; -import MonkeyError, { isFirebaseError } from "../../../src/utils/error"; -import { LeaderboardEntry } from "@monkeytype/contracts/schemas/leaderboards"; -import * as WeeklyXpLeaderboard from "../../../src/services/weekly-xp-leaderboard"; +import { pb } from "../../dal/leaderboards.spec"; const mockApp = request(app); const configuration = Configuration.getCachedConfiguration(); @@ -1917,6 +1917,7 @@ describe("user controller test", () => { tags: { none: false, }, + tagsFilterMode: "or", language: { english: true, }, @@ -1981,6 +1982,7 @@ describe("user controller test", () => { '"numbers" Required', '"date" Required', '"tags" Required', + '"tagsFilterMode" Required', '"language" Required', '"funbox" Required', ], From 9faac204bbaca348c5a8242b6e55e3335d45ef7d Mon Sep 17 00:00:00 2001 From: Tristan Marion Date: Sun, 23 Mar 2025 15:03:41 +0100 Subject: [PATCH 06/10] feat: add exact tag match filter mode --- frontend/src/html/pages/account.html | 5 +-- .../src/ts/elements/account/result-filters.ts | 15 ++++++--- frontend/src/ts/pages/account.ts | 31 +++++++++++++++++++ packages/contracts/src/schemas/users.ts | 2 +- 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/frontend/src/html/pages/account.html b/frontend/src/html/pages/account.html index ca942cd83cf1..fb33915fdc38 100644 --- a/frontend/src/html/pages/account.html +++ b/frontend/src/html/pages/account.html @@ -499,8 +499,9 @@ OR diff --git a/frontend/src/ts/elements/account/result-filters.ts b/frontend/src/ts/elements/account/result-filters.ts index 0945249888a9..1a3abe52a265 100644 --- a/frontend/src/ts/elements/account/result-filters.ts +++ b/frontend/src/ts/elements/account/result-filters.ts @@ -495,10 +495,10 @@ function updateTagsFilterModeIcon(): void { const modeTextElement = toggleElement.find(".mode-text"); if (filters.tagsFilterMode === "and") { - toggleElement.addClass("mode-and"); modeTextElement.text("AND"); + } else if (filters.tagsFilterMode === "exact") { + modeTextElement.text("EXACT"); } else { - toggleElement.removeClass("mode-and"); modeTextElement.text("OR"); } } @@ -941,7 +941,14 @@ $(".group.presetFilterButtons .filterBtns").on( ); $(document).on("click", ".pageAccount .tagsFilterModeToggle", () => { - filters.tagsFilterMode = filters.tagsFilterMode === "or" ? "and" : "or"; + // Cycle between "or" -> "and" -> "exact" -> "or" + if (filters.tagsFilterMode === "or") { + filters.tagsFilterMode = "and"; + } else if (filters.tagsFilterMode === "and") { + filters.tagsFilterMode = "exact"; + } else { + filters.tagsFilterMode = "or"; + } save(); updateTagsFilterModeIcon(); selectChangeCallbackFn(); @@ -960,6 +967,6 @@ function verifyResultFiltersStructure(filterIn: ResultFilters): ResultFilters { return filter; } -export function getTagsFilterMode(): "and" | "or" { +export function getTagsFilterMode(): "and" | "or" | "exact" { return filters.tagsFilterMode; } diff --git a/frontend/src/ts/pages/account.ts b/frontend/src/ts/pages/account.ts index 8cf2fb2e1b3a..9aa6427fe1cc 100644 --- a/frontend/src/ts/pages/account.ts +++ b/frontend/src/ts/pages/account.ts @@ -494,6 +494,37 @@ async function fillContent(): Promise { tagHide = false; } } + } else if (ResultFilters.getTagsFilterMode() === "exact") { + // EXACT mode - show results where tags exactly match the selected filters + // First, identify all the enabled tags + const enabledTagIds: string[] = []; + + // Loop through all valid tags to find which ones are enabled in the filter + validTags.forEach((tagId) => { + if (tagId !== "none" && ResultFilters.getFilter("tags", tagId)) { + enabledTagIds.push(tagId); + } + }); + + if (enabledTagIds.length === 0) { + // No tag filters enabled, show everything + tagHide = false; + } else { + // Check if result tags exactly match the enabled filters (same number and same tags) + const resultHasExactTags = + result.tags.length === enabledTagIds.length && + enabledTagIds.every((tagId) => result.tags?.includes(tagId)); + + if (resultHasExactTags) { + tagHide = false; + } else if ( + ResultFilters.getFilter("tags", "none") && + result.tags.length === 0 + ) { + // Special case: "none" tag is enabled and result has no tags + tagHide = false; + } + } } } diff --git a/packages/contracts/src/schemas/users.ts b/packages/contracts/src/schemas/users.ts index b102cd7417a0..0306efd89849 100644 --- a/packages/contracts/src/schemas/users.ts +++ b/packages/contracts/src/schemas/users.ts @@ -51,7 +51,7 @@ export const ResultFiltersSchema = z.object({ }) .strict(), tags: z.record(z.string(), z.boolean()), - tagsFilterMode: z.enum(["and", "or"]), + tagsFilterMode: z.enum(["and", "or", "exact"]), language: z.record(LanguageSchema, z.boolean()), funbox: z.record(z.string(), z.boolean()), }); From 9d040c4340ff5c3b77a5d7c2e5b27804f39a0480 Mon Sep 17 00:00:00 2001 From: Tristan Marion Date: Sun, 23 Mar 2025 17:57:28 +0100 Subject: [PATCH 07/10] refactor: reorder imports --- .../__tests__/api/controllers/user.spec.ts | 46 +++++++------- .../src/ts/elements/account/result-filters.ts | 24 ++++---- frontend/src/ts/pages/account.ts | 60 +++++++++---------- packages/contracts/src/schemas/users.ts | 14 ++--- 4 files changed, 72 insertions(+), 72 deletions(-) diff --git a/backend/__tests__/api/controllers/user.spec.ts b/backend/__tests__/api/controllers/user.spec.ts index 26823da0b161..2a1bfd1fbbe8 100644 --- a/backend/__tests__/api/controllers/user.spec.ts +++ b/backend/__tests__/api/controllers/user.spec.ts @@ -1,37 +1,37 @@ -import { LeaderboardEntry } from "@monkeytype/contracts/schemas/leaderboards"; -import { PersonalBest } from "@monkeytype/contracts/schemas/shared"; -import { MonkeyMail, UserStreak } from "@monkeytype/contracts/schemas/users"; -import { FirebaseError } from "firebase-admin"; -import _ from "lodash"; -import { ObjectId } from "mongodb"; -import { randomUUID } from "node:crypto"; import request from "supertest"; -import { generateCurrentTestActivity } from "../../../src/api/controllers/user"; import app from "../../../src/app"; -import * as ApeKeys from "../../../src/dal/ape-keys"; -import * as ApeKeysDal from "../../../src/dal/ape-keys"; +import * as Configuration from "../../../src/init/configuration"; +import { generateCurrentTestActivity } from "../../../src/api/controllers/user"; +import * as UserDal from "../../../src/dal/user"; +import * as AuthUtils from "../../../src/utils/auth"; import * as BlocklistDal from "../../../src/dal/blocklist"; -import * as ConfigDal from "../../../src/dal/config"; -import * as LeaderboardDal from "../../../src/dal/leaderboards"; -import * as LogDal from "../../../src/dal/logs"; +import * as ApeKeys from "../../../src/dal/ape-keys"; import * as PresetDal from "../../../src/dal/preset"; -import * as ReportDal from "../../../src/dal/report"; +import * as ConfigDal from "../../../src/dal/config"; import * as ResultDal from "../../../src/dal/result"; -import * as UserDal from "../../../src/dal/user"; -import * as Configuration from "../../../src/init/configuration"; -import * as FirebaseAdmin from "../../../src/init/firebase-admin"; -import GeorgeQueue from "../../../src/queues/george-queue"; -import * as WeeklyXpLeaderboard from "../../../src/services/weekly-xp-leaderboard"; -import * as AuthUtils from "../../../src/utils/auth"; -import * as Captcha from "../../../src/utils/captcha"; +import * as ReportDal from "../../../src/dal/report"; import * as DailyLeaderboards from "../../../src/utils/daily-leaderboards"; +import * as LeaderboardDal from "../../../src/dal/leaderboards"; +import GeorgeQueue from "../../../src/queues/george-queue"; import * as DiscordUtils from "../../../src/utils/discord"; -import MonkeyError, { isFirebaseError } from "../../../src/utils/error"; +import * as Captcha from "../../../src/utils/captcha"; +import * as FirebaseAdmin from "../../../src/init/firebase-admin"; +import { FirebaseError } from "firebase-admin"; +import * as ApeKeysDal from "../../../src/dal/ape-keys"; +import * as LogDal from "../../../src/dal/logs"; +import { ObjectId } from "mongodb"; +import { PersonalBest } from "@monkeytype/contracts/schemas/shared"; +import { pb } from "../../dal/leaderboards.spec"; import { mockAuthenticateWithApeKey, mockBearerAuthentication, } from "../../__testData__/auth"; -import { pb } from "../../dal/leaderboards.spec"; +import { randomUUID } from "node:crypto"; +import _ from "lodash"; +import { MonkeyMail, UserStreak } from "@monkeytype/contracts/schemas/users"; +import MonkeyError, { isFirebaseError } from "../../../src/utils/error"; +import { LeaderboardEntry } from "@monkeytype/contracts/schemas/leaderboards"; +import * as WeeklyXpLeaderboard from "../../../src/services/weekly-xp-leaderboard"; const mockApp = request(app); const configuration = Configuration.getCachedConfiguration(); diff --git a/frontend/src/ts/elements/account/result-filters.ts b/frontend/src/ts/elements/account/result-filters.ts index 1a3abe52a265..b6bc6fda4fc1 100644 --- a/frontend/src/ts/elements/account/result-filters.ts +++ b/frontend/src/ts/elements/account/result-filters.ts @@ -1,23 +1,23 @@ +import * as Misc from "../../utils/misc"; +import * as Strings from "../../utils/strings"; +import * as JSONData from "../../utils/json-data"; +import * as DB from "../../db"; +import Config from "../../config"; +import * as Notifications from "../notifications"; +import Ape from "../../ape/index"; +import * as Loader from "../loader"; +import SlimSelect from "slim-select"; import { QuoteLength } from "@monkeytype/contracts/schemas/configs"; import { ResultFilters, + ResultFiltersSchema, ResultFiltersGroup, ResultFiltersGroupItem, - ResultFiltersSchema, } from "@monkeytype/contracts/schemas/users"; -import { getAllFunboxes } from "@monkeytype/funbox"; -import SlimSelect from "slim-select"; -import Ape from "../../ape/index"; -import Config from "../../config"; +import { LocalStorageWithSchema } from "../../utils/local-storage-with-schema"; import defaultResultFilters from "../../constants/default-result-filters"; +import { getAllFunboxes } from "@monkeytype/funbox"; import { SnapshotUserTag } from "../../constants/default-snapshot"; -import * as DB from "../../db"; -import * as JSONData from "../../utils/json-data"; -import { LocalStorageWithSchema } from "../../utils/local-storage-with-schema"; -import * as Misc from "../../utils/misc"; -import * as Strings from "../../utils/strings"; -import * as Loader from "../loader"; -import * as Notifications from "../notifications"; export function mergeWithDefaultFilters( filters: Partial diff --git a/frontend/src/ts/pages/account.ts b/frontend/src/ts/pages/account.ts index 9aa6427fe1cc..bbfc5a2603f0 100644 --- a/frontend/src/ts/pages/account.ts +++ b/frontend/src/ts/pages/account.ts @@ -1,3 +1,32 @@ +import * as DB from "../db"; +import * as ResultFilters from "../elements/account/result-filters"; +import * as ThemeColors from "../elements/theme-colors"; +import * as ChartController from "../controllers/chart-controller"; +import Config, * as UpdateConfig from "../config"; +import * as MiniResultChartModal from "../modals/mini-result-chart"; +import * as PbTables from "../elements/account/pb-tables"; +import * as LoadingPage from "./loading"; +import * as Focus from "../test/focus"; +import * as TodayTracker from "../test/today-tracker"; +import * as Notifications from "../elements/notifications"; +import Page from "./page"; +import * as DateTime from "../utils/date-and-time"; +import * as Misc from "../utils/misc"; +import * as Arrays from "../utils/arrays"; +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"; +import * as ActivePage from "../states/active-page"; +import { Auth } from "../firebase"; +import * as Loader from "../elements/loader"; +import * as ResultBatches from "../elements/result-batches"; +import Format from "../utils/format"; +import * as TestActivity from "../elements/test-activity"; import { ChartData } from "@monkeytype/contracts/schemas/results"; import { Difficulty, @@ -6,38 +35,9 @@ import { Mode2Custom, } from "@monkeytype/contracts/schemas/shared"; import { ResultFiltersGroupItem } from "@monkeytype/contracts/schemas/users"; -import * as Numbers from "@monkeytype/util/numbers"; -import type { LinearScaleOptions, ScaleChartOptions } from "chart.js"; -import { format } from "date-fns/format"; -import Config, * as UpdateConfig from "../config"; +import { findLineByLeastSquares } from "../utils/numbers"; import defaultResultFilters from "../constants/default-result-filters"; import { SnapshotResult } from "../constants/default-snapshot"; -import * as ChartController from "../controllers/chart-controller"; -import * as DB from "../db"; -import * as PbTables from "../elements/account/pb-tables"; -import * as ResultFilters from "../elements/account/result-filters"; -import * as Loader from "../elements/loader"; -import * as Notifications from "../elements/notifications"; -import * as Profile from "../elements/profile"; -import * as ResultBatches from "../elements/result-batches"; -import * as TestActivity from "../elements/test-activity"; -import * as ThemeColors from "../elements/theme-colors"; -import { Auth } from "../firebase"; -import * as MiniResultChartModal from "../modals/mini-result-chart"; -import * as ConfigEvent from "../observables/config-event"; -import * as ActivePage from "../states/active-page"; -import * as ConnectionState from "../states/connection"; -import * as Focus from "../test/focus"; -import * as TodayTracker from "../test/today-tracker"; -import * as Arrays from "../utils/arrays"; -import * as DateTime from "../utils/date-and-time"; -import Format from "../utils/format"; -import * as Misc from "../utils/misc"; -import { findLineByLeastSquares } from "../utils/numbers"; -import * as Skeleton from "../utils/skeleton"; -import { get as getTypingSpeedUnit } from "../utils/typing-speed-units"; -import * as LoadingPage from "./loading"; -import Page from "./page"; let filterDebug = false; //toggle filterdebug diff --git a/packages/contracts/src/schemas/users.ts b/packages/contracts/src/schemas/users.ts index 0306efd89849..0cd6881371d6 100644 --- a/packages/contracts/src/schemas/users.ts +++ b/packages/contracts/src/schemas/users.ts @@ -1,16 +1,16 @@ import { z, ZodEffects, ZodOptional, ZodString } from "zod"; -import { doesNotContainProfanity } from "../validation/validation"; -import { CustomThemeColorsSchema } from "./configs"; +import { IdSchema, LanguageSchema, StringNumberSchema } from "./util"; import { - DefaultTimeModeSchema, - DefaultWordsModeSchema, - DifficultySchema, - Mode2Schema, ModeSchema, + Mode2Schema, PersonalBestsSchema, + DefaultWordsModeSchema, + DefaultTimeModeSchema, QuoteLengthSchema, + DifficultySchema, } from "./shared"; -import { IdSchema, LanguageSchema, StringNumberSchema } from "./util"; +import { CustomThemeColorsSchema } from "./configs"; +import { doesNotContainProfanity } from "../validation/validation"; export const ResultFiltersSchema = z.object({ _id: IdSchema, From 967d66dfa54755aae7a32cab53433a58a163f013 Mon Sep 17 00:00:00 2001 From: Tristan Marion Date: Tue, 25 Mar 2025 16:09:02 +0100 Subject: [PATCH 08/10] refactor: remove useless code --- frontend/src/ts/pages/account.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/frontend/src/ts/pages/account.ts b/frontend/src/ts/pages/account.ts index bbfc5a2603f0..289858563cd1 100644 --- a/frontend/src/ts/pages/account.ts +++ b/frontend/src/ts/pages/account.ts @@ -486,12 +486,6 @@ async function fillContent(): Promise { if (resultHasAllTags) { tagHide = false; - } else if ( - ResultFilters.getFilter("tags", "none") && - result.tags.length === 0 - ) { - // Special case: "none" tag is enabled and result has no tags - tagHide = false; } } } else if (ResultFilters.getTagsFilterMode() === "exact") { @@ -517,12 +511,6 @@ async function fillContent(): Promise { if (resultHasExactTags) { tagHide = false; - } else if ( - ResultFilters.getFilter("tags", "none") && - result.tags.length === 0 - ) { - // Special case: "none" tag is enabled and result has no tags - tagHide = false; } } } From 52fdf83688ae6d323fbae10046bf78ee99b63545 Mon Sep 17 00:00:00 2001 From: Miodec Date: Tue, 25 Mar 2025 17:59:24 +0100 Subject: [PATCH 09/10] update string --- frontend/src/ts/elements/account/result-filters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/ts/elements/account/result-filters.ts b/frontend/src/ts/elements/account/result-filters.ts index b6bc6fda4fc1..fb51da651248 100644 --- a/frontend/src/ts/elements/account/result-filters.ts +++ b/frontend/src/ts/elements/account/result-filters.ts @@ -872,7 +872,7 @@ export async function appendButtons( html += ""; - html += ""; + html += ""; html += ""; for (const tag of snapshot.tags) {