openzoo 0.48.83 → 0.48.84
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/lib/grokui.mjs +151 -0
- package/package.json +1 -1
package/lib/grokui.mjs
CHANGED
|
@@ -2757,8 +2757,25 @@ const APP_HTML = `<!doctype html>
|
|
|
2757
2757
|
font-size: 12px; }
|
|
2758
2758
|
#composeFoot kbd { margin-right: 4px; }
|
|
2759
2759
|
.mention { background: #3a3a3c; border-radius: 10px; padding: 1px 8px; font-size: 0.92em; }
|
|
2760
|
+
/* Sidebar names and header titles are readable text — they must be
|
|
2761
|
+
selectable so select-to-copy works there, not only in bubbles. */
|
|
2762
|
+
.tname, .tprev, .pname, .hname, .hdir, .hdr, .wbal, .wnote, .wsub, #chatHeader {
|
|
2763
|
+
-webkit-user-select: text; user-select: text; }
|
|
2764
|
+
/* SELECT-TO-COPY. A blink, not a dialog: selection landed on the clipboard. */
|
|
2765
|
+
#copiedToast {
|
|
2766
|
+
position: fixed; z-index: 4000; pointer-events: none;
|
|
2767
|
+
padding: 7px 14px; border-radius: 999px;
|
|
2768
|
+
background: #b8f240; color: #0b0b0d;
|
|
2769
|
+
font: 700 12px/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
2770
|
+
letter-spacing: .06em;
|
|
2771
|
+
box-shadow: 0 8px 24px rgba(0,0,0,.5);
|
|
2772
|
+
opacity: 0; transform: translate(-50%, -6px);
|
|
2773
|
+
transition: opacity .14s ease, transform .14s ease;
|
|
2774
|
+
}
|
|
2775
|
+
#copiedToast.show { opacity: 1; transform: translate(-50%, 0); }
|
|
2760
2776
|
</style></head>
|
|
2761
2777
|
<body>
|
|
2778
|
+
<div id="copiedToast" role="status" aria-live="polite">copied</div>
|
|
2762
2779
|
<div id="dragbar"></div>
|
|
2763
2780
|
<div id="sidebar">
|
|
2764
2781
|
<div id="sideTop">
|
|
@@ -3338,6 +3355,140 @@ const APP_HTML = `<!doctype html>
|
|
|
3338
3355
|
return b;
|
|
3339
3356
|
}
|
|
3340
3357
|
|
|
3358
|
+
/* SELECT → CLIPBOARD. A finished selection copies itself. No ⌘C, no hunting
|
|
3359
|
+
for a 10px button. Empty click / caret-only is a no-op so we do not spam
|
|
3360
|
+
"copied". The highlight stays put: copy must not steal focus or collapse
|
|
3361
|
+
the range. */
|
|
3362
|
+
function selectedText() {
|
|
3363
|
+
const el = document.activeElement;
|
|
3364
|
+
if (el && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA')) {
|
|
3365
|
+
if (el.type === 'password') return '';
|
|
3366
|
+
const a = el.selectionStart;
|
|
3367
|
+
const b = el.selectionEnd;
|
|
3368
|
+
if (typeof a === 'number' && typeof b === 'number' && b > a) {
|
|
3369
|
+
return String(el.value || '').slice(a, b);
|
|
3370
|
+
}
|
|
3371
|
+
return '';
|
|
3372
|
+
}
|
|
3373
|
+
const sel = window.getSelection();
|
|
3374
|
+
return sel ? sel.toString() : '';
|
|
3375
|
+
}
|
|
3376
|
+
function selectedRect() {
|
|
3377
|
+
const el = document.activeElement;
|
|
3378
|
+
if (el && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') && el.type !== 'password') {
|
|
3379
|
+
const a = el.selectionStart;
|
|
3380
|
+
const b = el.selectionEnd;
|
|
3381
|
+
if (typeof a === 'number' && typeof b === 'number' && b > a) return el.getBoundingClientRect();
|
|
3382
|
+
}
|
|
3383
|
+
const sel = window.getSelection();
|
|
3384
|
+
if (!sel || !sel.rangeCount) return null;
|
|
3385
|
+
const r = sel.getRangeAt(0).getBoundingClientRect();
|
|
3386
|
+
if (!r || (r.width === 0 && r.height === 0)) return null;
|
|
3387
|
+
return r;
|
|
3388
|
+
}
|
|
3389
|
+
function snapshotSelection() {
|
|
3390
|
+
const el = document.activeElement;
|
|
3391
|
+
if (el && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA')) {
|
|
3392
|
+
return { kind: 'field', el: el, start: el.selectionStart, end: el.selectionEnd };
|
|
3393
|
+
}
|
|
3394
|
+
const sel = window.getSelection();
|
|
3395
|
+
if (!sel || !sel.rangeCount) return { kind: 'none' };
|
|
3396
|
+
const ranges = [];
|
|
3397
|
+
for (let i = 0; i < sel.rangeCount; i++) ranges.push(sel.getRangeAt(i).cloneRange());
|
|
3398
|
+
return { kind: 'dom', ranges: ranges };
|
|
3399
|
+
}
|
|
3400
|
+
function restoreSelection(snap) {
|
|
3401
|
+
if (!snap) return;
|
|
3402
|
+
if (snap.kind === 'field' && snap.el && document.contains(snap.el)) {
|
|
3403
|
+
try {
|
|
3404
|
+
snap.el.focus({ preventScroll: true });
|
|
3405
|
+
snap.el.setSelectionRange(snap.start, snap.end);
|
|
3406
|
+
} catch (e) {}
|
|
3407
|
+
return;
|
|
3408
|
+
}
|
|
3409
|
+
if (snap.kind === 'dom') {
|
|
3410
|
+
const sel = window.getSelection();
|
|
3411
|
+
sel.removeAllRanges();
|
|
3412
|
+
for (let i = 0; i < snap.ranges.length; i++) {
|
|
3413
|
+
try { sel.addRange(snap.ranges[i]); } catch (e) {}
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
let copiedToastTimer = 0;
|
|
3418
|
+
function showCopiedToast(rect) {
|
|
3419
|
+
const el = document.getElementById('copiedToast');
|
|
3420
|
+
if (!el) return;
|
|
3421
|
+
let x = window.innerWidth / 2;
|
|
3422
|
+
let y = 40;
|
|
3423
|
+
if (rect && (rect.width || rect.height)) {
|
|
3424
|
+
x = rect.left + rect.width / 2;
|
|
3425
|
+
y = rect.top - 38;
|
|
3426
|
+
if (y < 8) y = rect.bottom + 10;
|
|
3427
|
+
}
|
|
3428
|
+
if (x < 48) x = 48;
|
|
3429
|
+
if (x > window.innerWidth - 48) x = window.innerWidth - 48;
|
|
3430
|
+
if (y < 8) y = 8;
|
|
3431
|
+
if (y > window.innerHeight - 36) y = window.innerHeight - 36;
|
|
3432
|
+
el.style.left = x + 'px';
|
|
3433
|
+
el.style.top = y + 'px';
|
|
3434
|
+
el.classList.add('show');
|
|
3435
|
+
clearTimeout(copiedToastTimer);
|
|
3436
|
+
copiedToastTimer = setTimeout(function () { el.classList.remove('show'); }, 1200);
|
|
3437
|
+
}
|
|
3438
|
+
let selectPointerDown = false;
|
|
3439
|
+
let selectShiftHeld = false;
|
|
3440
|
+
let selectCopying = false;
|
|
3441
|
+
let selectTimer = 0;
|
|
3442
|
+
let selectLastText = '';
|
|
3443
|
+
let selectLastAt = 0;
|
|
3444
|
+
async function copySettledSelection() {
|
|
3445
|
+
if (selectCopying) return;
|
|
3446
|
+
const text = selectedText();
|
|
3447
|
+
if (!text) return;
|
|
3448
|
+
const now = Date.now();
|
|
3449
|
+
if (text === selectLastText && (now - selectLastAt) < 500) return;
|
|
3450
|
+
selectLastText = text;
|
|
3451
|
+
selectLastAt = now;
|
|
3452
|
+
const rect = selectedRect();
|
|
3453
|
+
const snap = snapshotSelection();
|
|
3454
|
+
selectCopying = true;
|
|
3455
|
+
let ok = false;
|
|
3456
|
+
try { ok = await copyText(text); }
|
|
3457
|
+
finally { restoreSelection(snap); selectCopying = false; }
|
|
3458
|
+
if (ok) showCopiedToast(rect);
|
|
3459
|
+
}
|
|
3460
|
+
function scheduleCopySelection() {
|
|
3461
|
+
if (selectPointerDown || selectCopying) return;
|
|
3462
|
+
clearTimeout(selectTimer);
|
|
3463
|
+
selectTimer = setTimeout(copySettledSelection, 40);
|
|
3464
|
+
}
|
|
3465
|
+
document.addEventListener('pointerdown', function (e) {
|
|
3466
|
+
if (e.button !== undefined && e.button !== 0) return;
|
|
3467
|
+
selectPointerDown = true;
|
|
3468
|
+
});
|
|
3469
|
+
document.addEventListener('pointerup', function (e) {
|
|
3470
|
+
if (e.button !== undefined && e.button !== 0) return;
|
|
3471
|
+
selectPointerDown = false;
|
|
3472
|
+
scheduleCopySelection();
|
|
3473
|
+
});
|
|
3474
|
+
document.addEventListener('pointercancel', function () { selectPointerDown = false; });
|
|
3475
|
+
document.addEventListener('keydown', function (e) {
|
|
3476
|
+
if (e.key === 'Shift') selectShiftHeld = true;
|
|
3477
|
+
});
|
|
3478
|
+
document.addEventListener('keyup', function (e) {
|
|
3479
|
+
if (e.key === 'Shift') selectShiftHeld = false;
|
|
3480
|
+
const withMod = e.metaKey || e.ctrlKey;
|
|
3481
|
+
const selectish = e.key === 'Shift'
|
|
3482
|
+
|| e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === 'ArrowUp' || e.key === 'ArrowDown'
|
|
3483
|
+
|| e.key === 'Home' || e.key === 'End' || e.key === 'PageUp' || e.key === 'PageDown'
|
|
3484
|
+
|| ((e.key === 'a' || e.key === 'A') && withMod);
|
|
3485
|
+
if (selectish) scheduleCopySelection();
|
|
3486
|
+
});
|
|
3487
|
+
document.addEventListener('selectionchange', function () {
|
|
3488
|
+
if (selectPointerDown || selectCopying || selectShiftHeld) return;
|
|
3489
|
+
scheduleCopySelection();
|
|
3490
|
+
});
|
|
3491
|
+
|
|
3341
3492
|
function renderMentions(text) {
|
|
3342
3493
|
const fences = [];
|
|
3343
3494
|
const src = String(text).replace(/\`\`\`([\\w-]*)\\n?([\\s\\S]*?)\`\`\`/g, (m, lang, code) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.48.
|
|
3
|
+
"version": "0.48.84",
|
|
4
4
|
"description": "Local x402-paying proxy + MCP server for openzoo.fun — point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|