cobro-mcp 0.9.4 → 0.10.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/CHANGELOG.md +18 -2
- package/dist/overlay.js +463 -147
- package/dist/server.js +198 -38
- package/package.json +1 -1
- package/skills/claude-code/SKILL.md +4 -4
package/dist/overlay.js
CHANGED
|
@@ -25,7 +25,95 @@
|
|
|
25
25
|
// src/overlay/refs.ts
|
|
26
26
|
var isChildRef = (ref, g) => !!ref && new RegExp("^" + g + "[a-l]$").test(ref);
|
|
27
27
|
|
|
28
|
+
// src/core/page.ts
|
|
29
|
+
function samePage(a, b) {
|
|
30
|
+
try {
|
|
31
|
+
const ua = new URL(a);
|
|
32
|
+
const ub = new URL(b);
|
|
33
|
+
return ua.origin + ua.pathname + ua.search === ub.origin + ub.pathname + ub.search;
|
|
34
|
+
} catch {
|
|
35
|
+
return a === b;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/overlay/cart.ts
|
|
40
|
+
function currentDraft(drafts, href) {
|
|
41
|
+
return drafts.find((b) => !b.page || samePage(b.page.url, href)) ?? null;
|
|
42
|
+
}
|
|
43
|
+
function roundBatches(batches) {
|
|
44
|
+
const active = batches.filter((b) => b.status === "sent" || b.status === "working");
|
|
45
|
+
if (active.length === 0) return [];
|
|
46
|
+
const parseSentAt = (b) => {
|
|
47
|
+
const t = Date.parse(b.sentAt ?? "");
|
|
48
|
+
return Number.isNaN(t) ? Infinity : t;
|
|
49
|
+
};
|
|
50
|
+
const minSentAt = Math.min(...active.map(parseSentAt));
|
|
51
|
+
const done = batches.filter((b) => b.status === "done" && b.doneAt && Date.parse(b.doneAt) >= minSentAt);
|
|
52
|
+
return [...active, ...done];
|
|
53
|
+
}
|
|
54
|
+
function roundOf(batches) {
|
|
55
|
+
const round = roundBatches(batches);
|
|
56
|
+
const done = round.filter((b) => b.status === "done").length;
|
|
57
|
+
return { queue: round.length - done, done, total: round.length };
|
|
58
|
+
}
|
|
59
|
+
function pageLoc(url, href) {
|
|
60
|
+
const u = new URL(url);
|
|
61
|
+
const cur = new URL(href);
|
|
62
|
+
return u.origin === cur.origin ? u.pathname + u.search : u.host + u.pathname + u.search;
|
|
63
|
+
}
|
|
64
|
+
function pendingElsewhere(session, href) {
|
|
65
|
+
const item = (session?.pendingDone ?? []).find((p) => !samePage(p.url, href));
|
|
66
|
+
if (!item) return null;
|
|
67
|
+
try {
|
|
68
|
+
return { url: item.url, path: pageLoc(item.url, href) };
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function pathnameOf(url) {
|
|
74
|
+
try {
|
|
75
|
+
return new URL(url).pathname;
|
|
76
|
+
} catch {
|
|
77
|
+
return url;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function queueCards(drafts, batches, href, busy, labels) {
|
|
81
|
+
const noteMeta = (note) => {
|
|
82
|
+
const line = note.split("\n")[0] ?? "";
|
|
83
|
+
return line ? truncate40(line) : labels.noNote;
|
|
84
|
+
};
|
|
85
|
+
const draftCards = drafts.filter((b) => b.elements.length || b.regions?.length || b.note.trim()).map((b) => {
|
|
86
|
+
const url = b.page?.url ?? href;
|
|
87
|
+
const count = b.elements.length + (b.regions?.length ?? 0);
|
|
88
|
+
return {
|
|
89
|
+
kind: "draft",
|
|
90
|
+
url,
|
|
91
|
+
title: b.page?.title || pathnameOf(url),
|
|
92
|
+
path: pageLoc(url, href),
|
|
93
|
+
meta: `${labels.count(count)} \xB7 ${noteMeta(b.note)}`,
|
|
94
|
+
isCurrent: samePage(url, href),
|
|
95
|
+
count
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
const round = roundBatches(batches);
|
|
99
|
+
const progressSrc = busy ? round : round.filter((b) => b.status !== "done");
|
|
100
|
+
const progressCards = progressSrc.slice().sort((a, b) => (a.sentAt ?? "").localeCompare(b.sentAt ?? "")).map((b) => {
|
|
101
|
+
const url = b.page?.url ?? href;
|
|
102
|
+
const count = b.elements.length + (b.regions?.length ?? 0);
|
|
103
|
+
const meta = b.status === "done" ? "\u2713 " + truncate40(b.summary ?? "") : `${labels.count(count)} \xB7 ${noteMeta(b.note)}`;
|
|
104
|
+
return { kind: b.status, url, title: b.page?.title || pathnameOf(url), path: pageLoc(url, href), meta, isCurrent: samePage(url, href), count };
|
|
105
|
+
});
|
|
106
|
+
return [...draftCards, ...progressCards];
|
|
107
|
+
}
|
|
108
|
+
function lastRoundDone(batches) {
|
|
109
|
+
return batches.filter((b) => b.status === "done");
|
|
110
|
+
}
|
|
111
|
+
function truncate40(s) {
|
|
112
|
+
return s.length > 40 ? s.slice(0, 40) + "\u2026" : s;
|
|
113
|
+
}
|
|
114
|
+
|
|
28
115
|
// src/overlay/ui.ts
|
|
116
|
+
var QUEUE_ICON = '<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><rect x="2" y="5" width="12" height="2" rx="1" fill="currentColor"/><rect x="2" y="9" width="12" height="2" rx="1" fill="currentColor"/></svg>';
|
|
29
117
|
var CSS2 = `
|
|
30
118
|
:host{
|
|
31
119
|
--bg:#0e111a;--bg-2:#171b28;--bg-3:#232c42;--chip:#161b2a;--border:#2a3350;--border-2:#3a4a72;
|
|
@@ -62,12 +150,14 @@
|
|
|
62
150
|
.toolbar button:hover,.panel button:hover{background:var(--hover);border-color:var(--hover-border);color:var(--fg-hover)}
|
|
63
151
|
.toolbar button:focus-visible,.panel button:focus-visible{outline:2px solid var(--info);outline-offset:1px}
|
|
64
152
|
.toolbar button.on{color:var(--fg-on-accent);background:var(--accent);border-color:var(--accent)}
|
|
153
|
+
.toolbar button:disabled,.panel button:disabled{opacity:.4;cursor:not-allowed}
|
|
154
|
+
.toolbar button.locked{opacity:.6}
|
|
155
|
+
textarea:disabled{opacity:.6;cursor:not-allowed}
|
|
65
156
|
.els button{background:transparent;border-color:transparent;color:var(--fg-5);padding:0 6px}
|
|
66
157
|
.els button:hover{background:var(--hover);color:var(--fg-hover)}
|
|
67
158
|
.chip{display:inline-flex;align-items:center;gap:5px;padding:2px 8px;border-radius:999px;background:var(--chip);border:1px solid transparent;color:var(--fg-3);white-space:nowrap;cursor:default;user-select:none}
|
|
68
159
|
.chip[hidden]{display:none}
|
|
69
160
|
.chip .ico{display:inline-flex;line-height:0}
|
|
70
|
-
.chip.strategy .ico svg{width:12px;height:12px}
|
|
71
161
|
.chip.off{color:var(--warn);border-color:var(--chip-off-border)}
|
|
72
162
|
.dot{font-size:9px;line-height:1;color:var(--fg-5)}
|
|
73
163
|
.dot.waiting{color:var(--ok)}
|
|
@@ -79,6 +169,7 @@
|
|
|
79
169
|
.status-in{display:inline-block;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom;transition:transform .2s ease-out}
|
|
80
170
|
.status-in.scroll{max-width:none;overflow:visible;text-overflow:clip}
|
|
81
171
|
.status-in.enter{animation:cobroin .15s ease-out}
|
|
172
|
+
.status-in a.goto{color:inherit;text-decoration:underline;cursor:pointer}
|
|
82
173
|
@keyframes cobroin{from{opacity:.4;transform:translateX(-6px)}to{opacity:1;transform:translateX(0)}}
|
|
83
174
|
@media (prefers-reduced-motion: reduce){.status-in{transition:none}.status-in.enter{animation:none}}
|
|
84
175
|
.wbox{position:fixed;border:1.5px solid var(--info);border-radius:2px;pointer-events:none;box-sizing:border-box;animation:cobrobreathe 2.4s ease-in-out infinite}
|
|
@@ -101,6 +192,12 @@
|
|
|
101
192
|
.panel h4 .grip svg{width:14px;height:16px;fill:currentColor;display:block}
|
|
102
193
|
.panel h4 .close{margin-left:auto;background:transparent;border-color:transparent;color:var(--fg-5);padding:0 6px}
|
|
103
194
|
.panel h4 .close:hover{background:var(--hover);color:var(--fg-hover)}
|
|
195
|
+
.panel .tabs{display:flex;flex:1;border-bottom:1px solid var(--border)}
|
|
196
|
+
.panel .tabs button.tab{flex:1;min-height:40px;background:none;border:0;border-bottom:2px solid transparent;color:var(--fg-4);font-weight:600;font-family:inherit;letter-spacing:0;display:inline-flex;align-items:center;justify-content:center;gap:5px;border-radius:0;padding:4px 6px}
|
|
197
|
+
.panel .tabs button.tab.on{color:var(--fg);border-bottom-color:var(--accent)}
|
|
198
|
+
.panel .tabs .badge{display:inline-flex;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:var(--chip);font-size:10px;line-height:16px;text-align:center}
|
|
199
|
+
.sub{display:flex;align-items:center;gap:6px;margin:12px 0;color:var(--fg);font-size:12px;font-weight:700}
|
|
200
|
+
.sub .mark{color:var(--accent)}
|
|
104
201
|
.els{max-height:min(45vh,260px);overflow:auto;margin-bottom:8px;color:var(--fg-3);font-size:11px}
|
|
105
202
|
.els div{display:flex;align-items:center;gap:8px;padding:8px 10px;margin-bottom:8px;background:var(--bg-3);border:1px solid var(--border);border-radius:8px;word-break:break-all}
|
|
106
203
|
.els div:last-child{margin-bottom:0}
|
|
@@ -117,10 +214,34 @@
|
|
|
117
214
|
.marker.child{border-style:dotted}
|
|
118
215
|
.marker.child .n{background:#fff;color:var(--accent);border:1.5px solid var(--accent)}
|
|
119
216
|
textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);background:var(--bg);border:1px solid var(--border-2);border-radius:8px;padding:10px;margin-bottom:0;display:block}
|
|
217
|
+
div.queue{display:flex;flex-direction:column;gap:8px;max-height:min(45vh,320px);overflow:auto;margin:12px 0}
|
|
218
|
+
.queue .card{display:flex;gap:8px;padding:8px;border:1px solid var(--border);border-radius:10px;text-decoration:none;color:inherit;align-items:center}
|
|
219
|
+
.card.working{border-color:var(--accent)}
|
|
220
|
+
.card.here{background:var(--chip)}
|
|
221
|
+
.queue .card .body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
|
|
222
|
+
.queue .card .title{font-weight:600;color:var(--fg-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
223
|
+
.queue .card .path{font-size:10px;color:var(--fg-5);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
224
|
+
.queue .card .meta{font-size:11px;color:var(--fg-4);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
225
|
+
.queue .card .chip{flex:none;font-size:10px;line-height:16px;padding:0 6px;border-radius:999px;background:var(--chip);border:1px solid var(--border);color:var(--fg-4);align-self:flex-start}
|
|
226
|
+
.queue .card.working .chip{color:var(--accent)}
|
|
227
|
+
.queue .card.done .chip{color:var(--ok)}
|
|
228
|
+
.queue .card .here{flex:none;font-size:10px;color:var(--fg-5)}
|
|
229
|
+
.thumb{flex:none;width:44px;height:32px;background:#fff;border-radius:6px;display:flex;align-items:center;justify-content:center;gap:3px;padding:4px}
|
|
230
|
+
.thumb .rect{display:block;width:10px;height:14px;border:1.5px solid var(--border-2);border-radius:2px}
|
|
231
|
+
.thumb .rect.sent{border-color:var(--warn)}
|
|
232
|
+
.thumb .rect.working{border-color:var(--accent)}
|
|
233
|
+
.thumb .rect.done{border-color:var(--ok)}
|
|
234
|
+
.done-row{display:block;width:100%;text-align:left;background:transparent;border:1px dashed var(--border);color:var(--fg-4);padding:8px 10px;border-radius:8px}
|
|
235
|
+
.done-item{display:flex;gap:8px;padding:6px 10px;font-size:11px;color:var(--fg-4)}
|
|
236
|
+
.done-item .title{flex:none;color:var(--fg-3)}
|
|
237
|
+
.done-item .path{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
238
|
+
.done-item .summary{color:var(--ok)}
|
|
120
239
|
.row{display:flex;justify-content:space-between;margin-top:8px;gap:6px;align-items:center}
|
|
121
240
|
.row .sends{color:var(--fg-5);font-size:11px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
122
241
|
.row .send{color:var(--fg-on-accent);background:var(--accent);border-color:var(--accent);font-weight:700;flex:none;white-space:nowrap}
|
|
123
242
|
.row .send:disabled{opacity:.4;cursor:not-allowed}
|
|
243
|
+
.row .foot{font-size:11px;color:var(--fg-4)}
|
|
244
|
+
.row .foot.ok{color:var(--ok)}
|
|
124
245
|
.marker{position:fixed;border:2px solid var(--accent);border-radius:2px;pointer-events:none;box-sizing:border-box}
|
|
125
246
|
.marker.region{border-style:dashed;background:color-mix(in srgb, var(--accent) 6%, transparent)}
|
|
126
247
|
.marker .n{position:absolute;left:-2px;top:-2px;transform:translate(-50%,-50%);min-width:18px;height:18px;padding:0 5px;border-radius:9px;background:var(--accent);color:#fff;font:700 11px/18px ui-monospace,Menlo,Consolas,monospace;text-align:center;box-shadow:0 1px 3px rgba(0,0,0,.4)}
|
|
@@ -144,13 +265,17 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
144
265
|
.seg button.on{background:var(--bg-3);color:var(--fg)}
|
|
145
266
|
.seg button:disabled{opacity:.5;cursor:not-allowed}
|
|
146
267
|
.pop-note{margin-top:6px;color:var(--warn);font-size:11px}
|
|
268
|
+
.pop-row.strategy{align-items:center;flex-wrap:wrap}
|
|
269
|
+
.pop-row.strategy[hidden]{display:none}
|
|
270
|
+
.strategy-val{display:inline-flex;align-items:center;gap:4px;font-family:ui-monospace,Menlo,Consolas,monospace;padding:2px 8px;border-radius:999px;background:var(--chip);color:var(--fg-3)}
|
|
271
|
+
.strategy-val .ico svg{width:12px;height:12px}
|
|
272
|
+
.pop-sub{font-size:11px;color:var(--fg-4);margin-left:8px}
|
|
147
273
|
.ib{display:inline-flex;align-items:center;padding:4px 7px}
|
|
148
274
|
.ib .ico{display:inline-flex;line-height:0}
|
|
149
275
|
.ib .lbl{max-width:0;opacity:0;overflow:hidden;white-space:nowrap;margin-left:0;transition:max-width .1s ease-in,opacity .1s ease-in,margin-left .1s ease-in}
|
|
150
276
|
.ib:hover .lbl,.ib:focus-visible .lbl{max-width:9ch;opacity:1;margin-left:5px;transition-duration:.15s;transition-timing-function:ease-out}
|
|
151
277
|
@media (prefers-reduced-motion: reduce){.ib .lbl{transition:none}}
|
|
152
278
|
@media (max-width:760px){
|
|
153
|
-
.chip.strategy{display:none}
|
|
154
279
|
.status{width:180px}
|
|
155
280
|
.ib .lbl,.ib:hover .lbl,.ib:focus-visible .lbl{max-width:0;opacity:0;margin-left:0}
|
|
156
281
|
}
|
|
@@ -184,7 +309,7 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
184
309
|
hintMore: (n) => `${n}\uAC1C \uC120\uD0DD \xB7 \uB354 \uACE0\uB974\uAC70\uB098 \uBA54\uBAA8\uB97C \uC801\uC73C\uC138\uC694`,
|
|
185
310
|
hintNote: "\uBA54\uBAA8\uB97C \uC801\uACE0 Send\uB97C \uB204\uB974\uC138\uC694",
|
|
186
311
|
hintPick: "Ctrl+Shift+F \uB610\uB294 Select\uB85C \uC694\uC18C\uB97C \uACE0\uB974\uC138\uC694",
|
|
187
|
-
|
|
312
|
+
strategyDesc: (s) => ({ none: "HMR\uC774 \uC788\uC5B4 done \uB4A4 \uC0C8\uB85C\uACE0\uCE68 \uC5C6\uC74C", reload: "done \uB4A4 \uD398\uC774\uC9C0 \uC0C8\uB85C\uACE0\uCE68", event: "\uC571\uC774 cobro:done \uC774\uBCA4\uD2B8\uB85C \uC9C1\uC811 \uAC31\uC2E0" })[s],
|
|
188
313
|
selCount: (n) => `\uC694\uC18C ${n}\uAC1C \uC120\uD0DD\uB428`,
|
|
189
314
|
selCountMixed: (n, m) => `\uC694\uC18C ${n}\uAC1C + \uC601\uC5ED ${m}\uAC1C \uC120\uD0DD\uB428`,
|
|
190
315
|
regionRow: (w, h) => `\u25AD ${w}\xD7${h}`,
|
|
@@ -200,15 +325,31 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
200
325
|
tipDrag: "\uD234\uBC14 \uC774\uB3D9",
|
|
201
326
|
sends: "\uC120\uD0DD\uC790 \xB7 \uC2A4\uD0C0\uC77C \xB7 \uC2A4\uD06C\uB9B0\uC0F7 \xB7 \uCF58\uC194",
|
|
202
327
|
tipSend: "\uC120\uD0DD\uD55C \uC694\uC18C\uC640 \uBA54\uBAA8\uB97C \uC5D0\uC774\uC804\uD2B8\uC5D0 \uC804\uC1A1",
|
|
203
|
-
tipSendLocked: "\uC5D0\uC774\uC804\uD2B8\uAC00 \uC791\uC5C5 \uC911 \u2014 done \uB4A4\uC5D0 \uBCF4\uB0BC \uC218 \uC788\uC2B5\uB2C8\uB2E4",
|
|
204
328
|
tipRemove: "\uC774 \uC694\uC18C \uBE7C\uAE30",
|
|
329
|
+
cartNoNote: "\uBA54\uBAA8 \uC5C6\uC74C",
|
|
330
|
+
doneElsewhere: (path) => `\u2713 \uC644\uB8CC \xB7 ${path} \uBCF4\uAE30`,
|
|
331
|
+
tabHere: "\uC774 \uD398\uC774\uC9C0",
|
|
332
|
+
tabQueue: "\uD050",
|
|
333
|
+
herePage: "\uC9C0\uAE08 \uBCF4\uACE0 \uC788\uB294 \uD398\uC774\uC9C0",
|
|
334
|
+
queueCount: (n) => `${n}\uAC1C`,
|
|
335
|
+
footBusy: (d, n) => `\uCC98\uB9AC \uC911 ${d}/${n} \u2014 \uB05D\uB098\uBA74 \uB2F4\uC744 \uC218 \uC788\uC5B4\uC694`,
|
|
336
|
+
footPaused: (d, n) => `\uCC98\uB9AC \uC911 ${d}/${n} \u2014 \uC774 \uD398\uC774\uC9C0\uB97C \uB5A0\uB098 \uB530\uB77C\uAC00\uAE30\uB97C \uBA48\uCDC4\uC5B4\uC694`,
|
|
337
|
+
footAllDone: (n) => `\u2713 ${n}\uD398\uC774\uC9C0 \uC644\uB8CC \u2014 \uC694\uC18C\uB97C \uACE0\uB974\uBA74 \uC0C8 \uC694\uCCAD\uC774 \uC2DC\uC791\uB429\uB2C8\uB2E4`,
|
|
338
|
+
footHint: "\uBA54\uBAA8\uB97C \uC801\uC73C\uBA74 \uBCF4\uB0BC \uC218 \uC788\uC5B4\uC694",
|
|
339
|
+
tipSelectLocked: "\uC5D0\uC774\uC804\uD2B8\uAC00 \uC218\uC815 \uC911 \u2014 \uD050\uB97C \uBCFC \uC218 \uC788\uC5B4\uC694, \uB2F4\uAE30\uB294 \uC644\uB8CC \uD6C4",
|
|
340
|
+
followPaused: "\uB530\uB77C\uAC00\uAE30 \uC911\uC9C0 \u2014 \uC644\uB8CC\uB418\uBA74 \u300C\uBCF4\uAE30\u300D \uB9C1\uD06C\uB85C \uD655\uC778",
|
|
341
|
+
doneRow: (n) => `\u2713 \uC644\uB8CC ${n}\uD398\uC774\uC9C0 \xB7 \uC0C8\uB85C \uB2F4\uC73C\uBA74 \uC815\uB9AC\uB429\uB2C8\uB2E4`,
|
|
342
|
+
sendPages: (n) => `Send \xB7 ${n}\uD398\uC774\uC9C0 \u2192`,
|
|
343
|
+
chipDraft: "\uCD08\uC548",
|
|
344
|
+
progress: (d, n) => `${d}/${n}`,
|
|
205
345
|
tipSettings: "\uC124\uC815",
|
|
206
346
|
theme: "\uD14C\uB9C8",
|
|
207
347
|
themeAuto: "\uC790\uB3D9",
|
|
208
348
|
themeDark: "\uC5B4\uB461\uAC8C",
|
|
209
349
|
themeLight: "\uBC1D\uAC8C",
|
|
210
350
|
themeFrost: "\uC720\uB9AC",
|
|
211
|
-
themeLocked: "COBRO_THEME \uD658\uACBD \uBCC0\uC218\uB85C \uACE0\uC815\uB428"
|
|
351
|
+
themeLocked: "COBRO_THEME \uD658\uACBD \uBCC0\uC218\uB85C \uACE0\uC815\uB428",
|
|
352
|
+
strategyLabel: "\uAC31\uC2E0 \uC804\uB7B5"
|
|
212
353
|
},
|
|
213
354
|
en: {
|
|
214
355
|
agentIdle: "Agent not connected",
|
|
@@ -230,7 +371,7 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
230
371
|
hintMore: (n) => `${n} selected \xB7 pick more or write a note`,
|
|
231
372
|
hintNote: "Write a note, then press Send",
|
|
232
373
|
hintPick: "Press Ctrl+Shift+F or Select to pick an element",
|
|
233
|
-
|
|
374
|
+
strategyDesc: (s) => ({ none: "HMR present \u2014 no reload after done", reload: "page reloads after done", event: "the app refreshes itself on cobro:done" })[s],
|
|
234
375
|
selCount: (n) => `${n} element(s) selected`,
|
|
235
376
|
selCountMixed: (n, m) => `${n} element(s) + ${m} region(s) selected`,
|
|
236
377
|
regionRow: (w, h) => `\u25AD ${w}\xD7${h}`,
|
|
@@ -246,15 +387,31 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
246
387
|
tipDrag: "Move toolbar",
|
|
247
388
|
sends: "Sends selector \xB7 styles \xB7 shot \xB7 console",
|
|
248
389
|
tipSend: "Send the selected elements and note to the agent",
|
|
249
|
-
tipSendLocked: "Agent is working \u2014 you can send after done",
|
|
250
390
|
tipRemove: "Remove this element",
|
|
391
|
+
cartNoNote: "No note",
|
|
392
|
+
doneElsewhere: (path) => `\u2713 Done \xB7 view ${path}`,
|
|
393
|
+
tabHere: "This page",
|
|
394
|
+
tabQueue: "Queue",
|
|
395
|
+
herePage: "The page you're on",
|
|
396
|
+
queueCount: (n) => `${n} element${n === 1 ? "" : "s"}`,
|
|
397
|
+
footBusy: (d, n) => `Working ${d}/${n} \u2014 you can add more once it's done`,
|
|
398
|
+
footPaused: (d, n) => `Working ${d}/${n} \u2014 you left this page, so following paused`,
|
|
399
|
+
footAllDone: (n) => `\u2713 ${n} page${n === 1 ? "" : "s"} done \u2014 pick an element to start a new request`,
|
|
400
|
+
footHint: "Write a note to send",
|
|
401
|
+
tipSelectLocked: "Agent is working \u2014 view the queue; picking resumes after done",
|
|
402
|
+
followPaused: `Following paused \u2014 check the "view" link when it's done`,
|
|
403
|
+
doneRow: (n) => `\u2713 ${n} done \xB7 cleared when you pick again`,
|
|
404
|
+
sendPages: (n) => `Send \xB7 ${n} pages \u2192`,
|
|
405
|
+
chipDraft: "Draft",
|
|
406
|
+
progress: (d, n) => `${d}/${n}`,
|
|
251
407
|
tipSettings: "Settings",
|
|
252
408
|
theme: "Theme",
|
|
253
409
|
themeAuto: "Auto",
|
|
254
410
|
themeDark: "Dark",
|
|
255
411
|
themeLight: "Light",
|
|
256
412
|
themeFrost: "Frost",
|
|
257
|
-
themeLocked: "Pinned by COBRO_THEME"
|
|
413
|
+
themeLocked: "Pinned by COBRO_THEME",
|
|
414
|
+
strategyLabel: "Refresh"
|
|
258
415
|
}
|
|
259
416
|
}[LANG];
|
|
260
417
|
var THEME_LABEL = { auto: T.themeAuto, dark: T.themeDark, light: T.themeLight, frost: T.themeFrost };
|
|
@@ -267,7 +424,7 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
267
424
|
done: (t) => stripStatusLabel(t, LABELS.done)
|
|
268
425
|
};
|
|
269
426
|
var DOT_TITLE = { idle: T.agentIdle, waiting: T.agentWaiting, sent: T.agentSent, working: T.agentWorking, done: T.agentDone };
|
|
270
|
-
var CHIP_LABEL = { idle: T.chipIdle, waiting: T.chipWaiting, sent: T.chipSent, working: T.chipWorking, done: T.chipDone };
|
|
427
|
+
var CHIP_LABEL = { idle: T.chipIdle, waiting: T.chipWaiting, sent: T.chipSent, working: T.chipWorking, done: T.chipDone, draft: T.chipDraft };
|
|
271
428
|
function makeDraggable(target, handle, opts) {
|
|
272
429
|
const current = target.style.translate.split(" ").map((v) => parseFloat(v) || 0);
|
|
273
430
|
let off = { x: current[0] ?? 0, y: current[1] ?? 0 };
|
|
@@ -344,14 +501,6 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
344
501
|
const chipLabel = document.createElement("span");
|
|
345
502
|
chipLabel.className = "chip-label";
|
|
346
503
|
chip.append(dot, chipLabel);
|
|
347
|
-
const chip2 = document.createElement("span");
|
|
348
|
-
chip2.className = "chip strategy";
|
|
349
|
-
const chip2Ico = document.createElement("span");
|
|
350
|
-
chip2Ico.className = "ico";
|
|
351
|
-
chip2Ico.innerHTML = svg("sync");
|
|
352
|
-
const chip2Label = document.createElement("span");
|
|
353
|
-
chip2Label.className = "chip-label";
|
|
354
|
-
chip2.append(chip2Ico, chip2Label);
|
|
355
504
|
const status = document.createElement("span");
|
|
356
505
|
status.className = "status";
|
|
357
506
|
const statusIn = document.createElement("span");
|
|
@@ -362,7 +511,7 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
362
511
|
const gripParts = iconBtn("drag", "Move", T.tipDrag, "grip", false);
|
|
363
512
|
const grip = gripParts.btn;
|
|
364
513
|
grip.setAttribute("aria-label", "Move toolbar");
|
|
365
|
-
toolbar.append(grip, selectBtn, chip,
|
|
514
|
+
toolbar.append(grip, selectBtn, chip, status, gearBtn);
|
|
366
515
|
const line = document.createElement("span");
|
|
367
516
|
line.className = "line";
|
|
368
517
|
line.hidden = true;
|
|
@@ -386,10 +535,25 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
386
535
|
});
|
|
387
536
|
seg2.append(...segButtons);
|
|
388
537
|
popRow.append(popLabel, seg2);
|
|
538
|
+
const stratRow = document.createElement("div");
|
|
539
|
+
stratRow.className = "pop-row strategy";
|
|
540
|
+
const stratLabel = document.createElement("span");
|
|
541
|
+
stratLabel.className = "pop-label";
|
|
542
|
+
stratLabel.textContent = T.strategyLabel;
|
|
543
|
+
const stratVal = document.createElement("span");
|
|
544
|
+
stratVal.className = "strategy-val";
|
|
545
|
+
const stratIco = document.createElement("span");
|
|
546
|
+
stratIco.className = "ico";
|
|
547
|
+
stratIco.innerHTML = svg("sync");
|
|
548
|
+
const stratValText = document.createElement("span");
|
|
549
|
+
stratVal.append(stratIco, stratValText);
|
|
550
|
+
const stratSub = document.createElement("span");
|
|
551
|
+
stratSub.className = "pop-sub";
|
|
552
|
+
stratRow.append(stratLabel, stratVal, stratSub);
|
|
389
553
|
const popNote = document.createElement("div");
|
|
390
554
|
popNote.className = "pop-note";
|
|
391
555
|
popNote.textContent = T.themeLocked;
|
|
392
|
-
pop.append(popRow, popNote);
|
|
556
|
+
pop.append(popRow, stratRow, popNote);
|
|
393
557
|
let popOpen = false;
|
|
394
558
|
let popCloseListeners = null;
|
|
395
559
|
const closePop = () => {
|
|
@@ -487,6 +651,7 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
487
651
|
if (text != null) d.textContent = text;
|
|
488
652
|
return d;
|
|
489
653
|
};
|
|
654
|
+
const truncate402 = (s) => s.length > 40 ? s.slice(0, 40) + "\u2026" : s;
|
|
490
655
|
function render(vm) {
|
|
491
656
|
renderMarkers(vm);
|
|
492
657
|
updateTbH();
|
|
@@ -494,15 +659,22 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
494
659
|
const wasTa = active instanceof HTMLTextAreaElement ? active : null;
|
|
495
660
|
const sel = wasTa ? [wasTa.selectionStart, wasTa.selectionEnd] : null;
|
|
496
661
|
selectBtn.classList.toggle("on", vm.selecting);
|
|
662
|
+
selectBtn.classList.toggle("locked", vm.locked);
|
|
663
|
+
selectBtn.title = vm.locked ? T.tipSelectLocked : T.tipSelect;
|
|
497
664
|
for (const btn of segButtons) {
|
|
498
665
|
btn.classList.toggle("on", btn.dataset.theme === vm.prefs.theme);
|
|
499
666
|
btn.disabled = vm.prefs.themeLocked;
|
|
500
667
|
}
|
|
501
668
|
popNote.hidden = !vm.prefs.themeLocked;
|
|
502
|
-
const cur = vm.
|
|
669
|
+
const cur = vm.current;
|
|
503
670
|
const hasElements = !!cur && cur.elements.length + (cur.regions?.length ?? 0) > 0;
|
|
504
671
|
let hint;
|
|
672
|
+
let gotoUrl = null;
|
|
505
673
|
if (!vm.connected) hint = T.disconnected;
|
|
674
|
+
else if (!vm.busy && vm.pendingElsewhere) {
|
|
675
|
+
hint = T.doneElsewhere(vm.pendingElsewhere.path);
|
|
676
|
+
gotoUrl = vm.pendingElsewhere.url;
|
|
677
|
+
} else if (vm.locked && vm.followPaused) hint = T.followPaused;
|
|
506
678
|
else {
|
|
507
679
|
let text;
|
|
508
680
|
if (vm.agent.status === "sent" || vm.agent.status === "working" || vm.agent.status === "done") text = AGENT_TEXT[vm.agent.status](vm.agent.text);
|
|
@@ -523,7 +695,16 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
523
695
|
statusIn.classList.add("enter");
|
|
524
696
|
}
|
|
525
697
|
}
|
|
526
|
-
|
|
698
|
+
if (gotoUrl) {
|
|
699
|
+
statusIn.textContent = "";
|
|
700
|
+
const a = document.createElement("a");
|
|
701
|
+
a.className = "goto";
|
|
702
|
+
a.href = gotoUrl;
|
|
703
|
+
a.textContent = displayHint;
|
|
704
|
+
statusIn.append(a);
|
|
705
|
+
} else {
|
|
706
|
+
statusIn.textContent = displayHint;
|
|
707
|
+
}
|
|
527
708
|
statusIn.title = hint;
|
|
528
709
|
if (hovering) {
|
|
529
710
|
applyHoverScroll();
|
|
@@ -532,117 +713,181 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
532
713
|
status.classList.toggle("off", !vm.connected);
|
|
533
714
|
chip.classList.toggle("off", !vm.connected);
|
|
534
715
|
dot.className = vm.connected ? "dot " + vm.agent.status : "dot";
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
716
|
+
const round = roundOf(vm.queue);
|
|
717
|
+
const roundSuffix = vm.busy ? " " + T.progress(round.done, round.total) : "";
|
|
718
|
+
chipLabel.textContent = vm.connected ? CHIP_LABEL[vm.agent.status] + roundSuffix : T.chipOff;
|
|
719
|
+
chip.title = vm.connected ? DOT_TITLE[vm.agent.status] + roundSuffix : T.disconnected;
|
|
720
|
+
stratRow.hidden = !vm.strategy;
|
|
538
721
|
if (vm.strategy) {
|
|
539
|
-
|
|
540
|
-
|
|
722
|
+
stratValText.textContent = vm.strategy;
|
|
723
|
+
stratSub.textContent = T.strategyDesc(vm.strategy);
|
|
724
|
+
stratRow.title = T.strategyDesc(vm.strategy);
|
|
541
725
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
if (vm.drafts.length === 0) {
|
|
726
|
+
panel.classList.toggle("show", vm.panelOpen);
|
|
727
|
+
if (!vm.panelOpen) {
|
|
545
728
|
panel.textContent = "";
|
|
546
729
|
textareas.clear();
|
|
547
730
|
return;
|
|
548
731
|
}
|
|
549
|
-
if (!show) return;
|
|
550
732
|
panel.textContent = "";
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
row2.
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
row2.append(
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
const
|
|
602
|
-
if (
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
733
|
+
const labels = { noNote: T.cartNoNote, count: T.queueCount };
|
|
734
|
+
const cards = queueCards(vm.drafts, vm.batches, vm.href, vm.busy, labels);
|
|
735
|
+
const doneList = lastRoundDone(vm.batches);
|
|
736
|
+
const queueN = cards.length > 0 ? cards.length : doneList.length;
|
|
737
|
+
const h4 = el("h4");
|
|
738
|
+
const g = el("span", "grip");
|
|
739
|
+
g.innerHTML = svg("drag");
|
|
740
|
+
g.setAttribute("aria-hidden", "true");
|
|
741
|
+
h4.append(g);
|
|
742
|
+
const tabs = el("div", "tabs");
|
|
743
|
+
const tabHereBtn = el("button", "tab current" + (vm.tab === "here" ? " on" : ""), T.tabHere);
|
|
744
|
+
tabHereBtn.onclick = () => h.onTab("here");
|
|
745
|
+
const tabQueueBtn = el("button", "tab queue" + (vm.tab === "queue" ? " on" : ""));
|
|
746
|
+
tabQueueBtn.innerHTML = QUEUE_ICON;
|
|
747
|
+
tabQueueBtn.append(document.createTextNode(T.tabQueue), el("span", "badge", String(queueN)));
|
|
748
|
+
tabQueueBtn.onclick = () => h.onTab("queue");
|
|
749
|
+
tabs.append(tabHereBtn, tabQueueBtn);
|
|
750
|
+
h4.append(tabs);
|
|
751
|
+
const close = el("button", "close", "\u2715");
|
|
752
|
+
close.title = T.tipClose;
|
|
753
|
+
close.setAttribute("aria-label", "Close");
|
|
754
|
+
close.onclick = () => h.onClose();
|
|
755
|
+
h4.append(close);
|
|
756
|
+
panel.append(h4);
|
|
757
|
+
if (vm.tab === "here") {
|
|
758
|
+
if (cur) {
|
|
759
|
+
const sub = el("div", "sub");
|
|
760
|
+
sub.append(el("span", "mark", "\u25AE"), document.createTextNode(cur.regions?.length ? T.selCountMixed(cur.elements.length, cur.regions.length) : cur.elements.length > 0 ? T.selCount(cur.elements.length) : T.selNone));
|
|
761
|
+
panel.append(sub);
|
|
762
|
+
const list = el("div", "els");
|
|
763
|
+
const looseEls = cur.elements.filter((e) => /^\d+$/.test(e.ref ?? ""));
|
|
764
|
+
const rows = [];
|
|
765
|
+
looseEls.forEach((e) => {
|
|
766
|
+
const idx = cur.elements.indexOf(e);
|
|
767
|
+
rows.push({ ref: e.ref, render: () => {
|
|
768
|
+
const row2 = el("div", e.missing ? "missing" : "");
|
|
769
|
+
row2.append(el("span", "num", `${e.ref}.`));
|
|
770
|
+
const comp = (e.react ?? e.vue)?.component;
|
|
771
|
+
row2.append(el("span", "label", `${e.selector.split(" > ").pop()}${comp ? " \xB7 " + comp : ""}${e.missing ? " \xB7 " + T.elMissing : ""}`));
|
|
772
|
+
row2.append(el("span", "kind", e.tag));
|
|
773
|
+
const x = el("button", "", "\u2715");
|
|
774
|
+
x.title = T.tipRemove;
|
|
775
|
+
x.onclick = () => h.onRemoveElement(cur.id, idx);
|
|
776
|
+
row2.append(x);
|
|
777
|
+
list.append(row2);
|
|
778
|
+
} });
|
|
779
|
+
});
|
|
780
|
+
cur.regions?.forEach((r, i) => {
|
|
781
|
+
rows.push({ ref: r.ref ?? "", render: () => {
|
|
782
|
+
const kids = r.ref ? cur.elements.filter((e) => isChildRef(e.ref, r.ref)) : [];
|
|
783
|
+
const row2 = el("div", "");
|
|
784
|
+
if (kids.length) {
|
|
785
|
+
row2.classList.add("group");
|
|
786
|
+
const chev = el("span", "chev", vm.expanded === r.ref ? "\u25BE" : "\u25B8");
|
|
787
|
+
chev.title = T.tipExpand;
|
|
788
|
+
row2.append(chev);
|
|
789
|
+
}
|
|
790
|
+
row2.append(el("span", "num", `${r.ref ?? ""}.`));
|
|
791
|
+
row2.append(el("span", "label", `${T.regionRow(r.rect.w, r.rect.h)}${r.within ? T.regionIn(r.within.split(" > ").pop()) : ""}`));
|
|
792
|
+
if (kids.length) row2.append(el("span", "cnt", T.groupInside(kids.length)));
|
|
793
|
+
if (!kids.length) row2.append(el("span", "kind region", "region"));
|
|
794
|
+
const x = el("button", "", "\u2715");
|
|
795
|
+
x.title = T.tipRemove;
|
|
796
|
+
x.onclick = () => h.onRemoveRegion(cur.id, i);
|
|
797
|
+
row2.append(x);
|
|
798
|
+
if (kids.length) row2.addEventListener("click", (ev) => {
|
|
799
|
+
const t = ev.target;
|
|
800
|
+
if (t.closest("button") || t.closest(".num")) return;
|
|
801
|
+
h.onToggleGroup(r.ref);
|
|
619
802
|
});
|
|
803
|
+
list.append(row2);
|
|
804
|
+
if (kids.length && vm.expanded === r.ref) {
|
|
805
|
+
kids.forEach((c) => {
|
|
806
|
+
const cidx = cur.elements.indexOf(c);
|
|
807
|
+
const crow = el("div", "child" + (c.missing ? " missing" : ""));
|
|
808
|
+
crow.append(el("span", "num", `${c.ref}.`));
|
|
809
|
+
const ccomp = (c.react ?? c.vue)?.component;
|
|
810
|
+
crow.append(el("span", "label", `${c.selector.split(" > ").pop()}${ccomp ? " \xB7 " + ccomp : ""}${c.missing ? " \xB7 " + T.elMissing : ""}`));
|
|
811
|
+
crow.append(el("span", "kind", c.tag));
|
|
812
|
+
const cx = el("button", "", "\u2715");
|
|
813
|
+
cx.title = T.tipRemove;
|
|
814
|
+
cx.onclick = () => h.onRemoveElement(cur.id, cidx);
|
|
815
|
+
crow.append(cx);
|
|
816
|
+
list.append(crow);
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
} });
|
|
820
|
+
});
|
|
821
|
+
rows.sort((a, b) => Number(a.ref) - Number(b.ref));
|
|
822
|
+
rows.forEach((r) => r.render());
|
|
823
|
+
panel.append(list);
|
|
824
|
+
let ta = textareas.get(cur.id);
|
|
825
|
+
if (!ta) {
|
|
826
|
+
ta = document.createElement("textarea");
|
|
827
|
+
const id = cur.id;
|
|
828
|
+
ta.addEventListener("input", () => h.onNoteInput(id, ta.value));
|
|
829
|
+
textareas.set(id, ta);
|
|
830
|
+
}
|
|
831
|
+
ta.placeholder = cur.elements.length + (cur.regions?.length ?? 0) >= 2 ? T.notePlaceholderMulti : T.notePlaceholder;
|
|
832
|
+
if (ta.value !== cur.note) ta.value = cur.note;
|
|
833
|
+
ta.disabled = vm.locked;
|
|
834
|
+
panel.append(ta);
|
|
835
|
+
}
|
|
836
|
+
} else {
|
|
837
|
+
const queue = el("div", "queue");
|
|
838
|
+
for (const c of cards) {
|
|
839
|
+
const a = document.createElement("a");
|
|
840
|
+
a.className = `card ${c.kind}` + (c.isCurrent ? " here" : "");
|
|
841
|
+
a.href = c.url;
|
|
842
|
+
a.onclick = (ev) => {
|
|
843
|
+
ev.preventDefault();
|
|
844
|
+
h.onGoPage(c.url);
|
|
845
|
+
};
|
|
846
|
+
const thumb = el("div", "thumb");
|
|
847
|
+
for (let i = 0; i < Math.min(c.count, 2); i++) thumb.append(el("span", `rect ${c.kind}`));
|
|
848
|
+
a.append(thumb);
|
|
849
|
+
const body = el("div", "body");
|
|
850
|
+
body.append(el("div", "title", c.title), el("div", "path", c.path), el("div", "meta", c.meta));
|
|
851
|
+
a.append(body);
|
|
852
|
+
a.append(el("span", "chip", CHIP_LABEL[c.kind]));
|
|
853
|
+
if (c.isCurrent) a.append(el("span", "here", T.herePage));
|
|
854
|
+
queue.append(a);
|
|
855
|
+
}
|
|
856
|
+
if (!vm.busy && doneList.length > 0) {
|
|
857
|
+
const doneRow = el("button", "done-row", `${vm.doneOpen ? "\u25BE" : "\u25B8"} ${T.doneRow(doneList.length)}`);
|
|
858
|
+
doneRow.onclick = () => h.onToggleDone();
|
|
859
|
+
queue.append(doneRow);
|
|
860
|
+
if (vm.doneOpen) {
|
|
861
|
+
for (const b of doneList) {
|
|
862
|
+
const url = b.page?.url ?? vm.href;
|
|
863
|
+
const item = el("div", "done-item");
|
|
864
|
+
item.append(el("span", "title", b.page?.title || url), el("span", "path", pageLoc(url, vm.href)), el("span", "summary", "\u2713 " + truncate402(b.summary ?? "")));
|
|
865
|
+
queue.append(item);
|
|
620
866
|
}
|
|
621
|
-
}
|
|
622
|
-
});
|
|
623
|
-
rows.sort((a, b) => Number(a.ref) - Number(b.ref));
|
|
624
|
-
rows.forEach((r) => r.render());
|
|
625
|
-
panel.append(list);
|
|
626
|
-
let ta = textareas.get(cur.id);
|
|
627
|
-
if (!ta) {
|
|
628
|
-
ta = document.createElement("textarea");
|
|
629
|
-
const id = cur.id;
|
|
630
|
-
ta.addEventListener("input", () => h.onNoteInput(id, ta.value));
|
|
631
|
-
textareas.set(id, ta);
|
|
867
|
+
}
|
|
632
868
|
}
|
|
633
|
-
|
|
634
|
-
if (ta.value !== cur.note) ta.value = cur.note;
|
|
635
|
-
panel.append(ta);
|
|
869
|
+
panel.append(queue);
|
|
636
870
|
}
|
|
871
|
+
const readyDrafts = vm.drafts.filter((b) => b.note.trim());
|
|
637
872
|
const row = el("div", "row");
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
873
|
+
if (readyDrafts.length > 0) {
|
|
874
|
+
const send = el("button", "send", readyDrafts.length >= 2 ? T.sendPages(readyDrafts.length) : "Send \u2192");
|
|
875
|
+
send.title = T.tipSend;
|
|
876
|
+
send.onclick = () => {
|
|
877
|
+
closePop();
|
|
878
|
+
h.onSend();
|
|
879
|
+
};
|
|
880
|
+
row.append(el("span", "sends", T.sends), send);
|
|
881
|
+
} else {
|
|
882
|
+
let footText;
|
|
883
|
+
let footOk = false;
|
|
884
|
+
if (vm.busy) footText = vm.followPaused ? T.footPaused(round.done, round.total) : T.footBusy(round.done, round.total);
|
|
885
|
+
else if (doneList.length > 0) {
|
|
886
|
+
footText = T.footAllDone(doneList.length);
|
|
887
|
+
footOk = true;
|
|
888
|
+
} else footText = T.footHint;
|
|
889
|
+
row.append(el("span", `foot${footOk ? " ok" : ""}`, footText));
|
|
890
|
+
}
|
|
646
891
|
panel.append(row);
|
|
647
892
|
for (const id of [...textareas.keys()]) if (!vm.drafts.some((b) => b.id === id)) textareas.delete(id);
|
|
648
893
|
if (wasTa && sel && wasTa.isConnected) {
|
|
@@ -663,7 +908,7 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
663
908
|
m.append(el("span", "n", String(n)));
|
|
664
909
|
root.append(m);
|
|
665
910
|
};
|
|
666
|
-
const cur = vm.
|
|
911
|
+
const cur = vm.current;
|
|
667
912
|
if (cur) {
|
|
668
913
|
cur.elements.forEach((e) => {
|
|
669
914
|
if (e.missing) return;
|
|
@@ -683,7 +928,7 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
683
928
|
place(el("div", "marker region"), r.rect.x - scrollX, r.rect.y - scrollY, r.rect.w, r.rect.h, r.ref ?? "");
|
|
684
929
|
});
|
|
685
930
|
}
|
|
686
|
-
const working = vm.
|
|
931
|
+
const working = vm.busy && vm.connected;
|
|
687
932
|
line.hidden = !working;
|
|
688
933
|
if (working) {
|
|
689
934
|
const wbox = (cls) => {
|
|
@@ -693,7 +938,9 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
693
938
|
m.append(clip);
|
|
694
939
|
return m;
|
|
695
940
|
};
|
|
696
|
-
for (const b of vm.
|
|
941
|
+
for (const b of vm.queue) {
|
|
942
|
+
if (b.status === "done") continue;
|
|
943
|
+
if (b.page && !samePage(b.page.url, vm.href)) continue;
|
|
697
944
|
b.elements.forEach((e) => {
|
|
698
945
|
if (e.missing) return;
|
|
699
946
|
if (/[a-z]$/.test(e.ref ?? "")) return;
|
|
@@ -1193,23 +1440,25 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1193
1440
|
document.documentElement.setAttribute("data-cobro", "1");
|
|
1194
1441
|
let session = null;
|
|
1195
1442
|
let drafts = null;
|
|
1196
|
-
let current = null;
|
|
1197
1443
|
let connected = false;
|
|
1198
1444
|
let stateSeen = false;
|
|
1199
1445
|
let openPending = false;
|
|
1200
1446
|
let draftTimer = null;
|
|
1201
1447
|
let prefs = { theme: "auto", themeLocked: false };
|
|
1202
1448
|
let expandedGroup = null;
|
|
1449
|
+
let tab = "here";
|
|
1450
|
+
let doneOpen = false;
|
|
1451
|
+
let panelOpen = false;
|
|
1203
1452
|
const mq = matchMedia("(prefers-color-scheme: dark)");
|
|
1204
1453
|
const pageInfo = () => ({ url: location.href, title: document.title, viewport: { w: innerWidth, h: innerHeight } });
|
|
1205
|
-
const newBatch = () => ({ id: crypto.randomUUID(), note: "", elements: [], status: "draft", createdAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1454
|
+
const newBatch = () => ({ id: crypto.randomUUID(), note: "", elements: [], status: "draft", createdAt: (/* @__PURE__ */ new Date()).toISOString(), page: { url: location.href, title: document.title } });
|
|
1206
1455
|
const ensureCurrent = () => {
|
|
1207
1456
|
drafts ??= [];
|
|
1208
|
-
let b = drafts
|
|
1457
|
+
let b = currentDraft(drafts, location.href);
|
|
1209
1458
|
if (!b) {
|
|
1210
1459
|
b = newBatch();
|
|
1211
1460
|
drafts.push(b);
|
|
1212
|
-
|
|
1461
|
+
tab = "here";
|
|
1213
1462
|
}
|
|
1214
1463
|
return b;
|
|
1215
1464
|
};
|
|
@@ -1218,26 +1467,48 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1218
1467
|
clearTimeout(draftTimer);
|
|
1219
1468
|
draftTimer = null;
|
|
1220
1469
|
}
|
|
1221
|
-
|
|
1470
|
+
const href = location.href;
|
|
1471
|
+
const here = (drafts ?? []).filter((d) => !d.page || samePage(d.page.url, href));
|
|
1472
|
+
chan.send({ type: "draft", batches: here, page: { url: href, title: document.title } });
|
|
1222
1473
|
};
|
|
1223
1474
|
const pushDraft = () => {
|
|
1224
1475
|
if (draftTimer) clearTimeout(draftTimer);
|
|
1225
1476
|
draftTimer = setTimeout(flushDraft, 300);
|
|
1226
1477
|
};
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1478
|
+
const isLocked = () => session?.agent.status === "sent" || session?.agent.status === "working";
|
|
1479
|
+
const vm = () => {
|
|
1480
|
+
const href = location.href;
|
|
1481
|
+
return {
|
|
1482
|
+
selecting: picker.isActive(),
|
|
1483
|
+
connected,
|
|
1484
|
+
agent: session?.agent ?? { status: "idle", text: "" },
|
|
1485
|
+
strategy: session ? session.strategy ?? session.detected : null,
|
|
1486
|
+
drafts: drafts ?? [],
|
|
1487
|
+
current: currentDraft(drafts ?? [], href),
|
|
1488
|
+
queue: roundBatches(session?.batches ?? []),
|
|
1489
|
+
// R166·R169: sent·working·라운드 done(현재+다른 페이지 전부)
|
|
1490
|
+
batches: session?.batches ?? [],
|
|
1491
|
+
// R172·R174: 큐 카드·완료 정리는 전체 묶음이 필요
|
|
1492
|
+
busy: (session?.batches ?? []).some((b) => b.status === "sent" || b.status === "working"),
|
|
1493
|
+
locked: isLocked(),
|
|
1494
|
+
// R175
|
|
1495
|
+
followPaused: !!session?.followPaused,
|
|
1496
|
+
// R176
|
|
1497
|
+
pendingElsewhere: pendingElsewhere(session, href),
|
|
1498
|
+
// R171: 다른 페이지에서 끝난 완료의 폴백 "보기" 링크
|
|
1499
|
+
prefs,
|
|
1500
|
+
expanded: expandedGroup,
|
|
1501
|
+
href,
|
|
1502
|
+
tab,
|
|
1503
|
+
doneOpen,
|
|
1504
|
+
panelOpen
|
|
1505
|
+
};
|
|
1506
|
+
};
|
|
1238
1507
|
const render = () => ui.render(vm());
|
|
1239
1508
|
const setSelecting = (on) => {
|
|
1509
|
+
if (on && isLocked()) return;
|
|
1240
1510
|
picker.setActive(on);
|
|
1511
|
+
panelOpen = on;
|
|
1241
1512
|
if (!on) {
|
|
1242
1513
|
render();
|
|
1243
1514
|
return;
|
|
@@ -1251,6 +1522,15 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1251
1522
|
render();
|
|
1252
1523
|
ui.focusNote();
|
|
1253
1524
|
};
|
|
1525
|
+
const togglePanelLocked = () => {
|
|
1526
|
+
if (panelOpen) {
|
|
1527
|
+
panelOpen = false;
|
|
1528
|
+
} else {
|
|
1529
|
+
panelOpen = true;
|
|
1530
|
+
tab = "queue";
|
|
1531
|
+
}
|
|
1532
|
+
render();
|
|
1533
|
+
};
|
|
1254
1534
|
const GROUP_LETTERS = "abcdefghijkl";
|
|
1255
1535
|
const nextRef = (b) => {
|
|
1256
1536
|
b.refSeq = (b.refSeq ?? 0) + 1;
|
|
@@ -1311,7 +1591,15 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1311
1591
|
}) };
|
|
1312
1592
|
};
|
|
1313
1593
|
const ui = createUI({
|
|
1314
|
-
onToggleSelect: () =>
|
|
1594
|
+
onToggleSelect: () => {
|
|
1595
|
+
if (isLocked()) {
|
|
1596
|
+
togglePanelLocked();
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
setSelecting(!picker.isActive());
|
|
1600
|
+
},
|
|
1601
|
+
onClose: () => setSelecting(false),
|
|
1602
|
+
// R175: 패널 헤더 ✕ — 잠겨서 선택이 이미 꺼져 있어도 패널을 닫는다
|
|
1315
1603
|
onNoteInput: (id, note) => {
|
|
1316
1604
|
const b = drafts?.find((d) => d.id === id);
|
|
1317
1605
|
if (b) {
|
|
@@ -1345,15 +1633,29 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1345
1633
|
flushDraft();
|
|
1346
1634
|
chan.send({ type: "send", batchIds: ready.map((b) => b.id), page: pageInfo() });
|
|
1347
1635
|
drafts = (drafts ?? []).filter((b) => !ready.includes(b));
|
|
1348
|
-
current = null;
|
|
1349
1636
|
picker.setActive(false);
|
|
1350
1637
|
expandedGroup = null;
|
|
1638
|
+
tab = "queue";
|
|
1351
1639
|
render();
|
|
1352
1640
|
},
|
|
1353
1641
|
onSettings: (patch) => chan.send({ type: "settings", patch }),
|
|
1354
1642
|
onToggleGroup: (ref) => {
|
|
1355
1643
|
expandedGroup = expandedGroup === ref ? null : ref;
|
|
1356
1644
|
render();
|
|
1645
|
+
},
|
|
1646
|
+
onTab: (t) => {
|
|
1647
|
+
tab = t;
|
|
1648
|
+
render();
|
|
1649
|
+
},
|
|
1650
|
+
onToggleDone: () => {
|
|
1651
|
+
doneOpen = !doneOpen;
|
|
1652
|
+
render();
|
|
1653
|
+
},
|
|
1654
|
+
onGoPage: (url) => {
|
|
1655
|
+
if (samePage(url, location.href)) {
|
|
1656
|
+
tab = "here";
|
|
1657
|
+
render();
|
|
1658
|
+
} else location.assign(url);
|
|
1357
1659
|
}
|
|
1358
1660
|
});
|
|
1359
1661
|
const applyTheme = () => ui.setTheme(resolveTheme(prefs.theme, {
|
|
@@ -1417,8 +1719,12 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1417
1719
|
if (e.ctrlKey && e.shiftKey && e.code === "KeyF") {
|
|
1418
1720
|
e.preventDefault();
|
|
1419
1721
|
ui.closePop();
|
|
1722
|
+
if (isLocked()) {
|
|
1723
|
+
togglePanelLocked();
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1420
1726
|
setSelecting(!picker.isActive());
|
|
1421
|
-
} else if (e.key === "Escape" && picker.isActive()) {
|
|
1727
|
+
} else if (e.key === "Escape" && (picker.isActive() || panelOpen)) {
|
|
1422
1728
|
setSelecting(false);
|
|
1423
1729
|
}
|
|
1424
1730
|
}, true);
|
|
@@ -1432,14 +1738,24 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1432
1738
|
session = m.session;
|
|
1433
1739
|
prefs = m.ui;
|
|
1434
1740
|
applyTheme();
|
|
1741
|
+
if (isLocked() && picker.isActive()) picker.setActive(false);
|
|
1435
1742
|
const nonDraft = new Set(m.session.batches.filter((b) => b.status !== "draft").map((b) => b.id));
|
|
1436
1743
|
const serverDrafts = m.session.batches.filter((b) => b.status === "draft");
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1744
|
+
const href = location.href;
|
|
1745
|
+
const localById = new Map((drafts ?? []).map((d) => [d.id, d]));
|
|
1746
|
+
const merged = [];
|
|
1747
|
+
for (const raw of serverDrafts) {
|
|
1748
|
+
const b = raw.page ? raw : { ...raw, page: { url: href, title: document.title } };
|
|
1749
|
+
if (!samePage(b.page.url, href)) {
|
|
1750
|
+
merged.push(b);
|
|
1751
|
+
continue;
|
|
1752
|
+
}
|
|
1753
|
+
const local = localById.get(b.id);
|
|
1754
|
+
merged.push(local ?? (drafts === null ? resolveDraft(b) : b));
|
|
1442
1755
|
}
|
|
1756
|
+
const seen = new Set(serverDrafts.map((b) => b.id));
|
|
1757
|
+
for (const d of drafts ?? []) if (!seen.has(d.id) && !nonDraft.has(d.id)) merged.push(d);
|
|
1758
|
+
drafts = merged;
|
|
1443
1759
|
render();
|
|
1444
1760
|
stateSeen = true;
|
|
1445
1761
|
if (openPending) {
|