@zenithbuild/core 0.4.1 → 0.4.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.
@@ -244,16 +244,44 @@ export async function dev(options: DevOptions = {}): Promise<void> {
244
244
  }
245
245
 
246
246
  function findPageForRoute(route: string, pagesDir: string): string | null {
247
+ // 1. Try exact match first (e.g., /about -> about.zen)
247
248
  const exactPath = path.join(pagesDir, route === '/' ? 'index.zen' : `${route.slice(1)}.zen`)
248
249
  if (fs.existsSync(exactPath)) return exactPath
250
+
251
+ // 2. Try index.zen in directory (e.g., /about -> about/index.zen)
249
252
  const indexPath = path.join(pagesDir, route === '/' ? 'index.zen' : `${route.slice(1)}/index.zen`)
250
253
  if (fs.existsSync(indexPath)) return indexPath
254
+
255
+ // 3. Try dynamic routes [slug].zen, [...slug].zen
256
+ // Walk up the path looking for dynamic segments
257
+ const segments = route === '/' ? [] : route.slice(1).split('/').filter(Boolean)
258
+
259
+ // Try matching with dynamic [slug].zen at each level
260
+ for (let i = segments.length - 1; i >= 0; i--) {
261
+ const staticPart = segments.slice(0, i).join('/')
262
+ const baseDir = staticPart ? path.join(pagesDir, staticPart) : pagesDir
263
+
264
+ // Check for [slug].zen (single segment catch)
265
+ const singleDynamicPath = path.join(baseDir, '[slug].zen')
266
+ if (fs.existsSync(singleDynamicPath)) return singleDynamicPath
267
+
268
+ // Check for [...slug].zen (catch-all)
269
+ const catchAllPath = path.join(baseDir, '[...slug].zen')
270
+ if (fs.existsSync(catchAllPath)) return catchAllPath
271
+ }
272
+
273
+ // 4. Check for catch-all at root
274
+ const rootCatchAll = path.join(pagesDir, '[...slug].zen')
275
+ if (fs.existsSync(rootCatchAll)) return rootCatchAll
276
+
251
277
  return null
252
278
  }
253
279
 
