mirror of
https://github.com/abhijithvijayan/web-extension-starter.git
synced 2026-01-30 09:48:12 +01:00
refactor: use shared linting config
This commit is contained in:
parent
88fea12ee6
commit
74d10f2424
16
.editorconfig
Normal file
16
.editorconfig
Normal file
@ -0,0 +1,16 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@ -1,5 +0,0 @@
|
||||
node_modules/
|
||||
dist/
|
||||
extension/
|
||||
.yarn/
|
||||
.pnp.js
|
||||
@ -1,34 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"@abhijithvijayan/eslint-config/typescript",
|
||||
"@abhijithvijayan/eslint-config/node",
|
||||
"@abhijithvijayan/eslint-config/react"
|
||||
],
|
||||
"parserOptions": {
|
||||
"project": [
|
||||
"./tsconfig.json"
|
||||
],
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"no-extend-native": "off",
|
||||
"react/jsx-props-no-spreading": "off",
|
||||
"jsx-a11y/label-has-associated-control": "off",
|
||||
"class-methods-use-this": "off",
|
||||
"max-classes-per-file": "off",
|
||||
"node/no-missing-import": "off",
|
||||
"node/no-unpublished-import": "off",
|
||||
"node/no-unsupported-features/es-syntax": ["error", {
|
||||
"ignores": ["modules"]
|
||||
}]
|
||||
},
|
||||
"env": {
|
||||
"webextensions": true
|
||||
},
|
||||
"settings": {
|
||||
"node": {
|
||||
"tryExtensions": [".tsx"] // append tsx to the list as well
|
||||
}
|
||||
}
|
||||
}
|
||||
42
eslint.config.mjs
Normal file
42
eslint.config.mjs
Normal file
@ -0,0 +1,42 @@
|
||||
import nodeConfig from '@abhijithvijayan/eslint-config/node';
|
||||
import tsConfig from '@abhijithvijayan/eslint-config/typescript';
|
||||
import reactConfig from '@abhijithvijayan/eslint-config/react';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
'node_modules/**',
|
||||
'extension/**',
|
||||
'*.js',
|
||||
'*.mjs',
|
||||
'vite.config.ts',
|
||||
],
|
||||
},
|
||||
...nodeConfig({
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
}),
|
||||
...tsConfig({
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
}),
|
||||
...reactConfig({
|
||||
files: ['**/*.tsx'],
|
||||
}),
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'@typescript-eslint/no-use-before-define': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
// Disable due to resolver issues in ESM
|
||||
'import-x/no-duplicates': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.tsx'],
|
||||
rules: {
|
||||
'react/jsx-props-no-spreading': 'off',
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'jsx-a11y/label-has-associated-control': 'off',
|
||||
},
|
||||
},
|
||||
];
|
||||
4187
package-lock.json
generated
4187
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@ -20,8 +20,8 @@
|
||||
"build:chrome": "cross-env TARGET_BROWSER=chrome vite build --config vite.config.ts",
|
||||
"build:firefox": "cross-env TARGET_BROWSER=firefox vite build --config vite.config.ts",
|
||||
"build": "npm run build:chrome && npm run build:firefox",
|
||||
"lint": "eslint . --ext .ts,.tsx",
|
||||
"lint:fix": "eslint . --ext .ts,.tsx --fix"
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"advanced-css-reset": "^2.1.3",
|
||||
@ -30,15 +30,28 @@
|
||||
"webextension-polyfill": "^0.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@abhijithvijayan/eslint-config": "^3.0.0",
|
||||
"@abhijithvijayan/tsconfig": "^1.5.1",
|
||||
"@types/node": "^25.0.3",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/webextension-polyfill": "^0.12.4",
|
||||
"@typescript-eslint/eslint-plugin": "^8.51.0",
|
||||
"@typescript-eslint/parser": "^8.51.0",
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
"autoprefixer": "^10.4.23",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-prettier": "^9.1.2",
|
||||
"eslint-plugin-import-x": "^4.16.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-n": "^17.23.1",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"globals": "^15.15.0",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.7.4",
|
||||
"sass": "^1.97.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.0",
|
||||
|
||||
@ -21,6 +21,9 @@ browser.runtime.onMessage.addListener(
|
||||
// Log when content script loads (if logging is enabled)
|
||||
getStorage(['enableLogging']).then(({enableLogging}) => {
|
||||
if (enableLogging) {
|
||||
console.log('[Web Extension Starter] Content script loaded on:', window.location.href);
|
||||
console.log(
|
||||
'[Web Extension Starter] Content script loaded on:',
|
||||
window.location.href
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,20 +1,19 @@
|
||||
import * as React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import type { FC } from "react";
|
||||
import { getStorage, setStorage } from "../utils/storage";
|
||||
import { Button } from "../components/Button/Button";
|
||||
import { Input } from "../components/Input/Input";
|
||||
import { Checkbox } from "../components/Checkbox/Checkbox";
|
||||
import { GitHubIcon } from "../components/icons/GitHubIcon";
|
||||
import styles from "./Options.module.scss";
|
||||
import {useEffect, useState} from 'react';
|
||||
import type {FC} from 'react';
|
||||
import {getStorage, setStorage} from '../utils/storage';
|
||||
import {Button} from '../components/Button/Button';
|
||||
import {Input} from '../components/Input/Input';
|
||||
import {Checkbox} from '../components/Checkbox/Checkbox';
|
||||
import {GitHubIcon} from '../components/icons/GitHubIcon';
|
||||
import styles from './Options.module.scss';
|
||||
|
||||
const Options: FC = () => {
|
||||
const [username, setUsername] = useState("");
|
||||
const [username, setUsername] = useState('');
|
||||
const [enableLogging, setEnableLogging] = useState(false);
|
||||
const [saved, setSaved] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
getStorage(["username", "enableLogging"]).then((result) => {
|
||||
getStorage(['username', 'enableLogging']).then((result) => {
|
||||
setUsername(result.username);
|
||||
setEnableLogging(result.enableLogging);
|
||||
});
|
||||
@ -22,7 +21,7 @@ const Options: FC = () => {
|
||||
|
||||
const handleSave = async (e: React.FormEvent): Promise<void> => {
|
||||
e.preventDefault();
|
||||
await setStorage({ username, enableLogging });
|
||||
await setStorage({username, enableLogging});
|
||||
setSaved(true);
|
||||
setTimeout(() => setSaved(false), 2000);
|
||||
};
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import {StrictMode} from 'react';
|
||||
import {createRoot} from 'react-dom/client';
|
||||
|
||||
import Options from './Options';
|
||||
|
||||
const container = document.getElementById('options-root');
|
||||
|
||||
if (!container) {
|
||||
throw new Error("Could not find root container to mount the app");
|
||||
throw new Error('Could not find root container to mount the app');
|
||||
}
|
||||
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = createRoot(container);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<Options />
|
||||
</React.StrictMode>
|
||||
);
|
||||
<StrictMode>
|
||||
<Options />
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import * as React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import type { FC } from "react";
|
||||
import browser, { Tabs } from "webextension-polyfill";
|
||||
import { getStorage } from "../utils/storage";
|
||||
import { TabInfo } from "./components/TabInfo/TabInfo";
|
||||
import { FooterActions } from "./components/FooterActions/FooterActions";
|
||||
import styles from "./Popup.module.scss";
|
||||
import {useEffect, useState} from 'react';
|
||||
import type {FC} from 'react';
|
||||
import browser, {Tabs} from 'webextension-polyfill';
|
||||
import {getStorage} from '../utils/storage';
|
||||
import {TabInfo} from './components/TabInfo/TabInfo';
|
||||
import {FooterActions} from './components/FooterActions/FooterActions';
|
||||
import styles from './Popup.module.scss';
|
||||
|
||||
function openWebPage(url: string): Promise<Tabs.Tab> {
|
||||
return browser.tabs.create({ url });
|
||||
return browser.tabs.create({url});
|
||||
}
|
||||
|
||||
interface TabData {
|
||||
@ -19,21 +18,21 @@ interface TabData {
|
||||
|
||||
const Popup: FC = () => {
|
||||
const [tabInfo, setTabInfo] = useState<TabData | null>(null);
|
||||
const [username, setUsername] = useState<string>("");
|
||||
const [username, setUsername] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
|
||||
browser.tabs.query({active: true, currentWindow: true}).then((tabs) => {
|
||||
const tab = tabs[0];
|
||||
if (tab) {
|
||||
setTabInfo({
|
||||
title: tab.title || "Unknown",
|
||||
url: tab.url || "Unknown",
|
||||
title: tab.title || 'Unknown',
|
||||
url: tab.url || 'Unknown',
|
||||
favIconUrl: tab.favIconUrl,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
getStorage(["username"]).then(({ username: storedUsername }) => {
|
||||
getStorage(['username']).then(({username: storedUsername}) => {
|
||||
setUsername(storedUsername);
|
||||
});
|
||||
}, []);
|
||||
@ -69,13 +68,15 @@ const Popup: FC = () => {
|
||||
|
||||
<FooterActions
|
||||
onSettings={(): Promise<Tabs.Tab> =>
|
||||
openWebPage("/Options/options.html")
|
||||
openWebPage('/Options/options.html')
|
||||
}
|
||||
onGitHub={(): Promise<Tabs.Tab> =>
|
||||
openWebPage("https://github.com/abhijithvijayan/web-extension-starter")
|
||||
openWebPage(
|
||||
'https://github.com/abhijithvijayan/web-extension-starter'
|
||||
)
|
||||
}
|
||||
onSupport={(): Promise<Tabs.Tab> =>
|
||||
openWebPage("https://www.buymeacoffee.com/abhijithvijayan")
|
||||
openWebPage('https://www.buymeacoffee.com/abhijithvijayan')
|
||||
}
|
||||
/>
|
||||
</section>
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import * as React from "react";
|
||||
import type { FC } from "react";
|
||||
import { Button } from "../../../components/Button/Button";
|
||||
import { SettingsIcon } from "../../../components/icons/SettingsIcon";
|
||||
import { GitHubIcon } from "../../../components/icons/GitHubIcon";
|
||||
import { HeartIcon } from "../../../components/icons/HeartIcon";
|
||||
import styles from "./FooterActions.module.scss";
|
||||
import type {FC} from 'react';
|
||||
import {Button} from '../../../components/Button/Button';
|
||||
import {SettingsIcon} from '../../../components/icons/SettingsIcon';
|
||||
import {GitHubIcon} from '../../../components/icons/GitHubIcon';
|
||||
import {HeartIcon} from '../../../components/icons/HeartIcon';
|
||||
import styles from './FooterActions.module.scss';
|
||||
|
||||
interface FooterActionsProps {
|
||||
onSettings: () => void;
|
||||
@ -16,36 +15,34 @@ export const FooterActions: FC<FooterActionsProps> = ({
|
||||
onSettings,
|
||||
onGitHub,
|
||||
onSupport,
|
||||
}) => {
|
||||
return (
|
||||
<div className={styles.footer}>
|
||||
<Button
|
||||
variant="settings"
|
||||
size="small"
|
||||
className={styles.button}
|
||||
onClick={onSettings}
|
||||
>
|
||||
<SettingsIcon />
|
||||
<span>Settings</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="github"
|
||||
size="small"
|
||||
className={styles.button}
|
||||
onClick={onGitHub}
|
||||
>
|
||||
<GitHubIcon />
|
||||
<span>GitHub</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="support"
|
||||
size="small"
|
||||
className={styles.button}
|
||||
onClick={onSupport}
|
||||
>
|
||||
<HeartIcon />
|
||||
<span>Support</span>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}) => (
|
||||
<div className={styles.footer}>
|
||||
<Button
|
||||
variant="settings"
|
||||
size="small"
|
||||
className={styles.button}
|
||||
onClick={onSettings}
|
||||
>
|
||||
<SettingsIcon />
|
||||
<span>Settings</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="github"
|
||||
size="small"
|
||||
className={styles.button}
|
||||
onClick={onGitHub}
|
||||
>
|
||||
<GitHubIcon />
|
||||
<span>GitHub</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="support"
|
||||
size="small"
|
||||
className={styles.button}
|
||||
onClick={onSupport}
|
||||
>
|
||||
<HeartIcon />
|
||||
<span>Support</span>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import * as React from "react";
|
||||
import type { FC } from "react";
|
||||
import { Card } from "../../../components/Card/Card";
|
||||
import { Button } from "../../../components/Button/Button";
|
||||
import styles from "./TabInfo.module.scss";
|
||||
import type {FC} from 'react';
|
||||
import {Card} from '../../../components/Card/Card';
|
||||
import {Button} from '../../../components/Button/Button';
|
||||
import styles from './TabInfo.module.scss';
|
||||
|
||||
interface TabInfoProps {
|
||||
title: string;
|
||||
@ -17,9 +16,7 @@ export const TabInfo: FC<TabInfoProps> = ({
|
||||
favIconUrl,
|
||||
onReload,
|
||||
}) => {
|
||||
const getInitial = (text: string): string => {
|
||||
return text.charAt(0).toUpperCase();
|
||||
};
|
||||
const getInitial = (text: string): string => text.charAt(0).toUpperCase();
|
||||
|
||||
return (
|
||||
<Card title="Current Tab">
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import {StrictMode} from 'react';
|
||||
import {createRoot} from 'react-dom/client';
|
||||
|
||||
import Popup from './Popup';
|
||||
|
||||
const container = document.getElementById('popup-root');
|
||||
|
||||
if (!container) {
|
||||
throw new Error("Could not find root container to mount the app");
|
||||
throw new Error('Could not find root container to mount the app');
|
||||
}
|
||||
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = createRoot(container);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<Popup />
|
||||
</React.StrictMode>
|
||||
);
|
||||
<StrictMode>
|
||||
<Popup />
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
import * as React from "react";
|
||||
import type { FC, ReactNode, ButtonHTMLAttributes } from "react";
|
||||
import styles from "./Button.module.scss";
|
||||
import type {FC, ReactNode, ButtonHTMLAttributes} from 'react';
|
||||
import styles from './Button.module.scss';
|
||||
|
||||
type ButtonVariant = "primary" | "secondary" | "settings" | "github" | "support";
|
||||
type ButtonSize = "small" | "medium" | "large";
|
||||
type ButtonVariant =
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'settings'
|
||||
| 'github'
|
||||
| 'support';
|
||||
type ButtonSize = 'small' | 'medium' | 'large';
|
||||
|
||||
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: ButtonVariant;
|
||||
@ -13,8 +17,8 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
}
|
||||
|
||||
export const Button: FC<ButtonProps> = ({
|
||||
variant = "primary",
|
||||
size = "medium",
|
||||
variant = 'primary',
|
||||
size = 'medium',
|
||||
fullWidth = false,
|
||||
children,
|
||||
className,
|
||||
@ -28,7 +32,7 @@ export const Button: FC<ButtonProps> = ({
|
||||
className,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
.join(' ');
|
||||
|
||||
return (
|
||||
<button type="button" className={classNames} {...props}>
|
||||
|
||||
@ -1,27 +1,22 @@
|
||||
import * as React from "react";
|
||||
import type { FC, ReactNode } from "react";
|
||||
import styles from "./Card.module.scss";
|
||||
import type {FC, ReactNode} from 'react';
|
||||
import styles from './Card.module.scss';
|
||||
|
||||
interface CardProps {
|
||||
title?: string;
|
||||
size?: "default" | "large";
|
||||
size?: 'default' | 'large';
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Card: FC<CardProps> = ({
|
||||
title,
|
||||
size = "default",
|
||||
size = 'default',
|
||||
children,
|
||||
className,
|
||||
}) => {
|
||||
const classNames = [
|
||||
styles.card,
|
||||
size === "large" && styles.large,
|
||||
className,
|
||||
]
|
||||
const classNames = [styles.card, size === 'large' && styles.large, className]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
.join(' ');
|
||||
|
||||
return (
|
||||
<div className={classNames}>
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import * as React from "react";
|
||||
import type { FC, InputHTMLAttributes } from "react";
|
||||
import styles from "./Checkbox.module.scss";
|
||||
import type {FC, InputHTMLAttributes} from 'react';
|
||||
import styles from './Checkbox.module.scss';
|
||||
|
||||
interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type"> {
|
||||
interface CheckboxProps extends Omit<
|
||||
InputHTMLAttributes<HTMLInputElement>,
|
||||
'type'
|
||||
> {
|
||||
label: string;
|
||||
}
|
||||
|
||||
@ -12,27 +14,16 @@ export const Checkbox: FC<CheckboxProps> = ({
|
||||
checked,
|
||||
onChange,
|
||||
...props
|
||||
}) => {
|
||||
const handleWrapperClick = (e: React.MouseEvent<HTMLLabelElement>): void => {
|
||||
if ((e.target as HTMLElement).tagName !== "INPUT") {
|
||||
const input = e.currentTarget.querySelector("input");
|
||||
if (input) {
|
||||
input.click();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<label htmlFor={id} className={styles.wrapper} onClick={handleWrapperClick}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={id}
|
||||
className={styles.checkbox}
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
{...props}
|
||||
/>
|
||||
<span className={styles.text}>{label}</span>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
}) => (
|
||||
<label htmlFor={id} className={styles.wrapper}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={id}
|
||||
className={styles.checkbox}
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
{...props}
|
||||
/>
|
||||
<span className={styles.text}>{label}</span>
|
||||
</label>
|
||||
);
|
||||
|
||||
@ -1,24 +1,21 @@
|
||||
import * as React from "react";
|
||||
import type { FC, InputHTMLAttributes } from "react";
|
||||
import styles from "./Input.module.scss";
|
||||
import type {FC, InputHTMLAttributes} from 'react';
|
||||
import styles from './Input.module.scss';
|
||||
|
||||
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export const Input: FC<InputProps> = ({ label, id, className, ...props }) => {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
{label && (
|
||||
<label htmlFor={id} className={styles.label}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<input
|
||||
id={id}
|
||||
className={`${styles.input} ${className || ""}`.trim()}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export const Input: FC<InputProps> = ({label, id, className, ...props}) => (
|
||||
<div className={styles.wrapper}>
|
||||
{label && (
|
||||
<label htmlFor={id} className={styles.label}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<input
|
||||
id={id}
|
||||
className={`${styles.input} ${className || ''}`.trim()}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import * as React from "react";
|
||||
import type { FC } from "react";
|
||||
import type {FC} from 'react';
|
||||
|
||||
interface IconProps {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export const GitHubIcon: FC<IconProps> = ({ size = 14 }) => (
|
||||
export const GitHubIcon: FC<IconProps> = ({size = 14}) => (
|
||||
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
|
||||
</svg>
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import * as React from "react";
|
||||
import type { FC } from "react";
|
||||
import type {FC} from 'react';
|
||||
|
||||
interface IconProps {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export const HeartIcon: FC<IconProps> = ({ size = 14 }) => (
|
||||
export const HeartIcon: FC<IconProps> = ({size = 14}) => (
|
||||
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
|
||||
</svg>
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import * as React from "react";
|
||||
import type { FC } from "react";
|
||||
import type {FC} from 'react';
|
||||
|
||||
interface IconProps {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export const SettingsIcon: FC<IconProps> = ({ size = 14 }) => (
|
||||
export const SettingsIcon: FC<IconProps> = ({size = 14}) => (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
|
||||
6
source/globals.d.ts
vendored
6
source/globals.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
// https://www.typescriptlang.org/tsconfig/#noUncheckedSideEffectImports
|
||||
declare module "*.scss" {
|
||||
const content: { [className: string]: string };
|
||||
declare module '*.scss' {
|
||||
const content: {[className: string]: string};
|
||||
export default content;
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,10 @@
|
||||
/* Bundler-specific settings */
|
||||
"moduleResolution": "bundler",
|
||||
"isolatedModules": true,
|
||||
"declaration": false
|
||||
"declaration": false,
|
||||
|
||||
/* React JSX transform */
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"source"
|
||||
|
||||
213
vite.config.ts
213
vite.config.ts
@ -1,134 +1,135 @@
|
||||
import { defineConfig } from "vite";
|
||||
import path from "node:path";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import process from "node:process";
|
||||
import zipPack from "vite-plugin-zip-pack";
|
||||
import { defineConfig } from 'vite';
|
||||
import path from 'node:path';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import process from 'node:process';
|
||||
import zipPack from 'vite-plugin-zip-pack';
|
||||
import checker from 'vite-plugin-checker';
|
||||
import clean from 'vite-plugin-clean';
|
||||
import WextManifest from "vite-plugin-wext-manifest";
|
||||
import WextManifest from 'vite-plugin-wext-manifest';
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const isDevelopment = mode !== "production";
|
||||
const sourcePath = path.resolve(__dirname, "source");
|
||||
const destPath = path.resolve(__dirname, "extension");
|
||||
const targetBrowser = process.env.TARGET_BROWSER || "chrome";
|
||||
const isDevelopment = mode !== 'production';
|
||||
const sourcePath = path.resolve(__dirname, 'source');
|
||||
const destPath = path.resolve(__dirname, 'extension');
|
||||
const targetBrowser = process.env.TARGET_BROWSER || 'chrome';
|
||||
|
||||
const getOutDir = () => path.resolve(destPath, targetBrowser);
|
||||
const getOutDir = () => path.resolve(destPath, targetBrowser);
|
||||
|
||||
const getExtensionZipFileName = () => {
|
||||
switch (targetBrowser) {
|
||||
case 'opera': {
|
||||
return `${targetBrowser}.crx`;
|
||||
}
|
||||
const getExtensionZipFileName = () => {
|
||||
switch (targetBrowser) {
|
||||
case 'opera': {
|
||||
return `${targetBrowser}.crx`;
|
||||
}
|
||||
|
||||
case 'firefox': {
|
||||
return `${targetBrowser}.xpi`;
|
||||
}
|
||||
case 'firefox': {
|
||||
return `${targetBrowser}.xpi`;
|
||||
}
|
||||
|
||||
default: {
|
||||
return `${targetBrowser}.zip`;
|
||||
}
|
||||
}
|
||||
};
|
||||
default: {
|
||||
return `${targetBrowser}.zip`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
root: sourcePath,
|
||||
return {
|
||||
root: sourcePath,
|
||||
|
||||
publicDir: path.resolve(sourcePath, "public"),
|
||||
publicDir: path.resolve(sourcePath, 'public'),
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(sourcePath),
|
||||
"~": path.resolve(__dirname, "node_modules"),
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(sourcePath),
|
||||
'~': path.resolve(__dirname, 'node_modules'),
|
||||
},
|
||||
},
|
||||
|
||||
define: {
|
||||
__DEV__: isDevelopment,
|
||||
__TARGET_BROWSER__: JSON.stringify(targetBrowser),
|
||||
},
|
||||
define: {
|
||||
__DEV__: isDevelopment,
|
||||
__TARGET_BROWSER__: JSON.stringify(targetBrowser),
|
||||
},
|
||||
|
||||
plugins: [
|
||||
react(),
|
||||
plugins: [
|
||||
react(),
|
||||
|
||||
// delete previous built compressed file
|
||||
clean({
|
||||
targetFiles: [
|
||||
path.resolve(destPath, getExtensionZipFileName())
|
||||
],
|
||||
}),
|
||||
// delete previous built compressed file
|
||||
clean({
|
||||
targetFiles: [path.resolve(destPath, getExtensionZipFileName())],
|
||||
}),
|
||||
|
||||
// Run typescript checker in worker thread
|
||||
checker({
|
||||
typescript: {
|
||||
tsconfigPath: './tsconfig.json'
|
||||
},
|
||||
}),
|
||||
// Run typescript checker in worker thread
|
||||
checker({
|
||||
typescript: {
|
||||
tsconfigPath: './tsconfig.json',
|
||||
},
|
||||
}),
|
||||
|
||||
// Generate manifest.json for the browser
|
||||
WextManifest({
|
||||
manifestPath: "manifest.json",
|
||||
usePackageJSONVersion: true,
|
||||
}),
|
||||
// Generate manifest.json for the browser
|
||||
WextManifest({
|
||||
manifestPath: 'manifest.json',
|
||||
usePackageJSONVersion: true,
|
||||
}),
|
||||
|
||||
!isDevelopment &&
|
||||
zipPack({
|
||||
inDir: getOutDir(),
|
||||
outDir: destPath,
|
||||
outFileName: getExtensionZipFileName(),
|
||||
enableLogging: true,
|
||||
}),
|
||||
],
|
||||
!isDevelopment &&
|
||||
zipPack({
|
||||
inDir: getOutDir(),
|
||||
outDir: destPath,
|
||||
outFileName: getExtensionZipFileName(),
|
||||
enableLogging: true,
|
||||
}),
|
||||
],
|
||||
|
||||
build: {
|
||||
outDir: getOutDir(),
|
||||
build: {
|
||||
outDir: getOutDir(),
|
||||
|
||||
emptyOutDir: !isDevelopment,
|
||||
emptyOutDir: !isDevelopment,
|
||||
|
||||
sourcemap: isDevelopment ? "inline" : false,
|
||||
sourcemap: isDevelopment ? 'inline' : false,
|
||||
|
||||
minify: mode === "production",
|
||||
minify: mode === 'production',
|
||||
|
||||
rollupOptions: {
|
||||
input: {
|
||||
// For UI pages, use the HTML file as the entry.
|
||||
// Vite will find the <script> tag inside and bundle it.
|
||||
popup: path.resolve(sourcePath, 'Popup/popup.html'),
|
||||
options: path.resolve(sourcePath, 'Options/options.html'),
|
||||
// For script-only parts, use the TS file directly.
|
||||
background: path.resolve(sourcePath, 'Background/index.ts'),
|
||||
contentScript: path.resolve(sourcePath, 'ContentScript/index.ts'),
|
||||
},
|
||||
rollupOptions: {
|
||||
input: {
|
||||
// For UI pages, use the HTML file as the entry.
|
||||
// Vite will find the <script> tag inside and bundle it.
|
||||
popup: path.resolve(sourcePath, 'Popup/popup.html'),
|
||||
options: path.resolve(sourcePath, 'Options/options.html'),
|
||||
// For script-only parts, use the TS file directly.
|
||||
background: path.resolve(sourcePath, 'Background/index.ts'),
|
||||
contentScript: path.resolve(sourcePath, 'ContentScript/index.ts'),
|
||||
},
|
||||
|
||||
output: {
|
||||
// For main entry scripts (background, contentScript, etc.)
|
||||
entryFileNames: "assets/js/[name].bundle.js",
|
||||
output: {
|
||||
// For main entry scripts (background, contentScript, etc.)
|
||||
entryFileNames: 'assets/js/[name].bundle.js',
|
||||
|
||||
// For other assets like CSS
|
||||
assetFileNames: (assetInfo) => {
|
||||
if (!!assetInfo.name && /\.(css|s[ac]ss|less)$/.test(assetInfo.name)) {
|
||||
return "assets/css/[name]-[hash].css";
|
||||
}
|
||||
// For other assets like CSS
|
||||
assetFileNames: (assetInfo) => {
|
||||
if (
|
||||
!!assetInfo.name &&
|
||||
/\.(css|s[ac]ss|less)$/.test(assetInfo.name)
|
||||
) {
|
||||
return 'assets/css/[name]-[hash].css';
|
||||
}
|
||||
|
||||
// For other assets like fonts or images
|
||||
return "assets/[name]-[hash].[ext]";
|
||||
},
|
||||
// For other assets like fonts or images
|
||||
return 'assets/[name]-[hash].[ext]';
|
||||
},
|
||||
|
||||
// For code-split chunks (if any)
|
||||
chunkFileNames: "assets/js/[name]-[hash].chunk.js",
|
||||
},
|
||||
},
|
||||
// For code-split chunks (if any)
|
||||
chunkFileNames: 'assets/js/[name]-[hash].chunk.js',
|
||||
},
|
||||
},
|
||||
|
||||
terserOptions: {
|
||||
mangle: true,
|
||||
compress: {
|
||||
drop_console: true,
|
||||
drop_debugger: true,
|
||||
},
|
||||
format: {
|
||||
comments: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
terserOptions: {
|
||||
mangle: true,
|
||||
compress: {
|
||||
drop_console: true,
|
||||
drop_debugger: true,
|
||||
},
|
||||
format: {
|
||||
comments: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user