From 7c3502842fd1056f2bcd8784c021034093c8d927 Mon Sep 17 00:00:00 2001 From: penge Date: Sun, 30 Oct 2022 12:13:36 +0100 Subject: [PATCH] Add counter options (show, period), change icon - Add option to show or hide blocked counter, option to set blocked period (All Time, This Month, This Week, Today) - Change Block Site icon so it's more readable in Dark mode or Incognito mode - Click on Block Site icon to open Options - Fix blocked.html message to show blocked rule instead of url - Fix blocked.html message flickering on quick and repeated refresh - Make blocked list responsive (scales down on narrow window) - Replace checkboxes with Yes/No - Refactor storage, define DEFAULTS and VALIDATORS - Exclude node_modules (tsconfig.json, .eslintignore) - Put chunks into chunks folder - Check types before build --- .eslintignore | 1 + .eslintrc.json | 2 +- .gitignore | 1 + README.md | 23 ++- manifest.json | 11 +- package-lock.json | 139 +++++++++++------- package.json | 15 +- public/blocked.css | 2 +- public/blocked.html | 7 +- public/common.css | 10 ++ public/icon.png | Bin 1877 -> 0 bytes public/icon_128.png | Bin 0 -> 7587 bytes public/icon_32.png | Bin 0 -> 2128 bytes public/options.css | 39 ++++- public/options.html | 52 +++++-- public/toolbar/dark.png | Bin 0 -> 1405 bytes public/toolbar/light.png | Bin 0 -> 1586 bytes src/background.ts | 79 +++++----- src/blocked.ts | 30 ++-- src/helpers/__tests__/counter.test.ts | 41 +++++- .../__tests__/get-blocked-message.test.ts | 36 +++++ src/helpers/__tests__/get-blocked-url.test.ts | 25 ++++ src/helpers/counter.ts | 23 ++- src/helpers/dayjs.ts | 9 ++ src/helpers/get-blocked-message.ts | 21 +++ src/helpers/get-blocked-url.ts | 23 +++ src/options.ts | 128 ++++++++++------ src/storage/__tests__/init.test.ts | 36 +++++ src/{helpers/storage.ts => storage/index.ts} | 13 +- src/storage/init.ts | 25 ++++ src/storage/schema.ts | 41 ++++++ tsup.config.ts | 4 + 32 files changed, 629 insertions(+), 207 deletions(-) delete mode 100644 public/icon.png create mode 100644 public/icon_128.png create mode 100644 public/icon_32.png create mode 100644 public/toolbar/dark.png create mode 100644 public/toolbar/light.png create mode 100644 src/helpers/__tests__/get-blocked-message.test.ts create mode 100644 src/helpers/__tests__/get-blocked-url.test.ts create mode 100644 src/helpers/dayjs.ts create mode 100644 src/helpers/get-blocked-message.ts create mode 100644 src/helpers/get-blocked-url.ts create mode 100644 src/storage/__tests__/init.test.ts rename src/{helpers/storage.ts => storage/index.ts} (58%) create mode 100644 src/storage/init.ts create mode 100644 src/storage/schema.ts diff --git a/.eslintignore b/.eslintignore index a78c83f..81ddf0f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ +node_modules/ dist/ jest.config.js diff --git a/.eslintrc.json b/.eslintrc.json index c797d56..badbfab 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -17,7 +17,7 @@ ], "rules": { "indent": ["error", 2], - "quotes": ["error", "double"], + "quotes": ["error", "double", { "avoidEscape": true }], "semi": ["error", "always"], "no-multi-spaces": ["error", { "ignoreEOLComments": true }], "arrow-parens": ["error", "always"] diff --git a/.gitignore b/.gitignore index 3bdd52e..da446bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ dist/ +.vscode .DS_Store diff --git a/README.md b/README.md index 1f8a537..59dbb83 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,17 @@ # Block Site -**Block Site** (formerly known as Stop Social Media) is a simple Chrome extension that improves your productivity by blocking access to distracting websites as you specify. +**Block Site** (formerly known as _Stop Social Media_) is a simple **Chrome extension** that improves your productivity by blocking access to distracting websites as you specify. -### Usage +## Icon -To configure, click on the **B** icon in the toolbar and select **Options** from the menu. Enter sites in the textbox, leaving a new line for each site, like this: + + + + + +## Usage + +To configure, click on the **B** icon in the toolbar. Enter the sites you would like to block, each on a separate line, example: ``` facebook.com @@ -18,10 +25,14 @@ reddit.com Sites starting with `!` will be exception to the rule, allowed. -Choose what should be done when you try to visit a blocked site, options are: +Choose what should be done when you try to visit a blocked site: +
  1. Close Tab
  2. -
  3. Show Blocked info page (page that shows an information about the blocked site)
  4. +
  5. Show Blocked info page (shows information about the blocked site, and if enabled, a blocked count over a specified period of time: All Time, This Month, This Week, Today)
