zero-query 1.1.1 → 1.2.0
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/LICENSE +21 -21
- package/README.md +2 -0
- package/cli/args.js +33 -33
- package/cli/commands/build-api.js +443 -442
- package/cli/commands/build.js +254 -247
- package/cli/commands/bundle.js +1228 -1224
- package/cli/commands/create.js +137 -121
- package/cli/commands/dev/devtools/index.js +56 -56
- package/cli/commands/dev/devtools/js/components.js +49 -49
- package/cli/commands/dev/devtools/js/core.js +423 -423
- package/cli/commands/dev/devtools/js/elements.js +421 -421
- package/cli/commands/dev/devtools/js/network.js +166 -166
- package/cli/commands/dev/devtools/js/performance.js +73 -73
- package/cli/commands/dev/devtools/js/router.js +105 -105
- package/cli/commands/dev/devtools/js/source.js +132 -132
- package/cli/commands/dev/devtools/js/stats.js +35 -35
- package/cli/commands/dev/devtools/js/tabs.js +79 -79
- package/cli/commands/dev/devtools/panel.html +95 -95
- package/cli/commands/dev/devtools/styles.css +244 -244
- package/cli/commands/dev/index.js +107 -107
- package/cli/commands/dev/logger.js +75 -75
- package/cli/commands/dev/overlay.js +858 -858
- package/cli/commands/dev/server.js +220 -220
- package/cli/commands/dev/validator.js +94 -94
- package/cli/commands/dev/watcher.js +172 -172
- package/cli/help.js +114 -112
- package/cli/index.js +52 -52
- package/cli/scaffold/default/LICENSE +21 -21
- package/cli/scaffold/default/app/app.js +207 -207
- package/cli/scaffold/default/app/components/about.js +201 -201
- package/cli/scaffold/default/app/components/api-demo.js +143 -143
- package/cli/scaffold/default/app/components/contact-card.js +231 -231
- package/cli/scaffold/default/app/components/contacts/contacts.css +706 -706
- package/cli/scaffold/default/app/components/contacts/contacts.html +200 -200
- package/cli/scaffold/default/app/components/contacts/contacts.js +196 -196
- package/cli/scaffold/default/app/components/counter.js +127 -127
- package/cli/scaffold/default/app/components/home.js +249 -249
- package/cli/scaffold/default/app/components/not-found.js +16 -16
- package/cli/scaffold/default/app/components/playground/playground.css +115 -115
- package/cli/scaffold/default/app/components/playground/playground.html +161 -161
- package/cli/scaffold/default/app/components/playground/playground.js +116 -116
- package/cli/scaffold/default/app/components/todos.js +225 -225
- package/cli/scaffold/default/app/components/toolkit/toolkit.css +97 -97
- package/cli/scaffold/default/app/components/toolkit/toolkit.html +146 -146
- package/cli/scaffold/default/app/components/toolkit/toolkit.js +280 -280
- package/cli/scaffold/default/app/routes.js +15 -15
- package/cli/scaffold/default/app/store.js +101 -101
- package/cli/scaffold/default/global.css +552 -552
- package/cli/scaffold/default/index.html +99 -99
- package/cli/scaffold/minimal/app/app.js +85 -85
- package/cli/scaffold/minimal/app/components/about.js +68 -68
- package/cli/scaffold/minimal/app/components/counter.js +122 -122
- package/cli/scaffold/minimal/app/components/home.js +68 -68
- package/cli/scaffold/minimal/app/components/not-found.js +16 -16
- package/cli/scaffold/minimal/app/routes.js +9 -9
- package/cli/scaffold/minimal/app/store.js +36 -36
- package/cli/scaffold/minimal/global.css +300 -300
- package/cli/scaffold/minimal/index.html +44 -44
- package/cli/scaffold/ssr/app/app.js +41 -41
- package/cli/scaffold/ssr/app/components/about.js +55 -55
- package/cli/scaffold/ssr/app/components/blog/index.js +65 -65
- package/cli/scaffold/ssr/app/components/blog/post.js +86 -86
- package/cli/scaffold/ssr/app/components/home.js +37 -37
- package/cli/scaffold/ssr/app/components/not-found.js +15 -15
- package/cli/scaffold/ssr/app/routes.js +8 -8
- package/cli/scaffold/ssr/global.css +228 -228
- package/cli/scaffold/ssr/index.html +37 -37
- package/cli/scaffold/ssr/package.json +8 -8
- package/cli/scaffold/ssr/server/data/posts.js +144 -144
- package/cli/scaffold/ssr/server/index.js +213 -213
- package/cli/scaffold/webrtc/app/app.js +11 -0
- package/cli/scaffold/webrtc/app/components/video-room.js +295 -0
- package/cli/scaffold/webrtc/app/lib/room.js +252 -0
- package/cli/scaffold/webrtc/assets/.gitkeep +0 -0
- package/cli/scaffold/webrtc/global.css +250 -0
- package/cli/scaffold/webrtc/index.html +21 -0
- package/cli/utils.js +305 -287
- package/dist/API.md +661 -0
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +10313 -6614
- package/dist/zquery.min.js +8 -631
- package/index.d.ts +570 -371
- package/index.js +311 -240
- package/package.json +76 -70
- package/src/component.js +1709 -1691
- package/src/core.js +921 -921
- package/src/diff.js +497 -497
- package/src/errors.js +209 -209
- package/src/expression.js +922 -922
- package/src/http.js +242 -242
- package/src/package.json +1 -1
- package/src/reactive.js +255 -255
- package/src/router.js +843 -843
- package/src/ssr.js +418 -418
- package/src/store.js +318 -318
- package/src/utils.js +515 -515
- package/src/webrtc/e2ee.js +351 -0
- package/src/webrtc/errors.js +116 -0
- package/src/webrtc/ice.js +301 -0
- package/src/webrtc/index.js +131 -0
- package/src/webrtc/joinToken.js +119 -0
- package/src/webrtc/observe.js +172 -0
- package/src/webrtc/peer.js +351 -0
- package/src/webrtc/reactive.js +268 -0
- package/src/webrtc/room.js +625 -0
- package/src/webrtc/sdp.js +302 -0
- package/src/webrtc/sfu/index.js +43 -0
- package/src/webrtc/sfu/livekit.js +131 -0
- package/src/webrtc/sfu/mediasoup.js +150 -0
- package/src/webrtc/signaling.js +373 -0
- package/src/webrtc/turn.js +237 -0
- package/tests/_helpers/webrtcFakes.js +289 -0
- package/tests/audit.test.js +4158 -4158
- package/tests/cli.test.js +1136 -1103
- package/tests/compare.test.js +497 -486
- package/tests/component.test.js +3969 -3938
- package/tests/core.test.js +1910 -1910
- package/tests/dev-server.test.js +489 -489
- package/tests/diff.test.js +1416 -1416
- package/tests/docs.test.js +1664 -1650
- package/tests/electron-features.test.js +864 -864
- package/tests/errors.test.js +619 -619
- package/tests/expression.test.js +1056 -1056
- package/tests/http.test.js +648 -648
- package/tests/reactive.test.js +819 -819
- package/tests/router.test.js +2327 -2327
- package/tests/ssr.test.js +870 -870
- package/tests/store.test.js +830 -830
- package/tests/test-minifier.js +153 -153
- package/tests/test-ssr.js +27 -27
- package/tests/utils.test.js +1377 -1377
- package/tests/webrtc/e2ee.test.js +283 -0
- package/tests/webrtc/ice.test.js +202 -0
- package/tests/webrtc/joinToken.test.js +89 -0
- package/tests/webrtc/observe.test.js +111 -0
- package/tests/webrtc/peer.test.js +373 -0
- package/tests/webrtc/reactive.test.js +235 -0
- package/tests/webrtc/room.test.js +406 -0
- package/tests/webrtc/sdp.test.js +151 -0
- package/tests/webrtc/sfu-livekit.test.js +119 -0
- package/tests/webrtc/sfu.test.js +160 -0
- package/tests/webrtc/signaling.test.js +251 -0
- package/tests/webrtc/turn.test.js +256 -0
- package/types/collection.d.ts +383 -383
- package/types/component.d.ts +186 -186
- package/types/errors.d.ts +135 -135
- package/types/http.d.ts +92 -92
- package/types/misc.d.ts +201 -201
- package/types/reactive.d.ts +98 -98
- package/types/router.d.ts +190 -190
- package/types/ssr.d.ts +102 -102
- package/types/store.d.ts +146 -146
- package/types/utils.d.ts +245 -245
- package/types/webrtc.d.ts +653 -0
|
@@ -1,858 +1,858 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cli/commands/dev/overlay.js - Client-side error overlay + SSE live-reload
|
|
3
|
-
*
|
|
4
|
-
* Returns an HTML <script> snippet that is injected before </body> in
|
|
5
|
-
* every HTML response served by the dev server. Responsibilities:
|
|
6
|
-
*
|
|
7
|
-
* 1. Error Overlay - full-screen dark overlay with code frames, stack
|
|
8
|
-
* traces, and ZQueryError metadata. Dismissable via Esc or ×.
|
|
9
|
-
* 2. Runtime error hooks - window.onerror, unhandledrejection, AND
|
|
10
|
-
* the zQuery $.onError() hook so framework-level errors are
|
|
11
|
-
* surfaced in the overlay automatically.
|
|
12
|
-
* 3. SSE connection - listens for reload / css / error:syntax /
|
|
13
|
-
* error:clear events from the dev server watcher.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
'use strict';
|
|
17
|
-
|
|
18
|
-
// The snippet is a self-contained IIFE - no external dependencies.
|
|
19
|
-
// It must work in all browsers that support EventSource (IE11 excluded).
|
|
20
|
-
|
|
21
|
-
const OVERLAY_SCRIPT = `<script>
|
|
22
|
-
(function(){
|
|
23
|
-
// =====================================================================
|
|
24
|
-
// Error overlay
|
|
25
|
-
// =====================================================================
|
|
26
|
-
var overlayEl = null;
|
|
27
|
-
|
|
28
|
-
var OVERLAY_CSS =
|
|
29
|
-
'position:fixed;top:0;left:0;width:100%;height:100%;' +
|
|
30
|
-
'background:rgba(0,0,0,0.92);color:#fff;z-index:2147483647;' +
|
|
31
|
-
'font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;' +
|
|
32
|
-
'font-size:13px;overflow-y:auto;padding:0;margin:0;box-sizing:border-box;';
|
|
33
|
-
|
|
34
|
-
var HEADER_CSS =
|
|
35
|
-
'padding:20px 24px 12px;border-bottom:1px solid rgba(255,255,255,0.1);' +
|
|
36
|
-
'display:flex;align-items:flex-start;justify-content:space-between;';
|
|
37
|
-
|
|
38
|
-
var BADGE_CSS =
|
|
39
|
-
'display:inline-block;padding:3px 8px;border-radius:4px;font-size:11px;' +
|
|
40
|
-
'font-weight:700;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:8px;';
|
|
41
|
-
|
|
42
|
-
// Map ZQueryError code prefixes to colours so devs can see at a glance
|
|
43
|
-
// which subsystem produced the error.
|
|
44
|
-
var CODE_COLORS = {
|
|
45
|
-
'ZQ_REACTIVE': '#9b59b6',
|
|
46
|
-
'ZQ_SIGNAL': '#9b59b6',
|
|
47
|
-
'ZQ_EFFECT': '#9b59b6',
|
|
48
|
-
'ZQ_EXPR': '#2980b9',
|
|
49
|
-
'ZQ_COMP': '#16a085',
|
|
50
|
-
'ZQ_ROUTER': '#d35400',
|
|
51
|
-
'ZQ_STORE': '#8e44ad',
|
|
52
|
-
'ZQ_HTTP': '#2c3e50',
|
|
53
|
-
'ZQ_DEV': '#e74c3c',
|
|
54
|
-
'ZQ_INVALID': '#7f8c8d',
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
function badgeColor(data) {
|
|
58
|
-
if (data.code) {
|
|
59
|
-
var keys = Object.keys(CODE_COLORS);
|
|
60
|
-
for (var i = 0; i < keys.length; i++) {
|
|
61
|
-
if (data.code.indexOf(keys[i]) === 0) return CODE_COLORS[keys[i]];
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
if (data.type && /syntax|parse/i.test(data.type)) return '#e74c3c';
|
|
65
|
-
return '#e67e22';
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function esc(s) {
|
|
69
|
-
var d = document.createElement('div');
|
|
70
|
-
d.appendChild(document.createTextNode(s));
|
|
71
|
-
return d.innerHTML;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function createOverlay(data) {
|
|
75
|
-
removeOverlay();
|
|
76
|
-
var wrap = document.createElement('div');
|
|
77
|
-
wrap.id = '__zq_error_overlay';
|
|
78
|
-
wrap.setAttribute('style', OVERLAY_CSS);
|
|
79
|
-
wrap.setAttribute('tabindex', '-1');
|
|
80
|
-
|
|
81
|
-
var color = badgeColor(data);
|
|
82
|
-
var html = '';
|
|
83
|
-
|
|
84
|
-
// ----- header row -----
|
|
85
|
-
html += '<div style="' + HEADER_CSS + '">';
|
|
86
|
-
html += '<div>';
|
|
87
|
-
|
|
88
|
-
// Error code badge (if present)
|
|
89
|
-
if (data.code) {
|
|
90
|
-
html += '<span style="' + BADGE_CSS + 'background:' + color + ';margin-right:6px;">' + esc(data.code) + '</span>';
|
|
91
|
-
}
|
|
92
|
-
// Type badge
|
|
93
|
-
html += '<span style="' + BADGE_CSS + 'background:' + (data.code ? 'rgba(255,255,255,0.1)' : color) + ';">' + esc(data.type || 'Error') + '</span>';
|
|
94
|
-
|
|
95
|
-
// Message
|
|
96
|
-
html += '<div style="font-size:18px;font-weight:600;line-height:1.4;color:#ff6b6b;margin-top:4px;">';
|
|
97
|
-
html += esc(data.message || 'Unknown error');
|
|
98
|
-
html += '</div></div>';
|
|
99
|
-
|
|
100
|
-
// Close button
|
|
101
|
-
html += '<button id="__zq_close" style="' +
|
|
102
|
-
'background:none;border:1px solid rgba(255,255,255,0.2);color:#999;' +
|
|
103
|
-
'font-size:20px;cursor:pointer;border-radius:6px;width:32px;height:32px;' +
|
|
104
|
-
'display:flex;align-items:center;justify-content:center;flex-shrink:0;' +
|
|
105
|
-
'margin-left:16px;transition:all 0.15s;"' +
|
|
106
|
-
' onmouseover="this.style.color=\\'#fff\\';this.style.borderColor=\\'rgba(255,255,255,0.5)\\'"' +
|
|
107
|
-
' onmouseout="this.style.color=\\'#999\\';this.style.borderColor=\\'rgba(255,255,255,0.2)\\'"' +
|
|
108
|
-
'>×</button>';
|
|
109
|
-
html += '</div>';
|
|
110
|
-
|
|
111
|
-
// ----- file location -----
|
|
112
|
-
if (data.file) {
|
|
113
|
-
html += '<div style="padding:10px 24px;color:#8be9fd;font-size:13px;">';
|
|
114
|
-
html += '<span style="color:#888;">File: </span>' + esc(data.file);
|
|
115
|
-
if (data.line) html += '<span style="color:#888;">:</span>' + data.line;
|
|
116
|
-
if (data.column) html += '<span style="color:#888;">:</span>' + data.column;
|
|
117
|
-
html += '</div>';
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ----- ZQueryError context (key/value pairs) -----
|
|
121
|
-
if (data.context && typeof data.context === 'object' && Object.keys(data.context).length) {
|
|
122
|
-
html += '<div style="padding:8px 24px;display:flex;flex-wrap:wrap;gap:8px;">';
|
|
123
|
-
var ctxKeys = Object.keys(data.context);
|
|
124
|
-
for (var ci = 0; ci < ctxKeys.length; ci++) {
|
|
125
|
-
var k = ctxKeys[ci], v = data.context[k];
|
|
126
|
-
html += '<span style="background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08);' +
|
|
127
|
-
'padding:3px 10px;border-radius:4px;font-size:12px;">' +
|
|
128
|
-
'<span style="color:#888;">' + esc(k) + ': </span>' +
|
|
129
|
-
'<span style="color:#f1fa8c;">' + esc(typeof v === 'object' ? JSON.stringify(v) : String(v)) + '</span>' +
|
|
130
|
-
'</span>';
|
|
131
|
-
}
|
|
132
|
-
html += '</div>';
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// ----- code frame -----
|
|
136
|
-
if (data.frame) {
|
|
137
|
-
html += '<pre style="' +
|
|
138
|
-
'margin:0;padding:16px 24px;background:rgba(255,255,255,0.04);' +
|
|
139
|
-
'border-top:1px solid rgba(255,255,255,0.06);' +
|
|
140
|
-
'border-bottom:1px solid rgba(255,255,255,0.06);' +
|
|
141
|
-
'overflow-x:auto;line-height:1.6;font-size:13px;">';
|
|
142
|
-
var lines = data.frame.split('\\n');
|
|
143
|
-
for (var fi = 0; fi < lines.length; fi++) {
|
|
144
|
-
var fl = lines[fi];
|
|
145
|
-
if (fl.charAt(0) === '>') {
|
|
146
|
-
html += '<span style="color:#ff6b6b;font-weight:600;">' + esc(fl) + '</span>\\n';
|
|
147
|
-
} else if (fl.indexOf('^') !== -1 && fl.trim().replace(/[\\s|^]/g, '') === '') {
|
|
148
|
-
html += '<span style="color:#e74c3c;font-weight:700;">' + esc(fl) + '</span>\\n';
|
|
149
|
-
} else {
|
|
150
|
-
html += '<span style="color:#999;">' + esc(fl) + '</span>\\n';
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
html += '</pre>';
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// ----- stack trace -----
|
|
157
|
-
if (data.stack) {
|
|
158
|
-
html += '<div style="padding:16px 24px;">';
|
|
159
|
-
html += '<div style="color:#888;font-size:11px;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:8px;">Stack Trace</div>';
|
|
160
|
-
html += '<pre style="margin:0;color:#bbb;font-size:12px;line-height:1.7;white-space:pre-wrap;word-break:break-word;">';
|
|
161
|
-
html += esc(data.stack);
|
|
162
|
-
html += '</pre></div>';
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// ----- tip -----
|
|
166
|
-
html += '<div style="padding:16px 24px;color:#555;font-size:11px;border-top:1px solid rgba(255,255,255,0.06);">';
|
|
167
|
-
html += 'Fix the error and save \\u2014 the overlay will clear automatically. Press <kbd style="' +
|
|
168
|
-
'background:rgba(255,255,255,0.1);padding:1px 6px;border-radius:3px;font-size:11px;' +
|
|
169
|
-
'">Esc</kbd> to dismiss.';
|
|
170
|
-
html += '</div>';
|
|
171
|
-
|
|
172
|
-
wrap.innerHTML = html;
|
|
173
|
-
document.body.appendChild(wrap);
|
|
174
|
-
overlayEl = wrap;
|
|
175
|
-
|
|
176
|
-
var closeBtn = document.getElementById('__zq_close');
|
|
177
|
-
if (closeBtn) closeBtn.addEventListener('click', removeOverlay);
|
|
178
|
-
wrap.addEventListener('keydown', function(e) { if (e.key === 'Escape') removeOverlay(); });
|
|
179
|
-
wrap.focus();
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function removeOverlay() {
|
|
183
|
-
if (overlayEl && overlayEl.parentNode) overlayEl.parentNode.removeChild(overlayEl);
|
|
184
|
-
overlayEl = null;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// =====================================================================
|
|
188
|
-
// Console helper
|
|
189
|
-
// =====================================================================
|
|
190
|
-
function logToConsole(data) {
|
|
191
|
-
var label = data.code ? data.code + ' ' : '';
|
|
192
|
-
var msg = '\\n%c zQuery DevError %c ' + label + data.type + ': ' + data.message;
|
|
193
|
-
if (data.file) msg += '\\n at ' + data.file + (data.line ? ':' + data.line : '') + (data.column ? ':' + data.column : '');
|
|
194
|
-
console.error(msg, 'background:#e74c3c;color:#fff;padding:2px 6px;border-radius:3px;font-weight:700;', 'color:inherit;');
|
|
195
|
-
if (data.frame) console.error(data.frame);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function cleanStack(stack) {
|
|
199
|
-
return stack.split('\\n')
|
|
200
|
-
.filter(function(l) { return l.indexOf('__zq_') === -1 && l.indexOf('EventSource') === -1; })
|
|
201
|
-
.map(function(l) { return l.replace(location.origin, ''); })
|
|
202
|
-
.join('\\n');
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// =====================================================================
|
|
206
|
-
// Runtime error hooks
|
|
207
|
-
// =====================================================================
|
|
208
|
-
window.addEventListener('error', function(e) {
|
|
209
|
-
if (!e.filename) return;
|
|
210
|
-
var err = e.error || {};
|
|
211
|
-
var data = {
|
|
212
|
-
code: err.code || '',
|
|
213
|
-
type: (err.constructor && err.constructor.name) || 'Error',
|
|
214
|
-
message: e.message || String(err),
|
|
215
|
-
file: e.filename.replace(location.origin, ''),
|
|
216
|
-
line: e.lineno || 0,
|
|
217
|
-
column: e.colno || 0,
|
|
218
|
-
context: err.context || null,
|
|
219
|
-
stack: err.stack ? cleanStack(err.stack) : ''
|
|
220
|
-
};
|
|
221
|
-
createOverlay(data);
|
|
222
|
-
logToConsole(data);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
window.addEventListener('unhandledrejection', function(e) {
|
|
226
|
-
var err = e.reason || {};
|
|
227
|
-
var data = {
|
|
228
|
-
code: err.code || '',
|
|
229
|
-
type: err.name === 'ZQueryError' ? 'ZQueryError' : 'Unhandled Promise Rejection',
|
|
230
|
-
message: err.message || String(err),
|
|
231
|
-
context: err.context || null,
|
|
232
|
-
stack: err.stack ? cleanStack(err.stack) : ''
|
|
233
|
-
};
|
|
234
|
-
createOverlay(data);
|
|
235
|
-
logToConsole(data);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// =====================================================================
|
|
239
|
-
// Hook into zQuery's $.onError() when the library is loaded
|
|
240
|
-
// =====================================================================
|
|
241
|
-
function hookZQueryErrors() {
|
|
242
|
-
// $.onError is set by the framework - wait for it
|
|
243
|
-
if (typeof $ !== 'undefined' && typeof $.onError === 'function') {
|
|
244
|
-
$.onError(function(zqErr) {
|
|
245
|
-
var data = {
|
|
246
|
-
code: zqErr.code || '',
|
|
247
|
-
type: 'ZQueryError',
|
|
248
|
-
message: zqErr.message,
|
|
249
|
-
context: zqErr.context || null,
|
|
250
|
-
stack: zqErr.stack ? cleanStack(zqErr.stack) : ''
|
|
251
|
-
};
|
|
252
|
-
createOverlay(data);
|
|
253
|
-
logToConsole(data);
|
|
254
|
-
});
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
// Retry until the library has loaded (max ~5s)
|
|
258
|
-
if (hookZQueryErrors._tries < 50) {
|
|
259
|
-
hookZQueryErrors._tries++;
|
|
260
|
-
setTimeout(hookZQueryErrors, 100);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
hookZQueryErrors._tries = 0;
|
|
264
|
-
// Defer so the page's own scripts load first
|
|
265
|
-
if (document.readyState === 'loading') {
|
|
266
|
-
document.addEventListener('DOMContentLoaded', hookZQueryErrors);
|
|
267
|
-
} else {
|
|
268
|
-
setTimeout(hookZQueryErrors, 0);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// =====================================================================
|
|
272
|
-
// SSE connection (live-reload + server-pushed errors)
|
|
273
|
-
// =====================================================================
|
|
274
|
-
var es, reconnectTimer;
|
|
275
|
-
|
|
276
|
-
function connect() {
|
|
277
|
-
es = new EventSource('/__zq_reload');
|
|
278
|
-
|
|
279
|
-
es.addEventListener('reload', function() {
|
|
280
|
-
removeOverlay();
|
|
281
|
-
location.reload();
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
es.addEventListener('css', function(e) {
|
|
285
|
-
var changedPath = (e.data || '').replace(/^\\/+/, '');
|
|
286
|
-
var matched = false;
|
|
287
|
-
|
|
288
|
-
// 1) Try cache-busting matching <link rel="stylesheet"> tags
|
|
289
|
-
var sheets = document.querySelectorAll('link[rel="stylesheet"]');
|
|
290
|
-
sheets.forEach(function(l) {
|
|
291
|
-
var href = l.getAttribute('href');
|
|
292
|
-
if (!href) return;
|
|
293
|
-
var clean = href.replace(/[?&]_zqr=\\d+/, '').replace(/^\\/+/, '');
|
|
294
|
-
if (changedPath && clean.indexOf(changedPath) === -1) return;
|
|
295
|
-
matched = true;
|
|
296
|
-
var sep = href.indexOf('?') >= 0 ? '&' : '?';
|
|
297
|
-
l.setAttribute('href', href.replace(/[?&]_zqr=\\d+/, '') + sep + '_zqr=' + Date.now());
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
// 2) Try hot-swapping scoped <style data-zq-style-urls> elements
|
|
301
|
-
// These come from component styleUrl - the CSS was fetched, scoped,
|
|
302
|
-
// and injected as an inline <style>. We re-fetch and re-scope it.
|
|
303
|
-
if (!matched) {
|
|
304
|
-
var scopedEls = document.querySelectorAll('style[data-zq-style-urls]');
|
|
305
|
-
scopedEls.forEach(function(el) {
|
|
306
|
-
var urls = el.getAttribute('data-zq-style-urls') || '';
|
|
307
|
-
var hit = urls.split(' ').some(function(u) {
|
|
308
|
-
return u && u.replace(/^\\/+/, '').indexOf(changedPath) !== -1;
|
|
309
|
-
});
|
|
310
|
-
if (!hit) return;
|
|
311
|
-
matched = true;
|
|
312
|
-
|
|
313
|
-
var scopeAttr = el.getAttribute('data-zq-scope') || '';
|
|
314
|
-
var inlineStyles = el.getAttribute('data-zq-inline') || '';
|
|
315
|
-
|
|
316
|
-
// Re-fetch all style URLs (cache-busted)
|
|
317
|
-
var urlList = urls.split(' ').filter(Boolean);
|
|
318
|
-
var prevCSS = el.textContent; // preserve current styles as rollback
|
|
319
|
-
Promise.all(urlList.map(function(u) {
|
|
320
|
-
return fetch(u + (u.indexOf('?') >= 0 ? '&' : '?') + '_zqr=' + Date.now())
|
|
321
|
-
.then(function(r) {
|
|
322
|
-
if (!r.ok) throw new Error('CSS fetch failed: ' + r.status);
|
|
323
|
-
return r.text();
|
|
324
|
-
});
|
|
325
|
-
})).then(function(results) {
|
|
326
|
-
var raw = (inlineStyles ? inlineStyles + '\\n' : '') + results.join('\\n');
|
|
327
|
-
// Re-scope CSS with the same scope attribute
|
|
328
|
-
if (scopeAttr) {
|
|
329
|
-
var inAt = 0;
|
|
330
|
-
raw = raw.replace(/([^{}]+)\\{|\\}/g, function(m, sel) {
|
|
331
|
-
if (m === '}') { if (inAt > 0) inAt--; return m; }
|
|
332
|
-
var t = sel.trim();
|
|
333
|
-
if (t.charAt(0) === '@') { inAt++; return m; }
|
|
334
|
-
if (inAt > 0 && /^[\\d%\\s,fromto]+$/.test(t.replace(/\\s/g, ''))) return m;
|
|
335
|
-
return sel.split(',').map(function(s) { return '[' + scopeAttr + '] ' + s.trim(); }).join(', ') + ' {';
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
el.textContent = raw;
|
|
339
|
-
}).catch(function() {
|
|
340
|
-
// Restore previous CSS on failure to prevent blank page
|
|
341
|
-
el.textContent = prevCSS;
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// 3) Nothing matched - fall back to full reload
|
|
347
|
-
if (!matched) { location.reload(); }
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
es.addEventListener('error:syntax', function(e) {
|
|
351
|
-
try { var data = JSON.parse(e.data); createOverlay(data); logToConsole(data); } catch(_){}
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
es.addEventListener('error:runtime', function(e) {
|
|
355
|
-
try { var data = JSON.parse(e.data); createOverlay(data); logToConsole(data); } catch(_){}
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
es.addEventListener('error:clear', function() {
|
|
359
|
-
removeOverlay();
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
es.onerror = function() {
|
|
363
|
-
es.close();
|
|
364
|
-
clearTimeout(reconnectTimer);
|
|
365
|
-
reconnectTimer = setTimeout(connect, 2000);
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
connect();
|
|
370
|
-
|
|
371
|
-
// =====================================================================
|
|
372
|
-
// Fetch / $.http Interceptor - pretty console logging
|
|
373
|
-
// =====================================================================
|
|
374
|
-
var __zqChannel;
|
|
375
|
-
try { __zqChannel = new BroadcastChannel('__zq_devtools'); } catch(e) {}
|
|
376
|
-
|
|
377
|
-
var __zqRequests = [];
|
|
378
|
-
var __zqMorphEvents = [];
|
|
379
|
-
var __zqMorphCount = 0;
|
|
380
|
-
var __zqRenderCount = 0;
|
|
381
|
-
var __zqReqId = 0;
|
|
382
|
-
var _origFetch = window.fetch;
|
|
383
|
-
|
|
384
|
-
window.fetch = function(input, init) {
|
|
385
|
-
var url = typeof input === 'string' ? input : (input && input.url ? input.url : String(input));
|
|
386
|
-
var method = ((init && init.method) || (input && input.method) || 'GET').toUpperCase();
|
|
387
|
-
var id = ++__zqReqId;
|
|
388
|
-
var start = performance.now();
|
|
389
|
-
|
|
390
|
-
// Skip internal dev-server requests
|
|
391
|
-
if (url.indexOf('__zq_') !== -1 || url.indexOf('/_devtools') !== -1) {
|
|
392
|
-
return _origFetch.apply(this, arguments);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
return _origFetch.apply(this, arguments).then(function(response) {
|
|
396
|
-
var elapsed = Math.round(performance.now() - start);
|
|
397
|
-
var status = response.status;
|
|
398
|
-
var cloned = response.clone();
|
|
399
|
-
|
|
400
|
-
cloned.text().then(function(bodyText) {
|
|
401
|
-
var entry = {
|
|
402
|
-
id: id, method: method, url: url, status: status,
|
|
403
|
-
elapsed: elapsed, bodyPreview: bodyText.slice(0, 5000),
|
|
404
|
-
timestamp: Date.now()
|
|
405
|
-
};
|
|
406
|
-
__zqRequests.push(entry);
|
|
407
|
-
if (__zqRequests.length > 500) __zqRequests.shift();
|
|
408
|
-
updateDevBar();
|
|
409
|
-
|
|
410
|
-
// Pretty console log
|
|
411
|
-
var isOk = status >= 200 && status < 300;
|
|
412
|
-
var color = isOk ? '#2ecc71' : status < 400 ? '#f39c12' : '#e74c3c';
|
|
413
|
-
|
|
414
|
-
console.groupCollapsed(
|
|
415
|
-
'%c ' + method + ' %c' + status + '%c ' + url + ' %c' + elapsed + 'ms',
|
|
416
|
-
'background:' + color + ';color:#fff;padding:2px 6px;border-radius:3px;font-weight:700;font-size:11px',
|
|
417
|
-
'color:' + color + ';font-weight:700;margin-left:8px',
|
|
418
|
-
'color:inherit;margin-left:4px',
|
|
419
|
-
'color:#888;margin-left:8px;font-size:11px'
|
|
420
|
-
);
|
|
421
|
-
|
|
422
|
-
// Response body
|
|
423
|
-
try {
|
|
424
|
-
var parsed = JSON.parse(bodyText);
|
|
425
|
-
console.log('%c Response ', 'background:#1e1e2e;color:#8be9fd;padding:2px 6px;border-radius:2px;font-weight:600', parsed);
|
|
426
|
-
} catch(pe) {
|
|
427
|
-
if (bodyText.length > 0) {
|
|
428
|
-
console.log('%c Response ', 'background:#1e1e2e;color:#8be9fd;padding:2px 6px;border-radius:2px;font-weight:600',
|
|
429
|
-
bodyText.length > 500 ? bodyText.slice(0, 500) + '... (' + bodyText.length + ' chars)' : bodyText);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// Headers
|
|
434
|
-
try {
|
|
435
|
-
console.log('%c Headers ', 'background:#1e1e2e;color:#bd93f9;padding:2px 6px;border-radius:2px;font-weight:600',
|
|
436
|
-
Object.fromEntries(response.headers.entries()));
|
|
437
|
-
} catch(he) {}
|
|
438
|
-
|
|
439
|
-
// Request body (if sent)
|
|
440
|
-
if (init && init.body) {
|
|
441
|
-
try {
|
|
442
|
-
console.log('%c Request ', 'background:#1e1e2e;color:#f1fa8c;padding:2px 6px;border-radius:2px;font-weight:600',
|
|
443
|
-
JSON.parse(init.body));
|
|
444
|
-
} catch(re) {
|
|
445
|
-
console.log('%c Request ', 'background:#1e1e2e;color:#f1fa8c;padding:2px 6px;border-radius:2px;font-weight:600',
|
|
446
|
-
String(init.body).slice(0, 500));
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
console.groupEnd();
|
|
451
|
-
|
|
452
|
-
// Broadcast to devtools
|
|
453
|
-
if (__zqChannel) {
|
|
454
|
-
try { __zqChannel.postMessage({ type: 'http', data: entry }); } catch(ce) {}
|
|
455
|
-
}
|
|
456
|
-
}).catch(function() {});
|
|
457
|
-
|
|
458
|
-
return response;
|
|
459
|
-
}, function(err) {
|
|
460
|
-
var elapsed = Math.round(performance.now() - start);
|
|
461
|
-
console.groupCollapsed(
|
|
462
|
-
'%c ' + method + ' %cERR%c ' + url + ' %c' + elapsed + 'ms',
|
|
463
|
-
'background:#e74c3c;color:#fff;padding:2px 6px;border-radius:3px;font-weight:700;font-size:11px',
|
|
464
|
-
'color:#e74c3c;font-weight:700;margin-left:8px',
|
|
465
|
-
'color:inherit;margin-left:4px',
|
|
466
|
-
'color:#888;margin-left:8px;font-size:11px'
|
|
467
|
-
);
|
|
468
|
-
console.error(err);
|
|
469
|
-
console.groupEnd();
|
|
470
|
-
|
|
471
|
-
var entry = { id: id, method: method, url: url, status: 0, elapsed: elapsed, bodyPreview: err.message, timestamp: Date.now() };
|
|
472
|
-
__zqRequests.push(entry);
|
|
473
|
-
updateDevBar();
|
|
474
|
-
if (__zqChannel) {
|
|
475
|
-
try { __zqChannel.postMessage({ type: 'http', data: entry }); } catch(ce) {}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
throw err;
|
|
479
|
-
});
|
|
480
|
-
};
|
|
481
|
-
|
|
482
|
-
// =====================================================================
|
|
483
|
-
// Morph instrumentation - hook via window.__zqMorphHook (set by diff.js)
|
|
484
|
-
// =====================================================================
|
|
485
|
-
window.__zqMorphHook = function(el, elapsed) {
|
|
486
|
-
__zqMorphCount++;
|
|
487
|
-
updateDevBar();
|
|
488
|
-
|
|
489
|
-
var evt = { target: el.id || el.tagName.toLowerCase(), elapsed: elapsed, kind: 'morph', timestamp: Date.now() };
|
|
490
|
-
__zqMorphEvents.push(evt);
|
|
491
|
-
if (__zqMorphEvents.length > 200) __zqMorphEvents.shift();
|
|
492
|
-
|
|
493
|
-
// Console timing for slow morphs (> 4ms)
|
|
494
|
-
if (elapsed > 4) {
|
|
495
|
-
console.log(
|
|
496
|
-
'%c morph %c' + elapsed.toFixed(2) + 'ms%c ' + (el.id || el.tagName.toLowerCase()),
|
|
497
|
-
'background:#9b59b6;color:#fff;padding:2px 6px;border-radius:3px;font-weight:700;font-size:11px',
|
|
498
|
-
'color:' + (elapsed > 16 ? '#e74c3c' : '#f39c12') + ';font-weight:700;margin-left:8px',
|
|
499
|
-
'color:#888;margin-left:4px'
|
|
500
|
-
);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// Broadcast to devtools
|
|
504
|
-
if (__zqChannel) {
|
|
505
|
-
try {
|
|
506
|
-
__zqChannel.postMessage({
|
|
507
|
-
type: 'morph-detail',
|
|
508
|
-
data: evt
|
|
509
|
-
});
|
|
510
|
-
} catch(ce) {}
|
|
511
|
-
}
|
|
512
|
-
};
|
|
513
|
-
|
|
514
|
-
// =====================================================================
|
|
515
|
-
// Render instrumentation - hook for first-renders & route swaps
|
|
516
|
-
// =====================================================================
|
|
517
|
-
window.__zqRenderHook = function(el, elapsed, kind, name) {
|
|
518
|
-
__zqRenderCount++;
|
|
519
|
-
__zqMorphCount++; // count renders in the morph total for the toolbar
|
|
520
|
-
updateDevBar();
|
|
521
|
-
|
|
522
|
-
var evt = { target: name || el.id || el.tagName.toLowerCase(), elapsed: elapsed, kind: kind, timestamp: Date.now() };
|
|
523
|
-
__zqMorphEvents.push(evt);
|
|
524
|
-
if (__zqMorphEvents.length > 200) __zqMorphEvents.shift();
|
|
525
|
-
|
|
526
|
-
// Console log for route/mount renders
|
|
527
|
-
var label = kind === 'route' ? ' route ' : ' mount ';
|
|
528
|
-
var bg = kind === 'route' ? '#d29922' : '#3fb950';
|
|
529
|
-
console.log(
|
|
530
|
-
'%c' + label + '%c' + elapsed.toFixed(2) + 'ms%c ' + (name || el.id || el.tagName.toLowerCase()),
|
|
531
|
-
'background:' + bg + ';color:#fff;padding:2px 6px;border-radius:3px;font-weight:700;font-size:11px',
|
|
532
|
-
'color:' + (elapsed > 16 ? '#e74c3c' : '#888') + ';font-weight:700;margin-left:8px',
|
|
533
|
-
'color:#888;margin-left:4px'
|
|
534
|
-
);
|
|
535
|
-
|
|
536
|
-
// Broadcast to devtools
|
|
537
|
-
if (__zqChannel) {
|
|
538
|
-
try {
|
|
539
|
-
__zqChannel.postMessage({
|
|
540
|
-
type: 'render-detail',
|
|
541
|
-
data: evt
|
|
542
|
-
});
|
|
543
|
-
} catch(ce) {}
|
|
544
|
-
}
|
|
545
|
-
};
|
|
546
|
-
|
|
547
|
-
// =====================================================================
|
|
548
|
-
// Router instrumentation - history state tracking for devtools
|
|
549
|
-
// =====================================================================
|
|
550
|
-
var __zqRouterEvents = [];
|
|
551
|
-
|
|
552
|
-
var _origPushState = history.pushState;
|
|
553
|
-
history.pushState = function(state, title, url) {
|
|
554
|
-
_origPushState.apply(this, arguments);
|
|
555
|
-
var isSubstate = state && state.__zq === 'substate';
|
|
556
|
-
var evt = {
|
|
557
|
-
action: isSubstate ? 'substate' : 'navigate',
|
|
558
|
-
url: String(url || location.href).replace(location.origin, ''),
|
|
559
|
-
key: isSubstate ? state.key : null,
|
|
560
|
-
data: isSubstate ? state.data : null,
|
|
561
|
-
timestamp: Date.now()
|
|
562
|
-
};
|
|
563
|
-
__zqRouterEvents.push(evt);
|
|
564
|
-
if (__zqRouterEvents.length > 200) __zqRouterEvents.shift();
|
|
565
|
-
if (__zqChannel) {
|
|
566
|
-
try { __zqChannel.postMessage({ type: 'router', data: evt }); } catch(e) {}
|
|
567
|
-
}
|
|
568
|
-
updateDevBar();
|
|
569
|
-
};
|
|
570
|
-
|
|
571
|
-
var _origReplaceState = history.replaceState;
|
|
572
|
-
history.replaceState = function(state, title, url) {
|
|
573
|
-
_origReplaceState.apply(this, arguments);
|
|
574
|
-
var isSubstate = state && state.__zq === 'substate';
|
|
575
|
-
var evt = {
|
|
576
|
-
action: 'replace',
|
|
577
|
-
url: String(url || location.href).replace(location.origin, ''),
|
|
578
|
-
key: isSubstate ? state.key : null,
|
|
579
|
-
data: isSubstate ? state.data : null,
|
|
580
|
-
timestamp: Date.now()
|
|
581
|
-
};
|
|
582
|
-
__zqRouterEvents.push(evt);
|
|
583
|
-
if (__zqRouterEvents.length > 200) __zqRouterEvents.shift();
|
|
584
|
-
if (__zqChannel) {
|
|
585
|
-
try { __zqChannel.postMessage({ type: 'router', data: evt }); } catch(e) {}
|
|
586
|
-
}
|
|
587
|
-
updateDevBar();
|
|
588
|
-
};
|
|
589
|
-
|
|
590
|
-
window.addEventListener('popstate', function(e) {
|
|
591
|
-
var state = e.state;
|
|
592
|
-
var isSubstate = state && state.__zq === 'substate';
|
|
593
|
-
var evt = {
|
|
594
|
-
action: isSubstate ? 'pop-substate' : 'pop',
|
|
595
|
-
url: location.pathname + location.hash,
|
|
596
|
-
key: isSubstate ? state.key : null,
|
|
597
|
-
data: isSubstate ? state.data : null,
|
|
598
|
-
timestamp: Date.now()
|
|
599
|
-
};
|
|
600
|
-
__zqRouterEvents.push(evt);
|
|
601
|
-
if (__zqRouterEvents.length > 200) __zqRouterEvents.shift();
|
|
602
|
-
if (__zqChannel) {
|
|
603
|
-
try { __zqChannel.postMessage({ type: 'router', data: evt }); } catch(e) {}
|
|
604
|
-
}
|
|
605
|
-
updateDevBar();
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
window.addEventListener('hashchange', function() {
|
|
609
|
-
var evt = {
|
|
610
|
-
action: 'hashchange',
|
|
611
|
-
url: location.hash,
|
|
612
|
-
timestamp: Date.now()
|
|
613
|
-
};
|
|
614
|
-
__zqRouterEvents.push(evt);
|
|
615
|
-
if (__zqRouterEvents.length > 200) __zqRouterEvents.shift();
|
|
616
|
-
if (__zqChannel) {
|
|
617
|
-
try { __zqChannel.postMessage({ type: 'router', data: evt }); } catch(e) {}
|
|
618
|
-
}
|
|
619
|
-
updateDevBar();
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
// =====================================================================
|
|
623
|
-
// Dev Toolbar - expandable floating bar with stats
|
|
624
|
-
// =====================================================================
|
|
625
|
-
var devBar;
|
|
626
|
-
var __zqBarExpanded = false;
|
|
627
|
-
try { __zqBarExpanded = localStorage.getItem('__zq_bar_expanded') === '1'; } catch(e) {}
|
|
628
|
-
var __zqRouteColors = {
|
|
629
|
-
navigate: { bg: 'rgba(63,185,80,0.12)', fg: '#3fb950' },
|
|
630
|
-
replace: { bg: 'rgba(210,153,34,0.12)', fg: '#d29922' },
|
|
631
|
-
pop: { bg: 'rgba(248,81,73,0.12)', fg: '#f85149' },
|
|
632
|
-
'pop-substate':{ bg: 'rgba(248,81,73,0.12)', fg: '#f85149' },
|
|
633
|
-
substate: { bg: 'rgba(168,130,255,0.12)', fg: '#a882ff' },
|
|
634
|
-
hashchange: { bg: 'rgba(88,166,255,0.12)', fg: '#58a6ff' }
|
|
635
|
-
};
|
|
636
|
-
var __zqRouteDefault = { bg: 'rgba(227,155,55,0.12)', fg: '#e39b37' };
|
|
637
|
-
var __zqRouteFadeTimer = null;
|
|
638
|
-
|
|
639
|
-
function createDevBar() {
|
|
640
|
-
devBar = document.createElement('div');
|
|
641
|
-
devBar.id = '__zq_devbar';
|
|
642
|
-
devBar.setAttribute('style',
|
|
643
|
-
'position:fixed;bottom:12px;right:12px;z-index:2147483646;' +
|
|
644
|
-
'display:flex;align-items:center;gap:4px;' +
|
|
645
|
-
'background:rgba(22,27,34,0.92);border:1px solid rgba(48,54,61,0.8);' +
|
|
646
|
-
'border-radius:8px;padding:4px 6px;backdrop-filter:blur(8px);' +
|
|
647
|
-
'font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;' +
|
|
648
|
-
'font-size:11px;color:#8b949e;user-select:none;cursor:default;' +
|
|
649
|
-
'box-shadow:0 4px 12px rgba(0,0,0,0.4);transition:all .25s cubic-bezier(.22,1,.36,1);'
|
|
650
|
-
);
|
|
651
|
-
|
|
652
|
-
var statStyle = 'padding:2px 6px;border-radius:4px;font-size:10px;font-weight:600;cursor:pointer;white-space:nowrap;';
|
|
653
|
-
var expandedStyle = statStyle + 'display:none;transform:scale(0);opacity:0;transform-origin:left center;will-change:transform,opacity;transition:transform .25s cubic-bezier(.22,1,.36,1),opacity .2s ease;';
|
|
654
|
-
|
|
655
|
-
devBar.innerHTML =
|
|
656
|
-
'<span style="color:#58a6ff;font-weight:700;padding:0 4px;font-size:10px;letter-spacing:.5px">zQ</span>' +
|
|
657
|
-
// Expanded-only stats
|
|
658
|
-
'<span id="__zq_bar_route" class="__zq_ex" title="Current route" style="' + expandedStyle +
|
|
659
|
-
'background:rgba(227,155,55,0.12);color:#e39b37;outline-offset:1px;transition:transform .25s cubic-bezier(.22,1,.36,1),opacity .2s ease,background .3s ease,color .3s ease,outline-color .6s ease;">/</span>' +
|
|
660
|
-
'<span id="__zq_bar_comps" class="__zq_ex" title="Registered components" style="' + expandedStyle +
|
|
661
|
-
'background:rgba(168,130,255,0.1);color:#a882ff;">0 comps</span>' +
|
|
662
|
-
// Always-visible stats
|
|
663
|
-
'<span id="__zq_bar_morphs" title="Render operations" style="' + statStyle +
|
|
664
|
-
'background:rgba(188,140,255,0.1);color:#bc8cff;">0 render</span>' +
|
|
665
|
-
'<span id="__zq_bar_reqs" title="Network requests" style="' + statStyle +
|
|
666
|
-
'background:rgba(88,166,255,0.1);color:#58a6ff;">0 req</span>' +
|
|
667
|
-
// Expanded-only stats
|
|
668
|
-
'<span id="__zq_bar_els" class="__zq_ex" title="DOM elements" style="' + expandedStyle +
|
|
669
|
-
'background:rgba(63,185,80,0.1);color:#3fb950;">0 els</span>' +
|
|
670
|
-
// Toggle expand/collapse
|
|
671
|
-
'<button id="__zq_bar_toggle" title="Expand toolbar" style="' +
|
|
672
|
-
'padding:2px 6px;border-radius:4px;font-size:10px;font-weight:600;' +
|
|
673
|
-
'background:rgba(88,166,255,0.08);color:#58a6ff;border:1px solid rgba(88,166,255,0.2);' +
|
|
674
|
-
'cursor:pointer;font-family:inherit;transition:all .15s;line-height:1;' +
|
|
675
|
-
'"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 6 9 12 15 18"/></svg></button>' +
|
|
676
|
-
'<button id="__zq_bar_close" title="Close toolbar" style="' +
|
|
677
|
-
'padding:0 4px;color:#484f58;cursor:pointer;font-size:14px;border:none;' +
|
|
678
|
-
'background:none;font-family:inherit;line-height:1;' +
|
|
679
|
-
'">×</button>';
|
|
680
|
-
|
|
681
|
-
document.body.appendChild(devBar);
|
|
682
|
-
|
|
683
|
-
// If previously expanded, restore that state immediately
|
|
684
|
-
if (__zqBarExpanded) {
|
|
685
|
-
var items = devBar.querySelectorAll('.__zq_ex');
|
|
686
|
-
var btn = document.getElementById('__zq_bar_toggle');
|
|
687
|
-
if (btn) {
|
|
688
|
-
btn.innerHTML = '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 6 15 12 9 18"/></svg>';
|
|
689
|
-
btn.title = 'Collapse toolbar';
|
|
690
|
-
}
|
|
691
|
-
for (var i = 0; i < items.length; i++) {
|
|
692
|
-
items[i].style.display = 'inline';
|
|
693
|
-
items[i].style.transform = 'scale(1)';
|
|
694
|
-
items[i].style.opacity = '1';
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
updateDevBar();
|
|
698
|
-
|
|
699
|
-
// Live-poll stats so numbers stay current without waiting for events
|
|
700
|
-
setInterval(updateDevBar, 1000);
|
|
701
|
-
|
|
702
|
-
// Check if we're inside a devtools split-view iframe
|
|
703
|
-
function isInSplitFrame() {
|
|
704
|
-
try { return window.parent !== window && window.parent.document.getElementById('app-frame'); }
|
|
705
|
-
catch(e) { return false; }
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
// Switch tab in devtools (works for both split iframe and popup)
|
|
709
|
-
function switchDevTab(tab) {
|
|
710
|
-
if (__zqChannel) {
|
|
711
|
-
__zqChannel.postMessage({ type: 'switch-tab', tab: tab });
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
// Open devtools popup
|
|
716
|
-
var __zqPopup = null;
|
|
717
|
-
function openDevToolsPopup(tab) {
|
|
718
|
-
if (__zqPopup && !__zqPopup.closed) {
|
|
719
|
-
switchDevTab(tab);
|
|
720
|
-
__zqPopup.focus();
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
var w = 1080, h = 800;
|
|
724
|
-
var left = window.screenX + window.outerWidth - w - 20;
|
|
725
|
-
var top = window.screenY + 60;
|
|
726
|
-
var url = '/_devtools' + (tab ? '#' + tab : '');
|
|
727
|
-
__zqPopup = window.open(url, '__zq_devtools',
|
|
728
|
-
'width=' + w + ',height=' + h + ',left=' + left + ',top=' + top +
|
|
729
|
-
',resizable=yes,scrollbars=yes');
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
function openTab(tab) {
|
|
733
|
-
if (isInSplitFrame()) { switchDevTab(tab); } else { openDevToolsPopup(tab); }
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
// Stat click handlers → open relevant devtools tab
|
|
737
|
-
document.getElementById('__zq_bar_route').addEventListener('click', function() { openTab('router'); });
|
|
738
|
-
document.getElementById('__zq_bar_comps').addEventListener('click', function() { openTab('components'); });
|
|
739
|
-
document.getElementById('__zq_bar_morphs').addEventListener('click', function() { openTab('perf'); });
|
|
740
|
-
document.getElementById('__zq_bar_reqs').addEventListener('click', function() { openTab('network'); });
|
|
741
|
-
document.getElementById('__zq_bar_els').addEventListener('click', function() { openTab('dom'); });
|
|
742
|
-
|
|
743
|
-
// Expand / collapse toggle
|
|
744
|
-
document.getElementById('__zq_bar_toggle').addEventListener('click', function() {
|
|
745
|
-
__zqBarExpanded = !__zqBarExpanded;
|
|
746
|
-
try { localStorage.setItem('__zq_bar_expanded', __zqBarExpanded ? '1' : '0'); } catch(e) {}
|
|
747
|
-
var items = devBar.querySelectorAll('.__zq_ex');
|
|
748
|
-
var btn = this;
|
|
749
|
-
if (__zqBarExpanded) {
|
|
750
|
-
btn.innerHTML = '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 6 15 12 9 18"/></svg>';
|
|
751
|
-
btn.title = 'Collapse toolbar';
|
|
752
|
-
for (var i = 0; i < items.length; i++) {
|
|
753
|
-
items[i].style.display = 'inline';
|
|
754
|
-
items[i].offsetWidth; // reflow
|
|
755
|
-
items[i].style.transform = 'scale(1)';
|
|
756
|
-
items[i].style.opacity = '1';
|
|
757
|
-
}
|
|
758
|
-
updateDevBar();
|
|
759
|
-
} else {
|
|
760
|
-
btn.innerHTML = '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 6 9 12 15 18"/></svg>';
|
|
761
|
-
btn.title = 'Expand toolbar';
|
|
762
|
-
for (var i = 0; i < items.length; i++) {
|
|
763
|
-
items[i].style.transform = 'scale(0)';
|
|
764
|
-
items[i].style.opacity = '0';
|
|
765
|
-
}
|
|
766
|
-
setTimeout(function() {
|
|
767
|
-
if (!__zqBarExpanded) {
|
|
768
|
-
var items = devBar.querySelectorAll('.__zq_ex');
|
|
769
|
-
for (var i = 0; i < items.length; i++) items[i].style.display = 'none';
|
|
770
|
-
}
|
|
771
|
-
}, 250);
|
|
772
|
-
}
|
|
773
|
-
});
|
|
774
|
-
|
|
775
|
-
// Toggle hover
|
|
776
|
-
document.getElementById('__zq_bar_toggle').addEventListener('mouseover', function() {
|
|
777
|
-
this.style.background = 'rgba(88,166,255,0.18)';
|
|
778
|
-
});
|
|
779
|
-
document.getElementById('__zq_bar_toggle').addEventListener('mouseout', function() {
|
|
780
|
-
this.style.background = 'rgba(88,166,255,0.08)';
|
|
781
|
-
});
|
|
782
|
-
|
|
783
|
-
// Close button
|
|
784
|
-
document.getElementById('__zq_bar_close').addEventListener('click', function() {
|
|
785
|
-
devBar.style.display = 'none';
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
function updateDevBar() {
|
|
790
|
-
if (!devBar) return;
|
|
791
|
-
var reqEl = document.getElementById('__zq_bar_reqs');
|
|
792
|
-
var morphEl = document.getElementById('__zq_bar_morphs');
|
|
793
|
-
if (reqEl) reqEl.textContent = __zqRequests.length + ' req';
|
|
794
|
-
if (morphEl) morphEl.textContent = __zqMorphCount + ' render';
|
|
795
|
-
|
|
796
|
-
if (__zqBarExpanded) {
|
|
797
|
-
var routeEl = document.getElementById('__zq_bar_route');
|
|
798
|
-
var compsEl = document.getElementById('__zq_bar_comps');
|
|
799
|
-
var elsEl = document.getElementById('__zq_bar_els');
|
|
800
|
-
if (routeEl) {
|
|
801
|
-
var lastEvt = __zqRouterEvents.length ? __zqRouterEvents[__zqRouterEvents.length - 1] : null;
|
|
802
|
-
var action = lastEvt ? lastEvt.action : '';
|
|
803
|
-
var path = location.pathname + location.hash;
|
|
804
|
-
if (path.length > 16) path = path.substring(0, 14) + '…';
|
|
805
|
-
var colors = __zqRouteColors[action] || __zqRouteDefault;
|
|
806
|
-
routeEl.style.background = colors.bg;
|
|
807
|
-
routeEl.style.color = colors.fg;
|
|
808
|
-
if (action) {
|
|
809
|
-
var label = action === 'pop-substate' ? 'pop' : action;
|
|
810
|
-
routeEl.textContent = label + ' ' + path;
|
|
811
|
-
routeEl.title = action + ' → ' + location.pathname + location.hash;
|
|
812
|
-
} else {
|
|
813
|
-
routeEl.textContent = path;
|
|
814
|
-
}
|
|
815
|
-
// Flash brightness on fresh events
|
|
816
|
-
if (lastEvt && (Date.now() - lastEvt.timestamp) < 2000) {
|
|
817
|
-
routeEl.style.outline = '1px solid ' + colors.fg;
|
|
818
|
-
clearTimeout(__zqRouteFadeTimer);
|
|
819
|
-
__zqRouteFadeTimer = setTimeout(function() {
|
|
820
|
-
var el = document.getElementById('__zq_bar_route');
|
|
821
|
-
if (el) el.style.outline = 'none';
|
|
822
|
-
}, 1800);
|
|
823
|
-
} else {
|
|
824
|
-
routeEl.style.outline = 'none';
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
if (compsEl) {
|
|
828
|
-
var count = 0;
|
|
829
|
-
try {
|
|
830
|
-
if (window.$ && $.components) count = Object.keys($.components()).length;
|
|
831
|
-
else if (window.$ && $._components) count = Object.keys($._components).length;
|
|
832
|
-
} catch(e) {}
|
|
833
|
-
compsEl.textContent = count + ' comps';
|
|
834
|
-
}
|
|
835
|
-
if (elsEl) {
|
|
836
|
-
elsEl.textContent = document.querySelectorAll('*').length + ' els';
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
// Expose for devtools popup
|
|
842
|
-
window.__zqDevTools = {
|
|
843
|
-
get requests() { return __zqRequests; },
|
|
844
|
-
get morphEvents() { return __zqMorphEvents; },
|
|
845
|
-
get morphCount() { return __zqMorphCount; },
|
|
846
|
-
get renderCount() { return __zqRenderCount; },
|
|
847
|
-
get routerEvents() { return __zqRouterEvents; }
|
|
848
|
-
};
|
|
849
|
-
|
|
850
|
-
if (document.readyState === 'loading') {
|
|
851
|
-
document.addEventListener('DOMContentLoaded', createDevBar);
|
|
852
|
-
} else {
|
|
853
|
-
createDevBar();
|
|
854
|
-
}
|
|
855
|
-
})();
|
|
856
|
-
</script>`;
|
|
857
|
-
|
|
858
|
-
module.exports = OVERLAY_SCRIPT;
|
|
1
|
+
/**
|
|
2
|
+
* cli/commands/dev/overlay.js - Client-side error overlay + SSE live-reload
|
|
3
|
+
*
|
|
4
|
+
* Returns an HTML <script> snippet that is injected before </body> in
|
|
5
|
+
* every HTML response served by the dev server. Responsibilities:
|
|
6
|
+
*
|
|
7
|
+
* 1. Error Overlay - full-screen dark overlay with code frames, stack
|
|
8
|
+
* traces, and ZQueryError metadata. Dismissable via Esc or ×.
|
|
9
|
+
* 2. Runtime error hooks - window.onerror, unhandledrejection, AND
|
|
10
|
+
* the zQuery $.onError() hook so framework-level errors are
|
|
11
|
+
* surfaced in the overlay automatically.
|
|
12
|
+
* 3. SSE connection - listens for reload / css / error:syntax /
|
|
13
|
+
* error:clear events from the dev server watcher.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
// The snippet is a self-contained IIFE - no external dependencies.
|
|
19
|
+
// It must work in all browsers that support EventSource (IE11 excluded).
|
|
20
|
+
|
|
21
|
+
const OVERLAY_SCRIPT = `<script>
|
|
22
|
+
(function(){
|
|
23
|
+
// =====================================================================
|
|
24
|
+
// Error overlay
|
|
25
|
+
// =====================================================================
|
|
26
|
+
var overlayEl = null;
|
|
27
|
+
|
|
28
|
+
var OVERLAY_CSS =
|
|
29
|
+
'position:fixed;top:0;left:0;width:100%;height:100%;' +
|
|
30
|
+
'background:rgba(0,0,0,0.92);color:#fff;z-index:2147483647;' +
|
|
31
|
+
'font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;' +
|
|
32
|
+
'font-size:13px;overflow-y:auto;padding:0;margin:0;box-sizing:border-box;';
|
|
33
|
+
|
|
34
|
+
var HEADER_CSS =
|
|
35
|
+
'padding:20px 24px 12px;border-bottom:1px solid rgba(255,255,255,0.1);' +
|
|
36
|
+
'display:flex;align-items:flex-start;justify-content:space-between;';
|
|
37
|
+
|
|
38
|
+
var BADGE_CSS =
|
|
39
|
+
'display:inline-block;padding:3px 8px;border-radius:4px;font-size:11px;' +
|
|
40
|
+
'font-weight:700;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:8px;';
|
|
41
|
+
|
|
42
|
+
// Map ZQueryError code prefixes to colours so devs can see at a glance
|
|
43
|
+
// which subsystem produced the error.
|
|
44
|
+
var CODE_COLORS = {
|
|
45
|
+
'ZQ_REACTIVE': '#9b59b6',
|
|
46
|
+
'ZQ_SIGNAL': '#9b59b6',
|
|
47
|
+
'ZQ_EFFECT': '#9b59b6',
|
|
48
|
+
'ZQ_EXPR': '#2980b9',
|
|
49
|
+
'ZQ_COMP': '#16a085',
|
|
50
|
+
'ZQ_ROUTER': '#d35400',
|
|
51
|
+
'ZQ_STORE': '#8e44ad',
|
|
52
|
+
'ZQ_HTTP': '#2c3e50',
|
|
53
|
+
'ZQ_DEV': '#e74c3c',
|
|
54
|
+
'ZQ_INVALID': '#7f8c8d',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function badgeColor(data) {
|
|
58
|
+
if (data.code) {
|
|
59
|
+
var keys = Object.keys(CODE_COLORS);
|
|
60
|
+
for (var i = 0; i < keys.length; i++) {
|
|
61
|
+
if (data.code.indexOf(keys[i]) === 0) return CODE_COLORS[keys[i]];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (data.type && /syntax|parse/i.test(data.type)) return '#e74c3c';
|
|
65
|
+
return '#e67e22';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function esc(s) {
|
|
69
|
+
var d = document.createElement('div');
|
|
70
|
+
d.appendChild(document.createTextNode(s));
|
|
71
|
+
return d.innerHTML;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function createOverlay(data) {
|
|
75
|
+
removeOverlay();
|
|
76
|
+
var wrap = document.createElement('div');
|
|
77
|
+
wrap.id = '__zq_error_overlay';
|
|
78
|
+
wrap.setAttribute('style', OVERLAY_CSS);
|
|
79
|
+
wrap.setAttribute('tabindex', '-1');
|
|
80
|
+
|
|
81
|
+
var color = badgeColor(data);
|
|
82
|
+
var html = '';
|
|
83
|
+
|
|
84
|
+
// ----- header row -----
|
|
85
|
+
html += '<div style="' + HEADER_CSS + '">';
|
|
86
|
+
html += '<div>';
|
|
87
|
+
|
|
88
|
+
// Error code badge (if present)
|
|
89
|
+
if (data.code) {
|
|
90
|
+
html += '<span style="' + BADGE_CSS + 'background:' + color + ';margin-right:6px;">' + esc(data.code) + '</span>';
|
|
91
|
+
}
|
|
92
|
+
// Type badge
|
|
93
|
+
html += '<span style="' + BADGE_CSS + 'background:' + (data.code ? 'rgba(255,255,255,0.1)' : color) + ';">' + esc(data.type || 'Error') + '</span>';
|
|
94
|
+
|
|
95
|
+
// Message
|
|
96
|
+
html += '<div style="font-size:18px;font-weight:600;line-height:1.4;color:#ff6b6b;margin-top:4px;">';
|
|
97
|
+
html += esc(data.message || 'Unknown error');
|
|
98
|
+
html += '</div></div>';
|
|
99
|
+
|
|
100
|
+
// Close button
|
|
101
|
+
html += '<button id="__zq_close" style="' +
|
|
102
|
+
'background:none;border:1px solid rgba(255,255,255,0.2);color:#999;' +
|
|
103
|
+
'font-size:20px;cursor:pointer;border-radius:6px;width:32px;height:32px;' +
|
|
104
|
+
'display:flex;align-items:center;justify-content:center;flex-shrink:0;' +
|
|
105
|
+
'margin-left:16px;transition:all 0.15s;"' +
|
|
106
|
+
' onmouseover="this.style.color=\\'#fff\\';this.style.borderColor=\\'rgba(255,255,255,0.5)\\'"' +
|
|
107
|
+
' onmouseout="this.style.color=\\'#999\\';this.style.borderColor=\\'rgba(255,255,255,0.2)\\'"' +
|
|
108
|
+
'>×</button>';
|
|
109
|
+
html += '</div>';
|
|
110
|
+
|
|
111
|
+
// ----- file location -----
|
|
112
|
+
if (data.file) {
|
|
113
|
+
html += '<div style="padding:10px 24px;color:#8be9fd;font-size:13px;">';
|
|
114
|
+
html += '<span style="color:#888;">File: </span>' + esc(data.file);
|
|
115
|
+
if (data.line) html += '<span style="color:#888;">:</span>' + data.line;
|
|
116
|
+
if (data.column) html += '<span style="color:#888;">:</span>' + data.column;
|
|
117
|
+
html += '</div>';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ----- ZQueryError context (key/value pairs) -----
|
|
121
|
+
if (data.context && typeof data.context === 'object' && Object.keys(data.context).length) {
|
|
122
|
+
html += '<div style="padding:8px 24px;display:flex;flex-wrap:wrap;gap:8px;">';
|
|
123
|
+
var ctxKeys = Object.keys(data.context);
|
|
124
|
+
for (var ci = 0; ci < ctxKeys.length; ci++) {
|
|
125
|
+
var k = ctxKeys[ci], v = data.context[k];
|
|
126
|
+
html += '<span style="background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08);' +
|
|
127
|
+
'padding:3px 10px;border-radius:4px;font-size:12px;">' +
|
|
128
|
+
'<span style="color:#888;">' + esc(k) + ': </span>' +
|
|
129
|
+
'<span style="color:#f1fa8c;">' + esc(typeof v === 'object' ? JSON.stringify(v) : String(v)) + '</span>' +
|
|
130
|
+
'</span>';
|
|
131
|
+
}
|
|
132
|
+
html += '</div>';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ----- code frame -----
|
|
136
|
+
if (data.frame) {
|
|
137
|
+
html += '<pre style="' +
|
|
138
|
+
'margin:0;padding:16px 24px;background:rgba(255,255,255,0.04);' +
|
|
139
|
+
'border-top:1px solid rgba(255,255,255,0.06);' +
|
|
140
|
+
'border-bottom:1px solid rgba(255,255,255,0.06);' +
|
|
141
|
+
'overflow-x:auto;line-height:1.6;font-size:13px;">';
|
|
142
|
+
var lines = data.frame.split('\\n');
|
|
143
|
+
for (var fi = 0; fi < lines.length; fi++) {
|
|
144
|
+
var fl = lines[fi];
|
|
145
|
+
if (fl.charAt(0) === '>') {
|
|
146
|
+
html += '<span style="color:#ff6b6b;font-weight:600;">' + esc(fl) + '</span>\\n';
|
|
147
|
+
} else if (fl.indexOf('^') !== -1 && fl.trim().replace(/[\\s|^]/g, '') === '') {
|
|
148
|
+
html += '<span style="color:#e74c3c;font-weight:700;">' + esc(fl) + '</span>\\n';
|
|
149
|
+
} else {
|
|
150
|
+
html += '<span style="color:#999;">' + esc(fl) + '</span>\\n';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
html += '</pre>';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ----- stack trace -----
|
|
157
|
+
if (data.stack) {
|
|
158
|
+
html += '<div style="padding:16px 24px;">';
|
|
159
|
+
html += '<div style="color:#888;font-size:11px;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:8px;">Stack Trace</div>';
|
|
160
|
+
html += '<pre style="margin:0;color:#bbb;font-size:12px;line-height:1.7;white-space:pre-wrap;word-break:break-word;">';
|
|
161
|
+
html += esc(data.stack);
|
|
162
|
+
html += '</pre></div>';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ----- tip -----
|
|
166
|
+
html += '<div style="padding:16px 24px;color:#555;font-size:11px;border-top:1px solid rgba(255,255,255,0.06);">';
|
|
167
|
+
html += 'Fix the error and save \\u2014 the overlay will clear automatically. Press <kbd style="' +
|
|
168
|
+
'background:rgba(255,255,255,0.1);padding:1px 6px;border-radius:3px;font-size:11px;' +
|
|
169
|
+
'">Esc</kbd> to dismiss.';
|
|
170
|
+
html += '</div>';
|
|
171
|
+
|
|
172
|
+
wrap.innerHTML = html;
|
|
173
|
+
document.body.appendChild(wrap);
|
|
174
|
+
overlayEl = wrap;
|
|
175
|
+
|
|
176
|
+
var closeBtn = document.getElementById('__zq_close');
|
|
177
|
+
if (closeBtn) closeBtn.addEventListener('click', removeOverlay);
|
|
178
|
+
wrap.addEventListener('keydown', function(e) { if (e.key === 'Escape') removeOverlay(); });
|
|
179
|
+
wrap.focus();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function removeOverlay() {
|
|
183
|
+
if (overlayEl && overlayEl.parentNode) overlayEl.parentNode.removeChild(overlayEl);
|
|
184
|
+
overlayEl = null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// =====================================================================
|
|
188
|
+
// Console helper
|
|
189
|
+
// =====================================================================
|
|
190
|
+
function logToConsole(data) {
|
|
191
|
+
var label = data.code ? data.code + ' ' : '';
|
|
192
|
+
var msg = '\\n%c zQuery DevError %c ' + label + data.type + ': ' + data.message;
|
|
193
|
+
if (data.file) msg += '\\n at ' + data.file + (data.line ? ':' + data.line : '') + (data.column ? ':' + data.column : '');
|
|
194
|
+
console.error(msg, 'background:#e74c3c;color:#fff;padding:2px 6px;border-radius:3px;font-weight:700;', 'color:inherit;');
|
|
195
|
+
if (data.frame) console.error(data.frame);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function cleanStack(stack) {
|
|
199
|
+
return stack.split('\\n')
|
|
200
|
+
.filter(function(l) { return l.indexOf('__zq_') === -1 && l.indexOf('EventSource') === -1; })
|
|
201
|
+
.map(function(l) { return l.replace(location.origin, ''); })
|
|
202
|
+
.join('\\n');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// =====================================================================
|
|
206
|
+
// Runtime error hooks
|
|
207
|
+
// =====================================================================
|
|
208
|
+
window.addEventListener('error', function(e) {
|
|
209
|
+
if (!e.filename) return;
|
|
210
|
+
var err = e.error || {};
|
|
211
|
+
var data = {
|
|
212
|
+
code: err.code || '',
|
|
213
|
+
type: (err.constructor && err.constructor.name) || 'Error',
|
|
214
|
+
message: e.message || String(err),
|
|
215
|
+
file: e.filename.replace(location.origin, ''),
|
|
216
|
+
line: e.lineno || 0,
|
|
217
|
+
column: e.colno || 0,
|
|
218
|
+
context: err.context || null,
|
|
219
|
+
stack: err.stack ? cleanStack(err.stack) : ''
|
|
220
|
+
};
|
|
221
|
+
createOverlay(data);
|
|
222
|
+
logToConsole(data);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
window.addEventListener('unhandledrejection', function(e) {
|
|
226
|
+
var err = e.reason || {};
|
|
227
|
+
var data = {
|
|
228
|
+
code: err.code || '',
|
|
229
|
+
type: err.name === 'ZQueryError' ? 'ZQueryError' : 'Unhandled Promise Rejection',
|
|
230
|
+
message: err.message || String(err),
|
|
231
|
+
context: err.context || null,
|
|
232
|
+
stack: err.stack ? cleanStack(err.stack) : ''
|
|
233
|
+
};
|
|
234
|
+
createOverlay(data);
|
|
235
|
+
logToConsole(data);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// =====================================================================
|
|
239
|
+
// Hook into zQuery's $.onError() when the library is loaded
|
|
240
|
+
// =====================================================================
|
|
241
|
+
function hookZQueryErrors() {
|
|
242
|
+
// $.onError is set by the framework - wait for it
|
|
243
|
+
if (typeof $ !== 'undefined' && typeof $.onError === 'function') {
|
|
244
|
+
$.onError(function(zqErr) {
|
|
245
|
+
var data = {
|
|
246
|
+
code: zqErr.code || '',
|
|
247
|
+
type: 'ZQueryError',
|
|
248
|
+
message: zqErr.message,
|
|
249
|
+
context: zqErr.context || null,
|
|
250
|
+
stack: zqErr.stack ? cleanStack(zqErr.stack) : ''
|
|
251
|
+
};
|
|
252
|
+
createOverlay(data);
|
|
253
|
+
logToConsole(data);
|
|
254
|
+
});
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
// Retry until the library has loaded (max ~5s)
|
|
258
|
+
if (hookZQueryErrors._tries < 50) {
|
|
259
|
+
hookZQueryErrors._tries++;
|
|
260
|
+
setTimeout(hookZQueryErrors, 100);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
hookZQueryErrors._tries = 0;
|
|
264
|
+
// Defer so the page's own scripts load first
|
|
265
|
+
if (document.readyState === 'loading') {
|
|
266
|
+
document.addEventListener('DOMContentLoaded', hookZQueryErrors);
|
|
267
|
+
} else {
|
|
268
|
+
setTimeout(hookZQueryErrors, 0);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// =====================================================================
|
|
272
|
+
// SSE connection (live-reload + server-pushed errors)
|
|
273
|
+
// =====================================================================
|
|
274
|
+
var es, reconnectTimer;
|
|
275
|
+
|
|
276
|
+
function connect() {
|
|
277
|
+
es = new EventSource('/__zq_reload');
|
|
278
|
+
|
|
279
|
+
es.addEventListener('reload', function() {
|
|
280
|
+
removeOverlay();
|
|
281
|
+
location.reload();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
es.addEventListener('css', function(e) {
|
|
285
|
+
var changedPath = (e.data || '').replace(/^\\/+/, '');
|
|
286
|
+
var matched = false;
|
|
287
|
+
|
|
288
|
+
// 1) Try cache-busting matching <link rel="stylesheet"> tags
|
|
289
|
+
var sheets = document.querySelectorAll('link[rel="stylesheet"]');
|
|
290
|
+
sheets.forEach(function(l) {
|
|
291
|
+
var href = l.getAttribute('href');
|
|
292
|
+
if (!href) return;
|
|
293
|
+
var clean = href.replace(/[?&]_zqr=\\d+/, '').replace(/^\\/+/, '');
|
|
294
|
+
if (changedPath && clean.indexOf(changedPath) === -1) return;
|
|
295
|
+
matched = true;
|
|
296
|
+
var sep = href.indexOf('?') >= 0 ? '&' : '?';
|
|
297
|
+
l.setAttribute('href', href.replace(/[?&]_zqr=\\d+/, '') + sep + '_zqr=' + Date.now());
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// 2) Try hot-swapping scoped <style data-zq-style-urls> elements
|
|
301
|
+
// These come from component styleUrl - the CSS was fetched, scoped,
|
|
302
|
+
// and injected as an inline <style>. We re-fetch and re-scope it.
|
|
303
|
+
if (!matched) {
|
|
304
|
+
var scopedEls = document.querySelectorAll('style[data-zq-style-urls]');
|
|
305
|
+
scopedEls.forEach(function(el) {
|
|
306
|
+
var urls = el.getAttribute('data-zq-style-urls') || '';
|
|
307
|
+
var hit = urls.split(' ').some(function(u) {
|
|
308
|
+
return u && u.replace(/^\\/+/, '').indexOf(changedPath) !== -1;
|
|
309
|
+
});
|
|
310
|
+
if (!hit) return;
|
|
311
|
+
matched = true;
|
|
312
|
+
|
|
313
|
+
var scopeAttr = el.getAttribute('data-zq-scope') || '';
|
|
314
|
+
var inlineStyles = el.getAttribute('data-zq-inline') || '';
|
|
315
|
+
|
|
316
|
+
// Re-fetch all style URLs (cache-busted)
|
|
317
|
+
var urlList = urls.split(' ').filter(Boolean);
|
|
318
|
+
var prevCSS = el.textContent; // preserve current styles as rollback
|
|
319
|
+
Promise.all(urlList.map(function(u) {
|
|
320
|
+
return fetch(u + (u.indexOf('?') >= 0 ? '&' : '?') + '_zqr=' + Date.now())
|
|
321
|
+
.then(function(r) {
|
|
322
|
+
if (!r.ok) throw new Error('CSS fetch failed: ' + r.status);
|
|
323
|
+
return r.text();
|
|
324
|
+
});
|
|
325
|
+
})).then(function(results) {
|
|
326
|
+
var raw = (inlineStyles ? inlineStyles + '\\n' : '') + results.join('\\n');
|
|
327
|
+
// Re-scope CSS with the same scope attribute
|
|
328
|
+
if (scopeAttr) {
|
|
329
|
+
var inAt = 0;
|
|
330
|
+
raw = raw.replace(/([^{}]+)\\{|\\}/g, function(m, sel) {
|
|
331
|
+
if (m === '}') { if (inAt > 0) inAt--; return m; }
|
|
332
|
+
var t = sel.trim();
|
|
333
|
+
if (t.charAt(0) === '@') { inAt++; return m; }
|
|
334
|
+
if (inAt > 0 && /^[\\d%\\s,fromto]+$/.test(t.replace(/\\s/g, ''))) return m;
|
|
335
|
+
return sel.split(',').map(function(s) { return '[' + scopeAttr + '] ' + s.trim(); }).join(', ') + ' {';
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
el.textContent = raw;
|
|
339
|
+
}).catch(function() {
|
|
340
|
+
// Restore previous CSS on failure to prevent blank page
|
|
341
|
+
el.textContent = prevCSS;
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// 3) Nothing matched - fall back to full reload
|
|
347
|
+
if (!matched) { location.reload(); }
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
es.addEventListener('error:syntax', function(e) {
|
|
351
|
+
try { var data = JSON.parse(e.data); createOverlay(data); logToConsole(data); } catch(_){}
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
es.addEventListener('error:runtime', function(e) {
|
|
355
|
+
try { var data = JSON.parse(e.data); createOverlay(data); logToConsole(data); } catch(_){}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
es.addEventListener('error:clear', function() {
|
|
359
|
+
removeOverlay();
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
es.onerror = function() {
|
|
363
|
+
es.close();
|
|
364
|
+
clearTimeout(reconnectTimer);
|
|
365
|
+
reconnectTimer = setTimeout(connect, 2000);
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
connect();
|
|
370
|
+
|
|
371
|
+
// =====================================================================
|
|
372
|
+
// Fetch / $.http Interceptor - pretty console logging
|
|
373
|
+
// =====================================================================
|
|
374
|
+
var __zqChannel;
|
|
375
|
+
try { __zqChannel = new BroadcastChannel('__zq_devtools'); } catch(e) {}
|
|
376
|
+
|
|
377
|
+
var __zqRequests = [];
|
|
378
|
+
var __zqMorphEvents = [];
|
|
379
|
+
var __zqMorphCount = 0;
|
|
380
|
+
var __zqRenderCount = 0;
|
|
381
|
+
var __zqReqId = 0;
|
|
382
|
+
var _origFetch = window.fetch;
|
|
383
|
+
|
|
384
|
+
window.fetch = function(input, init) {
|
|
385
|
+
var url = typeof input === 'string' ? input : (input && input.url ? input.url : String(input));
|
|
386
|
+
var method = ((init && init.method) || (input && input.method) || 'GET').toUpperCase();
|
|
387
|
+
var id = ++__zqReqId;
|
|
388
|
+
var start = performance.now();
|
|
389
|
+
|
|
390
|
+
// Skip internal dev-server requests
|
|
391
|
+
if (url.indexOf('__zq_') !== -1 || url.indexOf('/_devtools') !== -1) {
|
|
392
|
+
return _origFetch.apply(this, arguments);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return _origFetch.apply(this, arguments).then(function(response) {
|
|
396
|
+
var elapsed = Math.round(performance.now() - start);
|
|
397
|
+
var status = response.status;
|
|
398
|
+
var cloned = response.clone();
|
|
399
|
+
|
|
400
|
+
cloned.text().then(function(bodyText) {
|
|
401
|
+
var entry = {
|
|
402
|
+
id: id, method: method, url: url, status: status,
|
|
403
|
+
elapsed: elapsed, bodyPreview: bodyText.slice(0, 5000),
|
|
404
|
+
timestamp: Date.now()
|
|
405
|
+
};
|
|
406
|
+
__zqRequests.push(entry);
|
|
407
|
+
if (__zqRequests.length > 500) __zqRequests.shift();
|
|
408
|
+
updateDevBar();
|
|
409
|
+
|
|
410
|
+
// Pretty console log
|
|
411
|
+
var isOk = status >= 200 && status < 300;
|
|
412
|
+
var color = isOk ? '#2ecc71' : status < 400 ? '#f39c12' : '#e74c3c';
|
|
413
|
+
|
|
414
|
+
console.groupCollapsed(
|
|
415
|
+
'%c ' + method + ' %c' + status + '%c ' + url + ' %c' + elapsed + 'ms',
|
|
416
|
+
'background:' + color + ';color:#fff;padding:2px 6px;border-radius:3px;font-weight:700;font-size:11px',
|
|
417
|
+
'color:' + color + ';font-weight:700;margin-left:8px',
|
|
418
|
+
'color:inherit;margin-left:4px',
|
|
419
|
+
'color:#888;margin-left:8px;font-size:11px'
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
// Response body
|
|
423
|
+
try {
|
|
424
|
+
var parsed = JSON.parse(bodyText);
|
|
425
|
+
console.log('%c Response ', 'background:#1e1e2e;color:#8be9fd;padding:2px 6px;border-radius:2px;font-weight:600', parsed);
|
|
426
|
+
} catch(pe) {
|
|
427
|
+
if (bodyText.length > 0) {
|
|
428
|
+
console.log('%c Response ', 'background:#1e1e2e;color:#8be9fd;padding:2px 6px;border-radius:2px;font-weight:600',
|
|
429
|
+
bodyText.length > 500 ? bodyText.slice(0, 500) + '... (' + bodyText.length + ' chars)' : bodyText);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Headers
|
|
434
|
+
try {
|
|
435
|
+
console.log('%c Headers ', 'background:#1e1e2e;color:#bd93f9;padding:2px 6px;border-radius:2px;font-weight:600',
|
|
436
|
+
Object.fromEntries(response.headers.entries()));
|
|
437
|
+
} catch(he) {}
|
|
438
|
+
|
|
439
|
+
// Request body (if sent)
|
|
440
|
+
if (init && init.body) {
|
|
441
|
+
try {
|
|
442
|
+
console.log('%c Request ', 'background:#1e1e2e;color:#f1fa8c;padding:2px 6px;border-radius:2px;font-weight:600',
|
|
443
|
+
JSON.parse(init.body));
|
|
444
|
+
} catch(re) {
|
|
445
|
+
console.log('%c Request ', 'background:#1e1e2e;color:#f1fa8c;padding:2px 6px;border-radius:2px;font-weight:600',
|
|
446
|
+
String(init.body).slice(0, 500));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
console.groupEnd();
|
|
451
|
+
|
|
452
|
+
// Broadcast to devtools
|
|
453
|
+
if (__zqChannel) {
|
|
454
|
+
try { __zqChannel.postMessage({ type: 'http', data: entry }); } catch(ce) {}
|
|
455
|
+
}
|
|
456
|
+
}).catch(function() {});
|
|
457
|
+
|
|
458
|
+
return response;
|
|
459
|
+
}, function(err) {
|
|
460
|
+
var elapsed = Math.round(performance.now() - start);
|
|
461
|
+
console.groupCollapsed(
|
|
462
|
+
'%c ' + method + ' %cERR%c ' + url + ' %c' + elapsed + 'ms',
|
|
463
|
+
'background:#e74c3c;color:#fff;padding:2px 6px;border-radius:3px;font-weight:700;font-size:11px',
|
|
464
|
+
'color:#e74c3c;font-weight:700;margin-left:8px',
|
|
465
|
+
'color:inherit;margin-left:4px',
|
|
466
|
+
'color:#888;margin-left:8px;font-size:11px'
|
|
467
|
+
);
|
|
468
|
+
console.error(err);
|
|
469
|
+
console.groupEnd();
|
|
470
|
+
|
|
471
|
+
var entry = { id: id, method: method, url: url, status: 0, elapsed: elapsed, bodyPreview: err.message, timestamp: Date.now() };
|
|
472
|
+
__zqRequests.push(entry);
|
|
473
|
+
updateDevBar();
|
|
474
|
+
if (__zqChannel) {
|
|
475
|
+
try { __zqChannel.postMessage({ type: 'http', data: entry }); } catch(ce) {}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
throw err;
|
|
479
|
+
});
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
// =====================================================================
|
|
483
|
+
// Morph instrumentation - hook via window.__zqMorphHook (set by diff.js)
|
|
484
|
+
// =====================================================================
|
|
485
|
+
window.__zqMorphHook = function(el, elapsed) {
|
|
486
|
+
__zqMorphCount++;
|
|
487
|
+
updateDevBar();
|
|
488
|
+
|
|
489
|
+
var evt = { target: el.id || el.tagName.toLowerCase(), elapsed: elapsed, kind: 'morph', timestamp: Date.now() };
|
|
490
|
+
__zqMorphEvents.push(evt);
|
|
491
|
+
if (__zqMorphEvents.length > 200) __zqMorphEvents.shift();
|
|
492
|
+
|
|
493
|
+
// Console timing for slow morphs (> 4ms)
|
|
494
|
+
if (elapsed > 4) {
|
|
495
|
+
console.log(
|
|
496
|
+
'%c morph %c' + elapsed.toFixed(2) + 'ms%c ' + (el.id || el.tagName.toLowerCase()),
|
|
497
|
+
'background:#9b59b6;color:#fff;padding:2px 6px;border-radius:3px;font-weight:700;font-size:11px',
|
|
498
|
+
'color:' + (elapsed > 16 ? '#e74c3c' : '#f39c12') + ';font-weight:700;margin-left:8px',
|
|
499
|
+
'color:#888;margin-left:4px'
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Broadcast to devtools
|
|
504
|
+
if (__zqChannel) {
|
|
505
|
+
try {
|
|
506
|
+
__zqChannel.postMessage({
|
|
507
|
+
type: 'morph-detail',
|
|
508
|
+
data: evt
|
|
509
|
+
});
|
|
510
|
+
} catch(ce) {}
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
// =====================================================================
|
|
515
|
+
// Render instrumentation - hook for first-renders & route swaps
|
|
516
|
+
// =====================================================================
|
|
517
|
+
window.__zqRenderHook = function(el, elapsed, kind, name) {
|
|
518
|
+
__zqRenderCount++;
|
|
519
|
+
__zqMorphCount++; // count renders in the morph total for the toolbar
|
|
520
|
+
updateDevBar();
|
|
521
|
+
|
|
522
|
+
var evt = { target: name || el.id || el.tagName.toLowerCase(), elapsed: elapsed, kind: kind, timestamp: Date.now() };
|
|
523
|
+
__zqMorphEvents.push(evt);
|
|
524
|
+
if (__zqMorphEvents.length > 200) __zqMorphEvents.shift();
|
|
525
|
+
|
|
526
|
+
// Console log for route/mount renders
|
|
527
|
+
var label = kind === 'route' ? ' route ' : ' mount ';
|
|
528
|
+
var bg = kind === 'route' ? '#d29922' : '#3fb950';
|
|
529
|
+
console.log(
|
|
530
|
+
'%c' + label + '%c' + elapsed.toFixed(2) + 'ms%c ' + (name || el.id || el.tagName.toLowerCase()),
|
|
531
|
+
'background:' + bg + ';color:#fff;padding:2px 6px;border-radius:3px;font-weight:700;font-size:11px',
|
|
532
|
+
'color:' + (elapsed > 16 ? '#e74c3c' : '#888') + ';font-weight:700;margin-left:8px',
|
|
533
|
+
'color:#888;margin-left:4px'
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
// Broadcast to devtools
|
|
537
|
+
if (__zqChannel) {
|
|
538
|
+
try {
|
|
539
|
+
__zqChannel.postMessage({
|
|
540
|
+
type: 'render-detail',
|
|
541
|
+
data: evt
|
|
542
|
+
});
|
|
543
|
+
} catch(ce) {}
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
// =====================================================================
|
|
548
|
+
// Router instrumentation - history state tracking for devtools
|
|
549
|
+
// =====================================================================
|
|
550
|
+
var __zqRouterEvents = [];
|
|
551
|
+
|
|
552
|
+
var _origPushState = history.pushState;
|
|
553
|
+
history.pushState = function(state, title, url) {
|
|
554
|
+
_origPushState.apply(this, arguments);
|
|
555
|
+
var isSubstate = state && state.__zq === 'substate';
|
|
556
|
+
var evt = {
|
|
557
|
+
action: isSubstate ? 'substate' : 'navigate',
|
|
558
|
+
url: String(url || location.href).replace(location.origin, ''),
|
|
559
|
+
key: isSubstate ? state.key : null,
|
|
560
|
+
data: isSubstate ? state.data : null,
|
|
561
|
+
timestamp: Date.now()
|
|
562
|
+
};
|
|
563
|
+
__zqRouterEvents.push(evt);
|
|
564
|
+
if (__zqRouterEvents.length > 200) __zqRouterEvents.shift();
|
|
565
|
+
if (__zqChannel) {
|
|
566
|
+
try { __zqChannel.postMessage({ type: 'router', data: evt }); } catch(e) {}
|
|
567
|
+
}
|
|
568
|
+
updateDevBar();
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
var _origReplaceState = history.replaceState;
|
|
572
|
+
history.replaceState = function(state, title, url) {
|
|
573
|
+
_origReplaceState.apply(this, arguments);
|
|
574
|
+
var isSubstate = state && state.__zq === 'substate';
|
|
575
|
+
var evt = {
|
|
576
|
+
action: 'replace',
|
|
577
|
+
url: String(url || location.href).replace(location.origin, ''),
|
|
578
|
+
key: isSubstate ? state.key : null,
|
|
579
|
+
data: isSubstate ? state.data : null,
|
|
580
|
+
timestamp: Date.now()
|
|
581
|
+
};
|
|
582
|
+
__zqRouterEvents.push(evt);
|
|
583
|
+
if (__zqRouterEvents.length > 200) __zqRouterEvents.shift();
|
|
584
|
+
if (__zqChannel) {
|
|
585
|
+
try { __zqChannel.postMessage({ type: 'router', data: evt }); } catch(e) {}
|
|
586
|
+
}
|
|
587
|
+
updateDevBar();
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
window.addEventListener('popstate', function(e) {
|
|
591
|
+
var state = e.state;
|
|
592
|
+
var isSubstate = state && state.__zq === 'substate';
|
|
593
|
+
var evt = {
|
|
594
|
+
action: isSubstate ? 'pop-substate' : 'pop',
|
|
595
|
+
url: location.pathname + location.hash,
|
|
596
|
+
key: isSubstate ? state.key : null,
|
|
597
|
+
data: isSubstate ? state.data : null,
|
|
598
|
+
timestamp: Date.now()
|
|
599
|
+
};
|
|
600
|
+
__zqRouterEvents.push(evt);
|
|
601
|
+
if (__zqRouterEvents.length > 200) __zqRouterEvents.shift();
|
|
602
|
+
if (__zqChannel) {
|
|
603
|
+
try { __zqChannel.postMessage({ type: 'router', data: evt }); } catch(e) {}
|
|
604
|
+
}
|
|
605
|
+
updateDevBar();
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
window.addEventListener('hashchange', function() {
|
|
609
|
+
var evt = {
|
|
610
|
+
action: 'hashchange',
|
|
611
|
+
url: location.hash,
|
|
612
|
+
timestamp: Date.now()
|
|
613
|
+
};
|
|
614
|
+
__zqRouterEvents.push(evt);
|
|
615
|
+
if (__zqRouterEvents.length > 200) __zqRouterEvents.shift();
|
|
616
|
+
if (__zqChannel) {
|
|
617
|
+
try { __zqChannel.postMessage({ type: 'router', data: evt }); } catch(e) {}
|
|
618
|
+
}
|
|
619
|
+
updateDevBar();
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// =====================================================================
|
|
623
|
+
// Dev Toolbar - expandable floating bar with stats
|
|
624
|
+
// =====================================================================
|
|
625
|
+
var devBar;
|
|
626
|
+
var __zqBarExpanded = false;
|
|
627
|
+
try { __zqBarExpanded = localStorage.getItem('__zq_bar_expanded') === '1'; } catch(e) {}
|
|
628
|
+
var __zqRouteColors = {
|
|
629
|
+
navigate: { bg: 'rgba(63,185,80,0.12)', fg: '#3fb950' },
|
|
630
|
+
replace: { bg: 'rgba(210,153,34,0.12)', fg: '#d29922' },
|
|
631
|
+
pop: { bg: 'rgba(248,81,73,0.12)', fg: '#f85149' },
|
|
632
|
+
'pop-substate':{ bg: 'rgba(248,81,73,0.12)', fg: '#f85149' },
|
|
633
|
+
substate: { bg: 'rgba(168,130,255,0.12)', fg: '#a882ff' },
|
|
634
|
+
hashchange: { bg: 'rgba(88,166,255,0.12)', fg: '#58a6ff' }
|
|
635
|
+
};
|
|
636
|
+
var __zqRouteDefault = { bg: 'rgba(227,155,55,0.12)', fg: '#e39b37' };
|
|
637
|
+
var __zqRouteFadeTimer = null;
|
|
638
|
+
|
|
639
|
+
function createDevBar() {
|
|
640
|
+
devBar = document.createElement('div');
|
|
641
|
+
devBar.id = '__zq_devbar';
|
|
642
|
+
devBar.setAttribute('style',
|
|
643
|
+
'position:fixed;bottom:12px;right:12px;z-index:2147483646;' +
|
|
644
|
+
'display:flex;align-items:center;gap:4px;' +
|
|
645
|
+
'background:rgba(22,27,34,0.92);border:1px solid rgba(48,54,61,0.8);' +
|
|
646
|
+
'border-radius:8px;padding:4px 6px;backdrop-filter:blur(8px);' +
|
|
647
|
+
'font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;' +
|
|
648
|
+
'font-size:11px;color:#8b949e;user-select:none;cursor:default;' +
|
|
649
|
+
'box-shadow:0 4px 12px rgba(0,0,0,0.4);transition:all .25s cubic-bezier(.22,1,.36,1);'
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
var statStyle = 'padding:2px 6px;border-radius:4px;font-size:10px;font-weight:600;cursor:pointer;white-space:nowrap;';
|
|
653
|
+
var expandedStyle = statStyle + 'display:none;transform:scale(0);opacity:0;transform-origin:left center;will-change:transform,opacity;transition:transform .25s cubic-bezier(.22,1,.36,1),opacity .2s ease;';
|
|
654
|
+
|
|
655
|
+
devBar.innerHTML =
|
|
656
|
+
'<span style="color:#58a6ff;font-weight:700;padding:0 4px;font-size:10px;letter-spacing:.5px">zQ</span>' +
|
|
657
|
+
// Expanded-only stats
|
|
658
|
+
'<span id="__zq_bar_route" class="__zq_ex" title="Current route" style="' + expandedStyle +
|
|
659
|
+
'background:rgba(227,155,55,0.12);color:#e39b37;outline-offset:1px;transition:transform .25s cubic-bezier(.22,1,.36,1),opacity .2s ease,background .3s ease,color .3s ease,outline-color .6s ease;">/</span>' +
|
|
660
|
+
'<span id="__zq_bar_comps" class="__zq_ex" title="Registered components" style="' + expandedStyle +
|
|
661
|
+
'background:rgba(168,130,255,0.1);color:#a882ff;">0 comps</span>' +
|
|
662
|
+
// Always-visible stats
|
|
663
|
+
'<span id="__zq_bar_morphs" title="Render operations" style="' + statStyle +
|
|
664
|
+
'background:rgba(188,140,255,0.1);color:#bc8cff;">0 render</span>' +
|
|
665
|
+
'<span id="__zq_bar_reqs" title="Network requests" style="' + statStyle +
|
|
666
|
+
'background:rgba(88,166,255,0.1);color:#58a6ff;">0 req</span>' +
|
|
667
|
+
// Expanded-only stats
|
|
668
|
+
'<span id="__zq_bar_els" class="__zq_ex" title="DOM elements" style="' + expandedStyle +
|
|
669
|
+
'background:rgba(63,185,80,0.1);color:#3fb950;">0 els</span>' +
|
|
670
|
+
// Toggle expand/collapse
|
|
671
|
+
'<button id="__zq_bar_toggle" title="Expand toolbar" style="' +
|
|
672
|
+
'padding:2px 6px;border-radius:4px;font-size:10px;font-weight:600;' +
|
|
673
|
+
'background:rgba(88,166,255,0.08);color:#58a6ff;border:1px solid rgba(88,166,255,0.2);' +
|
|
674
|
+
'cursor:pointer;font-family:inherit;transition:all .15s;line-height:1;' +
|
|
675
|
+
'"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 6 9 12 15 18"/></svg></button>' +
|
|
676
|
+
'<button id="__zq_bar_close" title="Close toolbar" style="' +
|
|
677
|
+
'padding:0 4px;color:#484f58;cursor:pointer;font-size:14px;border:none;' +
|
|
678
|
+
'background:none;font-family:inherit;line-height:1;' +
|
|
679
|
+
'">×</button>';
|
|
680
|
+
|
|
681
|
+
document.body.appendChild(devBar);
|
|
682
|
+
|
|
683
|
+
// If previously expanded, restore that state immediately
|
|
684
|
+
if (__zqBarExpanded) {
|
|
685
|
+
var items = devBar.querySelectorAll('.__zq_ex');
|
|
686
|
+
var btn = document.getElementById('__zq_bar_toggle');
|
|
687
|
+
if (btn) {
|
|
688
|
+
btn.innerHTML = '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 6 15 12 9 18"/></svg>';
|
|
689
|
+
btn.title = 'Collapse toolbar';
|
|
690
|
+
}
|
|
691
|
+
for (var i = 0; i < items.length; i++) {
|
|
692
|
+
items[i].style.display = 'inline';
|
|
693
|
+
items[i].style.transform = 'scale(1)';
|
|
694
|
+
items[i].style.opacity = '1';
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
updateDevBar();
|
|
698
|
+
|
|
699
|
+
// Live-poll stats so numbers stay current without waiting for events
|
|
700
|
+
setInterval(updateDevBar, 1000);
|
|
701
|
+
|
|
702
|
+
// Check if we're inside a devtools split-view iframe
|
|
703
|
+
function isInSplitFrame() {
|
|
704
|
+
try { return window.parent !== window && window.parent.document.getElementById('app-frame'); }
|
|
705
|
+
catch(e) { return false; }
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Switch tab in devtools (works for both split iframe and popup)
|
|
709
|
+
function switchDevTab(tab) {
|
|
710
|
+
if (__zqChannel) {
|
|
711
|
+
__zqChannel.postMessage({ type: 'switch-tab', tab: tab });
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Open devtools popup
|
|
716
|
+
var __zqPopup = null;
|
|
717
|
+
function openDevToolsPopup(tab) {
|
|
718
|
+
if (__zqPopup && !__zqPopup.closed) {
|
|
719
|
+
switchDevTab(tab);
|
|
720
|
+
__zqPopup.focus();
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
var w = 1080, h = 800;
|
|
724
|
+
var left = window.screenX + window.outerWidth - w - 20;
|
|
725
|
+
var top = window.screenY + 60;
|
|
726
|
+
var url = '/_devtools' + (tab ? '#' + tab : '');
|
|
727
|
+
__zqPopup = window.open(url, '__zq_devtools',
|
|
728
|
+
'width=' + w + ',height=' + h + ',left=' + left + ',top=' + top +
|
|
729
|
+
',resizable=yes,scrollbars=yes');
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function openTab(tab) {
|
|
733
|
+
if (isInSplitFrame()) { switchDevTab(tab); } else { openDevToolsPopup(tab); }
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Stat click handlers → open relevant devtools tab
|
|
737
|
+
document.getElementById('__zq_bar_route').addEventListener('click', function() { openTab('router'); });
|
|
738
|
+
document.getElementById('__zq_bar_comps').addEventListener('click', function() { openTab('components'); });
|
|
739
|
+
document.getElementById('__zq_bar_morphs').addEventListener('click', function() { openTab('perf'); });
|
|
740
|
+
document.getElementById('__zq_bar_reqs').addEventListener('click', function() { openTab('network'); });
|
|
741
|
+
document.getElementById('__zq_bar_els').addEventListener('click', function() { openTab('dom'); });
|
|
742
|
+
|
|
743
|
+
// Expand / collapse toggle
|
|
744
|
+
document.getElementById('__zq_bar_toggle').addEventListener('click', function() {
|
|
745
|
+
__zqBarExpanded = !__zqBarExpanded;
|
|
746
|
+
try { localStorage.setItem('__zq_bar_expanded', __zqBarExpanded ? '1' : '0'); } catch(e) {}
|
|
747
|
+
var items = devBar.querySelectorAll('.__zq_ex');
|
|
748
|
+
var btn = this;
|
|
749
|
+
if (__zqBarExpanded) {
|
|
750
|
+
btn.innerHTML = '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 6 15 12 9 18"/></svg>';
|
|
751
|
+
btn.title = 'Collapse toolbar';
|
|
752
|
+
for (var i = 0; i < items.length; i++) {
|
|
753
|
+
items[i].style.display = 'inline';
|
|
754
|
+
items[i].offsetWidth; // reflow
|
|
755
|
+
items[i].style.transform = 'scale(1)';
|
|
756
|
+
items[i].style.opacity = '1';
|
|
757
|
+
}
|
|
758
|
+
updateDevBar();
|
|
759
|
+
} else {
|
|
760
|
+
btn.innerHTML = '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 6 9 12 15 18"/></svg>';
|
|
761
|
+
btn.title = 'Expand toolbar';
|
|
762
|
+
for (var i = 0; i < items.length; i++) {
|
|
763
|
+
items[i].style.transform = 'scale(0)';
|
|
764
|
+
items[i].style.opacity = '0';
|
|
765
|
+
}
|
|
766
|
+
setTimeout(function() {
|
|
767
|
+
if (!__zqBarExpanded) {
|
|
768
|
+
var items = devBar.querySelectorAll('.__zq_ex');
|
|
769
|
+
for (var i = 0; i < items.length; i++) items[i].style.display = 'none';
|
|
770
|
+
}
|
|
771
|
+
}, 250);
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
// Toggle hover
|
|
776
|
+
document.getElementById('__zq_bar_toggle').addEventListener('mouseover', function() {
|
|
777
|
+
this.style.background = 'rgba(88,166,255,0.18)';
|
|
778
|
+
});
|
|
779
|
+
document.getElementById('__zq_bar_toggle').addEventListener('mouseout', function() {
|
|
780
|
+
this.style.background = 'rgba(88,166,255,0.08)';
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
// Close button
|
|
784
|
+
document.getElementById('__zq_bar_close').addEventListener('click', function() {
|
|
785
|
+
devBar.style.display = 'none';
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function updateDevBar() {
|
|
790
|
+
if (!devBar) return;
|
|
791
|
+
var reqEl = document.getElementById('__zq_bar_reqs');
|
|
792
|
+
var morphEl = document.getElementById('__zq_bar_morphs');
|
|
793
|
+
if (reqEl) reqEl.textContent = __zqRequests.length + ' req';
|
|
794
|
+
if (morphEl) morphEl.textContent = __zqMorphCount + ' render';
|
|
795
|
+
|
|
796
|
+
if (__zqBarExpanded) {
|
|
797
|
+
var routeEl = document.getElementById('__zq_bar_route');
|
|
798
|
+
var compsEl = document.getElementById('__zq_bar_comps');
|
|
799
|
+
var elsEl = document.getElementById('__zq_bar_els');
|
|
800
|
+
if (routeEl) {
|
|
801
|
+
var lastEvt = __zqRouterEvents.length ? __zqRouterEvents[__zqRouterEvents.length - 1] : null;
|
|
802
|
+
var action = lastEvt ? lastEvt.action : '';
|
|
803
|
+
var path = location.pathname + location.hash;
|
|
804
|
+
if (path.length > 16) path = path.substring(0, 14) + '…';
|
|
805
|
+
var colors = __zqRouteColors[action] || __zqRouteDefault;
|
|
806
|
+
routeEl.style.background = colors.bg;
|
|
807
|
+
routeEl.style.color = colors.fg;
|
|
808
|
+
if (action) {
|
|
809
|
+
var label = action === 'pop-substate' ? 'pop' : action;
|
|
810
|
+
routeEl.textContent = label + ' ' + path;
|
|
811
|
+
routeEl.title = action + ' → ' + location.pathname + location.hash;
|
|
812
|
+
} else {
|
|
813
|
+
routeEl.textContent = path;
|
|
814
|
+
}
|
|
815
|
+
// Flash brightness on fresh events
|
|
816
|
+
if (lastEvt && (Date.now() - lastEvt.timestamp) < 2000) {
|
|
817
|
+
routeEl.style.outline = '1px solid ' + colors.fg;
|
|
818
|
+
clearTimeout(__zqRouteFadeTimer);
|
|
819
|
+
__zqRouteFadeTimer = setTimeout(function() {
|
|
820
|
+
var el = document.getElementById('__zq_bar_route');
|
|
821
|
+
if (el) el.style.outline = 'none';
|
|
822
|
+
}, 1800);
|
|
823
|
+
} else {
|
|
824
|
+
routeEl.style.outline = 'none';
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (compsEl) {
|
|
828
|
+
var count = 0;
|
|
829
|
+
try {
|
|
830
|
+
if (window.$ && $.components) count = Object.keys($.components()).length;
|
|
831
|
+
else if (window.$ && $._components) count = Object.keys($._components).length;
|
|
832
|
+
} catch(e) {}
|
|
833
|
+
compsEl.textContent = count + ' comps';
|
|
834
|
+
}
|
|
835
|
+
if (elsEl) {
|
|
836
|
+
elsEl.textContent = document.querySelectorAll('*').length + ' els';
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// Expose for devtools popup
|
|
842
|
+
window.__zqDevTools = {
|
|
843
|
+
get requests() { return __zqRequests; },
|
|
844
|
+
get morphEvents() { return __zqMorphEvents; },
|
|
845
|
+
get morphCount() { return __zqMorphCount; },
|
|
846
|
+
get renderCount() { return __zqRenderCount; },
|
|
847
|
+
get routerEvents() { return __zqRouterEvents; }
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
if (document.readyState === 'loading') {
|
|
851
|
+
document.addEventListener('DOMContentLoaded', createDevBar);
|
|
852
|
+
} else {
|
|
853
|
+
createDevBar();
|
|
854
|
+
}
|
|
855
|
+
})();
|
|
856
|
+
</script>`;
|
|
857
|
+
|
|
858
|
+
module.exports = OVERLAY_SCRIPT;
|