This commit is contained in:
2025-11-28 09:37:21 +01:00
parent fc13f3e6ae
commit 7c03226054
6 changed files with 835 additions and 260 deletions

View File

@@ -34,10 +34,10 @@ fn play_pages(mut plbook PlayBook, mut website Site) ! {
return error('!!site.page_category: must specify "name"')
}
category_name = texttools.name_fix(category_name)
category_name_fixed := texttools.name_fix(category_name)
// Get label (derive from name if not specified)
mut label := p.get_default('label', texttools.name_fix_snake_to_pascal(category_name))!
mut label := p.get_default('label', texttools.name_fix_snake_to_pascal(category_name_fixed))!
mut position := p.get_int_default('position', next_category_position)!
// Auto-increment position if using default
@@ -46,14 +46,15 @@ fn play_pages(mut plbook PlayBook, mut website Site) ! {
}
// Create and store category info
categories[category_name] = CategoryInfo{
name: category_name
categories[category_name_fixed] = CategoryInfo{
name: category_name_fixed
label: label
position: position
nav_items: []NavItem{}
}
category_current = category_name
category_current = category_name_fixed
console.print_item('Created page category: "${label}" (${category_name_fixed})')
action.done = true
continue
}
@@ -131,12 +132,63 @@ fn play_pages(mut plbook PlayBook, mut website Site) ! {
mut cat_info := categories[category_current]
cat_info.nav_items << nav_doc
categories[category_current] = cat_info
console.print_debug('Added page "${page_id}" to category "${category_current}"')
}
} else {
root_nav_items << nav_doc
console.print_debug('Added root page "${page_id}"')
}
action.done = true
continue
}
}
// ============================================================
// PASS 2: Build final navigation structure from categories
// ============================================================
console.print_item('Building navigation structure...')
mut final_nav_items := []NavItem{}
// Add root items first
for item in root_nav_items {
final_nav_items << item
}
// Sort categories by position and add them
mut sorted_categories := []CategoryInfo{}
for _, cat_info in categories {
sorted_categories << cat_info
}
// Sort by position
sorted_categories.sort(a.position < b.position)
// Convert categories to NavCat items and add to navigation
for cat_info in sorted_categories {
// Unwrap NavDoc items from cat_info.nav_items (they're already NavItem)
nav_cat := NavCat{
label: cat_info.label
collapsible: true
collapsed: false
items: cat_info.nav_items
}
final_nav_items << nav_cat
console.print_debug('Added category to nav: "${cat_info.label}" with ${cat_info.nav_items.len} items')
}
// Update website navigation
website.nav.my_sidebar = final_nav_items
console.print_green('Navigation structure built with ${website.pages.len} pages in ${categories.len} categories')
}
// -------- Internal Type for Tracking --------
struct CategoryInfo {
pub mut:
name string
label string
position int
nav_items []NavItem
}

View File

