elsabro 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/elsabro-orchestrator.md +113 -0
- package/commands/elsabro/add-phase.md +17 -0
- package/commands/elsabro/add-todo.md +111 -53
- package/commands/elsabro/audit-milestone.md +19 -0
- package/commands/elsabro/check-todos.md +210 -31
- package/commands/elsabro/complete-milestone.md +20 -1
- package/commands/elsabro/debug.md +19 -0
- package/commands/elsabro/discuss-phase.md +18 -1
- package/commands/elsabro/execute.md +511 -58
- package/commands/elsabro/insert-phase.md +18 -1
- package/commands/elsabro/list-phase-assumptions.md +17 -0
- package/commands/elsabro/new-milestone.md +19 -0
- package/commands/elsabro/new.md +19 -0
- package/commands/elsabro/pause-work.md +19 -0
- package/commands/elsabro/plan-milestone-gaps.md +20 -1
- package/commands/elsabro/plan.md +264 -36
- package/commands/elsabro/progress.md +203 -79
- package/commands/elsabro/quick.md +19 -0
- package/commands/elsabro/remove-phase.md +17 -0
- package/commands/elsabro/research-phase.md +18 -1
- package/commands/elsabro/resume-work.md +19 -0
- package/commands/elsabro/start.md +399 -98
- package/commands/elsabro/verify-work.md +138 -5
- package/hooks/confirm-destructive.sh +145 -0
- package/hooks/hooks-config.json +81 -0
- package/hooks/lint-check.sh +238 -0
- package/hooks/post-edit-test.sh +189 -0
- package/package.json +3 -2
- package/references/SYSTEM_INDEX.md +241 -0
- package/references/command-flow.md +352 -0
- package/references/enforcement-rules.md +331 -0
- package/references/error-contracts-tests.md +1171 -0
- package/references/error-contracts.md +3102 -0
- package/references/error-handling-instructions.md +26 -12
- package/references/parallel-worktrees.md +293 -0
- package/references/state-sync.md +381 -0
- package/references/task-dispatcher.md +388 -0
- package/references/tasks-integration.md +380 -0
- package/scripts/setup-parallel-worktrees.sh +319 -0
- package/skills/api-microservice.md +765 -0
- package/skills/api-setup.md +76 -3
- package/skills/auth-setup.md +46 -6
- package/skills/chrome-extension.md +584 -0
- package/skills/cicd-setup.md +1206 -0
- package/skills/cli-tool.md +884 -0
- package/skills/database-setup.md +41 -5
- package/skills/desktop-app.md +1351 -0
- package/skills/expo-app.md +35 -2
- package/skills/full-stack-app.md +543 -0
- package/skills/memory-update.md +207 -0
- package/skills/mobile-app.md +813 -0
- package/skills/nextjs-app.md +33 -2
- package/skills/payments-setup.md +76 -1
- package/skills/review.md +331 -0
- package/skills/saas-starter.md +639 -0
- package/skills/sentry-setup.md +41 -7
- package/skills/techdebt.md +289 -0
- package/skills/testing-setup.md +1218 -0
- package/skills/tutor.md +219 -0
- package/templates/.planning/notes/.gitkeep +0 -0
- package/templates/CLAUDE.md.template +48 -0
- package/templates/error-handling-config.json +79 -2
- package/templates/mistakes.md.template +52 -0
- package/templates/patterns.md.template +114 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: chrome-extension
|
|
3
|
+
description: Crear extension de Chrome con Manifest V3, content scripts, background service worker, popup UI, Storage API y publicacion en Chrome Web Store.
|
|
4
|
+
tags: [chrome, extension, browser, manifest-v3, typescript, vite]
|
|
5
|
+
difficulty: intermediate
|
|
6
|
+
estimated_time: 35min
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Skill: Extension de Chrome
|
|
10
|
+
|
|
11
|
+
<when_to_use>
|
|
12
|
+
Usar cuando el usuario menciona:
|
|
13
|
+
- "extension de Chrome"
|
|
14
|
+
- "extension de navegador"
|
|
15
|
+
- "browser extension"
|
|
16
|
+
- "plugin de Chrome"
|
|
17
|
+
- "addon"
|
|
18
|
+
</when_to_use>
|
|
19
|
+
|
|
20
|
+
<pre_requisites>
|
|
21
|
+
## Pre-requisitos
|
|
22
|
+
|
|
23
|
+
- Chrome o Chromium
|
|
24
|
+
- Node.js 18+ (para build tools)
|
|
25
|
+
- Conocimiento basico de JavaScript/TypeScript
|
|
26
|
+
</pre_requisites>
|
|
27
|
+
|
|
28
|
+
<project_structure>
|
|
29
|
+
## Estructura de Proyecto
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
my-extension/
|
|
33
|
+
├── src/
|
|
34
|
+
│ ├── background/
|
|
35
|
+
│ │ └── index.ts # Service worker
|
|
36
|
+
│ ├── content/
|
|
37
|
+
│ │ ├── index.ts # Content script
|
|
38
|
+
│ │ └── styles.css # Estilos inyectados
|
|
39
|
+
│ ├── popup/
|
|
40
|
+
│ │ ├── index.html # Popup HTML
|
|
41
|
+
│ │ ├── index.ts # Popup logic
|
|
42
|
+
│ │ └── styles.css # Popup styles
|
|
43
|
+
│ ├── options/
|
|
44
|
+
│ │ ├── index.html # Options page
|
|
45
|
+
│ │ └── index.ts
|
|
46
|
+
│ └── utils/
|
|
47
|
+
│ ├── storage.ts # Storage helpers
|
|
48
|
+
│ └── messaging.ts # Message helpers
|
|
49
|
+
├── public/
|
|
50
|
+
│ ├── icons/
|
|
51
|
+
│ │ ├── icon16.png
|
|
52
|
+
│ │ ├── icon48.png
|
|
53
|
+
│ │ └── icon128.png
|
|
54
|
+
│ └── manifest.json
|
|
55
|
+
├── dist/ # Build output
|
|
56
|
+
├── package.json
|
|
57
|
+
├── tsconfig.json
|
|
58
|
+
├── vite.config.ts
|
|
59
|
+
└── README.md
|
|
60
|
+
```
|
|
61
|
+
</project_structure>
|
|
62
|
+
|
|
63
|
+
<setup_steps>
|
|
64
|
+
## Pasos de Setup
|
|
65
|
+
|
|
66
|
+
### Paso 1: Inicializar proyecto
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
mkdir my-extension && cd my-extension
|
|
70
|
+
npm init -y
|
|
71
|
+
npm install -D vite typescript @crxjs/vite-plugin@beta
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Paso 2: Crear manifest.json
|
|
75
|
+
|
|
76
|
+
Crear `public/manifest.json`:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"manifest_version": 3,
|
|
81
|
+
"name": "My Extension",
|
|
82
|
+
"version": "1.0.0",
|
|
83
|
+
"description": "Una extension increible para Chrome",
|
|
84
|
+
"permissions": [
|
|
85
|
+
"storage",
|
|
86
|
+
"activeTab",
|
|
87
|
+
"scripting",
|
|
88
|
+
"contextMenus"
|
|
89
|
+
],
|
|
90
|
+
"host_permissions": [
|
|
91
|
+
"https://*/*",
|
|
92
|
+
"http://*/*"
|
|
93
|
+
],
|
|
94
|
+
"background": {
|
|
95
|
+
"service_worker": "src/background/index.ts",
|
|
96
|
+
"type": "module"
|
|
97
|
+
},
|
|
98
|
+
"content_scripts": [
|
|
99
|
+
{
|
|
100
|
+
"matches": ["https://*/*", "http://*/*"],
|
|
101
|
+
"js": ["src/content/index.ts"],
|
|
102
|
+
"css": ["src/content/styles.css"],
|
|
103
|
+
"run_at": "document_end"
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
"action": {
|
|
107
|
+
"default_popup": "src/popup/index.html",
|
|
108
|
+
"default_icon": {
|
|
109
|
+
"16": "icons/icon16.png",
|
|
110
|
+
"48": "icons/icon48.png",
|
|
111
|
+
"128": "icons/icon128.png"
|
|
112
|
+
},
|
|
113
|
+
"default_title": "My Extension"
|
|
114
|
+
},
|
|
115
|
+
"options_page": "src/options/index.html",
|
|
116
|
+
"icons": {
|
|
117
|
+
"16": "icons/icon16.png",
|
|
118
|
+
"48": "icons/icon48.png",
|
|
119
|
+
"128": "icons/icon128.png"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Paso 3: Configurar TypeScript
|
|
125
|
+
|
|
126
|
+
Crear `tsconfig.json`:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"compilerOptions": {
|
|
131
|
+
"target": "ES2020",
|
|
132
|
+
"module": "ESNext",
|
|
133
|
+
"moduleResolution": "bundler",
|
|
134
|
+
"strict": true,
|
|
135
|
+
"esModuleInterop": true,
|
|
136
|
+
"skipLibCheck": true,
|
|
137
|
+
"forceConsistentCasingInFileNames": true,
|
|
138
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
139
|
+
"types": ["chrome"]
|
|
140
|
+
},
|
|
141
|
+
"include": ["src/**/*"],
|
|
142
|
+
"exclude": ["node_modules", "dist"]
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Instalar tipos de Chrome:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
npm install -D @types/chrome
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Paso 4: Configurar Vite
|
|
153
|
+
|
|
154
|
+
Crear `vite.config.ts`:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { defineConfig } from "vite";
|
|
158
|
+
import { crx } from "@crxjs/vite-plugin";
|
|
159
|
+
import manifest from "./public/manifest.json";
|
|
160
|
+
|
|
161
|
+
export default defineConfig({
|
|
162
|
+
plugins: [crx({ manifest })],
|
|
163
|
+
build: {
|
|
164
|
+
outDir: "dist",
|
|
165
|
+
emptyOutDir: true,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Paso 5: Crear Background Service Worker
|
|
171
|
+
|
|
172
|
+
Crear `src/background/index.ts`:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// Background service worker para Manifest V3
|
|
176
|
+
|
|
177
|
+
// Cuando la extension se instala
|
|
178
|
+
chrome.runtime.onInstalled.addListener((details) => {
|
|
179
|
+
console.log("Extension instalada:", details.reason);
|
|
180
|
+
|
|
181
|
+
// Crear context menu
|
|
182
|
+
chrome.contextMenus.create({
|
|
183
|
+
id: "myExtensionMenu",
|
|
184
|
+
title: "Procesar con My Extension",
|
|
185
|
+
contexts: ["selection"],
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Inicializar storage con valores por defecto
|
|
189
|
+
chrome.storage.local.set({
|
|
190
|
+
enabled: true,
|
|
191
|
+
settings: {
|
|
192
|
+
theme: "light",
|
|
193
|
+
notifications: true,
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Manejar click en context menu
|
|
199
|
+
chrome.contextMenus.onClicked.addListener((info, tab) => {
|
|
200
|
+
if (info.menuItemId === "myExtensionMenu" && info.selectionText) {
|
|
201
|
+
console.log("Texto seleccionado:", info.selectionText);
|
|
202
|
+
|
|
203
|
+
// Enviar mensaje al content script
|
|
204
|
+
if (tab?.id) {
|
|
205
|
+
chrome.tabs.sendMessage(tab.id, {
|
|
206
|
+
type: "PROCESS_SELECTION",
|
|
207
|
+
text: info.selectionText,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Escuchar mensajes del content script o popup
|
|
214
|
+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
215
|
+
console.log("Mensaje recibido:", message, "de:", sender);
|
|
216
|
+
|
|
217
|
+
switch (message.type) {
|
|
218
|
+
case "GET_SETTINGS":
|
|
219
|
+
chrome.storage.local.get(["settings"], (result) => {
|
|
220
|
+
sendResponse(result.settings || {});
|
|
221
|
+
});
|
|
222
|
+
return true; // Indica respuesta asincrona
|
|
223
|
+
|
|
224
|
+
case "SAVE_SETTINGS":
|
|
225
|
+
chrome.storage.local.set({ settings: message.payload }, () => {
|
|
226
|
+
sendResponse({ success: true });
|
|
227
|
+
});
|
|
228
|
+
return true;
|
|
229
|
+
|
|
230
|
+
default:
|
|
231
|
+
sendResponse({ error: "Unknown message type" });
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Paso 6: Crear Content Script
|
|
237
|
+
|
|
238
|
+
Crear `src/content/index.ts`:
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
// Content script - se ejecuta en cada pagina
|
|
242
|
+
|
|
243
|
+
console.log("Content script cargado en:", window.location.href);
|
|
244
|
+
|
|
245
|
+
// Escuchar mensajes del background o popup
|
|
246
|
+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
247
|
+
console.log("Content script recibio:", message);
|
|
248
|
+
|
|
249
|
+
switch (message.type) {
|
|
250
|
+
case "PROCESS_SELECTION":
|
|
251
|
+
processSelection(message.text);
|
|
252
|
+
sendResponse({ success: true });
|
|
253
|
+
break;
|
|
254
|
+
|
|
255
|
+
case "PING":
|
|
256
|
+
sendResponse({ pong: true, url: window.location.href });
|
|
257
|
+
break;
|
|
258
|
+
|
|
259
|
+
case "GET_PAGE_INFO":
|
|
260
|
+
sendResponse({
|
|
261
|
+
title: document.title,
|
|
262
|
+
url: window.location.href,
|
|
263
|
+
});
|
|
264
|
+
break;
|
|
265
|
+
|
|
266
|
+
default:
|
|
267
|
+
sendResponse({ error: "Unknown message type" });
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Funcion para procesar texto seleccionado (usando DOM APIs seguras)
|
|
272
|
+
function processSelection(text: string) {
|
|
273
|
+
// Crear tooltip usando DOM APIs seguras (evita innerHTML)
|
|
274
|
+
const existing = document.getElementById("my-extension-tooltip");
|
|
275
|
+
if (existing) existing.remove();
|
|
276
|
+
|
|
277
|
+
const tooltip = document.createElement("div");
|
|
278
|
+
tooltip.id = "my-extension-tooltip";
|
|
279
|
+
tooltip.style.cssText = `
|
|
280
|
+
position: fixed;
|
|
281
|
+
top: 20px;
|
|
282
|
+
right: 20px;
|
|
283
|
+
background: white;
|
|
284
|
+
border: 1px solid #ccc;
|
|
285
|
+
border-radius: 8px;
|
|
286
|
+
padding: 16px;
|
|
287
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
288
|
+
z-index: 999999;
|
|
289
|
+
max-width: 300px;
|
|
290
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
291
|
+
`;
|
|
292
|
+
|
|
293
|
+
const title = document.createElement("h4");
|
|
294
|
+
title.textContent = "My Extension";
|
|
295
|
+
title.style.cssText = "margin: 0 0 8px 0; font-size: 14px;";
|
|
296
|
+
|
|
297
|
+
const content = document.createElement("p");
|
|
298
|
+
const displayText = text.length > 100 ? text.substring(0, 100) + "..." : text;
|
|
299
|
+
content.textContent = "Seleccionaste: " + displayText;
|
|
300
|
+
content.style.cssText = "margin: 0; font-size: 13px; color: #666;";
|
|
301
|
+
|
|
302
|
+
const closeBtn = document.createElement("button");
|
|
303
|
+
closeBtn.textContent = "Cerrar";
|
|
304
|
+
closeBtn.style.cssText = `
|
|
305
|
+
margin-top: 12px;
|
|
306
|
+
padding: 6px 12px;
|
|
307
|
+
background: #007AFF;
|
|
308
|
+
color: white;
|
|
309
|
+
border: none;
|
|
310
|
+
border-radius: 4px;
|
|
311
|
+
cursor: pointer;
|
|
312
|
+
`;
|
|
313
|
+
closeBtn.addEventListener("click", () => tooltip.remove());
|
|
314
|
+
|
|
315
|
+
tooltip.appendChild(title);
|
|
316
|
+
tooltip.appendChild(content);
|
|
317
|
+
tooltip.appendChild(closeBtn);
|
|
318
|
+
document.body.appendChild(tooltip);
|
|
319
|
+
|
|
320
|
+
// Auto-cerrar despues de 5 segundos
|
|
321
|
+
setTimeout(() => tooltip.remove(), 5000);
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Paso 7: Crear Popup UI
|
|
326
|
+
|
|
327
|
+
Crear `src/popup/index.html`:
|
|
328
|
+
|
|
329
|
+
```html
|
|
330
|
+
<!DOCTYPE html>
|
|
331
|
+
<html lang="es">
|
|
332
|
+
<head>
|
|
333
|
+
<meta charset="UTF-8">
|
|
334
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
335
|
+
<title>My Extension</title>
|
|
336
|
+
<link rel="stylesheet" href="styles.css">
|
|
337
|
+
</head>
|
|
338
|
+
<body>
|
|
339
|
+
<div class="container">
|
|
340
|
+
<header class="header">
|
|
341
|
+
<h1>My Extension</h1>
|
|
342
|
+
</header>
|
|
343
|
+
|
|
344
|
+
<main class="content">
|
|
345
|
+
<div class="toggle-row">
|
|
346
|
+
<span>Habilitado</span>
|
|
347
|
+
<label class="toggle">
|
|
348
|
+
<input type="checkbox" id="enabledToggle" checked>
|
|
349
|
+
<span class="slider"></span>
|
|
350
|
+
</label>
|
|
351
|
+
</div>
|
|
352
|
+
|
|
353
|
+
<div class="form-group">
|
|
354
|
+
<label for="input">Texto:</label>
|
|
355
|
+
<input type="text" id="input" placeholder="Escribe algo...">
|
|
356
|
+
</div>
|
|
357
|
+
|
|
358
|
+
<div class="buttons">
|
|
359
|
+
<button id="saveBtn" class="btn btn-primary">Guardar</button>
|
|
360
|
+
<button id="actionBtn" class="btn btn-secondary">Ejecutar</button>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
<div id="status" class="status"></div>
|
|
364
|
+
</main>
|
|
365
|
+
|
|
366
|
+
<footer class="footer">
|
|
367
|
+
<a href="#" id="optionsLink">Configuracion</a>
|
|
368
|
+
</footer>
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
<script type="module" src="index.ts"></script>
|
|
372
|
+
</body>
|
|
373
|
+
</html>
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Crear `src/popup/styles.css`:
|
|
377
|
+
|
|
378
|
+
```css
|
|
379
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
380
|
+
|
|
381
|
+
body {
|
|
382
|
+
width: 320px;
|
|
383
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
384
|
+
font-size: 14px;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.header {
|
|
388
|
+
padding: 16px;
|
|
389
|
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
|
390
|
+
color: white;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.header h1 { font-size: 18px; }
|
|
394
|
+
|
|
395
|
+
.content { padding: 16px; }
|
|
396
|
+
|
|
397
|
+
.toggle-row {
|
|
398
|
+
display: flex;
|
|
399
|
+
justify-content: space-between;
|
|
400
|
+
margin-bottom: 16px;
|
|
401
|
+
padding-bottom: 16px;
|
|
402
|
+
border-bottom: 1px solid #eee;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.toggle {
|
|
406
|
+
position: relative;
|
|
407
|
+
width: 48px;
|
|
408
|
+
height: 26px;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.toggle input { opacity: 0; width: 0; height: 0; }
|
|
412
|
+
|
|
413
|
+
.slider {
|
|
414
|
+
position: absolute;
|
|
415
|
+
cursor: pointer;
|
|
416
|
+
inset: 0;
|
|
417
|
+
background: #ccc;
|
|
418
|
+
border-radius: 26px;
|
|
419
|
+
transition: 0.3s;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.slider:before {
|
|
423
|
+
position: absolute;
|
|
424
|
+
content: "";
|
|
425
|
+
height: 20px;
|
|
426
|
+
width: 20px;
|
|
427
|
+
left: 3px;
|
|
428
|
+
bottom: 3px;
|
|
429
|
+
background: white;
|
|
430
|
+
border-radius: 50%;
|
|
431
|
+
transition: 0.3s;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
input:checked + .slider { background: #667eea; }
|
|
435
|
+
input:checked + .slider:before { transform: translateX(22px); }
|
|
436
|
+
|
|
437
|
+
.form-group { margin-bottom: 16px; }
|
|
438
|
+
.form-group label { display: block; margin-bottom: 6px; }
|
|
439
|
+
.form-group input {
|
|
440
|
+
width: 100%;
|
|
441
|
+
padding: 10px;
|
|
442
|
+
border: 1px solid #ddd;
|
|
443
|
+
border-radius: 6px;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.buttons { display: flex; gap: 8px; }
|
|
447
|
+
|
|
448
|
+
.btn {
|
|
449
|
+
flex: 1;
|
|
450
|
+
padding: 10px;
|
|
451
|
+
border: none;
|
|
452
|
+
border-radius: 6px;
|
|
453
|
+
cursor: pointer;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.btn-primary { background: #667eea; color: white; }
|
|
457
|
+
.btn-secondary { background: #f0f0f0; }
|
|
458
|
+
|
|
459
|
+
.status { margin-top: 12px; padding: 8px; border-radius: 6px; display: none; }
|
|
460
|
+
.status.success { display: block; background: #d4edda; color: #155724; }
|
|
461
|
+
.status.error { display: block; background: #f8d7da; color: #721c24; }
|
|
462
|
+
|
|
463
|
+
.footer { padding: 12px; background: #f9f9f9; text-align: center; }
|
|
464
|
+
.footer a { color: #667eea; text-decoration: none; }
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Crear `src/popup/index.ts`:
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
const input = document.getElementById("input") as HTMLInputElement;
|
|
471
|
+
const enabledToggle = document.getElementById("enabledToggle") as HTMLInputElement;
|
|
472
|
+
const saveBtn = document.getElementById("saveBtn") as HTMLButtonElement;
|
|
473
|
+
const actionBtn = document.getElementById("actionBtn") as HTMLButtonElement;
|
|
474
|
+
const status = document.getElementById("status") as HTMLDivElement;
|
|
475
|
+
const optionsLink = document.getElementById("optionsLink") as HTMLAnchorElement;
|
|
476
|
+
|
|
477
|
+
// Cargar estado inicial
|
|
478
|
+
document.addEventListener("DOMContentLoaded", async () => {
|
|
479
|
+
const result = await chrome.storage.local.get(["enabled", "savedText"]);
|
|
480
|
+
enabledToggle.checked = result.enabled !== false;
|
|
481
|
+
input.value = result.savedText || "";
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// Toggle habilitado
|
|
485
|
+
enabledToggle.addEventListener("change", async () => {
|
|
486
|
+
await chrome.storage.local.set({ enabled: enabledToggle.checked });
|
|
487
|
+
showStatus(enabledToggle.checked ? "Habilitado" : "Deshabilitado", "success");
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
// Guardar
|
|
491
|
+
saveBtn.addEventListener("click", async () => {
|
|
492
|
+
const value = input.value.trim();
|
|
493
|
+
if (!value) {
|
|
494
|
+
showStatus("Escribe algo primero", "error");
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
await chrome.storage.local.set({ savedText: value });
|
|
498
|
+
showStatus("Guardado", "success");
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// Ejecutar
|
|
502
|
+
actionBtn.addEventListener("click", async () => {
|
|
503
|
+
try {
|
|
504
|
+
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
505
|
+
if (!tab?.id) {
|
|
506
|
+
showStatus("No hay tab activa", "error");
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const response = await chrome.tabs.sendMessage(tab.id, { type: "PING" });
|
|
510
|
+
showStatus("Pagina: " + response.url, "success");
|
|
511
|
+
} catch {
|
|
512
|
+
showStatus("Error de comunicacion", "error");
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// Opciones
|
|
517
|
+
optionsLink.addEventListener("click", (e) => {
|
|
518
|
+
e.preventDefault();
|
|
519
|
+
chrome.runtime.openOptionsPage();
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
function showStatus(message: string, type: "success" | "error") {
|
|
523
|
+
status.textContent = message;
|
|
524
|
+
status.className = "status " + type;
|
|
525
|
+
setTimeout(() => { status.className = "status"; }, 3000);
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### Paso 8: Scripts de package.json
|
|
530
|
+
|
|
531
|
+
```json
|
|
532
|
+
{
|
|
533
|
+
"scripts": {
|
|
534
|
+
"dev": "vite",
|
|
535
|
+
"build": "vite build"
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
</setup_steps>
|
|
540
|
+
|
|
541
|
+
<verification>
|
|
542
|
+
## Verificacion
|
|
543
|
+
|
|
544
|
+
### 1. Build
|
|
545
|
+
```bash
|
|
546
|
+
npm run dev
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### 2. Cargar en Chrome
|
|
550
|
+
1. Abre `chrome://extensions/`
|
|
551
|
+
2. Activa "Modo desarrollador"
|
|
552
|
+
3. Click "Cargar extension sin empaquetar"
|
|
553
|
+
4. Selecciona la carpeta `dist`
|
|
554
|
+
|
|
555
|
+
### 3. Probar popup
|
|
556
|
+
- Click en el icono de la extension
|
|
557
|
+
- Escribe algo y guarda
|
|
558
|
+
|
|
559
|
+
### 4. Probar content script
|
|
560
|
+
- Abre DevTools en cualquier pagina
|
|
561
|
+
- Deberia aparecer "Content script cargado"
|
|
562
|
+
</verification>
|
|
563
|
+
|
|
564
|
+
<publishing>
|
|
565
|
+
## Publicar en Chrome Web Store
|
|
566
|
+
|
|
567
|
+
1. Ir a https://chrome.google.com/webstore/devconsole
|
|
568
|
+
2. Pagar fee de $5
|
|
569
|
+
3. Crear ZIP: `cd dist && zip -r ../extension.zip .`
|
|
570
|
+
4. Subir y completar listing
|
|
571
|
+
5. Enviar para revision
|
|
572
|
+
</publishing>
|
|
573
|
+
|
|
574
|
+
<common_issues>
|
|
575
|
+
## Problemas Comunes
|
|
576
|
+
|
|
577
|
+
### "Service worker not found"
|
|
578
|
+
- Verificar path en manifest.json
|
|
579
|
+
- Rebuild y recargar extension
|
|
580
|
+
|
|
581
|
+
### "Content script not running"
|
|
582
|
+
- Verificar "matches" en manifest
|
|
583
|
+
- Recargar pagina web
|
|
584
|
+
</common_issues>
|