254
280
  function generateDevHTML(page: CompiledPage, contentData: any = {}): string {
255
281
  const runtimeTag = `<script src="/runtime.js"></script>`
256
- const contentTag = `<script>window.__ZENITH_CONTENT__ = ${JSON.stringify(contentData)};</script>`
282
+ // Escape </script> sequences in JSON content to prevent breaking the script tag
283
+ const contentJson = JSON.stringify(contentData).replace(/<\//g, '<\\/')
284
+ const contentTag = `<script>window.__ZENITH_CONTENT__ = ${contentJson};</script>`
257
285
  const scriptTag = `<script>\n${page.script}\n</script>`
258
286
  const allScripts = `${runtimeTag}\n${contentTag}\n${scriptTag}`
259
287
  return page.html.includes('</body>')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenithbuild/core",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Core library for the Zenith framework",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -506,6 +506,120 @@ export function generateBundleJS(): string {
506
506
  return new ZenCollection(data);
507
507
  }
508
508
 
509
+ // ============================================
510
+ // useZenOrder - Documentation ordering & navigation
511
+ // ============================================
512
+
513
+ function slugify(text) {
514
+ return String(text || '')
515
+ .toLowerCase()
516
+ .replace(/[^\\w\\s-]/g, '')
517
+ .replace(/\\s+/g, '-')
518
+ .replace(/-+/g, '-')
519
+ .trim();
520
+ }
521
+
522
+ function getDocSlug(doc) {
523
+ const slugOrId = String(doc.slug || doc.id || '');
524
+ const parts = slugOrId.split('/');
525
+ const filename = parts[parts.length - 1];
526
+ return filename ? slugify(filename) : slugify(doc.title || 'untitled');
527
+ }
528
+
529
+ function processRawSections(rawSections) {
530
+ const sections = (rawSections || []).map(function(rawSection) {
531
+ const sectionSlug = slugify(rawSection.title || rawSection.id || 'section');
532
+ const items = (rawSection.items || []).map(function(item) {
533
+ return Object.assign({}, item, {
534
+ slug: getDocSlug(item),
535
+ sectionSlug: sectionSlug,
536
+ isIntro: item.intro === true || (item.tags && item.tags.includes && item.tags.includes('intro'))
537
+ });
538
+ });
539
+
540
+ // Sort items: intro first, then order, then alphabetical
541
+ items.sort(function(a, b) {
542
+ if (a.isIntro && !b.isIntro) return -1;
543
+ if (!a.isIntro && b.isIntro) return 1;
544
+ if (a.order !== undefined && b.order !== undefined) return a.order - b.order;
545
+ if (a.order !== undefined) return -1;
546
+ if (b.order !== undefined) return 1;
547
+ return (a.title || '').localeCompare(b.title || '');
548
+ });
549
+
550
+ return {
551
+ id: rawSection.id || sectionSlug,
552
+ title: rawSection.title || 'Untitled',
553
+ slug: sectionSlug,
554
+ order: rawSection.order !== undefined ? rawSection.order : (rawSection.meta && rawSection.meta.order),
555
+ hasIntro: items.some(function(i) { return i.isIntro; }),
556
+ items: items
557
+ };
558
+ });
559
+
560
+ // Sort sections: order → hasIntro → alphabetical
561
+ sections.sort(function(a, b) {
562
+ if (a.order !== undefined && b.order !== undefined) return a.order - b.order;
563
+ if (a.order !== undefined) return -1;
564
+ if (b.order !== undefined) return 1;
565
+ if (a.hasIntro && !b.hasIntro) return -1;
566
+ if (!a.hasIntro && b.hasIntro) return 1;
567
+ return a.title.localeCompare(b.title);
568
+ });
569
+
570
+ return sections;
571
+ }
572
+
573
+ function createZenOrder(rawSections) {
574
+ const sections = processRawSections(rawSections);
575
+
576
+ return {
577
+ sections: sections,
578
+ selectedSection: sections[0] || null,
579
+ selectedDoc: sections[0] && sections[0].items[0] || null,
580
+
581
+ getSectionBySlug: function(sectionSlug) {
582
+ return sections.find(function(s) { return s.slug === sectionSlug; }) || null;
583
+ },
584
+
585
+ getDocBySlug: function(sectionSlug, docSlug) {
586
+ var section = sections.find(function(s) { return s.slug === sectionSlug; });
587
+ if (!section) return null;
588
+ return section.items.find(function(d) { return d.slug === docSlug; }) || null;
589
+ },
590
+
591
+ getNextDoc: function(currentDoc) {
592
+ if (!currentDoc) return null;
593
+ var currentSection = sections.find(function(s) { return s.slug === currentDoc.sectionSlug; });
594
+ if (!currentSection) return null;
595
+ var idx = currentSection.items.findIndex(function(d) { return d.slug === currentDoc.slug; });
596
+ if (idx < currentSection.items.length - 1) return currentSection.items[idx + 1];
597
+ var secIdx = sections.findIndex(function(s) { return s.slug === currentSection.slug; });
598
+ if (secIdx < sections.length - 1) return sections[secIdx + 1].items[0] || null;
599
+ return null;
600
+ },
601
+
602
+ getPrevDoc: function(currentDoc) {
603
+ if (!currentDoc) return null;
604
+ var currentSection = sections.find(function(s) { return s.slug === currentDoc.sectionSlug; });
605
+ if (!currentSection) return null;
606
+ var idx = currentSection.items.findIndex(function(d) { return d.slug === currentDoc.slug; });
607
+ if (idx > 0) return currentSection.items[idx - 1];
608
+ var secIdx = sections.findIndex(function(s) { return s.slug === currentSection.slug; });
609
+ if (secIdx > 0) {
610
+ var prev = sections[secIdx - 1];
611
+ return prev.items[prev.items.length - 1] || null;
612
+ }
613
+ return null;
614
+ },
615
+
616
+ buildDocUrl: function(sectionSlug, docSlug) {
617
+ if (!docSlug || docSlug === 'index') return '/documentation/' + sectionSlug;
618
+ return '/documentation/' + sectionSlug + '/' + docSlug;
619
+ }
620
+ };
621
+ }
622
+
509
623
  // Virtual DOM Helper for JSX-style expressions
510
624
  function h(tag, props, children) {
511
625
  const el = document.createElement(tag);
@@ -573,6 +687,10 @@ export function generateBundleJS(): string {
573
687
  // zenith:content
574
688
  defineSchema: defineSchema,
575
689
  zenCollection: zenCollection,
690
+ // useZenOrder hook
691
+ createZenOrder: createZenOrder,
692
+ processRawSections: processRawSections,
693
+ slugify: slugify,
576
694
  // Virtual DOM helper for JSX
577
695
  h: h,
578
696
  // Lifecycle
@@ -610,6 +728,11 @@ export function generateBundleJS(): string {
610
728
  global.onMount = zenOnMount;
611
729
  global.onUnmount = zenOnUnmount;
612
730
 
731
+ // useZenOrder hook exports
732
+ global.createZenOrder = createZenOrder;
733
+ global.processRawSections = processRawSections;
734
+ global.slugify = slugify;
735
+
613
736
  // ============================================
614
737
  // HMR Client (Development Only)
615
738
  // ============================================