-Last but not least, see a checkbox to enable/disable the blocking. +## Privacy notice + +Block Site doesn't collect any personal information or data. +Any user settings are stored in the browser only. diff --git a/manifest.json b/manifest.json index 6e4bb5a..69f7f50 100644 --- a/manifest.json +++ b/manifest.json @@ -3,7 +3,16 @@ "name": "Block Site", "description": "Blocks access to distracting websites to improve your productivity.", "version": "3.0.1", - "icons": { "128": "icon.png" }, + "icons": { + "32": "icon_32.png", + "128": "icon_128.png" + }, + "action": { + "default_icon": { + "32": "icon_32.png", + "128": "icon_128.png" + } + }, "options_page": "options.html", "permissions": ["storage", "webNavigation"], "background": { diff --git a/package-lock.json b/package-lock.json index 963de0d..9619d62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,16 @@ "name": "block-site", "version": "3.0.1", "license": "MIT", + "dependencies": { + "dayjs": "^1.11.6" + }, "devDependencies": { - "@types/chrome": "^0.0.198", + "@types/chrome": "^0.0.200", "@types/jest": "^29.1.2", - "@typescript-eslint/eslint-plugin": "^5.40.0", - "@typescript-eslint/parser": "^5.40.0", + "@typescript-eslint/eslint-plugin": "^5.41.0", + "@typescript-eslint/parser": "^5.41.0", "copyfiles": "^2.4.1", - "eslint": "^8.25.0", + "eslint": "^8.26.0", "jest": "^29.2.0", "ts-jest": "^29.0.3", "tsup": "^6.3.0" @@ -685,14 +688,14 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.10.7", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.7.tgz", - "integrity": "sha512-MDl6D6sBsaV452/QSdX+4CXIjZhIcI0PELsxUjk4U828yd58vk3bTIvk/6w5FY+4hIy9sLW0sfrV7K7Kc++j/w==", + "version": "0.11.7", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", + "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", - "minimatch": "^3.0.4" + "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" @@ -1251,9 +1254,9 @@ } }, "node_modules/@types/chrome": { - "version": "0.0.198", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.198.tgz", - "integrity": "sha512-zLn6JZXwSOOY0pw+dkIxItxvjsQThDkqaFnsnmr412Wa0MqesymfdTQ/0d30J/0wiFp3TYw0i63hzGGy0W7Paw==", + "version": "0.0.200", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.200.tgz", + "integrity": "sha512-oNT2/KHgZECTzj4oavLc20r3D2yFufLwGNaLFAN8YxYyNVJGenX3l3oGBynhoT/Azm3eAfyDynrdca6jB7CNzw==", "dev": true, "dependencies": { "@types/filesystem": "*", @@ -1342,6 +1345,12 @@ "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==", "dev": true }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -1364,14 +1373,14 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.40.0.tgz", - "integrity": "sha512-FIBZgS3DVJgqPwJzvZTuH4HNsZhHMa9SjxTKAZTlMsPw/UzpEjcf9f4dfgDJEHjK+HboUJo123Eshl6niwEm/Q==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.41.0.tgz", + "integrity": "sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.40.0", - "@typescript-eslint/type-utils": "5.40.0", - "@typescript-eslint/utils": "5.40.0", + "@typescript-eslint/scope-manager": "5.41.0", + "@typescript-eslint/type-utils": "5.41.0", + "@typescript-eslint/utils": "5.41.0", "debug": "^4.3.4", "ignore": "^5.2.0", "regexpp": "^3.2.0", @@ -1396,14 +1405,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.40.0.tgz", - "integrity": "sha512-Ah5gqyX2ySkiuYeOIDg7ap51/b63QgWZA7w6AHtFrag7aH0lRQPbLzUjk0c9o5/KZ6JRkTTDKShL4AUrQa6/hw==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.41.0.tgz", + "integrity": "sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.40.0", - "@typescript-eslint/types": "5.40.0", - "@typescript-eslint/typescript-estree": "5.40.0", + "@typescript-eslint/scope-manager": "5.41.0", + "@typescript-eslint/types": "5.41.0", + "@typescript-eslint/typescript-estree": "5.41.0", "debug": "^4.3.4" }, "engines": { @@ -1423,13 +1432,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.40.0.tgz", - "integrity": "sha512-d3nPmjUeZtEWRvyReMI4I1MwPGC63E8pDoHy0BnrYjnJgilBD3hv7XOiETKLY/zTwI7kCnBDf2vWTRUVpYw0Uw==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.41.0.tgz", + "integrity": "sha512-xOxPJCnuktUkY2xoEZBKXO5DBCugFzjrVndKdUnyQr3+9aDWZReKq9MhaoVnbL+maVwWJu/N0SEtrtEUNb62QQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.40.0", - "@typescript-eslint/visitor-keys": "5.40.0" + "@typescript-eslint/types": "5.41.0", + "@typescript-eslint/visitor-keys": "5.41.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1440,13 +1449,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.40.0.tgz", - "integrity": "sha512-nfuSdKEZY2TpnPz5covjJqav+g5qeBqwSHKBvz7Vm1SAfy93SwKk/JeSTymruDGItTwNijSsno5LhOHRS1pcfw==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.41.0.tgz", + "integrity": "sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.40.0", - "@typescript-eslint/utils": "5.40.0", + "@typescript-eslint/typescript-estree": "5.41.0", + "@typescript-eslint/utils": "5.41.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -1467,9 +1476,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.40.0.tgz", - "integrity": "sha512-V1KdQRTXsYpf1Y1fXCeZ+uhjW48Niiw0VGt4V8yzuaDTU8Z1Xl7yQDyQNqyAFcVhpYXIVCEuxSIWTsLDpHgTbw==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.41.0.tgz", + "integrity": "sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1480,13 +1489,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.40.0.tgz", - "integrity": "sha512-b0GYlDj8TLTOqwX7EGbw2gL5EXS2CPEWhF9nGJiGmEcmlpNBjyHsTwbqpyIEPVpl6br4UcBOYlcI2FJVtJkYhg==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.41.0.tgz", + "integrity": "sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.40.0", - "@typescript-eslint/visitor-keys": "5.40.0", + "@typescript-eslint/types": "5.41.0", + "@typescript-eslint/visitor-keys": "5.41.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1507,15 +1516,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.40.0.tgz", - "integrity": "sha512-MO0y3T5BQ5+tkkuYZJBjePewsY+cQnfkYeRqS6tPh28niiIwPnQ1t59CSRcs1ZwJJNOdWw7rv9pF8aP58IMihA==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.41.0.tgz", + "integrity": "sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.40.0", - "@typescript-eslint/types": "5.40.0", - "@typescript-eslint/typescript-estree": "5.40.0", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.41.0", + "@typescript-eslint/types": "5.41.0", + "@typescript-eslint/typescript-estree": "5.41.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" @@ -1532,12 +1542,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.40.0.tgz", - "integrity": "sha512-ijJ+6yig+x9XplEpG2K6FUdJeQGGj/15U3S56W9IqXKJqleuD7zJ2AX/miLezwxpd7ZxDAqO87zWufKg+RPZyQ==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.41.0.tgz", + "integrity": "sha512-vilqeHj267v8uzzakbm13HkPMl7cbYpKVjgFWZPIOHIJHZtinvypUhJ5xBXfWYg4eFKqztbMMpOgFpT9Gfx4fw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.40.0", + "@typescript-eslint/types": "5.41.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -2092,6 +2102,11 @@ "node": ">= 8" } }, + "node_modules/dayjs": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.6.tgz", + "integrity": "sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2584,14 +2599,15 @@ } }, "node_modules/eslint": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.25.0.tgz", - "integrity": "sha512-DVlJOZ4Pn50zcKW5bYH7GQK/9MsoQG2d5eDH0ebEkE8PbgzTTmtt/VTH9GGJ4BfeZCpBLqFfvsjX35UacUL83A==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz", + "integrity": "sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg==", "dev": true, "dependencies": { "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.10.5", + "@humanwhocodes/config-array": "^0.11.6", "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -2607,14 +2623,14 @@ "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", - "glob-parent": "^6.0.1", + "glob-parent": "^6.0.2", "globals": "^13.15.0", - "globby": "^11.1.0", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", @@ -3297,6 +3313,15 @@ "node": ">=0.12.0" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", diff --git a/package.json b/package.json index fb9ba1e..3d55ac3 100644 --- a/package.json +++ b/package.json @@ -12,19 +12,22 @@ }, "scripts": { "lint": "eslint src --ext .ts", - "test": "jest", - "build": "tsup && npm run copy", + "test": "TZ=UTC jest", + "build": "tsc --noEmit && tsup && npm run copy", "copy": "copyfiles --up 1 public/** dist && copyfiles manifest.json dist" }, "devDependencies": { - "@types/chrome": "^0.0.198", + "@types/chrome": "^0.0.200", "@types/jest": "^29.1.2", - "@typescript-eslint/eslint-plugin": "^5.40.0", - "@typescript-eslint/parser": "^5.40.0", + "@typescript-eslint/eslint-plugin": "^5.41.0", + "@typescript-eslint/parser": "^5.41.0", "copyfiles": "^2.4.1", - "eslint": "^8.25.0", + "eslint": "^8.26.0", "jest": "^29.2.0", "ts-jest": "^29.0.3", "tsup": "^6.3.0" + }, + "dependencies": { + "dayjs": "^1.11.6" } } diff --git a/public/blocked.css b/public/blocked.css index eff629e..61151a8 100644 --- a/public/blocked.css +++ b/public/blocked.css @@ -1,4 +1,4 @@ -#url { +#rule { font-family: monospace; opacity: .3; } diff --git a/public/blocked.html b/public/blocked.html index 8ffb322..18c74c9 100644 --- a/public/blocked.html +++ b/public/blocked.html @@ -9,8 +9,11 @@ -

