diff --git a/.eslintrc.js b/.eslintrc.js
index 32a2e07..8f58a9b 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -13,6 +13,8 @@ module.exports = {
'kentcdodds/import',
'kentcdodds/jest',
'kentcdodds/possible-errors',
+ 'plugin:jest-dom/recommended',
+ 'plugin:testing-library/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/errors',
'plugin:import/typescript',
diff --git a/package-lock.json b/package-lock.json
index af69205..cf03742 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5897,6 +5897,144 @@
}
}
},
+ "@testing-library/jest-dom": {
+ "version": "5.11.9",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.9.tgz",
+ "integrity": "sha512-Mn2gnA9d1wStlAIT2NU8J15LNob0YFBVjs2aEQ3j8rsfRQo+lAs7/ui1i2TGaJjapLmuNPLTsrm+nPjmZDwpcQ==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.9.2",
+ "@types/testing-library__jest-dom": "^5.9.1",
+ "aria-query": "^4.2.2",
+ "chalk": "^3.0.0",
+ "css": "^3.0.0",
+ "css.escape": "^1.5.1",
+ "lodash": "^4.17.15",
+ "redent": "^3.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "css": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz",
+ "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.4",
+ "source-map": "^0.6.1",
+ "source-map-resolve": "^0.6.0"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true
+ },
+ "redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "requires": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "source-map-resolve": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz",
+ "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==",
+ "dev": true,
+ "requires": {
+ "atob": "^2.1.2",
+ "decode-uri-component": "^0.2.0"
+ }
+ },
+ "strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "requires": {
+ "min-indent": "^1.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "@testing-library/react": {
+ "version": "11.2.3",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.3.tgz",
+ "integrity": "sha512-BirBUGPkTW28ULuCwIbYo0y2+0aavHczBT6N9r3LrsswEW3pg25l1wgoE7I8QBIy1upXWkwKpYdWY7NYYP0Bxw==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.12.5",
+ "@testing-library/dom": "^7.28.1"
+ }
+ },
+ "@testing-library/user-event": {
+ "version": "12.6.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.6.0.tgz",
+ "integrity": "sha512-FNEH/HLmOk5GO70I52tKjs7WvGYckeE/SrnLX/ip7z2IGbffyd5zOUM1tZ10vsTphqm+VbDFI0oaXu0wcfQsAQ==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.12.5"
+ }
+ },
"@types/anymatch": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
@@ -5960,6 +6098,12 @@
"@types/har-format": "*"
}
},
+ "@types/classnames": {
+ "version": "2.2.11",
+ "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.11.tgz",
+ "integrity": "sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw==",
+ "dev": true
+ },
"@types/copy-webpack-plugin": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/@types/copy-webpack-plugin/-/copy-webpack-plugin-6.4.0.tgz",
@@ -6278,6 +6422,15 @@
"integrity": "sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==",
"dev": true
},
+ "@types/testing-library__jest-dom": {
+ "version": "5.9.5",
+ "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.5.tgz",
+ "integrity": "sha512-ggn3ws+yRbOHog9GxnXiEZ/35Mow6YtPZpd7Z5mKDeZS/o7zx3yAle0ov/wjhVB5QT4N2Dt+GNoGCdqkBGCajQ==",
+ "dev": true,
+ "requires": {
+ "@types/jest": "*"
+ }
+ },
"@types/uglify-js": {
"version": "3.11.1",
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.11.1.tgz",
@@ -10645,6 +10798,12 @@
}
}
},
+ "css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=",
+ "dev": true
+ },
"cssom": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
@@ -20413,6 +20572,12 @@
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
"dev": true
},
+ "min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true
+ },
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
diff --git a/package.json b/package.json
index 620ef4a..ce055e6 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,12 @@
"webextension-polyfill-ts": "^0.22.0"
},
"devDependencies": {
+ "@testing-library/dom": "^7.29.4",
+ "@testing-library/jest-dom": "^5.11.9",
+ "@testing-library/react": "^11.2.3",
+ "@testing-library/user-event": "^12.6.0",
"@types/chrome": "0.0.128",
+ "@types/classnames": "^2.2.11",
"@types/copy-webpack-plugin": "^6.4.0",
"@types/firefox-webext-browser": "^82.0.0",
"@types/jest": "^26.0.20",
@@ -60,9 +65,11 @@
"eslint-config-kentcdodds": "^17.3.0",
"eslint-config-prettier": "^7.1.0",
"eslint-plugin-import": "^2.22.1",
+ "eslint-plugin-jest-dom": "^3.6.5",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-sort-keys-fix": "^1.1.1",
+ "eslint-plugin-testing-library": "^3.10.1",
"eslint-plugin-typescript-sort-keys": "^1.5.0",
"gulp": "^3.9.1",
"husky": "^4.3.7",
diff --git a/src/components/Alert.test.tsx b/src/components/Alert.test.tsx
new file mode 100644
index 0000000..88f0e6e
--- /dev/null
+++ b/src/components/Alert.test.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import Alert from './Alert';
+
+describe('Alert Component', () => {
+ it('should render with proper text on success type', () => {
+ const text = 'Test Text';
+ const { container } = render();
+ expect(screen.getByText(text)).toBeTruthy();
+ expect(container).toMatchInlineSnapshot(`
+
+ `);
+ });
+ it('should render wtih proper text on danger type', () => {
+ const text = 'Test Text';
+ const { container } = render();
+ expect(screen.getByText(text)).toBeTruthy();
+ expect(container).toMatchInlineSnapshot(`
+
+ `);
+ });
+});
diff --git a/src/components/Alert.tsx b/src/components/Alert.tsx
new file mode 100644
index 0000000..52b76e9
--- /dev/null
+++ b/src/components/Alert.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import classNames from 'classnames';
+import { SuccessOrFailType } from '../config';
+interface AlertProps {
+ text: string;
+ type: SuccessOrFailType;
+}
+
+export default function Alert({ type, text }: AlertProps): JSX.Element {
+ return {text}
;
+}
diff --git a/src/components/MainList.test.tsx b/src/components/MainList.test.tsx
new file mode 100644
index 0000000..bbd5d93
--- /dev/null
+++ b/src/components/MainList.test.tsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import MainList from './MainList';
+
+type onClick = (event: React.MouseEvent) => void;
+describe('MainList', () => {
+ let disableLogging: onClick;
+ let enableLogging: onClick;
+ let loggedIn: boolean;
+ let loggingEnabled: boolean;
+ let logoutUser: onClick;
+ let totalTimeLoggedToday: string;
+ beforeEach(() => {
+ disableLogging = jest.fn();
+ enableLogging = jest.fn();
+ loggingEnabled = false;
+ loggedIn = false;
+ logoutUser = jest.fn();
+ totalTimeLoggedToday = '1/1/1999';
+ });
+ it('should render properly', () => {
+ const { container } = render(
+ ,
+ );
+ expect(container).toMatchInlineSnapshot(`
+
+ `);
+ });
+});
diff --git a/src/components/MainList.tsx b/src/components/MainList.tsx
new file mode 100644
index 0000000..3764ddb
--- /dev/null
+++ b/src/components/MainList.tsx
@@ -0,0 +1,87 @@
+import React from 'react';
+import { browser } from 'webextension-polyfill-ts';
+
+export interface MainListProps {
+ disableLogging: (event: React.MouseEvent) => void;
+ enableLogging: (event: React.MouseEvent) => void;
+ loggedIn: boolean;
+ loggingEnabled: boolean;
+ logoutUser: (event: React.MouseEvent) => void;
+ totalTimeLoggedToday?: string;
+}
+const openOptionsPage = async (): Promise => {
+ await browser.runtime.openOptionsPage();
+};
+
+export default function MainList({
+ disableLogging,
+ enableLogging,
+ loggedIn,
+ loggingEnabled,
+ logoutUser,
+ totalTimeLoggedToday,
+}: MainListProps): JSX.Element {
+ return (
+
+ {loggedIn && (
+
+
+
+ {totalTimeLoggedToday}
+
+ TOTAL TIME LOGGED TODAY
+
+
+
+
+ )}
+ {loggingEnabled && loggedIn && (
+
+ )}
+ {!loggingEnabled && loggedIn && (
+
+ )}
+
+
+ );
+}
diff --git a/src/config.ts b/src/config.ts
index a7743c2..cfda2a6 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -3,16 +3,16 @@ import { browser } from 'webextension-polyfill-ts';
/**
* Logging
*/
-type ApiStates = 'allGood' | 'notLogging' | 'notSignedIn' | 'blacklisted' | 'whitelisted';
+export type ApiStates = 'allGood' | 'notLogging' | 'notSignedIn' | 'blacklisted' | 'whitelisted';
/**
* Supported logging style
*/
-type LoggingStyle = 'whitelist' | 'blacklist';
+export type LoggingStyle = 'whitelist' | 'blacklist';
/**
* Logging type
*/
-type LoggingType = 'domain' | 'url';
-type SuccessOrFailType = 'success' | 'danger';
+export type LoggingType = 'domain' | 'url';
+export type SuccessOrFailType = 'success' | 'danger';
/**
* Predefined alert type and text for success and failure.
*/
@@ -31,10 +31,10 @@ interface SuccessOrFailAlert {
* Different colors for different states of the extension
*/
interface Colors {
- allGood: string;
- lightTheme: string;
- notLogging: string;
- notSignedIn: string;
+ allGood: '';
+ lightTheme: 'white';
+ notLogging: 'gray';
+ notSignedIn: 'red';
}
/**
* Tooltip messages
diff --git a/src/manifests/chrome.json b/src/manifests/chrome.json
new file mode 100644
index 0000000..9ee22a9
--- /dev/null
+++ b/src/manifests/chrome.json
@@ -0,0 +1,38 @@
+{
+ "background": {
+ "persistent": false,
+ "scripts": ["background.js"]
+ },
+ "browser_action": {
+ "default_icon": {
+ "19": "graphics/wakatime-logo-19.png",
+ "38": "graphics/wakatime-logo-38.png"
+ },
+ "default_popup": "popup.html",
+ "default_title": "WakaTime"
+ },
+ "browser_specific_settings": {},
+ "description": "Automatic time tracking for Chrome.",
+ "devtools_page": "devtools.html",
+ "homepage_url": "https://wakatime.com",
+ "icons": {
+ "16": "graphics/wakatime-logo-16.png",
+ "48": "graphics/wakatime-logo-48.png",
+ "128": "graphics/wakatime-logo-128.png"
+ },
+ "manifest_version": 2,
+ "name": "WakaTime",
+ "options_ui": {
+ "chrome_style": false,
+ "page": "options.html"
+ },
+ "permissions": [
+ "https://api.wakatime.com/*",
+ "https://wakatime.com/*",
+ "alarms",
+ "tabs",
+ "storage",
+ "idle"
+ ],
+ "version": "2.0.1"
+}
diff --git a/src/html/manifest.json b/src/manifests/firefox.json
similarity index 100%
rename from src/html/manifest.json
rename to src/manifests/firefox.json
diff --git a/src/types/heartbeats.d.ts b/src/types/heartbeats.d.ts
new file mode 100644
index 0000000..e178266
--- /dev/null
+++ b/src/types/heartbeats.d.ts
@@ -0,0 +1,28 @@
+// Generated by https://quicktype.io
+
+export interface HeartBeatsPayload {
+ data: Datum[];
+ end: string;
+ start: string;
+ timezone: string;
+}
+
+export interface Datum {
+ branch: string;
+ category: string;
+ created_at: string;
+ cursorpos: null;
+ dependencies: string;
+ entity: string;
+ id: string;
+ is_write: boolean;
+ language: string;
+ lineno: null;
+ lines: number;
+ machine_name_id: string;
+ project: string;
+ time: number;
+ type: string;
+ user_agent_id: string;
+ user_id: string;
+}
diff --git a/src/types/summaries.d.ts b/src/types/summaries.d.ts
new file mode 100644
index 0000000..100569f
--- /dev/null
+++ b/src/types/summaries.d.ts
@@ -0,0 +1,47 @@
+// Generated by https://quicktype.io
+
+export interface SummariesPayload {
+ data: Datum[];
+ end: string;
+ start: string;
+}
+
+export interface Datum {
+ categories: Category[];
+ dependencies: Category[];
+ editors: Category[];
+ grand_total: GrandTotal;
+ languages: Category[];
+ machines: Category[];
+ operating_systems: Category[];
+ projects: Category[];
+ range: Range;
+}
+
+export interface Category {
+ digital: string;
+ hours: number;
+ machine_name_id?: string;
+ minutes: number;
+ name: string;
+ percent: number;
+ seconds: number;
+ text: string;
+ total_seconds: number;
+}
+
+export interface GrandTotal {
+ digital: string;
+ hours: number;
+ minutes: number;
+ text: string;
+ total_seconds: number;
+}
+
+export interface Range {
+ date: string;
+ end: string;
+ start: string;
+ text: string;
+ timezone: string;
+}
diff --git a/src/types/user.d.ts b/src/types/user.d.ts
new file mode 100644
index 0000000..4fd0abc
--- /dev/null
+++ b/src/types/user.d.ts
@@ -0,0 +1,44 @@
+// Generated by https://quicktype.io
+
+export interface UserPayload {
+ data: User;
+}
+
+export interface User {
+ bio: null;
+ color_scheme: string;
+ created_at: string;
+ date_format: string;
+ default_dashboard_range: string;
+ display_name: string;
+ email: string;
+ full_name: string;
+ has_premium_features: boolean;
+ human_readable_website: string;
+ id: string;
+ is_email_confirmed: boolean;
+ is_email_public: boolean;
+ is_hireable: boolean;
+ is_onboarding_finished: boolean;
+ languages_used_public: boolean;
+ last_heartbeat_at: string;
+ last_plugin: string;
+ last_plugin_name: string;
+ last_project: string;
+ location: string;
+ logged_time_public: boolean;
+ modified_at: string;
+ needs_payment_method: boolean;
+ photo: string;
+ photo_public: boolean;
+ plan: string;
+ public_email: string;
+ show_machine_name_ip: boolean;
+ time_format_24hr: boolean;
+ timeout: number;
+ timezone: string;
+ username: string;
+ website: string;
+ weekday_start: number;
+ writes_only: boolean;
+}
diff --git a/src/utils/changeExtensionIcon.ts b/src/utils/changeExtensionIcon.ts
new file mode 100644
index 0000000..5fdee48
--- /dev/null
+++ b/src/utils/changeExtensionIcon.ts
@@ -0,0 +1,28 @@
+import { browser } from 'webextension-polyfill-ts';
+import config from '../config';
+
+type ColorIconTypes = 'gray' | 'red' | 'white' | '';
+
+/**
+ * It changes the extension icon color.
+ */
+export default async function changeExtensionIcon(color?: ColorIconTypes): Promise {
+ if (color) {
+ const path = `./graphics/wakatime-logo-38-${color}.png`;
+
+ await browser.browserAction.setIcon({
+ path: path,
+ });
+ } else {
+ const { theme } = await browser.storage.sync.get({
+ theme: config.theme,
+ });
+ const path =
+ theme === config.theme
+ ? './graphics/wakatime-logo-38.png'
+ : './graphics/wakatime-logo-38-white.png';
+ await browser.browserAction.setIcon({
+ path: path,
+ });
+ }
+}
diff --git a/src/utils/changeExtensionState.ts b/src/utils/changeExtensionState.ts
new file mode 100644
index 0000000..79400b7
--- /dev/null
+++ b/src/utils/changeExtensionState.ts
@@ -0,0 +1,34 @@
+import config, { ApiStates } from '../config';
+
+import changeExtensionIcon from './changeExtensionIcon';
+import changeExtensionTooltip from './changeExtensionTooltip';
+
+/**
+ * Sets the current state of the extension.
+ */
+export default async function changeExtensionState(state: ApiStates): Promise {
+ switch (state) {
+ case 'allGood':
+ await changeExtensionIcon(config.colors.allGood);
+ await changeExtensionTooltip(config.tooltips.allGood);
+ break;
+ case 'notLogging':
+ await changeExtensionIcon(config.colors.notLogging);
+ await changeExtensionTooltip(config.tooltips.notLogging);
+ break;
+ case 'notSignedIn':
+ await changeExtensionIcon(config.colors.notSignedIn);
+ await changeExtensionTooltip(config.tooltips.notSignedIn);
+ break;
+ case 'blacklisted':
+ await changeExtensionIcon(config.colors.notLogging);
+ await changeExtensionTooltip(config.tooltips.blacklisted);
+ break;
+ case 'whitelisted':
+ await changeExtensionIcon(config.colors.notLogging);
+ await changeExtensionTooltip(config.tooltips.whitelisted);
+ break;
+ default:
+ break;
+ }
+}
diff --git a/src/utils/changeExtensionTooltip.ts b/src/utils/changeExtensionTooltip.ts
new file mode 100644
index 0000000..a30e13d
--- /dev/null
+++ b/src/utils/changeExtensionTooltip.ts
@@ -0,0 +1,16 @@
+import { browser } from 'webextension-polyfill-ts';
+import config from '../config';
+
+/**
+ * It changes the extension title
+ *
+ */
+export default async function changeExtensionTooltip(text: string): Promise {
+ if (text === '') {
+ text = config.name;
+ } else {
+ text = `${config.name} - ${text}`;
+ }
+
+ await browser.browserAction.setTitle({ title: text });
+}
diff --git a/src/utils/contains.ts b/src/utils/contains.ts
new file mode 100644
index 0000000..fe62501
--- /dev/null
+++ b/src/utils/contains.ts
@@ -0,0 +1,24 @@
+/**
+ * Creates an array from list using \n as delimiter
+ * and checks if any element in list is contained in the url.
+ */
+export default function contains(url: string, list: string): boolean {
+ const lines = list.split('\n');
+
+ for (let i = 0; i < lines.length; i++) {
+ // Trim all lines from the list one by one
+ const cleanLine = lines[i].trim();
+
+ // If by any chance one line in the list is empty, ignore it
+ if (cleanLine === '') continue;
+
+ const lineRe = new RegExp(cleanLine.replace('.', '.').replace('*', '.*'));
+
+ // If url matches the current line return true
+ if (lineRe.test(url)) {
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/utils/getDomainFromUrl.ts b/src/utils/getDomainFromUrl.ts
new file mode 100644
index 0000000..7d194fc
--- /dev/null
+++ b/src/utils/getDomainFromUrl.ts
@@ -0,0 +1,8 @@
+/**
+ * Returns domain from given URL.
+ */
+export function getDomainFromUrl(url: string): string {
+ const parts = url.split('/');
+
+ return parts[0] + '//' + parts[2];
+}
diff --git a/src/utils/inArray.ts b/src/utils/inArray.ts
new file mode 100644
index 0000000..487f942
--- /dev/null
+++ b/src/utils/inArray.ts
@@ -0,0 +1,12 @@
+/**
+ * Returns boolean if needle is found in haystack or not.
+ */
+export function in_array(needle: T, haystack: T[]): boolean {
+ for (let i = 0; i < haystack.length; i++) {
+ if (needle == haystack[i]) {
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/webpack.config.ts b/webpack.config.ts
index 8a4d43e..345ca50 100644
--- a/webpack.config.ts
+++ b/webpack.config.ts
@@ -12,7 +12,7 @@ const fontFolder = join(publicFolder, 'fonts');
const graphicsFolder = join(__dirname, 'graphics');
const srcFolder = join(__dirname, 'src');
const htmlFolder = join(srcFolder, 'html');
-const manifestFile = join(__dirname, 'manifest.json');
+const manifestFolder = join(srcFolder, 'manifests');
const getConfigByBrowser = (isProd: boolean, browser: BrowserTypes): webpack.Configuration => {
const cfg: webpack.Configuration = {
@@ -43,7 +43,7 @@ const getConfigByBrowser = (isProd: boolean, browser: BrowserTypes): webpack.Con
{ from: graphicsFolder, to: 'graphics' },
{ from: htmlFolder },
// TODO: Create a mechanism to have a firefox manifest vs chrome
- { from: manifestFile },
+ { from: join(manifestFolder, `${browser}.json`), to: 'manifest.json' },
],
}),
],
diff --git a/xclap.ts b/xclap.ts
index 88853ed..669558b 100644
--- a/xclap.ts
+++ b/xclap.ts
@@ -41,6 +41,8 @@ load({
'test-jest': [exec('jest --clearCache'), exec('jest --verbose --coverage')],
'test-jest-update': exec('jest -u'),
'test-js': 'phantomjs tests/run.js',
+ 'watch-jest': exec('jest --watch'),
webpack: [exec('webpack --mode production')],
'webpack:dev': [exec('webpack --mode development')],
+ 'webpack:watch': exec('webpack --mode development --watch'),
});