dolphin-client 1.0.9 → 1.1.2
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 +28 -0
- package/.vscode/settings.json +5 -0
- package/dist/dolphin-client.js +182 -33
- package/dist/dolphin-client.min.js +19 -19
- package/dist/index.cjs +182 -33
- package/dist/index.js +182 -33
- package/package.json +1 -1
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)) {
|
|
@@ -971,6 +972,82 @@ function attachDOMBinding(clientProto) {
|
|
|
971
972
|
function escapeRegExp(str) {
|
|
972
973
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
973
974
|
}
|
|
975
|
+
function evaluateExpression(expr, ctx) {
|
|
976
|
+
if (!ctx || typeof ctx !== "object") return void 0;
|
|
977
|
+
try {
|
|
978
|
+
const safeCtx = new Proxy(ctx, {
|
|
979
|
+
has(target, prop) {
|
|
980
|
+
return true;
|
|
981
|
+
},
|
|
982
|
+
get(target, prop) {
|
|
983
|
+
if (typeof prop === "string") {
|
|
984
|
+
if (prop in target) return target[prop];
|
|
985
|
+
if (typeof globalThis !== "undefined" && prop in globalThis) return globalThis[prop];
|
|
986
|
+
if (typeof window !== "undefined" && prop in window) return window[prop];
|
|
987
|
+
}
|
|
988
|
+
return void 0;
|
|
989
|
+
}
|
|
990
|
+
});
|
|
991
|
+
const fn = new Function("ctx", `with(ctx) { return (${expr}); }`);
|
|
992
|
+
return fn(safeCtx);
|
|
993
|
+
} catch {
|
|
994
|
+
return ctx[expr];
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
function splitByUnquotedChar(str, char) {
|
|
998
|
+
const parts = [];
|
|
999
|
+
let current = "";
|
|
1000
|
+
let inSingleQuote = false;
|
|
1001
|
+
let inDoubleQuote = false;
|
|
1002
|
+
let inBacktick = false;
|
|
1003
|
+
let depth = 0;
|
|
1004
|
+
for (let i = 0; i < str.length; i++) {
|
|
1005
|
+
const c = str[i];
|
|
1006
|
+
if (c === "'" && !inDoubleQuote && !inBacktick) {
|
|
1007
|
+
inSingleQuote = !inSingleQuote;
|
|
1008
|
+
} else if (c === '"' && !inSingleQuote && !inBacktick) {
|
|
1009
|
+
inDoubleQuote = !inDoubleQuote;
|
|
1010
|
+
} else if (c === "`" && !inSingleQuote && !inDoubleQuote) {
|
|
1011
|
+
inBacktick = !inBacktick;
|
|
1012
|
+
} else if (c === "(" || c === "[" || c === "{") {
|
|
1013
|
+
if (!inSingleQuote && !inDoubleQuote && !inBacktick) depth++;
|
|
1014
|
+
} else if (c === ")" || c === "]" || c === "}") {
|
|
1015
|
+
if (!inSingleQuote && !inDoubleQuote && !inBacktick) depth--;
|
|
1016
|
+
}
|
|
1017
|
+
if (c === char && !inSingleQuote && !inDoubleQuote && !inBacktick && depth === 0) {
|
|
1018
|
+
parts.push(current);
|
|
1019
|
+
current = "";
|
|
1020
|
+
} else {
|
|
1021
|
+
current += c;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
parts.push(current);
|
|
1025
|
+
return parts;
|
|
1026
|
+
}
|
|
1027
|
+
function splitFirstUnquotedColon(str) {
|
|
1028
|
+
let inSingleQuote = false;
|
|
1029
|
+
let inDoubleQuote = false;
|
|
1030
|
+
let inBacktick = false;
|
|
1031
|
+
let depth = 0;
|
|
1032
|
+
for (let i = 0; i < str.length; i++) {
|
|
1033
|
+
const c = str[i];
|
|
1034
|
+
if (c === "'" && !inDoubleQuote && !inBacktick) {
|
|
1035
|
+
inSingleQuote = !inSingleQuote;
|
|
1036
|
+
} else if (c === '"' && !inSingleQuote && !inBacktick) {
|
|
1037
|
+
inDoubleQuote = !inDoubleQuote;
|
|
1038
|
+
} else if (c === "`" && !inSingleQuote && !inDoubleQuote) {
|
|
1039
|
+
inBacktick = !inBacktick;
|
|
1040
|
+
} else if (c === "(" || c === "[" || c === "{") {
|
|
1041
|
+
if (!inSingleQuote && !inDoubleQuote && !inBacktick) depth++;
|
|
1042
|
+
} else if (c === ")" || c === "]" || c === "}") {
|
|
1043
|
+
if (!inSingleQuote && !inDoubleQuote && !inBacktick) depth--;
|
|
1044
|
+
}
|
|
1045
|
+
if (c === ":" && !inSingleQuote && !inDoubleQuote && !inBacktick && depth === 0) {
|
|
1046
|
+
return [str.slice(0, i), str.slice(i + 1)];
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
974
1051
|
function resolveTemplate(el) {
|
|
975
1052
|
const template = el.getAttribute("data-rt-template");
|
|
976
1053
|
if (!template) return null;
|
|
@@ -987,9 +1064,26 @@ function attachDOMBinding(clientProto) {
|
|
|
987
1064
|
if (!templateStr.includes("{#if") && !templateStr.includes("{#each")) {
|
|
988
1065
|
let result = templateStr;
|
|
989
1066
|
for (let key in context) {
|
|
990
|
-
const escapedKey = key.replace(/[
|
|
991
|
-
result = result.replace(new RegExp(
|
|
992
|
-
}
|
|
1067
|
+
const escapedKey = key.replace(/[.*+?^$${}()|[\]\\]/g, "\\$&");
|
|
1068
|
+
result = result.replace(new RegExp("\\{\\{" + escapedKey + "\\}}", "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
|
|
1069
|
+
}
|
|
1070
|
+
result = result.replace(/\{\{([\s\S]*?)\}\}/g, (match, expr) => {
|
|
1071
|
+
const trimmed = expr.trim();
|
|
1072
|
+
if (!trimmed) return "";
|
|
1073
|
+
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*(?:\??\.[a-zA-Z_$][a-zA-Z0-9_$]*)+$/.test(trimmed)) {
|
|
1074
|
+
const parts = trimmed.split(/\??\./);
|
|
1075
|
+
let val = context;
|
|
1076
|
+
for (const part of parts) {
|
|
1077
|
+
if (val === void 0 || val === null) {
|
|
1078
|
+
val = void 0;
|
|
1079
|
+
break;
|
|
1080
|
+
}
|
|
1081
|
+
val = val[part];
|
|
1082
|
+
}
|
|
1083
|
+
return val !== void 0 && val !== null ? val : "";
|
|
1084
|
+
}
|
|
1085
|
+
return match;
|
|
1086
|
+
});
|
|
993
1087
|
return result;
|
|
994
1088
|
}
|
|
995
1089
|
try {
|
|
@@ -1109,7 +1203,9 @@ function attachDOMBinding(clientProto) {
|
|
|
1109
1203
|
if (typeof document === "undefined") return html;
|
|
1110
1204
|
try {
|
|
1111
1205
|
const parser = new DOMParser();
|
|
1112
|
-
const
|
|
1206
|
+
const hasBodyOrHtml = /<\s*(?:body|html)\b/i.test(html);
|
|
1207
|
+
const parseString = hasBodyOrHtml ? html : `<body>${html}</body>`;
|
|
1208
|
+
const doc = parser.parseFromString(parseString, "text/html");
|
|
1113
1209
|
const body = doc.body;
|
|
1114
1210
|
const sanitizeNode = (el) => {
|
|
1115
1211
|
const tag = el.tagName.toLowerCase();
|
|
@@ -1223,7 +1319,7 @@ function attachDOMBinding(clientProto) {
|
|
|
1223
1319
|
});
|
|
1224
1320
|
}
|
|
1225
1321
|
}
|
|
1226
|
-
clientProto.setStoreState = function(storeName, key, val) {
|
|
1322
|
+
clientProto.setStoreState = function(storeName, key, val, originEl) {
|
|
1227
1323
|
this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
|
|
1228
1324
|
if (!this.uiStores.has(storeName)) {
|
|
1229
1325
|
this.uiStores.set(storeName, {});
|
|
@@ -1236,6 +1332,7 @@ function attachDOMBinding(clientProto) {
|
|
|
1236
1332
|
if (typeof document !== "undefined") {
|
|
1237
1333
|
const readElements = document.querySelectorAll(`[data-store-read="${storeName}.${key}"]`);
|
|
1238
1334
|
readElements.forEach((el) => {
|
|
1335
|
+
if (el === originEl) return;
|
|
1239
1336
|
if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
|
|
1240
1337
|
if (el.type === "checkbox") {
|
|
1241
1338
|
el.checked = !!val;
|
|
@@ -1311,20 +1408,49 @@ function attachDOMBinding(clientProto) {
|
|
|
1311
1408
|
if (key) return ctx[key];
|
|
1312
1409
|
return ctx;
|
|
1313
1410
|
}
|
|
1314
|
-
current = current.parentElement;
|
|
1411
|
+
current = current.parentElement || current.parentNode;
|
|
1315
1412
|
}
|
|
1316
1413
|
return null;
|
|
1317
1414
|
};
|
|
1318
1415
|
clientProto._executeStoreAction = function(expression, element) {
|
|
1319
1416
|
this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
|
|
1417
|
+
const parentCtx = element && typeof this.getClosestContext === "function" ? this.getClosestContext(element) : null;
|
|
1320
1418
|
const context = new Proxy({}, {
|
|
1321
1419
|
has: (target, prop) => {
|
|
1322
1420
|
return true;
|
|
1323
1421
|
},
|
|
1324
1422
|
get: (target, prop) => {
|
|
1325
1423
|
if (typeof prop === "string") {
|
|
1424
|
+
if (prop === "log") {
|
|
1425
|
+
return (arg) => {
|
|
1426
|
+
if (arg === void 0) {
|
|
1427
|
+
const allStores = {};
|
|
1428
|
+
this.uiStores.forEach((val, key) => {
|
|
1429
|
+
allStores[key] = { ...val };
|
|
1430
|
+
});
|
|
1431
|
+
console.log(`%c\u{1F4CA} [Dolphin All UI Stores]:`, "color: #06b6d4; font-weight: bold;", allStores);
|
|
1432
|
+
} else if (arg && typeof arg === "object" && arg.__isStoreProxy__) {
|
|
1433
|
+
const storeName = arg.__storeName__;
|
|
1434
|
+
const store = this.uiStores.get(storeName);
|
|
1435
|
+
console.log(`%c\u{1F4CA} [Dolphin Store: ${storeName}]:`, "color: #06b6d4; font-weight: bold;", store ? { ...store } : {});
|
|
1436
|
+
} else {
|
|
1437
|
+
console.log(`%c\u{1F4CA} [Dolphin Log]:`, "color: #06b6d4; font-weight: bold;", arg);
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
if (parentCtx && parentCtx[prop] !== void 0) {
|
|
1442
|
+
return parentCtx[prop];
|
|
1443
|
+
}
|
|
1444
|
+
if (typeof globalThis !== "undefined" && prop in globalThis) {
|
|
1445
|
+
return globalThis[prop];
|
|
1446
|
+
}
|
|
1447
|
+
if (typeof window !== "undefined" && prop in window) {
|
|
1448
|
+
return window[prop];
|
|
1449
|
+
}
|
|
1326
1450
|
return new Proxy({}, {
|
|
1327
1451
|
get: (subTarget, subProp) => {
|
|
1452
|
+
if (subProp === "__storeName__") return prop;
|
|
1453
|
+
if (subProp === "__isStoreProxy__") return true;
|
|
1328
1454
|
if (typeof subProp === "string") {
|
|
1329
1455
|
return this.getStoreState(prop, subProp);
|
|
1330
1456
|
}
|
|
@@ -1366,7 +1492,7 @@ function attachDOMBinding(clientProto) {
|
|
|
1366
1492
|
const storeName = parts[0];
|
|
1367
1493
|
const key = parts[1];
|
|
1368
1494
|
const val = e.target.type === "checkbox" ? e.target.checked : e.target.value;
|
|
1369
|
-
this.setStoreState(storeName, key, val);
|
|
1495
|
+
this.setStoreState(storeName, key, val, e.target);
|
|
1370
1496
|
}
|
|
1371
1497
|
}
|
|
1372
1498
|
const rules = e.target.getAttribute("data-rt-validate");
|
|
@@ -1726,24 +1852,33 @@ function attachDOMBinding(clientProto) {
|
|
|
1726
1852
|
const processNode = (node) => {
|
|
1727
1853
|
if (node.hasAttribute("data-rt-text")) {
|
|
1728
1854
|
const key = node.getAttribute("data-rt-text");
|
|
1729
|
-
if (key
|
|
1855
|
+
if (key) {
|
|
1856
|
+
const val = evaluateExpression(key, processedPayload);
|
|
1857
|
+
if (val !== void 0 && val !== null) node.textContent = val;
|
|
1858
|
+
}
|
|
1730
1859
|
}
|
|
1731
1860
|
if (node.hasAttribute("data-rt-html")) {
|
|
1732
1861
|
const key = node.getAttribute("data-rt-html");
|
|
1733
|
-
if (key
|
|
1734
|
-
|
|
1862
|
+
if (key) {
|
|
1863
|
+
const val = evaluateExpression(key, processedPayload);
|
|
1864
|
+
if (val !== void 0 && val !== null) {
|
|
1865
|
+
node.innerHTML = sanitizeHTML(val);
|
|
1866
|
+
}
|
|
1735
1867
|
}
|
|
1736
1868
|
}
|
|
1737
1869
|
if (node.hasAttribute("data-rt-attr")) {
|
|
1738
1870
|
const attrStr = node.getAttribute("data-rt-attr");
|
|
1739
1871
|
if (attrStr) {
|
|
1740
|
-
attrStr
|
|
1741
|
-
const
|
|
1742
|
-
if (
|
|
1743
|
-
const attrName =
|
|
1744
|
-
const key =
|
|
1745
|
-
if (attrName && key
|
|
1746
|
-
|
|
1872
|
+
splitByUnquotedChar(attrStr, ",").forEach((b) => {
|
|
1873
|
+
const pair = splitFirstUnquotedColon(b);
|
|
1874
|
+
if (pair) {
|
|
1875
|
+
const attrName = pair[0].trim();
|
|
1876
|
+
const key = pair[1].trim();
|
|
1877
|
+
if (attrName && key) {
|
|
1878
|
+
const val = evaluateExpression(key, processedPayload);
|
|
1879
|
+
if (val !== void 0 && val !== null) {
|
|
1880
|
+
node.setAttribute(attrName, val);
|
|
1881
|
+
}
|
|
1747
1882
|
}
|
|
1748
1883
|
}
|
|
1749
1884
|
});
|
|
@@ -1752,13 +1887,13 @@ function attachDOMBinding(clientProto) {
|
|
|
1752
1887
|
if (node.hasAttribute("data-rt-class")) {
|
|
1753
1888
|
const classStr = node.getAttribute("data-rt-class");
|
|
1754
1889
|
if (classStr) {
|
|
1755
|
-
classStr
|
|
1756
|
-
const
|
|
1757
|
-
if (
|
|
1758
|
-
const className =
|
|
1759
|
-
const key =
|
|
1890
|
+
splitByUnquotedChar(classStr, ",").forEach((b) => {
|
|
1891
|
+
const pair = splitFirstUnquotedColon(b);
|
|
1892
|
+
if (pair) {
|
|
1893
|
+
const className = pair[0].trim();
|
|
1894
|
+
const key = pair[1].trim();
|
|
1760
1895
|
const classNames = className.split(/\s+/).filter(Boolean);
|
|
1761
|
-
if (processedPayload
|
|
1896
|
+
if (evaluateExpression(key, processedPayload)) {
|
|
1762
1897
|
classNames.forEach((c) => node.classList.add(c));
|
|
1763
1898
|
} else {
|
|
1764
1899
|
classNames.forEach((c) => node.classList.remove(c));
|
|
@@ -1770,7 +1905,7 @@ function attachDOMBinding(clientProto) {
|
|
|
1770
1905
|
if (node.hasAttribute("data-rt-if")) {
|
|
1771
1906
|
const key = node.getAttribute("data-rt-if");
|
|
1772
1907
|
if (key) {
|
|
1773
|
-
if (processedPayload
|
|
1908
|
+
if (evaluateExpression(key, processedPayload)) {
|
|
1774
1909
|
node.style.display = "";
|
|
1775
1910
|
} else {
|
|
1776
1911
|
node.style.display = "none";
|
|
@@ -1780,7 +1915,7 @@ function attachDOMBinding(clientProto) {
|
|
|
1780
1915
|
if (node.hasAttribute("data-rt-hide")) {
|
|
1781
1916
|
const key = node.getAttribute("data-rt-hide");
|
|
1782
1917
|
if (key) {
|
|
1783
|
-
if (processedPayload
|
|
1918
|
+
if (evaluateExpression(key, processedPayload)) {
|
|
1784
1919
|
node.style.display = "none";
|
|
1785
1920
|
} else {
|
|
1786
1921
|
node.style.display = "";
|
|
@@ -1830,21 +1965,35 @@ function attachDOMBinding(clientProto) {
|
|
|
1830
1965
|
return;
|
|
1831
1966
|
}
|
|
1832
1967
|
resolvingSet.add(src);
|
|
1833
|
-
|
|
1968
|
+
const hashIndex = src.indexOf("#");
|
|
1969
|
+
const url = hashIndex !== -1 ? src.substring(0, hashIndex) : src;
|
|
1970
|
+
const selector = hashIndex !== -1 ? src.substring(hashIndex) : null;
|
|
1971
|
+
let promise = componentPromiseCache.get(url);
|
|
1834
1972
|
if (!promise) {
|
|
1835
|
-
promise = fetch(
|
|
1973
|
+
promise = fetch(url).then((res) => {
|
|
1836
1974
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1837
1975
|
return res.text();
|
|
1838
1976
|
});
|
|
1839
|
-
promise.catch(() => componentPromiseCache.delete(
|
|
1840
|
-
componentPromiseCache.set(
|
|
1977
|
+
promise.catch(() => componentPromiseCache.delete(url));
|
|
1978
|
+
componentPromiseCache.set(url, promise);
|
|
1841
1979
|
}
|
|
1842
1980
|
let content = "";
|
|
1843
1981
|
try {
|
|
1844
1982
|
content = await promise;
|
|
1983
|
+
if (selector && typeof DOMParser !== "undefined") {
|
|
1984
|
+
const parser = new DOMParser();
|
|
1985
|
+
const doc = parser.parseFromString(content, "text/html");
|
|
1986
|
+
const targetEl = doc.querySelector(selector);
|
|
1987
|
+
if (targetEl) {
|
|
1988
|
+
content = targetEl.outerHTML;
|
|
1989
|
+
} else {
|
|
1990
|
+
console.warn(`[Dolphin Component Warning]: Selector "${selector}" not found in imported file "${url}".`);
|
|
1991
|
+
content = `<span style="color:orange;font-weight:bold;">Selector ${selector} not found in ${url}</span>`;
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1845
1994
|
} catch (err) {
|
|
1846
|
-
console.error(`[Dolphin Component Error]: Failed to fetch component "${
|
|
1847
|
-
content = `<span style="color:red;font-weight:bold;">Failed to import ${
|
|
1995
|
+
console.error(`[Dolphin Component Error]: Failed to fetch component "${url}":`, err);
|
|
1996
|
+
content = `<span style="color:red;font-weight:bold;">Failed to import ${url}</span>`;
|
|
1848
1997
|
}
|
|
1849
1998
|
el.innerHTML = sanitizeHTML(content);
|
|
1850
1999
|
el.removeAttribute("data-import");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dolphin-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
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",
|