260 lines
7.1 KiB
V
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'
|
|
}
|