Skip to content

Commit f6e7bfc

Browse files
authored
First pass at a LeetCode Zen mode options page (#499)
If we don't like the "everything is easy" behavior, we can also go with "everything is hard" now. Not everything works entirely correctly, but this is a reasonable start.
1 parent 9d1ae45 commit f6e7bfc

33 files changed

+537
-71
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
diff --git a/index.d.ts b/index.d.ts
2+
index e0bdd370b90c09973074493e8ecaf3ae250b562d..a8981b34374e54d9beb588a2eeaaac43803cbe45 100644
3+
--- a/index.d.ts
4+
+++ b/index.d.ts
5+
@@ -5,9 +5,7 @@
6+
////////////////////
7+
// Global object
8+
////////////////////
9+
-interface Window {
10+
- chrome: typeof chrome;
11+
-}
12+
+export type Chrome = typeof chrome;
13+
14+
////////////////////
15+
// Accessibility Features

workspaces/leetcode-zen-mode/package.json

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
"url": "https://github.com/miorel"
1515
},
1616
"type": "module",
17-
"exports": "./src/main.ts",
17+
"exports": {
18+
"./content-script-isolated": "./src/extension/content-script-isolated/main.ts",
19+
"./content-script-non-isolated": "./src/extension/content-script-non-isolated/main.ts",
20+
"./options-ui": "./src/extension/options-ui/main.tsx",
21+
".": null
22+
},
1823
"scripts": {
1924
"build": "cross-env NODE_OPTIONS=\"--import tsx\" webpack",
2025
"format": "prettier --color --write .",
@@ -23,13 +28,18 @@
2328
},
2429
"dependencies": {
2530
"@code-chronicles/util": "workspace:*",
26-
"nullthrows": "patch:nullthrows@npm%3A1.1.1#~/.yarn/patches/nullthrows-npm-1.1.1-3d1f817134.patch"
31+
"immutability-helper": "patch:immutability-helper@npm%3A3.1.1#~/.yarn/patches/immutability-helper-npm-3.1.1-482f1f8f58.patch",
32+
"nullthrows": "patch:nullthrows@npm%3A1.1.1#~/.yarn/patches/nullthrows-npm-1.1.1-3d1f817134.patch",
33+
"react": "18.3.1",
34+
"react-dom": "18.3.1"
2735
},
2836
"devDependencies": {
2937
"@code-chronicles/eslint-config": "workspace:*",
3038
"@code-chronicles/repository-scripts": "workspace:*",
3139
"@code-chronicles/webpack-chrome-extension-manifest-plugin": "workspace:*",
3240
"@types/node": "22.9.0",
41+
"@types/react": "18.3.12",
42+
"@types/react-dom": "18.3.1",
3343
"cross-env": "7.0.3",
3444
"eslint": "9.14.0",
3545
"fork-ts-checker-webpack-plugin": "9.0.2",
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const SETTINGS_ATTRIBUTE = "data-leetcode-zen-mode-settings";
2+
3+
export const SETTINGS_STORAGE_KEY = "settings";
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { getChrome } from "@code-chronicles/util/browser-extensions/chrome/getChrome";
2+
3+
import { SETTINGS_ATTRIBUTE, SETTINGS_STORAGE_KEY } from "../constants.ts";
4+
5+
/**
6+
* Entry point for the extension content script that will run in an isolated
7+
* world, per:
8+
* https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts#isolated_world
9+
*
10+
* Because it's isolated, it should have access to APIs like `chrome.storage`,
11+
* and as such, its job is to relay the settings to the non-isolated content
12+
* script.
13+
*/
14+
async function main(): Promise<void> {
15+
const chrome = getChrome();
16+
if (!chrome) {
17+
console.error("Couldn't find `chrome` in the environment!");
18+
return;
19+
}
20+
21+
const settings = await chrome.storage.sync.get(SETTINGS_STORAGE_KEY);
22+
document.documentElement.setAttribute(
23+
SETTINGS_ATTRIBUTE,
24+
JSON.stringify(settings[SETTINGS_STORAGE_KEY]),
25+
);
26+
}
27+
28+
main();

workspaces/leetcode-zen-mode/src/main.ts renamed to workspaces/leetcode-zen-mode/src/extension/content-script-non-isolated/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { injectJsonParseMiddleware } from "@code-chronicles/util/browser-extensions/injectJsonParseMiddleware";
2-
import { injectWebpackChunkLoadingMiddleware } from "@code-chronicles/util/browser-extensions/injectWebpackChunkLoadingMiddleware";
2+
import { injectWebpackChunkLoadingMiddleware } from "@code-chronicles/util/browser-extensions/webpackChunkLoading";
33

44
import { patchLeetCodeModule } from "./patchLeetCodeModule.ts";
55
import { rewriteLeetCodeGraphQLData } from "./rewriteLeetCodeGraphQLData.ts";
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { isNonArrayObject } from "@code-chronicles/util/isNonArrayObject";
2+
import { isString } from "@code-chronicles/util/isString";
3+
import { mapObjectValues } from "@code-chronicles/util/mapObjectValues";
4+
import { stringToCase } from "@code-chronicles/util/stringToCase";
5+
6+
import { SETTINGS_ATTRIBUTE } from "../constants.ts";
7+
import { rewriteLeetCodeAggregateDataForDifficulty } from "./rewriteLeetCodeAggregateDataForDifficulty.ts";
8+
import { PREFERRED_STRING_CASE, STRING_CASE_CHECKERS } from "./stringCase.ts";
9+
import type { Difficulty } from "../usePreferredDifficulty.ts";
10+
11+
let preferredDifficulty: Difficulty | null = null;
12+
function getPreferredDifficulty(prevJsonParse: typeof JSON.parse): Difficulty {
13+
if (preferredDifficulty == null) {
14+
try {
15+
preferredDifficulty = (prevJsonParse(
16+
String(document.documentElement.getAttribute(SETTINGS_ATTRIBUTE)),
17+
) ?? "Easy") as Difficulty;
18+
} catch (err) {
19+
console.error(err);
20+
preferredDifficulty = "Easy";
21+
}
22+
}
23+
24+
return preferredDifficulty;
25+
}
26+
27+
export function rewriteLeetCodeGraphQLData(
28+
value: unknown,
29+
prevJsonParse: typeof JSON.parse,
30+
): unknown {
31+
if (Array.isArray(value)) {
32+
// Arrays get some extra processing.
33+
const rewrittenValue = rewriteLeetCodeAggregateDataForDifficulty(value);
34+
35+
// Recursively process array values.
36+
return rewrittenValue.map((value) =>
37+
rewriteLeetCodeGraphQLData(value, prevJsonParse),
38+
);
39+
}
40+
41+
if (isNonArrayObject(value)) {
42+
// Recursively process object values.
43+
return mapObjectValues(value, (value) =>
44+
rewriteLeetCodeGraphQLData(value, prevJsonParse),
45+
);
46+
}
47+
48+
// Rewrite difficulty strings!
49+
if (isString(value) && /^(?:easy|medium|hard)$/i.test(value)) {
50+
const stringCase =
51+
STRING_CASE_CHECKERS.find(([, checker]) => checker(value))?.[0] ??
52+
PREFERRED_STRING_CASE;
53+
return stringToCase(getPreferredDifficulty(prevJsonParse), stringCase);
54+
}
55+
56+
// Pass everything else through unchanged.
57+
return value;
58+
}

0 commit comments

Comments
 (0)