cobro-mcp 0.9.4 → 0.10.1
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 +23 -2
- package/README.ko.md +3 -2
- package/README.md +3 -2
- package/dist/overlay.js +468 -146
- 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);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
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,33 @@ 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 \
|
|
328
|
+
tipSendLocked: "\uC5D0\uC774\uC804\uD2B8\uAC00 \uC218\uC815 \uC911 \u2014 \uC644\uB8CC \uD6C4 \uBCF4\uB0BC \uC218 \uC788\uC5B4\uC694",
|
|
329
|
+
tipSendNote: "\uBA54\uBAA8\uB97C \uC801\uC73C\uBA74 \uBCF4\uB0BC \uC218 \uC788\uC5B4\uC694",
|
|
204
330
|
tipRemove: "\uC774 \uC694\uC18C \uBE7C\uAE30",
|
|
331
|
+
cartNoNote: "\uBA54\uBAA8 \uC5C6\uC74C",
|
|
332
|
+
doneElsewhere: (path) => `\u2713 ${path} \uBCF4\uAE30`,
|
|
333
|
+
tabHere: "\uC774 \uD398\uC774\uC9C0",
|
|
334
|
+
tabQueue: "\uD050",
|
|
335
|
+
herePage: "\uC9C0\uAE08 \uBCF4\uACE0 \uC788\uB294 \uD398\uC774\uC9C0",
|
|
336
|
+
queueCount: (n) => `${n}\uAC1C`,
|
|
337
|
+
footBusy: (d, n) => `\uCC98\uB9AC \uC911 ${d}/${n} \u2014 \uB05D\uB098\uBA74 \uB2F4\uC744 \uC218 \uC788\uC5B4\uC694`,
|
|
338
|
+
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`,
|
|
339
|
+
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`,
|
|
340
|
+
footHint: "\uBA54\uBAA8\uB97C \uC801\uC73C\uBA74 \uBCF4\uB0BC \uC218 \uC788\uC5B4\uC694",
|
|
341
|
+
tipSelectLocked: "\uC5D0\uC774\uC804\uD2B8\uAC00 \uC218\uC815 \uC911 \u2014 \uD050\uB97C \uBCFC \uC218 \uC788\uC5B4\uC694, \uB2F4\uAE30\uB294 \uC644\uB8CC \uD6C4",
|
|
342
|
+
followPaused: "\uB530\uB77C\uAC00\uAE30 \uC911\uC9C0 \u2014 \uC644\uB8CC\uB418\uBA74 \u300C\uBCF4\uAE30\u300D \uB9C1\uD06C\uB85C \uD655\uC778",
|
|
343
|
+
doneRow: (n) => `\u2713 \uC644\uB8CC ${n}\uD398\uC774\uC9C0 \xB7 \uC0C8\uB85C \uB2F4\uC73C\uBA74 \uC815\uB9AC\uB429\uB2C8\uB2E4`,
|
|
344
|
+
sendPages: (n) => `Send \xB7 ${n}\uD398\uC774\uC9C0 \u2192`,
|
|
345
|
+
chipDraft: "\uCD08\uC548",
|
|
346
|
+
progress: (d, n) => `${d}/${n}`,
|
|
205
347
|
tipSettings: "\uC124\uC815",
|
|
206
348
|
theme: "\uD14C\uB9C8",
|
|
207
349
|
themeAuto: "\uC790\uB3D9",
|
|
208
350
|
themeDark: "\uC5B4\uB461\uAC8C",
|
|
209
351
|
themeLight: "\uBC1D\uAC8C",
|
|
210
352
|
themeFrost: "\uC720\uB9AC",
|
|
211
|
-
themeLocked: "COBRO_THEME \uD658\uACBD \uBCC0\uC218\uB85C \uACE0\uC815\uB428"
|
|
353
|
+
themeLocked: "COBRO_THEME \uD658\uACBD \uBCC0\uC218\uB85C \uACE0\uC815\uB428",
|
|
354
|
+
strategyLabel: "\uAC31\uC2E0 \uC804\uB7B5"
|
|
212
355
|
},
|
|
213
356
|
en: {
|
|
214
357
|
agentIdle: "Agent not connected",
|
|
@@ -230,7 +373,7 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
230
373
|
hintMore: (n) => `${n} selected \xB7 pick more or write a note`,
|
|
231
374
|
hintNote: "Write a note, then press Send",
|
|
232
375
|
hintPick: "Press Ctrl+Shift+F or Select to pick an element",
|
|
233
|
-
|
|
376
|
+
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
377
|
selCount: (n) => `${n} element(s) selected`,
|
|
235
378
|
selCountMixed: (n, m) => `${n} element(s) + ${m} region(s) selected`,
|
|
236
379
|
regionRow: (w, h) => `\u25AD ${w}\xD7${h}`,
|
|
@@ -247,14 +390,32 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
247
390
|
sends: "Sends selector \xB7 styles \xB7 shot \xB7 console",
|
|
248
391
|
tipSend: "Send the selected elements and note to the agent",
|
|
249
392
|
tipSendLocked: "Agent is working \u2014 you can send after done",
|
|
393
|
+
tipSendNote: "Write a note to send",
|
|
250
394
|
tipRemove: "Remove this element",
|
|
395
|
+
cartNoNote: "No note",
|
|
396
|
+
doneElsewhere: (path) => `\u2713 view ${path}`,
|
|
397
|
+
tabHere: "This page",
|
|
398
|
+
tabQueue: "Queue",
|
|
399
|
+
herePage: "The page you're on",
|
|
400
|
+
queueCount: (n) => `${n} element${n === 1 ? "" : "s"}`,
|
|
401
|
+
footBusy: (d, n) => `Working ${d}/${n} \u2014 you can add more once it's done`,
|
|
402
|
+
footPaused: (d, n) => `Working ${d}/${n} \u2014 you left this page, so following paused`,
|
|
403
|
+
footAllDone: (n) => `\u2713 ${n} page${n === 1 ? "" : "s"} done \u2014 pick an element to start a new request`,
|
|
404
|
+
footHint: "Write a note to send",
|
|
405
|
+
tipSelectLocked: "Agent is working \u2014 view the queue; picking resumes after done",
|
|
406
|
+
followPaused: `Following paused \u2014 check the "view" link when it's done`,
|
|
407
|
+
doneRow: (n) => `\u2713 ${n} done \xB7 cleared when you pick again`,
|
|
408
|
+
sendPages: (n) => `Send \xB7 ${n} pages \u2192`,
|
|
409
|
+
chipDraft: "Draft",
|
|
410
|
+
progress: (d, n) => `${d}/${n}`,
|
|
251
411
|
tipSettings: "Settings",
|
|
252
412
|
theme: "Theme",
|
|
253
413
|
themeAuto: "Auto",
|
|
254
414
|
themeDark: "Dark",
|
|
255
415
|
themeLight: "Light",
|
|
256
416
|
themeFrost: "Frost",
|
|
257
|
-
themeLocked: "Pinned by COBRO_THEME"
|
|
417
|
+
themeLocked: "Pinned by COBRO_THEME",
|
|
418
|
+
strategyLabel: "Refresh"
|
|
258
419
|
}
|
|
259
420
|
}[LANG];
|
|
260
421
|
var THEME_LABEL = { auto: T.themeAuto, dark: T.themeDark, light: T.themeLight, frost: T.themeFrost };
|
|
@@ -267,7 +428,7 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
267
428
|
done: (t) => stripStatusLabel(t, LABELS.done)
|
|
268
429
|
};
|
|
269
430
|
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 };
|
|
431
|
+
var CHIP_LABEL = { idle: T.chipIdle, waiting: T.chipWaiting, sent: T.chipSent, working: T.chipWorking, done: T.chipDone, draft: T.chipDraft };
|
|
271
432
|
function makeDraggable(target, handle, opts) {
|
|
272
433
|
const current = target.style.translate.split(" ").map((v) => parseFloat(v) || 0);
|
|
273
434
|
let off = { x: current[0] ?? 0, y: current[1] ?? 0 };
|
|
@@ -344,14 +505,6 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
344
505
|
const chipLabel = document.createElement("span");
|
|
345
506
|
chipLabel.className = "chip-label";
|
|
346
507
|
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
508
|
const status = document.createElement("span");
|
|
356
509
|
status.className = "status";
|
|
357
510
|
const statusIn = document.createElement("span");
|
|
@@ -362,7 +515,7 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
362
515
|
const gripParts = iconBtn("drag", "Move", T.tipDrag, "grip", false);
|
|
363
516
|
const grip = gripParts.btn;
|
|
364
517
|
grip.setAttribute("aria-label", "Move toolbar");
|
|
365
|
-
toolbar.append(grip, selectBtn, chip,
|
|
518
|
+
toolbar.append(grip, selectBtn, chip, status, gearBtn);
|
|
366
519
|
const line = document.createElement("span");
|
|
367
520
|
line.className = "line";
|
|
368
521
|
line.hidden = true;
|
|
@@ -386,10 +539,25 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
386
539
|
});
|
|
387
540
|
seg2.append(...segButtons);
|
|
388
541
|
popRow.append(popLabel, seg2);
|
|
542
|
+
const stratRow = document.createElement("div");
|
|
543
|
+
stratRow.className = "pop-row strategy";
|
|
544
|
+
const stratLabel = document.createElement("span");
|
|
545
|
+
stratLabel.className = "pop-label";
|
|
546
|
+
stratLabel.textContent = T.strategyLabel;
|
|
547
|
+
const stratVal = document.createElement("span");
|
|
548
|
+
stratVal.className = "strategy-val";
|
|
549
|
+
const stratIco = document.createElement("span");
|
|
550
|
+
stratIco.className = "ico";
|
|
551
|
+
stratIco.innerHTML = svg("sync");
|
|
552
|
+
const stratValText = document.createElement("span");
|
|
553
|
+
stratVal.append(stratIco, stratValText);
|
|
554
|
+
const stratSub = document.createElement("span");
|
|
555
|
+
stratSub.className = "pop-sub";
|
|
556
|
+
stratRow.append(stratLabel, stratVal, stratSub);
|
|
389
557
|
const popNote = document.createElement("div");
|
|
390
558
|
popNote.className = "pop-note";
|
|
391
559
|
popNote.textContent = T.themeLocked;
|
|
392
|
-
pop.append(popRow, popNote);
|
|
560
|
+
pop.append(popRow, stratRow, popNote);
|
|
393
561
|
let popOpen = false;
|
|
394
562
|
let popCloseListeners = null;
|
|
395
563
|
const closePop = () => {
|
|
@@ -487,6 +655,7 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
487
655
|
if (text != null) d.textContent = text;
|
|
488
656
|
return d;
|
|
489
657
|
};
|
|
658
|
+
const truncate402 = (s) => s.length > 40 ? s.slice(0, 40) + "\u2026" : s;
|
|
490
659
|
function render(vm) {
|
|
491
660
|
renderMarkers(vm);
|
|
492
661
|
updateTbH();
|
|
@@ -494,15 +663,22 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
494
663
|
const wasTa = active instanceof HTMLTextAreaElement ? active : null;
|
|
495
664
|
const sel = wasTa ? [wasTa.selectionStart, wasTa.selectionEnd] : null;
|
|
496
665
|
selectBtn.classList.toggle("on", vm.selecting);
|
|
666
|
+
selectBtn.classList.toggle("locked", vm.locked);
|
|
667
|
+
selectBtn.title = vm.locked ? T.tipSelectLocked : T.tipSelect;
|
|
497
668
|
for (const btn of segButtons) {
|
|
498
669
|
btn.classList.toggle("on", btn.dataset.theme === vm.prefs.theme);
|
|
499
670
|
btn.disabled = vm.prefs.themeLocked;
|
|
500
671
|
}
|
|
501
672
|
popNote.hidden = !vm.prefs.themeLocked;
|
|
502
|
-
const cur = vm.
|
|
673
|
+
const cur = vm.current;
|
|
503
674
|
const hasElements = !!cur && cur.elements.length + (cur.regions?.length ?? 0) > 0;
|
|
504
675
|
let hint;
|
|
676
|
+
let gotoUrl = null;
|
|
505
677
|
if (!vm.connected) hint = T.disconnected;
|
|
678
|
+
else if (!vm.busy && vm.pendingElsewhere) {
|
|
679
|
+
hint = T.doneElsewhere(vm.pendingElsewhere.path);
|
|
680
|
+
gotoUrl = vm.pendingElsewhere.url;
|
|
681
|
+
} else if (vm.locked && vm.followPaused) hint = T.followPaused;
|
|
506
682
|
else {
|
|
507
683
|
let text;
|
|
508
684
|
if (vm.agent.status === "sent" || vm.agent.status === "working" || vm.agent.status === "done") text = AGENT_TEXT[vm.agent.status](vm.agent.text);
|
|
@@ -523,7 +699,16 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
523
699
|
statusIn.classList.add("enter");
|
|
524
700
|
}
|
|
525
701
|
}
|
|
526
|
-
|
|
702
|
+
if (gotoUrl) {
|
|
703
|
+
statusIn.textContent = "";
|
|
704
|
+
const a = document.createElement("a");
|
|
705
|
+
a.className = "goto";
|
|
706
|
+
a.href = gotoUrl;
|
|
707
|
+
a.textContent = displayHint;
|
|
708
|
+
statusIn.append(a);
|
|
709
|
+
} else {
|
|
710
|
+
statusIn.textContent = displayHint;
|
|
711
|
+
}
|
|
527
712
|
statusIn.title = hint;
|
|
528
713
|
if (hovering) {
|
|
529
714
|
applyHoverScroll();
|
|
@@ -532,117 +717,183 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
532
717
|
status.classList.toggle("off", !vm.connected);
|
|
533
718
|
chip.classList.toggle("off", !vm.connected);
|
|
534
719
|
dot.className = vm.connected ? "dot " + vm.agent.status : "dot";
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
720
|
+
const round = roundOf(vm.queue);
|
|
721
|
+
const roundSuffix = vm.busy ? " " + T.progress(round.done, round.total) : "";
|
|
722
|
+
chipLabel.textContent = vm.connected ? CHIP_LABEL[vm.agent.status] + roundSuffix : T.chipOff;
|
|
723
|
+
chip.title = vm.connected ? DOT_TITLE[vm.agent.status] + roundSuffix : T.disconnected;
|
|
724
|
+
stratRow.hidden = !vm.strategy;
|
|
538
725
|
if (vm.strategy) {
|
|
539
|
-
|
|
540
|
-
|
|
726
|
+
stratValText.textContent = vm.strategy;
|
|
727
|
+
stratSub.textContent = T.strategyDesc(vm.strategy);
|
|
728
|
+
stratRow.title = T.strategyDesc(vm.strategy);
|
|
541
729
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
if (vm.drafts.length === 0) {
|
|
730
|
+
panel.classList.toggle("show", vm.panelOpen);
|
|
731
|
+
if (!vm.panelOpen) {
|
|
545
732
|
panel.textContent = "";
|
|
546
733
|
textareas.clear();
|
|
547
734
|
return;
|
|
548
735
|
}
|
|
549
|
-
if (!show) return;
|
|
550
736
|
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
|
-
|
|
737
|
+
const labels = { noNote: T.cartNoNote, count: T.queueCount };
|
|
738
|
+
const cards = queueCards(vm.drafts, vm.batches, vm.href, vm.busy, labels);
|
|
739
|
+
const doneList = lastRoundDone(vm.batches);
|
|
740
|
+
const queueN = cards.length > 0 ? cards.length : doneList.length;
|
|
741
|
+
const h4 = el("h4");
|
|
742
|
+
const g = el("span", "grip");
|
|
743
|
+
g.innerHTML = svg("drag");
|
|
744
|
+
g.setAttribute("aria-hidden", "true");
|
|
745
|
+
h4.append(g);
|
|
746
|
+
const tabs = el("div", "tabs");
|
|
747
|
+
const tabHereBtn = el("button", "tab current" + (vm.tab === "here" ? " on" : ""), T.tabHere);
|
|
748
|
+
tabHereBtn.onclick = () => h.onTab("here");
|
|
749
|
+
const tabQueueBtn = el("button", "tab queue" + (vm.tab === "queue" ? " on" : ""));
|
|
750
|
+
tabQueueBtn.innerHTML = QUEUE_ICON;
|
|
751
|
+
tabQueueBtn.append(document.createTextNode(T.tabQueue), el("span", "badge", String(queueN)));
|
|
752
|
+
tabQueueBtn.onclick = () => h.onTab("queue");
|
|
753
|
+
tabs.append(tabHereBtn, tabQueueBtn);
|
|
754
|
+
h4.append(tabs);
|
|
755
|
+
const close = el("button", "close", "\u2715");
|
|
756
|
+
close.title = T.tipClose;
|
|
757
|
+
close.setAttribute("aria-label", "Close");
|
|
758
|
+
close.onclick = () => h.onClose();
|
|
759
|
+
h4.append(close);
|
|
760
|
+
panel.append(h4);
|
|
761
|
+
if (vm.tab === "here") {
|
|
762
|
+
if (cur) {
|
|
763
|
+
const sub = el("div", "sub");
|
|
764
|
+
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));
|
|
765
|
+
panel.append(sub);
|
|
766
|
+
const list = el("div", "els");
|
|
767
|
+
const looseEls = cur.elements.filter((e) => /^\d+$/.test(e.ref ?? ""));
|
|
768
|
+
const rows = [];
|
|
769
|
+
looseEls.forEach((e) => {
|
|
770
|
+
const idx = cur.elements.indexOf(e);
|
|
771
|
+
rows.push({ ref: e.ref, render: () => {
|
|
772
|
+
const row2 = el("div", e.missing ? "missing" : "");
|
|
773
|
+
row2.append(el("span", "num", `${e.ref}.`));
|
|
774
|
+
const comp = (e.react ?? e.vue)?.component;
|
|
775
|
+
row2.append(el("span", "label", `${e.selector.split(" > ").pop()}${comp ? " \xB7 " + comp : ""}${e.missing ? " \xB7 " + T.elMissing : ""}`));
|
|
776
|
+
row2.append(el("span", "kind", e.tag));
|
|
777
|
+
const x = el("button", "", "\u2715");
|
|
778
|
+
x.title = T.tipRemove;
|
|
779
|
+
x.onclick = () => h.onRemoveElement(cur.id, idx);
|
|
780
|
+
row2.append(x);
|
|
781
|
+
list.append(row2);
|
|
782
|
+
} });
|
|
783
|
+
});
|
|
784
|
+
cur.regions?.forEach((r, i) => {
|
|
785
|
+
rows.push({ ref: r.ref ?? "", render: () => {
|
|
786
|
+
const kids = r.ref ? cur.elements.filter((e) => isChildRef(e.ref, r.ref)) : [];
|
|
787
|
+
const row2 = el("div", "");
|
|
788
|
+
if (kids.length) {
|
|
789
|
+
row2.classList.add("group");
|
|
790
|
+
const chev = el("span", "chev", vm.expanded === r.ref ? "\u25BE" : "\u25B8");
|
|
791
|
+
chev.title = T.tipExpand;
|
|
792
|
+
row2.append(chev);
|
|
793
|
+
}
|
|
794
|
+
row2.append(el("span", "num", `${r.ref ?? ""}.`));
|
|
795
|
+
row2.append(el("span", "label", `${T.regionRow(r.rect.w, r.rect.h)}${r.within ? T.regionIn(r.within.split(" > ").pop()) : ""}`));
|
|
796
|
+
if (kids.length) row2.append(el("span", "cnt", T.groupInside(kids.length)));
|
|
797
|
+
if (!kids.length) row2.append(el("span", "kind region", "region"));
|
|
798
|
+
const x = el("button", "", "\u2715");
|
|
799
|
+
x.title = T.tipRemove;
|
|
800
|
+
x.onclick = () => h.onRemoveRegion(cur.id, i);
|
|
801
|
+
row2.append(x);
|
|
802
|
+
if (kids.length) row2.addEventListener("click", (ev) => {
|
|
803
|
+
const t = ev.target;
|
|
804
|
+
if (t.closest("button") || t.closest(".num")) return;
|
|
805
|
+
h.onToggleGroup(r.ref);
|
|
619
806
|
});
|
|
807
|
+
list.append(row2);
|
|
808
|
+
if (kids.length && vm.expanded === r.ref) {
|
|
809
|
+
kids.forEach((c) => {
|
|
810
|
+
const cidx = cur.elements.indexOf(c);
|
|
811
|
+
const crow = el("div", "child" + (c.missing ? " missing" : ""));
|
|
812
|
+
crow.append(el("span", "num", `${c.ref}.`));
|
|
813
|
+
const ccomp = (c.react ?? c.vue)?.component;
|
|
814
|
+
crow.append(el("span", "label", `${c.selector.split(" > ").pop()}${ccomp ? " \xB7 " + ccomp : ""}${c.missing ? " \xB7 " + T.elMissing : ""}`));
|
|
815
|
+
crow.append(el("span", "kind", c.tag));
|
|
816
|
+
const cx = el("button", "", "\u2715");
|
|
817
|
+
cx.title = T.tipRemove;
|
|
818
|
+
cx.onclick = () => h.onRemoveElement(cur.id, cidx);
|
|
819
|
+
crow.append(cx);
|
|
820
|
+
list.append(crow);
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
} });
|
|
824
|
+
});
|
|
825
|
+
rows.sort((a, b) => Number(a.ref) - Number(b.ref));
|
|
826
|
+
rows.forEach((r) => r.render());
|
|
827
|
+
panel.append(list);
|
|
828
|
+
let ta = textareas.get(cur.id);
|
|
829
|
+
if (!ta) {
|
|
830
|
+
ta = document.createElement("textarea");
|
|
831
|
+
const id = cur.id;
|
|
832
|
+
ta.addEventListener("input", () => h.onNoteInput(id, ta.value));
|
|
833
|
+
textareas.set(id, ta);
|
|
834
|
+
}
|
|
835
|
+
ta.placeholder = cur.elements.length + (cur.regions?.length ?? 0) >= 2 ? T.notePlaceholderMulti : T.notePlaceholder;
|
|
836
|
+
if (ta.value !== cur.note) ta.value = cur.note;
|
|
837
|
+
ta.disabled = vm.locked;
|
|
838
|
+
panel.append(ta);
|
|
839
|
+
}
|
|
840
|
+
} else {
|
|
841
|
+
const queue = el("div", "queue");
|
|
842
|
+
for (const c of cards) {
|
|
843
|
+
const a = document.createElement("a");
|
|
844
|
+
a.className = `card ${c.kind}` + (c.isCurrent ? " here" : "");
|
|
845
|
+
a.href = c.url;
|
|
846
|
+
a.onclick = (ev) => {
|
|
847
|
+
ev.preventDefault();
|
|
848
|
+
h.onGoPage(c.url);
|
|
849
|
+
};
|
|
850
|
+
const thumb = el("div", "thumb");
|
|
851
|
+
for (let i = 0; i < Math.min(c.count, 2); i++) thumb.append(el("span", `rect ${c.kind}`));
|
|
852
|
+
a.append(thumb);
|
|
853
|
+
const body = el("div", "body");
|
|
854
|
+
body.append(el("div", "title", c.title), el("div", "path", c.path), el("div", "meta", c.meta));
|
|
855
|
+
a.append(body);
|
|
856
|
+
a.append(el("span", "chip", CHIP_LABEL[c.kind]));
|
|
857
|
+
if (c.isCurrent) a.append(el("span", "here", T.herePage));
|
|
858
|
+
queue.append(a);
|
|
859
|
+
}
|
|
860
|
+
if (!vm.busy && doneList.length > 0) {
|
|
861
|
+
const doneRow = el("button", "done-row", `${vm.doneOpen ? "\u25BE" : "\u25B8"} ${T.doneRow(doneList.length)}`);
|
|
862
|
+
doneRow.onclick = () => h.onToggleDone();
|
|
863
|
+
queue.append(doneRow);
|
|
864
|
+
if (vm.doneOpen) {
|
|
865
|
+
for (const b of doneList) {
|
|
866
|
+
const url = b.page?.url ?? vm.href;
|
|
867
|
+
const item = el("div", "done-item");
|
|
868
|
+
item.append(el("span", "title", b.page?.title || url), el("span", "path", pageLoc(url, vm.href)), el("span", "summary", "\u2713 " + truncate402(b.summary ?? "")));
|
|
869
|
+
queue.append(item);
|
|
620
870
|
}
|
|
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);
|
|
871
|
+
}
|
|
632
872
|
}
|
|
633
|
-
|
|
634
|
-
if (ta.value !== cur.note) ta.value = cur.note;
|
|
635
|
-
panel.append(ta);
|
|
873
|
+
panel.append(queue);
|
|
636
874
|
}
|
|
875
|
+
const readyDrafts = vm.drafts.filter((b) => b.note.trim());
|
|
637
876
|
const row = el("div", "row");
|
|
638
|
-
const send = el("button", "send", "Send \u2192");
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
877
|
+
const send = el("button", "send", readyDrafts.length >= 2 ? T.sendPages(readyDrafts.length) : "Send \u2192");
|
|
878
|
+
if (readyDrafts.length > 0) {
|
|
879
|
+
send.title = T.tipSend;
|
|
880
|
+
send.onclick = () => {
|
|
881
|
+
closePop();
|
|
882
|
+
h.onSend();
|
|
883
|
+
};
|
|
884
|
+
row.append(el("span", "sends", T.sends), send);
|
|
885
|
+
} else {
|
|
886
|
+
send.disabled = true;
|
|
887
|
+
send.title = vm.locked ? T.tipSendLocked : T.tipSendNote;
|
|
888
|
+
let footText;
|
|
889
|
+
let footOk = false;
|
|
890
|
+
if (vm.busy) footText = vm.followPaused ? T.footPaused(round.done, round.total) : T.footBusy(round.done, round.total);
|
|
891
|
+
else if (doneList.length > 0) {
|
|
892
|
+
footText = T.footAllDone(doneList.length);
|
|
893
|
+
footOk = true;
|
|
894
|
+
} else footText = T.footHint;
|
|
895
|
+
row.append(el("span", `foot${footOk ? " ok" : ""}`, footText), send);
|
|
896
|
+
}
|
|
646
897
|
panel.append(row);
|
|
647
898
|
for (const id of [...textareas.keys()]) if (!vm.drafts.some((b) => b.id === id)) textareas.delete(id);
|
|
648
899
|
if (wasTa && sel && wasTa.isConnected) {
|
|
@@ -663,7 +914,7 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
663
914
|
m.append(el("span", "n", String(n)));
|
|
664
915
|
root.append(m);
|
|
665
916
|
};
|
|
666
|
-
const cur = vm.
|
|
917
|
+
const cur = vm.current;
|
|
667
918
|
if (cur) {
|
|
668
919
|
cur.elements.forEach((e) => {
|
|
669
920
|
if (e.missing) return;
|
|
@@ -683,7 +934,7 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
683
934
|
place(el("div", "marker region"), r.rect.x - scrollX, r.rect.y - scrollY, r.rect.w, r.rect.h, r.ref ?? "");
|
|
684
935
|
});
|
|
685
936
|
}
|
|
686
|
-
const working = vm.
|
|
937
|
+
const working = vm.busy && vm.connected;
|
|
687
938
|
line.hidden = !working;
|
|
688
939
|
if (working) {
|
|
689
940
|
const wbox = (cls) => {
|
|
@@ -693,7 +944,9 @@ textarea{width:100%;min-height:80px;resize:none;font:inherit;color:var(--fg);bac
|
|
|
693
944
|
m.append(clip);
|
|
694
945
|
return m;
|
|
695
946
|
};
|
|
696
|
-
for (const b of vm.
|
|
947
|
+
for (const b of vm.queue) {
|
|
948
|
+
if (b.status === "done") continue;
|
|
949
|
+
if (b.page && !samePage(b.page.url, vm.href)) continue;
|
|
697
950
|
b.elements.forEach((e) => {
|
|
698
951
|
if (e.missing) return;
|
|
699
952
|
if (/[a-z]$/.test(e.ref ?? "")) return;
|
|
@@ -1193,23 +1446,25 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1193
1446
|
document.documentElement.setAttribute("data-cobro", "1");
|
|
1194
1447
|
let session = null;
|
|
1195
1448
|
let drafts = null;
|
|
1196
|
-
let current = null;
|
|
1197
1449
|
let connected = false;
|
|
1198
1450
|
let stateSeen = false;
|
|
1199
1451
|
let openPending = false;
|
|
1200
1452
|
let draftTimer = null;
|
|
1201
1453
|
let prefs = { theme: "auto", themeLocked: false };
|
|
1202
1454
|
let expandedGroup = null;
|
|
1455
|
+
let tab = "here";
|
|
1456
|
+
let doneOpen = false;
|
|
1457
|
+
let panelOpen = false;
|
|
1203
1458
|
const mq = matchMedia("(prefers-color-scheme: dark)");
|
|
1204
1459
|
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() });
|
|
1460
|
+
const newBatch = () => ({ id: crypto.randomUUID(), note: "", elements: [], status: "draft", createdAt: (/* @__PURE__ */ new Date()).toISOString(), page: { url: location.href, title: document.title } });
|
|
1206
1461
|
const ensureCurrent = () => {
|
|
1207
1462
|
drafts ??= [];
|
|
1208
|
-
let b = drafts
|
|
1463
|
+
let b = currentDraft(drafts, location.href);
|
|
1209
1464
|
if (!b) {
|
|
1210
1465
|
b = newBatch();
|
|
1211
1466
|
drafts.push(b);
|
|
1212
|
-
|
|
1467
|
+
tab = "here";
|
|
1213
1468
|
}
|
|
1214
1469
|
return b;
|
|
1215
1470
|
};
|
|
@@ -1218,26 +1473,48 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1218
1473
|
clearTimeout(draftTimer);
|
|
1219
1474
|
draftTimer = null;
|
|
1220
1475
|
}
|
|
1221
|
-
|
|
1476
|
+
const href = location.href;
|
|
1477
|
+
const here = (drafts ?? []).filter((d) => !d.page || samePage(d.page.url, href));
|
|
1478
|
+
chan.send({ type: "draft", batches: here, page: { url: href, title: document.title } });
|
|
1222
1479
|
};
|
|
1223
1480
|
const pushDraft = () => {
|
|
1224
1481
|
if (draftTimer) clearTimeout(draftTimer);
|
|
1225
1482
|
draftTimer = setTimeout(flushDraft, 300);
|
|
1226
1483
|
};
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1484
|
+
const isLocked = () => session?.agent.status === "sent" || session?.agent.status === "working";
|
|
1485
|
+
const vm = () => {
|
|
1486
|
+
const href = location.href;
|
|
1487
|
+
return {
|
|
1488
|
+
selecting: picker.isActive(),
|
|
1489
|
+
connected,
|
|
1490
|
+
agent: session?.agent ?? { status: "idle", text: "" },
|
|
1491
|
+
strategy: session ? session.strategy ?? session.detected : null,
|
|
1492
|
+
drafts: drafts ?? [],
|
|
1493
|
+
current: currentDraft(drafts ?? [], href),
|
|
1494
|
+
queue: roundBatches(session?.batches ?? []),
|
|
1495
|
+
// R166·R169: sent·working·라운드 done(현재+다른 페이지 전부)
|
|
1496
|
+
batches: session?.batches ?? [],
|
|
1497
|
+
// R172·R174: 큐 카드·완료 정리는 전체 묶음이 필요
|
|
1498
|
+
busy: (session?.batches ?? []).some((b) => b.status === "sent" || b.status === "working"),
|
|
1499
|
+
locked: isLocked(),
|
|
1500
|
+
// R175
|
|
1501
|
+
followPaused: !!session?.followPaused,
|
|
1502
|
+
// R176
|
|
1503
|
+
pendingElsewhere: pendingElsewhere(session, href),
|
|
1504
|
+
// R171: 다른 페이지에서 끝난 완료의 폴백 "보기" 링크
|
|
1505
|
+
prefs,
|
|
1506
|
+
expanded: expandedGroup,
|
|
1507
|
+
href,
|
|
1508
|
+
tab,
|
|
1509
|
+
doneOpen,
|
|
1510
|
+
panelOpen
|
|
1511
|
+
};
|
|
1512
|
+
};
|
|
1238
1513
|
const render = () => ui.render(vm());
|
|
1239
1514
|
const setSelecting = (on) => {
|
|
1515
|
+
if (on && isLocked()) return;
|
|
1240
1516
|
picker.setActive(on);
|
|
1517
|
+
panelOpen = on;
|
|
1241
1518
|
if (!on) {
|
|
1242
1519
|
render();
|
|
1243
1520
|
return;
|
|
@@ -1251,6 +1528,15 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1251
1528
|
render();
|
|
1252
1529
|
ui.focusNote();
|
|
1253
1530
|
};
|
|
1531
|
+
const togglePanelLocked = () => {
|
|
1532
|
+
if (panelOpen) {
|
|
1533
|
+
panelOpen = false;
|
|
1534
|
+
} else {
|
|
1535
|
+
panelOpen = true;
|
|
1536
|
+
tab = "queue";
|
|
1537
|
+
}
|
|
1538
|
+
render();
|
|
1539
|
+
};
|
|
1254
1540
|
const GROUP_LETTERS = "abcdefghijkl";
|
|
1255
1541
|
const nextRef = (b) => {
|
|
1256
1542
|
b.refSeq = (b.refSeq ?? 0) + 1;
|
|
@@ -1311,7 +1597,15 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1311
1597
|
}) };
|
|
1312
1598
|
};
|
|
1313
1599
|
const ui = createUI({
|
|
1314
|
-
onToggleSelect: () =>
|
|
1600
|
+
onToggleSelect: () => {
|
|
1601
|
+
if (isLocked()) {
|
|
1602
|
+
togglePanelLocked();
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
setSelecting(!picker.isActive());
|
|
1606
|
+
},
|
|
1607
|
+
onClose: () => setSelecting(false),
|
|
1608
|
+
// R175: 패널 헤더 ✕ — 잠겨서 선택이 이미 꺼져 있어도 패널을 닫는다
|
|
1315
1609
|
onNoteInput: (id, note) => {
|
|
1316
1610
|
const b = drafts?.find((d) => d.id === id);
|
|
1317
1611
|
if (b) {
|
|
@@ -1345,15 +1639,29 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1345
1639
|
flushDraft();
|
|
1346
1640
|
chan.send({ type: "send", batchIds: ready.map((b) => b.id), page: pageInfo() });
|
|
1347
1641
|
drafts = (drafts ?? []).filter((b) => !ready.includes(b));
|
|
1348
|
-
current = null;
|
|
1349
1642
|
picker.setActive(false);
|
|
1350
1643
|
expandedGroup = null;
|
|
1644
|
+
tab = "queue";
|
|
1351
1645
|
render();
|
|
1352
1646
|
},
|
|
1353
1647
|
onSettings: (patch) => chan.send({ type: "settings", patch }),
|
|
1354
1648
|
onToggleGroup: (ref) => {
|
|
1355
1649
|
expandedGroup = expandedGroup === ref ? null : ref;
|
|
1356
1650
|
render();
|
|
1651
|
+
},
|
|
1652
|
+
onTab: (t) => {
|
|
1653
|
+
tab = t;
|
|
1654
|
+
render();
|
|
1655
|
+
},
|
|
1656
|
+
onToggleDone: () => {
|
|
1657
|
+
doneOpen = !doneOpen;
|
|
1658
|
+
render();
|
|
1659
|
+
},
|
|
1660
|
+
onGoPage: (url) => {
|
|
1661
|
+
if (samePage(url, location.href)) {
|
|
1662
|
+
tab = "here";
|
|
1663
|
+
render();
|
|
1664
|
+
} else location.assign(url);
|
|
1357
1665
|
}
|
|
1358
1666
|
});
|
|
1359
1667
|
const applyTheme = () => ui.setTheme(resolveTheme(prefs.theme, {
|
|
@@ -1417,8 +1725,12 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1417
1725
|
if (e.ctrlKey && e.shiftKey && e.code === "KeyF") {
|
|
1418
1726
|
e.preventDefault();
|
|
1419
1727
|
ui.closePop();
|
|
1728
|
+
if (isLocked()) {
|
|
1729
|
+
togglePanelLocked();
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1420
1732
|
setSelecting(!picker.isActive());
|
|
1421
|
-
} else if (e.key === "Escape" && picker.isActive()) {
|
|
1733
|
+
} else if (e.key === "Escape" && (picker.isActive() || panelOpen)) {
|
|
1422
1734
|
setSelecting(false);
|
|
1423
1735
|
}
|
|
1424
1736
|
}, true);
|
|
@@ -1432,14 +1744,24 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1432
1744
|
session = m.session;
|
|
1433
1745
|
prefs = m.ui;
|
|
1434
1746
|
applyTheme();
|
|
1747
|
+
if (isLocked() && picker.isActive()) picker.setActive(false);
|
|
1435
1748
|
const nonDraft = new Set(m.session.batches.filter((b) => b.status !== "draft").map((b) => b.id));
|
|
1436
1749
|
const serverDrafts = m.session.batches.filter((b) => b.status === "draft");
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1750
|
+
const href = location.href;
|
|
1751
|
+
const localById = new Map((drafts ?? []).map((d) => [d.id, d]));
|
|
1752
|
+
const merged = [];
|
|
1753
|
+
for (const raw of serverDrafts) {
|
|
1754
|
+
const b = raw.page ? raw : { ...raw, page: { url: href, title: document.title } };
|
|
1755
|
+
if (!samePage(b.page.url, href)) {
|
|
1756
|
+
merged.push(b);
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
const local = localById.get(b.id);
|
|
1760
|
+
merged.push(local ?? (drafts === null ? resolveDraft(b) : b));
|
|
1442
1761
|
}
|
|
1762
|
+
const seen = new Set(serverDrafts.map((b) => b.id));
|
|
1763
|
+
for (const d of drafts ?? []) if (!seen.has(d.id) && !nonDraft.has(d.id)) merged.push(d);
|
|
1764
|
+
drafts = merged;
|
|
1443
1765
|
render();
|
|
1444
1766
|
stateSeen = true;
|
|
1445
1767
|
if (openPending) {
|