...
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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}')
|
||||
console.print_green('Site configuration complete')
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
34
lib/web/site/play_announcement.v
Normal file
34
lib/web/site/play_announcement.v
Normal 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
|
||||
}
|
||||
}
|
||||
62
lib/web/site/play_footer.v
Normal file
62
lib/web/site/play_footer.v
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
51
lib/web/site/play_imports.v
Normal file
51
lib/web/site/play_imports.v
Normal 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
|
||||
}
|
||||
}
|
||||
76
lib/web/site/play_navbar.v
Normal file
76
lib/web/site/play_navbar.v
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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
142
lib/web/site/play_pages.v
Normal 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
|
||||
}
|
||||
}
|
||||
46
lib/web/site/play_publish.v
Normal file
46
lib/web/site/play_publish.v
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user