dolphin-client 1.0.6 → 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/.vscode/dolphin-tags.json +586 -0
- package/README.md +24 -0
- package/bin/cli.cjs +60 -0
- package/dist/dolphin-client.js +143 -4
- package/dist/dolphin-client.min.js +18 -18
- package/dist/index.cjs +143 -4
- package/dist/index.js +143 -4
- package/package.json +10 -3
- package/scripts/postinstall.js +57 -0
package/dist/index.cjs
CHANGED
|
@@ -41,7 +41,7 @@ var APIHandler = class {
|
|
|
41
41
|
target.patch = (pathOrBody, bodyOrOptions, options) => typeof pathOrBody === "string" ? this.request("PATCH", pathOrBody, bodyOrOptions, options) : this.request("PATCH", joined, pathOrBody, bodyOrOptions);
|
|
42
42
|
target.del = (pathOrOptions, options) => typeof pathOrOptions === "string" ? this.request("DELETE", pathOrOptions, null, options) : this.request("DELETE", joined, null, pathOrOptions);
|
|
43
43
|
target.request = (method, subPath, body, options) => {
|
|
44
|
-
const finalPath = subPath ? `${joined}/${subPath.startsWith("/") ? subPath.slice(1) : subPath}` : joined;
|
|
44
|
+
const finalPath = subPath ? joined ? `${joined}/${subPath.startsWith("/") ? subPath.slice(1) : subPath}` : subPath : joined;
|
|
45
45
|
return this.request(method, finalPath, body, options);
|
|
46
46
|
};
|
|
47
47
|
target.requestDirect = (method, path, body, options) => {
|
|
@@ -225,8 +225,9 @@ var APIHandler = class {
|
|
|
225
225
|
const _isRetry = options._isRetry === true;
|
|
226
226
|
let finalMethod = method.toUpperCase();
|
|
227
227
|
let finalBody = body;
|
|
228
|
+
const hasBody = !["GET", "HEAD"].includes(finalMethod);
|
|
228
229
|
const headers = {
|
|
229
|
-
"Content-Type": "application/json",
|
|
230
|
+
...hasBody ? { "Content-Type": "application/json" } : {},
|
|
230
231
|
...options.headers || {}
|
|
231
232
|
};
|
|
232
233
|
if (["PUT", "PATCH", "DELETE"].includes(finalMethod)) {
|
|
@@ -1376,6 +1377,86 @@ function attachDOMBinding(clientProto) {
|
|
|
1376
1377
|
console.error("%cFailed Expression:", "color: #3b82f6; font-style: italic;", expression);
|
|
1377
1378
|
}
|
|
1378
1379
|
};
|
|
1380
|
+
clientProto._showAlert = function(id, duration) {
|
|
1381
|
+
if (typeof document === "undefined") return;
|
|
1382
|
+
const el = document.getElementById(id) || document.querySelector(`[id="${id}"]`);
|
|
1383
|
+
if (!el) return;
|
|
1384
|
+
el.removeAttribute("hidden");
|
|
1385
|
+
el.style.display = "";
|
|
1386
|
+
if (duration && duration > 0) {
|
|
1387
|
+
setTimeout(() => {
|
|
1388
|
+
el.setAttribute("hidden", "");
|
|
1389
|
+
}, duration);
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
clientProto._showToast = function(message, type = "success") {
|
|
1393
|
+
if (typeof document === "undefined") return;
|
|
1394
|
+
const colors = {
|
|
1395
|
+
success: { bg: "rgba(16,185,129,0.15)", border: "#10b981", icon: "\u2705" },
|
|
1396
|
+
error: { bg: "rgba(239,68,68,0.15)", border: "#ef4444", icon: "\u274C" },
|
|
1397
|
+
info: { bg: "rgba(59,130,246,0.15)", border: "#3b82f6", icon: "\u2139\uFE0F" }
|
|
1398
|
+
};
|
|
1399
|
+
const c = colors[type] || colors.success;
|
|
1400
|
+
const toast = document.createElement("div");
|
|
1401
|
+
toast.setAttribute("data-dolphin-toast", "");
|
|
1402
|
+
const existing = document.querySelectorAll("[data-dolphin-toast]");
|
|
1403
|
+
const offset = existing.length * 72;
|
|
1404
|
+
toast.style.cssText = [
|
|
1405
|
+
"position:fixed",
|
|
1406
|
+
`bottom:${24 + offset}px`,
|
|
1407
|
+
"right:24px",
|
|
1408
|
+
"z-index:2147483647",
|
|
1409
|
+
`background:${c.bg}`,
|
|
1410
|
+
`border:1px solid ${c.border}`,
|
|
1411
|
+
"color:#fff",
|
|
1412
|
+
"padding:14px 20px",
|
|
1413
|
+
"border-radius:14px",
|
|
1414
|
+
"font-size:14px",
|
|
1415
|
+
"font-weight:600",
|
|
1416
|
+
"font-family:system-ui,sans-serif",
|
|
1417
|
+
"box-shadow:0 8px 32px rgba(0,0,0,0.4)",
|
|
1418
|
+
"display:flex",
|
|
1419
|
+
"align-items:center",
|
|
1420
|
+
"gap:10px",
|
|
1421
|
+
"max-width:380px",
|
|
1422
|
+
"word-break:break-word",
|
|
1423
|
+
"transform:translateY(80px)",
|
|
1424
|
+
"opacity:0",
|
|
1425
|
+
"transition:transform 0.35s cubic-bezier(0.34,1.56,0.64,1),opacity 0.3s ease",
|
|
1426
|
+
"pointer-events:auto",
|
|
1427
|
+
"backdrop-filter:blur(12px)"
|
|
1428
|
+
].join(";");
|
|
1429
|
+
toast.innerHTML = `<span style="font-size:18px">${c.icon}</span><span>${message}</span>`;
|
|
1430
|
+
document.body.appendChild(toast);
|
|
1431
|
+
const restack = () => {
|
|
1432
|
+
const remaining = document.querySelectorAll("[data-dolphin-toast]");
|
|
1433
|
+
remaining.forEach((el, i) => {
|
|
1434
|
+
el.style.bottom = `${24 + i * 72}px`;
|
|
1435
|
+
});
|
|
1436
|
+
};
|
|
1437
|
+
const raf = typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : (cb) => setTimeout(cb, 0);
|
|
1438
|
+
raf(() => raf(() => {
|
|
1439
|
+
toast.style.transform = "translateY(0)";
|
|
1440
|
+
toast.style.opacity = "1";
|
|
1441
|
+
}));
|
|
1442
|
+
const removeToast = () => {
|
|
1443
|
+
clearTimeout(toast._removeTimer);
|
|
1444
|
+
if (toast.parentNode) {
|
|
1445
|
+
toast.parentNode.removeChild(toast);
|
|
1446
|
+
restack();
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
const hideTimer = setTimeout(() => {
|
|
1450
|
+
toast.style.transform = "translateY(80px)";
|
|
1451
|
+
toast.style.opacity = "0";
|
|
1452
|
+
toast._removeTimer = setTimeout(removeToast, 400);
|
|
1453
|
+
}, 3500);
|
|
1454
|
+
toast._hideTimer = hideTimer;
|
|
1455
|
+
toast.addEventListener("click", () => {
|
|
1456
|
+
clearTimeout(toast._hideTimer);
|
|
1457
|
+
removeToast();
|
|
1458
|
+
}, { once: true });
|
|
1459
|
+
};
|
|
1379
1460
|
clientProto._initDOMBinding = function() {
|
|
1380
1461
|
if (this._domInitialized) return;
|
|
1381
1462
|
this._domInitialized = true;
|
|
@@ -1474,6 +1555,11 @@ function attachDOMBinding(clientProto) {
|
|
|
1474
1555
|
resolvedTopic = resolvedTopic.replace(new RegExp(`\\{\\{${escapedK}\\}\\}`, "g"), parentCtx[k] !== void 0 && parentCtx[k] !== null ? parentCtx[k] : "");
|
|
1475
1556
|
}
|
|
1476
1557
|
this.publish(resolvedTopic, data);
|
|
1558
|
+
const rtSuccessId = e.target.getAttribute("data-rt-api-success");
|
|
1559
|
+
if (rtSuccessId) {
|
|
1560
|
+
const rtSuccessEl = document.getElementById(rtSuccessId);
|
|
1561
|
+
if (rtSuccessEl) this._showToast(rtSuccessEl.textContent || rtSuccessEl.innerText || "", "success");
|
|
1562
|
+
}
|
|
1477
1563
|
} else if (apiTarget) {
|
|
1478
1564
|
let resolvedTarget = apiTarget;
|
|
1479
1565
|
for (const k in parentCtx) {
|
|
@@ -1490,16 +1576,40 @@ function attachDOMBinding(clientProto) {
|
|
|
1490
1576
|
const result = await this.api.request(method, path, data);
|
|
1491
1577
|
const resultBind = e.target.getAttribute("data-api-result");
|
|
1492
1578
|
if (resultBind) this._updateDOM(resultBind, result);
|
|
1579
|
+
const successToast = e.target.getAttribute("data-api-toast");
|
|
1580
|
+
if (successToast) this._showToast(successToast, "success");
|
|
1581
|
+
const successElId = e.target.getAttribute("data-rt-api-success");
|
|
1582
|
+
if (successElId) {
|
|
1583
|
+
const duration = parseInt(e.target.getAttribute("data-rt-alert-duration") || "0", 10);
|
|
1584
|
+
this._showAlert(successElId, duration);
|
|
1585
|
+
const errElId = e.target.getAttribute("data-rt-api-error");
|
|
1586
|
+
if (errElId) {
|
|
1587
|
+
const errEl = document.getElementById(errElId);
|
|
1588
|
+
if (errEl) errEl.setAttribute("hidden", "");
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1493
1591
|
const redirect = e.target.getAttribute("data-api-redirect");
|
|
1494
1592
|
if (redirect) window.location.href = redirect;
|
|
1495
1593
|
if (e.target.hasAttribute("data-api-reload")) window.location.reload();
|
|
1496
1594
|
} catch (err) {
|
|
1497
1595
|
console.error("[Dolphin] API Submit Error:", err);
|
|
1596
|
+
const errorToast = e.target.getAttribute("data-api-error-toast");
|
|
1597
|
+
if (errorToast) this._showToast(errorToast, "error");
|
|
1598
|
+
const errorElId = e.target.getAttribute("data-rt-api-error");
|
|
1599
|
+
if (errorElId) {
|
|
1600
|
+
const duration = parseInt(e.target.getAttribute("data-rt-alert-duration") || "0", 10);
|
|
1601
|
+
this._showAlert(errorElId, duration);
|
|
1602
|
+
const sucElId = e.target.getAttribute("data-rt-api-success");
|
|
1603
|
+
if (sucElId) {
|
|
1604
|
+
const sucEl = document.getElementById(sucElId);
|
|
1605
|
+
if (sucEl) sucEl.setAttribute("hidden", "");
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1498
1608
|
}
|
|
1499
1609
|
}
|
|
1500
1610
|
}
|
|
1501
1611
|
});
|
|
1502
|
-
const INTERACTION_EVENTS = ["click", "change", "
|
|
1612
|
+
const INTERACTION_EVENTS = ["click", "change", "input", "keydown", "keyup", "dblclick", "focus", "blur", "mouseenter", "mouseleave"];
|
|
1503
1613
|
INTERACTION_EVENTS.forEach((evtName) => {
|
|
1504
1614
|
this.addDomListener(document, evtName, async (e) => {
|
|
1505
1615
|
if (!e.target || !e.target.closest) return;
|
|
@@ -1530,7 +1640,12 @@ function attachDOMBinding(clientProto) {
|
|
|
1530
1640
|
const apiTarget = apiBtn.getAttribute(`data-api-${evtName}`);
|
|
1531
1641
|
const actionData = apiBtn.getAttribute("data-api-payload");
|
|
1532
1642
|
const parentCtx = this.getClosestContext(apiBtn) || {};
|
|
1533
|
-
|
|
1643
|
+
let resolvedApiTarget = apiTarget;
|
|
1644
|
+
for (const k in parentCtx) {
|
|
1645
|
+
const escapedK = escapeRegExp(k);
|
|
1646
|
+
resolvedApiTarget = resolvedApiTarget.replace(new RegExp(`\\{\\{${escapedK}\\}\\}`, "g"), parentCtx[k] !== void 0 && parentCtx[k] !== null ? parentCtx[k] : "");
|
|
1647
|
+
}
|
|
1648
|
+
const parts = resolvedApiTarget.trim().split(" ");
|
|
1534
1649
|
const method = parts.length > 1 ? parts[0].toUpperCase() : "POST";
|
|
1535
1650
|
const path = parts.length > 1 ? parts[1] : parts[0];
|
|
1536
1651
|
let payload = null;
|
|
@@ -1550,11 +1665,35 @@ function attachDOMBinding(clientProto) {
|
|
|
1550
1665
|
const result = await this.api.request(method, path, payload);
|
|
1551
1666
|
const resultBind = apiBtn.getAttribute("data-api-result");
|
|
1552
1667
|
if (resultBind) this._updateDOM(resultBind, result);
|
|
1668
|
+
const successToast = apiBtn.getAttribute("data-api-toast");
|
|
1669
|
+
if (successToast) this._showToast(successToast, "success");
|
|
1670
|
+
const successElId = apiBtn.getAttribute("data-rt-api-success");
|
|
1671
|
+
if (successElId) {
|
|
1672
|
+
const duration = parseInt(apiBtn.getAttribute("data-rt-alert-duration") || "0", 10);
|
|
1673
|
+
this._showAlert(successElId, duration);
|
|
1674
|
+
const errElId = apiBtn.getAttribute("data-rt-api-error");
|
|
1675
|
+
if (errElId) {
|
|
1676
|
+
const errEl = document.getElementById(errElId);
|
|
1677
|
+
if (errEl) errEl.setAttribute("hidden", "");
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1553
1680
|
const redirect = apiBtn.getAttribute("data-api-redirect");
|
|
1554
1681
|
if (redirect) window.location.href = redirect;
|
|
1555
1682
|
if (apiBtn.hasAttribute("data-api-reload")) window.location.reload();
|
|
1556
1683
|
} catch (err) {
|
|
1557
1684
|
console.error(`[Dolphin] API ${evtName} Error:`, err);
|
|
1685
|
+
const errorToast = apiBtn.getAttribute("data-api-error-toast");
|
|
1686
|
+
if (errorToast) this._showToast(errorToast, "error");
|
|
1687
|
+
const errorElId = apiBtn.getAttribute("data-rt-api-error");
|
|
1688
|
+
if (errorElId) {
|
|
1689
|
+
const duration = parseInt(apiBtn.getAttribute("data-rt-alert-duration") || "0", 10);
|
|
1690
|
+
this._showAlert(errorElId, duration);
|
|
1691
|
+
const sucElId = apiBtn.getAttribute("data-rt-api-success");
|
|
1692
|
+
if (sucElId) {
|
|
1693
|
+
const sucEl = document.getElementById(sucElId);
|
|
1694
|
+
if (sucEl) sucEl.setAttribute("hidden", "");
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1558
1697
|
}
|
|
1559
1698
|
}
|
|
1560
1699
|
const storeActionBtn = e.target.closest(`[data-store-${evtName}]`);
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ var APIHandler = class {
|
|
|
16
16
|
target.patch = (pathOrBody, bodyOrOptions, options) => typeof pathOrBody === "string" ? this.request("PATCH", pathOrBody, bodyOrOptions, options) : this.request("PATCH", joined, pathOrBody, bodyOrOptions);
|
|
17
17
|
target.del = (pathOrOptions, options) => typeof pathOrOptions === "string" ? this.request("DELETE", pathOrOptions, null, options) : this.request("DELETE", joined, null, pathOrOptions);
|
|
18
18
|
target.request = (method, subPath, body, options) => {
|
|
19
|
-
const finalPath = subPath ? `${joined}/${subPath.startsWith("/") ? subPath.slice(1) : subPath}` : joined;
|
|
19
|
+
const finalPath = subPath ? joined ? `${joined}/${subPath.startsWith("/") ? subPath.slice(1) : subPath}` : subPath : joined;
|
|
20
20
|
return this.request(method, finalPath, body, options);
|
|
21
21
|
};
|
|
22
22
|
target.requestDirect = (method, path, body, options) => {
|
|
@@ -200,8 +200,9 @@ var APIHandler = class {
|
|
|
200
200
|
const _isRetry = options._isRetry === true;
|
|
201
201
|
let finalMethod = method.toUpperCase();
|
|
202
202
|
let finalBody = body;
|
|
203
|
+
const hasBody = !["GET", "HEAD"].includes(finalMethod);
|
|
203
204
|
const headers = {
|
|
204
|
-
"Content-Type": "application/json",
|
|
205
|
+
...hasBody ? { "Content-Type": "application/json" } : {},
|
|
205
206
|
...options.headers || {}
|
|
206
207
|
};
|
|
207
208
|
if (["PUT", "PATCH", "DELETE"].includes(finalMethod)) {
|
|
@@ -1351,6 +1352,86 @@ function attachDOMBinding(clientProto) {
|
|
|
1351
1352
|
console.error("%cFailed Expression:", "color: #3b82f6; font-style: italic;", expression);
|
|
1352
1353
|
}
|
|
1353
1354
|
};
|
|
1355
|
+
clientProto._showAlert = function(id, duration) {
|
|
1356
|
+
if (typeof document === "undefined") return;
|
|
1357
|
+
const el = document.getElementById(id) || document.querySelector(`[id="${id}"]`);
|
|
1358
|
+
if (!el) return;
|
|
1359
|
+
el.removeAttribute("hidden");
|
|
1360
|
+
el.style.display = "";
|
|
1361
|
+
if (duration && duration > 0) {
|
|
1362
|
+
setTimeout(() => {
|
|
1363
|
+
el.setAttribute("hidden", "");
|
|
1364
|
+
}, duration);
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
clientProto._showToast = function(message, type = "success") {
|
|
1368
|
+
if (typeof document === "undefined") return;
|
|
1369
|
+
const colors = {
|
|
1370
|
+
success: { bg: "rgba(16,185,129,0.15)", border: "#10b981", icon: "\u2705" },
|
|
1371
|
+
error: { bg: "rgba(239,68,68,0.15)", border: "#ef4444", icon: "\u274C" },
|
|
1372
|
+
info: { bg: "rgba(59,130,246,0.15)", border: "#3b82f6", icon: "\u2139\uFE0F" }
|
|
1373
|
+
};
|
|
1374
|
+
const c = colors[type] || colors.success;
|
|
1375
|
+
const toast = document.createElement("div");
|
|
1376
|
+
toast.setAttribute("data-dolphin-toast", "");
|
|
1377
|
+
const existing = document.querySelectorAll("[data-dolphin-toast]");
|
|
1378
|
+
const offset = existing.length * 72;
|
|
1379
|
+
toast.style.cssText = [
|
|
1380
|
+
"position:fixed",
|
|
1381
|
+
`bottom:${24 + offset}px`,
|
|
1382
|
+
"right:24px",
|
|
1383
|
+
"z-index:2147483647",
|
|
1384
|
+
`background:${c.bg}`,
|
|
1385
|
+
`border:1px solid ${c.border}`,
|
|
1386
|
+
"color:#fff",
|
|
1387
|
+
"padding:14px 20px",
|
|
1388
|
+
"border-radius:14px",
|
|
1389
|
+
"font-size:14px",
|
|
1390
|
+
"font-weight:600",
|
|
1391
|
+
"font-family:system-ui,sans-serif",
|
|
1392
|
+
"box-shadow:0 8px 32px rgba(0,0,0,0.4)",
|
|
1393
|
+
"display:flex",
|
|
1394
|
+
"align-items:center",
|
|
1395
|
+
"gap:10px",
|
|
1396
|
+
"max-width:380px",
|
|
1397
|
+
"word-break:break-word",
|
|
1398
|
+
"transform:translateY(80px)",
|
|
1399
|
+
"opacity:0",
|
|
1400
|
+
"transition:transform 0.35s cubic-bezier(0.34,1.56,0.64,1),opacity 0.3s ease",
|
|
1401
|
+
"pointer-events:auto",
|
|
1402
|
+
"backdrop-filter:blur(12px)"
|
|
1403
|
+
].join(";");
|
|
1404
|
+
toast.innerHTML = `<span style="font-size:18px">${c.icon}</span><span>${message}</span>`;
|
|
1405
|
+
document.body.appendChild(toast);
|
|
1406
|
+
const restack = () => {
|
|
1407
|
+
const remaining = document.querySelectorAll("[data-dolphin-toast]");
|
|
1408
|
+
remaining.forEach((el, i) => {
|
|
1409
|
+
el.style.bottom = `${24 + i * 72}px`;
|
|
1410
|
+
});
|
|
1411
|
+
};
|
|
1412
|
+
const raf = typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : (cb) => setTimeout(cb, 0);
|
|
1413
|
+
raf(() => raf(() => {
|
|
1414
|
+
toast.style.transform = "translateY(0)";
|
|
1415
|
+
toast.style.opacity = "1";
|
|
1416
|
+
}));
|
|
1417
|
+
const removeToast = () => {
|
|
1418
|
+
clearTimeout(toast._removeTimer);
|
|
1419
|
+
if (toast.parentNode) {
|
|
1420
|
+
toast.parentNode.removeChild(toast);
|
|
1421
|
+
restack();
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
const hideTimer = setTimeout(() => {
|
|
1425
|
+
toast.style.transform = "translateY(80px)";
|
|
1426
|
+
toast.style.opacity = "0";
|
|
1427
|
+
toast._removeTimer = setTimeout(removeToast, 400);
|
|
1428
|
+
}, 3500);
|
|
1429
|
+
toast._hideTimer = hideTimer;
|
|
1430
|
+
toast.addEventListener("click", () => {
|
|
1431
|
+
clearTimeout(toast._hideTimer);
|
|
1432
|
+
removeToast();
|
|
1433
|
+
}, { once: true });
|
|
1434
|
+
};
|
|
1354
1435
|
clientProto._initDOMBinding = function() {
|
|
1355
1436
|
if (this._domInitialized) return;
|
|
1356
1437
|
this._domInitialized = true;
|
|
@@ -1449,6 +1530,11 @@ function attachDOMBinding(clientProto) {
|
|
|
1449
1530
|
resolvedTopic = resolvedTopic.replace(new RegExp(`\\{\\{${escapedK}\\}\\}`, "g"), parentCtx[k] !== void 0 && parentCtx[k] !== null ? parentCtx[k] : "");
|
|
1450
1531
|
}
|
|
1451
1532
|
this.publish(resolvedTopic, data);
|
|
1533
|
+
const rtSuccessId = e.target.getAttribute("data-rt-api-success");
|
|
1534
|
+
if (rtSuccessId) {
|
|
1535
|
+
const rtSuccessEl = document.getElementById(rtSuccessId);
|
|
1536
|
+
if (rtSuccessEl) this._showToast(rtSuccessEl.textContent || rtSuccessEl.innerText || "", "success");
|
|
1537
|
+
}
|
|
1452
1538
|
} else if (apiTarget) {
|
|
1453
1539
|
let resolvedTarget = apiTarget;
|
|
1454
1540
|
for (const k in parentCtx) {
|
|
@@ -1465,16 +1551,40 @@ function attachDOMBinding(clientProto) {
|
|
|
1465
1551
|
const result = await this.api.request(method, path, data);
|
|
1466
1552
|
const resultBind = e.target.getAttribute("data-api-result");
|
|
1467
1553
|
if (resultBind) this._updateDOM(resultBind, result);
|
|
1554
|
+
const successToast = e.target.getAttribute("data-api-toast");
|
|
1555
|
+
if (successToast) this._showToast(successToast, "success");
|
|
1556
|
+
const successElId = e.target.getAttribute("data-rt-api-success");
|
|
1557
|
+
if (successElId) {
|
|
1558
|
+
const duration = parseInt(e.target.getAttribute("data-rt-alert-duration") || "0", 10);
|
|
1559
|
+
this._showAlert(successElId, duration);
|
|
1560
|
+
const errElId = e.target.getAttribute("data-rt-api-error");
|
|
1561
|
+
if (errElId) {
|
|
1562
|
+
const errEl = document.getElementById(errElId);
|
|
1563
|
+
if (errEl) errEl.setAttribute("hidden", "");
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1468
1566
|
const redirect = e.target.getAttribute("data-api-redirect");
|
|
1469
1567
|
if (redirect) window.location.href = redirect;
|
|
1470
1568
|
if (e.target.hasAttribute("data-api-reload")) window.location.reload();
|
|
1471
1569
|
} catch (err) {
|
|
1472
1570
|
console.error("[Dolphin] API Submit Error:", err);
|
|
1571
|
+
const errorToast = e.target.getAttribute("data-api-error-toast");
|
|
1572
|
+
if (errorToast) this._showToast(errorToast, "error");
|
|
1573
|
+
const errorElId = e.target.getAttribute("data-rt-api-error");
|
|
1574
|
+
if (errorElId) {
|
|
1575
|
+
const duration = parseInt(e.target.getAttribute("data-rt-alert-duration") || "0", 10);
|
|
1576
|
+
this._showAlert(errorElId, duration);
|
|
1577
|
+
const sucElId = e.target.getAttribute("data-rt-api-success");
|
|
1578
|
+
if (sucElId) {
|
|
1579
|
+
const sucEl = document.getElementById(sucElId);
|
|
1580
|
+
if (sucEl) sucEl.setAttribute("hidden", "");
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1473
1583
|
}
|
|
1474
1584
|
}
|
|
1475
1585
|
}
|
|
1476
1586
|
});
|
|
1477
|
-
const INTERACTION_EVENTS = ["click", "change", "
|
|
1587
|
+
const INTERACTION_EVENTS = ["click", "change", "input", "keydown", "keyup", "dblclick", "focus", "blur", "mouseenter", "mouseleave"];
|
|
1478
1588
|
INTERACTION_EVENTS.forEach((evtName) => {
|
|
1479
1589
|
this.addDomListener(document, evtName, async (e) => {
|
|
1480
1590
|
if (!e.target || !e.target.closest) return;
|
|
@@ -1505,7 +1615,12 @@ function attachDOMBinding(clientProto) {
|
|
|
1505
1615
|
const apiTarget = apiBtn.getAttribute(`data-api-${evtName}`);
|
|
1506
1616
|
const actionData = apiBtn.getAttribute("data-api-payload");
|
|
1507
1617
|
const parentCtx = this.getClosestContext(apiBtn) || {};
|
|
1508
|
-
|
|
1618
|
+
let resolvedApiTarget = apiTarget;
|
|
1619
|
+
for (const k in parentCtx) {
|
|
1620
|
+
const escapedK = escapeRegExp(k);
|
|
1621
|
+
resolvedApiTarget = resolvedApiTarget.replace(new RegExp(`\\{\\{${escapedK}\\}\\}`, "g"), parentCtx[k] !== void 0 && parentCtx[k] !== null ? parentCtx[k] : "");
|
|
1622
|
+
}
|
|
1623
|
+
const parts = resolvedApiTarget.trim().split(" ");
|
|
1509
1624
|
const method = parts.length > 1 ? parts[0].toUpperCase() : "POST";
|
|
1510
1625
|
const path = parts.length > 1 ? parts[1] : parts[0];
|
|
1511
1626
|
let payload = null;
|
|
@@ -1525,11 +1640,35 @@ function attachDOMBinding(clientProto) {
|
|
|
1525
1640
|
const result = await this.api.request(method, path, payload);
|
|
1526
1641
|
const resultBind = apiBtn.getAttribute("data-api-result");
|
|
1527
1642
|
if (resultBind) this._updateDOM(resultBind, result);
|
|
1643
|
+
const successToast = apiBtn.getAttribute("data-api-toast");
|
|
1644
|
+
if (successToast) this._showToast(successToast, "success");
|
|
1645
|
+
const successElId = apiBtn.getAttribute("data-rt-api-success");
|
|
1646
|
+
if (successElId) {
|
|
1647
|
+
const duration = parseInt(apiBtn.getAttribute("data-rt-alert-duration") || "0", 10);
|
|
1648
|
+
this._showAlert(successElId, duration);
|
|
1649
|
+
const errElId = apiBtn.getAttribute("data-rt-api-error");
|
|
1650
|
+
if (errElId) {
|
|
1651
|
+
const errEl = document.getElementById(errElId);
|
|
1652
|
+
if (errEl) errEl.setAttribute("hidden", "");
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1528
1655
|
const redirect = apiBtn.getAttribute("data-api-redirect");
|
|
1529
1656
|
if (redirect) window.location.href = redirect;
|
|
1530
1657
|
if (apiBtn.hasAttribute("data-api-reload")) window.location.reload();
|
|
1531
1658
|
} catch (err) {
|
|
1532
1659
|
console.error(`[Dolphin] API ${evtName} Error:`, err);
|
|
1660
|
+
const errorToast = apiBtn.getAttribute("data-api-error-toast");
|
|
1661
|
+
if (errorToast) this._showToast(errorToast, "error");
|
|
1662
|
+
const errorElId = apiBtn.getAttribute("data-rt-api-error");
|
|
1663
|
+
if (errorElId) {
|
|
1664
|
+
const duration = parseInt(apiBtn.getAttribute("data-rt-alert-duration") || "0", 10);
|
|
1665
|
+
this._showAlert(errorElId, duration);
|
|
1666
|
+
const sucElId = apiBtn.getAttribute("data-rt-api-success");
|
|
1667
|
+
if (sucElId) {
|
|
1668
|
+
const sucEl = document.getElementById(sucElId);
|
|
1669
|
+
if (sucEl) sucEl.setAttribute("hidden", "");
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1533
1672
|
}
|
|
1534
1673
|
}
|
|
1535
1674
|
const storeActionBtn = e.target.closest(`[data-store-${evtName}]`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dolphin-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "HTML is back! Hookless, framework-agnostic real-time reactive DOM-binding client for Dolphin Server with WebSockets, WebRTC signaling, and offline REST API fallbacks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -13,11 +13,17 @@
|
|
|
13
13
|
"require": "./dist/index.cjs"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"dolphin-client": "./bin/cli.cjs"
|
|
18
|
+
},
|
|
16
19
|
"files": [
|
|
17
20
|
"dist",
|
|
18
21
|
"README.md",
|
|
19
22
|
"LICENSE",
|
|
20
|
-
"fulltutorial.md"
|
|
23
|
+
"fulltutorial.md",
|
|
24
|
+
"scripts",
|
|
25
|
+
".vscode",
|
|
26
|
+
"bin"
|
|
21
27
|
],
|
|
22
28
|
"scripts": {
|
|
23
29
|
"build": "npm run build:iife && npm run build:min && npm run build:esm && npm run build:cjs && tsc",
|
|
@@ -25,7 +31,8 @@
|
|
|
25
31
|
"build:min": "esbuild ./src/index.ts --bundle --minify --outfile=dist/dolphin-client.min.js --format=iife --global-name=DolphinModule",
|
|
26
32
|
"build:esm": "esbuild ./src/index.ts --bundle --outfile=dist/index.js --format=esm",
|
|
27
33
|
"build:cjs": "esbuild ./src/index.ts --bundle --outfile=dist/index.cjs --format=cjs",
|
|
28
|
-
"test": "jest"
|
|
34
|
+
"test": "jest",
|
|
35
|
+
"postinstall": "node scripts/postinstall.js"
|
|
29
36
|
},
|
|
30
37
|
"jest": {
|
|
31
38
|
"preset": "ts-jest",
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
// This script runs automatically after npm install dolphin-client.
|
|
5
|
+
// It sets up the VS Code Custom HTML Data configuration in the host project's .vscode folder
|
|
6
|
+
// so autocomplete for Dolphin Client attributes works instantly out-of-the-box!
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const currentPath = __dirname;
|
|
10
|
+
// Check if we are inside a node_modules folder (production installation)
|
|
11
|
+
if (currentPath.includes('node_modules')) {
|
|
12
|
+
// Resolve host project root (e.g. node_modules/dolphin-client/scripts -> hostRoot)
|
|
13
|
+
const hostRoot = path.resolve(__dirname, '../../..');
|
|
14
|
+
const vscodeDir = path.join(hostRoot, '.vscode');
|
|
15
|
+
|
|
16
|
+
// 1. Ensure the host's .vscode directory exists
|
|
17
|
+
if (!fs.existsSync(vscodeDir)) {
|
|
18
|
+
fs.mkdirSync(vscodeDir, { recursive: true });
|
|
19
|
+
console.log('[Dolphin Client] Created .vscode directory in project root.');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 2. Copy the dolphin-tags.json file to the host's .vscode folder
|
|
23
|
+
const sourceTags = path.resolve(__dirname, '../.vscode/dolphin-tags.json');
|
|
24
|
+
const destTags = path.join(vscodeDir, 'dolphin-tags.json');
|
|
25
|
+
if (fs.existsSync(sourceTags)) {
|
|
26
|
+
fs.copyFileSync(sourceTags, destTags);
|
|
27
|
+
console.log('[Dolphin Client] Copied HTML custom data configuration to .vscode/dolphin-tags.json');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 3. Update the host's settings.json to include the custom data file path
|
|
31
|
+
const settingsPath = path.join(vscodeDir, 'settings.json');
|
|
32
|
+
let settings = {};
|
|
33
|
+
if (fs.existsSync(settingsPath)) {
|
|
34
|
+
try {
|
|
35
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
36
|
+
} catch (e) {
|
|
37
|
+
settings = {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!settings['html.customData']) {
|
|
42
|
+
settings['html.customData'] = [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const relativePath = '.vscode/dolphin-tags.json';
|
|
46
|
+
if (!settings['html.customData'].includes(relativePath)) {
|
|
47
|
+
settings['html.customData'].push(relativePath);
|
|
48
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
49
|
+
console.log('[Dolphin Client] Registered dolphin-tags.json in .vscode/settings.json');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log('\x1b[36m%s\x1b[0m', '🐬 [Dolphin Client] VS Code Autocomplete successfully configured!');
|
|
53
|
+
console.log('\x1b[33m%s\x1b[0m', '👉 Note: Please run "Developer: Reload Window" in VS Code to activate suggestions.');
|
|
54
|
+
}
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.warn('[Dolphin Client] Failed to auto-configure VS Code autocomplete:', err.message);
|
|
57
|
+
}
|