dolphin-client 1.0.0 β 1.0.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/README.md +122 -30
- package/dist/dolphin-client.js +184 -24
- package/dist/dolphin-client.min.js +26 -0
- package/dist/index.cjs +184 -24
- package/dist/index.js +184 -24
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -20,14 +20,26 @@ 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](./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 (~39KB compressed bundle!).
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Installation & Setup
|
|
31
43
|
|
|
32
44
|
### Method 1: NPM (For Modern Bundlers)
|
|
33
45
|
```bash
|
|
@@ -37,19 +49,38 @@ npm install dolphin-client
|
|
|
37
49
|
### Method 2: Direct Local Download (For No-Install / Plain HTML)
|
|
38
50
|
Tired of the command line and `node_modules` clutter? We've got you covered!
|
|
39
51
|
|
|
40
|
-
[](https://unpkg.com/dolphin-client/dist/dolphin-client.js) [](https://github.com/Phuyalshankar/dolphin-server-modules/releases/latest/download/dolphin-bundle.zip)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
[](https://unpkg.com/dolphin-client/dist/dolphin-client.js) [](https://unpkg.com/dolphin-client/dist/dolphin-client.min.js) [](https://github.com/Phuyalshankar/dolphin-server-modules/releases/latest/download/dolphin-bundle.zip)
|
|
53
|
+
|
|
54
|
+
> [!IMPORTANT]
|
|
55
|
+
> **How to Download the JS Files:**
|
|
56
|
+
> Clicking the JS download buttons will display the raw JavaScript code in your browser.
|
|
57
|
+
> To download it as a file:
|
|
58
|
+
> 1. **Right-click** on the yellow/blue button and select **"Save Link As..."** (or "Save Target As...").
|
|
59
|
+
> 2. Or, if you clicked it, press **`Ctrl + S`** (or `Cmd + S` on Mac) on the code page to save it.
|
|
60
|
+
> 3. Alternatively, run these commands in your terminal to download directly:
|
|
61
|
+
> * **Standard (Development):**
|
|
62
|
+
> ```bash
|
|
63
|
+
> curl -o dolphin-client.js https://unpkg.com/dolphin-client/dist/dolphin-client.js
|
|
64
|
+
> ```
|
|
65
|
+
> * **Minified (Production):**
|
|
66
|
+
> ```bash
|
|
67
|
+
> curl -o dolphin-client.min.js https://unpkg.com/dolphin-client/dist/dolphin-client.min.js
|
|
68
|
+
> ```
|
|
69
|
+
|
|
70
|
+
> [!TIP]
|
|
71
|
+
> **If the ZIP file download is unavailable:**
|
|
72
|
+
> You can clone this repository directly or copy the `dist/dolphin-client.js` (or `dist/dolphin-client.min.js`) file from your clone.
|
|
73
|
+
> For the premium styling layer, create a `dolphin-css.css` file and add the custom effects (like `.fx-glass` and `.fx-neon`) described in the **[Full Developer Tutorial](./fulltutorial.md)**.
|
|
44
74
|
|
|
45
75
|
Extract the zip directly inside your project folder to get a clean local directory structure with pre-bundled assets:
|
|
46
76
|
```
|
|
47
77
|
my-project/
|
|
48
78
|
βββ css/
|
|
49
|
-
β βββ dolphin-css.css
|
|
79
|
+
β βββ dolphin-css.css (DolphinCSS Premium Visuals Layer)
|
|
50
80
|
βββ js/
|
|
51
|
-
β
|
|
52
|
-
βββ
|
|
81
|
+
β βββ dolphin-client.js (Standard Reactivity Engine)
|
|
82
|
+
β βββ dolphin-client.min.js (Minified Production Reactivity Engine)
|
|
83
|
+
βββ index.html (A ready-to-run skeleton template!)
|
|
53
84
|
```
|
|
54
85
|
Inside your HTML, simply link them locally:
|
|
55
86
|
```html
|
|
@@ -57,44 +88,105 @@ Inside your HTML, simply link them locally:
|
|
|
57
88
|
<script src="js/dolphin-client.js"></script>
|
|
58
89
|
```
|
|
59
90
|
|
|
60
|
-
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Interactive Examples Guide
|
|
61
94
|
|
|
62
|
-
|
|
95
|
+
Below are clean, ready-to-use HTML examples for the core features of Dolphin Client. Because Dolphin supports **Zero-Configuration Auto-Initialization**, these will run instantly!
|
|
96
|
+
|
|
97
|
+
### 1. Real-Time (RT) Pub/Sub (Real-time Chat)
|
|
98
|
+
Publish inputs on typing, and display incoming messages in real-time under a WebSocket topic:
|
|
99
|
+
|
|
100
|
+
```html
|
|
101
|
+
<!-- Automatically publishes typed values to topic 'chat/messages' -->
|
|
102
|
+
<input name="chatMessage" data-rt-push="chat/messages" placeholder="Type a message..." />
|
|
103
|
+
|
|
104
|
+
<!-- Displays and appends all incoming messages under 'chat/messages' -->
|
|
105
|
+
<div data-rt-bind="chat/messages" data-rt-template='
|
|
106
|
+
<div class="message-card">
|
|
107
|
+
<span class="user-label">@user:</span>
|
|
108
|
+
<p>{{chatMessage}}</p>
|
|
109
|
+
</div>
|
|
110
|
+
'></div>
|
|
111
|
+
```
|
|
63
112
|
|
|
64
|
-
|
|
65
|
-
|
|
113
|
+
### 2. REST API Integration (Reactive List Loader)
|
|
114
|
+
Fetch data instantly via REST API on page load and compile templates dynamically:
|
|
66
115
|
|
|
67
|
-
|
|
68
|
-
|
|
116
|
+
```html
|
|
117
|
+
<!-- Fetches from GET /api/devices, compiles items inside #device-card-template -->
|
|
118
|
+
<div data-api-get="/api/devices" data-rt-template="#device-card-template">
|
|
119
|
+
<!-- Loading spinner placeholder: automatically removed when data arrives! -->
|
|
120
|
+
<div class="spinner">Loading devices...</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<!-- Browser-Native Template Tag (No quote-escaping or backticks needed!) -->
|
|
124
|
+
<template id="device-card-template">
|
|
125
|
+
<div class="device-card">
|
|
126
|
+
<h3>{{name}}</h3>
|
|
127
|
+
<span class="badge">{{status}}</span>
|
|
128
|
+
</div>
|
|
129
|
+
</template>
|
|
69
130
|
```
|
|
70
131
|
|
|
71
|
-
###
|
|
132
|
+
### 3. Declarative Form Validation
|
|
133
|
+
Apply strong validation rules to inputs and display errors directly in the UI with absolutely **zero JavaScript**:
|
|
72
134
|
|
|
73
135
|
```html
|
|
74
|
-
<
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
136
|
+
<form id="login-form" data-api-submit="POST /api/login">
|
|
137
|
+
<div class="form-group">
|
|
138
|
+
<input name="email" data-validate="required,email" placeholder="Your Email" />
|
|
139
|
+
<!-- Automatically reads and displays validation error directly in the UI -->
|
|
140
|
+
<span class="error-msg" data-rt-bind="errors/email"></span>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div class="form-group">
|
|
144
|
+
<input name="password" type="password" data-validate="required,min:8" placeholder="Password" />
|
|
145
|
+
<span class="error-msg" data-rt-bind="errors/password"></span>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<button type="submit">Log In</button>
|
|
149
|
+
</form>
|
|
79
150
|
```
|
|
80
151
|
|
|
81
|
-
|
|
152
|
+
### 4. Global State & Declarative Store Actions (No-JS Actions!)
|
|
153
|
+
Manage local stores and run complex calculations, conditions, and toggles **directly in HTML attributes**:
|
|
82
154
|
|
|
83
|
-
### Pushing value changes
|
|
84
155
|
```html
|
|
85
|
-
|
|
156
|
+
<!-- 1. Auto-sync inputs directly into store key 'app.username' -->
|
|
157
|
+
<input data-store-write="app.username" placeholder="Type name..." />
|
|
158
|
+
<h3 data-store-read="app.username"></h3>
|
|
159
|
+
|
|
160
|
+
<!-- 2. Pure HTML Mathematical Counter -->
|
|
161
|
+
<div class="counter-box">
|
|
162
|
+
<div class="counter-value" data-store-read="app.count">0</div>
|
|
163
|
+
<!-- Updates state directly in HTML click. Dolphin updates the UI dynamically! -->
|
|
164
|
+
<button data-store-click="app.count = (app.count || 0) + 1">+</button>
|
|
165
|
+
<button data-store-click="app.count = (app.count || 0) - 1">-</button>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<!-- 3. Complex Calculations (e.g. Area & Billing) -->
|
|
169
|
+
<button data-store-click="
|
|
170
|
+
app.area = 3.14159 * (app.radius * app.radius);
|
|
171
|
+
app.circumference = 2 * 3.14159 * app.radius
|
|
172
|
+
">
|
|
173
|
+
Calculate Circle
|
|
174
|
+
</button>
|
|
175
|
+
|
|
176
|
+
<!-- 4. Logic Toggles (e.g. Dark Mode Toggle) -->
|
|
177
|
+
<button data-store-click="app.darkMode = !app.darkMode">Toggle Dark Mode</button>
|
|
86
178
|
```
|
|
87
179
|
|
|
88
|
-
###
|
|
180
|
+
### 5. Silent Zero-Configuration Auto-Initialization
|
|
181
|
+
When loaded via a standard `<script>` tag in browser environments, Dolphin Client automatically boots up a default client instance as `window.dolphin` on `DOMContentLoaded`.
|
|
182
|
+
|
|
183
|
+
For debugging, pass `data-debug="true"` on your script tag to turn on gorgeous, color-coded logging in your developer console for all API calls, WebSocket events, and Store updates:
|
|
89
184
|
```html
|
|
90
|
-
<
|
|
91
|
-
<div data-rt-type="context">
|
|
92
|
-
<h3>{{id}}</h3>
|
|
93
|
-
<button onclick="dialPeer('{{id}}')">Dial</button>
|
|
94
|
-
</div>
|
|
95
|
-
'></div>
|
|
185
|
+
<script src="js/dolphin-client.js" data-debug="true"></script>
|
|
96
186
|
```
|
|
97
187
|
|
|
188
|
+
---
|
|
189
|
+
|
|
98
190
|
## License
|
|
99
191
|
|
|
100
192
|
ISC License
|
package/dist/dolphin-client.js
CHANGED
|
@@ -108,6 +108,9 @@ var DolphinModule = (() => {
|
|
|
108
108
|
async requestDirect(method, path, body = null, options = {}) {
|
|
109
109
|
const _isRetry = options._isRetry === true;
|
|
110
110
|
const url = `${this.client.httpUrl}${path.startsWith("/") ? path : "/" + path}`;
|
|
111
|
+
if (this.client.options.debug) {
|
|
112
|
+
console.log(`%c\u{1F680} [Dolphin API Request]:`, "color: #3b82f6; font-weight: bold;", method.toUpperCase(), path, body || "");
|
|
113
|
+
}
|
|
111
114
|
const controller = new AbortController();
|
|
112
115
|
const timeoutId = setTimeout(
|
|
113
116
|
() => controller.abort(),
|
|
@@ -140,6 +143,9 @@ var DolphinModule = (() => {
|
|
|
140
143
|
const contentType = response.headers.get("content-type") || "";
|
|
141
144
|
const data = contentType.includes("application/json") ? await response.json() : await response.text();
|
|
142
145
|
if (!response.ok) throw { status: response.status, data };
|
|
146
|
+
if (this.client.options.debug) {
|
|
147
|
+
console.log(`%c\u2705 [Dolphin API Success]:`, "color: #10b981; font-weight: bold;", method.toUpperCase(), path, data);
|
|
148
|
+
}
|
|
143
149
|
if (data && typeof data === "object") {
|
|
144
150
|
if (data.accessToken) {
|
|
145
151
|
this.client.setToken(data.accessToken);
|
|
@@ -153,6 +159,9 @@ var DolphinModule = (() => {
|
|
|
153
159
|
return data;
|
|
154
160
|
} catch (err) {
|
|
155
161
|
clearTimeout(timeoutId);
|
|
162
|
+
if (this.client.options.debug) {
|
|
163
|
+
console.error(`%c\u274C [Dolphin API Error]:`, "color: #ef4444; font-weight: bold;", method.toUpperCase(), path, err);
|
|
164
|
+
}
|
|
156
165
|
if (err.name === "AbortError") {
|
|
157
166
|
throw { status: 408, data: { error: "Request timed out" } };
|
|
158
167
|
}
|
|
@@ -437,6 +446,7 @@ var DolphinModule = (() => {
|
|
|
437
446
|
// 64 KB
|
|
438
447
|
maxReconnect: 5,
|
|
439
448
|
autoRefreshToken: true,
|
|
449
|
+
debug: false,
|
|
440
450
|
...options
|
|
441
451
|
};
|
|
442
452
|
this.socket = null;
|
|
@@ -519,6 +529,9 @@ var DolphinModule = (() => {
|
|
|
519
529
|
_handleMessage(data) {
|
|
520
530
|
try {
|
|
521
531
|
const msg = JSON.parse(data);
|
|
532
|
+
if (this.options.debug) {
|
|
533
|
+
console.log("%c\u{1F4E5} [Dolphin WS Incoming]:", "color: #eab308; font-weight: bold;", msg);
|
|
534
|
+
}
|
|
522
535
|
if (msg.type && msg.from && (msg.to === this.deviceId || msg.to === "all")) {
|
|
523
536
|
if (msg.msgId && msg.type !== "ACK") this._sendAck(msg.from, msg.msgId);
|
|
524
537
|
this.signalHandlers.forEach((h) => h(msg));
|
|
@@ -556,6 +569,9 @@ var DolphinModule = (() => {
|
|
|
556
569
|
}
|
|
557
570
|
/** @private */
|
|
558
571
|
_sendRaw(msg) {
|
|
572
|
+
if (this.options.debug) {
|
|
573
|
+
console.log("%c\u{1F4E4} [Dolphin WS Outgoing]:", "color: #8b5cf6; font-weight: bold;", msg);
|
|
574
|
+
}
|
|
559
575
|
const str = typeof msg === "string" ? msg : JSON.stringify(msg);
|
|
560
576
|
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
|
561
577
|
this.socket.send(str);
|
|
@@ -788,6 +804,108 @@ var DolphinModule = (() => {
|
|
|
788
804
|
}
|
|
789
805
|
return template;
|
|
790
806
|
}
|
|
807
|
+
function renderTemplate(templateStr, context) {
|
|
808
|
+
if (!templateStr.includes("{#if") && !templateStr.includes("{#each")) {
|
|
809
|
+
let result = templateStr;
|
|
810
|
+
for (let key in context) {
|
|
811
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
812
|
+
result = result.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
|
|
813
|
+
}
|
|
814
|
+
return result;
|
|
815
|
+
}
|
|
816
|
+
try {
|
|
817
|
+
const escapeString = (str) => {
|
|
818
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
819
|
+
};
|
|
820
|
+
let compiled = 'let out = "";\n';
|
|
821
|
+
let lastIndex = 0;
|
|
822
|
+
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;
|
|
823
|
+
const eachStack = [];
|
|
824
|
+
let match;
|
|
825
|
+
while ((match = regex.exec(templateStr)) !== null) {
|
|
826
|
+
const plainText = templateStr.slice(lastIndex, match.index);
|
|
827
|
+
if (plainText) {
|
|
828
|
+
compiled += `out += "${escapeString(plainText)}";
|
|
829
|
+
`;
|
|
830
|
+
}
|
|
831
|
+
const token = match[0];
|
|
832
|
+
if (token.startsWith("{{")) {
|
|
833
|
+
const expr = match[2];
|
|
834
|
+
compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
|
|
835
|
+
`;
|
|
836
|
+
} else if (token.startsWith("{#if")) {
|
|
837
|
+
const expr = match[3];
|
|
838
|
+
compiled += `if (${expr}) {
|
|
839
|
+
`;
|
|
840
|
+
} else if (token.startsWith("{:else if")) {
|
|
841
|
+
const expr = match[4];
|
|
842
|
+
compiled += `} else if (${expr}) {
|
|
843
|
+
`;
|
|
844
|
+
} else if (token.startsWith("{:else}")) {
|
|
845
|
+
compiled += `} else {
|
|
846
|
+
`;
|
|
847
|
+
} else if (token.startsWith("{/if}")) {
|
|
848
|
+
compiled += `}
|
|
849
|
+
`;
|
|
850
|
+
} else if (token.startsWith("{#each")) {
|
|
851
|
+
const expr = match[5];
|
|
852
|
+
const itemVar = match[6];
|
|
853
|
+
const indexVar = match[7];
|
|
854
|
+
eachStack.push({ indexVar });
|
|
855
|
+
compiled += `if (typeof ${expr} !== "undefined" && ${expr} !== null && Array.isArray(${expr})) {
|
|
856
|
+
`;
|
|
857
|
+
if (indexVar) {
|
|
858
|
+
compiled += ` let ${indexVar} = 0;
|
|
859
|
+
`;
|
|
860
|
+
}
|
|
861
|
+
compiled += ` for (let ${itemVar} of ${expr}) {
|
|
862
|
+
`;
|
|
863
|
+
} else if (token.startsWith("{/each}")) {
|
|
864
|
+
const info = eachStack.pop();
|
|
865
|
+
if (info && info.indexVar) {
|
|
866
|
+
compiled += ` ${info.indexVar}++;
|
|
867
|
+
`;
|
|
868
|
+
}
|
|
869
|
+
compiled += ` }
|
|
870
|
+
}
|
|
871
|
+
`;
|
|
872
|
+
} else if (token.startsWith("{")) {
|
|
873
|
+
const expr = match[8];
|
|
874
|
+
if (expr) {
|
|
875
|
+
compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
|
|
876
|
+
`;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
lastIndex = regex.lastIndex;
|
|
880
|
+
}
|
|
881
|
+
const remaining = templateStr.slice(lastIndex);
|
|
882
|
+
if (remaining) {
|
|
883
|
+
compiled += `out += "${escapeString(remaining)}";
|
|
884
|
+
`;
|
|
885
|
+
}
|
|
886
|
+
compiled += "return out;\n";
|
|
887
|
+
const fnBody = `
|
|
888
|
+
with (context) {
|
|
889
|
+
try {
|
|
890
|
+
${compiled}
|
|
891
|
+
} catch (innerErr) {
|
|
892
|
+
console.warn('[Dolphin Template Eval Warning]:', innerErr);
|
|
893
|
+
return '';
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
`;
|
|
897
|
+
const fn = new Function("context", fnBody);
|
|
898
|
+
return fn(context);
|
|
899
|
+
} catch (e) {
|
|
900
|
+
console.error("[Dolphin Template Compiler Error]:", e);
|
|
901
|
+
let fallback = templateStr;
|
|
902
|
+
for (let key in context) {
|
|
903
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
904
|
+
fallback = fallback.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
|
|
905
|
+
}
|
|
906
|
+
return fallback;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
791
909
|
function sanitizeHTML(html) {
|
|
792
910
|
if (typeof document === "undefined") return html;
|
|
793
911
|
try {
|
|
@@ -911,6 +1029,9 @@ var DolphinModule = (() => {
|
|
|
911
1029
|
}
|
|
912
1030
|
const store = this.uiStores.get(storeName);
|
|
913
1031
|
store[key] = val;
|
|
1032
|
+
if (this.options.debug) {
|
|
1033
|
+
console.log(`%c\u{1F4BE} [Dolphin Store Update]:`, "color: #ec4899; font-weight: bold;", `${storeName}.${key}`, "=", val);
|
|
1034
|
+
}
|
|
914
1035
|
if (typeof document !== "undefined") {
|
|
915
1036
|
const readElements = document.querySelectorAll(`[data-store-read="${storeName}.${key}"]`);
|
|
916
1037
|
readElements.forEach((el) => {
|
|
@@ -990,6 +1111,42 @@ var DolphinModule = (() => {
|
|
|
990
1111
|
}
|
|
991
1112
|
return null;
|
|
992
1113
|
};
|
|
1114
|
+
clientProto._executeStoreAction = function(expression, element) {
|
|
1115
|
+
this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
|
|
1116
|
+
const context = new Proxy({}, {
|
|
1117
|
+
has: (target, prop) => {
|
|
1118
|
+
return true;
|
|
1119
|
+
},
|
|
1120
|
+
get: (target, prop) => {
|
|
1121
|
+
if (typeof prop === "string") {
|
|
1122
|
+
return new Proxy({}, {
|
|
1123
|
+
get: (subTarget, subProp) => {
|
|
1124
|
+
if (typeof subProp === "string") {
|
|
1125
|
+
return this.getStoreState(prop, subProp);
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
set: (subTarget, subProp, val) => {
|
|
1129
|
+
if (typeof subProp === "string") {
|
|
1130
|
+
this.setStoreState(prop, subProp, val);
|
|
1131
|
+
return true;
|
|
1132
|
+
}
|
|
1133
|
+
return false;
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
try {
|
|
1140
|
+
const fn = new Function("ctx", `with(ctx) { ${expression} }`);
|
|
1141
|
+
fn(context);
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
console.error("%c[Dolphin Store Action Error]:", "color: #ef4444; font-weight: bold;", err);
|
|
1144
|
+
if (element) {
|
|
1145
|
+
console.error("%cFailed Element:", "color: #f97316; font-weight: bold;", element);
|
|
1146
|
+
}
|
|
1147
|
+
console.error("%cFailed Expression:", "color: #3b82f6; font-style: italic;", expression);
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
993
1150
|
clientProto._initDOMBinding = function() {
|
|
994
1151
|
if (this._domInitialized) return;
|
|
995
1152
|
this._domInitialized = true;
|
|
@@ -1163,6 +1320,14 @@ var DolphinModule = (() => {
|
|
|
1163
1320
|
console.error(`[Dolphin] API ${evtName} Error:`, err);
|
|
1164
1321
|
}
|
|
1165
1322
|
}
|
|
1323
|
+
const storeActionBtn = e.target.closest(`[data-store-${evtName}]`);
|
|
1324
|
+
if (storeActionBtn) {
|
|
1325
|
+
if (evtName === "submit") e.preventDefault();
|
|
1326
|
+
const expr = storeActionBtn.getAttribute(`data-store-${evtName}`);
|
|
1327
|
+
if (expr) {
|
|
1328
|
+
this._executeStoreAction(expr, storeActionBtn);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1166
1331
|
});
|
|
1167
1332
|
});
|
|
1168
1333
|
this.subscribe("#", (payload, topic) => {
|
|
@@ -1188,21 +1353,11 @@ var DolphinModule = (() => {
|
|
|
1188
1353
|
if (Array.isArray(result)) {
|
|
1189
1354
|
let combinedHTML = "";
|
|
1190
1355
|
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;
|
|
1356
|
+
combinedHTML += renderTemplate(template, item);
|
|
1197
1357
|
}
|
|
1198
1358
|
scheduleDOMUpdate(el, combinedHTML);
|
|
1199
1359
|
} 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);
|
|
1360
|
+
scheduleDOMUpdate(el, renderTemplate(template, result));
|
|
1206
1361
|
}
|
|
1207
1362
|
} else {
|
|
1208
1363
|
if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
|
|
@@ -1296,21 +1451,11 @@ var DolphinModule = (() => {
|
|
|
1296
1451
|
if (Array.isArray(payload)) {
|
|
1297
1452
|
let combinedHTML = "";
|
|
1298
1453
|
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;
|
|
1454
|
+
combinedHTML += renderTemplate(template, item);
|
|
1305
1455
|
}
|
|
1306
1456
|
scheduleDOMUpdate(el, combinedHTML);
|
|
1307
1457
|
} 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);
|
|
1458
|
+
scheduleDOMUpdate(el, renderTemplate(template, payload));
|
|
1314
1459
|
}
|
|
1315
1460
|
return;
|
|
1316
1461
|
}
|
|
@@ -2037,6 +2182,21 @@ var DolphinModule = (() => {
|
|
|
2037
2182
|
attachTesting(DolphinClient.prototype);
|
|
2038
2183
|
if (typeof window !== "undefined") {
|
|
2039
2184
|
window.DolphinClient = DolphinClient;
|
|
2185
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
2186
|
+
if (!window.dolphin) {
|
|
2187
|
+
const scriptEl = document.querySelector('script[src*="dolphin-client"]');
|
|
2188
|
+
const debugMode = scriptEl ? scriptEl.getAttribute("data-debug") === "true" : false;
|
|
2189
|
+
const dolphin = new DolphinClient(void 0, void 0, { debug: debugMode });
|
|
2190
|
+
window.dolphin = dolphin;
|
|
2191
|
+
if (debugMode) {
|
|
2192
|
+
console.log("%c\u{1F42C} [Dolphin Client] Auto-initialized local reactive engine!", "color: #06b6d4; font-weight: bold; font-size: 14px;");
|
|
2193
|
+
console.log('%c\u{1F449} Tip: You can access the client instance via "window.dolphin" in console.', "color: #94a3b8; font-style: italic;");
|
|
2194
|
+
}
|
|
2195
|
+
if (document.querySelector('[data-store-write="app.username"]')) {
|
|
2196
|
+
dolphin.setStoreState("app", "username", "\u0928\u092E\u0938\u094D\u0924\u0947 \u0938\u093E\u0925\u0940!");
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
});
|
|
2040
2200
|
}
|
|
2041
2201
|
return __toCommonJS(index_exports);
|
|
2042
2202
|
})();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
var DolphinModule=(()=>{var $=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var V=Object.prototype.hasOwnProperty;var J=(p,t)=>{for(var e in t)$(p,e,{get:t[e],enumerable:!0})},Q=(p,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of K(t))!V.call(p,r)&&r!==e&&$(p,r,{get:()=>t[r],enumerable:!(n=z(t,r))||n.enumerable});return p};var X=p=>Q($({},"__esModule",{value:!0}),p);var Z={};J(Z,{DolphinClient:()=>v});var x=class{client;constructor(t){return this.client=t,this._createProxy([])}_createProxy(t){let e=t.join("/"),n=i=>this.request("GET",e,null,i);n.get=(i,a)=>typeof i=="string"?this.request("GET",i,null,a):this.request("GET",e,null,i),n.post=(i,a,l)=>typeof i=="string"?this.request("POST",i,a,l):this.request("POST",e,i,a),n.put=(i,a,l)=>typeof i=="string"?this.request("PUT",i,a,l):this.request("PUT",e,i,a),n.patch=(i,a,l)=>typeof i=="string"?this.request("PATCH",i,a,l):this.request("PATCH",e,i,a),n.del=(i,a)=>typeof i=="string"?this.request("DELETE",i,null,a):this.request("DELETE",e,null,i),n.request=(i,a,l,b)=>{let T=a?`${e}/${a.startsWith("/")?a.slice(1):a}`:e;return this.request(i,T,l,b)},n.requestDirect=(i,a,l,b)=>this.requestDirect(i,a,l,b);let r=["get","post","put","patch","del","request","requestDirect"];return new Proxy(n,{get:(i,a)=>typeof a=="string"&&!r.includes(a)?this._createProxy([...t,a]):i[a]})}async request(t,e,n=null,r={}){if(this.client.offline){let i=this.client.offline.isOnline,a=`${t.toUpperCase()}:${e}`;if(t.toUpperCase()==="GET")if(i)try{let l=await this.requestDirect(t,e,n,r);return await this.client.offline.setCache(a,l),l}catch(l){let b=await this.client.offline.getCache(a);if(b!=null)return b;throw l}else{let l=await this.client.offline.getCache(a);if(l!=null)return l;throw{status:503,data:{error:"Offline, no cache available"}}}else return i?this.requestDirect(t,e,n,r):(await this.client.offline.queueMutation(t,e,n),this.client._dispatch("offline:queued",{method:t,path:e,body:n}),{success:!0,offline:!0,message:"Mutation queued offline"})}return this.requestDirect(t,e,n,r)}async requestDirect(t,e,n=null,r={}){let i=r._isRetry===!0,a=`${this.client.httpUrl}${e.startsWith("/")?e:"/"+e}`;this.client.options.debug&&console.log("%c\u{1F680} [Dolphin API Request]:","color: #3b82f6; font-weight: bold;",t.toUpperCase(),e,n||"");let l=new AbortController,b=setTimeout(()=>l.abort(),this.client.options.timeout),T={"Content-Type":"application/json",...r.headers||{}};this.client.accessToken&&(T.Authorization=`Bearer ${this.client.accessToken}`);let h={...r};delete h._isRetry;try{let o=await fetch(a,{method:t,headers:T,signal:l.signal,...n?{body:JSON.stringify(n)}:{},...h});if(clearTimeout(b),o.status===401&&!i&&this.client.options.autoRefreshToken&&await this.client.auth._silentRefresh())return this.request(t,e,n,{...r,_isRetry:!0});let s=(o.headers.get("content-type")||"").includes("application/json")?await o.json():await o.text();if(!o.ok)throw{status:o.status,data:s};if(this.client.options.debug&&console.log("%c\u2705 [Dolphin API Success]:","color: #10b981; font-weight: bold;",t.toUpperCase(),e,s),s&&typeof s=="object"&&s.accessToken&&(this.client.setToken(s.accessToken),s.user&&(this.client.auth.user=s.user)),this.client.options.autoBroadcast&&["POST","PUT","PATCH","DELETE"].includes(t.toUpperCase())){let c=e.startsWith("/")?e.substring(1):e;this.client.publish(c,{method:t.toUpperCase(),payload:n,result:s})}return s}catch(o){throw clearTimeout(b),this.client.options.debug&&console.error("%c\u274C [Dolphin API Error]:","color: #ef4444; font-weight: bold;",t.toUpperCase(),e,o),o.name==="AbortError"?{status:408,data:{error:"Request timed out"}}:o}}};var C=class{client;user;_refreshing;constructor(t){this.client=t,this.user=null,this._refreshing=!1}async login(t,e){let n=await this.client.api.post("/api/auth/login",{email:t,password:e});return n.accessToken&&(this.client.setToken(n.accessToken),this.user=n.user||null),n}async register(t){return this.client.api.post("/api/auth/register",t)}async me(){let t=await this.client.api.get("/api/auth/me");return t.success&&(this.user=t.data),t}async logout(){try{await this.client.api.post("/api/auth/logout")}catch{}this.client.setToken(null),this.user=null}async refresh(){return this._silentRefresh()}async _silentRefresh(){if(this._refreshing)return!1;this._refreshing=!0;try{let t=await this.client.api.post("/api/auth/refresh",null,{_isRetry:!0});return t.accessToken?(this.client.setToken(t.accessToken),!0):!1}catch{return this.client.setToken(null),!1}finally{this._refreshing=!1}}async verify2FA(t,e){let n={code:t,email:e||this.user?.email},r=await this.client.api.post("/api/auth/2fa/verify",n);return r.accessToken&&(this.client.setToken(r.accessToken),r.user&&(this.user=r.user)),r}async enable2FA(){return this.client.api.post("/api/auth/2fa/enable")}async disable2FA(t){return this.client.api.post("/api/auth/2fa/disable",{code:t})}async forgotPassword(t){return this.client.api.post("/api/auth/forgot-password",{email:t})}async resetPassword(t,e){return this.client.api.post("/api/auth/reset-password",{token:t,newPassword:e})}};var M=class{client;data;listeners;subscribed;constructor(t){return this.client=t,this.data=new Map,this.listeners=new Set,this.subscribed=new Set,new Proxy(this,{get:(e,n)=>{if(n in e)return e[n];if(typeof n=="string")return this._getCollection(n)}})}_getCollection(t){if(!this.data.has(t)){let e={_rawItems:[],items:[],loading:!0,error:null,success:!1,_filter:null,_sort:null,where:n=>(e._filter=n,this._applyTransform(e),e),orderBy:(n,r="asc")=>(e._sort={key:n,direction:r},this._applyTransform(e),e),reset:()=>(e._filter=null,e._sort=null,this._applyTransform(e),e)};this.data.set(t,e),this._fetchAndSync(t)}return this.data.get(t)}async _fetchAndSync(t){let e=this.data.get(t);try{let n=await this.client.api.get(`/${t.toLowerCase()}`);e._rawItems=Array.isArray(n)?n:n.data||[],e.loading=!1,e.success=!0,e.error=null,this._applyTransform(e),this.subscribed.has(t)||(this.client.subscribe(`db:sync/${t.toLowerCase()}`,r=>{this._handleRemoteUpdate(t,r)}),this.subscribed.add(t))}catch(n){e.loading=!1,e.success=!1,e.error=n.data?.error||n.message||"Fetch failed",this._notify()}}_applyTransform(t){let e=[...t._rawItems];if(t._filter&&(e=e.filter(t._filter)),t._sort){let{key:n,direction:r}=t._sort;e.sort((i,a)=>i[n]===a[n]?0:(i[n]>a[n]?1:-1)*(r==="asc"?1:-1))}t.items=e,this._notify()}_handleRemoteUpdate(t,e){let n=this.data.get(t);if(!n)return;let{type:r,data:i}=e,a=n._rawItems;r==="create"?a=[...a,i]:r==="update"?a=a.map(l=>l.id===i.id||l._id===i._id?{...l,...i}:l):r==="delete"&&(a=a.filter(l=>!(i.id!=null&&l.id===i.id||i._id!=null&&l._id===i._id))),n._rawItems=a,this._applyTransform(n)}subscribe(t){return this.listeners.add(t),()=>this.listeners.delete(t)}getSnapshot(t){return this.data.get(t)||{items:[],loading:!1,error:null,success:!1}}_notify(){this.listeners.forEach(t=>t())}};var v=class{host;httpUrl;deviceId;options;socket;storage;accessToken;api;auth;store;handlers;signalHandlers;fileHandlers;_offlineQueue;reconnectAttempts;_attachedListeners;constructor(t="",e="",n={}){!t&&typeof window<"u"&&(t=window.location.host);let r="http:";t.startsWith("https://")?r="https:":t.startsWith("http://")?r="http:":typeof window<"u"&&(r=window.location.protocol),this.host=(t||"localhost").replace(/\/$/,"").replace(/^https?:\/\//,""),this.httpUrl=`${r}//${this.host}`,this.deviceId=e||"web_"+Math.random().toString(36).substr(2,8),this.options={timeout:15e3,chunkSize:65536,maxReconnect:5,autoRefreshToken:!0,debug:!1,...n},this.socket=null,this.storage=typeof localStorage<"u"?localStorage:{getItem:()=>null,setItem:()=>{},removeItem:()=>{}},this.accessToken=this.storage.getItem("dolphin_token"),this.api=new x(this),this.auth=new C(this),this.store=new M(this),this.handlers=new Map,this.signalHandlers=new Set,this.fileHandlers=new Set,this._offlineQueue=[],this.reconnectAttempts=0,this._attachedListeners=[],typeof window<"u"&&typeof this._initDOMBinding=="function"&&this._initDOMBinding(),typeof this._initOffline=="function"&&this._initOffline(),typeof this._initA11y=="function"&&this._initA11y(),typeof this._initI18n=="function"&&this._initI18n(),typeof this._initDragDrop=="function"&&this._initDragDrop(),typeof this._initCollab=="function"&&this._initCollab()}setToken(t){this.accessToken=t,t?this.storage.setItem("dolphin_token",t):this.storage.removeItem("dolphin_token")}async connect(){return new Promise((t,e)=>{let r=`${this.httpUrl.startsWith("https")?"wss:":"ws:"}//${this.host}/realtime?deviceId=${this.deviceId}`;console.log(`[Dolphin] Connecting to ${r}...`),this.socket=new WebSocket(r),this.socket.onopen=()=>{console.log(`[Dolphin] Connected as "${this.deviceId}" \u{1F42C}`),this.reconnectAttempts=0,this._flushOfflineQueue(),t()},this.socket.onmessage=i=>this._handleMessage(i.data),this.socket.onclose=()=>{console.warn("[Dolphin] Connection closed"),this._maybeReconnect()},this.socket.onerror=i=>{console.error("[Dolphin] WebSocket error:",i),e(i)}})}disconnect(){this.socket&&(this.socket.onclose=null,this.socket.close(),this.socket=null),this.cleanupDomListeners()}_handleMessage(t){try{let e=JSON.parse(t);this.options.debug&&console.log("%c\u{1F4E5} [Dolphin WS Incoming]:","color: #eab308; font-weight: bold;",e),e.type&&e.from&&(e.to===this.deviceId||e.to==="all")&&(e.msgId&&e.type!=="ACK"&&this._sendAck(e.from,e.msgId),this.signalHandlers.forEach(n=>n(e))),e.type==="FILE_AVAILABLE"&&this.fileHandlers.forEach(n=>n(e)),e.type==="FILE_CHUNK"&&(this.saveFileProgress(e.fileId,e.chunkIndex),this._dispatch("file:chunk",e),this._dispatch(`file:chunk/${e.fileId}`,e)),e.type==="FILE_UPLOAD_ACK"&&this._dispatch(`file:upload:ack/${e.fileId}`,e),e.type==="PULL_RESPONSE"&&(this._dispatch("pull:response",e.payload,e.topic),this._dispatch(`pull:response/${e.topic}`,e.payload,e.topic)),e.topic&&e.payload!==void 0&&this.handlers.forEach((n,r)=>{this._matchTopic(r,e.topic)&&n.forEach(i=>i(e.payload,e.topic))})}catch{this._dispatch("raw",t)}}_dispatch(t,e,n){let r=this.handlers.get(t);r&&r.forEach(i=>i(e,n||t))}_sendRaw(t){this.options.debug&&console.log("%c\u{1F4E4} [Dolphin WS Outgoing]:","color: #8b5cf6; font-weight: bold;",t);let e=typeof t=="string"?t:JSON.stringify(t);this.socket&&this.socket.readyState===WebSocket.OPEN?this.socket.send(e):this._offlineQueue.length<100&&this._offlineQueue.push(e)}_flushOfflineQueue(){for(;this._offlineQueue.length>0;){let t=this._offlineQueue.shift();this.socket&&this.socket.readyState===WebSocket.OPEN&&this.socket.send(t)}}_sendAck(t,e){this._sendRaw({type:"ACK",from:this.deviceId,to:t,data:{ackId:e},timestamp:Date.now()})}_matchTopic(t,e){if(t===e||t==="#")return!0;let n=t.split("/"),r=e.split("/");if(n.length!==r.length&&!t.includes("#"))return!1;for(let i=0;i<n.length;i++){if(n[i]==="#")return!0;if(n[i]!=="+"&&n[i]!==r[i])return!1}return n.length===r.length}_maybeReconnect(){if(this.reconnectAttempts<this.options.maxReconnect){this.reconnectAttempts++;let t=Math.pow(2,this.reconnectAttempts)*1e3;console.log(`[Dolphin] Reconnecting in ${t/1e3}s (attempt ${this.reconnectAttempts})...`),setTimeout(()=>this.connect().catch(()=>{}),t)}else console.error("[Dolphin] Max reconnect attempts reached.")}subscribe(t,e){this.handlers.has(t)||(this.handlers.set(t,new Set),this._sendRaw({type:"sub",topic:t})),this.handlers.get(t).add(e)}unsubscribe(t,e){if(this.handlers.has(t)){let n=this.handlers.get(t);n.delete(e),n.size===0&&(this.handlers.delete(t),this._sendRaw({type:"unsub",topic:t}))}}publish(t,e){this._sendRaw({topic:t,payload:e})}pubPush(t,e){this._sendRaw({type:"pub",topic:t,payload:e})}subPull(t,e=10){this._sendRaw({type:"PULL_REQUEST",topic:t,count:e})}async pubFile(t,e,n="",r){let i;e instanceof Blob?i=await e.arrayBuffer():e instanceof ArrayBuffer?i=e:i=e.buffer||e;let a=new Uint8Array(i),l=this.options.chunkSize,b=Math.ceil(a.length/l);this._sendRaw({type:"FILE_UPLOAD_START",fileId:t,name:n,size:a.length,totalChunks:b,chunkSize:l});for(let T=0;T<b;T++){let h=a.slice(T*l,(T+1)*l),o=this._uint8ToBase64(h);this._sendRaw({type:"FILE_UPLOAD_CHUNK",fileId:t,chunkIndex:T,totalChunks:b,data:o}),r&&r(Math.round((T+1)/b*100)),T%10===0&&await new Promise(u=>setTimeout(u,0))}this._sendRaw({type:"FILE_UPLOAD_DONE",fileId:t})}_uint8ToBase64(t){let e="";for(let n=0;n<t.length;n++)e+=String.fromCharCode(t[n]);return typeof btoa<"u"?btoa(e):Buffer.from(e,"binary").toString("base64")}subFile(t,e=0){this._sendRaw({type:"FILE_REQUEST",fileId:t,startChunk:e})}resumeFile(t){let e=parseInt(this.storage.getItem(`dolphin_file_${t}`)||"-1");this.subFile(t,e+1)}saveFileProgress(t,e){this.storage.setItem(`dolphin_file_${t}`,e.toString())}onSignal(t){this.signalHandlers.add(t)}offSignal(t){this.signalHandlers.delete(t)}onFileAvailable(t){this.fileHandlers.add(t)}offFileAvailable(t){this.fileHandlers.delete(t)}addDomListener(t,e,n){t&&(t.addEventListener(e,n),this._attachedListeners=this._attachedListeners||[],this._attachedListeners.push({target:t,event:e,cb:n}))}cleanupDomListeners(){this._attachedListeners&&(this._attachedListeners.forEach(({target:t,event:e,cb:n})=>{try{t.removeEventListener(e,n)}catch{}}),this._attachedListeners=[])}};function R(p){function t(h){return h.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function e(h){let o=h.getAttribute("data-rt-template");if(!o)return null;if(typeof document<"u"&&!o.includes("<"))try{let u=document.querySelector(o);if(u)return u.innerHTML}catch{}return o}function n(h,o){if(!h.includes("{#if")&&!h.includes("{#each")){let u=h;for(let s in o){let c=s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");u=u.replace(new RegExp(`\\{\\{${c}\\}\\}`,"g"),o[s]!==void 0&&o[s]!==null?o[s]:"")}return u}try{let u=g=>g.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t"),s=`let out = "";
|
|
2
|
+
`,c=0,m=/(\{\{([\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,f=[],d;for(;(d=m.exec(h))!==null;){let g=h.slice(c,d.index);g&&(s+=`out += "${u(g)}";
|
|
3
|
+
`);let w=d[0];if(w.startsWith("{{")){let A=d[2];s+=`out += (${A} !== undefined && ${A} !== null ? ${A} : "");
|
|
4
|
+
`}else if(w.startsWith("{#if")){let A=d[3];s+=`if (${A}) {
|
|
5
|
+
`}else if(w.startsWith("{:else if")){let A=d[4];s+=`} else if (${A}) {
|
|
6
|
+
`}else if(w.startsWith("{:else}"))s+=`} else {
|
|
7
|
+
`;else if(w.startsWith("{/if}"))s+=`}
|
|
8
|
+
`;else if(w.startsWith("{#each")){let A=d[5],D=d[6],S=d[7];f.push({indexVar:S}),s+=`if (typeof ${A} !== "undefined" && ${A} !== null && Array.isArray(${A})) {
|
|
9
|
+
`,S&&(s+=` let ${S} = 0;
|
|
10
|
+
`),s+=` for (let ${D} of ${A}) {
|
|
11
|
+
`}else if(w.startsWith("{/each}")){let A=f.pop();A&&A.indexVar&&(s+=` ${A.indexVar}++;
|
|
12
|
+
`),s+=` }
|
|
13
|
+
}
|
|
14
|
+
`}else if(w.startsWith("{")){let A=d[8];A&&(s+=`out += (${A} !== undefined && ${A} !== null ? ${A} : "");
|
|
15
|
+
`)}c=m.lastIndex}let y=h.slice(c);y&&(s+=`out += "${u(y)}";
|
|
16
|
+
`),s+=`return out;
|
|
17
|
+
`;let _=`
|
|
18
|
+
with (context) {
|
|
19
|
+
try {
|
|
20
|
+
${s}
|
|
21
|
+
} catch (innerErr) {
|
|
22
|
+
console.warn('[Dolphin Template Eval Warning]:', innerErr);
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
`;return new Function("context",_)(o)}catch(u){console.error("[Dolphin Template Compiler Error]:",u);let s=h;for(let c in o){let m=c.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");s=s.replace(new RegExp(`\\{\\{${m}\\}\\}`,"g"),o[c]!==void 0&&o[c]!==null?o[c]:"")}return s}}function r(h){if(typeof document>"u")return h;try{let s=new DOMParser().parseFromString(h,"text/html").body,c=m=>{let f=m.tagName.toLowerCase();if(["script","iframe","object","embed","link","style","meta","applet","svg"].includes(f)){m.parentNode?.removeChild(m);return}let d=m.attributes;for(let y=d.length-1;y>=0;y--){let _=d[y].name.toLowerCase(),E=d[y].value.toLowerCase();(_.startsWith("on")||["src","href","data"].includes(_)&&(E.includes("javascript:")||E.includes("data:text/html")))&&m.removeAttribute(d[y].name)}Array.from(m.children).forEach(c)};return Array.from(s.children).forEach(c),s.innerHTML}catch{return h}}function i(h,o){if(h.nodeType!==o.nodeType){h.parentNode?.replaceChild(o.cloneNode(!0),h);return}if(h.nodeType===Node.TEXT_NODE){h.textContent!==o.textContent&&(h.textContent=o.textContent);return}if(h.nodeType===Node.ELEMENT_NODE){let u=h,s=o;if(u.tagName!==s.tagName){u.parentNode?.replaceChild(s.cloneNode(!0),u);return}let c=u.attributes,m=s.attributes;for(let g=c.length-1;g>=0;g--){let w=c[g].name;s.hasAttribute(w)||u.removeAttribute(w)}for(let g=0;g<m.length;g++){let w=m[g].name,A=m[g].value;u.getAttribute(w)!==A&&u.setAttribute(w,A)}u.tagName==="INPUT"||u.tagName==="TEXTAREA"?(u.value!==s.value&&(u.value=s.value),u.checked!==s.checked&&(u.checked=s.checked)):u.tagName==="SELECT"&&u.value!==s.value&&(u.value=s.value);let f=Array.from(u.childNodes),d=Array.from(s.childNodes),y=f.length,_=d.length,E=Math.max(y,_);for(let g=0;g<E;g++)g>=y?u.appendChild(d[g].cloneNode(!0)):g>=_?u.removeChild(f[g]):i(f[g],d[g])}}function a(h,o){if(typeof document>"u")return;let u=document.createElement(h.tagName);u.innerHTML=o;let s=Array.from(h.childNodes),c=Array.from(u.childNodes),m=s.length,f=c.length,d=Math.max(m,f);for(let y=0;y<d;y++)y>=m?h.appendChild(c[y].cloneNode(!0)):y>=f?h.removeChild(s[y]):i(s[y],c[y])}let l=new Map,b=!1;function T(h,o){l.set(h,o),b||(b=!0,(typeof requestAnimationFrame<"u"?requestAnimationFrame:s=>setTimeout(s,0))(()=>{l.forEach((s,c)=>{a(c,s)}),l.clear(),b=!1}))}p.setStoreState=function(h,o,u){this.uiStores=this.uiStores||new Map,this.uiStores.has(h)||this.uiStores.set(h,{});let s=this.uiStores.get(h);s[o]=u,this.options.debug&&console.log("%c\u{1F4BE} [Dolphin Store Update]:","color: #ec4899; font-weight: bold;",`${h}.${o}`,"=",u),typeof document<"u"&&document.querySelectorAll(`[data-store-read="${h}.${o}"]`).forEach(m=>{m.tagName==="INPUT"||m.tagName==="TEXTAREA"?m.type==="checkbox"?m.checked=!!u:m.value=u??"":m.textContent=u??""}),this.publish(`store/${h}`,s)},p.getStoreState=function(h,o){this.uiStores=this.uiStores||new Map;let u=this.uiStores.get(h);return u?u[o]:void 0},p._scanStoreBinds=function(){if(typeof document>"u")return;document.querySelectorAll("[data-store-write]").forEach(u=>{let s=u.getAttribute("data-store-write");if(s){let c=s.split(".");if(c.length===2){let m=c[0],f=c[1],d=u.type==="checkbox"?u.checked:u.value;this.uiStores=this.uiStores||new Map,this.uiStores.has(m)||this.uiStores.set(m,{});let y=this.uiStores.get(m);y[f]===void 0&&(y[f]=d)}}}),document.querySelectorAll("[data-store-read]").forEach(u=>{let s=u.getAttribute("data-store-read");if(s){let c=s.split(".");if(c.length===2){let m=c[0],f=c[1],d=this.getStoreState(m,f);d!=null&&(u.tagName==="INPUT"||u.tagName==="TEXTAREA"?u.type==="checkbox"?u.checked=!!d:u.value=d:u.textContent=d)}}})},p.getClosestContext=function(h,o){let u=h;for(;u;){if(u._rtContext){let s=u._rtContext;return o?s[o]:s}u=u.parentElement}return null},p._executeStoreAction=function(h,o){this.uiStores=this.uiStores||new Map;let u=new Proxy({},{has:(s,c)=>!0,get:(s,c)=>{if(typeof c=="string")return new Proxy({},{get:(m,f)=>{if(typeof f=="string")return this.getStoreState(c,f)},set:(m,f,d)=>typeof f=="string"?(this.setStoreState(c,f,d),!0):!1})}});try{new Function("ctx",`with(ctx) { ${h} }`)(u)}catch(s){console.error("%c[Dolphin Store Action Error]:","color: #ef4444; font-weight: bold;",s),o&&console.error("%cFailed Element:","color: #f97316; font-weight: bold;",o),console.error("%cFailed Expression:","color: #3b82f6; font-style: italic;",h)}},p._initDOMBinding=function(){if(this._domInitialized)return;this._domInitialized=!0;let h=["input","change","keyup","paste","blur"],o=new Map;h.forEach(s=>{this.addDomListener(document,s,c=>{if(!c.target||!c.target.getAttribute)return;let m=c.target.getAttribute("data-store-write");if(m){let _=m.split(".");if(_.length===2){let E=_[0],g=_[1],w=c.target.type==="checkbox"?c.target.checked:c.target.value;this.setStoreState(E,g,w)}}let f=c.target.getAttribute("data-rt-validate"),d=c.target.name;if(f&&d&&typeof this.validateField=="function"){let _=c.target.closest("form"),E=_?Object.fromEntries(new FormData(_).entries()):{},g=this.validateField(c.target.value,f,E);g?(c.target.classList.add("invalid"),this.publish(`errors/${d}`,g)):(c.target.classList.remove("invalid"),this.publish(`errors/${d}`,""))}let y=c.target.getAttribute("data-rt-push");if(y){let _=c.target.getAttribute("data-rt-debounce"),E=_?parseInt(_,10):0,g=()=>{let w={name:c.target.name,value:c.target.value};this.pubPush(y,w)};if(E>0){o.has(c.target)&&clearTimeout(o.get(c.target));let w=setTimeout(g,E);o.set(c.target,w)}else g()}})}),this.addDomListener(document,"submit",async s=>{if(!s.target||!s.target.getAttribute)return;let c=s.target.getAttribute("data-rt-submit"),m=s.target.getAttribute("data-api-submit");if(c||m){let f=s.target.querySelectorAll("[data-rt-validate]"),d=!0;if(f.length>0&&typeof this.validateField=="function"){let g=Object.fromEntries(new FormData(s.target).entries());f.forEach(w=>{let A=w.getAttribute("data-rt-validate"),D=w.name;if(A&&D){let S=this.validateField(w.value,A,g);S?(d=!1,w.classList.add("invalid"),this.publish(`errors/${D}`,S)):(w.classList.remove("invalid"),this.publish(`errors/${D}`,""))}})}if(!d){s.preventDefault(),s.stopPropagation();return}s.preventDefault();let y=this.getClosestContext(s.target)||{},_=new FormData(s.target),E=Object.fromEntries(_.entries());if(c){let g=c;for(let w in y){let A=t(w);g=g.replace(new RegExp(`\\{\\{${A}\\}\\}`,"g"),y[w]!==void 0&&y[w]!==null?y[w]:"")}this.publish(g,E)}else if(m){let g=m;for(let S in y){let k=t(S);g=g.replace(new RegExp(`\\{\\{${k}\\}\\}`,"g"),y[S]!==void 0&&y[S]!==null?y[S]:"")}let w=g.trim().split(" "),A=w.length>1?w[0].toUpperCase():"POST",D=w.length>1?w[1]:w[0];try{let S=await this.api.request(A,D,E),k=s.target.getAttribute("data-api-result");k&&this._updateDOM(k,S);let L=s.target.getAttribute("data-api-redirect");L&&(window.location.href=L),s.target.hasAttribute("data-api-reload")&&window.location.reload()}catch(S){console.error("[Dolphin] API Submit Error:",S)}}}}),["click","change","submit","input","keydown","keyup","dblclick","focus","blur","mouseenter","mouseleave"].forEach(s=>{this.addDomListener(document,s,async c=>{if(!c.target||!c.target.closest)return;let m=c.target.closest(`[data-rt-${s}]`),f=c.target.closest(`[data-api-${s}]`);if(m){s==="submit"&&c.preventDefault();let y=m.getAttribute(`data-rt-${s}`),_=m.getAttribute("data-rt-payload"),E=this.getClosestContext(m)||{},g={};if(_){let w=_;for(let A in E){let D=t(A);w=w.replace(new RegExp(`\\{\\{${D}\\}\\}`,"g"),E[A]!==void 0&&E[A]!==null?E[A]:"")}try{g=JSON.parse(w)}catch{g={}}}this.publish(y,g)}if(f){s==="submit"&&c.preventDefault();let y=f.getAttribute(`data-api-${s}`),_=f.getAttribute("data-api-payload"),E=this.getClosestContext(f)||{},g=y.trim().split(" "),w=g.length>1?g[0].toUpperCase():"POST",A=g.length>1?g[1]:g[0],D=null;if(_){let S=_;for(let k in E){let L=t(k);S=S.replace(new RegExp(`\\{\\{${L}\\}\\}`,"g"),E[k]!==void 0&&E[k]!==null?E[k]:"")}try{D=JSON.parse(S)}catch{D=null}}try{let S=await this.api.request(w,A,D),k=f.getAttribute("data-api-result");k&&this._updateDOM(k,S);let L=f.getAttribute("data-api-redirect");L&&(window.location.href=L),f.hasAttribute("data-api-reload")&&window.location.reload()}catch(S){console.error(`[Dolphin] API ${s} Error:`,S)}}let d=c.target.closest(`[data-store-${s}]`);if(d){s==="submit"&&c.preventDefault();let y=d.getAttribute(`data-store-${s}`);y&&this._executeStoreAction(y,d)}})}),this.subscribe("#",(s,c)=>{this._updateDOM(c,s)}),this._scanAndFetchAPIBinds(),this._scanStoreBinds()},p._scanAndFetchAPIBinds=async function(){if(typeof document>"u")return;let h=document.querySelectorAll("[data-api-get]");for(let o of Array.from(h)){let u=o.getAttribute("data-api-get");if(u)try{let s=await this.api.get(u),c=o.getAttribute("data-rt-bind");if(c)this._updateDOM(c,s);else{let m=e(o);if(m&&typeof s=="object"&&s!==null)if(Array.isArray(s)){let f="";for(let d of s)f+=n(m,d);T(o,f)}else T(o,n(m,s));else o.tagName==="INPUT"||o.tagName==="TEXTAREA"?o.value=typeof s=="object"?s.value!==void 0?s.value:"":s:o.innerHTML=typeof s=="object"?s.html||s.text||JSON.stringify(s):s}}catch(s){console.error("[Dolphin] API Get Error:",s)}}},p._updateDOM=function(h,o){if(typeof document>"u")return;document.querySelectorAll(`[data-rt-bind="${h}"]`).forEach(s=>{if(s.getAttribute("data-rt-type")==="context"&&typeof o=="object"&&o!==null){s._rtContext=o;let m=f=>{if(f.hasAttribute("data-rt-text")){let d=f.getAttribute("data-rt-text");d&&o[d]!==void 0&&o[d]!==null&&(f.textContent=o[d])}if(f.hasAttribute("data-rt-html")){let d=f.getAttribute("data-rt-html");d&&o[d]!==void 0&&o[d]!==null&&(f.innerHTML=r(o[d]))}if(f.hasAttribute("data-rt-attr")){let d=f.getAttribute("data-rt-attr");d&&d.split(",").forEach(y=>{let _=y.split(":");if(_.length===2){let E=_[0].trim(),g=_[1].trim();E&&g&&o[g]!==void 0&&o[g]!==null&&f.setAttribute(E,o[g])}})}if(f.hasAttribute("data-rt-class")){let d=f.getAttribute("data-rt-class");d&&d.split(",").forEach(y=>{let _=y.split(":");if(_.length===2){let E=_[0].trim(),g=_[1].trim();o[g]?f.classList.add(E):f.classList.remove(E)}})}if(f.hasAttribute("data-rt-if")){let d=f.getAttribute("data-rt-if");d&&(o[d]?f.style.display="":f.style.display="none")}if(f.hasAttribute("data-rt-hide")){let d=f.getAttribute("data-rt-hide");d&&(o[d]?f.style.display="none":f.style.display="")}};m(s),s.querySelectorAll("[data-rt-text], [data-rt-html], [data-rt-attr], [data-rt-class], [data-rt-if], [data-rt-hide]").forEach(m);return}let c=e(s);if(c&&typeof o=="object"&&o!==null){if(Array.isArray(o)){let m="";for(let f of o)m+=n(c,f);T(s,m)}else T(s,n(c,o));return}s.tagName==="INPUT"||s.tagName==="TEXTAREA"?s.value=typeof o=="object"?o.value!==void 0?o.value:"":o:s.innerHTML=typeof o=="object"?o.html||o.text||JSON.stringify(o):o})}}var I=class{client;db;isOnline;memoryCache=new Map;memoryMutations=[];constructor(t){this.client=t,this.isOnline=typeof window<"u"&&typeof navigator<"u"?navigator.onLine:!0,this.initDB(),this.setupNetworkListeners()}initDB(){if(!(typeof indexedDB>"u"))try{let t=indexedDB.open("dolphin_offline",1);t.onupgradeneeded=e=>{let n=e.target.result;n.objectStoreNames.contains("cache")||n.createObjectStore("cache"),n.objectStoreNames.contains("mutations")||n.createObjectStore("mutations",{keyPath:"id",autoIncrement:!0})},t.onsuccess=e=>{this.db=e.target.result,this.isOnline&&this.syncMutations()}}catch(t){console.warn("[Dolphin Offline] Failed to initialize IndexedDB:",t)}}setupNetworkListeners(){typeof window>"u"||(this.client.addDomListener(window,"online",()=>{this.isOnline=!0,this.client._dispatch("network:status",{online:!0}),this.syncMutations()}),this.client.addDomListener(window,"offline",()=>{this.isOnline=!1,this.client._dispatch("network:status",{online:!1})}))}async getCache(t){return this.db?new Promise(e=>{try{let i=this.db.transaction("cache","readonly").objectStore("cache").get(t);i.onsuccess=()=>e(i.result?i.result.data:null),i.onerror=()=>e(null)}catch{e(null)}}):this.memoryCache.get(t)}async setCache(t,e){if(!this.db){this.memoryCache.set(t,e);return}return new Promise(n=>{try{let r=this.db.transaction("cache","readwrite");r.objectStore("cache").put({data:e,timestamp:Date.now()},t),r.oncomplete=()=>n()}catch{n()}})}async queueMutation(t,e,n){let r={method:t,path:e,payload:n,timestamp:Date.now()};if(!this.db){this.memoryMutations.push(r);return}return new Promise(i=>{try{let a=this.db.transaction("mutations","readwrite");a.objectStore("mutations").add(r),a.oncomplete=()=>i()}catch{i()}})}async getMutations(){return this.db?new Promise(t=>{try{let r=this.db.transaction("mutations","readonly").objectStore("mutations").getAll();r.onsuccess=()=>t(r.result||[]),r.onerror=()=>t([])}catch{t([])}}):[...this.memoryMutations]}async removeMutation(t){if(!this.db){this.memoryMutations=this.memoryMutations.filter(e=>e.id!==t);return}return new Promise(e=>{try{let n=this.db.transaction("mutations","readwrite");n.objectStore("mutations").delete(t),n.oncomplete=()=>e()}catch{e()}})}async syncMutations(){let t=await this.getMutations();if(t.length!==0){console.log(`[Dolphin Offline] Syncing ${t.length} queued mutations...`);for(let e of t)try{await this.client.api.requestDirect(e.method,e.path,e.payload),e.id!==void 0?await this.removeMutation(e.id):this.memoryMutations.shift()}catch(n){if(console.error(`[Dolphin Offline] Sync failed for mutation ${e.method} ${e.path}:`,n),n&&n.status&&n.status>=400&&n.status<500)console.warn("[Dolphin Offline] Discarding invalid mutation."),e.id!==void 0?await this.removeMutation(e.id):this.memoryMutations.shift();else break}}}};function q(p){p._initOffline=function(){this.offline=new I(this)}}function Y(p,t,e){let n=t.split(",");for(let r of n){let i=r.trim().split(":"),a=i[0],l=i[1];if(a==="required"){if(!p||p.trim()==="")return"This field is required"}else if(a==="email"){if(p&&p.trim()!==""&&!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(p))return"Please enter a valid email address"}else if(a==="min"){let b=parseInt(l,10);if(!p||p.length<b)return`Must be at least ${b} characters`}else if(a==="match"&&e&&p!==e[l])return`Must match ${l}`}return null}function O(p){p.validateField=Y}function N(p){p.animateElement=function(t,e,n=300){if(typeof t.animate!="function"){t.classList.add(e),setTimeout(()=>t.classList.remove(e),n);return}e==="fade-in"?t.animate([{opacity:0,transform:"translateY(10px)"},{opacity:1,transform:"translateY(0)"}],{duration:n,easing:"ease-out"}):e==="fade-out"&&t.animate([{opacity:1,transform:"translateY(0)"},{opacity:0,transform:"translateY(10px)"}],{duration:n,easing:"ease-in"})},p.staggerListItems=function(t,e,n=50){if(typeof document>"u")return;t.querySelectorAll(e).forEach((i,a)=>{i.style.animationDelay=`${a*n}ms`,i.classList.add("staggered-item")})}}function U(p){p._initA11y=function(){typeof document>"u"||(this.addDomListener(document,"keydown",t=>{if(t.key!=="Tab")return;document.querySelectorAll("[data-rt-a11y-focus-trap]").forEach(n=>{if(n.style.display==="none"||n.hasAttribute("aria-hidden")&&n.getAttribute("aria-hidden")==="true")return;let i=Array.from(n.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable]'));if(i.length===0)return;let a=i[0],l=i[i.length-1];t.shiftKey?document.activeElement===a&&(l.focus(),t.preventDefault()):document.activeElement===l&&(a.focus(),t.preventDefault())})}),this.addDomListener(document,"keydown",t=>{if(!["ArrowUp","ArrowDown","Enter"].includes(t.key))return;document.querySelectorAll("[data-rt-keynav]").forEach(n=>{let r=Array.from(n.children);if(r.length===0)return;let i=r.findIndex(a=>a.classList.contains("active")||document.activeElement===a);t.key==="ArrowDown"?(i=(i+1)%r.length,r[i].focus(),r.forEach((a,l)=>{l===i?a.classList.add("active"):a.classList.remove("active")}),t.preventDefault()):t.key==="ArrowUp"?(i=(i-1+r.length)%r.length,r[i].focus(),r.forEach((a,l)=>{l===i?a.classList.add("active"):a.classList.remove("active")}),t.preventDefault()):t.key==="Enter"&&i!==-1&&(r[i].click(),t.preventDefault())})}))},p.autoAriaModal=function(t,e){e?(t.setAttribute("role","dialog"),t.setAttribute("aria-modal","true"),t.setAttribute("aria-hidden","false"),t.focus()):t.setAttribute("aria-hidden","true")}}function F(p){p._initI18n=function(){if(this.i18n=this.i18n||{locale:"en",dicts:{}},typeof document>"u")return;if(document.querySelectorAll("[data-i18n-dict]").forEach(e=>{let n=e.getAttribute("data-i18n-dict");if(n)try{let r=JSON.parse(e.textContent||"{}");this.i18n.dicts[n]={...this.i18n.dicts[n]||{},...r}}catch(r){console.warn("[Dolphin i18n] Failed to parse dictionary for locale:",n,r)}}),!this.i18n.locale&&typeof navigator<"u"){let e=navigator.language.split("-")[0];this.i18n.dicts[e]&&(this.i18n.locale=e)}this.addDomListener(document,"click",e=>{let n=e.target.closest("[data-i18n-switch]");if(n){let r=n.getAttribute("data-i18n-switch");r&&this.setLocale(r)}}),this.translateDOM()},p.setLocale=function(t){this.i18n=this.i18n||{locale:"en",dicts:{}},this.i18n.locale=t,this.translateDOM(),this.publish("i18n/locale",t)},p.translateDOM=function(){if(typeof document>"u")return;this.i18n=this.i18n||{locale:"en",dicts:{}};let t=this.i18n.locale||"en",e=this.i18n.dicts[t]||{};document.querySelectorAll("[data-i18n-key]").forEach(r=>{let i=r.getAttribute("data-i18n-key");if(!i)return;let a=i.split(".").reduce((b,T)=>b?b[T]:null,e);a==null&&(a=i);let l=r.getAttribute("data-i18n-params");if(l)try{let b=JSON.parse(l),T=h=>h.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");for(let h in b){let o=T(h);a=a.replace(new RegExp(`\\{\\{${o}\\}\\}`,"g"),b[h])}}catch{}r.tagName==="INPUT"||r.tagName==="TEXTAREA"?r.placeholder=a:r.textContent=a})}}function H(p){p._initDragDrop=function(){typeof document>"u"||(this.addDomListener(document,"dragstart",t=>{let e=t.target.closest("[data-drag]");if(!e)return;let n=e.getAttribute("data-drag");n&&(t.dataTransfer.setData("text/plain",n),t.dataTransfer.effectAllowed="move",e.classList.add("dragging"))}),this.addDomListener(document,"dragend",t=>{let e=t.target.closest("[data-drag]");e&&e.classList.remove("dragging")}),this.addDomListener(document,"dragover",t=>{let e=t.target.closest("[data-drop]");e&&(t.preventDefault(),e.classList.add("drag-over"))}),this.addDomListener(document,"dragleave",t=>{let e=t.target.closest("[data-drop]");e&&e.classList.remove("drag-over")}),this.addDomListener(document,"drop",t=>{let e=t.target.closest("[data-drop]");if(!e)return;t.preventDefault(),e.classList.remove("drag-over");let n=e.getAttribute("data-drop"),r=t.dataTransfer.getData("text/plain");if(n&&r)try{let i=JSON.parse(r);this.publish(n,i)}catch{this.publish(n,{value:r})}}),this.addDomListener(document,"dragover",t=>{let e=t.target.closest("[data-sortable]");if(!e)return;t.preventDefault();let n=e.querySelector(".dragging");if(!n)return;let i=Array.from(e.querySelectorAll("[data-drag]:not(.dragging)")).find(a=>{let l=a.getBoundingClientRect();return t.clientY-l.top-l.height/2<0});i?e.insertBefore(n,i):e.appendChild(n)}),this.addDomListener(document,"drop",t=>{let e=t.target.closest("[data-sortable]");if(!e)return;let n=e.getAttribute("data-sortable");if(!n)return;let i=Array.from(e.querySelectorAll("[data-drag]")).map((a,l)=>{let b=a.getAttribute("data-drag");try{return{index:l,payload:JSON.parse(b||"{}")}}catch{return{index:l,payload:b}}});this.publish(n,i)}))}}function W(p){p._initCollab=function(){typeof document>"u"||(this.addDomListener(document,"mousemove",t=>{document.querySelectorAll("[data-rt-cursor-share]").forEach(n=>{let r=n.getAttribute("data-rt-cursor-share");if(!r)return;let i=n.getBoundingClientRect(),a=(t.clientX-i.left)/i.width,l=(t.clientY-i.top)/i.height,b=Date.now();(!n._lastSent||b-n._lastSent>50)&&(n._lastSent=b,this.pubPush(`collab/${r}/cursor/${this.deviceId}`,{deviceId:this.deviceId,x:a,y:l}))})}),this.addDomListener(document,"input",t=>{let e=t.target.getAttribute("data-rt-typing");if(!e)return;let n=e,r=i=>{this.pubPush(`collab/${n}/typing/${this.deviceId}`,{deviceId:this.deviceId,typing:i})};t.target._isTyping||(t.target._isTyping=!0,r(!0)),t.target._typingTimer&&clearTimeout(t.target._typingTimer),t.target._typingTimer=setTimeout(()=>{t.target._isTyping=!1,r(!1)},2e3)}),this.addDomListener(document,"input",t=>{let e=t.target.getAttribute("data-rt-crdt");if(!e)return;let n=e,r=t.target.value,i=Date.now();this.publish(`collab/${n}/crdt`,{deviceId:this.deviceId,value:r,timestamp:i,cursorPos:t.target.selectionStart})}),this.subscribe("collab/+/cursor/+",(t,e)=>{let n=e.split("/"),r=n[1],i=n[3];if(i===this.deviceId)return;let a=document.querySelector(`[data-rt-cursor-share="${r}"]`);if(!a)return;let l=a.querySelector(`.rt-cursor-${i}`);l||(l=document.createElement("div"),l.className=`rt-cursor rt-cursor-${i}`,l.style.position="absolute",l.style.width="10px",l.style.height="10px",l.style.borderRadius="50%",l.style.backgroundColor="#"+Math.floor(Math.random()*16777215).toString(16),l.style.pointerEvents="none",a.appendChild(l));let b=a.getBoundingClientRect();l.style.left=t.x*b.width+"px",l.style.top=t.y*b.height+"px"}),this.subscribe("collab/+/crdt",(t,e)=>{if(t.deviceId===this.deviceId)return;let r=e.split("/")[1];document.querySelectorAll(`[data-rt-crdt="${r}"]`).forEach(a=>{if(!a._lastUpdate||t.timestamp>a._lastUpdate){a._lastUpdate=t.timestamp;let l=a.selectionStart;a.value=t.value,document.activeElement===a&&a.setSelectionRange(l,l)}})}))}}function j(p){p.registerServiceWorker=async function(t="/sw.js"){if(typeof navigator>"u"||!("serviceWorker"in navigator))return console.warn("[Dolphin PWA] Service Workers are not supported in this browser."),null;try{let e=await navigator.serviceWorker.register(t);return console.log("[Dolphin PWA] Service Worker registered successfully with scope:",e.scope),e}catch(e){return console.error("[Dolphin PWA] Service Worker registration failed:",e),null}},p.subscribePushNotifications=async function(t){if(typeof window>"u"||!("serviceWorker"in navigator)||!("PushManager"in window))return console.warn("[Dolphin PWA] Push notifications are not supported in this browser."),null;try{let e=await navigator.serviceWorker.ready,n=await e.pushManager.getSubscription();if(!n){let r="=".repeat((4-t.length%4)%4),i=(t+r).replace(/\-/g,"+").replace(/_/g,"/"),a=window.atob(i),l=new Uint8Array(a.length);for(let b=0;b<a.length;++b)l[b]=a.charCodeAt(b);n=await e.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:l})}return console.log("[Dolphin PWA] Subscribed to push notifications:",n),n}catch(e){return console.error("[Dolphin PWA] Push notification subscription failed:",e),null}}}var P=class{static render(t){if(typeof document>"u")throw new Error("DolphinTestUtils.render requires a DOM document environment to execute.");let e=document.createElement("div");return e.innerHTML=t,document.body.appendChild(e),{container:e,find:n=>e.querySelector(n),fireEvent:(n,r)=>{let i=document.createEvent("Event");i.initEvent(r,!0,!0),n.dispatchEvent(i)}}}static mockWebSocket(){let t=[],e={readyState:1,send:n=>{t.push(n)},close:jest.fn(),onopen:jest.fn(),onmessage:jest.fn(),onclose:jest.fn(),onerror:jest.fn(),sentMessages:t};return global.WebSocket=class{static OPEN=1;readyState=e.readyState;send=e.send;close=e.close;set onopen(n){e.onopen=n}get onopen(){return e.onopen}set onmessage(n){e.onmessage=n}get onmessage(){return e.onmessage}set onclose(n){e.onclose=n}get getonclose(){return e.onclose}constructor(){setTimeout(()=>e.onopen&&e.onopen(),0)}},e}static simulateClick(t){let e={target:t,preventDefault:jest.fn(),stopPropagation:jest.fn()};(global.document._listeners?.click||[]).forEach(r=>r(e))}static simulateChange(t,e){t.value=e;let n={target:t,preventDefault:jest.fn(),stopPropagation:jest.fn()};(global.document._listeners?.change||[]).forEach(i=>i(n))}};function B(p){p.testing=P}R(v.prototype);q(v.prototype);O(v.prototype);N(v.prototype);U(v.prototype);F(v.prototype);H(v.prototype);W(v.prototype);j(v.prototype);B(v.prototype);typeof window<"u"&&(window.DolphinClient=v,document.addEventListener("DOMContentLoaded",()=>{if(!window.dolphin){let p=document.querySelector('script[src*="dolphin-client"]'),t=p?p.getAttribute("data-debug")==="true":!1,e=new v(void 0,void 0,{debug:t});window.dolphin=e,t&&(console.log("%c\u{1F42C} [Dolphin Client] Auto-initialized local reactive engine!","color: #06b6d4; font-weight: bold; font-size: 14px;"),console.log('%c\u{1F449} Tip: You can access the client instance via "window.dolphin" in console.',"color: #94a3b8; font-style: italic;")),document.querySelector('[data-store-write="app.username"]')&&e.setStoreState("app","username","\u0928\u092E\u0938\u094D\u0924\u0947 \u0938\u093E\u0925\u0940!")}}));return X(Z);})();
|
package/dist/index.cjs
CHANGED
|
@@ -108,6 +108,9 @@ var APIHandler = class {
|
|
|
108
108
|
async requestDirect(method, path, body = null, options = {}) {
|
|
109
109
|
const _isRetry = options._isRetry === true;
|
|
110
110
|
const url = `${this.client.httpUrl}${path.startsWith("/") ? path : "/" + path}`;
|
|
111
|
+
if (this.client.options.debug) {
|
|
112
|
+
console.log(`%c\u{1F680} [Dolphin API Request]:`, "color: #3b82f6; font-weight: bold;", method.toUpperCase(), path, body || "");
|
|
113
|
+
}
|
|
111
114
|
const controller = new AbortController();
|
|
112
115
|
const timeoutId = setTimeout(
|
|
113
116
|
() => controller.abort(),
|
|
@@ -140,6 +143,9 @@ var APIHandler = class {
|
|
|
140
143
|
const contentType = response.headers.get("content-type") || "";
|
|
141
144
|
const data = contentType.includes("application/json") ? await response.json() : await response.text();
|
|
142
145
|
if (!response.ok) throw { status: response.status, data };
|
|
146
|
+
if (this.client.options.debug) {
|
|
147
|
+
console.log(`%c\u2705 [Dolphin API Success]:`, "color: #10b981; font-weight: bold;", method.toUpperCase(), path, data);
|
|
148
|
+
}
|
|
143
149
|
if (data && typeof data === "object") {
|
|
144
150
|
if (data.accessToken) {
|
|
145
151
|
this.client.setToken(data.accessToken);
|
|
@@ -153,6 +159,9 @@ var APIHandler = class {
|
|
|
153
159
|
return data;
|
|
154
160
|
} catch (err) {
|
|
155
161
|
clearTimeout(timeoutId);
|
|
162
|
+
if (this.client.options.debug) {
|
|
163
|
+
console.error(`%c\u274C [Dolphin API Error]:`, "color: #ef4444; font-weight: bold;", method.toUpperCase(), path, err);
|
|
164
|
+
}
|
|
156
165
|
if (err.name === "AbortError") {
|
|
157
166
|
throw { status: 408, data: { error: "Request timed out" } };
|
|
158
167
|
}
|
|
@@ -437,6 +446,7 @@ var DolphinClient = class {
|
|
|
437
446
|
// 64 KB
|
|
438
447
|
maxReconnect: 5,
|
|
439
448
|
autoRefreshToken: true,
|
|
449
|
+
debug: false,
|
|
440
450
|
...options
|
|
441
451
|
};
|
|
442
452
|
this.socket = null;
|
|
@@ -519,6 +529,9 @@ var DolphinClient = class {
|
|
|
519
529
|
_handleMessage(data) {
|
|
520
530
|
try {
|
|
521
531
|
const msg = JSON.parse(data);
|
|
532
|
+
if (this.options.debug) {
|
|
533
|
+
console.log("%c\u{1F4E5} [Dolphin WS Incoming]:", "color: #eab308; font-weight: bold;", msg);
|
|
534
|
+
}
|
|
522
535
|
if (msg.type && msg.from && (msg.to === this.deviceId || msg.to === "all")) {
|
|
523
536
|
if (msg.msgId && msg.type !== "ACK") this._sendAck(msg.from, msg.msgId);
|
|
524
537
|
this.signalHandlers.forEach((h) => h(msg));
|
|
@@ -556,6 +569,9 @@ var DolphinClient = class {
|
|
|
556
569
|
}
|
|
557
570
|
/** @private */
|
|
558
571
|
_sendRaw(msg) {
|
|
572
|
+
if (this.options.debug) {
|
|
573
|
+
console.log("%c\u{1F4E4} [Dolphin WS Outgoing]:", "color: #8b5cf6; font-weight: bold;", msg);
|
|
574
|
+
}
|
|
559
575
|
const str = typeof msg === "string" ? msg : JSON.stringify(msg);
|
|
560
576
|
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
|
561
577
|
this.socket.send(str);
|
|
@@ -788,6 +804,108 @@ function attachDOMBinding(clientProto) {
|
|
|
788
804
|
}
|
|
789
805
|
return template;
|
|
790
806
|
}
|
|
807
|
+
function renderTemplate(templateStr, context) {
|
|
808
|
+
if (!templateStr.includes("{#if") && !templateStr.includes("{#each")) {
|
|
809
|
+
let result = templateStr;
|
|
810
|
+
for (let key in context) {
|
|
811
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
812
|
+
result = result.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
|
|
813
|
+
}
|
|
814
|
+
return result;
|
|
815
|
+
}
|
|
816
|
+
try {
|
|
817
|
+
const escapeString = (str) => {
|
|
818
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
819
|
+
};
|
|
820
|
+
let compiled = 'let out = "";\n';
|
|
821
|
+
let lastIndex = 0;
|
|
822
|
+
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;
|
|
823
|
+
const eachStack = [];
|
|
824
|
+
let match;
|
|
825
|
+
while ((match = regex.exec(templateStr)) !== null) {
|
|
826
|
+
const plainText = templateStr.slice(lastIndex, match.index);
|
|
827
|
+
if (plainText) {
|
|
828
|
+
compiled += `out += "${escapeString(plainText)}";
|
|
829
|
+
`;
|
|
830
|
+
}
|
|
831
|
+
const token = match[0];
|
|
832
|
+
if (token.startsWith("{{")) {
|
|
833
|
+
const expr = match[2];
|
|
834
|
+
compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
|
|
835
|
+
`;
|
|
836
|
+
} else if (token.startsWith("{#if")) {
|
|
837
|
+
const expr = match[3];
|
|
838
|
+
compiled += `if (${expr}) {
|
|
839
|
+
`;
|
|
840
|
+
} else if (token.startsWith("{:else if")) {
|
|
841
|
+
const expr = match[4];
|
|
842
|
+
compiled += `} else if (${expr}) {
|
|
843
|
+
`;
|
|
844
|
+
} else if (token.startsWith("{:else}")) {
|
|
845
|
+
compiled += `} else {
|
|
846
|
+
`;
|
|
847
|
+
} else if (token.startsWith("{/if}")) {
|
|
848
|
+
compiled += `}
|
|
849
|
+
`;
|
|
850
|
+
} else if (token.startsWith("{#each")) {
|
|
851
|
+
const expr = match[5];
|
|
852
|
+
const itemVar = match[6];
|
|
853
|
+
const indexVar = match[7];
|
|
854
|
+
eachStack.push({ indexVar });
|
|
855
|
+
compiled += `if (typeof ${expr} !== "undefined" && ${expr} !== null && Array.isArray(${expr})) {
|
|
856
|
+
`;
|
|
857
|
+
if (indexVar) {
|
|
858
|
+
compiled += ` let ${indexVar} = 0;
|
|
859
|
+
`;
|
|
860
|
+
}
|
|
861
|
+
compiled += ` for (let ${itemVar} of ${expr}) {
|
|
862
|
+
`;
|
|
863
|
+
} else if (token.startsWith("{/each}")) {
|
|
864
|
+
const info = eachStack.pop();
|
|
865
|
+
if (info && info.indexVar) {
|
|
866
|
+
compiled += ` ${info.indexVar}++;
|
|
867
|
+
`;
|
|
868
|
+
}
|
|
869
|
+
compiled += ` }
|
|
870
|
+
}
|
|
871
|
+
`;
|
|
872
|
+
} else if (token.startsWith("{")) {
|
|
873
|
+
const expr = match[8];
|
|
874
|
+
if (expr) {
|
|
875
|
+
compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
|
|
876
|
+
`;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
lastIndex = regex.lastIndex;
|
|
880
|
+
}
|
|
881
|
+
const remaining = templateStr.slice(lastIndex);
|
|
882
|
+
if (remaining) {
|
|
883
|
+
compiled += `out += "${escapeString(remaining)}";
|
|
884
|
+
`;
|
|
885
|
+
}
|
|
886
|
+
compiled += "return out;\n";
|
|
887
|
+
const fnBody = `
|
|
888
|
+
with (context) {
|
|
889
|
+
try {
|
|
890
|
+
${compiled}
|
|
891
|
+
} catch (innerErr) {
|
|
892
|
+
console.warn('[Dolphin Template Eval Warning]:', innerErr);
|
|
893
|
+
return '';
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
`;
|
|
897
|
+
const fn = new Function("context", fnBody);
|
|
898
|
+
return fn(context);
|
|
899
|
+
} catch (e) {
|
|
900
|
+
console.error("[Dolphin Template Compiler Error]:", e);
|
|
901
|
+
let fallback = templateStr;
|
|
902
|
+
for (let key in context) {
|
|
903
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
904
|
+
fallback = fallback.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
|
|
905
|
+
}
|
|
906
|
+
return fallback;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
791
909
|
function sanitizeHTML(html) {
|
|
792
910
|
if (typeof document === "undefined") return html;
|
|
793
911
|
try {
|
|
@@ -911,6 +1029,9 @@ function attachDOMBinding(clientProto) {
|
|
|
911
1029
|
}
|
|
912
1030
|
const store = this.uiStores.get(storeName);
|
|
913
1031
|
store[key] = val;
|
|
1032
|
+
if (this.options.debug) {
|
|
1033
|
+
console.log(`%c\u{1F4BE} [Dolphin Store Update]:`, "color: #ec4899; font-weight: bold;", `${storeName}.${key}`, "=", val);
|
|
1034
|
+
}
|
|
914
1035
|
if (typeof document !== "undefined") {
|
|
915
1036
|
const readElements = document.querySelectorAll(`[data-store-read="${storeName}.${key}"]`);
|
|
916
1037
|
readElements.forEach((el) => {
|
|
@@ -990,6 +1111,42 @@ function attachDOMBinding(clientProto) {
|
|
|
990
1111
|
}
|
|
991
1112
|
return null;
|
|
992
1113
|
};
|
|
1114
|
+
clientProto._executeStoreAction = function(expression, element) {
|
|
1115
|
+
this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
|
|
1116
|
+
const context = new Proxy({}, {
|
|
1117
|
+
has: (target, prop) => {
|
|
1118
|
+
return true;
|
|
1119
|
+
},
|
|
1120
|
+
get: (target, prop) => {
|
|
1121
|
+
if (typeof prop === "string") {
|
|
1122
|
+
return new Proxy({}, {
|
|
1123
|
+
get: (subTarget, subProp) => {
|
|
1124
|
+
if (typeof subProp === "string") {
|
|
1125
|
+
return this.getStoreState(prop, subProp);
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
set: (subTarget, subProp, val) => {
|
|
1129
|
+
if (typeof subProp === "string") {
|
|
1130
|
+
this.setStoreState(prop, subProp, val);
|
|
1131
|
+
return true;
|
|
1132
|
+
}
|
|
1133
|
+
return false;
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
try {
|
|
1140
|
+
const fn = new Function("ctx", `with(ctx) { ${expression} }`);
|
|
1141
|
+
fn(context);
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
console.error("%c[Dolphin Store Action Error]:", "color: #ef4444; font-weight: bold;", err);
|
|
1144
|
+
if (element) {
|
|
1145
|
+
console.error("%cFailed Element:", "color: #f97316; font-weight: bold;", element);
|
|
1146
|
+
}
|
|
1147
|
+
console.error("%cFailed Expression:", "color: #3b82f6; font-style: italic;", expression);
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
993
1150
|
clientProto._initDOMBinding = function() {
|
|
994
1151
|
if (this._domInitialized) return;
|
|
995
1152
|
this._domInitialized = true;
|
|
@@ -1163,6 +1320,14 @@ function attachDOMBinding(clientProto) {
|
|
|
1163
1320
|
console.error(`[Dolphin] API ${evtName} Error:`, err);
|
|
1164
1321
|
}
|
|
1165
1322
|
}
|
|
1323
|
+
const storeActionBtn = e.target.closest(`[data-store-${evtName}]`);
|
|
1324
|
+
if (storeActionBtn) {
|
|
1325
|
+
if (evtName === "submit") e.preventDefault();
|
|
1326
|
+
const expr = storeActionBtn.getAttribute(`data-store-${evtName}`);
|
|
1327
|
+
if (expr) {
|
|
1328
|
+
this._executeStoreAction(expr, storeActionBtn);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1166
1331
|
});
|
|
1167
1332
|
});
|
|
1168
1333
|
this.subscribe("#", (payload, topic) => {
|
|
@@ -1188,21 +1353,11 @@ function attachDOMBinding(clientProto) {
|
|
|
1188
1353
|
if (Array.isArray(result)) {
|
|
1189
1354
|
let combinedHTML = "";
|
|
1190
1355
|
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;
|
|
1356
|
+
combinedHTML += renderTemplate(template, item);
|
|
1197
1357
|
}
|
|
1198
1358
|
scheduleDOMUpdate(el, combinedHTML);
|
|
1199
1359
|
} 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);
|
|
1360
|
+
scheduleDOMUpdate(el, renderTemplate(template, result));
|
|
1206
1361
|
}
|
|
1207
1362
|
} else {
|
|
1208
1363
|
if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
|
|
@@ -1296,21 +1451,11 @@ function attachDOMBinding(clientProto) {
|
|
|
1296
1451
|
if (Array.isArray(payload)) {
|
|
1297
1452
|
let combinedHTML = "";
|
|
1298
1453
|
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;
|
|
1454
|
+
combinedHTML += renderTemplate(template, item);
|
|
1305
1455
|
}
|
|
1306
1456
|
scheduleDOMUpdate(el, combinedHTML);
|
|
1307
1457
|
} 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);
|
|
1458
|
+
scheduleDOMUpdate(el, renderTemplate(template, payload));
|
|
1314
1459
|
}
|
|
1315
1460
|
return;
|
|
1316
1461
|
}
|
|
@@ -2037,4 +2182,19 @@ attachPwa(DolphinClient.prototype);
|
|
|
2037
2182
|
attachTesting(DolphinClient.prototype);
|
|
2038
2183
|
if (typeof window !== "undefined") {
|
|
2039
2184
|
window.DolphinClient = DolphinClient;
|
|
2185
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
2186
|
+
if (!window.dolphin) {
|
|
2187
|
+
const scriptEl = document.querySelector('script[src*="dolphin-client"]');
|
|
2188
|
+
const debugMode = scriptEl ? scriptEl.getAttribute("data-debug") === "true" : false;
|
|
2189
|
+
const dolphin = new DolphinClient(void 0, void 0, { debug: debugMode });
|
|
2190
|
+
window.dolphin = dolphin;
|
|
2191
|
+
if (debugMode) {
|
|
2192
|
+
console.log("%c\u{1F42C} [Dolphin Client] Auto-initialized local reactive engine!", "color: #06b6d4; font-weight: bold; font-size: 14px;");
|
|
2193
|
+
console.log('%c\u{1F449} Tip: You can access the client instance via "window.dolphin" in console.', "color: #94a3b8; font-style: italic;");
|
|
2194
|
+
}
|
|
2195
|
+
if (document.querySelector('[data-store-write="app.username"]')) {
|
|
2196
|
+
dolphin.setStoreState("app", "username", "\u0928\u092E\u0938\u094D\u0924\u0947 \u0938\u093E\u0925\u0940!");
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
});
|
|
2040
2200
|
}
|
package/dist/index.js
CHANGED
|
@@ -83,6 +83,9 @@ var APIHandler = class {
|
|
|
83
83
|
async requestDirect(method, path, body = null, options = {}) {
|
|
84
84
|
const _isRetry = options._isRetry === true;
|
|
85
85
|
const url = `${this.client.httpUrl}${path.startsWith("/") ? path : "/" + path}`;
|
|
86
|
+
if (this.client.options.debug) {
|
|
87
|
+
console.log(`%c\u{1F680} [Dolphin API Request]:`, "color: #3b82f6; font-weight: bold;", method.toUpperCase(), path, body || "");
|
|
88
|
+
}
|
|
86
89
|
const controller = new AbortController();
|
|
87
90
|
const timeoutId = setTimeout(
|
|
88
91
|
() => controller.abort(),
|
|
@@ -115,6 +118,9 @@ var APIHandler = class {
|
|
|
115
118
|
const contentType = response.headers.get("content-type") || "";
|
|
116
119
|
const data = contentType.includes("application/json") ? await response.json() : await response.text();
|
|
117
120
|
if (!response.ok) throw { status: response.status, data };
|
|
121
|
+
if (this.client.options.debug) {
|
|
122
|
+
console.log(`%c\u2705 [Dolphin API Success]:`, "color: #10b981; font-weight: bold;", method.toUpperCase(), path, data);
|
|
123
|
+
}
|
|
118
124
|
if (data && typeof data === "object") {
|
|
119
125
|
if (data.accessToken) {
|
|
120
126
|
this.client.setToken(data.accessToken);
|
|
@@ -128,6 +134,9 @@ var APIHandler = class {
|
|
|
128
134
|
return data;
|
|
129
135
|
} catch (err) {
|
|
130
136
|
clearTimeout(timeoutId);
|
|
137
|
+
if (this.client.options.debug) {
|
|
138
|
+
console.error(`%c\u274C [Dolphin API Error]:`, "color: #ef4444; font-weight: bold;", method.toUpperCase(), path, err);
|
|
139
|
+
}
|
|
131
140
|
if (err.name === "AbortError") {
|
|
132
141
|
throw { status: 408, data: { error: "Request timed out" } };
|
|
133
142
|
}
|
|
@@ -412,6 +421,7 @@ var DolphinClient = class {
|
|
|
412
421
|
// 64 KB
|
|
413
422
|
maxReconnect: 5,
|
|
414
423
|
autoRefreshToken: true,
|
|
424
|
+
debug: false,
|
|
415
425
|
...options
|
|
416
426
|
};
|
|
417
427
|
this.socket = null;
|
|
@@ -494,6 +504,9 @@ var DolphinClient = class {
|
|
|
494
504
|
_handleMessage(data) {
|
|
495
505
|
try {
|
|
496
506
|
const msg = JSON.parse(data);
|
|
507
|
+
if (this.options.debug) {
|
|
508
|
+
console.log("%c\u{1F4E5} [Dolphin WS Incoming]:", "color: #eab308; font-weight: bold;", msg);
|
|
509
|
+
}
|
|
497
510
|
if (msg.type && msg.from && (msg.to === this.deviceId || msg.to === "all")) {
|
|
498
511
|
if (msg.msgId && msg.type !== "ACK") this._sendAck(msg.from, msg.msgId);
|
|
499
512
|
this.signalHandlers.forEach((h) => h(msg));
|
|
@@ -531,6 +544,9 @@ var DolphinClient = class {
|
|
|
531
544
|
}
|
|
532
545
|
/** @private */
|
|
533
546
|
_sendRaw(msg) {
|
|
547
|
+
if (this.options.debug) {
|
|
548
|
+
console.log("%c\u{1F4E4} [Dolphin WS Outgoing]:", "color: #8b5cf6; font-weight: bold;", msg);
|
|
549
|
+
}
|
|
534
550
|
const str = typeof msg === "string" ? msg : JSON.stringify(msg);
|
|
535
551
|
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
|
536
552
|
this.socket.send(str);
|
|
@@ -763,6 +779,108 @@ function attachDOMBinding(clientProto) {
|
|
|
763
779
|
}
|
|
764
780
|
return template;
|
|
765
781
|
}
|
|
782
|
+
function renderTemplate(templateStr, context) {
|
|
783
|
+
if (!templateStr.includes("{#if") && !templateStr.includes("{#each")) {
|
|
784
|
+
let result = templateStr;
|
|
785
|
+
for (let key in context) {
|
|
786
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
787
|
+
result = result.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
|
|
788
|
+
}
|
|
789
|
+
return result;
|
|
790
|
+
}
|
|
791
|
+
try {
|
|
792
|
+
const escapeString = (str) => {
|
|
793
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
794
|
+
};
|
|
795
|
+
let compiled = 'let out = "";\n';
|
|
796
|
+
let lastIndex = 0;
|
|
797
|
+
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;
|
|
798
|
+
const eachStack = [];
|
|
799
|
+
let match;
|
|
800
|
+
while ((match = regex.exec(templateStr)) !== null) {
|
|
801
|
+
const plainText = templateStr.slice(lastIndex, match.index);
|
|
802
|
+
if (plainText) {
|
|
803
|
+
compiled += `out += "${escapeString(plainText)}";
|
|
804
|
+
`;
|
|
805
|
+
}
|
|
806
|
+
const token = match[0];
|
|
807
|
+
if (token.startsWith("{{")) {
|
|
808
|
+
const expr = match[2];
|
|
809
|
+
compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
|
|
810
|
+
`;
|
|
811
|
+
} else if (token.startsWith("{#if")) {
|
|
812
|
+
const expr = match[3];
|
|
813
|
+
compiled += `if (${expr}) {
|
|
814
|
+
`;
|
|
815
|
+
} else if (token.startsWith("{:else if")) {
|
|
816
|
+
const expr = match[4];
|
|
817
|
+
compiled += `} else if (${expr}) {
|
|
818
|
+
`;
|
|
819
|
+
} else if (token.startsWith("{:else}")) {
|
|
820
|
+
compiled += `} else {
|
|
821
|
+
`;
|
|
822
|
+
} else if (token.startsWith("{/if}")) {
|
|
823
|
+
compiled += `}
|
|
824
|
+
`;
|
|
825
|
+
} else if (token.startsWith("{#each")) {
|
|
826
|
+
const expr = match[5];
|
|
827
|
+
const itemVar = match[6];
|
|
828
|
+
const indexVar = match[7];
|
|
829
|
+
eachStack.push({ indexVar });
|
|
830
|
+
compiled += `if (typeof ${expr} !== "undefined" && ${expr} !== null && Array.isArray(${expr})) {
|
|
831
|
+
`;
|
|
832
|
+
if (indexVar) {
|
|
833
|
+
compiled += ` let ${indexVar} = 0;
|
|
834
|
+
`;
|
|
835
|
+
}
|
|
836
|
+
compiled += ` for (let ${itemVar} of ${expr}) {
|
|
837
|
+
`;
|
|
838
|
+
} else if (token.startsWith("{/each}")) {
|
|
839
|
+
const info = eachStack.pop();
|
|
840
|
+
if (info && info.indexVar) {
|
|
841
|
+
compiled += ` ${info.indexVar}++;
|
|
842
|
+
`;
|
|
843
|
+
}
|
|
844
|
+
compiled += ` }
|
|
845
|
+
}
|
|
846
|
+
`;
|
|
847
|
+
} else if (token.startsWith("{")) {
|
|
848
|
+
const expr = match[8];
|
|
849
|
+
if (expr) {
|
|
850
|
+
compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
|
|
851
|
+
`;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
lastIndex = regex.lastIndex;
|
|
855
|
+
}
|
|
856
|
+
const remaining = templateStr.slice(lastIndex);
|
|
857
|
+
if (remaining) {
|
|
858
|
+
compiled += `out += "${escapeString(remaining)}";
|
|
859
|
+
`;
|
|
860
|
+
}
|
|
861
|
+
compiled += "return out;\n";
|
|
862
|
+
const fnBody = `
|
|
863
|
+
with (context) {
|
|
864
|
+
try {
|
|
865
|
+
${compiled}
|
|
866
|
+
} catch (innerErr) {
|
|
867
|
+
console.warn('[Dolphin Template Eval Warning]:', innerErr);
|
|
868
|
+
return '';
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
`;
|
|
872
|
+
const fn = new Function("context", fnBody);
|
|
873
|
+
return fn(context);
|
|
874
|
+
} catch (e) {
|
|
875
|
+
console.error("[Dolphin Template Compiler Error]:", e);
|
|
876
|
+
let fallback = templateStr;
|
|
877
|
+
for (let key in context) {
|
|
878
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
879
|
+
fallback = fallback.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
|
|
880
|
+
}
|
|
881
|
+
return fallback;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
766
884
|
function sanitizeHTML(html) {
|
|
767
885
|
if (typeof document === "undefined") return html;
|
|
768
886
|
try {
|
|
@@ -886,6 +1004,9 @@ function attachDOMBinding(clientProto) {
|
|
|
886
1004
|
}
|
|
887
1005
|
const store = this.uiStores.get(storeName);
|
|
888
1006
|
store[key] = val;
|
|
1007
|
+
if (this.options.debug) {
|
|
1008
|
+
console.log(`%c\u{1F4BE} [Dolphin Store Update]:`, "color: #ec4899; font-weight: bold;", `${storeName}.${key}`, "=", val);
|
|
1009
|
+
}
|
|
889
1010
|
if (typeof document !== "undefined") {
|
|
890
1011
|
const readElements = document.querySelectorAll(`[data-store-read="${storeName}.${key}"]`);
|
|
891
1012
|
readElements.forEach((el) => {
|
|
@@ -965,6 +1086,42 @@ function attachDOMBinding(clientProto) {
|
|
|
965
1086
|
}
|
|
966
1087
|
return null;
|
|
967
1088
|
};
|
|
1089
|
+
clientProto._executeStoreAction = function(expression, element) {
|
|
1090
|
+
this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
|
|
1091
|
+
const context = new Proxy({}, {
|
|
1092
|
+
has: (target, prop) => {
|
|
1093
|
+
return true;
|
|
1094
|
+
},
|
|
1095
|
+
get: (target, prop) => {
|
|
1096
|
+
if (typeof prop === "string") {
|
|
1097
|
+
return new Proxy({}, {
|
|
1098
|
+
get: (subTarget, subProp) => {
|
|
1099
|
+
if (typeof subProp === "string") {
|
|
1100
|
+
return this.getStoreState(prop, subProp);
|
|
1101
|
+
}
|
|
1102
|
+
},
|
|
1103
|
+
set: (subTarget, subProp, val) => {
|
|
1104
|
+
if (typeof subProp === "string") {
|
|
1105
|
+
this.setStoreState(prop, subProp, val);
|
|
1106
|
+
return true;
|
|
1107
|
+
}
|
|
1108
|
+
return false;
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
try {
|
|
1115
|
+
const fn = new Function("ctx", `with(ctx) { ${expression} }`);
|
|
1116
|
+
fn(context);
|
|
1117
|
+
} catch (err) {
|
|
1118
|
+
console.error("%c[Dolphin Store Action Error]:", "color: #ef4444; font-weight: bold;", err);
|
|
1119
|
+
if (element) {
|
|
1120
|
+
console.error("%cFailed Element:", "color: #f97316; font-weight: bold;", element);
|
|
1121
|
+
}
|
|
1122
|
+
console.error("%cFailed Expression:", "color: #3b82f6; font-style: italic;", expression);
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
968
1125
|
clientProto._initDOMBinding = function() {
|
|
969
1126
|
if (this._domInitialized) return;
|
|
970
1127
|
this._domInitialized = true;
|
|
@@ -1138,6 +1295,14 @@ function attachDOMBinding(clientProto) {
|
|
|
1138
1295
|
console.error(`[Dolphin] API ${evtName} Error:`, err);
|
|
1139
1296
|
}
|
|
1140
1297
|
}
|
|
1298
|
+
const storeActionBtn = e.target.closest(`[data-store-${evtName}]`);
|
|
1299
|
+
if (storeActionBtn) {
|
|
1300
|
+
if (evtName === "submit") e.preventDefault();
|
|
1301
|
+
const expr = storeActionBtn.getAttribute(`data-store-${evtName}`);
|
|
1302
|
+
if (expr) {
|
|
1303
|
+
this._executeStoreAction(expr, storeActionBtn);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1141
1306
|
});
|
|
1142
1307
|
});
|
|
1143
1308
|
this.subscribe("#", (payload, topic) => {
|
|
@@ -1163,21 +1328,11 @@ function attachDOMBinding(clientProto) {
|
|
|
1163
1328
|
if (Array.isArray(result)) {
|
|
1164
1329
|
let combinedHTML = "";
|
|
1165
1330
|
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;
|
|
1331
|
+
combinedHTML += renderTemplate(template, item);
|
|
1172
1332
|
}
|
|
1173
1333
|
scheduleDOMUpdate(el, combinedHTML);
|
|
1174
1334
|
} 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);
|
|
1335
|
+
scheduleDOMUpdate(el, renderTemplate(template, result));
|
|
1181
1336
|
}
|
|
1182
1337
|
} else {
|
|
1183
1338
|
if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
|
|
@@ -1271,21 +1426,11 @@ function attachDOMBinding(clientProto) {
|
|
|
1271
1426
|
if (Array.isArray(payload)) {
|
|
1272
1427
|
let combinedHTML = "";
|
|
1273
1428
|
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;
|
|
1429
|
+
combinedHTML += renderTemplate(template, item);
|
|
1280
1430
|
}
|
|
1281
1431
|
scheduleDOMUpdate(el, combinedHTML);
|
|
1282
1432
|
} 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);
|
|
1433
|
+
scheduleDOMUpdate(el, renderTemplate(template, payload));
|
|
1289
1434
|
}
|
|
1290
1435
|
return;
|
|
1291
1436
|
}
|
|
@@ -2012,6 +2157,21 @@ attachPwa(DolphinClient.prototype);
|
|
|
2012
2157
|
attachTesting(DolphinClient.prototype);
|
|
2013
2158
|
if (typeof window !== "undefined") {
|
|
2014
2159
|
window.DolphinClient = DolphinClient;
|
|
2160
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
2161
|
+
if (!window.dolphin) {
|
|
2162
|
+
const scriptEl = document.querySelector('script[src*="dolphin-client"]');
|
|
2163
|
+
const debugMode = scriptEl ? scriptEl.getAttribute("data-debug") === "true" : false;
|
|
2164
|
+
const dolphin = new DolphinClient(void 0, void 0, { debug: debugMode });
|
|
2165
|
+
window.dolphin = dolphin;
|
|
2166
|
+
if (debugMode) {
|
|
2167
|
+
console.log("%c\u{1F42C} [Dolphin Client] Auto-initialized local reactive engine!", "color: #06b6d4; font-weight: bold; font-size: 14px;");
|
|
2168
|
+
console.log('%c\u{1F449} Tip: You can access the client instance via "window.dolphin" in console.', "color: #94a3b8; font-style: italic;");
|
|
2169
|
+
}
|
|
2170
|
+
if (document.querySelector('[data-store-write="app.username"]')) {
|
|
2171
|
+
dolphin.setStoreState("app", "username", "\u0928\u092E\u0938\u094D\u0924\u0947 \u0938\u093E\u0925\u0940!");
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
});
|
|
2015
2175
|
}
|
|
2016
2176
|
export {
|
|
2017
2177
|
DolphinClient
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dolphin-client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.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",
|
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
"LICENSE"
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
|
-
"build": "npm run build:iife && npm run build:esm && npm run build:cjs && tsc",
|
|
22
|
+
"build": "npm run build:iife && npm run build:min && npm run build:esm && npm run build:cjs && tsc",
|
|
23
23
|
"build:iife": "esbuild ./src/index.ts --bundle --outfile=dist/dolphin-client.js --format=iife --global-name=DolphinModule",
|
|
24
|
+
"build:min": "esbuild ./src/index.ts --bundle --minify --outfile=dist/dolphin-client.min.js --format=iife --global-name=DolphinModule",
|
|
24
25
|
"build:esm": "esbuild ./src/index.ts --bundle --outfile=dist/index.js --format=esm",
|
|
25
26
|
"build:cjs": "esbuild ./src/index.ts --bundle --outfile=dist/index.cjs --format=cjs",
|
|
26
27
|
"test": "jest"
|