...
This commit is contained in:
@@ -5,7 +5,7 @@ import json
|
|||||||
// Top-level config
|
// Top-level config
|
||||||
pub struct NavConfig {
|
pub struct NavConfig {
|
||||||
pub mut:
|
pub mut:
|
||||||
mySidebar []NavItem
|
my_sidebar []NavItem
|
||||||
// myTopbar []NavItem //not used yet
|
// myTopbar []NavItem //not used yet
|
||||||
// myFooter []NavItem //not used yet
|
// myFooter []NavItem //not used yet
|
||||||
}
|
}
|
||||||
@@ -16,8 +16,8 @@ pub type NavItem = NavDoc | NavCat | NavLink
|
|||||||
// --------- DOC ITEM ----------
|
// --------- DOC ITEM ----------
|
||||||
pub struct NavDoc {
|
pub struct NavDoc {
|
||||||
pub:
|
pub:
|
||||||
id string //is the page id
|
id string // is the page id
|
||||||
label string
|
label string
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------- CATEGORY ----------
|
// --------- CATEGORY ----------
|
||||||
@@ -32,9 +32,9 @@ pub mut:
|
|||||||
// --------- LINK ----------
|
// --------- LINK ----------
|
||||||
pub struct NavLink {
|
pub struct NavLink {
|
||||||
pub:
|
pub:
|
||||||
label string
|
label string
|
||||||
href string
|
href string
|
||||||
description string
|
description string
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- JSON SERIALIZATION --------
|
// -------- JSON SERIALIZATION --------
|
||||||
@@ -42,17 +42,17 @@ pub:
|
|||||||
// NavItemJson is used for JSON export with type discrimination
|
// NavItemJson is used for JSON export with type discrimination
|
||||||
pub struct NavItemJson {
|
pub struct NavItemJson {
|
||||||
pub mut:
|
pub mut:
|
||||||
type_field string @[json: 'type']
|
type_field string @[json: 'type']
|
||||||
// For doc
|
// For doc
|
||||||
id string @[omitempty]
|
id string @[omitempty]
|
||||||
label string @[omitempty]
|
label string @[omitempty]
|
||||||
// For link
|
// For link
|
||||||
href string @[omitempty]
|
href string @[omitempty]
|
||||||
description string @[omitempty]
|
description string @[omitempty]
|
||||||
// For category
|
// For category
|
||||||
collapsible bool
|
collapsible bool
|
||||||
collapsed bool
|
collapsed bool
|
||||||
items []NavItemJson @[omitempty]
|
items []NavItemJson @[omitempty]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert a single NavItem to JSON-serializable format
|
// Convert a single NavItem to JSON-serializable format
|
||||||
@@ -60,9 +60,9 @@ fn nav_item_to_json(item NavItem) !NavItemJson {
|
|||||||
return match item {
|
return match item {
|
||||||
NavDoc {
|
NavDoc {
|
||||||
NavItemJson{
|
NavItemJson{
|
||||||
type_field: 'doc'
|
type_field: 'doc'
|
||||||
id: item.id
|
id: item.id
|
||||||
label: item.label
|
label: item.label
|
||||||
collapsible: false
|
collapsible: false
|
||||||
collapsed: false
|
collapsed: false
|
||||||
}
|
}
|
||||||
@@ -93,17 +93,15 @@ fn nav_item_to_json(item NavItem) !NavItemJson {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert entire NavConfig sidebar to JSON-serializable array
|
// Convert entire NavConfig sidebar to JSON string
|
||||||
fn (nc NavConfig) sidebar_to_json() ![]NavItemJson {
|
fn (nc NavConfig) sidebar_to_json() !string {
|
||||||
mut result := []NavItemJson{}
|
mut result := []NavItemJson{}
|
||||||
for item in nc.mySidebar {
|
for item in nc.my_sidebar {
|
||||||
result << nav_item_to_json(item)!
|
result << nav_item_to_json(item)!
|
||||||
}
|
}
|
||||||
return result
|
return json.encode_pretty(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// // Convert entire NavConfig topbar to JSON-serializable array
|
// // Convert entire NavConfig topbar to JSON-serializable array
|
||||||
// fn (nc NavConfig) topbar_to_json() ![]NavItemJson {
|
// fn (nc NavConfig) topbar_to_json() ![]NavItemJson {
|
||||||
// mut result := []NavItemJson{}
|
// mut result := []NavItemJson{}
|
||||||
@@ -122,7 +120,7 @@ fn (nc NavConfig) sidebar_to_json() ![]NavItemJson {
|
|||||||
// return result
|
// return result
|
||||||
// }
|
// }
|
||||||
|
|
||||||
port topbar as formatted JSON string
|
// port topbar as formatted JSON string
|
||||||
// pub fn (nc NavConfig) jsondump_topbar() !string {
|
// pub fn (nc NavConfig) jsondump_topbar() !string {
|
||||||
// items := nc.topbar_to_json()!
|
// items := nc.topbar_to_json()!
|
||||||
// return json.encode_pretty(items)
|
// return json.encode_pretty(items)
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
module site
|
module site
|
||||||
|
|
||||||
|
// Page represents a single documentation page
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
pub mut:
|
pub mut:
|
||||||
id string //unique identifier, by default as "collection:page_name", we can overrule this from play instructions if needed
|
id string // Unique identifier: "collection:page_name"
|
||||||
title string
|
title string // Display title (optional, extracted from markdown if empty)
|
||||||
description string
|
description string // Brief description for metadata
|
||||||
draft bool
|
draft bool // Mark as draft (hidden from navigation)
|
||||||
hide_title bool
|
hide_title bool // Hide the title when rendering
|
||||||
src string @[required] // always in format collection:page_name, can use the default collection if no : specified
|
src string // Source reference (same as id in this format)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module site
|
|||||||
@[heap]
|
@[heap]
|
||||||
pub struct Site {
|
pub struct Site {
|
||||||
pub mut:
|
pub mut:
|
||||||
pages map[string]Page // key: is the id of the page
|
pages map[string]Page // key: "collection:page_name"
|
||||||
nav NavConfig //navigation of the site
|
nav NavConfig // Navigation sidebar configuration
|
||||||
siteconfig SiteConfig
|
siteconfig SiteConfig // Full site configuration
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,222 +4,94 @@ import os
|
|||||||
import incubaid.herolib.core.playbook { PlayBook }
|
import incubaid.herolib.core.playbook { PlayBook }
|
||||||
import incubaid.herolib.core.texttools
|
import incubaid.herolib.core.texttools
|
||||||
import time
|
import time
|
||||||
|
import incubaid.herolib.ui.console
|
||||||
|
|
||||||
|
// Main entry point for processing site HeroScript
|
||||||
pub fn play(mut plbook PlayBook) ! {
|
pub fn play(mut plbook PlayBook) ! {
|
||||||
if !plbook.exists(filter: 'site.') {
|
if !plbook.exists(filter: 'site.') {
|
||||||
return
|
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 config_action := plbook.ensure_once(filter: 'site.config')!
|
||||||
|
|
||||||
mut p := config_action.params
|
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 website := new(name: name)!
|
||||||
mut config := &website.siteconfig
|
mut config := &website.siteconfig
|
||||||
|
|
||||||
|
// Load core configuration
|
||||||
config.name = texttools.name_fix(name)
|
config.name = texttools.name_fix(name)
|
||||||
config.title = p.get_default('title', 'Documentation Site')!
|
config.title = p.get_default('title', 'Documentation Site')!
|
||||||
config.description = p.get_default('description', 'Comprehensive documentation built with Docusaurus.')!
|
config.description = p.get_default('description', 'Comprehensive documentation built with Docusaurus.')!
|
||||||
config.tagline = p.get_default('tagline', 'Your awesome documentation')!
|
config.tagline = p.get_default('tagline', 'Your awesome documentation')!
|
||||||
config.favicon = p.get_default('favicon', 'img/favicon.png')!
|
config.favicon = p.get_default('favicon', 'img/favicon.png')!
|
||||||
config.image = p.get_default('image', 'img/tf_graph.png')!
|
config.image = p.get_default('image', 'img/tf_graph.png')!
|
||||||
config.copyright = p.get_default('copyright', '© ' + time.now().year.str() +
|
config.copyright = p.get_default('copyright', '© ${time.now().year} Example Organization')!
|
||||||
' Example Organization')!
|
|
||||||
config.url = p.get_default('url', '')!
|
config.url = p.get_default('url', '')!
|
||||||
config.base_url = p.get_default('base_url', '/')!
|
config.base_url = p.get_default('base_url', '/')!
|
||||||
config.url_home = p.get_default('url_home', '')!
|
config.url_home = p.get_default('url_home', '')!
|
||||||
|
|
||||||
// Process !!site.config_meta for specific metadata overrides
|
config_action.done = true
|
||||||
mut meta_action := plbook.ensure_once(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)!
|
// STEP 2: Apply optional metadata overrides
|
||||||
// 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)!
|
console.print_item('Step 2: Applying metadata overrides')
|
||||||
// If 'description' is present in site.config_meta, it overrides the main description
|
if plbook.exists_once(filter: 'site.config_meta') {
|
||||||
if p_meta.exists('description') {
|
mut meta_action := plbook.get(filter: 'site.config_meta')!
|
||||||
config.description = p_meta.get('description')!
|
mut p_meta := meta_action.params
|
||||||
|
|
||||||
|
config.meta_title = p_meta.get_default('title', config.title)!
|
||||||
|
config.meta_image = p_meta.get_default('image', config.image)!
|
||||||
|
if p_meta.exists('description') {
|
||||||
|
config.description = p_meta.get('description')!
|
||||||
|
}
|
||||||
|
|
||||||
|
meta_action.done = true
|
||||||
}
|
}
|
||||||
|
|
||||||
config_action.done = true // Mark the action as done
|
// ============================================================
|
||||||
meta_action.done = true
|
// STEP 3: Configure content imports
|
||||||
|
// ============================================================
|
||||||
|
console.print_item('Step 3: Configuring content imports')
|
||||||
|
play_imports(mut plbook, mut config)!
|
||||||
|
|
||||||
play_import(mut plbook, mut config)!
|
// ============================================================
|
||||||
play_menu(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)!
|
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_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)!
|
play_pages(mut plbook, mut website)!
|
||||||
}
|
|
||||||
|
console.print_green('Site configuration complete')
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
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"
|
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
|
## Factory Methods
|
||||||
|
|
||||||
### Create or Get a Site
|
### Create or Get a Site
|
||||||
|
|||||||
Reference in New Issue
Block a user