initial commit
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
FROM mcr.microsoft.com/devcontainers/javascript-node:22
|
||||||
|
|
||||||
|
# Install Chrome (stable)
|
||||||
|
RUN wget -q -O /tmp/chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \
|
||||||
|
&& apt-get update && apt-get install -y /tmp/chrome.deb \
|
||||||
|
&& rm /tmp/chrome.deb \
|
||||||
|
&& apt-get clean
|
||||||
|
|
||||||
|
# Install Chrome DevTools CLI for headless testing
|
||||||
|
RUN npm install -g chrome-devtools-cli
|
||||||
|
|
||||||
|
WORKDIR /workspace
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/sshd:1": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "ghcr.io/devcontainers/features/sshd@sha256:f5251b8e4325f68f7280973c6cd65daff414449c66f240621502d4e8e74eb7ee",
|
||||||
|
"integrity": "sha256:f5251b8e4325f68f7280973c6cd65daff414449c66f240621502d4e8e74eb7ee"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "Goon Plugin Dev",
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/javascript-node:22",
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/sshd:1": {}
|
||||||
|
},
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
|
"fmvilas.popup-previewer.ChromeExtensionPopupPreviewer",
|
||||||
|
"maslansnik.chromium-ext-debugger",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"bierner.markdown-preview-github-styles"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"forwardPorts": [5173],
|
||||||
|
"postCreateCommand": "npm install",
|
||||||
|
"remoteEnv": {
|
||||||
|
"CHROME_PATH": "/usr/bin/google-chrome"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Optional: use docker-compose if you need a custom Chrome setup.
|
||||||
|
# With podman, substitute `docker-compose` → `podman-compose`.
|
||||||
|
version: "3.8"
|
||||||
|
services:
|
||||||
|
dev:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
volumes:
|
||||||
|
- ..:/workspace:cached
|
||||||
|
command: sleep infinity
|
||||||
|
environment:
|
||||||
|
- CHROME_PATH=/usr/bin/google-chrome
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.vscode-insiders/
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Goon Plugin
|
||||||
|
|
||||||
|
Chrome extension scaffold (Manifest V3).
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Install deps
|
||||||
|
npm i
|
||||||
|
|
||||||
|
# 2. Run in watch mode
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# 3. Build for distribution
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Loading in Chrome
|
||||||
|
|
||||||
|
1. Open `chrome://extensions/`
|
||||||
|
2. Enable **Developer mode** (top-right)
|
||||||
|
3. Click **Load unpacked** → select the `dist/` folder
|
||||||
|
|
||||||
|
## Project structure
|
||||||
|
|
||||||
|
```
|
||||||
|
manifest.json — Extension manifest (MV3)
|
||||||
|
package.json — Dev tooling (Vite + Vitest + TS)
|
||||||
|
vite.config.ts — Build config
|
||||||
|
src/
|
||||||
|
background/ — Service worker (MV3 background)
|
||||||
|
popup/ — Popup UI (HTML + CSS + JS)
|
||||||
|
content/ — Content script
|
||||||
|
icons/ — Extension icons (place .png files here)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recommended extensions (VS Code)
|
||||||
|
|
||||||
|
- **Chrome Extension Developer** (extension-list-viewer)
|
||||||
|
- **ESLint**
|
||||||
|
- **Prettier**
|
||||||
|
|
||||||
|
## VS Code workspace
|
||||||
|
|
||||||
|
Open `goon_plugin.code-workspace` to get the project with recommended extensions pre-installed.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extensions": {
|
||||||
|
"recommendations": [
|
||||||
|
"fmvilas.popup-previewer.ChromeExtensionPopupPreviewer",
|
||||||
|
"maslansnik.chromium-ext-debugger",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"bierner.markdown-preview-github-styles"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"extensions.autoUpdate": true,
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"eslint.validate": ["javascript", "typescript"],
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "Goon Plugin",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "A Chrome extension",
|
||||||
|
"permissions": [
|
||||||
|
"storage",
|
||||||
|
"tabs",
|
||||||
|
"activeTab"
|
||||||
|
],
|
||||||
|
"host_permissions": [
|
||||||
|
"<all_urls>"
|
||||||
|
],
|
||||||
|
"background": {
|
||||||
|
"service_worker": "src/background/background.js",
|
||||||
|
"type": "module"
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"default_popup": "src/popup/popup.html",
|
||||||
|
"default_icon": {
|
||||||
|
"16": "icons/icon16.png",
|
||||||
|
"48": "icons/icon48.png",
|
||||||
|
"128": "icons/icon128.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": ["<all_urls>"],
|
||||||
|
"js": ["src/content/content.js"],
|
||||||
|
"run_at": "document_idle"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"icons": {
|
||||||
|
"16": "icons/icon16.png",
|
||||||
|
"48": "icons/icon48.png",
|
||||||
|
"128": "icons/icon128.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+2658
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "goon-plugin",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Chrome extension scaffold",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"lint:fix": "eslint . --fix",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"check": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"chrome-types": "^0.1.336",
|
||||||
|
"eslint": "^9.0.0",
|
||||||
|
"vite": "^6.0.0",
|
||||||
|
"vitest": "^3.0.0",
|
||||||
|
"typescript": "^5.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* background.js — Service worker for the Goon Plugin extension.
|
||||||
|
* Handles long-lived state, message routing, and lifecycle events.
|
||||||
|
* Uses Manifest V3 module-type service worker.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Fired when the extension is first installed / updated / Chrome starts.
|
||||||
|
chrome.runtime.onInstalled.addListener((details) => {
|
||||||
|
if (details.reason === 'install') {
|
||||||
|
console.log('[goon-plugin] Extension installed');
|
||||||
|
} else if (details.reason === 'update') {
|
||||||
|
console.log(`[goon-plugin] Extension updated from ${details.previousVersion}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for messages from popup or content scripts.
|
||||||
|
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
||||||
|
console.log('[goon-plugin] Received:', message);
|
||||||
|
|
||||||
|
switch (message.type) {
|
||||||
|
case 'PING':
|
||||||
|
sendResponse({ pong: true, timestamp: Date.now() });
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.warn('[goon-plugin] Unknown message type:', message.type);
|
||||||
|
sendResponse({ error: `Unknown type: ${message.type}` });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // keep sendResponse channel open for async work
|
||||||
|
});
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* content.js — Content script injected into matching pages.
|
||||||
|
* Communicates with the background service worker via chrome.runtime.
|
||||||
|
* Use cautiously: runs in the page's DOM context, so isolate from page globals.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
console.log('[goon-plugin] Content script loaded');
|
||||||
|
|
||||||
|
// Example: inject a small element into the page.
|
||||||
|
// Uncomment and customize as needed.
|
||||||
|
//
|
||||||
|
// const badge = document.createElement('div');
|
||||||
|
// badge.textContent = 'Goon Plugin Active';
|
||||||
|
// badge.style.cssText = 'position:fixed;bottom:8px;right:8px;padding:4px 8px;...';
|
||||||
|
// document.body.appendChild(badge);
|
||||||
|
})();
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Icons
|
||||||
|
|
||||||
|
Place your extension icons here.
|
||||||
|
|
||||||
|
- `icon16.png` — 16x16 px, PNG
|
||||||
|
- `icon48.png` — 48x48 px, PNG
|
||||||
|
- `icon128.png` — 128x128 px, PNG
|
||||||
|
|
||||||
|
Tip: generate them from a single SVG source using [icon-generator](https://chrome.dev/tools/icon-generator/) or the `resvg` CLI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @resvg/resvg-js icon.svg -w 16 -h 16 -o icon16.png
|
||||||
|
npx @resvg/resvg-js icon.svg -w 48 -h 48 -o icon48.png
|
||||||
|
npx @resvg/resvg-js icon.svg -w 128 -h 128 -o icon128.png
|
||||||
|
```
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||||
|
<rect width="128" height="128" rx="24" fill="#4361ee"/>
|
||||||
|
<text x="64" y="84" text-anchor="middle" dominant-baseline="middle" font-size="64" font-weight="bold" font-family="sans-serif" fill="white">G</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 279 B |
@@ -0,0 +1,53 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #ffffff;
|
||||||
|
--text: #1a1a2e;
|
||||||
|
--accent: #4361ee;
|
||||||
|
--accent-hover: #3a56d4;
|
||||||
|
--border: #e2e8f0;
|
||||||
|
--radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
min-width: 320px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: var(--accent);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: var(--accent-hover);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Goon Plugin</title>
|
||||||
|
<link rel="stylesheet" href="popup.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<h1>Goon Plugin</h1>
|
||||||
|
<p id="status">Ready</p>
|
||||||
|
<button id="action-btn">Click me</button>
|
||||||
|
</div>
|
||||||
|
<script src="popup.js" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* popup.js — UI logic for the extension popup.
|
||||||
|
* Runs inside the popup iframe; uses chrome.runtime / chrome.storage APIs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const actionBtn = document.getElementById('action-btn');
|
||||||
|
const statusEl = document.getElementById('status');
|
||||||
|
|
||||||
|
actionBtn.addEventListener('click', async () => {
|
||||||
|
// TODO: replace with actual extension logic
|
||||||
|
try {
|
||||||
|
const result = await chrome.storage.local.get(['counter']);
|
||||||
|
const count = (result.counter ?? 0) + 1;
|
||||||
|
await chrome.storage.local.set({ counter: count });
|
||||||
|
statusEl.textContent = `Clicked ${count} time${count === 1 ? '' : 's'}`;
|
||||||
|
} catch (err) {
|
||||||
|
statusEl.textContent = `Error: ${err.message}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": ["ES2022", "DOM"],
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"types": ["chrome-types"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "vite.config.ts"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Vite config for the Chrome extension.
|
||||||
|
* Builds the extension into dist/ for loading as an unpacked extension.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* npm run dev — watch mode, rebuild on change
|
||||||
|
* npm run build — production build
|
||||||
|
*/
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
root: 'src',
|
||||||
|
build: {
|
||||||
|
outDir: '../dist',
|
||||||
|
emptyOutDir: true,
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
background: resolve(__dirname, 'src/background/background.js'),
|
||||||
|
content: resolve(__dirname, 'src/content/content.js'),
|
||||||
|
popup: resolve(__dirname, 'src/popup/popup.html'),
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
entryFileNames: '[name].js',
|
||||||
|
chunkFileNames: 'chunks/[name].js',
|
||||||
|
assetFileNames: '[name][extname]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user