dolphin-client 1.0.0 → 1.0.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 +9 -1
- package/dist/dolphin-client.js +106 -24
- package/dist/index.cjs +106 -24
- package/dist/index.js +106 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,14 +20,22 @@ By breathing life back into standard HTML, we have resurrected the simplicity of
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
+
## Documentation
|
|
24
|
+
|
|
25
|
+
> [!TIP]
|
|
26
|
+
> 📖 Read our comprehensive **[Full Developer Tutorial & Integration Guide](file:///c:/Users/USER/Desktop/dolphin-test/fulltutorial.md)** for detailed guides, Next.js setups, WebRTC intercoms, global stores, drag-and-drop sortable lists, and real-world examples!
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
23
30
|
## Features
|
|
24
31
|
|
|
25
32
|
- **Hookless Reactivity**: Bind topics to DOM elements via HTML data attributes—no React, Vue, or Angular state management required.
|
|
33
|
+
- **Svelte-Style Templates Compiler**: Native browser compiler supporting Svelte-style loop blocks (`{#each ... as ...}`), multi-level nested conditionals (`{#if} / {:else if} / {:else}`), loop indices (`index`), optional chaining, and dynamic attribute interpolation.
|
|
26
34
|
- **Unified Event Binding**: Loop-based browser event handling for values (`data-rt-push`) and actions (`data-rt-[event]` / `data-api-[event]`).
|
|
27
35
|
- **Context API/Prop drilling in Pure DOM**: Crawls up the DOM tree (`getClosestContext`) to fetch parent contexts and inject parameters.
|
|
28
36
|
- **REST API + Realtime Hybrid Support**: Evaluates templates (`data-rt-template`) on initial HTTP fetches (`data-api-get`) and transitions seamlessly to real-time WebSockets on connection.
|
|
29
37
|
- **WebRTC Intercom Signaling**: Built-in methods to handle peer connections, track negotiation, ICE candidates, and signaling.
|
|
30
|
-
- **Ultralight weight**: Zero external dependencies, pure browser-native runtime APIs.
|
|
38
|
+
- **Ultralight weight**: Zero external dependencies, pure browser-native runtime APIs (~77KB bundle!).
|
|
31
39
|
|
|
32
40
|
### Method 1: NPM (For Modern Bundlers)
|
|
33
41
|
```bash
|
package/dist/dolphin-client.js
CHANGED
|
@@ -788,6 +788,108 @@ var DolphinModule = (() => {
|
|
|
788
788
|
}
|
|
789
789
|
return template;
|
|
790
790
|
}
|
|
791
|
+
function renderTemplate(templateStr, context) {
|
|
792
|
+
if (!templateStr.includes("{#if") && !templateStr.includes("{#each")) {
|
|
793
|
+
let result = templateStr;
|
|
794
|
+
for (let key in context) {
|
|
795
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
796
|
+
result = result.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
|
|
797
|
+
}
|
|
798
|
+
return result;
|
|
799
|
+
}
|
|
800
|
+
try {
|
|
801
|
+
const escapeString = (str) => {
|
|
802
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
803
|
+
};
|
|
804
|
+
let compiled = 'let out = "";\n';
|
|
805
|
+
let lastIndex = 0;
|
|
806
|
+
const regex = /(\{\{([\s\S]*?)\}\}|\{#if\s+([\s\S]*?)\}|\{:else\s+if\s+([\s\S]*?)\}|\{:else\}|\{\/if\}|\{#each\s+([\s\S]*?)\s+as\s+([a-zA-Z_$][a-zA-Z0-9_$]*)(?:\s*,\s*([a-zA-Z_$][a-zA-Z0-9_$]*))?\}|\{\/each\}|\{([^{}]+?)\})/g;
|
|
807
|
+
const eachStack = [];
|
|
808
|
+
let match;
|
|
809
|
+
while ((match = regex.exec(templateStr)) !== null) {
|
|
810
|
+
const plainText = templateStr.slice(lastIndex, match.index);
|
|
811
|
+
if (plainText) {
|
|
812
|
+
compiled += `out += "${escapeString(plainText)}";
|
|
813
|
+
`;
|
|
814
|
+
}
|
|
815
|
+
const token = match[0];
|
|
816
|
+
if (token.startsWith("{{")) {
|
|
817
|
+
const expr = match[2];
|
|
818
|
+
compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
|
|
819
|
+
`;
|
|
820
|
+
} else if (token.startsWith("{#if")) {
|
|
821
|
+
const expr = match[3];
|
|
822
|
+
compiled += `if (${expr}) {
|
|
823
|
+
`;
|
|
824
|
+
} else if (token.startsWith("{:else if")) {
|
|
825
|
+
const expr = match[4];
|
|
826
|
+
compiled += `} else if (${expr}) {
|
|
827
|
+
`;
|
|
828
|
+
} else if (token.startsWith("{:else}")) {
|
|
829
|
+
compiled += `} else {
|
|
830
|
+
`;
|
|
831
|
+
} else if (token.startsWith("{/if}")) {
|
|
832
|
+
compiled += `}
|
|
833
|
+
`;
|
|
834
|
+
} else if (token.startsWith("{#each")) {
|
|
835
|
+
const expr = match[5];
|
|
836
|
+
const itemVar = match[6];
|
|
837
|
+
const indexVar = match[7];
|
|
838
|
+
eachStack.push({ indexVar });
|
|
839
|
+
compiled += `if (typeof ${expr} !== "undefined" && ${expr} !== null && Array.isArray(${expr})) {
|
|
840
|
+
`;
|
|
841
|
+
if (indexVar) {
|
|
842
|
+
compiled += ` let ${indexVar} = 0;
|
|
843
|
+
`;
|
|
844
|
+
}
|
|
845
|
+
compiled += ` for (let ${itemVar} of ${expr}) {
|
|
846
|
+
`;
|
|
847
|
+
} else if (token.startsWith("{/each}")) {
|
|
848
|
+
const info = eachStack.pop();
|
|
849
|
+
if (info && info.indexVar) {
|
|
850
|
+
compiled += ` ${info.indexVar}++;
|
|
851
|
+
`;
|
|
852
|
+
}
|
|
853
|
+
compiled += ` }
|
|
854
|
+
}
|
|
855
|
+
`;
|
|
856
|
+
} else if (token.startsWith("{")) {
|
|
857
|
+
const expr = match[8];
|
|
858
|
+
if (expr) {
|
|
859
|
+
compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
|
|
860
|
+
`;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
lastIndex = regex.lastIndex;
|
|
864
|
+
}
|
|
865
|
+
const remaining = templateStr.slice(lastIndex);
|
|
866
|
+
if (remaining) {
|
|
867
|
+
compiled += `out += "${escapeString(remaining)}";
|
|
868
|
+
`;
|
|
869
|
+
}
|
|
870
|
+
compiled += "return out;\n";
|
|
871
|
+
const fnBody = `
|
|
872
|
+
with (context) {
|
|
873
|
+
try {
|
|
874
|
+
${compiled}
|
|
875
|
+
} catch (innerErr) {
|
|
876
|
+
console.warn('[Dolphin Template Eval Warning]:', innerErr);
|
|
877
|
+
return '';
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
`;
|
|
881
|
+
const fn = new Function("context", fnBody);
|
|
882
|
+
return fn(context);
|
|
883
|
+
} catch (e) {
|
|
884
|
+
console.error("[Dolphin Template Compiler Error]:", e);
|
|
885
|
+
let fallback = templateStr;
|
|
886
|
+
for (let key in context) {
|
|
887
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
888
|
+
fallback = fallback.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
|
|
889
|
+
}
|
|
890
|
+
return fallback;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
791
893
|
function sanitizeHTML(html) {
|
|
792
894
|
if (typeof document === "undefined") return html;
|
|
793
895
|
try {
|
|
@@ -1188,21 +1290,11 @@ var DolphinModule = (() => {
|
|
|
1188
1290
|
if (Array.isArray(result)) {
|
|
1189
1291
|
let combinedHTML = "";
|
|
1190
1292
|
for (const item of result) {
|
|
1191
|
-
|
|
1192
|
-
for (let key in item) {
|
|
1193
|
-
const escapedKey = escapeRegExp(key);
|
|
1194
|
-
finalItemHTML = finalItemHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), item[key] !== void 0 && item[key] !== null ? item[key] : "");
|
|
1195
|
-
}
|
|
1196
|
-
combinedHTML += finalItemHTML;
|
|
1293
|
+
combinedHTML += renderTemplate(template, item);
|
|
1197
1294
|
}
|
|
1198
1295
|
scheduleDOMUpdate(el, combinedHTML);
|
|
1199
1296
|
} else {
|
|
1200
|
-
|
|
1201
|
-
for (let key in result) {
|
|
1202
|
-
const escapedKey = escapeRegExp(key);
|
|
1203
|
-
finalHTML = finalHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), result[key] !== void 0 && result[key] !== null ? result[key] : "");
|
|
1204
|
-
}
|
|
1205
|
-
scheduleDOMUpdate(el, finalHTML);
|
|
1297
|
+
scheduleDOMUpdate(el, renderTemplate(template, result));
|
|
1206
1298
|
}
|
|
1207
1299
|
} else {
|
|
1208
1300
|
if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
|
|
@@ -1296,21 +1388,11 @@ var DolphinModule = (() => {
|
|
|
1296
1388
|
if (Array.isArray(payload)) {
|
|
1297
1389
|
let combinedHTML = "";
|
|
1298
1390
|
for (const item of payload) {
|
|
1299
|
-
|
|
1300
|
-
for (let key in item) {
|
|
1301
|
-
const escapedKey = escapeRegExp(key);
|
|
1302
|
-
finalItemHTML = finalItemHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), item[key] !== void 0 && item[key] !== null ? item[key] : "");
|
|
1303
|
-
}
|
|
1304
|
-
combinedHTML += finalItemHTML;
|
|
1391
|
+
combinedHTML += renderTemplate(template, item);
|
|
1305
1392
|
}
|
|
1306
1393
|
scheduleDOMUpdate(el, combinedHTML);
|
|
1307
1394
|
} else {
|
|
1308
|
-
|
|
1309
|
-
for (let key in payload) {
|
|
1310
|
-
const escapedKey = escapeRegExp(key);
|
|
1311
|
-
finalHTML = finalHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), payload[key] !== void 0 && payload[key] !== null ? payload[key] : "");
|
|
1312
|
-
}
|
|
1313
|
-
scheduleDOMUpdate(el, finalHTML);
|
|
1395
|
+
scheduleDOMUpdate(el, renderTemplate(template, payload));
|
|
1314
1396
|
}
|
|
1315
1397
|
return;
|
|
1316
1398
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -788,6 +788,108 @@ function attachDOMBinding(clientProto) {
|
|
|
788
788
|
}
|
|
789
789
|
return template;
|
|
790
790
|
}
|
|
791
|
+
function renderTemplate(templateStr, context) {
|
|
792
|
+
if (!templateStr.includes("{#if") && !templateStr.includes("{#each")) {
|
|
793
|
+
let result = templateStr;
|
|
794
|
+
for (let key in context) {
|
|
795
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
796
|
+
result = result.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
|
|
797
|
+
}
|
|
798
|
+
return result;
|
|
799
|
+
}
|
|
800
|
+
try {
|
|
801
|
+
const escapeString = (str) => {
|
|
802
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
803
|
+
};
|
|
804
|
+
let compiled = 'let out = "";\n';
|
|
805
|
+
let lastIndex = 0;
|
|
806
|
+
const regex = /(\{\{([\s\S]*?)\}\}|\{#if\s+([\s\S]*?)\}|\{:else\s+if\s+([\s\S]*?)\}|\{:else\}|\{\/if\}|\{#each\s+([\s\S]*?)\s+as\s+([a-zA-Z_$][a-zA-Z0-9_$]*)(?:\s*,\s*([a-zA-Z_$][a-zA-Z0-9_$]*))?\}|\{\/each\}|\{([^{}]+?)\})/g;
|
|
807
|
+
const eachStack = [];
|
|
808
|
+
let match;
|
|
809
|
+
while ((match = regex.exec(templateStr)) !== null) {
|
|
810
|
+
const plainText = templateStr.slice(lastIndex, match.index);
|
|
811
|
+
if (plainText) {
|
|
812
|
+
compiled += `out += "${escapeString(plainText)}";
|
|
813
|
+
`;
|
|
814
|
+
}
|
|
815
|
+
const token = match[0];
|
|
816
|
+
if (token.startsWith("{{")) {
|
|
817
|
+
const expr = match[2];
|
|
818
|
+
compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
|
|
819
|
+
`;
|
|
820
|
+
} else if (token.startsWith("{#if")) {
|
|
821
|
+
const expr = match[3];
|
|
822
|
+
compiled += `if (${expr}) {
|
|
823
|
+
`;
|
|
824
|
+
} else if (token.startsWith("{:else if")) {
|
|
825
|
+
const expr = match[4];
|
|
826
|
+
compiled += `} else if (${expr}) {
|
|
827
|
+
`;
|
|
828
|
+
} else if (token.startsWith("{:else}")) {
|
|
829
|
+
compiled += `} else {
|
|
830
|
+
`;
|
|
831
|
+
} else if (token.startsWith("{/if}")) {
|
|
832
|
+
compiled += `}
|
|
833
|
+
`;
|
|
834
|
+
} else if (token.startsWith("{#each")) {
|
|
835
|
+
const expr = match[5];
|
|
836
|
+
const itemVar = match[6];
|
|
837
|
+
const indexVar = match[7];
|
|
838
|
+
eachStack.push({ indexVar });
|
|
839
|
+
compiled += `if (typeof ${expr} !== "undefined" && ${expr} !== null && Array.isArray(${expr})) {
|
|
840
|
+
`;
|
|
841
|
+
if (indexVar) {
|
|
842
|
+
compiled += ` let ${indexVar} = 0;
|
|
843
|
+
`;
|
|
844
|
+
}
|
|
845
|
+
compiled += ` for (let ${itemVar} of ${expr}) {
|
|
846
|
+
`;
|
|
847
|
+
} else if (token.startsWith("{/each}")) {
|
|
848
|
+
const info = eachStack.pop();
|
|
849
|
+
if (info && info.indexVar) {
|
|
850
|
+
compiled += ` ${info.indexVar}++;
|
|
851
|
+
`;
|
|
852
|
+
}
|
|
853
|
+
compiled += ` }
|
|
854
|
+
}
|
|
855
|
+
`;
|
|
856
|
+
} else if (token.startsWith("{")) {
|
|
857
|
+
const expr = match[8];
|
|
858
|
+
if (expr) {
|
|
859
|
+
compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
|
|
860
|
+
`;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
lastIndex = regex.lastIndex;
|
|
864
|
+
}
|
|
865
|
+
const remaining = templateStr.slice(lastIndex);
|
|
866
|
+
if (remaining) {
|
|
867
|
+
compiled += `out += "${escapeString(remaining)}";
|
|
868
|
+
`;
|
|
869
|
+
}
|
|
870
|
+
compiled += "return out;\n";
|
|
871
|
+
const fnBody = `
|
|
872
|
+
with (context) {
|
|
873
|
+
try {
|
|
874
|
+
${compiled}
|
|
875
|
+
} catch (innerErr) {
|
|
876
|
+
console.warn('[Dolphin Template Eval Warning]:', innerErr);
|
|
877
|
+
return '';
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
`;
|
|
881
|
+
const fn = new Function("context", fnBody);
|
|
882
|
+
return fn(context);
|
|
883
|
+
} catch (e) {
|
|
884
|
+
console.error("[Dolphin Template Compiler Error]:", e);
|
|
885
|
+
let fallback = templateStr;
|
|
886
|
+
for (let key in context) {
|
|
887
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
888
|
+
fallback = fallback.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
|
|
889
|
+
}
|
|
890
|
+
return fallback;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
791
893
|
function sanitizeHTML(html) {
|
|
792
894
|
if (typeof document === "undefined") return html;
|
|
793
895
|
try {
|
|
@@ -1188,21 +1290,11 @@ function attachDOMBinding(clientProto) {
|
|
|
1188
1290
|
if (Array.isArray(result)) {
|
|
1189
1291
|
let combinedHTML = "";
|
|
1190
1292
|
for (const item of result) {
|
|
1191
|
-
|
|
1192
|
-
for (let key in item) {
|
|
1193
|
-
const escapedKey = escapeRegExp(key);
|
|
1194
|
-
finalItemHTML = finalItemHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), item[key] !== void 0 && item[key] !== null ? item[key] : "");
|
|
1195
|
-
}
|
|
1196
|
-
combinedHTML += finalItemHTML;
|
|
1293
|
+
combinedHTML += renderTemplate(template, item);
|
|
1197
1294
|
}
|
|
1198
1295
|
scheduleDOMUpdate(el, combinedHTML);
|
|
1199
1296
|
} else {
|
|
1200
|
-
|
|
1201
|
-
for (let key in result) {
|
|
1202
|
-
const escapedKey = escapeRegExp(key);
|
|
1203
|
-
finalHTML = finalHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), result[key] !== void 0 && result[key] !== null ? result[key] : "");
|
|
1204
|
-
}
|
|
1205
|
-
scheduleDOMUpdate(el, finalHTML);
|
|
1297
|
+
scheduleDOMUpdate(el, renderTemplate(template, result));
|
|
1206
1298
|
}
|
|
1207
1299
|
} else {
|
|
1208
1300
|
if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
|
|
@@ -1296,21 +1388,11 @@ function attachDOMBinding(clientProto) {
|
|
|
1296
1388
|
if (Array.isArray(payload)) {
|
|
1297
1389
|
let combinedHTML = "";
|
|
1298
1390
|
for (const item of payload) {
|
|
1299
|
-
|
|
1300
|
-
for (let key in item) {
|
|
1301
|
-
const escapedKey = escapeRegExp(key);
|
|
1302
|
-
finalItemHTML = finalItemHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), item[key] !== void 0 && item[key] !== null ? item[key] : "");
|
|
1303
|
-
}
|
|
1304
|
-
combinedHTML += finalItemHTML;
|
|
1391
|
+
combinedHTML += renderTemplate(template, item);
|
|
1305
1392
|
}
|
|
1306
1393
|
scheduleDOMUpdate(el, combinedHTML);
|
|
1307
1394
|
} else {
|
|
1308
|
-
|
|
1309
|
-
for (let key in payload) {
|
|
1310
|
-
const escapedKey = escapeRegExp(key);
|
|
1311
|
-
finalHTML = finalHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), payload[key] !== void 0 && payload[key] !== null ? payload[key] : "");
|
|
1312
|
-
}
|
|
1313
|
-
scheduleDOMUpdate(el, finalHTML);
|
|
1395
|
+
scheduleDOMUpdate(el, renderTemplate(template, payload));
|
|
1314
1396
|
}
|
|
1315
1397
|
return;
|
|
1316
1398
|
}
|
package/dist/index.js
CHANGED
|
@@ -763,6 +763,108 @@ function attachDOMBinding(clientProto) {
|
|
|
763
763
|
}
|
|
764
764
|
return template;
|
|
765
765
|
}
|
|
766
|
+
function renderTemplate(templateStr, context) {
|
|
767
|
+
if (!templateStr.includes("{#if") && !templateStr.includes("{#each")) {
|
|
768
|
+
let result = templateStr;
|
|
769
|
+
for (let key in context) {
|
|
770
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
771
|
+
result = result.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
|
|
772
|
+
}
|
|
773
|
+
return result;
|
|
774
|
+
}
|
|
775
|
+
try {
|
|
776
|
+
const escapeString = (str) => {
|
|
777
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
778
|
+
};
|
|
779
|
+
let compiled = 'let out = "";\n';
|
|
780
|
+
let lastIndex = 0;
|
|
781
|
+
const regex = /(\{\{([\s\S]*?)\}\}|\{#if\s+([\s\S]*?)\}|\{:else\s+if\s+([\s\S]*?)\}|\{:else\}|\{\/if\}|\{#each\s+([\s\S]*?)\s+as\s+([a-zA-Z_$][a-zA-Z0-9_$]*)(?:\s*,\s*([a-zA-Z_$][a-zA-Z0-9_$]*))?\}|\{\/each\}|\{([^{}]+?)\})/g;
|
|
782
|
+
const eachStack = [];
|
|
783
|
+
let match;
|
|
784
|
+
while ((match = regex.exec(templateStr)) !== null) {
|
|
785
|
+
const plainText = templateStr.slice(lastIndex, match.index);
|
|
786
|
+
if (plainText) {
|
|
787
|
+
compiled += `out += "${escapeString(plainText)}";
|
|
788
|
+
`;
|
|
789
|
+
}
|
|
790
|
+
const token = match[0];
|
|
791
|
+
if (token.startsWith("{{")) {
|
|
792
|
+
const expr = match[2];
|
|
793
|
+
compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
|
|
794
|
+
`;
|
|
795
|
+
} else if (token.startsWith("{#if")) {
|
|
796
|
+
const expr = match[3];
|
|
797
|
+
compiled += `if (${expr}) {
|
|
798
|
+
`;
|
|
799
|
+
} else if (token.startsWith("{:else if")) {
|
|
800
|
+
const expr = match[4];
|
|
801
|
+
compiled += `} else if (${expr}) {
|
|
802
|
+
`;
|
|
803
|
+
} else if (token.startsWith("{:else}")) {
|
|
804
|
+
compiled += `} else {
|
|
805
|
+
`;
|
|
806
|
+
} else if (token.startsWith("{/if}")) {
|
|
807
|
+
compiled += `}
|
|
808
|
+
`;
|
|
809
|
+
} else if (token.startsWith("{#each")) {
|
|
810
|
+
const expr = match[5];
|
|
811
|
+
const itemVar = match[6];
|
|
812
|
+
const indexVar = match[7];
|
|
813
|
+
eachStack.push({ indexVar });
|
|
814
|
+
compiled += `if (typeof ${expr} !== "undefined" && ${expr} !== null && Array.isArray(${expr})) {
|
|
815
|
+
`;
|
|
816
|
+
if (indexVar) {
|
|
817
|
+
compiled += ` let ${indexVar} = 0;
|
|
818
|
+
`;
|
|
819
|
+
}
|
|
820
|
+
compiled += ` for (let ${itemVar} of ${expr}) {
|
|
821
|
+
`;
|
|
822
|
+
} else if (token.startsWith("{/each}")) {
|
|
823
|
+
const info = eachStack.pop();
|
|
824
|
+
if (info && info.indexVar) {
|
|
825
|
+
compiled += ` ${info.indexVar}++;
|
|
826
|
+
`;
|
|
827
|
+
}
|
|
828
|
+
compiled += ` }
|
|
829
|
+
}
|
|
830
|
+
`;
|
|
831
|
+
} else if (token.startsWith("{")) {
|
|
832
|
+
const expr = match[8];
|
|
833
|
+
if (expr) {
|
|
834
|
+
compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
|
|
835
|
+
`;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
lastIndex = regex.lastIndex;
|
|
839
|
+
}
|
|
840
|
+
const remaining = templateStr.slice(lastIndex);
|
|
841
|
+
if (remaining) {
|
|
842
|
+
compiled += `out += "${escapeString(remaining)}";
|
|
843
|
+
`;
|
|
844
|
+
}
|
|
845
|
+
compiled += "return out;\n";
|
|
846
|
+
const fnBody = `
|
|
847
|
+
with (context) {
|
|
848
|
+
try {
|
|
849
|
+
${compiled}
|
|
850
|
+
} catch (innerErr) {
|
|
851
|
+
console.warn('[Dolphin Template Eval Warning]:', innerErr);
|
|
852
|
+
return '';
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
`;
|
|
856
|
+
const fn = new Function("context", fnBody);
|
|
857
|
+
return fn(context);
|
|
858
|
+
} catch (e) {
|
|
859
|
+
console.error("[Dolphin Template Compiler Error]:", e);
|
|
860
|
+
let fallback = templateStr;
|
|
861
|
+
for (let key in context) {
|
|
862
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
863
|
+
fallback = fallback.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
|
|
864
|
+
}
|
|
865
|
+
return fallback;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
766
868
|
function sanitizeHTML(html) {
|
|
767
869
|
if (typeof document === "undefined") return html;
|
|
768
870
|
try {
|
|
@@ -1163,21 +1265,11 @@ function attachDOMBinding(clientProto) {
|
|
|
1163
1265
|
if (Array.isArray(result)) {
|
|
1164
1266
|
let combinedHTML = "";
|
|
1165
1267
|
for (const item of result) {
|
|
1166
|
-
|
|
1167
|
-
for (let key in item) {
|
|
1168
|
-
const escapedKey = escapeRegExp(key);
|
|
1169
|
-
finalItemHTML = finalItemHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), item[key] !== void 0 && item[key] !== null ? item[key] : "");
|
|
1170
|
-
}
|
|
1171
|
-
combinedHTML += finalItemHTML;
|
|
1268
|
+
combinedHTML += renderTemplate(template, item);
|
|
1172
1269
|
}
|
|
1173
1270
|
scheduleDOMUpdate(el, combinedHTML);
|
|
1174
1271
|
} else {
|
|
1175
|
-
|
|
1176
|
-
for (let key in result) {
|
|
1177
|
-
const escapedKey = escapeRegExp(key);
|
|
1178
|
-
finalHTML = finalHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), result[key] !== void 0 && result[key] !== null ? result[key] : "");
|
|
1179
|
-
}
|
|
1180
|
-
scheduleDOMUpdate(el, finalHTML);
|
|
1272
|
+
scheduleDOMUpdate(el, renderTemplate(template, result));
|
|
1181
1273
|
}
|
|
1182
1274
|
} else {
|
|
1183
1275
|
if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
|
|
@@ -1271,21 +1363,11 @@ function attachDOMBinding(clientProto) {
|
|
|
1271
1363
|
if (Array.isArray(payload)) {
|
|
1272
1364
|
let combinedHTML = "";
|
|
1273
1365
|
for (const item of payload) {
|
|
1274
|
-
|
|
1275
|
-
for (let key in item) {
|
|
1276
|
-
const escapedKey = escapeRegExp(key);
|
|
1277
|
-
finalItemHTML = finalItemHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), item[key] !== void 0 && item[key] !== null ? item[key] : "");
|
|
1278
|
-
}
|
|
1279
|
-
combinedHTML += finalItemHTML;
|
|
1366
|
+
combinedHTML += renderTemplate(template, item);
|
|
1280
1367
|
}
|
|
1281
1368
|
scheduleDOMUpdate(el, combinedHTML);
|
|
1282
1369
|
} else {
|
|
1283
|
-
|
|
1284
|
-
for (let key in payload) {
|
|
1285
|
-
const escapedKey = escapeRegExp(key);
|
|
1286
|
-
finalHTML = finalHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), payload[key] !== void 0 && payload[key] !== null ? payload[key] : "");
|
|
1287
|
-
}
|
|
1288
|
-
scheduleDOMUpdate(el, finalHTML);
|
|
1370
|
+
scheduleDOMUpdate(el, renderTemplate(template, payload));
|
|
1289
1371
|
}
|
|
1290
1372
|
return;
|
|
1291
1373
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dolphin-client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.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",
|