...
This commit is contained in:
208
examples/web/doctree/doctree_meta.vsh
Executable file
208
examples/web/doctree/doctree_meta.vsh
Executable file
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import incubaid.herolib.web.doctree.meta
|
||||
|
||||
import incubaid.herolib.core.playbook
|
||||
import incubaid.herolib.ui.console
|
||||
|
||||
// Comprehensive HeroScript for testing multi-level navigation depths
|
||||
const test_heroscript_nav_depth = '
|
||||
!!site.config
|
||||
name: "nav_depth_test"
|
||||
title: "Navigation Depth Test Site"
|
||||
description: "Testing multi-level nested navigation"
|
||||
tagline: "Deep navigation structures"
|
||||
|
||||
!!site.navbar
|
||||
title: "Nav Depth Test"
|
||||
|
||||
!!site.navbar_item
|
||||
label: "Home"
|
||||
to: "/"
|
||||
position: "left"
|
||||
|
||||
// ============================================================
|
||||
// LEVEL 1: Simple top-level category
|
||||
// ============================================================
|
||||
!!site.page_category
|
||||
path: "Why"
|
||||
collapsible: true
|
||||
collapsed: false
|
||||
|
||||
//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"
|
||||
|
||||
!!site.page src: "benefits"
|
||||
label: "Key Benefits"
|
||||
title: "Key Benefits"
|
||||
description: "Main benefits overview"
|
||||
|
||||
// ============================================================
|
||||
// LEVEL 1: Simple top-level category
|
||||
// ============================================================
|
||||
!!site.page_category
|
||||
path: "Tutorials"
|
||||
collapsible: true
|
||||
collapsed: false
|
||||
|
||||
!!site.page src: "getting_started"
|
||||
label: "Getting Started"
|
||||
title: "Getting Started"
|
||||
description: "Basic tutorial to get started"
|
||||
|
||||
!!site.page src: "first_steps"
|
||||
label: "First Steps"
|
||||
title: "First Steps"
|
||||
description: "Your first steps with the platform"
|
||||
|
||||
// ============================================================
|
||||
// LEVEL 3: Three-level nested category (Tutorials > Operations > Urgent)
|
||||
// ============================================================
|
||||
!!site.page_category
|
||||
path: "Tutorials/Operations/Urgent"
|
||||
collapsible: true
|
||||
collapsed: false
|
||||
|
||||
!!site.page src: "emergency_restart"
|
||||
label: "Emergency Restart"
|
||||
title: "Emergency Restart"
|
||||
description: "How to emergency restart the system"
|
||||
|
||||
!!site.page src: "critical_fixes"
|
||||
label: "Critical Fixes"
|
||||
title: "Critical Fixes"
|
||||
description: "Apply critical fixes immediately"
|
||||
|
||||
!!site.page src: "incident_response"
|
||||
label: "Incident Response"
|
||||
title: "Incident Response"
|
||||
description: "Handle incidents in real-time"
|
||||
|
||||
// ============================================================
|
||||
// LEVEL 2: Two-level nested category (Tutorials > Operations)
|
||||
// ============================================================
|
||||
!!site.page_category
|
||||
path: "Tutorials/Operations"
|
||||
collapsible: true
|
||||
collapsed: false
|
||||
|
||||
!!site.page src: "daily_checks"
|
||||
label: "Daily Checks"
|
||||
title: "Daily Checks"
|
||||
description: "Daily maintenance checklist"
|
||||
|
||||
!!site.page src: "monitoring"
|
||||
label: "Monitoring"
|
||||
title: "Monitoring"
|
||||
description: "System monitoring procedures"
|
||||
|
||||
!!site.page src: "backups"
|
||||
label: "Backups"
|
||||
title: "Backups"
|
||||
description: "Backup and restore procedures"
|
||||
|
||||
// ============================================================
|
||||
// LEVEL 1: One-to-two level (Tutorials)
|
||||
// ============================================================
|
||||
// Note: This creates a sibling at the Tutorials level (not nested deeper)
|
||||
!!site.page src: "advanced_concepts"
|
||||
label: "Advanced Concepts"
|
||||
title: "Advanced Concepts"
|
||||
description: "Deep dive into advanced concepts"
|
||||
|
||||
!!site.page src: "troubleshooting"
|
||||
label: "Troubleshooting"
|
||||
title: "Troubleshooting"
|
||||
description: "Troubleshooting guide"
|
||||
|
||||
// ============================================================
|
||||
// LEVEL 2: Two-level nested category (Why > FAQ)
|
||||
// ============================================================
|
||||
!!site.page_category
|
||||
path: "Why/FAQ"
|
||||
collapsible: true
|
||||
collapsed: false
|
||||
|
||||
!!site.page src: "general"
|
||||
label: "General Questions"
|
||||
title: "General Questions"
|
||||
description: "Frequently asked questions"
|
||||
|
||||
!!site.page src: "pricing_questions"
|
||||
label: "Pricing"
|
||||
title: "Pricing Questions"
|
||||
description: "Questions about pricing"
|
||||
|
||||
!!site.page src: "technical_faq"
|
||||
label: "Technical FAQ"
|
||||
title: "Technical FAQ"
|
||||
description: "Technical frequently asked questions"
|
||||
|
||||
!!site.page src: "support_faq"
|
||||
label: "Support"
|
||||
title: "Support FAQ"
|
||||
description: "Support-related FAQ"
|
||||
|
||||
// ============================================================
|
||||
// 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: "query_optimization"
|
||||
label: "Query Optimization"
|
||||
title: "Query Optimization"
|
||||
description: "Optimize your database queries"
|
||||
|
||||
!!site.page src: "indexing_strategy"
|
||||
label: "Indexing Strategy"
|
||||
title: "Indexing Strategy"
|
||||
description: "Effective indexing strategies"
|
||||
|
||||
!!site.page_category
|
||||
path: "Tutorials/Operations/Database"
|
||||
collapsible: true
|
||||
collapsed: false
|
||||
|
||||
!!site.page src: "configuration"
|
||||
label: "Configuration"
|
||||
title: "Database Configuration"
|
||||
description: "Configure your database"
|
||||
|
||||
!!site.page src: "replication"
|
||||
label: "Replication"
|
||||
title: "Database Replication"
|
||||
description: "Set up database replication"
|
||||
|
||||
'
|
||||
|
||||
fn check(s2 meta.Site) {
|
||||
|
||||
// assert s == s2
|
||||
}
|
||||
|
||||
|
||||
// ========================================================
|
||||
// SETUP: Create and process playbook
|
||||
// ========================================================
|
||||
console.print_item('Creating playbook from HeroScript')
|
||||
mut plbook := playbook.new(text: test_heroscript_nav_depth)!
|
||||
console.print_green('✓ Playbook created')
|
||||
console.lf()
|
||||
|
||||
console.print_item('Processing site configuration')
|
||||
meta.play(mut plbook)!
|
||||
console.print_green('✓ Site processed')
|
||||
console.lf()
|
||||
|
||||
console.print_item('Retrieving configured site')
|
||||
mut nav_site := meta.get(name: 'nav_depth_test')!
|
||||
console.print_green('✓ Site retrieved')
|
||||
console.lf()
|
||||
|
||||
// check(nav_site)
|
||||
@@ -25,6 +25,7 @@ pub fn new(args FactoryArgs) !&Site {
|
||||
config: SiteConfig{
|
||||
name: name
|
||||
}
|
||||
root_category: Category{}
|
||||
}
|
||||
sites_global[name] = &site
|
||||
return get(name: name)!
|
||||
|
||||
@@ -1,8 +1,111 @@
|
||||
module meta
|
||||
|
||||
@[heap]
|
||||
struct Category {
|
||||
pub mut:
|
||||
path string // e.g. Operations/Daily (means 2 levels deep, first level is Operations)
|
||||
collapsible bool = true
|
||||
collapsed bool
|
||||
items []CategoryItem
|
||||
}
|
||||
|
||||
//return the label of the category (last part of the path)
|
||||
pub fn (mut c Category) label() !string {
|
||||
if c.path.count('/') == 0 {
|
||||
return c.path
|
||||
}
|
||||
return c.path.all_after_last('/')
|
||||
}
|
||||
|
||||
type CategoryItem = Page | Link | Category
|
||||
|
||||
|
||||
|
||||
pub fn (mut self Category) up(path string) !&Category {
|
||||
// Split the requested path into parts
|
||||
path_parts := path.split('/')
|
||||
|
||||
// Start at current category
|
||||
mut current := &self
|
||||
|
||||
// Navigate through each part of the path
|
||||
for part in path_parts {
|
||||
// Skip empty parts (from leading/trailing slashes)
|
||||
if part.len == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this part already exists in current category's items
|
||||
mut found := false
|
||||
for item in current.items {
|
||||
match item {
|
||||
&Category {
|
||||
item_label := item.label()!
|
||||
if item_label == part {
|
||||
current = item
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
// If not found, create a new category and add it
|
||||
if !found {
|
||||
mut new_category := Category{
|
||||
path: part
|
||||
collapsible: true
|
||||
collapsed: true
|
||||
items: []CategoryItem{}
|
||||
}
|
||||
current.items << new_category
|
||||
current = &new_category
|
||||
}
|
||||
}
|
||||
|
||||
return current
|
||||
}
|
||||
|
||||
|
||||
fn (mut self Category) page_get(src string)! &Page {
|
||||
for item in self.items {
|
||||
match item {
|
||||
Page {
|
||||
if item.src == src {
|
||||
return it
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
return error('Page with src="${src}" not found in site.')
|
||||
}
|
||||
|
||||
fn (mut self Category) link_get(href string)! &Link {
|
||||
for item in self.items {
|
||||
match item {
|
||||
Link {
|
||||
if item.href == href {
|
||||
return it
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
return error('Link with href="${href}" not found in site.')
|
||||
}
|
||||
|
||||
fn (mut self Category) category_get(path string)! &Category {
|
||||
for item in self.items {
|
||||
match item {
|
||||
Category {
|
||||
if item.path == path {
|
||||
return it
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
return error('Category with path="${path}" not found in site.')
|
||||
}
|
||||
76
lib/web/doctree/meta/model_category_str.v
Normal file
76
lib/web/doctree/meta/model_category_str.v
Normal file
@@ -0,0 +1,76 @@
|
||||
module meta
|
||||
|
||||
pub fn (mut self Category) str() string {
|
||||
mut result := []string{}
|
||||
|
||||
if self.items.len == 0 {
|
||||
return 'Sidebar is empty\n'
|
||||
}
|
||||
|
||||
result << '📑 SIDEBAR STRUCTURE'
|
||||
result << '━'.repeat(60)
|
||||
|
||||
for i, item in self.items {
|
||||
is_last := i == self.items.len - 1
|
||||
prefix := if is_last { '└── ' } else { '├── ' }
|
||||
|
||||
match item {
|
||||
NavDoc {
|
||||
result << '${prefix}📄 ${item.label}'
|
||||
result << ' └─ path: ${item.path}'
|
||||
}
|
||||
NavCat {
|
||||
// Category header
|
||||
collapse_icon := if item.collapsed { '▶ ' } else { '▼ ' }
|
||||
result << '${prefix}${collapse_icon}📁 ${item.label}'
|
||||
|
||||
// Category metadata
|
||||
if !item.collapsed {
|
||||
result << ' ├─ collapsible: ${item.collapsible}'
|
||||
result << ' └─ items: ${item.items.len}'
|
||||
|
||||
// Sub-items
|
||||
for j, sub_item in item.items {
|
||||
is_last_sub := j == item.items.len - 1
|
||||
sub_prefix := if is_last_sub { ' └── ' } else { ' ├── ' }
|
||||
|
||||
match sub_item {
|
||||
NavDoc {
|
||||
result << '${sub_prefix}📄 ${sub_item.label} [${sub_item.src_path}]'
|
||||
}
|
||||
NavCat {
|
||||
// Nested categories
|
||||
sub_collapse_icon := if sub_item.collapsed { '▶ ' } else { '▼ ' }
|
||||
result << '${sub_prefix}${sub_collapse_icon}📁 ${sub_item.label}'
|
||||
}
|
||||
NavLink {
|
||||
result << '${sub_prefix}🔗 ${sub_item.label}'
|
||||
if sub_item.description.len > 0 {
|
||||
result << ' └─ ${sub_item.description}'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NavLink {
|
||||
result << '${prefix}🔗 ${item.label}'
|
||||
result << ' └─ href: ${item.href}'
|
||||
if item.description.len > 0 {
|
||||
result << ' └─ desc: ${item.description}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add spacing between root items
|
||||
if i < self.items.len - 1 {
|
||||
result << ''
|
||||
}
|
||||
}
|
||||
|
||||
result << '━'.repeat(60)
|
||||
result << '📊 SUMMARY'
|
||||
result << ' Total items: ${self.items.len}'
|
||||
|
||||
return result.join('\n') + '\n'
|
||||
}
|
||||
@@ -11,4 +11,5 @@ pub mut:
|
||||
hide_title bool // Should the title be hidden on the page?
|
||||
hide bool // Should the page be hidden from navigation?
|
||||
category_id int // Optional category ID this page belongs to, if 0 it means its at root level
|
||||
nav_path string // navigation path e.g. "Operations/Daily"
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
module meta
|
||||
|
||||
// ============================================================================
|
||||
// Sidebar Navigation Models (Domain Types)
|
||||
// is the result of walking through the pages, links and categories to build the sidebar structure
|
||||
// ============================================================================
|
||||
|
||||
pub struct SideBar {
|
||||
pub mut:
|
||||
my_sidebar []NavItem
|
||||
}
|
||||
|
||||
pub type NavItem = NavDoc | NavCat | NavLink
|
||||
|
||||
pub struct NavDoc {
|
||||
pub:
|
||||
path string // path is $collection/$name without .md, this is a subdir of the doctree export dir
|
||||
label string
|
||||
}
|
||||
|
||||
pub struct NavCat {
|
||||
pub mut:
|
||||
label string
|
||||
collapsible bool = true
|
||||
collapsed bool
|
||||
items []NavItem
|
||||
}
|
||||
|
||||
pub struct NavLink {
|
||||
pub:
|
||||
label string
|
||||
href string
|
||||
description string
|
||||
}
|
||||
@@ -5,316 +5,28 @@ pub struct Site {
|
||||
pub mut:
|
||||
doctree_path string // path to the export of the doctree site
|
||||
config SiteConfig // Full site configuration
|
||||
pages []Page
|
||||
links []Link
|
||||
categories []Category
|
||||
root // The root category containing all top-level items
|
||||
announcements []Announcement // there can be more than 1 announcement
|
||||
imports []ImportItem
|
||||
build_dest []BuildDest // Production build destinations (from !!site.build_dest)
|
||||
build_dest_dev []BuildDest // Development build destinations (from !!site.build_dest_dev)
|
||||
}
|
||||
|
||||
pub fn (mut s Site) sidebar() SideBar {
|
||||
mut result := SideBar{
|
||||
my_sidebar: []NavItem{}
|
||||
}
|
||||
|
||||
// If no pages, return empty sidebar
|
||||
if s.pages.len == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
// Build a map of category_id -> pages for efficient lookup
|
||||
mut category_pages := map[int][]Page{}
|
||||
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() {
|
||||
category_pages[category_id].sort(a.src < b.src)
|
||||
}
|
||||
|
||||
// Sort uncategorized pages
|
||||
uncategorized_pages.sort(a.src < b.src)
|
||||
|
||||
// ============================================================
|
||||
// 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 first
|
||||
// Collect all paths first, then sort by depth (shallow first)
|
||||
mut all_paths := []string{}
|
||||
for i, category in s.categories {
|
||||
path_parts := if category.path.contains('/') {
|
||||
category.path.split('/')
|
||||
} else {
|
||||
[category.path]
|
||||
}
|
||||
|
||||
mut current_path := ''
|
||||
for part_idx, part in path_parts {
|
||||
if current_path.len > 0 {
|
||||
current_path += '/'
|
||||
}
|
||||
current_path += part
|
||||
|
||||
// Add this path if not already added
|
||||
if current_path !in category_tree {
|
||||
all_paths << current_path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 // 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]
|
||||
for page in category_pages[category_id] {
|
||||
if !page.hide {
|
||||
// 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 }
|
||||
}
|
||||
leaf_cat.items << nav_doc
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln(' ERROR: Category path "${full_path}" not in category_tree!')
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PASS 4: Add root-level categories to sidebar
|
||||
// ============================================================
|
||||
// Find all root-level categories (those without '/') and add them once
|
||||
mut added_roots := map[string]bool{}
|
||||
|
||||
for i, category in s.categories {
|
||||
// Only process top-level categories
|
||||
if !category.path.contains('/') && category.path.len > 0 {
|
||||
root_path := category.path
|
||||
// Only add each root once
|
||||
if root_path !in added_roots {
|
||||
if root_path in category_tree {
|
||||
result.my_sidebar << category_tree[root_path]
|
||||
added_roots[root_path] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PASS 5: Add uncategorized pages at root level
|
||||
// ============================================================
|
||||
for page in uncategorized_pages {
|
||||
if !page.hide {
|
||||
// Convert page src format "collection:name" to path "collection/name"
|
||||
path := page.src.replace(':', '/')
|
||||
|
||||
nav_doc := NavDoc{
|
||||
path: path
|
||||
label: if page.label.len > 0 { page.label } else { page.title }
|
||||
}
|
||||
result.my_sidebar << nav_doc
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PASS 6: Add standalone links (if needed)
|
||||
// ============================================================
|
||||
for link in s.links {
|
||||
nav_link := NavLink{
|
||||
label: link.label
|
||||
href: link.href
|
||||
description: link.description
|
||||
}
|
||||
result.my_sidebar << nav_link
|
||||
}
|
||||
|
||||
return result
|
||||
pub fn (mut self Site) page_get(src string)! &Page {
|
||||
return self.root.page_get(src)!
|
||||
}
|
||||
|
||||
pub fn (mut s Site) sidebar_str() string {
|
||||
mut result := []string{}
|
||||
mut sidebar := s.sidebar()
|
||||
|
||||
if sidebar.my_sidebar.len == 0 {
|
||||
return 'Sidebar is empty\n'
|
||||
}
|
||||
|
||||
result << '📑 SIDEBAR STRUCTURE'
|
||||
result << '━'.repeat(60)
|
||||
|
||||
for i, item in sidebar.my_sidebar {
|
||||
is_last := i == sidebar.my_sidebar.len - 1
|
||||
prefix := if is_last { '└── ' } else { '├── ' }
|
||||
|
||||
match item {
|
||||
NavDoc {
|
||||
result << '${prefix}📄 ${item.label}'
|
||||
result << ' └─ path: ${item.path}'
|
||||
}
|
||||
NavCat {
|
||||
// Category header
|
||||
collapse_icon := if item.collapsed { '▶ ' } else { '▼ ' }
|
||||
result << '${prefix}${collapse_icon}📁 ${item.label}'
|
||||
|
||||
// Category metadata
|
||||
if !item.collapsed {
|
||||
result << ' ├─ collapsible: ${item.collapsible}'
|
||||
result << ' └─ items: ${item.items.len}'
|
||||
|
||||
// Sub-items
|
||||
for j, sub_item in item.items {
|
||||
is_last_sub := j == item.items.len - 1
|
||||
sub_prefix := if is_last_sub { ' └── ' } else { ' ├── ' }
|
||||
|
||||
match sub_item {
|
||||
NavDoc {
|
||||
result << '${sub_prefix}📄 ${sub_item.label} [${sub_item.path}]'
|
||||
}
|
||||
NavCat {
|
||||
// Nested categories
|
||||
sub_collapse_icon := if sub_item.collapsed { '▶ ' } else { '▼ ' }
|
||||
result << '${sub_prefix}${sub_collapse_icon}📁 ${sub_item.label}'
|
||||
}
|
||||
NavLink {
|
||||
result << '${sub_prefix}🔗 ${sub_item.label}'
|
||||
if sub_item.description.len > 0 {
|
||||
result << ' └─ ${sub_item.description}'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NavLink {
|
||||
result << '${prefix}🔗 ${item.label}'
|
||||
result << ' └─ href: ${item.href}'
|
||||
if item.description.len > 0 {
|
||||
result << ' └─ desc: ${item.description}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add spacing between root items
|
||||
if i < sidebar.my_sidebar.len - 1 {
|
||||
result << ''
|
||||
}
|
||||
}
|
||||
|
||||
result << '━'.repeat(60)
|
||||
result << '📊 SUMMARY'
|
||||
result << ' Total items: ${sidebar.my_sidebar.len}'
|
||||
result << ' Pages: ${s.pages.len}'
|
||||
result << ' Categories: ${s.categories.len}'
|
||||
result << ' Links: ${s.links.len}'
|
||||
|
||||
return result.join('\n') + '\n'
|
||||
pub fn (mut self Site) link_get(href string)! &Link {
|
||||
return self.root.link_get(href)!
|
||||
}
|
||||
|
||||
pub fn (mut self Site) category_get(path string)! &Category {
|
||||
return self.root.category_get(path)!
|
||||
}
|
||||
|
||||
//sidebar returns the root category for building the sidebar navigation
|
||||
pub fn (mut self Site) sidebar()! &Category {
|
||||
return self.root
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ import incubaid.herolib.ui.console
|
||||
// ============================================================
|
||||
fn play_pages(mut plbook PlayBook, mut website Site) ! {
|
||||
mut collection_current := ''
|
||||
mut category_current := 0
|
||||
|
||||
mut category_current := &website.root_category // start at root category, this is basically the navigation tree root
|
||||
|
||||
// ============================================================
|
||||
// PASS 1: Process all page and category actions
|
||||
@@ -26,16 +27,18 @@ fn play_pages(mut plbook PlayBook, mut website Site) ! {
|
||||
mut p := action.params
|
||||
|
||||
// label is empty when not specified (we support label & path for flexibility)
|
||||
mut category_path := p.get_default('path', '')!
|
||||
category_current = category_current.up(category_path)!
|
||||
category_current.collapsible = p.get_default_true('collapsible')
|
||||
category_current.collapsed = p.get_default_true('collapsed')
|
||||
|
||||
mut category := Category{
|
||||
path: p.get_default('path', p.get_default('label', '')!)!
|
||||
collapsible: p.get_default_true('collapsible')
|
||||
collapsed: p.get_default_true('collapsed')
|
||||
}
|
||||
website.categories << category
|
||||
category_current = website.categories.len - 1
|
||||
console.print_item('Created page category: "${category.path}" ')
|
||||
console.print_item('Created page category: "${category_current.path}" ')
|
||||
action.done = true
|
||||
println(category_current)
|
||||
|
||||
website.categories << category_current
|
||||
|
||||
$dbg();
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -76,6 +79,8 @@ fn play_pages(mut plbook PlayBook, mut website Site) ! {
|
||||
page_title := p.get_default('title', '')! // is title shown on the page, if not from the page content, if empty then will be brought in from the content
|
||||
page_description := p.get_default('description', '')!
|
||||
|
||||
|
||||
|
||||
// Create page
|
||||
mut page := Page{
|
||||
src: '${page_collection}:${page_name}'
|
||||
|
||||
@@ -127,7 +127,7 @@ pub mut:
|
||||
}
|
||||
|
||||
// Generate sidebar navigation
|
||||
sidebar := mysite.sidebar() // Returns SideBar
|
||||
sidebar := mysite.sidebar()! // Returns SideBar
|
||||
|
||||
// Sidebar structure
|
||||
pub struct SideBar {
|
||||
@@ -159,7 +159,7 @@ pub:
|
||||
}
|
||||
|
||||
// Example: iterate navigation
|
||||
sidebar := mysite.sidebar()
|
||||
sidebar := mysite.sidebar()!
|
||||
for item in sidebar.my_sidebar {
|
||||
match item {
|
||||
NavDoc {
|
||||
|
||||
@@ -560,7 +560,7 @@ pub fn test_navigation_depth() ! {
|
||||
// ========================================================
|
||||
console.print_header('TEST 3: Navigation Structure Analysis')
|
||||
|
||||
mut sidebar := nav_site.sidebar()
|
||||
mut sidebar := nav_site.sidebar()!
|
||||
console.print_item('Sidebar root items: ${sidebar.my_sidebar.len}')
|
||||
console.lf()
|
||||
|
||||
@@ -403,7 +403,7 @@ fn test_site2() ! {
|
||||
// ========================================================
|
||||
console.print_header('Validating Navigation Structure (Sidebar)')
|
||||
|
||||
mut sidebar := test_site.sidebar()
|
||||
mut sidebar := test_site.sidebar()!
|
||||
|
||||
console.print_item('Sidebar has ${sidebar.my_sidebar.len} root items')
|
||||
assert sidebar.my_sidebar.len > 0, 'Sidebar should not be empty'
|
||||
@@ -1,7 +1,7 @@
|
||||
module docusaurus
|
||||
|
||||
import incubaid.herolib.core.pathlib
|
||||
import incubaid.herolib.web.site
|
||||
import incubaid.herolib.web.doctree.meta
|
||||
import incubaid.herolib.osal.core as osal
|
||||
import incubaid.herolib.ui.console
|
||||
|
||||
@@ -15,7 +15,7 @@ pub mut:
|
||||
path_build pathlib.Path
|
||||
errors []SiteError
|
||||
config Configuration
|
||||
website site.Site
|
||||
website meta.Site
|
||||
generated bool
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ pub fn (mut s DocSite) build_publish() ! {
|
||||
'
|
||||
retry: 0
|
||||
)!
|
||||
for item in s.website.siteconfig.build_dest {
|
||||
for item in s.build_dest {
|
||||
if item.path.trim_space().trim('/ ') == '' {
|
||||
$if debug {
|
||||
print_backtrace()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module docusaurus
|
||||
|
||||
import incubaid.herolib.web.site
|
||||
import incubaid.herolib.web.doctree.meta
|
||||
|
||||
// IS THE ONE AS USED BY DOCUSAURUS
|
||||
|
||||
@@ -87,9 +87,9 @@ pub mut:
|
||||
}
|
||||
|
||||
// This function is a pure transformer: site.SiteConfig -> docusaurus.Configuration
|
||||
fn new_configuration(mysite site.Site) !Configuration {
|
||||
fn new_configuration(mysite meta.Site) !Configuration {
|
||||
// Transform site.SiteConfig to docusaurus.Configuration
|
||||
mut site_cfg := mysite.siteconfig
|
||||
mut site_cfg := mysite.config
|
||||
mut nav_items := []NavbarItem{}
|
||||
for item in site_cfg.menu.items {
|
||||
nav_items << NavbarItem{
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
module doc
|
||||
import incubaid.herolib.web.site
|
||||
|
||||
//this is the logic to create docusaurus sidebar.json from site.NavItems
|
||||
import incubaid.herolib.web.doctree.meta as site
|
||||
import json
|
||||
|
||||
// this is the logic to create docusaurus sidebar.json from site.NavItems
|
||||
|
||||
struct Sidebar {
|
||||
pub mut:
|
||||
items []NavItem
|
||||
}
|
||||
|
||||
type NavItem = NavDoc | NavCat | NavLink
|
||||
|
||||
struct SidebarItem {
|
||||
typ string @[json: 'type']
|
||||
@@ -14,11 +23,32 @@ struct SidebarItem {
|
||||
items []SidebarItem @[omitempty]
|
||||
}
|
||||
|
||||
pub struct NavDoc {
|
||||
pub mut:
|
||||
id string
|
||||
label string
|
||||
}
|
||||
|
||||
pub struct NavCat {
|
||||
pub mut:
|
||||
label string
|
||||
collapsible bool = true
|
||||
collapsed bool
|
||||
items []NavItem
|
||||
}
|
||||
|
||||
pub struct NavLink {
|
||||
pub mut:
|
||||
label string
|
||||
href string
|
||||
description string
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// JSON Serialization
|
||||
// ============================================================================
|
||||
|
||||
pub fn sidebar_to_json(sb site.SideBar) !string {
|
||||
pub fn sidebar_to_json(sb site.SideBar) !string {
|
||||
items := sb.my_sidebar.map(to_sidebar_item(it))
|
||||
return json.encode_pretty(items)
|
||||
}
|
||||
@@ -57,4 +87,3 @@ fn from_category(cat site.NavCat) SidebarItem {
|
||||
items: cat.items.map(to_sidebar_item(it))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user