Block Site

-

was blocked.

+

+ + Block Site +

+

 

See Options diff --git a/public/common.css b/public/common.css index dee31c7..e3aaba6 100644 --- a/public/common.css +++ b/public/common.css @@ -9,6 +9,16 @@ body { line-height: 150%; } +h1 { + display: flex; + align-items: center; +} + +h1 > span { + margin-left: .2em; + margin-top: 3px; +} + a { font-weight: bold; color: black; diff --git a/public/icon.png b/public/icon.png deleted file mode 100644 index b48ecff6cb179377ffc6569afcce25b90dd75418..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1877 zcmeHH`#02i82)^H&5TQ9q*)EgTo}r2C0#~#`zC|QrNNXL7DMh@O=jH6s;i z?)#m{E+=QEi>uoncMs3KY;PaVK0mI1KwwZXKQt^nGAbta;E#fMVRFimqp4_S)=#H$ zPM9i*<$K6by zlzW$PswS5Yv^~&Hag=)r<)XVa^3;stNX6!R7mvz=P)!zdYV*)=Z~tP6WK0zIyp$V^ zna-C2vxv$`9~Vh64Yzn{RLrnU$VY>hlH;$_v}()68#=v-e5sg--_^nrH1pz$*qW1nj?~4cB%BlhCx$`(XM55z9Ye4+_d5IOhb!6>Gq@frdRh0!gTM zKh*XjL;T0wwG=y4?=8BR5nL_gkq+0+jGjs$tFQeGue9a~jy}_B(bQ0P6h<1 zHNC(@AptYV)n?poZLkn&fi}qiH{^MvK@5yg@f-L!(n;x(Pa+tk$;q>DzI4ee9o@#* zSrLi*r@ceXJQ0Wr0;ozV8WA&Ok3s!oR=SA5zjEo*Mvz)M(fuAak}id%Bik8d8KJ{` zff|{JRv$C0QBOo@jJvt(M;^N>j$z}_-3E+69T^a7eOl--j6j2}n^pq(7@6bBu^{qp z5_CH+%lN|htb;8y=;ZrXqiZio-Vd|W(w+8L82Wog?UTi}7=r=fsxZhL(a*z_p^rJb zm_{u%7RpG%YN)lj!_XL*c=QOELV+)-N4!ybMHz`n6vSvdEo`?EJ3sH zc<7dN!2|}h;(M~}{8IK0RjJ zoQD*u&%t(5J#sqo4ewQ_P7!lx|NJH89m?hDs7Ty~Z>49kiHIIAb6W$nQXJj9Is&aZ z7et1Wq0vhgQ@0>>kuMajx9rD{B$ut z2(YZ?95&d6C`Y3ynhkXT(PgiJD7Yx;JXRMLjbpN`Z)o!?XF289yMtmeMGB)~16%yT rTs+#MxV2X?+{i5|NasIYmewq;=}K$6O4jpp|MJXOOfTnZCw|s{Z8a!s diff --git a/public/icon_128.png b/public/icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..e78377af64666e26781f256b77a05ecbaa224883 GIT binary patch literal 7587 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 literal 0 HcmV?d00001 diff --git a/public/icon_32.png b/public/icon_32.png new file mode 100644 index 0000000000000000000000000000000000000000..bfcb6561cf4e599ce3f4128d9cf3a65b9b116bba GIT binary patch literal 2128 zcmZ{lc{J2*8^?dPv3n$B8)2jp8Y9XGV=eo>j0|JnmtlmmjBF_$#$?O#s=VpRHnuF& z!${JYEQKUHg=9CjY4A?(AMZKuAJ6xk>;By5y3Xg^_qqSMGpsFd@bgIU006*ma?{Y3 zNtxdz2x6{~mFF!?u>0v*=m9`|HZQ}2 zKy=PjXi3#cghlF1@5h%#u4`_@8j#i674Nz8CD=S6R<^wxUr?8LoKa)_p*S4`mpIAF z`u7e+|7Gd*B6|-xq)wpl)oc~AvWq7SuST1Z-&wS{UoX+v!4UdMi6pm0ej4k+vsl^N zXC77rVa7Zgi|S%BIzfC%dPqGa9#2EUx^AMUS$rXRH37yFx&{J@GVco;ZBK)>%6o0L8EO1qaAy9(88MJm z<;}58$+udiG{slX)~2FM$2PQmK7Rl9f*YXiZL8oCkk*mbk%sJ;TvLsTrbmljl0{Od zckE8>J+RI+a0^kc!lZth_n|5{AcLya!Qcd5^pG}2zwDScOp}Y3)Xd9^5$HD*5rR#| zrKgnmLTu`KgFS>@t~@3x3~wm6*LBeSD*Qsj#$l}_cVHdc;n;?Rm^X z>*BE$q7#9+7u!q}JS!t|_W5)AN;*VV*7K*qWLaoMC}(75PqXSX%?J&4!PRj*jwcXA7ozh#?d$ z%+1Z&x$qQ_u?H;V>!UIOXC)2}PJshi1Y{~ZMAVFYSICI`XlucQWV$6pC@xv;$P`+o zFGFUguW@gH67lf%2^r1v`7$tA1*r+i2HoAwe2(-(E3qJFIj$NZ5+9YYZ8I=3Ml8 zcMuf5DG+|G*7dI{wmWzB)+R{%vPm>l^5l6Lyu3W$^)==EWKLdQ7DB*+@yu`&%e73u zfM~zU`*DcNcrYA1rI)~RaG8uIR(GMHF|`I^#BozIGgI8q5S^X|8bk^T62s@78twOP z%8?bC@Qha)dWk;*QgF7#{l09jwfXt^qE;kCIrCysX^vlJM7 zn(e0i))t#COFDme{y;LIltHkolmh?#t7bw;l#$EeZsY4NfMm$4 zsHg~V3Fu&#H|L)89$70Y5n|~zpCLD-HBRs!3wX@dO{hLjI!7ZrViI+&ie7k#&4i1j2C=`Wq$%z$5{T^``njKsjnd5F<4oBr{AO|4L&-R*1_>?X|mu{#YgiGL}BCu3CVPc z_1%)im6bOHn%H3|9oulmh2FBWlwQ2Vwmh*i5U}ds4pSMB#0X>`jeW_|Wy9GkR`GsP zYiVr6KMag3#CJs>&#G^B*nyRzJp0zCK8x+hQfntCpS*&HvGReOd_+#n-g|~w3RHyS zXKGMXl1w}|-}s+_Z>2Qn;R+WpsNul#7Axzo{F<8&mE^-%_MYr4 div { +.option { display: flex; align-items: center; } +.option.baseline { + align-items: baseline; +} + +.option > div { + display: flex; + flex-direction: column; +} + label { font-weight: bold; margin-right: .6em; @@ -38,11 +59,15 @@ label { select { font-size: inherit; border: 1px solid; + padding: 3px; } -input[type=checkbox] { - width: 1.3em; - height: 1.3em; +fieldset { + display: inline-block; + margin: 0; + border: 1px solid; + border-top: none; + padding: 6px; } #credits { diff --git a/public/options.html b/public/options.html index bab1fff..21f17b7 100644 --- a/public/options.html +++ b/public/options.html @@ -9,7 +9,10 @@ -

