Files
herolib/lib/web/doctree/meta/model_site.v
2025-12-02 05:05:11 +01:00

260 lines
7.1 KiB
V

module meta
@[heap]
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
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
for page in s.pages {
if page.category_id == 0 {
// Page at root level (no category)
uncategorized_pages << page
} 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
}
}
// 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{}
// PASS 1: Create all category nodes (even empty intermediate ones)
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
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
}
}
}
}
}
// PASS 2: Add pages to their designated categories
for i, category in s.categories {
category_id := i + 1 // categories are 1-indexed
// Skip if no pages in this category
if category_id !in category_pages {
continue
}
// Build the full path for this category
full_path := category.path
// 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(':', '/')
nav_doc := NavDoc{
path: path
label: if page.label.len > 0 { page.label } else { page.title }
}
leaf_cat.items << nav_doc
}
}
}
}
// ============================================================
// PASS 3: Add root-level categories to sidebar
// ============================================================
// Find all root-level categories (those without '/')
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 4: 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 5: 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 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'
}