mirror of
https://github.com/JosunLP/BrowserExtensionTemplate.git
synced 2025-10-14 08:00:11 +00:00
Merge pull request #14 from JosunLP/dev
Some checks failed
CodeQL / Analyze (push) Has been cancelled
Some checks failed
CodeQL / Analyze (push) Has been cancelled
Release 1.7.0
This commit is contained in:
commit
11ecc63799
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/network)
|
||||||
[](https://github.com/JosunLP/BrowserExtensionTemplate/stargazers)
|
[](https://github.com/JosunLP/BrowserExtensionTemplate/stargazers)
|
||||||
[](https://github.com/JosunLP/BrowserExtensionTemplate)
|
[](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://www.codefactor.io/repository/github/josunlp/browserextensiontemplate)
|
||||||
[](https://snyk.io/test/github/JosunLP/BrowserExtensionTemplate)
|
|
||||||
|
|
||||||
## Description
|
## 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
|
## 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.
|
### Quick Start
|
||||||
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.
|
```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
|
## Usage
|
||||||
|
|
||||||
Your sourcecode can be written in the `src` folder. The `public` folder contains static files like images, html and the manifest.json.
|
### Project Structure
|
||||||
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.
|
|
||||||
|
|
||||||
## 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
|
## 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
|
## 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
|
||||||
|
|
|
@ -1,29 +1,29 @@
|
||||||
{
|
{
|
||||||
"AppData": {
|
"AppData": {
|
||||||
"id": "browser_extension_template",
|
"id": "browser_extension_template",
|
||||||
"name": "Browser Extension Template",
|
"name": "Browser Extension Template",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "A basic template based on SASS and TypeScript to create browser extensions without directly relying on a larger framework.",
|
"description": "A basic template based on SASS and TypeScript to create browser extensions without directly relying on a larger framework.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+ssh://git@github.com:JosunLP/BrowserExtensionTemplate.git"
|
"url": "git+ssh://git@github.com:JosunLP/BrowserExtensionTemplate.git"
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"homepage": "https://github.com/JosunLP/BrowserExtensionTemplate",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/JosunLP/BrowserExtensionTemplate/issues"
|
|
||||||
},
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Jonas Pfalzgraf",
|
|
||||||
"email": "info@josunlp.de"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"htmlTemplatePairs": [
|
"license": "MIT",
|
||||||
{
|
"homepage": "https://github.com/JosunLP/BrowserExtensionTemplate",
|
||||||
"key": "{{BET}}",
|
"bugs": {
|
||||||
"value": "Browser Extension Template"
|
"url": "https://github.com/JosunLP/BrowserExtensionTemplate/issues"
|
||||||
}
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jonas Pfalzgraf",
|
||||||
|
"email": "info@josunlp.de"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"htmlTemplatePairs": [
|
||||||
|
{
|
||||||
|
"key": "{{BET}}",
|
||||||
|
"value": "Browser Extension Template"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
27
package.json
27
package.json
|
@ -2,20 +2,39 @@
|
||||||
"name": "browser_extension_template",
|
"name": "browser_extension_template",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"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",
|
"deploy-v2": "npm run deploy-v3 && node ./tools/v2.js",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"build-tooling": "tsc --project ./tooling.tsconfig.json",
|
"build-tooling": "tsc --project ./tooling.tsconfig.json",
|
||||||
"watch": "vite build --watch",
|
"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": {
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
|
"@eslint/js": "^9.30.1",
|
||||||
"@types/chrome": "^0.0.268",
|
"@types/chrome": "^0.0.268",
|
||||||
"@types/node": "^20.13.0",
|
"@types/node": "^20.13.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.36.0",
|
||||||
|
"@typescript-eslint/parser": "^8.36.0",
|
||||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||||
|
"eslint": "^9.30.1",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
|
"rimraf": "^5.0.10",
|
||||||
"sass": "^1.77.4",
|
"sass": "^1.77.4",
|
||||||
"vite": "^5.2.12",
|
"typescript": "^5.8.3",
|
||||||
|
"vite": "^7.0.4",
|
||||||
"vite-tsconfig-paths": "^4.3.2"
|
"vite-tsconfig-paths": "^4.3.2"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
@ -43,4 +62,4 @@
|
||||||
"@webcomponents/custom-elements": "^1.6.0",
|
"@webcomponents/custom-elements": "^1.6.0",
|
||||||
"bootstrap": "^5.3.3"
|
"bootstrap": "^5.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,12 +15,27 @@
|
||||||
"default_popup": "popup.html"
|
"default_popup": "popup.html"
|
||||||
},
|
},
|
||||||
"options_ui": {
|
"options_ui": {
|
||||||
"page": "options.html"
|
"page": "options.html",
|
||||||
|
"open_in_tab": false
|
||||||
},
|
},
|
||||||
"permissions": [
|
"permissions": [
|
||||||
|
"storage",
|
||||||
"notifications"
|
"notifications"
|
||||||
],
|
],
|
||||||
"background": {
|
"background": {
|
||||||
"service_worker": "background.js"
|
"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>
|
<!doctype html>
|
||||||
<html lang="">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<link rel="icon" href="./favicon.ico" />
|
||||||
<link rel="icon" href="./favicon.ico">
|
|
||||||
<title>{{BET}} Options</title>
|
<title>{{BET}} Options</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
<strong>We're sorry but {{BET}} doesn't work properly without JavaScript enabled. Please enable it to
|
<strong
|
||||||
continue.</strong>
|
>We're sorry but {{BET}} doesn't work properly without JavaScript enabled. Please enable it
|
||||||
|
to continue.</strong
|
||||||
|
>
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<img src="./icons/icon128.png" class="logo" />
|
<img src="./icons/icon128.png" alt="Logo" class="logo" />
|
||||||
<h1>Settings</h1>
|
<h1>Settings</h1>
|
||||||
<div id="settings">
|
<div id="settings"></div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<footer>
|
||||||
<script type="module" src="./settings.js"></script>
|
<script type="module" src="./settings.js"></script>
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<link rel="icon" href="./favicon.ico" />
|
||||||
<link rel="icon" href="./favicon.ico">
|
|
||||||
<title>{{BET}}</title>
|
<title>{{BET}}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
<strong>We're sorry but {{BET}} doesn't work properly without JavaScript enabled. Please enable it to
|
<strong
|
||||||
continue.</strong>
|
>We're sorry but {{BET}} doesn't work properly without JavaScript enabled. Please enable it
|
||||||
|
to continue.</strong
|
||||||
|
>
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<img src="./icons/icon128.png" class="logo" />
|
<img src="./icons/icon128.png" alt="Logo" class="logo" />
|
||||||
<h1>{{BET}}</h1>
|
<h1>{{BET}}</h1>
|
||||||
<div id="content">
|
<div id="content"></div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<footer>
|
||||||
<script type="module" src="./app.js"></script>
|
<script type="module" src="./app.js"></script>
|
||||||
|
|
67
src/app.ts
67
src/app.ts
|
@ -1,31 +1,60 @@
|
||||||
import { Session } from "./classes/session"
|
import { Session } from './classes/session';
|
||||||
import "./sass/app.sass"
|
import './sass/app.sass';
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
|
private static readonly CONTENT_ENTRY = 'content';
|
||||||
|
private session: Session | null = null;
|
||||||
|
|
||||||
private static contentEntry: string = "content"
|
constructor() {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
private async init(): Promise<void> {
|
||||||
this.drawData()
|
try {
|
||||||
this.main()
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async main(): Promise<void> {
|
||||||
|
console.log('Hello World');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async drawData(): Promise<void> {
|
||||||
|
if (!this.session) {
|
||||||
|
throw new Error('Session not initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
async main(): Promise<void> {
|
const contentRoot = document.getElementById(App.CONTENT_ENTRY) as HTMLDivElement | null;
|
||||||
console.log("Hello World")
|
if (!contentRoot) {
|
||||||
|
throw new Error(`Element with id '${App.CONTENT_ENTRY}' not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async drawData(): Promise<void> {
|
const body = document.createElement('div');
|
||||||
const session = Session.getInstance()
|
body.className = 'app-content';
|
||||||
const contentRoot = <HTMLDivElement>document.getElementById(App.contentEntry)
|
|
||||||
const body = document.createElement("div")
|
const title = document.createElement('h1');
|
||||||
const title = document.createElement("h1")
|
title.innerText = 'Hello World';
|
||||||
const text = document.createElement("p")
|
|
||||||
title.innerText = "Hello World"
|
const text = document.createElement('p');
|
||||||
text.innerText = session.contentTest
|
text.innerText = this.session.contentTest;
|
||||||
body.appendChild(title)
|
|
||||||
body.appendChild(text)
|
body.appendChild(title);
|
||||||
contentRoot.appendChild(body)
|
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>`;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new App();
|
new App();
|
||||||
|
|
|
@ -1,9 +1,82 @@
|
||||||
class Background {
|
interface ExtensionMessage {
|
||||||
constructor() {
|
type: string;
|
||||||
this.main();
|
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();
|
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 {
|
interface SessionData {
|
||||||
|
sessionId: string;
|
||||||
|
contentTest: string;
|
||||||
|
}
|
||||||
|
|
||||||
private static instance: Session;
|
interface StorageService {
|
||||||
|
save(key: string, data: unknown): Promise<void>;
|
||||||
|
load<T>(key: string): Promise<T | null>;
|
||||||
|
remove(key: string): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
private constructor() {
|
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');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static getInstance() {
|
async load<T>(key: string): Promise<T | null> {
|
||||||
if (!Session.instance && !Session.load()) {
|
try {
|
||||||
Session.instance = new Session();
|
const item = localStorage.getItem(key);
|
||||||
}
|
return item ? (JSON.parse(item) as T) : null;
|
||||||
if (!Session.instance && Session.load()) {
|
} catch (error) {
|
||||||
Session.instance = <Session>Session.load();
|
console.error('Failed to load from localStorage:', error);
|
||||||
}
|
return null;
|
||||||
Session.save();
|
|
||||||
return Session.instance;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static save() {
|
async remove(key: string): Promise<void> {
|
||||||
localStorage.setItem('session', JSON.stringify(this.instance));
|
try {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to remove from localStorage:', error);
|
||||||
|
throw new Error('Storage operation failed');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static load(): Session | null {
|
export class Session implements SessionData {
|
||||||
const session = localStorage.getItem('session');
|
private static instance: Session | null = null;
|
||||||
if (session) {
|
private static readonly STORAGE_KEY = 'browser_extension_session';
|
||||||
const obj = <Session>JSON.parse(session);
|
private static readonly storageService: StorageService = new LocalStorageService();
|
||||||
const result = new Session();
|
|
||||||
result.contentTest = obj.contentTest;
|
public readonly sessionId: string;
|
||||||
return result;
|
public contentTest: string;
|
||||||
}
|
|
||||||
return null;
|
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 static async getInstance(): Promise<Session> {
|
||||||
|
if (!Session.instance) {
|
||||||
|
await Session.loadOrCreate();
|
||||||
}
|
}
|
||||||
|
return Session.instance!;
|
||||||
|
}
|
||||||
|
|
||||||
public static reloadSession() {
|
private static async loadOrCreate(): Promise<void> {
|
||||||
const session = localStorage.getItem('session');
|
try {
|
||||||
if (session) {
|
const savedData = await Session.storageService.load<SessionData>(Session.STORAGE_KEY);
|
||||||
const obj = <Session>JSON.parse(session);
|
Session.instance = new Session(savedData ?? undefined);
|
||||||
const result = new Session();
|
await Session.instance.save();
|
||||||
result.contentTest = obj.contentTest;
|
} catch (error) {
|
||||||
Session.instance = result;
|
console.error('Failed to load session, creating new one:', error);
|
||||||
}
|
Session.instance = new Session();
|
||||||
|
await Session.instance.save();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static resetSession() {
|
public async save(): Promise<void> {
|
||||||
localStorage.removeItem('session');
|
try {
|
||||||
sessionStorage.removeItem('session');
|
const data: SessionData = {
|
||||||
this.instance = new Session();
|
sessionId: this.sessionId,
|
||||||
Session.save();
|
contentTest: this.contentTest,
|
||||||
location.reload();
|
};
|
||||||
|
await Session.storageService.save(Session.STORAGE_KEY, data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save session:', error);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public readonly sessionId: string = crypto.randomUUID();
|
public static async reset(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await Session.storageService.remove(Session.STORAGE_KEY);
|
||||||
|
Session.instance = new Session();
|
||||||
|
await Session.instance.save();
|
||||||
|
|
||||||
public contentTest: string = 'This is a simple example of a web application';
|
// 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 {
|
export class BasicButton {
|
||||||
|
private readonly config: ButtonConfig;
|
||||||
|
|
||||||
private type: customButton;
|
constructor(type: customButton, text: string, id?: string, className?: string) {
|
||||||
|
this.config = {
|
||||||
|
type,
|
||||||
|
text,
|
||||||
|
id,
|
||||||
|
className,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private text: string;
|
public render(): string {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.type = 'button';
|
||||||
|
button.textContent = this.config.text;
|
||||||
|
button.className = this.getBootstrapClass();
|
||||||
|
|
||||||
private id: string | undefined;
|
if (this.config.id) {
|
||||||
|
button.id = this.config.id;
|
||||||
private className: string | undefined;
|
|
||||||
|
|
||||||
constructor(type: customButton, text: string, id?: string, className?: string) {
|
|
||||||
this.type = type;
|
|
||||||
this.text = text;
|
|
||||||
this.id = id;
|
|
||||||
this.className = className;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): string {
|
if (this.config.className) {
|
||||||
const result = document.createElement('button');
|
button.className += ` ${this.config.className}`;
|
||||||
result.type = "button";
|
|
||||||
result.className = this.type;
|
|
||||||
result.textContent = this.text;
|
|
||||||
|
|
||||||
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.id) {
|
|
||||||
result.id = this.id;
|
|
||||||
}
|
|
||||||
if (this.className) {
|
|
||||||
result.className += ' ' + this.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'
|
// CSS Custom Properties for theming support
|
||||||
$main-font-color: white
|
:root
|
||||||
$main-font-color-hover: lightgrey
|
--main-font: 'Ubuntu', 'Segoe UI', 'Roboto', sans-serif
|
||||||
$main-font-color-focus: lightgrey
|
--main-font-color: #ffffff
|
||||||
$main-font-color-disabled: lightgrey
|
--main-font-color-hover: #f8f9fa
|
||||||
$main-font-color-active: lightgrey
|
--main-font-color-focus: #e9ecef
|
||||||
$main-uschrift-font: 'Ubuntu', Arial
|
--main-font-color-disabled: #6c757d
|
||||||
$primary-color: #007bff
|
--main-font-color-active: #f8f9fa
|
||||||
$primary-color-hover: #0069d9
|
|
||||||
$primary-color-focus: #0062cc
|
--primary-color: #007bff
|
||||||
$primary-color-disabled: #0069d9
|
--primary-color-hover: #0056b3
|
||||||
$primary-color-active: #0062cc
|
--primary-color-focus: #004085
|
||||||
$background-color-content-shadow: #0069d9
|
--primary-color-disabled: #6c757d
|
||||||
$seccond-color: #6c757d
|
--primary-color-active: #004085
|
||||||
$seccondary-color: darkgrey
|
|
||||||
$seccondary-color-hover: black
|
--secondary-color: #6c757d
|
||||||
$seccondary-color-focus: black
|
--secondary-color-hover: #545b62
|
||||||
$seccondary-color-disabled: black
|
--secondary-color-focus: #4e555b
|
||||||
$seccondary-color-active: black
|
--secondary-color-disabled: #adb5bd
|
||||||
$background-color: #77B2FF
|
--secondary-color-active: #4e555b
|
||||||
$background-color-content: rgb(198, 223, 255)
|
|
||||||
$background-color-content-hover: rgb(198, 223, 255)
|
--background-color: #77B2FF
|
||||||
$background-color-content-focus: rgb(198, 223, 255)
|
--background-color-content: #c6dfff
|
||||||
$background-color-content-active: rgb(198, 223, 255)
|
--background-color-content-hover: #b3d7ff
|
||||||
$background-color-content-disabled: rgb(198, 223, 255)
|
--background-color-content-focus: #9fcdff
|
||||||
$logo-image: url('../icons/icon128.png')
|
--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 'root'
|
||||||
@import 'mixin'
|
@import 'mixin'
|
||||||
@import 'content'
|
@import 'content'
|
||||||
|
|
||||||
|
// Bootstrap with legacy import (suppressed warnings via Vite config)
|
||||||
@import "../../node_modules/bootstrap/scss/bootstrap"
|
@import "../../node_modules/bootstrap/scss/bootstrap"
|
||||||
|
|
||||||
body
|
body
|
||||||
|
|
125
src/settings.ts
125
src/settings.ts
|
@ -1,33 +1,120 @@
|
||||||
import { Session } from "./classes/session";
|
import { Session } from './classes/session';
|
||||||
import { BasicButton } from "./components/button";
|
import { BasicButton } from './components/button';
|
||||||
import "./sass/app.sass";
|
import './sass/app.sass';
|
||||||
|
|
||||||
class Settings {
|
class Settings {
|
||||||
|
private session: Session | null = null;
|
||||||
|
|
||||||
private session = Session.getInstance();
|
constructor() {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
private async init(): Promise<void> {
|
||||||
this.renderSettings();
|
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> {
|
||||||
|
if (!this.session) {
|
||||||
|
throw new Error('Session not initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
private async renderSettings(): Promise<void> {
|
const settingsElement = document.getElementById('settings') as HTMLDivElement | null;
|
||||||
const settings = <HTMLDivElement>document.getElementById('settings');
|
if (!settingsElement) {
|
||||||
const saveButton = new BasicButton('success', 'Save', 'saveSettings').render();
|
throw new Error('Settings element not found');
|
||||||
settings.innerHTML = `
|
}
|
||||||
|
|
||||||
|
const saveButton = new BasicButton('success', 'Save', 'saveSettings').render();
|
||||||
|
|
||||||
|
settingsElement.innerHTML = `
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="contentTest">Content Test</label>
|
<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>
|
</div>
|
||||||
|
${saveButton}
|
||||||
`;
|
`;
|
||||||
settings.innerHTML += saveButton;
|
|
||||||
|
|
||||||
const saveSettings = <HTMLButtonElement>document.getElementById('saveSettings');
|
this.attachEventListeners();
|
||||||
saveSettings.addEventListener('click', () => {
|
}
|
||||||
this.session.contentTest = (<HTMLInputElement>document.getElementById('contentTest')).value;
|
|
||||||
Session.save();
|
private attachEventListeners(): void {
|
||||||
Session.reloadSession();
|
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();
|
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';
|
||||||
|
|
108
tools/parse.ts
108
tools/parse.ts
|
@ -1,72 +1,72 @@
|
||||||
import * as fs from "fs";
|
import * as fs from 'fs';
|
||||||
import * as path from "path";
|
import * as path from 'path';
|
||||||
const appConfig = JSON.parse(fs.readFileSync("./app.config.json", "utf8"));
|
const appConfig = JSON.parse(fs.readFileSync('./app.config.json', 'utf8'));
|
||||||
const DEPLOY_TARGET = "./dist/";
|
const DEPLOY_TARGET = './dist/';
|
||||||
|
|
||||||
function findCssFileNames(source: string): string[] {
|
function findCssFileNames(source: string): string[] {
|
||||||
let files: string[] = [];
|
let files: string[] = [];
|
||||||
const dir = fs.readdirSync(source);
|
const dir = fs.readdirSync(source);
|
||||||
dir.forEach(function (file: string) {
|
dir.forEach(function (file: string) {
|
||||||
const sourceFile = path.join(source, file);
|
const sourceFile = path.join(source, file);
|
||||||
const stat = fs.lstatSync(sourceFile);
|
const stat = fs.lstatSync(sourceFile);
|
||||||
if (stat.isDirectory()) {
|
if (stat.isDirectory()) {
|
||||||
files = files.concat(findCssFileNames(sourceFile));
|
files = files.concat(findCssFileNames(sourceFile));
|
||||||
} else {
|
} else {
|
||||||
if (path.extname(sourceFile) == ".css") {
|
if (path.extname(sourceFile) === '.css') {
|
||||||
files.push(file);
|
files.push(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findHtmlFilesRecursive(source: string): string[] {
|
function findHtmlFilesRecursive(source: string): string[] {
|
||||||
let files: string[] = [];
|
let files: string[] = [];
|
||||||
const dir = fs.readdirSync(source);
|
const dir = fs.readdirSync(source);
|
||||||
dir.forEach(function (file: string) {
|
dir.forEach(function (file: string) {
|
||||||
const sourceFile = path.join(source, file);
|
const sourceFile = path.join(source, file);
|
||||||
const stat = fs.lstatSync(sourceFile);
|
const stat = fs.lstatSync(sourceFile);
|
||||||
if (stat.isDirectory()) {
|
if (stat.isDirectory()) {
|
||||||
files = files.concat(findHtmlFilesRecursive(sourceFile));
|
files = files.concat(findHtmlFilesRecursive(sourceFile));
|
||||||
} else {
|
} else {
|
||||||
if (path.extname(sourceFile) == ".html") {
|
if (path.extname(sourceFile) == '.html') {
|
||||||
files.push(sourceFile);
|
files.push(sourceFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceKeywordsInHtmlFile(file: 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;
|
const pairs: { key: string; value: string }[] = appConfig.htmlTemplatePairs;
|
||||||
pairs.forEach(function (pair: { key: string; value: string }) {
|
pairs.forEach(function (pair: { key: string; value: string }) {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
content = content.replaceAll(pair.key, pair.value);
|
content = content.replaceAll(pair.key, pair.value);
|
||||||
});
|
});
|
||||||
file = file.replace("public\\", DEPLOY_TARGET);
|
file = file.replace('public\\', DEPLOY_TARGET);
|
||||||
fs.writeFileSync(file, content);
|
fs.writeFileSync(file, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildHtmlFiles(source: string) {
|
function buildHtmlFiles(source: string) {
|
||||||
const files = findHtmlFilesRecursive(source);
|
const files = findHtmlFilesRecursive(source);
|
||||||
files.forEach(function (file: string) {
|
files.forEach(function (file: string) {
|
||||||
replaceKeywordsInHtmlFile(file);
|
replaceKeywordsInHtmlFile(file);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
findCssFileNames(DEPLOY_TARGET).forEach((file: string) => {
|
findCssFileNames(DEPLOY_TARGET).forEach((file: string) => {
|
||||||
const files = findHtmlFilesRecursive(DEPLOY_TARGET);
|
const files = findHtmlFilesRecursive(DEPLOY_TARGET);
|
||||||
files.forEach(function (htmlFile: string) {
|
files.forEach(function (htmlFile: string) {
|
||||||
let content = fs.readFileSync(htmlFile, "utf8");
|
let content = fs.readFileSync(htmlFile, 'utf8');
|
||||||
content = content.replace(
|
content = content.replace(
|
||||||
"</head>",
|
'</head>',
|
||||||
`<link rel="stylesheet" href="./assets/${file}">\n</head>`
|
`<link rel="stylesheet" href="./assets/${file}">\n</head>`
|
||||||
);
|
);
|
||||||
fs.writeFileSync(htmlFile, content);
|
fs.writeFileSync(htmlFile, content);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
buildHtmlFiles(DEPLOY_TARGET);
|
buildHtmlFiles(DEPLOY_TARGET);
|
||||||
|
|
||||||
console.log("Parsed Files: ", findHtmlFilesRecursive(DEPLOY_TARGET));
|
console.log('Parsed Files: ', findHtmlFilesRecursive(DEPLOY_TARGET));
|
||||||
|
|
|
@ -1,23 +1,151 @@
|
||||||
// @ts-ignore
|
import * as fs from 'fs';
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
const appConfig = JSON.parse(fs.readFileSync('./app.config.json', 'utf8'));
|
interface AppConfig {
|
||||||
const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
AppData: {
|
||||||
const manifestJson = JSON.parse(fs.readFileSync('./public/manifest.json', 'utf8'));
|
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;
|
interface PackageJson {
|
||||||
pkg.name = appConfig.AppData.id;
|
version: string;
|
||||||
pkg.authors = appConfig.AppData.authors;
|
name: string;
|
||||||
pkg.description = appConfig.AppData.description;
|
authors: Array<{
|
||||||
pkg.homepage = appConfig.AppData.homepage;
|
name: string;
|
||||||
pkg.license = appConfig.AppData.license;
|
email: string;
|
||||||
pkg.repository = appConfig.AppData.repository;
|
}>;
|
||||||
pkg.bugs = appConfig.AppData.bugs;
|
description: string;
|
||||||
|
homepage: string;
|
||||||
|
license: string;
|
||||||
|
repository: {
|
||||||
|
type: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
bugs: {
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
manifestJson.version = appConfig.AppData.version;
|
interface ManifestJson {
|
||||||
manifestJson.name = appConfig.AppData.name;
|
version: string;
|
||||||
manifestJson.description = appConfig.AppData.description;
|
name: string;
|
||||||
manifestJson.homepage_url = appConfig.AppData.homepage;
|
description: string;
|
||||||
|
homepage_url: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2));
|
class ConfigSyncer {
|
||||||
fs.writeFileSync('./public/manifest.json', JSON.stringify(manifestJson, null, 2));
|
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);
|
||||||
|
|
40
tools/v2.ts
40
tools/v2.ts
|
@ -3,44 +3,44 @@ const fs = require('fs');
|
||||||
|
|
||||||
const manifest = JSON.parse(fs.readFileSync('./dist/manifest.json', 'utf8'));
|
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)
|
manifest.background.scripts.push(manifest.background.service_worker);
|
||||||
delete manifest.background.type
|
delete manifest.background.type;
|
||||||
delete manifest.background.service_worker
|
delete manifest.background.service_worker;
|
||||||
manifest.background.persistent = true
|
manifest.background.persistent = true;
|
||||||
if (manifest.host_permissions) {
|
if (manifest.host_permissions) {
|
||||||
manifest.permissions.push(manifest.host_permissions)
|
manifest.permissions.push(manifest.host_permissions);
|
||||||
}
|
}
|
||||||
if (manifest.optional_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.host_permissions;
|
||||||
delete manifest.optional_host_permissions
|
delete manifest.optional_host_permissions;
|
||||||
|
|
||||||
let newContentSecurityPolicy = ""
|
let newContentSecurityPolicy = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const policy of manifest.content_security_policy) {
|
for (const policy of manifest.content_security_policy) {
|
||||||
newContentSecurityPolicy += policy.key + "'" + policy.value + "'" + " "
|
newContentSecurityPolicy += policy.key + "'" + policy.value + "'" + ' ';
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
newContentSecurityPolicy = "default-src 'self'"
|
newContentSecurityPolicy = "default-src 'self'";
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest.content_security_policy = newContentSecurityPolicy
|
manifest.content_security_policy = newContentSecurityPolicy;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
manifest.web_accessible_resources = manifest.web_accessible_resources.resources
|
manifest.web_accessible_resources = manifest.web_accessible_resources.resources;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
manifest.web_accessible_resources = []
|
manifest.web_accessible_resources = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (manifest.action) {
|
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));
|
fs.writeFileSync('./dist/manifest.json', JSON.stringify(manifest, null, 2));
|
||||||
|
|
112
tsconfig.json
112
tsconfig.json
|
@ -1,75 +1,49 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"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 */
|
/* Type Checking */
|
||||||
// "incremental": true, /* Enable incremental compilation */
|
"strict": true,
|
||||||
"target": "ESNext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */,
|
"noImplicitAny": true,
|
||||||
"module": "ESNext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
|
"strictNullChecks": true,
|
||||||
"lib": ["ESNext", "DOM"], /* Specify library files to be included in the compilation. */
|
"strictFunctionTypes": true,
|
||||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
"strictBindCallApply": true,
|
||||||
// "checkJs": true, /* Report errors in .js files. */
|
"strictPropertyInitialization": true,
|
||||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
"noImplicitThis": true,
|
||||||
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
"alwaysStrict": true,
|
||||||
"declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
"noUnusedLocals": true,
|
||||||
"sourceMap": true /* Generates corresponding '.map' file. */,
|
"noUnusedParameters": true,
|
||||||
// "outFile": "./public/js/app.js", /* Concatenate and emit output to single file. */
|
"exactOptionalPropertyTypes": true,
|
||||||
"outDir": "./dist/js/" /* Redirect output structure to the directory. */,
|
"noImplicitReturns": true,
|
||||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
"noFallthroughCasesInSwitch": true,
|
||||||
// "composite": true, /* Enable project compilation */
|
"noUncheckedIndexedAccess": true,
|
||||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
"noImplicitOverride": true,
|
||||||
"removeComments": true /* Do not emit comments to output. */,
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
// "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. */
|
|
||||||
|
|
||||||
/* Additional Checks */
|
/* Emit */
|
||||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
"declaration": true,
|
||||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
"declarationMap": true,
|
||||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
"sourceMap": true,
|
||||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
"outDir": "./dist",
|
||||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
"removeComments": false,
|
||||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
|
"importHelpers": true,
|
||||||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
|
||||||
|
|
||||||
/* Module Resolution Options */
|
/* Interop Constraints */
|
||||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
"esModuleInterop": true,
|
||||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
"allowSyntheticDefaultImports": true,
|
||||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
"forceConsistentCasingInFileNames": true,
|
||||||
// "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. */
|
|
||||||
|
|
||||||
/* Source Map Options */
|
/* Completeness */
|
||||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
"skipLibCheck": true
|
||||||
// "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. */
|
"include": ["src/**/*.ts"],
|
||||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
"exclude": ["node_modules", "dist", "tools/*.js"]
|
||||||
|
|
||||||
/* 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. */
|
|
||||||
},
|
|
||||||
"include": ["./src/**/*.ts", "./src/*.ts"],
|
|
||||||
"exclude": ["node_modules", "./tools/*.ts", "./dist/*.ts", "./dist/*.js"],
|
|
||||||
"types": ["node", "chrome"]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,71 @@
|
||||||
import { defineConfig } from "vite";
|
import { resolve } from 'path';
|
||||||
import { resolve } from "path";
|
import { defineConfig } from 'vite';
|
||||||
import tsconfigPaths from "vite-tsconfig-paths";
|
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
build: {
|
build: {
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
app: resolve(__dirname, "src/app.ts"),
|
app: resolve(__dirname, 'src/app.ts'),
|
||||||
settings: resolve(__dirname, "src/settings.ts"),
|
settings: resolve(__dirname, 'src/settings.ts'),
|
||||||
background: resolve(__dirname, "src/background.ts"),
|
background: resolve(__dirname, 'src/background.ts'),
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
entryFileNames: "[name].js",
|
entryFileNames: '[name].js',
|
||||||
dir: resolve(__dirname, "dist"),
|
chunkFileNames: 'chunks/[name]-[hash].js',
|
||||||
},
|
assetFileNames: assetInfo => {
|
||||||
},
|
if (assetInfo.name?.endsWith('.css')) {
|
||||||
sourcemap: true,
|
return 'assets/[name]-[hash][extname]';
|
||||||
},
|
}
|
||||||
plugins: [tsconfigPaths()],
|
return 'assets/[name]-[hash][extname]';
|
||||||
resolve: {
|
},
|
||||||
extensions: [".tsx", ".ts", ".scss", ".sass"],
|
dir: resolve(__dirname, 'dist'),
|
||||||
},
|
},
|
||||||
esbuild: {
|
},
|
||||||
include: /.*\.tsx?$/,
|
sourcemap: true,
|
||||||
exclude: [/node_modules/, /dist/],
|
target: 'es2022',
|
||||||
},
|
minify: 'esbuild',
|
||||||
|
reportCompressedSize: false,
|
||||||
|
chunkSizeWarningLimit: 1000,
|
||||||
|
},
|
||||||
|
plugins: [tsconfigPaths()],
|
||||||
|
resolve: {
|
||||||
|
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