Block Site

+

+ + Block Site +

Specify sites you want to block below. Each site must be on a seperate line. Sites starting with ! will be exception to the rule, allowed. @@ -18,23 +21,46 @@

Block Site


-
-
- - + +
-
- - + +
+ +
+ + +
+
+ + +
+ +
+ + +
+
+
-
- All changes are saved automatically. -

Created by Pavel Bucka (penge)
Support this project by starring it on GitHub. diff --git a/public/toolbar/dark.png b/public/toolbar/dark.png new file mode 100644 index 0000000000000000000000000000000000000000..100c228b956d6161bc459fc25e0a3e98590ee5f3 GIT binary patch literal 1405 zcmV-@1%mpCP)(^xB>_oNB=7(L0aH*+R7Es4Iv^n=AtEIrBPS##DJCf_EG#fE zF*PM9D=aTFA|fRzD=#xNI5RalCn_y6GdD3ZH9$5wbTBZXDJ8%k8`c#N;Svgtmsi^<`{r~^}{rUO$)YR^Da^wvQ+!GG= zv9O&eCi&&#^{=k?y1D)K^zL(U-4YAwIW_px()hl;{rC2tC?@3<5&7ie`Qzc&7ZLc% z$?RTS?rLZE$H()Ok<}Iu_~72<8yMjb4CW&u?rCQD#>VuXocZSE={Yz3_VsZvF+Mao z@OgLkxwzyH3-zd{|NQ*-)zwZkHIpkSy&)VqHaXE671S6KNHjQBGBrdsIJ+Vok}4^l zDJOC;Fu--x;s5{x!%0LzRCt{2-C0-DKmb7Dwke3M8&FCeAdrAiQ9#j%3o6zH#TEAr z+y!_4|C22xX|bJ|w9u9*_j~c2m_Fp;o=j$jBoYAt004j{iUMYdS@Dj}eFOq1#Jctq z2%wNi9v~1vA$gEM06DAC1FcqHAh11$2n0}wA0`k$&N@OMfLx-1g|zzGfbFvyH_B@D zr2#u2P9T6n!{)MDePO_ks9`;%yQeqRmrnPk`ZGrf1nofx_5WL8*VG3FhwQ-6@CbpR z9XPJ$gk-2ZM#qlXTcpP)2n6QKW_4$T*||7!+^#TrfN7X*^h0G8LjF!KF=x-sqiL8lJ)%&pR)1w3elR04m>Cyx;ChNntX9)xb z%ilm8M?1!fsrwl4=daG3^$O0-oxk7*a#1?SYRdHi+meq%xcXa&`b`dlehtMw3N8hO z%o7OAwef_me@yRG_0b)y4|?9^D|uHJJnx!+EGJu2Z_4!n^$kHnE;9!P8U=}x9Mr_Vx`Kpe#lzV!6D*~}^dbfguiv$A0 z1cf-oeI~_wiM)@fr3xff5y@3k9^q^)q|k>WKfTsKmYxv^%(kTvO&_%lv7W#1 zyq65IU^urlrV^U{Cbd7Lzv3YsrYqZHqFF0cPV@Gwr+NDc1V$2NqbkjQEagIBrXrBn zZ@jtS;9CNLk&1x`#}OODE!7Z9xz%JPR|+K9YO?Ad%dHRyj8xdD=&ycg0#R>P9QI=< zZB~5uUN^!b5bSb=jadGQx?BGwRwOdwPxOBHSlsUZX*K90{h2^uCVz8@Uy1xo-5*Si z+frYoKWg5E0(B6Z_m0g)L&Y+>{!eZvWqo{IBM_LWeD9TNA6A%`Hq5M7@8jEd`EQza zoj_fj@+5?#2qeU7f2OCZ^|Ag#@sp-pBv7yaipVD-J2T9S<9`0CGQLv0^oKyOyNQY$ zhAVjp3HR#Jwb|U(Jkr1A&mRPWwh{W})id*DiR2cg^ScCsc2dLV4^}e$spWKfIrZnS zZ2g=-&|dg}4j{)T*Z?^`;t9y{8E8PxYG5A(0@KACH@+ee7_VXTR|Ep{Sv9PG5D4~~ zWZ3m{0>Pe>NQT*;NFdmYVqJ|MToDM26tm(To$B{900000fSLXQv^q&!+HcxA00000 LNkvXXu0mjfKplb~ literal 0 HcmV?d00001 diff --git a/public/toolbar/light.png b/public/toolbar/light.png new file mode 100644 index 0000000000000000000000000000000000000000..5e1d85a8cbedac9a4d28be49a78f690241e9a69d GIT binary patch literal 1586 zcmV-22F>}2P)(^xB>_oNB=7(L0a;K?R7L;)|M2tl@$~ib^!4-h_xAYt`1<<# z{QUIy_xbw!_xbt#{r&#_{`>s=`TP6*^z!+%u=9L%>`+SRE+^#`5%YX@`L(hA_4Cfp z(D~op@p^OR9T?;d3G#b%`QYBDtF4EJi2eBX^ofGw6c6-@h5h*WVq<0f{r>7lKkQRX z`s(QU*3{@WF!|Zm`s(WIN<-^SNc547^{J@;{r&y<`S{e-?sRhG4GZNV9QLuW^M85y z<>U3QuJ^jR{r2?kb8+Pz8uj=1={Ys{)6)62viQEe{rC3seRuif;`!s@=PoDs%E|0r zT<&UT_s7Tcl#%E!DCHLu_~72<8yMym6y_r$?rCQD#>VuXocZSE^!E4ket79QH~seY z`M0$F_4M$0clNos`r_dAjfd=2P3bo==`}6-=;!&_*Zuzf`|a)S zSyA3fI02)E_2?7B$nokl4pcd49pyhi5focvB2%xc8>zRn}tplnGo+1!H ztGT3uN?_6&hwe~?oD z?}6=@`tcJd!`#VJ>jZ-T0~u9YKQ^u3w94c3nPt+VF{1l~pMcC|v=Gcb|EjW7W)Xp^A`vNUZs6hDkN?jcOij!|54gUQphHOz@wy( zIQ=zoA=0KV(}k>aFO~NZhA*Ec5O|ZkyW`}yOgb@Kic*Sg0$DZ$az@oh7+xe0_!Gsw zNM!HhWO|9kRgbZbXV{WbO$0Wu7l~H^w7v|aWjNlsA|#l54exPPjjh$3ZWH+6v=35S7PO+UVQUA&bqyhR}Jpj`~a zwvfTK;d~uA`g!TmmUd3>(Lx~M+v1&k;cWuJ?Df9uj~`+=45{aZ#Qk|D{e6jMM5Un% zWTg;D>s@gtU${Xan7t@FZy27Wr-w^+%0@qQWAfU|SnrJl^8N>j5AO5rl3g&6wK z22wc0`s{NdzTglGK2;ASjY8)CiS`WN{UrTMPSWAi6arZonPXxYE8NuuvZlYxd!Ils zd&4((l$}|-1|mmRFwDo&4ip|M1oG9_;&Q(58v?=XmAs=T3q;1TG~Sn}oocez83vM1 zHF=ON>+BH-W^W3C#OXZX{`Fo%Ao|IQr?MD^lNG=HE?c&yTFZ5d0SD6wBpW}EqscE~ z39;T9#PIzO>E~NNKFIs%{zM?~U^CkoKss|zm`;Nh& zp>C?9@s`^SRUf}>5eR%J4aDir^wGIj`e&n_&+i#C z_NWJ`d+>wBV}CB3FYDvs@0wK{HyCg+b`OX4UJjN2ygW%n^~yiYfrvl?aOC{$TIc{YN|9?#`p_e^le=1cLoVI}NJ}-x~%@fp#7*f#85^@IyQSC4LMvpw_6d z4+6ow+pX{Tia;>;YP)_#Aees*hw%>r!9mls9X}@!95_MKW_}`p;6Pfg`{0T|FrON` kH<$F!X8-^I0D$@QFO%Y1IjQ}liU0rr07*qoM6N<$g5>x`ApigX literal 0 HcmV?d00001 diff --git a/src/background.ts b/src/background.ts index 6d57055..3d615ac 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,65 +1,58 @@ -import storage, { Schema } from "./helpers/storage"; +import initStorage from "./storage/init"; +import storage, { Schema } from "./storage"; import findRule from "./helpers/find-rule"; import * as counterHelper from "./helpers/counter"; - -const CLOSE_TAB = "CLOSE_TAB"; -const SHOW_BLOCKED_INFO_PAGE = "SHOW_BLOCKED_INFO_PAGE"; - -const RESOLUTIONS = [ - CLOSE_TAB, - SHOW_BLOCKED_INFO_PAGE, -]; +import getBlockedUrl from "./helpers/get-blocked-url"; chrome.runtime.onInstalled.addListener(() => { - storage.get(["enabled", "blocked", "counter", "resolution"], (local) => { - if (typeof local.enabled !== "boolean") { - storage.set>({ enabled: false }); - } - - if (!Array.isArray(local.blocked)) { - storage.set>({ blocked: [] }); - } - - if (typeof local.counter !== "object") { - storage.set>({ counter: {} }); - } + initStorage(); +}); - if (!RESOLUTIONS.includes(local.resolution)) { - storage.set>({ resolution: CLOSE_TAB }); - } - }); +chrome.action.onClicked.addListener(() => { + chrome.runtime.openOptionsPage(); }); chrome.webNavigation.onBeforeNavigate.addListener((details) => { - const { tabId, url, timeStamp } = details; - if (!url || !url.startsWith("http") || details.frameId !== 0) { + const { tabId, url, timeStamp, frameId } = details; + if (!url || !url.startsWith("http") || frameId !== 0) { return; } - storage.get(["enabled", "blocked", "counter", "resolution"], (local) => { - const { enabled, blocked, counter, resolution } = local; - if (!enabled || blocked.length === 0 || !RESOLUTIONS.includes(resolution)) { + storage.get(["enabled", "blocked"], ({ enabled, blocked }) => { + if (!enabled || blocked.length === 0) { return; } - counterHelper.flushObsoleteEntries({ blocked, counter }); - const foundRule = findRule(url, blocked); if (!foundRule || foundRule.type === "allow") { - storage.set>({ counter }); + storage.get(["counter"], ({ counter }) => { + counterHelper.flushObsoleteEntries({ blocked, counter }); + storage.set>({ counter }); + }); return; } - const count = counterHelper.add(foundRule.path, timeStamp, { counter }); - storage.set>({ counter }); + storage.get(["counter", "counterShow", "counterPeriod", "resolution"], ({ counter, counterShow, counterPeriod, resolution }) => { + counterHelper.flushObsoleteEntries({ blocked, counter }); + const count = counterHelper.add(foundRule.path, timeStamp, { + counter, + countFromTimeStamp: counterHelper.counterPeriodToTimeStamp(counterPeriod, new Date().getTime()), + }); + storage.set>({ counter }); - switch (resolution) { - case CLOSE_TAB: - chrome.tabs.remove(tabId); - break; - case SHOW_BLOCKED_INFO_PAGE: - chrome.tabs.update(tabId, { url: `${chrome.runtime.getURL("blocked.html")}?url=${url}&count=${count}` }); - break; - } + switch (resolution) { + case "CLOSE_TAB": + chrome.tabs.remove(tabId); + break; + case "SHOW_BLOCKED_INFO_PAGE": { + chrome.tabs.update(tabId, { + url: getBlockedUrl({ + rule: foundRule.path, + countParams: counterShow ? { count, period: counterPeriod } : undefined }, + )}, + ); + break; + }} + }); }); }); diff --git a/src/blocked.ts b/src/blocked.ts index 6fac965..9ca6be0 100644 --- a/src/blocked.ts +++ b/src/blocked.ts @@ -1,18 +1,22 @@ -const getParam = (name: string) => (new URLSearchParams(window.location.search)).get(name); - -const setSpan = (id: string, content: string) => { - const span = document.getElementById(id) as HTMLSpanElement; - span.textContent = content; -}; +import { VALIDATORS, CounterPeriod } from "./storage"; +import getBlockedMessage from "./helpers/get-blocked-message"; window.addEventListener("DOMContentLoaded", () => { - const url = getParam("url"); - if (url) { - setSpan("url", url); - } + const params = new URLSearchParams(window.location.search); - const count = getParam("count"); - if (count) { - setSpan("count", ` ${count}x`); + const rule = params.get("rule"); + if (!rule) { + return; } + + const count = params.get("count"); + const period = params.get("period"); + + const message = getBlockedMessage({ + rule, + count: count || undefined, + period: VALIDATORS.counterPeriod(period) ? period as CounterPeriod : undefined, + }); + + (document.getElementById("message") as HTMLParagraphElement).innerHTML = message; }); diff --git a/src/helpers/__tests__/counter.test.ts b/src/helpers/__tests__/counter.test.ts index 04f2b6a..44a8a04 100644 --- a/src/helpers/__tests__/counter.test.ts +++ b/src/helpers/__tests__/counter.test.ts @@ -1,4 +1,4 @@ -import { flushObsoleteEntries, add } from "../counter"; +import * as c from "../counter"; describe("flushObsoleteEntries()", () => { const blocked = ["youtube.com", "!music.youtube.com"]; @@ -9,23 +9,36 @@ describe("flushObsoleteEntries()", () => { }; it("updates counter", () => { - flushObsoleteEntries({ blocked, counter }); + c.flushObsoleteEntries({ blocked, counter }); expect(counter).toEqual({ "youtube.com": [5000, 6000], }); - flushObsoleteEntries({ blocked: [], counter }); + c.flushObsoleteEntries({ blocked: [], counter }); expect(counter).toEqual({}); }); }); +describe("counterPeriodToTimeStamp()", () => { + it("converts counter period to timestamp", () => { + const now = 1667160846000; // Sun Oct 30 2022 20:14:06 UTC + + expect(c.counterPeriodToTimeStamp("ALL_TIME", 0)).toBe(0); + expect(c.counterPeriodToTimeStamp("ALL_TIME", now)).toBe(0); // now is not relevant + + expect(c.counterPeriodToTimeStamp("THIS_MONTH", now)).toBe(1664582400000); // Sat Oct 01 2022 00:00:00 UTC + expect(c.counterPeriodToTimeStamp("THIS_WEEK", now)).toBe(1666569600000); // Mon Oct 24 2022 00:00:00 UTC + expect(c.counterPeriodToTimeStamp("TODAY", now)).toBe(1667088000000); // Sun Oct 30 2022 00:00:00 UTC + }); +}); + describe("add()", () => { describe("empty", () => { const counter = {}; it("returns updated count", () => { expect( - add("youtube.com", 5000, { counter }) + c.add("youtube.com", 5000, { counter, countFromTimeStamp: 0 }) ).toBe(1); }); @@ -42,11 +55,11 @@ describe("add()", () => { it("returns updated count", () => { expect( - add("youtube.com", 8000, { counter }) + c.add("youtube.com", 8000, { counter, countFromTimeStamp: 0 }) ).toBe(4); expect( - add("something.com", 9000, { counter }) + c.add("something.com", 9000, { counter, countFromTimeStamp: 0 }) ).toBe(2); }); @@ -57,4 +70,20 @@ describe("add()", () => { }); }); }); + + describe("count since some time", () => { + it("returns count since specified time", () => { + const counter = { + "youtube.com": [5000, 6000, 7000, 8000, 9000, 10000, 11000], + }; + + expect( + c.add("youtube.com", 20000, { counter, countFromTimeStamp: 0 }) + ).toBe(8); + + expect( + c.add("youtube.com", 21000, { counter, countFromTimeStamp: 10000 }) + ).toBe(4); + }); + }); }); diff --git a/src/helpers/__tests__/get-blocked-message.test.ts b/src/helpers/__tests__/get-blocked-message.test.ts new file mode 100644 index 0000000..44fd4ae --- /dev/null +++ b/src/helpers/__tests__/get-blocked-message.test.ts @@ -0,0 +1,36 @@ +import getBlockedMessage from "../get-blocked-message"; + +test("getBlockedMessage() returns blocked message", () => { + expect(getBlockedMessage({ + rule: "youtube.com", + })).toBe('youtube.com was blocked.'); + + expect(getBlockedMessage({ + rule: "youtube.com", + count: "42", + })).toBe('youtube.com was blocked 42x.'); + + expect(getBlockedMessage({ + rule: "youtube.com", + count: "42", + period: "ALL_TIME", + })).toBe('youtube.com was blocked 42x.'); // ALL_TIME not part of the message + + expect(getBlockedMessage({ + rule: "youtube.com", + count: "5", + period: "TODAY", + })).toBe('youtube.com was blocked 5x today.'); + + expect(getBlockedMessage({ + rule: "youtube.com", + count: "12", + period: "THIS_WEEK", + })).toBe('youtube.com was blocked 12x this week.'); + + expect(getBlockedMessage({ + rule: "youtube.com", + count: "38", + period: "THIS_MONTH", + })).toBe('youtube.com was blocked 38x this month.'); +}); diff --git a/src/helpers/__tests__/get-blocked-url.test.ts b/src/helpers/__tests__/get-blocked-url.test.ts new file mode 100644 index 0000000..fedb7d3 --- /dev/null +++ b/src/helpers/__tests__/get-blocked-url.test.ts @@ -0,0 +1,25 @@ +import * as getBlockedUrl from "../get-blocked-url"; + +test("getBlockedUrl() returns blocked url", () => { + jest.spyOn(getBlockedUrl, "__getBlockedHtmlUrl").mockReturnValue("/blocked.html"); + + expect(getBlockedUrl.default({ + rule: "youtube.com", + })).toBe("/blocked.html?rule=youtube.com"); + + expect(getBlockedUrl.default({ + rule: "youtube.com", + countParams: { + count: 42, + period: "ALL_TIME", + }, + })).toBe("/blocked.html?rule=youtube.com&count=42"); + + expect(getBlockedUrl.default({ + rule: "youtube.com", + countParams: { + count: 5, + period: "TODAY", + }, + })).toBe("/blocked.html?rule=youtube.com&count=5&period=TODAY"); +}); diff --git a/src/helpers/counter.ts b/src/helpers/counter.ts index d7a379d..da2ba8e 100644 --- a/src/helpers/counter.ts +++ b/src/helpers/counter.ts @@ -1,5 +1,6 @@ -import { Schema } from "./storage"; +import { Schema, CounterPeriod } from "../storage"; import makeRules from "./make-rules"; +import dayjs from "./dayjs"; export const flushObsoleteEntries = ({ blocked, counter }: Pick) => { const allBlockRulePaths = makeRules(blocked).filter((rule) => rule.type === "block").map((rule) => rule.path); @@ -9,10 +10,26 @@ export const flushObsoleteEntries = ({ blocked, counter }: Pick): number => { +export const counterPeriodToTimeStamp = (counterPeriod: CounterPeriod, now: number): number => { + switch (counterPeriod) { + case "ALL_TIME": + return 0; + case "THIS_MONTH": + return +dayjs(now).startOf("month"); + case "THIS_WEEK": + return +dayjs(now).startOf("week"); + case "TODAY": + return +dayjs(now).startOf("day"); + } +}; + +export const add = (rulePath: string, timeStamp: number, { counter, countFromTimeStamp }: Pick & { countFromTimeStamp: number }): number => { if (!(rulePath in counter)) { counter[rulePath] = []; } - return counter[rulePath].push(timeStamp); + counter[rulePath].push(timeStamp); + return (countFromTimeStamp === 0) + ? counter[rulePath].length + : counter[rulePath].filter((value) => value >= countFromTimeStamp).length; }; diff --git a/src/helpers/dayjs.ts b/src/helpers/dayjs.ts new file mode 100644 index 0000000..19b4302 --- /dev/null +++ b/src/helpers/dayjs.ts @@ -0,0 +1,9 @@ +import dayjs from "dayjs"; +import updateLocale from "dayjs/plugin/updateLocale"; +dayjs.extend(updateLocale); + +dayjs.updateLocale("en", { + weekStart: 1, // Monday +}); + +export default dayjs; diff --git a/src/helpers/get-blocked-message.ts b/src/helpers/get-blocked-message.ts new file mode 100644 index 0000000..f105f0b --- /dev/null +++ b/src/helpers/get-blocked-message.ts @@ -0,0 +1,21 @@ +import { CounterPeriod } from "../storage"; + +const periodStrings: Record = { + "ALL_TIME": "", + "THIS_MONTH": "this month", + "THIS_WEEK": "this week", + "TODAY": "today", +}; + +interface GetBlockedMessageParams { + rule: string + count?: string + period?: CounterPeriod +} + +export default ({ rule, count, period }: GetBlockedMessageParams): string => { + const periodString: string = period ? periodStrings[period] : ""; + return count + ? `${rule} was blocked ${count}x${periodString ? ` ${periodString}` : ""}.` + : `${rule} was blocked.`; +}; diff --git a/src/helpers/get-blocked-url.ts b/src/helpers/get-blocked-url.ts new file mode 100644 index 0000000..4b2cbe7 --- /dev/null +++ b/src/helpers/get-blocked-url.ts @@ -0,0 +1,23 @@ +import { CounterPeriod } from "../storage"; + +export const __getBlockedHtmlUrl = () => chrome.runtime.getURL("blocked.html"); + +interface GetBlockedUrlParams { + rule: string + countParams?: { + count: number + period: CounterPeriod + } +} + +export default ({ rule, countParams }: GetBlockedUrlParams): string => { + const params = new URLSearchParams({ rule }); + if (countParams) { + params.append("count", countParams.count.toString()); + if (countParams.period !== "ALL_TIME") { + params.append("period", countParams.period); + } + } + + return `${__getBlockedHtmlUrl()}?${params.toString()}`; +}; diff --git a/src/options.ts b/src/options.ts index 21f3b0f..cadf532 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,55 +1,99 @@ -import storage, { Schema } from "./helpers/storage"; - -const blockedList = document.getElementById("blocked-list") as HTMLTextAreaElement; -const resolutionSelect = document.getElementById("resolution-select") as HTMLSelectElement; -const enabledToggle = document.getElementById("enabled-toggle") as HTMLInputElement; - -blockedList.placeholder = [ - "facebook.com", - "instagram.com", - "youtube.com", - "!music.youtube.com", - "twitter.com", - "reddit.com", - "!reddit.com/r/MachineLearning", -].join("\n"); - -blockedList.addEventListener("change", (event) => { - const blocked = (event.target as HTMLTextAreaElement).value.split("\n").map((s) => s.trim()).filter(Boolean); - - storage.set>({ blocked }); -}); +import storage, { Schema, Resolution, CounterPeriod, RESOLUTIONS } from "./storage"; -resolutionSelect.addEventListener("change", (event) => { - const resolution = (event.target as HTMLSelectElement).value; +const UI = (() => { + const elements = { + blockedList: document.getElementById("blocked-list") as HTMLTextAreaElement, + enabled: document.getElementById("enabled") as HTMLSelectElement, + resolution: document.getElementById("resolution") as HTMLSelectElement, + counterShow: document.getElementById("counter-show") as HTMLSelectElement, + counterPeriod: document.getElementById("counter-period") as HTMLSelectElement, + }; - storage.set>({ resolution }); -}); + elements.blockedList.placeholder = [ + "facebook.com", + "instagram.com", + "youtube.com", + "!music.youtube.com", + "twitter.com", + "reddit.com", + "!reddit.com/r/MachineLearning", + ].join("\n"); -enabledToggle.addEventListener("change", (event) => { - const enabled = (event.target as HTMLInputElement).checked; + const booleanToString = (b: boolean) => b ? "YES" : "NO"; + const stringToBoolean = (s: string) => s === "YES"; - storage.set>({ enabled }); -}); + const getEventTargetValue = (event: Event) => (event.target as HTMLTextAreaElement | HTMLSelectElement).value; -window.addEventListener("DOMContentLoaded", () => { - storage.get(["enabled", "blocked", "resolution"], (local) => { - const { enabled, blocked, resolution } = local; - if (!Array.isArray(blocked)) { - return; + elements.blockedList.addEventListener("input", (event) => { + const blocked = getEventTargetValue(event).split("\n").map((s) => s.trim()).filter(Boolean); + storage.set>({ blocked }); + }); + + elements.enabled.addEventListener("change", (event) => { + const enabled = stringToBoolean(getEventTargetValue(event)); + storage.set>({ enabled }); + }); + + elements.resolution.addEventListener("change", (event) => { + const resolution = getEventTargetValue(event) as Resolution; + storage.set>({ resolution }); + }); + + elements.counterShow.addEventListener("change", (event) => { + const counterShow = stringToBoolean(getEventTargetValue(event)); + storage.set>({ counterShow }); + }); + + elements.counterPeriod.addEventListener("change", (event) => { + const counterPeriod = getEventTargetValue(event) as CounterPeriod; + storage.set>({ counterPeriod }); + }); + + const init = >(items: T) => { + if (items.blocked !== undefined) { + elements.blockedList.value = items.blocked.join("\r\n"); // display every blocked on a new line + } + + if (items.enabled !== undefined) { + elements.enabled.value = booleanToString(items.enabled); + } + + if (items.resolution !== undefined) { + elements.resolution.value = items.resolution; + RESOLUTIONS.forEach((oneResolution) => { + document.body.classList.remove(`resolution-${oneResolution}`); + }); + document.body.classList.add(`resolution-${items.resolution}`); + } + + if (items.counterShow !== undefined) { + elements.counterShow.value = booleanToString(items.counterShow); + document.body.classList.toggle("counter-show", items.counterShow); } - // blocked - const value = (blocked as string[]).join("\r\n"); // display every blocked in new line - blockedList.value = value; + if (items.counterPeriod !== undefined) { + elements.counterPeriod.value = items.counterPeriod; + } + }; - // resolution - resolutionSelect.value = resolution; + return { elements, init }; +})(); - // enabled - enabledToggle.checked = enabled; +window.addEventListener("DOMContentLoaded", () => { + const keys: (keyof Schema)[] = ["blocked", "enabled", "resolution", "counterShow", "counterPeriod"]; - // UI ready + storage.get(keys, (local) => { + UI.init(local); document.body.classList.add("ready"); }); + + chrome.storage.onChanged.addListener((changes, namespace) => { + if (namespace === "local") { + keys.forEach((key) => { + if (changes[key]) { + UI.init({ [key]: changes[key].newValue }); + } + }); + } + }); }); diff --git a/src/storage/__tests__/init.test.ts b/src/storage/__tests__/init.test.ts new file mode 100644 index 0000000..0168c8b --- /dev/null +++ b/src/storage/__tests__/init.test.ts @@ -0,0 +1,36 @@ +import { getRevisitedSchema } from "../init"; +import { Schema, DEFAULTS } from "../schema"; + +test("getRevisitedSchema() returns defaults for any invalid attribute", () => { + expect(getRevisitedSchema({})).toEqual(DEFAULTS); + + expect(getRevisitedSchema(DEFAULTS)).toEqual({}); + + expect(getRevisitedSchema({ + enabled: DEFAULTS.enabled, + blocked: DEFAULTS.blocked, + })).toEqual({ + counter: DEFAULTS.counter, + counterShow: DEFAULTS.counterShow, + counterPeriod: DEFAULTS.counterPeriod, + resolution: "CLOSE_TAB", + } as Partial); + + expect(getRevisitedSchema({ + ...DEFAULTS, + enabled: "YES", // invalid + })).toEqual({ + enabled: DEFAULTS.enabled, + } as Partial); + + expect(getRevisitedSchema({ + ...DEFAULTS, + enabled: "YES", // invalid + blocked: "ALL", // invalid + resolution: "BLOCK", // invalid + })).toEqual({ + enabled: DEFAULTS.enabled, + blocked: DEFAULTS.blocked, + resolution: DEFAULTS.resolution, + } as Partial); +}); diff --git a/src/helpers/storage.ts b/src/storage/index.ts similarity index 58% rename from src/helpers/storage.ts rename to src/storage/index.ts index 8fde30d..9ee3c89 100644 --- a/src/helpers/storage.ts +++ b/src/storage/index.ts @@ -1,9 +1,6 @@ -export interface Schema { - enabled: boolean - blocked: string[] - counter: Record - resolution: string -} +import { Schema } from "./schema"; + +export * from "./schema"; const set = >(items: T) => { chrome.storage.local.set(items); @@ -13,7 +10,11 @@ const get = (keys: T[], callback: (items: Pick callback(items as Pick)); }; +const getAll = (callback: (items: Schema) => void) => + get(["enabled", "blocked", "counter", "counterShow", "counterPeriod", "resolution"], callback); + export default { set, get, + getAll, }; diff --git a/src/storage/init.ts b/src/storage/init.ts new file mode 100644 index 0000000..23a324e --- /dev/null +++ b/src/storage/init.ts @@ -0,0 +1,25 @@ +import storage, { Schema, DEFAULTS, VALIDATORS } from "."; + +export const getRevisitedSchema = (local: Partial | Record) => { + const revisitedSchema: Partial = {}; + const update = (key: K, value: Schema[K]) => revisitedSchema[key] = value; + + Object.keys(DEFAULTS).forEach((key) => { + const schemaKey = key as keyof Schema; + const isValid = VALIDATORS[schemaKey](local[schemaKey]); + if (!isValid) { + update(schemaKey, DEFAULTS[schemaKey]); + } + }); + + return revisitedSchema; +}; + +export default () => { + storage.getAll((local) => { + const revisitedSchema = getRevisitedSchema(local); + if (Object.keys(revisitedSchema).length) { + storage.set(revisitedSchema); + } + }); +}; diff --git a/src/storage/schema.ts b/src/storage/schema.ts new file mode 100644 index 0000000..8977f06 --- /dev/null +++ b/src/storage/schema.ts @@ -0,0 +1,41 @@ +export const RESOLUTIONS = [ + "CLOSE_TAB", + "SHOW_BLOCKED_INFO_PAGE", +] as const; + +export const COUNTER_PERIODS = [ + "ALL_TIME", + "THIS_MONTH", + "THIS_WEEK", + "TODAY", +] as const; + +export type Resolution = typeof RESOLUTIONS[number]; +export type CounterPeriod = typeof COUNTER_PERIODS[number]; + +export interface Schema { + enabled: boolean + blocked: string[] + counter: Record + counterShow: boolean + counterPeriod: CounterPeriod + resolution: Resolution +} + +export const DEFAULTS: Readonly = { + enabled: false, + blocked: [], + counter: {}, + counterShow: false, + counterPeriod: "ALL_TIME", + resolution: "CLOSE_TAB", +}; + +export const VALIDATORS: Readonly boolean>> = { + enabled: (value) => typeof value === "boolean", + blocked: (value) => Array.isArray(value), + counter: (value) => typeof value === "object", + counterShow: (value) => typeof value === "boolean", + counterPeriod: (value) => COUNTER_PERIODS.includes(value as CounterPeriod), + resolution: (value) => RESOLUTIONS.includes(value as Resolution), +}; diff --git a/tsup.config.ts b/tsup.config.ts index a0f77f8..76de772 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -9,4 +9,8 @@ export default defineConfig({ ], target: "es2022", format: "esm", + noExternal: ["dayjs"], + esbuildOptions(options) { + options.chunkNames = "chunks/[name]-[hash]"; + } });