opencode-browser-annotation-plugin 0.3.1 → 0.3.2
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/extension/manifest.json +1 -1
- package/extension/overlay.js +69 -20
- package/package.json +1 -1
package/extension/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "OpenCode Browser Annotation",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.2",
|
|
5
5
|
"description": "Alt+A to annotate an element, Alt+Shift+A for the annotation list. Sends element metadata to your OpenCode agent.",
|
|
6
6
|
"permissions": [
|
|
7
7
|
"activeTab",
|
package/extension/overlay.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
// Alt+A -> start element picking (background sends "oc-pick")
|
|
6
6
|
// Alt+Shift+A -> toggle the list sidebar ("oc-toggle-sidebar")
|
|
7
7
|
// Picking an element opens a floating popup near it (onUI-style): type an
|
|
8
|
-
// instruction,
|
|
9
|
-
//
|
|
10
|
-
//
|
|
8
|
+
// instruction, then Add (to the sidebar list) or Send (submit immediately). The
|
|
9
|
+
// sidebar is a floating rounded card holding pending annotations for batch
|
|
10
|
+
// submit, with a session-target picker.
|
|
11
11
|
|
|
12
12
|
(() => {
|
|
13
13
|
if (window.__ocAnnotationInjected) return;
|
|
@@ -95,11 +95,11 @@
|
|
|
95
95
|
.oc-desc { font: 11px ui-monospace, monospace; color: #aeb4c6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; background: rgba(255,255,255,.06); padding: 2px 7px; border-radius: 6px; flex: 1; }
|
|
96
96
|
.oc-card .oc-text { font-size: 12.5px; color: #d4d7e0; white-space: pre-wrap; word-break: break-word; }
|
|
97
97
|
.oc-foot-bar { padding: 11px 12px; border-top: 1px solid rgba(255,255,255,.07); position: relative; }
|
|
98
|
-
.oc-
|
|
99
|
-
.oc-
|
|
100
|
-
.oc-select { flex: 1; min-width: 0; font: inherit; font-size: 12px; color: #e6e8ee; background: #14151b; border: 1px solid rgba(255,255,255,.12); border-radius: 8px; padding: 6px 8px; }
|
|
98
|
+
.oc-status-row { display: flex; align-items: center; gap: 8px; margin-bottom: 9px; }
|
|
99
|
+
.oc-select { flex: none; max-width: 150px; font: inherit; font-size: 11.5px; color: #cfd3df; background: #14151b; border: 1px solid rgba(255,255,255,.12); border-radius: 7px; padding: 4px 6px; }
|
|
101
100
|
.oc-select:focus { outline: none; border-color: #4c8dff; }
|
|
102
|
-
.oc-status {
|
|
101
|
+
.oc-status { flex: 1; min-width: 0; font-size: 12px; display: flex; align-items: center; gap: 8px; color: #9298aa; }
|
|
102
|
+
.oc-status span, .oc-status { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
103
103
|
.oc-status::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: #5b6070; flex: none; }
|
|
104
104
|
.oc-status.good::before { background: #34d399; box-shadow: 0 0 8px rgba(52,211,153,.6); }
|
|
105
105
|
.oc-status.warn::before { background: #fbbf24; } .oc-status.bad::before { background: #f87171; } .oc-status.checking::before { background: #60a5fa; }
|
|
@@ -122,6 +122,44 @@
|
|
|
122
122
|
let targetSessionID = null; // user-chosen target; null = auto (last active)
|
|
123
123
|
let autoSessionID = null; // the plugin's last-active session
|
|
124
124
|
|
|
125
|
+
// A content script keeps running after its extension is reloaded/updated, but
|
|
126
|
+
// its chrome.* calls then throw "Extension context invalidated". Guard every
|
|
127
|
+
// message so a stale overlay fails quietly instead of spamming errors, and
|
|
128
|
+
// tear itself down once the context is gone.
|
|
129
|
+
function contextAlive() {
|
|
130
|
+
try {
|
|
131
|
+
return Boolean(chrome.runtime && chrome.runtime.id);
|
|
132
|
+
} catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function sendMsg(payload, cb) {
|
|
138
|
+
if (!contextAlive()) {
|
|
139
|
+
teardown();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
chrome.runtime.sendMessage(payload, (res) => {
|
|
144
|
+
if (chrome.runtime.lastError) {
|
|
145
|
+
cb(null);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
cb(res);
|
|
149
|
+
});
|
|
150
|
+
} catch {
|
|
151
|
+
teardown();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function teardown() {
|
|
156
|
+
if (statusTimer) {
|
|
157
|
+
clearInterval(statusTimer);
|
|
158
|
+
statusTimer = null;
|
|
159
|
+
}
|
|
160
|
+
if (host) host.style.display = "none";
|
|
161
|
+
}
|
|
162
|
+
|
|
125
163
|
function effectiveTarget() {
|
|
126
164
|
return targetSessionID || autoSessionID;
|
|
127
165
|
}
|
|
@@ -277,6 +315,19 @@
|
|
|
277
315
|
document.addEventListener("mousemove", onMove, true);
|
|
278
316
|
document.addEventListener("click", onClick, true);
|
|
279
317
|
document.addEventListener("keydown", onKey, true);
|
|
318
|
+
|
|
319
|
+
// Keep keystrokes typed inside our UI from reaching the page's global
|
|
320
|
+
// hotkeys (e.g. GitHub's "s"/"f"). Capture key events at the document before
|
|
321
|
+
// page listeners run; if the event originates inside our shadow UI, stop it
|
|
322
|
+
// (Esc is handled by onKey above, which runs first in capture order here).
|
|
323
|
+
const contain = (e) => {
|
|
324
|
+
if (e.composedPath && e.composedPath().includes(host)) {
|
|
325
|
+
e.stopImmediatePropagation();
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
for (const type of ["keydown", "keyup", "keypress"]) {
|
|
329
|
+
document.addEventListener(type, contain, true);
|
|
330
|
+
}
|
|
280
331
|
}
|
|
281
332
|
|
|
282
333
|
function startPicking() {
|
|
@@ -386,20 +437,21 @@
|
|
|
386
437
|
<span class="logo">${ICON.logo}</span>
|
|
387
438
|
<span class="title">Annotations</span>
|
|
388
439
|
<span class="badge" id="oc-badge">0</span>
|
|
440
|
+
<button class="iconbtn" id="oc-pick" title="Select element (Alt+A)">${ICON.target}</button>
|
|
389
441
|
<button class="iconbtn" id="oc-close" title="Close (Alt+Shift+A)">${ICON.close}</button>
|
|
390
442
|
</div>
|
|
391
443
|
<div class="oc-list" id="oc-list"></div>
|
|
392
444
|
<div class="oc-foot-bar">
|
|
393
|
-
<
|
|
394
|
-
<
|
|
395
|
-
<select class="oc-select" id="oc-session"></select>
|
|
396
|
-
</
|
|
397
|
-
<div class="oc-status" id="oc-status">…</div>
|
|
445
|
+
<div class="oc-status-row">
|
|
446
|
+
<div class="oc-status" id="oc-status">…</div>
|
|
447
|
+
<select class="oc-select" id="oc-session" title="Target session"></select>
|
|
448
|
+
</div>
|
|
398
449
|
<button class="btn primary oc-submit" id="oc-submit" disabled>${ICON.send}<span>Submit to agent</span></button>
|
|
399
450
|
<div class="oc-toast" id="oc-toast"></div>
|
|
400
451
|
</div>`;
|
|
401
452
|
root.appendChild(sb);
|
|
402
453
|
sb.querySelector("#oc-close").addEventListener("click", closeSidebar);
|
|
454
|
+
sb.querySelector("#oc-pick").addEventListener("click", () => startPicking());
|
|
403
455
|
sb.querySelector("#oc-session").addEventListener("change", (e) => {
|
|
404
456
|
targetSessionID = e.target.value || null;
|
|
405
457
|
});
|
|
@@ -473,8 +525,8 @@
|
|
|
473
525
|
if (!el) return;
|
|
474
526
|
el.className = "oc-status checking";
|
|
475
527
|
el.textContent = "Checking connection…";
|
|
476
|
-
|
|
477
|
-
if (
|
|
528
|
+
sendMsg({ type: "oc-status" }, (res) => {
|
|
529
|
+
if (!res) {
|
|
478
530
|
el.className = "oc-status bad";
|
|
479
531
|
el.textContent = "Extension error";
|
|
480
532
|
return;
|
|
@@ -541,16 +593,13 @@
|
|
|
541
593
|
if (!annotations.length) return;
|
|
542
594
|
toast("Submitting…");
|
|
543
595
|
const sessionID = effectiveTarget() || undefined;
|
|
544
|
-
|
|
545
|
-
if (
|
|
596
|
+
sendMsg({ type: "oc-submit", annotations, sessionID }, (res) => {
|
|
597
|
+
if (!res) {
|
|
546
598
|
toast("Extension error", true);
|
|
547
599
|
return;
|
|
548
600
|
}
|
|
549
601
|
if (res.ok) {
|
|
550
|
-
|
|
551
|
-
if (res.injected) parts.push(`${res.injected} sent`);
|
|
552
|
-
if (res.queued) parts.push(`${res.queued} queued`);
|
|
553
|
-
toast(parts.length ? parts.join(", ") : "Submitted");
|
|
602
|
+
toast(res.injected ? `${res.injected} sent to agent` : "Submitted");
|
|
554
603
|
if (!quick) {
|
|
555
604
|
pending = [];
|
|
556
605
|
renderCards();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-browser-annotation-plugin",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Select an element in your browser, type an instruction, and send it to your OpenCode agent over a loopback + SSH tunnel. Text and element metadata only (no screenshots).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode",
|