juxscript 1.1.371 ā 1.1.375
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/cli.js +18 -0
- package/juxconfig.example.js +11 -2
- package/machinery/build3.js +7 -2
- package/machinery/compiler4.js +285 -243
- package/machinery/serve.js +13 -6
- package/package.json +1 -1
- package/presets/sidebar/index.jux +43 -6
- package/presets/sidebar/usage.jux +1 -1
package/bin/cli.js
CHANGED
|
@@ -67,6 +67,24 @@ function resolveDestFolderName(parentDir, baseName) {
|
|
|
67
67
|
return `${baseName}-${Date.now()}`;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
function listFilesRecursive(dir, base = '') {
|
|
71
|
+
const results = [];
|
|
72
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
73
|
+
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
if (entry.name.startsWith('.')) continue;
|
|
76
|
+
const rel = base ? path.join(base, entry.name) : entry.name;
|
|
77
|
+
|
|
78
|
+
if (entry.isDirectory()) {
|
|
79
|
+
results.push(...listFilesRecursive(path.join(dir, entry.name), rel));
|
|
80
|
+
} else {
|
|
81
|
+
results.push(rel);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return results;
|
|
86
|
+
}
|
|
87
|
+
|
|
70
88
|
function promptPresetSelection(presets) {
|
|
71
89
|
return new Promise((resolve) => {
|
|
72
90
|
console.log('\nš¦ Available component presets:\n');
|
package/juxconfig.example.js
CHANGED
|
@@ -9,14 +9,23 @@ export const config = {
|
|
|
9
9
|
directories: {
|
|
10
10
|
source: './jux', // Where your .jux files live
|
|
11
11
|
distribution: './.jux-dist', // Where build artifacts go
|
|
12
|
-
public: './public' //
|
|
12
|
+
public: './public' // Static assets folder (CSS, images, fonts, etc.)
|
|
13
13
|
},
|
|
14
14
|
|
|
15
15
|
// Application Defaults
|
|
16
16
|
defaults: {
|
|
17
17
|
httpPort: 3000,
|
|
18
18
|
wsPort: 3001,
|
|
19
|
-
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
// Precompile: bootstrap .jux files that run before routing.
|
|
22
|
+
// These render into sibling containers of #app at boot time.
|
|
23
|
+
// For CSS/JS injection, use jux.include() inside your .jux files.
|
|
24
|
+
//
|
|
25
|
+
// String form: 'sidebar/usage.jux' ā auto-derives container id
|
|
26
|
+
// Object form: { file: 'sidebar/usage.jux', target: 'app-sidebar' }
|
|
27
|
+
precompile: {
|
|
28
|
+
juxfiles: [],
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
};
|
package/machinery/build3.js
CHANGED
|
@@ -33,6 +33,10 @@ const defaults = {
|
|
|
33
33
|
autoRoute: rawConfig.defaults?.autoRoute ?? true
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
const precompile = {
|
|
37
|
+
juxfiles: rawConfig.precompile?.juxfiles || [],
|
|
38
|
+
};
|
|
39
|
+
|
|
36
40
|
// Resolve absolute paths
|
|
37
41
|
const paths = {
|
|
38
42
|
source: path.resolve(PROJECT_ROOT, directories.source),
|
|
@@ -69,9 +73,10 @@ console.log(`\n`);
|
|
|
69
73
|
const compiler = new JuxCompiler({
|
|
70
74
|
srcDir: paths.source,
|
|
71
75
|
distDir: paths.distribution,
|
|
72
|
-
publicDir: directories.public,
|
|
76
|
+
publicDir: directories.public,
|
|
73
77
|
defaults,
|
|
74
|
-
paths
|
|
78
|
+
paths,
|
|
79
|
+
precompile
|
|
75
80
|
});
|
|
76
81
|
|
|
77
82
|
compiler.build()
|
package/machinery/compiler4.js
CHANGED
|
@@ -18,6 +18,7 @@ export class JuxCompiler {
|
|
|
18
18
|
this.publicDir = config.publicDir || './public';
|
|
19
19
|
this.defaults = config.defaults || {};
|
|
20
20
|
this.paths = config.paths || {};
|
|
21
|
+
this.precompile = config.precompile || { juxfiles: [] };
|
|
21
22
|
this._juxscriptExports = null;
|
|
22
23
|
this._juxscriptPath = null;
|
|
23
24
|
this._renderFunctionCounter = 0;
|
|
@@ -28,7 +29,6 @@ export class JuxCompiler {
|
|
|
28
29
|
|
|
29
30
|
const projectRoot = process.cwd();
|
|
30
31
|
|
|
31
|
-
// 1. User's project node_modules (standard npm install)
|
|
32
32
|
const userPath = path.resolve(projectRoot, 'node_modules/juxscript/dist/lib/index.js');
|
|
33
33
|
if (fs.existsSync(userPath)) {
|
|
34
34
|
console.log(`š¦ Using: ${userPath}`);
|
|
@@ -36,7 +36,6 @@ export class JuxCompiler {
|
|
|
36
36
|
return userPath;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// 2. User's project node_modules ā flat dist
|
|
40
39
|
const userPathFlat = path.resolve(projectRoot, 'node_modules/juxscript/dist/index.js');
|
|
41
40
|
if (fs.existsSync(userPathFlat)) {
|
|
42
41
|
console.log(`š¦ Using: ${userPathFlat}`);
|
|
@@ -44,7 +43,6 @@ export class JuxCompiler {
|
|
|
44
43
|
return userPathFlat;
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
// 3. Dev mode ā built from parent package
|
|
48
46
|
const packageRoot = path.resolve(__dirname, '..');
|
|
49
47
|
const devPath = path.resolve(packageRoot, 'dist', 'lib', 'index.js');
|
|
50
48
|
if (fs.existsSync(devPath)) {
|
|
@@ -53,7 +51,6 @@ export class JuxCompiler {
|
|
|
53
51
|
return devPath;
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
// 4. Dev mode ā flat dist
|
|
57
54
|
const devPathFlat = path.resolve(packageRoot, 'dist', 'index.js');
|
|
58
55
|
if (fs.existsSync(devPathFlat)) {
|
|
59
56
|
console.log(`š¦ Using (dev flat): ${devPathFlat}`);
|
|
@@ -61,7 +58,6 @@ export class JuxCompiler {
|
|
|
61
58
|
return devPathFlat;
|
|
62
59
|
}
|
|
63
60
|
|
|
64
|
-
// 5. Monorepo sibling
|
|
65
61
|
const monoPath = path.resolve(projectRoot, '../jux/dist/lib/index.js');
|
|
66
62
|
if (fs.existsSync(monoPath)) {
|
|
67
63
|
console.log(`š¦ Using (mono): ${monoPath}`);
|
|
@@ -69,7 +65,6 @@ export class JuxCompiler {
|
|
|
69
65
|
return monoPath;
|
|
70
66
|
}
|
|
71
67
|
|
|
72
|
-
// 6. Try require.resolve as last resort
|
|
73
68
|
try {
|
|
74
69
|
const resolved = require.resolve('juxscript');
|
|
75
70
|
if (fs.existsSync(resolved)) {
|
|
@@ -79,7 +74,6 @@ export class JuxCompiler {
|
|
|
79
74
|
}
|
|
80
75
|
} catch (_) { }
|
|
81
76
|
|
|
82
|
-
// Debug: show what was checked
|
|
83
77
|
console.error('ā Searched for juxscript at:');
|
|
84
78
|
console.error(` ${userPath}`);
|
|
85
79
|
console.error(` ${userPathFlat}`);
|
|
@@ -87,7 +81,6 @@ export class JuxCompiler {
|
|
|
87
81
|
console.error(` ${devPathFlat}`);
|
|
88
82
|
console.error(` ${monoPath}`);
|
|
89
83
|
|
|
90
|
-
// Check if node_modules/juxscript exists at all
|
|
91
84
|
const juxscriptDir = path.resolve(projectRoot, 'node_modules/juxscript');
|
|
92
85
|
if (fs.existsSync(juxscriptDir)) {
|
|
93
86
|
console.error(`\nš node_modules/juxscript exists. Contents:`);
|
|
@@ -137,7 +130,6 @@ export class JuxCompiler {
|
|
|
137
130
|
} else if (hasExports) {
|
|
138
131
|
sharedModules.push({ name, file: relativePath, content, originalContent: content });
|
|
139
132
|
} else {
|
|
140
|
-
// ā
Auto-wrap pageState references before wrapping in async function
|
|
141
133
|
let processedContent = content;
|
|
142
134
|
if (file.endsWith('.jux')) {
|
|
143
135
|
const result = autowrap(content, relativePath);
|
|
@@ -364,10 +356,8 @@ export class JuxCompiler {
|
|
|
364
356
|
content: m.content,
|
|
365
357
|
lines: m.content.split('\n')
|
|
366
358
|
};
|
|
367
|
-
|
|
368
359
|
let code = m.originalContent || m.content;
|
|
369
360
|
code = this._stripImportsAndExports(code);
|
|
370
|
-
|
|
371
361
|
entry += `\n// From: ${m.file}\n${code}\n`;
|
|
372
362
|
});
|
|
373
363
|
|
|
@@ -379,20 +369,18 @@ export class JuxCompiler {
|
|
|
379
369
|
content: m.content,
|
|
380
370
|
lines: m.content.split('\n')
|
|
381
371
|
};
|
|
382
|
-
|
|
383
372
|
let code = m.originalContent || m.content;
|
|
384
373
|
code = this._stripImportsAndExports(code);
|
|
385
|
-
|
|
386
374
|
entry += `\n// From: ${m.file}\n${code}\n`;
|
|
387
375
|
});
|
|
388
376
|
|
|
389
377
|
entry += `\n// --- VIEW FUNCTIONS ---\n`;
|
|
390
378
|
|
|
391
379
|
const routeToFunctionMap = new Map();
|
|
380
|
+
const fileToFunctionMap = new Map();
|
|
392
381
|
|
|
393
382
|
views.forEach((v, index) => {
|
|
394
383
|
const functionName = `renderJux${index}`;
|
|
395
|
-
|
|
396
384
|
let codeBody = v.autowrappedContent || v.originalContent || v.content;
|
|
397
385
|
const originalLines = (v.originalContent || codeBody).split('\n');
|
|
398
386
|
codeBody = this._stripImportsAndExports(codeBody);
|
|
@@ -406,6 +394,9 @@ export class JuxCompiler {
|
|
|
406
394
|
|
|
407
395
|
entry += `\nasync function ${functionName}() {\n${codeBody}\n}\n`;
|
|
408
396
|
|
|
397
|
+
const relNormalized = v.file.replace(/\\/g, '/');
|
|
398
|
+
fileToFunctionMap.set(relNormalized, functionName);
|
|
399
|
+
|
|
409
400
|
const routePath = v.name
|
|
410
401
|
.toLowerCase()
|
|
411
402
|
.replace(/\\/g, '/')
|
|
@@ -427,12 +418,207 @@ export class JuxCompiler {
|
|
|
427
418
|
this._sourceSnapshot = sourceSnapshot;
|
|
428
419
|
this._validationIssues = [];
|
|
429
420
|
|
|
430
|
-
// Embed source snapshot for runtime error overlay
|
|
431
421
|
entry += `\nwindow.__juxSources = ${JSON.stringify(sourceSnapshot)};\n`;
|
|
432
422
|
|
|
423
|
+
const bootCalls = this._resolvePrecompileBootCalls(fileToFunctionMap);
|
|
424
|
+
|
|
425
|
+
entry += `\n// --- STARTUP ---\n`;
|
|
426
|
+
entry += `window.addEventListener('DOMContentLoaded', async () => {\n`;
|
|
427
|
+
for (const bc of bootCalls) {
|
|
428
|
+
entry += ` await ${bc.functionName}(); // boot: ${bc.file}\n`;
|
|
429
|
+
}
|
|
430
|
+
entry += ` route(window.location.pathname);\n`;
|
|
431
|
+
entry += `});\n`;
|
|
432
|
+
|
|
433
|
+
this._bootCalls = bootCalls;
|
|
434
|
+
|
|
433
435
|
return entry;
|
|
434
436
|
}
|
|
435
437
|
|
|
438
|
+
_resolvePrecompileBootCalls(fileToFunctionMap) {
|
|
439
|
+
const bootCalls = [];
|
|
440
|
+
const pc = this.precompile;
|
|
441
|
+
|
|
442
|
+
for (const jfEntry of pc.juxfiles) {
|
|
443
|
+
const jf = typeof jfEntry === 'string'
|
|
444
|
+
? { file: jfEntry }
|
|
445
|
+
: jfEntry;
|
|
446
|
+
|
|
447
|
+
const fullPath = path.resolve(this.srcDir, jf.file);
|
|
448
|
+
const relativePath = path.relative(this.srcDir, fullPath).replace(/\\/g, '/');
|
|
449
|
+
|
|
450
|
+
const functionName = fileToFunctionMap.get(relativePath);
|
|
451
|
+
if (!functionName) {
|
|
452
|
+
console.error(`ā precompile.juxfiles: "${jf.file}" (${relativePath}) was not compiled as a view.`);
|
|
453
|
+
console.error(` It may have exports or be classified as a data/shared module.`);
|
|
454
|
+
console.error(` Available views: ${[...fileToFunctionMap.keys()].join(', ')}`);
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
let containerId = jf.target;
|
|
459
|
+
if (!containerId) {
|
|
460
|
+
const baseName = jf.file
|
|
461
|
+
.replace(/\.jux$/, '')
|
|
462
|
+
.replace(/\/index$/, '')
|
|
463
|
+
.replace(/[\/\\]/g, '-')
|
|
464
|
+
.replace(/[^a-z0-9-]/gi, '')
|
|
465
|
+
.replace(/-+/g, '-')
|
|
466
|
+
.replace(/^-|-$/g, '')
|
|
467
|
+
.toLowerCase();
|
|
468
|
+
containerId = 'app-' + (baseName || 'boot');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
bootCalls.push({ file: relativePath, functionName, containerId });
|
|
472
|
+
console.log(` šļø Boot: ${jf.file} ā ${functionName} ā <div id="${containerId}">`);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return bootCalls;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
_generateRouter(routeToFunctionMap) {
|
|
479
|
+
let router = `\n// --- ROUTER ---\n`;
|
|
480
|
+
router += `const routes = {\n`;
|
|
481
|
+
|
|
482
|
+
routeToFunctionMap.forEach((functionName, route) => {
|
|
483
|
+
router += ` '${route}': ${functionName},\n`;
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
router += `};\n\n`;
|
|
487
|
+
|
|
488
|
+
const routeIndex = [];
|
|
489
|
+
routeToFunctionMap.forEach((functionName, routePath) => {
|
|
490
|
+
const name = routePath === '/'
|
|
491
|
+
? 'Home'
|
|
492
|
+
: routePath.split('/').filter(Boolean).pop()
|
|
493
|
+
.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' ');
|
|
494
|
+
const sourceFile = this._sourceSnapshot
|
|
495
|
+
? Object.values(this._sourceSnapshot).find(s => s.functionName === functionName)
|
|
496
|
+
: null;
|
|
497
|
+
routeIndex.push({ path: routePath, name, file: sourceFile?.file || '' });
|
|
498
|
+
});
|
|
499
|
+
router += `window.__juxRouteIndex = ${JSON.stringify(routeIndex)};\n\n`;
|
|
500
|
+
|
|
501
|
+
router += `function route(path) {
|
|
502
|
+
const renderFn = routes[path] || routes['/'];
|
|
503
|
+
if (renderFn) {
|
|
504
|
+
const app = document.getElementById('app');
|
|
505
|
+
if (app) app.innerHTML = '';
|
|
506
|
+
|
|
507
|
+
if (typeof renderFn === 'function') {
|
|
508
|
+
renderFn();
|
|
509
|
+
}
|
|
510
|
+
} else {
|
|
511
|
+
const app = document.getElementById('app');
|
|
512
|
+
if (app) app.innerHTML = '<h1>404 - Page Not Found</h1>';
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
window.addEventListener('popstate', () => route(window.location.pathname));
|
|
517
|
+
|
|
518
|
+
document.addEventListener('click', (e) => {
|
|
519
|
+
const link = e.target.closest('a[href]');
|
|
520
|
+
if (link) {
|
|
521
|
+
const href = link.getAttribute('href');
|
|
522
|
+
if (href.startsWith('/') && !href.startsWith('//')) {
|
|
523
|
+
e.preventDefault();
|
|
524
|
+
window.history.pushState({}, '', href);
|
|
525
|
+
route(href);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
window.navigateTo = (path) => {
|
|
531
|
+
window.history.pushState({}, '', path);
|
|
532
|
+
route(path);
|
|
533
|
+
};
|
|
534
|
+
`;
|
|
535
|
+
|
|
536
|
+
return router;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
_generateIndexHtml() {
|
|
540
|
+
const bootCalls = this._bootCalls || [];
|
|
541
|
+
|
|
542
|
+
let bodyContainers = '';
|
|
543
|
+
for (const bc of bootCalls) {
|
|
544
|
+
bodyContainers += ` <div id="${bc.containerId}"></div>\n`;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const html = `<!DOCTYPE html>
|
|
548
|
+
<html lang="en">
|
|
549
|
+
<head>
|
|
550
|
+
<meta charset="UTF-8">
|
|
551
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
552
|
+
<title>JUX Application</title>
|
|
553
|
+
${generateErrorCollector({
|
|
554
|
+
enabled: process.env.NODE_ENV !== 'production',
|
|
555
|
+
maxErrors: 50
|
|
556
|
+
})}
|
|
557
|
+
<script type="module" src="/bundle.js"></script>
|
|
558
|
+
</head>
|
|
559
|
+
<body>
|
|
560
|
+
${bodyContainers} <div id="app"></div>
|
|
561
|
+
</body>
|
|
562
|
+
</html>`;
|
|
563
|
+
|
|
564
|
+
return html;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
copyPublicFolder() {
|
|
568
|
+
const publicSrc = this.paths?.public
|
|
569
|
+
? this.paths.public
|
|
570
|
+
: path.resolve(process.cwd(), this.publicDir);
|
|
571
|
+
|
|
572
|
+
if (!fs.existsSync(publicSrc)) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
console.log('š¦ Copying public assets...');
|
|
577
|
+
|
|
578
|
+
try {
|
|
579
|
+
this._copyDirRecursive(publicSrc, this.distDir, 0);
|
|
580
|
+
console.log('ā
Public assets copied');
|
|
581
|
+
} catch (err) {
|
|
582
|
+
console.warn('ā ļø Error copying public folder:', err.message);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
_copyDirRecursive(src, dest, depth = 0) {
|
|
587
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
588
|
+
|
|
589
|
+
entries.forEach(entry => {
|
|
590
|
+
if (entry.name.startsWith('.')) return;
|
|
591
|
+
|
|
592
|
+
const srcPath = path.join(src, entry.name);
|
|
593
|
+
const destPath = path.join(dest, entry.name);
|
|
594
|
+
|
|
595
|
+
if (entry.isDirectory()) {
|
|
596
|
+
if (!fs.existsSync(destPath)) {
|
|
597
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
598
|
+
}
|
|
599
|
+
this._copyDirRecursive(srcPath, destPath, depth + 1);
|
|
600
|
+
} else {
|
|
601
|
+
fs.copyFileSync(srcPath, destPath);
|
|
602
|
+
|
|
603
|
+
if (depth === 0) {
|
|
604
|
+
const ext = path.extname(entry.name);
|
|
605
|
+
const icon = this._getFileIcon(ext);
|
|
606
|
+
console.log(` ${icon} ${entry.name}`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
_getFileIcon(ext) {
|
|
613
|
+
const icons = {
|
|
614
|
+
'.html': 'š', '.css': 'šØ', '.js': 'š', '.json': 'š',
|
|
615
|
+
'.png': 'š¼ļø', '.jpg': 'š¼ļø', '.jpeg': 'š¼ļø', '.gif': 'š¼ļø',
|
|
616
|
+
'.svg': 'šØ', '.ico': 'š',
|
|
617
|
+
'.woff': 'š¤', '.woff2': 'š¤', '.ttf': 'š¤', '.eot': 'š¤'
|
|
618
|
+
};
|
|
619
|
+
return icons[ext.toLowerCase()] || 'š¦';
|
|
620
|
+
}
|
|
621
|
+
|
|
436
622
|
_stripImportsAndExports(code) {
|
|
437
623
|
try {
|
|
438
624
|
const ast = acorn.parse(code, {
|
|
@@ -455,31 +641,89 @@ export class JuxCompiler {
|
|
|
455
641
|
let result = code;
|
|
456
642
|
let offset = 0;
|
|
457
643
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
644
|
+
for (const node of nodesToRemove) {
|
|
645
|
+
const start = node.start - offset;
|
|
646
|
+
const end = node.end - offset;
|
|
461
647
|
|
|
462
648
|
if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
|
463
|
-
const
|
|
464
|
-
result = result.substring(0, start) + result.substring(
|
|
465
|
-
offset += (
|
|
649
|
+
const declStart = node.declaration.start - offset;
|
|
650
|
+
result = result.substring(0, start) + result.substring(declStart);
|
|
651
|
+
offset += (declStart - start);
|
|
466
652
|
} else if (node.type === 'ExportDefaultDeclaration') {
|
|
467
|
-
const
|
|
468
|
-
if (
|
|
469
|
-
result = result.substring(0, start) + result.substring(start +
|
|
470
|
-
offset +=
|
|
653
|
+
const match = result.substring(start, end).match(/export\s+default\s+/);
|
|
654
|
+
if (match) {
|
|
655
|
+
result = result.substring(0, start) + result.substring(start + match[0].length);
|
|
656
|
+
offset += match[0].length;
|
|
471
657
|
}
|
|
472
658
|
} else {
|
|
473
659
|
result = result.substring(0, start) + result.substring(end);
|
|
474
660
|
offset += (end - start);
|
|
475
661
|
}
|
|
476
|
-
}
|
|
662
|
+
}
|
|
477
663
|
|
|
478
664
|
return result.trim();
|
|
479
665
|
} catch (parseError) {
|
|
480
|
-
|
|
481
|
-
|
|
666
|
+
return code
|
|
667
|
+
.replace(/^\s*import\s+.*?from\s+['"][^'"]+['"][\s;]*$/gm, '')
|
|
668
|
+
.replace(/^\s*import\s*\{[\s\S]*?\}\s*from\s*['"][^'"]+['"][\s;]*/gm, '')
|
|
669
|
+
.replace(/^\s*import\s+\w+\s+from\s+['"][^'"]+['"][\s;]*$/gm, '')
|
|
670
|
+
.replace(/^\s*import\s*;?\s*$/gm, '')
|
|
671
|
+
.trim();
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
_validatePrecompile() {
|
|
676
|
+
const errors = [];
|
|
677
|
+
const pc = this.precompile;
|
|
678
|
+
|
|
679
|
+
for (const jfEntry of pc.juxfiles) {
|
|
680
|
+
const jf = typeof jfEntry === 'string' ? jfEntry : jfEntry.file;
|
|
681
|
+
const fullPath = path.resolve(this.srcDir, jf);
|
|
682
|
+
if (!fs.existsSync(fullPath)) {
|
|
683
|
+
errors.push({ message: `precompile.juxfiles: "${jf}" not found at ${fullPath}` });
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (errors.length > 0) {
|
|
688
|
+
console.error('\nā Precompile validation failed:\n');
|
|
689
|
+
errors.forEach(e => console.error(` ${e.message}`));
|
|
690
|
+
} else if (pc.juxfiles.length) {
|
|
691
|
+
console.log(`\nš§ Precompile:`);
|
|
692
|
+
console.log(` š juxfiles: ${pc.juxfiles.map(j => typeof j === 'string' ? j : j.file).join(', ')}`);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return { valid: errors.length === 0, errors };
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
reportValidationIssues() {
|
|
699
|
+
const errors = [];
|
|
700
|
+
const warnings = [];
|
|
701
|
+
|
|
702
|
+
if (this._validationIssues && this._validationIssues.length > 0) {
|
|
703
|
+
this._validationIssues.forEach(issue => {
|
|
704
|
+
if (issue.type === 'error') {
|
|
705
|
+
errors.push(issue);
|
|
706
|
+
} else if (issue.type === 'warning') {
|
|
707
|
+
warnings.push(issue);
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (warnings.length > 0) {
|
|
713
|
+
console.log('\nā ļø Warnings:\n');
|
|
714
|
+
warnings.forEach(w => {
|
|
715
|
+
console.log(` ${w.view}:${w.line} - ${w.message}`);
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (errors.length > 0) {
|
|
720
|
+
console.log('\nā Errors:\n');
|
|
721
|
+
errors.forEach(e => {
|
|
722
|
+
console.log(` ${e.view}:${e.line} - ${e.message}`);
|
|
723
|
+
});
|
|
482
724
|
}
|
|
725
|
+
|
|
726
|
+
return { isValid: errors.length === 0, errors, warnings };
|
|
483
727
|
}
|
|
484
728
|
|
|
485
729
|
async build() {
|
|
@@ -502,6 +746,12 @@ export class JuxCompiler {
|
|
|
502
746
|
|
|
503
747
|
this.copyPublicFolder();
|
|
504
748
|
|
|
749
|
+
const precompileResult = this._validatePrecompile();
|
|
750
|
+
if (!precompileResult.valid) {
|
|
751
|
+
console.log('š BUILD FAILED ā precompile errors\n');
|
|
752
|
+
return { success: false, errors: precompileResult.errors, warnings: [] };
|
|
753
|
+
}
|
|
754
|
+
|
|
505
755
|
const { views, dataModules, sharedModules } = this.scanFiles();
|
|
506
756
|
console.log(`š Found ${views.length} views, ${sharedModules.length} shared, ${dataModules.length} data`);
|
|
507
757
|
|
|
@@ -531,271 +781,63 @@ export class JuxCompiler {
|
|
|
531
781
|
platform: 'browser',
|
|
532
782
|
target: 'esnext',
|
|
533
783
|
sourcemap: true,
|
|
534
|
-
|
|
535
|
-
loader: {
|
|
536
|
-
'.jux': 'js',
|
|
537
|
-
'.css': 'empty'
|
|
538
|
-
},
|
|
539
|
-
|
|
784
|
+
loader: { '.jux': 'js', '.css': 'empty' },
|
|
540
785
|
plugins: [{
|
|
541
786
|
name: 'juxscript-resolver',
|
|
542
787
|
setup: (build) => {
|
|
543
788
|
build.onResolve({ filter: /^juxscript$/ }, () => ({
|
|
544
789
|
path: juxscriptPath
|
|
545
790
|
}));
|
|
546
|
-
|
|
547
791
|
build.onResolve({ filter: /^axios$/ }, () => {
|
|
548
792
|
const projectRoot = process.cwd();
|
|
549
793
|
const axiosPath = path.resolve(projectRoot, 'node_modules/axios/dist/esm/axios.js');
|
|
550
|
-
|
|
551
794
|
if (fs.existsSync(axiosPath)) {
|
|
552
795
|
console.log('ā
Found axios at:', axiosPath);
|
|
553
796
|
return { path: axiosPath };
|
|
554
797
|
}
|
|
555
|
-
|
|
556
798
|
console.error('ā axios not found in project node_modules');
|
|
557
799
|
return null;
|
|
558
800
|
});
|
|
559
|
-
|
|
560
801
|
build.onResolve({ filter: /\.jux$/ }, (args) => {
|
|
561
802
|
console.log(`š Resolving: ${args.path} from ${args.importer}`);
|
|
562
|
-
|
|
563
|
-
if (path.isAbsolute(args.path)) {
|
|
564
|
-
return { path: args.path };
|
|
565
|
-
}
|
|
566
|
-
|
|
803
|
+
if (path.isAbsolute(args.path)) return { path: args.path };
|
|
567
804
|
if (args.path.startsWith('.')) {
|
|
568
805
|
const importer = args.importer || entryPath;
|
|
569
806
|
const importerDir = path.dirname(importer);
|
|
570
807
|
const resolvedPath = path.resolve(importerDir, args.path);
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
return { path: resolvedPath };
|
|
574
|
-
} else {
|
|
575
|
-
console.error(`ā Could not resolve ${args.path} from ${importer}`);
|
|
576
|
-
}
|
|
808
|
+
if (fs.existsSync(resolvedPath)) return { path: resolvedPath };
|
|
809
|
+
else console.error(`ā Could not resolve ${args.path} from ${importer}`);
|
|
577
810
|
}
|
|
578
|
-
|
|
579
811
|
return null;
|
|
580
812
|
});
|
|
581
813
|
}
|
|
582
814
|
}],
|
|
583
|
-
|
|
584
815
|
mainFields: ['browser', 'module', 'main'],
|
|
585
816
|
conditions: ['browser', 'import', 'module', 'default'],
|
|
586
|
-
|
|
587
817
|
define: {
|
|
588
818
|
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
|
|
589
819
|
'global': 'globalThis',
|
|
590
820
|
'process.env': JSON.stringify({})
|
|
591
821
|
},
|
|
592
|
-
|
|
593
822
|
minify: false,
|
|
594
823
|
treeShaking: true,
|
|
595
824
|
metafile: true,
|
|
596
825
|
});
|
|
597
826
|
|
|
598
827
|
console.log('\nā
Build complete');
|
|
599
|
-
|
|
600
828
|
const bundlePath = path.join(this.distDir, 'bundle.js');
|
|
601
829
|
const bundleStats = fs.statSync(bundlePath);
|
|
602
|
-
|
|
603
|
-
console.log(`š¦ Bundle size: ${bundleSizeKB} KB`);
|
|
830
|
+
console.log(`š¦ Bundle size: ${(bundleStats.size / 1024).toFixed(2)} KB`);
|
|
604
831
|
|
|
605
832
|
} catch (err) {
|
|
606
833
|
console.error('ā esbuild failed:', err);
|
|
607
834
|
return { success: false, errors: [{ message: err.message }], warnings: [] };
|
|
608
835
|
}
|
|
609
836
|
|
|
610
|
-
const html =
|
|
611
|
-
|
|
612
|
-
<head>
|
|
613
|
-
<meta charset="UTF-8">
|
|
614
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
615
|
-
<title>JUX Application</title>
|
|
616
|
-
${generateErrorCollector({
|
|
617
|
-
enabled: process.env.NODE_ENV !== 'production',
|
|
618
|
-
maxErrors: 50
|
|
619
|
-
})}
|
|
620
|
-
<script type="module" src="/bundle.js"></script>
|
|
621
|
-
</head>
|
|
622
|
-
<body>
|
|
623
|
-
<div id="app"></div>
|
|
624
|
-
</body>
|
|
625
|
-
</html>`;
|
|
626
|
-
|
|
627
|
-
fs.writeFileSync(
|
|
628
|
-
path.join(this.config.distDir, 'index.html'),
|
|
629
|
-
html,
|
|
630
|
-
'utf8'
|
|
631
|
-
);
|
|
837
|
+
const html = this._generateIndexHtml();
|
|
838
|
+
fs.writeFileSync(path.join(this.distDir, 'index.html'), html, 'utf8');
|
|
632
839
|
|
|
633
840
|
console.log('ā
Generated index.html\n');
|
|
634
841
|
return { success: true, errors: [], warnings: validation.warnings };
|
|
635
842
|
}
|
|
636
|
-
|
|
637
|
-
_generateRouter(routeToFunctionMap) {
|
|
638
|
-
let router = `\n// --- ROUTER ---\n`;
|
|
639
|
-
router += `const routes = {\n`;
|
|
640
|
-
|
|
641
|
-
routeToFunctionMap.forEach((functionName, route) => {
|
|
642
|
-
router += ` '${route}': ${functionName},\n`;
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
router += `};\n\n`;
|
|
646
|
-
|
|
647
|
-
// Build route index for jux.routes.all()
|
|
648
|
-
const routeIndex = [];
|
|
649
|
-
routeToFunctionMap.forEach((functionName, routePath) => {
|
|
650
|
-
const name = routePath === '/'
|
|
651
|
-
? 'Home'
|
|
652
|
-
: routePath.split('/').filter(Boolean).pop()
|
|
653
|
-
.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' ');
|
|
654
|
-
// Find matching source file from snapshot
|
|
655
|
-
const sourceFile = this._sourceSnapshot
|
|
656
|
-
? Object.values(this._sourceSnapshot).find(s => s.functionName === functionName)
|
|
657
|
-
: null;
|
|
658
|
-
routeIndex.push({ path: routePath, name, file: sourceFile?.file || '' });
|
|
659
|
-
});
|
|
660
|
-
router += `window.__juxRouteIndex = ${JSON.stringify(routeIndex)};\n\n`;
|
|
661
|
-
|
|
662
|
-
router += `function route(path) {
|
|
663
|
-
const renderFn = routes[path] || routes['/'];
|
|
664
|
-
if (renderFn) {
|
|
665
|
-
const appMain = document.getElementById('appmain-content');
|
|
666
|
-
if (appMain) appMain.innerHTML = '';
|
|
667
|
-
|
|
668
|
-
const app = document.getElementById('app');
|
|
669
|
-
if (app) app.innerHTML = '';
|
|
670
|
-
|
|
671
|
-
if (typeof renderFn === 'function') {
|
|
672
|
-
renderFn();
|
|
673
|
-
}
|
|
674
|
-
} else {
|
|
675
|
-
const app = document.getElementById('app');
|
|
676
|
-
if (app) app.innerHTML = '<h1>404 - Page Not Found</h1>';
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
window.addEventListener('DOMContentLoaded', () => {
|
|
681
|
-
route(window.location.pathname);
|
|
682
|
-
});
|
|
683
|
-
|
|
684
|
-
window.addEventListener('popstate', () => route(window.location.pathname));
|
|
685
|
-
|
|
686
|
-
document.addEventListener('click', (e) => {
|
|
687
|
-
const link = e.target.closest('a[href]');
|
|
688
|
-
if (link) {
|
|
689
|
-
const href = link.getAttribute('href');
|
|
690
|
-
if (href.startsWith('/') && !href.startsWith('//')) {
|
|
691
|
-
e.preventDefault();
|
|
692
|
-
window.history.pushState({}, '', href);
|
|
693
|
-
route(href);
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
});
|
|
697
|
-
|
|
698
|
-
window.navigateTo = (path) => {
|
|
699
|
-
window.history.pushState({}, '', path);
|
|
700
|
-
route(path);
|
|
701
|
-
};
|
|
702
|
-
`;
|
|
703
|
-
|
|
704
|
-
return router;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
reportValidationIssues() {
|
|
708
|
-
const errors = [];
|
|
709
|
-
const warnings = [];
|
|
710
|
-
|
|
711
|
-
if (this._validationIssues && this._validationIssues.length > 0) {
|
|
712
|
-
this._validationIssues.forEach(issue => {
|
|
713
|
-
if (issue.type === 'error') {
|
|
714
|
-
errors.push(issue);
|
|
715
|
-
} else if (issue.type === 'warning') {
|
|
716
|
-
warnings.push(issue);
|
|
717
|
-
}
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
if (warnings.length > 0) {
|
|
722
|
-
console.log('\nā ļø Warnings:\n');
|
|
723
|
-
warnings.forEach(w => {
|
|
724
|
-
console.log(` ${w.view}:${w.line} - ${w.message}`);
|
|
725
|
-
});
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
if (errors.length > 0) {
|
|
729
|
-
console.log('\nā Errors:\n');
|
|
730
|
-
errors.forEach(e => {
|
|
731
|
-
console.log(` ${e.view}:${e.line} - ${e.message}`);
|
|
732
|
-
});
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
return { isValid: errors.length === 0, errors, warnings };
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
copyPublicFolder() {
|
|
739
|
-
const publicSrc = this.paths.public
|
|
740
|
-
? this.paths.public
|
|
741
|
-
: path.resolve(process.cwd(), this.publicDir);
|
|
742
|
-
|
|
743
|
-
if (!fs.existsSync(publicSrc)) {
|
|
744
|
-
return;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
console.log('š¦ Copying public assets...');
|
|
748
|
-
|
|
749
|
-
try {
|
|
750
|
-
this._copyDirRecursive(publicSrc, this.distDir, 0);
|
|
751
|
-
console.log('ā
Public assets copied');
|
|
752
|
-
} catch (err) {
|
|
753
|
-
console.warn('ā ļø Error copying public folder:', err.message);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
_copyDirRecursive(src, dest, depth = 0) {
|
|
758
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
759
|
-
|
|
760
|
-
entries.forEach(entry => {
|
|
761
|
-
if (entry.name.startsWith('.')) return;
|
|
762
|
-
|
|
763
|
-
const srcPath = path.join(src, entry.name);
|
|
764
|
-
const destPath = path.join(dest, entry.name);
|
|
765
|
-
|
|
766
|
-
if (entry.isDirectory()) {
|
|
767
|
-
if (!fs.existsSync(destPath)) {
|
|
768
|
-
fs.mkdirSync(destPath, { recursive: true });
|
|
769
|
-
}
|
|
770
|
-
this._copyDirRecursive(srcPath, destPath, depth + 1);
|
|
771
|
-
} else {
|
|
772
|
-
fs.copyFileSync(srcPath, destPath);
|
|
773
|
-
|
|
774
|
-
if (depth === 0) {
|
|
775
|
-
const ext = path.extname(entry.name);
|
|
776
|
-
const icon = this._getFileIcon(ext);
|
|
777
|
-
console.log(` ${icon} ${entry.name}`);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
});
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
_getFileIcon(ext) {
|
|
784
|
-
const icons = {
|
|
785
|
-
'.html': 'š', '.css': 'šØ', '.js': 'š', '.json': 'š',
|
|
786
|
-
'.png': 'š¼ļø', '.jpg': 'š¼ļø', '.jpeg': 'š¼ļø', '.gif': 'š¼ļø',
|
|
787
|
-
'.svg': 'šØ', '.ico': 'š',
|
|
788
|
-
'.woff': 'š¤', '.woff2': 'š¤', '.ttf': 'š¤', '.eot': 'š¤'
|
|
789
|
-
};
|
|
790
|
-
return icons[ext.toLowerCase()] || 'š¦';
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
_generateNameFromPath(filepath) {
|
|
794
|
-
return filepath
|
|
795
|
-
.replace(/\.jux$/, '')
|
|
796
|
-
.replace(/[\/\\]/g, '_')
|
|
797
|
-
.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
798
|
-
.replace(/_+/g, '_')
|
|
799
|
-
.replace(/^_|_$/g, '');
|
|
800
|
-
}
|
|
801
843
|
}
|
package/machinery/serve.js
CHANGED
|
@@ -29,11 +29,11 @@ function getArgValue(flag, shortFlag, defaultValue) {
|
|
|
29
29
|
const PORT = parseInt(getArgValue('--port', '-p', process.env.PORT || '3000'));
|
|
30
30
|
const WS_PORT = parseInt(getArgValue('--ws-port', '-w', process.env.WS_PORT || String(PORT + 1)));
|
|
31
31
|
|
|
32
|
-
//
|
|
32
|
+
// Where the config is loaded (near the top, after imports)
|
|
33
33
|
const PROJECT_ROOT = process.cwd();
|
|
34
34
|
const JUX_CONFIG_PATH = path.resolve(PROJECT_ROOT, 'juxconfig.js');
|
|
35
|
-
|
|
36
35
|
let rawConfig = {};
|
|
36
|
+
|
|
37
37
|
try {
|
|
38
38
|
const imported = await import(JUX_CONFIG_PATH);
|
|
39
39
|
rawConfig = imported.config || {};
|
|
@@ -70,8 +70,9 @@ if (!fs.existsSync(DIST_DIR) || !fs.existsSync(path.join(DIST_DIR, 'index.html')
|
|
|
70
70
|
const compiler = new JuxCompiler({
|
|
71
71
|
srcDir: SRC_DIR,
|
|
72
72
|
distDir: DIST_DIR,
|
|
73
|
-
publicDir: directories.public,
|
|
74
|
-
paths
|
|
73
|
+
publicDir: directories.public,
|
|
74
|
+
paths,
|
|
75
|
+
precompile
|
|
75
76
|
});
|
|
76
77
|
|
|
77
78
|
try {
|
|
@@ -106,6 +107,11 @@ app.use((req, res, next) => {
|
|
|
106
107
|
app.get('/favicon.ico', (req, res) => res.status(204).end());
|
|
107
108
|
app.get('/favicon.png', (req, res) => res.status(204).end());
|
|
108
109
|
|
|
110
|
+
// Near the top, after config is loaded ā ensure precompile is extracted:
|
|
111
|
+
const precompile = {
|
|
112
|
+
juxfiles: rawConfig.precompile?.juxfiles || [],
|
|
113
|
+
};
|
|
114
|
+
|
|
109
115
|
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
110
116
|
// API ROUTES ā BEFORE static and catch-all
|
|
111
117
|
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
@@ -276,8 +282,9 @@ if (HOT_RELOAD) {
|
|
|
276
282
|
const compiler = new JuxCompiler({
|
|
277
283
|
srcDir: SRC_DIR,
|
|
278
284
|
distDir: DIST_DIR,
|
|
279
|
-
publicDir: directories.public,
|
|
280
|
-
paths
|
|
285
|
+
publicDir: directories.public,
|
|
286
|
+
paths,
|
|
287
|
+
precompile
|
|
281
288
|
});
|
|
282
289
|
|
|
283
290
|
watcher = createWatcher(SRC_DIR, {
|
package/package.json
CHANGED
|
@@ -28,13 +28,14 @@ export function juxSidebar(id, sections, options) {
|
|
|
28
28
|
if (typeof item === 'string') {
|
|
29
29
|
var path = item.startsWith('/') ? item : '/' + item;
|
|
30
30
|
var seg = path.split('/').filter(Boolean).pop() || 'Home';
|
|
31
|
-
return { id: id + '-' +
|
|
31
|
+
return { id: id + '-' + path.replace(/^\//, '').replace(/[^a-z0-9]/gi, '-').toLowerCase() || id + '-home', label: seg.charAt(0).toUpperCase() + seg.slice(1), path: path };
|
|
32
32
|
}
|
|
33
33
|
// Support RouteInfo shape { path, name, file } from jux.routes.all()
|
|
34
34
|
var itemPath = item.path || '';
|
|
35
35
|
var seg = itemPath.split('/').filter(Boolean).pop() || 'home';
|
|
36
|
+
var fullSlug = itemPath.replace(/^\//, '').replace(/[^a-z0-9]/gi, '-').toLowerCase() || 'home';
|
|
36
37
|
return {
|
|
37
|
-
id: item.id || id + '-' +
|
|
38
|
+
id: item.id || id + '-' + fullSlug,
|
|
38
39
|
label: item.label || item.name || seg.charAt(0).toUpperCase() + seg.slice(1),
|
|
39
40
|
path: itemPath,
|
|
40
41
|
icon: item.icon
|
|
@@ -57,11 +58,34 @@ export function juxSidebar(id, sections, options) {
|
|
|
57
58
|
injectStyles();
|
|
58
59
|
var sidebarClass = 'jux-sidebar jux-sidebar--' + _density;
|
|
59
60
|
if (_collapsed) sidebarClass += ' jux-sidebar--collapsed';
|
|
60
|
-
|
|
61
|
+
|
|
62
|
+
// If the target container already has our ID, reuse it instead of nesting
|
|
63
|
+
var existingEl = document.getElementById(id);
|
|
64
|
+
if (_target === id && existingEl) {
|
|
65
|
+
existingEl.className = sidebarClass;
|
|
66
|
+
existingEl.style.width = _collapsed ? _collapsedWidth : _width;
|
|
67
|
+
} else {
|
|
68
|
+
jux.div(id, { class: sidebarClass, style: 'width:' + (_collapsed ? _collapsedWidth : _width), target: _target });
|
|
69
|
+
}
|
|
70
|
+
|
|
61
71
|
jux.div(id + '-header', { class: 'jux-sidebar-header', target: id });
|
|
62
|
-
jux.div(id + '-
|
|
72
|
+
jux.div(id + '-header-link', { class: 'jux-sidebar-header-link', target: id + '-header' });
|
|
73
|
+
jux.div(id + '-logo', { class: 'jux-sidebar-logo', target: id + '-header-link' });
|
|
63
74
|
renderLogo();
|
|
64
|
-
jux.span(id + '-title', { class: 'jux-sidebar-title-text', content: _title, target: id + '-header' });
|
|
75
|
+
jux.span(id + '-title', { class: 'jux-sidebar-title-text', content: _title, target: id + '-header-link' });
|
|
76
|
+
|
|
77
|
+
// Make header link navigate to root
|
|
78
|
+
var headerLinkEl = document.getElementById(id + '-header-link');
|
|
79
|
+
if (headerLinkEl) {
|
|
80
|
+
headerLinkEl.addEventListener('click', function () {
|
|
81
|
+
if (window.navigateTo) {
|
|
82
|
+
window.navigateTo('/');
|
|
83
|
+
} else {
|
|
84
|
+
window.location.href = '/';
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
65
89
|
if (_collapsible) {
|
|
66
90
|
jux.div(id + '-collapse-btn', {
|
|
67
91
|
class: 'jux-sidebar-collapse-btn',
|
|
@@ -116,10 +140,19 @@ export function juxSidebar(id, sections, options) {
|
|
|
116
140
|
}
|
|
117
141
|
|
|
118
142
|
function injectStyles() {
|
|
119
|
-
|
|
143
|
+
var appPadding = {
|
|
144
|
+
dense: '10px',
|
|
145
|
+
normal: '16px',
|
|
146
|
+
inflated: '24px'
|
|
147
|
+
};
|
|
148
|
+
var currentWidth = _collapsed ? _collapsedWidth : _width;
|
|
149
|
+
jux.style('sidebar-styles', '\
|
|
120
150
|
body { margin: 0; }\
|
|
151
|
+
#app { left: '+ currentWidth + '; position: fixed; top: 0; width: calc(100% - ' + currentWidth + '); height: 100vh; padding: ' + appPadding[_density] + '; box-sizing: border-box; transition: left 0.2s ease, width 0.2s ease, padding 0.2s ease; overflow-y: auto; }\
|
|
121
152
|
.jux-sidebar { display:flex; flex-direction:column; min-height:100vh; background:hsl(var(--card)); color:hsl(var(--card-foreground)); border-right:1px solid hsl(var(--border)); font-family:var(--font-sans,system-ui,sans-serif); transition:width 0.2s ease; overflow:hidden; }\
|
|
122
153
|
.jux-sidebar-header { display:flex; align-items:center; gap:8px; font-size:14px; font-weight:600; border-bottom:1px solid hsl(var(--border)); min-height:20px; position:relative; }\
|
|
154
|
+
.jux-sidebar-header-link { display:flex; align-items:center; gap:8px; cursor:pointer; flex:1; min-width:0; text-decoration:none; color:inherit; }\
|
|
155
|
+
.jux-sidebar-header-link:hover { opacity:0.8; }\
|
|
123
156
|
.jux-sidebar-logo { width:20px; height:20px; min-width:20px; border-radius:6px; background:hsl(var(--primary)); display:flex; align-items:center; justify-content:center; color:hsl(var(--primary-foreground)); font-size:11px; font-weight:700; flex-shrink:0; overflow:hidden; }\
|
|
124
157
|
.jux-sidebar-logo-img { width:100%; height:100%; object-fit:cover; border-radius:inherit; }\
|
|
125
158
|
.jux-sidebar-logo svg { width:14px; height:14px; }\
|
|
@@ -146,24 +179,28 @@ export function juxSidebar(id, sections, options) {
|
|
|
146
179
|
.jux-sidebar--collapsed .jux-sidebar-collapse-btn { margin-left:0; width:24px; height:24px; }\
|
|
147
180
|
.jux-sidebar--collapsed .jux-sidebar-collapse-btn svg { width:14px; height:14px; }\
|
|
148
181
|
.jux-sidebar--collapsed .jux-sidebar-logo { margin:0; }\
|
|
182
|
+
.jux-sidebar--collapsed ~ #app, .jux-sidebar--collapsed + #app { left: ' + _collapsedWidth + '; width: calc(100% - ' + _collapsedWidth + '); }\
|
|
149
183
|
\
|
|
150
184
|
.jux-sidebar--dense .jux-sidebar-header { padding:' + DENSITY.dense.headerPad + '; }\
|
|
151
185
|
.jux-sidebar--dense .jux-sidebar-label { padding:' + DENSITY.dense.labelPad + '; }\
|
|
152
186
|
.jux-sidebar--dense .jux-sidebar-footer { padding:' + DENSITY.dense.footerPad + '; }\
|
|
153
187
|
.jux-sidebar--dense .jux-nav-item { padding:' + DENSITY.dense.itemPad + '; margin:' + DENSITY.dense.gap + ' 0; font-size:' + DENSITY.dense.fontSize + '; gap:8px; }\
|
|
154
188
|
.jux-sidebar--dense .jux-nav-item-icon { width:16px; font-size:' + DENSITY.dense.iconSize + '; }\
|
|
189
|
+
.jux-sidebar--dense ~ #app, .jux-sidebar--dense + #app { padding: ' + appPadding.dense + '; }\
|
|
155
190
|
\
|
|
156
191
|
.jux-sidebar--normal .jux-sidebar-header { padding:' + DENSITY.normal.headerPad + '; }\
|
|
157
192
|
.jux-sidebar--normal .jux-sidebar-label { padding:' + DENSITY.normal.labelPad + '; }\
|
|
158
193
|
.jux-sidebar--normal .jux-sidebar-footer { padding:' + DENSITY.normal.footerPad + '; }\
|
|
159
194
|
.jux-sidebar--normal .jux-nav-item { padding:' + DENSITY.normal.itemPad + '; margin:' + DENSITY.normal.gap + ' 0; font-size:' + DENSITY.normal.fontSize + '; gap:10px; }\
|
|
160
195
|
.jux-sidebar--normal .jux-nav-item-icon { width:18px; font-size:' + DENSITY.normal.iconSize + '; }\
|
|
196
|
+
.jux-sidebar--normal ~ #app, .jux-sidebar--normal + #app { padding: ' + appPadding.normal + '; }\
|
|
161
197
|
\
|
|
162
198
|
.jux-sidebar--inflated .jux-sidebar-header { padding:' + DENSITY.inflated.headerPad + '; }\
|
|
163
199
|
.jux-sidebar--inflated .jux-sidebar-label { padding:' + DENSITY.inflated.labelPad + '; }\
|
|
164
200
|
.jux-sidebar--inflated .jux-sidebar-footer { padding:' + DENSITY.inflated.footerPad + '; }\
|
|
165
201
|
.jux-sidebar--inflated .jux-nav-item { padding:' + DENSITY.inflated.itemPad + '; margin:' + DENSITY.inflated.gap + ' 0; font-size:' + DENSITY.inflated.fontSize + '; gap:12px; }\
|
|
166
202
|
.jux-sidebar--inflated .jux-nav-item-icon { width:20px; font-size:' + DENSITY.inflated.iconSize + '; }\
|
|
203
|
+
.jux-sidebar--inflated ~ #app, .jux-sidebar--inflated + #app { padding: ' + appPadding.inflated + '; }\
|
|
167
204
|
');
|
|
168
205
|
}
|
|
169
206
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jux } from 'juxscript';
|
|
2
2
|
import { juxSidebar } from './index.jux';
|
|
3
3
|
const allRoutes = jux.routes.all();
|
|
4
|
-
|
|
4
|
+
// ['/route/path', '/another/route']
|
|
5
5
|
juxSidebar('my-sidebar', allRoutes,
|
|
6
6
|
{ title: 'Simple App', logo: 'J', footer: 'v1.0', density: 'inflated', collapsible: true });
|
|
7
7
|
|