@dinoreic/fez 0.1.1 → 0.2.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 +151 -137
- package/bin/fez +32 -0
- package/bin/fez-index +44 -0
- package/dist/fez.js +7 -7
- package/dist/fez.js.map +4 -4
- package/dist/log.js +5 -0
- package/dist/log.js.map +7 -0
- package/dist/rollup.js.map +2 -2
- package/package.json +14 -5
- package/src/fez/compile.js +73 -53
- package/src/fez/connect.js +7 -21
- package/src/fez/defaults.js +69 -0
- package/src/fez/instance.js +131 -56
- package/src/fez/lib/template.js +9 -0
- package/src/fez/root.js +42 -7
- package/src/fez.js +14 -23
- package/src/log.js +154 -0
- package/src/rollup.js +0 -6
package/src/fez/instance.js
CHANGED
|
@@ -19,6 +19,7 @@ export default class FezBase {
|
|
|
19
19
|
|
|
20
20
|
for (const [key, val] of Object.entries(attrs)) {
|
|
21
21
|
if ([':'].includes(key[0])) {
|
|
22
|
+
// LOG([key, val])
|
|
22
23
|
delete attrs[key]
|
|
23
24
|
try {
|
|
24
25
|
const newVal = new Function(`return (${val})`).bind(newNode)()
|
|
@@ -110,14 +111,29 @@ export default class FezBase {
|
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
// clear all node references
|
|
113
115
|
fezRemoveSelf() {
|
|
114
116
|
this._setIntervalCache ||= {}
|
|
115
117
|
Object.keys(this._setIntervalCache).forEach((key)=> {
|
|
116
118
|
clearInterval(this._setIntervalCache[key])
|
|
117
119
|
})
|
|
118
120
|
|
|
121
|
+
if (this._eventHandlers) {
|
|
122
|
+
Object.entries(this._eventHandlers).forEach(([eventName, handler]) => {
|
|
123
|
+
window.removeEventListener(eventName, handler);
|
|
124
|
+
});
|
|
125
|
+
this._eventHandlers = {};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (this._timeouts) {
|
|
129
|
+
Object.values(this._timeouts).forEach(timeoutId => {
|
|
130
|
+
clearTimeout(timeoutId);
|
|
131
|
+
});
|
|
132
|
+
this._timeouts = {};
|
|
133
|
+
}
|
|
134
|
+
|
|
119
135
|
this.onDestroy()
|
|
120
|
-
this.onDestroy = ()=> {}
|
|
136
|
+
this.onDestroy = () => {}
|
|
121
137
|
|
|
122
138
|
if (this.root) {
|
|
123
139
|
this.root.fez = undefined
|
|
@@ -162,43 +178,78 @@ export default class FezBase {
|
|
|
162
178
|
}
|
|
163
179
|
}
|
|
164
180
|
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
181
|
+
// Generic function to handle window events with automatic cleanup
|
|
182
|
+
// eventName: 'resize', 'scroll', etc.
|
|
183
|
+
// func: callback function to execute
|
|
184
|
+
// delay: throttle delay in ms (default: 100ms)
|
|
185
|
+
on(eventName, func, delay = 200) {
|
|
186
|
+
this._eventHandlers = this._eventHandlers || {};
|
|
187
|
+
this._timeouts = this._timeouts || {};
|
|
188
|
+
|
|
189
|
+
if (this._eventHandlers[eventName]) {
|
|
190
|
+
window.removeEventListener(eventName, this._eventHandlers[eventName]);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (this._timeouts[eventName]) {
|
|
194
|
+
clearTimeout(this._timeouts[eventName]);
|
|
195
|
+
}
|
|
170
196
|
|
|
171
|
-
|
|
197
|
+
let lastRun = 0;
|
|
172
198
|
|
|
173
|
-
const
|
|
199
|
+
const doExecute = () => {
|
|
174
200
|
if (!this.isConnected) {
|
|
175
|
-
|
|
176
|
-
|
|
201
|
+
if (this._eventHandlers[eventName]) {
|
|
202
|
+
window.removeEventListener(eventName, this._eventHandlers[eventName]);
|
|
203
|
+
delete this._eventHandlers[eventName];
|
|
204
|
+
}
|
|
205
|
+
if (this._timeouts[eventName]) {
|
|
206
|
+
clearTimeout(this._timeouts[eventName]);
|
|
207
|
+
delete this._timeouts[eventName];
|
|
208
|
+
}
|
|
209
|
+
return false;
|
|
177
210
|
}
|
|
178
211
|
func.call(this);
|
|
212
|
+
return true;
|
|
179
213
|
};
|
|
180
214
|
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
window.removeEventListener('resize', handleResize);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
215
|
+
const handleEvent = () => {
|
|
216
|
+
const now = Date.now();
|
|
186
217
|
|
|
187
|
-
if (delay) {
|
|
188
|
-
|
|
189
|
-
const now = Date.now();
|
|
190
|
-
if (now - lastRun >= delay) {
|
|
191
|
-
checkAndExecute();
|
|
218
|
+
if (now - lastRun >= delay) {
|
|
219
|
+
if (doExecute()) {
|
|
192
220
|
lastRun = now;
|
|
221
|
+
} else {
|
|
222
|
+
return;
|
|
193
223
|
}
|
|
194
|
-
} else {
|
|
195
|
-
// Debounce
|
|
196
|
-
clearTimeout(timeoutId);
|
|
197
|
-
timeoutId = setTimeout(checkAndExecute, 200);
|
|
198
224
|
}
|
|
225
|
+
|
|
226
|
+
// Clear previous timeout and set new one to ensure final event
|
|
227
|
+
if (this._timeouts[eventName]) {
|
|
228
|
+
clearTimeout(this._timeouts[eventName]);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
this._timeouts[eventName] = setTimeout(() => {
|
|
232
|
+
if (now > lastRun && doExecute()) {
|
|
233
|
+
lastRun = Date.now();
|
|
234
|
+
}
|
|
235
|
+
delete this._timeouts[eventName];
|
|
236
|
+
}, delay);
|
|
199
237
|
};
|
|
200
238
|
|
|
201
|
-
|
|
239
|
+
this._eventHandlers[eventName] = handleEvent;
|
|
240
|
+
window.addEventListener(eventName, handleEvent);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Helper function for resize events
|
|
244
|
+
onResize(func, delay) {
|
|
245
|
+
this.on('resize', func, delay);
|
|
246
|
+
func();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Helper function for scroll events
|
|
250
|
+
onScroll(func, delay) {
|
|
251
|
+
this.on('scroll', func, delay);
|
|
252
|
+
func();
|
|
202
253
|
}
|
|
203
254
|
|
|
204
255
|
// copy child nodes, natively to preserve bound events
|
|
@@ -235,14 +286,48 @@ export default class FezBase {
|
|
|
235
286
|
onDestroy() {}
|
|
236
287
|
onStateChange() {}
|
|
237
288
|
onGlobalStateChange() {}
|
|
238
|
-
|
|
289
|
+
|
|
290
|
+
// component publish will search for parent component that subscribes by name
|
|
291
|
+
publish(channel, ...args) {
|
|
292
|
+
const handle_publish = (component) => {
|
|
293
|
+
if (Fez._subs && Fez._subs[channel]) {
|
|
294
|
+
const sub = Fez._subs[channel].find(([comp]) => comp === component)
|
|
295
|
+
if (sub) {
|
|
296
|
+
sub[1].bind(component)(...args)
|
|
297
|
+
return true
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return false
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Check if current component has subscription
|
|
304
|
+
if (handle_publish(this)) {
|
|
305
|
+
return true
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Bubble up to parent components
|
|
309
|
+
let parent = this.root.parentElement
|
|
310
|
+
while (parent) {
|
|
311
|
+
if (parent.fez) {
|
|
312
|
+
if (handle_publish(parent.fez)) {
|
|
313
|
+
return true
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
parent = parent.parentElement
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// If no parent handled it, fall back to global publish
|
|
320
|
+
// Fez.publish(channel, ...args)
|
|
321
|
+
return false
|
|
322
|
+
}
|
|
323
|
+
|
|
239
324
|
fezBlocks = {}
|
|
240
325
|
|
|
241
326
|
parseHtml(text) {
|
|
242
327
|
const base = this.fezHtmlRoot.replaceAll('"', '"')
|
|
243
328
|
|
|
244
329
|
text = text
|
|
245
|
-
.replace(/([
|
|
330
|
+
.replace(/([!'"\s;])fez\.(\w)/g, `$1${base}$2`)
|
|
246
331
|
.replace(/>\s+</g, '><')
|
|
247
332
|
|
|
248
333
|
return text.trim()
|
|
@@ -297,21 +382,18 @@ export default class FezBase {
|
|
|
297
382
|
newNode.innerHTML = this.parseHtml(renderedTpl)
|
|
298
383
|
}
|
|
299
384
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
this.slot(this.root, slot.parentNode)
|
|
304
|
-
slot.parentNode.removeChild(slot)
|
|
305
|
-
}
|
|
385
|
+
newNode.querySelectorAll('[fez-keep]').forEach(newEl => {
|
|
386
|
+
const key = newEl.getAttribute('fez-keep')
|
|
387
|
+
const oldEl = this.root.querySelector(`[fez-keep="${key}"]`)
|
|
306
388
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
389
|
+
if (oldEl) {
|
|
390
|
+
// Keep the old element in place of the new one
|
|
391
|
+
newEl.parentNode.replaceChild(oldEl, newEl)
|
|
392
|
+
} else if (key === 'default-slot') {
|
|
393
|
+
// First render - populate the slot with current root children
|
|
394
|
+
Array.from(this.root.childNodes).forEach(child => newEl.appendChild(child))
|
|
313
395
|
}
|
|
314
|
-
}
|
|
396
|
+
})
|
|
315
397
|
|
|
316
398
|
Fez.morphdom(this.root, newNode)
|
|
317
399
|
|
|
@@ -385,8 +467,7 @@ export default class FezBase {
|
|
|
385
467
|
refresh(selector) {
|
|
386
468
|
alert('NEEDS FIX and remove htmlTemplate')
|
|
387
469
|
if (selector) {
|
|
388
|
-
const n =
|
|
389
|
-
n.innerHTML = this.class.htmlTemplate
|
|
470
|
+
const n = Fez.domRoot(this.class.htmlTemplate)
|
|
390
471
|
const tpl = n.querySelector(selector).innerHTML
|
|
391
472
|
this.render(selector, tpl)
|
|
392
473
|
} else {
|
|
@@ -459,7 +540,7 @@ export default class FezBase {
|
|
|
459
540
|
|
|
460
541
|
// get root node child nodes as array
|
|
461
542
|
childNodes(func) {
|
|
462
|
-
|
|
543
|
+
let children = Array.from(this.root.children)
|
|
463
544
|
|
|
464
545
|
if (func) {
|
|
465
546
|
// Create temporary container to avoid ancestor-parent errors
|
|
@@ -468,12 +549,11 @@ export default class FezBase {
|
|
|
468
549
|
document.body.appendChild(tmpContainer)
|
|
469
550
|
children.forEach(child => tmpContainer.appendChild(child))
|
|
470
551
|
|
|
471
|
-
|
|
552
|
+
children = Array.from(tmpContainer.children).map(func)
|
|
472
553
|
document.body.removeChild(tmpContainer)
|
|
473
|
-
return list
|
|
474
|
-
} else {
|
|
475
|
-
return children
|
|
476
554
|
}
|
|
555
|
+
|
|
556
|
+
return children
|
|
477
557
|
}
|
|
478
558
|
|
|
479
559
|
subscribe(channel, func) {
|
|
@@ -513,19 +593,14 @@ export default class FezBase {
|
|
|
513
593
|
|
|
514
594
|
fezHide() {
|
|
515
595
|
const node = this.root
|
|
596
|
+
const nodes = this.childNodes()
|
|
516
597
|
const parent = this.root.parentNode
|
|
517
|
-
const fragment = document.createDocumentFragment();
|
|
518
598
|
|
|
519
|
-
|
|
520
|
-
fragment.appendChild(node.firstChild)
|
|
521
|
-
}
|
|
599
|
+
nodes.reverse().forEach(el => parent.insertBefore(el, node.nextSibling))
|
|
522
600
|
|
|
523
|
-
|
|
524
|
-
node.parentNode.replaceChild(fragment, node);
|
|
525
|
-
// parent.classList.add('fez')
|
|
526
|
-
// parent.classList.add(`fez-${this.fezName}`)
|
|
601
|
+
this.root.remove()
|
|
527
602
|
this.root = parent
|
|
528
|
-
return
|
|
603
|
+
return nodes
|
|
529
604
|
}
|
|
530
605
|
|
|
531
606
|
reactiveStore(obj, handler) {
|
package/src/fez/lib/template.js
CHANGED
|
@@ -40,6 +40,11 @@ function parseBlock(data, ifStack) {
|
|
|
40
40
|
else {
|
|
41
41
|
const prefix = '@html '
|
|
42
42
|
|
|
43
|
+
if (data.startsWith('json ')) {
|
|
44
|
+
data = data.replace('json ', "@html '<pre class=json>'+JSON.stringify(")
|
|
45
|
+
data += ", null, 2) + '</pre>'"
|
|
46
|
+
}
|
|
47
|
+
|
|
43
48
|
if (data.startsWith(prefix)) {
|
|
44
49
|
data = data.replace(prefix, '')
|
|
45
50
|
} else {
|
|
@@ -93,6 +98,10 @@ export default function createTemplate(text, opts = {}) {
|
|
|
93
98
|
return parsedData
|
|
94
99
|
});
|
|
95
100
|
|
|
101
|
+
result = result
|
|
102
|
+
.replace(/<!\-\-.*?\-\->/g, '')
|
|
103
|
+
.replace(/>\s+</g, '><')
|
|
104
|
+
|
|
96
105
|
result = '`' + result.trim() + '`'
|
|
97
106
|
|
|
98
107
|
try {
|
package/src/fez/root.js
CHANGED
|
@@ -141,6 +141,7 @@ Fez.htmlEscape = (text) => {
|
|
|
141
141
|
text = text
|
|
142
142
|
// .replaceAll('&', "&")
|
|
143
143
|
.replace(/font-family\s*:\s*(?:&[^;]+;|[^;])*?;/gi, '')
|
|
144
|
+
.replaceAll("&", '&')
|
|
144
145
|
.replaceAll("'", ''')
|
|
145
146
|
.replaceAll('"', '"')
|
|
146
147
|
.replaceAll('<', '<')
|
|
@@ -191,6 +192,7 @@ Fez.error = (text, show) => {
|
|
|
191
192
|
}
|
|
192
193
|
Fez.log = (text) => {
|
|
193
194
|
if (Fez.LOG === true) {
|
|
195
|
+
text = String(text).substring(0, 180)
|
|
194
196
|
console.log(`Fez: ${text}`)
|
|
195
197
|
}
|
|
196
198
|
}
|
|
@@ -210,20 +212,34 @@ Fez.untilTrue = (func, pingRate) => {
|
|
|
210
212
|
}
|
|
211
213
|
|
|
212
214
|
// Script from URL
|
|
213
|
-
// head({ js: 'https://example.com/script.js' });
|
|
215
|
+
// Fez.head({ js: 'https://example.com/script.js' });
|
|
214
216
|
// Script with attributes
|
|
215
|
-
// head({ js: 'https://example.com/script.js', type: 'module', async: true });
|
|
217
|
+
// Fez.head({ js: 'https://example.com/script.js', type: 'module', async: true });
|
|
216
218
|
// Script with callback
|
|
217
|
-
// head({ js: 'https://example.com/script.js' }, () => { console.log('loaded') });
|
|
219
|
+
// Fez.head({ js: 'https://example.com/script.js' }, () => { console.log('loaded') });
|
|
218
220
|
// Module loading with auto-import to window
|
|
219
|
-
// head({ js: 'https://example.com/module.js', module: 'MyModule' }); // imports and sets window.MyModule
|
|
221
|
+
// Fez.head({ js: 'https://example.com/module.js', module: 'MyModule' }); // imports and sets window.MyModule
|
|
220
222
|
// CSS inclusion
|
|
221
|
-
// head({ css: 'https://example.com/styles.css' });
|
|
223
|
+
// Fez.head({ css: 'https://example.com/styles.css' });
|
|
222
224
|
// CSS with additional attributes and callback
|
|
223
|
-
// head({ css: 'https://example.com/styles.css', media: 'print' }, () => { console.log('CSS loaded') })
|
|
225
|
+
// Fez.head({ css: 'https://example.com/styles.css', media: 'print' }, () => { console.log('CSS loaded') })
|
|
224
226
|
// Inline script evaluation
|
|
225
|
-
// head({ script: 'console.log("Hello world")' })
|
|
227
|
+
// Fez.head({ script: 'console.log("Hello world")' })
|
|
228
|
+
// Extract from nodes
|
|
229
|
+
// Fez.head(domNode)
|
|
226
230
|
Fez.head = (config, callback) => {
|
|
231
|
+
if (config.nodeName) {
|
|
232
|
+
if (config.nodeName == 'SCRIPT') {
|
|
233
|
+
Fez.head({script: config.innerText})
|
|
234
|
+
config.remove()
|
|
235
|
+
} else {
|
|
236
|
+
config.querySelectorAll('script').forEach((n) => Fez.head(n) )
|
|
237
|
+
config.querySelectorAll('template[fez], xmp[fez], script[fez]').forEach((n) => Fez.compile(n) )
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
|
|
227
243
|
if (typeof config !== 'object' || config === null) {
|
|
228
244
|
throw new Error('head requires an object parameter');
|
|
229
245
|
}
|
|
@@ -459,6 +475,25 @@ Fez.store = {
|
|
|
459
475
|
}
|
|
460
476
|
};
|
|
461
477
|
|
|
478
|
+
// create dom root and return it
|
|
479
|
+
Fez.domRoot = (data, name = 'div') => {
|
|
480
|
+
if (data instanceof Node) {
|
|
481
|
+
return data
|
|
482
|
+
} else {
|
|
483
|
+
const root = document.createElement(name)
|
|
484
|
+
root.innerHTML = data
|
|
485
|
+
return root
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// add class by name to node and remove it from siblings
|
|
490
|
+
Fez.activateNode = (node, klass = 'active') => {
|
|
491
|
+
Array.from(node.parentElement.children).forEach(child => {
|
|
492
|
+
child.classList.remove(klass)
|
|
493
|
+
})
|
|
494
|
+
node.classList.add(klass)
|
|
495
|
+
}
|
|
496
|
+
|
|
462
497
|
Fez.compile = compile
|
|
463
498
|
Fez.state = state
|
|
464
499
|
|
package/src/fez.js
CHANGED
|
@@ -6,6 +6,9 @@ if (typeof window !== 'undefined') window.FezBase = FezBase
|
|
|
6
6
|
import Fez from './fez/root.js'
|
|
7
7
|
if (typeof window !== 'undefined') window.Fez = Fez
|
|
8
8
|
|
|
9
|
+
// Load defaults after Fez is properly initialized
|
|
10
|
+
import('./fez/defaults.js')
|
|
11
|
+
|
|
9
12
|
// clear all unattached nodes
|
|
10
13
|
setInterval(() => {
|
|
11
14
|
for (const [key, el] of Fez.instances) {
|
|
@@ -22,18 +25,23 @@ const observer = new MutationObserver((mutations) => {
|
|
|
22
25
|
for (const { addedNodes, removedNodes } of mutations) {
|
|
23
26
|
addedNodes.forEach((node) => {
|
|
24
27
|
if (node.nodeType !== 1) return; // only elements
|
|
25
|
-
|
|
28
|
+
|
|
26
29
|
if (node.matches('template[fez], xmp[fez], script[fez]')) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
Fez.compile(node);
|
|
31
|
+
node.remove();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (node.querySelectorAll) {
|
|
35
|
+
const nestedTemplates = node.querySelectorAll('template[fez], xmp[fez], script[fez]');
|
|
36
|
+
nestedTemplates.forEach(template => {
|
|
37
|
+
Fez.compile(template);
|
|
38
|
+
template.remove();
|
|
39
|
+
});
|
|
31
40
|
}
|
|
32
41
|
});
|
|
33
42
|
|
|
34
43
|
removedNodes.forEach((node) => {
|
|
35
44
|
if (node.nodeType === 1 && node.querySelectorAll) {
|
|
36
|
-
// check both the node itself and its descendants
|
|
37
45
|
const fezElements = node.querySelectorAll('.fez, :scope.fez');
|
|
38
46
|
fezElements
|
|
39
47
|
.forEach(el => {
|
|
@@ -53,22 +61,5 @@ observer.observe(document.documentElement, {
|
|
|
53
61
|
subtree: true
|
|
54
62
|
});
|
|
55
63
|
|
|
56
|
-
// fez custom tags
|
|
57
|
-
|
|
58
|
-
//<fez-component name="some-node" :props="fez.props"></fez-node>
|
|
59
|
-
Fez('fez-component', class {
|
|
60
|
-
init(props) {
|
|
61
|
-
const tag = document.createElement(props.name)
|
|
62
|
-
tag.props = props.props || props['data-props'] || props
|
|
63
|
-
|
|
64
|
-
while (this.root.firstChild) {
|
|
65
|
-
this.root.parentNode.insertBefore(this.root.lastChild, tag.nextSibling);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
this.root.innerHTML = ''
|
|
69
|
-
this.root.appendChild(tag)
|
|
70
|
-
}
|
|
71
|
-
})
|
|
72
|
-
|
|
73
64
|
export default Fez
|
|
74
65
|
export { Fez }
|
package/src/log.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// pretty print HTML
|
|
2
|
+
const LOG_PP = (html) => {
|
|
3
|
+
const parts = html
|
|
4
|
+
.split(/(<\/?[^>]+>)/g)
|
|
5
|
+
.map(p => p.trim())
|
|
6
|
+
.filter(p => p);
|
|
7
|
+
|
|
8
|
+
let indent = 0;
|
|
9
|
+
const lines = [];
|
|
10
|
+
|
|
11
|
+
for (let i = 0; i < parts.length; i++) {
|
|
12
|
+
const part = parts[i];
|
|
13
|
+
const nextPart = parts[i + 1];
|
|
14
|
+
const nextNextPart = parts[i + 2];
|
|
15
|
+
|
|
16
|
+
// Check if it's a tag
|
|
17
|
+
if (part.startsWith('<')) {
|
|
18
|
+
// Check if this is an opening tag followed by text and then its closing tag
|
|
19
|
+
if (!part.startsWith('</') && !part.endsWith('/>') && nextPart && !nextPart.startsWith('<') && nextNextPart && nextNextPart.startsWith('</')) {
|
|
20
|
+
// Combine them on one line
|
|
21
|
+
const actualIndent = Math.max(0, indent);
|
|
22
|
+
lines.push(' '.repeat(actualIndent) + part + nextPart + nextNextPart);
|
|
23
|
+
i += 2; // Skip the next two parts
|
|
24
|
+
}
|
|
25
|
+
// Closing tag
|
|
26
|
+
else if (part.startsWith('</')) {
|
|
27
|
+
indent--;
|
|
28
|
+
const actualIndent = Math.max(0, indent);
|
|
29
|
+
lines.push(' '.repeat(actualIndent) + part);
|
|
30
|
+
}
|
|
31
|
+
// Self-closing tag
|
|
32
|
+
else if (part.endsWith('/>') || part.includes(' />')) {
|
|
33
|
+
const actualIndent = Math.max(0, indent);
|
|
34
|
+
lines.push(' '.repeat(actualIndent) + part);
|
|
35
|
+
}
|
|
36
|
+
// Opening tag
|
|
37
|
+
else {
|
|
38
|
+
const actualIndent = Math.max(0, indent);
|
|
39
|
+
lines.push(' '.repeat(actualIndent) + part);
|
|
40
|
+
indent++;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Text node
|
|
44
|
+
else if (part) {
|
|
45
|
+
const actualIndent = Math.max(0, indent);
|
|
46
|
+
lines.push(' '.repeat(actualIndent) + part);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return lines.join('\n');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const LOG = (() => {
|
|
54
|
+
const logs = [];
|
|
55
|
+
const logTypes = []; // Track the original type of each log
|
|
56
|
+
let currentIndex = 0;
|
|
57
|
+
|
|
58
|
+
return o => {
|
|
59
|
+
if (!document.body) {
|
|
60
|
+
window.requestAnimationFrame( () => LOG(o) )
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (o instanceof Node) {
|
|
65
|
+
o = LOG_PP(o.outerHTML)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Store the original type
|
|
69
|
+
let originalType = typeof o;
|
|
70
|
+
|
|
71
|
+
if (o === undefined) { o = 'undefined' }
|
|
72
|
+
if (o === null) { o = 'null' }
|
|
73
|
+
|
|
74
|
+
if (Array.isArray(o)) {
|
|
75
|
+
originalType = 'array';
|
|
76
|
+
} else if (typeof o === 'object' && o !== null) {
|
|
77
|
+
originalType = 'object';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (typeof o != 'string') {
|
|
81
|
+
o = JSON.stringify(o, (key, value) => {
|
|
82
|
+
if (typeof value === 'function') {
|
|
83
|
+
return String(value);
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}, 2).replaceAll('<', '<')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
o = o.trim()
|
|
90
|
+
|
|
91
|
+
logs.push(o + `\n\ntype: ${originalType}`);
|
|
92
|
+
logTypes.push(originalType);
|
|
93
|
+
|
|
94
|
+
let d = document.getElementById('dump-dialog');
|
|
95
|
+
if (!d) {
|
|
96
|
+
d = document.body.appendChild(document.createElement('div'));
|
|
97
|
+
d.id = 'dump-dialog';
|
|
98
|
+
d.style.cssText =
|
|
99
|
+
'position:fixed;top:30px;left:30px;right:50px;bottom:50px;' +
|
|
100
|
+
'background:#fff;border:1px solid#333;box-shadow:0 0 10px rgba(0,0,0,0.5);' +
|
|
101
|
+
'padding:20px;overflow:auto;z-index:9999;font:13px/1.4 monospace;white-space:pre';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check if we have a saved index and it's still valid
|
|
105
|
+
const savedIndex = parseInt(localStorage.getItem('_LOG_INDEX'));
|
|
106
|
+
if (!isNaN(savedIndex) && savedIndex >= 0 && savedIndex < logs.length) {
|
|
107
|
+
currentIndex = savedIndex;
|
|
108
|
+
} else {
|
|
109
|
+
currentIndex = logs.length - 1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const renderContent = () => {
|
|
113
|
+
const buttons = logs.map((_, i) => {
|
|
114
|
+
let bgColor = '#f0f0f0'; // default
|
|
115
|
+
if (i !== currentIndex) {
|
|
116
|
+
if (logTypes[i] === 'object') {
|
|
117
|
+
bgColor = '#d6e3ef'; // super light blue
|
|
118
|
+
} else if (logTypes[i] === 'array') {
|
|
119
|
+
bgColor = '#d8d5ef'; // super light indigo
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return `<button style="padding:4px 8px;margin:0;cursor:pointer;background:${i === currentIndex ? '#333' : bgColor};color:${i === currentIndex ? '#fff' : '#000'}" data-index="${i}">${i + 1}</button>`
|
|
123
|
+
}).join('');
|
|
124
|
+
|
|
125
|
+
d.innerHTML =
|
|
126
|
+
'<div style="display:flex;flex-direction:column;height:100%">' +
|
|
127
|
+
'<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:10px">' +
|
|
128
|
+
'<div style="display:flex;flex-wrap:wrap;gap:4px;flex:1;margin-right:10px">' + buttons + '</div>' +
|
|
129
|
+
'<button style="padding:4px 8px;cursor:pointer;flex-shrink:0">×</button>' +
|
|
130
|
+
'</div>' +
|
|
131
|
+
'<xmp style="flex:1;overflow:auto;margin:0;padding:0;color:#000;background:#fff;font-size:14px;line-height:22px">' + logs[currentIndex] + '</xmp>' +
|
|
132
|
+
'</div>';
|
|
133
|
+
|
|
134
|
+
d.querySelector('button[style*="flex-shrink:0"]').onclick = () => d.remove();
|
|
135
|
+
|
|
136
|
+
d.querySelectorAll('button[data-index]').forEach(btn => {
|
|
137
|
+
btn.onclick = () => {
|
|
138
|
+
currentIndex = parseInt(btn.dataset.index);
|
|
139
|
+
localStorage.setItem('_LOG_INDEX', currentIndex);
|
|
140
|
+
renderContent();
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
renderContent();
|
|
146
|
+
};
|
|
147
|
+
})();
|
|
148
|
+
|
|
149
|
+
if (typeof window !== 'undefined') {
|
|
150
|
+
window.LOG = LOG
|
|
151
|
+
window.LOG_PP = LOG_PP
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export default LOG
|