@@ -2,43 +2,83 @@
The Site module provides a structured way to define website configurations, navigation menus, pages, and sections using HeroScript. It's designed to work with static site generators like Docusaurus.
## Purpose
The Site module allows you to:
- Define website structure and configuration in a declarative way using HeroScript
- Organize pages into sections/categories
- Configure navigation menus and footers
- Manage page metadata (title, description, slug, etc.)
- Support multiple content collections
- Define build and publish destinations
## Quick Start
### Minimal HeroScript Example
```heroscript
!!site.config
name: "my_docs"
title: "My Documentation"
!!site.page src: "docs:introduction"
title: "Getting Started"
!!site.page src: "setup"
title: "Installation"
```
### Processing with V Code
```v
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
import incubaid.herolib.develop.gittools
import incubaid.herolib.core.playbook
import incubaid.herolib.web.site
import incubaid.herolib.core.playcmds
import incubaid.herolib.ui.console
// Clone or use existing repository with HeroScript files
mysitepath := gittools.path(
git_url: 'https://git.ourworld.tf/tfgrid/docs_tfgrid4/src/branch/main/ebooks/tech'
git_pull: true
)!
// Process HeroScript file
mut plbook := playbook.new(path: './site_config.heroscript')!
// Process all HeroScript files in the path
playcmds.run(heroscript_path: mysitepath.path)!
// Execute site configuration
site.play(mut plbook)!
// Get the configured site
mut mysite := site.get(name: 'tfgrid_tech')!
println(mysite)
// Access the configured site
mut mysite := site.get(name: 'my_docs')!
// Print available pages
pages_map := mysite.list_pages()
for page_id, _ in pages_map {
console.print_item('Page: ${page_id}')
}
println('Site has ${mysite.pages.len} pages')
```
---
## Core Concepts
### Site
A website configuration that contains pages, navigation structure, and metadata.
### Page
A single page with:
- **ID**: `collection:page_name` format
- **Title**: Display name (optional - extracted from markdown if not provided)
- **Description**: SEO metadata
- **Draft**: Hidden from navigation if true
### Category (Section)
Groups related pages together in the navigation sidebar. Automatically collapsed/expandable.
### Collection
A logical group of pages. Pages reuse the collection once specified.
```heroscript
!!site.page src: "tech:intro" # Specifies collection "tech"
!!site.page src: "benefits" # Reuses collection "tech"
!!site.page src: "components" # Still uses collection "tech"
!!site.page src: "api:reference" # Switches to collection "api"
!!site.page src: "endpoints" # Uses collection "api"
```
---
## HeroScript Syntax
### Basic Configuration
### 1. Site Configuration (Required)
```heroscript
!!site.config
@@ -51,20 +91,49 @@ println(mysite)
copyright: "© 2024 My Organization"
url: "https://docs.example.com"
base_url: "/"
url_home: "/docs"
```
### Navigation Menu
**Parameters:**
- `name` - Internal site identifier (default: 'default')
- `title` - Main site title (shown in browser tab)
- `description` - Site description for SEO
- `tagline` - Short tagline/subtitle
- `favicon` - Path to favicon image
- `image` - Default OG image for social sharing
- `copyright` - Copyright notice
- `url` - Full site URL for Docusaurus
- `base_url` - Base URL path (e.g., "/" or "/docs/")
- `url_home` - Home page path
### 2. Metadata Overrides (Optional)
```heroscript
!!site.config_meta
title: "My Docs - Technical Reference"
image: "img/tech-og.png"
description: "Technical documentation and API reference"
```
Overrides specific metadata for SEO without changing core config.
### 3. Navigation Bar
```heroscript
!!site.navbar
title: "My Site"
title: "My Documentation"
logo_alt: "Site Logo"
logo_src: "img/logo.svg"
logo_src_dark: "img/logo-dark.svg"
!!site.navbar_item
label: "Documentation"
to: "docs/intro"
to: "intro"
position: "left"
!!site.navbar_item
label: "API Reference"
to: "docs/api"
position: "left"
!!site.navbar_item
@@ -73,7 +142,13 @@ println(mysite)
position: "right"
```
### Footer Configuration
**Parameters:**
- `label` - Display text (required)
- `to` - Internal link
- `href` - External URL
- `position` - "left" or "right" in navbar
### 4. Footer Configuration
```heroscript
!!site.footer
@@ -87,311 +162,234 @@ println(mysite)
!!site.footer_item
title: "Docs"
label: "Getting Started"
href: "https://docs.example.com/getting-started"
to: "getting-started"
!!site.footer_item
title: "Community"
label: "Discord"
href: "https://discord.gg/example"
!!site.footer_item
title: "Legal"
label: "Privacy"
href: "https://example.com/privacy"
```
## Page Organization
### Example 1: Simple Pages Without Categories
When you don't need categories, pages are added sequentially. The collection only needs to be specified once, then it's reused for subsequent pages.
### 5. Announcement Bar (Optional)
```heroscript
!!site.page src: "mycelium_tech:introduction"
description: "Introduction to ThreeFold Technology"
slug: "/"
!!site.page src: "vision"
description: "Our Vision for the Future Internet"
!!site.page src: "what"
description: "What ThreeFold is Building"
!!site.page src: "presentation"
description: "ThreeFold Technology Presentation"
!!site.page src: "status"
description: "Current Development Status"
!!site.announcement
id: "new-release"
content: "🎉 Version 2.0 is now available!"
background_color: "#20232a"
text_color: "#fff"
is_closeable: true
```
**Key Points:**
### 6. Pages and Categories
- First page specifies collection as `tech:introduction` (collection:page_name format)
- Subsequent pages only need the page name (e.g., `vision`) - the `tech` collection is reused
- If `title` is not specified, it will be extracted from the markdown file itself
- Pages are ordered by their appearance in the HeroScript file
- `slug` can be used to customize the URL path (e.g., `"/"` for homepage)
#### Simple: Pages Without Categories
### Example 2: Pages with Categories
```heroscript
!!site.page src: "guides:introduction"
title: "Getting Started"
description: "Introduction to the platform"
Categories (sections) help organize pages into logical groups with their own navigation structure.
!!site.page src: "installation"
title: "Installation"
!!site.page src: "configuration"
title: "Configuration"
```
#### Advanced: Pages With Categories
```heroscript
!!site.page_category
name: "first_principle_thinking"
label: "First Principle Thinking"
name: "basics"
label: "Getting Started"
!!site.page src: "first_principle_thinking:hardware_badly_used"
description: "Hardware is not used properly, why it is important to understand hardware"
!!site.page src: "guides:introduction"
title: "Introduction"
description: "Learn the basics"
!!site.page src: "internet_risk"
description: "Internet risk, how to mitigate it, and why it is important"
!!site.page src: "installation"
title: "Installation"
!!site.page src: "onion_analogy"
description: "Compare onion with a computer, layers of abstraction"
```
!!site.page src: "configuration"
title: "Configuration"
**Key Points:**
- `!!site.page_category` creates a new section/category
- `name` is the internal identifier (snake_case)
- `label` is the display name (automatically derived from `name` if not specified)
- Category name is converted to title case: `first_principle_thinking` → "First Principle Thinking"
- Once a category is defined, all subsequent pages belong to it until a new category is declared
- Collection persistence works the same: specify once (e.g., `first_principle_thinking:hardware_badly_used`), then reuse
### Example 3: Advanced Page Configuration
```heroscript
!!site.page_category
name: "components"
label: "System Components"
position: 100
name: "advanced"
label: "Advanced Topics"
!!site.page src: "mycelium_tech:mycelium"
title: "Mycelium Network"
description: "Peer-to-peer overlay network"
slug: "mycelium-network"
position: 1
draft: false
hide_title: false
!!site.page src: "advanced:performance"
title: "Performance Tuning"
!!site.page src: "fungistor"
title: "Fungistor Storage"
description: "Distributed storage system"
position: 2
!!site.page src: "scaling"
title: "Scaling Guide"
```
**Available Page Parameters:**
**Page Parameters:**
- `src` - Source as `collection:page` (first page) or just `page_name` (reuse collection)
- `title` - Page title (optional, extracted from markdown if not provided)
- `description` - Page description
- `draft` - Hide from navigation (default: false)
- `hide_title` - Don't show title in page (default: false)
- `src`: Source reference as `collection:page_name` (required for first page in collection)
- `title`: Page title (optional, extracted from markdown if not provided)
- `description`: Page description for metadata
- `slug`: Custom URL slug
- `position`: Manual ordering (auto-incremented if not specified)
- `draft`: Mark page as draft (default: false)
- `hide_title`: Hide the page title in rendering (default: false)
- `path`: Custom path for the page (defaults to category name)
- `category`: Override the current category for this page
## File Organization
HeroScript files should be organized with numeric prefixes to control execution order:
```
docs/
├── 0_config.heroscript # Site configuration
├── 1_menu.heroscript # Navigation and footer
├── 2_intro_pages.heroscript # Introduction pages
├── 3_tech_pages.heroscript # Technical documentation
└── 4_api_pages.heroscript # API reference
```
**Important:** Files are processed in alphabetical order, so use numeric prefixes (0_, 1_, 2_, etc.) to ensure correct execution sequence.
## Import External Content
### 7. Content Imports
```heroscript
!!site.import
url: "https://github.com/example/external-docs"
path: "/local/path/to/repo"
dest: "external"
replace: "PROJECT_NAME:My Project,VERSION:1.0.0"
visible: true
```
## Publish Destinations
### 8. Publishing Destinations
```heroscript
!!site.publish
path: "/var/www/html/docs"
ssh_name: "production_server"
ssh_name: "production"
!!site.publish_dev
path: "/tmp/docs-preview"
```
## HeroScript Processing Order
---
Atlas processes HeroScript actions in a fixed order. Each step depends on previous steps:
## Common Patterns
1. **Site Configuration** (`!!site.config`) - Required
- Sets basic site metadata and URLs
2. **Metadata Overrides** (`!!site.config_meta`) - Optional
- Overrides specific SEO metadata
3. **Content Imports** (`!!site.import`) - Optional
- Defines external content imports
4. **Navigation Menu** (`!!site.navbar` + `!!site.navbar_item`) - Recommended
- Configures top navigation bar
5. **Footer** (`!!site.footer` + `!!site.footer_item`) - Recommended
- Configures footer links
6. **Announcement Bar** (`!!site.announcement`) - Optional
- Configures optional announcement banner
7. **Publishing** (`!!site.publish` + `!!site.publish_dev`) - Optional
- Defines deployment destinations
8. **Pages** (`!!site.page_category` + `!!site.page`) - Recommended
- Defines content pages and navigation structure
### Pattern 1: Multi-Section Technical Documentation
### Error Handling
The play function validates parameters and provides helpful error messages:
```/dev/null/error_example.txt#L1-4
!!site.page: must specify source as "collection:page_name" in "src"
Got src="invalid_page" with no collection previously set
Either specify "collection:page_name" or define a collection first
```
### Best Practices for HeroScript
```heroscript/example.heroscript#L1-20
# 1. Always start with config
```heroscript
!!site.config
name: "my_docs"
title: "My Documentation"
name: "tech_docs"
title: "Technical Documentation"
# 2. Set up navigation
!!site.navbar title: "MyDocs"
!!site.page_category
name: "getting_started"
label: "Getting Started"
# 3. Define pages with reusable collection
!!site.page_category name: "intro"
!!site.page src: "docs:intro"
title: "Introduction"
!!site.page src: "guides:introduction"
title: "Getting Started"
!!site.page src: "setup" # Reuses "guides" collection
!!site.page src: "installation"
title: "Installation"
!!site.page src: "tutorial" # Still uses "guides"
title: "Tutorial"
!!site.page_category
name: "concepts"
label: "Core Concepts"
# 4. Change collection when needed
!!site.page src: "api:reference"
title: "API Reference"
!!site.page src: "concepts:architecture"
title: "Architecture"
!!site.page src: "endpoints" # Now uses "api" collection
title: "Endpoints"
!!site.page src: "components"
title: "Components"
!!site.page_category
name: "api"
label: "API Reference"
!!site.page src: "api:rest"
title: "REST API"
!!site.page src: "graphql"
title: "GraphQL"
```
## Factory Methods
### Pattern 2: Simple Blog/Knowledge Base
### Create or Get a Site
```heroscript
!!site.config
name: "blog"
title: "Knowledge Base"
```v
import incubaid.herolib.web.site
!!site.page src: "articles:first_post"
title: "Welcome to Our Blog"
// Create a new site
mut mysite := site.new(name: 'my_docs')!
!!site.page src: "second_post"
title: "Understanding the Basics"
// Get an existing site
mut mysite := site.get(name: 'my_docs')!
// Get default site
mut mysite := site.default()!
// Check if site exists
if site.exists(name: 'my_docs') {
println('Site exists')
}
// List all sites
sites := site.list()
println(sites)
!!site.page src: "third_post"
title: "Advanced Techniques"
```
### Using with PlayBook
### Pattern 3: Project with External Imports
```v
import incubaid.herolib.core.playbook
import incubaid.herolib.web.site
```heroscript
!!site.config
name: "project_docs"
title: "Project Documentation"
// Create playbook from path
mut plbook := playbook.new(path: '/path/to/heroscripts')!
!!site.import
url: "https://github.com/org/shared-docs"
dest: "shared"
visible: true
// Process site configuration
site.play(mut plbook)!
!!site.page_category
name: "product"
label: "Product Guide"
// Access the configured site
mut mysite := site.get(name: 'my_site')!
!!site.page src: "docs:overview"
title: "Overview"
!!site.page src: "features"
title: "Features"
!!site.page_category
name: "resources"
label: "Shared Resources"
!!site.page src: "shared:common"
title: "Common Patterns"
```
## Data Structures
---
### Site
## File Organization
```v
pub struct Site {
pub mut:
pages []Page
sections []Section
siteconfig SiteConfig
}
Organize HeroScript files with numeric prefixes to control execution order:
```
docs/
├── 0_config.heroscript
└── !!site.config and !!site.config_meta
├── 1_menu.heroscript
│ └── !!site.navbar and !!site.footer
├── 2_pages.heroscript
│ └── !!site.page_category and !!site.page actions
└── 3_publish.heroscript
└── !!site.publish destinations
```
### Page
**Why numeric prefixes?**
```v
pub struct Page {
pub mut:
name string // Page identifier
title string // Display title
description string // Page description
draft bool // Draft status
position int // Sort order
hide_title bool // Hide title in rendering
src string // Source as collection:page_name
path string // URL path (without page name)
section_name string // Category/section name
title_nr int // Title numbering level
slug string // Custom URL slug
}
```
Files are processed in alphabetical order. Numeric prefixes ensure:
- Site config runs first
- Navigation menu configures before pages
- Pages build the final structure
- Publishing configured last
### Section
---
```v
pub struct Section {
pub mut:
name string // Internal identifier
position int // Sort order
path string // URL path
label string // Display name
}
```
## Processing Order
## Best Practices
The Site module processes HeroScript in this strict order:
1. **File Naming**: Use numeric prefixes (0_, 1_, 2_) to control execution order
2. **Collection Reuse**: Specify collection once, then reuse for subsequent pages
3. **Category Organization**: Group related pages under categories for better navigation
4. **Title Extraction**: Let titles be extracted from markdown files when possible
5. **Position Management**: Use automatic positioning unless you need specific ordering
6. **Description**: Always provide descriptions for better SEO and navigation
7. **Draft Status**: Use `draft: true` for work-in-progress pages
1. Site Configuration
2. Metadata Overrides
3. Imports
4. Navigation
5. Footer
6. Announcement
7. Publishing
8. Pages & Categories
## Complete Example
See `examples/web/site/site_example.vsh` for a complete working example.
For a real-world example, check: <https://git.ourworld.tf/tfgrid/docs_tfgrid4/src/branch/main/ebooks/tech>
Each stage depends on previous stages completing successfully.