diff --git a/lib/web/doctree/meta/model_site.v b/lib/web/doctree/meta/model_site.v index 90774afe..590c1125 100644 --- a/lib/web/doctree/meta/model_site.v +++ b/lib/web/doctree/meta/model_site.v @@ -29,18 +29,26 @@ pub fn (mut s Site) sidebar() SideBar { mut uncategorized_pages := []Page{} // Group pages by category + eprintln('DEBUG: Grouping ${s.pages.len} pages into categories') for page in s.pages { if page.category_id == 0 { // Page at root level (no category) uncategorized_pages << page + eprintln(' Page "${page.src}": UNCATEGORIZED') } else { // Page belongs to a category if page.category_id !in category_pages { category_pages[page.category_id] = []Page{} } category_pages[page.category_id] << page + if page.category_id < s.categories.len { + eprintln(' Page "${page.src}": category_id=${page.category_id} -> "${s.categories[page.category_id].path}"') + } else { + eprintln(' Page "${page.src}": category_id=${page.category_id} -> INVALID INDEX!') + } } } + eprintln('DEBUG: Grouped into ${category_pages.len} categories + ${uncategorized_pages.len} uncategorized') // Sort pages within each category by their order in the pages array for category_id in category_pages.keys() { @@ -54,62 +62,111 @@ pub fn (mut s Site) sidebar() SideBar { // Build nested category structure from path // ============================================================ mut category_tree := map[string]&NavCat{} + mut parent_map := map[string]string{} // Map of path -> parent_path - // PASS 1: Create all category nodes (even empty intermediate ones) + // PASS 1: Create ALL category nodes first + // Collect all paths first, then sort by depth (shallow first) + mut all_paths := []string{} for i, category in s.categories { - category_id := i + 1 // categories are 1-indexed - - // Split path into parts (e.g., "Getting Started/Advanced/Deep" -> ["Getting Started", "Advanced", "Deep"]) path_parts := if category.path.contains('/') { category.path.split('/') } else { [category.path] } - // Create all nodes in the path hierarchy mut current_path := '' - for part_idx, part in path_parts { if current_path.len > 0 { current_path += '/' } current_path += part - // Check if this node already exists + // Add this path if not already added if current_path !in category_tree { - // Create new category node - mut new_cat := &NavCat{ - label: part - collapsible: category.collapsible - collapsed: category.collapsed - items: []NavItem{} - } - category_tree[current_path] = new_cat - - // If this is not the root of the path, add it to its parent - if part_idx > 0 { - parent_path := path_parts[0..part_idx].join('/') - if parent_path in category_tree { - mut parent_cat := category_tree[parent_path] - parent_cat.items << new_cat - } - } + all_paths << current_path } } } - // PASS 2: Add pages to their designated categories + // Sort paths by depth (number of '/') so we create parents before children + all_paths.sort(a.count('/') < b.count('/')) + + // Now create all nodes in order of depth + for path in all_paths { + if path !in category_tree { + path_parts := path.split('/') + part := path_parts[path_parts.len - 1] + + // Find the category with this path to get collapsible/collapsed settings + mut collapsible := true + mut collapsed := false + for category in s.categories { + if category.path == path { + collapsible = category.collapsible + collapsed = category.collapsed + break + } + } + + // Create new category node + mut new_cat := &NavCat{ + label: part + collapsible: collapsible + collapsed: collapsed + items: []NavItem{} + } + category_tree[path] = new_cat + + // Record parent for later linking + if path.contains('/') { + last_slash := path.last_index('/') or { 0 } + parent_path := path[0..last_slash] + parent_map[path] = parent_path + } + } + } + + // PASS 2: Link all parent-child relationships + // Process these in order of depth to ensure parents are linked first + mut sorted_paths := parent_map.keys() + sorted_paths.sort(a.count('/') < b.count('/')) + + for path in sorted_paths { + parent_path := parent_map[path] + if parent_path in category_tree && path in category_tree { + mut parent_cat := category_tree[parent_path] + child_cat := category_tree[path] + + // Only add if not already added + mut already_added := false + for item in parent_cat.items { + if item is NavCat && item.label == child_cat.label { + already_added = true + break + } + } + if !already_added { + parent_cat.items << child_cat + } + } + } + + // PASS 3: Add pages to their designated categories + eprintln('DEBUG PASS 3: Adding pages to categories') for i, category in s.categories { - category_id := i + 1 // categories are 1-indexed + category_id := i // categories are 0-indexed in the page assignment // Skip if no pages in this category if category_id !in category_pages { + eprintln(' Category ${category_id} ("${category.path}"): no pages') continue } // Build the full path for this category full_path := category.path + eprintln(' Category ${category_id} ("${full_path}"): ${category_pages[category_id].len} pages') + // Add pages to this category if full_path in category_tree { mut leaf_cat := category_tree[full_path] @@ -118,6 +175,8 @@ pub fn (mut s Site) sidebar() SideBar { // Convert page src format "collection:name" to path "collection/name" path := page.src.replace(':', '/') + eprintln(' Adding page: ${page.src} -> ${path}') + nav_doc := NavDoc{ path: path label: if page.label.len > 0 { page.label } else { page.title } @@ -125,13 +184,15 @@ pub fn (mut s Site) sidebar() SideBar { leaf_cat.items << nav_doc } } + } else { + eprintln(' ERROR: Category path "${full_path}" not in category_tree!') } } // ============================================================ - // PASS 3: Add root-level categories to sidebar + // PASS 4: Add root-level categories to sidebar // ============================================================ - // Find all root-level categories (those without '/') + // Find all root-level categories (those without '/') and add them once mut added_roots := map[string]bool{} for i, category in s.categories { @@ -149,7 +210,7 @@ pub fn (mut s Site) sidebar() SideBar { } // ============================================================ - // PASS 4: Add uncategorized pages at root level + // PASS 5: Add uncategorized pages at root level // ============================================================ for page in uncategorized_pages { if !page.hide { @@ -165,7 +226,7 @@ pub fn (mut s Site) sidebar() SideBar { } // ============================================================ - // PASS 5: Add standalone links (if needed) + // PASS 6: Add standalone links (if needed) // ============================================================ for link in s.links { nav_link := NavLink{ diff --git a/lib/web/doctree/meta/site_nav_test.v b/lib/web/doctree/meta/site_nav_test.v index 4af04fb7..26ec4c4b 100644 --- a/lib/web/doctree/meta/site_nav_test.v +++ b/lib/web/doctree/meta/site_nav_test.v @@ -27,7 +27,8 @@ const test_heroscript_nav_depth = ' collapsible: true collapsed: false -!!site.page src: "why:intro" +//COLLECTION WILL BE REPEATED, HAS NO INFLUENCE ON NAVIGATION LEVELS +!!site.page src: "mycollection:intro" label: "Why Choose Us" title: "Why Choose Us" description: "Reasons to use this platform" @@ -38,14 +39,14 @@ const test_heroscript_nav_depth = ' description: "Main benefits overview" // ============================================================ -// LEVEL 2: Two-level nested category +// LEVEL 1: Simple top-level category // ============================================================ !!site.page_category path: "Tutorials" collapsible: true collapsed: false -!!site.page src: "tutorials:getting_started" +!!site.page src: "getting_started" label: "Getting Started" title: "Getting Started" description: "Basic tutorial to get started" @@ -63,7 +64,7 @@ const test_heroscript_nav_depth = ' collapsible: true collapsed: false -!!site.page src: "operations:emergency_restart" +!!site.page src: "emergency_restart" label: "Emergency Restart" title: "Emergency Restart" description: "How to emergency restart the system" @@ -79,14 +80,14 @@ const test_heroscript_nav_depth = ' description: "Handle incidents in real-time" // ============================================================ -// LEVEL 2.5: Two-level nested category (Tutorials > Operations) +// LEVEL 2: Two-level nested category (Tutorials > Operations) // ============================================================ !!site.page_category path: "Tutorials/Operations" collapsible: true collapsed: false -!!site.page src: "ops:daily_checks" +!!site.page src: "daily_checks" label: "Daily Checks" title: "Daily Checks" description: "Daily maintenance checklist" @@ -102,7 +103,7 @@ const test_heroscript_nav_depth = ' description: "Backup and restore procedures" // ============================================================ -// LEVEL 1.5: One-to-two level (Tutorials) +// LEVEL 1: One-to-two level (Tutorials) // ============================================================ // Note: This creates a sibling at the Tutorials level (not nested deeper) !!site.page src: "advanced_concepts" @@ -123,7 +124,7 @@ const test_heroscript_nav_depth = ' collapsible: true collapsed: false -!!site.page src: "faq:general" +!!site.page src: "general" label: "General Questions" title: "General Questions" description: "Frequently asked questions" @@ -144,14 +145,14 @@ const test_heroscript_nav_depth = ' description: "Support-related FAQ" // ============================================================ -// LEVEL 3+: Even deeper nesting for comprehensive testing +// LEVEL 4: Four-level nested category (Tutorials > Operations > Database > Optimization) // ============================================================ !!site.page_category path: "Tutorials/Operations/Database/Optimization" collapsible: true collapsed: false -!!site.page src: "database:query_optimization" +!!site.page src: "query_optimization" label: "Query Optimization" title: "Query Optimization" description: "Optimize your database queries" @@ -166,7 +167,7 @@ const test_heroscript_nav_depth = ' collapsible: true collapsed: false -!!site.page src: "db:configuration" +!!site.page src: "configuration" label: "Configuration" title: "Database Configuration" description: "Configure your database" @@ -178,6 +179,289 @@ const test_heroscript_nav_depth = ' ' +fn check(s2 Site) { + mut s := Site{ + doctree_path: '' + config: SiteConfig{ + name: 'nav_depth_test' + title: 'Navigation Depth Test Site' + description: 'Testing multi-level nested navigation' + tagline: 'Deep navigation structures' + favicon: 'img/favicon.png' + image: 'img/tf_graph.png' + copyright: '© 2025 Example Organization' + footer: Footer{ + style: 'dark' + links: [] + } + menu: Menu{ + title: 'Nav Depth Test' + items: [ + MenuItem{ + href: '' + to: '/' + label: 'Home' + position: 'left' + }, + ] + logo_alt: '' + logo_src: '' + logo_src_dark: '' + } + url: '' + base_url: '/' + url_home: '' + meta_title: '' + meta_image: '' + } + pages: [ + Page{ + src: 'mycollection:intro' + label: 'Why Choose Us' + title: 'Why Choose Us' + description: 'Reasons to use this platform' + draft: false + hide_title: false + hide: false + category_id: 0 + }, + Page{ + src: 'mycollection:benefits' + label: 'Key Benefits' + title: 'Key Benefits' + description: 'Main benefits overview' + draft: false + hide_title: false + hide: false + category_id: 0 + }, + Page{ + src: 'mycollection:getting_started' + label: 'Getting Started' + title: 'Getting Started' + description: 'Basic tutorial to get started' + draft: false + hide_title: false + hide: false + category_id: 1 + }, + Page{ + src: 'mycollection:first_steps' + label: 'First Steps' + title: 'First Steps' + description: 'Your first steps with the platform' + draft: false + hide_title: false + hide: false + category_id: 1 + }, + Page{ + src: 'mycollection:emergency_restart' + label: 'Emergency Restart' + title: 'Emergency Restart' + description: 'How to emergency restart the system' + draft: false + hide_title: false + hide: false + category_id: 2 + }, + Page{ + src: 'mycollection:critical_fixes' + label: 'Critical Fixes' + title: 'Critical Fixes' + description: 'Apply critical fixes immediately' + draft: false + hide_title: false + hide: false + category_id: 2 + }, + Page{ + src: 'mycollection:incident_response' + label: 'Incident Response' + title: 'Incident Response' + description: 'Handle incidents in real-time' + draft: false + hide_title: false + hide: false + category_id: 2 + }, + Page{ + src: 'mycollection:daily_checks' + label: 'Daily Checks' + title: 'Daily Checks' + description: 'Daily maintenance checklist' + draft: false + hide_title: false + hide: false + category_id: 3 + }, + Page{ + src: 'mycollection:monitoring' + label: 'Monitoring' + title: 'Monitoring' + description: 'System monitoring procedures' + draft: false + hide_title: false + hide: false + category_id: 3 + }, + Page{ + src: 'mycollection:backups' + label: 'Backups' + title: 'Backups' + description: 'Backup and restore procedures' + draft: false + hide_title: false + hide: false + category_id: 3 + }, + Page{ + src: 'mycollection:advanced_concepts' + label: 'Advanced Concepts' + title: 'Advanced Concepts' + description: 'Deep dive into advanced concepts' + draft: false + hide_title: false + hide: false + category_id: 3 + }, + Page{ + src: 'mycollection:troubleshooting' + label: 'Troubleshooting' + title: 'Troubleshooting' + description: 'Troubleshooting guide' + draft: false + hide_title: false + hide: false + category_id: 3 + }, + Page{ + src: 'mycollection:general' + label: 'General Questions' + title: 'General Questions' + description: 'Frequently asked questions' + draft: false + hide_title: false + hide: false + category_id: 4 + }, + Page{ + src: 'mycollection:pricing_questions' + label: 'Pricing' + title: 'Pricing Questions' + description: 'Questions about pricing' + draft: false + hide_title: false + hide: false + category_id: 4 + }, + Page{ + src: 'mycollection:technical_faq' + label: 'Technical FAQ' + title: 'Technical FAQ' + description: 'Technical frequently asked questions' + draft: false + hide_title: false + hide: false + category_id: 4 + }, + Page{ + src: 'mycollection:support_faq' + label: 'Support' + title: 'Support FAQ' + description: 'Support-related FAQ' + draft: false + hide_title: false + hide: false + category_id: 4 + }, + Page{ + src: 'mycollection:query_optimization' + label: 'Query Optimization' + title: 'Query Optimization' + description: 'Optimize your database queries' + draft: false + hide_title: false + hide: false + category_id: 5 + }, + Page{ + src: 'mycollection:indexing_strategy' + label: 'Indexing Strategy' + title: 'Indexing Strategy' + description: 'Effective indexing strategies' + draft: false + hide_title: false + hide: false + category_id: 5 + }, + Page{ + src: 'mycollection:configuration' + label: 'Configuration' + title: 'Database Configuration' + description: 'Configure your database' + draft: false + hide_title: false + hide: false + category_id: 6 + }, + Page{ + src: 'mycollection:replication' + label: 'Replication' + title: 'Database Replication' + description: 'Set up database replication' + draft: false + hide_title: false + hide: false + category_id: 6 + }, + ] + links: [] + categories: [ + Category{ + path: 'Why' + collapsible: true + collapsed: false + }, + Category{ + path: 'Tutorials' + collapsible: true + collapsed: false + }, + Category{ + path: 'Tutorials/Operations/Urgent' + collapsible: true + collapsed: false + }, + Category{ + path: 'Tutorials/Operations' + collapsible: true + collapsed: false + }, + Category{ + path: 'Why/FAQ' + collapsible: true + collapsed: false + }, + Category{ + path: 'Tutorials/Operations/Database/Optimization' + collapsible: true + collapsed: false + }, + Category{ + path: 'Tutorials/Operations/Database' + collapsible: true + collapsed: false + }, + ] + announcements: [] + imports: [] + build_dest: [] + build_dest_dev: [] + } + assert s == s2 +} + pub fn test_navigation_depth() ! { console.print_header('🧭 Navigation Depth Multi-Level Test') console.lf() @@ -200,6 +484,8 @@ pub fn test_navigation_depth() ! { console.print_green('✓ Site retrieved') console.lf() + check(nav_site) + // ======================================================== // TEST 1: Validate Categories Structure // ========================================================