diff --git a/workspaces/leetcode-zen-mode/src/extension/content-script-isolated/main.ts b/workspaces/leetcode-zen-mode/src/extension/content-script-isolated/main.ts index 5d952183..3d6c7b00 100644 --- a/workspaces/leetcode-zen-mode/src/extension/content-script-isolated/main.ts +++ b/workspaces/leetcode-zen-mode/src/extension/content-script-isolated/main.ts @@ -1,6 +1,8 @@ import { getChrome } from "@code-chronicles/util/browser-extensions/chrome/getChrome"; -import { SETTINGS_ATTRIBUTE, SETTINGS_STORAGE_KEY } from "../constants.ts"; +import { PUBLIC_SETTINGS_STORAGE_KEY } from "../shared/public-settings/constants.ts"; +import { publicSettingsZodType } from "../shared/public-settings/publicSettingsZodType.ts"; +import { writePublicSettingsToDocumentAttribute } from "../shared/public-settings/writePublicSettingsToDocumentAttribute.ts"; /** * Entry point for the extension content script that will run in an isolated @@ -18,10 +20,15 @@ async function main(): Promise { return; } - const settings = await chrome.storage.sync.get(SETTINGS_STORAGE_KEY); - document.documentElement.setAttribute( - SETTINGS_ATTRIBUTE, - JSON.stringify(settings[SETTINGS_STORAGE_KEY]), + const unsafePublicSettings: unknown = ( + await chrome.storage.sync.get(PUBLIC_SETTINGS_STORAGE_KEY) + )[PUBLIC_SETTINGS_STORAGE_KEY]; + if (unsafePublicSettings == null) { + return; + } + + writePublicSettingsToDocumentAttribute( + publicSettingsZodType.parse(unsafePublicSettings), ); } diff --git a/workspaces/leetcode-zen-mode/src/extension/content-script-non-isolated/isArrayOfDataByDifficulty.ts b/workspaces/leetcode-zen-mode/src/extension/content-script-non-isolated/isArrayOfDataByDifficulty.ts index 533e9f6e..e8a143ca 100644 --- a/workspaces/leetcode-zen-mode/src/extension/content-script-non-isolated/isArrayOfDataByDifficulty.ts +++ b/workspaces/leetcode-zen-mode/src/extension/content-script-non-isolated/isArrayOfDataByDifficulty.ts @@ -1,10 +1,13 @@ import { isNonArrayObject } from "@code-chronicles/util/isNonArrayObject"; -import { isString } from "@code-chronicles/util/isString"; + +import { difficultyZodType, type Difficulty } from "../problemDifficulties.ts"; export function isArrayOfDataByDifficulty( arr: unknown[], -): arr is ({ difficulty: string } & Record)[] { +): arr is ({ difficulty: Difficulty } & Record)[] { return arr.every( - (elem) => isNonArrayObject(elem) && isString(elem.difficulty), + (elem) => + isNonArrayObject(elem) && + difficultyZodType.safeParse(elem.difficulty).success, ); } diff --git a/workspaces/leetcode-zen-mode/src/extension/content-script-non-isolated/patchJsxFactory.ts b/workspaces/leetcode-zen-mode/src/extension/content-script-non-isolated/patchJsxFactory.ts index e18361b1..87942786 100644 --- a/workspaces/leetcode-zen-mode/src/extension/content-script-non-isolated/patchJsxFactory.ts +++ b/workspaces/leetcode-zen-mode/src/extension/content-script-non-isolated/patchJsxFactory.ts @@ -3,6 +3,8 @@ import { isString } from "@code-chronicles/util/isString"; import { assignFunctionCosmeticProperties } from "@code-chronicles/util/object-properties/assignFunctionCosmeticProperties"; import { NullReactElement } from "@code-chronicles/util/browser-extensions/NullReactElement"; +import { difficultyZodType } from "../problemDifficulties.ts"; + type CreateElementFn = ( this: unknown, elementType: unknown, @@ -23,7 +25,7 @@ export function patchJsxFactory( Array.isArray(props.items) && props.items.some( (it: Record) => - isString(it.value) && /^easy$/i.test(it.value), + difficultyZodType.safeParse(it.value).success, ) ) { return createElementFn.apply(this, [NullReactElement, {}]); @@ -35,6 +37,7 @@ export function patchJsxFactory( if ( isNonArrayObject(props) && isString(props.category) && + // TODO: use the preferred difficulty /^(?:medium|hard)$/i.test(props.category) ) { return createElementFn.apply(this, [NullReactElement, {}]); diff --git a/workspaces/leetcode-zen-mode/src/extension/content-script-non-isolated/rewriteLeetCodeAggregateDataForDifficulty.ts b/workspaces/leetcode-zen-mode/src/extension/content-script-non-isolated/rewriteLeetCodeAggregateDataForDifficulty.ts index 61c898c2..0f72d28d 100644 --- a/workspaces/leetcode-zen-mode/src/extension/content-script-non-isolated/rewriteLeetCodeAggregateDataForDifficulty.ts +++ b/workspaces/leetcode-zen-mode/src/extension/content-script-non-isolated/rewriteLeetCodeAggregateDataForDifficulty.ts @@ -10,6 +10,7 @@ import { stringToCase, type Case } from "@code-chronicles/util/stringToCase"; import { isArrayOfDataByDifficulty } from "./isArrayOfDataByDifficulty.ts"; import { PREFERRED_STRING_CASE, STRING_CASE_CHECKERS } from "./stringCase.ts"; +import type { Difficulty } from "../problemDifficulties.ts"; /** * Some of the LeetCode GraphQL data is aggregate statistics about problems @@ -18,6 +19,7 @@ import { PREFERRED_STRING_CASE, STRING_CASE_CHECKERS } from "./stringCase.ts"; */ export function rewriteLeetCodeAggregateDataForDifficulty( arr: unknown[], + uncasedPreferredDifficulty: Difficulty, ): unknown[] { // Do nothing if it's not the kind of data we're looking for. if (!isArrayOfDataByDifficulty(arr)) { @@ -38,27 +40,33 @@ export function rewriteLeetCodeAggregateDataForDifficulty( // Prepare some difficulty strings that will come in handy below. const allDifficulty = stringToCase("all", difficultyStringCase); - const easyDifficulty = stringToCase("easy", difficultyStringCase); + const casedPreferredDifficulty = stringToCase( + uncasedPreferredDifficulty, + difficultyStringCase, + ); const elementsByDifficulty = groupBy(arr, (elem) => stringToCase(elem.difficulty, difficultyStringCase), ); - // If we have a single "All" item and items with difficulties besides - // "All" and "Easy", we will get rid of the extra items, and instead use - // a single "Easy" item that's a copy of the "All" item with an updated - // difficulty. + // If we have a single "All" item and items with difficulties besides "All" + // and the preferred difficulty, we will get rid of the extra items, and + // instead use a single item that's a copy of the "All" item with an updated + // difficulty to be the preferred one. if ( elementsByDifficulty.get(allDifficulty)?.length === 1 && [...elementsByDifficulty.keys()].some( (difficulty) => - difficulty !== allDifficulty && difficulty !== easyDifficulty, + difficulty !== allDifficulty && difficulty !== casedPreferredDifficulty, ) ) { const allElement = only( nullthrows(elementsByDifficulty.get(allDifficulty)), ); - return [allElement, { ...allElement, difficulty: easyDifficulty }]; + return [ + allElement, + { ...allElement, difficulty: casedPreferredDifficulty }, + ]; } // Another option is that we don't have an "All" item. In this case we @@ -71,18 +79,19 @@ export function rewriteLeetCodeAggregateDataForDifficulty( if ( [...elementsByDifficulty.values()].every((group) => group.length === 1) && [...elementsByDifficulty.keys()].some( - (difficulty) => difficulty !== easyDifficulty, + (difficulty) => difficulty !== casedPreferredDifficulty, ) ) { return [ mergeObjects(arr, (values, key) => { if (key === "difficulty") { - return easyDifficulty; + return casedPreferredDifficulty; } if (isArrayOfNumbers(values)) { const total = sum(values); + // TODO: weighted average if (key === "percentage") { return total / (values.length || 1); } diff --git a/workspaces/leetcode-zen-mode/src/extension/content-script-non-isolated/rewriteLeetCodeGraphQLData.ts b/workspaces/leetcode-zen-mode/src/extension/content-script-non-isolated/rewriteLeetCodeGraphQLData.ts index 66b4e80e..a02e3376 100644 --- a/workspaces/leetcode-zen-mode/src/extension/content-script-non-isolated/rewriteLeetCodeGraphQLData.ts +++ b/workspaces/leetcode-zen-mode/src/extension/content-script-non-isolated/rewriteLeetCodeGraphQLData.ts @@ -3,25 +3,16 @@ import { isString } from "@code-chronicles/util/isString"; import { mapObjectValues } from "@code-chronicles/util/mapObjectValues"; import { stringToCase } from "@code-chronicles/util/stringToCase"; -import { SETTINGS_ATTRIBUTE } from "../constants.ts"; import { rewriteLeetCodeAggregateDataForDifficulty } from "./rewriteLeetCodeAggregateDataForDifficulty.ts"; import { PREFERRED_STRING_CASE, STRING_CASE_CHECKERS } from "./stringCase.ts"; -import type { Difficulty } from "../usePreferredDifficulty.ts"; - -let preferredDifficulty: Difficulty | null = null; -function getPreferredDifficulty(prevJsonParse: typeof JSON.parse): Difficulty { - if (preferredDifficulty == null) { - try { - preferredDifficulty = (prevJsonParse( - String(document.documentElement.getAttribute(SETTINGS_ATTRIBUTE)), - ) ?? "Easy") as Difficulty; - } catch (err) { - console.error(err); - preferredDifficulty = "Easy"; - } - } - - return preferredDifficulty; +import { difficultyZodType } from "../problemDifficulties.ts"; +import type { PublicSettings } from "../shared/public-settings/publicSettingsZodType.ts"; +import { readPublicSettingsFromDocumentAttribute } from "../shared/public-settings/readPublicSettingsFromDocumentAttribute.ts"; + +let publicSettings: PublicSettings | null = null; +function getPublicSettings(prevJsonParse: typeof JSON.parse): PublicSettings { + return (publicSettings ??= + readPublicSettingsFromDocumentAttribute(prevJsonParse)); } export function rewriteLeetCodeGraphQLData( @@ -29,8 +20,13 @@ export function rewriteLeetCodeGraphQLData( prevJsonParse: typeof JSON.parse, ): unknown { if (Array.isArray(value)) { + const { preferredDifficulty } = getPublicSettings(prevJsonParse); + // Arrays get some extra processing. - const rewrittenValue = rewriteLeetCodeAggregateDataForDifficulty(value); + const rewrittenValue = rewriteLeetCodeAggregateDataForDifficulty( + value, + preferredDifficulty, + ); // Recursively process array values. return rewrittenValue.map((value) => @@ -46,11 +42,12 @@ export function rewriteLeetCodeGraphQLData( } // Rewrite difficulty strings! - if (isString(value) && /^(?:easy|medium|hard)$/i.test(value)) { + if (isString(value) && difficultyZodType.safeParse(value).success) { const stringCase = STRING_CASE_CHECKERS.find(([, checker]) => checker(value))?.[0] ?? PREFERRED_STRING_CASE; - return stringToCase(getPreferredDifficulty(prevJsonParse), stringCase); + const { preferredDifficulty } = getPublicSettings(prevJsonParse); + return stringToCase(preferredDifficulty, stringCase); } // Pass everything else through unchanged. diff --git a/workspaces/leetcode-zen-mode/src/extension/options-ui/components/App.tsx b/workspaces/leetcode-zen-mode/src/extension/options-ui/components/App.tsx index 864732cb..a5a5d7b2 100644 --- a/workspaces/leetcode-zen-mode/src/extension/options-ui/components/App.tsx +++ b/workspaces/leetcode-zen-mode/src/extension/options-ui/components/App.tsx @@ -1,10 +1,7 @@ import React from "react"; -import { - usePreferredDifficulty, - DIFFICULTIES, - type Difficulty, -} from "../../usePreferredDifficulty.ts"; +import { usePreferredDifficulty } from "../../usePreferredDifficulty.ts"; +import { DIFFICULTIES, difficultyZodType } from "../../problemDifficulties.ts"; function Options() { const [preferredDifficulty, setPreferredDifficulty] = @@ -17,7 +14,7 @@ function Options() {