diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9205a28..47fd671 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,105 +1,105 @@ name: CI on: - pull_request: - branches: [main] - push: - branches: [main] + pull_request: + branches: [main] + push: + branches: [main] jobs: - lint-and-format: - name: Lint & Format Check - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "20" - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - - name: Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - name: Setup pnpm cache - uses: actions/cache@v4 - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: Install dependencies - run: pnpm install - - - name: Run linter - run: pnpm lint - - - name: Run formatter check - run: pnpm format - - - name: Run type check - run: pnpm check - - build: - name: Build Extension - runs-on: ubuntu-latest - needs: lint-and-format - - strategy: - matrix: - browser: [chrome, firefox] - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "20" - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - - name: Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - name: Setup pnpm cache - uses: actions/cache@v4 - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: Install dependencies - run: pnpm install - - - name: Build for ${{ matrix.browser }} - run: | - if [ "${{ matrix.browser }}" = "firefox" ]; then - pnpm build:firefox - else - pnpm build - fi - - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: extension-${{ matrix.browser }} - path: .output/${{ matrix.browser == 'firefox' && 'firefox-mv2' || 'chrome-mv3' }} - retention-days: 7 + lint-and-format: + name: Lint & Format Check + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - name: Setup pnpm cache + uses: actions/cache@v4 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: pnpm install + + - name: Run linter + run: pnpm lint + + - name: Run formatter check + run: pnpm format + + - name: Run type check + run: pnpm check + + build: + name: Build Extension + runs-on: ubuntu-latest + needs: lint-and-format + + strategy: + matrix: + browser: [chrome, firefox] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - name: Setup pnpm cache + uses: actions/cache@v4 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: pnpm install + + - name: Build for ${{ matrix.browser }} + run: | + if [ "${{ matrix.browser }}" = "firefox" ]; then + pnpm build:firefox + else + pnpm build + fi + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: extension-${{ matrix.browser }} + path: .output/${{ matrix.browser == 'firefox' && 'firefox-mv2' || 'chrome-mv3' }} + retention-days: 7 diff --git a/.lintstagedrc.json b/.lintstagedrc.json index 3f39e00..4489242 100644 --- a/.lintstagedrc.json +++ b/.lintstagedrc.json @@ -1,4 +1,4 @@ { - "*.{js,ts,svelte}": ["prettier --write", "eslint --fix"], - "*.{json,md,css,html}": ["prettier --write"] + "*.{js,ts,svelte}": ["prettier --write", "eslint --fix"], + "*.{json,md,css,html}": ["prettier --write"] } diff --git a/.prettierrc b/.prettierrc index 43a65ec..79c2903 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,17 +1,17 @@ { - "semi": true, - "singleQuote": false, - "tabWidth": 2, - "useTabs": false, - "trailingComma": "es5", - "printWidth": 100, - "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], - "overrides": [ - { - "files": "*.svelte", - "options": { - "parser": "svelte" - } - } - ] + "semi": true, + "singleQuote": false, + "tabWidth": 4, + "useTabs": false, + "trailingComma": "es5", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ] } diff --git a/README.md b/README.md index b11a512..1cd7845 100644 --- a/README.md +++ b/README.md @@ -49,16 +49,16 @@ pnpm build:firefox # For Firefox **Chrome/Chromium:** -- Open `chrome://extensions/` -- Enable "Developer mode" -- Click "Load unpacked" -- Select the `.output/chrome-mv3` directory +- Open `chrome://extensions/` +- Enable "Developer mode" +- Click "Load unpacked" +- Select the `.output/chrome-mv3` directory **Firefox:** -- Open `about:debugging#/runtime/this-firefox` -- Click "Load Temporary Add-on" -- Select the `manifest.json` file from `.output/firefox-mv2` +- Open `about:debugging#/runtime/this-firefox` +- Click "Load Temporary Add-on" +- Select the `manifest.json` file from `.output/firefox-mv2` ### Development Mode @@ -77,8 +77,8 @@ pnpm dev:firefox # Firefox with hot reload 3. **Click the extension icon** to open the popup 4. **Click "Manage Filters"** to add your first filter 5. **Create a filter** with a name and GitHub search syntax: - - Name: `Hide Dependabot PRs` - - Value: `-author:app/dependabot` + - Name: `Hide Dependabot PRs` + - Value: `-author:app/dependabot` 6. **Save** and refresh the PR page - filter applies automatically! ### Filter Examples @@ -106,11 +106,11 @@ See [GitHub's search syntax documentation](https://docs.github.com/en/search-git ### Technology Stack -- **[WXT](https://wxt.dev/)** - Cross-browser extension framework -- **[Svelte 5](https://svelte.dev/)** - Reactive UI framework with runes API -- **[TypeScript](https://www.typescriptlang.org/)** - Type-safe development -- **[Tailwind CSS 4](https://tailwindcss.com/)** - Utility-first styling -- **[Vite](https://vitejs.dev/)** - Fast build tool +- **[WXT](https://wxt.dev/)** - Cross-browser extension framework +- **[Svelte 5](https://svelte.dev/)** - Reactive UI framework with runes API +- **[TypeScript](https://www.typescriptlang.org/)** - Type-safe development +- **[Tailwind CSS 4](https://tailwindcss.com/)** - Utility-first styling +- **[Vite](https://vitejs.dev/)** - Fast build tool ### Project Structure @@ -153,24 +153,24 @@ pullscope/ **Filter Store** (`src/lib/stores/filters.ts`) -- Centralized state management -- Automatic sync with browser storage -- Real-time updates across components -- Storage change listener for cross-tab sync +- Centralized state management +- Automatic sync with browser storage +- Real-time updates across components +- Storage change listener for cross-tab sync **GitHub Integration** (`src/lib/github.ts`) -- Multiple fallback selectors for robustness -- Token-based duplicate detection -- Form submission triggering -- Proper event dispatching +- Multiple fallback selectors for robustness +- Token-based duplicate detection +- Form submission triggering +- Proper event dispatching **Content Script** (`src/entrypoints/content/GithubPRFilter.svelte`) -- Waits for DOM elements to load -- Debounced URL change handler -- Real-time filter toggle listener -- Toast notifications +- Waits for DOM elements to load +- Debounced URL change handler +- Real-time filter toggle listener +- Toast notifications ## šŸ¤ Contributing @@ -190,9 +190,9 @@ Contributions are welcome! Please feel free to submit a Pull Request. ### Guidelines -- Follow the existing code style -- Use technology standards -- Update documentation as needed +- Follow the existing code style +- Use technology standards +- Update documentation as needed ## šŸ“ License @@ -202,10 +202,10 @@ MIT License - see [LICENSE](LICENSE) file for details **Guido Dinello** -- GitHub: [@guidodinello](https://github.com/guidodinello) -- Extension ID: `pullscope@guidodinello.dev` +- GitHub: [@guidodinello](https://github.com/guidodinello) +- Extension ID: `pullscope@guidodinello.dev` ## šŸ™ Acknowledgments -- Built with [WXT](https://wxt.dev/) -- Template used [oneezy/svelte-5-extension](https://github.com/oneezy/svelte-5-extension) +- Built with [WXT](https://wxt.dev/) +- Template used [oneezy/svelte-5-extension](https://github.com/oneezy/svelte-5-extension) diff --git a/eslint.config.js b/eslint.config.js index e90ecae..174b279 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,102 +5,123 @@ import sveltePlugin from "eslint-plugin-svelte"; import svelteParser from "svelte-eslint-parser"; export default [ - // Ignore patterns - { - ignores: [".output/**", ".wxt/**", "dist/**", "node_modules/**", "*.config.js", "*.config.ts"], - }, - - // JavaScript/TypeScript files - { - files: ["**/*.js", "**/*.ts"], - plugins: { - "@typescript-eslint": tseslint, - }, - languageOptions: { - parser: tsparser, - parserOptions: { - ecmaVersion: 2022, - sourceType: "module", - }, - globals: { - // Browser globals - browser: "readonly", - document: "readonly", - window: "readonly", - crypto: "readonly", - console: "readonly", - setTimeout: "readonly", - clearTimeout: "readonly", - Event: "readonly", - KeyboardEvent: "readonly", - HTMLInputElement: "readonly", - Element: "readonly", - MutationObserver: "readonly", - // WXT globals - defineBackground: "readonly", - defineContentScript: "readonly", - }, + // Ignore patterns + { + ignores: [ + ".output/**", + ".wxt/**", + "dist/**", + "node_modules/**", + "*.config.js", + "*.config.ts", + ], }, - rules: { - ...js.configs.recommended.rules, - ...tseslint.configs.recommended.rules, - "@typescript-eslint/no-unused-vars": [ - "error", - { - argsIgnorePattern: "^_", - varsIgnorePattern: "^_", + + // JavaScript/TypeScript files + { + files: ["**/*.js", "**/*.ts"], + plugins: { + "@typescript-eslint": tseslint, + }, + languageOptions: { + parser: tsparser, + parserOptions: { + ecmaVersion: 2022, + sourceType: "module", + }, + globals: { + // Browser globals + browser: "readonly", + document: "readonly", + window: "readonly", + crypto: "readonly", + console: "readonly", + setTimeout: "readonly", + clearTimeout: "readonly", + Event: "readonly", + KeyboardEvent: "readonly", + HTMLInputElement: "readonly", + Element: "readonly", + MutationObserver: "readonly", + // WXT globals + defineBackground: "readonly", + defineContentScript: "readonly", + // Svelte 5 runes + $state: "readonly", + $derived: "readonly", + $effect: "readonly", + $props: "readonly", + $bindable: "readonly", + $inspect: "readonly", + }, + }, + rules: { + ...js.configs.recommended.rules, + ...tseslint.configs.recommended.rules, + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + }, + ], + "@typescript-eslint/no-explicit-any": "warn", + "no-console": ["warn", { allow: ["warn", "error"] }], }, - ], - "@typescript-eslint/no-explicit-any": "warn", - "no-console": ["warn", { allow: ["warn", "error"] }], }, - }, - // Svelte files - { - files: ["**/*.svelte"], - plugins: { - svelte: sveltePlugin, - "@typescript-eslint": tseslint, - }, - languageOptions: { - parser: svelteParser, - parserOptions: { - parser: tsparser, - extraFileExtensions: [".svelte"], - }, - globals: { - // Browser globals - browser: "readonly", - document: "readonly", - window: "readonly", - crypto: "readonly", - console: "readonly", - setTimeout: "readonly", - clearTimeout: "readonly", - Event: "readonly", - KeyboardEvent: "readonly", - HTMLInputElement: "readonly", - Element: "readonly", - MutationObserver: "readonly", - // WXT globals - defineBackground: "readonly", - defineContentScript: "readonly", - }, - }, - rules: { - ...sveltePlugin.configs.recommended.rules, - ...tseslint.configs.recommended.rules, - "@typescript-eslint/no-unused-vars": [ - "error", - { - argsIgnorePattern: "^_", - varsIgnorePattern: "^_", + // Svelte files + { + files: ["**/*.svelte"], + plugins: { + svelte: sveltePlugin, + "@typescript-eslint": tseslint, + }, + languageOptions: { + parser: svelteParser, + parserOptions: { + parser: tsparser, + extraFileExtensions: [".svelte"], + }, + globals: { + // Browser globals + browser: "readonly", + document: "readonly", + window: "readonly", + crypto: "readonly", + console: "readonly", + setTimeout: "readonly", + clearTimeout: "readonly", + Event: "readonly", + KeyboardEvent: "readonly", + HTMLInputElement: "readonly", + Element: "readonly", + MutationObserver: "readonly", + // WXT globals + defineBackground: "readonly", + defineContentScript: "readonly", + // Svelte 5 runes + $state: "readonly", + $derived: "readonly", + $effect: "readonly", + $props: "readonly", + $bindable: "readonly", + $inspect: "readonly", + }, + }, + rules: { + ...sveltePlugin.configs.recommended.rules, + ...tseslint.configs.recommended.rules, + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + }, + ], + "@typescript-eslint/no-explicit-any": "warn", + "no-console": ["warn", { allow: ["warn", "error"] }], + "svelte/no-at-html-tags": "warn", }, - ], - "@typescript-eslint/no-explicit-any": "warn", - "no-console": ["warn", { allow: ["warn", "error"] }], - "svelte/no-at-html-tags": "warn", }, - }, ]; diff --git a/package.json b/package.json index ff78077..a584f1d 100644 --- a/package.json +++ b/package.json @@ -1,75 +1,75 @@ { - "name": "pullscope", - "description": "Automatically apply custom filters to GitHub Pull Request pages with real-time sync", - "private": true, - "version": "0.0.1", - "type": "module", - "keywords": [ - "github", - "pull-request", - "filters", - "browser-extension", - "chrome-extension", - "firefox-extension", - "wxt", - "svelte", - "svelte-5", - "tailwind", - "tailwind-4" - ], - "scripts": { - "dev": "wxt", - "dev:firefox": "wxt -b firefox", - "build": "wxt build", - "build:firefox": "wxt build -b firefox", - "zip": "wxt zip", - "zip:firefox": "wxt zip -b firefox", - "check": "svelte-check --tsconfig ./tsconfig.json", - "lint": "eslint .", - "lint:fix": "eslint . --fix", - "format": "prettier --write .", - "format:check": "prettier --check .", - "lint-staged": "lint-staged", - "fix": "pnpm run lint:fix && pnpm run format && pnpm run check", - "generate-icons": "node scripts/generate-icons.mjs", - "postinstall": "wxt prepare", - "clean": "git clean -fXd", - "prepare": "husky" - }, - "devDependencies": { - "@eslint/js": "^9.39.1", - "@resvg/resvg-js": "^2.6.2", - "@sveltejs/vite-plugin-svelte": "4.0.0-next.7", - "@tailwindcss/forms": "^0.5.9", - "@tailwindcss/typography": "^0.5.15", - "@tailwindcss/vite": "4.0.0-alpha.24", - "@tsconfig/svelte": "^5.0.4", - "@types/chrome": "^0.0.269", - "@typescript-eslint/eslint-plugin": "^8.46.4", - "@typescript-eslint/parser": "^8.46.4", - "@wxt-dev/module-svelte": "^1.0.1", - "eslint": "^9.39.1", - "eslint-plugin-svelte": "^3.13.0", - "husky": "^9.1.7", - "lint-staged": "^16.2.6", - "prettier": "^3.3.3", - "prettier-plugin-svelte": "^3.2.6", - "prettier-plugin-tailwindcss": "^0.5.14", - "svelte": "5.0.0-next.245", - "svelte-check": "^3.8.6", - "svelte-eslint-parser": "^1.4.0", - "tailwindcss": "4.0.0-alpha.24", - "tslib": "^2.7.0", - "typescript": "^5.6.2", - "vite": "^5.4.5", - "vite-plugin-top-level-await": "^1.4.4", - "vite-plugin-wasm": "^3.3.0", - "wxt": "^0.19.9" - }, - "overrides": { - "@sveltejs/vite-plugin-svelte": "^4.0.0-next" - }, - "resolutions": { - "@sveltejs/vite-plugin-svelte": "^4.0.0-next" - } + "name": "pullscope", + "description": "Automatically apply custom filters to GitHub Pull Request pages with real-time sync", + "private": true, + "version": "0.0.1", + "type": "module", + "keywords": [ + "github", + "pull-request", + "filters", + "browser-extension", + "chrome-extension", + "firefox-extension", + "wxt", + "svelte", + "svelte-5", + "tailwind", + "tailwind-4" + ], + "scripts": { + "dev": "wxt", + "dev:firefox": "wxt -b firefox", + "build": "wxt build", + "build:firefox": "wxt build -b firefox", + "zip": "wxt zip", + "zip:firefox": "wxt zip -b firefox", + "check": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write .", + "format:check": "prettier --check .", + "lint-staged": "lint-staged", + "fix": "pnpm run lint:fix && pnpm run format && pnpm run check", + "generate-icons": "node scripts/generate-icons.mjs", + "postinstall": "wxt prepare", + "clean": "git clean -fXd", + "prepare": "husky" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@resvg/resvg-js": "^2.6.2", + "@sveltejs/vite-plugin-svelte": "4.0.0-next.7", + "@tailwindcss/forms": "^0.5.9", + "@tailwindcss/typography": "^0.5.15", + "@tailwindcss/vite": "4.0.0-alpha.24", + "@tsconfig/svelte": "^5.0.4", + "@types/chrome": "^0.0.269", + "@typescript-eslint/eslint-plugin": "^8.46.4", + "@typescript-eslint/parser": "^8.46.4", + "@wxt-dev/module-svelte": "^1.0.1", + "eslint": "^9.39.1", + "eslint-plugin-svelte": "^3.13.0", + "husky": "^9.1.7", + "lint-staged": "^16.2.6", + "prettier": "^3.3.3", + "prettier-plugin-svelte": "^3.2.6", + "prettier-plugin-tailwindcss": "^0.5.14", + "svelte": "5.0.0-next.245", + "svelte-check": "^3.8.6", + "svelte-eslint-parser": "^1.4.0", + "tailwindcss": "4.0.0-alpha.24", + "tslib": "^2.7.0", + "typescript": "^5.6.2", + "vite": "^5.4.5", + "vite-plugin-top-level-await": "^1.4.4", + "vite-plugin-wasm": "^3.3.0", + "wxt": "^0.19.9" + }, + "overrides": { + "@sveltejs/vite-plugin-svelte": "^4.0.0-next" + }, + "resolutions": { + "@sveltejs/vite-plugin-svelte": "^4.0.0-next" + } } diff --git a/scripts/generate-icons.mjs b/scripts/generate-icons.mjs index ae738fb..b8786c8 100755 --- a/scripts/generate-icons.mjs +++ b/scripts/generate-icons.mjs @@ -17,22 +17,22 @@ const svg = readFileSync(svgPath, "utf-8"); console.log("Generating PNG icons from SVG...\n"); for (const size of sizes) { - const outputPath = join(outputDir, `icon-${size}.png`); + const outputPath = join(outputDir, `icon-${size}.png`); - // Render SVG to PNG with resvg - const resvg = new Resvg(svg, { - fitTo: { - mode: "width", - value: size, - }, - }); + // Render SVG to PNG with resvg + const resvg = new Resvg(svg, { + fitTo: { + mode: "width", + value: size, + }, + }); - const pngData = resvg.render(); - const pngBuffer = pngData.asPng(); + const pngData = resvg.render(); + const pngBuffer = pngData.asPng(); - writeFileSync(outputPath, pngBuffer); + writeFileSync(outputPath, pngBuffer); - console.log(`āœ“ Generated ${size}x${size} icon: icon-${size}.png`); + console.log(`āœ“ Generated ${size}x${size} icon: icon-${size}.png`); } console.log("\nāœ… All icons generated successfully!"); diff --git a/src/app.css b/src/app.css index db8bc70..7f8b4bb 100644 --- a/src/app.css +++ b/src/app.css @@ -4,44 +4,172 @@ @plugin "@tailwindcss/forms"; @theme { - /* Primary brand colors */ - --color-primary: #05d9ff; - --color-primary-hover: #04b8d9; - --color-primary-dark: #0396b3; - - /* Accent color */ - --color-accent: #f535aa; - --color-accent-hover: #dc2f99; - - /* Semantic colors */ - --color-success: #10b981; - --color-success-light: #d1fae5; - --color-success-dark: #065f46; - --color-error: #ef4444; - --color-error-light: #fee2e2; - --color-error-dark: #991b1b; - --color-warning: #f59e0b; - --color-warning-light: #fef3c7; - --color-warning-dark: #92400e; - --color-info: #3b82f6; - --color-info-light: #dbeafe; - --color-info-dark: #1e40af; - - /* Neutrals */ - --color-bg-primary: #ffffff; - --color-bg-secondary: #f9fafb; - --color-bg-tertiary: #f3f4f6; - --color-text-primary: #111827; - --color-text-secondary: #6b7280; - --color-text-tertiary: #9ca3af; - --color-border: #e5e7eb; - --color-border-light: #f3f4f6; + /* Primary - GitHub blue*/ + --color-primary: light-dark(#0969da, #58a6ff); + --color-primary-hover: light-dark(#0550ae, #1f6feb); + --color-primary-dark: light-dark(#033d8b, #0969da); + + /* Accent - Purple */ + --color-accent: light-dark(#8250df, #a371f7); + --color-accent-hover: light-dark(#6639ba, #8b5cf6); + + /* Success - Green*/ + --color-success: light-dark(#1a7f37, #3fb950); + --color-success-light: light-dark(#dafbe1, #1b4721); + --color-success-dark: light-dark(#044f1e, #56d364); + + /* Error - Red*/ + --color-error: light-dark(#d1242f, #f85149); + --color-error-light: light-dark(#ffebe9, #4c1f1f); + --color-error-dark: light-dark(#8e1519, #da3633); + + /* Warning - Orange/Yellow*/ + --color-warning: light-dark(#9a6700, #e3b341); + --color-warning-light: light-dark(#fff8c5, #3d2f00); + --color-warning-dark: light-dark(#7d4e00, #f0b72f); + + /* Info - Blue matching primary theme */ + --color-info: light-dark(#0969da, #58a6ff); + --color-info-light: light-dark(#ddf4ff, #0c2d6b); + --color-info-dark: light-dark(#0550ae, #1f6feb); + + /* Neutrals - GitHub-inspired with proper contrast */ + --color-bg-primary: light-dark(#ffffff, #0d1117); + --color-bg-secondary: light-dark(#f6f8fa, #161b22); + --color-bg-tertiary: light-dark(#eaeef2, #21262d); + + /* Text colors with WCAG AAA contrast ratios */ + --color-text-primary: light-dark(#24292f, #e6edf3); + --color-text-secondary: light-dark(#57606a, #8d96a0); + --color-text-tertiary: light-dark(#6e7781, #7d8590); + + /* Borders */ + --color-border: light-dark(#d1d9e0, #30363d); + --color-border-light: light-dark(#d8dee4, #21262d); + + /* Button text colors for contrast */ + --color-btn-text-primary: light-dark(#ffffff, #0d1117); + --color-btn-text-secondary: light-dark(#24292f, #e6edf3); } -/* Base -*************************************************/ @layer base { - body { - @apply bg-bg-secondary text-text-primary; - } + body { + @apply bg-bg-secondary text-text-primary; + } + + /* Ensure color scheme is set for light-dark() to work */ + :root { + color-scheme: light dark; + } + + /* Improve button text contrast */ + button { + font-weight: 500; + } +} + +@layer components { + .filter-badge { + @apply inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold; + } + + .filter-badge-enabled { + /* Light mode: dark text on light bg, Dark mode: light text on dark bg */ + background-color: light-dark(#dafbe1, #1b4721); + color: light-dark(#044f1e, #56d364); + } + + .filter-badge-disabled { + background-color: light-dark(#eaeef2, #21262d); + color: light-dark(#57606a, #8d96a0); + } + + /* Primary button */ + .btn-primary { + background-color: light-dark(#0969da, #238636); + color: #ffffff; + } + + .btn-primary:hover { + background-color: light-dark(#0550ae, #2ea043); + } + + .btn-primary:focus-visible { + outline: 2px solid light-dark(#0969da, #238636); + outline-offset: 2px; + } + + /* Secondary button */ + .btn-secondary { + background-color: light-dark(#f6f8fa, #21262d); + color: light-dark(#24292f, #e6edf3); + border: 1px solid light-dark(#d1d9e0, #30363d); + } + + .btn-secondary:hover { + background-color: light-dark(#eaeef2, #30363d); + } + + /* Danger button */ + .btn-danger { + background-color: light-dark(#d1242f, #da3633); + color: #ffffff; + } + + .btn-danger:hover { + background-color: light-dark(#a40e26, #b62324); + } + + /* Success button */ + .btn-success { + background-color: light-dark(#1a7f37, #238636); + color: #ffffff; + } + + .btn-success:hover { + background-color: light-dark(#146c2e, #2ea043); + } + + /* Input fields */ + input[type="text"], + input[type="checkbox"], + select, + textarea { + background-color: light-dark(#ffffff, #0d1117); + color: light-dark(#24292f, #e6edf3); + border-color: light-dark(#d1d9e0, #30363d); + } + + input[type="text"]:focus, + select:focus, + textarea:focus { + border-color: light-dark(#0969da, #58a6ff); + outline: 2px solid light-dark(#0969da80, #58a6ff40); + outline-offset: -1px; + } + + /* Toast notifications */ + .toast-success { + background-color: light-dark(#1a7f37, #238636); + border-color: light-dark(#044f1e, #2ea043); + color: #ffffff; + } + + .toast-error { + background-color: light-dark(#d1242f, #da3633); + border-color: light-dark(#8e1519, #f85149); + color: #ffffff; + } + + .toast-warning { + background-color: light-dark(#9a6700, #9a6700); + border-color: light-dark(#7d4e00, #bb8800); + color: #ffffff; + } + + .toast-info { + background-color: light-dark(#0969da, #0969da); + border-color: light-dark(#0550ae, #1f6feb); + color: #ffffff; + } } diff --git a/src/entrypoints/background.ts b/src/entrypoints/background.ts index aab47fd..11447e3 100644 --- a/src/entrypoints/background.ts +++ b/src/entrypoints/background.ts @@ -1,102 +1,107 @@ import { getFilters, saveFilters } from "@/lib/storage"; import type { PRFilter } from "@/lib/types/filter"; import type { - ExtensionMessage, - MessageResponse, - ApplyFiltersNowMessage, + ExtensionMessage, + MessageResponse, + ApplyFiltersNowMessage, } from "@/lib/types/messages"; import { MESSAGE_ACTIONS, GITHUB_PATTERNS, CONTEXT_MENU_IDS } from "@/lib/constants"; import { logger } from "@/lib/utils/logger"; +const DEFAULT_FILTERS = [ + { + id: crypto.randomUUID(), + name: "Hide Dependabot PRs", + value: "-author:app/dependabot", + enabled: true, + }, + { + id: crypto.randomUUID(), + name: "Only Open PRs", + value: "is:open", + enabled: true, + }, + { + id: crypto.randomUUID(), + name: "Is a PR", + value: "is:pr", + enabled: true, + }, +] satisfies PRFilter[]; + export default defineBackground(() => { - logger.info("GitHub PR Filters background script initialized"); + logger.info("GitHub PR Filters background script initialized"); - // Set up default filters if none exist - browser.runtime.onInstalled.addListener(async (details) => { - if (details.reason === "install") { - // Check if we already have filters - const existingFilters = await getFilters(); + // Set up default filters if none exist + browser.runtime.onInstalled.addListener(async (details) => { + if (details.reason === "install") { + // Check if we already have filters + const existingFilters = await getFilters(); - if (existingFilters.length === 0) { - // Add some default filters as examples - const defaultFilters: PRFilter[] = [ - { - id: crypto.randomUUID(), - name: "Hide Dependabot PRs", - value: "-author:app/dependabot", - enabled: true, - }, - { - id: crypto.randomUUID(), - name: "Only Open PRs", - value: "is:open", - enabled: true, - }, - ]; + if (existingFilters.length === 0) { + await saveFilters(DEFAULT_FILTERS); + logger.info("Default filters installed"); + } - await saveFilters(defaultFilters); - logger.info("Default filters installed"); - } + browser.tabs.create({ + url: browser.runtime.getURL("/options.html"), + }); + } + }); - browser.tabs.create({ - url: browser.runtime.getURL("/options.html"), - }); - } - }); + // Listen for commands from browser action + browser.runtime.onMessage.addListener( + async (message: ExtensionMessage, _sender): Promise => { + if (message.action === MESSAGE_ACTIONS.APPLY_FILTERS) { + const tabs = await browser.tabs.query({ + active: true, + currentWindow: true, + }); + const currentTab = tabs[0]; - // Listen for commands from browser action - browser.runtime.onMessage.addListener( - async (message: ExtensionMessage, _sender): Promise => { - if (message.action === MESSAGE_ACTIONS.APPLY_FILTERS) { - const tabs = await browser.tabs.query({ - active: true, - currentWindow: true, - }); - const currentTab = tabs[0]; + if (currentTab?.id) { + // Check if the tab URL is a GitHub PR page + if ( + currentTab.url?.includes(GITHUB_PATTERNS.PR_PAGE_PARTIAL) && + currentTab.url?.includes(GITHUB_PATTERNS.PR_PATH_PARTIAL) + ) { + // Send message to content script to apply filters + try { + await browser.tabs.sendMessage(currentTab.id, { + action: MESSAGE_ACTIONS.APPLY_FILTERS_NOW, + } satisfies ApplyFiltersNowMessage); + return { success: true }; + } catch (err) { + logger.error("Failed to send message to tab", err); + return { + success: false, + message: "Failed to communicate with page", + }; + } + } + } - if (currentTab?.id) { - // Check if the tab URL is a GitHub PR page - if ( - currentTab.url?.includes(GITHUB_PATTERNS.PR_PAGE_PARTIAL) && - currentTab.url?.includes(GITHUB_PATTERNS.PR_PATH_PARTIAL) - ) { - // Send message to content script to apply filters - try { - await browser.tabs.sendMessage(currentTab.id, { - action: MESSAGE_ACTIONS.APPLY_FILTERS_NOW, - } satisfies ApplyFiltersNowMessage); - return { success: true }; - } catch (err) { - logger.error("Failed to send message to tab", err); - return { - success: false, - message: "Failed to communicate with page", - }; + return { success: false, message: "Not a GitHub PR page" }; } - } - } - return { success: false, message: "Not a GitHub PR page" }; - } - - return undefined; - } - ); + return undefined; + } + ); - // Add context menu for quickly applying filters - browser.contextMenus?.create({ - id: CONTEXT_MENU_IDS.APPLY_PR_FILTERS, - title: "Apply PR Filters", - contexts: ["page"], - documentUrlPatterns: [GITHUB_PATTERNS.PR_PAGE_QUERY], - }); + // Add context menu for quickly applying filters + browser.contextMenus?.create({ + id: CONTEXT_MENU_IDS.APPLY_PR_FILTERS, + title: "Apply PR Filters", + contexts: ["page"], + documentUrlPatterns: [GITHUB_PATTERNS.PR_PAGE_QUERY], + }); - // Handle context menu clicks - browser.contextMenus?.onClicked.addListener((info, tab) => { - if (info.menuItemId === CONTEXT_MENU_IDS.APPLY_PR_FILTERS && tab?.id) { - browser.tabs.sendMessage(tab.id, { - action: MESSAGE_ACTIONS.APPLY_FILTERS_NOW, - } satisfies ApplyFiltersNowMessage); - } - }); + // Handle context menu clicks + browser.contextMenus?.onClicked.addListener((info, tab) => { + if (info.menuItemId === CONTEXT_MENU_IDS.APPLY_PR_FILTERS && tab?.id) { + browser.tabs.sendMessage(tab.id, { + action: MESSAGE_ACTIONS.APPLY_FILTERS_NOW, + } satisfies ApplyFiltersNowMessage); + } + }); }); diff --git a/src/entrypoints/content.ts b/src/entrypoints/content.ts index d8fa000..83cc4b6 100644 --- a/src/entrypoints/content.ts +++ b/src/entrypoints/content.ts @@ -1,21 +1,21 @@ import "../app.css"; import { mount } from "svelte"; -import GithubPrFilter from "./content/GithubPRFilter.svelte"; +import GithubPrFilter from "@/entrypoints/content/GithubPRFilter.svelte"; import { logger } from "@/lib/utils/logger"; export default defineContentScript({ - matches: ["https://github.com/*/*/pulls*"], - main() { - logger.info("GitHub PR Filters Content Script activated"); + matches: ["https://github.com/*/*/pulls*"], + main() { + logger.info("GitHub PR Filters Content Script activated"); - // Create a container for our component - const container = document.createElement("div"); - container.id = "github-pr-filters-container"; - document.body.appendChild(container); + // Create a container for our component + const container = document.createElement("div"); + container.id = "github-pr-filters-container"; + document.body.appendChild(container); - // Mount the Svelte component - mount(GithubPrFilter, { - target: container, - }); - }, + // Mount the Svelte component + mount(GithubPrFilter, { + target: container, + }); + }, }); diff --git a/src/entrypoints/content/GithubPRFilter.svelte b/src/entrypoints/content/GithubPRFilter.svelte index 2000fbf..a4764b9 100644 --- a/src/entrypoints/content/GithubPRFilter.svelte +++ b/src/entrypoints/content/GithubPRFilter.svelte @@ -1,121 +1,126 @@ {#if toastMessage} - (toastMessage = "")} - /> + (toastMessage = "")} + /> {/if} diff --git a/src/entrypoints/options/Options.svelte b/src/entrypoints/options/Options.svelte index 4fdd64f..b2ae529 100644 --- a/src/entrypoints/options/Options.svelte +++ b/src/entrypoints/options/Options.svelte @@ -1,224 +1,182 @@
-
-

GitHub PR Filters

- Logo -
- - - -
-

- Configure filters to be automatically applied when you visit GitHub Pull Request pages. -

-

- These filters use GitHub's search syntax. For example, add - -author:app/dependabot - to hide Dependabot PRs. -

-
- - {#if isLoading} -
-
+
+

GitHub PR Filters

+ Logo
- {:else} - {#if !editingFilter && !showAddForm} -
- -
- {/if} - - {#if editingFilter} -
- -
- {/if} - {#if showAddForm} -
- -
- {/if} + filterManager.retry()} /> + +
+

+ Configure filters to be automatically applied when you visit GitHub Pull Request pages. +

+

+ These filters use GitHub's search syntax. For example, add + -author:app/dependabot + to hide Dependabot PRs. +

+
- {#if filters.length === 0 && !showAddForm} -
-

You haven't added any filters yet.

-
+ {#if filterManager.isLoading} +
+
+
{:else} -

Your Filters

-
- {#each filters as filter (filter.id)} -
-
-
- {filter.name} - +
-
- {filter.value} -
+ + Add New Filter + +
+ {/if} + + {#if editingFilter} +
+
+ {/if} -
- - - + {#if showAddForm} +
+ +
+ {/if} + + {#if filterManager.filters.length === 0 && !showAddForm} +
+

You haven't added any filters yet.

+
+ {:else} +

Your Filters

+
+ {#each filterManager.filters as filter (filter.id)} +
+
+
+ {filter.name} + + {filter.enabled ? "Enabled" : "Disabled"} + +
+
+ {filter.value} +
+
+ +
+ + + +
+
+ {/each}
-
- {/each} -
+ {/if} {/if} - {/if} -
-

Pullscope Extension

-

by guidodinello

-
+
+

Pullscope Extension

+

by guidodinello

+
diff --git a/src/entrypoints/options/index.html b/src/entrypoints/options/index.html index f0fb217..e2530b8 100644 --- a/src/entrypoints/options/index.html +++ b/src/entrypoints/options/index.html @@ -1,12 +1,12 @@ - - - - Pullscope Options - - -
- - + + + + Pullscope Options + + +
+ + diff --git a/src/entrypoints/options/options.ts b/src/entrypoints/options/options.ts index 2e048d7..99cf20b 100644 --- a/src/entrypoints/options/options.ts +++ b/src/entrypoints/options/options.ts @@ -5,9 +5,9 @@ import { mount } from "svelte"; const appElement = document.getElementById("app"); if (!appElement) { - throw new Error("Failed to find app element. Cannot mount options page."); + throw new Error("Failed to find app element. Cannot mount options page."); } mount(Options, { - target: appElement, + target: appElement, }); diff --git a/src/entrypoints/popup/Popup.svelte b/src/entrypoints/popup/Popup.svelte index 5dbbb5c..bd2b5bb 100644 --- a/src/entrypoints/popup/Popup.svelte +++ b/src/entrypoints/popup/Popup.svelte @@ -1,119 +1,74 @@
-
-

GitHub PR Filters

- Logo -
- - {#if isLoading} -
-
+
+

GitHub PR Filters

+ Logo
- {:else if filters.length === 0} -
-

No filters configured yet

-
- {:else} -
- {#each filters as filter (filter.id)} -
-
- {filter.name} -
- {filter.value} -
-
- + {#if filterManager.isLoading} +
+
- {/each} -
- {/if} + {:else if filterManager.filters.length === 0} +
+

No filters configured yet

+
+ {:else} +
+ {#each filterManager.filters as filter (filter.id)} +
+
+ {filter.name} +
+ {filter.value} +
+
+ + +
+ {/each} +
+ {/if} -
- -
+
+ +
diff --git a/src/entrypoints/popup/index.html b/src/entrypoints/popup/index.html index 67de68e..4028201 100644 --- a/src/entrypoints/popup/index.html +++ b/src/entrypoints/popup/index.html @@ -1,13 +1,13 @@ - - - - Pullscope - - - -
- - + + + + Pullscope + + + +
+ + diff --git a/src/entrypoints/popup/popup.ts b/src/entrypoints/popup/popup.ts index 46c1e1d..dd59534 100644 --- a/src/entrypoints/popup/popup.ts +++ b/src/entrypoints/popup/popup.ts @@ -1,13 +1,13 @@ import "../../app.css"; -import Popup from "./Popup.svelte"; +import Popup from "@/entrypoints/popup/Popup.svelte"; import { mount } from "svelte"; const appElement = document.getElementById("app"); if (!appElement) { - throw new Error("Failed to find app element. Cannot mount popup."); + throw new Error("Failed to find app element. Cannot mount popup."); } mount(Popup, { - target: appElement, + target: appElement, }); diff --git a/src/lib/components/ErrorDisplay.svelte b/src/lib/components/ErrorDisplay.svelte index 029ac3f..b1055b8 100644 --- a/src/lib/components/ErrorDisplay.svelte +++ b/src/lib/components/ErrorDisplay.svelte @@ -1,41 +1,45 @@ {#if message} -