mirror of
https://github.com/JosunLP/BrowserExtensionTemplate.git
synced 2025-10-14 08:00:11 +00:00
feat: Implement ErrorBoundary class for global error handling
feat: Refactor Session class to use LocalStorageService for session management feat: Enhance BasicButton component with configuration options and event handling style: Update SASS variables to CSS custom properties for better theming support style: Modify app.sass to include Bootstrap with legacy import chore: Update settings.ts to handle session initialization and error notifications fix: Improve buttonType definition for better readability chore: Refactor parse.ts for cleaner file handling and keyword replacement chore: Enhance syncConfig.ts with TypeScript interfaces and async file operations chore: Update v2.ts to modify manifest.json for compatibility with manifest version 2 chore: Revise tsconfig.json for stricter type checking and improved module resolution chore: Refactor vite.config.ts for better build configuration and asset management
This commit is contained in:
parent
8f99895c88
commit
482151f980
24 changed files with 1321 additions and 418 deletions
31
.eslintrc.json
Normal file
31
.eslintrc.json
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2022": true,
|
||||
"webextensions": true
|
||||
},
|
||||
"extends": ["eslint:recommended", "@typescript-eslint/recommended"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/explicit-function-return-type": "warn",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"@typescript-eslint/prefer-const": "error",
|
||||
"@typescript-eslint/no-var-requires": "error",
|
||||
"prefer-const": "error",
|
||||
"no-var": "error",
|
||||
"no-console": "warn"
|
||||
},
|
||||
"ignorePatterns": ["dist/", "node_modules/", "*.js"]
|
||||
}
|
8
.prettierignore
Normal file
8
.prettierignore
Normal file
|
@ -0,0 +1,8 @@
|
|||
node_modules/
|
||||
dist/
|
||||
*.log
|
||||
.DS_Store
|
||||
.vscode/
|
||||
.idea/
|
||||
*.sass
|
||||
*.scss
|
19
.prettierrc.json
Normal file
19
.prettierrc.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"semi": true,
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "auto",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.json",
|
||||
"options": {
|
||||
"parser": "json"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
215
README.md
215
README.md
|
@ -4,35 +4,222 @@
|
|||
[](https://github.com/JosunLP/BrowserExtensionTemplate/network)
|
||||
[](https://github.com/JosunLP/BrowserExtensionTemplate/stargazers)
|
||||
[](https://github.com/JosunLP/BrowserExtensionTemplate)
|
||||
[](https://twitter.com/intent/tweet?text=Look+what+i+found+on+GitHub+%23Developer%2C+%23SoftwareDeveloper%3A&url=https%3A%2F%2Fgithub.com%2FJosunLP%2FBrowserExtensionTemplate)
|
||||
[](https://www.codefactor.io/repository/github/josunlp/browserextensiontemplate)
|
||||
[](https://snyk.io/test/github/JosunLP/BrowserExtensionTemplate)
|
||||
|
||||
## Description
|
||||
|
||||
A basic template based on SASS and TypeScript to create browser extensions without directly relying on a larger framework.
|
||||
A modern, production-ready template for building browser extensions using TypeScript, SASS, and Vite. This template provides a solid foundation with best practices, type safety, and modern development tools.
|
||||
|
||||
## Features
|
||||
|
||||
- 🚀 **Modern Tech Stack**: TypeScript, SASS, Vite, Bootstrap
|
||||
- 🛡️ **Type Safety**: Strict TypeScript configuration with comprehensive error checking
|
||||
- 🔧 **Development Tools**: ESLint, Prettier, automated workflows
|
||||
- 🎯 **Cross-Browser**: Supports both Chrome (Manifest v3) and Firefox (Manifest v2)
|
||||
- 📦 **Component System**: Reusable UI components with type safety
|
||||
- 💾 **Session Management**: Robust localStorage-based session handling
|
||||
- 🛠️ **Build System**: Optimized Vite configuration with code splitting
|
||||
- 🎨 **Modern CSS**: CSS Custom Properties with SASS preprocessing
|
||||
- 🔒 **Security**: Content Security Policy and secure coding practices
|
||||
- ⚡ **Error Handling**: Comprehensive error boundary system
|
||||
|
||||
## Installation
|
||||
|
||||
You can download the source code from [GitHub](https://github.com/JosunLP/BrowserExtensionTemplate). Just copy it in your project and run `npm install` to install the dependencies.
|
||||
The basic configuration, wich will sync with `npm run sync` with the `package.json` file and the `manifest.json` file, is in `app.config.json`.
|
||||
Alternatively, you can fork the project and run `npm install` in the forked project.
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
git clone https://github.com/JosunLP/BrowserExtensionTemplate.git
|
||||
cd BrowserExtensionTemplate
|
||||
npm install
|
||||
```
|
||||
|
||||
### Development Setup
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Start development mode with auto-rebuild
|
||||
npm run dev
|
||||
|
||||
# Type checking
|
||||
npm run type-check
|
||||
|
||||
# Linting and formatting
|
||||
npm run validate
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Your sourcecode can be written in the `src` folder. The `public` folder contains static files like images, html and the manifest.json.
|
||||
With the `npm run deploy-v3` command you can deploy the extension to the dist folder, ready to be published to the chrome web store.
|
||||
With the `npm run deploy-v2` command you can deploy the extension to the dist folder, ready to be published to the firefox web store.
|
||||
This is necessary because the firefox web store needs the `manifest.json` file to be present in the version v2.
|
||||
### Project Structure
|
||||
|
||||
## License
|
||||
```bash
|
||||
src/
|
||||
├── classes/ # Core classes (Session, ErrorBoundary)
|
||||
├── components/ # Reusable UI components
|
||||
├── sass/ # SASS styles with CSS custom properties
|
||||
├── types/ # TypeScript type definitions
|
||||
├── app.ts # Popup entry point
|
||||
├── settings.ts # Options page entry point
|
||||
└── background.ts # Background service worker
|
||||
|
||||
This project is licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
||||
public/
|
||||
├── icons/ # Extension icons
|
||||
├── manifest.json # Extension manifest
|
||||
├── popup.html # Popup HTML template
|
||||
└── options.html # Options page HTML template
|
||||
|
||||
tools/ # Build and automation scripts
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
The main configuration is in `app.config.json`. This file is automatically synchronized with `package.json` and `manifest.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"AppData": {
|
||||
"id": "your_extension_id",
|
||||
"name": "Your Extension Name",
|
||||
"version": "1.0.0",
|
||||
"description": "Your extension description"
|
||||
},
|
||||
"htmlTemplatePairs": [
|
||||
{
|
||||
"key": "{{PLACEHOLDER}}",
|
||||
"value": "Replacement Value"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Build Commands
|
||||
|
||||
```bash
|
||||
# Development
|
||||
npm run dev # Start development with watch mode
|
||||
npm run sync # Sync configuration files
|
||||
|
||||
# Production
|
||||
npm run deploy-v3 # Build for Chrome (Manifest v3)
|
||||
npm run deploy-v2 # Build for Firefox (Manifest v2)
|
||||
|
||||
# Quality Assurance
|
||||
npm run validate # Type check + lint
|
||||
npm run lint # ESLint with auto-fix
|
||||
npm run format # Prettier formatting
|
||||
|
||||
# Utilities
|
||||
npm run clean # Clean dist folder
|
||||
npm run build-tooling # Compile TypeScript tools
|
||||
```
|
||||
|
||||
### Development Workflow
|
||||
|
||||
1. **Configure your extension** in `app.config.json`
|
||||
2. **Run sync** to update all config files: `npm run sync`
|
||||
3. **Start development**: `npm run dev`
|
||||
4. **Write your code** in the `src/` directory
|
||||
5. **Build for production**: `npm run deploy-v3` or `npm run deploy-v2`
|
||||
6. **Load the extension** from the `dist/` folder in your browser
|
||||
|
||||
### Session Management
|
||||
|
||||
The template includes a robust session management system:
|
||||
|
||||
```typescript
|
||||
import { Session } from './classes/session';
|
||||
|
||||
// Get session instance (async)
|
||||
const session = await Session.getInstance();
|
||||
|
||||
// Save data
|
||||
session.contentTest = 'New value';
|
||||
await session.save();
|
||||
|
||||
// Reset session
|
||||
await Session.reset();
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
Built-in error boundary system:
|
||||
|
||||
```typescript
|
||||
import { ErrorBoundary } from './classes/errorBoundary';
|
||||
|
||||
const errorBoundary = ErrorBoundary.getInstance();
|
||||
|
||||
// Wrap async functions
|
||||
const safeAsyncFunction = errorBoundary.wrapAsync(asyncFunction);
|
||||
|
||||
// Add custom error handlers
|
||||
errorBoundary.addErrorHandler(error => {
|
||||
console.log('Custom error handling:', error);
|
||||
});
|
||||
```
|
||||
|
||||
### Component System
|
||||
|
||||
Type-safe, reusable components:
|
||||
|
||||
```typescript
|
||||
import { BasicButton } from './components/button';
|
||||
|
||||
// Create button
|
||||
const button = new BasicButton('primary', 'Click me', 'my-button');
|
||||
|
||||
// Render as HTML string
|
||||
const htmlString = button.render();
|
||||
|
||||
// Or create as DOM element
|
||||
const buttonElement = button.createElement();
|
||||
```
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
- **Chrome**: Manifest v3 (recommended)
|
||||
- **Firefox**: Manifest v2 (automatically converted)
|
||||
- **Edge**: Manifest v3 compatible
|
||||
|
||||
## Contributing
|
||||
|
||||
This project is open source. Feel free to fork and contribute!
|
||||
1. Fork the repository
|
||||
2. Create a feature branch: `git checkout -b feature/amazing-feature`
|
||||
3. Make your changes and ensure tests pass: `npm run validate`
|
||||
4. Commit your changes: `git commit -m 'Add amazing feature'`
|
||||
5. Push to the branch: `git push origin feature/amazing-feature`
|
||||
6. Open a Pull Request
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
- Follow TypeScript best practices
|
||||
- Use meaningful variable and function names
|
||||
- Add proper error handling
|
||||
- Write self-documenting code
|
||||
- Follow the established project structure
|
||||
- Run `npm run validate` before committing
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT License](https://opensource.org/licenses/MIT).
|
||||
|
||||
## Author
|
||||
|
||||
Jonas Pfalzgraf
|
||||
**_Jonas Pfalzgraf_**
|
||||
|
||||
- Email: <info@josunlp.de>
|
||||
- GitHub: [@JosunLP](https://github.com/JosunLP)
|
||||
|
||||
## Changelog
|
||||
|
||||
### v0.0.1 (Current)
|
||||
|
||||
- ✨ Modern TypeScript setup with strict type checking
|
||||
- 🛡️ Comprehensive error handling system
|
||||
- 🎨 CSS Custom Properties with SASS
|
||||
- 🔧 ESLint and Prettier configuration
|
||||
- 📦 Optimized Vite build system
|
||||
- 🚀 Cross-browser compatibility (Chrome/Firefox)
|
||||
- 💾 Robust session management
|
||||
- 🎯 Component-based architecture
|
||||
|
|
55
eslint.config.js
Normal file
55
eslint.config.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
import js from '@eslint/js';
|
||||
import tsPlugin from '@typescript-eslint/eslint-plugin';
|
||||
import tsParser from '@typescript-eslint/parser';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/', 'node_modules/', '**/*.js'],
|
||||
},
|
||||
js.configs.recommended,
|
||||
{
|
||||
files: ['src/**/*.ts'],
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
globals: {
|
||||
chrome: 'readonly',
|
||||
browser: 'readonly',
|
||||
console: 'readonly',
|
||||
document: 'readonly',
|
||||
window: 'readonly',
|
||||
localStorage: 'readonly',
|
||||
sessionStorage: 'readonly',
|
||||
HTMLElement: 'readonly',
|
||||
HTMLDivElement: 'readonly',
|
||||
HTMLButtonElement: 'readonly',
|
||||
HTMLInputElement: 'readonly',
|
||||
setTimeout: 'readonly',
|
||||
clearTimeout: 'readonly',
|
||||
crypto: 'readonly',
|
||||
Error: 'readonly',
|
||||
JSON: 'readonly',
|
||||
Date: 'readonly',
|
||||
String: 'readonly',
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
'@typescript-eslint': tsPlugin,
|
||||
},
|
||||
rules: {
|
||||
...tsPlugin.configs.recommended.rules,
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'prefer-const': 'error',
|
||||
'no-var': 'error',
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
];
|
25
package.json
25
package.json
|
@ -2,20 +2,39 @@
|
|||
"name": "browser_extension_template",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"deploy-v3": "npx rimraf ./dist/ && npm run build-tooling && npm run sync && npm run build && node ./tools/parse.js",
|
||||
"deploy-v3": "npm run clean && npm run build-tooling && npm run sync && npm run build && npm run parse",
|
||||
"deploy-v2": "npm run deploy-v3 && node ./tools/v2.js",
|
||||
"build": "vite build",
|
||||
"build-tooling": "tsc --project ./tooling.tsconfig.json",
|
||||
"watch": "vite build --watch",
|
||||
"sync": "npm run build-tooling && node ./tools/syncConfig.js"
|
||||
"sync": "npm run build-tooling && node ./tools/syncConfig.js",
|
||||
"parse": "node ./tools/parse.js",
|
||||
"clean": "npx rimraf ./dist/",
|
||||
"dev": "npm run sync && npm run watch",
|
||||
"lint": "eslint src/**/*.ts --fix",
|
||||
"format": "prettier --write src/**/*.{ts,json}",
|
||||
"format:ts": "prettier --write src/**/*.ts",
|
||||
"format:json": "prettier --write src/**/*.json",
|
||||
"type-check": "tsc --noEmit",
|
||||
"validate": "npm run type-check && npm run lint",
|
||||
"prepare": "npm run validate && npm run build-tooling"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "^9.30.1",
|
||||
"@types/chrome": "^0.0.268",
|
||||
"@types/node": "^20.13.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.36.0",
|
||||
"@typescript-eslint/parser": "^8.36.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||
"eslint": "^9.30.1",
|
||||
"prettier": "^3.6.2",
|
||||
"rimraf": "^5.0.10",
|
||||
"sass": "^1.77.4",
|
||||
"vite": "^5.2.12",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^7.0.4",
|
||||
"vite-tsconfig-paths": "^4.3.2"
|
||||
},
|
||||
"browserslist": [
|
||||
|
|
|
@ -15,12 +15,27 @@
|
|||
"default_popup": "popup.html"
|
||||
},
|
||||
"options_ui": {
|
||||
"page": "options.html"
|
||||
"page": "options.html",
|
||||
"open_in_tab": false
|
||||
},
|
||||
"permissions": [
|
||||
"storage",
|
||||
"notifications"
|
||||
],
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self'; object-src 'self'; style-src 'self' 'unsafe-inline';"
|
||||
},
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": [
|
||||
"icons/*.png"
|
||||
],
|
||||
"matches": [
|
||||
"<all_urls>"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,22 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="./favicon.ico">
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<link rel="icon" href="./favicon.ico" />
|
||||
<title>{{BET}} Options</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but {{BET}} doesn't work properly without JavaScript enabled. Please enable it to
|
||||
continue.</strong>
|
||||
<strong
|
||||
>We're sorry but {{BET}} doesn't work properly without JavaScript enabled. Please enable it
|
||||
to continue.</strong
|
||||
>
|
||||
</noscript>
|
||||
<div id="app">
|
||||
<img src="./icons/icon128.png" class="logo" />
|
||||
<img src="./icons/icon128.png" alt="Logo" class="logo" />
|
||||
<h1>Settings</h1>
|
||||
<div id="settings">
|
||||
</div>
|
||||
<div id="settings"></div>
|
||||
</div>
|
||||
<footer>
|
||||
<script type="module" src="./settings.js"></script>
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="./favicon.ico">
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<link rel="icon" href="./favicon.ico" />
|
||||
<title>{{BET}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but {{BET}} doesn't work properly without JavaScript enabled. Please enable it to
|
||||
continue.</strong>
|
||||
<strong
|
||||
>We're sorry but {{BET}} doesn't work properly without JavaScript enabled. Please enable it
|
||||
to continue.</strong
|
||||
>
|
||||
</noscript>
|
||||
<div id="app">
|
||||
<img src="./icons/icon128.png" class="logo" />
|
||||
<img src="./icons/icon128.png" alt="Logo" class="logo" />
|
||||
<h1>{{BET}}</h1>
|
||||
<div id="content">
|
||||
</div>
|
||||
<div id="content"></div>
|
||||
</div>
|
||||
<footer>
|
||||
<script type="module" src="./app.js"></script>
|
||||
|
|
67
src/app.ts
67
src/app.ts
|
@ -1,30 +1,59 @@
|
|||
import { Session } from "./classes/session"
|
||||
import "./sass/app.sass"
|
||||
import { Session } from './classes/session';
|
||||
import './sass/app.sass';
|
||||
|
||||
class App {
|
||||
|
||||
private static contentEntry: string = "content"
|
||||
private static readonly CONTENT_ENTRY = 'content';
|
||||
private session: Session | null = null;
|
||||
|
||||
constructor() {
|
||||
this.drawData()
|
||||
this.main()
|
||||
this.init();
|
||||
}
|
||||
|
||||
async main(): Promise<void> {
|
||||
console.log("Hello World")
|
||||
private async init(): Promise<void> {
|
||||
try {
|
||||
this.session = await Session.getInstance();
|
||||
await this.drawData();
|
||||
await this.main();
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize app:', error);
|
||||
this.handleError('Failed to initialize application');
|
||||
}
|
||||
}
|
||||
|
||||
async drawData(): Promise<void> {
|
||||
const session = Session.getInstance()
|
||||
const contentRoot = <HTMLDivElement>document.getElementById(App.contentEntry)
|
||||
const body = document.createElement("div")
|
||||
const title = document.createElement("h1")
|
||||
const text = document.createElement("p")
|
||||
title.innerText = "Hello World"
|
||||
text.innerText = session.contentTest
|
||||
body.appendChild(title)
|
||||
body.appendChild(text)
|
||||
contentRoot.appendChild(body)
|
||||
private async main(): Promise<void> {
|
||||
console.log('Hello World');
|
||||
}
|
||||
|
||||
private async drawData(): Promise<void> {
|
||||
if (!this.session) {
|
||||
throw new Error('Session not initialized');
|
||||
}
|
||||
|
||||
const contentRoot = document.getElementById(App.CONTENT_ENTRY) as HTMLDivElement | null;
|
||||
if (!contentRoot) {
|
||||
throw new Error(`Element with id '${App.CONTENT_ENTRY}' not found`);
|
||||
}
|
||||
|
||||
const body = document.createElement('div');
|
||||
body.className = 'app-content';
|
||||
|
||||
const title = document.createElement('h1');
|
||||
title.innerText = 'Hello World';
|
||||
|
||||
const text = document.createElement('p');
|
||||
text.innerText = this.session.contentTest;
|
||||
|
||||
body.appendChild(title);
|
||||
body.appendChild(text);
|
||||
contentRoot.appendChild(body);
|
||||
}
|
||||
|
||||
private handleError(message: string): void {
|
||||
console.error(message);
|
||||
const contentRoot = document.getElementById(App.CONTENT_ENTRY);
|
||||
if (contentRoot) {
|
||||
contentRoot.innerHTML = `<div class="error-message">${message}</div>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,82 @@
|
|||
class Background {
|
||||
constructor() {
|
||||
this.main();
|
||||
interface ExtensionMessage {
|
||||
type: string;
|
||||
payload?: unknown;
|
||||
}
|
||||
|
||||
async main(): Promise<void> {}
|
||||
class Background {
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
private async init(): Promise<void> {
|
||||
try {
|
||||
await this.setupEventListeners();
|
||||
await this.main();
|
||||
console.log('Background service worker initialized');
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize background service worker:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private async setupEventListeners(): Promise<void> {
|
||||
// Install event
|
||||
chrome.runtime.onInstalled.addListener(details => {
|
||||
console.log('Extension installed:', details.reason);
|
||||
this.handleInstall(details.reason);
|
||||
});
|
||||
|
||||
// Message handling
|
||||
chrome.runtime.onMessage.addListener((message: ExtensionMessage, sender, sendResponse) => {
|
||||
this.handleMessage(message, sender)
|
||||
.then(response => sendResponse(response))
|
||||
.catch(error => {
|
||||
console.error('Error handling message:', error);
|
||||
sendResponse({ error: error.message });
|
||||
});
|
||||
return true; // Indicates we will send a response asynchronously
|
||||
});
|
||||
|
||||
// Startup event
|
||||
chrome.runtime.onStartup.addListener(() => {
|
||||
console.log('Extension started');
|
||||
});
|
||||
}
|
||||
|
||||
private async handleInstall(reason: string): Promise<void> {
|
||||
if (reason === 'install') {
|
||||
// First time installation
|
||||
console.log('Extension installed for the first time');
|
||||
} else if (reason === 'update') {
|
||||
// Extension updated
|
||||
console.log('Extension updated');
|
||||
}
|
||||
}
|
||||
|
||||
private async handleMessage(
|
||||
message: ExtensionMessage,
|
||||
sender: chrome.runtime.MessageSender
|
||||
): Promise<unknown> {
|
||||
console.log('Received message:', message, 'from:', sender);
|
||||
|
||||
switch (message.type) {
|
||||
case 'ping':
|
||||
return { type: 'pong', timestamp: Date.now() };
|
||||
|
||||
case 'getVersion':
|
||||
return {
|
||||
type: 'version',
|
||||
version: chrome.runtime.getManifest().version,
|
||||
};
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown message type: ${message.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async main(): Promise<void> {
|
||||
// Main background logic can be implemented here
|
||||
// This method is called after initialization
|
||||
}
|
||||
}
|
||||
|
||||
new Background();
|
||||
|
|
99
src/classes/errorBoundary.ts
Normal file
99
src/classes/errorBoundary.ts
Normal file
|
@ -0,0 +1,99 @@
|
|||
export class ErrorBoundary {
|
||||
private static instance: ErrorBoundary;
|
||||
private errorHandlers: Array<(error: Error) => void> = [];
|
||||
|
||||
private constructor() {
|
||||
this.setupGlobalErrorHandlers();
|
||||
}
|
||||
|
||||
public static getInstance(): ErrorBoundary {
|
||||
if (!ErrorBoundary.instance) {
|
||||
ErrorBoundary.instance = new ErrorBoundary();
|
||||
}
|
||||
return ErrorBoundary.instance;
|
||||
}
|
||||
|
||||
private setupGlobalErrorHandlers(): void {
|
||||
// Handle uncaught errors
|
||||
window.addEventListener('error', event => {
|
||||
this.handleError(new Error(event.message), {
|
||||
filename: event.filename,
|
||||
lineno: event.lineno,
|
||||
colno: event.colno,
|
||||
});
|
||||
});
|
||||
|
||||
// Handle unhandled promise rejections
|
||||
window.addEventListener('unhandledrejection', event => {
|
||||
this.handleError(
|
||||
event.reason instanceof Error ? event.reason : new Error(String(event.reason)),
|
||||
{ type: 'unhandledrejection' }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public addErrorHandler(handler: (error: Error) => void): void {
|
||||
this.errorHandlers.push(handler);
|
||||
}
|
||||
|
||||
public removeErrorHandler(handler: (error: Error) => void): void {
|
||||
const index = this.errorHandlers.indexOf(handler);
|
||||
if (index > -1) {
|
||||
this.errorHandlers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public handleError(error: Error, context?: Record<string, unknown>): void {
|
||||
console.error('Error caught by ErrorBoundary:', error, context);
|
||||
|
||||
// Call all registered error handlers
|
||||
this.errorHandlers.forEach(handler => {
|
||||
try {
|
||||
handler(error);
|
||||
} catch (handlerError) {
|
||||
console.error('Error in error handler:', handlerError);
|
||||
}
|
||||
});
|
||||
|
||||
// Send to background script if available
|
||||
if (chrome.runtime) {
|
||||
chrome.runtime
|
||||
.sendMessage({
|
||||
type: 'error',
|
||||
payload: {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
context,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
})
|
||||
.catch(() => {
|
||||
// Ignore errors when sending to background
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public wrapAsync<T extends unknown[], R>(
|
||||
fn: (...args: T) => Promise<R>
|
||||
): (...args: T) => Promise<R> {
|
||||
return async (...args: T): Promise<R> => {
|
||||
try {
|
||||
return await fn(...args);
|
||||
} catch (error) {
|
||||
this.handleError(error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public wrapSync<T extends unknown[], R>(fn: (...args: T) => R): (...args: T) => R {
|
||||
return (...args: T): R => {
|
||||
try {
|
||||
return fn(...args);
|
||||
} catch (error) {
|
||||
this.handleError(error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,55 +1,109 @@
|
|||
export class Session {
|
||||
|
||||
private static instance: Session;
|
||||
|
||||
private constructor() {
|
||||
interface SessionData {
|
||||
sessionId: string;
|
||||
contentTest: string;
|
||||
}
|
||||
|
||||
static getInstance() {
|
||||
if (!Session.instance && !Session.load()) {
|
||||
Session.instance = new Session();
|
||||
}
|
||||
if (!Session.instance && Session.load()) {
|
||||
Session.instance = <Session>Session.load();
|
||||
}
|
||||
Session.save();
|
||||
return Session.instance;
|
||||
interface StorageService {
|
||||
save(key: string, data: unknown): Promise<void>;
|
||||
load<T>(key: string): Promise<T | null>;
|
||||
remove(key: string): Promise<void>;
|
||||
}
|
||||
|
||||
public static save() {
|
||||
localStorage.setItem('session', JSON.stringify(this.instance));
|
||||
class LocalStorageService implements StorageService {
|
||||
async save(key: string, data: unknown): Promise<void> {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(data));
|
||||
} catch (error) {
|
||||
console.error('Failed to save to localStorage:', error);
|
||||
throw new Error('Storage operation failed');
|
||||
}
|
||||
}
|
||||
|
||||
public static load(): Session | null {
|
||||
const session = localStorage.getItem('session');
|
||||
if (session) {
|
||||
const obj = <Session>JSON.parse(session);
|
||||
const result = new Session();
|
||||
result.contentTest = obj.contentTest;
|
||||
return result;
|
||||
}
|
||||
async load<T>(key: string): Promise<T | null> {
|
||||
try {
|
||||
const item = localStorage.getItem(key);
|
||||
return item ? (JSON.parse(item) as T) : null;
|
||||
} catch (error) {
|
||||
console.error('Failed to load from localStorage:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static reloadSession() {
|
||||
const session = localStorage.getItem('session');
|
||||
if (session) {
|
||||
const obj = <Session>JSON.parse(session);
|
||||
const result = new Session();
|
||||
result.contentTest = obj.contentTest;
|
||||
Session.instance = result;
|
||||
async remove(key: string): Promise<void> {
|
||||
try {
|
||||
localStorage.removeItem(key);
|
||||
} catch (error) {
|
||||
console.error('Failed to remove from localStorage:', error);
|
||||
throw new Error('Storage operation failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static resetSession() {
|
||||
localStorage.removeItem('session');
|
||||
sessionStorage.removeItem('session');
|
||||
this.instance = new Session();
|
||||
Session.save();
|
||||
location.reload();
|
||||
export class Session implements SessionData {
|
||||
private static instance: Session | null = null;
|
||||
private static readonly STORAGE_KEY = 'browser_extension_session';
|
||||
private static readonly storageService: StorageService = new LocalStorageService();
|
||||
|
||||
public readonly sessionId: string;
|
||||
public contentTest: string;
|
||||
|
||||
private constructor(data?: Partial<SessionData>) {
|
||||
this.sessionId = data?.sessionId ?? crypto.randomUUID();
|
||||
this.contentTest = data?.contentTest ?? 'This is a simple example of a web application';
|
||||
}
|
||||
|
||||
public readonly sessionId: string = crypto.randomUUID();
|
||||
|
||||
public contentTest: string = 'This is a simple example of a web application';
|
||||
public static async getInstance(): Promise<Session> {
|
||||
if (!Session.instance) {
|
||||
await Session.loadOrCreate();
|
||||
}
|
||||
return Session.instance!;
|
||||
}
|
||||
|
||||
private static async loadOrCreate(): Promise<void> {
|
||||
try {
|
||||
const savedData = await Session.storageService.load<SessionData>(Session.STORAGE_KEY);
|
||||
Session.instance = new Session(savedData ?? undefined);
|
||||
await Session.instance.save();
|
||||
} catch (error) {
|
||||
console.error('Failed to load session, creating new one:', error);
|
||||
Session.instance = new Session();
|
||||
await Session.instance.save();
|
||||
}
|
||||
}
|
||||
|
||||
public async save(): Promise<void> {
|
||||
try {
|
||||
const data: SessionData = {
|
||||
sessionId: this.sessionId,
|
||||
contentTest: this.contentTest,
|
||||
};
|
||||
await Session.storageService.save(Session.STORAGE_KEY, data);
|
||||
} catch (error) {
|
||||
console.error('Failed to save session:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public static async reset(): Promise<void> {
|
||||
try {
|
||||
await Session.storageService.remove(Session.STORAGE_KEY);
|
||||
Session.instance = new Session();
|
||||
await Session.instance.save();
|
||||
|
||||
// Reload page only if we're in a browser environment
|
||||
if (typeof window !== 'undefined' && window.location) {
|
||||
window.location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to reset session:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public toJSON(): SessionData {
|
||||
return {
|
||||
sessionId: this.sessionId,
|
||||
contentTest: this.contentTest,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,63 +1,96 @@
|
|||
import { customButton } from "../types/buttonType";
|
||||
import { customButton } from '../types/buttonType';
|
||||
|
||||
export interface ButtonConfig {
|
||||
type: customButton;
|
||||
text: string;
|
||||
id?: string | undefined;
|
||||
className?: string | undefined;
|
||||
disabled?: boolean | undefined;
|
||||
onClick?: (() => void) | undefined;
|
||||
}
|
||||
|
||||
export class BasicButton {
|
||||
|
||||
private type: customButton;
|
||||
|
||||
private text: string;
|
||||
|
||||
private id: string | undefined;
|
||||
|
||||
private className: string | undefined;
|
||||
private readonly config: ButtonConfig;
|
||||
|
||||
constructor(type: customButton, text: string, id?: string, className?: string) {
|
||||
this.type = type;
|
||||
this.text = text;
|
||||
this.id = id;
|
||||
this.className = className;
|
||||
this.config = {
|
||||
type,
|
||||
text,
|
||||
id,
|
||||
className,
|
||||
};
|
||||
}
|
||||
|
||||
public render(): string {
|
||||
const result = document.createElement('button');
|
||||
result.type = "button";
|
||||
result.className = this.type;
|
||||
result.textContent = this.text;
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.textContent = this.config.text;
|
||||
button.className = this.getBootstrapClass();
|
||||
|
||||
switch (this.type) {
|
||||
case "primary":
|
||||
result.className = "btn btn-primary";
|
||||
break;
|
||||
case "success":
|
||||
result.className = "btn btn-success";
|
||||
break;
|
||||
case "danger":
|
||||
result.className = "btn btn-danger";
|
||||
break;
|
||||
case "warning":
|
||||
result.className = "btn btn-warning";
|
||||
break;
|
||||
case "info":
|
||||
result.className = "btn btn-info";
|
||||
break;
|
||||
case "light":
|
||||
result.className = "btn btn-light";
|
||||
break;
|
||||
case "dark":
|
||||
result.className = "btn btn-dark";
|
||||
break;
|
||||
default:
|
||||
result.className = "btn btn-primary";
|
||||
break;
|
||||
if (this.config.id) {
|
||||
button.id = this.config.id;
|
||||
}
|
||||
|
||||
if (this.id) {
|
||||
result.id = this.id;
|
||||
}
|
||||
if (this.className) {
|
||||
result.className += ' ' + this.className;
|
||||
if (this.config.className) {
|
||||
button.className += ` ${this.config.className}`;
|
||||
}
|
||||
|
||||
return result.outerHTML;
|
||||
if (this.config.disabled) {
|
||||
button.disabled = true;
|
||||
}
|
||||
|
||||
return button.outerHTML;
|
||||
}
|
||||
|
||||
public createElement(): HTMLButtonElement {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.textContent = this.config.text;
|
||||
button.className = this.getBootstrapClass();
|
||||
|
||||
if (this.config.id) {
|
||||
button.id = this.config.id;
|
||||
}
|
||||
|
||||
if (this.config.className) {
|
||||
button.className += ` ${this.config.className}`;
|
||||
}
|
||||
|
||||
if (this.config.disabled) {
|
||||
button.disabled = true;
|
||||
}
|
||||
|
||||
if (this.config.onClick) {
|
||||
button.addEventListener('click', this.config.onClick);
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private getBootstrapClass(): string {
|
||||
const typeMap: Record<customButton, string> = {
|
||||
neutral: 'btn btn-secondary',
|
||||
primary: 'btn btn-primary',
|
||||
secondary: 'btn btn-secondary',
|
||||
success: 'btn btn-success',
|
||||
danger: 'btn btn-danger',
|
||||
warning: 'btn btn-warning',
|
||||
info: 'btn btn-info',
|
||||
light: 'btn btn-light',
|
||||
dark: 'btn btn-dark',
|
||||
};
|
||||
|
||||
return typeMap[this.config.type] ?? 'btn btn-primary';
|
||||
}
|
||||
|
||||
public static create(config: ButtonConfig): BasicButton {
|
||||
const button = new BasicButton(config.type, config.text, config.id, config.className);
|
||||
if (config.disabled !== undefined) {
|
||||
button.config.disabled = config.disabled;
|
||||
}
|
||||
if (config.onClick !== undefined) {
|
||||
button.config.onClick = config.onClick;
|
||||
}
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,64 @@
|
|||
$main-font: 'Ubuntu', 'Staatliches'
|
||||
$main-font-color: white
|
||||
$main-font-color-hover: lightgrey
|
||||
$main-font-color-focus: lightgrey
|
||||
$main-font-color-disabled: lightgrey
|
||||
$main-font-color-active: lightgrey
|
||||
$main-uschrift-font: 'Ubuntu', Arial
|
||||
$primary-color: #007bff
|
||||
$primary-color-hover: #0069d9
|
||||
$primary-color-focus: #0062cc
|
||||
$primary-color-disabled: #0069d9
|
||||
$primary-color-active: #0062cc
|
||||
$background-color-content-shadow: #0069d9
|
||||
$seccond-color: #6c757d
|
||||
$seccondary-color: darkgrey
|
||||
$seccondary-color-hover: black
|
||||
$seccondary-color-focus: black
|
||||
$seccondary-color-disabled: black
|
||||
$seccondary-color-active: black
|
||||
$background-color: #77B2FF
|
||||
$background-color-content: rgb(198, 223, 255)
|
||||
$background-color-content-hover: rgb(198, 223, 255)
|
||||
$background-color-content-focus: rgb(198, 223, 255)
|
||||
$background-color-content-active: rgb(198, 223, 255)
|
||||
$background-color-content-disabled: rgb(198, 223, 255)
|
||||
$logo-image: url('../icons/icon128.png')
|
||||
// CSS Custom Properties for theming support
|
||||
:root
|
||||
--main-font: 'Ubuntu', 'Segoe UI', 'Roboto', sans-serif
|
||||
--main-font-color: #ffffff
|
||||
--main-font-color-hover: #f8f9fa
|
||||
--main-font-color-focus: #e9ecef
|
||||
--main-font-color-disabled: #6c757d
|
||||
--main-font-color-active: #f8f9fa
|
||||
|
||||
--primary-color: #007bff
|
||||
--primary-color-hover: #0056b3
|
||||
--primary-color-focus: #004085
|
||||
--primary-color-disabled: #6c757d
|
||||
--primary-color-active: #004085
|
||||
|
||||
--secondary-color: #6c757d
|
||||
--secondary-color-hover: #545b62
|
||||
--secondary-color-focus: #4e555b
|
||||
--secondary-color-disabled: #adb5bd
|
||||
--secondary-color-active: #4e555b
|
||||
|
||||
--background-color: #77B2FF
|
||||
--background-color-content: #c6dfff
|
||||
--background-color-content-hover: #b3d7ff
|
||||
--background-color-content-focus: #9fcdff
|
||||
--background-color-content-active: #8cc4ff
|
||||
--background-color-content-disabled: #e9ecef
|
||||
|
||||
--shadow-color: rgba(0, 0, 0, 0.1)
|
||||
--border-radius: 0.375rem
|
||||
--transition-duration: 0.15s
|
||||
--font-size-base: 1rem
|
||||
--line-height-base: 1.5
|
||||
|
||||
// SASS Variables (for backwards compatibility)
|
||||
$main-font: var(--main-font)
|
||||
$main-font-color: var(--main-font-color)
|
||||
$main-font-color-hover: var(--main-font-color-hover)
|
||||
$main-font-color-focus: var(--main-font-color-focus)
|
||||
$main-font-color-disabled: var(--main-font-color-disabled)
|
||||
$main-font-color-active: var(--main-font-color-active)
|
||||
|
||||
$primary-color: var(--primary-color)
|
||||
$primary-color-hover: var(--primary-color-hover)
|
||||
$primary-color-focus: var(--primary-color-focus)
|
||||
$primary-color-disabled: var(--primary-color-disabled)
|
||||
$primary-color-active: var(--primary-color-active)
|
||||
|
||||
$secondary-color: var(--secondary-color)
|
||||
$secondary-color-hover: var(--secondary-color-hover)
|
||||
$secondary-color-focus: var(--secondary-color-focus)
|
||||
$secondary-color-disabled: var(--secondary-color-disabled)
|
||||
$secondary-color-active: var(--secondary-color-active)
|
||||
|
||||
$background-color: var(--background-color)
|
||||
$background-color-content: var(--background-color-content)
|
||||
$background-color-content-hover: var(--background-color-content-hover)
|
||||
$background-color-content-focus: var(--background-color-content-focus)
|
||||
$background-color-content-active: var(--background-color-content-active)
|
||||
$background-color-content-disabled: var(--background-color-content-disabled)
|
||||
|
||||
$shadow-color: var(--shadow-color)
|
||||
$border-radius: var(--border-radius)
|
||||
$transition-duration: var(--transition-duration)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
@import 'root'
|
||||
@import 'mixin'
|
||||
@import 'content'
|
||||
|
||||
// Bootstrap with legacy import (suppressed warnings via Vite config)
|
||||
@import "../../node_modules/bootstrap/scss/bootstrap"
|
||||
|
||||
body
|
||||
|
|
117
src/settings.ts
117
src/settings.ts
|
@ -1,33 +1,120 @@
|
|||
import { Session } from "./classes/session";
|
||||
import { BasicButton } from "./components/button";
|
||||
import "./sass/app.sass";
|
||||
import { Session } from './classes/session';
|
||||
import { BasicButton } from './components/button';
|
||||
import './sass/app.sass';
|
||||
|
||||
class Settings {
|
||||
|
||||
private session = Session.getInstance();
|
||||
private session: Session | null = null;
|
||||
|
||||
constructor() {
|
||||
this.renderSettings();
|
||||
this.init();
|
||||
}
|
||||
|
||||
private async init(): Promise<void> {
|
||||
try {
|
||||
this.session = await Session.getInstance();
|
||||
await this.renderSettings();
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize settings:', error);
|
||||
this.handleError('Failed to load settings');
|
||||
}
|
||||
}
|
||||
|
||||
private async renderSettings(): Promise<void> {
|
||||
const settings = <HTMLDivElement>document.getElementById('settings');
|
||||
if (!this.session) {
|
||||
throw new Error('Session not initialized');
|
||||
}
|
||||
|
||||
const settingsElement = document.getElementById('settings') as HTMLDivElement | null;
|
||||
if (!settingsElement) {
|
||||
throw new Error('Settings element not found');
|
||||
}
|
||||
|
||||
const saveButton = new BasicButton('success', 'Save', 'saveSettings').render();
|
||||
settings.innerHTML = `
|
||||
|
||||
settingsElement.innerHTML = `
|
||||
<div class="form-group">
|
||||
<label for="contentTest">Content Test</label>
|
||||
<input type="text" class="form-control text-input" id="contentTest" placeholder="Enter content test" value="${this.session.contentTest}">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control text-input"
|
||||
id="contentTest"
|
||||
placeholder="Enter content test"
|
||||
value="${this.escapeHtml(this.session.contentTest)}"
|
||||
>
|
||||
</div>
|
||||
${saveButton}
|
||||
`;
|
||||
settings.innerHTML += saveButton;
|
||||
|
||||
const saveSettings = <HTMLButtonElement>document.getElementById('saveSettings');
|
||||
saveSettings.addEventListener('click', () => {
|
||||
this.session.contentTest = (<HTMLInputElement>document.getElementById('contentTest')).value;
|
||||
Session.save();
|
||||
Session.reloadSession();
|
||||
this.attachEventListeners();
|
||||
}
|
||||
|
||||
private attachEventListeners(): void {
|
||||
const saveButton = document.getElementById('saveSettings') as HTMLButtonElement | null;
|
||||
const contentInput = document.getElementById('contentTest') as HTMLInputElement | null;
|
||||
|
||||
if (!saveButton || !contentInput) {
|
||||
console.error('Required elements not found');
|
||||
return;
|
||||
}
|
||||
|
||||
saveButton.addEventListener('click', async () => {
|
||||
try {
|
||||
await this.saveSettings(contentInput.value);
|
||||
} catch (error) {
|
||||
console.error('Failed to save settings:', error);
|
||||
this.showNotification('Failed to save settings', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async saveSettings(contentTest: string): Promise<void> {
|
||||
if (!this.session) {
|
||||
throw new Error('Session not initialized');
|
||||
}
|
||||
|
||||
this.session.contentTest = contentTest;
|
||||
await this.session.save();
|
||||
this.showNotification('Settings saved successfully!', 'success');
|
||||
}
|
||||
|
||||
private escapeHtml(text: string): string {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
private showNotification(message: string, type: 'success' | 'error'): void {
|
||||
// Simple notification - could be enhanced with a proper notification system
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification notification-${type}`;
|
||||
notification.textContent = message;
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
background-color: ${type === 'success' ? '#28a745' : '#dc3545'};
|
||||
z-index: 1000;
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
private handleError(message: string): void {
|
||||
console.error(message);
|
||||
const settingsElement = document.getElementById('settings');
|
||||
if (settingsElement) {
|
||||
settingsElement.innerHTML = `<div class="error-message">${message}</div>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new Settings();
|
||||
|
|
|
@ -1 +1,10 @@
|
|||
export type customButton = "neutral" | "primary" | "secondary" | "success" | "danger" | "warning" | "info" | "light" | "dark";
|
||||
export type customButton =
|
||||
| 'neutral'
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'success'
|
||||
| 'danger'
|
||||
| 'warning'
|
||||
| 'info'
|
||||
| 'light'
|
||||
| 'dark';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
const appConfig = JSON.parse(fs.readFileSync("./app.config.json", "utf8"));
|
||||
const DEPLOY_TARGET = "./dist/";
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
const appConfig = JSON.parse(fs.readFileSync('./app.config.json', 'utf8'));
|
||||
const DEPLOY_TARGET = './dist/';
|
||||
|
||||
function findCssFileNames(source: string): string[] {
|
||||
let files: string[] = [];
|
||||
|
@ -12,7 +12,7 @@ function findCssFileNames(source: string): string[] {
|
|||
if (stat.isDirectory()) {
|
||||
files = files.concat(findCssFileNames(sourceFile));
|
||||
} else {
|
||||
if (path.extname(sourceFile) == ".css") {
|
||||
if (path.extname(sourceFile) == '.css') {
|
||||
files.push(file);
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ function findHtmlFilesRecursive(source: string): string[] {
|
|||
if (stat.isDirectory()) {
|
||||
files = files.concat(findHtmlFilesRecursive(sourceFile));
|
||||
} else {
|
||||
if (path.extname(sourceFile) == ".html") {
|
||||
if (path.extname(sourceFile) == '.html') {
|
||||
files.push(sourceFile);
|
||||
}
|
||||
}
|
||||
|
@ -38,13 +38,13 @@ function findHtmlFilesRecursive(source: string): string[] {
|
|||
}
|
||||
|
||||
function replaceKeywordsInHtmlFile(file: string) {
|
||||
let content = fs.readFileSync(file, "utf8");
|
||||
let content = fs.readFileSync(file, 'utf8');
|
||||
const pairs: { key: string; value: string }[] = appConfig.htmlTemplatePairs;
|
||||
pairs.forEach(function (pair: { key: string; value: string }) {
|
||||
//@ts-ignore
|
||||
content = content.replaceAll(pair.key, pair.value);
|
||||
});
|
||||
file = file.replace("public\\", DEPLOY_TARGET);
|
||||
file = file.replace('public\\', DEPLOY_TARGET);
|
||||
fs.writeFileSync(file, content);
|
||||
}
|
||||
|
||||
|
@ -58,9 +58,9 @@ function buildHtmlFiles(source: string) {
|
|||
findCssFileNames(DEPLOY_TARGET).forEach((file: string) => {
|
||||
const files = findHtmlFilesRecursive(DEPLOY_TARGET);
|
||||
files.forEach(function (htmlFile: string) {
|
||||
let content = fs.readFileSync(htmlFile, "utf8");
|
||||
let content = fs.readFileSync(htmlFile, 'utf8');
|
||||
content = content.replace(
|
||||
"</head>",
|
||||
'</head>',
|
||||
`<link rel="stylesheet" href="./assets/${file}">\n</head>`
|
||||
);
|
||||
fs.writeFileSync(htmlFile, content);
|
||||
|
@ -69,4 +69,4 @@ findCssFileNames(DEPLOY_TARGET).forEach((file: string) => {
|
|||
|
||||
buildHtmlFiles(DEPLOY_TARGET);
|
||||
|
||||
console.log("Parsed Files: ", findHtmlFilesRecursive(DEPLOY_TARGET));
|
||||
console.log('Parsed Files: ', findHtmlFilesRecursive(DEPLOY_TARGET));
|
||||
|
|
|
@ -1,23 +1,151 @@
|
|||
// @ts-ignore
|
||||
const fs = require('fs');
|
||||
import * as fs from 'fs';
|
||||
|
||||
const appConfig = JSON.parse(fs.readFileSync('./app.config.json', 'utf8'));
|
||||
const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
||||
const manifestJson = JSON.parse(fs.readFileSync('./public/manifest.json', 'utf8'));
|
||||
interface AppConfig {
|
||||
AppData: {
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
description: string;
|
||||
repository: {
|
||||
type: string;
|
||||
url: string;
|
||||
};
|
||||
license: string;
|
||||
homepage: string;
|
||||
bugs: {
|
||||
url: string;
|
||||
};
|
||||
authors: Array<{
|
||||
name: string;
|
||||
email: string;
|
||||
}>;
|
||||
};
|
||||
htmlTemplatePairs: Array<{
|
||||
key: string;
|
||||
value: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
pkg.version = appConfig.AppData.version;
|
||||
pkg.name = appConfig.AppData.id;
|
||||
pkg.authors = appConfig.AppData.authors;
|
||||
pkg.description = appConfig.AppData.description;
|
||||
pkg.homepage = appConfig.AppData.homepage;
|
||||
pkg.license = appConfig.AppData.license;
|
||||
pkg.repository = appConfig.AppData.repository;
|
||||
pkg.bugs = appConfig.AppData.bugs;
|
||||
interface PackageJson {
|
||||
version: string;
|
||||
name: string;
|
||||
authors: Array<{
|
||||
name: string;
|
||||
email: string;
|
||||
}>;
|
||||
description: string;
|
||||
homepage: string;
|
||||
license: string;
|
||||
repository: {
|
||||
type: string;
|
||||
url: string;
|
||||
};
|
||||
bugs: {
|
||||
url: string;
|
||||
};
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
manifestJson.version = appConfig.AppData.version;
|
||||
manifestJson.name = appConfig.AppData.name;
|
||||
manifestJson.description = appConfig.AppData.description;
|
||||
manifestJson.homepage_url = appConfig.AppData.homepage;
|
||||
interface ManifestJson {
|
||||
version: string;
|
||||
name: string;
|
||||
description: string;
|
||||
homepage_url: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2));
|
||||
fs.writeFileSync('./public/manifest.json', JSON.stringify(manifestJson, null, 2));
|
||||
class ConfigSyncer {
|
||||
private readonly appConfigPath = './app.config.json';
|
||||
private readonly packageJsonPath = './package.json';
|
||||
private readonly manifestJsonPath = './public/manifest.json';
|
||||
|
||||
public async sync(): Promise<void> {
|
||||
try {
|
||||
console.log('Starting configuration synchronization...');
|
||||
|
||||
const appConfig = await this.loadAppConfig();
|
||||
const packageJson = await this.loadPackageJson();
|
||||
const manifestJson = await this.loadManifestJson();
|
||||
|
||||
this.updatePackageJson(packageJson, appConfig.AppData);
|
||||
this.updateManifestJson(manifestJson, appConfig.AppData);
|
||||
|
||||
await this.savePackageJson(packageJson);
|
||||
await this.saveManifestJson(manifestJson);
|
||||
|
||||
console.log('Configuration synchronization completed successfully!');
|
||||
} catch (error) {
|
||||
console.error('Failed to sync configuration:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadAppConfig(): Promise<AppConfig> {
|
||||
try {
|
||||
const content = await fs.promises.readFile(this.appConfigPath, 'utf8');
|
||||
return JSON.parse(content) as AppConfig;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to load app config: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadPackageJson(): Promise<PackageJson> {
|
||||
try {
|
||||
const content = await fs.promises.readFile(this.packageJsonPath, 'utf8');
|
||||
return JSON.parse(content) as PackageJson;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to load package.json: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadManifestJson(): Promise<ManifestJson> {
|
||||
try {
|
||||
const content = await fs.promises.readFile(this.manifestJsonPath, 'utf8');
|
||||
return JSON.parse(content) as ManifestJson;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to load manifest.json: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
private updatePackageJson(packageJson: PackageJson, appData: AppConfig['AppData']): void {
|
||||
packageJson.version = appData.version;
|
||||
packageJson.name = appData.id;
|
||||
packageJson.authors = appData.authors;
|
||||
packageJson.description = appData.description;
|
||||
packageJson.homepage = appData.homepage;
|
||||
packageJson.license = appData.license;
|
||||
packageJson.repository = appData.repository;
|
||||
packageJson.bugs = appData.bugs;
|
||||
}
|
||||
|
||||
private updateManifestJson(manifestJson: ManifestJson, appData: AppConfig['AppData']): void {
|
||||
manifestJson.version = appData.version;
|
||||
manifestJson.name = appData.name;
|
||||
manifestJson.description = appData.description;
|
||||
manifestJson.homepage_url = appData.homepage;
|
||||
}
|
||||
|
||||
private async savePackageJson(packageJson: PackageJson): Promise<void> {
|
||||
try {
|
||||
const content = JSON.stringify(packageJson, null, 2);
|
||||
await fs.promises.writeFile(this.packageJsonPath, content, 'utf8');
|
||||
console.log('✓ package.json updated');
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to save package.json: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async saveManifestJson(manifestJson: ManifestJson): Promise<void> {
|
||||
try {
|
||||
const content = JSON.stringify(manifestJson, null, 2);
|
||||
await fs.promises.writeFile(this.manifestJsonPath, content, 'utf8');
|
||||
console.log('✓ manifest.json updated');
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to save manifest.json: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run the sync process
|
||||
const syncer = new ConfigSyncer();
|
||||
syncer.sync().catch(console.error);
|
||||
|
|
36
tools/v2.ts
36
tools/v2.ts
|
@ -3,44 +3,44 @@ const fs = require('fs');
|
|||
|
||||
const manifest = JSON.parse(fs.readFileSync('./dist/manifest.json', 'utf8'));
|
||||
|
||||
manifest.manifest_version = 2
|
||||
manifest.manifest_version = 2;
|
||||
|
||||
manifest.background.scripts = []
|
||||
manifest.background.scripts = [];
|
||||
|
||||
manifest.background.scripts.push(manifest.background.service_worker)
|
||||
delete manifest.background.type
|
||||
delete manifest.background.service_worker
|
||||
manifest.background.persistent = true
|
||||
manifest.background.scripts.push(manifest.background.service_worker);
|
||||
delete manifest.background.type;
|
||||
delete manifest.background.service_worker;
|
||||
manifest.background.persistent = true;
|
||||
if (manifest.host_permissions) {
|
||||
manifest.permissions.push(manifest.host_permissions)
|
||||
manifest.permissions.push(manifest.host_permissions);
|
||||
}
|
||||
if (manifest.optional_host_permissions) {
|
||||
manifest.permissions.push(manifest.optional_host_permissions)
|
||||
manifest.permissions.push(manifest.optional_host_permissions);
|
||||
}
|
||||
delete manifest.host_permissions
|
||||
delete manifest.optional_host_permissions
|
||||
delete manifest.host_permissions;
|
||||
delete manifest.optional_host_permissions;
|
||||
|
||||
let newContentSecurityPolicy = ""
|
||||
let newContentSecurityPolicy = '';
|
||||
|
||||
try {
|
||||
for (const policy of manifest.content_security_policy) {
|
||||
newContentSecurityPolicy += policy.key + "'" + policy.value + "'" + " "
|
||||
newContentSecurityPolicy += policy.key + "'" + policy.value + "'" + ' ';
|
||||
}
|
||||
} catch (e) {
|
||||
newContentSecurityPolicy = "default-src 'self'"
|
||||
newContentSecurityPolicy = "default-src 'self'";
|
||||
}
|
||||
|
||||
manifest.content_security_policy = newContentSecurityPolicy
|
||||
manifest.content_security_policy = newContentSecurityPolicy;
|
||||
|
||||
try {
|
||||
manifest.web_accessible_resources = manifest.web_accessible_resources.resources
|
||||
manifest.web_accessible_resources = manifest.web_accessible_resources.resources;
|
||||
} catch (e) {
|
||||
manifest.web_accessible_resources = []
|
||||
manifest.web_accessible_resources = [];
|
||||
}
|
||||
|
||||
if (manifest.action) {
|
||||
manifest.browser_action = manifest.action
|
||||
manifest.browser_action = manifest.action;
|
||||
}
|
||||
delete manifest.action
|
||||
delete manifest.action;
|
||||
|
||||
fs.writeFileSync('./dist/manifest.json', JSON.stringify(manifest, null, 2));
|
||||
|
|
108
tsconfig.json
108
tsconfig.json
|
@ -1,75 +1,49 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
/* Language and Environment */
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "ESNext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */,
|
||||
"module": "ESNext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
|
||||
"lib": ["ESNext", "DOM"], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
"declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
"sourceMap": true /* Generates corresponding '.map' file. */,
|
||||
// "outFile": "./public/js/app.js", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./dist/js/" /* Redirect output structure to the directory. */,
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
"removeComments": true /* Do not emit comments to output. */,
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
"moduleResolution": "node",
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
/* Type Checking */
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||
/* Emit */
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"removeComments": false,
|
||||
"importHelpers": true,
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
/* Interop Constraints */
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true /* Skip type checking of declaration files. */,
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
/* Completeness */
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["./src/**/*.ts", "./src/*.ts"],
|
||||
"exclude": ["node_modules", "./tools/*.ts", "./dist/*.ts", "./dist/*.js"],
|
||||
"types": ["node", "chrome"]
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist", "tools/*.js"]
|
||||
}
|
||||
|
|
|
@ -1,28 +1,71 @@
|
|||
import { defineConfig } from "vite";
|
||||
import { resolve } from "path";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
import { resolve } from 'path';
|
||||
import { defineConfig } from 'vite';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: {
|
||||
app: resolve(__dirname, "src/app.ts"),
|
||||
settings: resolve(__dirname, "src/settings.ts"),
|
||||
background: resolve(__dirname, "src/background.ts"),
|
||||
app: resolve(__dirname, 'src/app.ts'),
|
||||
settings: resolve(__dirname, 'src/settings.ts'),
|
||||
background: resolve(__dirname, 'src/background.ts'),
|
||||
},
|
||||
output: {
|
||||
entryFileNames: "[name].js",
|
||||
dir: resolve(__dirname, "dist"),
|
||||
entryFileNames: '[name].js',
|
||||
chunkFileNames: 'chunks/[name]-[hash].js',
|
||||
assetFileNames: assetInfo => {
|
||||
if (assetInfo.name?.endsWith('.css')) {
|
||||
return 'assets/[name]-[hash][extname]';
|
||||
}
|
||||
return 'assets/[name]-[hash][extname]';
|
||||
},
|
||||
dir: resolve(__dirname, 'dist'),
|
||||
},
|
||||
},
|
||||
sourcemap: true,
|
||||
target: 'es2022',
|
||||
minify: 'esbuild',
|
||||
reportCompressedSize: false,
|
||||
chunkSizeWarningLimit: 1000,
|
||||
},
|
||||
plugins: [tsconfigPaths()],
|
||||
resolve: {
|
||||
extensions: [".tsx", ".ts", ".scss", ".sass"],
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx', '.scss', '.sass'],
|
||||
alias: {
|
||||
'@': resolve(__dirname, './src'),
|
||||
'@components': resolve(__dirname, './src/components'),
|
||||
'@classes': resolve(__dirname, './src/classes'),
|
||||
'@types': resolve(__dirname, './src/types'),
|
||||
'@sass': resolve(__dirname, './src/sass'),
|
||||
},
|
||||
},
|
||||
esbuild: {
|
||||
target: 'es2022',
|
||||
include: /.*\.tsx?$/,
|
||||
exclude: [/node_modules/, /dist/],
|
||||
legalComments: 'none',
|
||||
},
|
||||
define: {
|
||||
__DEV__: JSON.stringify(process.env.NODE_ENV === 'development'),
|
||||
__VERSION__: JSON.stringify(process.env.npm_package_version || '0.0.1'),
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
sass: {
|
||||
additionalData: `@import "@sass/_root.sass"\n@import "@sass/_mixin.sass"\n`,
|
||||
quietDeps: true,
|
||||
verbose: false,
|
||||
charset: false,
|
||||
silenceDeprecations: [
|
||||
'import',
|
||||
'global-builtin',
|
||||
'color-functions',
|
||||
'legacy-js-api',
|
||||
'mixed-decls',
|
||||
'slash-div',
|
||||
],
|
||||
},
|
||||
},
|
||||
devSourcemap: true,
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue