@zenithbuild/core 0.3.3 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/zen-build.ts +0 -0
- package/bin/zen-dev.ts +0 -0
- package/bin/zen-preview.ts +0 -0
- package/cli/commands/dev.ts +156 -91
- package/cli/utils/branding.ts +25 -0
- package/cli/utils/content.ts +112 -0
- package/cli/utils/logger.ts +22 -16
- package/compiler/parse/parseTemplate.ts +120 -49
- package/compiler/parse/scriptAnalysis.ts +6 -0
- package/compiler/runtime/dataExposure.ts +12 -4
- package/compiler/runtime/generateHydrationBundle.ts +20 -15
- package/compiler/runtime/transformIR.ts +4 -8
- package/compiler/runtime/wrapExpression.ts +23 -12
- package/compiler/runtime/wrapExpressionWithLoop.ts +8 -2
- package/compiler/ssg-build.ts +7 -3
- package/compiler/transform/expressionTransformer.ts +385 -0
- package/compiler/transform/transformNode.ts +1 -1
- package/core/config/index.ts +16 -0
- package/core/config/loader.ts +69 -0
- package/core/config/types.ts +89 -0
- package/core/plugins/index.ts +7 -0
- package/core/plugins/registry.ts +81 -0
- package/dist/cli.js +1 -0
- package/dist/zen-build.js +568 -292
- package/dist/zen-dev.js +568 -292
- package/dist/zen-preview.js +568 -292
- package/dist/zenith.js +568 -292
- package/package.json +4 -1
- package/runtime/bundle-generator.ts +298 -37
- package/runtime/client-runtime.ts +17 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenithbuild/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Core library for the Zenith framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"exports": {
|
|
27
27
|
".": "./index.ts",
|
|
28
28
|
"./compiler": "./compiler/index.ts",
|
|
29
|
+
"./config": "./core/config/index.ts",
|
|
29
30
|
"./core": "./core/index.ts",
|
|
30
31
|
"./router": "./router/index.ts",
|
|
31
32
|
"./runtime": "./runtime/index.ts"
|
|
@@ -58,7 +59,9 @@
|
|
|
58
59
|
"typescript": "^5"
|
|
59
60
|
},
|
|
60
61
|
"dependencies": {
|
|
62
|
+
"@types/marked": "^6.0.0",
|
|
61
63
|
"@types/parse5": "^7.0.0",
|
|
64
|
+
"marked": "^17.0.1",
|
|
62
65
|
"parse5": "^8.0.0"
|
|
63
66
|
}
|
|
64
67
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* This is served as an external JS file, not inlined
|
|
18
18
|
*/
|
|
19
19
|
export function generateBundleJS(): string {
|
|
20
|
-
|
|
20
|
+
return `/*!
|
|
21
21
|
* Zenith Runtime v0.1.0
|
|
22
22
|
* Shared client-side runtime for hydration and reactivity
|
|
23
23
|
*/
|
|
@@ -296,6 +296,57 @@ export function generateBundleJS(): string {
|
|
|
296
296
|
return expressionRegistry.get(id);
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
+
function updateNode(node, exprId, pageState) {
|
|
300
|
+
const expr = getExpression(exprId);
|
|
301
|
+
if (!expr) return;
|
|
302
|
+
|
|
303
|
+
zenEffect(function() {
|
|
304
|
+
const result = expr(pageState);
|
|
305
|
+
|
|
306
|
+
if (node.hasAttribute('data-zen-text')) {
|
|
307
|
+
// Handle complex text/children results
|
|
308
|
+
if (result === null || result === undefined || result === false) {
|
|
309
|
+
node.textContent = '';
|
|
310
|
+
} else if (typeof result === 'string') {
|
|
311
|
+
if (result.trim().startsWith('<') && result.trim().endsWith('>')) {
|
|
312
|
+
node.innerHTML = result;
|
|
313
|
+
} else {
|
|
314
|
+
node.textContent = result;
|
|
315
|
+
}
|
|
316
|
+
} else if (result instanceof Node) {
|
|
317
|
+
node.innerHTML = '';
|
|
318
|
+
node.appendChild(result);
|
|
319
|
+
} else if (Array.isArray(result)) {
|
|
320
|
+
node.innerHTML = '';
|
|
321
|
+
const fragment = document.createDocumentFragment();
|
|
322
|
+
result.flat(Infinity).forEach(item => {
|
|
323
|
+
if (item instanceof Node) fragment.appendChild(item);
|
|
324
|
+
else if (item != null && item !== false) fragment.appendChild(document.createTextNode(String(item)));
|
|
325
|
+
});
|
|
326
|
+
node.appendChild(fragment);
|
|
327
|
+
} else {
|
|
328
|
+
node.textContent = String(result);
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
// Attribute update
|
|
332
|
+
const attrNames = ['class', 'style', 'src', 'href', 'disabled', 'checked'];
|
|
333
|
+
for (const attr of attrNames) {
|
|
334
|
+
if (node.hasAttribute('data-zen-attr-' + attr)) {
|
|
335
|
+
if (attr === 'class' || attr === 'className') {
|
|
336
|
+
node.className = String(result || '');
|
|
337
|
+
} else if (attr === 'disabled' || attr === 'checked') {
|
|
338
|
+
if (result) node.setAttribute(attr, '');
|
|
339
|
+
else node.removeAttribute(attr);
|
|
340
|
+
} else {
|
|
341
|
+
if (result != null && result !== false) node.setAttribute(attr, String(result));
|
|
342
|
+
else node.removeAttribute(attr);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
299
350
|
/**
|
|
300
351
|
* Hydrate a page with reactive bindings
|
|
301
352
|
* Called after page HTML is in DOM
|
|
@@ -303,49 +354,209 @@ export function generateBundleJS(): string {
|
|
|
303
354
|
function zenithHydrate(pageState, container) {
|
|
304
355
|
container = container || document;
|
|
305
356
|
|
|
306
|
-
// Find all
|
|
307
|
-
const
|
|
357
|
+
// Find all text expression placeholders
|
|
358
|
+
const textNodes = container.querySelectorAll('[data-zen-text]');
|
|
359
|
+
textNodes.forEach(el => updateNode(el, el.getAttribute('data-zen-text'), pageState));
|
|
308
360
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (
|
|
314
|
-
const expr = getExpression(exprId);
|
|
315
|
-
if (expr) {
|
|
316
|
-
zenEffect(function() {
|
|
317
|
-
el.textContent = expr(pageState);
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
} else if (bindType === 'attr') {
|
|
321
|
-
const attrName = el.getAttribute('data-zen-attr');
|
|
322
|
-
const expr = getExpression(exprId);
|
|
323
|
-
if (expr && attrName) {
|
|
324
|
-
zenEffect(function() {
|
|
325
|
-
el.setAttribute(attrName, expr(pageState));
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
}
|
|
361
|
+
// Find all attribute expression placeholders
|
|
362
|
+
const attrNodes = container.querySelectorAll('[data-zen-attr-class], [data-zen-attr-style], [data-zen-attr-src], [data-zen-attr-href]');
|
|
363
|
+
attrNodes.forEach(el => {
|
|
364
|
+
const attrMatch = Array.from(el.attributes).find(a => a.name.startsWith('data-zen-attr-'));
|
|
365
|
+
if (attrMatch) updateNode(el, attrMatch.value, pageState);
|
|
329
366
|
});
|
|
330
367
|
|
|
331
368
|
// Wire up event handlers
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
369
|
+
const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur', 'keyup', 'keydown'];
|
|
370
|
+
eventTypes.forEach(eventType => {
|
|
371
|
+
const elements = container.querySelectorAll('[data-zen-' + eventType + ']');
|
|
372
|
+
elements.forEach(el => {
|
|
373
|
+
const handlerName = el.getAttribute('data-zen-' + eventType);
|
|
374
|
+
if (handlerName && (global[handlerName] || getExpression(handlerName))) {
|
|
375
|
+
el.addEventListener(eventType, function(e) {
|
|
376
|
+
const handler = global[handlerName] || getExpression(handlerName);
|
|
377
|
+
if (typeof handler === 'function') handler(e, el);
|
|
378
|
+
});
|
|
341
379
|
}
|
|
342
|
-
}
|
|
380
|
+
});
|
|
343
381
|
});
|
|
344
382
|
|
|
345
383
|
// Trigger mount
|
|
346
384
|
triggerMount();
|
|
347
385
|
}
|
|
348
386
|
|
|
387
|
+
// ============================================
|
|
388
|
+
// zenith:content - Content Engine
|
|
389
|
+
// ============================================
|
|
390
|
+
|
|
391
|
+
const schemaRegistry = new Map();
|
|
392
|
+
const builtInEnhancers = {
|
|
393
|
+
readTime: (item) => {
|
|
394
|
+
const wordsPerMinute = 200;
|
|
395
|
+
const text = item.content || '';
|
|
396
|
+
const wordCount = text.split(/\\s+/).length;
|
|
397
|
+
const minutes = Math.ceil(wordCount / wordsPerMinute);
|
|
398
|
+
return Object.assign({}, item, { readTime: minutes + ' min' });
|
|
399
|
+
},
|
|
400
|
+
wordCount: (item) => {
|
|
401
|
+
const text = item.content || '';
|
|
402
|
+
const wordCount = text.split(/\\s+/).length;
|
|
403
|
+
return Object.assign({}, item, { wordCount: wordCount });
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
async function applyEnhancers(item, enhancers) {
|
|
408
|
+
let enrichedItem = Object.assign({}, item);
|
|
409
|
+
for (const enhancer of enhancers) {
|
|
410
|
+
if (typeof enhancer === 'string') {
|
|
411
|
+
const fn = builtInEnhancers[enhancer];
|
|
412
|
+
if (fn) enrichedItem = await fn(enrichedItem);
|
|
413
|
+
} else if (typeof enhancer === 'function') {
|
|
414
|
+
enrichedItem = await enhancer(enrichedItem);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return enrichedItem;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
class ZenCollection {
|
|
421
|
+
constructor(items) {
|
|
422
|
+
this.items = [...items];
|
|
423
|
+
this.filters = [];
|
|
424
|
+
this.sortField = null;
|
|
425
|
+
this.sortOrder = 'desc';
|
|
426
|
+
this.limitCount = null;
|
|
427
|
+
this.selectedFields = null;
|
|
428
|
+
this.enhancers = [];
|
|
429
|
+
this._groupByFolder = false;
|
|
430
|
+
}
|
|
431
|
+
where(fn) { this.filters.push(fn); return this; }
|
|
432
|
+
sortBy(field, order = 'desc') { this.sortField = field; this.sortOrder = order; return this; }
|
|
433
|
+
limit(n) { this.limitCount = n; return this; }
|
|
434
|
+
fields(f) { this.selectedFields = f; return this; }
|
|
435
|
+
enhanceWith(e) { this.enhancers.push(e); return this; }
|
|
436
|
+
groupByFolder() { this._groupByFolder = true; return this; }
|
|
437
|
+
get() {
|
|
438
|
+
let results = [...this.items];
|
|
439
|
+
for (const filter of this.filters) results = results.filter(filter);
|
|
440
|
+
if (this.sortField) {
|
|
441
|
+
results.sort((a, b) => {
|
|
442
|
+
const valA = a[this.sortField];
|
|
443
|
+
const valB = b[this.sortField];
|
|
444
|
+
if (valA < valB) return this.sortOrder === 'asc' ? -1 : 1;
|
|
445
|
+
if (valA > valB) return this.sortOrder === 'asc' ? 1 : -1;
|
|
446
|
+
return 0;
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
if (this.limitCount !== null) results = results.slice(0, this.limitCount);
|
|
450
|
+
|
|
451
|
+
// Apply enhancers synchronously if possible
|
|
452
|
+
if (this.enhancers.length > 0) {
|
|
453
|
+
results = results.map(item => {
|
|
454
|
+
let enrichedItem = Object.assign({}, item);
|
|
455
|
+
for (const enhancer of this.enhancers) {
|
|
456
|
+
if (typeof enhancer === 'string') {
|
|
457
|
+
const fn = builtInEnhancers[enhancer];
|
|
458
|
+
if (fn) enrichedItem = fn(enrichedItem);
|
|
459
|
+
} else if (typeof enhancer === 'function') {
|
|
460
|
+
enrichedItem = enhancer(enrichedItem);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return enrichedItem;
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (this.selectedFields) {
|
|
468
|
+
results = results.map(item => {
|
|
469
|
+
const newItem = {};
|
|
470
|
+
this.selectedFields.forEach(f => { newItem[f] = item[f]; });
|
|
471
|
+
return newItem;
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Group by folder if requested
|
|
476
|
+
if (this._groupByFolder) {
|
|
477
|
+
const groups = {};
|
|
478
|
+
const groupOrder = [];
|
|
479
|
+
for (const item of results) {
|
|
480
|
+
// Extract folder from slug (e.g., "getting-started/installation" -> "getting-started")
|
|
481
|
+
const slug = item.slug || item.id || '';
|
|
482
|
+
const parts = slug.split('/');
|
|
483
|
+
const folder = parts.length > 1 ? parts[0] : 'root';
|
|
484
|
+
|
|
485
|
+
if (!groups[folder]) {
|
|
486
|
+
groups[folder] = {
|
|
487
|
+
id: folder,
|
|
488
|
+
title: folder.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
|
|
489
|
+
items: []
|
|
490
|
+
};
|
|
491
|
+
groupOrder.push(folder);
|
|
492
|
+
}
|
|
493
|
+
groups[folder].items.push(item);
|
|
494
|
+
}
|
|
495
|
+
return groupOrder.map(f => groups[f]);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return results;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function defineSchema(name, schema) { schemaRegistry.set(name, schema); }
|
|
503
|
+
|
|
504
|
+
function zenCollection(collectionName) {
|
|
505
|
+
const data = (global.__ZENITH_CONTENT__ && global.__ZENITH_CONTENT__[collectionName]) || [];
|
|
506
|
+
return new ZenCollection(data);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Virtual DOM Helper for JSX-style expressions
|
|
510
|
+
function h(tag, props, children) {
|
|
511
|
+
const el = document.createElement(tag);
|
|
512
|
+
if (props) {
|
|
513
|
+
for (const [key, value] of Object.entries(props)) {
|
|
514
|
+
if (key.startsWith('on') && typeof value === 'function') {
|
|
515
|
+
el.addEventListener(key.slice(2).toLowerCase(), value);
|
|
516
|
+
} else if (key === 'class' || key === 'className') {
|
|
517
|
+
el.className = String(value || '');
|
|
518
|
+
} else if (key === 'style' && typeof value === 'object') {
|
|
519
|
+
Object.assign(el.style, value);
|
|
520
|
+
} else if (value != null && value !== false) {
|
|
521
|
+
el.setAttribute(key, String(value));
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (children != null && children !== false) {
|
|
526
|
+
// Flatten nested arrays (from .map() calls)
|
|
527
|
+
const childrenArray = Array.isArray(children) ? children.flat(Infinity) : [children];
|
|
528
|
+
for (const child of childrenArray) {
|
|
529
|
+
// Skip null, undefined, and false
|
|
530
|
+
if (child == null || child === false) continue;
|
|
531
|
+
|
|
532
|
+
if (typeof child === 'string') {
|
|
533
|
+
// Check if string looks like HTML
|
|
534
|
+
if (child.trim().startsWith('<') && child.trim().endsWith('>')) {
|
|
535
|
+
// Render as HTML
|
|
536
|
+
const wrapper = document.createElement('div');
|
|
537
|
+
wrapper.innerHTML = child;
|
|
538
|
+
while (wrapper.firstChild) {
|
|
539
|
+
el.appendChild(wrapper.firstChild);
|
|
540
|
+
}
|
|
541
|
+
} else {
|
|
542
|
+
el.appendChild(document.createTextNode(child));
|
|
543
|
+
}
|
|
544
|
+
} else if (typeof child === 'number') {
|
|
545
|
+
el.appendChild(document.createTextNode(String(child)));
|
|
546
|
+
} else if (child instanceof Node) {
|
|
547
|
+
el.appendChild(child);
|
|
548
|
+
} else if (Array.isArray(child)) {
|
|
549
|
+
// Handle nested arrays (shouldn't happen after flat() but just in case)
|
|
550
|
+
for (const c of child) {
|
|
551
|
+
if (c instanceof Node) el.appendChild(c);
|
|
552
|
+
else if (c != null && c !== false) el.appendChild(document.createTextNode(String(c)));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return el;
|
|
558
|
+
}
|
|
559
|
+
|
|
349
560
|
// ============================================
|
|
350
561
|
// Export to window.__zenith
|
|
351
562
|
// ============================================
|
|
@@ -359,6 +570,11 @@ export function generateBundleJS(): string {
|
|
|
359
570
|
ref: zenRef,
|
|
360
571
|
batch: zenBatch,
|
|
361
572
|
untrack: zenUntrack,
|
|
573
|
+
// zenith:content
|
|
574
|
+
defineSchema: defineSchema,
|
|
575
|
+
zenCollection: zenCollection,
|
|
576
|
+
// Virtual DOM helper for JSX
|
|
577
|
+
h: h,
|
|
362
578
|
// Lifecycle
|
|
363
579
|
onMount: zenOnMount,
|
|
364
580
|
onUnmount: zenOnUnmount,
|
|
@@ -394,6 +610,51 @@ export function generateBundleJS(): string {
|
|
|
394
610
|
global.onMount = zenOnMount;
|
|
395
611
|
global.onUnmount = zenOnUnmount;
|
|
396
612
|
|
|
613
|
+
// ============================================
|
|
614
|
+
// HMR Client (Development Only)
|
|
615
|
+
// ============================================
|
|
616
|
+
|
|
617
|
+
if (typeof window !== 'undefined' && (location.hostname === 'localhost' || location.hostname === '127.0.0.1')) {
|
|
618
|
+
let socket;
|
|
619
|
+
function connectHMR() {
|
|
620
|
+
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
621
|
+
socket = new WebSocket(protocol + '//' + location.host + '/hmr');
|
|
622
|
+
|
|
623
|
+
socket.onmessage = function(event) {
|
|
624
|
+
try {
|
|
625
|
+
const data = JSON.parse(event.data);
|
|
626
|
+
if (data.type === 'reload') {
|
|
627
|
+
console.log('[Zenith] HMR: Reloading page...');
|
|
628
|
+
location.reload();
|
|
629
|
+
} else if (data.type === 'style-update') {
|
|
630
|
+
console.log('[Zenith] HMR: Updating style ' + data.url);
|
|
631
|
+
const links = document.querySelectorAll('link[rel="stylesheet"]');
|
|
632
|
+
for (let i = 0; i < links.length; i++) {
|
|
633
|
+
const link = links[i];
|
|
634
|
+
const url = new URL(link.href);
|
|
635
|
+
if (url.pathname === data.url) {
|
|
636
|
+
link.href = data.url + '?t=' + Date.now();
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
} catch (e) {
|
|
642
|
+
console.error('[Zenith] HMR Error:', e);
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
socket.onclose = function() {
|
|
647
|
+
console.log('[Zenith] HMR: Connection closed. Retrying in 2s...');
|
|
648
|
+
setTimeout(connectHMR, 2000);
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Connect unless explicitly disabled
|
|
653
|
+
if (!window.__ZENITH_NO_HMR__) {
|
|
654
|
+
connectHMR();
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
397
658
|
})(typeof window !== 'undefined' ? window : this);
|
|
398
659
|
`
|
|
399
660
|
}
|
|
@@ -403,14 +664,14 @@ export function generateBundleJS(): string {
|
|
|
403
664
|
* For production builds
|
|
404
665
|
*/
|
|
405
666
|
export function generateMinifiedBundleJS(): string {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
667
|
+
// For now, return non-minified
|
|
668
|
+
// TODO: Add minification via terser or similar
|
|
669
|
+
return generateBundleJS()
|
|
409
670
|
}
|
|
410
671
|
|
|
411
672
|
/**
|
|
412
673
|
* Get bundle version for cache busting
|
|
413
674
|
*/
|
|
414
675
|
export function getBundleVersion(): string {
|
|
415
|
-
|
|
676
|
+
return '0.1.0'
|
|
416
677
|
}
|
|
@@ -358,6 +358,23 @@ function updateTextBinding(node: Element, expressionId: string, state: any): voi
|
|
|
358
358
|
const result = expression(state);
|
|
359
359
|
if (result === null || result === undefined || result === false) {
|
|
360
360
|
node.textContent = '';
|
|
361
|
+
} else if (typeof result === 'string') {
|
|
362
|
+
if (result.trim().startsWith('<') && result.trim().endsWith('>')) {
|
|
363
|
+
node.innerHTML = result;
|
|
364
|
+
} else {
|
|
365
|
+
node.textContent = result;
|
|
366
|
+
}
|
|
367
|
+
} else if (result instanceof Node) {
|
|
368
|
+
node.innerHTML = '';
|
|
369
|
+
node.appendChild(result);
|
|
370
|
+
} else if (Array.isArray(result)) {
|
|
371
|
+
node.innerHTML = '';
|
|
372
|
+
const fragment = document.createDocumentFragment();
|
|
373
|
+
result.flat(Infinity).forEach(item => {
|
|
374
|
+
if (item instanceof Node) fragment.appendChild(item);
|
|
375
|
+
else if (item != null && item !== false) fragment.appendChild(document.createTextNode(String(item)));
|
|
376
|
+
});
|
|
377
|
+
node.appendChild(fragment);
|
|
361
378
|
} else {
|
|
362
379
|
node.textContent = String(result);
|
|
363
380
|
}
|