From 92790f36827d8a0ee181e486f8d757c94b377035 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 21 Jul 2025 13:50:33 +0200 Subject: [PATCH 1/9] refactor(config): config metadata (@miodec) (#6753) Create config metadata object Move all the special code on config change to config listeners Create a generic set function which will work with the metadata object to update any config key Update all setters to use the generic set. (Later probably only use the generic settter and remove all the specific ones) Also orders config groups and config schema. --------- Co-authored-by: Christian Fehmer --- .../__tests__/api/controllers/config.spec.ts | 2 +- .../__tests__/api/controllers/preset.spec.ts | 6 +- frontend/__tests__/root/config.spec.ts | 528 +++- frontend/__tests__/utils/url-handler.spec.ts | 2 +- .../src/ts/commandline/lists/quote-length.ts | 12 +- frontend/src/ts/config.ts | 2330 ++++++----------- .../src/ts/controllers/theme-controller.ts | 42 +- frontend/src/ts/db.ts | 2 +- frontend/src/ts/elements/keymap.ts | 32 + frontend/src/ts/modals/mobile-test-config.ts | 26 +- frontend/src/ts/modals/quote-search.ts | 3 +- frontend/src/ts/pages/account.ts | 9 +- frontend/src/ts/test/caret.ts | 17 +- frontend/src/ts/test/pace-caret.ts | 15 + frontend/src/ts/test/test-config.ts | 18 +- frontend/src/ts/test/test-logic.ts | 21 +- frontend/src/ts/test/test-ui.ts | 3 + frontend/src/ts/test/words-generator.ts | 6 +- frontend/src/ts/ui.ts | 24 +- frontend/src/ts/utils/config.ts | 42 + frontend/src/ts/utils/url-handler.ts | 2 +- packages/contracts/src/schemas/configs.ts | 296 ++- 22 files changed, 1721 insertions(+), 1717 deletions(-) diff --git a/backend/__tests__/api/controllers/config.spec.ts b/backend/__tests__/api/controllers/config.spec.ts index 3cbf6dc89b56..650a5d021f49 100644 --- a/backend/__tests__/api/controllers/config.spec.ts +++ b/backend/__tests__/api/controllers/config.spec.ts @@ -100,8 +100,8 @@ describe("ConfigController", () => { expect(body).toStrictEqual({ message: "Invalid request data schema", validationErrors: [ - `"autoSwitchTheme" Expected boolean, received string`, `"confidenceMode" Invalid enum value. Expected 'off' | 'on' | 'max', received 'pretty'`, + `"autoSwitchTheme" Expected boolean, received string`, ], }); diff --git a/backend/__tests__/api/controllers/preset.spec.ts b/backend/__tests__/api/controllers/preset.spec.ts index 3d9f5eb80acb..90158714507e 100644 --- a/backend/__tests__/api/controllers/preset.spec.ts +++ b/backend/__tests__/api/controllers/preset.spec.ts @@ -249,8 +249,8 @@ describe("PresetController", () => { expect(body).toStrictEqual({ message: "Invalid request data schema", validationErrors: [ - `"config.autoSwitchTheme" Expected boolean, received string`, `"config.confidenceMode" Invalid enum value. Expected 'off' | 'on' | 'max', received 'pretty'`, + `"config.autoSwitchTheme" Expected boolean, received string`, `"config" Unrecognized key(s) in object: 'extra'`, `Unrecognized key(s) in object: '_id', 'extra'`, ], @@ -427,9 +427,9 @@ describe("PresetController", () => { expect(body).toStrictEqual({ message: "Invalid request data schema", validationErrors: [ - `"settingGroups.0" Invalid enum value. Expected 'test' | 'behavior' | 'input' | 'sound' | 'caret' | 'appearance' | 'theme' | 'hideElements' | 'ads' | 'hidden', received 'mappers'`, - `"config.autoSwitchTheme" Expected boolean, received string`, + `"settingGroups.0" Invalid enum value. Expected 'test' | 'behavior' | 'input' | 'sound' | 'caret' | 'appearance' | 'theme' | 'hideElements' | 'hidden' | 'ads', received 'mappers'`, `"config.confidenceMode" Invalid enum value. Expected 'off' | 'on' | 'max', received 'pretty'`, + `"config.autoSwitchTheme" Expected boolean, received string`, `"config" Unrecognized key(s) in object: 'extra'`, `Unrecognized key(s) in object: 'extra'`, ], diff --git a/frontend/__tests__/root/config.spec.ts b/frontend/__tests__/root/config.spec.ts index 08059aa43ca7..c3f4c2427161 100644 --- a/frontend/__tests__/root/config.spec.ts +++ b/frontend/__tests__/root/config.spec.ts @@ -1,12 +1,499 @@ import * as Config from "../../src/ts/config"; - +import * as Misc from "../../src/ts/utils/misc"; import { CustomThemeColors, FunboxName, + ConfigKey, + Config as ConfigType, } from "@monkeytype/contracts/schemas/configs"; import { randomBytes } from "crypto"; +import { vi } from "vitest"; +import * as FunboxValidation from "../../src/ts/test/funbox/funbox-validation"; +import * as ConfigValidation from "../../src/ts/config-validation"; +import * as ConfigEvent from "../../src/ts/observables/config-event"; +import * as DB from "../../src/ts/db"; +import * as AccountButton from "../../src/ts/elements/account-button"; +import * as Notifications from "../../src/ts/elements/notifications"; + +type TestsByConfig = Partial<{ + [K in keyof ConfigType]: (T & { value: ConfigType[K] })[]; +}>; + +const { configMetadata, replaceConfig, getConfig } = Config.__testing; describe("Config", () => { + const isDevEnvironmentMock = vi.spyOn(Misc, "isDevEnvironment"); + beforeEach(() => isDevEnvironmentMock.mockReset()); + + describe("configMeta", () => { + afterAll(() => { + replaceConfig({}); + vi.resetModules(); + }); + it("should have changeRequiresRestart defined", () => { + const configsRequiringRestarts = Object.entries(configMetadata) + .filter(([_key, value]) => value.changeRequiresRestart === true) + .map(([key]) => key) + .sort(); + + expect(configsRequiringRestarts).toEqual( + [ + "punctuation", + "numbers", + "words", + "time", + "mode", + "quoteLength", + "language", + "difficulty", + "minWpmCustomSpeed", + "minWpm", + "minAcc", + "minAccCustom", + "minBurst", + "minBurstCustomSpeed", + "britishEnglish", + "funbox", + "customLayoutfluid", + "strictSpace", + "stopOnError", + "lazyMode", + "layout", + "codeUnindentOnBackspace", + ].sort() + ); + }); + + it("should have triggerResize defined", () => { + const configsWithTriggeResize = Object.entries(configMetadata) + .filter(([_key, value]) => value.triggerResize === true) + .map(([key]) => key) + .sort(); + + expect(configsWithTriggeResize).toEqual( + ["fontSize", "keymapSize", "maxLineWidth", "tapeMode"].sort() + ); + }); + + it("should throw if config key in not found in metadata", () => { + expect(() => { + Config.genericSet("nonExistentKey" as ConfigKey, true); + }).toThrowError( + `Config metadata for key "nonExistentKey" is not defined.` + ); + }); + + describe("overrideValue", () => { + const testCases: TestsByConfig<{ + given?: Partial; + expected: Partial; + }> = { + punctuation: [ + { value: true, expected: { punctuation: true } }, + { + value: true, + given: { mode: "quote" }, + expected: { punctuation: false }, + }, + ], + numbers: [ + { value: true, expected: { numbers: true } }, + { + value: true, + given: { mode: "quote" }, + expected: { numbers: false }, + }, + ], + customLayoutfluid: [ + { + value: ["qwerty", "qwerty", "qwertz"], + expected: { customLayoutfluid: ["qwerty", "qwertz"] }, + }, + ], + customPolyglot: [ + { + value: ["english", "polish", "english"], + expected: { customPolyglot: ["english", "polish"] }, + }, + ], + keymapSize: [ + { value: 1, expected: { keymapSize: 1 } }, + { value: 1.234, expected: { keymapSize: 1.2 } }, + { value: 0.4, expected: { keymapSize: 0.5 } }, + { value: 3.6, expected: { keymapSize: 3.5 } }, + ], + customBackground: [ + { + value: " https://example.com/test.jpg ", + expected: { customBackground: "https://example.com/test.jpg" }, + }, + ], + accountChart: [ + { + value: ["on", "off", "off", "off"], + expected: { accountChart: ["on", "off", "off", "off"] }, + }, + { + value: ["off", "off", "off", "off"], + given: { accountChart: ["on", "off", "off", "off"] }, + expected: { accountChart: ["off", "on", "off", "off"] }, + }, + { + value: ["off", "off", "on", "on"], + given: { accountChart: ["off", "on", "off", "off"] }, + expected: { accountChart: ["on", "off", "on", "on"] }, + }, + ], + }; + + it.for( + Object.entries(testCases).flatMap(([key, value]) => + value.flatMap((it) => ({ key: key as ConfigKey, ...it })) + ) + )( + `$key value=$value given=$given expect=$expected`, + ({ key, value, given, expected }) => { + //GIVEN + replaceConfig(given ?? {}); + + //WHEN + Config.genericSet(key, value as any); + + //THEN + expect(getConfig()).toMatchObject(expected); + } + ); + }); + + describe("isBlocked", () => { + const testCases: TestsByConfig<{ + given?: Partial; + fail?: true; + }> = { + funbox: [ + { + value: "gibberish" as any, + given: { mode: "quote" }, + fail: true, + }, + ], + showAllLines: [ + { value: true, given: { tapeMode: "off" } }, + { value: false, given: { tapeMode: "word" } }, + { value: true, given: { tapeMode: "word" }, fail: true }, + ], + }; + + it.for( + Object.entries(testCases).flatMap(([key, value]) => + value.flatMap((it) => ({ key: key as ConfigKey, ...it })) + ) + )( + `$key value=$value given=$given fail=$fail`, + ({ key, value, given, fail }) => { + //GIVEN + replaceConfig(given ?? {}); + + //WHEN + const applied = Config.genericSet(key, value as any); + + //THEN + expect(applied).toEqual(!fail); + } + ); + }); + + describe("overrideConfig", () => { + const testCases: TestsByConfig<{ + given: Partial; + expected?: Partial; + }> = { + mode: [ + { value: "time", given: { numbers: true, punctuation: true } }, + { + value: "custom", + given: { numbers: true, punctuation: true }, + expected: { numbers: false, punctuation: false }, + }, + { + value: "quote", + given: { numbers: true, punctuation: true }, + expected: { numbers: false, punctuation: false }, + }, + { + value: "zen", + given: { numbers: true, punctuation: true }, + expected: { numbers: false, punctuation: false }, + }, + ], + numbers: [{ value: false, given: { mode: "quote" } }], + freedomMode: [ + { + value: false, + given: { confidenceMode: "on" }, + expected: { confidenceMode: "on" }, + }, + { + value: true, + given: { confidenceMode: "on" }, + expected: { confidenceMode: "off" }, + }, + ], + stopOnError: [ + { + value: "off", + given: { confidenceMode: "on" }, + expected: { confidenceMode: "on" }, + }, + { + value: "word", + given: { confidenceMode: "on" }, + expected: { confidenceMode: "off" }, + }, + ], + confidenceMode: [ + { + value: "off", + given: { freedomMode: true, stopOnError: "word" }, + expected: { freedomMode: true, stopOnError: "word" }, + }, + { + value: "on", + given: { freedomMode: true, stopOnError: "word" }, + expected: { freedomMode: false, stopOnError: "off" }, + }, + ], + tapeMode: [ + { + value: "off", + given: { showAllLines: true }, + expected: { showAllLines: true }, + }, + { + value: "letter", + given: { showAllLines: true }, + expected: { showAllLines: false }, + }, + ], + theme: [ + { + value: "8008", + given: { customTheme: true }, + expected: { customTheme: false }, + }, + ], + }; + + it.for( + Object.entries(testCases).flatMap(([key, value]) => + value.flatMap((it) => ({ key: key as ConfigKey, ...it })) + ) + )( + `$key value=$value given=$given expected=$expected`, + ({ key, value, given, expected }) => { + //GIVEN + replaceConfig(given); + + //WHEN + Config.genericSet(key, value as any); + + //THEN + expect(getConfig()).toMatchObject(expected ?? {}); + } + ); + }); + + describe("test with mocks", () => { + const canSetConfigWithCurrentFunboxesMock = vi.spyOn( + FunboxValidation, + "canSetConfigWithCurrentFunboxes" + ); + const isConfigValueValidMock = vi.spyOn( + ConfigValidation, + "isConfigValueValid" + ); + const dispatchConfigEventMock = vi.spyOn(ConfigEvent, "dispatch"); + const dbSaveConfigMock = vi.spyOn(DB, "saveConfig"); + const accountButtonLoadingMock = vi.spyOn(AccountButton, "loading"); + const notificationAddMock = vi.spyOn(Notifications, "add"); + const miscReloadAfterMock = vi.spyOn(Misc, "reloadAfter"); + + const mocks = [ + canSetConfigWithCurrentFunboxesMock, + isConfigValueValidMock, + dispatchConfigEventMock, + dbSaveConfigMock, + accountButtonLoadingMock, + notificationAddMock, + miscReloadAfterMock, + ]; + + beforeEach(async () => { + vi.useFakeTimers(); + mocks.forEach((it) => it.mockReset()); + + vi.mock("../../src/ts/test/test-state", () => ({ + isActive: true, + })); + + isConfigValueValidMock.mockReturnValue(true); + canSetConfigWithCurrentFunboxesMock.mockReturnValue(true); + dbSaveConfigMock.mockResolvedValue(); + }); + + afterAll(() => { + mocks.forEach((it) => it.mockRestore()); + vi.useRealTimers(); + }); + + it("cannot set if funbox disallows", () => { + //GIVEN + canSetConfigWithCurrentFunboxesMock.mockReturnValue(false); + + //WHEN / THEN + expect(Config.genericSet("numbers", true)).toBe(false); + }); + + it("fails if config is invalid", () => { + //GIVEN + isConfigValueValidMock.mockReturnValue(false); + + //WHEN / THEN + expect(Config.genericSet("numbers", "off" as any)).toBe(false); + }); + + it("dispatches event on set", () => { + //GIVEN + replaceConfig({ numbers: false }); + + //WHEN + Config.genericSet("numbers", true, true); + + //THEN + + expect(dispatchConfigEventMock).toHaveBeenCalledWith( + "numbers", + true, + true, + false + ); + }); + + it("saves to localstorage if nosave=false", async () => { + //GIVEN + replaceConfig({ numbers: false }); + + //WHEN + Config.genericSet("numbers", true); + + //THEN + //wait for debounce + await vi.advanceTimersByTimeAsync(2500); + + //show loading + expect(accountButtonLoadingMock).toHaveBeenNthCalledWith(1, true); + + //save + expect(dbSaveConfigMock).toHaveBeenCalledWith({ numbers: true }); + + //hide loading + expect(accountButtonLoadingMock).toHaveBeenNthCalledWith(2, false); + + //send event + expect(dispatchConfigEventMock).toHaveBeenCalledWith( + "saveToLocalStorage", + expect.stringContaining("numbers") + ); + }); + it("does not save to localstorage if nosave=true", async () => { + //GIVEN + + replaceConfig({ numbers: false }); + + //WHEN + Config.genericSet("numbers", true, true); + + //THEN + //wait for debounce + await vi.advanceTimersByTimeAsync(2500); + + expect(accountButtonLoadingMock).not.toHaveBeenCalled(); + expect(dbSaveConfigMock).not.toHaveBeenCalled(); + + expect(dispatchConfigEventMock).not.toHaveBeenCalledWith( + "saveToLocalStorage", + expect.any(String) + ); + }); + it("calls afterSet", () => { + //GIVEN + isDevEnvironmentMock.mockReturnValue(false); + replaceConfig({ ads: "off" }); + + //WHEN + Config.genericSet("ads", "sellout"); + + //THEN + expect(notificationAddMock).toHaveBeenCalledWith( + "Ad settings changed. Refreshing...", + 0 + ); + expect(miscReloadAfterMock).toHaveBeenCalledWith(3); + }); + + it("fails if test is active and funbox no_quit", () => { + //GIVEN + replaceConfig({ funbox: ["no_quit"], numbers: false }); + + //WHEN + expect(Config.genericSet("numbers", true, true)).toBe(false); + + //THEN + expect(notificationAddMock).toHaveBeenCalledWith( + "No quit funbox is active. Please finish the test.", + 0, + { + important: true, + } + ); + }); + + it("sends configEvents for overrideConfigs", () => { + //GIVEN + replaceConfig({ + confidenceMode: "off", + freedomMode: true, + stopOnError: "letter", + }); + + //WHEN + Config.genericSet("confidenceMode", "max"); + + //THEN + expect(dispatchConfigEventMock).toHaveBeenCalledWith( + "freedomMode", + false, + true, + true + ); + + expect(dispatchConfigEventMock).toHaveBeenCalledWith( + "stopOnError", + "off", + true, + "letter" + ); + + expect(dispatchConfigEventMock).toHaveBeenCalledWith( + "confidenceMode", + "max", + false, + "off" + ); + }); + }); + }); + it("setMode", () => { expect(Config.setMode("zen")).toBe(true); expect(Config.setMode("invalid" as any)).toBe(false); @@ -33,7 +520,7 @@ describe("Config", () => { it("setAccountChart", () => { expect(Config.setAccountChart(["on", "off", "off", "on"])).toBe(true); //arrays not having 4 values will get [on, on, on, on] as default - expect(Config.setAccountChart(["on", "off"] as any)).toBe(true); + expect(Config.setAccountChart(["on", "off"] as any)).toBe(false); expect(Config.setAccountChart(["on", "off", "on", "true"] as any)).toBe( false ); @@ -199,13 +686,13 @@ describe("Config", () => { //invalid values being "auto-fixed" expect(Config.setKeymapSize(0)).toBe(true); - expect(Config.default.keymapSize).toBe(0.5); + expect(getConfig().keymapSize).toBe(0.5); expect(Config.setKeymapSize(4)).toBe(true); - expect(Config.default.keymapSize).toBe(3.5); + expect(getConfig().keymapSize).toBe(3.5); expect(Config.setKeymapSize(1.25)).toBe(true); - expect(Config.default.keymapSize).toBe(1.3); + expect(getConfig().keymapSize).toBe(1.3); expect(Config.setKeymapSize(1.24)).toBe(true); - expect(Config.default.keymapSize).toBe(1.2); + expect(getConfig().keymapSize).toBe(1.2); }); it("setCustomBackgroundSize", () => { expect(Config.setCustomBackgroundSize("contain")).toBe(true); @@ -214,8 +701,10 @@ describe("Config", () => { }); it("setCustomBackgroundFilter", () => { expect(Config.setCustomBackgroundFilter([0, 1, 2, 3])).toBe(true); - //gets converted - expect(Config.setCustomBackgroundFilter([0, 1, 2, 3, 4] as any)).toBe(true); + + expect(Config.setCustomBackgroundFilter([0, 1, 2, 3, 4] as any)).toBe( + false + ); expect(Config.setCustomBackgroundFilter([] as any)).toBe(false); expect(Config.setCustomBackgroundFilter(["invalid"] as any)).toBe(false); expect(Config.setCustomBackgroundFilter([1, 2, 3, 4, 5, 6] as any)).toBe( @@ -231,9 +720,7 @@ describe("Config", () => { it("setCustomThemeColors", () => { expect(Config.setCustomThemeColors(customThemeColors(10))).toBe(true); - //gets converted - expect(Config.setCustomThemeColors(customThemeColors(9))).toBe(true); - + expect(Config.setCustomThemeColors(customThemeColors(9))).toBe(false); expect(Config.setCustomThemeColors([] as any)).toBe(false); expect(Config.setCustomThemeColors(["invalid"] as any)).toBe(false); expect(Config.setCustomThemeColors(customThemeColors(5))).toBe(false); @@ -258,7 +745,7 @@ describe("Config", () => { }); it("setAccountChart", () => { expect(Config.setAccountChart(["on", "off", "off", "on"])).toBe(true); - expect(Config.setAccountChart(["on", "off"] as any)).toBe(true); + expect(Config.setAccountChart(["on", "off"] as any)).toBe(false); expect(Config.setAccountChart(["on", "off", "on", "true"] as any)).toBe( false ); @@ -358,8 +845,6 @@ describe("Config", () => { expect(Config.setMinAccCustom(0)).toBe(true); expect(Config.setMinAccCustom(1)).toBe(true); expect(Config.setMinAccCustom(11.11)).toBe(true); - //gets converted - expect(Config.setMinAccCustom(120)).toBe(true); expect(Config.setMinAccCustom("invalid" as any)).toBe(false); expect(Config.setMinAccCustom(-1)).toBe(false); @@ -376,19 +861,12 @@ describe("Config", () => { expect(Config.setTimeConfig(0)).toBe(true); expect(Config.setTimeConfig(1)).toBe(true); - //gets converted - expect(Config.setTimeConfig("invalid" as any)).toBe(true); - expect(Config.setTimeConfig(-1)).toBe(true); - expect(Config.setTimeConfig(11.11)).toBe(false); }); it("setWordCount", () => { expect(Config.setWordCount(0)).toBe(true); expect(Config.setWordCount(1)).toBe(true); - //gets converted - expect(Config.setWordCount(-1)).toBe(true); - expect(Config.setWordCount("invalid" as any)).toBe(false); expect(Config.setWordCount(11.11)).toBe(false); }); @@ -486,12 +964,14 @@ describe("Config", () => { expect(Config.setCustomBackground("invalid")).toBe(false); }); it("setQuoteLength", () => { - expect(Config.setQuoteLength(0)).toBe(true); - expect(Config.setQuoteLength(-3)).toBe(true); - expect(Config.setQuoteLength(3)).toBe(true); + expect(Config.setQuoteLength([0])).toBe(true); + expect(Config.setQuoteLength([-3])).toBe(true); + expect(Config.setQuoteLength([3])).toBe(true); expect(Config.setQuoteLength(-4 as any)).toBe(false); expect(Config.setQuoteLength(4 as any)).toBe(false); + expect(Config.setQuoteLength(3 as any)).toBe(false); + expect(Config.setQuoteLength(2 as any)).toBe(false); expect(Config.setQuoteLength([0, -3, 2])).toBe(true); diff --git a/frontend/__tests__/utils/url-handler.spec.ts b/frontend/__tests__/utils/url-handler.spec.ts index 6321d8131032..18504b90c426 100644 --- a/frontend/__tests__/utils/url-handler.spec.ts +++ b/frontend/__tests__/utils/url-handler.spec.ts @@ -119,7 +119,7 @@ describe("url-handler", () => { //THEN expect(setModeMock).toHaveBeenCalledWith("quote", true); - expect(setQuoteLengthMock).toHaveBeenCalledWith(-2, false); + expect(setQuoteLengthMock).toHaveBeenCalledWith([-2], false); expect(setSelectedQuoteIdMock).toHaveBeenCalledWith(512); expect(restartTestMock).toHaveBeenCalled(); }); diff --git a/frontend/src/ts/commandline/lists/quote-length.ts b/frontend/src/ts/commandline/lists/quote-length.ts index 740e1022a927..a9d4a3acbcac 100644 --- a/frontend/src/ts/commandline/lists/quote-length.ts +++ b/frontend/src/ts/commandline/lists/quote-length.ts @@ -19,7 +19,7 @@ const commands: Command[] = [ configValue: [0, 1, 2, 3], exec: (): void => { UpdateConfig.setMode("quote"); - UpdateConfig.setQuoteLength([0, 1, 2, 3]); + UpdateConfig.setQuoteLengthAll(); TestLogic.restart(); }, }, @@ -30,7 +30,7 @@ const commands: Command[] = [ configValueMode: "include", exec: (): void => { UpdateConfig.setMode("quote"); - UpdateConfig.setQuoteLength(0); + UpdateConfig.setQuoteLength([0]); TestLogic.restart(); }, }, @@ -41,7 +41,7 @@ const commands: Command[] = [ configValueMode: "include", exec: (): void => { UpdateConfig.setMode("quote"); - UpdateConfig.setQuoteLength(1); + UpdateConfig.setQuoteLength([1]); TestLogic.restart(); }, }, @@ -52,7 +52,7 @@ const commands: Command[] = [ configValueMode: "include", exec: (): void => { UpdateConfig.setMode("quote"); - UpdateConfig.setQuoteLength(2); + UpdateConfig.setQuoteLength([2]); TestLogic.restart(); }, }, @@ -63,7 +63,7 @@ const commands: Command[] = [ configValueMode: "include", exec: (): void => { UpdateConfig.setMode("quote"); - UpdateConfig.setQuoteLength(3); + UpdateConfig.setQuoteLength([3]); TestLogic.restart(); }, }, @@ -77,7 +77,7 @@ const commands: Command[] = [ }, exec: (): void => { UpdateConfig.setMode("quote"); - UpdateConfig.setQuoteLength(-3); + UpdateConfig.setQuoteLength([-3]); TestLogic.restart(); }, }, diff --git a/frontend/src/ts/config.ts b/frontend/src/ts/config.ts index a161b2ed941a..345a2a04026a 100644 --- a/frontend/src/ts/config.ts +++ b/frontend/src/ts/config.ts @@ -1,10 +1,6 @@ import * as DB from "./db"; -import * as OutOfFocus from "./test/out-of-focus"; import * as Notifications from "./elements/notifications"; -import { - isConfigValueValidBoolean, - isConfigValueValid, -} from "./config-validation"; +import { isConfigValueValid } from "./config-validation"; import * as ConfigEvent from "./observables/config-event"; import { isAuthenticated } from "./firebase"; import * as AccountButton from "./elements/account-button"; @@ -23,16 +19,14 @@ import { } from "./utils/misc"; import * as ConfigSchemas from "@monkeytype/contracts/schemas/configs"; import { Config, FunboxName } from "@monkeytype/contracts/schemas/configs"; -import { Mode, ModeSchema } from "@monkeytype/contracts/schemas/shared"; -import { - Language, - LanguageSchema, -} from "@monkeytype/contracts/schemas/languages"; +import { Mode } from "@monkeytype/contracts/schemas/shared"; +import { Language } from "@monkeytype/contracts/schemas/languages"; import { LocalStorageWithSchema } from "./utils/local-storage-with-schema"; import { migrateConfig } from "./utils/config"; import { roundTo1 } from "@monkeytype/util/numbers"; import { getDefaultConfig } from "./constants/default-config"; import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json"; +import { ZodSchema } from "zod"; import * as TestState from "./test/test-state"; const configLS = new LocalStorageWithSchema({ @@ -49,11 +43,11 @@ const configLS = new LocalStorageWithSchema({ }, }); -const config = { +let config = { ...getDefaultConfig(), }; -let configToSend = {} as Config; +let configToSend: Partial = {}; const saveToDatabase = debounce(1000, () => { if (Object.keys(configToSend).length > 0) { AccountButton.loading(true); @@ -92,6 +86,20 @@ export function saveFullConfigToLocalStorage(noDbCheck = false): void { ConfigEvent.dispatch("saveToLocalStorage", stringified); } +// type ConfigMetadata = Partial< +// Record< +// ConfigSchemas.ConfigKey, +// { +// configKey: ConfigSchemas.ConfigKey; +// schema: z.ZodTypeAny; +// displayString?: string; +// preventSet: ( +// value: ConfigSchemas.Config[keyof ConfigSchemas.Config] +// ) => boolean; +// } +// > +// >; + function isConfigChangeBlocked(): boolean { if (TestState.isActive && config.funbox.includes("no_quit")) { Notifications.add("No quit funbox is active. Please finish the test.", 0, { @@ -102,138 +110,749 @@ function isConfigChangeBlocked(): boolean { return false; } -//numbers -export function setNumbers(numb: boolean, nosave?: boolean): boolean { - if (isConfigChangeBlocked()) return false; +// type SetBlock = { +// [K in keyof ConfigSchemas.Config]?: ConfigSchemas.Config[K][]; +// }; + +// type RequiredConfig = { +// [K in keyof ConfigSchemas.Config]?: ConfigSchemas.Config[K]; +// }; + +export type ConfigMetadata = { + [K in keyof ConfigSchemas.Config]: { + /** + * Optional display string for the config key. + */ + displayString?: string; + /** + * Should the config change trigger a resize event? handled in ui.ts:108 + */ + triggerResize?: true; + + /** + * Is a test restart required after this config change? + */ + changeRequiresRestart: boolean; + /** + * Optional function that checks if the config value is blocked from being set. + * Returns true if setting the config value should be blocked. + * @param value - The value being set for the config key. + */ + isBlocked?: (value: ConfigSchemas.Config[K]) => boolean; + /** + * Optional function to override the value before setting it. + * Returns the modified value. + * @param value - The value being set for the config key. + * @param currentValue - The current value of the config key. + */ + overrideValue?: ( + value: ConfigSchemas.Config[K], + currentValue: ConfigSchemas.Config[K] + ) => ConfigSchemas.Config[K]; + /** + * Optional function to override other config values before this one is set. + * Returns an object with the config keys and their new values. + * @param value - The value being set for the config key. + */ + overrideConfig?: ( + value: ConfigSchemas.Config[K] + ) => Partial; + /** + * Optional function that is called after the config value is set. + * It can be used to perform additional actions, like reloading the page. + * @param nosave - If true, the change is not saved to localStorage or database. + */ + afterSet?: (nosave: boolean) => void; + }; +}; - if (!isConfigValueValidBoolean("numbers", numb)) return false; +//todo: +// maybe have generic set somehow handle test restarting +// maybe add config group to each metadata object? all though its already defined in ConfigGroupsLiteral + +const configMetadata: ConfigMetadata = { + // test + punctuation: { + changeRequiresRestart: true, + overrideValue: (value) => { + if (config.mode === "quote") { + return false; + } + return value; + }, + }, + numbers: { + changeRequiresRestart: true, + overrideValue: (value) => { + if (config.mode === "quote") { + return false; + } + return value; + }, + }, + words: { + displayString: "word count", + changeRequiresRestart: true, + }, + time: { + changeRequiresRestart: true, + displayString: "time", + }, + mode: { + changeRequiresRestart: true, + overrideConfig: (value) => { + if (value === "custom" || value === "quote" || value === "zen") { + return { + numbers: false, + punctuation: false, + }; + } + return {}; + }, + afterSet: () => { + if (config.mode === "zen" && config.paceCaret !== "off") { + Notifications.add(`Pace caret will not work with zen mode.`, 0); + } + }, + }, + quoteLength: { + displayString: "quote length", + changeRequiresRestart: true, + }, + language: { + displayString: "language", + changeRequiresRestart: true, + }, + burstHeatmap: { + displayString: "burst heatmap", + changeRequiresRestart: false, + }, - if (!canSetConfigWithCurrentFunboxes("numbers", numb, config.funbox)) { - return false; - } + // behavior + difficulty: { + changeRequiresRestart: true, + }, + quickRestart: { + displayString: "quick restart", + changeRequiresRestart: false, + }, + repeatQuotes: { + displayString: "repeat quotes", + changeRequiresRestart: false, + }, + blindMode: { + displayString: "blind mode", + changeRequiresRestart: false, + }, + alwaysShowWordsHistory: { + displayString: "always show words history", + changeRequiresRestart: false, + }, + singleListCommandLine: { + displayString: "single list command line", + changeRequiresRestart: false, + }, + minWpm: { + displayString: "min speed", + changeRequiresRestart: true, + }, + minWpmCustomSpeed: { + displayString: "min speed custom", + changeRequiresRestart: true, + }, + minAcc: { + displayString: "min accuracy", + changeRequiresRestart: true, + }, + minAccCustom: { + displayString: "min accuracy custom", + changeRequiresRestart: true, + }, + minBurst: { + displayString: "min burst", + changeRequiresRestart: true, + }, + minBurstCustomSpeed: { + displayString: "min burst custom speed", + changeRequiresRestart: true, + }, + britishEnglish: { + displayString: "british english", + changeRequiresRestart: true, + }, + funbox: { + changeRequiresRestart: true, + isBlocked: (value) => { + for (const funbox of config.funbox) { + if (!canSetFunboxWithConfig(funbox, config)) { + Notifications.add( + `${value}" cannot be enabled with the current config`, + 0 + ); + return true; + } + } + return false; + }, + }, + customLayoutfluid: { + displayString: "custom layoutfluid", + changeRequiresRestart: true, + overrideValue: (value) => { + return Array.from(new Set(value)); + }, + }, + customPolyglot: { + displayString: "custom polyglot", + changeRequiresRestart: false, + overrideValue: (value) => { + return Array.from(new Set(value)); + }, + }, - if (config.mode === "quote") { - numb = false; - } - config.numbers = numb; - saveToLocalStorage("numbers", nosave); - ConfigEvent.dispatch("numbers", config.numbers); + // input + freedomMode: { + changeRequiresRestart: false, + displayString: "freedom mode", + overrideConfig: (value) => { + if (value) { + return { + confidenceMode: "off", + }; + } + return {}; + }, + }, + strictSpace: { + displayString: "strict space", + changeRequiresRestart: true, + }, + oppositeShiftMode: { + displayString: "opposite shift mode", + changeRequiresRestart: false, + }, + stopOnError: { + displayString: "stop on error", + changeRequiresRestart: true, + overrideConfig: (value) => { + if (value !== "off") { + return { + confidenceMode: "off", + }; + } + return {}; + }, + }, + confidenceMode: { + displayString: "confidence mode", + changeRequiresRestart: false, + overrideConfig: (value) => { + if (value !== "off") { + return { + freedomMode: false, + stopOnError: "off", + }; + } + return {}; + }, + }, + quickEnd: { + displayString: "quick end", + changeRequiresRestart: false, + }, + indicateTypos: { + displayString: "indicate typos", + changeRequiresRestart: false, + }, + hideExtraLetters: { + displayString: "hide extra letters", + changeRequiresRestart: false, + }, + lazyMode: { + displayString: "lazy mode", + changeRequiresRestart: true, + }, + layout: { + displayString: "layout", + changeRequiresRestart: true, + }, + codeUnindentOnBackspace: { + displayString: "code unindent on backspace", + changeRequiresRestart: true, + }, - return true; -} + // sound + soundVolume: { + displayString: "sound volume", + changeRequiresRestart: false, + }, + playSoundOnClick: { + displayString: "play sound on click", + changeRequiresRestart: false, + }, + playSoundOnError: { + displayString: "play sound on error", + changeRequiresRestart: false, + }, -//punctuation -export function setPunctuation(punc: boolean, nosave?: boolean): boolean { - if (isConfigChangeBlocked()) return false; + // caret + smoothCaret: { + displayString: "smooth caret", + changeRequiresRestart: false, + }, + caretStyle: { + displayString: "caret style", + changeRequiresRestart: false, + }, + paceCaret: { + displayString: "pace caret", + changeRequiresRestart: false, + isBlocked: (value) => { + if (document.readyState === "complete") { + if ((value === "pb" || value === "tagPb") && !isAuthenticated()) { + Notifications.add( + `Pace caret "pb" and "tag pb" are unavailable without an account`, + 0 + ); + return true; + } + } + return false; + }, + }, + paceCaretCustomSpeed: { + displayString: "pace caret custom speed", + changeRequiresRestart: false, + }, + paceCaretStyle: { + displayString: "pace caret style", + changeRequiresRestart: false, + }, + repeatedPace: { + displayString: "repeated pace", + changeRequiresRestart: false, + }, + + // appearance + timerStyle: { + displayString: "timer style", + changeRequiresRestart: false, + }, + liveSpeedStyle: { + displayString: "live speed style", + changeRequiresRestart: false, + }, + liveAccStyle: { + displayString: "live accuracy style", + changeRequiresRestart: false, + }, + liveBurstStyle: { + displayString: "live burst style", + changeRequiresRestart: false, + }, + timerColor: { + displayString: "timer color", + changeRequiresRestart: false, + }, + timerOpacity: { + displayString: "timer opacity", + changeRequiresRestart: false, + }, + highlightMode: { + displayString: "highlight mode", + changeRequiresRestart: false, + }, + tapeMode: { + triggerResize: true, + changeRequiresRestart: false, + displayString: "tape mode", + overrideConfig: (value) => { + if (value !== "off") { + return { + showAllLines: false, + }; + } + return {}; + }, + }, + tapeMargin: { + displayString: "tape margin", + changeRequiresRestart: false, + overrideValue: (value) => { + //TODO move to migration after settings validation + if (value < 10) { + value = 10; + } + if (value > 90) { + value = 90; + } + return value; + }, + }, + smoothLineScroll: { + displayString: "smooth line scroll", + changeRequiresRestart: false, + }, + showAllLines: { + changeRequiresRestart: false, + displayString: "show all lines", + isBlocked: (value) => { + if (value && config.tapeMode !== "off") { + Notifications.add("Show all lines doesn't support tape mode.", 0); + return true; + } + return false; + }, + }, + alwaysShowDecimalPlaces: { + displayString: "always show decimal places", + changeRequiresRestart: false, + }, + typingSpeedUnit: { + displayString: "typing speed unit", + changeRequiresRestart: false, + }, + startGraphsAtZero: { + displayString: "start graphs at zero", + changeRequiresRestart: false, + }, + maxLineWidth: { + changeRequiresRestart: false, + triggerResize: true, + displayString: "max line width", + overrideValue: (value) => { + //TODO move to migration after settings validation + if (value < 20 && value !== 0) { + value = 20; + } + if (value > 1000) { + value = 1000; + } + return value; + }, + }, + fontSize: { + changeRequiresRestart: false, + triggerResize: true, + displayString: "font size", + overrideValue: (value) => { + //TODO move to migration after settings validation + if (value < 0) { + value = 1; + } + return value; + }, + }, + fontFamily: { + displayString: "font family", + changeRequiresRestart: false, + }, + keymapMode: { + displayString: "keymap mode", + changeRequiresRestart: false, + }, + keymapLayout: { + displayString: "keymap layout", + changeRequiresRestart: false, + }, + keymapStyle: { + displayString: "keymap style", + changeRequiresRestart: false, + }, + keymapLegendStyle: { + displayString: "keymap legend style", + changeRequiresRestart: false, + }, + keymapShowTopRow: { + displayString: "keymap show top row", + changeRequiresRestart: false, + }, + keymapSize: { + triggerResize: true, + changeRequiresRestart: false, + displayString: "keymap size", + overrideValue: (value) => { + if (value < 0.5) value = 0.5; + if (value > 3.5) value = 3.5; + return roundTo1(value); + }, + }, + + // theme + flipTestColors: { + displayString: "flip test colors", + changeRequiresRestart: false, + }, + colorfulMode: { + displayString: "colorful mode", + changeRequiresRestart: false, + }, + customBackground: { + displayString: "custom background", + changeRequiresRestart: false, + overrideValue: (value) => { + return value.trim(); + }, + }, + customBackgroundSize: { + displayString: "custom background size", + changeRequiresRestart: false, + }, + customBackgroundFilter: { + displayString: "custom background filter", + changeRequiresRestart: false, + }, + autoSwitchTheme: { + displayString: "auto switch theme", + changeRequiresRestart: false, + }, + themeLight: { + displayString: "theme light", + changeRequiresRestart: false, + }, + themeDark: { + displayString: "theme dark", + changeRequiresRestart: false, + }, + randomTheme: { + changeRequiresRestart: false, + displayString: "random theme", + isBlocked: (value) => { + if (value === "custom") { + const snapshot = DB.getSnapshot(); + if (!isAuthenticated()) { + Notifications.add( + "Random theme 'custom' is unavailable without an account", + 0 + ); + return true; + } + if (!snapshot) { + Notifications.add( + "Random theme 'custom' requires a snapshot to be set", + 0 + ); + return true; + } + if (snapshot?.customThemes?.length === 0) { + Notifications.add( + "Random theme 'custom' requires at least one custom theme to be saved", + 0 + ); + return true; + } + } + return false; + }, + }, + favThemes: { + displayString: "favorite themes", + changeRequiresRestart: false, + }, + theme: { + changeRequiresRestart: false, + overrideConfig: () => { + return { + customTheme: false, + }; + }, + }, + customTheme: { + displayString: "custom theme", + changeRequiresRestart: false, + }, + customThemeColors: { + displayString: "custom theme colors", + changeRequiresRestart: false, + }, + + // hide elements + showKeyTips: { + displayString: "show key tips", + changeRequiresRestart: false, + }, + showOutOfFocusWarning: { + displayString: "show out of focus warning", + changeRequiresRestart: false, + }, + capsLockWarning: { + displayString: "caps lock warning", + changeRequiresRestart: false, + }, + showAverage: { + displayString: "show average", + changeRequiresRestart: false, + }, - if (!isConfigValueValidBoolean("punctuation", punc)) return false; + // other (hidden) + accountChart: { + displayString: "account chart", + changeRequiresRestart: false, + overrideValue: (value, currentValue) => { + // if both speed and accuracy are off, set opposite to on + // i dedicate this fix to AshesOfAFallen and our 2 collective brain cells + if (value[0] === "off" && value[1] === "off") { + const changedIndex = value[0] === currentValue[0] ? 0 : 1; + value[changedIndex] = "on"; + } + return value; + }, + }, + monkey: { + displayString: "monkey", + changeRequiresRestart: false, + }, + monkeyPowerLevel: { + displayString: "monkey power level", + changeRequiresRestart: false, + }, + + // ads + ads: { + changeRequiresRestart: false, + isBlocked: (value) => { + if (value !== "off" && isDevEnvironment()) { + Notifications.add("Ads are disabled in development mode.", 0); + return true; + } + return false; + }, + afterSet: (nosave) => { + if (!nosave && !isDevEnvironment()) { + reloadAfter(3); + Notifications.add("Ad settings changed. Refreshing...", 0); + } + }, + }, +}; + +export function genericSet( + key: T, + value: ConfigSchemas.Config[T], + nosave: boolean = false +): boolean { + const metadata = configMetadata[key] as ConfigMetadata[T]; + if (metadata === undefined) { + throw new Error(`Config metadata for key "${key}" is not defined.`); + } + + const previousValue = config[key]; - if (!canSetConfigWithCurrentFunboxes("punctuation", punc, config.funbox)) { + if ( + metadata.changeRequiresRestart && + TestState.isActive && + config.funbox.includes("no_quit") + ) { + Notifications.add("No quit funbox is active. Please finish the test.", 0, { + important: true, + }); return false; } - if (config.mode === "quote") { - punc = false; + // if (metadata.setBlock) { + // let block = false; + // for (const blockKey of typedKeys(metadata.setBlock)) { + // const blockValues = metadata.setBlock[blockKey] ?? []; + // if ( + // config[blockKey] !== undefined && + // (blockValues as Array<(typeof config)[typeof blockKey]>).includes( + // config[blockKey] + // ) + // ) { + // block = true; + // break; + // } + // } + // if (block) { + // return false; + // } + // } + + if (metadata.isBlocked?.(value)) { + return false; } - config.punctuation = punc; - saveToLocalStorage("punctuation", nosave); - ConfigEvent.dispatch("punctuation", config.punctuation); - return true; -} + if (metadata.overrideValue) { + value = metadata.overrideValue(value, config[key]); + } -export function setMode(mode: Mode, nosave?: boolean): boolean { - if (isConfigChangeBlocked()) return false; + const schema = ConfigSchemas.ConfigSchema.shape[key] as ZodSchema; - if (!isConfigValueValid("mode", mode, ModeSchema)) { + if (!isConfigValueValid(metadata.displayString ?? key, value, schema)) { return false; } - if (!canSetConfigWithCurrentFunboxes("mode", mode, config.funbox)) { + if (!canSetConfigWithCurrentFunboxes(key, value, config.funbox)) { return false; } - const previous = config.mode; - config.mode = mode; - if (config.mode === "custom") { - setPunctuation(false, true); - setNumbers(false, true); - } else if (config.mode === "quote") { - setPunctuation(false, true); - setNumbers(false, true); - } else if (config.mode === "zen") { - if (config.paceCaret !== "off") { - Notifications.add(`Pace caret will not work with zen mode.`, 0); + if (metadata.overrideConfig) { + const targetConfig = metadata.overrideConfig(value); + + for (const targetKey of typedKeys(targetConfig)) { + const targetValue = targetConfig[ + targetKey + ] as ConfigSchemas.Config[keyof typeof configMetadata]; + + if (config[targetKey] === targetValue) { + continue; // no need to set if the value is already the same + } + + const set = genericSet(targetKey, targetValue, true); + if (!set) { + throw new Error( + `Failed to set config key "${targetKey}" with value "${targetValue}" for ${metadata.displayString} config override.` + ); + } } } - saveToLocalStorage("mode", nosave); - ConfigEvent.dispatch("mode", config.mode, nosave, previous); + + config[key] = value; + if (!nosave) saveToLocalStorage(key, nosave); + ConfigEvent.dispatch(key, value, nosave, previousValue); + + if (metadata.triggerResize && !nosave) { + $(window).trigger("resize"); + } + + metadata.afterSet?.(nosave || false); return true; } +//numbers +export function setNumbers(numb: boolean, nosave?: boolean): boolean { + return genericSet("numbers", numb, nosave); +} + +//punctuation +export function setPunctuation(punc: boolean, nosave?: boolean): boolean { + return genericSet("punctuation", punc, nosave); +} + +export function setMode(mode: Mode, nosave?: boolean): boolean { + return genericSet("mode", mode, nosave); +} + export function setPlaySoundOnError( val: ConfigSchemas.PlaySoundOnError, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "play sound on error", - val, - ConfigSchemas.PlaySoundOnErrorSchema - ) - ) { - return false; - } - - config.playSoundOnError = val; - saveToLocalStorage("playSoundOnError", nosave); - ConfigEvent.dispatch("playSoundOnError", config.playSoundOnError); - - return true; + return genericSet("playSoundOnError", val, nosave); } export function setPlaySoundOnClick( val: ConfigSchemas.PlaySoundOnClick, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "play sound on click", - val, - ConfigSchemas.PlaySoundOnClickSchema - ) - ) { - return false; - } - - config.playSoundOnClick = val; - saveToLocalStorage("playSoundOnClick", nosave); - ConfigEvent.dispatch("playSoundOnClick", config.playSoundOnClick); - - return true; + return genericSet("playSoundOnClick", val, nosave); } export function setSoundVolume( val: ConfigSchemas.SoundVolume, nosave?: boolean ): boolean { - if (val < 0 || val > 1) { - Notifications.add("Sound volume must be between 0 and 1", 0); - val = 0.5; - } - - if ( - !isConfigValueValid("sound volume", val, ConfigSchemas.SoundVolumeSchema) - ) { - return false; - } - - config.soundVolume = val; - saveToLocalStorage("soundVolume", nosave); - ConfigEvent.dispatch("soundVolume", config.soundVolume); - - return true; + return genericSet("soundVolume", val, nosave); } //difficulty @@ -241,17 +860,7 @@ export function setDifficulty( diff: ConfigSchemas.Difficulty, nosave?: boolean ): boolean { - if (isConfigChangeBlocked()) return false; - - if (!isConfigValueValid("difficulty", diff, ConfigSchemas.DifficultySchema)) { - return false; - } - - config.difficulty = diff; - saveToLocalStorage("difficulty", nosave); - ConfigEvent.dispatch("difficulty", config.difficulty, nosave); - - return true; + return genericSet("difficulty", diff, nosave); } //set fav themes @@ -259,42 +868,14 @@ export function setFavThemes( themes: ConfigSchemas.FavThemes, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "favorite themes", - themes, - ConfigSchemas.FavThemesSchema - ) - ) { - return false; - } - config.favThemes = themes; - saveToLocalStorage("favThemes", nosave); - ConfigEvent.dispatch("favThemes", config.favThemes); - - return true; + return genericSet("favThemes", themes, nosave); } export function setFunbox( funbox: ConfigSchemas.Funbox, nosave?: boolean ): boolean { - if (isConfigChangeBlocked()) return false; - - if (!isConfigValueValid("funbox", funbox, ConfigSchemas.FunboxSchema)) - return false; - - for (const funbox of config.funbox) { - if (!canSetFunboxWithConfig(funbox, config)) { - return false; - } - } - - config.funbox = funbox; - saveToLocalStorage("funbox", nosave); - ConfigEvent.dispatch("funbox", config.funbox); - - return true; + return genericSet("funbox", funbox, nosave); } export function toggleFunbox(funbox: FunboxName, nosave?: boolean): boolean { @@ -325,123 +906,42 @@ export function toggleFunbox(funbox: FunboxName, nosave?: boolean): boolean { } export function setBlindMode(blind: boolean, nosave?: boolean): boolean { - if (!isConfigValueValidBoolean("blind mode", blind)) return false; - - config.blindMode = blind; - saveToLocalStorage("blindMode", nosave); - ConfigEvent.dispatch("blindMode", config.blindMode, nosave); - - return true; + return genericSet("blindMode", blind, nosave); } export function setAccountChart( array: ConfigSchemas.AccountChart, nosave?: boolean ): boolean { - if (array.length !== 4) { - array = ["on", "on", "on", "on"]; - } - - if ( - !isConfigValueValid( - "account chart", - array, - ConfigSchemas.AccountChartSchema - ) - ) { - return false; - } - - // if both speed and accuracy are off, set speed to on - // i dedicate this fix to AshesOfAFallen and our 2 collective brain cells - if (array[0] === "off" && array[1] === "off") { - array[0] = "on"; - } - - config.accountChart = array; - saveToLocalStorage("accountChart", nosave); - ConfigEvent.dispatch("accountChart", config.accountChart); - - return true; + return genericSet("accountChart", array, nosave); } export function setStopOnError( soe: ConfigSchemas.StopOnError, nosave?: boolean ): boolean { - if (isConfigChangeBlocked()) return false; - - if ( - !isConfigValueValid("stop on error", soe, ConfigSchemas.StopOnErrorSchema) - ) { - return false; - } - - config.stopOnError = soe; - if (config.stopOnError !== "off") { - config.confidenceMode = "off"; - saveToLocalStorage("confidenceMode", nosave); - } - saveToLocalStorage("stopOnError", nosave); - ConfigEvent.dispatch("stopOnError", config.stopOnError, nosave); - - return true; + return genericSet("stopOnError", soe, nosave); } export function setAlwaysShowDecimalPlaces( val: boolean, nosave?: boolean ): boolean { - if (!isConfigValueValidBoolean("always show decimal places", val)) { - return false; - } - - config.alwaysShowDecimalPlaces = val; - saveToLocalStorage("alwaysShowDecimalPlaces", nosave); - ConfigEvent.dispatch( - "alwaysShowDecimalPlaces", - config.alwaysShowDecimalPlaces - ); - - return true; + return genericSet("alwaysShowDecimalPlaces", val, nosave); } export function setTypingSpeedUnit( val: ConfigSchemas.TypingSpeedUnit, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "typing speed unit", - val, - ConfigSchemas.TypingSpeedUnitSchema - ) - ) { - return false; - } - config.typingSpeedUnit = val; - saveToLocalStorage("typingSpeedUnit", nosave); - ConfigEvent.dispatch("typingSpeedUnit", config.typingSpeedUnit, nosave); - - return true; + return genericSet("typingSpeedUnit", val, nosave); } export function setShowOutOfFocusWarning( val: boolean, nosave?: boolean ): boolean { - if (!isConfigValueValidBoolean("show out of focus warning", val)) { - return false; - } - - config.showOutOfFocusWarning = val; - if (!config.showOutOfFocusWarning) { - OutOfFocus.hide(); - } - saveToLocalStorage("showOutOfFocusWarning", nosave); - ConfigEvent.dispatch("showOutOfFocusWarning", config.showOutOfFocusWarning); - - return true; + return genericSet("showOutOfFocusWarning", val, nosave); } //pace caret @@ -449,59 +949,18 @@ export function setPaceCaret( val: ConfigSchemas.PaceCaret, nosave?: boolean ): boolean { - if (!isConfigValueValid("pace caret", val, ConfigSchemas.PaceCaretSchema)) { - return false; - } - - if (document.readyState === "complete") { - if ((val === "pb" || val === "tagPb") && !isAuthenticated()) { - Notifications.add( - `Pace caret "pb" and "tag pb" are unavailable without an account`, - 0 - ); - return false; - } - } - // if (config.mode === "zen" && val !== "off") { - // Notifications.add(`Can't use pace caret with zen mode.`, 0); - // val = "off"; - // } - config.paceCaret = val; - saveToLocalStorage("paceCaret", nosave); - ConfigEvent.dispatch("paceCaret", config.paceCaret, nosave); - - return true; + return genericSet("paceCaret", val, nosave); } export function setPaceCaretCustomSpeed( val: ConfigSchemas.PaceCaretCustomSpeed, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "pace caret custom speed", - val, - ConfigSchemas.PaceCaretCustomSpeedSchema - ) - ) { - return false; - } - - config.paceCaretCustomSpeed = val; - saveToLocalStorage("paceCaretCustomSpeed", nosave); - ConfigEvent.dispatch("paceCaretCustomSpeed", config.paceCaretCustomSpeed); - - return true; + return genericSet("paceCaretCustomSpeed", val, nosave); } export function setRepeatedPace(pace: boolean, nosave?: boolean): boolean { - if (!isConfigValueValidBoolean("repeated pace", pace)) return false; - - config.repeatedPace = pace; - saveToLocalStorage("repeatedPace", nosave); - ConfigEvent.dispatch("repeatedPace", config.repeatedPace); - - return true; + return genericSet("repeatedPace", pace, nosave); } //min wpm @@ -509,46 +968,14 @@ export function setMinWpm( minwpm: ConfigSchemas.MinimumWordsPerMinute, nosave?: boolean ): boolean { - if (isConfigChangeBlocked()) return false; - - if ( - !isConfigValueValid( - "min speed", - minwpm, - ConfigSchemas.MinimumWordsPerMinuteSchema - ) - ) { - return false; - } - - config.minWpm = minwpm; - saveToLocalStorage("minWpm", nosave); - ConfigEvent.dispatch("minWpm", config.minWpm, nosave); - - return true; + return genericSet("minWpm", minwpm, nosave); } export function setMinWpmCustomSpeed( val: ConfigSchemas.MinWpmCustomSpeed, nosave?: boolean ): boolean { - if (isConfigChangeBlocked()) return false; - - if ( - !isConfigValueValid( - "min speed custom", - val, - ConfigSchemas.MinWpmCustomSpeedSchema - ) - ) { - return false; - } - - config.minWpmCustomSpeed = val; - saveToLocalStorage("minWpmCustomSpeed", nosave); - ConfigEvent.dispatch("minWpmCustomSpeed", config.minWpmCustomSpeed); - - return true; + return genericSet("minWpmCustomSpeed", val, nosave); } //min acc @@ -556,40 +983,14 @@ export function setMinAcc( min: ConfigSchemas.MinimumAccuracy, nosave?: boolean ): boolean { - if (isConfigChangeBlocked()) return false; - - if (!isConfigValueValid("min acc", min, ConfigSchemas.MinimumAccuracySchema)) - return false; - - config.minAcc = min; - saveToLocalStorage("minAcc", nosave); - ConfigEvent.dispatch("minAcc", config.minAcc, nosave); - - return true; + return genericSet("minAcc", min, nosave); } export function setMinAccCustom( val: ConfigSchemas.MinimumAccuracyCustom, nosave?: boolean ): boolean { - if (isConfigChangeBlocked()) return false; - - //migrate legacy configs - if (val > 100) val = 100; - if ( - !isConfigValueValid( - "min acc custom", - val, - ConfigSchemas.MinimumAccuracyCustomSchema - ) - ) - return false; - - config.minAccCustom = val; - saveToLocalStorage("minAccCustom", nosave); - ConfigEvent.dispatch("minAccCustom", config.minAccCustom); - - return true; + return genericSet("minAccCustom", val, nosave); } //min burst @@ -597,40 +998,14 @@ export function setMinBurst( min: ConfigSchemas.MinimumBurst, nosave?: boolean ): boolean { - if (isConfigChangeBlocked()) return false; - - if (!isConfigValueValid("min burst", min, ConfigSchemas.MinimumBurstSchema)) { - return false; - } - - config.minBurst = min; - saveToLocalStorage("minBurst", nosave); - ConfigEvent.dispatch("minBurst", config.minBurst, nosave); - - return true; + return genericSet("minBurst", min, nosave); } export function setMinBurstCustomSpeed( val: ConfigSchemas.MinimumBurstCustomSpeed, nosave?: boolean ): boolean { - if (isConfigChangeBlocked()) return false; - - if ( - !isConfigValueValid( - "min burst custom speed", - val, - ConfigSchemas.MinimumBurstCustomSpeedSchema - ) - ) { - return false; - } - - config.minBurstCustomSpeed = val; - saveToLocalStorage("minBurstCustomSpeed", nosave); - ConfigEvent.dispatch("minBurstCustomSpeed", config.minBurstCustomSpeed); - - return true; + return genericSet("minBurstCustomSpeed", val, nosave); } //always show words history @@ -638,15 +1013,7 @@ export function setAlwaysShowWordsHistory( val: boolean, nosave?: boolean ): boolean { - if (!isConfigValueValidBoolean("always show words history", val)) { - return false; - } - - config.alwaysShowWordsHistory = val; - saveToLocalStorage("alwaysShowWordsHistory", nosave); - ConfigEvent.dispatch("alwaysShowWordsHistory", config.alwaysShowWordsHistory); - - return true; + return genericSet("alwaysShowWordsHistory", val, nosave); } //single list command line @@ -654,130 +1021,46 @@ export function setSingleListCommandLine( option: ConfigSchemas.SingleListCommandLine, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "single list command line", - option, - ConfigSchemas.SingleListCommandLineSchema - ) - ) { - return false; - } - - config.singleListCommandLine = option; - saveToLocalStorage("singleListCommandLine", nosave); - ConfigEvent.dispatch("singleListCommandLine", config.singleListCommandLine); - - return true; + return genericSet("singleListCommandLine", option, nosave); } //caps lock warning export function setCapsLockWarning(val: boolean, nosave?: boolean): boolean { - if (!isConfigValueValidBoolean("caps lock warning", val)) return false; - - config.capsLockWarning = val; - saveToLocalStorage("capsLockWarning", nosave); - ConfigEvent.dispatch("capsLockWarning", config.capsLockWarning); - - return true; + return genericSet("capsLockWarning", val, nosave); } export function setShowAllLines(sal: boolean, nosave?: boolean): boolean { - if (!isConfigValueValidBoolean("show all lines", sal)) return false; - - if (sal && config.tapeMode !== "off") { - Notifications.add("Show all lines doesn't support tape mode", 0); - return false; - } - - config.showAllLines = sal; - saveToLocalStorage("showAllLines", nosave); - ConfigEvent.dispatch("showAllLines", config.showAllLines, nosave); - - return true; + return genericSet("showAllLines", sal, nosave); } export function setQuickEnd(qe: boolean, nosave?: boolean): boolean { - if (!isConfigValueValidBoolean("quick end", qe)) return false; - - config.quickEnd = qe; - saveToLocalStorage("quickEnd", nosave); - ConfigEvent.dispatch("quickEnd", config.quickEnd); - - return true; + return genericSet("quickEnd", qe, nosave); } export function setAds(val: ConfigSchemas.Ads, nosave?: boolean): boolean { - if (!isConfigValueValid("ads", val, ConfigSchemas.AdsSchema)) { - return false; - } - - if (isDevEnvironment()) { - val = "off"; - console.debug("Ads are disabled in dev environment"); - } - - config.ads = val; - saveToLocalStorage("ads", nosave); - if (!nosave && !isDevEnvironment()) { - reloadAfter(3); - Notifications.add("Ad settings changed. Refreshing...", 0); - } - ConfigEvent.dispatch("ads", config.ads); - - return true; + return genericSet("ads", val, nosave); } export function setRepeatQuotes( val: ConfigSchemas.RepeatQuotes, nosave?: boolean ): boolean { - if ( - !isConfigValueValid("repeat quotes", val, ConfigSchemas.RepeatQuotesSchema) - ) { - return false; - } - - config.repeatQuotes = val; - saveToLocalStorage("repeatQuotes", nosave); - ConfigEvent.dispatch("repeatQuotes", config.repeatQuotes); - - return true; + return genericSet("repeatQuotes", val, nosave); } //flip colors export function setFlipTestColors(flip: boolean, nosave?: boolean): boolean { - if (!isConfigValueValidBoolean("flip test colors", flip)) return false; - - config.flipTestColors = flip; - saveToLocalStorage("flipTestColors", nosave); - ConfigEvent.dispatch("flipTestColors", config.flipTestColors); - - return true; + return genericSet("flipTestColors", flip, nosave); } //extra color export function setColorfulMode(extra: boolean, nosave?: boolean): boolean { - if (!isConfigValueValidBoolean("colorful mode", extra)) return false; - - config.colorfulMode = extra; - saveToLocalStorage("colorfulMode", nosave); - ConfigEvent.dispatch("colorfulMode", config.colorfulMode); - - return true; + return genericSet("colorfulMode", extra, nosave); } //strict space export function setStrictSpace(val: boolean, nosave?: boolean): boolean { - if (isConfigChangeBlocked()) return false; - - if (!isConfigValueValidBoolean("strict space", val)) return false; - - config.strictSpace = val; - saveToLocalStorage("strictSpace", nosave); - ConfigEvent.dispatch("strictSpace", config.strictSpace); - - return true; + return genericSet("strictSpace", val, nosave); } //opposite shift space @@ -785,340 +1068,99 @@ export function setOppositeShiftMode( val: ConfigSchemas.OppositeShiftMode, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "opposite shift mode", - val, - ConfigSchemas.OppositeShiftModeSchema - ) - ) { - return false; - } - - config.oppositeShiftMode = val; - saveToLocalStorage("oppositeShiftMode", nosave); - ConfigEvent.dispatch("oppositeShiftMode", config.oppositeShiftMode); - - return true; + return genericSet("oppositeShiftMode", val, nosave); } export function setCaretStyle( caretStyle: ConfigSchemas.CaretStyle, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "caret style", - caretStyle, - ConfigSchemas.CaretStyleSchema - ) - ) { - return false; - } - - config.caretStyle = caretStyle; - $("#caret").removeClass("off"); - $("#caret").removeClass("default"); - $("#caret").removeClass("underline"); - $("#caret").removeClass("outline"); - $("#caret").removeClass("block"); - $("#caret").removeClass("carrot"); - $("#caret").removeClass("banana"); - - if (caretStyle === "off") { - $("#caret").addClass("off"); - } else if (caretStyle === "default") { - $("#caret").addClass("default"); - } else if (caretStyle === "block") { - $("#caret").addClass("block"); - } else if (caretStyle === "outline") { - $("#caret").addClass("outline"); - } else if (caretStyle === "underline") { - $("#caret").addClass("underline"); - } else if (caretStyle === "carrot") { - $("#caret").addClass("carrot"); - } else if (caretStyle === "banana") { - $("#caret").addClass("banana"); - } - saveToLocalStorage("caretStyle", nosave); - ConfigEvent.dispatch("caretStyle", config.caretStyle); - - return true; + return genericSet("caretStyle", caretStyle, nosave); } export function setPaceCaretStyle( caretStyle: ConfigSchemas.CaretStyle, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "pace caret style", - caretStyle, - ConfigSchemas.CaretStyleSchema - ) - ) { - return false; - } - - config.paceCaretStyle = caretStyle; - $("#paceCaret").removeClass("off"); - $("#paceCaret").removeClass("default"); - $("#paceCaret").removeClass("underline"); - $("#paceCaret").removeClass("outline"); - $("#paceCaret").removeClass("block"); - $("#paceCaret").removeClass("carrot"); - $("#paceCaret").removeClass("banana"); - - if (caretStyle === "default") { - $("#paceCaret").addClass("default"); - } else if (caretStyle === "block") { - $("#paceCaret").addClass("block"); - } else if (caretStyle === "outline") { - $("#paceCaret").addClass("outline"); - } else if (caretStyle === "underline") { - $("#paceCaret").addClass("underline"); - } else if (caretStyle === "carrot") { - $("#paceCaret").addClass("carrot"); - } else if (caretStyle === "banana") { - $("#paceCaret").addClass("banana"); - } - saveToLocalStorage("paceCaretStyle", nosave); - ConfigEvent.dispatch("paceCaretStyle", config.paceCaretStyle); - - return true; + return genericSet("paceCaretStyle", caretStyle, nosave); } export function setShowAverage( value: ConfigSchemas.ShowAverage, nosave?: boolean ): boolean { - if ( - !isConfigValueValid("show average", value, ConfigSchemas.ShowAverageSchema) - ) { - return false; - } - - config.showAverage = value; - saveToLocalStorage("showAverage", nosave); - ConfigEvent.dispatch("showAverage", config.showAverage, nosave); - - return true; + return genericSet("showAverage", value, nosave); } export function setHighlightMode( mode: ConfigSchemas.HighlightMode, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "highlight mode", - mode, - ConfigSchemas.HighlightModeSchema - ) - ) { - return false; - } - - if (!canSetConfigWithCurrentFunboxes("highlightMode", mode, config.funbox)) { - return false; - } - - config.highlightMode = mode; - saveToLocalStorage("highlightMode", nosave); - ConfigEvent.dispatch("highlightMode", config.highlightMode); - - return true; + return genericSet("highlightMode", mode, nosave); } export function setTapeMode( mode: ConfigSchemas.TapeMode, nosave?: boolean ): boolean { - if (!isConfigValueValid("tape mode", mode, ConfigSchemas.TapeModeSchema)) { - return false; - } - - if (mode !== "off" && config.showAllLines) { - setShowAllLines(false, true); - } - - config.tapeMode = mode; - saveToLocalStorage("tapeMode", nosave); - ConfigEvent.dispatch("tapeMode", config.tapeMode); - - return true; + return genericSet("tapeMode", mode, nosave); } export function setTapeMargin( value: ConfigSchemas.TapeMargin, nosave?: boolean ): boolean { - if (value < 10) { - value = 10; - } - if (value > 90) { - value = 90; - } - - if ( - !isConfigValueValid("tape margin", value, ConfigSchemas.TapeMarginSchema) - ) { - return false; - } - - config.tapeMargin = value; - - saveToLocalStorage("tapeMargin", nosave); - ConfigEvent.dispatch("tapeMargin", config.tapeMargin, nosave); - - // trigger a resize event to update the layout - handled in ui.ts:108 - $(window).trigger("resize"); - - return true; + return genericSet("tapeMargin", value, nosave); } export function setHideExtraLetters(val: boolean, nosave?: boolean): boolean { - if (!isConfigValueValidBoolean("hide extra letters", val)) return false; - - config.hideExtraLetters = val; - saveToLocalStorage("hideExtraLetters", nosave); - ConfigEvent.dispatch("hideExtraLetters", config.hideExtraLetters); - - return true; + return genericSet("hideExtraLetters", val, nosave); } export function setTimerStyle( style: ConfigSchemas.TimerStyle, nosave?: boolean ): boolean { - if ( - !isConfigValueValid("timer style", style, ConfigSchemas.TimerStyleSchema) - ) { - return false; - } - - config.timerStyle = style; - saveToLocalStorage("timerStyle", nosave); - ConfigEvent.dispatch("timerStyle", config.timerStyle); - - return true; + return genericSet("timerStyle", style, nosave); } export function setLiveSpeedStyle( style: ConfigSchemas.LiveSpeedAccBurstStyle, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "live speed style", - style, - ConfigSchemas.LiveSpeedAccBurstStyleSchema - ) - ) { - return false; - } - - config.liveSpeedStyle = style; - saveToLocalStorage("liveSpeedStyle", nosave); - ConfigEvent.dispatch("liveSpeedStyle", config.timerStyle); - - return true; + return genericSet("liveSpeedStyle", style, nosave); } export function setLiveAccStyle( style: ConfigSchemas.LiveSpeedAccBurstStyle, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "live acc style", - style, - ConfigSchemas.LiveSpeedAccBurstStyleSchema - ) - ) { - return false; - } - - config.liveAccStyle = style; - saveToLocalStorage("liveAccStyle", nosave); - ConfigEvent.dispatch("liveAccStyle", config.timerStyle); - - return true; + return genericSet("liveAccStyle", style, nosave); } export function setLiveBurstStyle( style: ConfigSchemas.LiveSpeedAccBurstStyle, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "live burst style", - style, - ConfigSchemas.LiveSpeedAccBurstStyleSchema - ) - ) { - return false; - } - - config.liveBurstStyle = style; - saveToLocalStorage("liveBurstStyle", nosave); - ConfigEvent.dispatch("liveBurstStyle", config.timerStyle); - - return true; + return genericSet("liveBurstStyle", style, nosave); } export function setTimerColor( color: ConfigSchemas.TimerColor, nosave?: boolean ): boolean { - if ( - !isConfigValueValid("timer color", color, ConfigSchemas.TimerColorSchema) - ) { - return false; - } - - config.timerColor = color; - - saveToLocalStorage("timerColor", nosave); - ConfigEvent.dispatch("timerColor", config.timerColor); - - return true; + return genericSet("timerColor", color, nosave); } export function setTimerOpacity( opacity: ConfigSchemas.TimerOpacity, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "timer opacity", - opacity, - ConfigSchemas.TimerOpacitySchema - ) - ) { - return false; - } - - config.timerOpacity = opacity; - saveToLocalStorage("timerOpacity", nosave); - ConfigEvent.dispatch("timerOpacity", config.timerOpacity); - - return true; + return genericSet("timerOpacity", opacity, nosave); } //key tips export function setKeyTips(keyTips: boolean, nosave?: boolean): boolean { - if (!isConfigValueValidBoolean("key tips", keyTips)) return false; - - config.showKeyTips = keyTips; - if (config.showKeyTips) { - $("footer .keyTips").removeClass("hidden"); - } else { - $("footer .keyTips").addClass("hidden"); - } - saveToLocalStorage("showKeyTips", nosave); - ConfigEvent.dispatch("showKeyTips", config.showKeyTips); - - return true; + return genericSet("showKeyTips", keyTips, nosave); } //mode @@ -1126,100 +1168,25 @@ export function setTimeConfig( time: ConfigSchemas.TimeConfig, nosave?: boolean ): boolean { - if (isConfigChangeBlocked()) return false; - - time = isNaN(time) || time < 0 ? getDefaultConfig().time : time; - if (!isConfigValueValid("time", time, ConfigSchemas.TimeConfigSchema)) - return false; - - if (!canSetConfigWithCurrentFunboxes("words", time, config.funbox)) { - return false; - } - - config.time = time; - saveToLocalStorage("time", nosave); - ConfigEvent.dispatch("time", config.time); - - return true; + return genericSet("time", time, nosave); } export function setQuoteLength( - len: ConfigSchemas.QuoteLength[] | ConfigSchemas.QuoteLength, - nosave?: boolean, - multipleMode?: boolean + len: ConfigSchemas.QuoteLengthConfig, + nosave?: boolean ): boolean { - if (isConfigChangeBlocked()) return false; - - if (Array.isArray(len)) { - if ( - !isConfigValueValid( - "quote length", - len, - ConfigSchemas.QuoteLengthConfigSchema - ) - ) { - return false; - } - - //config load - if (len.length === 1 && len[0] === -1) len = [1]; - config.quoteLength = len; - } else { - if ( - !isConfigValueValid("quote length", len, ConfigSchemas.QuoteLengthSchema) - ) { - return false; - } - - if (!Array.isArray(config.quoteLength)) config.quoteLength = []; - if (len === null || isNaN(len) || len < -3 || len > 3) { - len = 1; - } - len = parseInt(len.toString()) as ConfigSchemas.QuoteLength; - - if (len === -1) { - config.quoteLength = [0, 1, 2, 3]; - } else if (multipleMode && len >= 0) { - if (!config.quoteLength.includes(len)) { - config.quoteLength.push(len); - } else { - if (config.quoteLength.length > 1) { - config.quoteLength = config.quoteLength.filter((ql) => ql !== len); - } - } - } else { - config.quoteLength = [len]; - } - } - // if (!nosave) setMode("quote", nosave); - saveToLocalStorage("quoteLength", nosave); - ConfigEvent.dispatch("quoteLength", config.quoteLength); + return genericSet("quoteLength", len, nosave); +} - return true; +export function setQuoteLengthAll(nosave?: boolean): boolean { + return genericSet("quoteLength", [0, 1, 2, 3], nosave); } export function setWordCount( wordCount: ConfigSchemas.WordCount, nosave?: boolean ): boolean { - if (isConfigChangeBlocked()) return false; - - wordCount = - wordCount < 0 || wordCount > 100000 ? getDefaultConfig().words : wordCount; - - if (!isConfigValueValid("words", wordCount, ConfigSchemas.WordCountSchema)) - return false; - - if (!canSetConfigWithCurrentFunboxes("words", wordCount, config.funbox)) { - return false; - } - - config.words = wordCount; - - saveToLocalStorage("words", nosave); - ConfigEvent.dispatch("words", config.words); - - return true; + return genericSet("words", wordCount, nosave); } //caret @@ -1227,65 +1194,23 @@ export function setSmoothCaret( mode: ConfigSchemas.SmoothCaret, nosave?: boolean ): boolean { - if ( - !isConfigValueValid("smooth caret", mode, ConfigSchemas.SmoothCaretSchema) - ) { - return false; - } - config.smoothCaret = mode; - if (mode === "off") { - $("#caret").css("animation-name", "caretFlashHard"); - } else { - $("#caret").css("animation-name", "caretFlashSmooth"); - } - - saveToLocalStorage("smoothCaret", nosave); - ConfigEvent.dispatch("smoothCaret", config.smoothCaret); - - return true; + return genericSet("smoothCaret", mode, nosave); } export function setCodeUnindentOnBackspace( mode: boolean, nosave?: boolean ): boolean { - if (!isConfigValueValidBoolean("code unindent on backspace", mode)) { - return false; - } - config.codeUnindentOnBackspace = mode; - - saveToLocalStorage("codeUnindentOnBackspace", nosave); - ConfigEvent.dispatch( - "codeUnindentOnBackspace", - config.codeUnindentOnBackspace, - nosave - ); - return true; + return genericSet("codeUnindentOnBackspace", mode, nosave); } export function setStartGraphsAtZero(mode: boolean, nosave?: boolean): boolean { - if (!isConfigValueValidBoolean("start graphs at zero", mode)) { - return false; - } - - config.startGraphsAtZero = mode; - saveToLocalStorage("startGraphsAtZero", nosave); - ConfigEvent.dispatch("startGraphsAtZero", config.startGraphsAtZero); - - return true; + return genericSet("startGraphsAtZero", mode, nosave); } //linescroll export function setSmoothLineScroll(mode: boolean, nosave?: boolean): boolean { - if (!isConfigValueValidBoolean("smooth line scroll", mode)) { - return false; - } - - config.smoothLineScroll = mode; - saveToLocalStorage("smoothLineScroll", nosave); - ConfigEvent.dispatch("smoothLineScroll", config.smoothLineScroll); - - return true; + return genericSet("smoothLineScroll", mode, nosave); } //quick restart @@ -1293,21 +1218,7 @@ export function setQuickRestartMode( mode: ConfigSchemas.QuickRestart, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "quick restart mode", - mode, - ConfigSchemas.QuickRestartSchema - ) - ) { - return false; - } - - config.quickRestart = mode; - saveToLocalStorage("quickRestart", nosave); - ConfigEvent.dispatch("quickRestart", config.quickRestart); - - return true; + return genericSet("quickRestart", mode, nosave); } //font family @@ -1315,717 +1226,197 @@ export function setFontFamily( font: ConfigSchemas.FontFamily, nosave?: boolean ): boolean { - if (!isConfigValueValid("font family", font, ConfigSchemas.FontFamilySchema)) - return false; - - if (font === "") { - font = "roboto_mono"; - Notifications.add( - "Empty input received, reverted to the default font.", - 0, - { - customTitle: "Custom font", - } - ); - } - if (!font || !/^[0-9a-zA-Z_.\-#+()]+$/.test(font)) { - Notifications.add(`Invalid font name value: "${font}".`, -1, { - customTitle: "Custom font", - duration: 3, - }); - return false; - } - config.fontFamily = font; - document.documentElement.style.setProperty( - "--font", - `"${font.replace(/_/g, " ")}", "Roboto Mono", "Vazirmatn", monospace` - ); - saveToLocalStorage("fontFamily", nosave); - ConfigEvent.dispatch("fontFamily", config.fontFamily); - - return true; + return genericSet("fontFamily", font, nosave); } //freedom export function setFreedomMode(freedom: boolean, nosave?: boolean): boolean { - if (!isConfigValueValidBoolean("freedom mode", freedom)) return false; - - if (freedom === null || freedom === undefined) { - freedom = false; - } - config.freedomMode = freedom; - if (config.freedomMode && config.confidenceMode !== "off") { - config.confidenceMode = "off"; - } - saveToLocalStorage("freedomMode", nosave); - ConfigEvent.dispatch("freedomMode", config.freedomMode); - - return true; + return genericSet("freedomMode", freedom, nosave); } export function setConfidenceMode( cm: ConfigSchemas.ConfidenceMode, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "confidence mode", - cm, - ConfigSchemas.ConfidenceModeSchema - ) - ) { - return false; - } - - config.confidenceMode = cm; - if (config.confidenceMode !== "off") { - config.freedomMode = false; - config.stopOnError = "off"; - saveToLocalStorage("freedomMode", nosave); - saveToLocalStorage("stopOnError", nosave); - } - saveToLocalStorage("confidenceMode", nosave); - ConfigEvent.dispatch("confidenceMode", config.confidenceMode, nosave); - - return true; + return genericSet("confidenceMode", cm, nosave); } export function setIndicateTypos( value: ConfigSchemas.IndicateTypos, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "indicate typos", - value, - ConfigSchemas.IndicateTyposSchema - ) - ) { - return false; - } - - config.indicateTypos = value; - saveToLocalStorage("indicateTypos", nosave); - ConfigEvent.dispatch("indicateTypos", config.indicateTypos); - - return true; + return genericSet("indicateTypos", value, nosave); } export function setAutoSwitchTheme( boolean: boolean, nosave?: boolean ): boolean { - if (!isConfigValueValidBoolean("auto switch theme", boolean)) { - return false; - } - - boolean = boolean ?? getDefaultConfig().autoSwitchTheme; - config.autoSwitchTheme = boolean; - saveToLocalStorage("autoSwitchTheme", nosave); - ConfigEvent.dispatch("autoSwitchTheme", config.autoSwitchTheme); - - return true; + return genericSet("autoSwitchTheme", boolean, nosave); } export function setCustomTheme(boolean: boolean, nosave?: boolean): boolean { - if (!isConfigValueValidBoolean("custom theme", boolean)) return false; - - config.customTheme = boolean; - saveToLocalStorage("customTheme", nosave); - ConfigEvent.dispatch("customTheme", config.customTheme); - - return true; + return genericSet("customTheme", boolean, nosave); } export function setTheme( name: ConfigSchemas.ThemeName, nosave?: boolean ): boolean { - if (!isConfigValueValid("theme", name, ConfigSchemas.ThemeNameSchema)) - return false; - - config.theme = name; - if (config.customTheme) setCustomTheme(false); - saveToLocalStorage("theme", nosave); - ConfigEvent.dispatch("theme", config.theme); - - return true; + return genericSet("theme", name, nosave); } export function setThemeLight( name: ConfigSchemas.ThemeName, nosave?: boolean ): boolean { - if (!isConfigValueValid("theme light", name, ConfigSchemas.ThemeNameSchema)) - return false; - - config.themeLight = name; - saveToLocalStorage("themeLight", nosave); - ConfigEvent.dispatch("themeLight", config.themeLight, nosave); - - return true; + return genericSet("themeLight", name, nosave); } export function setThemeDark( name: ConfigSchemas.ThemeName, nosave?: boolean ): boolean { - if (!isConfigValueValid("theme dark", name, ConfigSchemas.ThemeNameSchema)) - return false; - - config.themeDark = name; - saveToLocalStorage("themeDark", nosave); - ConfigEvent.dispatch("themeDark", config.themeDark, nosave); - - return true; -} - -function setThemes( - theme: ConfigSchemas.ThemeName, - customState: boolean, - customThemeColors: ConfigSchemas.CustomThemeColors, - autoSwitchTheme: boolean, - nosave?: boolean -): boolean { - if (!isConfigValueValid("themes", theme, ConfigSchemas.ThemeNameSchema)) - return false; - - //@ts-expect-error config used to have 9 - if (customThemeColors.length === 9) { - //color missing - if (customState) { - Notifications.add( - "Missing sub alt color. Please edit it in the custom theme settings and save your changes.", - 0, - { - duration: 7, - } - ); - } - customThemeColors.splice(4, 0, "#000000"); - } - - config.customThemeColors = customThemeColors; - config.theme = theme; - config.customTheme = customState; - config.autoSwitchTheme = autoSwitchTheme; - saveToLocalStorage("theme", nosave); - ConfigEvent.dispatch("setThemes", customState); - - return true; + return genericSet("themeDark", name, nosave); } export function setRandomTheme( val: ConfigSchemas.RandomTheme, nosave?: boolean ): boolean { - if ( - !isConfigValueValid("random theme", val, ConfigSchemas.RandomThemeSchema) - ) { - return false; - } - - if (val === "custom") { - if (!isAuthenticated()) { - config.randomTheme = val; - return false; - } - if (!DB.getSnapshot()) return true; - if (DB.getSnapshot()?.customThemes?.length === 0) { - Notifications.add("You need to create a custom theme first", 0); - config.randomTheme = "off"; - return false; - } - } - - config.randomTheme = val; - saveToLocalStorage("randomTheme", nosave); - ConfigEvent.dispatch("randomTheme", config.randomTheme); - - return true; + return genericSet("randomTheme", val, nosave); } export function setBritishEnglish(val: boolean, nosave?: boolean): boolean { - if (isConfigChangeBlocked()) return false; - - if (!isConfigValueValidBoolean("british english", val)) return false; - - if (!val) { - val = false; - } - config.britishEnglish = val; - saveToLocalStorage("britishEnglish", nosave); - ConfigEvent.dispatch("britishEnglish", config.britishEnglish); - - return true; + return genericSet("britishEnglish", val, nosave); } export function setLazyMode(val: boolean, nosave?: boolean): boolean { - if (isConfigChangeBlocked()) return false; - - if (!isConfigValueValidBoolean("lazy mode", val)) return false; - - if (!val) { - val = false; - } - config.lazyMode = val; - saveToLocalStorage("lazyMode", nosave); - ConfigEvent.dispatch("lazyMode", config.lazyMode, nosave); - - return true; + return genericSet("lazyMode", val, nosave); } export function setCustomThemeColors( colors: ConfigSchemas.CustomThemeColors, nosave?: boolean ): boolean { - // migrate existing configs missing sub alt color - // @ts-expect-error legacy configs - if (colors.length === 9) { - //color missing - Notifications.add( - "Missing sub alt color. Please edit it in the custom theme settings and save your changes.", - 0, - { - duration: 7, - } - ); - colors.splice(4, 0, "#000000"); - } - - if ( - !isConfigValueValid( - "custom theme colors", - colors, - ConfigSchemas.CustomThemeColorsSchema - ) - ) { - return false; - } - - if (colors !== undefined) { - config.customThemeColors = colors; - // ThemeController.set("custom"); - // applyCustomThemeColors(); - } - saveToLocalStorage("customThemeColors", nosave); - ConfigEvent.dispatch("customThemeColors", config.customThemeColors, nosave); - - return true; + return genericSet("customThemeColors", colors, nosave); } export function setLanguage(language: Language, nosave?: boolean): boolean { - if (isConfigChangeBlocked()) return false; - - if (!isConfigValueValid("language", language, LanguageSchema)) return false; - - config.language = language; - saveToLocalStorage("language", nosave); - ConfigEvent.dispatch("language", config.language); - - return true; + return genericSet("language", language, nosave); } export function setMonkey(monkey: boolean, nosave?: boolean): boolean { - if (!isConfigValueValidBoolean("monkey", monkey)) return false; - - config.monkey = monkey; - saveToLocalStorage("monkey", nosave); - ConfigEvent.dispatch("monkey", config.monkey); - - return true; + return genericSet("monkey", monkey, nosave); } export function setKeymapMode( mode: ConfigSchemas.KeymapMode, nosave?: boolean ): boolean { - if ( - !isConfigValueValid("keymap mode", mode, ConfigSchemas.KeymapModeSchema) - ) { - return false; - } - - $(".activeKey").removeClass("activeKey"); - $(".keymapKey").attr("style", ""); - config.keymapMode = mode; - saveToLocalStorage("keymapMode", nosave); - ConfigEvent.dispatch("keymapMode", config.keymapMode, nosave); - - return true; + return genericSet("keymapMode", mode, nosave); } export function setKeymapLegendStyle( style: ConfigSchemas.KeymapLegendStyle, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "keymap legend style", - style, - ConfigSchemas.KeymapLegendStyleSchema - ) - ) { - return false; - } - - // Remove existing styles - const keymapLegendStyles = ["lowercase", "uppercase", "blank", "dynamic"]; - keymapLegendStyles.forEach((name) => { - $(".keymapLegendStyle").removeClass(name); - }); - - style = style || "lowercase"; - - // Mutate the keymap in the DOM, if it exists. - // 1. Remove everything - $(".keymapKey > .letter").css("display", ""); - $(".keymapKey > .letter").css("text-transform", ""); - - // 2. Append special styles onto the DOM elements - if (style === "uppercase") { - $(".keymapKey > .letter").css("text-transform", "capitalize"); - } - if (style === "blank") { - $(".keymapKey > .letter").css("display", "none"); - } - - // Update and save to cookie for persistence - $(".keymapLegendStyle").addClass(style); - config.keymapLegendStyle = style; - saveToLocalStorage("keymapLegendStyle", nosave); - ConfigEvent.dispatch("keymapLegendStyle", config.keymapLegendStyle); - - return true; + return genericSet("keymapLegendStyle", style, nosave); } export function setKeymapStyle( style: ConfigSchemas.KeymapStyle, nosave?: boolean ): boolean { - if ( - !isConfigValueValid("keymap style", style, ConfigSchemas.KeymapStyleSchema) - ) { - return false; - } - - style = style || "staggered"; - config.keymapStyle = style; - saveToLocalStorage("keymapStyle", nosave); - ConfigEvent.dispatch("keymapStyle", config.keymapStyle); - - return true; + return genericSet("keymapStyle", style, nosave); } export function setKeymapLayout( layout: ConfigSchemas.KeymapLayout, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "keymap layout", - layout, - ConfigSchemas.KeymapLayoutSchema - ) - ) - return false; - - config.keymapLayout = layout; - saveToLocalStorage("keymapLayout", nosave); - ConfigEvent.dispatch("keymapLayout", config.keymapLayout); - - return true; + return genericSet("keymapLayout", layout, nosave); } export function setKeymapShowTopRow( show: ConfigSchemas.KeymapShowTopRow, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "keymapShowTopRow", - show, - ConfigSchemas.KeymapShowTopRowSchema - ) - ) { - return false; - } - - config.keymapShowTopRow = show; - saveToLocalStorage("keymapShowTopRow", nosave); - ConfigEvent.dispatch("keymapShowTopRow", config.keymapShowTopRow); - - return true; + return genericSet("keymapShowTopRow", show, nosave); } export function setKeymapSize( keymapSize: ConfigSchemas.KeymapSize, nosave?: boolean ): boolean { - //auto-fix values to avoid validation errors - if (keymapSize < 0.5) keymapSize = 0.5; - if (keymapSize > 3.5) keymapSize = 3.5; - keymapSize = roundTo1(keymapSize); - - if ( - !isConfigValueValid( - "keymap size", - keymapSize, - ConfigSchemas.KeymapSizeSchema - ) - ) { - return false; - } - - config.keymapSize = keymapSize; - - $("#keymap").css("zoom", keymapSize); - - saveToLocalStorage("keymapSize", nosave); - ConfigEvent.dispatch("keymapSize", config.keymapSize, nosave); - - // trigger a resize event to update the layout - handled in ui.ts:108 - $(window).trigger("resize"); - - return true; + return genericSet("keymapSize", keymapSize, nosave); } export function setLayout( layout: ConfigSchemas.Layout, nosave?: boolean ): boolean { - if (isConfigChangeBlocked()) return false; - - if (!isConfigValueValid("layout", layout, ConfigSchemas.LayoutSchema)) - return false; - - config.layout = layout; - saveToLocalStorage("layout", nosave); - ConfigEvent.dispatch("layout", config.layout, nosave); - - return true; + return genericSet("layout", layout, nosave); } -// export function setSavedLayout(layout: string, nosave?: boolean): boolean { -// if (layout === null || layout === undefined) { -// layout = "qwerty"; -// } -// config.savedLayout = layout; -// setLayout(layout, nosave); - -// return true; -// } - export function setFontSize( fontSize: ConfigSchemas.FontSize, nosave?: boolean ): boolean { - if (fontSize < 0) { - fontSize = 1; - } - - if ( - !isConfigValueValid("font size", fontSize, ConfigSchemas.FontSizeSchema) - ) { - return false; - } - - config.fontSize = fontSize; - - $("#caret, #paceCaret, #liveStatsMini, #typingTest, #wordsInput").css( - "fontSize", - fontSize + "rem" - ); - - saveToLocalStorage("fontSize", nosave); - ConfigEvent.dispatch("fontSize", config.fontSize, nosave); - - // trigger a resize event to update the layout - handled in ui.ts:108 - if (!nosave) $(window).trigger("resize"); - - return true; + return genericSet("fontSize", fontSize, nosave); } export function setMaxLineWidth( maxLineWidth: ConfigSchemas.MaxLineWidth, nosave?: boolean ): boolean { - if (maxLineWidth < 20 && maxLineWidth !== 0) { - maxLineWidth = 20; - } - if (maxLineWidth > 1000) { - maxLineWidth = 1000; - } - - if ( - !isConfigValueValid( - "max line width", - maxLineWidth, - ConfigSchemas.MaxLineWidthSchema - ) - ) { - return false; - } - - config.maxLineWidth = maxLineWidth; - - saveToLocalStorage("maxLineWidth", nosave); - ConfigEvent.dispatch("maxLineWidth", config.maxLineWidth, nosave); - - // trigger a resize event to update the layout - handled in ui.ts:108 - $(window).trigger("resize"); - - return true; + return genericSet("maxLineWidth", maxLineWidth, nosave); } export function setCustomBackground( value: ConfigSchemas.CustomBackground, nosave?: boolean ): boolean { - value = value.trim(); - if ( - !isConfigValueValid( - "custom background", - value, - ConfigSchemas.CustomBackgroundSchema - ) - ) - return false; - - config.customBackground = value; - saveToLocalStorage("customBackground", nosave); - ConfigEvent.dispatch("customBackground", config.customBackground); - - return true; + return genericSet("customBackground", value, nosave); } export function setCustomLayoutfluid( value: ConfigSchemas.CustomLayoutFluid, nosave?: boolean ): boolean { - if (isConfigChangeBlocked()) return false; - - // Remove duplicates - const deduped = Array.from(new Set(value)); - if ( - !isConfigValueValid( - "layoutfluid", - deduped, - ConfigSchemas.CustomLayoutFluidSchema - ) - ) { - return false; - } - - config.customLayoutfluid = deduped; - saveToLocalStorage("customLayoutfluid", nosave); - ConfigEvent.dispatch("customLayoutfluid", config.customLayoutfluid); - - return true; + return genericSet("customLayoutfluid", value, nosave); } export function setCustomPolyglot( value: ConfigSchemas.CustomPolyglot, nosave?: boolean ): boolean { - if (isConfigChangeBlocked()) return false; - - // remove duplicates - const deduped = Array.from(new Set(value)); - if ( - !isConfigValueValid( - "customPolyglot", - deduped, - ConfigSchemas.CustomPolyglotSchema - ) - ) - return false; - - config.customPolyglot = deduped; - saveToLocalStorage("customPolyglot", nosave); - ConfigEvent.dispatch("customPolyglot", config.customPolyglot); - - return true; + return genericSet("customPolyglot", value, nosave); } export function setCustomBackgroundSize( value: ConfigSchemas.CustomBackgroundSize, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "custom background size", - value, - ConfigSchemas.CustomBackgroundSizeSchema - ) - ) { - return false; - } - - config.customBackgroundSize = value; - saveToLocalStorage("customBackgroundSize", nosave); - ConfigEvent.dispatch("customBackgroundSize", config.customBackgroundSize); - - return true; + return genericSet("customBackgroundSize", value, nosave); } export function setCustomBackgroundFilter( array: ConfigSchemas.CustomBackgroundFilter, nosave?: boolean ): boolean { - // @ts-expect-error this used to be 5 - // need to convert existing configs using five values down to four - if (array.length === 5) { - array = [array[0], array[1], array[2], array[3]]; - } - - if ( - !isConfigValueValid( - "custom background filter", - array, - ConfigSchemas.CustomBackgroundFilterSchema - ) - ) { - return false; - } - - config.customBackgroundFilter = array; - saveToLocalStorage("customBackgroundFilter", nosave); - ConfigEvent.dispatch("customBackgroundFilter", config.customBackgroundFilter); - - return true; + return genericSet("customBackgroundFilter", array, nosave); } + export function setMonkeyPowerLevel( level: ConfigSchemas.MonkeyPowerLevel, nosave?: boolean ): boolean { - if ( - !isConfigValueValid( - "monkey power level", - level, - ConfigSchemas.MonkeyPowerLevelSchema - ) - ) { - return false; - } - config.monkeyPowerLevel = level; - saveToLocalStorage("monkeyPowerLevel", nosave); - ConfigEvent.dispatch("monkeyPowerLevel", config.monkeyPowerLevel); - - return true; + return genericSet("monkeyPowerLevel", level, nosave); } export function setBurstHeatmap(value: boolean, nosave?: boolean): boolean { - if (!isConfigValueValidBoolean("burst heatmap", value)) return false; - - if (!value) { - value = false; - } - config.burstHeatmap = value; - saveToLocalStorage("burstHeatmap", nosave); - ConfigEvent.dispatch("burstHeatmap", config.burstHeatmap); - - return true; + return genericSet("burstHeatmap", value, nosave); } export async function apply( @@ -2043,97 +1434,10 @@ export async function apply( } }); if (configObj !== undefined && configObj !== null) { - setAds(configObj.ads, true); - setThemeLight(configObj.themeLight, true); - setThemeDark(configObj.themeDark, true); - setThemes( - configObj.theme, - configObj.customTheme, - configObj.customThemeColors, - configObj.autoSwitchTheme, - true - ); - setCustomLayoutfluid(configObj.customLayoutfluid, true); - setCustomPolyglot(configObj.customPolyglot, true); - setCustomBackground(configObj.customBackground, true); - setCustomBackgroundSize(configObj.customBackgroundSize, true); - setCustomBackgroundFilter(configObj.customBackgroundFilter, true); - setQuickRestartMode(configObj.quickRestart, true); - setKeyTips(configObj.showKeyTips, true); - setTimeConfig(configObj.time, true); - setQuoteLength(configObj.quoteLength, true); - setWordCount(configObj.words, true); - setLanguage(configObj.language, true); - setLayout(configObj.layout, true); - setFontSize(configObj.fontSize, true); - setMaxLineWidth(configObj.maxLineWidth, true); - setFreedomMode(configObj.freedomMode, true); - setCaretStyle(configObj.caretStyle, true); - setPaceCaretStyle(configObj.paceCaretStyle, true); - setDifficulty(configObj.difficulty, true); - setBlindMode(configObj.blindMode, true); - setQuickEnd(configObj.quickEnd, true); - setFlipTestColors(configObj.flipTestColors, true); - setColorfulMode(configObj.colorfulMode, true); - setConfidenceMode(configObj.confidenceMode, true); - setIndicateTypos(configObj.indicateTypos, true); - setTimerStyle(configObj.timerStyle, true); - setLiveSpeedStyle(configObj.liveSpeedStyle, true); - setLiveAccStyle(configObj.liveAccStyle, true); - setLiveBurstStyle(configObj.liveBurstStyle, true); - setTimerColor(configObj.timerColor, true); - setTimerOpacity(configObj.timerOpacity, true); - setKeymapMode(configObj.keymapMode, true); - setKeymapStyle(configObj.keymapStyle, true); - setKeymapLegendStyle(configObj.keymapLegendStyle, true); - setKeymapLayout(configObj.keymapLayout, true); - setKeymapShowTopRow(configObj.keymapShowTopRow, true); - setKeymapSize(configObj.keymapSize, true); - setFontFamily(configObj.fontFamily, true); - setSmoothCaret(configObj.smoothCaret, true); - setCodeUnindentOnBackspace(configObj.codeUnindentOnBackspace, true); - setSmoothLineScroll(configObj.smoothLineScroll, true); - setAlwaysShowDecimalPlaces(configObj.alwaysShowDecimalPlaces, true); - setAlwaysShowWordsHistory(configObj.alwaysShowWordsHistory, true); - setSingleListCommandLine(configObj.singleListCommandLine, true); - setCapsLockWarning(configObj.capsLockWarning, true); - setPlaySoundOnError(configObj.playSoundOnError, true); - setPlaySoundOnClick(configObj.playSoundOnClick, true); - setSoundVolume(configObj.soundVolume, true); - setStopOnError(configObj.stopOnError, true); - setFavThemes(configObj.favThemes, true); - setFunbox(configObj.funbox, true); - setRandomTheme(configObj.randomTheme, true); - setShowAllLines(configObj.showAllLines, true); - setShowOutOfFocusWarning(configObj.showOutOfFocusWarning, true); - setPaceCaret(configObj.paceCaret, true); - setPaceCaretCustomSpeed(configObj.paceCaretCustomSpeed, true); - setRepeatedPace(configObj.repeatedPace, true); - setAccountChart(configObj.accountChart, true); - setMinBurst(configObj.minBurst, true); - setMinBurstCustomSpeed(configObj.minBurstCustomSpeed, true); - setMinWpm(configObj.minWpm, true); - setMinWpmCustomSpeed(configObj.minWpmCustomSpeed, true); - setMinAcc(configObj.minAcc, true); - setMinAccCustom(configObj.minAccCustom, true); - setHighlightMode(configObj.highlightMode, true); - setTypingSpeedUnit(configObj.typingSpeedUnit, true); - setHideExtraLetters(configObj.hideExtraLetters, true); - setStartGraphsAtZero(configObj.startGraphsAtZero, true); - setStrictSpace(configObj.strictSpace, true); - setOppositeShiftMode(configObj.oppositeShiftMode, true); - setMode(configObj.mode, true); - setNumbers(configObj.numbers, true); - setPunctuation(configObj.punctuation, true); - setMonkey(configObj.monkey, true); - setRepeatQuotes(configObj.repeatQuotes, true); - setMonkeyPowerLevel(configObj.monkeyPowerLevel, true); - setBurstHeatmap(configObj.burstHeatmap, true); - setBritishEnglish(configObj.britishEnglish, true); - setLazyMode(configObj.lazyMode, true); - setShowAverage(configObj.showAverage, true); - setTapeMode(configObj.tapeMode, true); - setTapeMargin(configObj.tapeMargin, true); + for (const configKey of typedKeys(configObj)) { + const configValue = configObj[configKey]; + genericSet(configKey, configValue, true); + } ConfigEvent.dispatch( "configApplied", @@ -2205,3 +1509,11 @@ const { promise: loadPromise, resolve: loadDone } = promiseWithResolvers(); export { loadPromise }; export default config; +export const __testing = { + configMetadata, + replaceConfig: (setConfig: Partial): void => { + config = { ...getDefaultConfig(), ...setConfig }; + configToSend = {} as Config; + }, + getConfig: () => config, +}; diff --git a/frontend/src/ts/controllers/theme-controller.ts b/frontend/src/ts/controllers/theme-controller.ts index 3ff1c8f5e729..4ae10593d3f7 100644 --- a/frontend/src/ts/controllers/theme-controller.ts +++ b/frontend/src/ts/controllers/theme-controller.ts @@ -415,22 +415,15 @@ window } }); +let ignoreConfigEvent = false; + ConfigEvent.subscribe(async (eventKey, eventValue, nosave) => { - if (eventKey === "randomTheme") { - void changeThemeList(); - } - if (eventKey === "customTheme") { - (eventValue as boolean) ? await set("custom") : await set(Config.theme); - } - if (eventKey === "customThemeColors") { - nosave ? preview("custom") : await set("custom"); - } - if (eventKey === "theme") { - await clearRandom(); - await clearPreview(false); - await set(eventValue as string); + if (eventKey === "fullConfigChange") { + ignoreConfigEvent = true; } - if (eventKey === "setThemes") { + if (eventKey === "fullConfigChangeFinished") { + ignoreConfigEvent = false; + await clearRandom(); await clearPreview(false); if (Config.autoSwitchTheme) { @@ -440,13 +433,32 @@ ConfigEvent.subscribe(async (eventKey, eventValue, nosave) => { await set(Config.themeLight, true); } } else { - if (eventValue as boolean) { + if (Config.customTheme) { await set("custom"); } else { await set(Config.theme); } } } + + // this is here to prevent calling set / preview multiple times during a full config loading + // once the full config is loaded, we can apply everything once + if (ignoreConfigEvent) return; + + if (eventKey === "randomTheme") { + void changeThemeList(); + } + if (eventKey === "customTheme") { + (eventValue as boolean) ? await set("custom") : await set(Config.theme); + } + if (eventKey === "customThemeColors") { + nosave ? preview("custom") : await set("custom"); + } + if (eventKey === "theme") { + await clearRandom(); + await clearPreview(false); + await set(eventValue as string); + } if (eventKey === "randomTheme" && eventValue === "off") await clearRandom(); if (eventKey === "customBackground") applyCustomBackground(); if (eventKey === "customBackgroundSize") applyCustomBackgroundSize(); diff --git a/frontend/src/ts/db.ts b/frontend/src/ts/db.ts index ec9ae73eb1d2..c6174292b301 100644 --- a/frontend/src/ts/db.ts +++ b/frontend/src/ts/db.ts @@ -930,7 +930,7 @@ export async function updateLbMemory( } } -export async function saveConfig(config: Config): Promise { +export async function saveConfig(config: Partial): Promise { if (isAuthenticated()) { const response = await Ape.configs.save({ body: config }); if (response.status !== 200) { diff --git a/frontend/src/ts/elements/keymap.ts b/frontend/src/ts/elements/keymap.ts index efb87d07e9ad..0c8ca511c6b6 100644 --- a/frontend/src/ts/elements/keymap.ts +++ b/frontend/src/ts/elements/keymap.ts @@ -595,8 +595,40 @@ ConfigEvent.subscribe((eventKey, newValue) => { void refresh(); } if (eventKey === "keymapMode") { + $(".activeKey").removeClass("activeKey"); + $(".keymapKey").attr("style", ""); newValue === "off" ? hide() : show(); } + if (eventKey === "keymapSize") { + $("#keymap").css("zoom", newValue as string); + } + if (eventKey === "keymapLegendStyle") { + let style = newValue as string; + + // Remove existing styles + const keymapLegendStyles = ["lowercase", "uppercase", "blank", "dynamic"]; + keymapLegendStyles.forEach((name) => { + $(".keymapLegendStyle").removeClass(name); + }); + + style = style || "lowercase"; + + // Mutate the keymap in the DOM, if it exists. + // 1. Remove everything + $(".keymapKey > .letter").css("display", ""); + $(".keymapKey > .letter").css("text-transform", ""); + + // 2. Append special styles onto the DOM elements + if (style === "uppercase") { + $(".keymapKey > .letter").css("text-transform", "capitalize"); + } + if (style === "blank") { + $(".keymapKey > .letter").css("display", "none"); + } + + // Update and save to cookie for persistence + $(".keymapLegendStyle").addClass(style); + } }); KeymapEvent.subscribe((mode, key, correct) => { diff --git a/frontend/src/ts/modals/mobile-test-config.ts b/frontend/src/ts/modals/mobile-test-config.ts index 049224d55e4a..7ceac9dad97d 100644 --- a/frontend/src/ts/modals/mobile-test-config.ts +++ b/frontend/src/ts/modals/mobile-test-config.ts @@ -6,7 +6,10 @@ import * as CustomTestDurationPopup from "./custom-test-duration"; import * as QuoteSearchModal from "./quote-search"; import * as CustomTextPopup from "./custom-text"; import AnimatedModal from "../utils/animated-modal"; -import { QuoteLength } from "@monkeytype/contracts/schemas/configs"; +import { + QuoteLength, + QuoteLengthConfig, +} from "@monkeytype/contracts/schemas/configs"; import { Mode } from "@monkeytype/contracts/schemas/shared"; function update(): void { @@ -126,22 +129,25 @@ async function setup(modalEl: HTMLElement): Promise { for (const button of quoteGroupButtons) { button.addEventListener("click", (e) => { const target = e.currentTarget as HTMLElement; - const len = parseInt(target.getAttribute("data-quoteLength") ?? "0", 10); + const len = parseInt( + target.getAttribute("data-quoteLength") ?? "0", + 10 + ) as QuoteLength; if (len === -2) { void QuoteSearchModal.show({ modalChain: modal, }); } else { - let newVal: number[] | number = len; - if (len === -1) { - newVal = [0, 1, 2, 3]; + let arr: QuoteLengthConfig = []; + + if ((e as MouseEvent).shiftKey) { + arr = [...Config.quoteLength, len]; + } else { + arr = [len]; } - UpdateConfig.setQuoteLength( - newVal as QuoteLength | QuoteLength[], - false, - (e as MouseEvent).shiftKey - ); + + UpdateConfig.setQuoteLength(arr, false); ManualRestart.set(); TestLogic.restart(); } diff --git a/frontend/src/ts/modals/quote-search.ts b/frontend/src/ts/modals/quote-search.ts index 8267e6044f12..04880f0b6068 100644 --- a/frontend/src/ts/modals/quote-search.ts +++ b/frontend/src/ts/modals/quote-search.ts @@ -21,7 +21,6 @@ import * as TestState from "../test/test-state"; import AnimatedModal, { ShowOptions } from "../utils/animated-modal"; import * as TestLogic from "../test/test-logic"; import { createErrorMessage } from "../utils/misc"; -import { QuoteLength } from "@monkeytype/contracts/schemas/configs"; const searchServiceCache: Record> = {}; @@ -326,7 +325,7 @@ function apply(val: number): void { ); } if (val !== null && !isNaN(val) && val >= 0) { - UpdateConfig.setQuoteLength(-2 as QuoteLength, false); + UpdateConfig.setQuoteLength([-2], false); TestState.setSelectedQuoteId(val); ManualRestart.set(); } else { diff --git a/frontend/src/ts/pages/account.ts b/frontend/src/ts/pages/account.ts index 66f40a4b854d..a4c05b585464 100644 --- a/frontend/src/ts/pages/account.ts +++ b/frontend/src/ts/pages/account.ts @@ -39,6 +39,7 @@ import { findLineByLeastSquares } from "../utils/numbers"; import defaultResultFilters from "../constants/default-result-filters"; import { SnapshotResult } from "../constants/default-snapshot"; import Ape from "../ape"; +import { AccountChart } from "@monkeytype/contracts/schemas/configs"; let filterDebug = false; //toggle filterdebug @@ -1122,25 +1123,25 @@ function sortAndRefreshHistory( } $(".pageAccount button.toggleResultsOnChart").on("click", () => { - const newValue = Config.accountChart; + const newValue = [...Config.accountChart] as AccountChart; newValue[0] = newValue[0] === "on" ? "off" : "on"; UpdateConfig.setAccountChart(newValue); }); $(".pageAccount button.toggleAccuracyOnChart").on("click", () => { - const newValue = Config.accountChart; + const newValue = [...Config.accountChart] as AccountChart; newValue[1] = newValue[1] === "on" ? "off" : "on"; UpdateConfig.setAccountChart(newValue); }); $(".pageAccount button.toggleAverage10OnChart").on("click", () => { - const newValue = Config.accountChart; + const newValue = [...Config.accountChart] as AccountChart; newValue[2] = newValue[2] === "on" ? "off" : "on"; UpdateConfig.setAccountChart(newValue); }); $(".pageAccount button.toggleAverage100OnChart").on("click", () => { - const newValue = Config.accountChart; + const newValue = [...Config.accountChart] as AccountChart; newValue[3] = newValue[3] === "on" ? "off" : "on"; UpdateConfig.setAccountChart(newValue); }); diff --git a/frontend/src/ts/test/caret.ts b/frontend/src/ts/test/caret.ts index 200a2d1fb2d2..89b3a008e603 100644 --- a/frontend/src/ts/test/caret.ts +++ b/frontend/src/ts/test/caret.ts @@ -270,11 +270,26 @@ export async function updatePosition(noAnim = false): Promise { } } +function updateStyle(): void { + caret.style.width = ""; + caret.classList.remove( + ...["off", "default", "underline", "outline", "block", "carrot", "banana"] + ); + caret.classList.add(Config.caretStyle); +} + subscribe((eventKey) => { if (eventKey === "caretStyle") { - caret.style.width = ""; + updateStyle(); void updatePosition(true); } + if (eventKey === "smoothCaret") { + if (Config.smoothCaret === "off") { + caret.style.animationName = "caretFlashHard"; + } else { + caret.style.animationName = "caretFlashSmooth"; + } + } }); export function show(noAnim = false): void { diff --git a/frontend/src/ts/test/pace-caret.ts b/frontend/src/ts/test/pace-caret.ts index bff3c9988222..a57ca1867ae6 100644 --- a/frontend/src/ts/test/pace-caret.ts +++ b/frontend/src/ts/test/pace-caret.ts @@ -322,6 +322,21 @@ export function start(): void { void update(performance.now() + (settings?.spc ?? 0) * 1000); } +function updateStyle(): void { + const paceCaret = $("#paceCaret"); + paceCaret.removeClass([ + "off", + "default", + "underline", + "outline", + "block", + "carrot", + "banana", + ]); + paceCaret.addClass(Config.paceCaretStyle); +} + ConfigEvent.subscribe((eventKey) => { if (eventKey === "paceCaret") void init(); + if (eventKey === "paceCaretStyle") updateStyle(); }); diff --git a/frontend/src/ts/test/test-config.ts b/frontend/src/ts/test/test-config.ts index 97216774b600..3f5c2950e239 100644 --- a/frontend/src/ts/test/test-config.ts +++ b/frontend/src/ts/test/test-config.ts @@ -7,6 +7,7 @@ import Config from "../config"; import * as ConfigEvent from "../observables/config-event"; import * as ActivePage from "../states/active-page"; import { applyReducedMotion } from "../utils/misc"; +import { areUnsortedArraysEqual } from "../utils/arrays"; export function show(): void { $("#testConfig").removeClass("invisible"); @@ -225,11 +226,18 @@ export function updateExtras(key: string, value: ConfigValue): void { ).addClass("active"); } else if (key === "quoteLength") { $("#testConfig .quoteLength .textButton").removeClass("active"); - (value as QuoteLength[]).forEach((ql) => { - $( - "#testConfig .quoteLength .textButton[quoteLength='" + ql + "']" - ).addClass("active"); - }); + + if (areUnsortedArraysEqual(value as QuoteLength[], [0, 1, 2, 3])) { + $("#testConfig .quoteLength .textButton[quoteLength='-1']").addClass( + "active" + ); + } else { + (value as QuoteLength[]).forEach((ql) => { + $( + "#testConfig .quoteLength .textButton[quoteLength='" + ql + "']" + ).addClass("active"); + }); + } } else if (key === "numbers") { if (value === false) { $("#testConfig .numbersMode.textButton").removeClass("active"); diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index 3c810f2c112d..7a7ec4c67678 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -58,7 +58,10 @@ import * as KeymapEvent from "../observables/keymap-event"; import * as LayoutfluidFunboxTimer from "../test/funbox/layoutfluid-funbox-timer"; import * as ArabicLazyMode from "../states/arabic-lazy-mode"; import Format from "../utils/format"; -import { QuoteLength } from "@monkeytype/contracts/schemas/configs"; +import { + QuoteLength, + QuoteLengthConfig, +} from "@monkeytype/contracts/schemas/configs"; import { Mode } from "@monkeytype/contracts/schemas/shared"; import { CompletedEvent, @@ -461,7 +464,7 @@ export async function init(): Promise { if (Config.mode === "quote") { if (Config.quoteLength.includes(-3) && !isAuthenticated()) { - UpdateConfig.setQuoteLength(-1); + UpdateConfig.setQuoteLengthAll(); } } @@ -1442,14 +1445,20 @@ $(".pageTest").on("click", "#testConfig .time .textButton", (e) => { $(".pageTest").on("click", "#testConfig .quoteLength .textButton", (e) => { if (TestUI.testRestarting) return; - let len: QuoteLength | QuoteLength[] = parseInt( + const len = parseInt( $(e.currentTarget).attr("quoteLength") ?? "1" ) as QuoteLength; + if (len !== -2) { - if (len === -1) { - len = [0, 1, 2, 3]; + let arr: QuoteLengthConfig = []; + + if (e.shiftKey) { + arr = [...Config.quoteLength, len]; + } else { + arr = [len]; } - if (UpdateConfig.setQuoteLength(len, false, e.shiftKey)) { + + if (UpdateConfig.setQuoteLength(arr, false)) { ManualRestart.set(); restart(); } diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index 55bac876c535..081022115c78 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -1756,4 +1756,7 @@ ConfigEvent.subscribe((key, value) => { if (key === "timerColor") { updateLiveStatsColor(value as TimerColor); } + if (key === "showOutOfFocusWarning" && value === false) { + OutOfFocus.hide(); + } }); diff --git a/frontend/src/ts/test/words-generator.ts b/frontend/src/ts/test/words-generator.ts index a2af3e80dda9..b3731697f13f 100644 --- a/frontend/src/ts/test/words-generator.ts +++ b/frontend/src/ts/test/words-generator.ts @@ -525,7 +525,7 @@ async function getQuoteWordList( TestState.selectedQuoteId ); if (targetQuote === undefined) { - UpdateConfig.setQuoteLength(-1); + UpdateConfig.setQuoteLengthAll(); throw new WordGenError( `Quote ${TestState.selectedQuoteId} does not exist` ); @@ -536,14 +536,14 @@ async function getQuoteWordList( Config.language ); if (randomQuote === null) { - UpdateConfig.setQuoteLength(-1); + UpdateConfig.setQuoteLengthAll(); throw new WordGenError("No favorite quotes found"); } rq = randomQuote; } else { const randomQuote = QuotesController.getRandomQuote(); if (randomQuote === null) { - UpdateConfig.setQuoteLength(-1); + UpdateConfig.setQuoteLengthAll(); throw new WordGenError("No quotes found for selected quote length"); } rq = randomQuote; diff --git a/frontend/src/ts/ui.ts b/frontend/src/ts/ui.ts index a593fc15cfb8..9b2f1c5643a1 100644 --- a/frontend/src/ts/ui.ts +++ b/frontend/src/ts/ui.ts @@ -117,6 +117,28 @@ $(window).on("resize", () => { debouncedEvent(); }); -ConfigEvent.subscribe((eventKey) => { +ConfigEvent.subscribe((eventKey, value) => { if (eventKey === "quickRestart") updateKeytips(); + if (eventKey === "showKeyTips") { + if (Config.showKeyTips) { + $("footer .keyTips").removeClass("hidden"); + } else { + $("footer .keyTips").addClass("hidden"); + } + } + if (eventKey === "fontSize") { + $("#caret, #paceCaret, #liveStatsMini, #typingTest, #wordsInput").css( + "fontSize", + value + "rem" + ); + } + if (eventKey === "fontFamily") { + document.documentElement.style.setProperty( + "--font", + `"${(value as string).replace( + /_/g, + " " + )}", "Roboto Mono", "Vazirmatn", monospace` + ); + } }); diff --git a/frontend/src/ts/utils/config.ts b/frontend/src/ts/utils/config.ts index 0ad6a97edb5f..9f805c3c25ab 100644 --- a/frontend/src/ts/utils/config.ts +++ b/frontend/src/ts/utils/config.ts @@ -156,5 +156,47 @@ export function replaceLegacyValues( configObj.fontSize = newValue; } + if ( + Array.isArray(configObj.accountChart) && + configObj.accountChart.length !== 4 + ) { + configObj.accountChart = ["on", "on", "on", "on"]; + } + + if ( + typeof configObj.minAccCustom === "number" && + configObj.minAccCustom > 100 + ) { + configObj.minAccCustom = 100; + } + + if ( + Array.isArray(configObj.customThemeColors) && + //@ts-expect-error legacy configs + configObj.customThemeColors.length === 9 + ) { + // migrate existing configs missing sub alt color + const colors = configObj.customThemeColors; + colors.splice(4, 0, "#000000"); + configObj.customThemeColors = colors; + } + + if ( + Array.isArray(configObj.customBackgroundFilter) && + //@ts-expect-error legacy configs + configObj.customBackgroundFilter.length === 5 + ) { + const arr = configObj.customBackgroundFilter; + configObj.customBackgroundFilter = [arr[0], arr[1], arr[2], arr[3]]; + } + + if (typeof configObj.quoteLength === "number") { + if (configObj.quoteLength === -1) { + configObj.quoteLength = [0, 1, 2, 3]; + } else { + configObj.quoteLength = [configObj.quoteLength]; + } + } + return configObj; } diff --git a/frontend/src/ts/utils/url-handler.ts b/frontend/src/ts/utils/url-handler.ts index 034d4e0d45c7..5c2b4c31f0dc 100644 --- a/frontend/src/ts/utils/url-handler.ts +++ b/frontend/src/ts/utils/url-handler.ts @@ -188,7 +188,7 @@ export function loadTestSettingsFromUrl(getOverride?: string): void { } else if (mode === "words") { UpdateConfig.setWordCount(parseInt(de[1], 10), true); } else if (mode === "quote") { - UpdateConfig.setQuoteLength(-2, false); + UpdateConfig.setQuoteLength([-2], false); TestState.setSelectedQuoteId(parseInt(de[1], 10)); ManualRestart.set(); } diff --git a/packages/contracts/src/schemas/configs.ts b/packages/contracts/src/schemas/configs.ts index 08fda8157b75..932482d9801b 100644 --- a/packages/contracts/src/schemas/configs.ts +++ b/packages/contracts/src/schemas/configs.ts @@ -13,7 +13,6 @@ export type QuickRestart = z.infer; export const QuoteLengthSchema = z.union([ z.literal(-3), z.literal(-2), - z.literal(-1), z.literal(0), z.literal(1), z.literal(2), @@ -21,7 +20,19 @@ export const QuoteLengthSchema = z.union([ ]); export type QuoteLength = z.infer; -export const QuoteLengthConfigSchema = z.array(QuoteLengthSchema); +export const QuoteLengthConfigSchema = z + .array(QuoteLengthSchema) + .describe( + [ + "|value|description|\n|-|-|", + "|-3|Favorite quotes|", + "|-2|Quote search|", + "|0|Short quotes|", + "|1|Medium quotes|", + "|2|Long quotes|", + "|3|Thicc quotes|", + ].join("\n") + ); export type QuoteLengthConfig = z.infer; export const CaretStyleSchema = z.enum([ @@ -346,18 +357,7 @@ export type CustomBackground = z.infer; export const ConfigSchema = z .object({ - theme: ThemeNameSchema, - themeLight: ThemeNameSchema, - themeDark: ThemeNameSchema, - autoSwitchTheme: z.boolean(), - customTheme: z.boolean(), - //customThemeId: token().nonnegative().max(24), - customThemeColors: CustomThemeColorsSchema, - favThemes: FavThemesSchema, - showKeyTips: z.boolean(), - smoothCaret: SmoothCaretSchema, - codeUnindentOnBackspace: z.boolean(), - quickRestart: QuickRestartSchema, + // test punctuation: z.boolean(), numbers: z.boolean(), words: WordCountSchema, @@ -365,76 +365,105 @@ export const ConfigSchema = z mode: Shared.ModeSchema, quoteLength: QuoteLengthConfigSchema, language: LanguageSchema, - fontSize: FontSizeSchema, - freedomMode: z.boolean(), + burstHeatmap: z.boolean(), + + // behavior difficulty: DifficultySchema, + quickRestart: QuickRestartSchema, + repeatQuotes: RepeatQuotesSchema, blindMode: z.boolean(), - quickEnd: z.boolean(), - caretStyle: CaretStyleSchema, - paceCaretStyle: CaretStyleSchema, - flipTestColors: z.boolean(), - layout: LayoutSchema, + alwaysShowWordsHistory: z.boolean(), + singleListCommandLine: SingleListCommandLineSchema, + minWpm: MinimumWordsPerMinuteSchema, + minWpmCustomSpeed: MinWpmCustomSpeedSchema, + minAcc: MinimumAccuracySchema, + minAccCustom: MinimumAccuracyCustomSchema, + minBurst: MinimumBurstSchema, + minBurstCustomSpeed: MinimumBurstCustomSpeedSchema, + britishEnglish: z.boolean(), funbox: FunboxSchema, + customLayoutfluid: CustomLayoutFluidSchema, + customPolyglot: CustomPolyglotSchema, + + // input + freedomMode: z.boolean(), + strictSpace: z.boolean(), + oppositeShiftMode: OppositeShiftModeSchema, + stopOnError: StopOnErrorSchema, confidenceMode: ConfidenceModeSchema, + quickEnd: z.boolean(), indicateTypos: IndicateTyposSchema, + hideExtraLetters: z.boolean(), + lazyMode: z.boolean(), + layout: LayoutSchema, + codeUnindentOnBackspace: z.boolean(), + + // sound + soundVolume: SoundVolumeSchema, + playSoundOnClick: PlaySoundOnClickSchema, + playSoundOnError: PlaySoundOnErrorSchema, + + // caret + smoothCaret: SmoothCaretSchema, + caretStyle: CaretStyleSchema, + paceCaret: PaceCaretSchema, + paceCaretCustomSpeed: PaceCaretCustomSpeedSchema, + paceCaretStyle: CaretStyleSchema, + repeatedPace: z.boolean(), + + // appearance timerStyle: TimerStyleSchema, liveSpeedStyle: LiveSpeedAccBurstStyleSchema, liveAccStyle: LiveSpeedAccBurstStyleSchema, liveBurstStyle: LiveSpeedAccBurstStyleSchema, - colorfulMode: z.boolean(), - randomTheme: RandomThemeSchema, timerColor: TimerColorSchema, timerOpacity: TimerOpacitySchema, - stopOnError: StopOnErrorSchema, + highlightMode: HighlightModeSchema, + tapeMode: TapeModeSchema, + tapeMargin: TapeMarginSchema, + smoothLineScroll: z.boolean(), showAllLines: z.boolean(), + alwaysShowDecimalPlaces: z.boolean(), + typingSpeedUnit: TypingSpeedUnitSchema, + startGraphsAtZero: z.boolean(), + maxLineWidth: MaxLineWidthSchema, + fontSize: FontSizeSchema, + fontFamily: FontFamilySchema, keymapMode: KeymapModeSchema, + keymapLayout: KeymapLayoutSchema, keymapStyle: KeymapStyleSchema, keymapLegendStyle: KeymapLegendStyleSchema, - keymapLayout: KeymapLayoutSchema, keymapShowTopRow: KeymapShowTopRowSchema, keymapSize: KeymapSizeSchema, - fontFamily: FontFamilySchema, - smoothLineScroll: z.boolean(), - alwaysShowDecimalPlaces: z.boolean(), - alwaysShowWordsHistory: z.boolean(), - singleListCommandLine: SingleListCommandLineSchema, - capsLockWarning: z.boolean(), - playSoundOnError: PlaySoundOnErrorSchema, - playSoundOnClick: PlaySoundOnClickSchema, - soundVolume: SoundVolumeSchema, - startGraphsAtZero: z.boolean(), - showOutOfFocusWarning: z.boolean(), - paceCaret: PaceCaretSchema, - paceCaretCustomSpeed: PaceCaretCustomSpeedSchema, - repeatedPace: z.boolean(), - accountChart: AccountChartSchema, - minWpm: MinimumWordsPerMinuteSchema, - minWpmCustomSpeed: MinWpmCustomSpeedSchema, - highlightMode: HighlightModeSchema, - tapeMode: TapeModeSchema, - tapeMargin: TapeMarginSchema, - typingSpeedUnit: TypingSpeedUnitSchema, - ads: AdsSchema, - hideExtraLetters: z.boolean(), - strictSpace: z.boolean(), - minAcc: MinimumAccuracySchema, - minAccCustom: MinimumAccuracyCustomSchema, - monkey: z.boolean(), - repeatQuotes: RepeatQuotesSchema, - oppositeShiftMode: OppositeShiftModeSchema, + + // theme + flipTestColors: z.boolean(), + colorfulMode: z.boolean(), customBackground: CustomBackgroundSchema, customBackgroundSize: CustomBackgroundSizeSchema, customBackgroundFilter: CustomBackgroundFilterSchema, - customLayoutfluid: CustomLayoutFluidSchema, - monkeyPowerLevel: MonkeyPowerLevelSchema, - minBurst: MinimumBurstSchema, - minBurstCustomSpeed: MinimumBurstCustomSpeedSchema, - burstHeatmap: z.boolean(), - britishEnglish: z.boolean(), - lazyMode: z.boolean(), + autoSwitchTheme: z.boolean(), + themeLight: ThemeNameSchema, + themeDark: ThemeNameSchema, + randomTheme: RandomThemeSchema, + favThemes: FavThemesSchema, + theme: ThemeNameSchema, + customTheme: z.boolean(), + customThemeColors: CustomThemeColorsSchema, + + // hide elements + showKeyTips: z.boolean(), + showOutOfFocusWarning: z.boolean(), + capsLockWarning: z.boolean(), showAverage: ShowAverageSchema, - maxLineWidth: MaxLineWidthSchema, - customPolyglot: CustomPolyglotSchema, + + // other (hidden) + accountChart: AccountChartSchema, + monkey: z.boolean(), + monkeyPowerLevel: MonkeyPowerLevelSchema, + + // ads + ads: AdsSchema, } satisfies Record) .strict(); @@ -455,24 +484,14 @@ export const ConfigGroupNameSchema = z.enum([ "appearance", "theme", "hideElements", - "ads", "hidden", + "ads", ]); export type ConfigGroupName = z.infer; export const ConfigGroupsLiteral = { - theme: "theme", - themeLight: "theme", - themeDark: "theme", - autoSwitchTheme: "theme", - customTheme: "theme", - customThemeColors: "theme", - favThemes: "theme", - showKeyTips: "hideElements", - smoothCaret: "caret", - codeUnindentOnBackspace: "input", - quickRestart: "behavior", + //test punctuation: "test", numbers: "test", words: "test", @@ -480,76 +499,105 @@ export const ConfigGroupsLiteral = { mode: "test", quoteLength: "test", language: "test", - fontSize: "appearance", - freedomMode: "input", + burstHeatmap: "test", + + //behavior difficulty: "behavior", + quickRestart: "behavior", + repeatQuotes: "behavior", blindMode: "behavior", + alwaysShowWordsHistory: "behavior", + singleListCommandLine: "behavior", + minWpm: "behavior", + minWpmCustomSpeed: "behavior", + minAcc: "behavior", + minAccCustom: "behavior", + minBurst: "behavior", + minBurstCustomSpeed: "behavior", + britishEnglish: "behavior", + funbox: "behavior", //todo: maybe move to test? + customLayoutfluid: "behavior", + customPolyglot: "behavior", + + //input + freedomMode: "input", + strictSpace: "input", + oppositeShiftMode: "input", + stopOnError: "input", + confidenceMode: "input", quickEnd: "input", + indicateTypos: "input", + hideExtraLetters: "input", + lazyMode: "input", + layout: "input", + codeUnindentOnBackspace: "input", + + //sound + soundVolume: "sound", + playSoundOnClick: "sound", + playSoundOnError: "sound", + + //caret + smoothCaret: "caret", caretStyle: "caret", + paceCaret: "caret", + paceCaretCustomSpeed: "caret", paceCaretStyle: "caret", - flipTestColors: "theme", - layout: "input", - funbox: "behavior", - confidenceMode: "input", - indicateTypos: "input", + repeatedPace: "caret", + + //appearance timerStyle: "appearance", liveSpeedStyle: "appearance", liveAccStyle: "appearance", liveBurstStyle: "appearance", - colorfulMode: "theme", - randomTheme: "theme", timerColor: "appearance", timerOpacity: "appearance", - stopOnError: "input", + highlightMode: "appearance", + tapeMode: "appearance", + tapeMargin: "appearance", + smoothLineScroll: "appearance", showAllLines: "appearance", + alwaysShowDecimalPlaces: "appearance", + typingSpeedUnit: "appearance", + startGraphsAtZero: "appearance", + maxLineWidth: "appearance", + fontSize: "appearance", + fontFamily: "appearance", keymapMode: "appearance", + keymapLayout: "appearance", keymapStyle: "appearance", keymapLegendStyle: "appearance", - keymapLayout: "appearance", keymapShowTopRow: "appearance", keymapSize: "appearance", - fontFamily: "appearance", - smoothLineScroll: "appearance", - alwaysShowDecimalPlaces: "appearance", - alwaysShowWordsHistory: "behavior", - singleListCommandLine: "behavior", - capsLockWarning: "hideElements", - playSoundOnError: "sound", - playSoundOnClick: "sound", - soundVolume: "sound", - startGraphsAtZero: "appearance", - showOutOfFocusWarning: "hideElements", - paceCaret: "caret", - paceCaretCustomSpeed: "caret", - repeatedPace: "caret", - accountChart: "hidden", - minWpm: "behavior", - minWpmCustomSpeed: "behavior", - highlightMode: "appearance", - tapeMode: "appearance", - tapeMargin: "appearance", - typingSpeedUnit: "appearance", - ads: "ads", - hideExtraLetters: "input", - strictSpace: "input", - minAcc: "behavior", - minAccCustom: "behavior", - monkey: "hidden", - repeatQuotes: "behavior", - oppositeShiftMode: "input", + + //theme + flipTestColors: "theme", + colorfulMode: "theme", customBackground: "theme", customBackgroundSize: "theme", customBackgroundFilter: "theme", - customLayoutfluid: "behavior", - monkeyPowerLevel: "hidden", - minBurst: "behavior", - minBurstCustomSpeed: "behavior", - burstHeatmap: "test", - britishEnglish: "behavior", - lazyMode: "input", + autoSwitchTheme: "theme", + themeLight: "theme", + themeDark: "theme", + randomTheme: "theme", + favThemes: "theme", + theme: "theme", + customTheme: "theme", + customThemeColors: "theme", + + //hide elements + showKeyTips: "hideElements", + showOutOfFocusWarning: "hideElements", + capsLockWarning: "hideElements", showAverage: "hideElements", - maxLineWidth: "appearance", - customPolyglot: "behavior", + + //other + accountChart: "hidden", + monkey: "hidden", + monkeyPowerLevel: "hidden", + + //ads + ads: "ads", } as const satisfies Record; export type ConfigGroups = typeof ConfigGroupsLiteral; From 69cbbe4ab24d899256cb32483b3418725186c672 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 21 Jul 2025 14:55:54 +0200 Subject: [PATCH 2/9] refactor(contracts / schemas): move schemas into their own package (@miodec) (#6754) --- backend/__tests__/__testData__/auth.ts | 2 +- .../api/controllers/configuration.spec.ts | 2 +- .../api/controllers/leaderboard.spec.ts | 2 +- .../__tests__/api/controllers/quotes.spec.ts | 2 +- .../__tests__/api/controllers/user.spec.ts | 6 +- backend/__tests__/dal/leaderboards.spec.ts | 2 +- backend/__tests__/dal/user.spec.ts | 9 +- backend/__tests__/middlewares/auth.spec.ts | 2 +- .../middlewares/configuration.spec.ts | 2 +- .../__tests__/middlewares/permission.spec.ts | 2 +- .../utils/daily-leaderboards.spec.ts | 2 +- backend/__tests__/utils/pb.spec.ts | 6 +- backend/package.json | 1 + backend/scripts/openapi.ts | 7 +- backend/src/anticheat/index.ts | 5 +- backend/src/api/controllers/admin.ts | 2 +- backend/src/api/controllers/ape-key.ts | 2 +- backend/src/api/controllers/config.ts | 2 +- backend/src/api/controllers/dev.ts | 8 +- backend/src/api/controllers/leaderboard.ts | 2 +- backend/src/api/controllers/preset.ts | 2 +- backend/src/api/controllers/quote.ts | 2 +- backend/src/api/controllers/result.ts | 6 +- backend/src/api/controllers/user.ts | 2 +- backend/src/api/routes/index.ts | 2 +- backend/src/constants/base-configuration.ts | 2 +- backend/src/dal/ape-keys.ts | 2 +- backend/src/dal/blocklist.ts | 2 +- backend/src/dal/config.ts | 2 +- backend/src/dal/leaderboards.ts | 2 +- backend/src/dal/new-quotes.ts | 4 +- backend/src/dal/preset.ts | 5 +- backend/src/dal/psa.ts | 2 +- backend/src/dal/public.ts | 5 +- backend/src/dal/quote-ratings.ts | 4 +- backend/src/dal/result.ts | 2 +- backend/src/dal/user.ts | 12 +- backend/src/init/configuration.ts | 2 +- backend/src/middlewares/auth.ts | 4 +- backend/src/middlewares/configuration.ts | 4 +- backend/src/middlewares/context.ts | 2 +- backend/src/middlewares/permission.ts | 2 +- backend/src/middlewares/utility.ts | 2 +- backend/src/queues/george-queue.ts | 2 +- backend/src/queues/later-queue.ts | 2 +- backend/src/services/weekly-xp-leaderboard.ts | 4 +- backend/src/utils/daily-leaderboards.ts | 6 +- backend/src/utils/error.ts | 2 +- backend/src/utils/monkey-mail.ts | 2 +- backend/src/utils/monkey-response.ts | 2 +- backend/src/utils/pb.ts | 8 +- backend/src/utils/prometheus.ts | 2 +- backend/src/utils/result.ts | 4 +- backend/src/utils/validation.ts | 2 +- backend/src/workers/later-worker.ts | 2 +- .../__tests__/constants/languages.spec.ts | 2 +- frontend/__tests__/constants/layouts.spec.ts | 2 +- frontend/__tests__/root/config.spec.ts | 2 +- .../test/funbox/funbox-validation.spec.ts | 2 +- frontend/__tests__/utils/config.spec.ts | 2 +- frontend/__tests__/utils/format.spec.ts | 2 +- frontend/__tests__/utils/url-handler.spec.ts | 4 +- frontend/package.json | 1 + frontend/src/ts/ape/server-configuration.ts | 2 +- frontend/src/ts/commandline/lists.ts | 2 +- .../lists/add-or-remove-theme-to-favorites.ts | 2 +- .../src/ts/commandline/lists/font-size.ts | 2 +- .../ts/commandline/lists/keymap-layouts.ts | 2 +- frontend/src/ts/commandline/types.ts | 2 +- frontend/src/ts/config.ts | 8 +- frontend/src/ts/constants/default-config.ts | 5 +- .../ts/constants/default-result-filters.ts | 2 +- frontend/src/ts/constants/default-snapshot.ts | 12 +- frontend/src/ts/constants/languages.ts | 5 +- frontend/src/ts/constants/layouts.ts | 2 +- frontend/src/ts/constants/themes.ts | 2 +- .../ts/controllers/challenge-controller.ts | 11 +- .../src/ts/controllers/preset-controller.ts | 2 +- .../src/ts/controllers/quotes-controller.ts | 2 +- .../src/ts/controllers/sound-controller.ts | 2 +- frontend/src/ts/controllers/tag-controller.ts | 2 +- .../src/ts/controllers/theme-controller.ts | 2 +- frontend/src/ts/db.ts | 8 +- .../account-settings/ape-key-table.ts | 2 +- frontend/src/ts/elements/account/pb-tables.ts | 4 +- .../src/ts/elements/account/result-filters.ts | 4 +- frontend/src/ts/elements/alerts.ts | 2 +- .../ts/elements/custom-background-filter.ts | 2 +- frontend/src/ts/elements/profile.ts | 2 +- frontend/src/ts/elements/psa.ts | 4 +- .../ts/elements/settings/settings-group.ts | 2 +- .../src/ts/elements/settings/theme-picker.ts | 5 +- frontend/src/ts/elements/xp-bar.ts | 2 +- frontend/src/ts/modals/custom-text.ts | 2 +- frontend/src/ts/modals/edit-preset.ts | 7 +- frontend/src/ts/modals/edit-profile.ts | 2 +- frontend/src/ts/modals/edit-tag.ts | 2 +- .../src/ts/modals/last-signed-out-result.ts | 2 +- frontend/src/ts/modals/mini-result-chart.ts | 2 +- frontend/src/ts/modals/mobile-test-config.ts | 7 +- frontend/src/ts/modals/pb-tables.ts | 6 +- frontend/src/ts/modals/quote-approve.ts | 2 +- frontend/src/ts/modals/quote-rate.ts | 2 +- frontend/src/ts/modals/quote-report.ts | 2 +- frontend/src/ts/modals/quote-submit.ts | 2 +- frontend/src/ts/modals/share-test-settings.ts | 4 +- frontend/src/ts/modals/simple-modals.ts | 2 +- frontend/src/ts/modals/user-report.ts | 2 +- frontend/src/ts/modals/word-filter.ts | 2 +- frontend/src/ts/observables/config-event.ts | 6 +- frontend/src/ts/pages/about.ts | 5 +- frontend/src/ts/pages/account.ts | 8 +- frontend/src/ts/pages/leaderboards.ts | 9 +- frontend/src/ts/pages/profile.ts | 4 +- frontend/src/ts/pages/settings.ts | 6 +- frontend/src/ts/test/custom-text.ts | 7 +- .../src/ts/test/funbox/funbox-functions.ts | 8 +- frontend/src/ts/test/funbox/funbox-memory.ts | 2 +- .../src/ts/test/funbox/funbox-validation.ts | 6 +- frontend/src/ts/test/funbox/funbox.ts | 7 +- frontend/src/ts/test/funbox/list.ts | 2 +- frontend/src/ts/test/practise-words.ts | 2 +- frontend/src/ts/test/result.ts | 4 +- frontend/src/ts/test/test-config.ts | 7 +- frontend/src/ts/test/test-logic.ts | 9 +- frontend/src/ts/test/test-stats.ts | 5 +- frontend/src/ts/test/test-timer.ts | 2 +- frontend/src/ts/test/test-ui.ts | 5 +- frontend/src/ts/test/wikipedia.ts | 2 +- frontend/src/ts/utils/config.ts | 4 +- frontend/src/ts/utils/format.ts | 2 +- frontend/src/ts/utils/json-data.ts | 4 +- frontend/src/ts/utils/misc.ts | 10 +- frontend/src/ts/utils/results.ts | 2 +- frontend/src/ts/utils/strings.ts | 2 +- frontend/src/ts/utils/typing-speed-units.ts | 2 +- frontend/src/ts/utils/url-handler.ts | 6 +- .../contracts/__test__/schema/config.spec.ts | 2 +- .../__test__/validation/validation.spec.ts | 2 +- packages/contracts/package.json | 1 + packages/contracts/src/admin.ts | 4 +- packages/contracts/src/ape-keys.ts | 9 +- packages/contracts/src/configs.ts | 5 +- packages/contracts/src/configuration.ts | 4 +- packages/contracts/src/dev.ts | 4 +- packages/contracts/src/leaderboards.ts | 8 +- packages/contracts/src/presets.ts | 9 +- packages/contracts/src/psas.ts | 4 +- packages/contracts/src/public.ts | 11 +- packages/contracts/src/quotes.ts | 8 +- .../src/require-configuration/index.ts | 2 +- packages/contracts/src/results.ts | 6 +- packages/contracts/src/users.ts | 18 +- .../contracts/src/{schemas => util}/api.ts | 0 packages/contracts/src/webhooks.ts | 4 +- packages/funbox/package.json | 4 +- packages/funbox/src/index.ts | 2 +- packages/funbox/src/list.ts | 2 +- packages/funbox/src/types.ts | 2 +- packages/funbox/src/validation.ts | 2 +- packages/schemas/.eslintrc.cjs | 5 + packages/schemas/.oxlintrc.json | 6 + packages/schemas/package.json | 39 +++ .../src/schemas => schemas/src}/ape-keys.ts | 0 .../src/schemas => schemas/src}/configs.ts | 0 .../schemas => schemas/src}/configuration.ts | 0 .../src/schemas => schemas/src}/languages.ts | 0 .../src/schemas => schemas/src}/layouts.ts | 0 .../schemas => schemas/src}/leaderboards.ts | 0 .../src/schemas => schemas/src}/presets.ts | 0 .../src/schemas => schemas/src}/psas.ts | 1 + .../src/schemas => schemas/src}/public.ts | 0 .../src/schemas => schemas/src}/quotes.ts | 0 .../src/schemas => schemas/src}/results.ts | 0 .../src/schemas => schemas/src}/shared.ts | 0 .../src/schemas => schemas/src}/themes.ts | 0 .../src/schemas => schemas/src}/users.ts | 2 +- .../src/schemas => schemas/src}/util.ts | 0 .../src/validation/homoglyphs.ts | 0 .../src/validation/validation.ts | 0 packages/schemas/tsconfig.json | 12 + packages/schemas/tsup.config.js | 3 + pnpm-lock.yaml | 247 ++++++------------ 183 files changed, 411 insertions(+), 506 deletions(-) rename packages/contracts/src/{schemas => util}/api.ts (100%) create mode 100644 packages/schemas/.eslintrc.cjs create mode 100644 packages/schemas/.oxlintrc.json create mode 100644 packages/schemas/package.json rename packages/{contracts/src/schemas => schemas/src}/ape-keys.ts (100%) rename packages/{contracts/src/schemas => schemas/src}/configs.ts (100%) rename packages/{contracts/src/schemas => schemas/src}/configuration.ts (100%) rename packages/{contracts/src/schemas => schemas/src}/languages.ts (100%) rename packages/{contracts/src/schemas => schemas/src}/layouts.ts (100%) rename packages/{contracts/src/schemas => schemas/src}/leaderboards.ts (100%) rename packages/{contracts/src/schemas => schemas/src}/presets.ts (100%) rename packages/{contracts/src/schemas => schemas/src}/psas.ts (99%) rename packages/{contracts/src/schemas => schemas/src}/public.ts (100%) rename packages/{contracts/src/schemas => schemas/src}/quotes.ts (100%) rename packages/{contracts/src/schemas => schemas/src}/results.ts (100%) rename packages/{contracts/src/schemas => schemas/src}/shared.ts (100%) rename packages/{contracts/src/schemas => schemas/src}/themes.ts (100%) rename packages/{contracts/src/schemas => schemas/src}/users.ts (99%) rename packages/{contracts/src/schemas => schemas/src}/util.ts (100%) rename packages/{contracts => schemas}/src/validation/homoglyphs.ts (100%) rename packages/{contracts => schemas}/src/validation/validation.ts (100%) create mode 100644 packages/schemas/tsconfig.json create mode 100644 packages/schemas/tsup.config.js diff --git a/backend/__tests__/__testData__/auth.ts b/backend/__tests__/__testData__/auth.ts index 844bf9bbd829..dbe21a4b5e83 100644 --- a/backend/__tests__/__testData__/auth.ts +++ b/backend/__tests__/__testData__/auth.ts @@ -1,4 +1,4 @@ -import { Configuration } from "@monkeytype/contracts/schemas/configuration"; +import { Configuration } from "@monkeytype/schemas/configuration"; import { randomBytes } from "crypto"; import { hash } from "bcrypt"; import { ObjectId } from "mongodb"; diff --git a/backend/__tests__/api/controllers/configuration.spec.ts b/backend/__tests__/api/controllers/configuration.spec.ts index ce38520197da..f7069d48950a 100644 --- a/backend/__tests__/api/controllers/configuration.spec.ts +++ b/backend/__tests__/api/controllers/configuration.spec.ts @@ -5,7 +5,7 @@ import { CONFIGURATION_FORM_SCHEMA, } from "../../../src/constants/base-configuration"; import * as Configuration from "../../../src/init/configuration"; -import type { Configuration as ConfigurationType } from "@monkeytype/contracts/schemas/configuration"; +import type { Configuration as ConfigurationType } from "@monkeytype/schemas/configuration"; import { ObjectId } from "mongodb"; import * as Misc from "../../../src/utils/misc"; import * as AdminUuids from "../../../src/dal/admin-uids"; diff --git a/backend/__tests__/api/controllers/leaderboard.spec.ts b/backend/__tests__/api/controllers/leaderboard.spec.ts index 5cae0e151ebb..a7de03ada532 100644 --- a/backend/__tests__/api/controllers/leaderboard.spec.ts +++ b/backend/__tests__/api/controllers/leaderboard.spec.ts @@ -10,7 +10,7 @@ import { mockAuthenticateWithApeKey, mockBearerAuthentication, } from "../../__testData__/auth"; -import { XpLeaderboardEntry } from "@monkeytype/contracts/schemas/leaderboards"; +import { XpLeaderboardEntry } from "@monkeytype/schemas/leaderboards"; const mockApp = request(app); const configuration = Configuration.getCachedConfiguration(); diff --git a/backend/__tests__/api/controllers/quotes.spec.ts b/backend/__tests__/api/controllers/quotes.spec.ts index 4aeafa62bf1f..244391f84ebe 100644 --- a/backend/__tests__/api/controllers/quotes.spec.ts +++ b/backend/__tests__/api/controllers/quotes.spec.ts @@ -9,7 +9,7 @@ import * as ReportDal from "../../../src/dal/report"; import * as Captcha from "../../../src/utils/captcha"; import { ObjectId } from "mongodb"; import _ from "lodash"; -import { ApproveQuote } from "@monkeytype/contracts/schemas/quotes"; +import { ApproveQuote } from "@monkeytype/schemas/quotes"; import { mockBearerAuthentication } from "../../__testData__/auth"; const mockApp = request(app); diff --git a/backend/__tests__/api/controllers/user.spec.ts b/backend/__tests__/api/controllers/user.spec.ts index bcc26e5b1c9a..f32d40dbe733 100644 --- a/backend/__tests__/api/controllers/user.spec.ts +++ b/backend/__tests__/api/controllers/user.spec.ts @@ -19,7 +19,7 @@ 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 { PersonalBest } from "@monkeytype/schemas/shared"; import { pb } from "../../dal/leaderboards.spec"; import { mockAuthenticateWithApeKey, @@ -27,9 +27,9 @@ import { } from "../../__testData__/auth"; import { randomUUID } from "node:crypto"; import _ from "lodash"; -import { MonkeyMail, UserStreak } from "@monkeytype/contracts/schemas/users"; +import { MonkeyMail, UserStreak } from "@monkeytype/schemas/users"; import MonkeyError, { isFirebaseError } from "../../../src/utils/error"; -import { LeaderboardEntry } from "@monkeytype/contracts/schemas/leaderboards"; +import { LeaderboardEntry } from "@monkeytype/schemas/leaderboards"; import * as WeeklyXpLeaderboard from "../../../src/services/weekly-xp-leaderboard"; const mockApp = request(app); diff --git a/backend/__tests__/dal/leaderboards.spec.ts b/backend/__tests__/dal/leaderboards.spec.ts index b02f83e662cc..4181852671c3 100644 --- a/backend/__tests__/dal/leaderboards.spec.ts +++ b/backend/__tests__/dal/leaderboards.spec.ts @@ -5,7 +5,7 @@ import * as LeaderboardsDal from "../../src/dal/leaderboards"; import * as PublicDal from "../../src/dal/public"; import * as Configuration from "../../src/init/configuration"; import type { DBLeaderboardEntry } from "../../src/dal/leaderboards"; -import type { PersonalBest } from "@monkeytype/contracts/schemas/shared"; +import type { PersonalBest } from "@monkeytype/schemas/shared"; const configuration = Configuration.getCachedConfiguration(); import * as DB from "../../src/init/db"; diff --git a/backend/__tests__/dal/user.spec.ts b/backend/__tests__/dal/user.spec.ts index 8f6d57305ff2..d763a06efa6d 100644 --- a/backend/__tests__/dal/user.spec.ts +++ b/backend/__tests__/dal/user.spec.ts @@ -2,12 +2,9 @@ import _ from "lodash"; import * as UserDAL from "../../src/dal/user"; import * as UserTestData from "../__testData__/users"; import { ObjectId } from "mongodb"; -import { MonkeyMail, ResultFilters } from "@monkeytype/contracts/schemas/users"; -import { - PersonalBest, - PersonalBests, -} from "@monkeytype/contracts/schemas/shared"; -import { CustomThemeColors } from "@monkeytype/contracts/schemas/configs"; +import { MonkeyMail, ResultFilters } from "@monkeytype/schemas/users"; +import { PersonalBest, PersonalBests } from "@monkeytype/schemas/shared"; +import { CustomThemeColors } from "@monkeytype/schemas/configs"; const mockPersonalBest = { acc: 1, diff --git a/backend/__tests__/middlewares/auth.spec.ts b/backend/__tests__/middlewares/auth.spec.ts index 0ee92b90a0a7..378714ae0097 100644 --- a/backend/__tests__/middlewares/auth.spec.ts +++ b/backend/__tests__/middlewares/auth.spec.ts @@ -12,7 +12,7 @@ import crypto from "crypto"; import { EndpointMetadata, RequestAuthenticationOptions, -} from "@monkeytype/contracts/schemas/api"; +} from "@monkeytype/schemas/api"; import * as Prometheus from "../../src/utils/prometheus"; import { TsRestRequestWithContext } from "../../src/api/types"; diff --git a/backend/__tests__/middlewares/configuration.spec.ts b/backend/__tests__/middlewares/configuration.spec.ts index 8a63c601b89f..a2312933cb7f 100644 --- a/backend/__tests__/middlewares/configuration.spec.ts +++ b/backend/__tests__/middlewares/configuration.spec.ts @@ -1,6 +1,6 @@ import { RequireConfiguration } from "@monkeytype/contracts/require-configuration/index"; import { verifyRequiredConfiguration } from "../../src/middlewares/configuration"; -import { Configuration } from "@monkeytype/contracts/schemas/configuration"; +import { Configuration } from "@monkeytype/schemas/configuration"; import { Response } from "express"; import MonkeyError from "../../src/utils/error"; import { TsRestRequest } from "../../src/api/types"; diff --git a/backend/__tests__/middlewares/permission.spec.ts b/backend/__tests__/middlewares/permission.spec.ts index b87c14f13a30..1448119ea0ee 100644 --- a/backend/__tests__/middlewares/permission.spec.ts +++ b/backend/__tests__/middlewares/permission.spec.ts @@ -1,6 +1,6 @@ import { Response } from "express"; import { verifyPermissions } from "../../src/middlewares/permission"; -import { EndpointMetadata } from "@monkeytype/contracts/schemas/api"; +import { EndpointMetadata } from "@monkeytype/schemas/api"; import * as Misc from "../../src/utils/misc"; import * as AdminUids from "../../src/dal/admin-uids"; import * as UserDal from "../../src/dal/user"; diff --git a/backend/__tests__/utils/daily-leaderboards.spec.ts b/backend/__tests__/utils/daily-leaderboards.spec.ts index 041ef06af179..9d765eb1a84b 100644 --- a/backend/__tests__/utils/daily-leaderboards.spec.ts +++ b/backend/__tests__/utils/daily-leaderboards.spec.ts @@ -1,4 +1,4 @@ -import { Mode } from "@monkeytype/contracts/schemas/shared"; +import { Mode } from "@monkeytype/schemas/shared"; import { getDailyLeaderboard } from "../../src/utils/daily-leaderboards"; const dailyLeaderboardsConfig = { diff --git a/backend/__tests__/utils/pb.spec.ts b/backend/__tests__/utils/pb.spec.ts index cedd37487145..182da3c6365e 100644 --- a/backend/__tests__/utils/pb.spec.ts +++ b/backend/__tests__/utils/pb.spec.ts @@ -1,8 +1,8 @@ import _ from "lodash"; import * as pb from "../../src/utils/pb"; -import { Mode, PersonalBests } from "@monkeytype/contracts/schemas/shared"; -import { Result } from "@monkeytype/contracts/schemas/results"; -import { FunboxName } from "@monkeytype/contracts/schemas/configs"; +import { Mode, PersonalBests } from "@monkeytype/schemas/shared"; +import { Result } from "@monkeytype/schemas/results"; +import { FunboxName } from "@monkeytype/schemas/configs"; describe("Pb Utils", () => { it("funboxCatGetPb", () => { diff --git a/backend/package.json b/backend/package.json index 228d227d9fa9..7ebd4153daf3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -67,6 +67,7 @@ "devDependencies": { "@monkeytype/eslint-config": "workspace:*", "@monkeytype/oxlint-config": "workspace:*", + "@monkeytype/schemas": "workspace:*", "@monkeytype/typescript-config": "workspace:*", "@redocly/cli": "1.28.5", "@types/bcrypt": "5.0.2", diff --git a/backend/scripts/openapi.ts b/backend/scripts/openapi.ts index 80b6aaf35934..d42e06d05b7e 100644 --- a/backend/scripts/openapi.ts +++ b/backend/scripts/openapi.ts @@ -1,10 +1,7 @@ import { generateOpenApi } from "@ts-rest/open-api"; import { contract } from "@monkeytype/contracts/index"; import { writeFileSync, mkdirSync } from "fs"; -import { - EndpointMetadata, - PermissionId, -} from "@monkeytype/contracts/schemas/api"; +import { EndpointMetadata, PermissionId } from "@monkeytype/contracts/util/api"; import type { OpenAPIObject, OperationObject } from "openapi3-ts"; import { RateLimitIds, @@ -277,7 +274,7 @@ function addRequiredConfiguration( if (metadata === undefined || metadata.requireConfiguration === undefined) return; - //@ts-expect-error + //@ts-expect-error somehow path doesnt exist operation.description += `**Required configuration:** This operation can only be called if the [configuration](#tag/configuration/operation/configuration.get) for \`${metadata.requireConfiguration.path}\` is \`true\`.\n\n`; } diff --git a/backend/src/anticheat/index.ts b/backend/src/anticheat/index.ts index 715b799e590e..5249ebaa9ce2 100644 --- a/backend/src/anticheat/index.ts +++ b/backend/src/anticheat/index.ts @@ -1,9 +1,6 @@ const hasAnticheatImplemented = process.env["BYPASS_ANTICHEAT"] === "true"; -import { - CompletedEvent, - KeyStats, -} from "@monkeytype/contracts/schemas/results"; +import { CompletedEvent, KeyStats } from "@monkeytype/schemas/results"; import Logger from "../utils/logger"; export function implemented(): boolean { diff --git a/backend/src/api/controllers/admin.ts b/backend/src/api/controllers/admin.ts index 8adbbd0138e4..02df86c98d50 100644 --- a/backend/src/api/controllers/admin.ts +++ b/backend/src/api/controllers/admin.ts @@ -13,7 +13,7 @@ import { ToggleBanResponse, } from "@monkeytype/contracts/admin"; import MonkeyError, { getErrorMessage } from "../../utils/error"; -import { Configuration } from "@monkeytype/contracts/schemas/configuration"; +import { Configuration } from "@monkeytype/schemas/configuration"; import { addImportantLog } from "../../dal/logs"; import { MonkeyRequest } from "../types"; diff --git a/backend/src/api/controllers/ape-key.ts b/backend/src/api/controllers/ape-key.ts index 5ff616f34731..828d7c3a0455 100644 --- a/backend/src/api/controllers/ape-key.ts +++ b/backend/src/api/controllers/ape-key.ts @@ -14,7 +14,7 @@ import { EditApeKeyRequest, GetApeKeyResponse, } from "@monkeytype/contracts/ape-keys"; -import { ApeKey } from "@monkeytype/contracts/schemas/ape-keys"; +import { ApeKey } from "@monkeytype/schemas/ape-keys"; import { MonkeyRequest } from "../types"; function cleanApeKey(apeKey: ApeKeysDAL.DBApeKey): ApeKey { diff --git a/backend/src/api/controllers/config.ts b/backend/src/api/controllers/config.ts index afd68c8750e4..36f6695d4735 100644 --- a/backend/src/api/controllers/config.ts +++ b/backend/src/api/controllers/config.ts @@ -1,4 +1,4 @@ -import { PartialConfig } from "@monkeytype/contracts/schemas/configs"; +import { PartialConfig } from "@monkeytype/schemas/configs"; import * as ConfigDAL from "../../dal/config"; import { MonkeyResponse } from "../../utils/monkey-response"; import { GetConfigResponse } from "@monkeytype/contracts/configs"; diff --git a/backend/src/api/controllers/dev.ts b/backend/src/api/controllers/dev.ts index 512625c253af..add8db0c0797 100644 --- a/backend/src/api/controllers/dev.ts +++ b/backend/src/api/controllers/dev.ts @@ -9,11 +9,7 @@ import { ObjectId } from "mongodb"; import * as LeaderboardDal from "../../dal/leaderboards"; import MonkeyError from "../../utils/error"; -import { - Mode, - PersonalBest, - PersonalBests, -} from "@monkeytype/contracts/schemas/shared"; +import { Mode, PersonalBest, PersonalBests } from "@monkeytype/schemas/shared"; import { GenerateDataRequest, GenerateDataResponse, @@ -22,7 +18,7 @@ import { roundTo2 } from "@monkeytype/util/numbers"; import { MonkeyRequest } from "../types"; import { DBResult } from "../../utils/result"; import { LbPersonalBests } from "../../utils/pb"; -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { Language } from "@monkeytype/schemas/languages"; const CREATE_RESULT_DEFAULT_OPTIONS = { firstTestTimestamp: DateUtils.startOfDay(new UTCDate(Date.now())).valueOf(), diff --git a/backend/src/api/controllers/leaderboard.ts b/backend/src/api/controllers/leaderboard.ts index ffa9b3746262..2e27c5509642 100644 --- a/backend/src/api/controllers/leaderboard.ts +++ b/backend/src/api/controllers/leaderboard.ts @@ -19,7 +19,7 @@ import { GetWeeklyXpLeaderboardRankResponse, GetWeeklyXpLeaderboardResponse, } from "@monkeytype/contracts/leaderboards"; -import { Configuration } from "@monkeytype/contracts/schemas/configuration"; +import { Configuration } from "@monkeytype/schemas/configuration"; import { getCurrentDayTimestamp, getCurrentWeekTimestamp, diff --git a/backend/src/api/controllers/preset.ts b/backend/src/api/controllers/preset.ts index 361acdd7cad9..a811d2f87a71 100644 --- a/backend/src/api/controllers/preset.ts +++ b/backend/src/api/controllers/preset.ts @@ -7,7 +7,7 @@ import { import * as PresetDAL from "../../dal/preset"; import { MonkeyResponse } from "../../utils/monkey-response"; import { replaceObjectId } from "../../utils/misc"; -import { EditPresetRequest } from "@monkeytype/contracts/schemas/presets"; +import { EditPresetRequest } from "@monkeytype/schemas/presets"; import { MonkeyRequest } from "../types"; export async function getPresets( diff --git a/backend/src/api/controllers/quote.ts b/backend/src/api/controllers/quote.ts index 9b583452cb33..6862c811df1a 100644 --- a/backend/src/api/controllers/quote.ts +++ b/backend/src/api/controllers/quote.ts @@ -23,7 +23,7 @@ import { } from "@monkeytype/contracts/quotes"; import { replaceObjectId, replaceObjectIds } from "../../utils/misc"; import { MonkeyRequest } from "../types"; -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { Language } from "@monkeytype/schemas/languages"; async function verifyCaptcha(captcha: string): Promise { if (!(await verify(captcha))) { diff --git a/backend/src/api/controllers/result.ts b/backend/src/api/controllers/result.ts index 5656d4e64492..96c0854823ad 100644 --- a/backend/src/api/controllers/result.ts +++ b/backend/src/api/controllers/result.ts @@ -31,7 +31,7 @@ import { DBResult, replaceLegacyValues, } from "../../utils/result"; -import { Configuration } from "@monkeytype/contracts/schemas/configuration"; +import { Configuration } from "@monkeytype/schemas/configuration"; import { addImportantLog, addLog } from "../../dal/logs"; import { AddResultRequest, @@ -50,8 +50,8 @@ import { Result, PostResultResponse, XpBreakdown, -} from "@monkeytype/contracts/schemas/results"; -import { Mode } from "@monkeytype/contracts/schemas/shared"; +} from "@monkeytype/schemas/results"; +import { Mode } from "@monkeytype/schemas/shared"; import { isSafeNumber, mapRange, diff --git a/backend/src/api/controllers/user.ts b/backend/src/api/controllers/user.ts index d1a40d1afa02..348b66724baa 100644 --- a/backend/src/api/controllers/user.ts +++ b/backend/src/api/controllers/user.ts @@ -39,7 +39,7 @@ import { CountByYearAndDay, TestActivity, UserProfileDetails, -} from "@monkeytype/contracts/schemas/users"; +} from "@monkeytype/schemas/users"; import { addImportantLog, addLog, deleteUserLogs } from "../../dal/logs"; import { sendForgotPasswordEmail as authSendForgotPasswordEmail } from "../../utils/auth"; import { diff --git a/backend/src/api/routes/index.ts b/backend/src/api/routes/index.ts index 91d061fd88df..d416c9f3b5ea 100644 --- a/backend/src/api/routes/index.ts +++ b/backend/src/api/routes/index.ts @@ -31,7 +31,7 @@ import { getLiveConfiguration } from "../../init/configuration"; import Logger from "../../utils/logger"; import { createExpressEndpoints, initServer } from "@ts-rest/express"; import { ZodIssue } from "zod"; -import { MonkeyValidationError } from "@monkeytype/contracts/schemas/api"; +import { MonkeyValidationError } from "@monkeytype/contracts/util/api"; import { authenticateTsRestRequest } from "../../middlewares/auth"; import { rateLimitRequest } from "../../middlewares/rate-limit"; import { verifyPermissions } from "../../middlewares/permission"; diff --git a/backend/src/constants/base-configuration.ts b/backend/src/constants/base-configuration.ts index c4c77a770280..d69f74e46698 100644 --- a/backend/src/constants/base-configuration.ts +++ b/backend/src/constants/base-configuration.ts @@ -1,4 +1,4 @@ -import { Configuration } from "@monkeytype/contracts/schemas/configuration"; +import { Configuration } from "@monkeytype/schemas/configuration"; /** * This is the base schema for the configuration of the API backend. diff --git a/backend/src/dal/ape-keys.ts b/backend/src/dal/ape-keys.ts index 2cf9ce8a34f1..78a1153a17c4 100644 --- a/backend/src/dal/ape-keys.ts +++ b/backend/src/dal/ape-keys.ts @@ -8,7 +8,7 @@ import { Collection, } from "mongodb"; import MonkeyError from "../utils/error"; -import { ApeKey } from "@monkeytype/contracts/schemas/ape-keys"; +import { ApeKey } from "@monkeytype/schemas/ape-keys"; export type DBApeKey = ApeKey & { _id: ObjectId; diff --git a/backend/src/dal/blocklist.ts b/backend/src/dal/blocklist.ts index 5c0cf9c0d128..63827f5ad09d 100644 --- a/backend/src/dal/blocklist.ts +++ b/backend/src/dal/blocklist.ts @@ -1,7 +1,7 @@ import { Collection } from "mongodb"; import * as db from "../init/db"; import { createHash } from "crypto"; -import { User } from "@monkeytype/contracts/schemas/users"; +import { User } from "@monkeytype/schemas/users"; import { WithObjectId } from "../utils/misc"; type BlocklistEntryProperties = Pick; diff --git a/backend/src/dal/config.ts b/backend/src/dal/config.ts index a11b32fe5bbf..e94ef0216750 100644 --- a/backend/src/dal/config.ts +++ b/backend/src/dal/config.ts @@ -1,7 +1,7 @@ import { Collection, ObjectId, UpdateResult } from "mongodb"; import * as db from "../init/db"; import _ from "lodash"; -import { Config, PartialConfig } from "@monkeytype/contracts/schemas/configs"; +import { Config, PartialConfig } from "@monkeytype/schemas/configs"; const configLegacyProperties = [ "swapEscAndTab", diff --git a/backend/src/dal/leaderboards.ts b/backend/src/dal/leaderboards.ts index a724c4de1f7d..86269fb8437a 100644 --- a/backend/src/dal/leaderboards.ts +++ b/backend/src/dal/leaderboards.ts @@ -10,7 +10,7 @@ import { import { addLog } from "./logs"; import { Collection, ObjectId } from "mongodb"; -import { LeaderboardEntry } from "@monkeytype/contracts/schemas/leaderboards"; +import { LeaderboardEntry } from "@monkeytype/schemas/leaderboards"; import { omit } from "lodash"; import { DBUser, getUsersCollection } from "./user"; import MonkeyError from "../utils/error"; diff --git a/backend/src/dal/new-quotes.ts b/backend/src/dal/new-quotes.ts index 0f17d7aabf20..c562e47bc934 100644 --- a/backend/src/dal/new-quotes.ts +++ b/backend/src/dal/new-quotes.ts @@ -6,12 +6,12 @@ import { readFile } from "node:fs/promises"; import * as db from "../init/db"; import MonkeyError from "../utils/error"; import { compareTwoStrings } from "string-similarity"; -import { ApproveQuote, Quote } from "@monkeytype/contracts/schemas/quotes"; +import { ApproveQuote, Quote } from "@monkeytype/schemas/quotes"; import { WithObjectId } from "../utils/misc"; import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json"; import { z } from "zod"; import { tryCatchSync } from "@monkeytype/util/trycatch"; -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { Language } from "@monkeytype/schemas/languages"; const JsonQuoteSchema = z.object({ text: z.string(), diff --git a/backend/src/dal/preset.ts b/backend/src/dal/preset.ts index 0ffb3075e163..aae2d3c42dfb 100644 --- a/backend/src/dal/preset.ts +++ b/backend/src/dal/preset.ts @@ -1,10 +1,7 @@ import MonkeyError from "../utils/error"; import * as db from "../init/db"; import { ObjectId, type Filter, Collection, type WithId } from "mongodb"; -import { - EditPresetRequest, - Preset, -} from "@monkeytype/contracts/schemas/presets"; +import { EditPresetRequest, Preset } from "@monkeytype/schemas/presets"; import { omit } from "lodash"; import { WithObjectId } from "../utils/misc"; diff --git a/backend/src/dal/psa.ts b/backend/src/dal/psa.ts index 2f57bb2b03c3..817e341f7eeb 100644 --- a/backend/src/dal/psa.ts +++ b/backend/src/dal/psa.ts @@ -1,4 +1,4 @@ -import { PSA } from "@monkeytype/contracts/schemas/psas"; +import { PSA } from "@monkeytype/schemas/psas"; import * as db from "../init/db"; import { WithObjectId } from "../utils/misc"; diff --git a/backend/src/dal/public.ts b/backend/src/dal/public.ts index 7791935fdf90..edef961025cf 100644 --- a/backend/src/dal/public.ts +++ b/backend/src/dal/public.ts @@ -1,10 +1,7 @@ import { roundTo2 } from "@monkeytype/util/numbers"; import * as db from "../init/db"; import MonkeyError from "../utils/error"; -import { - TypingStats, - SpeedHistogram, -} from "@monkeytype/contracts/schemas/public"; +import { TypingStats, SpeedHistogram } from "@monkeytype/schemas/public"; export type PublicTypingStatsDB = TypingStats & { _id: "stats" }; export type PublicSpeedStatsDB = { diff --git a/backend/src/dal/quote-ratings.ts b/backend/src/dal/quote-ratings.ts index c7b1f3cf3f26..075094d39eba 100644 --- a/backend/src/dal/quote-ratings.ts +++ b/backend/src/dal/quote-ratings.ts @@ -1,8 +1,8 @@ -import { QuoteRating } from "@monkeytype/contracts/schemas/quotes"; +import { QuoteRating } from "@monkeytype/schemas/quotes"; import * as db from "../init/db"; import { Collection } from "mongodb"; import { WithObjectId } from "../utils/misc"; -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { Language } from "@monkeytype/schemas/languages"; type DBQuoteRating = WithObjectId; diff --git a/backend/src/dal/result.ts b/backend/src/dal/result.ts index f5708f60b9fd..499734086d6b 100644 --- a/backend/src/dal/result.ts +++ b/backend/src/dal/result.ts @@ -10,7 +10,7 @@ import * as db from "../init/db"; import { getUser, getTags } from "./user"; import { DBResult } from "../utils/result"; -import { FunboxName } from "@monkeytype/contracts/schemas/configs"; +import { FunboxName } from "@monkeytype/schemas/configs"; import { tryCatch } from "@monkeytype/util/trycatch"; export const getResultCollection = (): Collection => diff --git a/backend/src/dal/user.ts b/backend/src/dal/user.ts index 9bc4f3896c90..17b0615ad30b 100644 --- a/backend/src/dal/user.ts +++ b/backend/src/dal/user.ts @@ -26,15 +26,11 @@ import { UserTag, User, CountByYearAndDay, -} from "@monkeytype/contracts/schemas/users"; -import { - Mode, - Mode2, - PersonalBest, -} from "@monkeytype/contracts/schemas/shared"; +} from "@monkeytype/schemas/users"; +import { Mode, Mode2, PersonalBest } from "@monkeytype/schemas/shared"; import { addImportantLog } from "./logs"; -import { Result as ResultType } from "@monkeytype/contracts/schemas/results"; -import { Configuration } from "@monkeytype/contracts/schemas/configuration"; +import { Result as ResultType } from "@monkeytype/schemas/results"; +import { Configuration } from "@monkeytype/schemas/configuration"; import { isToday, isYesterday } from "@monkeytype/util/date-and-time"; import GeorgeQueue from "../queues/george-queue"; diff --git a/backend/src/init/configuration.ts b/backend/src/init/configuration.ts index 99d316d52846..6fc5e218c861 100644 --- a/backend/src/init/configuration.ts +++ b/backend/src/init/configuration.ts @@ -4,7 +4,7 @@ import { ObjectId } from "mongodb"; import Logger from "../utils/logger"; import { identity } from "../utils/misc"; import { BASE_CONFIGURATION } from "../constants/base-configuration"; -import { Configuration } from "@monkeytype/contracts/schemas/configuration"; +import { Configuration } from "@monkeytype/schemas/configuration"; import { addLog } from "../dal/logs"; import { PartialConfiguration, diff --git a/backend/src/middlewares/auth.ts b/backend/src/middlewares/auth.ts index b8ac87be3fc0..3c04c981c4b3 100644 --- a/backend/src/middlewares/auth.ts +++ b/backend/src/middlewares/auth.ts @@ -17,8 +17,8 @@ import { AppRoute, AppRouter } from "@ts-rest/core"; import { EndpointMetadata, RequestAuthenticationOptions, -} from "@monkeytype/contracts/schemas/api"; -import { Configuration } from "@monkeytype/contracts/schemas/configuration"; +} from "@monkeytype/contracts/util/api"; +import { Configuration } from "@monkeytype/schemas/configuration"; import { getMetadata } from "./utility"; import { TsRestRequestWithContext } from "../api/types"; diff --git a/backend/src/middlewares/configuration.ts b/backend/src/middlewares/configuration.ts index 02b04ecf55a0..cda2f4147469 100644 --- a/backend/src/middlewares/configuration.ts +++ b/backend/src/middlewares/configuration.ts @@ -1,8 +1,8 @@ import type { Response, NextFunction } from "express"; import { TsRestRequestHandler } from "@ts-rest/express"; -import { EndpointMetadata } from "@monkeytype/contracts/schemas/api"; +import { EndpointMetadata } from "@monkeytype/contracts/util/api"; import MonkeyError from "../utils/error"; -import { Configuration } from "@monkeytype/contracts/schemas/configuration"; +import { Configuration } from "@monkeytype/schemas/configuration"; import { ConfigurationPath, RequireConfiguration, diff --git a/backend/src/middlewares/context.ts b/backend/src/middlewares/context.ts index ae76941a1595..e8c6d4ed51ce 100644 --- a/backend/src/middlewares/context.ts +++ b/backend/src/middlewares/context.ts @@ -5,7 +5,7 @@ import type { Request as ExpressRequest, } from "express"; import { DecodedToken } from "./auth"; -import { Configuration } from "@monkeytype/contracts/schemas/configuration"; +import { Configuration } from "@monkeytype/schemas/configuration"; import { ExpressRequestWithContext } from "../api/types"; export type Context = { diff --git a/backend/src/middlewares/permission.ts b/backend/src/middlewares/permission.ts index 7c40b0385fa1..4fdcdaddc42b 100644 --- a/backend/src/middlewares/permission.ts +++ b/backend/src/middlewares/permission.ts @@ -8,7 +8,7 @@ import { EndpointMetadata, RequestAuthenticationOptions, PermissionId, -} from "@monkeytype/contracts/schemas/api"; +} from "@monkeytype/contracts/util/api"; import { isDevEnvironment } from "../utils/misc"; import { getMetadata } from "./utility"; import { TsRestRequestWithContext } from "../api/types"; diff --git a/backend/src/middlewares/utility.ts b/backend/src/middlewares/utility.ts index 28d210bf534b..8964f9de5d32 100644 --- a/backend/src/middlewares/utility.ts +++ b/backend/src/middlewares/utility.ts @@ -3,7 +3,7 @@ import type { Request, Response, NextFunction, RequestHandler } from "express"; import { recordClientVersion as prometheusRecordClientVersion } from "../utils/prometheus"; import { isDevEnvironment } from "../utils/misc"; import MonkeyError from "../utils/error"; -import { EndpointMetadata } from "@monkeytype/contracts/schemas/api"; +import { EndpointMetadata } from "@monkeytype/contracts/util/api"; import { TsRestRequestWithContext } from "../api/types"; /** diff --git a/backend/src/queues/george-queue.ts b/backend/src/queues/george-queue.ts index 4e8d5994292c..6d2b31380e39 100644 --- a/backend/src/queues/george-queue.ts +++ b/backend/src/queues/george-queue.ts @@ -1,4 +1,4 @@ -import { LeaderboardEntry } from "@monkeytype/contracts/schemas/leaderboards"; +import { LeaderboardEntry } from "@monkeytype/schemas/leaderboards"; import { MonkeyQueue } from "./monkey-queue"; const QUEUE_NAME = "george-tasks"; diff --git a/backend/src/queues/later-queue.ts b/backend/src/queues/later-queue.ts index 7108cf60cafb..71d3db22b6e4 100644 --- a/backend/src/queues/later-queue.ts +++ b/backend/src/queues/later-queue.ts @@ -1,7 +1,7 @@ import LRUCache from "lru-cache"; import Logger from "../utils/logger"; import { MonkeyQueue } from "./monkey-queue"; -import { ValidModeRule } from "@monkeytype/contracts/schemas/configuration"; +import { ValidModeRule } from "@monkeytype/schemas/configuration"; import { getCurrentDayTimestamp, getCurrentWeekTimestamp, diff --git a/backend/src/services/weekly-xp-leaderboard.ts b/backend/src/services/weekly-xp-leaderboard.ts index 7f79256fff40..310ae6627e11 100644 --- a/backend/src/services/weekly-xp-leaderboard.ts +++ b/backend/src/services/weekly-xp-leaderboard.ts @@ -1,4 +1,4 @@ -import { Configuration } from "@monkeytype/contracts/schemas/configuration"; +import { Configuration } from "@monkeytype/schemas/configuration"; import * as RedisClient from "../init/redis"; import LaterQueue from "../queues/later-queue"; import { @@ -6,7 +6,7 @@ import { RedisXpLeaderboardEntrySchema, RedisXpLeaderboardScore, XpLeaderboardEntry, -} from "@monkeytype/contracts/schemas/leaderboards"; +} from "@monkeytype/schemas/leaderboards"; import { getCurrentWeekTimestamp } from "@monkeytype/util/date-and-time"; import MonkeyError from "../utils/error"; import { omit } from "lodash"; diff --git a/backend/src/utils/daily-leaderboards.ts b/backend/src/utils/daily-leaderboards.ts index b0f1600462dd..f8661186d6e1 100644 --- a/backend/src/utils/daily-leaderboards.ts +++ b/backend/src/utils/daily-leaderboards.ts @@ -6,14 +6,14 @@ import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json"; import { Configuration, ValidModeRule, -} from "@monkeytype/contracts/schemas/configuration"; +} from "@monkeytype/schemas/configuration"; import { LeaderboardEntry, RedisDailyLeaderboardEntry, RedisDailyLeaderboardEntrySchema, -} from "@monkeytype/contracts/schemas/leaderboards"; +} from "@monkeytype/schemas/leaderboards"; import MonkeyError from "./error"; -import { Mode, Mode2 } from "@monkeytype/contracts/schemas/shared"; +import { Mode, Mode2 } from "@monkeytype/schemas/shared"; import { getCurrentDayTimestamp } from "@monkeytype/util/date-and-time"; const dailyLeaderboardNamespace = "monkeytype:dailyleaderboard"; diff --git a/backend/src/utils/error.ts b/backend/src/utils/error.ts index 2646c55c1645..8b04b2d5cf21 100644 --- a/backend/src/utils/error.ts +++ b/backend/src/utils/error.ts @@ -1,6 +1,6 @@ import { v4 as uuidv4 } from "uuid"; import { isDevEnvironment } from "./misc"; -import { MonkeyServerErrorType } from "@monkeytype/contracts/schemas/api"; +import { MonkeyServerErrorType } from "@monkeytype/contracts/util/api"; import { FirebaseError } from "firebase-admin"; type FirebaseErrorParent = { diff --git a/backend/src/utils/monkey-mail.ts b/backend/src/utils/monkey-mail.ts index d97bb866585b..26be818761bb 100644 --- a/backend/src/utils/monkey-mail.ts +++ b/backend/src/utils/monkey-mail.ts @@ -1,4 +1,4 @@ -import { MonkeyMail } from "@monkeytype/contracts/schemas/users"; +import { MonkeyMail } from "@monkeytype/schemas/users"; import { v4 } from "uuid"; type MonkeyMailOptions = Partial>; diff --git a/backend/src/utils/monkey-response.ts b/backend/src/utils/monkey-response.ts index 7945759915ea..39b4c5b5d891 100644 --- a/backend/src/utils/monkey-response.ts +++ b/backend/src/utils/monkey-response.ts @@ -1,4 +1,4 @@ -import { MonkeyResponseType } from "@monkeytype/contracts/schemas/api"; +import { MonkeyResponseType } from "@monkeytype/contracts/util/api"; export type MonkeyDataAware = { data: T | null; diff --git a/backend/src/utils/pb.ts b/backend/src/utils/pb.ts index 2ddd5b2b0149..80897febb6cb 100644 --- a/backend/src/utils/pb.ts +++ b/backend/src/utils/pb.ts @@ -1,10 +1,6 @@ import _ from "lodash"; -import { - Mode, - PersonalBest, - PersonalBests, -} from "@monkeytype/contracts/schemas/shared"; -import { Result as ResultType } from "@monkeytype/contracts/schemas/results"; +import { Mode, PersonalBest, PersonalBests } from "@monkeytype/schemas/shared"; +import { Result as ResultType } from "@monkeytype/schemas/results"; import { getFunbox } from "@monkeytype/funbox"; export type LbPersonalBests = { diff --git a/backend/src/utils/prometheus.ts b/backend/src/utils/prometheus.ts index 3f0af8cc5ba5..9761e5d2f3ed 100644 --- a/backend/src/utils/prometheus.ts +++ b/backend/src/utils/prometheus.ts @@ -1,6 +1,6 @@ import "dotenv/config"; import { Counter, Histogram, Gauge } from "prom-client"; -import { CompletedEvent } from "@monkeytype/contracts/schemas/results"; +import { CompletedEvent } from "@monkeytype/schemas/results"; import { Request } from "express"; const auth = new Counter({ diff --git a/backend/src/utils/result.ts b/backend/src/utils/result.ts index 51f4f3cde8a2..c59b92777e89 100644 --- a/backend/src/utils/result.ts +++ b/backend/src/utils/result.ts @@ -1,5 +1,5 @@ -import { CompletedEvent, Result } from "@monkeytype/contracts/schemas/results"; -import { Mode } from "@monkeytype/contracts/schemas/shared"; +import { CompletedEvent, Result } from "@monkeytype/schemas/results"; +import { Mode } from "@monkeytype/schemas/shared"; import { ObjectId } from "mongodb"; import { WithObjectId } from "./misc"; diff --git a/backend/src/utils/validation.ts b/backend/src/utils/validation.ts index 12a958a3cf36..66bc2b4a52aa 100644 --- a/backend/src/utils/validation.ts +++ b/backend/src/utils/validation.ts @@ -1,5 +1,5 @@ import _ from "lodash"; -import { CompletedEvent } from "@monkeytype/contracts/schemas/results"; +import { CompletedEvent } from "@monkeytype/schemas/results"; export function isTestTooShort(result: CompletedEvent): boolean { const { mode, mode2, customText, testDuration, bailedOut } = result; diff --git a/backend/src/workers/later-worker.ts b/backend/src/workers/later-worker.ts index 39d9e6a0d50c..2ca2ea8f31dc 100644 --- a/backend/src/workers/later-worker.ts +++ b/backend/src/workers/later-worker.ts @@ -15,7 +15,7 @@ import LaterQueue, { } from "../queues/later-queue"; import { recordTimeToCompleteJob } from "../utils/prometheus"; import { WeeklyXpLeaderboard } from "../services/weekly-xp-leaderboard"; -import { MonkeyMail } from "@monkeytype/contracts/schemas/users"; +import { MonkeyMail } from "@monkeytype/schemas/users"; import { isSafeNumber, mapRange } from "@monkeytype/util/numbers"; async function handleDailyLeaderboardResults( diff --git a/frontend/__tests__/constants/languages.spec.ts b/frontend/__tests__/constants/languages.spec.ts index 873da2891d6c..c406e8429242 100644 --- a/frontend/__tests__/constants/languages.spec.ts +++ b/frontend/__tests__/constants/languages.spec.ts @@ -1,6 +1,6 @@ import { readdirSync } from "fs"; import { LanguageGroups, LanguageList } from "../../src/ts/constants/languages"; -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { Language } from "@monkeytype/schemas/languages"; describe("languages", () => { describe("LanguageList", () => { diff --git a/frontend/__tests__/constants/layouts.spec.ts b/frontend/__tests__/constants/layouts.spec.ts index 13881b15e9ab..c3349fbf7a55 100644 --- a/frontend/__tests__/constants/layouts.spec.ts +++ b/frontend/__tests__/constants/layouts.spec.ts @@ -1,6 +1,6 @@ import { readdirSync } from "fs"; import { LayoutsList } from "../../src/ts/constants/layouts"; -import { LayoutName } from "@monkeytype/contracts/schemas/layouts"; +import { LayoutName } from "@monkeytype/schemas/layouts"; describe("layouts", () => { it("should not have duplicates", () => { diff --git a/frontend/__tests__/root/config.spec.ts b/frontend/__tests__/root/config.spec.ts index c3f4c2427161..2fd5860a2bea 100644 --- a/frontend/__tests__/root/config.spec.ts +++ b/frontend/__tests__/root/config.spec.ts @@ -5,7 +5,7 @@ import { FunboxName, ConfigKey, Config as ConfigType, -} from "@monkeytype/contracts/schemas/configs"; +} from "@monkeytype/schemas/configs"; import { randomBytes } from "crypto"; import { vi } from "vitest"; import * as FunboxValidation from "../../src/ts/test/funbox/funbox-validation"; diff --git a/frontend/__tests__/test/funbox/funbox-validation.spec.ts b/frontend/__tests__/test/funbox/funbox-validation.spec.ts index 03dd7404f291..48d9e484c895 100644 --- a/frontend/__tests__/test/funbox/funbox-validation.spec.ts +++ b/frontend/__tests__/test/funbox/funbox-validation.spec.ts @@ -1,7 +1,7 @@ import { canSetConfigWithCurrentFunboxes } from "../../../src/ts/test/funbox/funbox-validation"; import * as Notifications from "../../../src/ts/elements/notifications"; -import { FunboxName } from "@monkeytype/contracts/schemas/configs"; +import { FunboxName } from "@monkeytype/schemas/configs"; describe("funbox-validation", () => { describe("canSetConfigWithCurrentFunboxes", () => { const addNotificationMock = vi.spyOn(Notifications, "add"); diff --git a/frontend/__tests__/utils/config.spec.ts b/frontend/__tests__/utils/config.spec.ts index 09ed2f718085..1f7049f5a144 100644 --- a/frontend/__tests__/utils/config.spec.ts +++ b/frontend/__tests__/utils/config.spec.ts @@ -1,6 +1,6 @@ import { getDefaultConfig } from "../../src/ts/constants/default-config"; import { migrateConfig } from "../../src/ts/utils/config"; -import { PartialConfig } from "@monkeytype/contracts/schemas/configs"; +import { PartialConfig } from "@monkeytype/schemas/configs"; const defaultConfig = getDefaultConfig(); diff --git a/frontend/__tests__/utils/format.spec.ts b/frontend/__tests__/utils/format.spec.ts index 7b348f735282..b9718fd96eba 100644 --- a/frontend/__tests__/utils/format.spec.ts +++ b/frontend/__tests__/utils/format.spec.ts @@ -1,6 +1,6 @@ import { getDefaultConfig } from "../../src/ts/constants/default-config"; import { Formatting } from "../../src/ts/utils/format"; -import { Config } from "@monkeytype/contracts/schemas/configs"; +import { Config } from "@monkeytype/schemas/configs"; describe("format.ts", () => { describe("typingsSpeed", () => { diff --git a/frontend/__tests__/utils/url-handler.spec.ts b/frontend/__tests__/utils/url-handler.spec.ts index 18504b90c426..f26013b0c35d 100644 --- a/frontend/__tests__/utils/url-handler.spec.ts +++ b/frontend/__tests__/utils/url-handler.spec.ts @@ -1,5 +1,5 @@ // eslint-disable no-useless-escape -import { Difficulty, Mode, Mode2 } from "@monkeytype/contracts/schemas/shared"; +import { Difficulty, Mode, Mode2 } from "@monkeytype/schemas/shared"; import { compressToURI } from "lz-ts"; import * as UpdateConfig from "../../src/ts/config"; import * as Notifications from "../../src/ts/elements/notifications"; @@ -8,7 +8,7 @@ import * as TestLogic from "../../src/ts/test/test-logic"; import * as TestState from "../../src/ts/test/test-state"; import * as Misc from "../../src/ts/utils/misc"; import { loadTestSettingsFromUrl } from "../../src/ts/utils/url-handler"; -import { FunboxName } from "@monkeytype/contracts/schemas/configs"; +import { FunboxName } from "@monkeytype/schemas/configs"; //mock modules to avoid dependencies vi.mock("../../src/ts/test/test-logic", () => ({ diff --git a/frontend/package.json b/frontend/package.json index 9d492ab7d3bf..22d037d0b390 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -36,6 +36,7 @@ "@fortawesome/fontawesome-free": "5.15.4", "@monkeytype/eslint-config": "workspace:*", "@monkeytype/oxlint-config": "workspace:*", + "@monkeytype/schemas": "workspace:*", "@monkeytype/typescript-config": "workspace:*", "@types/canvas-confetti": "1.4.3", "@types/chartjs-plugin-trendline": "1.0.1", diff --git a/frontend/src/ts/ape/server-configuration.ts b/frontend/src/ts/ape/server-configuration.ts index b7a08d498747..96ca23f2ecbc 100644 --- a/frontend/src/ts/ape/server-configuration.ts +++ b/frontend/src/ts/ape/server-configuration.ts @@ -1,4 +1,4 @@ -import { Configuration } from "@monkeytype/contracts/schemas/configuration"; +import { Configuration } from "@monkeytype/schemas/configuration"; import Ape from "."; import { promiseWithResolvers } from "../utils/misc"; diff --git a/frontend/src/ts/commandline/lists.ts b/frontend/src/ts/commandline/lists.ts index cb5bd9ff7e70..a05e1b584555 100644 --- a/frontend/src/ts/commandline/lists.ts +++ b/frontend/src/ts/commandline/lists.ts @@ -104,7 +104,7 @@ import { CustomLayoutFluidSchema, CustomPolyglot, CustomPolyglotSchema, -} from "@monkeytype/contracts/schemas/configs"; +} from "@monkeytype/schemas/configs"; import { Command, CommandsSubgroup, withValidation } from "./types"; import * as TestLogic from "../test/test-logic"; import * as ActivePage from "../states/active-page"; diff --git a/frontend/src/ts/commandline/lists/add-or-remove-theme-to-favorites.ts b/frontend/src/ts/commandline/lists/add-or-remove-theme-to-favorites.ts index d1554824b7b1..0ddd9462d212 100644 --- a/frontend/src/ts/commandline/lists/add-or-remove-theme-to-favorites.ts +++ b/frontend/src/ts/commandline/lists/add-or-remove-theme-to-favorites.ts @@ -1,4 +1,4 @@ -import { ThemeName } from "@monkeytype/contracts/schemas/configs"; +import { ThemeName } from "@monkeytype/schemas/configs"; import Config, * as UpdateConfig from "../../config"; import { randomTheme } from "../../controllers/theme-controller"; import { Command } from "../types"; diff --git a/frontend/src/ts/commandline/lists/font-size.ts b/frontend/src/ts/commandline/lists/font-size.ts index 7960d60b0aaa..63245fc12a33 100644 --- a/frontend/src/ts/commandline/lists/font-size.ts +++ b/frontend/src/ts/commandline/lists/font-size.ts @@ -1,4 +1,4 @@ -import { FontSizeSchema } from "@monkeytype/contracts/schemas/configs"; +import { FontSizeSchema } from "@monkeytype/schemas/configs"; import Config, * as UpdateConfig from "../../config"; import { Command, withValidation } from "../types"; diff --git a/frontend/src/ts/commandline/lists/keymap-layouts.ts b/frontend/src/ts/commandline/lists/keymap-layouts.ts index b61db3861397..54e082369e35 100644 --- a/frontend/src/ts/commandline/lists/keymap-layouts.ts +++ b/frontend/src/ts/commandline/lists/keymap-layouts.ts @@ -1,4 +1,4 @@ -import { KeymapLayout } from "@monkeytype/contracts/schemas/configs"; +import { KeymapLayout } from "@monkeytype/schemas/configs"; import * as UpdateConfig from "../../config"; import { LayoutsList } from "../../constants/layouts"; import * as TestLogic from "../../test/test-logic"; diff --git a/frontend/src/ts/commandline/types.ts b/frontend/src/ts/commandline/types.ts index fe3483b47ad5..ad1c7ecbf142 100644 --- a/frontend/src/ts/commandline/types.ts +++ b/frontend/src/ts/commandline/types.ts @@ -1,4 +1,4 @@ -import { Config } from "@monkeytype/contracts/schemas/configs"; +import { Config } from "@monkeytype/schemas/configs"; import AnimatedModal from "../utils/animated-modal"; import { z } from "zod"; diff --git a/frontend/src/ts/config.ts b/frontend/src/ts/config.ts index 345a2a04026a..5738b650f7af 100644 --- a/frontend/src/ts/config.ts +++ b/frontend/src/ts/config.ts @@ -17,10 +17,10 @@ import { reloadAfter, typedKeys, } from "./utils/misc"; -import * as ConfigSchemas from "@monkeytype/contracts/schemas/configs"; -import { Config, FunboxName } from "@monkeytype/contracts/schemas/configs"; -import { Mode } from "@monkeytype/contracts/schemas/shared"; -import { Language } from "@monkeytype/contracts/schemas/languages"; +import * as ConfigSchemas from "@monkeytype/schemas/configs"; +import { Config, FunboxName } from "@monkeytype/schemas/configs"; +import { Mode } from "@monkeytype/schemas/shared"; +import { Language } from "@monkeytype/schemas/languages"; import { LocalStorageWithSchema } from "./utils/local-storage-with-schema"; import { migrateConfig } from "./utils/config"; import { roundTo1 } from "@monkeytype/util/numbers"; diff --git a/frontend/src/ts/constants/default-config.ts b/frontend/src/ts/constants/default-config.ts index 7d49be1eb23f..7ae2db10906d 100644 --- a/frontend/src/ts/constants/default-config.ts +++ b/frontend/src/ts/constants/default-config.ts @@ -1,7 +1,4 @@ -import { - Config, - CustomThemeColors, -} from "@monkeytype/contracts/schemas/configs"; +import { Config, CustomThemeColors } from "@monkeytype/schemas/configs"; import { deepClone } from "../utils/misc"; const obj = { diff --git a/frontend/src/ts/constants/default-result-filters.ts b/frontend/src/ts/constants/default-result-filters.ts index b18f66b36726..298fef6d1f97 100644 --- a/frontend/src/ts/constants/default-result-filters.ts +++ b/frontend/src/ts/constants/default-result-filters.ts @@ -1,4 +1,4 @@ -import { ResultFilters } from "@monkeytype/contracts/schemas/users"; +import { ResultFilters } from "@monkeytype/schemas/users"; import { deepClone } from "../utils/misc"; import { LanguageList } from "./languages"; import { getFunboxNames } from "@monkeytype/funbox"; diff --git a/frontend/src/ts/constants/default-snapshot.ts b/frontend/src/ts/constants/default-snapshot.ts index 8b5a63a4fa08..fc03037eb126 100644 --- a/frontend/src/ts/constants/default-snapshot.ts +++ b/frontend/src/ts/constants/default-snapshot.ts @@ -3,18 +3,18 @@ import { User, UserProfileDetails, UserTag, -} from "@monkeytype/contracts/schemas/users"; +} from "@monkeytype/schemas/users"; import { deepClone } from "../utils/misc"; import { getDefaultConfig } from "./default-config"; -import { Mode } from "@monkeytype/contracts/schemas/shared"; -import { Result } from "@monkeytype/contracts/schemas/results"; -import { Config, FunboxName } from "@monkeytype/contracts/schemas/configs"; +import { Mode } from "@monkeytype/schemas/shared"; +import { Result } from "@monkeytype/schemas/results"; +import { Config, FunboxName } from "@monkeytype/schemas/configs"; import { ModifiableTestActivityCalendar, TestActivityCalendar, } from "../elements/test-activity-calendar"; -import { Preset } from "@monkeytype/contracts/schemas/presets"; -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { Preset } from "@monkeytype/schemas/presets"; +import { Language } from "@monkeytype/schemas/languages"; export type SnapshotUserTag = UserTag & { active?: boolean; diff --git a/frontend/src/ts/constants/languages.ts b/frontend/src/ts/constants/languages.ts index dfffbe96bb77..2c47dd738313 100644 --- a/frontend/src/ts/constants/languages.ts +++ b/frontend/src/ts/constants/languages.ts @@ -1,7 +1,4 @@ -import { - Language, - LanguageSchema, -} from "@monkeytype/contracts/schemas/languages"; +import { Language, LanguageSchema } from "@monkeytype/schemas/languages"; export const LanguageList: Language[] = LanguageSchema._def.values; diff --git a/frontend/src/ts/constants/layouts.ts b/frontend/src/ts/constants/layouts.ts index 2b0ec1c252f6..ff8f5a9ec340 100644 --- a/frontend/src/ts/constants/layouts.ts +++ b/frontend/src/ts/constants/layouts.ts @@ -1,3 +1,3 @@ -import { LayoutName, LayoutNameSchema } from "@monkeytype/contracts/schemas/layouts"; +import { LayoutName, LayoutNameSchema } from "@monkeytype/schemas/layouts"; export const LayoutsList:LayoutName[] = LayoutNameSchema._def.values; \ No newline at end of file diff --git a/frontend/src/ts/constants/themes.ts b/frontend/src/ts/constants/themes.ts index b43fc0830abf..65ff67be00df 100644 --- a/frontend/src/ts/constants/themes.ts +++ b/frontend/src/ts/constants/themes.ts @@ -1,4 +1,4 @@ -import { ThemeName } from "@monkeytype/contracts/schemas/configs"; +import { ThemeName } from "@monkeytype/schemas/configs"; import { hexToHSL } from "../utils/colors"; export type Theme = { diff --git a/frontend/src/ts/controllers/challenge-controller.ts b/frontend/src/ts/controllers/challenge-controller.ts index 15a4137a5759..7d438b441c4b 100644 --- a/frontend/src/ts/controllers/challenge-controller.ts +++ b/frontend/src/ts/controllers/challenge-controller.ts @@ -9,18 +9,15 @@ import * as TestUI from "../test/test-ui"; import * as ConfigEvent from "../observables/config-event"; import * as TestState from "../test/test-state"; import * as Loader from "../elements/loader"; -import { - CustomTextLimitMode, - CustomTextMode, -} from "@monkeytype/contracts/schemas/util"; +import { CustomTextLimitMode, CustomTextMode } from "@monkeytype/schemas/util"; import { Config as ConfigType, Difficulty, ThemeName, FunboxName, -} from "@monkeytype/contracts/schemas/configs"; -import { Mode } from "@monkeytype/contracts/schemas/shared"; -import { CompletedEvent } from "@monkeytype/contracts/schemas/results"; +} from "@monkeytype/schemas/configs"; +import { Mode } from "@monkeytype/schemas/shared"; +import { CompletedEvent } from "@monkeytype/schemas/results"; import { areUnsortedArraysEqual } from "../utils/arrays"; import { tryCatch } from "@monkeytype/util/trycatch"; diff --git a/frontend/src/ts/controllers/preset-controller.ts b/frontend/src/ts/controllers/preset-controller.ts index ed56984a0079..450d21664bb1 100644 --- a/frontend/src/ts/controllers/preset-controller.ts +++ b/frontend/src/ts/controllers/preset-controller.ts @@ -1,4 +1,4 @@ -import { Preset } from "@monkeytype/contracts/schemas/presets"; +import { Preset } from "@monkeytype/schemas/presets"; import * as UpdateConfig from "../config"; import * as DB from "../db"; import * as Notifications from "../elements/notifications"; diff --git a/frontend/src/ts/controllers/quotes-controller.ts b/frontend/src/ts/controllers/quotes-controller.ts index 19c5836a8a19..ac43b790335b 100644 --- a/frontend/src/ts/controllers/quotes-controller.ts +++ b/frontend/src/ts/controllers/quotes-controller.ts @@ -5,7 +5,7 @@ import { subscribe } from "../observables/config-event"; import * as DB from "../db"; import Ape from "../ape"; import { tryCatch } from "@monkeytype/util/trycatch"; -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { Language } from "@monkeytype/schemas/languages"; export type Quote = { text: string; diff --git a/frontend/src/ts/controllers/sound-controller.ts b/frontend/src/ts/controllers/sound-controller.ts index cb7fb5fa285d..aa2a01275d16 100644 --- a/frontend/src/ts/controllers/sound-controller.ts +++ b/frontend/src/ts/controllers/sound-controller.ts @@ -8,7 +8,7 @@ import { capsState } from "../test/caps-warning"; import * as Notifications from "../elements/notifications"; import type { Howl } from "howler"; -import { PlaySoundOnClick } from "@monkeytype/contracts/schemas/configs"; +import { PlaySoundOnClick } from "@monkeytype/schemas/configs"; async function gethowler(): Promise { return await import("howler"); diff --git a/frontend/src/ts/controllers/tag-controller.ts b/frontend/src/ts/controllers/tag-controller.ts index a6d3fbbbfdcc..7e9bd4308cfb 100644 --- a/frontend/src/ts/controllers/tag-controller.ts +++ b/frontend/src/ts/controllers/tag-controller.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import * as DB from "../db"; import * as ModesNotice from "../elements/modes-notice"; import { LocalStorageWithSchema } from "../utils/local-storage-with-schema"; -import { IdSchema } from "@monkeytype/contracts/schemas/util"; +import { IdSchema } from "@monkeytype/schemas/util"; const activeTagsLS = new LocalStorageWithSchema({ key: "activeTags", diff --git a/frontend/src/ts/controllers/theme-controller.ts b/frontend/src/ts/controllers/theme-controller.ts index 4ae10593d3f7..4a3ae641d98e 100644 --- a/frontend/src/ts/controllers/theme-controller.ts +++ b/frontend/src/ts/controllers/theme-controller.ts @@ -10,7 +10,7 @@ import * as DB from "../db"; import * as Notifications from "../elements/notifications"; import * as Loader from "../elements/loader"; import { debounce } from "throttle-debounce"; -import { ThemeName } from "@monkeytype/contracts/schemas/configs"; +import { ThemeName } from "@monkeytype/schemas/configs"; import { ThemesList } from "../constants/themes"; export let randomTheme: ThemeName | string | null = null; diff --git a/frontend/src/ts/db.ts b/frontend/src/ts/db.ts index c6174292b301..a5050be52f06 100644 --- a/frontend/src/ts/db.ts +++ b/frontend/src/ts/db.ts @@ -12,14 +12,14 @@ import { } from "./elements/test-activity-calendar"; import * as Loader from "./elements/loader"; -import { Badge, CustomTheme } from "@monkeytype/contracts/schemas/users"; -import { Config, Difficulty } from "@monkeytype/contracts/schemas/configs"; +import { Badge, CustomTheme } from "@monkeytype/schemas/users"; +import { Config, Difficulty } from "@monkeytype/schemas/configs"; import { Mode, Mode2, PersonalBest, PersonalBests, -} from "@monkeytype/contracts/schemas/shared"; +} from "@monkeytype/schemas/shared"; import { getDefaultSnapshot, Snapshot, @@ -30,7 +30,7 @@ import { import { getDefaultConfig } from "./constants/default-config"; import { FunboxMetadata } from "../../../packages/funbox/src/types"; import { getFirstDayOfTheWeek } from "./utils/date-and-time"; -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { Language } from "@monkeytype/schemas/languages"; let dbSnapshot: Snapshot | undefined; const firstDayOfTheWeek = getFirstDayOfTheWeek(); 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 deecddd4d336..d68d3f98108f 100644 --- a/frontend/src/ts/elements/account-settings/ape-key-table.ts +++ b/frontend/src/ts/elements/account-settings/ape-key-table.ts @@ -1,7 +1,7 @@ import * as Loader from "../../elements/loader"; import * as Notifications from "../../elements/notifications"; import Ape from "../../ape"; -import { ApeKey, ApeKeys } from "@monkeytype/contracts/schemas/ape-keys"; +import { ApeKey, ApeKeys } from "@monkeytype/schemas/ape-keys"; import { format } from "date-fns/format"; import { SimpleModal, TextArea } from "../../utils/simple-modal"; diff --git a/frontend/src/ts/elements/account/pb-tables.ts b/frontend/src/ts/elements/account/pb-tables.ts index dd6dfc77b277..fa724d0a6a14 100644 --- a/frontend/src/ts/elements/account/pb-tables.ts +++ b/frontend/src/ts/elements/account/pb-tables.ts @@ -1,8 +1,8 @@ import Config from "../../config"; import { format as dateFormat } from "date-fns/format"; import Format from "../../utils/format"; -import { Mode2, PersonalBests } from "@monkeytype/contracts/schemas/shared"; -import { StringNumber } from "@monkeytype/contracts/schemas/util"; +import { Mode2, PersonalBests } from "@monkeytype/schemas/shared"; +import { StringNumber } from "@monkeytype/schemas/util"; function clearTables(isProfile: boolean): void { const source = isProfile ? "Profile" : "Account"; diff --git a/frontend/src/ts/elements/account/result-filters.ts b/frontend/src/ts/elements/account/result-filters.ts index a8e62f933438..b1357beda2dc 100644 --- a/frontend/src/ts/elements/account/result-filters.ts +++ b/frontend/src/ts/elements/account/result-filters.ts @@ -6,13 +6,13 @@ 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 { QuoteLength } from "@monkeytype/schemas/configs"; import { ResultFilters, ResultFiltersSchema, ResultFiltersGroup, ResultFiltersGroupItem, -} from "@monkeytype/contracts/schemas/users"; +} from "@monkeytype/schemas/users"; import { LocalStorageWithSchema } from "../../utils/local-storage-with-schema"; import defaultResultFilters from "../../constants/default-result-filters"; import { getAllFunboxes } from "@monkeytype/funbox"; diff --git a/frontend/src/ts/elements/alerts.ts b/frontend/src/ts/elements/alerts.ts index 768afd71aa77..9456d6c7a0f7 100644 --- a/frontend/src/ts/elements/alerts.ts +++ b/frontend/src/ts/elements/alerts.ts @@ -9,7 +9,7 @@ import * as ConnectionState from "../states/connection"; import { escapeHTML } from "../utils/misc"; import AnimatedModal from "../utils/animated-modal"; import { updateXp as accountPageUpdateProfile } from "./profile"; -import { MonkeyMail } from "@monkeytype/contracts/schemas/users"; +import { MonkeyMail } from "@monkeytype/schemas/users"; import * as XPBar from "../elements/xp-bar"; let accountAlerts: MonkeyMail[] = []; diff --git a/frontend/src/ts/elements/custom-background-filter.ts b/frontend/src/ts/elements/custom-background-filter.ts index 93a56be88fa9..87da2102db0a 100644 --- a/frontend/src/ts/elements/custom-background-filter.ts +++ b/frontend/src/ts/elements/custom-background-filter.ts @@ -1,4 +1,4 @@ -import { CustomBackgroundFilter } from "@monkeytype/contracts/schemas/configs"; +import { CustomBackgroundFilter } from "@monkeytype/schemas/configs"; import * as UpdateConfig from "../config"; import * as ConfigEvent from "../observables/config-event"; import { debounce } from "throttle-debounce"; diff --git a/frontend/src/ts/elements/profile.ts b/frontend/src/ts/elements/profile.ts index d90578908fa1..81111eb41a26 100644 --- a/frontend/src/ts/elements/profile.ts +++ b/frontend/src/ts/elements/profile.ts @@ -11,7 +11,7 @@ import * as ActivePage from "../states/active-page"; import { formatDistanceToNowStrict } from "date-fns/formatDistanceToNowStrict"; import { getHtmlByUserFlags } from "../controllers/user-flag-controller"; import Format from "../utils/format"; -import { UserProfile, RankAndCount } from "@monkeytype/contracts/schemas/users"; +import { UserProfile, RankAndCount } from "@monkeytype/schemas/users"; import { abbreviateNumber, convertRemToPixels } from "../utils/numbers"; import { secondsToString } from "../utils/date-and-time"; import { Auth } from "../firebase"; diff --git a/frontend/src/ts/elements/psa.ts b/frontend/src/ts/elements/psa.ts index 95507069cdbd..e09fe6c0d806 100644 --- a/frontend/src/ts/elements/psa.ts +++ b/frontend/src/ts/elements/psa.ts @@ -4,10 +4,10 @@ import { secondsToString } from "../utils/date-and-time"; import * as Notifications from "./notifications"; import { format } from "date-fns/format"; import * as Alerts from "./alerts"; -import { PSA } from "@monkeytype/contracts/schemas/psas"; +import { PSA } from "@monkeytype/schemas/psas"; import { z } from "zod"; import { LocalStorageWithSchema } from "../utils/local-storage-with-schema"; -import { IdSchema } from "@monkeytype/contracts/schemas/util"; +import { IdSchema } from "@monkeytype/schemas/util"; import { tryCatch } from "@monkeytype/util/trycatch"; import { isSafeNumber } from "@monkeytype/util/numbers"; diff --git a/frontend/src/ts/elements/settings/settings-group.ts b/frontend/src/ts/elements/settings/settings-group.ts index c6c76cd11deb..e093626a279f 100644 --- a/frontend/src/ts/elements/settings/settings-group.ts +++ b/frontend/src/ts/elements/settings/settings-group.ts @@ -1,4 +1,4 @@ -import { ConfigValue } from "@monkeytype/contracts/schemas/configs"; +import { ConfigValue } from "@monkeytype/schemas/configs"; import Config from "../../config"; import * as Notifications from "../notifications"; import SlimSelect from "slim-select"; diff --git a/frontend/src/ts/elements/settings/theme-picker.ts b/frontend/src/ts/elements/settings/theme-picker.ts index b269664ada98..867fe0b2829e 100644 --- a/frontend/src/ts/elements/settings/theme-picker.ts +++ b/frontend/src/ts/elements/settings/theme-picker.ts @@ -10,10 +10,7 @@ import * as DB from "../../db"; import * as ConfigEvent from "../../observables/config-event"; import { isAuthenticated } from "../../firebase"; import * as ActivePage from "../../states/active-page"; -import { - CustomThemeColors, - ThemeName, -} from "@monkeytype/contracts/schemas/configs"; +import { CustomThemeColors, ThemeName } from "@monkeytype/schemas/configs"; import { captureException } from "../../sentry"; import { ThemesListSorted } from "../../constants/themes"; diff --git a/frontend/src/ts/elements/xp-bar.ts b/frontend/src/ts/elements/xp-bar.ts index 51565e125e3b..9e0837d716f6 100644 --- a/frontend/src/ts/elements/xp-bar.ts +++ b/frontend/src/ts/elements/xp-bar.ts @@ -2,7 +2,7 @@ import * as Misc from "../utils/misc"; import * as Levels from "../utils/levels"; import { getAll } from "./theme-colors"; import * as SlowTimer from "../states/slow-timer"; -import { XpBreakdown } from "@monkeytype/contracts/schemas/results"; +import { XpBreakdown } from "@monkeytype/schemas/results"; import { isSafeNumber, mapRange } from "@monkeytype/util/numbers"; let breakdownVisible = false; diff --git a/frontend/src/ts/modals/custom-text.ts b/frontend/src/ts/modals/custom-text.ts index 1c29b0718faf..b2c1f464cc51 100644 --- a/frontend/src/ts/modals/custom-text.ts +++ b/frontend/src/ts/modals/custom-text.ts @@ -10,7 +10,7 @@ import * as Notifications from "../elements/notifications"; import * as SavedTextsPopup from "./saved-texts"; import * as SaveCustomTextPopup from "./save-custom-text"; import AnimatedModal, { ShowOptions } from "../utils/animated-modal"; -import { CustomTextMode } from "@monkeytype/contracts/schemas/util"; +import { CustomTextMode } from "@monkeytype/schemas/util"; const popup = "#customTextModal .modal"; diff --git a/frontend/src/ts/modals/edit-preset.ts b/frontend/src/ts/modals/edit-preset.ts index 0b43f6f7cfab..29b213bf1054 100644 --- a/frontend/src/ts/modals/edit-preset.ts +++ b/frontend/src/ts/modals/edit-preset.ts @@ -6,10 +6,7 @@ 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 { - PresetType, - PresetTypeSchema, -} from "@monkeytype/contracts/schemas/presets"; +import { PresetType, PresetTypeSchema } from "@monkeytype/schemas/presets"; import { getPreset } from "../controllers/preset-controller"; import { ConfigGroupName, @@ -17,7 +14,7 @@ import { ConfigGroupsLiteral, ConfigKey, Config as ConfigType, -} from "@monkeytype/contracts/schemas/configs"; +} from "@monkeytype/schemas/configs"; import { getDefaultConfig } from "../constants/default-config"; import { SnapshotPreset } from "../constants/default-snapshot"; diff --git a/frontend/src/ts/modals/edit-profile.ts b/frontend/src/ts/modals/edit-profile.ts index 8a4cc1723c89..2cff8f102481 100644 --- a/frontend/src/ts/modals/edit-profile.ts +++ b/frontend/src/ts/modals/edit-profile.ts @@ -13,7 +13,7 @@ import { TwitterProfileSchema, UserProfileDetails, WebsiteSchema, -} from "@monkeytype/contracts/schemas/users"; +} from "@monkeytype/schemas/users"; import { InputIndicator } from "../elements/input-indicator"; export function show(): void { diff --git a/frontend/src/ts/modals/edit-tag.ts b/frontend/src/ts/modals/edit-tag.ts index a67562239f1e..27abc796d798 100644 --- a/frontend/src/ts/modals/edit-tag.ts +++ b/frontend/src/ts/modals/edit-tag.ts @@ -3,7 +3,7 @@ import * as DB from "../db"; import * as Settings from "../pages/settings"; import AnimatedModal, { ShowOptions } from "../utils/animated-modal"; import { SimpleModal, TextInput } from "../utils/simple-modal"; -import { TagNameSchema } from "@monkeytype/contracts/schemas/users"; +import { TagNameSchema } from "@monkeytype/schemas/users"; const cleanTagName = (tagName: string): string => tagName.replaceAll(" ", "_"); const tagNameValidation = async (tagName: string): Promise => { diff --git a/frontend/src/ts/modals/last-signed-out-result.ts b/frontend/src/ts/modals/last-signed-out-result.ts index f7f19f387a0e..cf86ac8d585b 100644 --- a/frontend/src/ts/modals/last-signed-out-result.ts +++ b/frontend/src/ts/modals/last-signed-out-result.ts @@ -2,7 +2,7 @@ import AnimatedModal from "../utils/animated-modal"; import * as TestLogic from "../test/test-logic"; import * as Notifications from "../elements/notifications"; -import { CompletedEvent } from "@monkeytype/contracts/schemas/results"; +import { CompletedEvent } from "@monkeytype/schemas/results"; import { Auth } from "../firebase"; import { syncNotSignedInLastResult } from "../utils/results"; diff --git a/frontend/src/ts/modals/mini-result-chart.ts b/frontend/src/ts/modals/mini-result-chart.ts index 31ce28eb240b..a189d1dab407 100644 --- a/frontend/src/ts/modals/mini-result-chart.ts +++ b/frontend/src/ts/modals/mini-result-chart.ts @@ -1,4 +1,4 @@ -import { ChartData } from "@monkeytype/contracts/schemas/results"; +import { ChartData } from "@monkeytype/schemas/results"; import AnimatedModal from "../utils/animated-modal"; import * as ChartController from "../controllers/chart-controller"; import Config from "../config"; diff --git a/frontend/src/ts/modals/mobile-test-config.ts b/frontend/src/ts/modals/mobile-test-config.ts index 7ceac9dad97d..5104a155460b 100644 --- a/frontend/src/ts/modals/mobile-test-config.ts +++ b/frontend/src/ts/modals/mobile-test-config.ts @@ -6,11 +6,8 @@ import * as CustomTestDurationPopup from "./custom-test-duration"; import * as QuoteSearchModal from "./quote-search"; import * as CustomTextPopup from "./custom-text"; import AnimatedModal from "../utils/animated-modal"; -import { - QuoteLength, - QuoteLengthConfig, -} from "@monkeytype/contracts/schemas/configs"; -import { Mode } from "@monkeytype/contracts/schemas/shared"; +import { QuoteLength, QuoteLengthConfig } from "@monkeytype/schemas/configs"; +import { Mode } from "@monkeytype/schemas/shared"; function update(): void { const el = $("#mobileTestConfigModal"); diff --git a/frontend/src/ts/modals/pb-tables.ts b/frontend/src/ts/modals/pb-tables.ts index a78a8c9c8573..3c45ea19bae5 100644 --- a/frontend/src/ts/modals/pb-tables.ts +++ b/frontend/src/ts/modals/pb-tables.ts @@ -4,11 +4,7 @@ import { getLanguageDisplayString } from "../utils/strings"; import Config from "../config"; import Format from "../utils/format"; import AnimatedModal from "../utils/animated-modal"; -import { - Mode, - Mode2, - PersonalBest, -} from "@monkeytype/contracts/schemas/shared"; +import { Mode, Mode2, PersonalBest } from "@monkeytype/schemas/shared"; type PBWithMode2 = { mode2: Mode2; diff --git a/frontend/src/ts/modals/quote-approve.ts b/frontend/src/ts/modals/quote-approve.ts index 85bde3235574..7d18370d461a 100644 --- a/frontend/src/ts/modals/quote-approve.ts +++ b/frontend/src/ts/modals/quote-approve.ts @@ -3,7 +3,7 @@ import * as Loader from "../elements/loader"; import * as Notifications from "../elements/notifications"; import { format } from "date-fns/format"; import AnimatedModal, { ShowOptions } from "../utils/animated-modal"; -import { Quote } from "@monkeytype/contracts/schemas/quotes"; +import { Quote } from "@monkeytype/schemas/quotes"; let quotes: Quote[] = []; diff --git a/frontend/src/ts/modals/quote-rate.ts b/frontend/src/ts/modals/quote-rate.ts index 921182a4dbec..3e27231a34f3 100644 --- a/frontend/src/ts/modals/quote-rate.ts +++ b/frontend/src/ts/modals/quote-rate.ts @@ -1,4 +1,4 @@ -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { Language } from "@monkeytype/schemas/languages"; import Ape from "../ape"; import { Quote } from "../controllers/quotes-controller"; import * as DB from "../db"; diff --git a/frontend/src/ts/modals/quote-report.ts b/frontend/src/ts/modals/quote-report.ts index 52ea7855b214..dabbe57eaaaf 100644 --- a/frontend/src/ts/modals/quote-report.ts +++ b/frontend/src/ts/modals/quote-report.ts @@ -8,7 +8,7 @@ import { removeLanguageSize } from "../utils/strings"; import SlimSelect from "slim-select"; import AnimatedModal, { ShowOptions } from "../utils/animated-modal"; import { CharacterCounter } from "../elements/character-counter"; -import { QuoteReportReason } from "@monkeytype/contracts/schemas/quotes"; +import { QuoteReportReason } from "@monkeytype/schemas/quotes"; type State = { quoteToReport?: Quote; diff --git a/frontend/src/ts/modals/quote-submit.ts b/frontend/src/ts/modals/quote-submit.ts index 30ba86e4085f..8ec80a2b140e 100644 --- a/frontend/src/ts/modals/quote-submit.ts +++ b/frontend/src/ts/modals/quote-submit.ts @@ -7,7 +7,7 @@ import Config from "../config"; import SlimSelect from "slim-select"; import AnimatedModal, { ShowOptions } from "../utils/animated-modal"; import { CharacterCounter } from "../elements/character-counter"; -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { Language } from "@monkeytype/schemas/languages"; import { LanguageGroupNames } from "../constants/languages"; let dropdownReady = false; diff --git a/frontend/src/ts/modals/share-test-settings.ts b/frontend/src/ts/modals/share-test-settings.ts index 96bd38e26af9..914c93e80c91 100644 --- a/frontend/src/ts/modals/share-test-settings.ts +++ b/frontend/src/ts/modals/share-test-settings.ts @@ -4,8 +4,8 @@ import { getMode2 } from "../utils/misc"; import * as CustomText from "../test/custom-text"; import { compressToURI } from "lz-ts"; import AnimatedModal, { ShowOptions } from "../utils/animated-modal"; -import { Difficulty, FunboxName } from "@monkeytype/contracts/schemas/configs"; -import { Mode, Mode2 } from "@monkeytype/contracts/schemas/shared"; +import { Difficulty, FunboxName } from "@monkeytype/schemas/configs"; +import { Mode, Mode2 } from "@monkeytype/schemas/shared"; function getCheckboxValue(checkbox: string): boolean { return $(`#shareTestSettingsModal label.${checkbox} input`).prop( diff --git a/frontend/src/ts/modals/simple-modals.ts b/frontend/src/ts/modals/simple-modals.ts index d4857c0b5830..30a36ea4549c 100644 --- a/frontend/src/ts/modals/simple-modals.ts +++ b/frontend/src/ts/modals/simple-modals.ts @@ -25,7 +25,7 @@ import { } from "../utils/misc"; import * as CustomTextState from "../states/custom-text-name"; import * as ThemeController from "../controllers/theme-controller"; -import { CustomThemeColors } from "@monkeytype/contracts/schemas/configs"; +import { CustomThemeColors } from "@monkeytype/schemas/configs"; import * as AccountSettings from "../pages/account-settings"; import { ExecReturn, diff --git a/frontend/src/ts/modals/user-report.ts b/frontend/src/ts/modals/user-report.ts index dddab1e86359..a74ecccd8d7d 100644 --- a/frontend/src/ts/modals/user-report.ts +++ b/frontend/src/ts/modals/user-report.ts @@ -6,7 +6,7 @@ import SlimSelect from "slim-select"; import AnimatedModal from "../utils/animated-modal"; import { isAuthenticated } from "../firebase"; import { CharacterCounter } from "../elements/character-counter"; -import { ReportUserReason } from "@monkeytype/contracts/schemas/users"; +import { ReportUserReason } from "@monkeytype/schemas/users"; type State = { userUid?: string; diff --git a/frontend/src/ts/modals/word-filter.ts b/frontend/src/ts/modals/word-filter.ts index 4480f638e17c..a98cf066e991 100644 --- a/frontend/src/ts/modals/word-filter.ts +++ b/frontend/src/ts/modals/word-filter.ts @@ -10,7 +10,7 @@ import AnimatedModal, { import { LayoutsList } from "../constants/layouts"; import { tryCatch } from "@monkeytype/util/trycatch"; import { LanguageList } from "../constants/languages"; -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { Language } from "@monkeytype/schemas/languages"; type FilterPreset = { display: string; diff --git a/frontend/src/ts/observables/config-event.ts b/frontend/src/ts/observables/config-event.ts index 370479dae2c6..6cfb1f8b9109 100644 --- a/frontend/src/ts/observables/config-event.ts +++ b/frontend/src/ts/observables/config-event.ts @@ -1,8 +1,4 @@ -import { - Config, - ConfigKey, - ConfigValue, -} from "@monkeytype/contracts/schemas/configs"; +import { Config, ConfigKey, ConfigValue } from "@monkeytype/schemas/configs"; export type ConfigEventKey = | ConfigKey diff --git a/frontend/src/ts/pages/about.ts b/frontend/src/ts/pages/about.ts index 9952e81df457..9f1c880c9414 100644 --- a/frontend/src/ts/pages/about.ts +++ b/frontend/src/ts/pages/about.ts @@ -7,10 +7,7 @@ import * as ChartController from "../controllers/chart-controller"; import * as ConnectionState from "../states/connection"; import { intervalToDuration } from "date-fns/intervalToDuration"; import * as Skeleton from "../utils/skeleton"; -import { - TypingStats, - SpeedHistogram, -} from "@monkeytype/contracts/schemas/public"; +import { TypingStats, SpeedHistogram } from "@monkeytype/schemas/public"; import { getNumberWithMagnitude, numberWithSpaces } from "../utils/numbers"; import { tryCatch } from "@monkeytype/util/trycatch"; diff --git a/frontend/src/ts/pages/account.ts b/frontend/src/ts/pages/account.ts index a4c05b585464..8d3bde71059d 100644 --- a/frontend/src/ts/pages/account.ts +++ b/frontend/src/ts/pages/account.ts @@ -27,19 +27,19 @@ 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 { ChartData } from "@monkeytype/schemas/results"; import { Difficulty, Mode, Mode2, Mode2Custom, -} from "@monkeytype/contracts/schemas/shared"; -import { ResultFiltersGroupItem } from "@monkeytype/contracts/schemas/users"; +} from "@monkeytype/schemas/shared"; +import { ResultFiltersGroupItem } from "@monkeytype/schemas/users"; import { findLineByLeastSquares } from "../utils/numbers"; import defaultResultFilters from "../constants/default-result-filters"; import { SnapshotResult } from "../constants/default-snapshot"; import Ape from "../ape"; -import { AccountChart } from "@monkeytype/contracts/schemas/configs"; +import { AccountChart } from "@monkeytype/schemas/configs"; let filterDebug = false; //toggle filterdebug diff --git a/frontend/src/ts/pages/leaderboards.ts b/frontend/src/ts/pages/leaderboards.ts index 6c40ed4066a3..5eb4a9e632ae 100644 --- a/frontend/src/ts/pages/leaderboards.ts +++ b/frontend/src/ts/pages/leaderboards.ts @@ -4,7 +4,7 @@ import Config from "../config"; import { LeaderboardEntry, XpLeaderboardEntry, -} from "@monkeytype/contracts/schemas/leaderboards"; +} from "@monkeytype/schemas/leaderboards"; import { capitalizeFirstLetter } from "../utils/strings"; import Ape from "../ape"; import * as Notifications from "../elements/notifications"; @@ -36,12 +36,9 @@ import { UTCDateMini } from "@date-fns/utc"; import * as ConfigEvent from "../observables/config-event"; import * as ActivePage from "../states/active-page"; import { PaginationQuery } from "@monkeytype/contracts/leaderboards"; -import { - Language, - LanguageSchema, -} from "@monkeytype/contracts/schemas/languages"; +import { Language, LanguageSchema } from "@monkeytype/schemas/languages"; import { isSafeNumber } from "@monkeytype/util/numbers"; -import { Mode, Mode2, ModeSchema } from "@monkeytype/contracts/schemas/shared"; +import { Mode, Mode2, ModeSchema } from "@monkeytype/schemas/shared"; import * as ServerConfiguration from "../ape/server-configuration"; import { getAvatarElement } from "../utils/discord-avatar"; diff --git a/frontend/src/ts/pages/profile.ts b/frontend/src/ts/pages/profile.ts index c3e5f984f44e..64a78cfe1896 100644 --- a/frontend/src/ts/pages/profile.ts +++ b/frontend/src/ts/pages/profile.ts @@ -6,8 +6,8 @@ import * as Notifications from "../elements/notifications"; import { checkIfGetParameterExists } from "../utils/misc"; import * as UserReportModal from "../modals/user-report"; import * as Skeleton from "../utils/skeleton"; -import { UserProfile } from "@monkeytype/contracts/schemas/users"; -import { PersonalBests } from "@monkeytype/contracts/schemas/shared"; +import { UserProfile } from "@monkeytype/schemas/users"; +import { PersonalBests } from "@monkeytype/schemas/shared"; function reset(): void { $(".page.pageProfile .error").addClass("hidden"); diff --git a/frontend/src/ts/pages/settings.ts b/frontend/src/ts/pages/settings.ts index 74ac46d9b606..6ce4190b5622 100644 --- a/frontend/src/ts/pages/settings.ts +++ b/frontend/src/ts/pages/settings.ts @@ -26,7 +26,7 @@ import { CustomLayoutFluid, FunboxName, ConfigKeySchema, -} from "@monkeytype/contracts/schemas/configs"; +} from "@monkeytype/schemas/configs"; import { getAllFunboxes, checkCompatibility } from "@monkeytype/funbox"; import { getActiveFunboxNames } from "../test/funbox/list"; import { SnapshotPreset } from "../constants/default-snapshot"; @@ -35,9 +35,9 @@ import { DataArrayPartial, Optgroup, OptionOptional } from "slim-select/store"; import { tryCatch } from "@monkeytype/util/trycatch"; import { Theme, ThemesList } from "../constants/themes"; import { areSortedArraysEqual, areUnsortedArraysEqual } from "../utils/arrays"; -import { LayoutName } from "@monkeytype/contracts/schemas/layouts"; +import { LayoutName } from "@monkeytype/schemas/layouts"; import { LanguageGroupNames, LanguageGroups } from "../constants/languages"; -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { Language } from "@monkeytype/schemas/languages"; import { z } from "zod"; let settingsInitialized = false; diff --git a/frontend/src/ts/test/custom-text.ts b/frontend/src/ts/test/custom-text.ts index 908a949733f8..c4d77326c939 100644 --- a/frontend/src/ts/test/custom-text.ts +++ b/frontend/src/ts/test/custom-text.ts @@ -1,10 +1,7 @@ -import { - CustomTextLimitMode, - CustomTextMode, -} from "@monkeytype/contracts/schemas/util"; +import { CustomTextLimitMode, CustomTextMode } from "@monkeytype/schemas/util"; import { LocalStorageWithSchema } from "../utils/local-storage-with-schema"; import { z } from "zod"; -import { CompletedEventCustomTextSchema } from "@monkeytype/contracts/schemas/results"; +import { CompletedEventCustomTextSchema } from "@monkeytype/schemas/results"; import { deepClone } from "../utils/misc"; const CustomTextObjectSchema = z.record(z.string(), z.string()); diff --git a/frontend/src/ts/test/funbox/funbox-functions.ts b/frontend/src/ts/test/funbox/funbox-functions.ts index eb5735521dd1..8e93f7c1f6e4 100644 --- a/frontend/src/ts/test/funbox/funbox-functions.ts +++ b/frontend/src/ts/test/funbox/funbox-functions.ts @@ -22,12 +22,8 @@ import * as WeakSpot from "../weak-spot"; import * as IPAddresses from "../../utils/ip-addresses"; import * as TestState from "../test-state"; import { WordGenError } from "../../utils/word-gen-error"; -import { - FunboxName, - KeymapLayout, - Layout, -} from "@monkeytype/contracts/schemas/configs"; -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { FunboxName, KeymapLayout, Layout } from "@monkeytype/schemas/configs"; +import { Language } from "@monkeytype/schemas/languages"; export type FunboxFunctions = { getWord?: (wordset?: Wordset, wordIndex?: number) => string; punctuateWord?: (word: string) => string; diff --git a/frontend/src/ts/test/funbox/funbox-memory.ts b/frontend/src/ts/test/funbox/funbox-memory.ts index 07ee80668372..1dd619c5a58a 100644 --- a/frontend/src/ts/test/funbox/funbox-memory.ts +++ b/frontend/src/ts/test/funbox/funbox-memory.ts @@ -1,4 +1,4 @@ -import { ConfigValue } from "@monkeytype/contracts/schemas/configs"; +import { ConfigValue } from "@monkeytype/schemas/configs"; type SetFunction = (param: T, nosave?: boolean) => boolean; diff --git a/frontend/src/ts/test/funbox/funbox-validation.ts b/frontend/src/ts/test/funbox/funbox-validation.ts index c8c19a8f93c4..c01624f854bc 100644 --- a/frontend/src/ts/test/funbox/funbox-validation.ts +++ b/frontend/src/ts/test/funbox/funbox-validation.ts @@ -1,10 +1,6 @@ import * as Notifications from "../../elements/notifications"; import * as Strings from "../../utils/strings"; -import { - Config, - ConfigValue, - FunboxName, -} from "@monkeytype/contracts/schemas/configs"; +import { Config, ConfigValue, FunboxName } from "@monkeytype/schemas/configs"; import { FunboxMetadata, getFunbox } from "@monkeytype/funbox"; import { intersect } from "@monkeytype/util/arrays"; diff --git a/frontend/src/ts/test/funbox/funbox.ts b/frontend/src/ts/test/funbox/funbox.ts index 089f5d5ae6e8..4ffef71b1b71 100644 --- a/frontend/src/ts/test/funbox/funbox.ts +++ b/frontend/src/ts/test/funbox/funbox.ts @@ -6,11 +6,8 @@ import * as ManualRestart from "../manual-restart-tracker"; import Config, * as UpdateConfig from "../../config"; import * as MemoryTimer from "./memory-funbox-timer"; import * as FunboxMemory from "./funbox-memory"; -import { - HighlightMode, - FunboxName, -} from "@monkeytype/contracts/schemas/configs"; -import { Mode } from "@monkeytype/contracts/schemas/shared"; +import { HighlightMode, FunboxName } from "@monkeytype/schemas/configs"; +import { Mode } from "@monkeytype/schemas/shared"; import { checkCompatibility } from "@monkeytype/funbox"; import { getActiveFunboxes, diff --git a/frontend/src/ts/test/funbox/list.ts b/frontend/src/ts/test/funbox/list.ts index 609527d05960..274ea4ae9ba2 100644 --- a/frontend/src/ts/test/funbox/list.ts +++ b/frontend/src/ts/test/funbox/list.ts @@ -6,7 +6,7 @@ import { } from "@monkeytype/funbox"; import { FunboxFunctions, getFunboxFunctions } from "./funbox-functions"; -import { FunboxName } from "@monkeytype/contracts/schemas/configs"; +import { FunboxName } from "@monkeytype/schemas/configs"; type FunboxMetadataWithFunctions = FunboxMetadata & { functions?: FunboxFunctions; diff --git a/frontend/src/ts/test/practise-words.ts b/frontend/src/ts/test/practise-words.ts index b527ac5bac18..116a307d739a 100644 --- a/frontend/src/ts/test/practise-words.ts +++ b/frontend/src/ts/test/practise-words.ts @@ -5,7 +5,7 @@ import * as CustomText from "./custom-text"; import * as TestInput from "./test-input"; import * as ConfigEvent from "../observables/config-event"; import { setCustomTextName } from "../states/custom-text-name"; -import { Mode } from "@monkeytype/contracts/schemas/shared"; +import { Mode } from "@monkeytype/schemas/shared"; type Before = { mode: Mode | null; diff --git a/frontend/src/ts/test/result.ts b/frontend/src/ts/test/result.ts index 78a23f24a41c..588c39bd8bdd 100644 --- a/frontend/src/ts/test/result.ts +++ b/frontend/src/ts/test/result.ts @@ -36,11 +36,11 @@ import type { LabelPosition, } from "chartjs-plugin-annotation"; import Ape from "../ape"; -import { CompletedEvent } from "@monkeytype/contracts/schemas/results"; +import { CompletedEvent } from "@monkeytype/schemas/results"; import { getActiveFunboxes, isFunboxActiveWithProperty } from "./funbox/list"; import { getFunbox } from "@monkeytype/funbox"; import { SnapshotUserTag } from "../constants/default-snapshot"; -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { Language } from "@monkeytype/schemas/languages"; import { canQuickRestart as canQuickRestartFn } from "../utils/quick-restart"; let result: CompletedEvent; diff --git a/frontend/src/ts/test/test-config.ts b/frontend/src/ts/test/test-config.ts index 3f5c2950e239..08692b8593c3 100644 --- a/frontend/src/ts/test/test-config.ts +++ b/frontend/src/ts/test/test-config.ts @@ -1,8 +1,5 @@ -import { - ConfigValue, - QuoteLength, -} from "@monkeytype/contracts/schemas/configs"; -import { Mode } from "@monkeytype/contracts/schemas/shared"; +import { ConfigValue, QuoteLength } from "@monkeytype/schemas/configs"; +import { Mode } from "@monkeytype/schemas/shared"; import Config from "../config"; import * as ConfigEvent from "../observables/config-event"; import * as ActivePage from "../states/active-page"; diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index 7a7ec4c67678..8a0d3de61017 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -58,15 +58,12 @@ import * as KeymapEvent from "../observables/keymap-event"; import * as LayoutfluidFunboxTimer from "../test/funbox/layoutfluid-funbox-timer"; import * as ArabicLazyMode from "../states/arabic-lazy-mode"; import Format from "../utils/format"; -import { - QuoteLength, - QuoteLengthConfig, -} from "@monkeytype/contracts/schemas/configs"; -import { Mode } from "@monkeytype/contracts/schemas/shared"; +import { QuoteLength, QuoteLengthConfig } from "@monkeytype/schemas/configs"; +import { Mode } from "@monkeytype/schemas/shared"; import { CompletedEvent, CompletedEventCustomText, -} from "@monkeytype/contracts/schemas/results"; +} from "@monkeytype/schemas/results"; import * as XPBar from "../elements/xp-bar"; import { findSingleActiveFunboxWithFunction, diff --git a/frontend/src/ts/test/test-stats.ts b/frontend/src/ts/test/test-stats.ts index 3181b6cfdb0b..8f0de5086ed1 100644 --- a/frontend/src/ts/test/test-stats.ts +++ b/frontend/src/ts/test/test-stats.ts @@ -5,10 +5,7 @@ import * as TestInput from "./test-input"; import * as TestWords from "./test-words"; import * as TestState from "./test-state"; import * as Numbers from "@monkeytype/util/numbers"; -import { - CompletedEvent, - IncompleteTest, -} from "@monkeytype/contracts/schemas/results"; +import { CompletedEvent, IncompleteTest } from "@monkeytype/schemas/results"; import { isFunboxActiveWithProperty } from "./funbox/list"; type CharCount = { diff --git a/frontend/src/ts/test/test-timer.ts b/frontend/src/ts/test/test-timer.ts index 4d5c0d7b7159..b9b2db08a708 100644 --- a/frontend/src/ts/test/test-timer.ts +++ b/frontend/src/ts/test/test-timer.ts @@ -16,7 +16,7 @@ import * as TestState from "./test-state"; import * as Time from "../states/time"; import * as TimerEvent from "../observables/timer-event"; import * as LayoutfluidFunboxTimer from "../test/funbox/layoutfluid-funbox-timer"; -import { KeymapLayout, Layout } from "@monkeytype/contracts/schemas/configs"; +import { KeymapLayout, Layout } from "@monkeytype/schemas/configs"; type TimerStats = { dateNow: number; diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index 081022115c78..b5ac9df55543 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -19,10 +19,7 @@ import { debounce } from "throttle-debounce"; import * as ResultWordHighlight from "../elements/result-word-highlight"; import * as ActivePage from "../states/active-page"; import Format from "../utils/format"; -import { - TimerColor, - TimerOpacity, -} from "@monkeytype/contracts/schemas/configs"; +import { TimerColor, TimerOpacity } from "@monkeytype/schemas/configs"; import { convertRemToPixels } from "../utils/numbers"; import { findSingleActiveFunboxWithFunction } from "./funbox/list"; import * as TestState from "./test-state"; diff --git a/frontend/src/ts/test/wikipedia.ts b/frontend/src/ts/test/wikipedia.ts index 95360f407002..98fbe277e898 100644 --- a/frontend/src/ts/test/wikipedia.ts +++ b/frontend/src/ts/test/wikipedia.ts @@ -5,7 +5,7 @@ import * as JSONData from "../utils/json-data"; import { z } from "zod"; import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json"; import { getGroupForLanguage, LanguageGroupName } from "../constants/languages"; -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { Language } from "@monkeytype/schemas/languages"; export async function getTLD( languageGroup: LanguageGroupName diff --git a/frontend/src/ts/utils/config.ts b/frontend/src/ts/utils/config.ts index 9f805c3c25ab..be4f46274119 100644 --- a/frontend/src/ts/utils/config.ts +++ b/frontend/src/ts/utils/config.ts @@ -3,9 +3,9 @@ import { ConfigValue, PartialConfig, FunboxName, -} from "@monkeytype/contracts/schemas/configs"; +} from "@monkeytype/schemas/configs"; import { sanitize, typedKeys } from "./misc"; -import * as ConfigSchemas from "@monkeytype/contracts/schemas/configs"; +import * as ConfigSchemas from "@monkeytype/schemas/configs"; import { getDefaultConfig } from "../constants/default-config"; /** * migrates possible outdated config and merges with the default config values diff --git a/frontend/src/ts/utils/format.ts b/frontend/src/ts/utils/format.ts index 68b2decca639..3daa0f1fe5f2 100644 --- a/frontend/src/ts/utils/format.ts +++ b/frontend/src/ts/utils/format.ts @@ -1,6 +1,6 @@ import { get as getTypingSpeedUnit } from "../utils/typing-speed-units"; import * as Numbers from "@monkeytype/util/numbers"; -import { Config as ConfigType } from "@monkeytype/contracts/schemas/configs"; +import { Config as ConfigType } from "@monkeytype/schemas/configs"; import Config from "../config"; export type FormatOptions = { diff --git a/frontend/src/ts/utils/json-data.ts b/frontend/src/ts/utils/json-data.ts index 38a55f41b0d3..12ac62b9817e 100644 --- a/frontend/src/ts/utils/json-data.ts +++ b/frontend/src/ts/utils/json-data.ts @@ -1,5 +1,5 @@ -import { FunboxName } from "@monkeytype/contracts/schemas/configs"; -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { FunboxName } from "@monkeytype/schemas/configs"; +import { Language } from "@monkeytype/schemas/languages"; import { Accents } from "../test/lazy-mode"; //pin implementation diff --git a/frontend/src/ts/utils/misc.ts b/frontend/src/ts/utils/misc.ts index 63fd5edebd4d..debae38660d8 100644 --- a/frontend/src/ts/utils/misc.ts +++ b/frontend/src/ts/utils/misc.ts @@ -1,13 +1,9 @@ import * as Loader from "../elements/loader"; import { envConfig } from "../constants/env-config"; import { lastElementFromArray } from "./arrays"; -import { Config } from "@monkeytype/contracts/schemas/configs"; -import { - Mode, - Mode2, - PersonalBests, -} from "@monkeytype/contracts/schemas/shared"; -import { Result } from "@monkeytype/contracts/schemas/results"; +import { Config } from "@monkeytype/schemas/configs"; +import { Mode, Mode2, PersonalBests } from "@monkeytype/schemas/shared"; +import { Result } from "@monkeytype/schemas/results"; import { z } from "zod"; export function whorf(speed: number, wordlen: number): number { diff --git a/frontend/src/ts/utils/results.ts b/frontend/src/ts/utils/results.ts index 80eafea878dc..54922887b9a5 100644 --- a/frontend/src/ts/utils/results.ts +++ b/frontend/src/ts/utils/results.ts @@ -3,7 +3,7 @@ import * as Notifications from "../elements/notifications"; import * as DB from "../db"; import * as TestLogic from "../test/test-logic"; import { deepClone } from "./misc"; -import { Mode } from "@monkeytype/contracts/schemas/shared"; +import { Mode } from "@monkeytype/schemas/shared"; import { SnapshotResult } from "../constants/default-snapshot"; export async function syncNotSignedInLastResult(uid: string): Promise { diff --git a/frontend/src/ts/utils/strings.ts b/frontend/src/ts/utils/strings.ts index 68c697dce9fe..96e8a3829795 100644 --- a/frontend/src/ts/utils/strings.ts +++ b/frontend/src/ts/utils/strings.ts @@ -1,4 +1,4 @@ -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { Language } from "@monkeytype/schemas/languages"; /** * Removes accents from a string. diff --git a/frontend/src/ts/utils/typing-speed-units.ts b/frontend/src/ts/utils/typing-speed-units.ts index 214b5a5eaedd..ed2d17486de5 100644 --- a/frontend/src/ts/utils/typing-speed-units.ts +++ b/frontend/src/ts/utils/typing-speed-units.ts @@ -1,4 +1,4 @@ -import { TypingSpeedUnit } from "@monkeytype/contracts/schemas/configs"; +import { TypingSpeedUnit } from "@monkeytype/schemas/configs"; type TypingSpeedUnitSettings = { fromWpm: (number: number) => number; diff --git a/frontend/src/ts/utils/url-handler.ts b/frontend/src/ts/utils/url-handler.ts index 5c2b4c31f0dc..49d05c5c8013 100644 --- a/frontend/src/ts/utils/url-handler.ts +++ b/frontend/src/ts/utils/url-handler.ts @@ -15,7 +15,7 @@ import { DifficultySchema, Mode2Schema, ModeSchema, -} from "@monkeytype/contracts/schemas/shared"; +} from "@monkeytype/schemas/shared"; import { CustomBackgroundFilter, CustomBackgroundFilterSchema, @@ -25,11 +25,11 @@ import { CustomThemeColorsSchema, FunboxSchema, FunboxName, -} from "@monkeytype/contracts/schemas/configs"; +} from "@monkeytype/schemas/configs"; import { z } from "zod"; import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json"; import { tryCatchSync } from "@monkeytype/util/trycatch"; -import { Language } from "@monkeytype/contracts/schemas/languages"; +import { Language } from "@monkeytype/schemas/languages"; export async function linkDiscord(hashOverride: string): Promise { if (!hashOverride) return; diff --git a/packages/contracts/__test__/schema/config.spec.ts b/packages/contracts/__test__/schema/config.spec.ts index 6e2161513fff..d2c9b4adb513 100644 --- a/packages/contracts/__test__/schema/config.spec.ts +++ b/packages/contracts/__test__/schema/config.spec.ts @@ -1,4 +1,4 @@ -import { CustomBackgroundSchema } from "../../src/schemas/configs"; +import { CustomBackgroundSchema } from "@monkeytype/schemas/configs"; describe("config schema", () => { describe("CustomBackgroundSchema", () => { diff --git a/packages/contracts/__test__/validation/validation.spec.ts b/packages/contracts/__test__/validation/validation.spec.ts index 58903a05ea4a..4adf9c0a34b8 100644 --- a/packages/contracts/__test__/validation/validation.spec.ts +++ b/packages/contracts/__test__/validation/validation.spec.ts @@ -1,4 +1,4 @@ -import * as Validation from "../../src/validation/validation"; +import * as Validation from "@monkeytype/schemas/validation/validation"; describe("validation", () => { it("containsProfanity", () => { diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 3b9089b5585e..3db4c1dcce58 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -17,6 +17,7 @@ }, "devDependencies": { "@monkeytype/eslint-config": "workspace:*", + "@monkeytype/schemas": "workspace:*", "@monkeytype/tsup-config": "workspace:*", "@monkeytype/typescript-config": "workspace:*", "chokidar": "3.6.0", diff --git a/packages/contracts/src/admin.ts b/packages/contracts/src/admin.ts index 3273a8458e96..5bab6e0d456e 100644 --- a/packages/contracts/src/admin.ts +++ b/packages/contracts/src/admin.ts @@ -1,12 +1,12 @@ import { initContract } from "@ts-rest/core"; import { z } from "zod"; +import { IdSchema } from "@monkeytype/schemas/util"; import { CommonResponses, meta, MonkeyResponseSchema, responseWithData, -} from "./schemas/api"; -import { IdSchema } from "./schemas/util"; +} from "./util/api"; export const ToggleBanRequestSchema = z .object({ diff --git a/packages/contracts/src/ape-keys.ts b/packages/contracts/src/ape-keys.ts index d63b2d6450c4..dcf42a4d49cd 100644 --- a/packages/contracts/src/ape-keys.ts +++ b/packages/contracts/src/ape-keys.ts @@ -1,19 +1,17 @@ import { initContract } from "@ts-rest/core"; import { z } from "zod"; - import { CommonResponses, meta, MonkeyResponseSchema, responseWithData, -} from "./schemas/api"; - -import { IdSchema } from "./schemas/util"; +} from "./util/api"; import { ApeKeySchema, ApeKeysSchema, ApeKeyUserDefinedSchema, -} from "./schemas/ape-keys"; +} from "@monkeytype/schemas/ape-keys"; +import { IdSchema } from "@monkeytype/schemas/util"; export const GetApeKeyResponseSchema = responseWithData(ApeKeysSchema); export type GetApeKeyResponse = z.infer; @@ -39,7 +37,6 @@ export const ApeKeyParamsSchema = z.object({ export type ApeKeyParams = z.infer; const c = initContract(); - export const apeKeysContract = c.router( { get: { diff --git a/packages/contracts/src/configs.ts b/packages/contracts/src/configs.ts index 351b917d95bd..bd3ce16c480f 100644 --- a/packages/contracts/src/configs.ts +++ b/packages/contracts/src/configs.ts @@ -1,13 +1,12 @@ import { initContract } from "@ts-rest/core"; import { z } from "zod"; - +import { PartialConfigSchema } from "@monkeytype/schemas/configs"; import { CommonResponses, meta, MonkeyResponseSchema, responseWithNullableData, -} from "./schemas/api"; -import { PartialConfigSchema } from "./schemas/configs"; +} from "./util/api"; export const GetConfigResponseSchema = responseWithNullableData(PartialConfigSchema); diff --git a/packages/contracts/src/configuration.ts b/packages/contracts/src/configuration.ts index fa85788ec732..7eeb5ad2aff3 100644 --- a/packages/contracts/src/configuration.ts +++ b/packages/contracts/src/configuration.ts @@ -1,13 +1,13 @@ import { initContract } from "@ts-rest/core"; import { z } from "zod"; +import { ConfigurationSchema } from "@monkeytype/schemas/configuration"; import { CommonResponses, meta, MonkeyResponseSchema, responseWithData, -} from "./schemas/api"; -import { ConfigurationSchema } from "./schemas/configuration"; +} from "./util/api"; export const GetConfigurationResponseSchema = responseWithData(ConfigurationSchema); diff --git a/packages/contracts/src/dev.ts b/packages/contracts/src/dev.ts index 55de09bb8483..e6f9e2f50163 100644 --- a/packages/contracts/src/dev.ts +++ b/packages/contracts/src/dev.ts @@ -1,7 +1,7 @@ import { initContract } from "@ts-rest/core"; import { z } from "zod"; -import { CommonResponses, meta, responseWithData } from "./schemas/api"; -import { IdSchema } from "./schemas/util"; +import { CommonResponses, meta, responseWithData } from "./util/api"; +import { IdSchema } from "@monkeytype/schemas/util"; export const GenerateDataRequestSchema = z.object({ username: z.string(), diff --git a/packages/contracts/src/leaderboards.ts b/packages/contracts/src/leaderboards.ts index 15268f550c49..9e8fab615bd7 100644 --- a/packages/contracts/src/leaderboards.ts +++ b/packages/contracts/src/leaderboards.ts @@ -5,14 +5,14 @@ import { MonkeyClientError, responseWithData, responseWithNullableData, -} from "./schemas/api"; +} from "./util/api"; import { LeaderboardEntrySchema, XpLeaderboardEntrySchema, -} from "./schemas/leaderboards"; -import { Mode2Schema, ModeSchema } from "./schemas/shared"; +} from "@monkeytype/schemas/leaderboards"; +import { Mode2Schema, ModeSchema } from "@monkeytype/schemas/shared"; import { initContract } from "@ts-rest/core"; -import { LanguageSchema } from "./schemas/languages"; +import { LanguageSchema } from "@monkeytype/schemas/languages"; const LanguageAndModeQuerySchema = z.object({ language: LanguageSchema, diff --git a/packages/contracts/src/presets.ts b/packages/contracts/src/presets.ts index adb5c7ddd090..e47d75edf927 100644 --- a/packages/contracts/src/presets.ts +++ b/packages/contracts/src/presets.ts @@ -6,9 +6,12 @@ import { meta, MonkeyResponseSchema, responseWithData, -} from "./schemas/api"; -import { EditPresetRequestSchema, PresetSchema } from "./schemas/presets"; -import { IdSchema } from "./schemas/util"; +} from "./util/api"; +import { + EditPresetRequestSchema, + PresetSchema, +} from "@monkeytype/schemas/presets"; +import { IdSchema } from "@monkeytype/schemas/util"; export const GetPresetResponseSchema = responseWithData(z.array(PresetSchema)); export type GetPresetResponse = z.infer; diff --git a/packages/contracts/src/psas.ts b/packages/contracts/src/psas.ts index 49a978f83008..7fae5086e39e 100644 --- a/packages/contracts/src/psas.ts +++ b/packages/contracts/src/psas.ts @@ -1,8 +1,8 @@ import { initContract } from "@ts-rest/core"; import { z } from "zod"; -import { PSASchema } from "./schemas/psas"; +import { PSASchema } from "@monkeytype/schemas/psas"; +import { CommonResponses, meta, responseWithData } from "./util/api"; -import { CommonResponses, meta, responseWithData } from "./schemas/api"; export const GetPsaResponseSchema = responseWithData(z.array(PSASchema)); export type GetPsaResponse = z.infer; diff --git a/packages/contracts/src/public.ts b/packages/contracts/src/public.ts index bdb8c1087c9c..8286ae2d643f 100644 --- a/packages/contracts/src/public.ts +++ b/packages/contracts/src/public.ts @@ -1,9 +1,12 @@ import { initContract } from "@ts-rest/core"; import { z } from "zod"; -import { CommonResponses, meta, responseWithData } from "./schemas/api"; -import { SpeedHistogramSchema, TypingStatsSchema } from "./schemas/public"; -import { Mode2Schema, ModeSchema } from "./schemas/shared"; -import { LanguageSchema } from "./schemas/languages"; +import { CommonResponses, meta, responseWithData } from "./util/api"; +import { + SpeedHistogramSchema, + TypingStatsSchema, +} from "@monkeytype/schemas/public"; +import { Mode2Schema, ModeSchema } from "@monkeytype/schemas/shared"; +import { LanguageSchema } from "@monkeytype/schemas/languages"; export const GetSpeedHistogramQuerySchema = z .object({ diff --git a/packages/contracts/src/quotes.ts b/packages/contracts/src/quotes.ts index 9b2f839c0d3b..b2de99c23272 100644 --- a/packages/contracts/src/quotes.ts +++ b/packages/contracts/src/quotes.ts @@ -7,16 +7,16 @@ import { MonkeyResponseSchema, responseWithData, responseWithNullableData, -} from "./schemas/api"; +} from "./util/api"; import { ApproveQuoteSchema, QuoteIdSchema, QuoteRatingSchema, QuoteReportReasonSchema, QuoteSchema, -} from "./schemas/quotes"; -import { IdSchema, NullableStringSchema } from "./schemas/util"; -import { LanguageSchema } from "./schemas/languages"; +} from "@monkeytype/schemas/quotes"; +import { IdSchema, NullableStringSchema } from "@monkeytype/schemas/util"; +import { LanguageSchema } from "@monkeytype/schemas/languages"; export const GetQuotesResponseSchema = responseWithData(z.array(QuoteSchema)); export type GetQuotesResponse = z.infer; diff --git a/packages/contracts/src/require-configuration/index.ts b/packages/contracts/src/require-configuration/index.ts index c1e89d16634b..74e01920ac06 100644 --- a/packages/contracts/src/require-configuration/index.ts +++ b/packages/contracts/src/require-configuration/index.ts @@ -1,4 +1,4 @@ -import { Configuration } from "../schemas/configuration"; +import { Configuration } from "@monkeytype/schemas/configuration"; type BooleanPaths = { [K in keyof T]: T[K] extends boolean diff --git a/packages/contracts/src/results.ts b/packages/contracts/src/results.ts index da18c42c6f03..e047c0aadd05 100644 --- a/packages/contracts/src/results.ts +++ b/packages/contracts/src/results.ts @@ -5,14 +5,14 @@ import { meta, MonkeyResponseSchema, responseWithData, -} from "./schemas/api"; +} from "./util/api"; import { CompletedEventSchema, PostResultResponseSchema, ResultMinifiedSchema, ResultSchema, -} from "./schemas/results"; -import { IdSchema } from "./schemas/util"; +} from "@monkeytype/schemas/results"; +import { IdSchema } from "@monkeytype/schemas/util"; export const GetResultsQuerySchema = z.object({ onOrAfterTimestamp: z diff --git a/packages/contracts/src/users.ts b/packages/contracts/src/users.ts index 2cfbb5a9e288..5ec48b8477df 100644 --- a/packages/contracts/src/users.ts +++ b/packages/contracts/src/users.ts @@ -7,7 +7,7 @@ import { MonkeyResponseSchema, responseWithData, responseWithNullableData, -} from "./schemas/api"; +} from "./util/api"; import { CountByYearAndDaySchema, CustomThemeNameSchema, @@ -24,12 +24,16 @@ import { UserSchema, UserStreakSchema, UserTagSchema, -} from "./schemas/users"; -import { Mode2Schema, ModeSchema, PersonalBestSchema } from "./schemas/shared"; -import { IdSchema, StringNumberSchema } from "./schemas/util"; -import { LanguageSchema } from "./schemas/languages"; -import { CustomThemeColorsSchema } from "./schemas/configs"; -import { doesNotContainProfanity } from "./validation/validation"; +} from "@monkeytype/schemas/users"; +import { + Mode2Schema, + ModeSchema, + PersonalBestSchema, +} from "@monkeytype/schemas/shared"; +import { IdSchema, StringNumberSchema } from "@monkeytype/schemas/util"; +import { LanguageSchema } from "@monkeytype/schemas/languages"; +import { CustomThemeColorsSchema } from "@monkeytype/schemas/configs"; +import { doesNotContainProfanity } from "@monkeytype/schemas/validation/validation"; export const UserEmailSchema = z.string().email(); diff --git a/packages/contracts/src/schemas/api.ts b/packages/contracts/src/util/api.ts similarity index 100% rename from packages/contracts/src/schemas/api.ts rename to packages/contracts/src/util/api.ts diff --git a/packages/contracts/src/webhooks.ts b/packages/contracts/src/webhooks.ts index 2fcd11793f5b..48a74bf05c71 100644 --- a/packages/contracts/src/webhooks.ts +++ b/packages/contracts/src/webhooks.ts @@ -1,13 +1,13 @@ import { initContract } from "@ts-rest/core"; import { z } from "zod"; -import { PSASchema } from "./schemas/psas"; +import { PSASchema } from "@monkeytype/schemas/psas"; import { CommonResponses, meta, MonkeyResponseSchema, responseWithData, -} from "./schemas/api"; +} from "./util/api"; /** *Schema: https://docs.github.com/en/webhooks/webhook-events-and-payloads?actionType=published#release diff --git a/packages/funbox/package.json b/packages/funbox/package.json index 9167eab22854..1b5b15ba9b46 100644 --- a/packages/funbox/package.json +++ b/packages/funbox/package.json @@ -13,6 +13,7 @@ }, "devDependencies": { "@monkeytype/eslint-config": "workspace:*", + "@monkeytype/schemas": "workspace:*", "@monkeytype/tsup-config": "workspace:*", "@monkeytype/typescript-config": "workspace:*", "chokidar": "3.6.0", @@ -26,9 +27,6 @@ "dependencies": { "@monkeytype/util": "workspace:*" }, - "peerDependencies": { - "@monkeytype/contracts": "workspace:*" - }, "exports": { ".": { "types": "./src/index.ts", diff --git a/packages/funbox/src/index.ts b/packages/funbox/src/index.ts index b8c82ea91166..2d937562b4a7 100644 --- a/packages/funbox/src/index.ts +++ b/packages/funbox/src/index.ts @@ -1,4 +1,4 @@ -import { FunboxName } from "@monkeytype/contracts/schemas/configs"; +import { FunboxName } from "@monkeytype/schemas/configs"; import { getList, getFunbox, getObject, getFunboxNames } from "./list"; import { FunboxMetadata, FunboxProperty } from "./types"; import { checkCompatibility } from "./validation"; diff --git a/packages/funbox/src/list.ts b/packages/funbox/src/list.ts index 8d7cf4693f93..a6414dd865f1 100644 --- a/packages/funbox/src/list.ts +++ b/packages/funbox/src/list.ts @@ -1,4 +1,4 @@ -import { FunboxName } from "@monkeytype/contracts/schemas/configs"; +import { FunboxName } from "@monkeytype/schemas/configs"; import { FunboxMetadata } from "./types"; const list: Record = { diff --git a/packages/funbox/src/types.ts b/packages/funbox/src/types.ts index c5c95796b182..0e0e99d30e29 100644 --- a/packages/funbox/src/types.ts +++ b/packages/funbox/src/types.ts @@ -1,4 +1,4 @@ -import { FunboxName } from "@monkeytype/contracts/schemas/configs"; +import { FunboxName } from "@monkeytype/schemas/configs"; export type FunboxForcedConfig = Record; diff --git a/packages/funbox/src/validation.ts b/packages/funbox/src/validation.ts index d646f40d07ae..0d3972378134 100644 --- a/packages/funbox/src/validation.ts +++ b/packages/funbox/src/validation.ts @@ -1,7 +1,7 @@ import { intersect } from "@monkeytype/util/arrays"; import { FunboxForcedConfig } from "./types"; import { getFunbox } from "./list"; -import { FunboxName } from "@monkeytype/contracts/schemas/configs"; +import { FunboxName } from "@monkeytype/schemas/configs"; import { safeNumber } from "@monkeytype/util/numbers"; export function checkCompatibility( diff --git a/packages/schemas/.eslintrc.cjs b/packages/schemas/.eslintrc.cjs new file mode 100644 index 000000000000..922de4abe568 --- /dev/null +++ b/packages/schemas/.eslintrc.cjs @@ -0,0 +1,5 @@ +/** @type {import("eslint").Linter.Config} */ +module.exports = { + root: true, + extends: ["@monkeytype/eslint-config"], +}; diff --git a/packages/schemas/.oxlintrc.json b/packages/schemas/.oxlintrc.json new file mode 100644 index 000000000000..d83cb2611bc3 --- /dev/null +++ b/packages/schemas/.oxlintrc.json @@ -0,0 +1,6 @@ +{ + "extends": [ + "../oxlint-config/index.json" + // "@monkeytype/oxlint-config" + ] +} diff --git a/packages/schemas/package.json b/packages/schemas/package.json new file mode 100644 index 000000000000..5b4fec8f258b --- /dev/null +++ b/packages/schemas/package.json @@ -0,0 +1,39 @@ +{ + "name": "@monkeytype/schemas", + "private": true, + "scripts": { + "dev": "tsup-node --watch", + "build": "npm run madge && tsup-node", + "madge": " madge --circular --extensions ts ./src", + "ts-check": "tsc --noEmit", + "eslint": "eslint \"./src/**/*.ts\"", + "oxlint": "oxlint .", + "lint": "npm run oxlint && npm run eslint" + }, + "peerDependencies": { + "zod": "3.23.8" + }, + "devDependencies": { + "@monkeytype/eslint-config": "workspace:*", + "@monkeytype/tsup-config": "workspace:*", + "@monkeytype/typescript-config": "workspace:*", + "chokidar": "3.6.0", + "eslint": "8.57.1", + "madge": "8.0.0", + "oxlint": "1.7.0", + "tsup": "8.4.0", + "typescript": "5.5.4" + }, + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./*": { + "types": "./src/*.ts", + "import": "./dist/*.mjs", + "require": "./dist/*.js" + } + } +} diff --git a/packages/contracts/src/schemas/ape-keys.ts b/packages/schemas/src/ape-keys.ts similarity index 100% rename from packages/contracts/src/schemas/ape-keys.ts rename to packages/schemas/src/ape-keys.ts diff --git a/packages/contracts/src/schemas/configs.ts b/packages/schemas/src/configs.ts similarity index 100% rename from packages/contracts/src/schemas/configs.ts rename to packages/schemas/src/configs.ts diff --git a/packages/contracts/src/schemas/configuration.ts b/packages/schemas/src/configuration.ts similarity index 100% rename from packages/contracts/src/schemas/configuration.ts rename to packages/schemas/src/configuration.ts diff --git a/packages/contracts/src/schemas/languages.ts b/packages/schemas/src/languages.ts similarity index 100% rename from packages/contracts/src/schemas/languages.ts rename to packages/schemas/src/languages.ts diff --git a/packages/contracts/src/schemas/layouts.ts b/packages/schemas/src/layouts.ts similarity index 100% rename from packages/contracts/src/schemas/layouts.ts rename to packages/schemas/src/layouts.ts diff --git a/packages/contracts/src/schemas/leaderboards.ts b/packages/schemas/src/leaderboards.ts similarity index 100% rename from packages/contracts/src/schemas/leaderboards.ts rename to packages/schemas/src/leaderboards.ts diff --git a/packages/contracts/src/schemas/presets.ts b/packages/schemas/src/presets.ts similarity index 100% rename from packages/contracts/src/schemas/presets.ts rename to packages/schemas/src/presets.ts diff --git a/packages/contracts/src/schemas/psas.ts b/packages/schemas/src/psas.ts similarity index 99% rename from packages/contracts/src/schemas/psas.ts rename to packages/schemas/src/psas.ts index 2ec6d074ade9..0aadd12d05d5 100644 --- a/packages/contracts/src/schemas/psas.ts +++ b/packages/schemas/src/psas.ts @@ -8,4 +8,5 @@ export const PSASchema = z.object({ level: z.number().int().optional(), sticky: z.boolean().optional(), }); + export type PSA = z.infer; diff --git a/packages/contracts/src/schemas/public.ts b/packages/schemas/src/public.ts similarity index 100% rename from packages/contracts/src/schemas/public.ts rename to packages/schemas/src/public.ts diff --git a/packages/contracts/src/schemas/quotes.ts b/packages/schemas/src/quotes.ts similarity index 100% rename from packages/contracts/src/schemas/quotes.ts rename to packages/schemas/src/quotes.ts diff --git a/packages/contracts/src/schemas/results.ts b/packages/schemas/src/results.ts similarity index 100% rename from packages/contracts/src/schemas/results.ts rename to packages/schemas/src/results.ts diff --git a/packages/contracts/src/schemas/shared.ts b/packages/schemas/src/shared.ts similarity index 100% rename from packages/contracts/src/schemas/shared.ts rename to packages/schemas/src/shared.ts diff --git a/packages/contracts/src/schemas/themes.ts b/packages/schemas/src/themes.ts similarity index 100% rename from packages/contracts/src/schemas/themes.ts rename to packages/schemas/src/themes.ts diff --git a/packages/contracts/src/schemas/users.ts b/packages/schemas/src/users.ts similarity index 99% rename from packages/contracts/src/schemas/users.ts rename to packages/schemas/src/users.ts index e7ba65ce2ea0..a72ecca0f624 100644 --- a/packages/contracts/src/schemas/users.ts +++ b/packages/schemas/src/users.ts @@ -11,7 +11,7 @@ import { DifficultySchema, } from "./shared"; import { CustomThemeColorsSchema, FunboxNameSchema } from "./configs"; -import { doesNotContainProfanity } from "../validation/validation"; +import { doesNotContainProfanity } from "./validation/validation"; const NoneFilterSchema = z.literal("none"); export const ResultFiltersSchema = z.object({ diff --git a/packages/contracts/src/schemas/util.ts b/packages/schemas/src/util.ts similarity index 100% rename from packages/contracts/src/schemas/util.ts rename to packages/schemas/src/util.ts diff --git a/packages/contracts/src/validation/homoglyphs.ts b/packages/schemas/src/validation/homoglyphs.ts similarity index 100% rename from packages/contracts/src/validation/homoglyphs.ts rename to packages/schemas/src/validation/homoglyphs.ts diff --git a/packages/contracts/src/validation/validation.ts b/packages/schemas/src/validation/validation.ts similarity index 100% rename from packages/contracts/src/validation/validation.ts rename to packages/schemas/src/validation/validation.ts diff --git a/packages/schemas/tsconfig.json b/packages/schemas/tsconfig.json new file mode 100644 index 000000000000..b0f299a6257e --- /dev/null +++ b/packages/schemas/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@monkeytype/typescript-config/base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "declarationMap": true, + "target": "ES6" + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/schemas/tsup.config.js b/packages/schemas/tsup.config.js new file mode 100644 index 000000000000..18ad6a0dacdb --- /dev/null +++ b/packages/schemas/tsup.config.js @@ -0,0 +1,3 @@ +import { extendConfig } from "@monkeytype/tsup-config"; + +export default extendConfig(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e57abaedfc5c..fbbece044694 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,7 +19,7 @@ importers: version: link:packages/release '@vitest/coverage-v8': specifier: 2.1.9 - version: 2.1.9(vitest@2.1.9(@types/node@20.5.1)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.42.0)) + version: 2.1.9(vitest@2.1.9(@types/node@20.5.1)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.43.1)) conventional-changelog: specifier: 6.0.0 version: 6.0.0(conventional-commits-filter@5.0.0) @@ -49,7 +49,7 @@ importers: version: 2.3.3 vitest: specifier: 2.1.9 - version: 2.1.9(@types/node@20.5.1)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.42.0) + version: 2.1.9(@types/node@20.5.1)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.43.1) backend: dependencies: @@ -177,6 +177,9 @@ importers: '@monkeytype/oxlint-config': specifier: workspace:* version: link:../packages/oxlint-config + '@monkeytype/schemas': + specifier: workspace:* + version: link:../packages/schemas '@monkeytype/typescript-config': specifier: workspace:* version: link:../packages/typescript-config @@ -239,7 +242,7 @@ importers: version: 10.0.0 '@vitest/coverage-v8': specifier: 2.1.9 - version: 2.1.9(vitest@2.1.9(@types/node@20.14.11)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.42.0)) + version: 2.1.9(vitest@2.1.9(@types/node@20.14.11)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.43.1)) concurrently: specifier: 8.2.2 version: 8.2.2 @@ -272,7 +275,7 @@ importers: version: 5.5.4 vitest: specifier: 2.1.9 - version: 2.1.9(@types/node@20.14.11)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.42.0) + version: 2.1.9(@types/node@20.14.11)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.43.1) vitest-mongodb: specifier: 1.0.0 version: 1.0.0 @@ -382,6 +385,9 @@ importers: '@monkeytype/oxlint-config': specifier: workspace:* version: link:../packages/oxlint-config + '@monkeytype/schemas': + specifier: workspace:* + version: link:../packages/schemas '@monkeytype/typescript-config': specifier: workspace:* version: link:../packages/typescript-config @@ -518,6 +524,9 @@ importers: '@monkeytype/eslint-config': specifier: workspace:* version: link:../eslint-config + '@monkeytype/schemas': + specifier: workspace:* + version: link:../schemas '@monkeytype/tsup-config': specifier: workspace:* version: link:../tsup-config @@ -544,7 +553,7 @@ importers: version: 5.5.4 vitest: specifier: 2.1.9 - version: 2.1.9(@types/node@20.14.11)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.42.0) + version: 2.1.9(@types/node@20.14.11)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.43.1) packages/eslint-config: devDependencies: @@ -575,9 +584,6 @@ importers: packages/funbox: dependencies: - '@monkeytype/contracts': - specifier: workspace:* - version: link:../contracts '@monkeytype/util': specifier: workspace:* version: link:../util @@ -585,6 +591,9 @@ importers: '@monkeytype/eslint-config': specifier: workspace:* version: link:../eslint-config + '@monkeytype/schemas': + specifier: workspace:* + version: link:../schemas '@monkeytype/tsup-config': specifier: workspace:* version: link:../tsup-config @@ -611,7 +620,7 @@ importers: version: 5.5.4 vitest: specifier: 2.1.9 - version: 2.1.9(@types/node@20.14.11)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.42.0) + version: 2.1.9(@types/node@20.14.11)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.43.1) packages/oxlint-config: {} @@ -640,6 +649,40 @@ importers: specifier: 1.7.0 version: 1.7.0 + packages/schemas: + dependencies: + zod: + specifier: 3.23.8 + version: 3.23.8 + devDependencies: + '@monkeytype/eslint-config': + specifier: workspace:* + version: link:../eslint-config + '@monkeytype/tsup-config': + specifier: workspace:* + version: link:../tsup-config + '@monkeytype/typescript-config': + specifier: workspace:* + version: link:../typescript-config + chokidar: + specifier: 3.6.0 + version: 3.6.0 + eslint: + specifier: 8.57.1 + version: 8.57.1 + madge: + specifier: 8.0.0 + version: 8.0.0(typescript@5.5.4) + oxlint: + specifier: 1.7.0 + version: 1.7.0 + tsup: + specifier: 8.4.0 + version: 8.4.0(postcss@8.5.3)(tsx@4.16.2)(typescript@5.5.4)(yaml@2.5.0) + typescript: + specifier: 5.5.4 + version: 5.5.4 + packages/tsup-config: dependencies: tsup: @@ -692,7 +735,7 @@ importers: version: 5.5.4 vitest: specifier: 2.1.9 - version: 2.1.9(@types/node@20.14.11)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.42.0) + version: 2.1.9(@types/node@20.14.11)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.43.1) zod: specifier: 3.23.8 version: 3.23.8 @@ -3697,11 +3740,6 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - browserslist@4.25.0: - resolution: {integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - browserslist@4.25.1: resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -9303,11 +9341,6 @@ packages: engines: {node: '>=10'} hasBin: true - terser@5.42.0: - resolution: {integrity: sha512-UYCvU9YQW2f/Vwl+P0GfhxJxbUGLwd+5QrrGgLajzWAtC/23AX0vcise32kkP7Eu0Wu9VlzzHAXkLObgjQfFlQ==} - engines: {node: '>=10'} - hasBin: true - terser@5.43.1: resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==} engines: {node: '>=10'} @@ -10581,7 +10614,7 @@ snapshots: dependencies: '@babel/compat-data': 7.28.0 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.25.0 + browserslist: 4.25.1 lru-cache: 5.1.1 semver: 6.3.1 @@ -11600,7 +11633,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.4.0 + debug: 4.4.1 espree: 9.6.1 globals: 13.24.0 ignore: 5.3.1 @@ -12220,7 +12253,7 @@ snapshots: '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.0 + debug: 4.4.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -13264,24 +13297,6 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@20.14.11)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.42.0))': - dependencies: - '@ampproject/remapping': 2.3.0 - '@bcoe/v8-coverage': 0.2.3 - debug: 4.4.0 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.1.7 - magic-string: 0.30.17 - magicast: 0.3.5 - std-env: 3.8.0 - test-exclude: 7.0.1 - tinyrainbow: 1.2.0 - vitest: 2.1.9(@types/node@20.14.11)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.42.0) - transitivePeerDependencies: - - supports-color - '@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@20.14.11)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.43.1))': dependencies: '@ampproject/remapping': 2.3.0 @@ -13300,7 +13315,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@20.5.1)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.42.0))': + '@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@20.5.1)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.43.1))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -13314,7 +13329,7 @@ snapshots: std-env: 3.8.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.1.9(@types/node@20.5.1)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.42.0) + vitest: 2.1.9(@types/node@20.5.1)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.43.1) transitivePeerDependencies: - supports-color @@ -13325,14 +13340,6 @@ snapshots: chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.9(vite@5.4.17(@types/node@20.14.11)(sass@1.70.0)(terser@5.42.0))': - dependencies: - '@vitest/spy': 2.1.9 - estree-walker: 3.0.3 - magic-string: 0.30.17 - optionalDependencies: - vite: 5.4.17(@types/node@20.14.11)(sass@1.70.0)(terser@5.42.0) - '@vitest/mocker@2.1.9(vite@5.4.17(@types/node@20.14.11)(sass@1.70.0)(terser@5.43.1))': dependencies: '@vitest/spy': 2.1.9 @@ -13341,13 +13348,13 @@ snapshots: optionalDependencies: vite: 5.4.17(@types/node@20.14.11)(sass@1.70.0)(terser@5.43.1) - '@vitest/mocker@2.1.9(vite@5.4.17(@types/node@20.5.1)(sass@1.70.0)(terser@5.42.0))': + '@vitest/mocker@2.1.9(vite@5.4.17(@types/node@20.5.1)(sass@1.70.0)(terser@5.43.1))': dependencies: '@vitest/spy': 2.1.9 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 5.4.17(@types/node@20.5.1)(sass@1.70.0)(terser@5.42.0) + vite: 5.4.17(@types/node@20.5.1)(sass@1.70.0)(terser@5.43.1) '@vitest/pretty-format@2.1.9': dependencies: @@ -13376,7 +13383,7 @@ snapshots: '@vue/compiler-core@3.4.37': dependencies: - '@babel/parser': 7.26.8 + '@babel/parser': 7.28.0 '@vue/shared': 3.4.37 entities: 5.0.0 estree-walker: 2.0.2 @@ -13389,7 +13396,7 @@ snapshots: '@vue/compiler-sfc@3.4.37': dependencies: - '@babel/parser': 7.26.8 + '@babel/parser': 7.28.0 '@vue/compiler-core': 3.4.37 '@vue/compiler-dom': 3.4.37 '@vue/compiler-ssr': 3.4.37 @@ -13431,13 +13438,13 @@ snapshots: mime-types: 3.0.1 negotiator: 1.0.0 - acorn-jsx@5.3.2(acorn@8.14.0): + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: - acorn: 8.14.0 + acorn: 8.15.0 acorn-walk@8.3.3: dependencies: - acorn: 8.14.0 + acorn: 8.15.0 acorn@8.14.0: {} @@ -13991,13 +13998,6 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.24.4) - browserslist@4.25.0: - dependencies: - caniuse-lite: 1.0.30001727 - electron-to-chromium: 1.5.187 - node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.25.0) - browserslist@4.25.1: dependencies: caniuse-lite: 1.0.30001727 @@ -15510,7 +15510,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.4.0 + debug: 4.4.1 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -15549,8 +15549,8 @@ snapshots: espree@9.6.1: dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 3.4.3 esprima@4.0.1: {} @@ -15897,7 +15897,7 @@ snapshots: enhanced-resolve: 5.17.1 module-definition: 6.0.0 module-lookup-amd: 9.0.2 - resolve: 1.22.8 + resolve: 1.22.10 resolve-dependency-path: 4.0.0 sass-lookup: 6.0.1 stylus-lookup: 6.0.0 @@ -17873,7 +17873,7 @@ snapshots: chalk: 4.1.2 commander: 7.2.0 commondir: 1.0.1 - debug: 4.3.6(supports-color@5.5.0) + debug: 4.4.1 dependency-tree: 11.0.1 ora: 5.4.1 pluralize: 8.0.0 @@ -18706,7 +18706,7 @@ snapshots: node-source-walk@7.0.0: dependencies: - '@babel/parser': 7.26.8 + '@babel/parser': 7.28.0 nodemailer@6.9.14: {} @@ -20694,7 +20694,7 @@ snapshots: sucrase@3.35.0: dependencies: - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.12 commander: 4.1.1 glob: 10.4.5 lines-and-columns: 1.2.4 @@ -20874,14 +20874,6 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 - terser@5.42.0: - dependencies: - '@jridgewell/source-map': 0.3.10 - acorn: 8.15.0 - commander: 2.20.3 - source-map-support: 0.5.21 - optional: true - terser@5.43.1: dependencies: '@jridgewell/source-map': 0.3.10 @@ -21070,7 +21062,7 @@ snapshots: '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 '@types/node': 20.5.1 - acorn: 8.14.0 + acorn: 8.15.0 acorn-walk: 8.3.3 arg: 4.1.3 create-require: 1.1.1 @@ -21105,17 +21097,17 @@ snapshots: cac: 6.7.14 chokidar: 4.0.3 consola: 3.4.0 - debug: 4.4.0 + debug: 4.4.1 esbuild: 0.25.0 joycon: 3.1.1 picocolors: 1.1.1 postcss-load-config: 6.0.1(postcss@8.5.3)(tsx@4.16.2)(yaml@2.5.0) resolve-from: 5.0.0 - rollup: 4.34.8 + rollup: 4.40.0 source-map: 0.8.0-beta.0 sucrase: 3.35.0 tinyexec: 0.3.2 - tinyglobby: 0.2.12 + tinyglobby: 0.2.13 tree-kill: 1.2.2 optionalDependencies: postcss: 8.5.3 @@ -21413,12 +21405,6 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - update-browserslist-db@1.1.3(browserslist@4.25.0): - dependencies: - browserslist: 4.25.0 - escalade: 3.2.0 - picocolors: 1.1.1 - update-browserslist-db@1.1.3(browserslist@4.25.1): dependencies: browserslist: 4.25.1 @@ -21603,24 +21589,6 @@ snapshots: dependencies: vite: 6.3.4(@types/node@20.14.11)(sass@1.70.0)(terser@5.43.1)(tsx@4.16.2)(yaml@2.5.0) - vite-node@2.1.9(@types/node@20.14.11)(sass@1.70.0)(terser@5.42.0): - dependencies: - cac: 6.7.14 - debug: 4.4.0 - es-module-lexer: 1.6.0 - pathe: 1.1.2 - vite: 5.4.17(@types/node@20.14.11)(sass@1.70.0)(terser@5.42.0) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - vite-node@2.1.9(@types/node@20.14.11)(sass@1.70.0)(terser@5.43.1): dependencies: cac: 6.7.14 @@ -21639,13 +21607,13 @@ snapshots: - supports-color - terser - vite-node@2.1.9(@types/node@20.5.1)(sass@1.70.0)(terser@5.42.0): + vite-node@2.1.9(@types/node@20.5.1)(sass@1.70.0)(terser@5.43.1): dependencies: cac: 6.7.14 debug: 4.4.0 es-module-lexer: 1.6.0 pathe: 1.1.2 - vite: 5.4.17(@types/node@20.5.1)(sass@1.70.0)(terser@5.42.0) + vite: 5.4.17(@types/node@20.5.1)(sass@1.70.0)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - less @@ -21723,17 +21691,6 @@ snapshots: transitivePeerDependencies: - supports-color - vite@5.4.17(@types/node@20.14.11)(sass@1.70.0)(terser@5.42.0): - dependencies: - esbuild: 0.21.5 - postcss: 8.5.1 - rollup: 4.34.8 - optionalDependencies: - '@types/node': 20.14.11 - fsevents: 2.3.3 - sass: 1.70.0 - terser: 5.42.0 - vite@5.4.17(@types/node@20.14.11)(sass@1.70.0)(terser@5.43.1): dependencies: esbuild: 0.21.5 @@ -21745,7 +21702,7 @@ snapshots: sass: 1.70.0 terser: 5.43.1 - vite@5.4.17(@types/node@20.5.1)(sass@1.70.0)(terser@5.42.0): + vite@5.4.17(@types/node@20.5.1)(sass@1.70.0)(terser@5.43.1): dependencies: esbuild: 0.21.5 postcss: 8.5.1 @@ -21754,7 +21711,7 @@ snapshots: '@types/node': 20.5.1 fsevents: 2.3.3 sass: 1.70.0 - terser: 5.42.0 + terser: 5.43.1 vite@6.3.4(@types/node@20.14.11)(sass@1.70.0)(terser@5.43.1)(tsx@4.16.2)(yaml@2.5.0): dependencies: @@ -21784,42 +21741,6 @@ snapshots: - snappy - supports-color - vitest@2.1.9(@types/node@20.14.11)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.42.0): - dependencies: - '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.17(@types/node@20.14.11)(sass@1.70.0)(terser@5.42.0)) - '@vitest/pretty-format': 2.1.9 - '@vitest/runner': 2.1.9 - '@vitest/snapshot': 2.1.9 - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 - chai: 5.1.2 - debug: 4.4.0 - expect-type: 1.1.0 - magic-string: 0.30.17 - pathe: 1.1.2 - std-env: 3.8.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinypool: 1.0.2 - tinyrainbow: 1.2.0 - vite: 5.4.17(@types/node@20.14.11)(sass@1.70.0)(terser@5.42.0) - vite-node: 2.1.9(@types/node@20.14.11)(sass@1.70.0)(terser@5.42.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 20.14.11 - happy-dom: 15.10.2 - transitivePeerDependencies: - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - vitest@2.1.9(@types/node@20.14.11)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.43.1): dependencies: '@vitest/expect': 2.1.9 @@ -21856,10 +21777,10 @@ snapshots: - supports-color - terser - vitest@2.1.9(@types/node@20.5.1)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.42.0): + vitest@2.1.9(@types/node@20.5.1)(happy-dom@15.10.2)(sass@1.70.0)(terser@5.43.1): dependencies: '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.17(@types/node@20.5.1)(sass@1.70.0)(terser@5.42.0)) + '@vitest/mocker': 2.1.9(vite@5.4.17(@types/node@20.5.1)(sass@1.70.0)(terser@5.43.1)) '@vitest/pretty-format': 2.1.9 '@vitest/runner': 2.1.9 '@vitest/snapshot': 2.1.9 @@ -21875,8 +21796,8 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 1.2.0 - vite: 5.4.17(@types/node@20.5.1)(sass@1.70.0)(terser@5.42.0) - vite-node: 2.1.9(@types/node@20.5.1)(sass@1.70.0)(terser@5.42.0) + vite: 5.4.17(@types/node@20.5.1)(sass@1.70.0)(terser@5.43.1) + vite-node: 2.1.9(@types/node@20.5.1)(sass@1.70.0)(terser@5.43.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.5.1 From fb82a528822adf5d12da2ca77b678604b0e14850 Mon Sep 17 00:00:00 2001 From: sobczaktm Date: Mon, 21 Jul 2025 15:28:57 +0200 Subject: [PATCH 3/9] impr(quotes): 15 polish proverbs and other quotes for polish language (@sobczaktm) (#6760) Description 15 polish proverbs and other quotes for polish language Checks Translations: Agreement is reached when one party pretends to believe the nonsense told by the other. - "id":216 Only six people in the Galaxy knew that the president's job was not to wield power, but to distract from it. - "id":217 I have become death, the destroyer of worlds. - "id":218 If something is impossible to do, let the Poles do it.- "id":219 It's not how long you live, but how wisely you live. It's never too late to learn and grow. Wisdom is priceless, regardless of age. - "id":220 Don't waste time, for it is the stuff life is made of. - "id":221 You praise others, you do not know your own, you do not know what you have. - "id":222 Don't argue with a fool because he will drag you down to his level and beat you with experience. - "id":223 It is better to remain silent and be thought a fool than to open your mouth and remove all doubt. - "id":224 Clothes do not make the man, clothes are for the man, not man for clothes. -"id":225 He who digs holes for another will fall into them himself. Therefore, do not do to others what you would not want done to you. - "id":226 Don't judge a man by his clothes, but by the friends he has. For a man is what he associates with. - "id":227 What goes around comes around. Don't put off until tomorrow what you can do today. - "id":228 First a person learns to walk, then to run, then to speak, then to be silent. It's crucial to do certain things in the right order. - "id":229 Don't praise the day before sunset, you're not sure what the end of the day may bring.- "id":230 --------- Co-authored-by: Tomasz.Sobczak --- frontend/static/quotes/polish.json | 92 +++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/frontend/static/quotes/polish.json b/frontend/static/quotes/polish.json index fbc3799b122b..fadde2b0aa38 100644 --- a/frontend/static/quotes/polish.json +++ b/frontend/static/quotes/polish.json @@ -1268,7 +1268,7 @@ "id": 213 }, { - "text": "Mówiłem panu zawsze – procesów zaniechać. Mówiłem panu zawsze – najechać, zajechać!", + "text": "Mówiłem panu zawsze - procesów zaniechać. Mówiłem panu zawsze - najechać, zajechać!", "source": "Pan Tadeusz (1999)", "length": 83, "id": 214 @@ -1278,6 +1278,96 @@ "source": "Ziemia obiecana (1975)", "length": 106, "id": 215 + }, + { + "text": "Porozumienie osiąga się, gdy jedna ze stron udaje, że wierzy w dyrdymały opowiadane przez drugą.", + "source": "Andrzej Sapkowski, Lux perpetua", + "length": 96, + "id": 216 + }, + { + "text": "Tylko sześciu ludzi w Galaktyce wiedziało, że zadanie prezydenta polegało nie na sprawowaniu władzy, ale na odwracaniu od niej uwagi.", + "source": "Douglas Adams, Autostopem przez galaktykę", + "length": 133, + "id": 217 + }, + { + "text": "Stałem się śmiercią, niszczycielem światów.", + "source": "Robert Oppenheimer", + "length": 43, + "id": 218 + }, + { + "text": "Jeśli coś jest niemożliwe do wykonania, dajcie to zrobić Polakom.", + "source": "Napoleon Bonaparte", + "length": 65, + "id": 219 + }, + { + "text": "Nie ważne, jak długo żyjesz, ale jak mądrze. Nigdy nie jest za późno na naukę i rozwijanie się. Mądrość jest bezcenna, niezależnie od wieku.", + "source": "Janusz Korczak", + "length": 140, + "id": 220 + }, + { + "text": "Nie marnuj czasu, bo to materiał, z którego jest zrobione życie.", + "source": "Benjamin Franklin", + "length": 64, + "id": 221 + }, + { + "text": "Cudze chwalicie, swego nie znacie, sami nie wiecie co posiadacie.", + "source": "Polskie przysłowie", + "length": 65, + "id": 222 + }, + { + "text": "Nie kłóć się z głupcem, bo sprowadzi cię do swojego poziomu i pokona doświadczeniem.", + "source": "Polskie przysłowie", + "length": 84, + "id": 223 + }, + { + "text": "Lepiej jest milczeć i być uważanym za głupca, niż otworzyć usta i rozwiać wszelkie wątpliwości.", + "source": "Abraham Lincoln", + "length": 95, + "id": 224 + }, + { + "text": "Nie szata zdobi człowieka, szata jest dla człowieka, a nie człowiek dla szaty.", + "source": "Polskie przysłowie", + "length": 78, + "id": 225 + }, + { + "text": "Kto pod kim dołki kopie, ten sam w nie wpada. Czyli nie czyń drugiemu co tobie niemiłe.", + "source": "Polskie przysłowie", + "length": 87, + "id": 226 + }, + { + "text": "Nie sądź o człowieku po ubiorze, ale po tym, jakich ma przyjaciół. Człowiek jest bowiem tym, do kogo się przyłącza.", + "source": "Staropolskie Przysłowie", + "length": 115, + "id": 227 + }, + { + "text": "Co się odwlecze, to nie uciecze. Nie odwlekaj na jutro tego, co możesz zrobić dzisiaj.", + "source": "Polskie przysłowie", + "length": 86, + "id": 228 + }, + { + "text": "Najpierw człowiek uczy się chodzić, a potem biegać, później mówić, a następnie milczeć. Niezmiernie ważne jest, aby pewne rzeczy robić w odpowiedniej kolejności.", + "source": "Stefan Żeromski", + "length": 161, + "id": 229 + }, + { + "text": "Nie chwal dnia przed zachodem słońca, nie jesteś pewien, co może przynieść koniec dnia.", + "source": "Polskie przysłowie", + "length": 87, + "id": 230 } ] } From 62a5145f020de2fc8c7392a1076e4856357b5f5d Mon Sep 17 00:00:00 2001 From: david <69175738+8e3@users.noreply.github.com> Date: Mon, 21 Jul 2025 15:29:41 +0200 Subject: [PATCH 4/9] fix(german_250k.json): remove non-german accents (@8e3) (#6756) Removed non-German accents / untypable loanwords ### Description Since the German qwertz layout doesn't support certain accents, used in some loanwords in this list, typing them is impossible, without changing your keyboard layout mid-session. Using an automated script, I decided to remove them, enhancing playability. --------- Co-authored-by: fehmer <3728838+fehmer@users.noreply.github.com> --- frontend/static/languages/german_250k.json | 371 --------------------- 1 file changed, 371 deletions(-) diff --git a/frontend/static/languages/german_250k.json b/frontend/static/languages/german_250k.json index cb67534f849c..24789daf9849 100644 --- a/frontend/static/languages/german_250k.json +++ b/frontend/static/languages/german_250k.json @@ -118,7 +118,6 @@ "Abbauvorgang", "abbauwürdig", "Abbe", - "Abbé", "abbeeren", "abbehalten", "abbeißen", @@ -3153,7 +3152,6 @@ "abzwingen", "abzwitschern", "Academia", - "Académie", "Academy", "Accademia", "Accenture", @@ -4021,7 +4019,6 @@ "Agrarwissenschaftler", "agrarwissenschaftlich", "Agreement", - "Agrément", "Agressionskrieg", "Agricola", "Agricole", @@ -4596,7 +4593,6 @@ "Alamo", "Alan", "Aland", - "Åland", "Alane", "Alanen", "Alarich", @@ -4701,7 +4697,6 @@ "Albumtitel", "Albuquerque", "Albus", - "Alcalá", "Alcatel", "Alcatraz", "Alchemie", @@ -6217,12 +6212,10 @@ "Ameisenstraße", "Amelia", "Amelie", - "Amélie", "amen", "Amen", "Amenophis", "America", - "América", "American", "Americans", "Amerika", @@ -6920,21 +6913,18 @@ "andrängen", "Andrangs", "Andre", - "André", "Andrea", "Andreae", "Andreas", "Andreasberg", "Andreaskirche", "Andreaskreuz", - "Andrée", "andrehen", "Andrei", "Andrej", "Andreotti", "andrerseits", "Andres", - "Andrés", "andreschen", "Andresen", "Andrew", @@ -7681,7 +7671,6 @@ "Angorakatze", "Angorawolle", "Angoraziege", - "Angoulême", "angrapschen", "angreifbar", "angreifen", @@ -9302,7 +9291,6 @@ "Antonie", "Antoninus", "Antonio", - "António", "Antonius", "Antoniuskirche", "Antonow", @@ -9709,7 +9697,6 @@ "apathisch", "Apax", "Apel", - "Aperçu", "Aperitif", "apern", "Apertura", @@ -9989,7 +9976,6 @@ "Arafat", "Arafats", "Aragon", - "Aragón", "Aragonien", "Aral", "Aralsee", @@ -10930,7 +10916,6 @@ "Argwohn", "argwöhnen", "argwöhnisch", - "Århus", "Ari", "Ariadne", "Ariadnefaden", @@ -11026,7 +11011,6 @@ "arme", "Arme", "Armee", - "Armée", "Armeeangabe", "Armeeangaben", "Armeeangehörige", @@ -11262,7 +11246,6 @@ "Arsenals", "Arsenblende", "Arsene", - "Arsène", "Arseneisen", "arsenig", "Arsenik", @@ -12006,7 +11989,6 @@ "Atlasschleife", "Atlasschuh", "Atletico", - "Atlético", "atmen", "Atmen", "atmet", @@ -12224,7 +12206,6 @@ "ätsch", "Atta", "Attac", - "Attaché", "attachieren", "Attack", "Attacke", @@ -14804,7 +14785,6 @@ "Ausflügen", "Ausflügler", "Ausflugsboot", - "Ausflugscafé", "Ausflugsdampfer", "Ausflugsfahrt", "Ausflugsfahrten", @@ -17378,7 +17358,6 @@ "Autocorso", "Autocross", "Autodach", - "Autodafé", "Autodidakt", "Autodidaktin", "autodidaktisch", @@ -19457,7 +19436,6 @@ "barbusig", "Barby", "Barca", - "Barça", "Barcelona", "Barcelonas", "Barchef", @@ -19751,7 +19729,6 @@ "bartlos", "Bartmann", "Bartmoos", - "Bartók", "Bartoli", "Bartolomeo", "Barton", @@ -21259,7 +21236,6 @@ "Bebauungsplanung", "Bebauungsplanverfahren", "Bebauungsvorschlag", - "Bébé", "Bebel", "beben", "Beben", @@ -21281,9 +21257,6 @@ "bebt", "bebte", "bebuscht", - "Béchamel", - "Béchamelkartoffel", - "Béchamelsauce", "Becher", "Becherbach", "Becherblume", @@ -23197,7 +23170,6 @@ "Bekundungen", "Bel", "Bela", - "Béla", "belächeln", "belächelt", "belachen", @@ -23669,7 +23641,6 @@ "Benno", "Benny", "Benoit", - "Benoît", "benommen", "Benommenheit", "benörgeln", @@ -24837,7 +24808,6 @@ "Besamungsstation", "Besamungstechniker", "Besan", - "Besançon", "besandet", "besänftigen", "besänftigt", @@ -25332,7 +25302,6 @@ "Besiegung", "Besigheim", "Besiktas", - "Beşiktaş", "besingen", "besingt", "besinnen", @@ -27228,7 +27197,6 @@ "Bey", "Beyeler", "Beyer", - "Beyoncé", "Beyond", "bezahlbar", "bezahlbare", @@ -27546,8 +27514,6 @@ "Bhutan", "Bhutto", "Biaggi", - "Białogard", - "Białystok", "Bianca", "Bianchi", "Bianco", @@ -27645,7 +27611,6 @@ "Bibliothekswesen", "Bibliothekswissenschaft", "Bibliothekszimmer", - "Bibliothèque", "Biblis", "biblisch", "biblische", @@ -28362,7 +28327,6 @@ "Bill", "Billard", "Billardball", - "Billardcafé", "Billardfreund", "Billardklub", "Billardkugel", @@ -28943,7 +28907,6 @@ "Björk", "Björn", "Björndalen", - "Bjørndalen", "Blabla", "Blache", "Blachfeld", @@ -29445,7 +29408,6 @@ "Blendrahmen", "Blendung", "Blendwerk", - "Blériot", "Blesse", "Blessente", "Blessgans", @@ -30510,10 +30472,8 @@ "Bogislaw", "Bogner", "Bogota", - "Bogotá", "Böheimkirche", "Boheme", - "Bohème", "Bohemien", "Bohemund", "Bohl", @@ -30619,7 +30579,6 @@ "Bolero", "Boleslav", "Boleslaw", - "Bolesław", "Boleyn", "Bolid", "Bolide", @@ -31306,7 +31265,6 @@ "Bourges", "Bourget", "Bourne", - "Bourrée", "Bouteille", "Boutique", "Boutiquen", @@ -31681,7 +31639,6 @@ "Brasilien", "Brasiliens", "Brasilier", - "Braşov", "Brass", "Brasse", "brassen", @@ -35470,7 +35427,6 @@ "Caches", "Cactus", "Cadillac", - "Cádiz", "Cadmium", "Caen", "Caesar", @@ -35478,12 +35434,7 @@ "Caesars", "Caetano", "Cafe", - "Café", - "Cafégast", - "Caféhaus", - "Cafékonzert", "Cafes", - "Cafés", "Cafeteria", "Cafetier", "Cafetiere", @@ -35505,7 +35456,6 @@ "Calciumcarbonat", "Calder", "Caldera", - "Calderón", "Caldwell", "Cale", "Caleb", @@ -35603,7 +35553,6 @@ "Canadiens", "Canaille", "Canal", - "Canapé", "Canaria", "Canaris", "Canasta", @@ -35626,7 +35575,6 @@ "Cannon", "Cannstatt", "Canon", - "Cañon", "Canossa", "cantabile", "Cantate", @@ -35842,7 +35790,6 @@ "Celesta", "Celia", "Celine", - "Céline", "Cell", "Cella", "Celle", @@ -35899,15 +35846,10 @@ "ces", "Ces", "Cesar", - "César", "Cesare", - "Česká", - "České", - "Český", "Cessna", "Ceuta", "Ceylon", - "Cézanne", "cg", "Cha", "Chabarowsk", @@ -35936,7 +35878,6 @@ "Chamber", "Chamberlain", "Chambers", - "Chambéry", "Chamenei", "chamois", "chamoisfarben", @@ -36100,7 +36041,6 @@ "charismatische", "charismatischen", "Charisteas", - "Charité", "Charity", "Charivari", "Charkiw", @@ -36154,9 +36094,7 @@ "Chat", "Chatami", "Chateau", - "Château", "Chatham", - "Châtillon", "Chatraum", "Chatroom", "Chats", @@ -36187,7 +36125,6 @@ "chauvinistisch", "Chaux", "Chavez", - "Chávez", "Che", "Cheb", "Check", @@ -36403,7 +36340,6 @@ "Chick", "Chicken", "Chico", - "Chicorée", "Chief", "Chiefs", "Chiemgau", @@ -36438,7 +36374,6 @@ "Chin", "China", "Chinabrokat", - "Chinacrêpe", "Chinagras", "Chinakohl", "Chinapapier", @@ -36490,7 +36425,6 @@ "chirurgisch", "chirurgische", "chirurgischen", - "Chişinău", "Chlodwig", "Chloe", "Chlor", @@ -36837,14 +36771,12 @@ "cis", "Cis", "Cisco", - "Cité", "Citibank", "Cities", "Citigroup", "Citizen", "Citoyen", "Citroen", - "Citroën", "Citrus", "City", "Citybike", @@ -36912,7 +36844,6 @@ "Clearingstelle", "Clemens", "Clement", - "Clément", "Clemente", "Clementine", "Clements", @@ -36953,7 +36884,6 @@ "Clodius", "Clooney", "Cloppenburg", - "Cloqué", "Clos", "Close", "Clou", @@ -37079,7 +37009,6 @@ "collagiert", "Collection", "College", - "Collège", "Colleges", "Collegiate", "Collegium", @@ -37092,7 +37021,6 @@ "Cologne", "Colombier", "Colombo", - "Colón", "Colonel", "Colonia", "Colonna", @@ -37137,7 +37065,6 @@ "Comicstrips", "Comiczeichner", "Coming", - "Comité", "Command", "Commander", "Commanders", @@ -37154,7 +37081,6 @@ "Common", "Commons", "Commonwealth", - "Communauté", "communes", "Communication", "Communications", @@ -37167,7 +37093,6 @@ "Company", "Compaq", "Competition", - "Compiègne", "Compilation", "Compiler", "Complex", @@ -37296,7 +37221,6 @@ "Comte", "con", "Conan", - "Concepción", "Concept", "Concert", "Concertino", @@ -37306,7 +37230,6 @@ "Concorde", "Concordia", "Concours", - "Condé", "Condoleezza", "Condor", "Conergy", @@ -37314,8 +37237,6 @@ "Confederation", "Confederations", "Conference", - "Conférence", - "Conférencier", "Configuration", "Congress", "Conn", @@ -37389,7 +37310,6 @@ "contrastieren", "control", "Control", - "Contrôlée", "Controller", "Controlling", "Convair", @@ -37429,7 +37349,6 @@ "Cordes", "Cordjacke", "Cordoba", - "Córdoba", "Cordsamt", "Cordula", "Core", @@ -37468,13 +37387,11 @@ "Corso", "Corsofahrt", "Cortes", - "Cortés", "Cortese", "Cortex", "Cortina", "Corts", "Coruna", - "Coruña", "Corvette", "Corvey", "Corvinus", @@ -37494,8 +37411,6 @@ "cot", "Cotabato", "Cote", - "Côte", - "Côtes", "Cotta", "Cottage", "Cottbus", @@ -37522,8 +37437,6 @@ "Countys", "Coup", "Coupe", - "Coupé", - "Coupés", "Coupland", "Couplet", "Coupletsänger", @@ -37595,7 +37508,6 @@ "Creation", "Creative", "Credit", - "Crédit", "Creditanstalt", "Creditreform", "Credits", @@ -37604,7 +37516,6 @@ "Creek", "creme", "Creme", - "Crème", "cremefarben", "Cremer", "Cremerolle", @@ -37614,12 +37525,9 @@ "Cremetörtchen", "Cremetorte", "Cremona", - "Crêpekleid", - "Crêpeschuh", "crescendo", "Crescendo", "Crescent", - "Crêt", "Creutzfeldt", "Crew", "Crewmitglied", @@ -37635,7 +37543,6 @@ "Cristiano", "Cristina", "Cristo", - "Cristóbal", "Critics", "Croce", "Crockett", @@ -37674,7 +37581,6 @@ "Cruzeiro", "Cry", "Crystal", - "Csárdás", "Cuba", "Cuban", "Cube", @@ -38022,7 +37928,6 @@ "Dahomey", "Dahrendorf", "Daihatsu", - "Dáil", "Daily", "Daimler", "DaimlerChrysler", @@ -38093,7 +37998,6 @@ "Damenbinde", "Damenchef", "Damenchor", - "Damencoupé", "Damendoppel", "Dameneinzel", "Damenfahrrad", @@ -38324,7 +38228,6 @@ "Danaiden", "Danaidenarbeit", "Danaidenfass", - "Danaïdenfass", "Dance", "Dancefloor", "dancen", @@ -38976,7 +38879,6 @@ "daunenweich", "daunig", "Dauphin", - "Dauphiné", "Daus", "Davao", "Dave", @@ -39314,7 +39216,6 @@ "Deckungsvorschlag", "Deckweiß", "Declaration", - "Déco", "Decoder", "decouvrieren", "Dede", @@ -39589,7 +39490,6 @@ "dekodieren", "Dekodierung", "Dekokt", - "Dekolleté", "Dekolletee", "dekolletiert", "Dekompositum", @@ -39667,7 +39567,6 @@ "Delegierung", "delektieren", "delektiert", - "Delémont", "Delfin", "Delfine", "Delfinschlag", @@ -40049,9 +39948,7 @@ "Departamento", "Departamentos", "Departement", - "Département", "Departements", - "Départements", "Departementsstraße", "Department", "Departments", @@ -41103,7 +41000,6 @@ "Dienstbotenklatsch", "Dienstbuch", "Dienstchef", - "Dienstcoupé", "Dienste", "Diensteanbieter", "Diensteid", @@ -42053,7 +41949,6 @@ "Divine", "Divis", "Division", - "División", "Divisionär", "Divisionen", "Divisionsarzt", @@ -42075,7 +41970,6 @@ "Dizzy", "Django", "Djindjic", - "Djurgårdens", "dkg", "dl", "dm", @@ -43075,7 +42969,6 @@ "draften", "Drag", "Dragan", - "Dragée", "Dräger", "dragieren", "Dragon", @@ -45388,7 +45281,6 @@ "Dürreperiode", "dürreresistent", "Dürreresistenz", - "Durrës", "Dürreschaden", "Dürrholz", "Dürrkräutler", @@ -45727,7 +45619,6 @@ "Eclipse", "Eco", "Ecole", - "École", "Economic", "Economics", "Economist", @@ -45862,7 +45753,6 @@ "Edna", "Edo", "Edouard", - "Édouard", "Eduard", "Eduardo", "Eduards", @@ -49742,7 +49632,6 @@ "Einzylindermotor", "Eiplasma", "Eipulver", - "Éireann", "eirund", "Eis", "Eisarena", @@ -49782,7 +49671,6 @@ "Eisbombe", "Eisbrecher", "Eisbrocken", - "Eiscafé", "Eischnee", "Eiscreme", "Eisdeckchen", @@ -49817,7 +49705,6 @@ "Eisenbahnbetriebswerk", "Eisenbahnbrücke", "Eisenbahnbundesamt", - "Eisenbahncoupé", "Eisenbahndamm", "Eisenbahndirektion", "Eisenbahnen", @@ -50817,7 +50704,6 @@ "Elternbesuch", "Elternbildung", "Elternbrief", - "Elterncafé", "Elternermäßigung", "Elternfreibetrag", "Elternfreude", @@ -50961,7 +50847,6 @@ "emigrierten", "Emil", "Emile", - "Émile", "Emilia", "Emilie", "Emilio", @@ -51235,7 +51120,6 @@ "En", "Enantiomere", "Encyclopedia", - "Encyclopédie", "End", "Endabnahme", "Endabnehmer", @@ -53199,7 +53083,6 @@ "Epstein", "Equipage", "Equipe", - "Équipe", "Equipierung", "Equipment", "Equity", @@ -56014,7 +55897,6 @@ "Erzählabend", "Erzählband", "erzählbar", - "Erzählcafé", "erzähle", "Erzählebene", "erzählen", @@ -56334,7 +56216,6 @@ "esoterische", "esoterischen", "Espace", - "España", "Espanyol", "Espe", "espen", @@ -56452,7 +56333,6 @@ "Esten", "Ester", "esterau", - "Esterházy", "Esther", "Estin", "Estland", @@ -56495,7 +56375,6 @@ "Etagentür", "Etagenwohnung", "Etagere", - "Étang", "Etappe", "Etappen", "Etappenerfolg", @@ -56566,7 +56445,6 @@ "ethnologisch", "Ethos", "Etienne", - "Étienne", "Etikett", "Etikette", "Etiketten", @@ -56580,7 +56458,6 @@ "etlichen", "etlicher", "etliches", - "Étoile", "Eton", "Etrusker", "etruskisch", @@ -56619,7 +56496,6 @@ "euertwegen", "Eugen", "Eugene", - "Eugène", "Eugenia", "Eugenie", "Eugenik", @@ -57492,7 +57368,6 @@ "Extrablatt", "extrabreit", "Extrachor", - "Extracoupé", "extradünn", "Extraeinladung", "Extrafahrt", @@ -58548,7 +58423,6 @@ "Fahrzeugwrack", "Fahrziel", "Faible", - "Fáil", "fair", "Fair", "Fairbanks", @@ -59905,7 +59779,6 @@ "Feder", "Federal", "Federation", - "Fédération", "Federball", "Federballschläger", "Federballspiel", @@ -60213,7 +60086,6 @@ "Feime", "fein", "Fein", - "Féin", "Feinabstimmung", "Feinansprache", "Feinarbeit", @@ -60545,7 +60417,6 @@ "Felicitas", "Felipe", "Felix", - "Félix", "Fell", "Fellabfall", "Fellache", @@ -60705,7 +60576,6 @@ "Fencheltee", "Fender", "Fenerbahce", - "Fenerbahçe", "Feng", "Fenn", "Fenner", @@ -60914,7 +60784,6 @@ "Fernand", "Fernandes", "Fernandez", - "Fernández", "Fernando", "Fernanschluss", "Fernbahn", @@ -65659,7 +65528,6 @@ "forensisch", "forensische", "Forest", - "Forêt", "Forever", "Forgeard", "Forint", @@ -66605,11 +66473,7 @@ "Fran", "Franc", "Franca", - "français", - "Français", "Francaise", - "française", - "Française", "France", "Frances", "Francesca", @@ -66624,8 +66488,6 @@ "Francke", "Franco", "Francois", - "François", - "Françoise", "Franconia", "Francos", "Francs", @@ -66690,7 +66552,6 @@ "fransen", "Fransen", "fransig", - "František", "Frantz", "Franz", "Franzbranntwein", @@ -66804,7 +66665,6 @@ "Frauenbündnis", "Frauenbüro", "Frauenbüste", - "Frauencafé", "Frauenchor", "Frauendelegation", "Frauendiskriminierung", @@ -67050,7 +66910,6 @@ "Fredeburg", "Fredenbeck", "Frederic", - "Frédéric", "Frederick", "Frederik", "Fredi", @@ -67306,7 +67165,6 @@ "Freiluftarena", "Freiluftbehandlung", "Freiluftbühne", - "Freiluftcafé", "Freiluftkino", "Freiluftkonzert", "Freiluftmuseum", @@ -67680,7 +67538,6 @@ "Frequenzen", "Frequenzgang", "Frequenzspektrum", - "Frères", "Fresenius", "Fresh", "Fresken", @@ -68529,12 +68386,7 @@ "Frostwarnung", "Frostwetter", "frostzitternd", - "Frotté", "Frotteesöckchen", - "Frottéhandtuch", - "Frottékleid", - "Frottésocke", - "Frottéstoff", "frottieren", "Frottiergewebe", "Frottierhandtuch", @@ -70139,7 +69991,6 @@ "Fußwege", "Fußzehe", "Fußzeug", - "Fútbol", "futsch", "Futter", "Futterage", @@ -70267,7 +70118,6 @@ "Gablung", "Gabmann", "Gabor", - "Gábor", "Gabriel", "Gabriela", "Gabriele", @@ -70704,8 +70554,6 @@ "Gärbottich", "Garching", "Garcia", - "García", - "Garçon", "Gardasee", "Garde", "Gardeinfanterie", @@ -75119,7 +74967,6 @@ "Generaldirektorin", "Generale", "Generäle", - "Générale", "Generaleinfall", "Generälen", "Generalfeldmarschall", @@ -75277,7 +75124,6 @@ "Genetische", "genetischen", "genetischer", - "Genève", "Genf", "Genfer", "Genferin", @@ -75765,7 +75611,6 @@ "Gerank", "gerannt", "Gerard", - "Gérard", "Geraschel", "geraspelt", "Gerassel", @@ -76520,7 +76365,6 @@ "Gesandter", "Gesandtin", "Gesandtschaft", - "Gesandtschaftsattaché", "Gesandtschaftsgebäude", "Gesandtschaftsrat", "Gesandtschaftssekretär", @@ -79241,7 +79085,6 @@ "gewütet", "Geyer", "Geysir", - "Géza", "gezackt", "gezahlt", "gezählt", @@ -79690,7 +79533,6 @@ "Gizeh", "Glace", "Glaceehandschuh", - "Glacéleder", "Glacier", "glacieren", "Glacis", @@ -79712,7 +79554,6 @@ "glamourösen", "Glamourwelt", "Glan", - "Glâne", "Glanz", "Glanzabzug", "Glanzband", @@ -81066,7 +80907,6 @@ "Gomera", "Gomes", "Gomez", - "Gómez", "Gomorra", "Gomorrha", "gon", @@ -81106,7 +80946,6 @@ "Gonzaga", "Gonzales", "Gonzalez", - "González", "Gonzalo", "good", "Good", @@ -81127,7 +80966,6 @@ "Göppinger", "Gör", "Gora", - "Góra", "Goran", "Göran", "Gorbatschow", @@ -83863,7 +83701,6 @@ "Guangzhou", "Guano", "Guantanamo", - "Guantánamo", "Guarani", "Guard", "Guardia", @@ -84120,7 +83957,6 @@ "Gus", "Gusche", "Gusen", - "Gusmão", "Guss", "Gussasphalt", "Gussbeton", @@ -84286,7 +84122,6 @@ "Gutherzigkeit", "Guthrie", "Gutierrez", - "Gutiérrez", "gütig", "Gütigkeit", "gutinformiert", @@ -84363,7 +84198,6 @@ "Guy", "Guyana", "Guys", - "Guzmán", "Gwen", "Gwyneth", "Gy", @@ -84580,7 +84414,6 @@ "Habitat", "Habitate", "Habitaten", - "Habitué", "habituell", "Habitus", "hablich", @@ -84952,7 +84785,6 @@ "Hakim", "Häkkinen", "Hakoah", - "Håkon", "hakt", "hakte", "Hal", @@ -85696,7 +85528,6 @@ "Handelsaktivität", "Handelsangestellte", "Handelsartikel", - "Handelsattaché", "Handelsauftakt", "Handelsausdruck", "Handelsaustausch", @@ -89245,7 +89076,6 @@ "Helen", "Helena", "Helene", - "Hélène", "Helens", "helfe", "helfen", @@ -90449,9 +90279,7 @@ "Hermsdorf", "hernach", "Hernals", - "Hernán", "Hernandez", - "Hernández", "Herne", "hernehmen", "Hernie", @@ -91151,7 +90979,6 @@ "herunterziehen", "herunterzuladen", "herunterzuspielen", - "Hervé", "hervor", "hervorarbeiten", "hervorbaumeln", @@ -95471,7 +95298,6 @@ "Honoratiorenfamilie", "Honoratiorenstübchen", "Honoratiorenstube", - "Honoré", "honorieren", "honoriert", "honorierte", @@ -95849,7 +95675,6 @@ "Hosts", "Hot", "Hotel", - "Hôtel", "Hotelangebot", "Hotelangestellte", "Hotelanlage", @@ -95977,7 +95802,6 @@ "hPa", "hr", "Hradec", - "Hradiště", "Hrubesch", "http", "hu", @@ -95986,7 +95810,6 @@ "Hua", "Huan", "Huang", - "Huáscar", "Hub", "Hubbard", "Hubbert", @@ -96323,7 +96146,6 @@ "Hundebiss", "Hundeblick", "Hundeblume", - "Hundecoupé", "Hundedame", "Hundedreck", "Hundedressur", @@ -97179,7 +97001,6 @@ "Ikonographie", "il", "Il", - "Île", "Ilias", "Ilja", "Iljitsch", @@ -99554,7 +99375,6 @@ "Internetauktionshaus", "Internetausgabe", "Internetbenutzer", - "Internetcafé", "Internetdienst", "internetfähig", "Internetfirma", @@ -99971,7 +99791,6 @@ "Irena", "Irenäus", "Irene", - "Irène", "irgend", "Irgend", "irgendein", @@ -100264,7 +100083,6 @@ "Ist", "Istaf", "Istanbul", - "İstanbul", "Istanbuler", "Istanbuls", "Istgehalt", @@ -100272,7 +100090,6 @@ "Istrien", "Iststärke", "Istvan", - "István", "Istzustand", "Iswestija", "it", @@ -100328,7 +100145,6 @@ "Iwanowitsch", "Iwein", "Izmir", - "İzmir", "ja", "Ja", "Jaap", @@ -100935,7 +100751,6 @@ "Janne", "Janneck", "Jänner", - "János", "Janowski", "Jansen", "Janssen", @@ -101198,7 +101013,6 @@ "Jerichow", "Jermaine", "Jerome", - "Jérôme", "Jerry", "Jersey", "Jerusalem", @@ -101223,7 +101037,6 @@ "Jesuitenpater", "jesuitisch", "Jesus", - "Jesús", "Jesuskind", "Jet", "Jetlag", @@ -101275,12 +101088,10 @@ "Jil", "Jill", "Jim", - "Jiménez", "Jimi", "Jimmie", "Jimmy", "Jin", - "Jindřich", "Jing", "jingeln", "Jintao", @@ -101292,9 +101103,7 @@ "Joanna", "Joanne", "Joao", - "João", "Joaquin", - "Joaquín", "Job", "Jobabbau", "Jobangebot", @@ -101439,7 +101248,6 @@ "Jollenkreuzer", "Jolly", "Jon", - "Jón", "Jona", "Jonas", "Jonathan", @@ -101465,13 +101273,11 @@ "Jörg", "Jorge", "Jörgen", - "Jørgen", "Joris", "Jörn", "Jos", "Joschka", "Jose", - "José", "Josef", "Josefin", "Josefine", @@ -101531,11 +101337,8 @@ "Joyce", "Joystick", "Jozef", - "Józef", - "József", "Ju", "Juan", - "Juárez", "Jubel", "Jubelablass", "Jubelarie", @@ -101752,7 +101555,6 @@ "Jugendbund", "Jugendbündnis", "Jugendbüro", - "Jugendcafé", "Jugendcamp", "Jugendchor", "Jugendclique", @@ -102442,7 +102244,6 @@ "Jupitermond", "Jupiters", "Jupp", - "Juppé", "Jura", "Jurack", "Jurakette", @@ -106211,7 +106012,6 @@ "Kerry", "Kersten", "Kerstin", - "Kertész", "Kerwe", "Kerwedorf", "Kerweplatz", @@ -106672,7 +106472,6 @@ "Kinderbuchverlag", "Kinderbühne", "Kinderbüro", - "Kindercafé", "Kinderchirurg", "Kinderchirurgie", "Kinderchor", @@ -107370,7 +107169,6 @@ "Kirchenbus", "Kirchenbuße", "Kirchenbüßer", - "Kirchencafé", "Kirchencantate", "Kirchenchor", "Kirchenchorprobe", @@ -110909,7 +110707,6 @@ "Kommunionhelfer", "Kommunionkind", "Kommunionkleid", - "Kommuniqué", "Kommunismus", "Kommunist", "Kommunisten", @@ -113251,7 +113048,6 @@ "Kösener", "Koserei", "Kosewort", - "Košice", "kosig", "Kosinus", "Köslin", @@ -113818,7 +113614,6 @@ "krallenförmig", "Krallenspur", "krallig", - "Králové", "Kram", "Krambambuli", "Krambude", @@ -116194,7 +115989,6 @@ "Kulturanthropologie", "Kulturarbeit", "Kulturarbeiter", - "Kulturattaché", "Kulturauftrag", "Kulturausgabe", "Kulturausschuss", @@ -116232,7 +116026,6 @@ "Kulturbürgermeister", "Kulturbüro", "Kulturbürokratie", - "Kulturcafé", "Kulturchef", "Kulturchefin", "Kulturclub", @@ -120198,7 +119991,6 @@ "Lasurlack", "lasziv", "Laszlo", - "László", "Late", "Latein", "Lateinamerika", @@ -122600,7 +122392,6 @@ "Lektorin", "Lektüre", "Lem", - "Léman", "Lemberg", "Lemgo", "Lemieux", @@ -122671,7 +122462,6 @@ "Lenkzeit", "Lennart", "Lenne", - "Lenné", "Lennestadt", "Lennon", "Lennons", @@ -122692,8 +122482,6 @@ "Leo", "Leoben", "Leon", - "León", - "Léon", "Leonard", "Leonardo", "Leonberg", @@ -123224,7 +123012,6 @@ "Liberalität", "liberalkonservativ", "Liberation", - "Libération", "Liberec", "Liberia", "Liberianer", @@ -124311,7 +124098,6 @@ "Linksverteidiger", "Linkswendung", "Linn", - "Linné", "linnen", "Linnen", "Linoldruck", @@ -124468,7 +124254,6 @@ "Literaturbetrachtung", "Literaturbetrieb", "Literaturbüro", - "Literaturcafé", "Literaturchef", "Literaturdenkmal", "Literaturdiskussion", @@ -125195,7 +124980,6 @@ "Lop", "Lopes", "Lopez", - "López", "Lorant", "Lorbeer", "Lorbeerbaum", @@ -126100,7 +125884,6 @@ "Luitpold", "Luiz", "Luka", - "Lukács", "Lukarne", "Lukas", "Lukaschenko", @@ -126124,7 +125907,6 @@ "lumbal", "Lumberjack", "Lumen", - "Lumière", "Lumineszenz", "lumineszieren", "Lümmel", @@ -126166,7 +125948,6 @@ "Lüneburg", "Lüneburger", "Lünen", - "Lunéville", "Lunge", "Lungen", "Lungenabteilung", @@ -126388,7 +126169,6 @@ "luziferisch", "Luzon", "lx", - "Lycée", "Lyceum", "Lycos", "Lydia", @@ -127055,7 +126835,6 @@ "Maiblüte", "Maibowle", "Maibummel", - "Maîche", "Maid", "Maidemonstration", "Maiden", @@ -127173,7 +126952,6 @@ "Maizeit", "Maizena", "Maiziere", - "Maizière", "Maja", "Majdanek", "Majestät", @@ -127259,7 +127037,6 @@ "malachitgrün", "malad", "Malaga", - "Málaga", "Malaie", "malaiisch", "malaiischen", @@ -128147,7 +127924,6 @@ "Mari", "Maria", "Mariä", - "María", "Mariae", "Mariah", "Mariahilf", @@ -128248,7 +128024,6 @@ "mariniert", "Marino", "Mario", - "Mário", "Marion", "Marionette", "Marionetten", @@ -130398,7 +130173,6 @@ "Megahit", "Megalithanlagen", "Megan", - "Mégane", "Megaparty", "Megapixel", "Megaprojekt", @@ -131379,7 +131153,6 @@ "Merinoschaf", "Merinowolle", "Merit", - "Mérite", "Meriten", "Meritum", "Merk", @@ -131867,7 +131640,6 @@ "Methylalkohol", "Metier", "Metin", - "Métis", "Metrik", "metrisch", "metrische", @@ -131928,7 +131700,6 @@ "meutern", "MeV", "Mexico", - "México", "Mexikaner", "Mexikanerin", "mexikanisch", @@ -132176,7 +131947,6 @@ "Mikado", "Mikael", "Mike", - "Miklós", "Mikro", "Mikroanalyse", "Mikroaufnahme", @@ -132410,7 +132180,6 @@ "Militärarchiv", "Militäraristokratie", "Militärarzt", - "Militärattaché", "Militärausgabe", "Militärausgaben", "Militärausschuss", @@ -132790,7 +132559,6 @@ "Milorad", "Milos", "Milosevic", - "Milošević", "Milosevics", "Miltenberg", "Milton", @@ -133230,7 +132998,6 @@ "Mirjam", "Mirko", "Miro", - "Miró", "Miroslav", "Mirow", "Mirror", @@ -135011,7 +134778,6 @@ "Mohrenwäsche", "Mohrmann", "Mohrrübe", - "Moiré", "Mojo", "mokant", "Mokassin", @@ -135057,8 +134823,6 @@ "Molenspitze", "Molesten", "molestieren", - "Molière", - "Molières", "Molin", "Molina", "Molke", @@ -135493,7 +135257,6 @@ "Montanwirtschaft", "Montanwissenschaft", "montanwissenschaftlich", - "Montbéliard", "Montblanc", "Montbretie", "Monte", @@ -135527,7 +135290,6 @@ "Montparnasse", "Montpellier", "Montreal", - "Montréal", "Montreux", "Montserrat", "Montur", @@ -136715,7 +136477,6 @@ "Munkelei", "munkeln", "munkelt", - "Muñoz", "Munro", "Münsingen", "Munster", @@ -136814,7 +136575,6 @@ "Murrkopf", "murrköpfig", "Murrmann", - "Muršili", "Murten", "Mus", "Musa", @@ -136836,7 +136596,6 @@ "museale", "musealen", "Musealisierung", - "Musée", "Museen", "Muselman", "muselmanisch", @@ -136938,7 +136697,6 @@ "music", "Music", "Musica", - "Música", "Musical", "Musicalaufführung", "Musicalbühne", @@ -137004,7 +136762,6 @@ "Musikbranche", "Musikbühne", "Musikbusiness", - "Musikcafé", "Musikchef", "Musikclip", "Musikclown", @@ -138442,7 +138199,6 @@ "Nachtbulletin", "Nachtbus", "Nachtbuslinie", - "Nachtcafé", "Nachtclub", "Nachtclubs", "Nachtcreme", @@ -139322,14 +139078,12 @@ "Naphthalin", "Napier", "Napoleon", - "Napoléon", "napoleonisch", "napoleonische", "Napoleonische", "napoleonischen", "Napoleonischen", "Napoleons", - "Napoléons", "Napolitain", "Napolitano", "Nappa", @@ -140235,7 +139989,6 @@ "Nebenbuhlerin", "Nebenbühne", "Nebenchor", - "Nebencoupé", "Nebendarsteller", "Nebendarstellerin", "Nebending", @@ -140473,7 +140226,6 @@ "negieren", "negiert", "Negierung", - "Négligé", "Negligee", "Neheim", "Nehm", @@ -140799,7 +140551,6 @@ "nestjung", "Nestküken", "Nestle", - "Nestlé", "Nestler", "Nestling", "Nestlinge", @@ -141071,7 +140822,6 @@ "Neuburger", "Neubürger", "Neubürgerin", - "Neuchâtel", "Neudeck", "Neudecker", "Neudefinition", @@ -141676,7 +141426,6 @@ "Nicaragua", "Nicaraguaner", "nicaraguanisch", - "Niccolò", "Nice", "nich", "Nicholas", @@ -141859,7 +141608,6 @@ "Nicolae", "Nicolai", "Nicolas", - "Nicolás", "Nicolaus", "Nicole", "Nidda", @@ -142347,7 +142095,6 @@ "Niro", "Nirvana", "Nirwana", - "Niš", "Nische", "Nischel", "Nischen", @@ -142466,7 +142213,6 @@ "Nockerl", "Nocturne", "Noel", - "Noël", "Nofretete", "noir", "Noir", @@ -143294,12 +143040,10 @@ "Nouvel", "Nouvelle", "Nova", - "Nová", "Novak", "Novalis", "Novara", "Novartis", - "Nové", "Novel", "Novell", "Novelle", @@ -143335,7 +143079,6 @@ "Novizin", "Novo", "Novum", - "Nový", "now", "Now", "Nowa", @@ -143482,7 +143225,6 @@ "Nummernvergabe", "nun", "Nun", - "Núñez", "nunmal", "nunmehr", "Nunmehr", @@ -144457,7 +144199,6 @@ "Oettingen", "Oettinger", "Oeuvre", - "Œuvre", "Oeynhausen", "of", "Of", @@ -145353,7 +145094,6 @@ "Opening", "Oper", "Opera", - "Opéra", "operabel", "Operateur", "Operating", @@ -145938,7 +145678,6 @@ "Ordo", "Ordonanz", "Ordre", - "Oréal", "Oregano", "Oregon", "Orest", @@ -146249,7 +145988,6 @@ "Orlando", "Orlean", "Orleans", - "Orléans", "Ornament", "ornamental", "ornamentale", @@ -146533,11 +146271,9 @@ "Ortungssystem", "Ortwin", "Orwell", - "Orzeł", "Os", "Osage", "Osaka", - "Ōsaka", "Osama", "Osborne", "Osbourne", @@ -147228,7 +146964,6 @@ "Painting", "Pair", "Pais", - "País", "Paisley", "Pak", "Paket", @@ -147644,7 +147379,6 @@ "Papierlaterne", "papierleicht", "papierlos", - "Papiermaché", "Papiermacher", "Papiermanschette", "Papiermarkt", @@ -147882,7 +147616,6 @@ "paramilitärischen", "Paramilitärs", "Paramount", - "Paraná", "Paranoia", "paranoid", "Paranoiker", @@ -147982,7 +147715,6 @@ "Parkbesucher", "Parkbucht", "Parkbühne", - "Parkcafé", "Parkchef", "Parkdauer", "Parkdeck", @@ -148656,7 +148388,6 @@ "Passbildaufnahme", "passe", "Passe", - "passé", "Pässe", "passee", "Passempfänger", @@ -148868,7 +148599,6 @@ "Paternosteraufzug", "Paterson", "Path", - "Pathé", "pathetisch", "pathetischen", "pathogen", @@ -149163,7 +148893,6 @@ "Pechvogel", "Peck", "Peckinpah", - "Pécs", "Pedal", "Pedale", "Pedalritter", @@ -149231,7 +148960,6 @@ "pelagisch", "Pelargonie", "Pele", - "Pelé", "Pelerine", "Pelham", "Pelikan", @@ -149441,13 +149169,11 @@ "Peptide", "Pequot", "per", - "Percé", "Percival", "Percussion", "Percussionist", "Percy", "perdu", - "Père", "Pereira", "Peremtion", "peremtorisch", @@ -149455,7 +149181,6 @@ "Peres", "Perestroika", "Perez", - "Pérez", "Perfect", "perfekt", "Perfekt", @@ -149612,7 +149337,6 @@ "permutabel", "Permutation", "permutieren", - "Perón", "Peroxyd", "Perpendikel", "perpetuieren", @@ -150010,7 +149734,6 @@ "Pestsäule", "Pet", "Petacchi", - "Pétain", "Petar", "Pete", "Peter", @@ -151332,7 +151055,6 @@ "Pie", "Piece", "Piech", - "Piëch", "Pieck", "Piedestal", "Piefke", @@ -151373,7 +151095,6 @@ "piesacken", "Piet", "Pieta", - "Pietà", "Pietät", "pietätlos", "Pietätlosigkeit", @@ -151647,7 +151368,6 @@ "Pippi", "Pippin", "Pips", - "Piqué", "Piquet", "Piranha", "Pirat", @@ -152625,7 +152345,6 @@ "Pohl", "Pohle", "Pohlmann", - "Poincaré", "Point", "Pointe", "Pointen", @@ -152678,7 +152397,6 @@ "pökeln", "Pökelsalz", "Pökelzunge", - "Pokémon", "Poker", "Pokerface", "pokern", @@ -154649,8 +154367,6 @@ "Premio", "Premium", "Premiumbereich", - "Přemysl", - "Přemysliden", "Prenzlau", "Prenzlauer", "Presbyter", @@ -154681,7 +154397,6 @@ "Pressearbeit", "Pressearchiv", "Presseartikel", - "Presseattaché", "Presseattacke", "Presseauftritt", "Presseaussendung", @@ -156333,7 +156048,6 @@ "Protagonisten", "Protagonistin", "Protection", - "Protegé", "protegieren", "protegiert", "Protein", @@ -157807,7 +157521,6 @@ "Quattrocento", "que", "Quebec", - "Québec", "Quebecer", "Quechua", "Quecke", @@ -158750,7 +158463,6 @@ "Raketenunterseeboot", "Raketenwaffe", "Raketenwerfer", - "Rákóczi", "Raleigh", "Ralf", "Ralle", @@ -158789,7 +158501,6 @@ "rammt", "rammte", "Ramon", - "Ramón", "Ramona", "Ramones", "Ramos", @@ -159633,7 +159344,6 @@ "Raucherberatung", "Raucherbereich", "Raucherclub", - "Rauchercoupé", "Raucherei", "Räucherei", "Raucherentwöhnung", @@ -159774,7 +159484,6 @@ "rauhen", "Rauhheit", "Raul", - "Raúl", "Raum", "Raumakustik", "raumakustisch", @@ -161694,8 +161403,6 @@ "Regietheater", "Regietisch", "Regime", - "régime", - "Régime", "Regimegegner", "Regimekritiker", "regimekritisch", @@ -161722,7 +161429,6 @@ "Region", "regional", "Regional", - "Régional", "Regionalagentur", "Regionalausgabe", "Regionalausscheidung", @@ -163352,8 +163058,6 @@ "Renditeziel", "Rendsburg", "Rene", - "René", - "Renée", "Renegat", "Renegatentum", "Reneklode", @@ -163758,7 +163462,6 @@ "reptilisch", "Repubblica", "Republic", - "República", "Republican", "Republik", "Republika", @@ -163870,7 +163573,6 @@ "resigniert", "resignierte", "Resistance", - "Résistance", "resistent", "Resistenz", "Resistenzen", @@ -164229,7 +163931,6 @@ "Reumut", "reumütig", "Reunion", - "Réunion", "Reuse", "Reusenfischerei", "Reuss", @@ -164628,7 +164329,6 @@ "Rhombus", "Rhön", "Rhone", - "Rhône", "Rhönrad", "Rhys", "Rhythm", @@ -165745,7 +165445,6 @@ "Rodrigo", "Rodrigues", "Rodriguez", - "Rodríguez", "Rodung", "Rodungen", "Rodungsarbeit", @@ -166330,8 +166029,6 @@ "Rösch", "Röschen", "Rose", - "rosé", - "roséfarben", "Rosegger", "Rosemarie", "Rosemary", @@ -166725,7 +166422,6 @@ "rotznasig", "rotznäsig", "Roubaix", - "Roué", "Rouen", "Rouge", "Rough", @@ -169133,7 +168829,6 @@ "Sanatorium", "Sanatoriumsbehandlung", "Sanchez", - "Sánchez", "Sancho", "sancti", "Sancti", @@ -169497,8 +169192,6 @@ "Santo", "Santos", "Sao", - "São", - "Saône", "Saphir", "saphirblau", "saphiren", @@ -173493,7 +173186,6 @@ "Schlusskapitel", "Schlusskette", "Schlussklassement", - "Schlusskommuniqué", "Schlusskonsonant", "Schlusskonzert", "Schlusskundgebung", @@ -175940,7 +175632,6 @@ "Schülerbrigade", "Schülerbücherei", "Schülerbühne", - "Schülercafé", "Schülerchor", "Schülercup", "Schülerdelegation", @@ -178243,7 +177934,6 @@ "Sealmantel", "Sealskin", "Sean", - "Séance", "Search", "Searle", "Sears", @@ -178254,11 +177944,9 @@ "Seattle", "Sebald", "Sebastian", - "Sebastián", "Sebastiano", "Sebastianskapelle", "Sebastien", - "Sébastien", "Secession", "sechs", "Sechs", @@ -178925,7 +178613,6 @@ "Seidenborte", "Seidenbrokat", "Seidenbuch", - "Seidencrêpe", "Seidendamast", "Seidendessous", "seidenen", @@ -180089,7 +179776,6 @@ "Seniorenbetreuung", "Seniorenbund", "Seniorenbüro", - "Seniorencafé", "Seniorenchor", "Senioreneinrichtung", "Seniorenfahrt", @@ -180613,7 +180299,6 @@ "Severn", "Severus", "Sevilla", - "Sèvres", "Sewastopol", "Sex", "Sexaffäre", @@ -181789,7 +181474,6 @@ "Silberklang", "Silberknauf", "Silberkrone", - "Silberlamé", "Silberlegierung", "Silberleuchter", "Silberlicht", @@ -182900,7 +182584,6 @@ "Socialista", "Sociedad", "Societe", - "Société", "Society", "Söckchen", "Socke", @@ -183210,7 +182893,6 @@ "Solidaritätsveranstaltung", "Solidaritätszuschlag", "Solidarnosc", - "Solidarność", "Solidarpakt", "Solidarprinzip", "Solidarsystem", @@ -183621,7 +183303,6 @@ "Sonderbotschafterin", "Sonderbriefmarke", "Sonderbus", - "Sondercoupé", "Sonderdezernat", "Sonderdienst", "Sonderdividende", @@ -186649,7 +186330,6 @@ "Spitzenmodell", "Spitzenmusiker", "Spitzenmuster", - "Spitzennégligé", "Spitzenniveau", "Spitzennummer", "Spitzenorchester", @@ -186928,7 +186608,6 @@ "Sportchef", "Sportclub", "Sportclubs", - "Sportcoupé", "Sportcrack", "Sportdezernent", "Sportdezernentin", @@ -188664,7 +188343,6 @@ "Stadtbuslinie", "Stadtbuslinien", "Stadtbusse", - "Stadtcafé", "Stadtcharakter", "Stadtchef", "Stadtchefin", @@ -189431,7 +189109,6 @@ "Stammbuch", "Stammbuchvers", "Stammburg", - "Stammcafé", "Stämmchen", "Stammdaten", "Stammdatum", @@ -189821,7 +189498,6 @@ "Stanislaus", "Stanislav", "Stanislaw", - "Stanisław", "Stanislawski", "Stanitzl", "Stank", @@ -189873,7 +189549,6 @@ "Stardirigent", "Stardust", "Stare", - "Staré", "Starenkasten", "Starensemble", "Starfighter", @@ -190444,7 +190119,6 @@ "Stehbankett", "Stehbierhalle", "Stehbündchen", - "Stehcafé", "stehe", "Stehempfang", "stehen", @@ -191017,7 +190691,6 @@ "Step", "Stephan", "Stephane", - "Stéphane", "Stephanie", "Stephanos", "Stephans", @@ -192972,7 +192645,6 @@ "Strandbad", "Strandbar", "Strandburg", - "Strandcafé", "Stranddistel", "Strände", "stranden", @@ -193110,8 +192782,6 @@ "Straßenböschung", "Straßenbreite", "Straßenbrücke", - "Straßencafé", - "Straßencafés", "Straßendamm", "Straßendecke", "Straßendienst", @@ -195669,7 +195339,6 @@ "Superhelden", "Superhirn", "Superhit", - "Supérieure", "Superintendent", "Superintendenten", "Superintendentin", @@ -196382,7 +196051,6 @@ "Tablettensucht", "tablettensüchtig", "Tabor", - "Tábor", "Tabori", "tabu", "Tabu", @@ -196555,7 +196223,6 @@ "Tagesbesucher", "Tagesbetreuung", "Tagesbetrieb", - "Tagescafé", "Tagescreme", "Tagesdecke", "Tagesdiäten", @@ -197117,7 +196784,6 @@ "Tanzbewegung", "Tanzboden", "Tanzbühne", - "Tanzcafé", "Tänzchen", "Tanzclub", "Tanzdarbietung", @@ -198503,7 +198169,6 @@ "Telebörse", "Telebus", "Telecom", - "Télécom", "Telecommunications", "Telefax", "Telefon", @@ -198541,7 +198206,6 @@ "Telefonhäuschen", "Telefonhörer", "Telefonica", - "Telefónica", "Telefonie", "telefonieren", "Telefonieren", @@ -198971,7 +198635,6 @@ "Tenniszirkus", "Tenno", "Tennyson", - "Tenochtitlán", "Tenor", "tenoral", "Tenorbuffo", @@ -199633,7 +199296,6 @@ "Theaterbuffet", "Theaterbühne", "Theaterbühnen", - "Theatercafé", "Theaterchef", "Theaterchefin", "Theaterchor", @@ -199809,7 +199471,6 @@ "theatralische", "theatralischen", "Theatre", - "Théâtre", "Theben", "Theismus", "Theiss", @@ -199887,7 +199548,6 @@ "Theodor", "Theodora", "Theodore", - "Théodore", "Theodosius", "Theokratie", "theokratisch", @@ -199980,7 +199640,6 @@ "There", "Theresa", "Therese", - "Thérèse", "Theresia", "Theresias", "Theresienstadt", @@ -200627,7 +200286,6 @@ "Timer", "Times", "Timing", - "Timişoara", "Timm", "Timmendorfer", "Timmermann", @@ -201225,7 +200883,6 @@ "Tom", "Tomahawk", "Tomas", - "Tomás", "Tomasz", "Tomate", "Tomaten", @@ -201244,7 +200901,6 @@ "Tombolagewinn", "Tombolapreis", "Tombolaspende", - "Tomé", "Tomislav", "Tomlinson", "Tommaso", @@ -201711,7 +201367,6 @@ "Torschützenliste", "Torschützin", "Torschwelle", - "Tórshavn", "Torsicherung", "Torso", "Torstange", @@ -203975,7 +203630,6 @@ "Trompetenstoß", "Trompetentierchen", "Trompeter", - "Trøndelag", "Trondheim", "Tropen", "Tropenausrüstung", @@ -204393,7 +204047,6 @@ "tückschen", "tucktuck", "Tucson", - "Tucumán", "tüdern", "Tudor", "tue", @@ -206765,7 +206418,6 @@ "Umdruck", "umdüstern", "Umdüsterung", - "Umeå", "umeinander", "umerziehen", "Umerziehung", @@ -209388,7 +209040,6 @@ "uninteressiert", "Uninteressiertheit", "Union", - "Unión", "Unioner", "Unionist", "Unionisten", @@ -209548,7 +209199,6 @@ "Universitätsviertel", "Universitätswesen", "Universitätszeit", - "Université", "University", "Universum", "Universums", @@ -212102,7 +211752,6 @@ "Vabanque", "Vabanquespiel", "Vaclav", - "Václav", "Vademekum", "Vader", "Vaduz", @@ -212170,7 +211819,6 @@ "Validität", "Valladolid", "Valle", - "Vallée", "valleri", "Valletta", "Valley", @@ -212257,12 +211905,7 @@ "Variationsreichtum", "Varietät", "Varietäten", - "Varieté", - "Varietébühne", "Varietee", - "Varieténummer", - "Varietéprogramm", - "Varietéstern", "Variety", "variierbar", "variieren", @@ -212293,7 +211936,6 @@ "Vashem", "Vasomotoren", "vasomotorisch", - "Västerås", "Vater", "Väter", "Vaterbruder", @@ -212428,11 +212070,9 @@ "velar", "Velar", "Velasco", - "Velázquez", "Velbert", "Velde", "Velen", - "Velká", "Velo", "Velobörse", "Velodrom", @@ -212463,7 +212103,6 @@ "Velveton", "Vena", "Vendetta", - "Vendôme", "Vene", "Venedig", "Venedigs", @@ -222259,7 +221898,6 @@ "Vorstadtmilieu", "Vorstadtsiedlung", "Vorstadtstraße", - "Vorstadtvarieté", "vorstammeln", "vorstand", "Vorstand", @@ -222786,7 +222424,6 @@ "vwd", "VwVfG", "Vylan", - "Vyšehrad", "vzbv", "wa", "Waadt", @@ -227990,7 +227627,6 @@ "Weltstadt", "weltstädtisch", "Weltstadtpublikum", - "Weltstadtvarieté", "Weltstand", "Weltstandard", "Weltstar", @@ -231461,7 +231097,6 @@ "Wjatscheslaw", "Wladimir", "Wladiwostok", - "Władysław", "wo", "Wo", "woanders", @@ -232506,7 +232141,6 @@ "Wringmaschine", "Writer", "Writers", - "Wrocław", "Wu", "Wucher", "Wucherei", @@ -233135,7 +232769,6 @@ "Ypsilon", "Yu", "Yuan", - "Yucatán", "Yucca", "Yukon", "Yukos", @@ -233675,12 +233308,9 @@ "zausen", "zB", "Zbigniew", - "Zdeněk", "ZdK", - "Zdrój", "ze", "Ze", - "Zé", "Zealand", "Zebra", "Zebrabarbe", @@ -236138,7 +235768,6 @@ "Zollwachebeamter", "Zollwesen", "Zoltan", - "Zoltán", "Zombie", "Zombies", "zonal", From 231cd2ecb882f40de5e97629d798befbed56db51 Mon Sep 17 00:00:00 2001 From: nafets-st <559940+nafets-st@users.noreply.github.com> Date: Mon, 21 Jul 2025 09:30:21 -0400 Subject: [PATCH 5/9] fix(quotes): code_rust compilation and whitespace (@nafets-st) (#6755) ### Description * Fixes various compile errors in the Rust quotes. * Removes trailing whitespace. * Converts leading whitespace to tabs. Note: This does not fix out of date issues (such as the rand API), or the likely unnecessary uses of `extern crate` (since rust 2018). --- frontend/static/quotes/code_rust.json | 82 +++++++++++++-------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/frontend/static/quotes/code_rust.json b/frontend/static/quotes/code_rust.json index 28df25a4354f..a30a550b0b71 100644 --- a/frontend/static/quotes/code_rust.json +++ b/frontend/static/quotes/code_rust.json @@ -147,9 +147,9 @@ }, { "id": 24, - "length": 56, + "length": 54, "source": "Create a Tree data structure - programming-idioms.org", - "text": "struct Node {\n value: T,\n children: Vec>,\n}" + "text": "struct Node {\n\tvalue: T,\n\tchildren: Vec>,\n}" }, { "id": 25, @@ -201,9 +201,9 @@ }, { "id": 33, - "length": 66, + "length": 64, "source": "Convert string to integer - programming-idioms.org", - "text": "let i = match s.parse::() {\n Ok(i) => i,\n Err(_e) => -1,\n};" + "text": "let i = match s.parse::() {\n\tOk(i) => i,\n\tErr(_e) => -1,\n};" }, { "id": 34, @@ -213,9 +213,9 @@ }, { "id": 36, - "length": 211, + "length": 209, "source": "Send a value to another thread - programming-idioms.org", - "text": "use std::thread;\nuse std::sync::mpsc::channel;\nlet (send, recv) = channel();\nthread::spawn(move || {\n\tloop {\n\t\tlet msg = recv.recv().unwrap();\n\t\tprintln!(\"Hello, {:?}\", msg);\n\t} \n});\nsend.send(\"Alan\").unwrap();" + "text": "use std::thread;\nuse std::sync::mpsc::channel;\nlet (send, recv) = channel();\nthread::spawn(move || {\n\tloop {\n\t\tlet msg = recv.recv().unwrap();\n\t\tprintln!(\"Hello, {:?}\", msg);\n\t}\n});\nsend.send(\"Alan\").unwrap();" }, { "id": 37, @@ -279,9 +279,9 @@ }, { "id": 47, - "length": 145, + "length": 143, "source": "Integer exponentiation by squaring - programming-idioms.org", - "text": "fn exp(x: u64, n: u64) -> u64 {\n\tmatch n {\n\t\t0 => 1,\n\t\t1 => x,\n\t\ti if i % 2 == 0 => exp(x * x, n / 2),\n\t\t_ => x * exp(x * x, (n - 1) / 2),\n\t}\t \n}" + "text": "fn exp(x: u64, n: u64) -> u64 {\n\tmatch n {\n\t\t0 => 1,\n\t\t1 => x,\n\t\ti if i % 2 == 0 => exp(x * x, n / 2),\n\t\t_ => x * exp(x * x, (n - 1) / 2),\n\t}\n}" }, { "id": 48, @@ -297,9 +297,9 @@ }, { "id": 50, - "length": 145, + "length": 149, "source": "First-class function : compose - programming-idioms.org", - "text": "fn compose<'a, A, B, C, G, F>(f: F, g: G) -> Box C + 'a>\n\t\twhere F: 'a + Fn(A) -> B, G: 'a + Fn(B) -> C\n{\n\t\tBox::new(move |x| g(f(x)))\n}" + "text": "fn compose<'a, A, B, C, G, F>(f: F, g: G) -> Box C + 'a>\n\t\twhere F: 'a + Fn(A) -> B, G: 'a + Fn(B) -> C\n{\n\t\tBox::new(move |x| g(f(x)))\n}" }, { "id": 51, @@ -309,9 +309,9 @@ }, { "id": 52, - "length": 145, + "length": 149, "source": "First-class function : generic composition - programming-idioms.org", - "text": "fn compose<'a, A, B, C, G, F>(f: F, g: G) -> Box C + 'a>\n\t\twhere F: 'a + Fn(A) -> B, G: 'a + Fn(B) -> C\n{\n\t\tBox::new(move |x| g(f(x)))\n}" + "text": "fn compose<'a, A, B, C, G, F>(f: F, g: G) -> Box C + 'a>\n\t\twhere F: 'a + Fn(A) -> B, G: 'a + Fn(B) -> C\n{\n\t\tBox::new(move |x| g(f(x)))\n}" }, { "id": 53, @@ -357,15 +357,15 @@ }, { "id": 60, - "length": 103, + "length": 104, "source": "Continue outer loop - programming-idioms.org", - "text": "outer: for va in &a {\n\tfor vb in &b {\n\t\tif va == vb {\n\t\t\tcontinue 'outer;\n\t\t}\n\t}\n\tprintln!(\"{}\", va);\n}" + "text": "'outer: for va in &a {\n\tfor vb in &b {\n\t\tif va == vb {\n\t\t\tcontinue 'outer;\n\t\t}\n\t}\n\tprintln!(\"{}\", va);\n}" }, { "id": 61, - "length": 108, + "length": 109, "source": "Break outer loop - programming-idioms.org", - "text": "outer: for v in m {\n\t'inner: for i in v {\n\t\tif i < 0 {\n\t\t\tprintln!(\"Found {}\", i);\n\t\t\tbreak 'outer;\n\t\t}\n\t}\n}" + "text": "'outer: for v in m {\n\t'inner: for i in v {\n\t\tif i < 0 {\n\t\t\tprintln!(\"Found {}\", i);\n\t\t\tbreak 'outer;\n\t\t}\n\t}\n}" }, { "id": 62, @@ -537,9 +537,9 @@ }, { "id": 90, - "length": 267, + "length": 266, "source": "Binomial coefficient \"n choose k\" - programming-idioms.org", - "text": "extern crate num;\nuse num::bigint::BigInt;\nuse num::bigint::ToBigInt;\nuse num::traits::One;\nfn binom(n: u64, k: u64) -> BigInt {\n\tlet mut res = BigInt::one();\n\tfor i in 0..k {\n\t\tres = (res * (n - i).to_bigint().unwrap()) /\n\t\t\t (i + 1).to_bigint().unwrap();\n\t}\n\tres\n}" + "text": "extern crate num;\nuse num::bigint::BigInt;\nuse num::bigint::ToBigInt;\nuse num::traits::One;\nfn binom(n: u64, k: u64) -> BigInt {\n\tlet mut res = BigInt::one();\n\tfor i in 0..k {\n\t\tres = (res * (n - i).to_bigint().unwrap()) /\n\t\t\t\t(i + 1).to_bigint().unwrap();\n\t}\n\tres\n}" }, { "id": 91, @@ -963,9 +963,9 @@ }, { "id": 161, - "length": 87, + "length": 88, "source": "Measure duration of procedure execution - programming-idioms.org", - "text": "use std::time::Instant;\nlet start = Instant:now();\nf();\nlet duration = start.elapsed();" + "text": "use std::time::Instant;\nlet start = Instant::now();\nf();\nlet duration = start.elapsed();" }, { "id": 162, @@ -1167,9 +1167,9 @@ }, { "id": 195, - "length": 204, + "length": 205, "source": "Execute procedures depending on options - programming-idioms.org", - "text": "if let Some(arg) = ::std::env::args().nth(1) {\n\tif &arg == \"f\" {\n\t\tfox();\n\t} else if &arg = \"b\" {\n\t\tbat();\n\t} else {\n\t\teprintln!(\"invalid argument: {}\", arg),\n\t}\n} else {\n\teprintln!(\"missing argument\");\n}" + "text": "if let Some(arg) = ::std::env::args().nth(1) {\n\tif &arg == \"f\" {\n\t\tfox();\n\t} else if &arg == \"b\" {\n\t\tbat();\n\t} else {\n\t\teprintln!(\"invalid argument: {}\", arg);\n\t}\n} else {\n\teprintln!(\"missing argument\");\n}" }, { "id": 196, @@ -1257,9 +1257,9 @@ }, { "id": 210, - "length": 45, + "length": 46, "source": "Insert entry in map - programming-idioms.org", - "text": "use std::collection::HashMap;\nm.insert(k, v);" + "text": "use std::collections::HashMap;\nm.insert(k, v);" }, { "id": 211, @@ -1287,9 +1287,9 @@ }, { "id": 215, - "length": 80, + "length": 81, "source": "Hex string to byte array - programming-idioms.org", - "text": "use hex::FromHex\nlet a: Vec = Vec::from_hex(s).expect(\"Invalid Hex String\");" + "text": "use hex::FromHex;\nlet a: Vec = Vec::from_hex(s).expect(\"Invalid Hex String\");" }, { "id": 216, @@ -1311,9 +1311,9 @@ }, { "id": 219, - "length": 61, + "length": 62, "source": "List files in directory - programming-idioms.org", - "text": "let x = std::fs::read_dir(d)?.collect::, _>()?;" + "text": "let x = std::fs::read_dir(d)?.collect::, _>>()?;" }, { "id": 220, @@ -1341,9 +1341,9 @@ }, { "id": 224, - "length": 48, + "length": 47, "source": "Exit program cleanly - programming-idioms.org", - "text": "use std::process::exit;\nfn main() {\n exit(0);\n}" + "text": "use std::process::exit;\nfn main() {\n\texit(0);\n}" }, { "id": 225, @@ -1461,15 +1461,15 @@ }, { "id": 244, - "length": 67, + "length": 65, "source": "Pad string on the right - programming-idioms.org", - "text": "use std::iter();\ns += &iter::repeat(c).take(m).collect::();" + "text": "use std::iter;\ns += &iter::repeat(c).take(m).collect::();" }, { "id": 245, - "length": 451, + "length": 453, "source": "Pad string on the left - programming-idioms.org", - "text": "use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};\nif let Some(columns_short) = m.checked_sub(s.width()) {\n\tlet padding_width = c\n\t\t.width()\n\t\t.filter(|n| *n > 0)\n\t\t.expect(\"padding character should be visible\");\n\t// Saturate the columns_short\n\tlet padding_needed = columns_short + padding_width - 1 / padding_width;\n\tlet mut t = String::with_capacity(s.len() + padding_needed);\n\tt.extend((0..padding_needed).map(|_| c)\n\tt.push_str(&s);\n\ts = t;\n}" + "text": "use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};\nif let Some(columns_short) = m.checked_sub(s.width()) {\n\tlet padding_width = c\n\t\t.width()\n\t\t.filter(|n| *n > 0)\n\t\t.expect(\"padding character should be visible\");\n\t// Saturate the columns_short\n\tlet padding_needed = columns_short + padding_width - 1 / padding_width;\n\tlet mut t = String::with_capacity(s.len() + padding_needed);\n\tt.extend((0..padding_needed).map(|_| c));\n\tt.push_str(&s);\n\ts = t;\n}" }, { "id": 246, @@ -1491,9 +1491,9 @@ }, { "id": 249, - "length": 188, + "length": 190, "source": "List intersection - programming-idioms.org", - "text": "use std::collections::HashSet;\nlet unique_a = a.iter().collect::>();\nlet unique_b = b.iter().collect::>();\nlet c = unique_a.intersection(&unique_b).collect>();" + "text": "use std::collections::HashSet;\nlet unique_a = a.iter().collect::>();\nlet unique_b = b.iter().collect::>();\nlet c = unique_a.intersection(&unique_b).collect::>();" }, { "id": 250, @@ -1521,9 +1521,9 @@ }, { "id": 254, - "length": 118, + "length": 114, "source": "Find first index of an element in list - programming-idioms.org", - "text": "let opt_i = items.iter().position(|y| *y == x);\nlet i = match opt_i {\n Some(index) => index as i32,\n None => -1\n};" + "text": "let opt_i = items.iter().position(|y| *y == x);\nlet i = match opt_i {\n\tSome(index) => index as i32,\n\tNone => -1\n};" }, { "id": 255, @@ -1557,9 +1557,9 @@ }, { "id": 260, - "length": 112, + "length": 111, "source": "Declare and use an optional argument - programming-idioms.org", - "text": "fn f(x: Option<()>) {\n\tmatch x {\n\t\tSome(x) => println!(\"Present {}\", x),\n\t\tNone => println!(\"Not present\"),\n\t}\n}" + "text": "fn f(x: Option) {\n\tmatch x {\n\t\tSome(x) => println!(\"Present {}\", x),\n\t\tNone => println!(\"Not present\"),\n\t}\n}" }, { "id": 261, @@ -1619,7 +1619,7 @@ "id": 270, "length": 156, "source": "Sort 2 lists together - programming-idioms.org", - "text": "let mut tmp: Vec<_> = a.iter().zip(b).collect();\ntmp.as_mut_slice().sort_by_key(|(&x, _y)| x);\nlet (aa, bb): (Vec, Vec) = tmp.into_iter().unzip();" + "text": "let mut tmp: Vec<_> = a.iter().zip(b).collect();\ntmp.as_mut_slice().sort_by_key(|&(x, _y)| x);\nlet (aa, bb): (Vec, Vec) = tmp.into_iter().unzip();" }, { "id": 271, From 5b9f7be35f6a690dbd33e1b1d9599b91168848fc Mon Sep 17 00:00:00 2001 From: ~ayko Date: Mon, 21 Jul 2025 15:34:43 +0200 Subject: [PATCH 6/9] feat(font): add 0xProto font (@0x7375) (#6750) ### Description - added [0xProto](https://github.com/0xType/0xProto) font file to webfonts - updated `frontend/static/{fonts/_list.json,styles/fonts.scss}` files - tried it locally, here's a screenshot: Screenshot-18-07-2025-0414 ### Checks - [ ] Adding quotes? - [ ] Make sure to include translations for the quotes in the description (or another comment) so we can verify their content. - [ ] Adding a language? - Make sure to follow the [languages documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/LANGUAGES.md) - [ ] Add language to `packages/contracts/src/schemas/languages.ts` - [ ] Add language to exactly one group in `frontend/src/ts/constants/languages.ts` - [ ] Add language json file to `frontend/static/languages` - [ ] Adding a theme? - Make sure to follow the [themes documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/THEMES.md) - [ ] Add theme to `packages/contracts/src/schemas/themes.ts` - [ ] Add theme to `frontend/src/ts/constants/themes.ts` - [ ] Add theme css file to `frontend/static/themes` - Also please add a screenshot of the theme, it would be extra awesome if you do so! - [ ] Adding a layout? - [ ] Make sure to follow the [layouts documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/LAYOUTS.md) - [ ] Add layout to `packages/contracts/src/schemas/layouts.ts` - [ ] Add layout json file to `frontend/static/layouts` - [ ] Check if any open issues are related to this PR; if so, be sure to tag them below. - [x] Make sure the PR title follows the Conventional Commits standard. (https://www.conventionalcommits.org for more info) - [x] Make sure to include your GitHub username prefixed with @ inside parentheses at the end of the PR title. --- frontend/src/styles/fonts.scss | 3 +++ frontend/static/fonts/_list.json | 3 +++ frontend/static/webfonts/0xProto-Regular.woff2 | Bin 0 -> 56732 bytes 3 files changed, 6 insertions(+) create mode 100644 frontend/static/webfonts/0xProto-Regular.woff2 diff --git a/frontend/src/styles/fonts.scss b/frontend/src/styles/fonts.scss index f7cb0ee5fb85..a3c94095fa32 100644 --- a/frontend/src/styles/fonts.scss +++ b/frontend/src/styles/fonts.scss @@ -110,6 +110,9 @@ $fonts: ( "Iosevka": ( "src": "Iosevka-Regular", ), + "0xProto": ( + "src": "0xProto-Regular", + ), ); $font-defauls: ( diff --git a/frontend/static/fonts/_list.json b/frontend/static/fonts/_list.json index edf5d95b416e..5a028c74e1c0 100644 --- a/frontend/static/fonts/_list.json +++ b/frontend/static/fonts/_list.json @@ -117,5 +117,8 @@ }, { "name": "Iosevka" + }, + { + "name": "0xProto" } ] diff --git a/frontend/static/webfonts/0xProto-Regular.woff2 b/frontend/static/webfonts/0xProto-Regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..1247c5a5275efdb239d67056b42f4878318c9120 GIT binary patch literal 56732 zcmV)LK)JtnPew8T0RR910NtDb5dZ)H12p6S0NpwO0RR9100000000000000000000 z0000Qfm$1&HXO-n24Db>bO=TXoD2~N3X0YUiO4VunsfjGHUcCAnnDC11%pfnyju)` zGFxolk_i3Z@a|Eil5aMc-J~kSYE$>v!{M~u`Df&( zy{3l$vC_B4EuLT7%)ZZYR2fNB8HL!`DA6V= zB{pJYFON=RAM#>_B4VgZa2n2La)f`N&M_DvKcIac0dJc{2cyiYxfkCJI>Dn)Pf z7O1(@VNaP|N>xhZkc1Kc6bHfAv~|ibWtfvI(pf#dJ3+IOtkcONICCzJ;&IaJc^$>; z<)o8C`6!&^q~dW%JdZ=kNltR&5=BvR(oudDMd=a_iKjST4p~p(ka#)cgcA-4XGn8l z8(m|mH%b{lo@7+(5S-t4G$xLhnP0y>veDYpY*3{c?icft`w);!Px zfz0x&-1uJ4bdN+J0tpfmAn;ejPShT51|jOINn%-&?HrLG7i>V|>fWS3uY zw1H_ufw0>(De#m0Ccop_5ofF;#xmWGzh-1Ir%D@Jfma!P9*_rwQ33G&<9FT#ptwMZ zL*)7rhhz;vhA<`I?es4G|Cg%$?|ss}x2jM;p%5aIaH{zwFufytl%Aw48)?C8aBbD^`)4A5u<2NodJ~ zu28KChj18!@Zee<+p5Ia@#E}hU)8wz`8g}w?y`fU1!t%|0(rum&{^yh25c8-d|9=!|5F|YiE%g!A zNp>eHoYQivlSqo}L#dMjCxozAjzcGR*5uvf)q?6MduFrf1*a~7M2 zHjnPh4tgg`E28lgA-PiNWwkvH$jK|74ztqzJNz*VkI zRn3f7w=^tU#80yeUqjLJfF*y z{%4PD$?lL{(MdCg?BueenfvYt7(nmIWxZ}C2@B#OI1mIgnT3b1GsiP;%DJdog8Tqw z*L*`gF1kQ#d{C!Wf*#9EGcW_U1je#fx4EPw?XQn#{Z9P<|Gj2t?t8E0R8?#h5iw#! zk4Kq-gb*Q<1c~EjzwfKmVZSVYG0Dz0#-#UNb3_FJPgGP8 zQ1D3FzrOe5&-rgCYM-Iw`)+Fzz`>dc^JL*$klI9o3rJznq_Jso?0hcm+>l^9$Xpdc z&{@lP*S|~?6N(CgfRvWdOdtWh8HB(RpZ;uf39LYr15p942zMR`FVToti$N@f0VG)# zWO)`Kiy8uQ7!QIx!lNKh@Fb|9>QJiw)<@M~8zKk|fIR?jKpY^GKq)8y%1G%!Wx6&n zfh1T&nK7!soxgAmwUTNq06;;55p>W15F>3lww!ooDqwCD zDCl4vpAhZwkSu+ULMVN$M{=yt(_-5mEOuG!wyQ>+-7@O^PQ>n5v({SmVfh`0<_#A+0yBuGbu^GDQH1vKxE5LH&y zL{QZr=6NTI31D8l<;bJ9Dz>3R4WtgUnJ&#droCxxmlN5)f4U${SEr1X>6$6e^;+-s z+u)5l)yHcA=GOFVfr%6y0L8|^)CBr9ovp15<&`J3VO)wd0&@`M z091gha&YIte!0S3^GC6ZCXHe#!MprXpb6knVj5 zvdMH7@*CF@(iozh5)SFQBiQu7Xr&>1t)Glw9f3|a!s7M~E<`uUalTR#S$KRdiFjoPa2ikBcA^c|C#F&T%19~;>px&LoWqjcgI%R&_tR2`TwY}kIAh9;o zMdJE$e~OmT5P=mkD4H$Q&Zmhe4XG&;VE)|eP*jl#!cyK+WNW&O$-r5<+XM$8Nr z62iEm15)Y2xJB#s#$k9d1H>K9G)_*Tc>FTGu!!WSVgX|2M5QT0LR47pqOG)Lg`Ko2 zg(w#N5&B}Sw@H9Xj=Aq*i~$iA(YF@Bqg-!>>6v+Bp<=uS2+5w$7h8!x*>dHd_7f=0 zRoM0bJjZkhnR?hWY_GGQD6}$|D`2_)q(>!v2FEZzf4$v{yf1aq#zY+0G zZVG1W`01%z_DSCm4*ME#JA9O!~y1XN)#ruZ5uSokYzIx2G zbTYFip^e$IyzwaR!c!x4Dz<5>Im5-SN%YIr1oM(!rH?tG_XUGuXl4JC*bd?!L=^Gd z2W`<2-~_SiuJ<%!jKA zf5Tlof%W_e9^_*b=Mg-^k7I(@VGG{JXGSizUotVAT3OpIB_B2#)E_x9}<3Mv>bC5 z`N~4Es=B7OuD+p>6K5`5xpC*kgC{TEeE9NX;t!J}m)y(?oXXQ&!)H0u^KJ7o+niI; z8V&#;wgVv610Xg6AT|IXwgM0U1Ym?6*oEEL3n?)+;)^s@!75)Y*g;0Dq=SsA;3J-2 zZ~+Yd(^1ZX9;VD;32WHG9%WDt6;K(DoVjx6$%nr{!9sZqr_1{!Iqxt7{! zCs~?I**fZ?n>>Yj>ZOnVN({mf496&p#RN>oR7}S#%*6sM!V)aQN~{3^(8`elK+8k~ zKwGhlE+Ew_egoFNb(4lekBFo=EnQ644q}!)OfAf74QwJzC0mG4Wz|l{q`HEBgJn6LGFR=Xek99u z;Y(4NO0i17T;}6vA&UvHj1>f*V0DY7YNZk(ifc`&ghKfA9Wkc13dMP}@ZZQJi{A9b zCOgQ+K@Pb%&E?94qNvuC_!gwmlXPWBHriZ69Hr|Mbk}laTER+%!5GRZ!_DeP1<%C3 zbJ~>jl*<`A=L&Q56#OVBkxLsk^##3y)fLOrz(V~>E~%Y}m9jUg)3>R+ARWZZqkEBC zc*gsUHoL!?IjiuwXH@_KCLrho{H=@Y2izE9(E}Ae$}=1=?^?|_@tRw(4J^vpfhIt% z=Uvl3LU|=`s891Ie%-w&<1GUDGhq?w>7k!k2uEgQVLVk7Z|X!m@O=3x%-jS;1+5lG zMe~>MO}ZOF0nSxcgFT#@{WH|dL^4>kHvyI=-CI~`(N9k-^67Ca3|jp3%tD`@w#q<@ zpU$>QfyGaU+rdwVNLXP0-}0lyiCf8>Xw5?Fs@jxkGan{%qc`uITjWF0Ow76a584BW{UD?V%iuGR%G|B^I^oQXBQP zvL|Ynr6$U?@>|rJr7Fr55{cS5*%aksHV`1hvPp%6a_Y*`V(l{BpwALeE(FN|asY_* zgJOkP4JeC1)RSjGOY>qJ^4Ey+~TlVl%|Q;bPe2@C3|Kcen!QxziWo~TRO5^v}4 zxuwJ8e$ut@hg>%cxgl4R@W#hE|C_$x1-VJ&f%dr|t&pa~_lN-;wt$AR%c_Ole*rnN zC5d#02wAq4Qenw_HKpz-L;Q))kMyE6{wjq@H{Mg+OZ-K~BtFWmI-IW-WlPi- zr#YO9-QD`+PqV&+$0TW^t99nxu^1<}F`J2hhiD4y7sbbBz`?0_L6O|!na!_<(mEv% zD2hAtX>}P~w)?nL5{Wf(ubfIK(Z*40VEaQuB#)@K9c&Lf%s`&A&8pciRQn)ERbrc1 zrEE$#TYcCtf6L)b=)w0K0d+G_zKQhII1tee3VX(0vA($RAtxO*9ar4W%UO;-lbj~ z7EOSn9aOT9>?bG4LBsuA{USXB%<=#TjRRu$I+)W>>t&N|h@s!s2Kt*wo)|O?2_m_^5&e!_&mcMV)GwurwQJ9U zko`cE4hCMyq^qWTQv(oE352?p&*E@Yq;?5&6K(`Ms^;zE^2B;LWksGzq8?&)9tIqse^NW1#t+Qbp zx)B?`0qeg(8%T7=;XGC<*_j9!4Vgt)W!pHIqumR%n2yC*Nk#G2FA$_*rz{t$(nQVE zLN5@hFl^VwA#X-LKws$_%{Z%gPFU`Qn>-nB<$No$9Gl2AN?6S5Toz**ohc_wHQS`_ zL|8y~vT)1>g{>HV!uEg|$MYHBk>*p@5}OiUrlee4=1zVBdF!}}kz`4`07x=cwxn#( zJ=DG5Aepo#o-910t`CU+%wpPW0hC>3Z%{g34=D5BLQodH0Z^`$8vtcQ?f{haG7hL4 zpA1m+jT4l|Wiy~`kei{V5|1ZP%&^w5!4N&KUg)ic-wgGJ zIM7!9eLr6qt{U1v)eO5bqQ0HA?v;s9+jXkgJx^5l>6eMkccgb5SDHRn9 zM#-mwS)Q#Zph!gzsyg?x@U!f*8auC0*Q!6L6*rctAvLT<#NTGda;`PN4R;JLjA%3& zM_k`6Mi`C8RAc(J)izddEWQB-ez&IUkl{0j4jIX(JSMq; z7kCqxPZRgvNx5ttVJ8m#)WR)S&mlj~`LQ)VoV1+hv(~jQ0KQU5)}`LxiZ!5Yl^tx9 zgK}o~BYWQ^VaZ$k&L8~AU*aHTq^y*a@=`(E$7ACN---$02Qlk`&TjTn&AcYXx=vl| zH*x>2O=ln!5ArBaLh%%j@emXb^EgjKi7}t@2^7DHCBH)B0UqHA2GICP%=uBOGZH;t z@HyY`HH@TW8cx(SnZ|UI+GD9bwWj9yu;`_PPX7s+@QLg_pGi=!B)E6{0z0YEXQ?y3 z<2U}(s7KOZqG!NFOxVOu%+#O2sW$-#`;!=bulghSj|QPAv6h5Mob<`fHkr;L?enXKuWDRX79mdZ+5 zE7hjIl`^WVDyPb;3aX;2q$;Z_s;Y8ar*($=ruL>ThB-Q=lE6)rA~1!9SgBG3VaQg1 znb?ScQ4vup$<`2MYlO3PGt;^ydm2~jpUom#TY{YqdCv_D1j(rrFJt@G@a6FD^)e*jvh{Ct(j0A0)Qjie&|i1Fh!tfI?~8bpML;+PguDR!|@f(kUpUAO}Gn z=k+Bz(9o~u&%Du;H;%FsTWrOrtQ{1&gKwv*`|{;RP+;Yi@?6zFabpG$g@#52sZz1W(1jkfN`#(cfcqe=KOL{(7jCK|rA#Z+0p`^>6kCT44o z8mp^WxH1>JFPlH$vjwzr1TfC%$mYOsVa96dOJ|l+tl4G)ol`O^%v;TWbbiU$T4jEx zt4k)RDDOquJ2t$P^ARMGb&`>29B50^WBO9ZxTEOs1 z`3U6U>~|;obHLq5A*C}H6~?LJw&zE zBj{!HV@7Rcm&{bbHS{s#BESIXeL;Ue1-`&SO&0$f8Xi+t60vB=v&+$25^CN#sIdLEV2`;hvF4-@~<*eLPP`cMPF6+L2R~PXh zj6fI|B*@6Ydz2_c2UfmjEAd0bO!jeRTQJfA6o=@#fLMNBbWge6;=1 zRuIRkN7X~@5xOVs0lN+D007#bi+lhASPI}E<7--bU}~9VPy95UT#(ChRj$id`3}f; z`6>SulmLK&(kZ<%SC*=ra)jHYDyb^Ub=}u_>B3#8TL<_Au6B}oOy{= zOJ0#oH+jj||@stD)JPly&8X{s4dMTVf4z9yhlx*LxjSci?+g!7opq44W_5I^zTBGVQG#wY)w4FnvYXzuGlsb)GT$^7o59|uvzK`+ zv5T2Y> zjbZFy7iYu7b2Q0UpW55NiP*;J*vu)|!l~HGX{b>pjAM5NwX}$Xv5KQ%gZCK3OxHLx z{{)5AVs<*zno^fdE7zIyDqs)>Q4*v93T6T?$bz^Vke(ST;wRiw@JNGeM4cQp$X)$h z#pNQYSjknBRbAQDlT)1ZYUxx1oolE|jpWr_ek~N%O81(X$v$SWui5Np4*OfoAy~~Z zSi`Yc$x%4Q=9jA_-BT|ECgkV;H(gkIa(I1d4UEq%TVU%SF!=6 zDk;rCrt4pd;p}K6I~&C=MzgCC>|{GL5h7s_r|mkP8oN4K*JOicR;XuHgGCBtt7bvIOjUMx^`OJH7kAq1 z6LL=ue-sj+$DSa!Wbw<#Gu_P7=a4)yI59{iz4UYB$&FKnwBl#LG5@Z~^}~pBVh$8} z0mO2HE~d0KihEmo|jp)tSE*&OT- zm?5MTs>)KR^8+HIlNA^hWHiWg^3AGOj8O_~E*|$}H0a|w+<(l;NJ5{%45c81o4C+E zkL+wZ-O~LdYbeG$ZaJ;e5^Q zkr^)3XRM#++XM!+mvbx7SDj}iDplq|??o0QK|L*v5opRh2OT8Y5kJDO1@Ex-AM!b^y%&RUXYa2V5p=^belwF>SYN0g09q<$>V z)6w*(>4E7OT(c*FfZ!&}RQ)AskfJlGGoA*c);P^>8Tw`7GrLKFk%o&4^L6jVfkt87 z8Vy7`8<85NkVYlBHkvXW4lW{NF6k&#w1Kx)6YfeYMMGBAVf!PAHPtyiK+x?H;*^_v z>>`eWad;4|5mwi&XF^xT4mr$2&!i@H0VZgxG5INopiE}aost|XNZ}%}7&*GUn&ZC6iX}Oixr(2aKff-mFZ}gh?QoE;Q8>{atDW$cQ14 z2-`g(2KQJ_ul)mdcoLZw0VBpmU`&v81JX^gh>(H|?1vycWW9;KdTp4Sy} z%Cj&F2t|QGNid)+m{1Wcs0udJ1PAJZ3k|_*QBG99Oq%ZYNffli&kF@BQd(D(io);8 zsnSyT^OSM77ABO4cN2S8TL6M+h0}yH0Y||31xLaO;I!bh;dJ0k!Y@rbbjB((*CrOb z7|7lS`V^#2-krAB;aOd&H@d#QJSB4)m|!5MB!84iUBA{ff|Gf+nW{Vc(Aq8(&P`Se zbBFPz3K%TNM!SA`QR5xcTbJ}2Smdy~Ns?ki1)GBWe|TAN@J&9}&oL+FQ?jhlVZ!$@X1$ zgnU>SMojs#QMS|w_CIEXnnn2_Kvz~_kc~E4TEnz24rT}Jx|73q590;%N8C)a!K3nP zaI(97LJ`=*GB(TFG*;c@=-3VE$1o|)g%;(xtnEg!K{q)$DgJmCbnX?%IDa&{lO`;Eai?{A08NZrGAAiQhaB|4kPTzvPBhj{U59W}4Gm;QtpIG1&cBzoTJ-Dc zV}LWl0EXLen-iww>g9G1u|+4=;MN68fmu0X9*upfZv~)gDt}yp-=Lm{-_HMen zk@D;4cgwDm9#CL4{rCln2j)&AI&JGLW>sb2-XD8pZWiAs)5iWDljC}GwuJ|d8%^Qp z?CTWED%w7@spTy94*7M>2Lh(_Et%0h#y1#eUD|h`m5;RN9=Yn9GJtNI^Xp<_!5|K} zu*Y`t(KKyk)GCYQ_)b`F6w)|3RC(>Fl@qTBmY<&bxjrBDFu3cPTzPqNC}$rzcNK6q z60TtVqTX2hkjqO!{!q|jTtu#^=*n?(wNP94xpsXFfH;AXgg|Lt5wr-DmIl$RgTiKqh3hQo@7)6nSqjJKs?!jl2Z|M z2=ql@MM4{tqdl~b3RI$sD_W_m-e%+Z^P8jGRmC^CkNrXWb>hd1Eq!&RFN2fb z7Da}|7NcToGzLpoCcUdnOJBa1{_w}p*yd-k&97pc-^DiBc2Ez<;F2<=;V~7Qbw1&G{*OhnNj=2kLxOK)^zR9KZ{p+FQZ$}p12-c)TE0ieqvXDkvc!LnoqGa*R}rm9HvGpt^b*5y;$qxTM}QU z&$q-UQ1nff9fcxA3Q9?U6oMIVM=}MJWUjXO5SFP6#ah<7zT2E@c1omJ3nOrhp+cil zsYiXb;M&g}dA3>YvS;IrnLStzwO%6b8a>x*Z=r*RAQTcbSReyU5>N*j zIBbz6Yi70tGQ|wfL*EO4`BasIOrLLwPapz}oCuKGt?v(dIpC!A+d=PKasbP2Ka*be zDqUJ6A6cTjK-)_+E<}Q8Gp^)Hul};DePvfXk5%Jt)M$|rE^e;Kv5J5cA$T~&n;*;Z zF{O{&FJo63z&6DKfd$;O`u&|OY)%d6;~UM>|R z>(Hw}NY!eU9Qp`YU&sY7E)+<#Y^K5_72v~eMU-tz-&FY})8|{_6NmsKCjz8)t97n2 zbJF^4uFfUL(Ytx|oZWid$HDsL-Yl-}#-tHAi9@$@IoI=FSNq(~`Pv}k)u^M59I=s& zZ4^0H5s)H;kJ*N@X`3@SJENSnGd~mAU~@)pd@~GIvH70t>0W-1dwXQi%J+N^oQ=6& zwvW*LkfZjOZrk&IZ(!LPNAkf0#7j>agVbwCZwMl(shBp4nj|9If0N~~ekxlCAwVPb zAc}DxBd>*62yIch9`bD~(O!H3pjErTgoN(&w2XEmq;Ot34N+4gpD>RVSZhau&)#8G z9ZAWQ8fd9?8mKkWONx9?B0~`GWj&NrU4F7!H?@{nYnS3j2L3+uCwEOxMdt+`H{18X z`@N)LO6~h8BC;Z+)xy|$ZK@aIJ2+(-Sp@c@P~0SF*U5K&hDBUUxi(X0?KuUa4` zwzbJbMYlR>69^hpsiBQj{B0XmWGh~F98S4h&?rpab#zQF^mQoh{PM$wKQ)s+Bu{4`4Z`;^MYvMo8%_}Hf%ixt4 zz9g$t@gXY1s;MgoY8$nsmfcO#3=!-;yu@?J|4`o z7WLinP|D^5;jS=~6_65k-gsf{{B3i2*TR1|0*{ej2K&$=voA?bXXox-e)&%QmFr0i z<2XvAAiD5fx&~u~JsgNOsr2T1=?VpZIEQn(o!=#ZfUOG*9+Dh!iPiH@_9Y;)oxUCjt;Aq$Ftha_UjkCv-O=s{SM+ zVxeOZit*kEu%5Zc0+PP75JByuMP1?=w8pnUI8r(P9fle7Rt+5^GTWVcWzeVnwe~b^2MHTI(L5Zlo35G(S#;xCCEi@^pNv} z0w~80Qpkx~5ujWLLY|*d1l-JpKmY>ww9!FMWKXvNCG?_iV?hJfG9nff?odP z_Q>v-rBBJBnSXJA)gvFmp$_s6q}-o-gg)V*YahE%UtX22o0%l`e#m8Z%1 zWxwnzEvxYi;Wh}fcQ><-&@9hHp72JXoNx&lf5LDHo!WJx_*bpTjuXd~v-3Ku%7PH_ zLgL5$1bB5mCL*Ak4R_~uD_$kLYxJZdL>)^DftRHx8O8Ffhsno(tvId)CxL*h9}a01 zaML!);b959qgGAIr**vqP}(D(UWTdQ9SISo?wZWfIA$k7eIOxR7l_I2GF>37U%oCR z*Hl@_F;Q{%8Z>k7LGuPL%&57N&YOnNaw%)Io|dnL>Q)@hU~zsz_Nt`p`s?h_tg)W> zzQyNCy|#?IBjo|jhm9(f_egc%ToNr<5yye! zJA|uRj{Nc(%0Yf<5%Xvg)1FKpRk{h6KD$rSuXn^QC-N&D_7gDL*)mnkYd=jbOUdhJ zw1owNN4;r}QXO$Ad7Mj~AOV}62bjdGlS$gj2p?{e|KA1VTGw9Pu@{2 z$C(KFGnjIhhq zujG5?$YmrpjW4`k6O=qO)S9KpPf$ zCJ~Cm2g!5TtU?}rg6jQ6$rB|R$&%x1s;%TnopI_|2BE}ZlnnCt2JAYf31s~-CSe&L zp1^|;EWiq=iM4wQqf6KZt0m@>*GwmSM7-j1AEa#!_odLY-DBmfaO5N^plDmK+@J&+=-+9OTG;uxZOwKjl5MlY4#0dI^JW)=%U4 zu{?j&&W^s73wPH7MLuA!2~y4aMXXcRhTNaxR0c0S_Cij%V4cWHy!U^N3^Xh^RaY1q z*F>p*KcE&GUp%DR*B+wJY*&Y!c1TFiiGuuXt#OVnZU!AM% zRE@*5#&LhxPKSX-#OxN1X_@6C%&}d}aqEi_Ia6lr4C2tMA&95?a#ajjdmrz*=8|+E z`-0U_>Ke?ujY)>iO`veTHjt}ws2&-N%SjIE;%a}rgcu@)jPxriB3uN*o`H2Md?H$A zM)N7Tl%cDai^`J0hNVRQ1k3U=v0u`f^k^vEOaGNASJr8hfgP^|1C)69RhUTQdf)82 z;fFTUXKvbQuV`M^Kv%-E<}aS4q}4Y-<;B~^yNi}!WA75KXhmIW(k*5r7x5Wb9bL7* zaQdr+I?)GyQwC}>+Nao#A(P_>XI5#2n>vyNH!cQ7In#p9$%yy31G5$#Tl z#w>*~+NM>_FZfLQ(M+1(47?KZw|DmLflbxaFUllN&P9T~PGtx4$Zl4B;5$zBBjmSE zqqcGj*_6s_9IYT(-H^96)Gbh*_C^I)GMIV-MnVp;hapVw=Xt$JI777PVSW#1AK??x zRvyOm_(hyV<3h9*(m^AQB6~N#mzzu?YNFpiQ{pDGZ~5& z9k04uh8`#=^%JtEZwJg;$WeaZ%;7GQy0o=_Bh0(uY0b}= zJ(BsRfDjC||6nn!oz#iG&EP40KTJJ0)X0`AOl5?W&(-lFXDTo3GNz_i`>D-+@EukZ zM$g^v*zR#KPNp?!Lh%l&8|#=gng9)hBt<<&iGmw-k_LFwKj1CFxmR^xu7)+yv`5|U zr7ft_(^@Vxx*9P#$;8#u$2z7;Ws_6TQo7wq9M>*1YuGj`y4(KZ3f{MG{kFSXkBiC+ z+SSsD0GwR?By)q;RVxBBw7u zl3_ykP6naX5tAi4f-&@cKk(_#EdkXK#DD~NZj5eWEOu_Z1_WO@vvsItS16?}j zJO@&k^polspCxl_{T{amx=GyuEouN#_lz5^a(+@XK1M|D#Vh$51&slNxzK z`Zk)05iCEoevJ$l?<)fn?$~QgJZ7w@6V+d_3;KZpLJwt69c~;c*=48hfz8n}5{D(T z1)7A3I~xkqmm#9eEGqNcFkcC`EBAm(bSe(qVmm^K#M%R4yR)ui+Nfh7pZI%O9u*)yq9M^a}8D0@T>zDP3dfG4H1$OaXS4 zQ9w0ie)=dFn(;#~BVmL5p$5m=h#$)2u3tfeLF(nFxa^n|SSjGQx72vQFzt~6H2irt zbIX&pI_F_cPs+`l2`X24YcCD2(y#Q=Lq`6CQF?FO;nk=aYo%!op;qGIl*7hXv64TX zm^u_ufUzl~XK5;@Lh95VBjNUFHLo%&KUd?h9P(xpXpPPUF90m;{Q-7KNR8FM>4$ju5ROv9N z91y~?gH5p|NGzU+WM%0_`dKN0T9XLWy!u}reQ+yuojQs8G~;ioD6;U6Zv+YRGl=cI zNNgAXOCD}VP`Jkh&Akezw%FZQ!#heiOMnRk@x%SI+4C8xY<^t2H@#+jb zcVz;YNyKGL^Uq#F#@+oVXz-7Y4sUEIF63L~O0rF0rA76qHX?scu--q@KT`(aN$;Gr z9#MAI#;yl1?!|^cpakYmo!Z~GaO)O`cC-rXY+pZi%Uz4iK3@^WVn@ta0y(?mC|Pg! zK2}Iyd~jx_9~W5UBi=MbJ3~uPjm`=*p2vQ$_SC5WG`#{Sryf>6vB%?*oZH?Ea-#4G6`bPhC+uCm-QbkwI}mH#o% z;L#hG8;Qt^eF~>{ZSei@YA@DUlV3fH_~ zjBtc6ouoGgjjw_b-c<$q=pVtYApO(Nj8fCyPac0q$6P^~e+}Vo?lAn69K-gJ z2Xb3u^hWkI{P1qGIhG&727Y_0?-&{m*Gee9<^S@bcuV*SO)*NfP{WZ!_rS zxp+KKRdRxe$b?mL0c!kg;~oEF&yReHG<+D8pLsKANn|~5xaLdjcK{Y5)PBQjz80k0^z^j2%`e|R zPS*O3e{E@`JGuUCN_H&WsrOt15+C?!z2`M}CQ1v?36k*sU|8H}Kd|Bf->IZ~)WDo! zyN8yo&dkj|nz3)_c)+)5&JL$CXWZ+DUaX^ma8NUi4FwOQ$&OVuAuy*zZAnbR;QUm*Lcz2Jj z55PrfpytS1td3Im{LtCWEZHW!8wJ#81LMR42CG~R-QHP@F&3MJ&YqXai;qBGSqhL| z1kqg##+7LBkJ{D(3B1&SHbuYE_?jxvxXUFLIJNa-409RqbEFvIPK5^IV=)aFjeM6} z7VbRlpaW!E@T&8`&2ulUPxh!d*#ohlfsG1Be~~UWHQey|_hEnZ&^Ly=ay%5vbs0tb zY#zZ8bF?8Ey`y^d)x^8GyUIW0ShOlF^~MOyEp9j{+LO$?zPG52v-DEi?f&V+E4PK5 z#%pL9NA^JmTG~g?eb7qhudcJk{i3@5wHdbEJT~z2v0ujk z7YZSGR-ZyG38?8&7Ay>+*@fXXdM6h~-RY>GC2vvmvla8_&0+iz4m^BfTld!%!B{|X zAuT7awwZAQ^;1NU9n?B}KoLS(kuSOv3O`xh2sjM{h8%fu+yY|+5wcQ}YNAvu7NGIE z8)r0!M5D+*qW6JQ2Ro64pwZcaHl89cj=SZ;ZbYyP*#Irb->zu@@-JSe;BL$KMh98; zLiGuRFn9}iG-g$?bK^$EVl09q|KTI7wjU1}Moggkh%I)E>QD#DB2whQI^CxK2X3~f zIyx_=#E9of;C3*5Dj3559sg&WJ>4n0)wF-AC$1Z8+p_h^>pxyjGaO+sIGq4Ldfb(uyYk8~bO)so%q$kl#lnGiu<*PBH0OK-3E)Tq2-ohE zNopCg#JDtket*b?@FP2tvmG`7addqe)^#q*n#B2r0~8z8sjjI5t@IqHHUHQ zQ!hTfhp+w8e5*2tGv@>F^%~0h9g~%rxfF$3nu%&dq1v{lSO82wv%mM5X9BBOvpaM04M^fTrwQOk z{(c#P{&(fReR1ic6wpd*#l=q~AnTGBEzSSWCi_X`B?A9BJY~N*0YpLx(uibK*Zbsz z)FeS%>M3$B`Dq;78e87J&2Fuk2y6ML&jZ^xtl zAhpz6b?yzB*1uS@724k0zfsxYkKpU*&1u(zU+IO~F@P`ZO&lXM$8=WQ;Odsp>1K0_ zYv?r;defOuKZH51A>)w1-dQBJKjoWJCWec*DQh=CoR3_bLBuGy$ z76TTPltr|pQ@%tBGrLbqt79@$NfR@uJjI9OM#C&KYlamk!epxT;#k1lLm%Q#fu$QY z>9OwFn13*qbTj`f^iZ<3Mbq(QlN;c$El}td*eBQEYx!wjiq(jT8R)*>_Jfnl(-aXZ zfjA#E_sPvDM;W!A*n%ocD{u2$X?& zezxuUkF6~pdk$8t`&6g3iHq4~L55=e2i-C`oy(Tz%evtqrapN$gT7l2WrnIzuXp4o zmVwo71rKbya_so}vA)uF6TxUC?=f-tF8}%^i|Pidm`=ZKai@-+QZ25d!W1yM z0f9%#C)aVk*U_H>ZHQ3MCv#HfU)YB9uC(UJ68Fx%6%}RT@o`i07wzys5x>XA*c#%7 zkV8-$Xw7BQZdf6%)4K*-V+5a$StZv$EpEa&^^9)s9L@e!r2(JQFSar0HfXT0E!YNc4RG!U;dJw{2Wf@rIsh^Si`em2rE1667I^Zfx?KM%H2~n~4ax|i$x_}cpIvnj> z3ZG&on7Z%mj0>xoj5vI35*|IMzLH1N?oG-+{hTeMw`Wc%qcwtd`x~NY)JdMq#b1bP zswElv(ku53G1nOEsy$^wygL)5GoXi^ z09mDYM)WAIkrT3%7u9+Dki9b1kUBy8*TS#v-EX|f+3tmQ`JUOA`cC&5>g4=ciGyJM z2Ql@bdFC{A;tv+IGOE@Rw%DxUGSBsoUA_k_7mIHZS^+R#h@hD+#`uiCjQA-CxrA5z10qUgg(>HQ$6pz1bv7S39y+$wFv~!M zaZJENdNE>+VKA9V2B4JoyZ)^ZENRMmoTvHROkfr3IEL;2xIF&>q(u88Rf;Piid}g| z4k6**5c9s7V(0(myY6FySTjK&Hmx0`es6aFKrbMK;z2S`5Gvy4JY;1VSY^TU%qE*l z1mcq6fYI2?3EAOzcmq)ImFO4YaR3U}`?w6T4ezmg@)7fg@jFmy$J`&m{ruEA0TQ+Y za7aEp)_@wBoxe+J4JN2^fF47h+n0574$$*O`)ixah#i}mYqlIAltpQhma>_>$3T>1 zvNafu1)~{e5oAQ4f%Ao4i{UU;oGDbGg=TC@8^Ee^RzdbyJ2oTJcYQVih4u*w#orN! z0AqjSho6MPGECG~|8RweEo=sir%Kk+wFJTc1+BF9I1tW5ukMJiT^RKJgP$#eKE?~) z8%_ctb2F7q$DIe8TX_hve=It@5T}2Wa!H^W>aJa-J zKC`GuOd}p7Cb0@DI|~}=N75k%MPor@tfy##j>b(dXpj~R3*o}XNRhcq;xJL4W=UeBgvBo~&43IB3*Qg`dWZ(FKjXmP-p$FJcNBF^&sbP9qcCJ&=%1 zNZ+9$$Jk(Y6f_c#$iRFrwfyIru0@PNqAnfbtOr`b4RO-K^U0%PY`=AAC5$KM5BPKa zx$Od@TVYZGSyXgOtS_&Lt+%DV6lqaq;*>B~^L^(%)=@W-)Asj|n`1ao&*(Rrzqk~Y|?6_6I@F8UB( z@gud9P^H>|&h29CYAcQXpDlwN=Y~8XCx$#z04n?T)v<)fD9~`wOngwNn=Zsed#PFj z_uRV=z+5et7Jw}QXaPFt;=F1dmy3Ow#re~A>*o`;6HmXkO>=PHqs!3xE$2E7SaO~L z+O1YY=jZ9CCxzEjI-1+7tHbSW$AIO>lu|uUC{-(u*?42YOX&V0{Xys-$C9IJ+y(u% zt+16aqJ8VjIA=P{qBqu78Bf`CLyZ*T3XQC|Z$Z7XymiOmqa_U|ygi|3_5*3?MRB#@*&b*P$rb_YocjN zE2jW+UV*b?>_kNYRaQW!sZ|AbBt_O)bX+W0YQQkEB*@2D>_sKso@FB{=~QWcX-|Ep zslQm`!c6lIhX?_$U9-R$VGT9?Le)C}tSzB>V3Iv$Ur#Gz=JP_%QoYZXU-wxJVurob zEp@M>X!!YG$N2q-I$L7Wc|WUos-cxDS+-QNwNcEhhOg`{w+Yjpn=tD1k5(m`Q!76V zL_Y5-P9Dv-lK(QVSQ1im0x?bFu2|Q2lQc%NO|$L(tLppP5O}6DBNK;)Q7ZhBi7tz zjifY_Mk~#nrLD?3a-LJ3V^ff`vy;Rd&%QmWX89)}{m~gK6|y@VJhx5kXa@!`tjl++wz*V;xD?mLr&$#t4iex8}K42F{Fd1V=U*39j(oZ zp-mTM#Bt^^d_)`zW}i0~zRuVnkRa5t*;SqwRsH)N!e-5d@;E zo?K43u?TeCd7~6mw+XxsTnGB7^w1Slw-JnLQ_Tl5fkIf)ACvKDC#Z3kGFCN$7Dq-g z%;GK+3f3>6hoY8JP01|;49FKgL&}$4C?(F4aDgvB3?dDaZ>Y6^F~fyz}u3 z6%-oFn8!4y2RYjOo(hxMLB!urRN!!l^80vVu9%m03n^xQgmD8e-NG`k9+<%0fc_0D zmM`BjPf{y6sKr5krx;!JRTX7*wtQ_?Mu8$+M{&a@3?)%fFCy0v7^B?APT-KA0&SpB z&x5Lz`hmG9c^r?5+R=-y937aOif8C%N*m?~_}gPnak6~2O3~x+lyry0Lj17*mx_A! zTeB{r{*n|*bQH~Wyj5hHwY8q&oHBL6$Ji*#o}O_}R)|}Z99L;^cTgt4`b00vKfPV7 zDx)2Fb~US*=2kK&h-y+fl%-QQ-A#e(Ayj$jG`H~C#a1Zh+uh>MkVt?#9jG4A45%yA z4vph3{@cKt83L+Hou6AcUmfX8H|hh=(&LFxrfK-&;o(1PGt0nws{B&Hnpr6lG4y0j zJ>Q={$9}LzGu1`Y*u>ORwYu{jiB8Y|0I2Y$w6Utnxk`C8mlLclQL0P%B3Y@DC$osi zCowK1*w`+SE%2^dwsE%FU(BwK?}+aT7K-$lxd>}wDpo5&dy{SpSwgHqK`&w`=mbMU z7q*w(3vzU$!e+9!(S>*-=%;3OuV+#ipQa zdUj2PfSmUAdg%Hh1Cz@w^)p0b{*s?ap<;qMQPo6{lo&&V=U1;nk_3_rbXTKEV(O}1 z;Or3)<$rpMe+Ohm5=^a(_xjgtK=)cgg-K zoT2;pZ*G<4#zzj{!`|%aL-*r&_CYph2aOB-HJuNYRu>t5=VM#6z;oc+8cd42Q?1;z&4w%D-CAiG2wJW!Z@u<9} zoVdFQ&nu#oQGbDIKN5H{V+Zp84Gkm5z%R^ z3Sbw1U8S?m97n8Vq%jvu5);T4dAmQvGbnfqj^8#tIbktj#kW*n%*I};UJQy zeJtO5TqWc=$c<6&dB9%A1REPw>>j6!y>RYoG&QKYx}5BuV=_9}q9T-s$49w;b!D@} zMP2zkl&8YljJ6VclM#akrl&DhJPq_vC-D?Ch)W8>^I@M=NdP*g?In6T$P z+67c2$oODRt{Q3D7Z=rNa?_ZzkE$dlY&`oil?)0olL>CP(1RH5Aj)=QwuZ^LFH1>! zn7yUhrTt*?izEAn_QdocOWTDFTIGX!RP^R)&t2LvW6JXw(U6(ZMVDUh;=%Su-ygCV1og20>#uksfw~dDrV125>GI&JYHl znR(G8{BM(UA+o?UkaVO*^SfjBTI2#rhnqm-pG2en)S_)DE$OcuQv078C_(6 zWoFY9gv^+yTC_b&OHHIIo9(sIF1jJLx2Dr^Jr|p+YHEXYtpD_;aYOD80BOMHjRHGk z_UYD!YFgQb@JW|V71O7!R?YtWl%w=%B*S0*jT`RE)vwEINMfKWNqn1ML+^FFNc?!uRfc+S z%I8tZdg_7P?LMQKlMhrnKco359visQ^g@9xeTt?HBEsxTH%a(c@D){dcBRGTC-MY? zmyFk-U-nlHWvqN-7DwK`y(>^KE}?{Qy>P_8W-+efCh$0kPH%T$JPsA4%n-yty#Pr! zg2iSny-bXU^)7!5B7_m#%5CK)jqU+TC7Wc0NS6bpl5C3?FI@;yFJOv|ZG>LfUKTP7 z1*4&j!WM1#iII8;m@2<4-fpPTfAll=>5rJ)Jqvw>>o0c)=-)%8IMk0-FqzHMiD|(U z_NH(X! zhSu&}(r;-ObNiK8(ZO8N7nrwfSGe<7-;+4Rn)Igp;rxZeNt&hY6l>B#dAstu>9tpz zMha^4x`~w@Z+O1!(TGbgHeV9}TG8U6@V(h(TK4GB3?HPgN zS>(x#K`lCecPFFli5|rn{Pu9ZJxLffUJO5~wQhUjucdY3uVCk##Ge8r^<8<^B$EGa zx6$OnxFPss0z5RPn4|GxxEtUiY8@mL?@5I9r;LPNk|mQTGsZQ9hE3-m6$jn`q@~D~ z;|7MQJt^UAmwu18y9#ILxeiaGWG+h|;?IRcK)}t;4?#jfYcB#T(c!$w6Zro=Xvr*b ztq+7k_wJR>heA59)K$L~vqKV7fJ+Z2PP~Z4^pQ z;l@1pDdz>dhw zer+&V8vcUPugJ2K{?eyQsx)Gn?6sN5AHmc!H;3nQ0d4Tm*UD*o*U8*~&Z z;}fb1tOu-|gprA?vPV~1tr6mk6d&~v!JR=Hwb34B-BA8%ce$2Q0CRRbX2Z4fOyFU* z+)G}K9U|JO#mV={`$=OV05rb&JPm7An zyo&|iT&9)Fq`QB4QQ{8KzPjAoecLH6Y_YBHt)$)3F=X86ERHl-6m&4UPW{!u; z_KSrae+^fu3W+$aKOg03)B?Un&EspdT)swQ|06!gxupG^(h=F`dwO4#J>T@5U@Rdt zmkCxi(u*Cu61`uHO=8-()I4)3Qz9;9<(qT3EE`&kHTp|<#g0b0@?Zg1l#|x*Kbe@+ zg~$GV=FP|G5djyixC|xb%AZ+?o_*XvZ^yneChhx^*Uy&e_u>5UJr%& zJI{8W^n*Tq>HqYXm+LDBYLeq4y1EM5HjQz@Zo)4UzXbU^Pj;T2WE=-3EF7|3&3BOC zhF|YL4m|9=jV4T{2e3@^XIS)MG;9H2^$(u}mD*~5iRna6MtAOv{_G#q`M5)i+PkwY zjp(;ip5!P08VKjOi8V_^H)FTy_&My4&7Jm`_^0m_@^K+aC{OfIjC2@W~`E9NuI+rZYu{-J%AnHM}> zeSGxl*?NhJ@}AVC+z2pi@rw8Jqi-#V^=`#JzTA1}W((RTuc*%aYTU$IwDB4hU4BXr z!ygm6{2Fc126GL9hZ=aM?an&V z4)-smcAJtpOV1{gGkDlh>x*j7fnANRh8t>?_4p^Pru8L&Us@ zm%?f#t^T3d5FBYt0~&-*IV>gqko)cF#k!ebI2eHd!x7U6#PsL;?c3*Ji?gy)D$r8g zGnvGUI}zwY`)VlxUqcyaDY_E`XD$VV`a?6>R`?eu48FQr9vBiPGPH-D6z`Lky z$4du!=Q3Ar&l2SUpHE|MmFz+_JjbAEy3Dr@I1PR zMld&_kkiR1flGi&o)IPP`^xM8o^aQHV)wrobi0#7-koDsKsZJ4VN}{w&AT0W;@Y-?{L6@o zeXQUsq*p3G77kv$$k+avci3oW<+p~JHx{;4S5_~7*eLc@J9hoQ-M2k9O(rlcK(2AD zF$g7{?o>{59sLRtmR|UA8#>nYZ+=3}%>o<4kRi{f%5!~a{dwq}b(OhX8A~(A;RRY` z=67W|0t3$xEn))4t(AFv1xFcnl*ii9%8B}|ef|dZPMvTCCZ*<<=ST~tuM$N$A5G$j!~o|~UK0A6Xut(14A99R{+8@y8m8k*rZ*7bF&6RObz(z%FG3h$2$ zQQcNORe2U1jI~_}#o(jhLr5q~XpcsP(cE>_*n#&x)43*Ko`GuYj?7gR7t}w?+*U!T zNU^aiqdBQAlpUCS3w98BX`wsk;Wg(|K!MI@o!0iC(bGl=L%q{O&SeIaiF z5+;ob&vn6%E6J6+NI;wRe z%yIWV>fl$PJIQx{A-XUA0kSm9intT16K|8Jd`8uryL0YTflklxqdMovKv#B0T6ddO z_f_`}yvx=O#oKI|L=@vEGwUpIaIVET_x$le04(1x`dl~b)&xim9!&CB`lXqjTfjhvUINS*g$nNQ!4ZxKm1$&{NU;w?im zuitl(xtmp*Y+m8KC#E>u6Qz((gQkX!F&~N>eEBI8#2bm|kLTC=oZI2O`Wc(Cn;gp$P6km62ym z?P8@9;OQnZG**+fvZY8`=BW~xt}lfdWmqERwo_r>IOm8PWSw#}LS4hIQktZG9_T~9 zq?BcAk&`N=Uo}tZH(h)5Xq}tzD#!Ll^TyHPb8!pH1%kqI7uN@{CNCz{{H{(Ruv&5t zHoUQ8Ni=y@V$3r&+Je(if2Lpu2gU;g(sD6H3nTEWDSI2!RT)%NOMVpcJ9U@O=5_P1Ny?hZ@wh7+ED-!7%<0Dc91 zp3y|_^egqcfRf&6GSWK&3ZpzvOQX4EGOAadpUXV)s`4O>Qlt~gw&CGBtz(}{kUol^ zubE%;38j1vbzhx#d5qelp#T53VXPcRupFLvle>NgA8>BL`UL@1&Wm1ztTufR` zAgw0hx049lxz#s#mN#{j`^u|I&~98}QTJ^(Z0`e)8bghKNw1=BZJ$VQGHe1V{w4_$ z@1}#9aKsa6A|6MgMW^h zh47Llk*v*REorId7mAj6ma3M@95SoQdb02vomiJ}*>F4nAeC7RT2O(LnLmT%|iEW`KC z;g#MBu$2rXNI-FM$kgY+-aT>r?o3^GrtoCiG}fjeSv_&Zyz-$5z$C zBa0#ZP=(HV+mA|cLT(`)bn?fHfRH{Wnj&kM}dL0uO|T(?SfCj5z`hh0uH#R=qY`jSZE1hul)_y zM@Ps0?MP5Awtys^VaG0E_^BhwIA{T>mo+e%DpPwiir|HwdD)-H{Tq2l3u2i&LkI`*DErQZL)2X2mQCkw~xqohMifNl23 zkgGzJmD9(`fuia&_TU`(-73%KU7z7-E2uP>D5jSJjm}U7S|^{DiXw{v;*;GGYP1c3 z4wRI?+CUZU?{J>khi{6FoSR^&aPo?`qnyF&E8J}rIKKLu)J3irRkZ&pVaXc`fu4@v zc9c|uf)BqoUR+X$|zwCGUV8l==*cp&^T7${Gz7N$iYDVWV+wRrX$=LCDzwGh>3tG>XQ7gKfx5 z5!?0`h97-^3#`kt&>SNvqX$)i+%Yu>v$!7>A-f}Hv$#$GdOKHha47YvsewF#rt)LE zbBES>^);Gb)K(H8KAG@~f03va3<_H&k%F7J$sE86_p0mPojfW`2vqWq`3z#C~d9=S$KPAqSwvvfR zbrf=-RNvmET2Ek!OYu{YL)$f%GdSEC>(pYZAb;Y;Tg91?3+_`d?Ie|Qf$s!kdVK>jrJHCi&mW`b zq_vne%BDC<05WTeUO0?HuI1fXB5SDHlXT5cIr#_Y3|ywJ!vL_zS!32t!83tW2ar&b z{{nuIE<)JZ+VT2otz` z4ylw3i+HLfxaRnV@y0oKQ+chMtLGWnw}3@f;u5zIuUR*mw`*k7Wg+EdPE06@|8yU6 z2)f2R*;Vdwp3yVezHKRmN(L_`fHb8G1>_wn8Ppy?QqrUYz2$MXaN%d*VomkvozO@S zl0|PGmCgB^UL82GAQ5djf+g6aQTlk;N^1@(1%4$tXQ?1N0ywPuR6)dX0MZIT$>{(w zD}$uVbC&42Ia0LIqhiCFb$CVRewUa@A_V#l#J)^RMMUQ34;`|NTNgTwh3ZU+rFQAz z#ehORteN6`hsTzYZLGE&TI0v#1464Q=m9xp1~9=x#(>$aga;& zW&;9G9TKni*vQN#nPv^WF!qL)7rK^gC82n1LcvHk4H@9Z@|kQ5dGfUqGvZo-0cLT? z(c=jyY?SaLPlRU4m@Oq@!!8s%bFE0TwHr#HflVi-jjvOaHq)sE>+V_!UNhHv{i-@$ z6XMKJK;6cI6S<5?TsNz5@~`i`*Ft?;;3V;KP7u!#}W6J z!K$=5`G&%istS-|DMqB1-z3&ce{D*n6Di>|4`#!a~0?jC1r;Z?roy*HIl*& z3hg}RxB5T>UwnM&`+uY`2u&Uk%wLKXa=}-fDPVXM6$+nVBDTYs+=DWC6Zk+k!Vts0 z?;|g7^_d~`{@zspDBzKVY1jiQ^Ox#Hb)m01YbXXK&iQSoPpI&JEbQA+zK6dRdlQ7> zv#C#f()W|`DChO99?02QT6)9U-})m2Fu{pNc#a3$<}XW$`@&y!jwo{t8J5q`72!_= zfA1plj+EmbOpZ4}xod&Ti+0;?SIgOS++l#hO*Vu=e$7fjyr|vWi_)le25M5lqclmp z`X?50s`3LF^Oy5TN{jTW^9FrxNF`C7D?U|DPsD#EIafPMmG8k-`Awiv?hZT`Xo$CK zoAqiw^YJgksBP+!V6d*ubM^e8Ze6qe^1P%})Xx{2$J>=f zng~wsSF*BkAW54yxB44cDgo*C*Z2s%BxIy~b}DGf6D~tRGJG6pQY|=fLQ}WRfFC)3 zxjsu>FKRx&w2$#hb)F#x6P&3vu{_-&m#4~)?aZyBjjBB(KGdduqI&+OX?HAba}QD5 z$l}MEjKL-GXN7Z$w@YF^cFk$}hrzpMyIxNEZaAn*4#SMIah}~GV1jEV6+``N=86O# z0mUN{+>$?FI)7Z|yX_jVRP&X=hbBjPo zbhbt~cp{HZgi62U*`^+XA$d}a0_&?E58JvzAiA!~i(3Q%l~+`}+n2d@6bRwDGEq9! z&j2p1f7b>nN_rrC{<0>CSj4N&E9^O5=;c%WiWE$t#tZ$`DRR&h{7q1=+7;9SEc`Gfgmfq)XA*tq+XHaQ(X`^)hfohg~3Fu}(1ph|{Z7FfUH zl2gE--5}3z5%dQ0&!L1&fiLxhtWcC^(1ib0LoCF?l~ENPwbi(A*)!QE!#??^#s?DD zxcuurO^||gs;|=FbQHAulZ+nX?c^NxcA>*7O0(}sGRW&&1Qk>K0CSNku>Xz%zMx3w zVU|>o$|pxiS|j#4qOJzDDNlL-wMnXKQ4RW$ytO7QPqpRpH2I#&h#$P%Pw1bjuAXR~ zzpe*6=0SZA;h>g9XQ-R$Z~L!D{ED~hy2{8N*N?JZEf;&*E!EZIL!F7w3MCCZZkqmD zre9s81!kt@b1|y~Xld{&$1X1yd7kHeKk2$|RORih?o7uoum%$$#OYKr(yT?m%7`lW z&n}{d9er1F)RZ|h6L=su^?WZ0;RnNgQ$0`U!-m}x!Z;%f2$v7C(;T?i%x_MEaa=4| zxtlGCjd4U&1#t7B%namt-DuF1JcQk1#N7M|M5X29wWFYrKSHF3llo$L^9|Z*==rpn zBg#RVpy`vULkyYvsnQjrdi$%XdR9-APd42xH9}s8r)k3nuuUh{TW@wRgkEoG@Wu8r zNG3m~wl;ybm2G~31d)Zj>P$xPm&X*!6O_~)R(TJkocWyRVv7wn%SG=@PCt7}pT#o~ z>Zu@w=6198nDE%R_G_R>r06*>U*xAjLwv)|>uH@7Z#V0{YvzM_mo}Qq_eU3&5nOTJv}^qO>B_Xmys+avBO@4JDI&-Z7nwM>6TAIp+gbK)2;Ol5J5$Z2r+9 zv2VY};LP_Ezumoq!tNnOKLmHT9qbF5zYY>$I_byg(ES?qwPd=Zr+*#-H}NB;>Fwa& zrD=1>CgBLR$4T2k{99vu9mK$MUR{>+zQYK^YtPLyD48deNRd9IR3z(ZYxyXwKdeqE zN_*`3Yk2$&_}W`cs+3+1VNn%TnG+h;`7(&`0NlO|uKth!F7T=|0wGE{VSwI6ycosX zARtV}ZIc{JE?tMdEW8d|b=y!}>TVR6r0s2bz;p}!-sNz!$c13!r48n4Cql&UAhx)? zWu3@){ueftU&O_Dl=QKmG-QI?ZlvKC)KDGTG03ScaJK_)Te1EDdQem^G$Z3LZSXMv zNUFviN+d4x}Bn=x2D3W1tA5s)2%6r9F8$2TVL< z2Blou`l(Sk^ z3d6O5aWFQi>{=_G<-+4h)T*!yCGrd_7~K3rDkzYVGBH8)0~R~%TrC&#!sx@MLY`an}`@k8CydzYeUh@HGPnVK+=s#I5uLHVk zfp*z07ypH+!O+4;BXN)q==O0$@_^6=`vEcKJtdc0&H#u}svk{E#M^_{U@$6F-_~8I-ph_oidBmZk+QJa!L6} z#sG+X?lvh`dp-s2@irOHSMwqHo~f#`OM3ZIy$-6Vac|~L`;4^%rw$#6^<uN_Js_KfnwfkrI*%*iU=)6w%MF$XVU43+1;NiOtmPJR_A8WW}qd~9f zPTt;Tb1CLQyAP>t7j4P)TsOs$V7X3A^OMa_@XLObhxc{`;JvV})OeHCf5 zL(=sv`e-C-I4qdqS*m*JRI))pDlLeE6R`;mTcJ8@EpGB-K4>!KxM7oa9IH#!n02e)}Z+;B` zT(f4)ZP;vRL$j;v`S(+;{-d6D$0e4uXZGU8Y7)!Z9^Z1>SWOcD*j`|Bf5=ByS#Uaw zXB#2n{6FT91hjpOE+}g8TSs6nHfB0OUgMo#4KGNPDo2?J5)j^i^HxJX9Oi-1Tcl{6 zP$qTf%7v!(9<2f{BL^LDV% zWW<^ulib_x`40Y7&HgsXf!nTiWp`ap#=~~6!z;y%3C61CB z7DEqY+lt8!(Y~-z0O6pQ6K#HkzqicN9ju!i9v)-~>v{Fd@q3gBi5xULy_tFfKQo{u z^j*AFAQG%b9yzFUS}x7ay2HwA!xZM{i)Apy84;hmM`l1v*i|OoIeS^_@Bi%TGa>&hi6W@jMfK_4MIZMzVmCDs7=LqO ztps%uui!@(QdH?ULC#+qR$kZCnjTVc2iF(-h+)QXa{_$uO3kGj;3a9+l+T5VXzPC7 z&qdD+PK7%69hUBTMay9Lac)Xi5F-dV&%z%J>lnW=5WtG5x>HG! z(90xUBcMO{F=2=D#iWHMy}C@Pc!!PaJ*IsTP^vsGcB$*}LsLfU`ztV^kp!X4n@8Dw z75zixY7!+Xnn9U112V(pGt)`m7&%C&Sa46el4N z9GbopgmA-kM29(NLGpGSDkf@JYQu$XHmrfTOmk4|uHo=$JeXAzTX1K?J~XK%R!f@z2PDO4AcjQc%NK#r0yy z1~90jOC8lx%A>@XD4^NcnDGdhKg%c&Cw-EO=5jBT6mLDDL<_ax`O>g5>xR{| zo?5YDTp^!5qZ|rT`@oh|o_DI)#lWng3Uv)5bqrh#y{InPO_(*e?hmea3^AO|#t&I- zkcgW~aDwug;%=>Y*pv!(QGK$1RUxlK-A%gL$n*O4P>zcFUb917Ox$WNn()Is#1<2A zQ*N?@>9f5n<#z#Tx7&hJ-}*Z?^HH(J{fRO^Uq-6^xhdhQU9U7e$F!qa#+dbR-c^d4 z&ZBGUtbiO(7Yf2+x>-%HimUpf`JlZO5gdJwF1FxD=R^pz3)W%swi}Nuub%CUlGr6D zWpPo`Nm$dWZ8_tctc%P(_CS{jwncoGQaWF-^>}uP4%nRh{(anam!Ntz)B{xAuF-1N zF5}dYZh*Ea3KL5NXQesi=x&XW#Cr+I7?ZpqsL1?gdROhwBEobs({PS7NxDcy&Q*Ug-?bvbPCDdV z%#%S_i6WNs`kb(IsTH>c_dHCetLCQH>1U*{&q- zw;aF=RitQ%vPBb3&6rdQjML^B^if`zH_irG@RE@NNls+!ML{B?8@n78UCD~pXD>$`63p*E?n4B zDqd0FN+mn;_Cf@WI;Y$YYTrN#Pbaa{Ebb89up@Wq$Ti!aci;E&3wlR8jwk!OXpu1_wZ5~2Y&qQ%(4DNE zC-EScZQRz8RT*Fwz6Zom!kd595k0e6VWr4T6P+Iab~;6t36Q;oMh`%h3ih%9FXHn> z;$D+MN^0Uu9-dwz+4TinE5h5D7q*GKYL7^FlAazrtoq?KG0`^N>`qb$U|-4jqso?- zeY2>(+kH3D^kSD_(5l$cn&?2as;ldy8P@sL8e$Cj%Mcc!l)2xEemS`&I-Yuk$4@qY zq_eLfMNO^P8A4opofy8`Lo#tpjhCMj&o|P~W)!57c zDprU`Kyq4)qlW-jK&ZbEIU8taObI^DP2OgHrV2}8j6C#6%?e?(K5#F}@C1MtmF>0C zsg;rlh@(y$mIDzAvTwLK>VuG zpXMxEHy$;)Sw5I3KSXLg+_bBV5jV)ch%@#;cN50(h>^(J?C=Xk$<+W>9w6VFe4uqWml4%5;J7Jwh7X!Y%Lx<=Ib@* z*Tctr^`77Lau~?9FY!w{Ws`~*JG&k(!QF+d=CX5n9gMpGFm(NwNqZTWKWmDTS1+o? zZ^cn-C)hs=A=7=RLVJscBC3Dsd!HnJuTEAo2``Fb8h)2rW~4$k1T~>t$GyLy ziax;%YUOE_3V8gCy1W-)4jWnB<3#NA?`E=f4!%7P&#rcAY`@vvpWJeViBvSHMX9cVzZV0E2n#Q8G-=7!JeR?ZPlv;u5io#b!*o&UE<_amK-d6gd zZ19Hxh2%yAcI!|979)qhh(`9d1%_QX=o0TDG0j;dBS4jKfRVreZw)|)d92_br}a9U za-&*2Ze_};iJRiE0y(?22TvF%$|B=i>)DZz@A%o~k!335ZG3(~ux3kqF;-i?!5a>( z3pU&|tx8ZR2G1OE0}$ezxG&DR08`jeLD<+c=kx8mL5&L4bN0MaFpoiVO1k!ox(+=$!va7^wGerJ8(QeIrN|?Ubx|3DoNp7hZ8lD7F zCLVI+0>$~z8kkZBkY>aH9rDpwN3QUvnP}wQmZw+r3Vhw5=`Nh}E*~EMAyZMb^Mb?+~nc$F~Mzl6`5n~Dl2GYyv-Z)FTqw)OSSL+qR*Zx)jl`fLjZ3y_`)YR*rgMy=_(ZLl;v{+G zRkK{Ax=HDwa@tf9_6l6`>zZdI)WTc}f9j^I*YGu{k>Su$7pz`Pgr(B>=_>BrL0=*q zKa=YgV#H0KMwNpery^$*Zhxj&-SNy5vSM1!1rU7;#4kE1tF_rpcl|P5;?uOZv5mX= zjr`zZ>vXULDvGPhfZ37AjWkeciIW79UseF>hsz2o7INb1;Y$QCPIq%M&@ZZB=ZB$Y zR-S66D?;Zu)SFOQ^Hr&XFEJv^oGDL+^sBqD%aJ&Q`du$I+wtWf6TH00s&2VSLjn9= zt8svX$P*``>zuF%Wh7Oop$BfkSBv~4F^>Gl#aox~N%T+K20`arBYi5$8Vm(*t>+Ka zfRfl`#5Al9&dAFgujk`)AmE3X3VLa*8yVS$5ljDO0g8w>sj1F;Mqbt`1rW3A@|Uzo zt&hvrjuuH%PFz7+@L@Wo{3hzq0e$HhtcxMZFcd3N%& zd!Yu)Q^6f2q$eD>VbXBz5JUJpL&@8{5lBH+t;Bwb_f}^wwkDxN zh1+w7oWS!YXL4H3>$NDiDp5R#2E^7?i}gG~^YxL2h6GDzI*+9fj=+OHv;U^6)(Djz z)E6mH28OReOgANmeKiuwV2XkfFwX@M;Qf!dTxC|&5>*A9;g__qhb(`%6Zt9TH8V;P zJx)~`HN^ECOtf?+Q6!J+-g!3F+(@eog$J4zJ=&tTGuU@88a>iK@zA1C&ZNx9opEOz z>Thw@=;f)ii!X*O(m^aG`cf z`|dEV=${dErMKX`xZEzDMPscm*|3CnC^6-0;Xn#G1__Y&Th9COpvF}V$}3e-ZMf(o zRa#Zmudulc$u$FwPjvEH#w+m+Rd;(CbUPCA#ldv~-;dRfvcM)L)|zdVnjeA5hWRP* z1I!573DmO?atXmQ?h8y7Og8^8r8#N}3(F0z=7M@{YDx{sagzUkGfWb0m* zqF4|m?nOc#$6|@gVki;-gPnGt4_=oAzVDm8m&Cg0J_HQO5}1ICO2t|j91;OrO0p%u z(JnzW-w)ld3dP`WVPmvp#X=eODT+0+qSU1tV@uEuawQHSFX7_iSUncc@*5GfwyJ(T z_DkS|ti2dg7Ng1QKUv0)FBda2w!B4DOxAA5QO&k2Z+$FZXe8LG?0MQOuFDQzgxgjr z)-3)u`r$yEYpIvIA?wE14DVs4d-P$@s0G3Xgt_uW0`qcz3!ezVl>8E^L0>x32AogR z5kI9&ai!UFDhg6XEEkfvA-?MyGwUOm5~LL?6@zIS@{WU_hI~-_s+mD=ookN`9WY0SK?#DbaDP!2`(ij_JhFYq!#4FZlU{!|m<(Fzan*j5fdD%zQRd~Pf zd3d|JDxtz$pbGxhWTgmZ7m!^zXJa3Z;U^~Ji7L;mSM=j9sBJk6VjGLLF9|amB`>o1 zpQj>hcX3thUCV+_=&}vo8z$o^EkA?^e0HHjy^SEkuT^Mg+S-mBMAc zx8|B~gUC7B?FR`q#GC|Q>#e+V=)>`ZYhs;yG;B(CpSuA>4N(UBE^6NT{0**;>--X= zHrf9hjp4wWx0%n8js{inR2>v!A#bqb90o1Ue@%QAK6*4_-?nJKZs2kr=kaLQO^rN0 zI8M5D;K1tvUd$^B&AxuAeb!a8#7j-q)Mv3eRFNHMQe<5rS1P6Wn_Ps`a`Nbwqk6yb zsGc>mtX+RvCKFlgV$Lb=Ua6Bfn}`GX3yLBria!v+qGa2++bkZ8O5pvx`9qOhO%StX z4256{>YvUEN$TNNVc4%j{VQr^2acSqf53ydDdDAg9X4yNEp$%}=*xPwbcyi+Wuue< zrC0{NtkrB&1Mc}F^_jIL5Oo*ey#vF$4J2$kF7=yO9jd74RF6Cuf)x3ES;k2sTmKR} zwIIq9!OTkArhNfUjk~e;lJi1J&_WaK#Ns5V+{O+%-?o?vS}5+Vx8tsVcNX)0miS^u zGhKe~;dH32--&fz_Hrq8(+48LcQlh8uV+QaM``re7~)Nra5II)RtdIGDuCqSgQ@{tslfI*%eNhyL!Gv;xGL@B#Cm2*0J(a_X zsf6%SLNi`PV*68hD^HPd1#0TKN|-j`j=k7iMs=TW7Jff0krD}`Cf-QIHHs9alW%g^ z->=>ofQ-c01#4e)!QJVhcpU3rafr`w7-d_bOM+@aN&k0e{Dk^ zBRtWz#2VN3=h`IKDEUwJcJ`PQ)cuOCaUr*zxVKDwLF%HA)bwp5=;CT3jj@&~3| zy$fdkK11ES zt}f+db16E{J&ug{dCR%ha`iIA*832QLsw{nd^~@xzU1utx{~LSZHh%lx+U$8+-pAAWjvxetkN`_COJ>zzAQfa%v9-nT`qsJ zU>*i<{Gw>T(*N~1h=IjNMo7ms`W<{q{{8jhv*pK<`M(*GUE%u5e+{+yx1rR#KO(lj zg_L%*2JdV^Hu&l~3vH^}yG{LIIQE5W-5fYHSMQmW@_VO}u_7M6`&6-`7YZFieEX1q zHm;#*dQ>;frw!2lMhe$xImXuk9dbZjHFbdP8MC?J$3P}ic?S=C6{^kMzp4oRSH`6; zGCA|{>1lLyHHdy4mf=4RbNlYQDBX99uxdz7{^91c)U5G3M|9PoYh^4lRfnr&X@>{S z!>jiis;Yk9sOyUwSa}TZhqO*3hBbrc3qp^82m<+TTdiIW!k7 z+HwCD^dV=ogJFA(nmY(}Q`H|2?W)P=D{pa0{P&;fXUB3Tz22gVg_U5ft>KKmfyv>s z$pNsxmnl~3At0^`0e?hb3lw5OPk_kUlGWo_>o$w4O2?X0H-PpxLXMF~fn^;5&*C|2 zTePSew&ane;oQCJ2SpB8J#~htjS|`a} zv)s_?a#u_#=F1qHN}nBkCeLVD7^AG%)p>5e()Hs2B}>H@!1jL*JQ^ggUYd`CtOyi4 z>xWFS%x=^~M}HkRiG!T9jJd|Tf$=37N?wY3y$6vT*NE6B1vsMaF6~4E-oJuZpz$RH z{r0N})j4=3ZYAeMo0nb^t*AFR$jJkgyt{60w}YU%NxqE@Rf_o-KElEA%Al=TjBI>& zIBuHnzsI3v$f>Fj+U`0k4%1FF;Qf`KLRCM3RDJb9R*KSoFY!kVkj2^mFUnl?X8bJFSiA>=5Wb?0(54ljK zy1GI8OXK)q3AGhszPMZz*naiNbHp4k)7bay&uQo}ySr!>9g&2W1McUFi=81Q9bq`) z8ht3sw=7IXlV)yG$C;QMhkr4kuP>G=XwaH{FpH6Gkbw6vVzwCI7^nS`2XqGPPl5Px zt(dSol@tc^I-w`8PGfmBn{;eBwfbY6J7~T$JVL?CXxLccY$T1k^LIlJnEbwFCpO8a z@?`#&WjL#6znW_j(uEQ_v?F(S)22#qe4FIkfVi1Wt#u~H`6+GU96^TU$OfaG_g*G> zquG2i>efd@uj+uFfS1P*{FnE_>LEDm^mEg)*prao42DC5NOxGM4xvddsOIMWQ25ps zRsnz2bo6R#-fi#>~&A{=shD=pI#%gzp#J9-L%mA%P!Bsx-&5L zNuvn&cbr9Vm$j(D2G}<1XhAM)TP+pN!{v&D9a!URAv$;w5Mll5g3JQd5UaWMch$rYJy;cNekAxtltTm|F8 zJ>31ei2Kl9mJM(*HCY4H5Q?;qmEsnSzjm z0S@Hh16w~&_ISQWVqgV~P8k=|i&YF9hsYCwH@B$v0k;HEf(b5Zw5aND1$#ipne_XWbHn#6^eX(l`ql2G`OM|-ztyzbvHklhBI9IiEbk&%d)a@}zf;F+ znvbi^n!s2l+6v)k!{$A8Y!ROVCtEpn>Zr+|*P!_u5ZEG@3%1O7s-^KPY{pfq*M9Ta zaKGHQkr@T=rNZNPx+rbK9FhqdLc(v9IZ8XV!yW}4GkVhNp}>gD|><5rciM=fBu6i;tSTW@g;=gqod$RtKJYDU ztEVUlnW{A3PVs13dYxGwf1|>Y=P4fTFlbHojuyRpkhDJ=HkJ7Hn2_QE8uzt1?BgHl zto!?PFhMC znZFYmRAM{#`uItFtB(+6W2 z*_9|;b3NGoFgP&Qu7e#}OWf$6}k=OgL%dm}a67-@SBAj$IqIB$kr_>{JD3$Pdd1#&W) z!{m}}VJwp=3vqj8O313gAUc#_d`<=Z#5O%^h5IVCuARTh&F5uZj%U+hzdqU@Jztpv zC?7jRrbg(<_~;ApK;g!U&z8VN-&YD zl-`AChn3^Ownb++A;(r*xwTBRWdM%2?Q(4d+*y%nx|56bP#k}ma0QPGJXN7(O`ZMK zIDVr|wbxGHfg3ciw)%xJ{n|~{)!DE@=?kL4Ir}^5V4=oe*6}#A7K3lQk)24WS_W^0Pg$2sqf@f`2#zOUtuJ0S)0Nr7N&3N;4hR26IrcZC1l%yGqWoN`@ zO0sOCa%&hWYU>0!wgvyVLBCteGAsukB-3*Oj@({aoASv;SV@#+wnfzgutalX2_9dR z-Af2m`HP|WMo9;BRunWm$wB25`HP|L_X{|6#b`x^j`b*x@Juakv|Is^l7i20VFD<4 z#OR?O3HA`^4p>(5_#?_X3B93=9qpMo>Jl4ZK!ld1_UqZ!=qFsvpEhM7dEYs-x`)5A zK|OE)Ig*=rB0ZYSEwH{rrvP`27WZoO{N(mKy&3j<-K1_tvz`R;*Mv3ma5px9B400h z*bDI9D=FOiQ070Y>%so$L<9AR$VDfLx%q8a6hi9AL}26o0JI@SS%v!x?W| zVU~^wR5wn0$P4Q9r*~)<+smNy$znr_oEAibwGCQM8)UEA`c|dMw_m*4uWj?%RYgR# zjyl|F@o(<0?2z%)Tt*tB*H>VjME9+wf{_k=9^Lz@5-NC6Ur^wzxE6_pjFz=cpv|TL zc8r<9Lmmh={A2>O{>g0&JR=~8O0g_R2-fFp*^sha;LXx^^~6XneJGiw+g`V7h`ySH z5aF7mL~c+ts&V8%uP)orMEyL^{_h$S3UpWKo*uiwlQvIpTE02f!f4}d-{$O3R)8b( zJ0l~&_ET|ejp7iKg{e}JoT9^kJYO;zIu9Pbglv=B)rKKbxY*%aWK}= zqXil%-KVnpf`|dw2Ue%7@s=96ljVP!F_O_<=Gao5hkQVc{AAoqkTi*oX9Ny3I4uhz z!TOAmk!zjYfBq&Av*msheMu z%nEc~*y=hGFd{gFvQt>109Q6YavJjWC#Q5vQ$I(M<&d`Gez(<=QT=4$nWZRA&|U)h+Gaz~-YUb-5Kls5%}&=M+`0(oT{q_5Okx19jj0DSbYJ2|H`2low zdooZQA$*k6Kti>JQB@kXigz6o$F||F7$7?BHe8rDyuo4W?LWip*r(}UVvbkv8dU!x9yAKpY*yE8gxS&_m;HWFIfvYkgy)e+ z5;&JPFJh9rqqA&fXr2;f)F2V?W|+AJ-8Y9u5|7So1d8@g};If{S2&AMde zm0oeTmQx|jnCotMCzsI#JJgEGdF-1`+qA6+3qb2e&?p>5LA_dgWJ)V8%e zKcF?h1R`qYA-CJj2H;@OSg?freT)krg;1L`90L-xQ)CCTGiTfFJZswVN`Z-%owv(D z!y>27I;}qTA*0GkNe`9w&XjbA)jkRJ|ubNw`iFRSl-^cqyo`1lng zOB~;;SQV|N>AFx|7U@q;8$pOYYG|J9Dp6AxZ~^up%7{YAlnT|A&VxYa7$gPW^@vl~ zhCC(qD9S)wqk0|cUlQj&1{RF)6fj4{fh5+rRuYyP-HOcyHZZ&Y8|rF3)%Y>k)t^Nr zpgkDdI*Y`miV8SUm5Nt1CIPgn_f8cAi9a{hMAorF)Ttg@Ms0%HsJ1NMAL>L^ z#ZGpE|0YMMz4~H8J_fX83`jyJswz;A;cRlk?}TFxU6#(KStsyIh6AtyImT^ejCsiB zz6eGbc9U!7V*V)&P{r(%md$bdR#dMRh5ru;;ftIz#FQSz#Vl;%yCu5BR* zHmVfGDwNlfDXpiLm>+`r{*aQ?nS^aD2W<~P8FcA^GW-9Qp*H%SLoX`ndlIVZ{3q+vsm>^sC0A;JX&RiQ9Swpd@iYtUwboIkaUnAf&alt!kjX;oPoN-@+ z8(y!g3n2_$3|}?MLj`?Uw}P9Rdr?C$J0d_uIU_ufgaX2a1|U(cAg`1ql*c_|Im$<# z@=mD)ptPWcpXUPbEj$ET7E_h_QZT6`m@dr0g{8cf~f6)W()gt zB%C{J?2Xgvd(wUrrSRftrJ z#y+(8XLL_Oqllsk4i>4=-uO5Lrt1hJS~(%Z2=Kbn3%|IFA;h24&v5^|%F)o7Q82QL zENP}%tF}e2MMS(qyLm3;(tYJ+x-Qqn_k&nQB1rZnn-LDB#7Mreh*J*y(_KYZ^?EVy zJH_2xs+B}cyflSJb(!2*qjE82f@(M}wz$9Fo0M*|rh9q(I91Ipu5|&&WI&W>-9Tcp z%BzBs2&);wkW4GOS2uyJ0@X1|!1`uambf)s>7g&lvyK#0SFBz(4##QlX+{P#hq-&^ z$^hNMdnH;?`cJ5v?xib^CM;T_tCuP=npc>S#IS1k0@cXId$Q!%4LIwMTWR` zXEzv0Aoj&3BQ;vZ@Goed!ILqa(xtxArA5nD?HD(CX;Rn~=P& zh_vdI{eMH>W^Z|jygJyaw8@ER+BFWY+s+46MY|>6h~tug3~hdMcAJKji}M`Q$i2c+ zCtK$E1Tq#Wo$odYfdHO`rJlU$NF+$w`8b{-${bQqZF!Rr*qwmTsvGh~B%73!XMfo; z_r2&{U5yweGyg+$;ie#EhFOSi&%i^iw%q6Adk-)Iz2vCB-jt-QDNAI4!T-#}dX_Re zS`Tb`0{6Hx_PfL_tZ5$t3nk(GVv`csErZZ1CO?b6g(c#!Rtmi_Mw@_8@-1I}z56A8 z#IEYUW1g$42OiS1EPHUq3|>&I$q2rbmI#WaR>_QWZ!wTKAoulMT|(0~g@e31&@8E` zw+<-2>6xXnRt^BM;)G}FKsAp-@kTSV^Ueqi*(xGUo&QysuDYNT= zR%PniDRtH8*Ic@vjCE~K4{DL|ZnEQHW9VdZUR_hvx-j(gYr&r?V-)z5j3?uxqDY&B zl$CXUYfuBL&uVV=tS#*O;tLrdx&yHKHFU7NI(>+D6TLOO3Oc8g<9V@u=al$mZ_+z97h5 z=UoXk#_Tb!wxY5oHGZrHZi2(S)pk9-DTel!qM5%Dn5}x#Q^5a%k!|1b0iAAf%G304`uR z^fG2Sbi+w1PR#@nZNz7#abDow!kS^Do=sAxo>?!8VAC-hP*YZ!` z$H~-`lUAVOmt#427^%V&NC5nP1hI(XV6A~)p0H+^@`FT$dKS;NoY>~r!L;Cw`4JF@ z?C!r9Q3@{YXHCiGy6i>V9vL40a&jIdyvwBfc5F@EA`Ffq8z)4=PHb=2=B6?MYO6qm z3Wtp(+3(h&xNm&Jg?7IRjG5r1tt$&#PemgFwA(+OZza8Hd!z2a-&S zruO|JDegpgWk@Iz67Re&F3ZqhmYdG~X!yZbmyGHSE!_{K84IV}7in=o4u92~A`MB4 zzbW3=)uY4CuD2>e4X)%Oaig8}+*Z!9hbo%Ydd^Pp2f0dXvh#wC6^<@!RyU<}P9MA& zH>doOVoiN{BRM@%dmU(%adh@0xBiO#Bn7BQSo~}5bsC2Y4UK-ew;M2IO3r|Zf=cIIZmE24(FO4*{>bC*;=Vk$&V@^ z6PXs00&seg5%0jgN0yP2h|SjlA>p0`Vt0HZ@J8IsC6lor2}t*p$K)3h^G8Q1WJNW# zU)A<@mcOS|xDU?abx5^rE3vI|4E-P$Q0)-i>U^Xc+pHcem}AO6NTQatgV zl;xfdRh-+yF+0$F5G|5-ux@VA=wTM3c-kpn#j0`*lrGnqBGQ8EUGepU>7<;oGSLVd!h&x(=><8sH)$pOinUMKp2XtGb zhtGyQWIPH)b>JxYRa(f388O?;HhsXP;e<3Wl_G0rk1~12V;uFK-qpBzq6<#X>4)DLrMWHZ`Z*r+KA|Hm9RW&Y%yj61p!b z0grdywxe)CrFyA6s5u{~2aynsCOQxB7!kY|Hp=wy+2ICQI6zfRgrRCt9GXzV$wRT2 zGRR%_K;dY&1P|s;A_7yvr)#Qkthh?f`DQsyw-m!~G@K7KIdX=IIKkRB?w0r3vg5{c z?C}OzIDpTHuv7=r&RCXASJT+^s5q!F;=q6;kv<|a;#rN_#<^O)PdBV9zl=+KzH`mr ztYY|=1_yql+4(>{$h0qOKwAeObFogTpJwlU%XkTSiXos596i=Qr%)jJhz6*xcL14< z*2jz@g+lAtW;sg`p7pFlW{bOnP%M{^%P5S!DrZsXKCi4ieZ5*t`u6M94X~@UTU<_0 z=|+v2yQFx4CgBUsPC!btd>hMNz!9IO?`>qzh5Q~Sg#x7;5t-_Gtiz6#8AIwxGHchk zJzg@Y-w}~D<&TJwtBU}oM0mjH0`pnl4VrXKdVn7B#}^WK00DjxLB>LpP7dN`pz}f$ z+*Ep?GYnq6u`F7wxha`w%K)q#c8wU(XjWR`HxBp*8qght#%nR+40JX`!S_36d7v{4 zUip4~b|WgC0~v{bYeJCSFR(4xd(vuy9%wABoRNJmW$pz~;Fvig7J%QVIv;3sgE zhk?lc*FoOIyDl}ostCbv`kJ9A=y41cdIKzEpo1mqr8@K+WH#C+Go^iL>;%?&YHh9y zQCt8XL;4kcIJiRcF}228!F%8=4LbmHN@*Djk%G=}wm##Ehjxxis2la?JI&3&$>h-L zP2~)nFfo>JQ=C$P8~sgbh_wvBN)+jb*4)i#SvtgT9Pkgn<+)o&s)Xk=n%Q``(@V#T z23WnLP%Iv37$uM?G20jN$i-43qpU0}t7%ULH0O+YW}rMdnDL(c_O=nkg_Go>HM@O#xuA5U_T%s|8v~FQBH`1djASqaGDntfS6q7(Uh^<<8(+P4O=uxRZ#6JLY z;{5fZHU^I)(7k)j;1}oMp`ydDMN*bGH$om_-WYlCVwW2H2;AWmPG4v}pBaRMopJU8 zj7!<&hbT+FQm8ByKRXnjhD&m9OO*0M)Jwk7lr)ju zSa}oVjg&V{USE0F>*4;Uc*}eI*XYl4OE7Q`Tt)l~VDZpkECw@!A5~+<&4=DO9d-nO@p{yPcE9FWsHvAD)zt0_Vn1`H+AuUuzbvSOR!*Dk&APz@7!-$ zNyzD%r8vll_?Pin*EYUqOFSRHQeR0}==*4nO#7G({Kf(QAb=DmBGQM!*dSNW~;x`WX2LYsTt-me~IpZU|R(CCOH8Ae(D7%k! z3tIH#S94RN!DE_QFy`{eGXtNs@0R0E&hAobUX(&%UR|@x{FX{f3tL>97rGc=izzk; zr4r{o^#DHF0Mo6YiQhQj9|RD(Jts&CDhAPky?BC%LAA06_98-;I>;8gnd7b%hEze< zYEu4Sa)xVPJUPz-4;jtZz}g8zipB&A13lXZcWLBL{$#U49o zgPmI^x{kUimdiR4e}gMr_Rm)fy8vvb6Lo8-%gpE=952;)Fz<0>x?vaH6z`sP1kU>j zy{Y9$fT|3}2tf*YA0DAVV+hMiiF(`!Llb(Ujj4-b5!Us%^My=TD1Jfb;FxFz^voF45oqB|ntw8a*?O~stVkYlxfJ?D?$EX*Pq4$o9|bpV zeWbgxi(>iC^gQQ&WfvjadMu!H1ul(nIF*`WNKbO5LeTCvL^wT$YHY2u!pBVDpuwky zYUL**O_!CB9(6ua=ojV*N(|;WBN8Khic~anF{ckc;icHlJ}oPbogcfLzkXW%6z86H z1YFXqy7i$|)_PgJA4sRQqe}SG)Dx>eR-x$xTXOtH7z{ik%0v(+)uK-O=+W zyUk>ZcVvvML+=cI7zOqg=W#}YX7(GIjCg~aYilONp zsJCIW=k{1gF9SbiR{MO(G-Ou~$10jox9`zs;G-ex`>6;%bnE%es#pFIhYDFCeaB`fpe{ueiXF^7uSqK6&`6_&UuB0!YmGot{l3vd%N#uNh?_U2a z>C0&)ZTc1OiLS@2J^*C{gb&IYQ^Mp2Y5wTmxCkyHj3{L9z`aEZT?wKTue!I$8{$p0 zO=h*?+V*YGiCDU#xVnCNf+9`niBnlclvAf*y*uiWGUXOG2Rf=S7~fHQz+*%2ev|c? zzRFrc=tm1HX-vB)xKd0l>ek7um)3{`9kuuxH@KzM%16X z=d{98n5Ed>;!I-^nhbo6baf2gQ&3J|U4B)ufFb_VNkNrD*I% zGOL89i9%yU)Nv*`xx^}Um1cwDMdc?rq6!1=c_>E!((!ZEs5#cz%?)=ZE4)LH!pXY?8+hVM z?Q+&bEgBSHV%5FVc*E#}mmVL7IBgSscmV_Y#sDWc z*B78PCGU4&MxI2TFG==X$wO)UfGI}ORrBD#XQ@G-oC1e~IikKl8(_6jZDtF-fVg?@ zLDbaaCzhI#EdHZMFPA5pn4M0=q5^qvFMhbqVU9GFh|}@2BhuZD z%_?}$@wS=7=l(hM&&wwtJw3T!#9h+a!yME7XE^%gu;g}JB_VQ(W2Gop`{-_)web?~ zYW_GABk%_(7CHI=`K-w0&hXZf55K@}x88Z?~)lweC zPaoOtn4<1Zgyg7Zb6)G1_fcWF>&Rm_Y7{exjf8ThX4s6pMz7BoY6?<_CLx(xfd&20 zK$U!@QDw~d0v=hdBDigL16OaRO9!p=AS$oVqMgXQl-N0bcrx*4eGFUGyc!zTSI2)D zHJIXrR3!KTuFck}+R^^`QIg8Z$OO3mWmE6JSU+>wXUD1l^ZH;|W|sWVOWHE2CdmpN za&IB#1}42@Ca4h)iC;zeCKg&{@H+BRILzII0ShR%5)LWIex6A9S(n^Xe5qXp18V?s zKOnW)SX4X0&&*W>J;*^dcuCP{Eu((KJ;z7)>sfgn>LfR81DkVBB4%o=l8M|R+&Re~PlfF3 zf#_|wB+&DP3=km_n9f3g6^^$J;g1uC*0r{TNlD0B(SBQ*;%6f+?&uiALZb>n#SVNS zW+vbNwbg%rF%3=r$Is-MhX2-x%~<=%&og$T+mw*#lNtX;^I3Hi%2cqNZz^2i$AsRK z?Fz1dSN%T;{1U&ulpN0eMreiLi`PA1PahbR@_fdusUlOZyP+1)H{CG`gmmr^P?6vh zc=ET)LZDq`^Pd0Li>1zu9ZYnXMU12{ZdYJqJ3p$A<6NQhgp{6PLcv@-yoAwDGeYb( zqhP+X)sGW6)2P1u;~pJG){Tzaw{2rK9;gz=Hzj%3u~y}DWPqM`GGTju?!w_#7Jey}hKsZ>={Xbf;_{g(hbwmc)9`8aY!G<{SbQ z{r`wAF9o$+_YTu`sLGusGotT;SN!_-5%_qH%lY(fz&9-|Mt$4{4r?Ae7QL=P`!9)c z^bg9m?*nh;{p>Cf8Pp_MxNtUR6lsLJ6Qm6p9O_{u8eFk6pQT5F?1Q?;|9aw1Hnk1y=0VlZ3RY38905=Q7r2tOMS$#~acFEnN=uGO#k3A~< zfNfm8B#y@mIL!DI-x~5JO9y?DBJo>~F3&QhSrm~LEg_gq>`mI&GYN)Q?k`P%zSC1I zZuHIJ&kY$n%o6L;wu(Xh@TnRHYb!rv6`b7)9Z`u~ECNtk&!EKdo>RYeqlkF98l4`j zt>o&fz;=7tIfxoUa3VwsT@9rSk|R(>bWYs+-Ys;twU0 zNDWI!u3^H|iZ=g9Qy?A5iX}%mRkCWntH@aoEkv`vMSeZ<(Jn@FQLv@kbm4D%5bIm4 z{{L_OMAtWOZK9>A@K1|pHdgXtDcHcR{WH}eTr?Avq59;JS+)!Fls%xTcyLIm`6J}G zL!$3ev6I_LR(HFo;{EU?{`33$ACA?0F~3vvv@fqRpqY|4@+k;1_Sh_SQW z=nhxY#MYhuA9gK8)r*bB@Le<_1TSW2X5a+&_om92Oc1;O{AA!hu$33hiQsEOrt~le z33NjB5td+w{hCQL=|*L&xP8o~Dr%XyTrbd+DmUbuM3ROtv+X-z=b`1z1nS-h*nLfMW5Zv^v7e95y5F&$<-8<{m(}`2S0$mRFSy{N393_0JCd~m3f;X^ zHPi%>Fti9A;?gXc6~Qn^eAeQtDwj$(&Yg{0D=`aRx7@afIMlMxv{S znsTHMAS7tChB|ge;|bbCWlalLfLO}knqWWN4@j)qVpMk~H*m{v`@vm5~g6oFVXVHMrG+NiL@ zcG*~g6c$u5VQ^|3!!xv~(5pkm{V4Pau>_<;6Gbs@SR0-#WB+14p7u}r(zV)wCwa|n zq(XzzOkW8`sj~0edEYSlDy|-->#DJ}28yK$urZGtaLhI+;_b6Z59o)P;g*T84XuCc zP}J{QgT`$tn!Z^38tLoO>YCTB1zT&NT7rOZvyg{EWlj$=oha8eD_z+5dbCA!BqVZ1 zT!>@Hnx}Bnl9(`@RF%3!4>D@Wa(dWSC3GA0ok21ryRNMWEJ(f)+#8EVSXVaIEy zSWZ=TV-q>#mK)Pr5^_iu;=R+VaP zW_FMT;g}}RTN%lc_39R^>XQe~9xtp#@Nb zGmE-lzy6`tV!z?F6iJ7ElB|Y*+r$&g;+nj=iq$9bl_?~%zp8ZKxR^&BVm_)8t=MHg zpMC+e98BXO&$QxpY?RV6&WWYo66lI*N}x@Q;K&N_gC4((W%U>Yv4a~StB+)qqlx^! z;_1lJ?U>jdPsWVFf5q=+C=*?T=9w^iF+uoypnL=jPf&=f9G_;B3ktBE|J(0J5u012 zdlkn>Bw3v#Qb%Ffix#9S;{CWL9FM-sO!QD%%TQu|qRmDdzN`#BA=y=>RC&=G>MZ{H zc5rlaiNCce?qhKg3qx*JI~54R^96GJVPao(uXa&2hcfMdM$vZ zv*}4#YdBi}RlQaO3(ZrRD4L8@KdrRcjyF2$n$f9J3J<$Ab9Hk2auZ!=?^lnZJtpbg z^LzShH(jmG0&_TSoXv#HHi-;o84NB4i%;-s6RO|xExtke_csf@oP*X4IvbpotXVS4 z$@-B^{N}D?rKVLU_5kqy)~ZUoO|5FZH-SbYSN{&w1S{*S>dQ)P74{0FUS(BVTl5X1 z@v%P(i$qDdmN(EZ%mkP%YN@8_SASp) z1DEgALd@mG zKJ3SmG>(Pzbn2r136wm|o|w4&xiyS^-!Ce|^L+Z#mgD@D_RLd)iX!iv&me6N`j*TV zhg;6J8-*ikp_``% z1ISPJ`RRTXu5A1T_o)X1s8gQ`(ZxjJ11r}yK(RStik9qYxm1X%HVC_Z&d(GPE-lDeXB`v@?lTa^omScQC#O9gRR||;!c^amigdI{t>C(Y? zx@2>qBuaE0LK5XfiGi;Eb~f+2+4x&EsExV2MI-oS-nAv1sOyd^cB}XHmxf=}h79_< z$OugEC-}ZI=E&&_C~?gZg`Dx$;-_3bu|2(+dF%)@f)_~mCQ`o7S6GW`O2^{`0V`gY zoQ~r+1hiG|p4c9;shgh!81JlPX^(rS9c4*JYBmCz@b>Mf6Hdn6$^d;=aa=>gy)$-N z11g3hzvM}b3i7G&n#*$4zST6ZcCgjlo5AP_9NzT)wIM2=!!js??4g`6jfMCNNi!BMoHtU>ZfC-{32;T^2Y?rs+k zKTY@;uVa>uzvNV4+}uXkg7eUUU@TPeScP_v9!?wY=jA1&EY8jNj4JQ6LO$qqn-!Mh z^cjKCQ4Y9Z6!#eEWDv8&RFD*sxs$>U$tfbmkzo)oq#n2VNS@6&z>p?0=fkS}!JdX6cx{;Xp$7nZ1+qTM*OycFK3L}dL z`*ck~&jF!Sk88Q4*FlC$L4cxOr{ayev&(cskE`Q_$BPe+agYB3fPcUt5uJW0Bi+*A zU)HxSt+~j$>D~yBV{9Q$hfj(hg`0OwG3~~5OoP>+6r7$XLg3F^A(RvfD;@&ohpLZ+ zj0#V=^$plEY`|{S-DnBk&T2=9fCmLYRwde(9`Qw2i1s1_VY!{rls zR~{3`%$2?lqNVsREgDitxkZhHIz4U`RgSYtYU5;7wTeAYZJVknspcF!C4M~$;s2s# z&zt3MFQ1MNeK)8}PL2f^ZRF(XPxNLR5n38xmY@@dF~``4z@f)8Kp>DP!15Uo;Ut+< zrTcDbYt)|S@5#vHF~jI=9U)Bz_`au^Xqiit{}=F3O~`G6Gb8U6PMYYv=m~wS@)$Ke z1Y72(F!WN$@_4W3+B7=Ddq}U^?O-XXNIHuTpg#ZT$Yh9(_Y4a&cNlus^y37FjZVv6 zW#nOMIo|f$ri%_!89Hp?tt)=!i6&cHm8mE&A1SLXMFwN_t&ZLwy1MfhQj#cJZCG}+Fkmghg&k>;x_m(2jpNSct^vTVt#kd)xp{(R{Bt z9xQS`fOV|l{@BpXkw*CTvm!ba;t>DziVEsUuS%a5(_{w=D>RZgCb%@>fHwahG((w+ zU7Y-~jFR8@=7h`RIO98E_De8T`K)K|>T%KNH~U%^&b0D)S)|kV!-1_)rT`3vVU{40ckv89Nzf$q&`%vNGMMwAJ8IL zfS^HhErJO@p8NRViwIIg9MjZYe8bOB{^#pIOsf>iny=Y-)08cxIyONSBE~hGcup}8 zX7Wc=&}_WC-?GM=Md}=qCYBgVOFIO=8ODnK9K}B#fhVU*#5Xk9unsOL{1!dj6x)R9 zb>2&_{W#j4DtjEU-<9q>Z?VrF&b>=?x>~DWCu)<%H5r2cpe=-2IJjt^L*j#q2o<|7 z-usXGCxOBrNFr3@699S9evA2pKa&yz=>Ywg+=)269HmO8(byh7F0 ztMks*Y&9xRm93><9X2b}F-u7wcLdGsPCdrQ5{Nfmfb(2oo=U+0+bvqV2~pc~72k<4 zS=DCPb*L3JUJv9|^|K6KEK#d-KHyCgd{medJ&eg~E0%n%oy zH}7HmGT1#SQ1j8$TFbM+a8X7PaVXM)*!@mDtTtdMz3_g+-=Nps-RsV@*=x6?u_>{@6OuHao^j+bK(zyEO{7O)1swgV&CDxWNB?D_-=(W zOL?2yIHe{&eT~rr3KiL$-f6z3Pf)m~HBhrKA5?M964;2rK*&ES`!F;(nqxmVpBwWyx7)=#WX8Rwjc>g`lf zXW+3yMjLNxk|KoaEurOwuKUcCEYr#jXy*bnb?7I_ubE)p{b=}`On80mL{_~bSr>|8 zr;GPgN#pIP7&E%pST>MUVK*yi)7WI$Vb`)wmD1h(@3o)r)yhpB#eA;!KmQtMYef&% zrKCt(g~DSKQ_mzDA!gJmns%v3%8H}NaWQS7x8njVrh|mgjZd+C^4zJO7bYrzceya1 zr=z}|MZAuz^!QuuQsU9CF6p!0!-Yj;EbZ7bakVeGBa$Cmj7`YbMLEOP&EzbB$>0T{ z>7sN9lNtcZMW7l9>i;*I%N|Cv3Da*}|y`*hus}}69qKN>c6yD#1f@z7^UwUKp z5&)jId8GaOXOF+<{%;XESL*`MId~i3)^>yLSaK`&-_&HF_1gtiudfJZ|FepVv?s~f ze29Z3eaTbjsKE{Bczh|CG|3u3LTI-!jZ%9C&!_)|1Hk*T5Q-Cv;(>0*6;41A!iV@6 zG5Ip!O~MGx9#1LDntr`6gG^xvvTsSviWiv%<|9F%Fl!hIsDu|CfE1B0>k!^gCB(3g zvW}{l)Qf5qEtIon@k_A`1UN15A0vgY711Qh(0UJgS*Bc!%Qoh5`Mq%eI{m?FX>!xA zwFBAfGTBka)&Vp%<)xHWUG|-ZuB&b#IwU9_0od&SC*1oh^3wiy*}d<~za4VJ{ntRZ zsgdLEeHd!yrOE&9-}@O)B;?v~x=u{6k6$>2T3?dMOL5Ms$fcfTbUo@#$@@eIEonwd z7dtUA%w;wxAU7*@t<~RsN)T|20s8ql>44|24tKpmyTG6ecfYUa|6oo@fXeF8vjD0MMe)pjGWgQ`e2YK>t4={rt|S|KL-= zSKM+FwQyA#&z7Gx^fCe#YW%XioO%LyE;X)^&##@{V1E9_9)7hoenLxDPJI#KI+_}< zPX&})+D0*S#T@SfzT%?vcK`sedD9J+H*(lTBy)@3bQM_7=PY0=x0p0<*v?%()bHfF zPR1#3Jq=jZ?J`6twAE!=Z`9@81%4#gtjPF$TFV;a)_tep97-`~{`+|i&+!ctFb6+y z5Rq$?wK&9{2Pz4*` zZG=|NzgveYblznK_iD-;y<`7;`^b~SQGx( zk5*^PQ9L6s@5?H~Tm;(`@ef}m16-Y=P*I~1bu^|PCuovsW;CaT%q(ezb+o1pZINX? zCc-T{xcdhKva_cHJSxMoEXVS!fLHL8Q$<$7NmgbRR;6Q3PSZITXXu(+?s??Ni?^Tj z$~&KY^W&3$0R}c)dW<%8r7^;wX0Lz>UoU7 z;;LV~pc>S$Mm4TUO>0(y4mB6-B3snbCAO+{ZE9Phj4&!i+;7E?*Xhbyz(Z+Hd$8!Q=Ohl|VoP<4`%qg78X`Id(sAeXf za~5ZF4(Fn-&gOAG7jPjLaWR*mYfHI|%ejIpk=rV+MwixbE!UyDZWf|m>k;QafgUz+ zBkH$_n@MirR=h^MytZ*W3fjS)+{NA8gTnT5AM)GJ13bt>Jd6e!`Zf1Q5Ap;W&HK@T zS^W98at@8%KvCy;0mXWCk(bcZWnSS`UPF_v^9FCCX}5TrchIc6yvO@|z=wRq$9#e( zNa!h_@j06Jf-m`sula^=`Ht_=yANogPapXS{rb!={K{|qj{g1NPxS2#Y7t*&-yPiUd+Y9C_r0DVkOpMBer5E_DHN- zs+2(~2F;<(|2tX{1ErEwmMT(J967z} zWT*_2;W9!-$|xBvV`Qw1lkqY^Cdwq4EK_8vOq1y{11^~~62mbDqh%(>VhGZDiNQE5 zvt+i+!B7~>T$v~HWq~Y|MY0&fWC^CrQdr`H_YphcXabz7ifNdNDOiDw_UOF@6dDW` z93BD@5*Z2=8XX1`78?#19v^`akr;_oCRZq(RL(B0ZtfnQUfw>we*S8WR;M=@O=gSL zW*0huPq_+`x&tIh6kx=P(;9&dR?WmQ?#^ocdR*B7#Lj3;K0 z#@W#0z#S|YFk;0nHL6KJe>1IhgW>4M1=uSM9njDz{-#$)B^7|W=AqYE8&?xhC_c8p zW+E7Hh^{^P+>rLNQ9U=4+dg;`^zGn@K8YtM?r3ySYunNP%}w494l9B=0H%=ky_m!{ zP(?d>9Es6uZbVe_Ls8U`ny*LasOa)&*Sd}v+~8<7O92P`jFV1YfG0g3EPor(>j}l9 z1W%`BXH98Euld_4SuO6E<4lCAW;l%v1SvS!+gzn&DcJb(ye}#&vtmYo9BpWHr53VdeO{{qaaKaKo!_%ncq6F{Ne| zLrb0A&o_-W3ZwRUya_2aY!W^?d;QNCF-{8NYCb5B0p2k~T+N5q+XsYF+;o4Q=lFoO zIYT|$@$&fiSd{nx!^<#F)CI&v&zMIh7>1t};oB|EHuuc8RPq4hG)FB4NwZU;QbNUrz^JIFAGXaKMQExT)WagE4ufz^@t)KYn+d1to zsVb;uV6{EbZar8&CEGu>sg^+=PX(EfnDb-t_#sRMazba%K`QU7L)i{E=wY1|_~}pi zv1M?q^xTf;ZEHKI`9WDeg?+W#=<2%q|wu1Xu`m$-H!$gGF|+1r$;i<}GJYc$)^3 zz85FXkY*P{2iNF={6h2_-oq@V$cbGr`KIR+XXnipLEX(fcHlZ{y1RNvSEKmg)kVrziGVDlBme3(P zIT6+EL5l*rQclasL`1A@?WAQXa7BS*Sj^Q-khThlEL0`%I7*|IsM$DYB5Z^Z+hVte zAuVZeoCU3os9gXUidL$iX0xO!9Z(^(m=Q8Buuvu{v9=3(NTNnn3Ct+bG^x3|Jq#+O zVQsp4kqQ>1)R>bdE~ceY9Ahb5NRksH<)KQcgt!t=loF+JkT`2C2D4w_ZL0mbcRs}6 zZ&6=*5RxV~%jY#?FAttKKYd$cvv-;OafK^!*Vj)LRs_5*X*9iF?u>6DJZ*k7TKDGx z_YDwXcpBz=t?l=L`-Y078KD@?wjO8BI`yWc3C(~XB-|!7iGx9fchB!p(wTClxj}Kz zGG_sKmTD$)XCbOyKr=I)4T4ZpB;-Sy8x#lSOthA%l&MxGr97w(s)MSznu(bRaC4^S z33B2R|-#h57L`)ZNt*o=fvzeUwYE-HL0y9a> z-in%PUhy*&Q1`WKBbz@sUh`$*nq(WElV|ks=b-Mxj7=Wvz0T=NL9J&j>rN@W{P-u! Z>TD>M-k)d~wox0mNzB~0M%jl@_Y4wDAL#%9 literal 0 HcmV?d00001 From dc7c94f6cc097f2329a499b6e31689bd9ed15c11 Mon Sep 17 00:00:00 2001 From: Blerru <44450511+blru@users.noreply.github.com> Date: Mon, 21 Jul 2025 09:42:57 -0400 Subject: [PATCH 7/9] impr(modes-notice): make average wpm and accuracy show decimal places when appropriate (@blru) (#6749) ### Description Makes it so that the average wpm and accuracy indicators shown above the typing area have their decimals visible when the `alwaysShowDecimalPlaces` config option is set to true. ### Checks - [x] Check if any open issues are related to this PR; if so, be sure to tag them below. - [x] Make sure the PR title follows the Conventional Commits standard. (https://www.conventionalcommits.org for more info) - [x] Make sure to include your GitHub username prefixed with @ inside parentheses at the end of the PR title. --- frontend/src/ts/elements/modes-notice.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/src/ts/elements/modes-notice.ts b/frontend/src/ts/elements/modes-notice.ts index 2aca2b18ff41..d0f8f912610a 100644 --- a/frontend/src/ts/elements/modes-notice.ts +++ b/frontend/src/ts/elements/modes-notice.ts @@ -26,6 +26,7 @@ ConfigEvent.subscribe((eventKey) => { "typingSpeedUnit", "quickRestart", "customPolyglot", + "alwaysShowDecimalPlaces", ]; if (configKeys.includes(eventKey)) { void update(); @@ -170,14 +171,11 @@ export async function update(): Promise { if (isAuthenticated() && avgWPM > 0) { const avgWPMText = ["speed", "both"].includes(Config.showAverage) - ? Format.typingSpeed(avgWPM, { - suffix: ` ${Config.typingSpeedUnit}`, - showDecimalPlaces: false, - }) + ? Format.typingSpeed(avgWPM, { suffix: ` ${Config.typingSpeedUnit}` }) : ""; const avgAccText = ["acc", "both"].includes(Config.showAverage) - ? Format.accuracy(avgAcc, { suffix: " acc", showDecimalPlaces: false }) + ? Format.accuracy(avgAcc, { suffix: " acc" }) : ""; const text = `${avgWPMText} ${avgAccText}`.trim(); From 27019d189f396f82be74cd64475fcfca07b36cb7 Mon Sep 17 00:00:00 2001 From: Seif Soliman Date: Mon, 21 Jul 2025 17:39:43 +0300 Subject: [PATCH 8/9] fix(account-history): highlight animation is not working on result selection (@byseif21) (#6744) ### Description fix the highlight animation for the account history table (when clicking a chart dot) was not visible or only worked inconsistently. This was because the animation was applied to the element, but the background color for table rows is set on the elements. As a result, the animation was hidden by the static background color of the table cells. Closes # --------- Co-authored-by: Miodec --- frontend/src/styles/account.scss | 9 +++++++-- frontend/src/styles/animations.scss | 12 +++--------- frontend/src/ts/pages/account.ts | 21 +++++++++++++-------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/frontend/src/styles/account.scss b/frontend/src/styles/account.scss index ea6ffedd0949..d8396ca16e3f 100644 --- a/frontend/src/styles/account.scss +++ b/frontend/src/styles/account.scss @@ -194,8 +194,13 @@ table td { -webkit-appearance: unset; } + + table tr { + border-radius: var(--roundness); + } + .active { - animation: flashHighlight 4s linear 0s 1; + animation: accountRowHighlight 4s linear 0s 1; } .loadMoreButton { @@ -315,7 +320,7 @@ font-size: 0.75rem; } - tbody tr:nth-child(odd) td { + tbody tr:nth-child(odd) { background: var(--sub-alt-color); } diff --git a/frontend/src/styles/animations.scss b/frontend/src/styles/animations.scss index 9c46a96edd81..a08824103c72 100644 --- a/frontend/src/styles/animations.scss +++ b/frontend/src/styles/animations.scss @@ -64,18 +64,12 @@ } } -@keyframes flashHighlight { +@keyframes accountRowHighlight { 0% { - background-color: var(--bg-color) !important; - } - 10% { - background-color: var(--main-color) !important; - } - 40% { - background-color: var(--main-color) !important; + outline: 0.25em solid var(--main-color); } 100% { - background-color: var(--bg-color) !important; + outline: 0.25em solid transparent; } } diff --git a/frontend/src/ts/pages/account.ts b/frontend/src/ts/pages/account.ts index 8d3bde71059d..d7ed86c3cb35 100644 --- a/frontend/src/ts/pages/account.ts +++ b/frontend/src/ts/pages/account.ts @@ -1157,14 +1157,19 @@ $(".pageAccount #accountHistoryChart").on("click", () => { const windowHeight = $(window).height() ?? 0; const offset = $(`#result-${index}`).offset()?.top ?? 0; const scrollTo = offset - windowHeight / 2; - $([document.documentElement, document.body]).animate( - { - scrollTop: scrollTo, - }, - Misc.applyReducedMotion(500) - ); - $(".resultRow").removeClass("active"); - $(`#result-${index}`).addClass("active"); + $([document.documentElement, document.body]) + .stop(true) + .animate( + { scrollTop: scrollTo }, + { + duration: Misc.applyReducedMotion(500), + done: () => { + const element = $(`#result-${index}`); + $(".resultRow").removeClass("active"); + requestAnimationFrame(() => element.addClass("active")); + }, + } + ); }); $(".pageAccount").on("click", ".miniResultChartButton", async (event) => { From 6dad5415c2d8fc08cacbc1b0e05cd8eb0d10db01 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 21 Jul 2025 16:40:27 +0200 Subject: [PATCH 9/9] feat(sound): add play time warning (@miodec) (#6759) ### Description ### Checks - [ ] Adding quotes? - [ ] Make sure to include translations for the quotes in the description (or another comment) so we can verify their content. - [ ] Adding a language? - Make sure to follow the [languages documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/LANGUAGES.md) - [ ] Add language to `packages/contracts/src/schemas/languages.ts` - [ ] Add language to exactly one group in `frontend/src/ts/constants/languages.ts` - [ ] Add language json file to `frontend/static/languages` - [ ] Adding a theme? - Make sure to follow the [themes documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/THEMES.md) - [ ] Add theme to `packages/contracts/src/schemas/themes.ts` - [ ] Add theme to `frontend/src/ts/constants/themes.ts` - [ ] Add theme css file to `frontend/static/themes` - Also please add a screenshot of the theme, it would be extra awesome if you do so! - [ ] Adding a layout? - [ ] Make sure to follow the [layouts documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/LAYOUTS.md) - [ ] Add layout to `packages/contracts/src/schemas/layouts.ts` - [ ] Add layout json file to `frontend/static/layouts` - [ ] Check if any open issues are related to this PR; if so, be sure to tag them below. - [ ] Make sure the PR title follows the Conventional Commits standard. (https://www.conventionalcommits.org for more info) - [ ] Make sure to include your GitHub username prefixed with @ inside parentheses at the end of the PR title. Closes # --------- Co-authored-by: Christian Fehmer --- frontend/src/html/pages/settings.html | 19 +++++++++ frontend/src/ts/commandline/lists.ts | 2 + .../src/ts/commandline/lists/time-warning.ts | 37 ++++++++++++++++++ frontend/src/ts/config.ts | 11 ++++++ frontend/src/ts/constants/default-config.ts | 1 + .../src/ts/controllers/sound-controller.ts | 18 +++++++++ frontend/src/ts/pages/settings.ts | 8 ++++ frontend/src/ts/test/test-timer.ts | 22 +++++++++++ frontend/static/sound/timeWarning.wav | Bin 0 -> 249460 bytes packages/schemas/src/configs.ts | 9 +++++ 10 files changed, 127 insertions(+) create mode 100644 frontend/src/ts/commandline/lists/time-warning.ts create mode 100644 frontend/static/sound/timeWarning.wav diff --git a/frontend/src/html/pages/settings.html b/frontend/src/html/pages/settings.html index 07919efa3cc1..c77a28756bd5 100644 --- a/frontend/src/html/pages/settings.html +++ b/frontend/src/html/pages/settings.html @@ -705,6 +705,25 @@ +
+
+ + play time warning + +
+
+ Play a short warning sound if you are close to the end of a timed test. +
+
+ + + + + +
+
diff --git a/frontend/src/ts/commandline/lists.ts b/frontend/src/ts/commandline/lists.ts index a05e1b584555..ef4f1bc1885d 100644 --- a/frontend/src/ts/commandline/lists.ts +++ b/frontend/src/ts/commandline/lists.ts @@ -24,6 +24,7 @@ import QuickEndCommands from "./lists/quick-end"; import OppositeShiftModeCommands from "./lists/opposite-shift-mode"; import SoundOnErrorCommands from "./lists/sound-on-error"; import SoundVolumeCommands from "./lists/sound-volume"; +import TimeWarningCommands from "./lists/time-warning"; import FlipTestColorsCommands from "./lists/flip-test-colors"; import SmoothLineScrollCommands from "./lists/smooth-line-scroll"; import AlwaysShowDecimalCommands from "./lists/always-show-decimal"; @@ -242,6 +243,7 @@ export const commands: CommandsSubgroup = { ...SoundVolumeCommands, ...SoundOnClickCommands, ...SoundOnErrorCommands, + ...TimeWarningCommands, //caret ...SmoothCaretCommands, diff --git a/frontend/src/ts/commandline/lists/time-warning.ts b/frontend/src/ts/commandline/lists/time-warning.ts new file mode 100644 index 000000000000..4a021ca3938e --- /dev/null +++ b/frontend/src/ts/commandline/lists/time-warning.ts @@ -0,0 +1,37 @@ +import { + PlayTimeWarning, + PlayTimeWarningSchema, +} from "@monkeytype/schemas/configs"; +import * as UpdateConfig from "../../config"; +import * as SoundController from "../../controllers/sound-controller"; +import { Command, CommandsSubgroup } from "../types"; + +const subgroup: CommandsSubgroup = { + title: "Time warning...", + configKey: "playTimeWarning", + list: (Object.keys(PlayTimeWarningSchema.Values) as PlayTimeWarning[]).map( + (time) => ({ + id: `setPlayTimeWarning${time}`, + display: + time === "off" ? "off" : `${time} second${time !== "1" ? "s" : ""}`, + configValue: time, + exec: (): void => { + UpdateConfig.setPlayTimeWarning(time); + if (time !== "off") { + void SoundController.playTimeWarning(); + } + }, + }) + ), +}; + +const commands: Command[] = [ + { + id: "changePlayTimeWarning", + display: "Time warning...", + icon: "fa-exclamation-triangle", + subgroup, + }, +]; + +export default commands; diff --git a/frontend/src/ts/config.ts b/frontend/src/ts/config.ts index 5738b650f7af..8d15c52b7b20 100644 --- a/frontend/src/ts/config.ts +++ b/frontend/src/ts/config.ts @@ -394,6 +394,10 @@ const configMetadata: ConfigMetadata = { displayString: "play sound on error", changeRequiresRestart: false, }, + playTimeWarning: { + displayString: "play time warning", + changeRequiresRestart: false, + }, // caret smoothCaret: { @@ -855,6 +859,13 @@ export function setSoundVolume( return genericSet("soundVolume", val, nosave); } +export function setPlayTimeWarning( + value: ConfigSchemas.PlayTimeWarning, + nosave?: boolean +): boolean { + return genericSet("playTimeWarning", value, nosave); +} + //difficulty export function setDifficulty( diff: ConfigSchemas.Difficulty, diff --git a/frontend/src/ts/constants/default-config.ts b/frontend/src/ts/constants/default-config.ts index 7ae2db10906d..4ab407952d2e 100644 --- a/frontend/src/ts/constants/default-config.ts +++ b/frontend/src/ts/constants/default-config.ts @@ -101,6 +101,7 @@ const obj = { tapeMode: "off", tapeMargin: 50, maxLineWidth: 0, + playTimeWarning: "off", } as Config; export function getDefaultConfig(): Config { diff --git a/frontend/src/ts/controllers/sound-controller.ts b/frontend/src/ts/controllers/sound-controller.ts index aa2a01275d16..6d2e140d4e4b 100644 --- a/frontend/src/ts/controllers/sound-controller.ts +++ b/frontend/src/ts/controllers/sound-controller.ts @@ -33,6 +33,16 @@ type ErrorSounds = Record< let errorSounds: ErrorSounds | null = null; let clickSounds: ClickSounds | null = null; +let timeWarning: Howl | null = null; + +async function initTimeWarning(): Promise { + const Howl = (await gethowler()).Howl; + if (timeWarning !== null) return; + timeWarning = new Howl({ + src: "../sound/timeWarning.wav", + }); +} + async function initErrorSound(): Promise { const Howl = (await gethowler()).Howl; if (errorSounds !== null) return; @@ -610,6 +620,14 @@ function playScale(scale: ValidScales, scaleMeta: ScaleData): void { oscillatorNode.stop(audioCtx.currentTime + 2); } +export async function playTimeWarning(): Promise { + if (timeWarning === null) await initTimeWarning(); + const soundToPlay = timeWarning as Howl; + soundToPlay.stop(); + soundToPlay.seek(0); + soundToPlay.play(); +} + export function playNote( codeOverride?: string, oscillatorTypeOverride?: SupportedOscillatorTypes diff --git a/frontend/src/ts/pages/settings.ts b/frontend/src/ts/pages/settings.ts index 6ce4190b5622..260b18c7b619 100644 --- a/frontend/src/ts/pages/settings.ts +++ b/frontend/src/ts/pages/settings.ts @@ -285,6 +285,14 @@ async function initGroups(): Promise { UpdateConfig.setSoundVolume, "range" ) as SettingsGroup; + groups["playTimeWarning"] = new SettingsGroup( + "playTimeWarning", + UpdateConfig.setPlayTimeWarning, + "button", + () => { + if (Config.playTimeWarning !== "off") void Sound.playTimeWarning(); + } + ) as SettingsGroup; groups["playSoundOnError"] = new SettingsGroup( "playSoundOnError", UpdateConfig.setPlaySoundOnError, diff --git a/frontend/src/ts/test/test-timer.ts b/frontend/src/ts/test/test-timer.ts index b9b2db08a708..60dbd97ae021 100644 --- a/frontend/src/ts/test/test-timer.ts +++ b/frontend/src/ts/test/test-timer.ts @@ -17,6 +17,7 @@ import * as Time from "../states/time"; import * as TimerEvent from "../observables/timer-event"; import * as LayoutfluidFunboxTimer from "../test/funbox/layoutfluid-funbox-timer"; import { KeymapLayout, Layout } from "@monkeytype/schemas/configs"; +import * as SoundController from "../controllers/sound-controller"; type TimerStats = { dateNow: number; @@ -175,6 +176,26 @@ function checkIfTimeIsUp(): void { if (timerDebug) console.timeEnd("times up check"); } +function playTimeWarning(): void { + if (timerDebug) console.time("play timer warning"); + + let maxTime = undefined; + + if (Config.mode === "time") { + maxTime = Config.time; + } else if (Config.mode === "custom" && CustomText.getLimitMode() === "time") { + maxTime = CustomText.getLimitValue(); + } + + if ( + maxTime !== undefined && + Time.get() === maxTime - parseInt(Config.playTimeWarning, 10) + ) { + void SoundController.playTimeWarning(); + } + if (timerDebug) console.timeEnd("play timer warning"); +} + // --------------------------------------- let timerStats: TimerStats[] = []; @@ -188,6 +209,7 @@ async function timerStep(): Promise { Time.increment(); premid(); updateTimer(); + if (Config.playTimeWarning !== "off") playTimeWarning(); const wpmAndRaw = calculateWpmRaw(); const acc = calculateAcc(); monkey(wpmAndRaw); diff --git a/frontend/static/sound/timeWarning.wav b/frontend/static/sound/timeWarning.wav new file mode 100644 index 0000000000000000000000000000000000000000..85c2727111a31c5d09831b0fa721f03e5fe7f93c GIT binary patch literal 249460 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 +export const PlayTimeWarningSchema = z + .enum(["off", "1", "3", "5", "10"]) + .describe( + "How many seconds before the end of the test to play a warning sound." + ); +export type PlayTimeWarning = z.infer; + export const ConfigSchema = z .object({ // test @@ -402,6 +409,7 @@ export const ConfigSchema = z soundVolume: SoundVolumeSchema, playSoundOnClick: PlaySoundOnClickSchema, playSoundOnError: PlaySoundOnErrorSchema, + playTimeWarning: PlayTimeWarningSchema, // caret smoothCaret: SmoothCaretSchema, @@ -536,6 +544,7 @@ export const ConfigGroupsLiteral = { soundVolume: "sound", playSoundOnClick: "sound", playSoundOnError: "sound", + playTimeWarning: "sound", //caret smoothCaret: "caret",