db-gn 1.0.8 → 1.1.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/README.md +1 -1
- package/index.html +1 -1
- package/package.json +1 -1
- package/v2.html +769 -51
package/README.md
CHANGED
package/index.html
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<button onclick="(()=>{document.body.innerHTML='<iframe src="https://cdn.jsdelivr.net/npm/db-gn@latest/index.svg" style="width:100%;height:100%;border:0"></iframe>'})()">Click
|
|
1
|
+
<button onclick="(()=>{document.body.innerHTML='<iframe src="https://cdn.jsdelivr.net/npm/db-gn@latest/index.svg" style="width:100%;height:100%;border:0"></iframe>'})()">Click</button>
|
package/package.json
CHANGED
package/v2.html
CHANGED
|
@@ -257,6 +257,219 @@
|
|
|
257
257
|
cursor: not-allowed;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
#autoclick-menu {
|
|
261
|
+
position: fixed;
|
|
262
|
+
z-index: 60;
|
|
263
|
+
display: none;
|
|
264
|
+
flex-direction: column;
|
|
265
|
+
gap: 4px;
|
|
266
|
+
min-width: 240px;
|
|
267
|
+
max-width: 320px;
|
|
268
|
+
max-height: 60vh;
|
|
269
|
+
overflow-y: auto;
|
|
270
|
+
padding: 6px;
|
|
271
|
+
background: rgba(30, 26, 46, 0.97);
|
|
272
|
+
border: 2px solid rgba(176, 0, 230, 0.45);
|
|
273
|
+
border-radius: 10px;
|
|
274
|
+
box-shadow: 0 10px 26px rgba(0, 0, 0, 0.45);
|
|
275
|
+
backdrop-filter: blur(6px);
|
|
276
|
+
font-family: 'Comic Sans MS', sans-serif;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
#autoclick-menu.open {
|
|
280
|
+
display: flex;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
#autoclick-menu .ac-item {
|
|
284
|
+
appearance: none;
|
|
285
|
+
border: 0;
|
|
286
|
+
background: none;
|
|
287
|
+
font-family: inherit;
|
|
288
|
+
font-size: 15px;
|
|
289
|
+
text-align: left;
|
|
290
|
+
color: #fff;
|
|
291
|
+
padding: 9px 10px;
|
|
292
|
+
border-radius: 6px;
|
|
293
|
+
cursor: pointer;
|
|
294
|
+
transition: background 0.15s ease, color 0.15s ease;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
#autoclick-menu .ac-item:hover:not(:disabled),
|
|
298
|
+
#autoclick-menu .ac-item:focus-visible {
|
|
299
|
+
background: rgba(176, 0, 230, 0.35);
|
|
300
|
+
outline: none;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
#autoclick-menu .ac-item:disabled {
|
|
304
|
+
opacity: 0.4;
|
|
305
|
+
cursor: not-allowed;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
#autoclick-menu .ac-new {
|
|
309
|
+
font-weight: 700;
|
|
310
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
|
|
311
|
+
border-radius: 6px 6px 0 0;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
#autoclick-menu .ac-list {
|
|
315
|
+
display: flex;
|
|
316
|
+
flex-direction: column;
|
|
317
|
+
gap: 2px;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
#autoclick-menu .ac-row {
|
|
321
|
+
display: flex;
|
|
322
|
+
align-items: center;
|
|
323
|
+
gap: 4px;
|
|
324
|
+
padding: 2px 4px 2px 0;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
#autoclick-menu .ac-row.off .ac-name,
|
|
328
|
+
#autoclick-menu .ac-row.off .ac-loc {
|
|
329
|
+
opacity: 0.45;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
#autoclick-menu .ac-name {
|
|
333
|
+
flex: 1;
|
|
334
|
+
appearance: none;
|
|
335
|
+
border: 0;
|
|
336
|
+
background: none;
|
|
337
|
+
font-family: inherit;
|
|
338
|
+
font-size: 15px;
|
|
339
|
+
text-align: left;
|
|
340
|
+
color: #fff;
|
|
341
|
+
padding: 7px 8px;
|
|
342
|
+
border-radius: 6px;
|
|
343
|
+
cursor: text;
|
|
344
|
+
min-width: 0;
|
|
345
|
+
overflow: hidden;
|
|
346
|
+
text-overflow: ellipsis;
|
|
347
|
+
white-space: nowrap;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
#autoclick-menu .ac-name:hover,
|
|
351
|
+
#autoclick-menu .ac-name:focus-visible {
|
|
352
|
+
background: rgba(176, 0, 230, 0.35);
|
|
353
|
+
outline: none;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
#autoclick-menu .ac-name-input {
|
|
357
|
+
flex: 1;
|
|
358
|
+
min-width: 0;
|
|
359
|
+
font-family: inherit;
|
|
360
|
+
font-size: 15px;
|
|
361
|
+
color: #fff;
|
|
362
|
+
padding: 6px 7px;
|
|
363
|
+
border-radius: 6px;
|
|
364
|
+
border: 1px solid rgba(176, 0, 230, 0.7);
|
|
365
|
+
background: rgba(0, 0, 0, 0.35);
|
|
366
|
+
outline: none;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
#autoclick-menu .ac-loc {
|
|
370
|
+
flex: none;
|
|
371
|
+
appearance: none;
|
|
372
|
+
border: 0;
|
|
373
|
+
background: none;
|
|
374
|
+
font-family: inherit;
|
|
375
|
+
font-size: 13px;
|
|
376
|
+
color: rgba(255, 255, 255, 0.6);
|
|
377
|
+
padding: 4px 6px;
|
|
378
|
+
border-radius: 6px;
|
|
379
|
+
cursor: pointer;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
#autoclick-menu .ac-loc:hover {
|
|
383
|
+
background: rgba(176, 0, 230, 0.35);
|
|
384
|
+
color: #fff;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
#autoclick-menu .ac-all {
|
|
388
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
|
|
389
|
+
margin-bottom: 2px;
|
|
390
|
+
padding-bottom: 6px;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
#autoclick-menu .ac-all-label {
|
|
394
|
+
flex: 1;
|
|
395
|
+
padding: 4px 8px;
|
|
396
|
+
font-size: 14px;
|
|
397
|
+
font-weight: 700;
|
|
398
|
+
color: rgba(255, 255, 255, 0.85);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
#autoclick-menu .ac-switch {
|
|
402
|
+
flex: none;
|
|
403
|
+
appearance: none;
|
|
404
|
+
border: 0;
|
|
405
|
+
width: 34px;
|
|
406
|
+
height: 18px;
|
|
407
|
+
padding: 0;
|
|
408
|
+
border-radius: 999px;
|
|
409
|
+
background: rgba(255, 255, 255, 0.25);
|
|
410
|
+
cursor: pointer;
|
|
411
|
+
position: relative;
|
|
412
|
+
transition: background 0.15s ease;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
#autoclick-menu .ac-switch.on {
|
|
416
|
+
background: rgba(0, 220, 120, 0.85);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
#autoclick-menu .ac-switch:disabled {
|
|
420
|
+
opacity: 0.35;
|
|
421
|
+
cursor: not-allowed;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
#autoclick-menu .ac-knob {
|
|
425
|
+
position: absolute;
|
|
426
|
+
top: 3px;
|
|
427
|
+
left: 3px;
|
|
428
|
+
width: 12px;
|
|
429
|
+
height: 12px;
|
|
430
|
+
border-radius: 50%;
|
|
431
|
+
background: #fff;
|
|
432
|
+
transition: transform 0.15s ease;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
#autoclick-menu .ac-switch.on .ac-knob {
|
|
436
|
+
transform: translateX(16px);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
#autoclick-menu .ac-remove {
|
|
440
|
+
appearance: none;
|
|
441
|
+
border: 0;
|
|
442
|
+
background: none;
|
|
443
|
+
color: rgba(255, 255, 255, 0.6);
|
|
444
|
+
font-family: inherit;
|
|
445
|
+
font-size: 18px;
|
|
446
|
+
line-height: 1;
|
|
447
|
+
padding: 6px 8px;
|
|
448
|
+
border-radius: 6px;
|
|
449
|
+
cursor: pointer;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
#autoclick-menu .ac-remove:hover {
|
|
453
|
+
background: rgba(255, 60, 60, 0.3);
|
|
454
|
+
color: #fff;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
#autoclick-menu .ac-group {
|
|
458
|
+
padding: 8px 10px 2px;
|
|
459
|
+
font-size: 13px;
|
|
460
|
+
font-weight: 700;
|
|
461
|
+
color: rgba(214, 140, 255, 0.9);
|
|
462
|
+
overflow: hidden;
|
|
463
|
+
text-overflow: ellipsis;
|
|
464
|
+
white-space: nowrap;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
#autoclick-menu .ac-empty {
|
|
468
|
+
padding: 9px 10px;
|
|
469
|
+
font-size: 14px;
|
|
470
|
+
color: rgba(255, 255, 255, 0.55);
|
|
471
|
+
}
|
|
472
|
+
|
|
260
473
|
.autoclick-overlay {
|
|
261
474
|
position: absolute;
|
|
262
475
|
inset: 0;
|
|
@@ -292,6 +505,14 @@
|
|
|
292
505
|
z-index: 51;
|
|
293
506
|
}
|
|
294
507
|
|
|
508
|
+
.autoclick-marker.paused {
|
|
509
|
+
border-color: rgba(255, 255, 255, 0.55);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.autoclick-marker.paused::after {
|
|
513
|
+
background: rgba(255, 255, 255, 0.55);
|
|
514
|
+
}
|
|
515
|
+
|
|
295
516
|
.autoclick-marker::after {
|
|
296
517
|
content: "";
|
|
297
518
|
position: absolute;
|
|
@@ -777,6 +998,24 @@
|
|
|
777
998
|
background: #006094;
|
|
778
999
|
}
|
|
779
1000
|
|
|
1001
|
+
.runner-check {
|
|
1002
|
+
display: flex;
|
|
1003
|
+
align-items: center;
|
|
1004
|
+
gap: 8px;
|
|
1005
|
+
margin-top: 10px;
|
|
1006
|
+
color: rgba(255, 255, 255, 0.85);
|
|
1007
|
+
font-size: 14px;
|
|
1008
|
+
cursor: pointer;
|
|
1009
|
+
user-select: none;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
.runner-check input {
|
|
1013
|
+
width: 16px;
|
|
1014
|
+
height: 16px;
|
|
1015
|
+
accent-color: #03a5fc;
|
|
1016
|
+
cursor: pointer;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
780
1019
|
.runner-frame {
|
|
781
1020
|
width: 100%;
|
|
782
1021
|
height: 100%;
|
|
@@ -1036,9 +1275,9 @@
|
|
|
1036
1275
|
btn.id = "autoclick-btn";
|
|
1037
1276
|
btn.type = "button";
|
|
1038
1277
|
btn.innerHTML = ICON_AUTOCLICK;
|
|
1039
|
-
btn.title = "Auto
|
|
1040
|
-
btn.setAttribute("aria-label", "Auto
|
|
1041
|
-
btn.onclick =
|
|
1278
|
+
btn.title = "Auto Clickers";
|
|
1279
|
+
btn.setAttribute("aria-label", "Auto Clickers");
|
|
1280
|
+
btn.onclick = toggleAutoclickMenu;
|
|
1042
1281
|
tabBar.appendChild(btn);
|
|
1043
1282
|
}
|
|
1044
1283
|
|
|
@@ -1159,7 +1398,64 @@
|
|
|
1159
1398
|
|
|
1160
1399
|
const AUTOCLICK_INTERVAL_MS = 1;
|
|
1161
1400
|
|
|
1162
|
-
|
|
1401
|
+
// clicker id -> clicker state. A tab can hold any number of them.
|
|
1402
|
+
const autoclickers = new Map();
|
|
1403
|
+
let autoclickSeq = 0;
|
|
1404
|
+
|
|
1405
|
+
/* Placed clickers survive closing a game: tab id -> [{ name, x, y }].
|
|
1406
|
+
Only the spots are kept, never the on/off state, so a reopened game
|
|
1407
|
+
always comes back with every clicker switched off. */
|
|
1408
|
+
const CLICKERS_KEY = "dancing-bunny-clickers-v1";
|
|
1409
|
+
let savedClickers = {};
|
|
1410
|
+
try {
|
|
1411
|
+
const parsed = JSON.parse(localStorage.getItem(CLICKERS_KEY) || "{}");
|
|
1412
|
+
if (parsed && typeof parsed === "object") savedClickers = parsed;
|
|
1413
|
+
} catch (e) { savedClickers = {}; }
|
|
1414
|
+
|
|
1415
|
+
function persistClickers() {
|
|
1416
|
+
try { localStorage.setItem(CLICKERS_KEY, JSON.stringify(savedClickers)); } catch (e) { }
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
function saveClickersForTab(tabId) {
|
|
1420
|
+
if (!tabId) return;
|
|
1421
|
+
const placed = clickersForTab(tabId)
|
|
1422
|
+
.filter(c => c.mode === "running")
|
|
1423
|
+
.map(c => ({ name: c.name, x: c.x, y: c.y }));
|
|
1424
|
+
if (placed.length) savedClickers[tabId] = placed;
|
|
1425
|
+
else delete savedClickers[tabId];
|
|
1426
|
+
persistClickers();
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
/* Rebuilds a game's saved clickers onto a freshly loaded iframe. They
|
|
1430
|
+
come back parked (off) with their markers in place. */
|
|
1431
|
+
function restoreClickersForTab(tabId, panel, iframe) {
|
|
1432
|
+
const saved = savedClickers[tabId];
|
|
1433
|
+
if (!Array.isArray(saved) || saved.length === 0) return;
|
|
1434
|
+
|
|
1435
|
+
saved.forEach((entry, i) => {
|
|
1436
|
+
if (!entry || typeof entry.x !== "number" || typeof entry.y !== "number") return;
|
|
1437
|
+
const clicker = {
|
|
1438
|
+
id: `ac-${++autoclickSeq}`,
|
|
1439
|
+
tabId,
|
|
1440
|
+
name: entry.name || `Clicker ${i + 1}`,
|
|
1441
|
+
enabled: false,
|
|
1442
|
+
panel,
|
|
1443
|
+
iframe,
|
|
1444
|
+
overlay: null,
|
|
1445
|
+
marker: null,
|
|
1446
|
+
timer: null,
|
|
1447
|
+
x: entry.x,
|
|
1448
|
+
y: entry.y,
|
|
1449
|
+
mode: "running"
|
|
1450
|
+
};
|
|
1451
|
+
autoclickers.set(clicker.id, clicker);
|
|
1452
|
+
clicker.fire = makeClickerFire(clicker);
|
|
1453
|
+
placeClickerMarker(clicker);
|
|
1454
|
+
});
|
|
1455
|
+
|
|
1456
|
+
setAutoclickButtonState();
|
|
1457
|
+
refreshAutoclickMenu();
|
|
1458
|
+
}
|
|
1163
1459
|
|
|
1164
1460
|
function panelTabId(panel) {
|
|
1165
1461
|
if (!panel || !panel.id.startsWith("panel-")) return null;
|
|
@@ -1173,41 +1469,46 @@
|
|
|
1173
1469
|
return iframe ? { id: panelTabId(panel), panel, iframe } : null;
|
|
1174
1470
|
}
|
|
1175
1471
|
|
|
1176
|
-
function
|
|
1472
|
+
function clickersForTab(tabId) {
|
|
1473
|
+
return [...autoclickers.values()].filter(c => c.tabId === tabId);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
function currentAutoclickers() {
|
|
1177
1477
|
const target = activeGamePanel();
|
|
1178
|
-
return target ?
|
|
1478
|
+
return target ? clickersForTab(target.id) : [];
|
|
1179
1479
|
}
|
|
1180
1480
|
|
|
1181
1481
|
function runningAutoclickCount() {
|
|
1182
1482
|
let count = 0;
|
|
1183
|
-
autoclickers.forEach(c => { if (c.mode === "running") count++; });
|
|
1483
|
+
autoclickers.forEach(c => { if (c.mode === "running" && c.enabled) count++; });
|
|
1184
1484
|
return count;
|
|
1185
1485
|
}
|
|
1186
1486
|
|
|
1187
1487
|
function setAutoclickButtonState() {
|
|
1188
1488
|
const btn = document.getElementById("autoclick-btn");
|
|
1189
1489
|
if (!btn) return;
|
|
1190
|
-
const
|
|
1191
|
-
const
|
|
1192
|
-
const
|
|
1490
|
+
const mine = currentAutoclickers();
|
|
1491
|
+
const arming = mine.some(c => c.mode === "arming");
|
|
1492
|
+
const runningHere = mine.filter(c => c.mode === "running" && c.enabled).length;
|
|
1493
|
+
const elsewhere = runningAutoclickCount() - runningHere;
|
|
1193
1494
|
|
|
1194
|
-
btn.classList.toggle("arming",
|
|
1195
|
-
btn.classList.toggle("running",
|
|
1495
|
+
btn.classList.toggle("arming", arming);
|
|
1496
|
+
btn.classList.toggle("running", runningHere > 0);
|
|
1196
1497
|
|
|
1197
1498
|
let title =
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
"Auto
|
|
1499
|
+
arming ? "Auto Clicker: pick a spot (Esc to cancel)" :
|
|
1500
|
+
runningHere > 0 ? `Auto Clickers: ${runningHere} running on this tab` :
|
|
1501
|
+
"Auto Clickers";
|
|
1201
1502
|
if (elsewhere > 0) {
|
|
1202
|
-
title += `
|
|
1503
|
+
title += ` ‧ ${elsewhere} running in background`;
|
|
1203
1504
|
}
|
|
1204
1505
|
btn.title = title;
|
|
1205
1506
|
btn.setAttribute("aria-label", title);
|
|
1206
|
-
btn.setAttribute("aria-pressed",
|
|
1207
|
-
btn.classList.toggle("background-active",
|
|
1507
|
+
btn.setAttribute("aria-pressed", (arming || runningHere > 0) ? "true" : "false");
|
|
1508
|
+
btn.classList.toggle("background-active", runningHere === 0 && !arming && elsewhere > 0);
|
|
1208
1509
|
}
|
|
1209
1510
|
|
|
1210
|
-
function stopAutoclick(clicker =
|
|
1511
|
+
function stopAutoclick(clicker, opts = {}) {
|
|
1211
1512
|
if (!clicker) return;
|
|
1212
1513
|
if (clicker.timer) clearInterval(clicker.timer);
|
|
1213
1514
|
clicker.timer = null;
|
|
@@ -1217,18 +1518,21 @@
|
|
|
1217
1518
|
clicker.marker = null;
|
|
1218
1519
|
clicker.mode = "off";
|
|
1219
1520
|
autoclickers.delete(clicker.id);
|
|
1521
|
+
// Closing a tab tears its clickers down but must not forget them.
|
|
1522
|
+
if (!opts.keepSaved) saveClickersForTab(clicker.tabId);
|
|
1220
1523
|
setAutoclickButtonState();
|
|
1524
|
+
refreshAutoclickMenu();
|
|
1221
1525
|
}
|
|
1222
1526
|
|
|
1223
1527
|
function stopAutoclickForPanel(panel) {
|
|
1224
1528
|
const id = panelTabId(panel);
|
|
1225
|
-
|
|
1226
|
-
|
|
1529
|
+
if (!id) return;
|
|
1530
|
+
clickersForTab(id).forEach(c => stopAutoclick(c, { keepSaved: true }));
|
|
1227
1531
|
}
|
|
1228
1532
|
|
|
1229
1533
|
// A picker that never got a spot shouldn't linger on a tab you left.
|
|
1230
1534
|
function cancelArmingAutoclickers() {
|
|
1231
|
-
autoclickers.forEach(clicker => {
|
|
1535
|
+
[...autoclickers.values()].forEach(clicker => {
|
|
1232
1536
|
if (clicker.mode === "arming") stopAutoclick(clicker);
|
|
1233
1537
|
});
|
|
1234
1538
|
}
|
|
@@ -1246,7 +1550,10 @@
|
|
|
1246
1550
|
}
|
|
1247
1551
|
|
|
1248
1552
|
const clicker = {
|
|
1249
|
-
id:
|
|
1553
|
+
id: `ac-${++autoclickSeq}`,
|
|
1554
|
+
tabId: target.id,
|
|
1555
|
+
name: `Clicker ${clickersForTab(target.id).length + 1}`,
|
|
1556
|
+
enabled: true,
|
|
1250
1557
|
panel: target.panel,
|
|
1251
1558
|
iframe: target.iframe,
|
|
1252
1559
|
overlay: null,
|
|
@@ -1260,7 +1567,7 @@
|
|
|
1260
1567
|
|
|
1261
1568
|
const overlay = document.createElement("div");
|
|
1262
1569
|
overlay.className = "autoclick-overlay";
|
|
1263
|
-
overlay.innerHTML = `<div class="autoclick-hint">Click a spot to auto-click
|
|
1570
|
+
overlay.innerHTML = `<div class="autoclick-hint">Click a spot to auto-click ‧ Esc to cancel</div>`;
|
|
1264
1571
|
overlay.addEventListener("click", (e) => {
|
|
1265
1572
|
const rect = clicker.iframe.getBoundingClientRect();
|
|
1266
1573
|
startAutoclick(clicker, e.clientX - rect.left, e.clientY - rect.top);
|
|
@@ -1272,46 +1579,333 @@
|
|
|
1272
1579
|
setAutoclickButtonState();
|
|
1273
1580
|
}
|
|
1274
1581
|
|
|
1275
|
-
function
|
|
1276
|
-
|
|
1277
|
-
clicker.overlay = null;
|
|
1278
|
-
clicker.x = x;
|
|
1279
|
-
clicker.y = y;
|
|
1280
|
-
clicker.mode = "running";
|
|
1281
|
-
|
|
1282
|
-
const marker = document.createElement("div");
|
|
1283
|
-
marker.className = "autoclick-marker";
|
|
1284
|
-
const rect = clicker.iframe.getBoundingClientRect();
|
|
1285
|
-
const panelRect = clicker.panel.getBoundingClientRect();
|
|
1286
|
-
marker.style.left = `${rect.left - panelRect.left + x}px`;
|
|
1287
|
-
marker.style.top = `${rect.top - panelRect.top + y}px`;
|
|
1288
|
-
clicker.marker = marker;
|
|
1289
|
-
clicker.panel.appendChild(marker);
|
|
1290
|
-
|
|
1291
|
-
const fire = () => {
|
|
1582
|
+
function makeClickerFire(clicker) {
|
|
1583
|
+
return () => {
|
|
1292
1584
|
const frame = clicker.iframe;
|
|
1293
1585
|
// The game was closed or reloaded out from under us.
|
|
1294
|
-
if (!frame || !frame.isConnected) { stopAutoclick(clicker); return; }
|
|
1586
|
+
if (!frame || !frame.isConnected) { stopAutoclick(clicker, { keepSaved: true }); return; }
|
|
1295
1587
|
try {
|
|
1296
1588
|
frame.contentWindow?.postMessage(
|
|
1297
1589
|
{ type: "AUTO_CLICK", x: clicker.x, y: clicker.y }, "*"
|
|
1298
1590
|
);
|
|
1299
1591
|
} catch (e) { }
|
|
1300
1592
|
};
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
function placeClickerMarker(clicker) {
|
|
1596
|
+
clicker.marker?.remove();
|
|
1597
|
+
const marker = document.createElement("div");
|
|
1598
|
+
marker.className = "autoclick-marker";
|
|
1599
|
+
marker.classList.toggle("paused", !clicker.enabled);
|
|
1600
|
+
|
|
1601
|
+
// A hidden panel measures as 0x0; the frame fills the panel anyway,
|
|
1602
|
+
// so fall back to the raw coordinates.
|
|
1603
|
+
const rect = clicker.iframe.getBoundingClientRect();
|
|
1604
|
+
const panelRect = clicker.panel.getBoundingClientRect();
|
|
1605
|
+
const offsetX = rect.width ? rect.left - panelRect.left : 0;
|
|
1606
|
+
const offsetY = rect.height ? rect.top - panelRect.top : 0;
|
|
1607
|
+
marker.style.left = `${offsetX + clicker.x}px`;
|
|
1608
|
+
marker.style.top = `${offsetY + clicker.y}px`;
|
|
1301
1609
|
|
|
1302
|
-
|
|
1303
|
-
clicker.
|
|
1610
|
+
clicker.marker = marker;
|
|
1611
|
+
clicker.panel.appendChild(marker);
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
function startAutoclick(clicker, x, y) {
|
|
1615
|
+
clicker.overlay?.remove();
|
|
1616
|
+
clicker.overlay = null;
|
|
1617
|
+
clicker.x = x;
|
|
1618
|
+
clicker.y = y;
|
|
1619
|
+
clicker.mode = "running";
|
|
1620
|
+
|
|
1621
|
+
placeClickerMarker(clicker);
|
|
1622
|
+
clicker.fire = makeClickerFire(clicker);
|
|
1623
|
+
|
|
1624
|
+
if (clicker.enabled) {
|
|
1625
|
+
clicker.fire();
|
|
1626
|
+
clicker.timer = setInterval(clicker.fire, AUTOCLICK_INTERVAL_MS);
|
|
1627
|
+
}
|
|
1628
|
+
saveClickersForTab(clicker.tabId);
|
|
1629
|
+
setAutoclickButtonState();
|
|
1630
|
+
refreshAutoclickMenu();
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
/* Switching a clicker off parks it: the spot is kept, only the firing
|
|
1634
|
+
stops, so it can be switched back on without picking again. */
|
|
1635
|
+
function setClickerEnabled(clicker, enabled) {
|
|
1636
|
+
if (!clicker || clicker.enabled === enabled) return;
|
|
1637
|
+
clicker.enabled = enabled;
|
|
1638
|
+
|
|
1639
|
+
if (!enabled) {
|
|
1640
|
+
if (clicker.timer) clearInterval(clicker.timer);
|
|
1641
|
+
clicker.timer = null;
|
|
1642
|
+
} else if (clicker.mode === "running" && !clicker.timer && clicker.fire) {
|
|
1643
|
+
clicker.fire();
|
|
1644
|
+
clicker.timer = setInterval(clicker.fire, AUTOCLICK_INTERVAL_MS);
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
clicker.marker?.classList.toggle("paused", !enabled);
|
|
1304
1648
|
setAutoclickButtonState();
|
|
1305
1649
|
}
|
|
1306
1650
|
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1651
|
+
/* ---------------- Auto clicker menu ----------------
|
|
1652
|
+
The toolbar button opens a list: a "New Auto Clicker" action on top,
|
|
1653
|
+
then the clickers themselves. On a game tab only that game's clickers
|
|
1654
|
+
are listed; on the home tab every clicker is listed under its game. */
|
|
1655
|
+
|
|
1656
|
+
function ensureAutoclickMenu() {
|
|
1657
|
+
let menu = document.getElementById("autoclick-menu");
|
|
1658
|
+
if (menu) return menu;
|
|
1659
|
+
|
|
1660
|
+
menu = document.createElement("div");
|
|
1661
|
+
menu.id = "autoclick-menu";
|
|
1662
|
+
menu.setAttribute("role", "menu");
|
|
1663
|
+
document.body.appendChild(menu);
|
|
1664
|
+
|
|
1665
|
+
document.addEventListener("mousedown", (e) => {
|
|
1666
|
+
if (!isAutoclickMenuOpen()) return;
|
|
1667
|
+
if (menu.contains(e.target)) return;
|
|
1668
|
+
if (e.target.closest("#autoclick-btn")) return;
|
|
1669
|
+
closeAutoclickMenu();
|
|
1670
|
+
});
|
|
1671
|
+
window.addEventListener("resize", closeAutoclickMenu);
|
|
1672
|
+
window.addEventListener("blur", closeAutoclickMenu);
|
|
1673
|
+
|
|
1674
|
+
return menu;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
function isAutoclickMenuOpen() {
|
|
1678
|
+
return !!document.getElementById("autoclick-menu")?.classList.contains("open");
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
function closeAutoclickMenu() {
|
|
1682
|
+
document.getElementById("autoclick-menu")?.classList.remove("open");
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
function clickerLocationLabel(clicker) {
|
|
1686
|
+
if (clicker.mode === "arming") return "picking a spot…";
|
|
1687
|
+
return `${Math.round(clicker.x)}, ${Math.round(clicker.y)}`;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
function tabTitleFor(tabId) {
|
|
1691
|
+
return tabs.get(tabId)?.title || "Game";
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
/* A small on/off switch. `state` is the current value; `onToggle` gets
|
|
1695
|
+
the value it was flipped to. */
|
|
1696
|
+
function makeAutoclickSwitch(state, label, onToggle, disabled = false) {
|
|
1697
|
+
const sw = document.createElement("button");
|
|
1698
|
+
sw.type = "button";
|
|
1699
|
+
sw.className = "ac-switch";
|
|
1700
|
+
sw.setAttribute("role", "switch");
|
|
1701
|
+
sw.setAttribute("aria-checked", state ? "true" : "false");
|
|
1702
|
+
sw.setAttribute("aria-label", label);
|
|
1703
|
+
sw.title = label;
|
|
1704
|
+
sw.classList.toggle("on", !!state);
|
|
1705
|
+
sw.disabled = disabled;
|
|
1706
|
+
sw.innerHTML = `<span class="ac-knob"></span>`;
|
|
1707
|
+
sw.onclick = () => { onToggle(!state); };
|
|
1708
|
+
return sw;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
/* Clicking a clicker's name swaps it for a text field. Enter or blur
|
|
1712
|
+
commits, Escape puts the old name back. */
|
|
1713
|
+
function startClickerRename(clicker, nameBtn) {
|
|
1714
|
+
const input = document.createElement("input");
|
|
1715
|
+
input.type = "text";
|
|
1716
|
+
input.className = "ac-name-input";
|
|
1717
|
+
input.value = clicker.name;
|
|
1718
|
+
input.maxLength = 40;
|
|
1719
|
+
|
|
1720
|
+
let done = false;
|
|
1721
|
+
const finish = (save) => {
|
|
1722
|
+
if (done) return;
|
|
1723
|
+
done = true;
|
|
1724
|
+
if (save) {
|
|
1725
|
+
const next = input.value.replace(/\s+/g, " ").trim();
|
|
1726
|
+
if (next && next !== clicker.name) {
|
|
1727
|
+
clicker.name = next;
|
|
1728
|
+
saveClickersForTab(clicker.tabId);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
nameBtn.textContent = clicker.name;
|
|
1732
|
+
input.replaceWith(nameBtn);
|
|
1733
|
+
};
|
|
1734
|
+
|
|
1735
|
+
input.addEventListener("keydown", (e) => {
|
|
1736
|
+
e.stopPropagation();
|
|
1737
|
+
if (e.key === "Enter") { e.preventDefault(); finish(true); }
|
|
1738
|
+
else if (e.key === "Escape") { e.preventDefault(); finish(false); }
|
|
1739
|
+
});
|
|
1740
|
+
input.addEventListener("blur", () => finish(true));
|
|
1741
|
+
|
|
1742
|
+
nameBtn.replaceWith(input);
|
|
1743
|
+
input.focus();
|
|
1744
|
+
input.select();
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
function makeAutoclickRow(clicker) {
|
|
1748
|
+
const row = document.createElement("div");
|
|
1749
|
+
row.className = "ac-row";
|
|
1750
|
+
row.classList.toggle("off", !clicker.enabled);
|
|
1751
|
+
|
|
1752
|
+
const nameBtn = document.createElement("button");
|
|
1753
|
+
nameBtn.type = "button";
|
|
1754
|
+
nameBtn.className = "ac-name";
|
|
1755
|
+
nameBtn.textContent = clicker.name;
|
|
1756
|
+
nameBtn.title = "Click to rename";
|
|
1757
|
+
nameBtn.onclick = () => startClickerRename(clicker, nameBtn);
|
|
1758
|
+
|
|
1759
|
+
const loc = document.createElement("button");
|
|
1760
|
+
loc.type = "button";
|
|
1761
|
+
loc.className = "ac-loc";
|
|
1762
|
+
loc.textContent = clickerLocationLabel(clicker);
|
|
1763
|
+
loc.title = "Go to this clicker's tab";
|
|
1764
|
+
loc.onclick = () => {
|
|
1765
|
+
closeAutoclickMenu();
|
|
1766
|
+
if (tabs.has(clicker.tabId)) activateTab(clicker.tabId);
|
|
1767
|
+
};
|
|
1768
|
+
|
|
1769
|
+
const sw = makeAutoclickSwitch(clicker.enabled, `Turn ${clicker.name} ${clicker.enabled ? "off" : "on"}`, (on) => {
|
|
1770
|
+
setClickerEnabled(clicker, on);
|
|
1771
|
+
renderAutoclickMenu();
|
|
1772
|
+
});
|
|
1773
|
+
|
|
1774
|
+
const remove = document.createElement("button");
|
|
1775
|
+
remove.type = "button";
|
|
1776
|
+
remove.className = "ac-remove";
|
|
1777
|
+
remove.textContent = "×";
|
|
1778
|
+
remove.title = "Delete this auto clicker";
|
|
1779
|
+
remove.setAttribute("aria-label", "Delete this auto clicker");
|
|
1780
|
+
remove.onclick = () => { stopAutoclick(clicker); };
|
|
1781
|
+
|
|
1782
|
+
row.appendChild(nameBtn);
|
|
1783
|
+
row.appendChild(loc);
|
|
1784
|
+
row.appendChild(sw);
|
|
1785
|
+
row.appendChild(remove);
|
|
1786
|
+
return row;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
/* The "all of this game's clickers" switch: on only when every clicker
|
|
1790
|
+
in the group is on. */
|
|
1791
|
+
function makeAutoclickAllRow(label, clickers) {
|
|
1792
|
+
const row = document.createElement("div");
|
|
1793
|
+
row.className = "ac-row ac-all";
|
|
1794
|
+
|
|
1795
|
+
const text = document.createElement("span");
|
|
1796
|
+
text.className = "ac-all-label";
|
|
1797
|
+
text.textContent = label;
|
|
1798
|
+
|
|
1799
|
+
const allOn = clickers.length > 0 && clickers.every(c => c.enabled);
|
|
1800
|
+
const sw = makeAutoclickSwitch(allOn, `Turn all ${label.toLowerCase()} ${allOn ? "off" : "on"}`, (on) => {
|
|
1801
|
+
clickers.forEach(c => setClickerEnabled(c, on));
|
|
1802
|
+
renderAutoclickMenu();
|
|
1803
|
+
}, clickers.length === 0);
|
|
1804
|
+
|
|
1805
|
+
row.appendChild(text);
|
|
1806
|
+
row.appendChild(sw);
|
|
1807
|
+
return row;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
function renderAutoclickMenu() {
|
|
1811
|
+
const menu = ensureAutoclickMenu();
|
|
1812
|
+
menu.innerHTML = "";
|
|
1813
|
+
|
|
1814
|
+
const target = activeGamePanel();
|
|
1815
|
+
|
|
1816
|
+
const newBtn = document.createElement("button");
|
|
1817
|
+
newBtn.type = "button";
|
|
1818
|
+
newBtn.className = "ac-item ac-new";
|
|
1819
|
+
newBtn.textContent = "New Auto Clicker";
|
|
1820
|
+
if (target) {
|
|
1821
|
+
newBtn.onclick = () => { closeAutoclickMenu(); armAutoclick(); };
|
|
1822
|
+
} else {
|
|
1823
|
+
newBtn.disabled = true;
|
|
1824
|
+
newBtn.title = "Open a game tab first";
|
|
1825
|
+
}
|
|
1826
|
+
menu.appendChild(newBtn);
|
|
1827
|
+
|
|
1828
|
+
const list = document.createElement("div");
|
|
1829
|
+
list.className = "ac-list";
|
|
1830
|
+
|
|
1831
|
+
if (target) {
|
|
1832
|
+
// On a game tab: just this game's clickers, with a master switch.
|
|
1833
|
+
const mine = clickersForTab(target.id);
|
|
1834
|
+
list.appendChild(makeAutoclickAllRow("All Clickers", mine));
|
|
1835
|
+
if (mine.length === 0) {
|
|
1836
|
+
list.appendChild(makeAutoclickEmpty("No auto clickers on this game."));
|
|
1837
|
+
} else {
|
|
1838
|
+
mine.forEach(clicker => list.appendChild(makeAutoclickRow(clicker)));
|
|
1839
|
+
}
|
|
1840
|
+
} else {
|
|
1841
|
+
// On the home tab: everything, grouped under its game's name,
|
|
1842
|
+
// each group with its own master switch.
|
|
1843
|
+
const byTab = new Map();
|
|
1844
|
+
autoclickers.forEach(clicker => {
|
|
1845
|
+
if (!byTab.has(clicker.tabId)) byTab.set(clicker.tabId, []);
|
|
1846
|
+
byTab.get(clicker.tabId).push(clicker);
|
|
1847
|
+
});
|
|
1848
|
+
|
|
1849
|
+
if (byTab.size === 0) {
|
|
1850
|
+
list.appendChild(makeAutoclickEmpty("No auto clickers running."));
|
|
1851
|
+
} else {
|
|
1852
|
+
byTab.forEach((clickers, tabId) => {
|
|
1853
|
+
const header = document.createElement("div");
|
|
1854
|
+
header.className = "ac-group";
|
|
1855
|
+
header.textContent = tabTitleFor(tabId);
|
|
1856
|
+
list.appendChild(header);
|
|
1857
|
+
list.appendChild(makeAutoclickAllRow("All", clickers));
|
|
1858
|
+
clickers.forEach(clicker => list.appendChild(makeAutoclickRow(clicker)));
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
menu.appendChild(list);
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
/* Live updates skip the redraw while a name is being typed. */
|
|
1867
|
+
function refreshAutoclickMenu() {
|
|
1868
|
+
const menu = document.getElementById("autoclick-menu");
|
|
1869
|
+
if (!menu || !menu.classList.contains("open")) return;
|
|
1870
|
+
if (menu.querySelector(".ac-name-input")) return;
|
|
1871
|
+
renderAutoclickMenu();
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
function makeAutoclickEmpty(text) {
|
|
1875
|
+
const empty = document.createElement("div");
|
|
1876
|
+
empty.className = "ac-empty";
|
|
1877
|
+
empty.textContent = text;
|
|
1878
|
+
return empty;
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
function openAutoclickMenu() {
|
|
1882
|
+
const btn = document.getElementById("autoclick-btn");
|
|
1883
|
+
const menu = ensureAutoclickMenu();
|
|
1884
|
+
renderAutoclickMenu();
|
|
1885
|
+
menu.classList.add("open");
|
|
1886
|
+
|
|
1887
|
+
if (!btn) return;
|
|
1888
|
+
const anchor = btn.getBoundingClientRect();
|
|
1889
|
+
const rect = menu.getBoundingClientRect();
|
|
1890
|
+
const margin = 8;
|
|
1891
|
+
let left = anchor.right - rect.width;
|
|
1892
|
+
let top = anchor.bottom + 6;
|
|
1893
|
+
left = Math.max(margin, Math.min(left, window.innerWidth - rect.width - margin));
|
|
1894
|
+
top = Math.max(margin, Math.min(top, window.innerHeight - rect.height - margin));
|
|
1895
|
+
menu.style.left = `${left}px`;
|
|
1896
|
+
menu.style.top = `${top}px`;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
function toggleAutoclickMenu() {
|
|
1900
|
+
if (isAutoclickMenuOpen()) closeAutoclickMenu();
|
|
1901
|
+
else openAutoclickMenu();
|
|
1311
1902
|
}
|
|
1312
1903
|
|
|
1313
1904
|
document.addEventListener("keydown", (e) => {
|
|
1314
|
-
if (e.key
|
|
1905
|
+
if (e.key !== "Escape") return;
|
|
1906
|
+
if (document.activeElement?.classList.contains("ac-name-input")) return;
|
|
1907
|
+
closeAutoclickMenu();
|
|
1908
|
+
cancelArmingAutoclickers();
|
|
1315
1909
|
});
|
|
1316
1910
|
|
|
1317
1911
|
(function protectTabIdentity() {
|
|
@@ -1421,6 +2015,7 @@
|
|
|
1421
2015
|
|
|
1422
2016
|
function activateTab(id) {
|
|
1423
2017
|
setTabStatus(id, null);
|
|
2018
|
+
closeAutoclickMenu();
|
|
1424
2019
|
cancelArmingAutoclickers();
|
|
1425
2020
|
setActiveTabButton(id);
|
|
1426
2021
|
setActivePanel(id);
|
|
@@ -2088,6 +2683,7 @@ ${icons}
|
|
|
2088
2683
|
iframe.referrerPolicy = "no-referrer";
|
|
2089
2684
|
applyMuteToIframe(iframe);
|
|
2090
2685
|
tab.panel.appendChild(iframe);
|
|
2686
|
+
restoreClickersForTab(id, tab.panel, iframe);
|
|
2091
2687
|
setTabStatus(id, isTabActive(id) ? null : "ready");
|
|
2092
2688
|
|
|
2093
2689
|
} catch (err) {
|
|
@@ -2101,14 +2697,119 @@ ${icons}
|
|
|
2101
2697
|
}
|
|
2102
2698
|
}
|
|
2103
2699
|
|
|
2700
|
+
/* Pulls the text of the code's own <title> tag, entities decoded. */
|
|
2701
|
+
function extractDocTitle(htmlCode) {
|
|
2702
|
+
const match = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(htmlCode || "");
|
|
2703
|
+
if (!match) return "";
|
|
2704
|
+
const decoder = document.createElement("textarea");
|
|
2705
|
+
decoder.innerHTML = match[1];
|
|
2706
|
+
return decoder.value.replace(/\s+/g, " ").trim();
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
function escapeHtmlText(text) {
|
|
2710
|
+
return String(text)
|
|
2711
|
+
.replace(/&/g, "&")
|
|
2712
|
+
.replace(/</g, "<")
|
|
2713
|
+
.replace(/>/g, ">")
|
|
2714
|
+
.replace(/"/g, """);
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
/* The home tab's own favicon links, as markup. */
|
|
2718
|
+
function homeIconsHtml() {
|
|
2719
|
+
return [...document.querySelectorAll("link[rel='icon']")].map(l => l.outerHTML).join("\n");
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
/* Icon links declared in the code. Relative hrefs are dropped: the popup
|
|
2723
|
+
lives on about:blank, so they would resolve to nothing. */
|
|
2724
|
+
function extractIconLinksHtml(htmlCode) {
|
|
2725
|
+
const links = [];
|
|
2726
|
+
const tagPattern = /<link\b[^>]*>/gi;
|
|
2727
|
+
let tag;
|
|
2728
|
+
while ((tag = tagPattern.exec(htmlCode || "")) !== null) {
|
|
2729
|
+
const text = tag[0];
|
|
2730
|
+
const rel = /\brel\s*=\s*("([^"]*)"|'([^']*)'|([^\s>]+))/i.exec(text);
|
|
2731
|
+
const relValue = rel ? (rel[2] ?? rel[3] ?? rel[4] ?? "") : "";
|
|
2732
|
+
if (!/\bicon\b/i.test(relValue)) continue;
|
|
2733
|
+
const href = /\bhref\s*=\s*("([^"]*)"|'([^']*)'|([^\s>]+))/i.exec(text);
|
|
2734
|
+
const hrefValue = href ? (href[2] ?? href[3] ?? href[4] ?? "").trim() : "";
|
|
2735
|
+
if (!/^(data:|https?:|\/\/)/i.test(hrefValue)) continue;
|
|
2736
|
+
links.push(text);
|
|
2737
|
+
}
|
|
2738
|
+
return links.join("\n");
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
/* Runs the typed HTML in a real browser tab. With cloaking on (or when the
|
|
2742
|
+
code declares no title/icon of its own) the tab wears the home page's
|
|
2743
|
+
title and favicon instead. */
|
|
2744
|
+
function openHtmlInNewWindow(htmlCode, keepCloaking) {
|
|
2745
|
+
const win = window.open("about:blank", "_blank");
|
|
2746
|
+
if (!win) {
|
|
2747
|
+
alert("The popup was blocked. Allow popups for this page to open code in a new tab.");
|
|
2748
|
+
return;
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
const codeTitle = keepCloaking ? "" : extractDocTitle(htmlCode);
|
|
2752
|
+
const codeIcons = keepCloaking ? "" : extractIconLinksHtml(htmlCode);
|
|
2753
|
+
const title = codeTitle || "New tab";
|
|
2754
|
+
const icons = codeIcons || homeIconsHtml();
|
|
2755
|
+
|
|
2756
|
+
const prepared = injectBaseHref(
|
|
2757
|
+
injectOffTabSupport(injectAutoclickSupport(injectMuteSupport(htmlCode, isMuted)), bgRunEnabled),
|
|
2758
|
+
document.baseURI
|
|
2759
|
+
);
|
|
2760
|
+
|
|
2761
|
+
win.document.write(`<!DOCTYPE html>
|
|
2762
|
+
<html>
|
|
2763
|
+
<head>
|
|
2764
|
+
<meta charset="utf-8">
|
|
2765
|
+
<title>${escapeHtmlText(title)}</title>
|
|
2766
|
+
${icons}
|
|
2767
|
+
<style>
|
|
2768
|
+
html, body { margin: 0; height: 100%; background: #fff; }
|
|
2769
|
+
iframe { border: 0; display: block; width: 100%; height: 100%; }
|
|
2770
|
+
</style>
|
|
2771
|
+
</head>
|
|
2772
|
+
<body>
|
|
2773
|
+
<script>
|
|
2774
|
+
// Same trick as the main page: re-append the icon links so the browser
|
|
2775
|
+
// picks the favicon back up after the document is written.
|
|
2776
|
+
function refreshFavicon() {
|
|
2777
|
+
const existingIcons = document.querySelectorAll("link[rel='icon']");
|
|
2778
|
+
existingIcons.forEach(oldIcon => {
|
|
2779
|
+
const newIcon = oldIcon.cloneNode(true);
|
|
2780
|
+
oldIcon.remove();
|
|
2781
|
+
document.head.appendChild(newIcon);
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
setTimeout(refreshFavicon, 51);
|
|
2785
|
+
<\/script>
|
|
2786
|
+
</body>
|
|
2787
|
+
</html>`);
|
|
2788
|
+
win.document.close();
|
|
2789
|
+
|
|
2790
|
+
// document.write resets the title in some browsers; pin it afterwards.
|
|
2791
|
+
try { win.document.title = title; } catch (err) { }
|
|
2792
|
+
|
|
2793
|
+
const iframe = win.document.createElement("iframe");
|
|
2794
|
+
iframe.srcdoc = prepared;
|
|
2795
|
+
iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-modals");
|
|
2796
|
+
iframe.referrerPolicy = "no-referrer";
|
|
2797
|
+
win.document.body.appendChild(iframe);
|
|
2798
|
+
|
|
2799
|
+
try { win.refreshFavicon && win.setTimeout(win.refreshFavicon, 51); } catch (err) { }
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2104
2802
|
function openHtmlPreviewTab(htmlCode) {
|
|
2105
2803
|
const id = "html-preview";
|
|
2106
|
-
const title = "HTML Code";
|
|
2804
|
+
const title = extractDocTitle(htmlCode) || "HTML Code";
|
|
2107
2805
|
|
|
2108
2806
|
const prepared = injectBaseHref(injectAutoclickSupport(injectMuteSupport(htmlCode, isMuted)), document.baseURI);
|
|
2109
2807
|
|
|
2110
2808
|
if (tabs.has(id)) {
|
|
2111
2809
|
const existing = tabs.get(id);
|
|
2810
|
+
existing.title = title;
|
|
2811
|
+
const label = existing.button.querySelector(".tab-title");
|
|
2812
|
+
if (label) label.textContent = sanitizeTitle(title);
|
|
2112
2813
|
existing.panel.innerHTML = "";
|
|
2113
2814
|
const iframe = document.createElement("iframe");
|
|
2114
2815
|
iframe.className = "game-frame";
|
|
@@ -2184,9 +2885,14 @@ ${icons}
|
|
|
2184
2885
|
</body>
|
|
2185
2886
|
</html></textarea>
|
|
2186
2887
|
<div class="runner-controls">
|
|
2187
|
-
<button id="runner-run-btn">Open in New
|
|
2888
|
+
<button id="runner-run-btn">Open in New Subtab</button>
|
|
2889
|
+
<button id="runner-newtab-btn">Open in New Tab</button>
|
|
2188
2890
|
<button id="runner-clear-btn">Clear</button>
|
|
2189
2891
|
</div>
|
|
2892
|
+
<label class="runner-check" for="runner-cloak">
|
|
2893
|
+
<input type="checkbox" id="runner-cloak" checked>
|
|
2894
|
+
<span>Keep Cloaking</span>
|
|
2895
|
+
</label>
|
|
2190
2896
|
</div>
|
|
2191
2897
|
</div>
|
|
2192
2898
|
`;
|
|
@@ -2201,12 +2907,18 @@ ${icons}
|
|
|
2201
2907
|
|
|
2202
2908
|
const code = panel.querySelector("#runner-code");
|
|
2203
2909
|
const runBtn = panel.querySelector("#runner-run-btn");
|
|
2910
|
+
const newTabBtn = panel.querySelector("#runner-newtab-btn");
|
|
2911
|
+
const cloakBox = panel.querySelector("#runner-cloak");
|
|
2204
2912
|
const clearBtn = panel.querySelector("#runner-clear-btn");
|
|
2205
2913
|
|
|
2206
2914
|
runBtn.onclick = () => {
|
|
2207
2915
|
openHtmlPreviewTab(code.value);
|
|
2208
2916
|
};
|
|
2209
2917
|
|
|
2918
|
+
newTabBtn.onclick = () => {
|
|
2919
|
+
openHtmlInNewWindow(code.value, cloakBox.checked);
|
|
2920
|
+
};
|
|
2921
|
+
|
|
2210
2922
|
clearBtn.onclick = () => {
|
|
2211
2923
|
code.value = "";
|
|
2212
2924
|
};
|
|
@@ -2232,6 +2944,7 @@ ${icons}
|
|
|
2232
2944
|
<textarea class="runner-textarea" placeholder="Paste your bookmarklet javascript: here..."></textarea>
|
|
2233
2945
|
<div class="runner-controls">
|
|
2234
2946
|
<button class="run-btn">Run Bookmarklet</button>
|
|
2947
|
+
<button class="clear-btn">Clear</button>
|
|
2235
2948
|
</div>
|
|
2236
2949
|
</div>
|
|
2237
2950
|
</div>
|
|
@@ -2239,6 +2952,11 @@ ${icons}
|
|
|
2239
2952
|
|
|
2240
2953
|
const textarea = tab.panel.querySelector(".runner-textarea");
|
|
2241
2954
|
const runBtn = tab.panel.querySelector(".run-btn");
|
|
2955
|
+
const clearBtn = tab.panel.querySelector(".clear-btn");
|
|
2956
|
+
|
|
2957
|
+
clearBtn.addEventListener("click", () => {
|
|
2958
|
+
textarea.value = "";
|
|
2959
|
+
});
|
|
2242
2960
|
|
|
2243
2961
|
runBtn.addEventListener("click", () => {
|
|
2244
2962
|
let code = textarea.value.trim();
|