This commit is contained in:
2025-11-28 09:27:19 +01:00
parent 0414ea85df
commit fc13f3e6ae
12 changed files with 571 additions and 355 deletions

View File

@@ -5,7 +5,7 @@ import json
// Top-level config
pub struct NavConfig {
pub mut:
mySidebar []NavItem
my_sidebar []NavItem
// myTopbar []NavItem //not used yet
// myFooter []NavItem //not used yet
}
@@ -16,7 +16,7 @@ pub type NavItem = NavDoc | NavCat | NavLink
// --------- DOC ITEM ----------
pub struct NavDoc {
pub:
id string //is the page id
id string // is the page id
label string
}
@@ -93,17 +93,15 @@ fn nav_item_to_json(item NavItem) !NavItemJson {
}
}
// Convert entire NavConfig sidebar to JSON-serializable array
fn (nc NavConfig) sidebar_to_json() ![]NavItemJson {
// Convert entire NavConfig sidebar to JSON string
fn (nc NavConfig) sidebar_to_json() !string {
mut result := []NavItemJson{}
for item in nc.mySidebar {
for item in nc.my_sidebar {
result << nav_item_to_json(item)!
}
return result
return json.encode_pretty(result)
}
// // Convert entire NavConfig topbar to JSON-serializable array
// fn (nc NavConfig) topbar_to_json() ![]NavItemJson {
// mut result := []NavItemJson{}
@@ -122,7 +120,7 @@ fn (nc NavConfig) sidebar_to_json() ![]NavItemJson {
// return result
// }
port topbar as formatted JSON string
// port topbar as formatted JSON string
// pub fn (nc NavConfig) jsondump_topbar() !string {
// items := nc.topbar_to_json()!
// return json.encode_pretty(items)

View File

@@ -1,11 +1,12 @@
module site
// Page represents a single documentation page
pub struct Page {
pub mut:
id string //unique identifier, by default as "collection:page_name", we can overrule this from play instructions if needed
title string
description string
draft bool
hide_title bool
src string @[required] // always in format collection:page_name, can use the default collection if no : specified
id string // Unique identifier: "collection:page_name"
title string // Display title (optional, extracted from markdown if empty)
description string // Brief description for metadata
draft bool // Mark as draft (hidden from navigation)
hide_title bool // Hide the title when rendering
src string // Source reference (same as id in this format)
}

View File

@@ -3,7 +3,7 @@ module site
@[heap]
pub struct Site {
pub mut:
pages map[string]Page // key: is the id of the page
nav NavConfig //navigation of the site
siteconfig SiteConfig
pages map[string]Page // key: "collection:page_name"
nav NavConfig // Navigation sidebar configuration
siteconfig SiteConfig // Full site configuration
}

View File

@@ -4,222 +4,94 @@ import os
import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.core.texttools
import time
import incubaid.herolib.ui.console
// Main entry point for processing site HeroScript
pub fn play(mut plbook PlayBook) ! {
if !plbook.exists(filter: 'site.') {
return
}
console.print_header('Processing Site Configuration')
// ============================================================
// STEP 1: Initialize core site configuration
// ============================================================
console.print_item('Step 1: Loading site configuration')
mut config_action := plbook.ensure_once(filter: 'site.config')!
mut p := config_action.params
name := p.get_default('name', 'default')! // Use 'default' as fallback name
// configure the website
name := p.get_default('name', 'default')!
mut website := new(name: name)!
mut config := &website.siteconfig
// Load core configuration
config.name = texttools.name_fix(name)
config.title = p.get_default('title', 'Documentation Site')!
config.description = p.get_default('description', 'Comprehensive documentation built with Docusaurus.')!
config.tagline = p.get_default('tagline', 'Your awesome documentation')!
config.favicon = p.get_default('favicon', 'img/favicon.png')!
config.image = p.get_default('image', 'img/tf_graph.png')!
config.copyright = p.get_default('copyright', '© ' + time.now().year.str() +
' Example Organization')!
config.copyright = p.get_default('copyright', '© ${time.now().year} Example Organization')!
config.url = p.get_default('url', '')!
config.base_url = p.get_default('base_url', '/')!
config.url_home = p.get_default('url_home', '')!
// Process !!site.config_meta for specific metadata overrides
mut meta_action := plbook.ensure_once(filter: 'site.config_meta')!
config_action.done = true
// ============================================================
// STEP 2: Apply optional metadata overrides
// ============================================================
console.print_item('Step 2: Applying metadata overrides')
if plbook.exists_once(filter: 'site.config_meta') {
mut meta_action := plbook.get(filter: 'site.config_meta')!
mut p_meta := meta_action.params
// If 'title' is present in site.config_meta, it overrides. Otherwise, meta_title remains empty or uses site.config.title logic in docusaurus model.
config.meta_title = p_meta.get_default('title', config.title)!
// If 'image' is present in site.config_meta, it overrides. Otherwise, meta_image remains empty or uses site.config.image logic.
config.meta_image = p_meta.get_default('image', config.image)!
// If 'description' is present in site.config_meta, it overrides the main description
if p_meta.exists('description') {
config.description = p_meta.get('description')!
}
config_action.done = true // Mark the action as done
meta_action.done = true
}
play_import(mut plbook, mut config)!
play_menu(mut plbook, mut config)!
// ============================================================
// STEP 3: Configure content imports
// ============================================================
console.print_item('Step 3: Configuring content imports')
play_imports(mut plbook, mut config)!
// ============================================================
// STEP 4: Configure navigation menu
// ============================================================
console.print_item('Step 4: Configuring navigation menu')
play_navbar(mut plbook, mut config)!
// ============================================================
// STEP 5: Configure footer
// ============================================================
console.print_item('Step 5: Configuring footer')
play_footer(mut plbook, mut config)!
// ============================================================
// STEP 6: Configure announcement bar (optional)
// ============================================================
console.print_item('Step 6: Configuring announcement bar (if present)')
play_announcement(mut plbook, mut config)!
play_publish(mut plbook, mut config)!
play_publish_dev(mut plbook, mut config)!
// ============================================================
// STEP 7: Configure publish destinations
// ============================================================
console.print_item('Step 7: Configuring publish destinations')
play_publishing(mut plbook, mut config)!
// ============================================================
// STEP 8: Build pages and navigation structure
// ============================================================
console.print_item('Step 8: Processing pages and building navigation')
play_pages(mut plbook, mut website)!
}
fn play_import(mut plbook PlayBook, mut config SiteConfig) ! {
mut import_actions := plbook.find(filter: 'site.import')!
// println('import_actions: ${import_actions}')
for mut action in import_actions {
mut p := action.params
mut replace_map := map[string]string{}
if replace_str := p.get_default('replace', '') {
parts := replace_str.split(',')
for part in parts {
kv := part.split(':')
if kv.len == 2 {
replace_map[kv[0].trim_space()] = kv[1].trim_space()
}
}
}
mut importpath := p.get_default('path', '')!
if importpath != '' {
if !importpath.starts_with('/') {
importpath = os.abs_path('${plbook.path}/${importpath}')
}
}
mut import_ := ImportItem{
name: p.get_default('name', '')!
url: p.get_default('url', '')!
path: importpath
dest: p.get_default('dest', '')!
replace: replace_map
visible: p.get_default_false('visible')
}
config.imports << import_
action.done = true // Mark the action as done
}
}
fn play_menu(mut plbook PlayBook, mut config SiteConfig) ! {
mut navbar_actions := plbook.find(filter: 'site.navbar')!
if navbar_actions.len > 0 {
for mut action in navbar_actions { // Should ideally be one, but loop for safety
mut p := action.params
config.menu.title = p.get_default('title', config.title)! // Use existing config.title as ultimate fallback
config.menu.logo_alt = p.get_default('logo_alt', '')!
config.menu.logo_src = p.get_default('logo_src', '')!
config.menu.logo_src_dark = p.get_default('logo_src_dark', '')!
action.done = true // Mark the action as done
}
} else {
// Fallback to site.menu for title if site.navbar is not found
mut menu_actions := plbook.find(filter: 'site.menu')!
for mut action in menu_actions {
mut p := action.params
config.menu.title = p.get_default('title', config.title)!
config.menu.logo_alt = p.get_default('logo_alt', '')!
config.menu.logo_src = p.get_default('logo_src', '')!
config.menu.logo_src_dark = p.get_default('logo_src_dark', '')!
action.done = true // Mark the action as done
}
}
mut menu_item_actions := plbook.find(filter: 'site.navbar_item')!
if menu_item_actions.len == 0 {
// Fallback to site.menu_item if site.navbar_item is not found
menu_item_actions = plbook.find(filter: 'site.menu_item')!
}
// Clear existing menu items to prevent duplication
config.menu.items = []MenuItem{}
for mut action in menu_item_actions {
mut p := action.params
mut item := MenuItem{
label: p.get_default('label', 'Documentation')!
href: p.get_default('href', '')!
to: p.get_default('to', '')!
position: p.get_default('position', 'right')!
}
config.menu.items << item
action.done = true // Mark the action as done
}
}
fn play_footer(mut plbook PlayBook, mut config SiteConfig) ! {
mut footer_actions := plbook.find(filter: 'site.footer')!
for mut action in footer_actions {
mut p := action.params
config.footer.style = p.get_default('style', 'dark')!
action.done = true // Mark the action as done
}
mut footer_item_actions := plbook.find(filter: 'site.footer_item')!
mut links_map := map[string][]FooterItem{}
// Clear existing footer links to prevent duplication
config.footer.links = []FooterLink{}
for mut action in footer_item_actions {
mut p := action.params
title := p.get_default('title', 'Docs')!
mut item := FooterItem{
label: p.get_default('label', 'Introduction')!
href: p.get_default('href', '')!
to: p.get_default('to', '')!
}
if title !in links_map {
links_map[title] = []FooterItem{}
}
links_map[title] << item
action.done = true // Mark the action as done
}
// Convert map to footer links array
for title, items in links_map {
config.footer.links << FooterLink{
title: title
items: items
}
}
}
fn play_announcement(mut plbook PlayBook, mut config SiteConfig) ! {
mut announcement_actions := plbook.find(filter: 'site.announcement')!
if announcement_actions.len > 0 {
// Only process the first announcement action
mut action := announcement_actions[0]
mut p := action.params
config.announcement = AnnouncementBar{
id: p.get_default('id', 'announcement')!
content: p.get_default('content', '')!
background_color: p.get_default('background_color', '#20232a')!
text_color: p.get_default('text_color', '#fff')!
is_closeable: p.get_default_true('is_closeable')
}
action.done = true // Mark the action as done
}
}
fn play_publish(mut plbook PlayBook, mut config SiteConfig) ! {
mut build_dest_actions := plbook.find(filter: 'site.publish')!
for mut action in build_dest_actions {
mut p := action.params
mut dest := BuildDest{
path: p.get_default('path', '')! // can be url
ssh_name: p.get_default('ssh_name', '')!
}
config.build_dest << dest
action.done = true // Mark the action as done
}
}
fn play_publish_dev(mut plbook PlayBook, mut config SiteConfig) ! {
mut build_dest_actions := plbook.find(filter: 'site.publish_dev')!
for mut action in build_dest_actions {
mut p := action.params
mut dest := BuildDest{
path: p.get_default('path', '')! // can be url
ssh_name: p.get_default('ssh_name', '')!
}
config.build_dest_dev << dest
action.done = true // Mark the action as done
}
console.print_green('Site configuration complete')
}

View File

@@ -0,0 +1,34 @@
module site
import os
import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.core.texttools
import time
import incubaid.herolib.ui.console
// ============================================================
// ANNOUNCEMENT: Process announcement bar (optional)
// ============================================================
fn play_announcement(mut plbook PlayBook, mut config SiteConfig) ! {
mut announcement_actions := plbook.find(filter: 'site.announcement')!
if announcement_actions.len > 0 {
// Only process the first announcement action
mut action := announcement_actions[0]
mut p := action.params
content := p.get('content') or {
return error('!!site.announcement: must specify "content"')
}
config.announcement = AnnouncementBar{
id: p.get_default('id', 'announcement')!
content: content
background_color: p.get_default('background_color', '#20232a')!
text_color: p.get_default('text_color', '#fff')!
is_closeable: p.get_default_true('is_closeable')
}
action.done = true
}
}

View File

@@ -0,0 +1,62 @@
module site
import os
import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.core.texttools
import time
import incubaid.herolib.ui.console
// ============================================================
// FOOTER: Process footer configuration
// ============================================================
fn play_footer(mut plbook PlayBook, mut config SiteConfig) ! {
// Process footer style (optional)
mut footer_actions := plbook.find(filter: 'site.footer')!
for mut action in footer_actions {
mut p := action.params
config.footer.style = p.get_default('style', 'dark')!
action.done = true
}
// Process footer items (multiple)
mut footer_item_actions := plbook.find(filter: 'site.footer_item')!
mut links_map := map[string][]FooterItem{}
// Clear existing links to prevent duplication
config.footer.links = []FooterLink{}
for mut action in footer_item_actions {
mut p := action.params
title := p.get_default('title', 'Docs')!
label := p.get('label') or {
return error('!!site.footer_item: must specify "label"')
}
mut item := FooterItem{
label: label
href: p.get_default('href', '')!
to: p.get_default('to', '')!
}
// Validate that href or to is specified
if item.href.len == 0 && item.to.len == 0 {
return error('!!site.footer_item for "${label}": must specify either "href" or "to"')
}
if title !in links_map {
links_map[title] = []FooterItem{}
}
links_map[title] << item
action.done = true
}
// Convert map to footer links array
for title, items in links_map {
config.footer.links << FooterLink{
title: title
items: items
}
}
}

View File

@@ -0,0 +1,51 @@
module site
import os
import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.core.texttools
import time
import incubaid.herolib.ui.console
// ============================================================
// IMPORTS: Process content imports
// ============================================================
fn play_imports(mut plbook PlayBook, mut config SiteConfig) ! {
mut import_actions := plbook.find(filter: 'site.import')!
for mut action in import_actions {
mut p := action.params
// Parse replacement patterns (comma-separated key:value pairs)
mut replace_map := map[string]string{}
if replace_str := p.get_default('replace', '') {
parts := replace_str.split(',')
for part in parts {
kv := part.split(':')
if kv.len == 2 {
replace_map[kv[0].trim_space()] = kv[1].trim_space()
}
}
}
// Get path (can be relative to playbook path)
mut import_path := p.get_default('path', '')!
if import_path != '' {
if !import_path.starts_with('/') {
import_path = os.abs_path('${plbook.path}/${import_path}')
}
}
// Create import item
mut import_item := ImportItem{
name: p.get_default('name', '')!
url: p.get_default('url', '')!
path: import_path
dest: p.get_default('dest', '')!
replace: replace_map
visible: p.get_default_false('visible')
}
config.imports << import_item
action.done = true
}
}

View File

@@ -0,0 +1,76 @@
module site
import os
import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.core.texttools
import time
import incubaid.herolib.ui.console
// ============================================================
// Internal structure for tracking category information
// ============================================================
struct CategoryInfo {
pub mut:
name string
label string
position int
nav_items []NavItem
}
// ============================================================
// NAVBAR: Process navigation menu
// ============================================================
fn play_navbar(mut plbook PlayBook, mut config SiteConfig) ! {
// Try 'site.navbar' first, then fallback to deprecated 'site.menu'
mut navbar_actions := plbook.find(filter: 'site.navbar')!
if navbar_actions.len == 0 {
navbar_actions = plbook.find(filter: 'site.menu')!
}
// Configure navbar metadata
if navbar_actions.len > 0 {
for mut action in navbar_actions {
mut p := action.params
config.menu.title = p.get_default('title', config.title)!
config.menu.logo_alt = p.get_default('logo_alt', '')!
config.menu.logo_src = p.get_default('logo_src', '')!
config.menu.logo_src_dark = p.get_default('logo_src_dark', '')!
action.done = true
}
}
// Process navbar items
mut navbar_item_actions := plbook.find(filter: 'site.navbar_item')!
if navbar_item_actions.len == 0 {
navbar_item_actions = plbook.find(filter: 'site.menu_item')!
}
// Clear existing items to prevent duplication
config.menu.items = []MenuItem{}
for mut action in navbar_item_actions {
mut p := action.params
label := p.get('label') or {
return error('!!site.navbar_item: must specify "label"')
}
mut item := MenuItem{
label: label
href: p.get_default('href', '')!
to: p.get_default('to', '')!
position: p.get_default('position', 'right')!
}
// Validate that at least href or to is specified
if item.href.len == 0 && item.to.len == 0 {
return error('!!site.navbar_item: must specify either "href" or "to" for label "${label}"')
}
config.menu.items << item
action.done = true
}
}

View File

@@ -1,135 +0,0 @@
module site
import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.core.texttools
// plays the sections & pages
fn play_pages(mut plbook PlayBook, mut site Site) ! {
// mut siteconfig := &site.siteconfig
// if only 1 doctree is specified, then we use that as the default doctree name
// mut doctreename := 'main' // Not used for now, keep commented for future doctree integration
// if plbook.exists(filter: 'site.doctree') {
// if plbook.exists_once(filter: 'site.doctree') {
// mut action := plbook.get(filter: 'site.doctree')!
// mut p := action.params
// doctreename = p.get('name') or { return error('need to specify name in site.doctree') }
// } else {
// return error("can't have more than one site.doctree")
// }
// }
mut section_current := Section{} // is the category
mut position_section := 1
mut position_category := 100 // Start categories at position 100
mut collection_current := '' // current collection we are working on
mut all_actions := plbook.find(filter: 'site.')!
for mut action in all_actions {
if action.done {
continue
}
mut p := action.params
if action.name == 'page_category' {
mut section := Section{}
section.name = p.get('name') or {
return error('need to specify name in site.page_category. Action: ${action}')
}
position_section = 1 // go back to default position for pages in the category
section.position = p.get_int_default('position', position_category)!
if section.position == position_category {
position_category += 100 // Increment for next category
}
section.label = p.get_default('label', texttools.name_fix_snake_to_pascal(section.name))!
section.path = p.get_default('path', texttools.name_fix(section.label))!
section.description = p.get_default('description', '')!
site.sections << section
action.done = true // Mark the action as done
section_current = section
continue // next action
}
if action.name == 'page' {
mut pagesrc := p.get_default('src', '')!
mut pagename := p.get_default('name', '')!
mut pagecollection := ''
if pagesrc.contains(':') {
pagecollection = pagesrc.split(':')[0]
pagename = pagesrc.split(':')[1]
} else {
if collection_current.len > 0 {
pagecollection = collection_current
pagename = pagesrc // ADD THIS LINE - use pagesrc as the page name
} else {
return error('need to specify collection in page.src path as collection:page_name or make sure someone before you did. Got src="${pagesrc}" with no collection set. Action: ${action}')
}
}
pagecollection = texttools.name_fix(pagecollection)
collection_current = pagecollection
pagename = texttools.name_fix_keepext(pagename)
if pagename.ends_with('.md') {
pagename = pagename.replace('.md', '')
}
if pagename == '' {
return error('need to specify name in page.src or specify in path as collection:page_name. Action: ${action}')
}
if pagecollection == '' {
return error('need to specify collection in page.src or specify in path as collection:page_name. Action: ${action}')
}
// recreate the pagepath
pagesrc = '${pagecollection}:${pagename}'
// get sectionname from category, page_category or section, if not specified use current section
section_name := p.get_default('category', p.get_default('page_category', p.get_default('section',
section_current.name)!)!)!
mut pagepath := p.get_default('path', section_current.path)!
pagepath = pagepath.trim_space().trim('/')
// Only apply name_fix if it's a simple name (no path separators)
// For paths like 'appendix/internet_today', preserve the structure
if !pagepath.contains('/') {
pagepath = texttools.name_fix(pagepath)
}
// Ensure pagepath ends with / to indicate it's a directory path
if pagepath.len > 0 && !pagepath.ends_with('/') {
pagepath += '/'
}
mut mypage := Page{
section_name: section_name
name: pagename
path: pagepath
src: pagesrc
}
mypage.position = p.get_int_default('position', 0)!
if mypage.position == 0 {
mypage.position = section_current.position + position_section
position_section += 1
}
mypage.title = p.get_default('title', '')!
mypage.description = p.get_default('description', '')!
mypage.slug = p.get_default('slug', '')!
mypage.draft = p.get_default_false('draft')
mypage.hide_title = p.get_default_false('hide_title')
mypage.title_nr = p.get_int_default('title_nr', 0)!
site.pages << mypage
action.done = true // Mark the action as done
}
// println(action)
// println(section_current)
// println(site.pages.last())
// $dbg;
}
}

142
lib/web/site/play_pages.v Normal file
View File

@@ -0,0 +1,142 @@
module site
import os
import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.core.texttools
import time
import incubaid.herolib.ui.console
// ============================================================
// PAGES: Process pages and build navigation structure
// ============================================================
fn play_pages(mut plbook PlayBook, mut website Site) ! {
mut collection_current := '' // Track current collection for reuse
mut categories := map[string]CategoryInfo{} // Map of category name -> info
mut category_current := '' // Track current active category
mut root_nav_items := []NavItem{} // Root-level items (pages without category)
mut next_category_position := 100 // Auto-increment position for categories
// ============================================================
// PASS 1: Process all page and category actions
// ============================================================
mut all_actions := plbook.find(filter: 'site.')!
for mut action in all_actions {
if action.done {
continue
}
// ========== PAGE CATEGORY ==========
if action.name == 'page_category' {
mut p := action.params
category_name := p.get('name') or {
return error('!!site.page_category: must specify "name"')
}
category_name = 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 position := p.get_int_default('position', next_category_position)!
// Auto-increment position if using default
if position == next_category_position {
next_category_position += 100
}
// Create and store category info
categories[category_name] = CategoryInfo{
name: category_name
label: label
position: position
nav_items: []NavItem{}
}
category_current = category_name
action.done = true
continue
}
// ========== PAGE ==========
if action.name == 'page' {
mut p := action.params
mut page_src := p.get_default('src', '')!
mut page_collection := ''
mut page_name := ''
// Parse collection:page format from src
if page_src.contains(':') {
parts := page_src.split(':')
page_collection = texttools.name_fix(parts[0])
page_name = texttools.name_fix_keepext(parts[1])
} else {
// Use previously specified collection if available
if collection_current.len > 0 {
page_collection = collection_current
page_name = texttools.name_fix_keepext(page_src)
} else {
return error('!!site.page: must specify source as "collection:page_name" in "src".\nGot src="${page_src}" with no collection previously set.\nEither specify "collection:page_name" or define a collection first.')
}
}
// Clean up page name (remove .md if present)
if page_name.ends_with('.md') {
page_name = page_name[0..page_name.len - 3]
}
page_name = texttools.name_fix(page_name)
// Validation
if page_name.len == 0 {
return error('!!site.page: could not extract valid page name from src="${page_src}"')
}
if page_collection.len == 0 {
return error('!!site.page: could not determine collection')
}
// Store collection for subsequent pages
collection_current = page_collection
// Build page ID
page_id := '${page_collection}:${page_name}'
// Get optional page metadata
page_title := p.get_default('title', '')!
page_description := p.get_default('description', '')!
page_draft := p.get_default_false('draft')
page_hide_title := p.get_default_false('hide_title')
// Create page
mut page := Page{
id: page_id
title: page_title
description: page_description
draft: page_draft
hide_title: page_hide_title
src: page_id
}
website.pages[page_id] = page
// Create navigation item
nav_doc := NavDoc{
id: page_id
label: if page_title.len > 0 { page_title } else { page_name }
}
// Add to appropriate category or root
if category_current.len > 0 {
if category_current in categories {
mut cat_info := categories[category_current]
cat_info.nav_items << nav_doc
categories[category_current] = cat_info
}
} else {
root_nav_items << nav_doc
}
action.done = true
continue
}
}

View File

@@ -0,0 +1,46 @@
module site
import os
import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.core.texttools
import time
import incubaid.herolib.ui.console
// ============================================================
// PUBLISHING: Configure build and publish destinations
// ============================================================
fn play_publishing(mut plbook PlayBook, mut config SiteConfig) ! {
// Production publish destinations
mut build_dest_actions := plbook.find(filter: 'site.publish')!
for mut action in build_dest_actions {
mut p := action.params
path := p.get('path') or {
return error('!!site.publish: must specify "path"')
}
mut dest := BuildDest{
path: path
ssh_name: p.get_default('ssh_name', '')!
}
config.build_dest << dest
action.done = true
}
// Development publish destinations
mut build_dest_dev_actions := plbook.find(filter: 'site.publish_dev')!
for mut action in build_dest_dev_actions {
mut p := action.params
path := p.get('path') or {
return error('!!site.publish_dev: must specify "path"')
}
mut dest := BuildDest{
path: path
ssh_name: p.get_default('ssh_name', '')!
}
config.build_dest_dev << dest
action.done = true
}
}

View File

@@ -225,6 +225,75 @@ docs/
path: "/tmp/docs-preview"
```
## HeroScript Processing Order
Atlas processes HeroScript actions in a fixed order. Each step depends on previous steps:
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
### 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
!!site.config
name: "my_docs"
title: "My Documentation"
# 2. Set up navigation
!!site.navbar title: "MyDocs"
# 3. Define pages with reusable collection
!!site.page_category name: "intro"
!!site.page src: "guides:introduction"
title: "Getting Started"
!!site.page src: "setup" # Reuses "guides" collection
title: "Installation"
!!site.page src: "tutorial" # Still uses "guides"
title: "Tutorial"
# 4. Change collection when needed
!!site.page src: "api:reference"
title: "API Reference"
!!site.page src: "endpoints" # Now uses "api" collection
title: "Endpoints"
```
## Factory Methods
### Create or Get a Site