Compare commits
No commits in common. "main" and "8.0.1" have entirely different histories.
4
.gitignore
vendored
@ -1,7 +1,9 @@
|
||||
_metadata/
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
.DS_Store
|
||||
.env
|
||||
.plasmo/
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
build/
|
||||
node_modules/
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
pnpm commitlint --edit "$1"
|
||||
yarn commitlint --edit "$1"
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
pnpm lint-staged
|
||||
yarn lint-staged
|
||||
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"packages/**/*.{js,ts}": ["prettier --loglevel=silent --write", "bash -c 'pnpm lint'"]
|
||||
"packages/**/*.{js,ts}": ["prettier --loglevel=silent --write", "bash -c 'yarn lint'"]
|
||||
}
|
||||
|
2
.prettierignore
Normal file
@ -0,0 +1,2 @@
|
||||
package.json
|
||||
.yarnrc.yml
|
@ -1,12 +1,4 @@
|
||||
{
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.jsonc", ".eslintrc", "tsconfig*.json"],
|
||||
"options": {
|
||||
"trailingComma": "none"
|
||||
}
|
||||
}
|
||||
],
|
||||
"printWidth": 100,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
|
3
.vscode/settings.json
vendored
@ -1,7 +1,4 @@
|
||||
{
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "always"
|
||||
},
|
||||
"files.associations": {
|
||||
".commitlintrc": "json",
|
||||
".lintstagedrc": "json"
|
||||
|
33
.yarn/plugins/@yarnpkg/plugin-outdated.cjs
vendored
Normal file
894
.yarn/releases/yarn-4.2.1.cjs
vendored
Executable file
9
.yarnrc.yml
Normal file
@ -0,0 +1,9 @@
|
||||
enableGlobalCache: true
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-outdated.cjs
|
||||
spec: "https://mskelton.dev/yarn-outdated/v2"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.2.1.cjs
|
28
README.md
@ -1,6 +1,24 @@
|
||||
> AN IMPORTANT UPDATE
|
||||
> <br><br>
|
||||
> On the morning of October 11, 2024, GitHub unexpectedly hid our repository without any prior notification. This sudden action immediately disrupted all of our API services, as they relied on files from the repository that were no longer accessible. Our team reached out to GitHub support to understand the situation, but we have not yet received any response. Given the critical impact on our services and the lack of communication from GitHub, we decided to migrate the entire repository to an alternative platform to ensure continuity and reliability.
|
||||
> <br><br>
|
||||
> We initially migrated the repository, releases, and open issues (excluding discussions) to GitLab. However, during the migration of issues, GitLab's own spam detection mechanism mistakenly identified its bot activity as spam, leading to our issues being hidden. Despite reaching out to GitLab support, we have not yet received a resolution for this incident. Faced with these ongoing platform limitations and communication delays, we have opted to move forward with self-hosting our own Git system using Gitea to ensure full control over our repository and services.
|
||||
> <br><br>
|
||||
> We will no longer support platforms that overlook the contributions of our users and the significant work invested over the last five years. Once our GitHub account is reinstated, we will set up a redirect to guide users to our new, self-hosted repository.
|
||||
> <br><br>
|
||||
> Thank you to our community for your patience. Your contributions remain vital to this project, and we are committed to ensuring its stability and growth in a more secure environment.
|
||||
|
||||
# Cookie Dialog Monster
|
||||
|
||||
Cookie Dialog Monster is a browser extension that hides cookie consent dialogs without changing user preferences. By default, we do NOT accept cookies (except in [a few cases](https://git.wanhose.dev/wanhose/cookie-dialog-monster/src/branch/main/database.json) where the pages do not function without accepting them). You can report broken sites with a single click, which will create an issue in this repository to be fixed promptly.
|
||||
Cookie Dialog Monster is a browser extension that hides cookie consent dialogs without changing user preferences. By default, we do NOT accept cookies (except in [a few cases](https://git.wanhose.dev/wanhose/cookie-dialog-monster/src/branch/main/database.json#L248) where the pages do not function without accepting them). You can report broken sites with a single click, which will create an issue in this repository to be fixed promptly.
|
||||
|
||||
## Contributing
|
||||
|
||||
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://choosealicense.com/licenses/mit/)
|
||||
|
||||
## Repositories
|
||||
|
||||
@ -16,11 +34,3 @@ Cookie Dialog Monster is a browser extension that hides cookie consent dialogs w
|
||||
- [Pull requests](https://git.wanhose.dev/wanhose/cookie-dialog-monster/pulls)
|
||||
- [Releases](https://git.wanhose.dev/wanhose/cookie-dialog-monster/releases)
|
||||
- [Wiki](https://git.wanhose.dev/wanhose/cookie-dialog-monster/wiki/Help-or-issues%3F)
|
||||
|
||||
## Contributing
|
||||
|
||||
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://choosealicense.com/licenses/mit/)
|
||||
|
793
database.json
@ -3,9 +3,8 @@
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"build": "pnpm -r run build",
|
||||
"lint": "pnpm -r run lint",
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"build": "yarn workspaces foreach --all -p run build",
|
||||
"lint": "yarn workspaces foreach --all -p run lint",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -15,8 +14,12 @@
|
||||
"lint-staged": "^15.2.2",
|
||||
"prettier": "^3.2.5"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"engines": {
|
||||
"node": "20.x"
|
||||
},
|
||||
"packageManager": "yarn@4.2.1",
|
||||
"license": "MIT"
|
||||
}
|
||||
|
@ -2,26 +2,26 @@
|
||||
|
||||
## Installation
|
||||
|
||||
Make sure you have [Node.js](https://nodejs.org/) (version 20.x) and [pnpm](https://pnpm.io/) installed.
|
||||
Make sure you have [Node.js](https://nodejs.org/) (version 20.x) and [Yarn](https://yarnpkg.com/) installed.
|
||||
|
||||
```bash
|
||||
pnpm i
|
||||
yarn install
|
||||
```
|
||||
|
||||
## Scripts
|
||||
|
||||
### `pnpm build`
|
||||
### `yarn build`
|
||||
|
||||
Removes the build directory and compiles TypeScript files.
|
||||
|
||||
### `pnpm dev`
|
||||
### `yarn dev`
|
||||
|
||||
Starts the server in development mode with nodemon.
|
||||
|
||||
### `pnpm lint`
|
||||
### `yarn lint`
|
||||
|
||||
Lints the codebase using ESLint.
|
||||
|
||||
### `pnpm start`
|
||||
### `yarn start`
|
||||
|
||||
Starts the API server instance in production mode.
|
||||
|
@ -33,5 +33,6 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": "20.x"
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@4.2.1"
|
||||
}
|
||||
|
@ -12,12 +12,7 @@ import v4ReportRoutes from 'routes/v4/report';
|
||||
import v5DataRoutes from 'routes/v5/data';
|
||||
import v5IssuesRoutes from 'routes/v5/issues';
|
||||
import v5ReportRoutes from 'routes/v5/report';
|
||||
import v6DataRoutes from 'routes/v6/data';
|
||||
import v6IssuesRoutes from 'routes/v6/issues';
|
||||
import v6ReportRoutes from 'routes/v6/report';
|
||||
import v6VersionRoutes from 'routes/v6/version';
|
||||
import environment from 'services/environment';
|
||||
import { keyGenerator } from 'services/rateLimit';
|
||||
|
||||
const server = fastify({ logger: true });
|
||||
|
||||
@ -31,7 +26,6 @@ server.register(cors, {
|
||||
|
||||
server.register(rateLimit, {
|
||||
global: false,
|
||||
keyGenerator,
|
||||
});
|
||||
|
||||
server.register(v1EntriesRoutes, { prefix: '/rest/v1' });
|
||||
@ -45,10 +39,6 @@ server.register(v4ReportRoutes, { prefix: '/rest/v4' });
|
||||
server.register(v5DataRoutes, { prefix: '/rest/v5' });
|
||||
server.register(v5IssuesRoutes, { prefix: '/rest/v5' });
|
||||
server.register(v5ReportRoutes, { prefix: '/rest/v5' });
|
||||
server.register(v6DataRoutes, { prefix: '/rest/v6' });
|
||||
server.register(v6IssuesRoutes, { prefix: '/rest/v6' });
|
||||
server.register(v6ReportRoutes, { prefix: '/rest/v6' });
|
||||
server.register(v6VersionRoutes, { prefix: '/rest/v6' });
|
||||
|
||||
server.listen({ host: '0.0.0.0', port: environment.port }, (error, address) => {
|
||||
if (error) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FastifyInstance, RouteShorthandOptions } from 'fastify';
|
||||
import fetch from 'node-fetch';
|
||||
import { parseAction } from 'services/compatibility';
|
||||
import { parseNewFix } from 'services/compatibility';
|
||||
import environment from 'services/environment';
|
||||
import { RATE_LIMIT_10_PER_MIN } from 'services/rateLimit';
|
||||
|
||||
@ -14,18 +14,17 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done:
|
||||
},
|
||||
async (_request, reply) => {
|
||||
try {
|
||||
const database = `${environment.gitea.raw}/database.json`;
|
||||
const response = await fetch(database);
|
||||
const { actions, exclusions, keywords, tokens } = await response.json();
|
||||
const url = `${environment.gitea.raw}/database.json`;
|
||||
const result = await (await fetch(url)).json();
|
||||
|
||||
reply.send({
|
||||
data: {
|
||||
classes: tokens.classes,
|
||||
commonWords: keywords,
|
||||
elements: tokens.selectors,
|
||||
fixes: actions.map(parseAction),
|
||||
skips: exclusions.overflows,
|
||||
tags: exclusions.tags,
|
||||
classes: result.tokens.classes,
|
||||
commonWords: result.commonWords,
|
||||
elements: result.tokens.selectors,
|
||||
fixes: result.fixes.map(parseNewFix),
|
||||
skips: result.skips.domains,
|
||||
tags: result.skips.tags,
|
||||
},
|
||||
success: true,
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FastifyInstance, RouteShorthandOptions } from 'fastify';
|
||||
import { formatMessage, formatDomainFromURL } from 'services/format';
|
||||
import { formatMessage } from 'services/format';
|
||||
import { createIssue, createIssueComment, getIssue, updateIssue } from 'services/git';
|
||||
import { RATE_LIMIT_1_PER_MIN } from 'services/rateLimit';
|
||||
import { validatorCompiler } from 'services/validation';
|
||||
@ -34,8 +34,8 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done:
|
||||
async (request, reply) => {
|
||||
try {
|
||||
const { reason, url, userAgent, version } = request.body;
|
||||
const domain = formatDomainFromURL(new URL(url));
|
||||
const issue = await getIssue({ title: domain });
|
||||
const hostname = new URL(url).hostname.split('.').slice(-3).join('.').replace('www.', '');
|
||||
const issue = await getIssue({ title: hostname });
|
||||
const ua = new UAParser(userAgent ?? '').getResult();
|
||||
|
||||
if (issue) {
|
||||
@ -61,7 +61,7 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done:
|
||||
await createIssue({
|
||||
description: formatMessage({ reason, ua, url, version }),
|
||||
labels: ['bug'],
|
||||
title: domain,
|
||||
title: hostname,
|
||||
});
|
||||
|
||||
reply.send({
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FastifyInstance, RouteShorthandOptions } from 'fastify';
|
||||
import fetch from 'node-fetch';
|
||||
import { parseAction } from 'services/compatibility';
|
||||
import { parseNewFix } from 'services/compatibility';
|
||||
import environment from 'services/environment';
|
||||
import { RATE_LIMIT_3_PER_MIN } from 'services/rateLimit';
|
||||
|
||||
@ -14,16 +14,13 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done:
|
||||
},
|
||||
async (_request, reply) => {
|
||||
try {
|
||||
const database = `${environment.gitea.raw}/database.json`;
|
||||
const response = await fetch(database);
|
||||
const { actions, exclusions, keywords, ...rest } = await response.json();
|
||||
const url = `${environment.gitea.raw}/database.json`;
|
||||
const result = await (await fetch(url)).json();
|
||||
|
||||
reply.send({
|
||||
data: {
|
||||
...rest,
|
||||
commonWords: keywords,
|
||||
fixes: actions.map(parseAction),
|
||||
skips: { domains: exclusions.overflows, tags: exclusions.tags },
|
||||
...result,
|
||||
fixes: result.fixes.map(parseNewFix),
|
||||
},
|
||||
success: true,
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FastifyInstance, RouteShorthandOptions } from 'fastify';
|
||||
import { formatMessage, formatDomainFromURL } from 'services/format';
|
||||
import { formatMessage } from 'services/format';
|
||||
import { createIssue, createIssueComment, getIssue, updateIssue } from 'services/git';
|
||||
import { RATE_LIMIT_1_PER_MIN } from 'services/rateLimit';
|
||||
import { validatorCompiler } from 'services/validation';
|
||||
@ -34,8 +34,8 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done:
|
||||
async (request, reply) => {
|
||||
try {
|
||||
const { reason, url, userAgent, version } = request.body;
|
||||
const domain = formatDomainFromURL(new URL(url));
|
||||
const issue = await getIssue({ title: domain });
|
||||
const hostname = new URL(url).hostname.split('.').slice(-3).join('.').replace('www.', '');
|
||||
const issue = await getIssue({ title: hostname });
|
||||
const ua = new UAParser(userAgent ?? '').getResult();
|
||||
|
||||
if (issue) {
|
||||
@ -62,7 +62,7 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done:
|
||||
const newIssue = await createIssue({
|
||||
description: formatMessage({ reason, ua, url, version }),
|
||||
labels: ['bug'],
|
||||
title: domain,
|
||||
title: hostname,
|
||||
});
|
||||
|
||||
reply.send({
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { FastifyInstance, RouteShorthandOptions } from 'fastify';
|
||||
import fetch from 'node-fetch';
|
||||
import { parseActionName, toDeclarativeNetRequestRule } from 'services/compatibility';
|
||||
import environment from 'services/environment';
|
||||
import { RATE_LIMIT_3_PER_MIN } from 'services/rateLimit';
|
||||
|
||||
@ -14,18 +13,14 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done:
|
||||
},
|
||||
async (_request, reply) => {
|
||||
try {
|
||||
const database = `${environment.gitea.raw}/database.json`;
|
||||
const options = { headers: { 'Cache-Control': 'no-cache' } };
|
||||
const response = await fetch(database, options);
|
||||
const { actions, exclusions, keywords, rules, ...rest } = await response.json();
|
||||
const url = `${environment.gitea.raw}/database.json`;
|
||||
const { rules, ...result } = await (await fetch(url, options)).json();
|
||||
|
||||
reply.send({
|
||||
data: {
|
||||
...rest,
|
||||
actions: actions.map(parseActionName),
|
||||
commonWords: keywords,
|
||||
...result,
|
||||
rules: rules.map(toDeclarativeNetRequestRule),
|
||||
skips: { domains: exclusions.overflows, tags: exclusions.tags },
|
||||
},
|
||||
success: true,
|
||||
});
|
||||
@ -40,3 +35,17 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done:
|
||||
|
||||
done();
|
||||
};
|
||||
|
||||
function toDeclarativeNetRequestRule(urlFilter: string, index: number) {
|
||||
return {
|
||||
action: {
|
||||
type: 'block',
|
||||
},
|
||||
condition: {
|
||||
resourceTypes: ['font', 'image', 'media', 'object', 'script', 'stylesheet', 'xmlhttprequest'],
|
||||
urlFilter,
|
||||
},
|
||||
id: index + 1,
|
||||
priority: 1,
|
||||
};
|
||||
}
|
||||
|
@ -5,14 +5,14 @@ import { validatorCompiler } from 'services/validation';
|
||||
import * as yup from 'yup';
|
||||
|
||||
const GetIssuesParamsSchema = yup.object().shape({
|
||||
domain: yup.string().required(),
|
||||
hostname: yup.string().required(),
|
||||
});
|
||||
|
||||
type GetIssuesParams = yup.InferType<typeof GetIssuesParamsSchema>;
|
||||
|
||||
export default (server: FastifyInstance, _options: RouteShorthandOptions, done: () => void) => {
|
||||
server.get<{ Params: GetIssuesParams }>(
|
||||
'/issues/:domain/',
|
||||
'/issues/:hostname/',
|
||||
{
|
||||
config: {
|
||||
rateLimit: RATE_LIMIT_10_PER_MIN,
|
||||
@ -24,8 +24,8 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done:
|
||||
},
|
||||
async (request, reply) => {
|
||||
try {
|
||||
const { domain } = request.params;
|
||||
const issue = await getIssue({ title: domain });
|
||||
const { hostname } = request.params;
|
||||
const issue = await getIssue({ title: hostname });
|
||||
|
||||
if (
|
||||
issue &&
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FastifyInstance, RouteShorthandOptions } from 'fastify';
|
||||
import { formatMessage, formatDomainFromURL } from 'services/format';
|
||||
import { formatMessage } from 'services/format';
|
||||
import { createIssue, getIssue, updateIssue } from 'services/git';
|
||||
import { RATE_LIMIT_1_PER_MIN } from 'services/rateLimit';
|
||||
import { validatorCompiler } from 'services/validation';
|
||||
@ -34,8 +34,8 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done:
|
||||
async (request, reply) => {
|
||||
try {
|
||||
const { reason, url, userAgent, version } = request.body;
|
||||
const domain = formatDomainFromURL(new URL(url));
|
||||
const issue = await getIssue({ title: domain });
|
||||
const hostname = new URL(url).hostname.split('.').slice(-3).join('.').replace('www.', '');
|
||||
const issue = await getIssue({ title: hostname });
|
||||
const ua = new UAParser(userAgent ?? '').getResult();
|
||||
|
||||
if (issue) {
|
||||
@ -75,7 +75,7 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done:
|
||||
const newIssue = await createIssue({
|
||||
description: formatMessage({ reason, ua, url, version }),
|
||||
labels: ['bug'],
|
||||
title: domain,
|
||||
title: hostname,
|
||||
});
|
||||
|
||||
reply.send({
|
||||
|
@ -1,39 +0,0 @@
|
||||
import { FastifyInstance, RouteShorthandOptions } from 'fastify';
|
||||
import fetch from 'node-fetch';
|
||||
import { toDeclarativeNetRequestRule } from 'services/compatibility';
|
||||
import environment from 'services/environment';
|
||||
import { RATE_LIMIT_3_PER_MIN } from 'services/rateLimit';
|
||||
|
||||
export default (server: FastifyInstance, _options: RouteShorthandOptions, done: () => void) => {
|
||||
server.get(
|
||||
'/data/',
|
||||
{
|
||||
config: {
|
||||
rateLimit: RATE_LIMIT_3_PER_MIN,
|
||||
},
|
||||
},
|
||||
async (_request, reply) => {
|
||||
try {
|
||||
const database = `${environment.gitea.raw}/database.json`;
|
||||
const options = { headers: { 'Cache-Control': 'no-cache' } };
|
||||
const response = await fetch(database, options);
|
||||
const { rules, ...rest } = await response.json();
|
||||
|
||||
reply.send({
|
||||
data: {
|
||||
...rest,
|
||||
rules: rules.map(toDeclarativeNetRequestRule),
|
||||
},
|
||||
success: true,
|
||||
});
|
||||
} catch (error) {
|
||||
reply.send({
|
||||
errors: [error.message],
|
||||
success: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
done();
|
||||
};
|
@ -1 +0,0 @@
|
||||
export { default as default } from '../v5/issues';
|
@ -1 +0,0 @@
|
||||
export { default as default } from '../v5/report';
|
@ -1,35 +0,0 @@
|
||||
import { FastifyInstance, RouteShorthandOptions } from 'fastify';
|
||||
import fetch from 'node-fetch';
|
||||
import environment from 'services/environment';
|
||||
import { RATE_LIMIT_10_PER_MIN } from 'services/rateLimit';
|
||||
|
||||
export default (server: FastifyInstance, _options: RouteShorthandOptions, done: () => void) => {
|
||||
server.get(
|
||||
'/version/',
|
||||
{
|
||||
config: {
|
||||
rateLimit: RATE_LIMIT_10_PER_MIN,
|
||||
},
|
||||
},
|
||||
async (_request, reply) => {
|
||||
try {
|
||||
const manifest = `${environment.gitea.raw}/packages/browser-extension/src/manifest.json`;
|
||||
const options = { headers: { 'Cache-Control': 'no-cache' } };
|
||||
const response = await fetch(manifest, options);
|
||||
const { version } = await response.json();
|
||||
|
||||
reply.send({
|
||||
data: version,
|
||||
success: true,
|
||||
});
|
||||
} catch (error) {
|
||||
reply.send({
|
||||
errors: [error.message],
|
||||
success: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
done();
|
||||
};
|
@ -1,32 +1,13 @@
|
||||
export function parseAction(action: Action): string {
|
||||
return `${action.domain}##${action.selector}##${action.name}${action.property ? `##${action.property}` : ''}`;
|
||||
/**
|
||||
* Parse the new fix object into the old string format used by older versions of the extension
|
||||
*/
|
||||
export function parseNewFix(fix: Fix): string {
|
||||
return `${fix.domain}##${fix.selector}##${fix.action}${fix.property ? `##${fix.property}` : ''}`;
|
||||
}
|
||||
|
||||
export function parseActionName(
|
||||
action: Action
|
||||
): Omit<Action, 'name'> & { readonly action: string } {
|
||||
const { name, ...rest } = action;
|
||||
|
||||
return { action: name, ...rest };
|
||||
}
|
||||
|
||||
export function toDeclarativeNetRequestRule(urlFilter: string, index: number) {
|
||||
return {
|
||||
action: {
|
||||
type: 'block',
|
||||
},
|
||||
condition: {
|
||||
resourceTypes: ['font', 'image', 'media', 'object', 'script', 'stylesheet', 'xmlhttprequest'],
|
||||
urlFilter,
|
||||
},
|
||||
id: index + 1,
|
||||
priority: 1,
|
||||
};
|
||||
}
|
||||
|
||||
export interface Action {
|
||||
export interface Fix {
|
||||
readonly action: string;
|
||||
readonly domain: string;
|
||||
readonly name: string;
|
||||
readonly property?: string;
|
||||
readonly selector: string;
|
||||
}
|
||||
|
@ -20,16 +20,6 @@ export function formatMessage(params: FormatMessageParams): string {
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function formatDomainFromURL(value: URL): string {
|
||||
let result: string = value.hostname;
|
||||
|
||||
if (result.startsWith('www.')) {
|
||||
result = result.replace('www.', '');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface FormatMessageParams {
|
||||
readonly reason?: string;
|
||||
readonly ua: UAParserResult;
|
||||
|
@ -1,5 +1,3 @@
|
||||
import type { FastifyRequest } from 'fastify';
|
||||
|
||||
export const RATE_LIMIT_1_PER_HOUR = {
|
||||
max: 1,
|
||||
timeWindow: '1 hour',
|
||||
@ -19,9 +17,3 @@ export const RATE_LIMIT_3_PER_MIN = {
|
||||
max: 3,
|
||||
timeWindow: '1 minute',
|
||||
};
|
||||
|
||||
export function keyGenerator(req: FastifyRequest): string {
|
||||
const userIdentifier = req.headers['x-forwarded-for'] || req.headers['x-real-ip'] || req.ip;
|
||||
|
||||
return `${userIdentifier}:${req.routerPath}`;
|
||||
}
|
||||
|
@ -1,41 +1,9 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:jest-dom/recommended",
|
||||
"plugin:jsx-a11y/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:react/jsx-runtime",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:testing-library/dom"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": ["plugin:prettier/recommended"],
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["import", "simple-import-sort"],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"import/first": "error",
|
||||
"import/newline-after-import": "error",
|
||||
"import/no-duplicates": "error",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error"
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
"ecmaVersion": "latest"
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,7 @@
|
||||
|
||||
- [Chrome Web Store](https://chrome.google.com/webstore/detail/djcbfpkdhdkaflcigibkbpboflaplabg)
|
||||
- [Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/hbogodfciblakeneadpcolhmfckmjcii)
|
||||
- [Mozilla Firefox](https://www.cookie-dialog-monster.com/releases/mozilla/latest.xpi)
|
||||
- [Mozilla Firefox Mobile](https://www.cookie-dialog-monster.com/releases/mozilla-mobile/latest.xpi)
|
||||
- [Mozilla Firefox (.xpi)](https://www.cookie-dialog-monster.com/releases/latest.xpi)
|
||||
|
||||
## Installation
|
||||
|
||||
|
Before Width: | Height: | Size: 21 KiB |
@ -1,25 +0,0 @@
|
||||
import { createRequire } from 'module';
|
||||
import { pathsToModuleNameMapper } from 'ts-jest';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const tsconfig = require('./tsconfig.json');
|
||||
|
||||
/**
|
||||
* @type {import('@jest/types').Config.InitialOptions}
|
||||
*/
|
||||
const config = {
|
||||
extensionsToTreatAsEsm: ['.ts', '.tsx'],
|
||||
moduleNameMapper: {
|
||||
...pathsToModuleNameMapper(tsconfig.compilerOptions.paths, { prefix: '<rootDir>/' }),
|
||||
'^url:~assets/(.+).png$': '<rootDir>/mocks/assets/$1.mock.ts',
|
||||
},
|
||||
preset: 'ts-jest/presets/default-esm',
|
||||
setupFiles: ['./jest.setup.ts'],
|
||||
testEnvironment: 'jsdom',
|
||||
testRegex: ['^.+\\.test.tsx?$'],
|
||||
transform: {
|
||||
'^.+.tsx?$': ['ts-jest', { isolatedModules: true, useESM: true }],
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
@ -1,5 +0,0 @@
|
||||
import 'jest-webextension-mock';
|
||||
import './mocks/chrome.mock';
|
||||
import './mocks/plasmo.mock';
|
||||
import './mocks/utils/domain.mock';
|
||||
import './mocks/utils/storage.mock';
|
@ -1,98 +0,0 @@
|
||||
{
|
||||
"appDesc": {
|
||||
"message": "هل ذكر أحد حوارات موافقة ملفات تعريف الارتباط؟ 😋"
|
||||
},
|
||||
"contextMenu_issueOption": {
|
||||
"message": "متابعة هذه المشكلة"
|
||||
},
|
||||
"contextMenu_reportOption": {
|
||||
"message": "الإبلاغ عن هذا الموقع"
|
||||
},
|
||||
"contextMenu_settingsOption": {
|
||||
"message": "إدارة قائمة الاستبعاد"
|
||||
},
|
||||
"options_addButton": {
|
||||
"message": "إضافة استبعاد"
|
||||
},
|
||||
"options_addPrompt": {
|
||||
"message": "أدخل النطاق"
|
||||
},
|
||||
"options_clearButton": {
|
||||
"message": "مسح القائمة"
|
||||
},
|
||||
"options_empty": {
|
||||
"message": "لم يتم العثور على استثناءات"
|
||||
},
|
||||
"options_exclusionListTitle": {
|
||||
"message": "قائمة الاستبعاد"
|
||||
},
|
||||
"options_exportButton": {
|
||||
"message": "تصدير القائمة"
|
||||
},
|
||||
"options_filterPlaceholder": {
|
||||
"message": "اضغط ENTER للتصفية بعد الكتابة"
|
||||
},
|
||||
"options_importButton": {
|
||||
"message": "استيراد القائمة"
|
||||
},
|
||||
"popup_bannerIssueOpen": {
|
||||
"message": "تم الإبلاغ عن مشكلة في هذه الصفحة. قد لا تعمل الإضافة بشكل متوقع مؤقتًا. تابع المشكلة أو أضف المزيد من المعلومات"
|
||||
},
|
||||
"popup_bannerIssueWontFix": {
|
||||
"message": "تم الإبلاغ عن مشكلة في هذه الصفحة ولكن لن يتم إصلاحها. يُرجى العلم أن الإضافة قد لا تعمل كما هو متوقع في هذه الصفحة. نوصي بشدة بإيقاف تشغيل الإضافة باستخدام الزر أدناه"
|
||||
},
|
||||
"popup_bannerSupport": {
|
||||
"message": "لم يعد هذا الموقع مدعومًا. لمزيد من المعلومات، يرجى المتابعة هنا"
|
||||
},
|
||||
"popup_bannerUpdateAvailable": {
|
||||
"message": "التحديث متاح. يرجى تثبيت أحدث إصدار لحل المشكلات المعروفة قبل الإبلاغ عن مشكلات جديدة"
|
||||
},
|
||||
"popup_contributeOption": {
|
||||
"message": "ساهم في هذا المشروع"
|
||||
},
|
||||
"popup_databaseVersion": {
|
||||
"message": "إصدار قاعدة البيانات"
|
||||
},
|
||||
"popup_extensionVersion": {
|
||||
"message": "إصدار الإضافة"
|
||||
},
|
||||
"popup_helpOption": {
|
||||
"message": "المساعدة أو المشاكل؟"
|
||||
},
|
||||
"popup_rateOption": {
|
||||
"message": "قيّم هذه الإضافة"
|
||||
},
|
||||
"report_bodyText": {
|
||||
"message": "يرجى عدم مشاركة أي معلومات شخصية في هذا التقرير. إذا كنت تريد إضافة المزيد من التفاصيل، انتقل إلى المشكلة في الخطوة التالية وأضف تعليقًا."
|
||||
},
|
||||
"report_cancelButtonText": {
|
||||
"message": "إلغاء"
|
||||
},
|
||||
"report_reasonInputError": {
|
||||
"message": "يرجى إدخال سبب يتكون من 10 أحرف على الأقل ولا يزيد عن 1000 حرف"
|
||||
},
|
||||
"report_reasonInputLabel": {
|
||||
"message": "السبب"
|
||||
},
|
||||
"report_reasonInputPlaceholder": {
|
||||
"message": "ظهر إشعار"
|
||||
},
|
||||
"report_submitErrorExtraText": {
|
||||
"message": "يبدو أن هناك مشكلة مفتوحة بالفعل أو تم تحديدها بأنها لن يتم إصلاحها. يرجى الدخول إلى المشكلة إذا كنت ترغب في إضافة المزيد من المعلومات باستخدام الزر التالي."
|
||||
},
|
||||
"report_submitErrorText": {
|
||||
"message": "لم يتم إرسال التقرير؟"
|
||||
},
|
||||
"report_submitSuccessExtraText": {
|
||||
"message": "بينما نعمل على هذه المسألة، يمكنك إضافة هذا الموقع إلى قائمة الاستبعاد في إعدادات الإضافة أو تعطيل الإضافة لهذا الموقع بالنقر فوق أيقونة الإضافة في شريط الأدوات الخاص بالمتصفح"
|
||||
},
|
||||
"report_submitSuccessText": {
|
||||
"message": "تم إرسال التقرير!"
|
||||
},
|
||||
"report_urlInputError": {
|
||||
"message": "يرجى إدخال عنوان URL صالح لا يزيد عن 1000 حرف"
|
||||
},
|
||||
"report_urlInputLabel": {
|
||||
"message": "URL"
|
||||
}
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
{
|
||||
"appDesc": {
|
||||
"message": "क्या किसी ने कुकी सहमति संवाद का उल्लेख किया? 😋"
|
||||
},
|
||||
"contextMenu_issueOption": {
|
||||
"message": "इस समस्या का पालन करें"
|
||||
},
|
||||
"contextMenu_reportOption": {
|
||||
"message": "इस वेबसाइट की रिपोर्ट करें"
|
||||
},
|
||||
"contextMenu_settingsOption": {
|
||||
"message": "बहिष्करण सूची प्रबंधित करें"
|
||||
},
|
||||
"options_addButton": {
|
||||
"message": "बहिष्करण जोड़ें"
|
||||
},
|
||||
"options_addPrompt": {
|
||||
"message": "डोमेन दर्ज करें"
|
||||
},
|
||||
"options_clearButton": {
|
||||
"message": "सूची साफ़ करें"
|
||||
},
|
||||
"options_empty": {
|
||||
"message": "कोई बहिष्कार नहीं मिला।"
|
||||
},
|
||||
"options_exclusionListTitle": {
|
||||
"message": "बहिष्करण सूची"
|
||||
},
|
||||
"options_exportButton": {
|
||||
"message": "सूची निर्यात करें"
|
||||
},
|
||||
"options_filterPlaceholder": {
|
||||
"message": "टाइप करने के बाद ENTER दबाएं फ़िल्टर करने के लिए"
|
||||
},
|
||||
"options_importButton": {
|
||||
"message": "सूची आयात करें"
|
||||
},
|
||||
"popup_bannerIssueOpen": {
|
||||
"message": "इस पृष्ठ के लिए एक समस्या दर्ज की गई है। अस्थायी रूप से एक्सटेंशन ठीक से काम नहीं कर सकता है। समस्या का पालन करें या अधिक जानकारी जोड़ें"
|
||||
},
|
||||
"popup_bannerIssueWontFix": {
|
||||
"message": "इस पृष्ठ के लिए एक समस्या दर्ज की गई है, लेकिन इसे ठीक नहीं किया जाएगा। कृपया ध्यान दें कि एक्सटेंशन इस पृष्ठ पर अपेक्षित रूप से कार्य नहीं कर सकता है। हम नीचे दिए गए बटन से एक्सटेंशन को बंद करने की सलाह देते हैं"
|
||||
},
|
||||
"popup_bannerSupport": {
|
||||
"message": "इस साइट को अब समर्थन प्राप्त नहीं है। अधिक जानकारी के लिए कृपया यहाँ पढ़ें"
|
||||
},
|
||||
"popup_bannerUpdateAvailable": {
|
||||
"message": "अपडेट उपलब्ध है। कृपया नई समस्याओं की रिपोर्ट करने से पहले ज्ञात मुद्दों को हल करने के लिए नवीनतम संस्करण इंस्टॉल करें"
|
||||
},
|
||||
"popup_contributeOption": {
|
||||
"message": "इस प्रोजेक्ट में योगदान करें"
|
||||
},
|
||||
"popup_databaseVersion": {
|
||||
"message": "डेटाबेस संस्करण"
|
||||
},
|
||||
"popup_extensionVersion": {
|
||||
"message": "एक्सटेंशन संस्करण"
|
||||
},
|
||||
"popup_helpOption": {
|
||||
"message": "मदद या समस्याएँ?"
|
||||
},
|
||||
"popup_rateOption": {
|
||||
"message": "इस एक्सटेंशन को रेट करें"
|
||||
},
|
||||
"report_bodyText": {
|
||||
"message": "कृपया इस रिपोर्ट में कोई भी व्यक्तिगत जानकारी साझा न करें। यदि आप अधिक विवरण जोड़ना चाहते हैं, तो अगले चरण में समस्या पर जाएं और एक टिप्पणी जोड़ें।"
|
||||
},
|
||||
"report_cancelButtonText": {
|
||||
"message": "रद्द करें"
|
||||
},
|
||||
"report_reasonInputError": {
|
||||
"message": "कृपया कम से कम 10 अक्षरों और 1000 से अधिक नहीं अक्षरों के साथ एक कारण दर्ज करें"
|
||||
},
|
||||
"report_reasonInputLabel": {
|
||||
"message": "कारण"
|
||||
},
|
||||
"report_reasonInputPlaceholder": {
|
||||
"message": "पॉपअप दिखाई दिया"
|
||||
},
|
||||
"report_submitErrorExtraText": {
|
||||
"message": "ऐसा लगता है कि पहले से ही एक मुद्दा खुला है, या इसे 'फिक्स नहीं होगा' के रूप में चिह्नित किया गया है। कृपया अधिक जानकारी जोड़ने के लिए अगले बटन का उपयोग करके समस्या में जाएं।"
|
||||
},
|
||||
"report_submitErrorText": {
|
||||
"message": "रिपोर्ट नहीं भेजी गई?"
|
||||
},
|
||||
"report_submitSuccessExtraText": {
|
||||
"message": "जब तक हम इस पर काम कर रहे हैं, तब तक आप इस वेबसाइट को एक्सटेंशन सेटिंग में बहिष्करण सूची में जोड़ सकते हैं या अपने ब्राउज़र टूलबार में एक्सटेंशन आइकन पर क्लिक करके इस वेबसाइट के लिए एक्सटेंशन को अक्षम कर सकते हैं"
|
||||
},
|
||||
"report_submitSuccessText": {
|
||||
"message": "रिपोर्ट भेज दी गई!"
|
||||
},
|
||||
"report_urlInputError": {
|
||||
"message": "कृपया अधिकतम 1000 अक्षरों के साथ एक मान्य URL दर्ज करें"
|
||||
},
|
||||
"report_urlInputLabel": {
|
||||
"message": "URL"
|
||||
}
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
{
|
||||
"appDesc": {
|
||||
"message": "Ada yang bilang dialog persetujuan cookie? 😋"
|
||||
},
|
||||
"contextMenu_issueOption": {
|
||||
"message": "Ikuti masalah ini"
|
||||
},
|
||||
"contextMenu_reportOption": {
|
||||
"message": "Laporkan situs ini"
|
||||
},
|
||||
"contextMenu_settingsOption": {
|
||||
"message": "Kelola daftar pengecualian"
|
||||
},
|
||||
"options_addButton": {
|
||||
"message": "Tambahkan pengecualian"
|
||||
},
|
||||
"options_addPrompt": {
|
||||
"message": "Masukkan domain"
|
||||
},
|
||||
"options_clearButton": {
|
||||
"message": "Bersihkan daftar"
|
||||
},
|
||||
"options_empty": {
|
||||
"message": "Tidak ada pengecualian ditemukan"
|
||||
},
|
||||
"options_exclusionListTitle": {
|
||||
"message": "Daftar pengecualian"
|
||||
},
|
||||
"options_exportButton": {
|
||||
"message": "Ekspor daftar"
|
||||
},
|
||||
"options_filterPlaceholder": {
|
||||
"message": "Tekan ENTER untuk memfilter setelah mengetik"
|
||||
},
|
||||
"options_importButton": {
|
||||
"message": "Impor daftar"
|
||||
},
|
||||
"popup_bannerIssueOpen": {
|
||||
"message": "Ada masalah yang dilaporkan untuk halaman ini. Ekstensi mungkin tidak berfungsi seperti yang diharapkan untuk sementara. Ikuti masalahnya atau tambahkan informasi lainnya"
|
||||
},
|
||||
"popup_bannerIssueWontFix": {
|
||||
"message": "Ada masalah yang dilaporkan untuk halaman ini, tetapi tidak akan diperbaiki. Harap diperhatikan bahwa ekstensi mungkin tidak berfungsi seperti yang diharapkan di halaman ini. Kami sangat menyarankan untuk mematikan ekstensi menggunakan tombol di bawah ini"
|
||||
},
|
||||
"popup_bannerSupport": {
|
||||
"message": "Situs ini tidak lagi didukung. Untuk informasi lebih lanjut, silakan baca di sini"
|
||||
},
|
||||
"popup_bannerUpdateAvailable": {
|
||||
"message": "Pembaruan tersedia. Harap instal versi terbaru untuk menyelesaikan masalah yang diketahui sebelum melaporkan masalah baru"
|
||||
},
|
||||
"popup_contributeOption": {
|
||||
"message": "Berkontribusi pada proyek ini"
|
||||
},
|
||||
"popup_databaseVersion": {
|
||||
"message": "Versi basis data"
|
||||
},
|
||||
"popup_extensionVersion": {
|
||||
"message": "Versi ekstensi"
|
||||
},
|
||||
"popup_helpOption": {
|
||||
"message": "Butuh bantuan atau ada masalah?"
|
||||
},
|
||||
"popup_rateOption": {
|
||||
"message": "Beri peringkat pada ekstensi ini"
|
||||
},
|
||||
"report_bodyText": {
|
||||
"message": "Jangan membagikan informasi pribadi apa pun dalam laporan ini. Jika Anda ingin menambahkan lebih banyak detail, buka masalah pada langkah berikutnya dan tambahkan komentar."
|
||||
},
|
||||
"report_cancelButtonText": {
|
||||
"message": "Batal"
|
||||
},
|
||||
"report_reasonInputError": {
|
||||
"message": "Harap masukkan alasan yang terdiri dari minimal 10 karakter dan maksimal 1000 karakter"
|
||||
},
|
||||
"report_reasonInputLabel": {
|
||||
"message": "Alasan"
|
||||
},
|
||||
"report_reasonInputPlaceholder": {
|
||||
"message": "Muncul pop-up"
|
||||
},
|
||||
"report_submitErrorExtraText": {
|
||||
"message": "Tampaknya sudah ada masalah yang terbuka atau ditandai sebagai 'tidak akan diperbaiki'. Silakan buka masalah menggunakan tombol di bawah ini untuk menambahkan lebih banyak informasi."
|
||||
},
|
||||
"report_submitErrorText": {
|
||||
"message": "Laporan tidak terkirim?"
|
||||
},
|
||||
"report_submitSuccessExtraText": {
|
||||
"message": "Sambil kami menangani masalah ini, Anda dapat menambahkan situs web ini ke daftar pengecualian di pengaturan ekstensi atau menonaktifkan ekstensi untuk situs ini dengan mengklik ikon ekstensi di bilah alat peramban Anda"
|
||||
},
|
||||
"report_submitSuccessText": {
|
||||
"message": "Laporan terkirim!"
|
||||
},
|
||||
"report_urlInputError": {
|
||||
"message": "Masukkan URL yang valid dengan maksimal 1000 karakter"
|
||||
},
|
||||
"report_urlInputLabel": {
|
||||
"message": "URL"
|
||||
}
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
{
|
||||
"appDesc": {
|
||||
"message": "クッキー同意ダイアログの話をしている? 😋"
|
||||
},
|
||||
"contextMenu_issueOption": {
|
||||
"message": "この問題をフォローする"
|
||||
},
|
||||
"contextMenu_reportOption": {
|
||||
"message": "このウェブサイトを報告する"
|
||||
},
|
||||
"contextMenu_settingsOption": {
|
||||
"message": "除外リストを管理する"
|
||||
},
|
||||
"options_addButton": {
|
||||
"message": "除外を追加"
|
||||
},
|
||||
"options_addPrompt": {
|
||||
"message": "ドメインを入力してください"
|
||||
},
|
||||
"options_clearButton": {
|
||||
"message": "リストをクリア"
|
||||
},
|
||||
"options_empty": {
|
||||
"message": "除外項目が見つかりませんでした"
|
||||
},
|
||||
"options_exclusionListTitle": {
|
||||
"message": "除外リスト"
|
||||
},
|
||||
"options_exportButton": {
|
||||
"message": "リストをエクスポート"
|
||||
},
|
||||
"options_filterPlaceholder": {
|
||||
"message": "入力後にENTERキーを押してフィルタリング"
|
||||
},
|
||||
"options_importButton": {
|
||||
"message": "リストをインポート"
|
||||
},
|
||||
"popup_bannerIssueOpen": {
|
||||
"message": "このページで問題が報告されました。拡張機能が一時的に正常に動作しない可能性があります。問題をフォローするか、追加情報を提供してください"
|
||||
},
|
||||
"popup_bannerIssueWontFix": {
|
||||
"message": "このページで問題が報告されましたが、修正されません。このページで拡張機能が期待通りに機能しない可能性があります。以下のボタンで拡張機能をオフにすることをお勧めします"
|
||||
},
|
||||
"popup_bannerSupport": {
|
||||
"message": "このサイトはもうサポートされていません。詳しくは、ここをお読みください"
|
||||
},
|
||||
"popup_bannerUpdateAvailable": {
|
||||
"message": "アップデートが利用可能です。新しい問題を報告する前に、既知の問題を解決するため最新バージョンをインストールしてください"
|
||||
},
|
||||
"popup_contributeOption": {
|
||||
"message": "このプロジェクトに貢献する"
|
||||
},
|
||||
"popup_databaseVersion": {
|
||||
"message": "データベースバージョン"
|
||||
},
|
||||
"popup_extensionVersion": {
|
||||
"message": "拡張機能のバージョン"
|
||||
},
|
||||
"popup_helpOption": {
|
||||
"message": "ヘルプまたは問題?"
|
||||
},
|
||||
"popup_rateOption": {
|
||||
"message": "この拡張機能を評価する"
|
||||
},
|
||||
"report_bodyText": {
|
||||
"message": "このレポートには個人情報を含めないでください。追加の詳細を提供する場合は、次のステップで問題に移動し、コメントを追加してください。"
|
||||
},
|
||||
"report_cancelButtonText": {
|
||||
"message": "キャンセル"
|
||||
},
|
||||
"report_reasonInputError": {
|
||||
"message": "10文字以上、1000文字以下の理由を入力してください"
|
||||
},
|
||||
"report_reasonInputLabel": {
|
||||
"message": "理由"
|
||||
},
|
||||
"report_reasonInputPlaceholder": {
|
||||
"message": "ポップアップが表示されました"
|
||||
},
|
||||
"report_submitErrorExtraText": {
|
||||
"message": "既に問題が開かれているか、「修正しない」とマークされています。追加情報を提供する場合は、次のボタンから問題に移動してください。"
|
||||
},
|
||||
"report_submitErrorText": {
|
||||
"message": "レポートが送信されませんでしたか?"
|
||||
},
|
||||
"report_submitSuccessExtraText": {
|
||||
"message": "処理が完了するまでの間、このウェブサイトを拡張機能の設定で除外リストに追加するか、ブラウザツールバーの拡張アイコンをクリックしてこのウェブサイトで拡張機能を無効にしてください"
|
||||
},
|
||||
"report_submitSuccessText": {
|
||||
"message": "レポートが送信されました!"
|
||||
},
|
||||
"report_urlInputError": {
|
||||
"message": "1000文字以下の有効なURLを入力してください"
|
||||
},
|
||||
"report_urlInputLabel": {
|
||||
"message": "URL"
|
||||
}
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
{
|
||||
"appDesc": {
|
||||
"message": "쿠키 동의 대화 상자를 말한 사람이 있나요? 😋"
|
||||
},
|
||||
"contextMenu_issueOption": {
|
||||
"message": "이 문제를 팔로우하기"
|
||||
},
|
||||
"contextMenu_reportOption": {
|
||||
"message": "이 웹사이트 신고하기"
|
||||
},
|
||||
"contextMenu_settingsOption": {
|
||||
"message": "제외 목록 관리하기"
|
||||
},
|
||||
"options_addButton": {
|
||||
"message": "제외 추가"
|
||||
},
|
||||
"options_addPrompt": {
|
||||
"message": "도메인 입력"
|
||||
},
|
||||
"options_clearButton": {
|
||||
"message": "목록 지우기"
|
||||
},
|
||||
"options_empty": {
|
||||
"message": "제외 항목을 찾을 수 없습니다"
|
||||
},
|
||||
"options_exclusionListTitle": {
|
||||
"message": "제외 목록"
|
||||
},
|
||||
"options_exportButton": {
|
||||
"message": "목록 내보내기"
|
||||
},
|
||||
"options_filterPlaceholder": {
|
||||
"message": "입력 후 ENTER를 눌러 필터링"
|
||||
},
|
||||
"options_importButton": {
|
||||
"message": "목록 가져오기"
|
||||
},
|
||||
"popup_bannerIssueOpen": {
|
||||
"message": "이 페이지에 대한 문제가 보고되었습니다. 확장이 일시적으로 예상대로 작동하지 않을 수 있습니다. 문제를 팔로우하거나 추가 정보를 제공하십시오"
|
||||
},
|
||||
"popup_bannerIssueWontFix": {
|
||||
"message": "이 페이지에 대한 문제가 보고되었지만 수정되지 않습니다. 이 페이지에서 확장이 예상대로 작동하지 않을 수 있습니다. 아래 버튼을 사용하여 확장을 끄는 것이 좋습니다"
|
||||
},
|
||||
"popup_bannerSupport": {
|
||||
"message": "이 사이트는 더 이상 지원되지 않습니다. 자세한 내용은 여기를 계속 읽어 주세요"
|
||||
},
|
||||
"popup_bannerUpdateAvailable": {
|
||||
"message": "업데이트 가능. 새 문제를 보고하기 전에 알려진 문제를 해결하려면 최신 버전을 설치하십시오"
|
||||
},
|
||||
"popup_contributeOption": {
|
||||
"message": "이 프로젝트에 기여하기"
|
||||
},
|
||||
"popup_databaseVersion": {
|
||||
"message": "데이터베이스 버전"
|
||||
},
|
||||
"popup_extensionVersion": {
|
||||
"message": "확장 버전"
|
||||
},
|
||||
"popup_helpOption": {
|
||||
"message": "도움말 또는 문제?"
|
||||
},
|
||||
"popup_rateOption": {
|
||||
"message": "이 확장 평가하기"
|
||||
},
|
||||
"report_bodyText": {
|
||||
"message": "이 보고서에 개인 정보를 공유하지 마십시오. 더 자세한 내용을 추가하려면 다음 단계에서 문제로 이동하여 댓글을 추가하십시오."
|
||||
},
|
||||
"report_cancelButtonText": {
|
||||
"message": "취소"
|
||||
},
|
||||
"report_reasonInputError": {
|
||||
"message": "10자 이상, 1000자 이하의 이유를 입력하십시오"
|
||||
},
|
||||
"report_reasonInputLabel": {
|
||||
"message": "이유"
|
||||
},
|
||||
"report_reasonInputPlaceholder": {
|
||||
"message": "팝업이 나타났습니다"
|
||||
},
|
||||
"report_submitErrorExtraText": {
|
||||
"message": "이미 열린 문제가 있거나 '수정하지 않음'으로 표시된 것 같습니다. 추가 정보를 추가하려면 다음 버튼을 사용하여 문제로 이동하십시오."
|
||||
},
|
||||
"report_submitErrorText": {
|
||||
"message": "보고서가 전송되지 않았습니까?"
|
||||
},
|
||||
"report_submitSuccessExtraText": {
|
||||
"message": "이 문제를 처리하는 동안 확장 설정에서 제외 목록에 이 웹사이트를 추가하거나 브라우저 툴바의 확장 아이콘을 클릭하여 이 웹사이트에서 확장을 비활성화할 수 있습니다"
|
||||
},
|
||||
"report_submitSuccessText": {
|
||||
"message": "보고서가 전송되었습니다!"
|
||||
},
|
||||
"report_urlInputError": {
|
||||
"message": "유효한 URL을 1000자 이하로 입력하십시오"
|
||||
},
|
||||
"report_urlInputLabel": {
|
||||
"message": "URL"
|
||||
}
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
{
|
||||
"appDesc": {
|
||||
"message": "Biri çerez onayı diyaloglarını mı söyledi? 😋"
|
||||
},
|
||||
"contextMenu_issueOption": {
|
||||
"message": "Bu sorunu takip et"
|
||||
},
|
||||
"contextMenu_reportOption": {
|
||||
"message": "Bu web sitesini bildir"
|
||||
},
|
||||
"contextMenu_settingsOption": {
|
||||
"message": "Hariç tutma listesini yönet"
|
||||
},
|
||||
"options_addButton": {
|
||||
"message": "Hariç tutma ekle"
|
||||
},
|
||||
"options_addPrompt": {
|
||||
"message": "Alan adı girin"
|
||||
},
|
||||
"options_clearButton": {
|
||||
"message": "Listeyi temizle"
|
||||
},
|
||||
"options_empty": {
|
||||
"message": "Hariç tutma bulunamadı"
|
||||
},
|
||||
"options_exclusionListTitle": {
|
||||
"message": "Hariç tutma listesi"
|
||||
},
|
||||
"options_exportButton": {
|
||||
"message": "Listeyi dışa aktar"
|
||||
},
|
||||
"options_filterPlaceholder": {
|
||||
"message": "Yazdıktan sonra ENTER tuşuna basarak filtreleyin"
|
||||
},
|
||||
"options_importButton": {
|
||||
"message": "Listeyi içe aktar"
|
||||
},
|
||||
"popup_bannerIssueOpen": {
|
||||
"message": "Bu sayfa için bir sorun bildirildi. Uzantı geçici olarak beklenildiği gibi çalışmayabilir. Sorunu takip edin veya daha fazla bilgi ekleyin"
|
||||
},
|
||||
"popup_bannerIssueWontFix": {
|
||||
"message": "Bu sayfa için bir sorun bildirildi ancak düzeltilmeyecek. Bu sayfada uzantının beklenildiği gibi çalışmayabileceğini unutmayın. Aşağıdaki düğmeyi kullanarak uzantıyı kapatmanızı öneririz"
|
||||
},
|
||||
"popup_bannerSupport": {
|
||||
"message": "Bu site artık desteklenmiyor. Daha fazla bilgi için lütfen buradan okumaya devam edin"
|
||||
},
|
||||
"popup_bannerUpdateAvailable": {
|
||||
"message": "Güncelleme mevcut. Yeni sorunları bildirmeden önce bilinen sorunları gidermek için lütfen en son sürümü yükleyin"
|
||||
},
|
||||
"popup_contributeOption": {
|
||||
"message": "Bu projeye katkıda bulun"
|
||||
},
|
||||
"popup_databaseVersion": {
|
||||
"message": "Veritabanı sürümü"
|
||||
},
|
||||
"popup_extensionVersion": {
|
||||
"message": "Uzantı sürümü"
|
||||
},
|
||||
"popup_helpOption": {
|
||||
"message": "Yardım veya sorunlar?"
|
||||
},
|
||||
"popup_rateOption": {
|
||||
"message": "Bu uzantıyı değerlendir"
|
||||
},
|
||||
"report_bodyText": {
|
||||
"message": "Bu raporda kişisel bilgi paylaşmayın. Daha fazla ayrıntı eklemek istiyorsanız, bir sonraki adımda soruna gidin ve yorum ekleyin."
|
||||
},
|
||||
"report_cancelButtonText": {
|
||||
"message": "İptal"
|
||||
},
|
||||
"report_reasonInputError": {
|
||||
"message": "Lütfen en az 10 ve en fazla 1000 karakterlik bir sebep girin"
|
||||
},
|
||||
"report_reasonInputLabel": {
|
||||
"message": "Sebep"
|
||||
},
|
||||
"report_reasonInputPlaceholder": {
|
||||
"message": "Açılır pencere göründü"
|
||||
},
|
||||
"report_submitErrorExtraText": {
|
||||
"message": "Zaten açık bir sorun var gibi görünüyor veya 'düzeltmeyecek' olarak işaretlenmiş. Aşağıdaki düğmeyi kullanarak sorun sayfasına giderek daha fazla bilgi ekleyin."
|
||||
},
|
||||
"report_submitErrorText": {
|
||||
"message": "Rapor gönderilmedi mi?"
|
||||
},
|
||||
"report_submitSuccessExtraText": {
|
||||
"message": "Bu sorun üzerinde çalışırken, uzantı ayarlarında bu web sitesini hariç tutma listesine ekleyebilir veya tarayıcı araç çubuğundaki uzantı simgesine tıklayarak bu web sitesi için uzantıyı devre dışı bırakabilirsiniz"
|
||||
},
|
||||
"report_submitSuccessText": {
|
||||
"message": "Rapor gönderildi!"
|
||||
},
|
||||
"report_urlInputError": {
|
||||
"message": "Lütfen en fazla 1000 karakterle geçerli bir URL girin"
|
||||
},
|
||||
"report_urlInputLabel": {
|
||||
"message": "URL"
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
export default 'off-icon';
|
@ -1 +0,0 @@
|
||||
export default 'on-icon';
|
@ -1 +0,0 @@
|
||||
export default 'warn-icon';
|
@ -1,56 +0,0 @@
|
||||
import { jest } from '@jest/globals';
|
||||
|
||||
chrome.action = {
|
||||
...chrome.action,
|
||||
openPopup: jest.fn(),
|
||||
setBadgeBackgroundColor: jest.fn(),
|
||||
setBadgeText: jest.fn(),
|
||||
setIcon: jest.fn(),
|
||||
} as typeof chrome.action;
|
||||
|
||||
chrome.contextMenus = {
|
||||
...chrome.contextMenus,
|
||||
create: jest.fn(),
|
||||
onClicked: {
|
||||
...(chrome.contextMenus ?? {}).onClicked,
|
||||
addListener: jest.fn(),
|
||||
},
|
||||
removeAll: jest.fn(),
|
||||
} as typeof chrome.contextMenus;
|
||||
|
||||
chrome.declarativeNetRequest = {
|
||||
...chrome.declarativeNetRequest,
|
||||
updateSessionRules: jest.fn(),
|
||||
} as typeof chrome.declarativeNetRequest;
|
||||
|
||||
chrome.runtime = {
|
||||
...chrome.runtime,
|
||||
getManifest: jest.fn(
|
||||
() =>
|
||||
({
|
||||
content_scripts: [{ matches: ['https://example.com/*'] }],
|
||||
version: '1.0.0',
|
||||
}) as chrome.runtime.ManifestV3
|
||||
),
|
||||
onInstalled: {
|
||||
...(chrome.runtime ?? {}).onInstalled,
|
||||
addListener: jest.fn(),
|
||||
},
|
||||
onStartup: {
|
||||
...(chrome.runtime ?? {}).onStartup,
|
||||
addListener: jest.fn(),
|
||||
},
|
||||
openOptionsPage: jest.fn(),
|
||||
} as typeof chrome.runtime;
|
||||
|
||||
chrome.webRequest = {
|
||||
...chrome.webRequest,
|
||||
onBeforeRequest: {
|
||||
...(chrome.webRequest ?? {}).onBeforeRequest,
|
||||
addListener: jest.fn(),
|
||||
},
|
||||
onErrorOccurred: {
|
||||
...(chrome.webRequest ?? {}).onErrorOccurred,
|
||||
addListener: jest.fn(),
|
||||
},
|
||||
} as typeof chrome.webRequest;
|
@ -1,5 +0,0 @@
|
||||
import { jest } from '@jest/globals';
|
||||
|
||||
jest.mock('@plasmohq/messaging', () => ({
|
||||
sendToContentScript: jest.fn(),
|
||||
}));
|
@ -1,6 +0,0 @@
|
||||
import { jest } from '@jest/globals';
|
||||
|
||||
jest.mock('~utils/domain', () => ({
|
||||
formatDomainFromURL: jest.fn(() => 'example.com'),
|
||||
validateSupport: jest.fn(),
|
||||
}));
|
@ -1,9 +0,0 @@
|
||||
import { jest } from '@jest/globals';
|
||||
|
||||
jest.mock('~utils/storage', () => ({
|
||||
storage: {
|
||||
get: async (key: string) => (await chrome.storage.local.get(key))?.[key],
|
||||
remove: (key: string) => chrome.storage.local.remove(key),
|
||||
set: (key: string, value: any) => chrome.storage.local.set({ [key]: value }),
|
||||
},
|
||||
}));
|
@ -2,84 +2,18 @@
|
||||
"name": "browser-extension",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "plasmo dev",
|
||||
"build": "plasmo build",
|
||||
"lint": "eslint --fix",
|
||||
"package": "plasmo package",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@plasmohq/messaging": "^0.6.2",
|
||||
"@plasmohq/storage": "^1.13.0",
|
||||
"plasmo": "^0.89.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
"build": "rimraf build; sh scripts/build.sh; sh scripts/pack.sh",
|
||||
"lint": "eslint --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@jest/types": "29.6.3",
|
||||
"@testing-library/react": "^16.0.1",
|
||||
"@types/chrome": "^0.0.283",
|
||||
"@types/node": "^20.11.5",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.14.0",
|
||||
"@typescript-eslint/parser": "^8.14.0",
|
||||
"eslint": "^8.57.1",
|
||||
"@types/firefox-webext-browser": "^120.0.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jest-dom": "^5.4.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"eslint-plugin-testing-library": "^6.4.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-webextension-mock": "^3.9.0",
|
||||
"postcss": "^8.4.49",
|
||||
"postcss-modules": "^6.0.1",
|
||||
"prettier": "^3.3.3",
|
||||
"ts-jest": "^29.2.5",
|
||||
"typescript": "^5.6.3"
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"rimraf": "^5.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20.x"
|
||||
},
|
||||
"manifest": {
|
||||
"author": "wanhose",
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "{77e2c00b-e173-4604-863d-01645d8d2826}",
|
||||
"strict_min_version": "126.0",
|
||||
"update_url": "https://www.cookie-dialog-monster.com/releases/mozilla/updates.json"
|
||||
}
|
||||
},
|
||||
"default_locale": "en",
|
||||
"description": "__MSG_appDesc__",
|
||||
"host_permissions": [
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
],
|
||||
"name": "Cookie Dialog Monster",
|
||||
"permissions": [
|
||||
"contextMenus",
|
||||
"declarativeNetRequest",
|
||||
"storage",
|
||||
"webRequest"
|
||||
],
|
||||
"version": "8.0.5",
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"matches": [
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
],
|
||||
"resources": [
|
||||
"https://fonts.googleapis.com/css?family=Inter"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
"packageManager": "yarn@4.2.1"
|
||||
}
|
||||
|
16
packages/browser-extension/scripts/build.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
input="./src"
|
||||
output="./build"
|
||||
|
||||
mkdir -p "$output"
|
||||
|
||||
for file in $(find "$input" -name "*.css" -o -name "*.html" -o -name "*.js" | sed "s|^$input/||"); do
|
||||
input_file="$input/$file"
|
||||
output_file="$output/$file"
|
||||
|
||||
mkdir -p "${output_file%/*}"
|
||||
cp "$input_file" "$output_file"
|
||||
done
|
||||
|
||||
cp -nR "$input/." "$output"
|
7
packages/browser-extension/scripts/pack.sh
Normal file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
path=$(pwd)
|
||||
version=$(jq -r '.version' "$path/build/manifest.json")
|
||||
|
||||
cd "$path/build" || exit
|
||||
zip -r "$path/$version.zip" . -x */\.* *.git* \.* *.md *.sh *.zip
|
@ -3,7 +3,7 @@
|
||||
"message": "Hat jemand Cookie-Einwilligungsdialoge gesagt? 😋"
|
||||
},
|
||||
"contextMenu_issueOption": {
|
||||
"message": "Folgen Sie diesem Problem"
|
||||
"message": "Folgen Sie diesem Problem auf GitHub"
|
||||
},
|
||||
"contextMenu_reportOption": {
|
||||
"message": "Diese Website melden"
|
||||
@ -20,9 +20,6 @@
|
||||
"options_clearButton": {
|
||||
"message": "Liste leeren"
|
||||
},
|
||||
"options_empty": {
|
||||
"message": "Keine Ausschlüsse gefunden"
|
||||
},
|
||||
"options_exclusionListTitle": {
|
||||
"message": "Ausschlussliste"
|
||||
},
|
||||
@ -41,9 +38,6 @@
|
||||
"popup_bannerIssueWontFix": {
|
||||
"message": "Ein Problem wurde für diese Seite gemeldet, wird jedoch nicht behoben. Bitte beachten Sie, dass die Erweiterung auf dieser Seite möglicherweise nicht wie erwartet funktioniert. Wir empfehlen dringend, die Erweiterung über die Schaltfläche unten zu deaktivieren."
|
||||
},
|
||||
"popup_bannerSupport": {
|
||||
"message": "Diese Seite wird nicht mehr unterstützt. Für weitere Informationen lesen Sie bitte hier weiter"
|
||||
},
|
||||
"popup_bannerUpdateAvailable": {
|
||||
"message": "Update verfügbar. Bitte installieren Sie die neueste Version, um bekannte Probleme zu beheben, bevor Sie neue melden"
|
||||
},
|
||||
@ -63,7 +57,7 @@
|
||||
"message": "Bewerten Sie diese Erweiterung"
|
||||
},
|
||||
"report_bodyText": {
|
||||
"message": "Bitte teilen Sie in diesem Bericht keine persönlichen Informationen. Wenn Sie weitere Details hinzufügen möchten, gehen Sie im nächsten Schritt zum Problem und fügen Sie einen Kommentar hinzu."
|
||||
"message": "Bitte geben Sie in diesem Bericht keine persönlichen Informationen weiter. Wenn Sie weitere Details hinzufügen möchten, öffnen Sie das GitHub-Problem im nächsten Schritt und fügen Sie einen Kommentar hinzu."
|
||||
},
|
||||
"report_cancelButtonText": {
|
||||
"message": "Abbrechen"
|
@ -3,7 +3,7 @@
|
||||
"message": "Did someone say cookie consent dialogs? 😋"
|
||||
},
|
||||
"contextMenu_issueOption": {
|
||||
"message": "Follow this issue"
|
||||
"message": "Follow this issue in GitHub"
|
||||
},
|
||||
"contextMenu_reportOption": {
|
||||
"message": "Report this website"
|
||||
@ -20,9 +20,6 @@
|
||||
"options_clearButton": {
|
||||
"message": "Clear list"
|
||||
},
|
||||
"options_empty": {
|
||||
"message": "No exclusions found"
|
||||
},
|
||||
"options_exclusionListTitle": {
|
||||
"message": "Exclusion list"
|
||||
},
|
||||
@ -41,9 +38,6 @@
|
||||
"popup_bannerIssueWontFix": {
|
||||
"message": "An issue has been reported for this page, but it won't be fixed. Please be aware that the extension may not function as expected on this page. We highly recommend turning off the extension using the button below"
|
||||
},
|
||||
"popup_bannerSupport": {
|
||||
"message": "This site is no longer supported. For more information, please keep reading here"
|
||||
},
|
||||
"popup_bannerUpdateAvailable": {
|
||||
"message": "Update available. Please install the latest version to address known issues before reporting new ones"
|
||||
},
|
||||
@ -63,7 +57,7 @@
|
||||
"message": "Rate this extension"
|
||||
},
|
||||
"report_bodyText": {
|
||||
"message": "Please, do not share any personal information in this report. If you want to add more details, go to the issue in the next step and add a comment."
|
||||
"message": "Please, do not share any personal information in this report. If you want to add more details, open the GitHub issue in the next step and add a comment."
|
||||
},
|
||||
"report_cancelButtonText": {
|
||||
"message": "Cancel"
|
@ -3,7 +3,7 @@
|
||||
"message": "¿Alguien mencionó los diálogos de consentimiento de cookies? 😋"
|
||||
},
|
||||
"contextMenu_issueOption": {
|
||||
"message": "Sigue este problema"
|
||||
"message": "Sigue este problema en GitHub"
|
||||
},
|
||||
"contextMenu_reportOption": {
|
||||
"message": "Reportar este sitio web"
|
||||
@ -20,9 +20,6 @@
|
||||
"options_clearButton": {
|
||||
"message": "Borrar lista"
|
||||
},
|
||||
"options_empty": {
|
||||
"message": "No se encontraron exclusiones"
|
||||
},
|
||||
"options_exclusionListTitle": {
|
||||
"message": "Lista de exclusión"
|
||||
},
|
||||
@ -41,9 +38,6 @@
|
||||
"popup_bannerIssueWontFix": {
|
||||
"message": "Se ha informado de un problema en esta página, pero no se solucionará. Tenga en cuenta que la extensión puede no funcionar como se espera en esta página. Recomendamos encarecidamente desactivar la extensión utilizando el botón a continuación."
|
||||
},
|
||||
"popup_bannerSupport": {
|
||||
"message": "Este sitio ya no es compatible. Para más información, sigue leyendo aquí"
|
||||
},
|
||||
"popup_bannerUpdateAvailable": {
|
||||
"message": "Actualización disponible. Por favor, instale la última versión para solucionar problemas conocidos antes de informar nuevos"
|
||||
},
|
||||
@ -63,7 +57,7 @@
|
||||
"message": "Califica esta extensión"
|
||||
},
|
||||
"report_bodyText": {
|
||||
"message": "Por favor, no compartas información personal en este informe. Si deseas agregar más detalles, ve al problema en el siguiente paso y añade un comentario."
|
||||
"message": "Por favor, no compartas ninguna información personal en este informe. Si deseas agregar más detalles, abre el problema de GitHub en el siguiente paso y agrega un comentario."
|
||||
},
|
||||
"report_cancelButtonText": {
|
||||
"message": "Cancelar"
|
@ -3,7 +3,7 @@
|
||||
"message": "Quelqu'un a parlé de boîtes de dialogue de consentement aux cookies ? 😋"
|
||||
},
|
||||
"contextMenu_issueOption": {
|
||||
"message": "Suivre ce problème"
|
||||
"message": "Suivre ce problème sur GitHub"
|
||||
},
|
||||
"contextMenu_reportOption": {
|
||||
"message": "Signaler ce site web"
|
||||
@ -20,9 +20,6 @@
|
||||
"options_clearButton": {
|
||||
"message": "Vider la liste"
|
||||
},
|
||||
"options_empty": {
|
||||
"message": "Aucune exclusion trouvée"
|
||||
},
|
||||
"options_exclusionListTitle": {
|
||||
"message": "Liste d'exclusion"
|
||||
},
|
||||
@ -41,9 +38,6 @@
|
||||
"popup_bannerIssueWontFix": {
|
||||
"message": "Un problème a été signalé pour cette page, mais il ne sera pas résolu. Veuillez noter que l'extension pourrait ne pas fonctionner comme prévu sur cette page. Nous vous recommandons vivement de désactiver l'extension en utilisant le bouton ci-dessous."
|
||||
},
|
||||
"popup_bannerSupport": {
|
||||
"message": "Ce site n'est plus pris en charge. Pour plus d'informations, veuillez continuer à lire ici"
|
||||
},
|
||||
"popup_bannerUpdateAvailable": {
|
||||
"message": "Mise à jour disponible. Veuillez installer la dernière version pour résoudre les problèmes connus avant d'en signaler de nouveaux"
|
||||
},
|
||||
@ -63,7 +57,7 @@
|
||||
"message": "Évaluer cette extension"
|
||||
},
|
||||
"report_bodyText": {
|
||||
"message": "Veuillez ne partager aucune information personnelle dans ce rapport. Si vous souhaitez ajouter plus de détails, allez à l'issue à l'étape suivante et ajoutez un commentaire."
|
||||
"message": "Veuillez ne pas partager d'informations personnelles dans ce rapport. Si vous souhaitez ajouter plus de détails, ouvrez le problème GitHub à l'étape suivante et ajoutez un commentaire."
|
||||
},
|
||||
"report_cancelButtonText": {
|
||||
"message": "Annuler"
|
@ -3,7 +3,7 @@
|
||||
"message": "Qualcuno ha parlato di dialoghi di consenso sui cookie? 😋"
|
||||
},
|
||||
"contextMenu_issueOption": {
|
||||
"message": "Segui questo problema"
|
||||
"message": "Segui questo problema su GitHub"
|
||||
},
|
||||
"contextMenu_reportOption": {
|
||||
"message": "Segnala questo sito web"
|
||||
@ -20,9 +20,6 @@
|
||||
"options_clearButton": {
|
||||
"message": "Cancella lista"
|
||||
},
|
||||
"options_empty": {
|
||||
"message": "Nessuna esclusione trovata"
|
||||
},
|
||||
"options_exclusionListTitle": {
|
||||
"message": "Lista di esclusione"
|
||||
},
|
||||
@ -41,9 +38,6 @@
|
||||
"popup_bannerIssueWontFix": {
|
||||
"message": "È stato segnalato un problema per questa pagina, ma non verrà risolto. Si prega di notare che l'estensione potrebbe non funzionare come previsto su questa pagina. Si consiglia vivamente di disattivare l'estensione utilizzando il pulsante qui sotto."
|
||||
},
|
||||
"popup_bannerSupport": {
|
||||
"message": "Questo sito non è più supportato. Per ulteriori informazioni, continua a leggere qui"
|
||||
},
|
||||
"popup_bannerUpdateAvailable": {
|
||||
"message": "Aggiornamento disponibile. Si prega di installare l'ultima versione per risolvere i problemi noti prima di segnalarne di nuovi"
|
||||
},
|
||||
@ -63,7 +57,7 @@
|
||||
"message": "Valuta questa estensione"
|
||||
},
|
||||
"report_bodyText": {
|
||||
"message": "Per favore, non condividere informazioni personali in questo rapporto. Se desideri aggiungere ulteriori dettagli, vai al problema nel prossimo passaggio e aggiungi un commento."
|
||||
"message": "Per favore, non condividere informazioni personali in questo rapporto. Se vuoi aggiungere più dettagli, apri il problema su GitHub nel passaggio successivo e aggiungi un commento."
|
||||
},
|
||||
"report_cancelButtonText": {
|
||||
"message": "Annulla"
|
@ -3,7 +3,7 @@
|
||||
"message": "Czy ktoś wspomniał o dialogach zgody na pliki cookie? 😋"
|
||||
},
|
||||
"contextMenu_issueOption": {
|
||||
"message": "Śledź ten problem"
|
||||
"message": "Śledź ten problem na GitHub"
|
||||
},
|
||||
"contextMenu_reportOption": {
|
||||
"message": "Zgłoś tę stronę"
|
||||
@ -20,9 +20,6 @@
|
||||
"options_clearButton": {
|
||||
"message": "Wyczyść listę"
|
||||
},
|
||||
"options_empty": {
|
||||
"message": "Nie znaleziono wykluczeń"
|
||||
},
|
||||
"options_exclusionListTitle": {
|
||||
"message": "Lista wykluczeń"
|
||||
},
|
||||
@ -41,9 +38,6 @@
|
||||
"popup_bannerIssueWontFix": {
|
||||
"message": "Zgłoszono problem z tą stroną, ale nie zostanie on naprawiony. Należy pamiętać, że rozszerzenie może nie działać zgodnie z oczekiwaniami na tej stronie. Zdecydowanie zalecamy wyłączenie rozszerzenia za pomocą poniższego przycisku."
|
||||
},
|
||||
"popup_bannerSupport": {
|
||||
"message": "Ta strona nie jest już obsługiwana. Aby uzyskać więcej informacji, proszę czytać dalej tutaj"
|
||||
},
|
||||
"popup_bannerUpdateAvailable": {
|
||||
"message": "Dostępna aktualizacja. Zainstaluj najnowszą wersję, aby rozwiązać znane problemy przed zgłoszeniem nowych"
|
||||
},
|
||||
@ -63,7 +57,7 @@
|
||||
"message": "Oceń to rozszerzenie"
|
||||
},
|
||||
"report_bodyText": {
|
||||
"message": "Proszę, nie udostępniaj żadnych danych osobowych w tym raporcie. Jeśli chcesz dodać więcej szczegółów, przejdź do zgłoszenia w kolejnym kroku i dodaj komentarz."
|
||||
"message": "Prosimy, nie udostępniaj żadnych danych osobowych w tym raporcie. Jeśli chcesz dodać więcej szczegółów, otwórz zgłoszenie na GitHub w następnym kroku i dodaj komentarz."
|
||||
},
|
||||
"report_cancelButtonText": {
|
||||
"message": "Anuluj"
|
@ -3,7 +3,7 @@
|
||||
"message": "Alguém falou sobre diálogos de consentimento de cookies? 😋"
|
||||
},
|
||||
"contextMenu_issueOption": {
|
||||
"message": "Siga este problema"
|
||||
"message": "Siga este problema no GitHub"
|
||||
},
|
||||
"contextMenu_reportOption": {
|
||||
"message": "Denunciar este site"
|
||||
@ -20,9 +20,6 @@
|
||||
"options_clearButton": {
|
||||
"message": "Limpar lista"
|
||||
},
|
||||
"options_empty": {
|
||||
"message": "Nenhuma exclusão encontrada"
|
||||
},
|
||||
"options_exclusionListTitle": {
|
||||
"message": "Lista de exclusão"
|
||||
},
|
||||
@ -41,9 +38,6 @@
|
||||
"popup_bannerIssueWontFix": {
|
||||
"message": "Um problema foi relatado para esta página, mas não será corrigido. Esteja ciente de que a extensão pode não funcionar conforme o esperado nesta página. Recomendamos desativar a extensão usando o botão abaixo."
|
||||
},
|
||||
"popup_bannerSupport": {
|
||||
"message": "Este site não é mais compatível. Para mais informações, continue lendo aqui"
|
||||
},
|
||||
"popup_bannerUpdateAvailable": {
|
||||
"message": "Atualização disponível. Por favor, instale a versão mais recente para corrigir problemas conhecidos antes de relatar novos"
|
||||
},
|
||||
@ -63,7 +57,7 @@
|
||||
"message": "Avalie esta extensão"
|
||||
},
|
||||
"report_bodyText": {
|
||||
"message": "Por favor, não compartilhe informações pessoais neste relatório. Se quiser adicionar mais detalhes, vá até o problema no próximo passo e adicione um comentário."
|
||||
"message": "Por favor, não compartilhe nenhuma informação pessoal neste relatório. Se você quiser adicionar mais detalhes, abra o problema no GitHub na próxima etapa e adicione um comentário."
|
||||
},
|
||||
"report_cancelButtonText": {
|
||||
"message": "Cancelar"
|
@ -3,7 +3,7 @@
|
||||
"message": "Alguém mencionou diálogos de consentimento de cookies? 😋"
|
||||
},
|
||||
"contextMenu_issueOption": {
|
||||
"message": "Siga este problema"
|
||||
"message": "Siga este problema no GitHub"
|
||||
},
|
||||
"contextMenu_reportOption": {
|
||||
"message": "Reportar este site"
|
||||
@ -20,9 +20,6 @@
|
||||
"options_clearButton": {
|
||||
"message": "Limpar lista"
|
||||
},
|
||||
"options_empty": {
|
||||
"message": "Nenhuma exclusão encontrada"
|
||||
},
|
||||
"options_exclusionListTitle": {
|
||||
"message": "Lista de exclusão"
|
||||
},
|
||||
@ -41,9 +38,6 @@
|
||||
"popup_bannerIssueWontFix": {
|
||||
"message": "Foi relatado um problema para esta página, mas não será resolvido. Esteja ciente de que a extensão pode não funcionar conforme o esperado nesta página. Recomendamos desativar a extensão usando o botão abaixo."
|
||||
},
|
||||
"popup_bannerSupport": {
|
||||
"message": "Este site já não é suportado. Para mais informações, continue a ler aqui"
|
||||
},
|
||||
"popup_bannerUpdateAvailable": {
|
||||
"message": "Atualização disponível. Por favor, instale a versão mais recente para resolver problemas conhecidos antes de relatar novos"
|
||||
},
|
||||
@ -63,7 +57,7 @@
|
||||
"message": "Avalie esta extensão"
|
||||
},
|
||||
"report_bodyText": {
|
||||
"message": "Por favor, não partilhe informações pessoais neste relatório. Se quiser adicionar mais detalhes, vá até ao problema no próximo passo e adicione um comentário."
|
||||
"message": "Por favor, não partilhe nenhuma informação pessoal neste relatório. Se quiser adicionar mais detalhes, abra o problema no GitHub na próxima etapa e adicione um comentário."
|
||||
},
|
||||
"report_cancelButtonText": {
|
||||
"message": "Cancelar"
|
@ -3,7 +3,7 @@
|
||||
"message": "A menționat cineva dialogurile de consimțământ pentru cookie-uri? 😋"
|
||||
},
|
||||
"contextMenu_issueOption": {
|
||||
"message": "Urmărește această problemă"
|
||||
"message": "Urmărește această problemă pe GitHub"
|
||||
},
|
||||
"contextMenu_reportOption": {
|
||||
"message": "Raportează acest site web"
|
||||
@ -20,9 +20,6 @@
|
||||
"options_clearButton": {
|
||||
"message": "Golește lista"
|
||||
},
|
||||
"options_empty": {
|
||||
"message": "Nu au fost găsite excluderi"
|
||||
},
|
||||
"options_exclusionListTitle": {
|
||||
"message": "Lista de excludere"
|
||||
},
|
||||
@ -41,9 +38,6 @@
|
||||
"popup_bannerIssueWontFix": {
|
||||
"message": "A fost raportată o problemă pentru această pagină, dar nu va fi rezolvată. Rețineți că extensia poate să nu funcționeze conform așteptărilor pe această pagină. Vă recomandăm cu insistență să dezactivați extensia folosind butonul de mai jos."
|
||||
},
|
||||
"popup_bannerSupport": {
|
||||
"message": "Acest site nu mai este acceptat. Pentru mai multe informații, continuați să citiți aici"
|
||||
},
|
||||
"popup_bannerUpdateAvailable": {
|
||||
"message": "Actualizare disponibilă. Vă rugăm să instalați cea mai recentă versiune pentru a rezolva problemele cunoscute înainte de a raporta altele noi."
|
||||
},
|
||||
@ -63,7 +57,7 @@
|
||||
"message": "Evaluează această extensie"
|
||||
},
|
||||
"report_bodyText": {
|
||||
"message": "Vă rugăm să nu împărtășiți informații personale în acest raport. Dacă doriți să adăugați mai multe detalii, accesați problema în pasul următor și adăugați un comentariu."
|
||||
"message": "Te rugăm să nu împărtășești informații personale în acest raport. Dacă dorești să adaugi mai multe detalii, deschide problema GitHub în pasul următor și adaugă un comentariu."
|
||||
},
|
||||
"report_cancelButtonText": {
|
||||
"message": "Anulează"
|
@ -3,7 +3,7 @@
|
||||
"message": "Кто-то сказал диалоги согласия на использование cookie? 😋"
|
||||
},
|
||||
"contextMenu_issueOption": {
|
||||
"message": "Следить за этой проблемой"
|
||||
"message": "Следить за этим вопросом на GitHub"
|
||||
},
|
||||
"contextMenu_reportOption": {
|
||||
"message": "Сообщить об этом сайте"
|
||||
@ -20,9 +20,6 @@
|
||||
"options_clearButton": {
|
||||
"message": "Очистить список"
|
||||
},
|
||||
"options_empty": {
|
||||
"message": "Исключения не найдены"
|
||||
},
|
||||
"options_exclusionListTitle": {
|
||||
"message": "Список исключений"
|
||||
},
|
||||
@ -41,9 +38,6 @@
|
||||
"popup_bannerIssueWontFix": {
|
||||
"message": "Для этой страницы было сообщено о проблеме, но она не будет исправлена. Обратите внимание, что расширение может работать некорректно на этой странице. Мы настоятельно рекомендуем отключить расширение, используя кнопку ниже."
|
||||
},
|
||||
"popup_bannerSupport": {
|
||||
"message": "Этот сайт больше не поддерживается. Для получения дополнительной информации продолжайте читать здесь"
|
||||
},
|
||||
"popup_bannerUpdateAvailable": {
|
||||
"message": "Доступно обновление. Установите последнюю версию, чтобы устранить известные проблемы перед отправкой новых отчётов."
|
||||
},
|
||||
@ -63,7 +57,7 @@
|
||||
"message": "Оцените это расширение"
|
||||
},
|
||||
"report_bodyText": {
|
||||
"message": "Пожалуйста, не делитесь личной информацией в этом отчете. Если вы хотите добавить больше деталей, перейдите к проблеме на следующем этапе и добавьте комментарий."
|
||||
"message": "Пожалуйста, не делитесь личной информацией в этом отчете. Если вы хотите добавить больше деталей, откройте вопрос на GitHub на следующем шаге и добавьте комментарий."
|
||||
},
|
||||
"report_cancelButtonText": {
|
||||
"message": "Отмена"
|
BIN
packages/browser-extension/src/assets/icons/128.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
packages/browser-extension/src/assets/icons/16.png
Normal file
After Width: | Height: | Size: 641 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
BIN
packages/browser-extension/src/assets/icons/on.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@ -1,11 +0,0 @@
|
||||
import onClicked from './utils/contextMenus/onClicked';
|
||||
import onInstalled from './utils/runtime/onInstalled';
|
||||
import onStartup from './utils/runtime/onStartup';
|
||||
import onBeforeRequest from './utils/webRequest/onBeforeRequest';
|
||||
import onErrorOccurred from './utils/webRequest/onErrorOccurred';
|
||||
|
||||
chrome.contextMenus.onClicked.addListener(onClicked);
|
||||
chrome.runtime.onInstalled.addListener(onInstalled);
|
||||
chrome.runtime.onStartup.addListener(onStartup);
|
||||
chrome.webRequest.onBeforeRequest.addListener(onBeforeRequest as any, { urls: ['<all_urls>'] });
|
||||
chrome.webRequest.onErrorOccurred.addListener(onErrorOccurred, { urls: ['<all_urls>'] });
|
@ -1,19 +0,0 @@
|
||||
import type { PlasmoMessaging } from '@plasmohq/messaging';
|
||||
|
||||
import { DEFAULT_EXTENSION_DATA } from '~utils/constants';
|
||||
import { storage } from '~utils/storage';
|
||||
import type { ExtensionData } from '~utils/types';
|
||||
|
||||
const handler: PlasmoMessaging.MessageHandler<never, Response> = async (req, res) => {
|
||||
const data = (await storage.get<ExtensionData>('data')) || DEFAULT_EXTENSION_DATA;
|
||||
|
||||
res.send({ data, success: true });
|
||||
return;
|
||||
};
|
||||
|
||||
interface Response {
|
||||
readonly data?: ExtensionData;
|
||||
readonly success: boolean;
|
||||
}
|
||||
|
||||
export default handler;
|
@ -1,29 +0,0 @@
|
||||
import type { PlasmoMessaging } from '@plasmohq/messaging';
|
||||
|
||||
import { API_URL } from '~utils/constants';
|
||||
import { storage } from '~utils/storage';
|
||||
import type { ExtensionData } from '~utils/types';
|
||||
|
||||
const handler: PlasmoMessaging.MessageHandler<never, Response> = async (req, res) => {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/data/`);
|
||||
const { data } = await response.json();
|
||||
|
||||
if (data) {
|
||||
await storage.set('data', data);
|
||||
res.send({ data, success: true });
|
||||
return;
|
||||
}
|
||||
|
||||
res.send({ success: false });
|
||||
} catch {
|
||||
res.send({ success: false });
|
||||
}
|
||||
};
|
||||
|
||||
interface Response {
|
||||
readonly data?: ExtensionData;
|
||||
readonly success: boolean;
|
||||
}
|
||||
|
||||
export default handler;
|
@ -1,63 +0,0 @@
|
||||
import type { PlasmoMessaging } from '@plasmohq/messaging';
|
||||
|
||||
import { API_URL, DEFAULT_DOMAIN_CONFIG } from '~utils/constants';
|
||||
import { noop } from '~utils/error';
|
||||
import { storage } from '~utils/storage';
|
||||
import type { DomainConfig } from '~utils/types';
|
||||
|
||||
import updateIconHandler from '../extension/updateIcon';
|
||||
|
||||
const handler: PlasmoMessaging.MessageHandler<Request, Response> = async (req, res) => {
|
||||
const { domain } = req.body ?? {};
|
||||
const { tab } = req.sender || {};
|
||||
|
||||
if (domain) {
|
||||
let data = (await storage.get<DomainConfig>(domain)) || DEFAULT_DOMAIN_CONFIG;
|
||||
const now = Date.now();
|
||||
|
||||
if ((data.issue?.expiresAt && now > data.issue.expiresAt) || !data.issue?.expiresAt) {
|
||||
const response = await fetch(`${API_URL}/issues/${domain}/`);
|
||||
|
||||
if (response.status === 429) {
|
||||
res.send({ data, success: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const issue = await response.json();
|
||||
|
||||
if (issue.success) {
|
||||
data = { ...data, issue: { ...issue.data, expiresAt: now + 8 * 60 * 60 * 1000 } };
|
||||
await storage.set(domain, data);
|
||||
|
||||
if (tab?.id !== undefined) {
|
||||
await updateIconHandler(
|
||||
{ body: { domain }, name: 'extension/updateIcon', sender: { tab } },
|
||||
{ send: noop }
|
||||
);
|
||||
}
|
||||
|
||||
res.send({ data, success: true });
|
||||
return;
|
||||
}
|
||||
|
||||
data = { ...data, issue: { expiresAt: now + 24 * 60 * 60 * 1000 } };
|
||||
await storage.set(domain, data);
|
||||
}
|
||||
|
||||
res.send({ data, success: true });
|
||||
return;
|
||||
}
|
||||
|
||||
res.send({ success: false });
|
||||
};
|
||||
|
||||
interface Request {
|
||||
readonly domain: string;
|
||||
}
|
||||
|
||||
interface Response {
|
||||
readonly data?: DomainConfig;
|
||||
readonly success: boolean;
|
||||
}
|
||||
|
||||
export default handler;
|
@ -1,35 +0,0 @@
|
||||
import type { PlasmoMessaging } from '@plasmohq/messaging';
|
||||
|
||||
import { API_URL } from '~utils/constants';
|
||||
import { storage } from '~utils/storage';
|
||||
|
||||
const handler: PlasmoMessaging.MessageHandler<never, Response> = async (req, res) => {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/version/`);
|
||||
|
||||
if (response.status === 429) {
|
||||
res.send({ success: false });
|
||||
return;
|
||||
}
|
||||
|
||||
const { data } = await response.json();
|
||||
const { version } = chrome.runtime.getManifest();
|
||||
|
||||
if (data !== version) {
|
||||
await storage.set('updateAvailable', data);
|
||||
res.send({ success: true });
|
||||
return;
|
||||
}
|
||||
|
||||
await storage.remove('updateAvailable');
|
||||
res.send({ success: false });
|
||||
} catch {
|
||||
res.send({ success: false });
|
||||
}
|
||||
};
|
||||
|
||||
interface Response {
|
||||
readonly success: boolean;
|
||||
}
|
||||
|
||||
export default handler;
|
@ -1,25 +0,0 @@
|
||||
import type { PlasmoMessaging } from '@plasmohq/messaging';
|
||||
|
||||
const handler: PlasmoMessaging.MessageHandler<Request, Response> = async (req, res) => {
|
||||
const { value } = req.body || {};
|
||||
const { frameId, tab } = req.sender || {};
|
||||
|
||||
if (frameId === 0 && tab?.id !== undefined) {
|
||||
await chrome.action.setBadgeBackgroundColor({ color: '#6b7280' });
|
||||
await chrome.action.setBadgeText({ tabId: tab.id, text: value ? `${value}` : '' });
|
||||
res.send({ success: true });
|
||||
return;
|
||||
}
|
||||
|
||||
res.send({ success: false });
|
||||
};
|
||||
|
||||
interface Request {
|
||||
readonly value: number;
|
||||
}
|
||||
|
||||
interface Response {
|
||||
readonly success: boolean;
|
||||
}
|
||||
|
||||
export default handler;
|
@ -1,49 +0,0 @@
|
||||
import type { PlasmoMessaging } from '@plasmohq/messaging';
|
||||
import offIcon from 'url:~assets/off.png';
|
||||
import onIcon from 'url:~assets/on.png';
|
||||
import warnIcon from 'url:~assets/warn.png';
|
||||
|
||||
import { DEFAULT_DOMAIN_CONFIG, DEFAULT_EXTENSION_DATA } from '~utils/constants';
|
||||
import { validateSupport } from '~utils/domain';
|
||||
import { storage } from '~utils/storage';
|
||||
import type { DomainConfig, ExtensionData } from '~utils/types';
|
||||
|
||||
const handler: PlasmoMessaging.MessageHandler<Request, Response> = async (req, res) => {
|
||||
const { domain } = req.body || {};
|
||||
const { frameId, tab } = req.sender || {};
|
||||
|
||||
if (domain && frameId === 0 && tab?.id !== undefined) {
|
||||
const config = (await storage.get<DomainConfig>(domain)) || DEFAULT_DOMAIN_CONFIG;
|
||||
const data = (await storage.get<ExtensionData>('data')) || DEFAULT_EXTENSION_DATA;
|
||||
|
||||
if (
|
||||
config.on &&
|
||||
tab.url &&
|
||||
validateSupport(new URL(tab.url).hostname, data.exclusions.domains)
|
||||
) {
|
||||
if (config.issue?.url) {
|
||||
await chrome.action.setIcon({ path: warnIcon, tabId: tab.id });
|
||||
res.send({ success: true });
|
||||
return;
|
||||
}
|
||||
|
||||
await chrome.action.setIcon({ path: onIcon, tabId: tab.id });
|
||||
res.send({ success: true });
|
||||
} else {
|
||||
await chrome.action.setIcon({ path: offIcon, tabId: tab.id });
|
||||
res.send({ success: true });
|
||||
}
|
||||
}
|
||||
|
||||
res.send({ success: false });
|
||||
};
|
||||
|
||||
interface Request {
|
||||
readonly domain: string;
|
||||
}
|
||||
|
||||
interface Response {
|
||||
readonly success: boolean;
|
||||
}
|
||||
|
||||
export default handler;
|
@ -1,15 +0,0 @@
|
||||
import { REPORT_MENU_ITEM_ID, SETTINGS_MENU_ITEM_ID } from '~utils/constants';
|
||||
import { suppressLastError } from '~utils/error';
|
||||
|
||||
export default function onClicked(data: chrome.contextMenus.OnClickData) {
|
||||
switch (data.menuItemId) {
|
||||
case REPORT_MENU_ITEM_ID:
|
||||
chrome.action.openPopup(suppressLastError);
|
||||
break;
|
||||
case SETTINGS_MENU_ITEM_ID:
|
||||
chrome.runtime.openOptionsPage(suppressLastError);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
import databaseRefreshHandler from '~background/messages/database/refresh';
|
||||
import {
|
||||
EXTENSION_MENU_ITEM_ID,
|
||||
REPORT_MENU_ITEM_ID,
|
||||
SETTINGS_MENU_ITEM_ID,
|
||||
} from '~utils/constants';
|
||||
import { noop } from '~utils/error';
|
||||
import { storage } from '~utils/storage';
|
||||
|
||||
export default async function onInstalled() {
|
||||
await chrome.contextMenus.removeAll();
|
||||
|
||||
const documentUrlPatterns = chrome.runtime.getManifest().content_scripts?.[0].matches;
|
||||
|
||||
await chrome.contextMenus.create({
|
||||
contexts: ['all'],
|
||||
documentUrlPatterns,
|
||||
id: EXTENSION_MENU_ITEM_ID,
|
||||
title: 'Cookie Dialog Monster',
|
||||
});
|
||||
await chrome.contextMenus.create({
|
||||
contexts: ['all'],
|
||||
documentUrlPatterns,
|
||||
id: SETTINGS_MENU_ITEM_ID,
|
||||
parentId: EXTENSION_MENU_ITEM_ID,
|
||||
title: chrome.i18n.getMessage('contextMenu_settingsOption'),
|
||||
});
|
||||
await chrome.contextMenus.create({
|
||||
contexts: ['all'],
|
||||
documentUrlPatterns,
|
||||
id: REPORT_MENU_ITEM_ID,
|
||||
parentId: EXTENSION_MENU_ITEM_ID,
|
||||
title: chrome.i18n.getMessage('contextMenu_reportOption'),
|
||||
});
|
||||
|
||||
await storage.remove('updateAvailable');
|
||||
await databaseRefreshHandler({ name: 'database/refresh' }, { send: noop });
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import databaseRefreshHandler from '~background/messages/database/refresh';
|
||||
import extensionUpdateAvailableHandler from '~background/messages/extension/updateAvailable';
|
||||
import { noop } from '~utils/error';
|
||||
import { storage } from '~utils/storage';
|
||||
|
||||
export default async function onStartup() {
|
||||
await storage.remove('updateAvailable');
|
||||
await databaseRefreshHandler({ name: 'database/refresh' }, { send: noop });
|
||||
await extensionUpdateAvailableHandler({ name: 'extension/updateAvailable' }, { send: noop });
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import { DEFAULT_DOMAIN_CONFIG, DEFAULT_EXTENSION_DATA } from '~utils/constants';
|
||||
import { formatDomainFromURL, validateSupport } from '~utils/domain';
|
||||
import { storage } from '~utils/storage';
|
||||
import type { DomainConfig, ExtensionData } from '~utils/types';
|
||||
|
||||
export default async function onBeforeRequest(details: chrome.webRequest.WebRequestBodyDetails) {
|
||||
const { tabId, type, url } = details;
|
||||
const location = new URL(url);
|
||||
const domain = formatDomainFromURL(location);
|
||||
|
||||
if (tabId > -1 && type === 'main_frame') {
|
||||
const data = (await storage.get<ExtensionData>('data')) || DEFAULT_EXTENSION_DATA;
|
||||
|
||||
if (!validateSupport(location.hostname, data.exclusions.domains)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const config = (await storage.get<DomainConfig>(domain)) || DEFAULT_DOMAIN_CONFIG;
|
||||
|
||||
if (data.rules.length) {
|
||||
const rulesWithTabId = data.rules.map((rule) => ({
|
||||
...rule,
|
||||
condition: { ...rule.condition, tabIds: [tabId] },
|
||||
}));
|
||||
|
||||
chrome.declarativeNetRequest.updateSessionRules({
|
||||
addRules: config.on ? rulesWithTabId : undefined,
|
||||
removeRuleIds: data.rules.map((rule) => rule.id),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
import { sendToContentScript } from '@plasmohq/messaging';
|
||||
|
||||
export default async function onErrorOccurred(details: chrome.webRequest.WebResponseErrorDetails) {
|
||||
const { error, tabId } = details;
|
||||
|
||||
if (error === 'net::ERR_BLOCKED_BY_CLIENT' && tabId > -1) {
|
||||
await sendToContentScript({
|
||||
body: {
|
||||
value: error,
|
||||
},
|
||||
name: 'INCREASE_LOG_COUNT',
|
||||
tabId,
|
||||
});
|
||||
}
|
||||
}
|
@ -1,368 +0,0 @@
|
||||
import { sendToBackground } from '@plasmohq/messaging';
|
||||
import type { PlasmoCSConfig } from 'plasmo';
|
||||
|
||||
import { DEFAULT_DOMAIN_CONFIG, DEFAULT_EXTENSION_DATA } from '~utils/constants';
|
||||
import { formatDomainFromURL, validateSupport } from '~utils/domain';
|
||||
import type { DomainConfig, ExtensionData } from '~utils/types';
|
||||
|
||||
export const config: PlasmoCSConfig = {
|
||||
all_frames: true,
|
||||
matches: ['http://*/*', 'https://*/*'],
|
||||
run_at: 'document_start',
|
||||
};
|
||||
|
||||
class NotifiableSet extends Set {
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
}
|
||||
|
||||
add(value: any): this {
|
||||
super.add(value);
|
||||
sendToBackground({ body: { value: super.size }, name: 'extension/updateBadge' });
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
let { actions, exclusions, keywords, tokens }: ExtensionData = DEFAULT_EXTENSION_DATA;
|
||||
let domainConfig: DomainConfig = DEFAULT_DOMAIN_CONFIG;
|
||||
let initiallyVisible: boolean = false;
|
||||
|
||||
const domain = formatDomainFromURL(new URL(location.href));
|
||||
const log = new NotifiableSet();
|
||||
const observer = new MutationObserver(mutationHandler);
|
||||
const options: MutationObserverInit = { childList: true, subtree: true };
|
||||
const seen = new Set<HTMLElement>();
|
||||
|
||||
document.addEventListener('visibilitychange', setUpAfterWaitForBody);
|
||||
window.addEventListener('pageshow', setUpAfterWaitForBody);
|
||||
setUpAfterWaitForBody();
|
||||
|
||||
function clean(elements: readonly HTMLElement[], skipMatch?: boolean): void {
|
||||
let index = 0;
|
||||
const size = 50;
|
||||
|
||||
function chunk() {
|
||||
const end = Math.min(index + size, elements.length);
|
||||
|
||||
for (; index < end; index++) {
|
||||
const element = elements[index];
|
||||
|
||||
if (match(element, skipMatch)) {
|
||||
if (element instanceof HTMLDialogElement) element.close();
|
||||
hide(element);
|
||||
log.add(`${Date.now()}`);
|
||||
}
|
||||
|
||||
seen.add(element);
|
||||
}
|
||||
|
||||
if (index < elements.length) {
|
||||
requestAnimationFrame(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(chunk);
|
||||
}
|
||||
|
||||
function forceClean(from: HTMLElement): void {
|
||||
const elements = getElements(tokens.selectors, { filterEarly: true, from });
|
||||
|
||||
if (elements.length) {
|
||||
fix();
|
||||
clean(elements, true);
|
||||
}
|
||||
}
|
||||
|
||||
function hasKeyword(element: HTMLElement): boolean {
|
||||
return !!keywords.length && !!element.outerHTML.match(new RegExp(keywords.join('|')));
|
||||
}
|
||||
|
||||
function filterNodeEarly(node: Node, stopRecursion?: boolean): readonly HTMLElement[] {
|
||||
if (node.nodeType !== Node.ELEMENT_NODE || !(node instanceof HTMLElement)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (hasKeyword(node) && !stopRecursion) {
|
||||
return [node, ...[...node.children].flatMap((node) => filterNodeEarly(node, true))];
|
||||
}
|
||||
|
||||
return [node];
|
||||
}
|
||||
|
||||
function fix(): void {
|
||||
for (const action of actions) {
|
||||
const { name, property, selector } = action;
|
||||
|
||||
if (domain.match(action.domain.replaceAll(/\*/g, '[^ ]*'))) {
|
||||
switch (name) {
|
||||
case 'click': {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (element instanceof HTMLElement) {
|
||||
element.click();
|
||||
log.add(name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'remove': {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (element instanceof HTMLElement && property) {
|
||||
element.style.removeProperty(property);
|
||||
log.add(name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'reload': {
|
||||
window.location.reload();
|
||||
break;
|
||||
}
|
||||
case 'reset': {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (element instanceof HTMLElement && property) {
|
||||
element.style.setProperty(property, 'initial', 'important');
|
||||
log.add(name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'resetAll': {
|
||||
const elements = getElements(selector);
|
||||
|
||||
if (property) {
|
||||
elements.forEach((e) => e?.style?.setProperty(property, 'initial', 'important'));
|
||||
log.add(name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const backdrops = getElements(tokens.backdrops);
|
||||
|
||||
for (const backdrop of backdrops) {
|
||||
if (backdrop.children.length === 0 && !seen.has(backdrop)) {
|
||||
log.add(`${Date.now()}`);
|
||||
seen.add(backdrop);
|
||||
hide(backdrop);
|
||||
}
|
||||
}
|
||||
|
||||
const skips = exclusions.overflows.map((x) => (x.split('.').length < 3 ? `*${x}` : x));
|
||||
|
||||
if (!skips.some((x) => domain.match(x.replaceAll(/\*/g, '[^ ]*')))) {
|
||||
for (const element of [document.body, document.documentElement]) {
|
||||
element?.classList.remove(...(tokens.classes ?? []));
|
||||
element?.style.setProperty('position', 'initial', 'important');
|
||||
element?.style.setProperty('overflow-y', 'initial', 'important');
|
||||
}
|
||||
}
|
||||
|
||||
const ionRouterOutlet = document.getElementsByTagName('ion-router-outlet')[0];
|
||||
|
||||
if (ionRouterOutlet) {
|
||||
// 2024-08-02: fix #644 temporarily
|
||||
ionRouterOutlet.removeAttribute('inert');
|
||||
log.add('ion-router-outlet');
|
||||
}
|
||||
|
||||
const t4Wrapper = document.getElementsByClassName('t4-wrapper')[0];
|
||||
|
||||
if (t4Wrapper) {
|
||||
log.add('t4-wrapper');
|
||||
// 2024-09-12: fix #945 temporarily
|
||||
t4Wrapper.removeAttribute('inert');
|
||||
}
|
||||
}
|
||||
|
||||
function getElements(selector: Selector, params: GetElementsParams = {}): readonly HTMLElement[] {
|
||||
const { filterEarly, from } = params;
|
||||
let result: HTMLElement[] = [];
|
||||
|
||||
if (selector.length) {
|
||||
result = [
|
||||
...(from ?? document).querySelectorAll(selector as string),
|
||||
] as unknown as HTMLElement[];
|
||||
|
||||
if (filterEarly) {
|
||||
result = result.flatMap((node) => filterNodeEarly(node));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getElementsWithChildren(
|
||||
selector: Selector,
|
||||
params: GetElementsParams = {}
|
||||
): readonly HTMLElement[] {
|
||||
const elements = getElements(selector, params);
|
||||
const elementsWithChildren = elements.flatMap((element) => [element, ...element.children]);
|
||||
|
||||
return elementsWithChildren as unknown as readonly HTMLElement[];
|
||||
}
|
||||
|
||||
function hide(element: HTMLElement) {
|
||||
element.style.setProperty('clip-path', 'circle(0px)', 'important');
|
||||
element.style.setProperty('display', 'none', 'important');
|
||||
element.style.setProperty('height', '0px', 'important');
|
||||
element.style.setProperty('overflow', 'hidden', 'important');
|
||||
element.style.setProperty('transform', 'scale(0)', 'important');
|
||||
}
|
||||
|
||||
function isInViewport(element: HTMLElement): boolean {
|
||||
const styles = window.getComputedStyle(element);
|
||||
const height = window.innerHeight || document.documentElement.clientHeight;
|
||||
const position = element.getBoundingClientRect();
|
||||
const scroll = window.scrollY;
|
||||
|
||||
return (
|
||||
position.bottom === position.top ||
|
||||
(scroll + position.top <= scroll + height && scroll + position.bottom >= scroll) ||
|
||||
styles.animationDuration !== '0s' ||
|
||||
styles.transitionDuration !== '0s'
|
||||
);
|
||||
}
|
||||
|
||||
function match(element: HTMLElement, skipMatch?: boolean): boolean {
|
||||
if (!exclusions.tags.length || !tokens.selectors.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(element instanceof HTMLElement) || !element.tagName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (seen.has(element)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tagName = element.tagName.toUpperCase();
|
||||
|
||||
if (exclusions.tags.includes(tagName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasAttributes = !!element.getAttributeNames().filter((x) => x !== 'data-nosnippet').length;
|
||||
|
||||
if (!hasAttributes && !tagName.includes('-')) {
|
||||
forceClean(element);
|
||||
}
|
||||
|
||||
// 2023-06-10: fix #113 temporarily
|
||||
if (element.classList.contains('chat-line__message')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2024-08-03: fix #701 temporarily
|
||||
if (element.classList.contains('sellos')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isDialog = tagName === 'DIALOG' && element.getAttribute('open') === 'true';
|
||||
const isFakeDialog = tagName === 'DIV' && element.className.includes('cmp');
|
||||
|
||||
return (
|
||||
(isDialog || isFakeDialog || isInViewport(element)) &&
|
||||
(skipMatch || element.matches(tokens.selectors as unknown as string))
|
||||
);
|
||||
}
|
||||
|
||||
function mutationHandler(mutations: readonly MutationRecord[]): void {
|
||||
if (!domainConfig.on || !tokens.selectors.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodes = mutations.flatMap((mutation) => [...mutation.addedNodes]);
|
||||
const elements = nodes.flatMap((node) => filterNodeEarly(node));
|
||||
|
||||
run({ elements });
|
||||
}
|
||||
|
||||
function run(params: RunParams = {}): void {
|
||||
const { containers, elements, skipMatch } = params;
|
||||
|
||||
if (document.body?.children.length && domainConfig.on && tokens.selectors.length) {
|
||||
fix();
|
||||
|
||||
if (elements?.length) {
|
||||
clean(elements, skipMatch);
|
||||
}
|
||||
|
||||
if (elements === undefined && containers?.length) {
|
||||
clean(containers.flatMap((x) => getElementsWithChildren(x, { filterEarly: true })));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function runtimeMessageHandler(message: RuntimeMessage): void {
|
||||
switch (message.name) {
|
||||
case 'INCREASE_LOG_COUNT': {
|
||||
log.add(message.value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async function setUp(params: SetUpParams = {}): Promise<void> {
|
||||
const { data } = await sendToBackground({ name: 'database/get' });
|
||||
|
||||
exclusions = data?.exclusions ?? exclusions;
|
||||
sendToBackground({ body: { domain }, name: 'extension/updateIcon' });
|
||||
|
||||
if (!validateSupport(location.hostname, exclusions.domains)) {
|
||||
observer.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
domainConfig = (await sendToBackground({ body: { domain }, name: 'domain/config' }))?.data;
|
||||
|
||||
if (domainConfig.on) {
|
||||
chrome.runtime.onMessage.addListener(runtimeMessageHandler);
|
||||
|
||||
actions = data?.actions ?? actions;
|
||||
keywords = data?.keywords ?? keywords;
|
||||
tokens = data?.tokens ?? tokens;
|
||||
|
||||
observer.observe(document.body ?? document.documentElement, options);
|
||||
if (!params.skipRunFn) run({ containers: tokens.containers });
|
||||
}
|
||||
}
|
||||
|
||||
async function setUpAfterWaitForBody(): Promise<void> {
|
||||
if (document.visibilityState === 'visible' && !initiallyVisible) {
|
||||
if (document.body) {
|
||||
initiallyVisible = true;
|
||||
await setUp();
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(setUpAfterWaitForBody, 50);
|
||||
}
|
||||
}
|
||||
|
||||
interface GetElementsParams {
|
||||
readonly filterEarly?: boolean;
|
||||
readonly from?: HTMLElement;
|
||||
}
|
||||
|
||||
interface RunParams {
|
||||
readonly containers?: readonly string[];
|
||||
readonly elements?: readonly HTMLElement[];
|
||||
readonly skipMatch?: boolean;
|
||||
}
|
||||
|
||||
interface RuntimeMessage {
|
||||
readonly name: string;
|
||||
readonly value?: string;
|
||||
}
|
||||
|
||||
type Selector = string | readonly string[];
|
||||
|
||||
interface SetUpParams {
|
||||
readonly skipRunFn?: boolean;
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
export function useBrowser(): UseBrowserResult {
|
||||
const isChromium = navigator.userAgent.indexOf('Chrome') !== -1;
|
||||
const isEdge = navigator.userAgent.indexOf('Edg') !== -1;
|
||||
const isFirefox = navigator.userAgent.indexOf('Firefox') !== -1;
|
||||
|
||||
return {
|
||||
isChromium,
|
||||
isEdge,
|
||||
isFirefox,
|
||||
};
|
||||
}
|
||||
|
||||
export interface UseBrowserResult {
|
||||
readonly isChromium: boolean;
|
||||
readonly isEdge: boolean;
|
||||
readonly isFirefox: boolean;
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import { sendToBackground } from '@plasmohq/messaging';
|
||||
import { useStorage } from '@plasmohq/storage/hook';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { storage } from '~utils/storage';
|
||||
import type { ExtensionData } from '~utils/types';
|
||||
|
||||
export function useData(): UseDataResult {
|
||||
const timeoutId = useRef<number | undefined>(undefined);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [isRefetched, setIsRefetched] = useState<boolean>(false);
|
||||
const [data] = useStorage<ExtensionData>({ instance: storage, key: 'data' });
|
||||
|
||||
const refetch = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
await sendToBackground({ name: 'database/refresh' });
|
||||
setIsLoading(false);
|
||||
setIsRefetched(true);
|
||||
timeoutId.current = window.setTimeout(() => setIsRefetched(false), 60000);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
window.clearTimeout(timeoutId.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
data,
|
||||
isLoading,
|
||||
isRefetched,
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
|
||||
export interface UseDataResult {
|
||||
readonly data?: ExtensionData;
|
||||
readonly isLoading: boolean;
|
||||
readonly isRefetched: boolean;
|
||||
readonly refetch: () => Promise<void>;
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
import { useStorage } from '@plasmohq/storage/hook';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { DEFAULT_DOMAIN_CONFIG } from '~utils/constants';
|
||||
import { formatDomainFromURL, validateSupport } from '~utils/domain';
|
||||
import { storage } from '~utils/storage';
|
||||
import type { DomainConfig, ExtensionData } from '~utils/types';
|
||||
|
||||
import { useTab } from './useTab';
|
||||
|
||||
export function useDomain(): UseDomainResult {
|
||||
const [domain, setDomain] = useState<string>('');
|
||||
const [config, setConfig] = useStorage<DomainConfig>({ instance: storage, key: domain });
|
||||
const [data] = useStorage<ExtensionData>({ instance: storage, key: 'data' });
|
||||
const tab = useTab();
|
||||
const tabId = tab ? (tab.id ?? -1) : -1;
|
||||
|
||||
const isSupported = useMemo(
|
||||
() =>
|
||||
!!data?.exclusions.domains.length &&
|
||||
!!tab?.url &&
|
||||
validateSupport(new URL(tab.url).hostname, data.exclusions.domains),
|
||||
[data, tab]
|
||||
);
|
||||
|
||||
const toggleStatus = useCallback(async () => {
|
||||
if (tabId > -1) {
|
||||
await setConfig((prev = DEFAULT_DOMAIN_CONFIG) => ({ ...prev, on: !prev.on }));
|
||||
await chrome.tabs.reload(tabId, { bypassCache: true });
|
||||
}
|
||||
}, [domain, tabId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tab?.url) {
|
||||
setDomain(formatDomainFromURL(new URL(tab.url)));
|
||||
}
|
||||
}, [tab]);
|
||||
|
||||
return {
|
||||
config: config || DEFAULT_DOMAIN_CONFIG,
|
||||
domain,
|
||||
isSupported,
|
||||
toggleStatus,
|
||||
};
|
||||
}
|
||||
|
||||
export interface UseDomainResult {
|
||||
readonly config: DomainConfig;
|
||||
readonly domain: string;
|
||||
readonly isSupported: boolean;
|
||||
readonly toggleStatus: () => Promise<void>;
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { API_URL } from '~utils/constants';
|
||||
import type { ReportParams, ReportResult } from '~utils/types';
|
||||
|
||||
import { useExtension } from './useExtension';
|
||||
|
||||
export function useDomainReport(): UseDomainReportResult {
|
||||
const extension = useExtension();
|
||||
const [errors, setErrors] = useState<{ readonly [key: string]: string }>({});
|
||||
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||
|
||||
const report = useCallback(
|
||||
async (params: ReportParams): Promise<ReportResult | undefined> => {
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
|
||||
const reason = params.reason;
|
||||
const url = params.url;
|
||||
const errors = validateForm(params);
|
||||
|
||||
if (errors) {
|
||||
setErrors(errors);
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const userAgent = navigator.userAgent;
|
||||
const version = extension.version;
|
||||
const body = JSON.stringify({ reason, url, userAgent, version });
|
||||
const headers = { 'Cache-Control': 'no-cache', 'Content-type': 'application/json' };
|
||||
const requestInit = { body, headers, method: 'POST' };
|
||||
const response = await fetch(`${API_URL}/report/`, requestInit);
|
||||
|
||||
if (response.ok) {
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
if (response.status === 429) {
|
||||
throw new Error(chrome.i18n.getMessage('report_rateLimitError'));
|
||||
}
|
||||
|
||||
throw new Error(chrome.i18n.getMessage('report_unknownError'));
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
setErrors({ url: error.message });
|
||||
}
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
},
|
||||
[extension.version]
|
||||
);
|
||||
|
||||
return {
|
||||
errors,
|
||||
isSubmitting,
|
||||
report,
|
||||
};
|
||||
}
|
||||
|
||||
function validateForm(params: ReportParams): Partial<ReportParams> | undefined {
|
||||
const { reason, url } = params;
|
||||
let errors: Partial<ReportParams> | undefined = undefined;
|
||||
|
||||
if (!reason || reason.length < 10 || reason.length > 1000) {
|
||||
errors = {
|
||||
...(errors ?? {}),
|
||||
reason: chrome.i18n.getMessage('report_reasonInputError'),
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
if (/\s/.test(url)) throw new Error();
|
||||
new URL(url);
|
||||
} catch {
|
||||
errors = {
|
||||
...(errors ?? {}),
|
||||
url: chrome.i18n.getMessage('report_urlInputError'),
|
||||
};
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
export interface UseDomainReportResult {
|
||||
readonly errors: { readonly [key: string]: string };
|
||||
readonly isSubmitting: boolean;
|
||||
readonly report: (params: ReportParams) => Promise<ReportResult | undefined>;
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { storage } from '~utils/storage';
|
||||
|
||||
async function getExclusions(): Promise<readonly Exclusion[]> {
|
||||
const result = await storage.rawGetAll();
|
||||
|
||||
return Object.keys(result).flatMap((key) => (result[key]?.on === false ? [{ name: key }] : []));
|
||||
}
|
||||
|
||||
export function useExclusions(): readonly Exclusion[] {
|
||||
const [exclusions, setExclusions] = useState<readonly Exclusion[]>([]);
|
||||
|
||||
const handleStorageChange = useCallback(
|
||||
(
|
||||
changes: { [key: string]: chrome.storage.StorageChange },
|
||||
areaName: chrome.storage.AreaName
|
||||
) => {
|
||||
if (areaName === 'local') {
|
||||
for (const key in changes) {
|
||||
if (key === 'data') {
|
||||
continue;
|
||||
}
|
||||
|
||||
setExclusions((prev) => {
|
||||
if (changes[key].newValue?.on === false) {
|
||||
return [...prev, { name: key }];
|
||||
}
|
||||
|
||||
return prev.filter((exclusion) => exclusion.name !== key);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
chrome.storage.onChanged.addListener(handleStorageChange);
|
||||
getExclusions().then(setExclusions);
|
||||
|
||||
return () => {
|
||||
chrome.storage.onChanged.removeListener(handleStorageChange);
|
||||
};
|
||||
}, [handleStorageChange]);
|
||||
|
||||
return exclusions;
|
||||
}
|
||||
|
||||
export interface Exclusion {
|
||||
readonly name: string;
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import { useStorage } from '@plasmohq/storage/hook';
|
||||
|
||||
import { storage } from '~utils/storage';
|
||||
|
||||
export function useExtension(): UseExtensionResult {
|
||||
const [updateAvailable] = useStorage({ instance: storage, key: 'updateAvailable' });
|
||||
const { version } = chrome.runtime.getManifest();
|
||||
|
||||
return {
|
||||
updateAvailable,
|
||||
version,
|
||||
};
|
||||
}
|
||||
|
||||
export interface UseExtensionResult {
|
||||
readonly updateAvailable?: string;
|
||||
readonly version: string;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function useTab(): chrome.tabs.Tab | undefined {
|
||||
const [tab, setTab] = useState<chrome.tabs.Tab | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }).then((tabs) => setTab(tabs[0]));
|
||||
}, []);
|
||||
|
||||
return tab;
|
||||
}
|
67
packages/browser-extension/src/manifest.json
Normal file
@ -0,0 +1,67 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Cookie Dialog Monster",
|
||||
"version": "8.0.1",
|
||||
"default_locale": "en",
|
||||
"description": "__MSG_appDesc__",
|
||||
"icons": {
|
||||
"16": "assets/icons/16.png",
|
||||
"48": "assets/icons/48.png",
|
||||
"128": "assets/icons/128.png"
|
||||
},
|
||||
"action": {
|
||||
"default_icon": "assets/icons/off.png",
|
||||
"default_title": "Cookie Dialog Monster"
|
||||
},
|
||||
"options_page": "options.html",
|
||||
"author": "wanhose",
|
||||
"background": {
|
||||
"scripts": ["scripts/background.js"],
|
||||
"service_worker": "scripts/background.js"
|
||||
},
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "{77e2c00b-e173-4604-863d-01645d8d2826}",
|
||||
"strict_min_version": "126.0",
|
||||
"update_url": "https://www.cookie-dialog-monster.com/mozilla/updates.json"
|
||||
}
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"all_frames": true,
|
||||
"exclude_matches": [
|
||||
"*://*.bauhaus.cz/*",
|
||||
"*://*.codesandbox.io/*",
|
||||
"*://*.facebook.com/*",
|
||||
"*://*.googleapis.com/embed/*",
|
||||
"*://*.olympics.com/*",
|
||||
"*://*.youtube-nocookie.com/embed/*",
|
||||
"*://*.youtube.com/embed/*",
|
||||
"*://www.youtube.com/*",
|
||||
"*://translate.google.ca/*",
|
||||
"*://translate.google.co.in/*",
|
||||
"*://translate.google.co.jp/*",
|
||||
"*://translate.google.co.uk/*",
|
||||
"*://translate.google.com.au/*",
|
||||
"*://translate.google.com.br/*",
|
||||
"*://translate.google.com/*",
|
||||
"*://translate.google.de/*",
|
||||
"*://translate.google.es/*",
|
||||
"*://translate.google.fr/*",
|
||||
"*://translate.google.it/*",
|
||||
"*://www.cookie-dialog-monster.com/*"
|
||||
],
|
||||
"js": ["scripts/content.js"],
|
||||
"matches": ["http://*/*", "https://*/*"],
|
||||
"run_at": "document_start"
|
||||
}
|
||||
],
|
||||
"host_permissions": ["http://*/*", "https://*/*"],
|
||||
"permissions": ["contextMenus", "declarativeNetRequest", "scripting", "storage", "webRequest"],
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"matches": ["http://*/*", "https://*/*"],
|
||||
"resources": ["https://fonts.googleapis.com/css?family=Inter"]
|
||||
}
|
||||
]
|
||||
}
|
127
packages/browser-extension/src/options.html
Normal file
@ -0,0 +1,127 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Cookie Dialog Monster > Exclusion List</title>
|
||||
<link rel="stylesheet" href="/styles/reset.css" />
|
||||
<link rel="stylesheet" href="/styles/options.css" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter" />
|
||||
<script src="/scripts/options.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div>
|
||||
<h1 class="header-title">
|
||||
Cookie Dialog Monster > <span data-i18n="options_exclusionListTitle"></span>
|
||||
</h1>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div class="button-group">
|
||||
<button data-variant="large" id="add-button">
|
||||
<span data-i18n="options_addButton"></span>
|
||||
<svg
|
||||
fill="none"
|
||||
height="14"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="14"
|
||||
>
|
||||
<line x1="12" y1="5" x2="12" y2="19"></line>
|
||||
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||
</svg>
|
||||
</button>
|
||||
<button data-variant="large" id="clear-button">
|
||||
<span data-i18n="options_clearButton"></span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="14"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="14"
|
||||
>
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path
|
||||
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
|
||||
/>
|
||||
<line x1="10" y1="11" x2="10" y2="17"></line>
|
||||
<line x1="14" y1="11" x2="14" y2="17"></line>
|
||||
</svg>
|
||||
</button>
|
||||
<button data-variant="large" id="import-button">
|
||||
<span data-i18n="options_importButton"></span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="14"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="14"
|
||||
>
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="17 8 12 3 7 8"></polyline>
|
||||
<line x1="12" y1="3" x2="12" y2="15"></line>
|
||||
</svg>
|
||||
<input accept=".cdm" id="file-input" style="display: none" type="file" />
|
||||
</button>
|
||||
<button data-variant="large" id="export-button">
|
||||
<span data-i18n="options_exportButton"></span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="14"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="14"
|
||||
>
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7 10 12 15 17 10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<input id="filter-input" data-i18n-placeholder="options_filterPlaceholder" />
|
||||
<ul id="exclusion-list">
|
||||
<li id="exclusion-list-item-template" style="display: none">
|
||||
<span></span>
|
||||
<button>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="14"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="14"
|
||||
>
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path
|
||||
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
|
||||
/>
|
||||
<line x1="10" y1="11" x2="10" y2="17"></line>
|
||||
<line x1="14" y1="11" x2="14" y2="17"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li id="exclusion-list-item-empty" style="display: none"></li>
|
||||
</ul>
|
||||
</main>
|
||||
<footer></footer>
|
||||
</body>
|
||||
</html>
|
@ -1,13 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Cookie Dialog Monster > Exclusion List</title>
|
||||
<link rel="stylesheet" href="~src/styles/reset.css" />
|
||||
<link rel="stylesheet" href="~src/styles/common.css" />
|
||||
<link rel="stylesheet" href="~src/styles/options.css" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter" />
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
@ -1,227 +0,0 @@
|
||||
import { type ChangeEvent, type JSX, type MouseEvent, useCallback, useRef, useState } from 'react';
|
||||
|
||||
import { useExclusions } from '~hooks/useExclusions';
|
||||
import { DOMAIN_REG_EXP } from '~utils/domain';
|
||||
import { storage } from '~utils/storage';
|
||||
import type { DomainConfig } from '~utils/types';
|
||||
|
||||
export default function Options(): JSX.Element {
|
||||
const exclusions = useExclusions();
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const [filterValue, setFilterValue] = useState<string>('');
|
||||
|
||||
const handleAddClick = useCallback(async () => {
|
||||
const message = chrome.i18n.getMessage('options_addPrompt');
|
||||
const domain = window.prompt(message)?.trim().replace('www.', '');
|
||||
|
||||
if (domain && (DOMAIN_REG_EXP.test(domain) || domain === 'localhost')) {
|
||||
const prev = await storage.get<DomainConfig>(domain);
|
||||
|
||||
await storage.set(domain, { ...prev, on: false });
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleClearClick = useCallback(async () => {
|
||||
for (const domain of exclusions) {
|
||||
const prev = await storage.get<DomainConfig>(domain.name);
|
||||
|
||||
if (prev?.issue?.url) await storage.set(domain.name, { ...prev, on: true });
|
||||
else await storage.remove(domain.name);
|
||||
}
|
||||
}, [exclusions]);
|
||||
|
||||
const handleDeleteClick = useCallback(async (event: MouseEvent<HTMLButtonElement>) => {
|
||||
const { value: domain } = event.currentTarget.dataset;
|
||||
|
||||
if (domain) {
|
||||
const prev = await storage.get<DomainConfig>(domain);
|
||||
|
||||
if (prev?.issue?.url) await storage.set(domain, { ...prev, on: true });
|
||||
else await storage.remove(domain);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleExportClick = useCallback(() => {
|
||||
const anchor = document.createElement('a');
|
||||
const now = new Date();
|
||||
const day = now.getDate().toString().padStart(2, '0');
|
||||
const month = now.getMonth().toString().padStart(2, '0');
|
||||
const year = now.getUTCFullYear();
|
||||
const text = exclusions.reduce((prev, curr) => `${prev}\n${curr.name}`, '');
|
||||
const defaultTitle = `${year}${month}${day}`;
|
||||
const customTitle = window.prompt('Enter a file name', defaultTitle);
|
||||
|
||||
if (customTitle) {
|
||||
const blob = new Blob([text], { type: 'octet/stream' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
anchor.href = url;
|
||||
anchor.download = `${customTitle || defaultTitle}.cdm`;
|
||||
anchor.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
}, [exclusions]);
|
||||
|
||||
const handleFileChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.currentTarget.files?.[0];
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.addEventListener('load', async (event) => {
|
||||
const input =
|
||||
event.currentTarget && 'result' in event.currentTarget
|
||||
? (event.currentTarget.result as string)?.split('\n')
|
||||
: [];
|
||||
|
||||
for (const value of input) {
|
||||
const domain = value.replace('www.', '').trim();
|
||||
|
||||
if (domain && (DOMAIN_REG_EXP.test(domain) || domain === 'localhost')) {
|
||||
const prev = await storage.get<DomainConfig>(domain);
|
||||
|
||||
await storage.set(domain, { ...prev, on: false });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (file) {
|
||||
event.currentTarget.value = '';
|
||||
reader.readAsText(file);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleFilterChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||
setFilterValue(event.currentTarget.value);
|
||||
}, []);
|
||||
|
||||
const handleImportClick = useCallback(() => {
|
||||
fileInputRef.current?.click();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="header">
|
||||
<div>
|
||||
<h1>Cookie Dialog Monster > {chrome.i18n.getMessage('options_exclusionListTitle')}</h1>
|
||||
</div>
|
||||
</header>
|
||||
<main className="main">
|
||||
<div className="button-group">
|
||||
<button className="button" data-variant="large" onClick={handleAddClick}>
|
||||
<span>{chrome.i18n.getMessage('options_addButton')}</span>
|
||||
<svg
|
||||
fill="none"
|
||||
height="14"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="14"
|
||||
>
|
||||
<line x1="12" y1="5" x2="12" y2="19" />
|
||||
<line x1="5" y1="12" x2="19" y2="12" />
|
||||
</svg>
|
||||
</button>
|
||||
<button className="button" data-variant="large" onClick={handleClearClick}>
|
||||
<span>{chrome.i18n.getMessage('options_clearButton')}</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="14"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="14"
|
||||
>
|
||||
<polyline points="3 6 5 6 21 6" />
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
||||
<line x1="10" y1="11" x2="10" y2="17" />
|
||||
<line x1="14" y1="11" x2="14" y2="17" />
|
||||
</svg>
|
||||
</button>
|
||||
<button className="button" data-variant="large" onClick={handleImportClick}>
|
||||
<span>{chrome.i18n.getMessage('options_importButton')}</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="14"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="14"
|
||||
>
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
||||
<polyline points="17 8 12 3 7 8" />
|
||||
<line x1="12" y1="3" x2="12" y2="15" />
|
||||
</svg>
|
||||
<input
|
||||
accept=".cdm"
|
||||
onChange={handleFileChange}
|
||||
ref={fileInputRef}
|
||||
style={{ display: 'none' }}
|
||||
type="file"
|
||||
/>
|
||||
</button>
|
||||
<button className="button" data-variant="large" onClick={handleExportClick}>
|
||||
<span>{chrome.i18n.getMessage('options_exportButton')}</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="14"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="14"
|
||||
>
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
||||
<polyline points="7 10 12 15 17 10" />
|
||||
<line x1="12" y1="15" x2="12" y2="3" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
className="input"
|
||||
onChange={handleFilterChange}
|
||||
placeholder={chrome.i18n.getMessage('options_filterPlaceholder')}
|
||||
value={filterValue}
|
||||
/>
|
||||
<ul className="exclusion-list">
|
||||
{exclusions.length ? (
|
||||
exclusions.map((exclusion) => (
|
||||
<li key={exclusion.name}>
|
||||
<span>{exclusion.name}</span>
|
||||
<button className="button" data-value={exclusion.name} onClick={handleDeleteClick}>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="14"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="14"
|
||||
>
|
||||
<polyline points="3 6 5 6 21 6" />
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
||||
<line x1="10" y1="11" x2="10" y2="17" />
|
||||
<line x1="14" y1="11" x2="14" y2="17" />
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
))
|
||||
) : (
|
||||
<li>{chrome.i18n.getMessage('options_empty')}</li>
|
||||
)}
|
||||
</ul>
|
||||
</main>
|
||||
<footer className="footer" />
|
||||
</>
|
||||
);
|
||||
}
|
328
packages/browser-extension/src/popup.html
Normal file
@ -0,0 +1,328 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="/styles/reset.css" />
|
||||
<link rel="stylesheet" href="/styles/popup.css" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter" />
|
||||
<script src="/scripts/popup.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1 class="header-title">Cookie Dialog Monster</h1>
|
||||
<div class="header-actions">
|
||||
<button disabled id="report-button">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="18"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="18"
|
||||
>
|
||||
<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"></path>
|
||||
<line x1="4" y1="22" x2="4" y2="15"></line>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="settings-button">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="18"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="18"
|
||||
>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
<path
|
||||
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<p aria-hidden="true" class="banner" data-variant="warning" id="issue-banner" role="alert">
|
||||
<span id="issue-banner-text"></span>
|
||||
<a id="issue-banner-url" target="_blank">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="12"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="12"
|
||||
>
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
||||
<polyline points="15 3 21 3 21 9"></polyline>
|
||||
<line x1="10" y1="14" x2="21" y2="3"></line>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
<p aria-hidden="true" class="banner" data-variant="notice" id="update-banner" role="alert">
|
||||
<span data-i18n="popup_bannerUpdateAvailable"></span>
|
||||
<a
|
||||
href="https://git.wanhose.dev/wanhose/cookie-dialog-monster/releases"
|
||||
id="update-banner-url"
|
||||
target="_blank"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="12"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="12"
|
||||
>
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
||||
<polyline points="15 3 21 3 21 9"></polyline>
|
||||
<line x1="10" y1="14" x2="21" y2="3"></line>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
<div class="content">
|
||||
<popup-button id="power-option" role="button" tabindex="0">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="32"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="32"
|
||||
>
|
||||
<path d="M18.36 6.64a9 9 0 1 1-12.73 0" />
|
||||
<line x1="12" y1="2" x2="12" y2="12" />
|
||||
</svg>
|
||||
<span id="host"></span>
|
||||
</popup-button>
|
||||
<popup-button
|
||||
data-href="https://git.wanhose.dev/wanhose/cookie-dialog-monster/wiki/Help-or-issues%3F"
|
||||
id="help-option"
|
||||
role="link"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="32"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="32"
|
||||
>
|
||||
<path
|
||||
d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"
|
||||
/>
|
||||
</svg>
|
||||
<span data-i18n="popup_helpOption"></span>
|
||||
</popup-button>
|
||||
<popup-button
|
||||
data-href="https://git.wanhose.dev/wanhose/cookie-dialog-monster"
|
||||
id="contribute-option"
|
||||
role="link"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="32"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="32"
|
||||
>
|
||||
<circle cx="18" cy="18" r="3"></circle>
|
||||
<circle cx="6" cy="6" r="3"></circle>
|
||||
<path d="M13 6h3a2 2 0 0 1 2 2v7"></path>
|
||||
<line x1="6" y1="9" x2="6" y2="21"></line>
|
||||
</svg>
|
||||
<span data-i18n="popup_contributeOption"></span>
|
||||
</popup-button>
|
||||
<popup-button data-href="#" id="rate-option" role="link" tabindex="0">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="32"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="32"
|
||||
>
|
||||
<polygon
|
||||
points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"
|
||||
/>
|
||||
</svg>
|
||||
<span data-i18n="popup_rateOption"></span>
|
||||
</popup-button>
|
||||
<popup-data-container>
|
||||
<popup-data>
|
||||
<strong data-i18n="popup_databaseVersion"></strong>
|
||||
<span id="database-version"></span>
|
||||
<popup-data-button
|
||||
data-animation="flip"
|
||||
id="refresh-database-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="12"
|
||||
id="refresh-database-spinner"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="12"
|
||||
>
|
||||
<polyline points="1 4 1 10 7 10"></polyline>
|
||||
<polyline points="23 20 23 14 17 14"></polyline>
|
||||
<path
|
||||
d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"
|
||||
></path>
|
||||
</svg>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="12"
|
||||
id="refresh-database-check"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
stroke="var(--color-success)"
|
||||
viewBox="0 0 24 24"
|
||||
width="12"
|
||||
>
|
||||
<polyline points="20 6 9 17 4 12"></polyline>
|
||||
</svg>
|
||||
</popup-data-button>
|
||||
</popup-data>
|
||||
<popup-data>
|
||||
<strong data-i18n="popup_extensionVersion"></strong>
|
||||
<span id="extension-version"></span>
|
||||
</popup-data>
|
||||
</popup-data-container>
|
||||
</div>
|
||||
<div class="report" style="display: none">
|
||||
<div class="report-form-view">
|
||||
<div class="report-body-text" data-i18n="report_bodyText"></div>
|
||||
<div class="report-form">
|
||||
<div class="report-input-group">
|
||||
<div class="report-input-label" id="report-label-url">
|
||||
<span data-i18n="report_urlInputLabel"></span>
|
||||
<span class="report-input-label-required">*</span>
|
||||
</div>
|
||||
<input
|
||||
aria-labelledby="report-label-url"
|
||||
aria-required="true"
|
||||
class="report-input"
|
||||
id="report-input-url"
|
||||
/>
|
||||
<div
|
||||
class="report-input-error"
|
||||
data-i18n="report_urlInputError"
|
||||
id="report-input-url-error"
|
||||
></div>
|
||||
</div>
|
||||
<div class="report-input-group">
|
||||
<div class="report-input-label" id="report-label-reason">
|
||||
<span data-i18n="report_reasonInputLabel"></span>
|
||||
<span class="report-input-label-required">*</span>
|
||||
</div>
|
||||
<textarea
|
||||
aria-labelledby="report-label-reason"
|
||||
aria-required="true"
|
||||
class="report-input"
|
||||
data-i18n="report_reasonInputPlaceholder"
|
||||
id="report-input-reason"
|
||||
rows="4"
|
||||
></textarea>
|
||||
<div
|
||||
class="report-input-error"
|
||||
data-i18n="report_reasonInputError"
|
||||
id="report-input-reason-error"
|
||||
></div>
|
||||
</div>
|
||||
<div class="report-buttons">
|
||||
<button class="report-submit-button" data-i18n="contextMenu_reportOption"></button>
|
||||
<button class="report-cancel-button" data-i18n="report_cancelButtonText"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="report-submit-error-view" hidden>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="48"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
stroke="var(--color-error)"
|
||||
viewBox="0 0 24 24"
|
||||
width="48"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<line x1="12" y1="8" x2="12" y2="12"></line>
|
||||
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
||||
</svg>
|
||||
<div class="report-submit-text" data-i18n="report_submitErrorText"></div>
|
||||
<div class="report-submit-extra-text" data-i18n="report_submitErrorExtraText"></div>
|
||||
<div
|
||||
class="report-issue-button"
|
||||
data-i18n="contextMenu_issueOption"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
></div>
|
||||
</div>
|
||||
<div class="report-submit-success-view" hidden>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="48"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
stroke="var(--color-success)"
|
||||
viewBox="0 0 24 24"
|
||||
width="48"
|
||||
>
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
|
||||
<polyline points="22 4 12 14.01 9 11.01" />
|
||||
</svg>
|
||||
<div class="report-submit-text" data-i18n="report_submitSuccessText"></div>
|
||||
<div class="report-submit-extra-text" data-i18n="report_submitSuccessExtraText"></div>
|
||||
<div
|
||||
class="report-issue-button"
|
||||
data-i18n="contextMenu_issueOption"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer></footer>
|
||||
</body>
|
||||
</html>
|
@ -1,12 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="~src/styles/reset.css" />
|
||||
<link rel="stylesheet" href="~src/styles/common.css" />
|
||||
<link rel="stylesheet" href="~src/styles/popup.css" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter" />
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
@ -1,472 +0,0 @@
|
||||
import { type FormEvent, type JSX, type KeyboardEvent, useCallback, useState } from 'react';
|
||||
|
||||
import { useBrowser } from '~hooks/useBrowser';
|
||||
import { useData } from '~hooks/useData';
|
||||
import { useDomain } from '~hooks/useDomain';
|
||||
import { useDomainReport } from '~hooks/useDomainReport';
|
||||
import { useExtension } from '~hooks/useExtension';
|
||||
import { CHROME_STORE_URL, EDGE_STORE_URL, FIREFOX_STORE_URL } from '~utils/constants';
|
||||
import { suppressLastError } from '~utils/error';
|
||||
import type { ReportParams } from '~utils/types';
|
||||
|
||||
export default function Popup(): JSX.Element {
|
||||
const { isChromium, isEdge, isFirefox } = useBrowser();
|
||||
const { data, isLoading, isRefetched, refetch } = useData();
|
||||
const { config, domain, isSupported, toggleStatus } = useDomain();
|
||||
const { errors, isSubmitting, report } = useDomainReport();
|
||||
const extension = useExtension();
|
||||
const [issueExists, setIssueExists] = useState<boolean>(false);
|
||||
const [issueURL, setIssueURL] = useState<string>('');
|
||||
const [view, setView] = useState<'main' | 'report'>('main');
|
||||
|
||||
const handleCancelClick = useCallback(() => {
|
||||
setIssueExists(false);
|
||||
setIssueURL('');
|
||||
setView('main');
|
||||
}, []);
|
||||
|
||||
const handleInputKeyDown = useCallback(
|
||||
(event: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
event.currentTarget.blur();
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handlePowerClick = useCallback(async () => {
|
||||
await toggleStatus();
|
||||
window.close();
|
||||
}, [toggleStatus]);
|
||||
|
||||
const handleRefreshClick = useCallback(async () => {
|
||||
await refetch();
|
||||
}, [refetch]);
|
||||
|
||||
const handleReportClick = useCallback(() => {
|
||||
setView('report');
|
||||
}, []);
|
||||
|
||||
const handleSettingsClick = useCallback(async () => {
|
||||
chrome.runtime.openOptionsPage(suppressLastError);
|
||||
window.close();
|
||||
}, []);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
const params = Object.fromEntries(Object.entries(new FormData(event.currentTarget)));
|
||||
const result = await report(params as ReportParams);
|
||||
|
||||
if (result) {
|
||||
setIssueExists(!result.success);
|
||||
setIssueURL(result.data);
|
||||
}
|
||||
},
|
||||
[report]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="header">
|
||||
<h1>Cookie Dialog Monster</h1>
|
||||
<div className="header-actions">
|
||||
<a
|
||||
className="header-button"
|
||||
href="https://git.wanhose.dev/wanhose/cookie-dialog-monster/wiki/Help-or-issues%3F"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="18"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="18"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />
|
||||
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||||
</svg>
|
||||
</a>
|
||||
<button className="header-button" onClick={handleSettingsClick}>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="18"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="18"
|
||||
>
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
{config.issue?.url ? (
|
||||
<p className="banner" data-variant="warning" role="alert">
|
||||
<span>
|
||||
{chrome.i18n.getMessage(
|
||||
config.issue.flags?.includes('wontfix')
|
||||
? 'popup_bannerIssueWontFix'
|
||||
: 'popup_bannerIssueOpen'
|
||||
)}
|
||||
</span>
|
||||
|
||||
<a href={config.issue.url} target="_blank" rel="noreferrer">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="12"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="12"
|
||||
>
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
||||
<polyline points="15 3 21 3 21 9" />
|
||||
<line x1="10" y1="14" x2="21" y2="3" />
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
) : null}
|
||||
{extension.updateAvailable ? (
|
||||
<p className="banner" data-variant="notice" role="alert">
|
||||
<span>{chrome.i18n.getMessage('popup_bannerUpdateAvailable')}</span>
|
||||
|
||||
<a
|
||||
href={`https://git.wanhose.dev/wanhose/cookie-dialog-monster/releases/${extension.updateAvailable}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="12"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="12"
|
||||
>
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
||||
<polyline points="15 3 21 3 21 9" />
|
||||
<line x1="10" y1="14" x2="21" y2="3" />
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
) : null}
|
||||
{isSupported ? null : (
|
||||
<p className="banner" data-variant="error" role="alert">
|
||||
<span>{chrome.i18n.getMessage('popup_bannerSupport')}</span>
|
||||
|
||||
<a
|
||||
href="https://git.wanhose.dev/wanhose/cookie-dialog-monster/wiki/List-of-unsupported-sites"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="12"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="12"
|
||||
>
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
||||
<polyline points="15 3 21 3 21 9" />
|
||||
<line x1="10" y1="14" x2="21" y2="3" />
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
{view === 'main' ? (
|
||||
<div className="content">
|
||||
<button
|
||||
className="popup-button power-option"
|
||||
data-value={config.on ? 'on' : 'off'}
|
||||
disabled={!!config.issue || !!extension.updateAvailable || !isSupported}
|
||||
onClick={handlePowerClick}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="32"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="32"
|
||||
>
|
||||
<path d="M18.36 6.64a9 9 0 1 1-12.73 0" />
|
||||
<line x1="12" y1="2" x2="12" y2="12" />
|
||||
</svg>
|
||||
<span>{domain}</span>
|
||||
</button>
|
||||
<button
|
||||
className="popup-button"
|
||||
disabled={!!config.issue || !!extension.updateAvailable || !isSupported}
|
||||
onClick={handleReportClick}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="32"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="32"
|
||||
>
|
||||
<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z" />
|
||||
<line x1="4" y1="22" x2="4" y2="15" />
|
||||
</svg>
|
||||
<span>{chrome.i18n.getMessage('contextMenu_reportOption')}</span>
|
||||
</button>
|
||||
<a
|
||||
className="popup-button"
|
||||
href="https://git.wanhose.dev/wanhose/cookie-dialog-monster"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="32"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="32"
|
||||
>
|
||||
<circle cx="18" cy="18" r="3" />
|
||||
<circle cx="6" cy="6" r="3" />
|
||||
<path d="M13 6h3a2 2 0 0 1 2 2v7" />
|
||||
<line x1="6" y1="9" x2="6" y2="21" />
|
||||
</svg>
|
||||
<span>{chrome.i18n.getMessage('popup_contributeOption')}</span>
|
||||
</a>
|
||||
<a
|
||||
className="popup-button rate-option"
|
||||
href={
|
||||
isEdge
|
||||
? EDGE_STORE_URL
|
||||
: isChromium
|
||||
? CHROME_STORE_URL
|
||||
: isFirefox
|
||||
? FIREFOX_STORE_URL
|
||||
: '#'
|
||||
}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="32"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="32"
|
||||
>
|
||||
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
|
||||
</svg>
|
||||
<span>{chrome.i18n.getMessage('popup_rateOption')}</span>
|
||||
</a>
|
||||
<div className="popup-data-container">
|
||||
<div className="popup-data">
|
||||
<b>{chrome.i18n.getMessage('popup_databaseVersion')}</b>
|
||||
<span>{data?.version || '?'}</span>
|
||||
<button
|
||||
className="popup-data-button"
|
||||
data-animation="flip"
|
||||
data-refreshing={isLoading && !isRefetched}
|
||||
data-refreshed={isRefetched}
|
||||
disabled={isLoading || isRefetched}
|
||||
onClick={handleRefreshClick}
|
||||
>
|
||||
{isRefetched ? (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="12"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="var(--color-success)"
|
||||
viewBox="0 0 24 24"
|
||||
width="12"
|
||||
>
|
||||
<polyline points="20 6 9 17 4 12" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="12"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
width="12"
|
||||
>
|
||||
<polyline points="1 4 1 10 7 10" />
|
||||
<polyline points="23 20 23 14 17 14" />
|
||||
<path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="popup-data">
|
||||
<b>{chrome.i18n.getMessage('popup_extensionVersion')}</b>
|
||||
<span>{extension.version}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="report">
|
||||
{issueURL ? (
|
||||
<>
|
||||
{issueExists ? (
|
||||
<div className="report-submit-error-view" hidden>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="48"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="var(--color-error)"
|
||||
viewBox="0 0 24 24"
|
||||
width="48"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<line x1="12" y1="8" x2="12" y2="12" />
|
||||
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||||
</svg>
|
||||
<div className="report-submit-text">
|
||||
{chrome.i18n.getMessage('report_submitErrorText')}
|
||||
</div>
|
||||
<div className="report-submit-extra-text">
|
||||
{chrome.i18n.getMessage('report_submitErrorExtraText')}
|
||||
</div>
|
||||
<div className="report-issue-button">
|
||||
{chrome.i18n.getMessage('contextMenu_issueOption')}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="report-submit-success-view" hidden>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="48"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
stroke="var(--color-success)"
|
||||
viewBox="0 0 24 24"
|
||||
width="48"
|
||||
>
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
|
||||
<polyline points="22 4 12 14.01 9 11.01" />
|
||||
</svg>
|
||||
<div className="report-submit-text">
|
||||
{chrome.i18n.getMessage('report_submitSuccessText')}
|
||||
</div>
|
||||
<div className="report-submit-extra-text">
|
||||
{chrome.i18n.getMessage('report_submitSuccessExtraText')}
|
||||
</div>
|
||||
<div className="report-issue-button">
|
||||
{chrome.i18n.getMessage('contextMenu_issueOption')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="report-form-view">
|
||||
<div className="report-body-text">{chrome.i18n.getMessage('report_bodyText')}</div>
|
||||
<form className="report-form" noValidate onSubmit={handleSubmit}>
|
||||
<div className="report-input-group">
|
||||
<div className="report-input-label" id="report-label-url">
|
||||
<span>{chrome.i18n.getMessage('report_urlInputLabel')}</span>
|
||||
<span className="report-input-label-required">*</span>
|
||||
</div>
|
||||
<input
|
||||
aria-invalid={!!errors['url']}
|
||||
aria-labelledby="report-label-url"
|
||||
aria-required="true"
|
||||
className="report-input"
|
||||
disabled={isSubmitting}
|
||||
name="url"
|
||||
onKeyDown={handleInputKeyDown}
|
||||
placeholder="https://www.example.com/"
|
||||
/>
|
||||
<div aria-hidden={!errors['url']} className="report-input-error">
|
||||
{chrome.i18n.getMessage('report_urlInputError')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="report-input-group">
|
||||
<div className="report-input-label" id="report-label-reason">
|
||||
<span>{chrome.i18n.getMessage('report_reasonInputLabel')}</span>
|
||||
<span className="report-input-label-required">*</span>
|
||||
</div>
|
||||
<textarea
|
||||
aria-invalid={!!errors['reason']}
|
||||
aria-labelledby="report-label-reason"
|
||||
aria-required="true"
|
||||
className="report-input"
|
||||
disabled={isSubmitting}
|
||||
name="reason"
|
||||
onKeyDown={handleInputKeyDown}
|
||||
placeholder={chrome.i18n.getMessage('report_reasonInputPlaceholder')}
|
||||
rows={4}
|
||||
/>
|
||||
<div aria-hidden={!errors['reason']} className="report-input-error">
|
||||
{chrome.i18n.getMessage('report_reasonInputError')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="report-buttons">
|
||||
<button className="report-submit-button" disabled={isSubmitting} type="submit">
|
||||
{chrome.i18n.getMessage('contextMenu_reportOption')}
|
||||
</button>
|
||||
<button
|
||||
className="report-cancel-button"
|
||||
disabled={isSubmitting}
|
||||
onClick={handleCancelClick}
|
||||
>
|
||||
{chrome.i18n.getMessage('report_cancelButtonText')}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
<footer />
|
||||
</>
|
||||
);
|
||||
}
|
501
packages/browser-extension/src/scripts/background.js
Normal file
@ -0,0 +1,501 @@
|
||||
/**
|
||||
* @typedef {Object} ExtensionIssue
|
||||
* @property {number} [expiresIn]
|
||||
* @property {string[]} [flags]
|
||||
* @property {string} [url]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ExtensionState
|
||||
* @property {ExtensionIssue} [issue]
|
||||
* @property {boolean} on
|
||||
* @property {string} [updateAvailable]
|
||||
*/
|
||||
|
||||
if (typeof browser === 'undefined') {
|
||||
browser = chrome;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Class for request batching
|
||||
*/
|
||||
class RequestManager {
|
||||
constructor() {
|
||||
this.requests = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Fetch wrapper to play with the request map
|
||||
* @param {string} input
|
||||
* @param {RequestInit} [init]
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
fetch(input, init) {
|
||||
if (this.requests.has(input)) {
|
||||
return this.requests.get(input);
|
||||
}
|
||||
|
||||
const promise = fetch(input, init)
|
||||
.then((response) => response.json())
|
||||
.finally(() => this.requests.delete(input));
|
||||
|
||||
this.requests.set(input, promise);
|
||||
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description API URL
|
||||
* @type {string}
|
||||
*/
|
||||
const apiUrl = 'https://api.cookie-dialog-monster.com/rest/v5';
|
||||
|
||||
/**
|
||||
* @description Context menu identifier
|
||||
* @type {string}
|
||||
*/
|
||||
const extensionMenuItemId = 'CDM-MENU';
|
||||
|
||||
/**
|
||||
* @description Context menu identifier
|
||||
* @type {string}
|
||||
*/
|
||||
const reportMenuItemId = 'CDM-REPORT';
|
||||
|
||||
/**
|
||||
* @description Request manager instance
|
||||
*/
|
||||
const requestManager = new RequestManager();
|
||||
|
||||
/**
|
||||
* @description Context menu identifier
|
||||
* @type {string}
|
||||
*/
|
||||
const settingsMenuItemId = 'CDM-SETTINGS';
|
||||
|
||||
/**
|
||||
* @description A shortcut for browser.scripting
|
||||
* @type {browser.scripting}
|
||||
*/
|
||||
const script = browser.scripting;
|
||||
|
||||
/**
|
||||
* @description Default value for extension state
|
||||
* @type {ExtensionState}
|
||||
*/
|
||||
const stateByDefault = { issue: { expiresIn: 0 }, on: true };
|
||||
|
||||
/**
|
||||
* @description The storage to use
|
||||
* @type {browser.storage.StorageArea}
|
||||
*/
|
||||
const storage = browser.storage.local;
|
||||
|
||||
/**
|
||||
* @description Supress `browser.runtime.lastError`
|
||||
*/
|
||||
const suppressLastError = () => void browser.runtime.lastError;
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Enable extension icon
|
||||
* @param {number} tabId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function enableIcon(hostname, tabId) {
|
||||
const state = await getState(hostname);
|
||||
const path = state.issue?.url ? '/assets/icons/warn.png' : '/assets/icons/on.png';
|
||||
|
||||
await browser.action.setIcon({ path, tabId }, suppressLastError);
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Get database
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function getData() {
|
||||
const { data } = await storage.get('data');
|
||||
|
||||
if (!data) {
|
||||
return await refreshData();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Disable report context menu option
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function disableReport() {
|
||||
return browser.contextMenus.update(reportMenuItemId, { enabled: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Get current hostname
|
||||
* @param {string} url
|
||||
* @returns {string}
|
||||
*/
|
||||
function getHostname(url) {
|
||||
return new URL(url).hostname.split('.').slice(-3).join('.').replace('www.', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Get current active tab
|
||||
* @returns {Promise<browser.tabs.Tab>}
|
||||
*/
|
||||
async function getTab() {
|
||||
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
|
||||
|
||||
return tabs[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Get state for the given hostname
|
||||
* @param {string} hostname
|
||||
* @returns {Promise<ExtensionState>}
|
||||
*/
|
||||
async function getState(hostname) {
|
||||
const keys = [hostname, 'updateAvailable'];
|
||||
const { [hostname]: state = stateByDefault, updateAvailable } = await storage.get(keys);
|
||||
|
||||
if ((state.issue && Date.now() > state.issue.expiresIn) || !state.issue?.expiresIn) {
|
||||
state.issue = await refreshIssue(hostname);
|
||||
}
|
||||
|
||||
return { ...stateByDefault, ...state, updateAvailable };
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Format number to avoid errors
|
||||
* @param {number} [value]
|
||||
* @returns {string | null}
|
||||
*/
|
||||
function formatNumber(value) {
|
||||
if (value) {
|
||||
if (value >= 1e6) {
|
||||
return `${Math.floor(value / 1e6)}M`;
|
||||
} else if (value >= 1e3) {
|
||||
return `${Math.floor(value / 1e3)}K`;
|
||||
} else {
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Convert match string to pattern string
|
||||
* @param {string} match
|
||||
* @returns {string}
|
||||
*/
|
||||
function matchToPattern(match) {
|
||||
return `^${match.replaceAll('*.', '*(.)?').replaceAll('*', '.*')}$`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Refresh data
|
||||
* @param {number} [attempt]
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function refreshData(attempt = 1) {
|
||||
if (attempt <= 3) {
|
||||
try {
|
||||
const { data } = await requestManager.fetch(`${apiUrl}/data/`);
|
||||
|
||||
await updateStore('data', data);
|
||||
|
||||
return data;
|
||||
} catch {
|
||||
return await refreshData(attempt + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Refresh issues for the given hostname
|
||||
* @param {string} hostname
|
||||
* @param {number} [attempt]
|
||||
* @returns {Promise<ExtensionIssue | undefined>}
|
||||
*/
|
||||
async function refreshIssue(hostname, attempt = 1) {
|
||||
if (attempt <= 3) {
|
||||
try {
|
||||
const { data = {} } = await requestManager.fetch(`${apiUrl}/issues/${hostname}/`);
|
||||
|
||||
if (Object.keys(data).length === 0) {
|
||||
await updateStore(hostname, { issue: { expiresIn: Date.now() + 8 * 60 * 60 * 1000 } });
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const issue = {
|
||||
expiresIn: Date.now() + 4 * 60 * 60 * 1000,
|
||||
flags: data.flags,
|
||||
url: data.url,
|
||||
};
|
||||
|
||||
await updateStore(hostname, { issue });
|
||||
|
||||
return data;
|
||||
} catch {
|
||||
return await refreshIssue(hostname, attempt + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Report given page
|
||||
* @param {any} message
|
||||
* @param {browser.tabs.Tab} tab
|
||||
* @param {void?} callback
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function report(message) {
|
||||
try {
|
||||
const reason = message.reason;
|
||||
const url = message.url;
|
||||
const userAgent = message.userAgent;
|
||||
const version = browser.runtime.getManifest().version;
|
||||
const body = JSON.stringify({ reason, url, userAgent, version });
|
||||
const headers = { 'Cache-Control': 'no-cache', 'Content-type': 'application/json' };
|
||||
const requestInit = { body, headers, method: 'POST' };
|
||||
|
||||
return await requestManager.fetch(`${apiUrl}/report/`, requestInit);
|
||||
} catch {
|
||||
console.error("Can't send report");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Update extension store for a given key
|
||||
* @param {string} [key]
|
||||
* @param {Object} value
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function updateStore(key, value) {
|
||||
if (key) {
|
||||
const { [key]: prev } = await storage.get(key);
|
||||
|
||||
await storage.set({ [key]: { ...prev, ...value } }, suppressLastError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Listen to context menus clicked
|
||||
*/
|
||||
browser.contextMenus.onClicked.addListener((info) => {
|
||||
switch (info.menuItemId) {
|
||||
case reportMenuItemId:
|
||||
browser.action.openPopup();
|
||||
break;
|
||||
case settingsMenuItemId:
|
||||
browser.runtime.openOptionsPage();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @description Listens to messages
|
||||
*/
|
||||
browser.runtime.onMessage.addListener((message, sender, callback) => {
|
||||
const hostname = message.hostname;
|
||||
const isPage = sender.frameId === 0;
|
||||
const tabId = sender.tab?.id;
|
||||
|
||||
switch (message.type) {
|
||||
case 'DISABLE_REPORT':
|
||||
if (isPage && tabId !== undefined) disableReport();
|
||||
break;
|
||||
case 'DISABLE_ICON':
|
||||
if (isPage && tabId !== undefined) {
|
||||
browser.action.setIcon({ path: '/assets/icons/off.png', tabId }, suppressLastError);
|
||||
}
|
||||
break;
|
||||
case 'ENABLE_ICON':
|
||||
if (isPage && tabId !== undefined) {
|
||||
enableIcon(hostname, tabId).then(callback);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case 'ENABLE_POPUP':
|
||||
if (isPage && tabId !== undefined) {
|
||||
browser.action.setPopup({ popup: '/popup.html', tabId }, suppressLastError);
|
||||
}
|
||||
break;
|
||||
case 'ENABLE_REPORT':
|
||||
if (isPage && tabId !== undefined) {
|
||||
browser.contextMenus.update(reportMenuItemId, { enabled: true });
|
||||
}
|
||||
break;
|
||||
case 'GET_DATA':
|
||||
getData().then(callback);
|
||||
return true;
|
||||
case 'GET_EXCLUSION_LIST':
|
||||
storage.get(null, (exclusions) => {
|
||||
const exclusionList = Object.entries(exclusions || {}).flatMap((exclusion) => {
|
||||
return exclusion[0] !== 'data' && exclusion[1].on === false ? [exclusion[0]] : [];
|
||||
});
|
||||
callback(exclusionList);
|
||||
});
|
||||
return true;
|
||||
case 'GET_STATE':
|
||||
if (hostname) {
|
||||
getState(hostname).then(callback);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case 'GET_TAB':
|
||||
getTab().then(callback);
|
||||
return true;
|
||||
case 'REFRESH_DATA':
|
||||
refreshData().then(callback);
|
||||
return true;
|
||||
case 'REPORT':
|
||||
report(message).then(callback);
|
||||
return true;
|
||||
|
||||
case 'UPDATE_BADGE':
|
||||
if (isPage && tabId !== undefined) {
|
||||
browser.action.setBadgeBackgroundColor({ color: '#6b7280' });
|
||||
browser.action.setBadgeText({ tabId, text: formatNumber(message.value) });
|
||||
}
|
||||
break;
|
||||
case 'UPDATE_STORE':
|
||||
updateStore(hostname, message.state).then(callback);
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @description Listens to extension installed
|
||||
*/
|
||||
browser.runtime.onInstalled.addListener((details) => {
|
||||
const documentUrlPatterns = browser.runtime.getManifest().content_scripts[0].matches;
|
||||
|
||||
browser.contextMenus.create(
|
||||
{
|
||||
contexts: ['all'],
|
||||
documentUrlPatterns,
|
||||
id: extensionMenuItemId,
|
||||
title: 'Cookie Dialog Monster',
|
||||
},
|
||||
suppressLastError
|
||||
);
|
||||
browser.contextMenus.create(
|
||||
{
|
||||
contexts: ['all'],
|
||||
documentUrlPatterns,
|
||||
id: settingsMenuItemId,
|
||||
parentId: extensionMenuItemId,
|
||||
title: browser.i18n.getMessage('contextMenu_settingsOption'),
|
||||
},
|
||||
suppressLastError
|
||||
);
|
||||
browser.contextMenus.create(
|
||||
{
|
||||
contexts: ['all'],
|
||||
documentUrlPatterns,
|
||||
enabled: false,
|
||||
id: reportMenuItemId,
|
||||
parentId: extensionMenuItemId,
|
||||
title: browser.i18n.getMessage('contextMenu_reportOption'),
|
||||
},
|
||||
suppressLastError
|
||||
);
|
||||
|
||||
if (details.reason === 'update') {
|
||||
storage.remove('updateAvailable');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @description Listen to available updates
|
||||
*/
|
||||
browser.runtime.onUpdateAvailable.addListener((details) => {
|
||||
storage.set({ updateAvailable: details.version }, suppressLastError);
|
||||
});
|
||||
|
||||
/**
|
||||
* @description Listen to first start
|
||||
*/
|
||||
browser.runtime.onStartup.addListener(() => {
|
||||
refreshData();
|
||||
});
|
||||
|
||||
/**
|
||||
* @description Listen to tab changes
|
||||
*/
|
||||
browser.tabs.onActivated.addListener(() => {
|
||||
disableReport();
|
||||
});
|
||||
|
||||
/**
|
||||
* @description Listen to the moment before a request is made to apply the rules
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
browser.webRequest.onBeforeRequest.addListener(
|
||||
async (details) => {
|
||||
const { tabId, type, url } = details;
|
||||
|
||||
if (tabId > -1 && type === 'main_frame') {
|
||||
const manifest = browser.runtime.getManifest();
|
||||
const excludeMatches = manifest.content_scripts[0].exclude_matches;
|
||||
const excludePatterns = excludeMatches.map(matchToPattern);
|
||||
|
||||
if (excludePatterns.some((pattern) => new RegExp(pattern).test(url))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await getData();
|
||||
const hostname = getHostname(url);
|
||||
const state = await getState(hostname);
|
||||
|
||||
if (data?.rules?.length) {
|
||||
const rules = data.rules.map((rule) => ({
|
||||
...rule,
|
||||
condition: { ...rule.condition, tabIds: [tabId] },
|
||||
}));
|
||||
|
||||
await browser.declarativeNetRequest.updateSessionRules({
|
||||
addRules: state.on ? rules : undefined,
|
||||
removeRuleIds: data.rules.map((rule) => rule.id),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{ urls: ['<all_urls>'] }
|
||||
);
|
||||
|
||||
/**
|
||||
* @description Listen for errors on network requests
|
||||
*/
|
||||
browser.webRequest.onErrorOccurred.addListener(
|
||||
async (details) => {
|
||||
const { error, tabId, url } = details;
|
||||
|
||||
if (error === 'net::ERR_BLOCKED_BY_CLIENT' && tabId > -1) {
|
||||
const hostname = getHostname(url);
|
||||
const state = await getState(hostname);
|
||||
|
||||
if (state.on) {
|
||||
await browser.tabs.sendMessage(tabId, { type: 'INCREASE_ACTIONS_COUNT' });
|
||||
}
|
||||
}
|
||||
},
|
||||
{ urls: ['<all_urls>'] }
|
||||
);
|
515
packages/browser-extension/src/scripts/content.js
Normal file
@ -0,0 +1,515 @@
|
||||
/**
|
||||
* @typedef {Object} ExtensionData
|
||||
* @property {string[]} commonWords
|
||||
* @property {Fix[]} fixes
|
||||
* @property {{ domains: string[], tags: string[] }} skips
|
||||
* @property {{ backdrops: string[], classes: string[], containers: string[], selectors: string[] }} tokens
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ContentState
|
||||
* @property {boolean} on
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Fix
|
||||
* @property {string} action
|
||||
* @property {string} domain
|
||||
* @property {string} [property]
|
||||
* @property {string} selector
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} RunParams
|
||||
* @property {HTMLElement[]} [containers]
|
||||
* @property {HTMLElement[]} [elements]
|
||||
* @property {boolean} [skipMatch]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} GetElementsParams
|
||||
* @property {boolean} [filterEarly]
|
||||
* @property {HTMLElement} [from]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} SetUpParams
|
||||
* @property {boolean} [skipRunFn]
|
||||
*/
|
||||
|
||||
if (typeof browser === 'undefined') {
|
||||
browser = chrome;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Actions done by the extension
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
const actions = new Set();
|
||||
|
||||
/**
|
||||
* @description Data object with all the necessary information
|
||||
* @type {ExtensionData}
|
||||
*/
|
||||
let { commonWords, fixes, skips, tokens } = {
|
||||
commonWords: [],
|
||||
fixes: [],
|
||||
skips: {
|
||||
domains: [],
|
||||
tags: [],
|
||||
},
|
||||
tokens: {
|
||||
backdrops: [],
|
||||
classes: [],
|
||||
selectors: [],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Shortcut to send messages to background script
|
||||
*/
|
||||
const dispatch = browser.runtime.sendMessage;
|
||||
|
||||
/**
|
||||
* @description Current hostname
|
||||
* @type {string}
|
||||
*/
|
||||
const hostname = getHostname();
|
||||
|
||||
/**
|
||||
* @description Initial visibility state
|
||||
* @type {boolean}
|
||||
*/
|
||||
let initiallyVisible = document.visibilityState === 'visible';
|
||||
|
||||
/**
|
||||
* @description Options provided to observer
|
||||
* @type {MutationObserverInit}
|
||||
*/
|
||||
const options = { childList: true, subtree: true };
|
||||
|
||||
/**
|
||||
* @description Elements that were already matched and are removable
|
||||
* @type {Set<HTMLElement>}
|
||||
*/
|
||||
const seen = new Set();
|
||||
|
||||
/**
|
||||
* @description Extension state
|
||||
* @type {ContentState}
|
||||
*/
|
||||
let state = { on: true };
|
||||
|
||||
/**
|
||||
* @description Clean DOM
|
||||
* @param {Element[]} elements
|
||||
* @param {boolean} [skipMatch]
|
||||
* @returns {void}
|
||||
*/
|
||||
function clean(elements, skipMatch) {
|
||||
let index = 0;
|
||||
const size = 50;
|
||||
|
||||
function chunk() {
|
||||
const end = Math.min(index + size, elements.length);
|
||||
|
||||
for (; index < end; index++) {
|
||||
const element = elements[index];
|
||||
|
||||
if (match(element, skipMatch)) {
|
||||
if (element instanceof HTMLDialogElement) element.close();
|
||||
hide(element);
|
||||
|
||||
actions.add(`${Date.now()}`);
|
||||
dispatch({ type: 'UPDATE_BADGE', value: actions.size });
|
||||
}
|
||||
|
||||
seen.add(element);
|
||||
}
|
||||
|
||||
if (index < elements.length) {
|
||||
requestAnimationFrame(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(chunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Check if element contains a common word
|
||||
* @param {HTMLElement} element
|
||||
*/
|
||||
function containsCommonWord(element) {
|
||||
return !!commonWords.length && !!element.outerHTML.match(new RegExp(commonWords.join('|')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Force a DOM clean in the specific element
|
||||
* @param {HTMLElement} from
|
||||
* @returns {void}
|
||||
*/
|
||||
function forceClean(from) {
|
||||
const elements = getElements(tokens.selectors, { filterEarly: true, from });
|
||||
|
||||
if (elements.length) {
|
||||
fix();
|
||||
clean(elements, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all elements that match the selector
|
||||
* @param {string | string[]} [selector]
|
||||
* @param {GetElementsParams} [params]
|
||||
* @returns {HTMLElement[]}
|
||||
*/
|
||||
function getElements(selector, params = {}) {
|
||||
const { filterEarly, from } = params;
|
||||
let result = [];
|
||||
|
||||
if (selector?.length) {
|
||||
result = [...(from ?? document).querySelectorAll(selector)];
|
||||
|
||||
if (filterEarly) {
|
||||
result = result.flatMap((node) => filterNodeEarly(node));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all elements with their children that match the selector
|
||||
* @param {string | string[]} selector
|
||||
* @param {GetElementsParams} [params]
|
||||
* @returns {HTMLElement[]}
|
||||
*/
|
||||
function getElementsWithChildren(selector, params) {
|
||||
return getElements(selector, params).flatMap((element) => [element, ...element.children]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Calculate current hostname
|
||||
* @returns {string}
|
||||
*/
|
||||
function getHostname() {
|
||||
let hostname = document.location.hostname;
|
||||
const referrer = document.referrer;
|
||||
|
||||
if (referrer && window.self !== window.top) {
|
||||
hostname = new URL(referrer).hostname;
|
||||
}
|
||||
|
||||
return hostname.split('.').slice(-3).join('.').replace('www.', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Check if an element is visible in the viewport
|
||||
* @param {HTMLElement} element
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isInViewport(element) {
|
||||
const styles = window.getComputedStyle(element);
|
||||
const height = window.innerHeight || document.documentElement.clientHeight;
|
||||
const position = element.getBoundingClientRect();
|
||||
const scroll = window.scrollY;
|
||||
|
||||
return (
|
||||
position.bottom === position.top ||
|
||||
(scroll + position.top <= scroll + height && scroll + position.bottom >= scroll) ||
|
||||
styles.animationDuration !== '0s' ||
|
||||
styles.transitionDuration !== '0s'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Check if element element is removable
|
||||
* @param {Element} element
|
||||
* @param {boolean} [skipMatch]
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function match(element, skipMatch) {
|
||||
if (!tokens.selectors.length || !skips.tags.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(element instanceof HTMLElement) || !element.tagName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (seen.has(element)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tagName = element.tagName.toUpperCase();
|
||||
|
||||
if (skips.tags.includes(tagName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasAttributes = !!element.getAttributeNames().filter((x) => x !== 'data-nosnippet').length;
|
||||
|
||||
if (!hasAttributes && !tagName.includes('-')) {
|
||||
forceClean(element);
|
||||
}
|
||||
|
||||
// 2023-06-10: fix #113 temporarily
|
||||
if (element.classList.contains('chat-line__message')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2024-08-03: fix #701 temporarily
|
||||
if (element.classList.contains('sellos')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isDialog = tagName === 'DIALOG' && element.getAttribute('open') === 'true';
|
||||
const isFakeDialog = tagName === 'DIV' && element.className.includes('cmp');
|
||||
|
||||
return (
|
||||
(isDialog || isFakeDialog || isInViewport(element)) &&
|
||||
(skipMatch || element.matches(tokens.selectors))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Filter early nodes
|
||||
* @param {Node} node
|
||||
* @param {boolean} stopRecursion
|
||||
* @returns {HTMLElement[]}
|
||||
*/
|
||||
function filterNodeEarly(node, stopRecursion) {
|
||||
if (node.nodeType !== Node.ELEMENT_NODE || !(node instanceof HTMLElement)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (commonWords && containsCommonWord(node) && !stopRecursion) {
|
||||
return [node, ...[...node.children].flatMap((node) => filterNodeEarly(node, true))];
|
||||
}
|
||||
|
||||
return [node];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Fix data, middle consent page and scroll issues
|
||||
* @returns {void}
|
||||
*/
|
||||
function fix() {
|
||||
const backdrops = getElements(tokens.backdrops);
|
||||
const domains = skips.domains.map((x) => (x.split('.').length < 3 ? `*${x}` : x));
|
||||
|
||||
for (const backdrop of backdrops) {
|
||||
if (backdrop.children.length === 0 && !seen.has(backdrop)) {
|
||||
actions.add(`${Date.now()}`);
|
||||
seen.add(backdrop);
|
||||
hide(backdrop);
|
||||
}
|
||||
}
|
||||
|
||||
if (domains.every((x) => !hostname.match(x.replaceAll(/\*/g, '[^ ]*')))) {
|
||||
for (const element of [document.body, document.documentElement]) {
|
||||
element?.classList.remove(...(tokens.classes ?? []));
|
||||
element?.style.setProperty('position', 'initial', 'important');
|
||||
element?.style.setProperty('overflow-y', 'initial', 'important');
|
||||
}
|
||||
}
|
||||
|
||||
for (const fix of fixes) {
|
||||
const { action, domain, property, selector } = fix;
|
||||
|
||||
if (hostname.includes(domain)) {
|
||||
switch (action) {
|
||||
case 'click': {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
actions.add('click');
|
||||
element?.click();
|
||||
break;
|
||||
}
|
||||
case 'remove': {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
actions.add('remove');
|
||||
element?.style?.removeProperty(property);
|
||||
break;
|
||||
}
|
||||
case 'reload': {
|
||||
window.location.reload();
|
||||
break;
|
||||
}
|
||||
case 'reset': {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
actions.add('reset');
|
||||
element?.style?.setProperty(property, 'initial', 'important');
|
||||
break;
|
||||
}
|
||||
case 'resetAll': {
|
||||
const elements = getElements(selector);
|
||||
|
||||
actions.add('resetAll');
|
||||
elements.forEach((e) => e?.style?.setProperty(property, 'initial', 'important'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ionRouterOutlet = document.getElementsByTagName('ion-router-outlet')[0];
|
||||
|
||||
if (ionRouterOutlet) {
|
||||
actions.add('ion-router-outlet');
|
||||
// 2024-08-02: fix #644 temporarily
|
||||
ionRouterOutlet.removeAttribute('inert');
|
||||
}
|
||||
|
||||
const t4Wrapper = document.getElementsByClassName('t4-wrapper')[0];
|
||||
|
||||
if (t4Wrapper) {
|
||||
actions.add('t4-wrapper');
|
||||
// 2024-09-12: fix #945 temporarily
|
||||
t4Wrapper.removeAttribute('inert');
|
||||
}
|
||||
|
||||
dispatch({ type: 'UPDATE_BADGE', value: actions.size });
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Hide DOM element
|
||||
* @param {HTMLElement} element
|
||||
* @returns {void}
|
||||
*/
|
||||
function hide(element) {
|
||||
element.style.setProperty('clip-path', 'circle(0px)', 'important');
|
||||
element.style.setProperty('display', 'none', 'important');
|
||||
element.style.setProperty('height', '0px', 'important');
|
||||
element.style.setProperty('overflow', 'hidden', 'important');
|
||||
element.style.setProperty('transform', 'scale(0)', 'important');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Clean DOM when this function is called
|
||||
* @param {RunParams} [params]
|
||||
* @returns {void}
|
||||
*/
|
||||
function run(params = {}) {
|
||||
const { containers, elements, skipMatch } = params;
|
||||
|
||||
if (document.body?.children.length && state.on && tokens.selectors.length) {
|
||||
fix();
|
||||
|
||||
if (elements?.length) {
|
||||
clean(elements, skipMatch);
|
||||
}
|
||||
|
||||
if (elements === undefined && containers?.length) {
|
||||
clean(containers.flatMap((x) => getElementsWithChildren(x, { filterEarly: true })));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Set up the extension
|
||||
* @param {SetUpParams} [params]
|
||||
*/
|
||||
async function setUp(params = {}) {
|
||||
state = await dispatch({ hostname, type: 'GET_STATE' });
|
||||
dispatch({ type: 'ENABLE_POPUP' });
|
||||
|
||||
if (state.on) {
|
||||
const data = await dispatch({ hostname, type: 'GET_DATA' });
|
||||
|
||||
commonWords = data?.commonWords ?? commonWords;
|
||||
fixes = data?.fixes ?? fixes;
|
||||
skips = data?.skips ?? skips;
|
||||
tokens = data?.tokens ?? tokens;
|
||||
|
||||
dispatch({ type: 'ENABLE_REPORT' });
|
||||
dispatch({ hostname, type: 'ENABLE_ICON' });
|
||||
dispatch({ type: 'UPDATE_BADGE', value: actions.size });
|
||||
observer.observe(document.body ?? document.documentElement, options);
|
||||
if (!params.skipRunFn) run({ containers: tokens.containers });
|
||||
} else {
|
||||
dispatch({ type: 'DISABLE_REPORT' });
|
||||
dispatch({ type: 'DISABLE_ICON' });
|
||||
dispatch({ type: 'UPDATE_BADGE', value: actions.size });
|
||||
observer.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Wait for the body to exist
|
||||
* @param {void} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
async function setUpAfterWaitForBody() {
|
||||
if (document.body) {
|
||||
await setUp();
|
||||
} else {
|
||||
setTimeout(setUpAfterWaitForBody, 50);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Mutation Observer instance
|
||||
* @type {MutationObserver}
|
||||
*/
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
if (!state.on || !tokens.selectors.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodes = mutations.flatMap((mutation) => [...mutation.addedNodes]);
|
||||
const elements = nodes.flatMap((node) => filterNodeEarly(node));
|
||||
|
||||
run({ elements });
|
||||
});
|
||||
|
||||
/**
|
||||
* @description Listen to messages from any other scripts
|
||||
* @listens browser.runtime#onMessage
|
||||
*/
|
||||
browser.runtime.onMessage.addListener(async (message) => {
|
||||
switch (message.type) {
|
||||
case 'INCREASE_ACTIONS_COUNT': {
|
||||
actions.add(`${Date.now()}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @description Fix bfcache issues
|
||||
* @listens window#pageshow
|
||||
* @returns {void}
|
||||
*/
|
||||
window.addEventListener('pageshow', async (event) => {
|
||||
if (document.visibilityState === 'visible' && event.persisted) {
|
||||
await setUp();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Run if the page wasn't visited yet
|
||||
* @listens window#visibilitychange
|
||||
* @returns {void}
|
||||
*/
|
||||
window.addEventListener('visibilitychange', async () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
if (!initiallyVisible) {
|
||||
initiallyVisible = true;
|
||||
await setUp();
|
||||
}
|
||||
|
||||
dispatch({ type: state.on ? 'ENABLE_REPORT' : 'DISABLE_REPORT' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @description Run as soon as possible, if the user is in front of the page
|
||||
*/
|
||||
if (document.visibilityState === 'visible') {
|
||||
setUpAfterWaitForBody();
|
||||
}
|
275
packages/browser-extension/src/scripts/options.js
Normal file
@ -0,0 +1,275 @@
|
||||
if (typeof browser === 'undefined') {
|
||||
browser = chrome;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Shortcut to send messages to background script
|
||||
*/
|
||||
const dispatch = browser.runtime.sendMessage;
|
||||
|
||||
/**
|
||||
* @description RegExp for matching domains
|
||||
*/
|
||||
const domainRegExp = /^(?!-)[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z]{2,})+$/;
|
||||
|
||||
/**
|
||||
* @description Exclusion list, URLs where the user prefers to disable the extension
|
||||
* @type {string[]}
|
||||
*/
|
||||
let exclusionList = [];
|
||||
|
||||
/**
|
||||
* @description Render exclusion items into exclusion list
|
||||
* @returns {void}
|
||||
*/
|
||||
function createList() {
|
||||
const emptyItemElement = document.getElementById('exclusion-list-item-empty');
|
||||
const exclusionListElement = document.getElementById('exclusion-list');
|
||||
const exclusionListItemTemplateElement = document.getElementById('exclusion-list-item-template');
|
||||
|
||||
Array.from(exclusionListElement.querySelectorAll('[data-value]')).forEach((exclusionItem) => {
|
||||
exclusionItem.remove();
|
||||
});
|
||||
|
||||
if (exclusionList.length) {
|
||||
for (const exclusionValue of exclusionList) {
|
||||
const ariaLabelOrTitle = `Delete ${exclusionValue}`;
|
||||
const itemElement = exclusionListItemTemplateElement.cloneNode(true);
|
||||
const deleteButtonElement = itemElement.getElementsByTagName('button')[0];
|
||||
|
||||
deleteButtonElement.addEventListener('click', handleDeleteClick);
|
||||
deleteButtonElement.setAttribute('aria-label', ariaLabelOrTitle);
|
||||
deleteButtonElement.setAttribute('title', ariaLabelOrTitle);
|
||||
itemElement.removeAttribute('id');
|
||||
itemElement.getElementsByTagName('span')[0].innerText = exclusionValue;
|
||||
itemElement.setAttribute('data-value', exclusionValue);
|
||||
itemElement.style.removeProperty('display');
|
||||
exclusionListElement.appendChild(itemElement);
|
||||
}
|
||||
} else {
|
||||
emptyItemElement.innerText = "You don't have any exclusions yet";
|
||||
emptyItemElement.style.removeProperty('display');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Add a new item to the exclusion list
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function handleAddClick() {
|
||||
const message = browser.i18n.getMessage('options_addPrompt');
|
||||
const value = window.prompt(message)?.trim().replace('www.', '');
|
||||
|
||||
if (value && (domainRegExp.test(value) || value === 'localhost')) {
|
||||
const filterInputElement = document.getElementById('filter-input');
|
||||
const state = { on: false };
|
||||
await dispatch({ hostname: value, state, type: 'UPDATE_STORE' });
|
||||
|
||||
exclusionList = [...new Set([...exclusionList, value])].sort();
|
||||
createList();
|
||||
updateList(filterInputElement.value.trim());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Clear all items from the exclusion list
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function handleClearClick() {
|
||||
const filterInputElement = document.getElementById('filter-input');
|
||||
|
||||
for (const exclusionValue of exclusionList) {
|
||||
const state = { on: true };
|
||||
await dispatch({ hostname: exclusionValue, state, type: 'UPDATE_STORE' });
|
||||
}
|
||||
|
||||
exclusionList = [];
|
||||
createList();
|
||||
updateList(filterInputElement.value.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Setup handlers and items
|
||||
*/
|
||||
async function handleContentLoaded() {
|
||||
exclusionList = await dispatch({ type: 'GET_EXCLUSION_LIST' });
|
||||
createList();
|
||||
|
||||
const addButtonElement = document.getElementById('add-button');
|
||||
addButtonElement.addEventListener('click', handleAddClick);
|
||||
|
||||
const clearButtonElement = document.getElementById('clear-button');
|
||||
clearButtonElement.addEventListener('click', handleClearClick);
|
||||
|
||||
const exportButtonElement = document.getElementById('export-button');
|
||||
exportButtonElement.addEventListener('click', handleExportClick);
|
||||
|
||||
const fileInputElement = document.getElementById('file-input');
|
||||
fileInputElement.addEventListener('change', handleFileChange);
|
||||
|
||||
const filterInputElement = document.getElementById('filter-input');
|
||||
filterInputElement.addEventListener('keydown', handleFilterKeyDown);
|
||||
|
||||
const importButtonElement = document.getElementById('import-button');
|
||||
importButtonElement.addEventListener('click', handleImportClick);
|
||||
|
||||
translate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Delete the clicked element from the exclusion list
|
||||
* @param {MouseEvent} event
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function handleDeleteClick(event) {
|
||||
const filterInputElement = document.getElementById('filter-input');
|
||||
const { value } = event.currentTarget.parentElement.dataset;
|
||||
const itemElement = document.querySelector(`[data-value="${value}"]`);
|
||||
const state = { on: true };
|
||||
|
||||
await dispatch({ hostname: value, state, type: 'UPDATE_STORE' });
|
||||
exclusionList = exclusionList.filter((exclusionValue) => exclusionValue !== value);
|
||||
itemElement?.remove();
|
||||
updateList(filterInputElement.value.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Export a file with the current exclusion list
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleExportClick() {
|
||||
const anchor = document.createElement('a');
|
||||
const now = new Date();
|
||||
const day = now.getDate().toString().padStart(2, '0');
|
||||
const month = now.getMonth().toString().padStart(2, '0');
|
||||
const year = now.getUTCFullYear();
|
||||
const text = exclusionList.join('\n');
|
||||
const defaultTitle = `${year}${month}${day}`;
|
||||
const customTitle = window.prompt('Enter a file name', defaultTitle);
|
||||
const blob = new Blob([text], { type: 'octet/stream' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
anchor.href = url;
|
||||
anchor.download = `${customTitle || defaultTitle}.cdm`;
|
||||
anchor.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Process a file and send the updates
|
||||
* @param {InputEvent} event
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleFileChange(event) {
|
||||
const file = event.currentTarget.files[0];
|
||||
const filterInputElement = document.getElementById('filter-input');
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.addEventListener('load', async (event) => {
|
||||
const input = event.currentTarget.result.split('\n');
|
||||
const exclusions = [];
|
||||
|
||||
for (let value of input) {
|
||||
value = value.replace('www.', '');
|
||||
|
||||
if (value && (domainRegExp.test(value) || value === 'localhost')) {
|
||||
const state = { on: false };
|
||||
|
||||
await dispatch({ hostname: value, state, type: 'UPDATE_STORE' });
|
||||
exclusions.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
exclusionList = [...new Set([...exclusionList, ...exclusions])].sort();
|
||||
createList();
|
||||
updateList(filterInputElement.value.trim());
|
||||
});
|
||||
|
||||
event.currentTarget.value = '';
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Apply filter to the exclusion list when the user presses ENTER key
|
||||
* @param {KeyboardEvent} event
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleFilterKeyDown(event) {
|
||||
if (event.key === 'Enter') {
|
||||
const filterValue = event.currentTarget.value.trim();
|
||||
updateList(filterValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Shallow click an hidden input to open the file explorer
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleImportClick() {
|
||||
const fileInputElement = document.getElementById('file-input');
|
||||
fileInputElement.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Apply translations to tags with i18n data attribute
|
||||
* @returns {void}
|
||||
*/
|
||||
function translate() {
|
||||
const nodes = document.querySelectorAll('[data-i18n], [data-i18n-placeholder]');
|
||||
|
||||
for (let i = nodes.length; i--; ) {
|
||||
const node = nodes[i];
|
||||
const { i18n, i18nPlaceholder } = node.dataset;
|
||||
|
||||
if (i18n) {
|
||||
node.innerHTML = browser.i18n.getMessage(i18n);
|
||||
}
|
||||
|
||||
if (i18nPlaceholder) {
|
||||
node.setAttribute('placeholder', browser.i18n.getMessage(i18nPlaceholder));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Update exclusion items in DOM
|
||||
* @param {string | undefined} filterValue
|
||||
* @returns {void}
|
||||
*/
|
||||
function updateList(filterValue) {
|
||||
const emptyItemElement = document.getElementById('exclusion-list-item-empty');
|
||||
const exclusionListElement = document.getElementById('exclusion-list');
|
||||
const exclusionListElements = exclusionListElement.querySelectorAll(`[data-value]`);
|
||||
|
||||
if (exclusionListElements.length) {
|
||||
let isEmpty = true;
|
||||
emptyItemElement.style.setProperty('display', 'none');
|
||||
|
||||
for (const exclusionItemElement of Array.from(exclusionListElements)) {
|
||||
if (exclusionItemElement.matches(`[data-value*="${filterValue}"]`) || !filterValue) {
|
||||
exclusionItemElement.style.removeProperty('display');
|
||||
isEmpty = false;
|
||||
} else {
|
||||
exclusionItemElement.style.setProperty('display', 'none');
|
||||
}
|
||||
}
|
||||
|
||||
if (isEmpty) {
|
||||
emptyItemElement.innerText = 'No exclusions found';
|
||||
emptyItemElement.style.removeProperty('display');
|
||||
}
|
||||
} else {
|
||||
emptyItemElement.innerText = "You don't have any exclusions yet";
|
||||
emptyItemElement.style.removeProperty('display');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Listen to document ready
|
||||
* @listens document#DOMContentLoaded
|
||||
*/
|
||||
document.addEventListener('DOMContentLoaded', handleContentLoaded);
|
412
packages/browser-extension/src/scripts/popup.js
Normal file
@ -0,0 +1,412 @@
|
||||
/**
|
||||
* @typedef {Object} ExtensionState
|
||||
* @property {ExtensionIssue} [issue]
|
||||
* @property {boolean} on
|
||||
* @property {string} [updateAvailable]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} PopupState
|
||||
* @extends {ExtensionState}
|
||||
* @property {number} [tabId]
|
||||
*/
|
||||
|
||||
if (typeof browser === 'undefined') {
|
||||
browser = chrome;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Chrome Web Store link
|
||||
* @type {string}
|
||||
*/
|
||||
const chromeUrl = 'https://chrome.google.com/webstore/detail/djcbfpkdhdkaflcigibkbpboflaplabg';
|
||||
|
||||
/**
|
||||
* @description Shortcut to send messages to background script
|
||||
*/
|
||||
const dispatch = browser.runtime.sendMessage;
|
||||
|
||||
/**
|
||||
* @description Edge Add-ons link
|
||||
* @type {string}
|
||||
*/
|
||||
const edgeUrl =
|
||||
'https://microsoftedge.microsoft.com/addons/detail/hbogodfciblakeneadpcolhmfckmjcii';
|
||||
|
||||
/**
|
||||
* @description Firefox Add-ons link
|
||||
* @type {string}
|
||||
*/
|
||||
const firefoxUrl = 'https://addons.mozilla.org/firefox/addon/cookie-dialog-monster';
|
||||
|
||||
/**
|
||||
* @description Current hostname
|
||||
* @type {string}
|
||||
*/
|
||||
let hostname = '?';
|
||||
|
||||
/**
|
||||
* @description Is current browser an instance of Chromium?
|
||||
* @type {boolean}
|
||||
*/
|
||||
const isChromium = navigator.userAgent.indexOf('Chrome') !== -1;
|
||||
|
||||
/**
|
||||
* @description Is current browser an instance of Edge?
|
||||
* @type {boolean}
|
||||
*/
|
||||
const isEdge = navigator.userAgent.indexOf('Edg') !== -1;
|
||||
|
||||
/**
|
||||
* @description Is current browser an instance of Firefox?
|
||||
* @type {boolean}
|
||||
*/
|
||||
const isFirefox = navigator.userAgent.indexOf('Firefox') !== -1;
|
||||
|
||||
/**
|
||||
* @description Popup state
|
||||
* @type {PopupState}
|
||||
*/
|
||||
let state = { on: true };
|
||||
|
||||
/**
|
||||
* @description Close report form
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleCancelClick() {
|
||||
const content = document.getElementsByClassName('content')[0];
|
||||
const report = document.getElementsByClassName('report')[0];
|
||||
|
||||
if (content instanceof HTMLElement && report instanceof HTMLElement) {
|
||||
content.style.removeProperty('display');
|
||||
report.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Setup stars handlers and result message links
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function handleContentLoaded() {
|
||||
const tab = await dispatch({ type: 'GET_TAB' });
|
||||
const tabUrl = tab?.url ? new URL(tab.url) : undefined;
|
||||
|
||||
hostname = tabUrl?.hostname.split('.').slice(-3).join('.').replace('www.', '');
|
||||
|
||||
const next = await dispatch({ hostname, type: 'GET_STATE' });
|
||||
state = { ...(next ?? state), tabId: tab?.id };
|
||||
|
||||
if (state.issue?.url) {
|
||||
const issueBanner = document.getElementById('issue-banner');
|
||||
issueBanner.removeAttribute('aria-hidden');
|
||||
|
||||
const issueBannerText = document.getElementById('issue-banner-text');
|
||||
if (state.issue.flags.includes('wontfix'))
|
||||
issueBannerText.innerText = browser.i18n.getMessage('popup_bannerIssueWontFix');
|
||||
else issueBannerText.innerText = browser.i18n.getMessage('popup_bannerIssueOpen');
|
||||
|
||||
const issueBannerUrl = document.getElementById('issue-banner-url');
|
||||
issueBannerUrl.setAttribute('href', state.issue.url);
|
||||
} else {
|
||||
const cancelButtonElement = document.getElementsByClassName('report-cancel-button')[0];
|
||||
cancelButtonElement?.addEventListener('click', handleCancelClick);
|
||||
|
||||
const reasonInputElement = document.getElementById('report-input-reason');
|
||||
reasonInputElement?.addEventListener('input', handleInputChange);
|
||||
reasonInputElement?.addEventListener('keydown', handleInputKeyDown);
|
||||
|
||||
if (!state.updateAvailable) {
|
||||
const reportButtonElement = document.getElementById('report-button');
|
||||
reportButtonElement?.addEventListener('click', handleReportClick);
|
||||
reportButtonElement?.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
const urlInputElement = document.getElementById('report-input-url');
|
||||
urlInputElement?.addEventListener('input', handleInputChange);
|
||||
urlInputElement?.addEventListener('keydown', handleInputKeyDown);
|
||||
if (tabUrl) urlInputElement?.setAttribute('value', `${tabUrl.origin}${tabUrl.pathname}`);
|
||||
|
||||
const submitButtonElement = document.getElementsByClassName('report-submit-button')[0];
|
||||
submitButtonElement?.addEventListener('click', handleSubmitButtonClick);
|
||||
}
|
||||
|
||||
if (state.updateAvailable) {
|
||||
const updateBanner = document.getElementById('update-banner');
|
||||
updateBanner.removeAttribute('aria-hidden');
|
||||
|
||||
const updateBannerUrl = document.getElementById('update-banner-url');
|
||||
updateBannerUrl.href += `/tag/${state.updateAvailable}`;
|
||||
}
|
||||
|
||||
const hostTextElement = document.getElementById('host');
|
||||
hostTextElement.innerText = hostname ?? 'unknown';
|
||||
|
||||
const contributeButtonElement = document.getElementById('contribute-option');
|
||||
contributeButtonElement?.addEventListener('click', handleLinkRedirect);
|
||||
|
||||
const databaseRefreshButtonElement = document.getElementById('refresh-database-button');
|
||||
databaseRefreshButtonElement?.addEventListener('click', handleDatabaseRefresh);
|
||||
|
||||
const extensionVersionElement = document.getElementById('extension-version');
|
||||
extensionVersionElement.innerText = browser.runtime.getManifest().version;
|
||||
|
||||
const helpButtonElement = document.getElementById('help-option');
|
||||
helpButtonElement?.addEventListener('click', handleLinkRedirect);
|
||||
|
||||
const powerButtonElement = document.getElementById('power-option');
|
||||
powerButtonElement?.addEventListener('click', handlePowerToggle);
|
||||
if (state.on) powerButtonElement?.setAttribute('data-value', 'on');
|
||||
else powerButtonElement?.setAttribute('data-value', 'off');
|
||||
|
||||
const rateButtonElement = document.getElementById('rate-option');
|
||||
rateButtonElement?.addEventListener('click', handleLinkRedirect);
|
||||
if (isEdge) rateButtonElement?.setAttribute('data-href', edgeUrl);
|
||||
else if (isChromium) rateButtonElement?.setAttribute('data-href', chromeUrl);
|
||||
else if (isFirefox) rateButtonElement?.setAttribute('data-href', firefoxUrl);
|
||||
|
||||
const settingsButtonElement = document.getElementById('settings-button');
|
||||
settingsButtonElement?.addEventListener('click', handleSettingsClick);
|
||||
|
||||
translate();
|
||||
await updateDatabaseVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Refresh the database
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
async function handleDatabaseRefresh(event) {
|
||||
const target = event.currentTarget;
|
||||
|
||||
if (target.getAttribute('aria-disabled') === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
const checkIcon = target.querySelector('#refresh-database-check');
|
||||
const spinnerIcon = target.querySelector('#refresh-database-spinner');
|
||||
|
||||
target.setAttribute('data-refreshing', 'true');
|
||||
target.setAttribute('aria-disabled', 'true');
|
||||
await dispatch({ type: 'REFRESH_DATA' });
|
||||
checkIcon.style.setProperty('display', 'block');
|
||||
spinnerIcon.style.setProperty('display', 'none');
|
||||
target.removeAttribute('data-animation');
|
||||
target.removeAttribute('data-refreshing');
|
||||
await updateDatabaseVersion();
|
||||
|
||||
window.setTimeout(() => {
|
||||
checkIcon.style.setProperty('display', 'none');
|
||||
spinnerIcon.style.setProperty('display', 'block');
|
||||
target.removeAttribute('aria-disabled');
|
||||
target.setAttribute('data-animation', 'flip');
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Input change handler
|
||||
* @param {InputEvent} event
|
||||
*/
|
||||
function handleInputChange(event) {
|
||||
event.currentTarget.removeAttribute('aria-invalid');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Input key down handler
|
||||
* @param {KeyboardEvent} event
|
||||
*/
|
||||
function handleInputKeyDown(event) {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
event.currentTarget.blur();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Open a new tab
|
||||
* @param {MouseEvent} event
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function handleLinkRedirect(event) {
|
||||
const { href } = event.currentTarget.dataset;
|
||||
|
||||
if (href) {
|
||||
await browser.tabs.create({ url: href });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Disable or enable extension on current page
|
||||
* @returns {void}
|
||||
*/
|
||||
async function handlePowerToggle() {
|
||||
const next = { on: !state.on };
|
||||
|
||||
await dispatch({ hostname, state: next, type: 'UPDATE_STORE' });
|
||||
await browser.tabs.reload(state.tabId, { bypassCache: true });
|
||||
window.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Show report form
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleReportClick() {
|
||||
const content = document.getElementsByClassName('content')[0];
|
||||
const report = document.getElementsByClassName('report')[0];
|
||||
|
||||
if (content instanceof HTMLElement && report instanceof HTMLElement) {
|
||||
content.style.display = 'none';
|
||||
report.style.removeProperty('display');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Open options page
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function handleSettingsClick() {
|
||||
await browser.runtime.openOptionsPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Report submit button click handler
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
async function handleSubmitButtonClick(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (event.currentTarget.getAttribute('aria-disabled') === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
event.currentTarget.setAttribute('aria-disabled', 'true');
|
||||
|
||||
const reasonInput = document.getElementById('report-input-reason');
|
||||
const reasonText = reasonInput?.value.trim();
|
||||
const urlInput = document.getElementById('report-input-url');
|
||||
const urlText = urlInput?.value.trim();
|
||||
const errors = validateForm({ reason: reasonText, url: urlText });
|
||||
|
||||
if (errors) {
|
||||
if (errors.reason) {
|
||||
reasonInput?.setAttribute('aria-invalid', 'true');
|
||||
reasonInput?.setAttribute('aria-errormessage', 'report-input-reason-error');
|
||||
}
|
||||
|
||||
if (errors.url) {
|
||||
urlInput?.setAttribute('aria-invalid', 'true');
|
||||
urlInput?.setAttribute('aria-errormessage', 'report-input-url-error');
|
||||
}
|
||||
|
||||
event.currentTarget.setAttribute('aria-disabled', 'false');
|
||||
return;
|
||||
}
|
||||
|
||||
const issueButtons = document.getElementsByClassName('report-issue-button');
|
||||
const formView = document.getElementsByClassName('report-form-view')[0];
|
||||
const userAgent = window.navigator.userAgent;
|
||||
const response = await dispatch({ userAgent, reason: reasonText, url: urlText, type: 'REPORT' });
|
||||
const hostname = new URL(urlText).hostname.split('.').slice(-3).join('.').replace('www.', '');
|
||||
const issue = { expiresIn: Date.now() + 8 * 60 * 60 * 1000, flags: ['bug'], url: response.data };
|
||||
|
||||
if (response.success) {
|
||||
const successView = document.getElementsByClassName('report-submit-success-view')[0];
|
||||
|
||||
await dispatch({ hostname, state: { issue }, type: 'UPDATE_STORE' });
|
||||
await dispatch({ hostname, type: 'ENABLE_ICON' });
|
||||
formView?.setAttribute('hidden', 'true');
|
||||
issueButtons[1]?.addEventListener('click', () => window.open(response.data, '_blank'));
|
||||
successView?.removeAttribute('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
const errorView = document.getElementsByClassName('report-submit-error-view')[0];
|
||||
|
||||
if (response.errors?.some((error) => error.includes('wontfix'))) {
|
||||
issue.flags.push('wontfix');
|
||||
}
|
||||
|
||||
await dispatch({ hostname, state: { issue }, type: 'UPDATE_STORE' });
|
||||
errorView?.removeAttribute('hidden');
|
||||
formView?.setAttribute('hidden', 'true');
|
||||
issueButtons[0]?.addEventListener('click', () => window.open(response.data, '_blank'));
|
||||
return;
|
||||
}
|
||||
|
||||
window.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Apply translations to tags with i18n data attribute
|
||||
* @returns {void}
|
||||
*/
|
||||
function translate() {
|
||||
const nodes = document.querySelectorAll('[data-i18n], [data-i18n-placeholder]');
|
||||
|
||||
for (let i = nodes.length; i--; ) {
|
||||
const node = nodes[i];
|
||||
const { i18n, i18nPlaceholder } = node.dataset;
|
||||
|
||||
if (i18n) {
|
||||
node.innerHTML = browser.i18n.getMessage(i18n);
|
||||
}
|
||||
|
||||
if (i18nPlaceholder) {
|
||||
node.setAttribute('placeholder', browser.i18n.getMessage(i18nPlaceholder));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @description Update the database version element
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function updateDatabaseVersion() {
|
||||
const data = await dispatch({ hostname, type: 'GET_DATA' });
|
||||
const databaseVersionElement = document.getElementById('database-version');
|
||||
|
||||
if (data.version) databaseVersionElement.innerText = data.version;
|
||||
else databaseVersionElement.style.setProperty('display', 'none');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Validate form
|
||||
* @param {{ reason: string | undefined | undefined, url: string | undefined }} params
|
||||
* @returns {{ reason: string | undefined, url: string | undefined } | undefined}
|
||||
*/
|
||||
function validateForm(params) {
|
||||
const { reason, url } = params;
|
||||
let errors = undefined;
|
||||
|
||||
if (!reason || reason.length < 10 || reason.length > 1000) {
|
||||
errors = {
|
||||
...(errors ?? {}),
|
||||
reason: browser.i18n.getMessage('report_reasonInputError'),
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
new URL(url);
|
||||
} catch {
|
||||
errors = {
|
||||
...(errors ?? {}),
|
||||
url: browser.i18n.getMessage('report_urlInputError'),
|
||||
};
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Listen to document ready
|
||||
* @listens document#DOMContentLoaded
|
||||
*/
|
||||
document.addEventListener('DOMContentLoaded', handleContentLoaded);
|
@ -1,15 +0,0 @@
|
||||
:root {
|
||||
--color-error: #cc0000;
|
||||
--color-primary: #3dd9eb;
|
||||
--color-secondary: #34495e;
|
||||
--color-success: #5cb85c;
|
||||
--color-tertiary: #6b7280;
|
||||
--color-transparent: transparent;
|
||||
--color-warning: #ffdf00;
|
||||
--color-white: #ffffff;
|
||||
}
|
||||
|
||||
body * {
|
||||
box-sizing: border-box;
|
||||
font-family: Inter, Arial, Helvetica, sans-serif;
|
||||
}
|
@ -1,4 +1,29 @@
|
||||
.button {
|
||||
:root {
|
||||
--color-error: #cc0000;
|
||||
--color-primary: #3dd9eb;
|
||||
--color-secondary: #34495e;
|
||||
--color-success: #5cb85c;
|
||||
--color-tertiary: #6b7280;
|
||||
--color-transparent: transparent;
|
||||
--color-warning: #ffdf00;
|
||||
--color-white: #ffffff;
|
||||
}
|
||||
|
||||
body {
|
||||
box-sizing: border-box;
|
||||
color: var(--color-tertiary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: Inter, Arial, Helvetica, sans-serif;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
body * {
|
||||
box-sizing: border-box;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
button {
|
||||
align-items: center;
|
||||
background-color: var(--color-white);
|
||||
border: none;
|
||||
@ -10,24 +35,24 @@
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
.button:focus,
|
||||
.button:hover {
|
||||
background-color: var(--color-secondary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.button[data-variant='large'] {
|
||||
button[data-variant='large'] {
|
||||
direction: rtl;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@media only screen and (max-device-width: 768px) {
|
||||
.button[data-variant='large'] {
|
||||
button[data-variant='large'] {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
button:focus,
|
||||
button:hover {
|
||||
background-color: var(--color-secondary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--color-secondary);
|
||||
font-size: 12px;
|
||||
height: 4px;
|
||||
@ -35,14 +60,14 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header {
|
||||
header {
|
||||
background-color: var(--color-secondary);
|
||||
color: var(--color-white);
|
||||
font-size: 16px !important;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.header > div {
|
||||
header > div {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
@ -50,7 +75,7 @@
|
||||
margin: auto 0px;
|
||||
}
|
||||
|
||||
.input {
|
||||
main input {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background-color: var(--color-white);
|
||||
@ -65,18 +90,18 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input::placeholder {
|
||||
main input::placeholder {
|
||||
color: var(--color-tertiary);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input:focus,
|
||||
.input:hover {
|
||||
main input:focus,
|
||||
main input:hover {
|
||||
border-bottom: 1px solid var(--color-primary);
|
||||
}
|
||||
|
||||
.header > div,
|
||||
.main {
|
||||
header > div,
|
||||
main {
|
||||
margin: 0px auto;
|
||||
max-width: 768px;
|
||||
padding: 16px;
|
||||
@ -97,13 +122,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.exclusion-list {
|
||||
#exclusion-list {
|
||||
font-size: 14px;
|
||||
list-style: none;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.exclusion-list > li {
|
||||
#exclusion-list > li {
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
@ -112,23 +137,14 @@
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
.exclusion-list > li:focus-within,
|
||||
.exclusion-list > li:hover {
|
||||
#exclusion-list > li:focus-within,
|
||||
#exclusion-list > li:hover {
|
||||
background-color: var(--color-secondary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.exclusion-list > button {
|
||||
#exclusion-list > li > button {
|
||||
background-color: var(--color-white);
|
||||
color: var(--color-error);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
#__plasmo {
|
||||
box-sizing: border-box;
|
||||
color: var(--color-tertiary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: Inter, Arial, Helvetica, sans-serif;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|