Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1688c4f9b0 | ||
|
|
9a7b9b8a10 | ||
|
|
2b5f53b83f | ||
|
|
d65c795ccb | ||
|
|
3918148378 | ||
|
|
f5694dd4d7 | ||
|
|
5fc0909ce7 | ||
| 230c684725 | |||
| 081aafa8c6 | |||
| 25eb9bbb86 | |||
| 5d4fe2fa2f | |||
| 956c92ee79 | |||
|
|
c6ea409bd8 |
101
.github/workflows/hero_build.yml
vendored
101
.github/workflows/hero_build.yml
vendored
@@ -5,71 +5,112 @@ permissions:
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 60
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: x86_64-unknown-linux-musl
|
||||
os: alpine-latest
|
||||
short-name: linux-i64
|
||||
- target: aarch64-unknown-linux-musl
|
||||
os: alpine-latest
|
||||
short-name: linux-arm64
|
||||
arch: arm64
|
||||
- target: x86_64-linux
|
||||
os: ubuntu-latest
|
||||
- target: aarch64-linux
|
||||
os: ubuntu-24.04-arm
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
short-name: macos-arm64
|
||||
# - target: x86_64-apple-darwin
|
||||
# os: macos-13
|
||||
# short-name: macos-i64
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{ matrix.container }}
|
||||
|
||||
steps:
|
||||
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
|
||||
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
|
||||
- run: echo "🔎 The name of your branch is ${{ github.ref_name }} and your repository is ${{ github.repository }}."
|
||||
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
if: runner.os == 'macOS'
|
||||
with:
|
||||
xcode-version: latest-stable
|
||||
|
||||
- name: Check out repository code
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# - name: Setup dependencies on Alpine
|
||||
# if: runner.os == 'Linux'
|
||||
# run: |
|
||||
# sudo apk add --no-cache gcc musl-dev openssl-dev openssl-libs-static
|
||||
|
||||
# We do the workaround as described here https://github.com/Incubaid/herolib?tab=readme-ov-file#tcc-compiler-error-on-macos
|
||||
# gcc and clang also don't work on macOS due to https://github.com/vlang/v/issues/25467
|
||||
# We can change the compiler or remove this when one is fixed
|
||||
- name: Setup V & Herolib
|
||||
id: setup
|
||||
shell: bash
|
||||
run: |
|
||||
./install_v.sh
|
||||
git clone https://github.com/vlang/v
|
||||
cd v
|
||||
make
|
||||
./v symlink
|
||||
if [ "${{ runner.os }}" = "macOS" ]; then
|
||||
sudo sed -i '' '618,631d' /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/math.h
|
||||
fi
|
||||
cd -
|
||||
|
||||
mkdir -p ~/.vmodules/incubaid
|
||||
ln -s $(pwd)/lib ~/.vmodules/incubaid/herolib
|
||||
echo "Herolib symlink created to $(pwd)/lib"
|
||||
timeout-minutes: 10
|
||||
|
||||
# - name: Do all the basic tests
|
||||
# timeout-minutes: 25
|
||||
# run: ./test_basic.vsh
|
||||
|
||||
# We can't make static builds for Linux easily, since we link to libql
|
||||
# (Postgres) and this has no static version available in the Alpine
|
||||
# repos. Therefore we build dynamic binaries for both glibc and musl.
|
||||
#
|
||||
# Again we work around a bug limiting our choice of C compiler tcc won't
|
||||
# work on Alpine due to https://github.com/vlang/v/issues/24866
|
||||
# So always use gcc for Linux
|
||||
#
|
||||
# For macOS, we can only use tcc (see above), but then we hit issues using
|
||||
# the garbage collector, so disable that
|
||||
- name: Build Hero
|
||||
timeout-minutes: 15
|
||||
run: |
|
||||
set -e
|
||||
if [ "${{ runner.os }}" = "Linux" ]; then
|
||||
v -w -d use_openssl -enable-globals -cflags -cc gcc -static cli/hero.v -o cli/hero-${{ matrix.target }}
|
||||
sudo apt-get install libpq-dev
|
||||
# Build for glibc
|
||||
v -w -d use_openssl -enable-globals -cc gcc cli/hero.v -o cli/hero-${{ matrix.target }}
|
||||
|
||||
# Build for musl using Alpine in Docker
|
||||
docker run --rm \
|
||||
-v ${{ github.workspace }}/lib:/root/.vmodules/incubaid/herolib \
|
||||
-v ${{ github.workspace }}:/herolib \
|
||||
-w /herolib \
|
||||
alpine \
|
||||
sh -c '
|
||||
apk add --no-cache bash git build-base openssl-dev libpq-dev
|
||||
cd v
|
||||
make clean
|
||||
make
|
||||
./v symlink
|
||||
cd ..
|
||||
v -w -d use_openssl -enable-globals -cc gcc cli/hero.v -o cli/hero-${{ matrix.target }}-musl
|
||||
'
|
||||
|
||||
else
|
||||
v -w -d use_openssl -enable-globals cli/hero.v -o cli/hero-${{ matrix.target }}
|
||||
v -w -d use_openssl -enable-globals -gc none -cc tcc cli/hero.v -o cli/hero-${{ matrix.target }}
|
||||
fi
|
||||
|
||||
- name: Upload glibc binary
|
||||
if: runner.os == 'Linux'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: hero-${{ matrix.target }}
|
||||
path: cli/hero-${{ matrix.target }}
|
||||
|
||||
- name: Upload musl binary
|
||||
if: runner.os == 'Linux'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: hero-${{ matrix.target }}-musl
|
||||
path: cli/hero-${{ matrix.target }}-musl
|
||||
|
||||
- name: Upload
|
||||
if: runner.os != 'Linux'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: hero-${{ matrix.target }}
|
||||
@@ -80,12 +121,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
|
||||
12
.github/workflows/test.yml
vendored
12
.github/workflows/test.yml
vendored
@@ -5,24 +5,20 @@ permissions:
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: x86_64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
short-name: linux-i64
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
|
||||
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
|
||||
- run: echo "🔎 The name of your branch is ${{ github.ref_name }} and your repository is ${{ github.repository }}."
|
||||
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup V
|
||||
run: |
|
||||
|
||||
@@ -53,7 +53,7 @@ fn do() ! {
|
||||
mut cmd := Command{
|
||||
name: 'hero'
|
||||
description: 'Your HERO toolset.'
|
||||
version: '1.0.34'
|
||||
version: '1.0.36'
|
||||
}
|
||||
|
||||
// herocmds.cmd_run_add_flags(mut cmd)
|
||||
|
||||
21
examples/web/docusaurus_example.hero
Executable file
21
examples/web/docusaurus_example.hero
Executable file
@@ -0,0 +1,21 @@
|
||||
|
||||
!!docusaurus.define
|
||||
path_build: "/tmp/docusaurus_build"
|
||||
path_publish: "/tmp/docusaurus_publish"
|
||||
// reset: 1
|
||||
// install: 1
|
||||
// template_update: 1
|
||||
|
||||
!!docusaurus.add sitename:"owh_intro"
|
||||
git_url:"https://git.ourworld.tf/ourworld_holding/docs_owh/src/branch/main/ebooks/owh_intro"
|
||||
git_root:"/tmp/code"
|
||||
git_reset:1
|
||||
git_pull:1
|
||||
play:true
|
||||
|
||||
// !!docusaurus.build
|
||||
|
||||
!!docusaurus.dev site:"owh_intro" open:true
|
||||
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import incubaid.herolib.core.playcmds
|
||||
|
||||
playcmds.run(
|
||||
heroscript: '
|
||||
!!docusaurus.define
|
||||
path_build: "/tmp/docusaurus_build"
|
||||
path_publish: "/tmp/docusaurus_publish"
|
||||
// reset: 1
|
||||
// install: 1
|
||||
// template_update: 1
|
||||
|
||||
!!docusaurus.add sitename:"owh_intro"
|
||||
git_url:"https://git.ourworld.tf/ourworld_holding/docs_owh/src/branch/main/ebooks/owh_intro"
|
||||
git_root:"/tmp/code"
|
||||
git_reset:1
|
||||
git_pull:1
|
||||
play:true
|
||||
|
||||
// !!docusaurus.build
|
||||
|
||||
!!docusaurus.dev site:"owh_intro" open:true
|
||||
'
|
||||
)!
|
||||
99
examples/web/site/site_example.vsh
Executable file
99
examples/web/site/site_example.vsh
Executable file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import incubaid.herolib.develop.gittools
|
||||
import incubaid.herolib.web.site
|
||||
import incubaid.herolib.core.playcmds
|
||||
|
||||
// Example 1: Load site configuration from a Git repository
|
||||
println('=== Example 1: Loading from Git Repository ===')
|
||||
url := 'https://git.ourworld.tf/tfgrid/docs_tfgrid4/src/branch/main/ebooks/tech'
|
||||
|
||||
mysitepath := gittools.path(
|
||||
git_url: url
|
||||
// git_pull: true
|
||||
// git_reset: true // Uncomment to reset to latest
|
||||
)!
|
||||
|
||||
// Process all HeroScript files in the repository
|
||||
playcmds.run(heroscript_path: mysitepath.path)!
|
||||
|
||||
// Get the configured site
|
||||
mut mysite := site.get(name: 'tfgrid_tech')!
|
||||
println('Site loaded: ${mysite.siteconfig.name}')
|
||||
println('Title: ${mysite.siteconfig.title}')
|
||||
println('Pages: ${mysite.pages.len}')
|
||||
println('Sections: ${mysite.sections.len}')
|
||||
println('')
|
||||
|
||||
// Example 2: Inspect site structure
|
||||
println('=== Example 2: Site Structure ===')
|
||||
println('Sections:')
|
||||
for section in mysite.sections {
|
||||
println(' - ${section.label} (${section.name})')
|
||||
}
|
||||
println('')
|
||||
|
||||
println('Pages (first 5):')
|
||||
for i, page in mysite.pages {
|
||||
if i >= 5 {
|
||||
break
|
||||
}
|
||||
println(' - ${page.name}: ${page.description}')
|
||||
println(' Section: ${page.section_name}, Position: ${page.position}')
|
||||
}
|
||||
println('')
|
||||
|
||||
// Example 3: Access menu configuration
|
||||
println('=== Example 3: Navigation Menu ===')
|
||||
println('Menu Title: ${mysite.siteconfig.menu.title}')
|
||||
println('Menu Items:')
|
||||
for item in mysite.siteconfig.menu.items {
|
||||
if item.href != '' {
|
||||
println(' - ${item.label} -> ${item.href} (external)')
|
||||
} else {
|
||||
println(' - ${item.label} -> ${item.to} (internal)')
|
||||
}
|
||||
}
|
||||
println('')
|
||||
|
||||
// Example 4: Access footer configuration
|
||||
println('=== Example 4: Footer Configuration ===')
|
||||
println('Footer Style: ${mysite.siteconfig.footer.style}')
|
||||
println('Footer Links:')
|
||||
for link in mysite.siteconfig.footer.links {
|
||||
println(' ${link.title}:')
|
||||
for item in link.items {
|
||||
if item.href != '' {
|
||||
println(' - ${item.label} -> ${item.href}')
|
||||
} else {
|
||||
println(' - ${item.label} -> ${item.to}')
|
||||
}
|
||||
}
|
||||
}
|
||||
println('')
|
||||
|
||||
// Example 5: List all configured sites
|
||||
println('=== Example 5: All Configured Sites ===')
|
||||
all_sites := site.list()
|
||||
println('Total sites: ${all_sites.len}')
|
||||
for site_name in all_sites {
|
||||
println(' - ${site_name}')
|
||||
}
|
||||
println('')
|
||||
|
||||
// Example 6: Check if a site exists
|
||||
println('=== Example 6: Site Existence Check ===')
|
||||
if site.exists(name: 'tfgrid_tech') {
|
||||
println('Site "tfgrid_tech" exists')
|
||||
} else {
|
||||
println('Site "tfgrid_tech" does not exist')
|
||||
}
|
||||
|
||||
if site.exists(name: 'nonexistent_site') {
|
||||
println('Site "nonexistent_site" exists')
|
||||
} else {
|
||||
println('Site "nonexistent_site" does not exist')
|
||||
}
|
||||
println('')
|
||||
|
||||
println('=== Example Complete ===')
|
||||
@@ -4,7 +4,7 @@ set -e
|
||||
|
||||
os_name="$(uname -s)"
|
||||
arch_name="$(uname -m)"
|
||||
version='1.0.34'
|
||||
version='1.0.36'
|
||||
|
||||
|
||||
# Base URL for GitHub releases
|
||||
|
||||
@@ -9,7 +9,7 @@ import cli { Command, Flag }
|
||||
|
||||
pub fn cmd_docusaurus(mut cmdroot Command) Command {
|
||||
mut cmd_run := Command{
|
||||
name: 'docusaurus'
|
||||
name: 'docs'
|
||||
description: 'Generate, build, run docusaurus sites.'
|
||||
required_args: 0
|
||||
execute: cmd_docusaurus_execute
|
||||
|
||||
@@ -42,7 +42,6 @@ you can now just execute this script and hero will interprete the content
|
||||
```v
|
||||
|
||||
import incubaid.herolib.core.playbook
|
||||
import incubaid.herolib.core.playcmds
|
||||
|
||||
mut plbook := playbook.new(path: "....") or { panic(err) }
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# how to use the playcmds
|
||||
|
||||
## simplified
|
||||
|
||||
```v
|
||||
import incubaid.herolib.core.playcmds
|
||||
|
||||
@@ -11,3 +13,29 @@ playcmds.run(
|
||||
)!
|
||||
|
||||
```
|
||||
|
||||
## with more control
|
||||
|
||||
```v
|
||||
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import incubaid.herolib.develop.gittools
|
||||
import incubaid.herolib.web.site
|
||||
import incubaid.herolib.core.playcmds
|
||||
|
||||
url := "https://git.ourworld.tf/tfgrid/docs_tfgrid4/src/branch/main/ebooks/tech"
|
||||
|
||||
mysitepath := gittools.path(
|
||||
// git_pull: true
|
||||
// git_reset: true
|
||||
git_url: url
|
||||
git_root: '/tmp/code'
|
||||
)!
|
||||
|
||||
playcmds.run(heroscript_path:mysitepath.path)!
|
||||
|
||||
mut mysite:=site.get(name:"tfgrid_tech")!
|
||||
println(mysite)
|
||||
|
||||
|
||||
```
|
||||
@@ -1,7 +1,6 @@
|
||||
module data
|
||||
|
||||
import incubaid.herolib.core.texttools
|
||||
import incubaid.herolib.core.base
|
||||
import incubaid.herolib.data.markdown.elements
|
||||
import incubaid.herolib.data.doctree.pointer
|
||||
|
||||
@@ -33,27 +32,11 @@ pub fn (page Page) process_links(paths map[string]string) ![]string {
|
||||
doc.linked_pages << ptr.str()
|
||||
}
|
||||
|
||||
// Check if Docusaurus-specific paths are available in Redis
|
||||
mut context := base.context() or { base.context_new()! }
|
||||
mut redis := context.redis() or { panic('Redis not available') }
|
||||
|
||||
// Try to get Docusaurus-specific path from Redis
|
||||
if docusaurus_path := redis.hget('doctree_docusaurus_paths', ptr.str()) {
|
||||
// Use Docusaurus path (already without .md extension)
|
||||
// Ensure it starts with / for absolute path
|
||||
if docusaurus_path.starts_with('/') {
|
||||
path = docusaurus_path
|
||||
} else {
|
||||
path = '/' + docusaurus_path
|
||||
}
|
||||
if ptr.collection == page.collection_name {
|
||||
// same directory
|
||||
path = './' + path.all_after_first('/')
|
||||
} else {
|
||||
// Fall back to default behavior: relative paths with .md
|
||||
if ptr.collection == page.collection_name {
|
||||
// same directory
|
||||
path = './' + path.all_after_first('/')
|
||||
} else {
|
||||
path = '../${path}'
|
||||
}
|
||||
path = '../${path}'
|
||||
}
|
||||
|
||||
if ptr.cat == .image && element.extra.trim_space() != '' {
|
||||
|
||||
@@ -9,3 +9,4 @@
|
||||
build: true
|
||||
startupmanager: true
|
||||
supported_platforms: ""
|
||||
// active: false
|
||||
|
||||
@@ -15,16 +15,15 @@ For quick setup and development, use the hero command:
|
||||
|
||||
```bash
|
||||
# Start development server
|
||||
hero docusaurus -d -path /path/to/your/site
|
||||
hero docs -d -path /path/to/your/site
|
||||
|
||||
# Build for production
|
||||
hero docusaurus -b -path /path/to/your/site
|
||||
hero docs -b -path /path/to/your/site
|
||||
|
||||
# Build and publish
|
||||
hero docusaurus -bp -path /path/to/your/site
|
||||
hero docs -bp -path /path/to/your/site
|
||||
```
|
||||
|
||||
|
||||
### Example HeroScript
|
||||
|
||||
```heroscript
|
||||
|
||||
536
lib/web/site/ai_instructions.md
Normal file
536
lib/web/site/ai_instructions.md
Normal file
@@ -0,0 +1,536 @@
|
||||
# AI Instructions for Site Module HeroScript
|
||||
|
||||
This document provides comprehensive instructions for AI agents working with the Site module's HeroScript format.
|
||||
|
||||
## HeroScript Format Overview
|
||||
|
||||
HeroScript is a declarative configuration language with the following characteristics:
|
||||
|
||||
### Basic Syntax
|
||||
|
||||
```heroscript
|
||||
!!actor.action
|
||||
param1: "value1"
|
||||
param2: "value2"
|
||||
multiline_param: "
|
||||
This is a multiline value.
|
||||
It can span multiple lines.
|
||||
"
|
||||
arg1 arg2 // Arguments without keys
|
||||
```
|
||||
|
||||
**Key Rules:**
|
||||
1. Actions start with `!!` followed by `actor.action` format
|
||||
2. Parameters are indented and use `key: "value"` or `key: value` format
|
||||
3. Values with spaces must be quoted
|
||||
4. Multiline values are supported with quotes
|
||||
5. Arguments without keys are space-separated
|
||||
6. Comments start with `//`
|
||||
|
||||
## Site Module Actions
|
||||
|
||||
### 1. Site Configuration (`!!site.config`)
|
||||
|
||||
**Purpose:** Define the main site configuration including title, description, and metadata.
|
||||
|
||||
**Required Parameters:**
|
||||
- `name`: Site identifier (will be normalized to snake_case)
|
||||
|
||||
**Optional Parameters:**
|
||||
- `title`: Site title (default: "Documentation Site")
|
||||
- `description`: Site description
|
||||
- `tagline`: Site tagline
|
||||
- `favicon`: Path to favicon (default: "img/favicon.png")
|
||||
- `image`: Default site image (default: "img/tf_graph.png")
|
||||
- `copyright`: Copyright text
|
||||
- `url`: Main site URL
|
||||
- `base_url`: Base URL path (default: "/")
|
||||
- `url_home`: Home page path
|
||||
|
||||
**Example:**
|
||||
```heroscript
|
||||
!!site.config
|
||||
name: "my_documentation"
|
||||
title: "My Documentation Site"
|
||||
description: "Comprehensive technical documentation"
|
||||
tagline: "Learn everything you need"
|
||||
url: "https://docs.example.com"
|
||||
base_url: "/"
|
||||
```
|
||||
|
||||
**AI Guidelines:**
|
||||
- Always include `name` parameter
|
||||
- Use descriptive titles and descriptions
|
||||
- Ensure URLs are properly formatted with protocol
|
||||
|
||||
### 2. Metadata Configuration (`!!site.config_meta`)
|
||||
|
||||
**Purpose:** Override specific metadata for SEO purposes.
|
||||
|
||||
**Optional Parameters:**
|
||||
- `title`: SEO-specific title (overrides site.config title for meta tags)
|
||||
- `image`: SEO-specific image (overrides site.config image for og:image)
|
||||
- `description`: SEO-specific description
|
||||
|
||||
**Example:**
|
||||
```heroscript
|
||||
!!site.config_meta
|
||||
title: "My Docs - Complete Guide"
|
||||
image: "img/social-preview.png"
|
||||
description: "The ultimate guide to using our platform"
|
||||
```
|
||||
|
||||
**AI Guidelines:**
|
||||
- Use only when SEO metadata needs to differ from main config
|
||||
- Keep titles concise for social media sharing
|
||||
- Use high-quality images for social previews
|
||||
|
||||
### 3. Navigation Bar (`!!site.navbar` or `!!site.menu`)
|
||||
|
||||
**Purpose:** Configure the main navigation bar.
|
||||
|
||||
**Optional Parameters:**
|
||||
- `title`: Navigation title (defaults to site.config title)
|
||||
- `logo_alt`: Logo alt text
|
||||
- `logo_src`: Logo image path
|
||||
- `logo_src_dark`: Dark mode logo path
|
||||
|
||||
**Example:**
|
||||
```heroscript
|
||||
!!site.navbar
|
||||
title: "My Site"
|
||||
logo_alt: "My Site Logo"
|
||||
logo_src: "img/logo.svg"
|
||||
logo_src_dark: "img/logo-dark.svg"
|
||||
```
|
||||
|
||||
**AI Guidelines:**
|
||||
- Use `!!site.navbar` for modern syntax (preferred)
|
||||
- `!!site.menu` is supported for backward compatibility
|
||||
- Provide both light and dark logos when possible
|
||||
|
||||
### 4. Navigation Items (`!!site.navbar_item` or `!!site.menu_item`)
|
||||
|
||||
**Purpose:** Add items to the navigation bar.
|
||||
|
||||
**Required Parameters (one of):**
|
||||
- `to`: Internal link path
|
||||
- `href`: External URL
|
||||
|
||||
**Optional Parameters:**
|
||||
- `label`: Display text (required in practice)
|
||||
- `position`: "left" or "right" (default: "right")
|
||||
|
||||
**Example:**
|
||||
```heroscript
|
||||
!!site.navbar_item
|
||||
label: "Documentation"
|
||||
to: "docs/intro"
|
||||
position: "left"
|
||||
|
||||
!!site.navbar_item
|
||||
label: "GitHub"
|
||||
href: "https://github.com/myorg/repo"
|
||||
position: "right"
|
||||
```
|
||||
|
||||
**AI Guidelines:**
|
||||
- Use `to` for internal navigation
|
||||
- Use `href` for external links
|
||||
- Position important items on the left, secondary items on the right
|
||||
|
||||
### 5. Footer Configuration (`!!site.footer`)
|
||||
|
||||
**Purpose:** Configure footer styling.
|
||||
|
||||
**Optional Parameters:**
|
||||
- `style`: "dark" or "light" (default: "dark")
|
||||
|
||||
**Example:**
|
||||
```heroscript
|
||||
!!site.footer
|
||||
style: "dark"
|
||||
```
|
||||
|
||||
### 6. Footer Items (`!!site.footer_item`)
|
||||
|
||||
**Purpose:** Add links to the footer, grouped by title.
|
||||
|
||||
**Required Parameters:**
|
||||
- `title`: Group title (items with same title are grouped together)
|
||||
- `label`: Link text
|
||||
|
||||
**Required Parameters (one of):**
|
||||
- `to`: Internal link path
|
||||
- `href`: External URL
|
||||
|
||||
**Example:**
|
||||
```heroscript
|
||||
!!site.footer_item
|
||||
title: "Docs"
|
||||
label: "Introduction"
|
||||
to: "intro"
|
||||
|
||||
!!site.footer_item
|
||||
title: "Docs"
|
||||
label: "API Reference"
|
||||
to: "api"
|
||||
|
||||
!!site.footer_item
|
||||
title: "Community"
|
||||
label: "Discord"
|
||||
href: "https://discord.gg/example"
|
||||
```
|
||||
|
||||
**AI Guidelines:**
|
||||
- Group related links under the same title
|
||||
- Use consistent title names across related items
|
||||
- Provide both internal and external links as appropriate
|
||||
|
||||
### 7. Page Categories (`!!site.page_category`)
|
||||
|
||||
**Purpose:** Create a section/category to organize pages.
|
||||
|
||||
**Required Parameters:**
|
||||
- `name`: Category identifier (snake_case)
|
||||
|
||||
**Optional Parameters:**
|
||||
- `label`: Display name (auto-generated from name if not provided)
|
||||
- `position`: Manual sort order (auto-incremented if not specified)
|
||||
- `path`: URL path segment (defaults to normalized label)
|
||||
|
||||
**Example:**
|
||||
```heroscript
|
||||
!!site.page_category
|
||||
name: "getting_started"
|
||||
label: "Getting Started"
|
||||
position: 100
|
||||
|
||||
!!site.page_category
|
||||
name: "advanced_topics"
|
||||
label: "Advanced Topics"
|
||||
```
|
||||
|
||||
**AI Guidelines:**
|
||||
- Use descriptive snake_case names
|
||||
- Let label be auto-generated when possible (name_fix converts to Title Case)
|
||||
- Categories persist for all subsequent pages until a new category is declared
|
||||
- Position values should leave gaps (100, 200, 300) for future insertions
|
||||
|
||||
### 8. Pages (`!!site.page`)
|
||||
|
||||
**Purpose:** Define individual pages in the site.
|
||||
|
||||
**Required Parameters:**
|
||||
- `src`: Source reference as `collection:page_name` (required for first page in a collection)
|
||||
|
||||
**Optional Parameters:**
|
||||
- `name`: Page identifier (extracted from src if not provided)
|
||||
- `title`: Page title (extracted from markdown if not provided)
|
||||
- `description`: Page description for metadata
|
||||
- `slug`: Custom URL slug
|
||||
- `position`: Manual sort order (auto-incremented if not specified)
|
||||
- `draft`: Mark as draft (default: false)
|
||||
- `hide_title`: Hide title in rendering (default: false)
|
||||
- `path`: Custom path (defaults to current category name)
|
||||
- `category`: Override current category
|
||||
- `title_nr`: Title numbering level
|
||||
|
||||
**Example:**
|
||||
```heroscript
|
||||
!!site.page src: "docs:introduction"
|
||||
description: "Introduction to the platform"
|
||||
slug: "/"
|
||||
|
||||
!!site.page src: "quickstart"
|
||||
description: "Get started in 5 minutes"
|
||||
|
||||
!!site.page src: "installation"
|
||||
title: "Installation Guide"
|
||||
description: "How to install and configure"
|
||||
position: 10
|
||||
```
|
||||
|
||||
**AI Guidelines:**
|
||||
- **Collection Persistence:** Specify collection once (e.g., `docs:introduction`), then subsequent pages only need page name (e.g., `quickstart`)
|
||||
- **Category Persistence:** Pages belong to the most recently declared category
|
||||
- **Title Extraction:** Prefer extracting titles from markdown files
|
||||
- **Position Management:** Use automatic positioning unless specific order is required
|
||||
- **Description Required:** Always provide descriptions for SEO
|
||||
- **Slug Usage:** Use slug for special pages like homepage (`slug: "/"`)
|
||||
|
||||
### 9. Import External Content (`!!site.import`)
|
||||
|
||||
**Purpose:** Import content from external sources.
|
||||
|
||||
**Optional Parameters:**
|
||||
- `name`: Import identifier
|
||||
- `url`: Git URL or HTTP URL
|
||||
- `path`: Local file system path
|
||||
- `dest`: Destination path in site
|
||||
- `replace`: Comma-separated key:value pairs for variable replacement
|
||||
- `visible`: Whether imported content is visible (default: true)
|
||||
|
||||
**Example:**
|
||||
```heroscript
|
||||
!!site.import
|
||||
url: "https://github.com/example/docs"
|
||||
dest: "external"
|
||||
replace: "VERSION:1.0.0,PROJECT:MyProject"
|
||||
visible: true
|
||||
```
|
||||
|
||||
**AI Guidelines:**
|
||||
- Use for shared documentation across multiple sites
|
||||
- Replace variables using `${VARIABLE}` syntax in source content
|
||||
- Set `visible: false` for imported templates or partials
|
||||
|
||||
### 10. Publish Destinations (`!!site.publish` and `!!site.publish_dev`)
|
||||
|
||||
**Purpose:** Define where to publish the built site.
|
||||
|
||||
**Optional Parameters:**
|
||||
- `path`: File system path or URL
|
||||
- `ssh_name`: SSH connection name for remote deployment
|
||||
|
||||
**Example:**
|
||||
```heroscript
|
||||
!!site.publish
|
||||
path: "/var/www/html/docs"
|
||||
ssh_name: "production_server"
|
||||
|
||||
!!site.publish_dev
|
||||
path: "/tmp/docs-preview"
|
||||
```
|
||||
|
||||
**AI Guidelines:**
|
||||
- Use `!!site.publish` for production deployments
|
||||
- Use `!!site.publish_dev` for development/preview deployments
|
||||
- Can specify multiple destinations
|
||||
|
||||
## File Organization Best Practices
|
||||
|
||||
### Naming Convention
|
||||
|
||||
Use numeric prefixes to control execution order:
|
||||
|
||||
```
|
||||
0_config.heroscript # Site configuration
|
||||
1_navigation.heroscript # Menu and footer
|
||||
2_intro.heroscript # Introduction pages
|
||||
3_guides.heroscript # User guides
|
||||
4_reference.heroscript # API reference
|
||||
```
|
||||
|
||||
**AI Guidelines:**
|
||||
- Always use numeric prefixes (0_, 1_, 2_, etc.)
|
||||
- Leave gaps in numbering (0, 10, 20) for future insertions
|
||||
- Group related configurations in the same file
|
||||
- Process order matters: config → navigation → pages
|
||||
|
||||
### Execution Order Rules
|
||||
|
||||
1. **Configuration First:** `!!site.config` must be processed before other actions
|
||||
2. **Categories Before Pages:** Declare `!!site.page_category` before pages in that category
|
||||
3. **Collection Persistence:** First page in a collection must specify `collection:page_name`
|
||||
4. **Category Persistence:** Pages inherit the most recent category declaration
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern 1: Simple Documentation Site
|
||||
|
||||
```heroscript
|
||||
!!site.config
|
||||
name: "simple_docs"
|
||||
title: "Simple Documentation"
|
||||
|
||||
!!site.navbar
|
||||
title: "Simple Docs"
|
||||
|
||||
!!site.page src: "docs:index"
|
||||
description: "Welcome page"
|
||||
slug: "/"
|
||||
|
||||
!!site.page src: "getting-started"
|
||||
description: "Getting started guide"
|
||||
|
||||
!!site.page src: "api"
|
||||
description: "API reference"
|
||||
```
|
||||
|
||||
### Pattern 2: Multi-Section Documentation
|
||||
|
||||
```heroscript
|
||||
!!site.config
|
||||
name: "multi_section_docs"
|
||||
title: "Complete Documentation"
|
||||
|
||||
!!site.page_category
|
||||
name: "introduction"
|
||||
label: "Introduction"
|
||||
|
||||
!!site.page src: "docs:welcome"
|
||||
description: "Welcome to our documentation"
|
||||
|
||||
!!site.page src: "overview"
|
||||
description: "Platform overview"
|
||||
|
||||
!!site.page_category
|
||||
name: "tutorials"
|
||||
label: "Tutorials"
|
||||
|
||||
!!site.page src: "tutorial_basics"
|
||||
description: "Basic tutorial"
|
||||
|
||||
!!site.page src: "tutorial_advanced"
|
||||
description: "Advanced tutorial"
|
||||
```
|
||||
|
||||
### Pattern 3: Complex Site with External Links
|
||||
|
||||
```heroscript
|
||||
!!site.config
|
||||
name: "complex_site"
|
||||
title: "Complex Documentation Site"
|
||||
url: "https://docs.example.com"
|
||||
|
||||
!!site.navbar
|
||||
title: "My Platform"
|
||||
logo_src: "img/logo.svg"
|
||||
|
||||
!!site.navbar_item
|
||||
label: "Docs"
|
||||
to: "docs/intro"
|
||||
position: "left"
|
||||
|
||||
!!site.navbar_item
|
||||
label: "API"
|
||||
to: "api"
|
||||
position: "left"
|
||||
|
||||
!!site.navbar_item
|
||||
label: "GitHub"
|
||||
href: "https://github.com/example/repo"
|
||||
position: "right"
|
||||
|
||||
!!site.footer
|
||||
style: "dark"
|
||||
|
||||
!!site.footer_item
|
||||
title: "Documentation"
|
||||
label: "Getting Started"
|
||||
to: "docs/intro"
|
||||
|
||||
!!site.footer_item
|
||||
title: "Community"
|
||||
label: "Discord"
|
||||
href: "https://discord.gg/example"
|
||||
|
||||
!!site.page_category
|
||||
name: "getting_started"
|
||||
|
||||
!!site.page src: "docs:introduction"
|
||||
description: "Introduction to the platform"
|
||||
slug: "/"
|
||||
|
||||
!!site.page src: "installation"
|
||||
description: "Installation guide"
|
||||
```
|
||||
|
||||
## Error Prevention
|
||||
|
||||
### Common Mistakes to Avoid
|
||||
|
||||
1. **Missing Collection on First Page:**
|
||||
```heroscript
|
||||
# WRONG - no collection specified
|
||||
!!site.page src: "introduction"
|
||||
|
||||
# CORRECT
|
||||
!!site.page src: "docs:introduction"
|
||||
```
|
||||
|
||||
2. **Category Without Name:**
|
||||
```heroscript
|
||||
# WRONG - missing name
|
||||
!!site.page_category
|
||||
label: "Getting Started"
|
||||
|
||||
# CORRECT
|
||||
!!site.page_category
|
||||
name: "getting_started"
|
||||
label: "Getting Started"
|
||||
```
|
||||
|
||||
3. **Missing Description:**
|
||||
```heroscript
|
||||
# WRONG - no description
|
||||
!!site.page src: "docs:intro"
|
||||
|
||||
# CORRECT
|
||||
!!site.page src: "docs:intro"
|
||||
description: "Introduction to the platform"
|
||||
```
|
||||
|
||||
4. **Incorrect File Ordering:**
|
||||
```
|
||||
# WRONG - pages before config
|
||||
pages.heroscript
|
||||
config.heroscript
|
||||
|
||||
# CORRECT - config first
|
||||
0_config.heroscript
|
||||
1_pages.heroscript
|
||||
```
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
When generating HeroScript for the Site module, verify:
|
||||
|
||||
- [ ] `!!site.config` includes `name` parameter
|
||||
- [ ] All pages have `description` parameter
|
||||
- [ ] First page in each collection specifies `collection:page_name`
|
||||
- [ ] Categories are declared before their pages
|
||||
- [ ] Files use numeric prefixes for ordering
|
||||
- [ ] Navigation items have either `to` or `href`
|
||||
- [ ] Footer items are grouped by `title`
|
||||
- [ ] External URLs include protocol (https://)
|
||||
- [ ] Paths don't have trailing slashes unless intentional
|
||||
- [ ] Draft pages are marked with `draft: true`
|
||||
|
||||
## Integration with V Code
|
||||
|
||||
When working with the Site module in V code:
|
||||
|
||||
```v
|
||||
import incubaid.herolib.web.site
|
||||
import incubaid.herolib.core.playbook
|
||||
|
||||
// Process HeroScript files
|
||||
mut plbook := playbook.new(path: '/path/to/heroscripts')!
|
||||
site.play(mut plbook)!
|
||||
|
||||
// Access configured site
|
||||
mut mysite := site.get(name: 'my_site')!
|
||||
|
||||
// Iterate through pages
|
||||
for page in mysite.pages {
|
||||
println('Page: ${page.name} - ${page.description}')
|
||||
}
|
||||
|
||||
// Iterate through sections
|
||||
for section in mysite.sections {
|
||||
println('Section: ${section.label}')
|
||||
}
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
The Site module's HeroScript format provides a declarative way to configure websites with:
|
||||
- Clear separation of concerns (config, navigation, content)
|
||||
- Automatic ordering and organization
|
||||
- Collection and category persistence for reduced repetition
|
||||
- Flexible metadata and SEO configuration
|
||||
- Support for both internal and external content
|
||||
|
||||
Always follow the execution order rules, use numeric file prefixes, and provide complete metadata for best results.
|
||||
@@ -1,100 +0,0 @@
|
||||
!!site.config
|
||||
name:"depin"
|
||||
description:"ThreeFold is laying the foundation for a geo aware Web 4, the next generation of the Internet."
|
||||
tagline:"Geo Aware Internet Platform"
|
||||
favicon:"img/favicon.png"
|
||||
image:"img/tf_graph.png"
|
||||
copyright:"ThreeFold"
|
||||
|
||||
!!site.menu
|
||||
title:"ThreeFold DePIN Tech"
|
||||
logo_alt:"ThreeFold Logo"
|
||||
logo_src:"img/logo.svg"
|
||||
logo_src_dark:"img/new_logo_tft.png"
|
||||
|
||||
!!site.menu_item
|
||||
label:"ThreeFold.io"
|
||||
href:"https://threefold.io"
|
||||
position:"right"
|
||||
|
||||
!!site.menu_item
|
||||
label:"Mycelium Network"
|
||||
href:"https://mycelium.threefold.io/"
|
||||
position:"right"
|
||||
|
||||
!!site.menu_item
|
||||
label:"AI Box"
|
||||
href:"https://aibox.threefold.io/"
|
||||
position:"right"
|
||||
|
||||
!!site.footer
|
||||
style:"dark"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Docs"
|
||||
label:"Introduction"
|
||||
to:"intro"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Docs"
|
||||
label:"Litepaper"
|
||||
href:"https://docs.threefold.io/docs/litepaper/"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Docs"
|
||||
label:"Roadmap"
|
||||
href:"https://docs.threefold.io/docs/roadmap"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Docs"
|
||||
label:"Manual"
|
||||
href:"https://manual.grid.tf/"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Features"
|
||||
label:"Become a Farmer"
|
||||
href:"https://docs.threefold.io/docs/category/become-a-farmer"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Features"
|
||||
label:"Components"
|
||||
href:"https://docs.threefold.io/docs/category/components"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Features"
|
||||
label:"Technology"
|
||||
href:"https://threefold.info/tech/"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Features"
|
||||
label:"Tokenomics"
|
||||
href:"https://docs.threefold.io/docs/tokens/tokenomics"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Web"
|
||||
label:"ThreeFold.io"
|
||||
href:"https://threefold.io"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Web"
|
||||
label:"Dashboard"
|
||||
href:"https://dashboard.grid.tf"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Web"
|
||||
label:"GitHub"
|
||||
href:"https://github.com/threefoldtech/home"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Web"
|
||||
label:"Mycelium Network"
|
||||
href:"https://mycelium.threefold.io/"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Web"
|
||||
label:"AI Box"
|
||||
href:"https://www2.aibox.threefold.io/"
|
||||
|
||||
!!site.collections
|
||||
url:"https://github.com/example/external-docs"
|
||||
replace:"PROJECT_NAME:My Project, VERSION:1.0.0"
|
||||
@@ -1,18 +0,0 @@
|
||||
!!site.page name:intro
|
||||
description:"ThreeFold is laying the foundation for a geo aware Web 4, the next generation of the Internet."
|
||||
|
||||
//next is example where we use all properties, folder is where the page is located, prio is the order of the page, if not used the filled in from order in which we parse this config file
|
||||
!!site.page name:mycelium draft:true folder:"/specs/components" prio:4
|
||||
content:"the page content itself, only for small pages"
|
||||
title:"Mycelium as Title"
|
||||
description:"..."
|
||||
|
||||
!!site.page name:fungistor folder:"/specs/components" prio:1
|
||||
src:"mycollection:mycelium.md"
|
||||
title:"fungistor as Title"
|
||||
description:"...."
|
||||
|
||||
!!site.page name:fungistor folder:"/specs/components" prio:1
|
||||
src:"mycollection:mycelium" //can be without .md
|
||||
title:"fungistor as Title"
|
||||
description:"..."
|
||||
@@ -1,29 +1,16 @@
|
||||
module site
|
||||
|
||||
@[heap]
|
||||
pub struct Site {
|
||||
pub mut:
|
||||
pages []Page
|
||||
sections []Section
|
||||
siteconfig SiteConfig
|
||||
}
|
||||
|
||||
pub struct Page {
|
||||
pub mut:
|
||||
name string
|
||||
title string
|
||||
description string
|
||||
draft bool
|
||||
position int
|
||||
hide_title bool
|
||||
src string @[required] // always in format collection:page_name
|
||||
path string @[required]
|
||||
src string @[required] // always in format collection:page_name, can use the default collection if no : specified
|
||||
path string @[required] //is without the page name, so just the path to the folder where the page is in
|
||||
section_name string
|
||||
title_nr int
|
||||
slug string
|
||||
}
|
||||
|
||||
pub struct Section {
|
||||
pub mut:
|
||||
position int
|
||||
path string
|
||||
label string
|
||||
}
|
||||
17
lib/web/site/model_site_section.v
Normal file
17
lib/web/site/model_site_section.v
Normal file
@@ -0,0 +1,17 @@
|
||||
module site
|
||||
|
||||
@[heap]
|
||||
pub struct Site {
|
||||
pub mut:
|
||||
pages []Page
|
||||
sections []Section
|
||||
siteconfig SiteConfig
|
||||
}
|
||||
|
||||
pub struct Section {
|
||||
pub mut:
|
||||
name string
|
||||
position int
|
||||
path string
|
||||
label string
|
||||
}
|
||||
@@ -73,6 +73,7 @@ pub mut:
|
||||
ssh_name string
|
||||
}
|
||||
|
||||
//is to import one docusaurus site into another, can be used to e.g. import static parts from one location into the build one we are building
|
||||
pub struct ImportItem {
|
||||
pub mut:
|
||||
name string // will normally be empty
|
||||
|
||||
@@ -1,89 +1,126 @@
|
||||
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'
|
||||
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 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")
|
||||
// }
|
||||
// }
|
||||
|
||||
// LETS FIRST DO THE CATEGORIES
|
||||
mut category_actions := plbook.find(filter: 'site.page_category')!
|
||||
mut section := Section{}
|
||||
for mut action in category_actions {
|
||||
// println(action)
|
||||
mut p := action.params
|
||||
section.position = p.get_int_default('position', 20)!
|
||||
section.label = p.get('label') or {
|
||||
return error('need to specify label in site.page_category')
|
||||
}
|
||||
section.path = p.get('path') or {
|
||||
return error('need to specify path in site.page_category')
|
||||
}
|
||||
site.sections << section
|
||||
action.done = true // Mark the action as done
|
||||
}
|
||||
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 page_actions := plbook.find(filter: 'site.page')!
|
||||
mut mypage := Page{
|
||||
src: ''
|
||||
path: ''
|
||||
}
|
||||
mut position_next := 1
|
||||
mut position := 0
|
||||
mut path := ''
|
||||
for mut action in page_actions {
|
||||
// println(action)
|
||||
mut p := action.params
|
||||
pathnew := p.get_default('path', '')!
|
||||
if pathnew != '' {
|
||||
mypage.path = path
|
||||
if pathnew.ends_with('.md') {
|
||||
// means we fully specified the name
|
||||
mypage.path = pathnew
|
||||
|
||||
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))!
|
||||
|
||||
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 {
|
||||
// only remember path if no .md file specified
|
||||
path = pathnew
|
||||
if !path.ends_with('/') {
|
||||
path += '/'
|
||||
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}')
|
||||
}
|
||||
// println(' -- NEW PATH: ${path}')
|
||||
mypage.path = path
|
||||
}
|
||||
} else {
|
||||
mypage.path = path
|
||||
}
|
||||
position = p.get_int_default('position', 0)!
|
||||
if position == 0 {
|
||||
position = position_next
|
||||
position_next += 1
|
||||
} else {
|
||||
if position > position_next {
|
||||
position_next = position + 1
|
||||
|
||||
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_name)!
|
||||
pagepath = pagepath.trim_space().trim('/')
|
||||
pagepath = texttools.name_fix(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
|
||||
}
|
||||
mypage.position = position
|
||||
mypage.src = p.get('src') or { return error('need to specify src in site.page') }
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,298 +1,324 @@
|
||||
# Site Module
|
||||
|
||||
## config heroscript
|
||||
The Site module provides a structured way to define website configurations, navigation menus, pages, and sections using HeroScript. It's designed to work with static site generators like Docusaurus.
|
||||
|
||||
```yaml
|
||||
## Purpose
|
||||
|
||||
The Site module allows you to:
|
||||
- Define website structure and configuration in a declarative way using HeroScript
|
||||
- Organize pages into sections/categories
|
||||
- Configure navigation menus and footers
|
||||
- Manage page metadata (title, description, slug, etc.)
|
||||
- Support multiple content collections
|
||||
- Define build and publish destinations
|
||||
|
||||
## Quick Start
|
||||
|
||||
```v
|
||||
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import incubaid.herolib.develop.gittools
|
||||
import incubaid.herolib.web.site
|
||||
import incubaid.herolib.core.playcmds
|
||||
|
||||
// Clone or use existing repository with HeroScript files
|
||||
mysitepath := gittools.path(
|
||||
git_url: 'https://git.ourworld.tf/tfgrid/docs_tfgrid4/src/branch/main/ebooks/tech'
|
||||
git_pull: true
|
||||
)!
|
||||
|
||||
// Process all HeroScript files in the path
|
||||
playcmds.run(heroscript_path: mysitepath.path)!
|
||||
|
||||
// Get the configured site
|
||||
mut mysite := site.get(name: 'tfgrid_tech')!
|
||||
println(mysite)
|
||||
```
|
||||
|
||||
## HeroScript Syntax
|
||||
|
||||
### Basic Configuration
|
||||
|
||||
```heroscript
|
||||
!!site.config
|
||||
name:"ThreeFold DePIN Tech"
|
||||
description:"ThreeFold is laying the foundation for a geo aware Web 4, the next generation of the Internet."
|
||||
tagline:"Geo Aware Internet Platform"
|
||||
favicon:"img/favicon.png"
|
||||
image:"img/tf_graph.png"
|
||||
copyright:"ThreeFold"
|
||||
name: "my_site"
|
||||
title: "My Documentation Site"
|
||||
description: "Comprehensive documentation"
|
||||
tagline: "Your awesome documentation"
|
||||
favicon: "img/favicon.png"
|
||||
image: "img/site-image.png"
|
||||
copyright: "© 2024 My Organization"
|
||||
url: "https://docs.example.com"
|
||||
base_url: "/"
|
||||
```
|
||||
|
||||
!!site.menu
|
||||
title:"ThreeFold DePIN Tech"
|
||||
logo_alt:"ThreeFold Logo"
|
||||
logo_src:"img/logo.svg"
|
||||
logo_src_dark:"img/new_logo_tft.png"
|
||||
### Navigation Menu
|
||||
|
||||
!!site.menu_item
|
||||
label:"ThreeFold.io"
|
||||
href:"https://threefold.io"
|
||||
position:"right"
|
||||
```heroscript
|
||||
!!site.navbar
|
||||
title: "My Site"
|
||||
logo_alt: "Site Logo"
|
||||
logo_src: "img/logo.svg"
|
||||
logo_src_dark: "img/logo-dark.svg"
|
||||
|
||||
!!site.menu_item
|
||||
label:"Mycelium Network"
|
||||
href:"https://mycelium.threefold.io/"
|
||||
position:"right"
|
||||
!!site.navbar_item
|
||||
label: "Documentation"
|
||||
to: "docs/intro"
|
||||
position: "left"
|
||||
|
||||
!!site.menu_item
|
||||
label:"AI Box"
|
||||
href:"https://aibox.threefold.io/"
|
||||
position:"right"
|
||||
!!site.navbar_item
|
||||
label: "GitHub"
|
||||
href: "https://github.com/myorg/myrepo"
|
||||
position: "right"
|
||||
```
|
||||
|
||||
### Footer Configuration
|
||||
|
||||
```heroscript
|
||||
!!site.footer
|
||||
style:"dark"
|
||||
style: "dark"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Docs"
|
||||
label:"Introduction"
|
||||
href:"https://docs.threefold.io/docs/introduction"
|
||||
title: "Docs"
|
||||
label: "Introduction"
|
||||
to: "intro"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Docs"
|
||||
label:"Litepaper"
|
||||
href:"https://docs.threefold.io/docs/litepaper/"
|
||||
title: "Docs"
|
||||
label: "Getting Started"
|
||||
href: "https://docs.example.com/getting-started"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Features"
|
||||
label:"Become a Farmer"
|
||||
href:"https://docs.threefold.io/docs/category/become-a-farmer"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Features"
|
||||
label:"Components"
|
||||
href:"https://docs.threefold.io/docs/category/components"
|
||||
|
||||
|
||||
!!site.footer_item
|
||||
title:"Web"
|
||||
label:"ThreeFold.io"
|
||||
href:"https://threefold.io"
|
||||
|
||||
!!site.footer_item
|
||||
title:"Web"
|
||||
label:"Dashboard"
|
||||
href:"https://dashboard.grid.tf"
|
||||
|
||||
!!site.collections
|
||||
url:"https://github.com/example/external-docs"
|
||||
replace:"PROJECT_NAME:My Project, VERSION:1.0.0"
|
||||
title: "Community"
|
||||
label: "Discord"
|
||||
href: "https://discord.gg/example"
|
||||
```
|
||||
|
||||
## site structure
|
||||
## Page Organization
|
||||
|
||||
```yaml
|
||||
!!site.page name:intro
|
||||
description:"ThreeFold is laying the foundation for a geo aware Web 4, the next generation of the Internet."
|
||||
### Example 1: Simple Pages Without Categories
|
||||
|
||||
#next is example where we use all properties, folder is where the page is located, prio is the order of the page, if not used the filled in from order in which we parse this config file
|
||||
!!site.page name:mycelium draft:true folder:"/specs/components" prio:4
|
||||
content:"the page content itself, only for small pages"
|
||||
title:"Mycelium as Title"
|
||||
description:"..."
|
||||
When you don't need categories, pages are added sequentially. The collection only needs to be specified once, then it's reused for subsequent pages.
|
||||
|
||||
!!site.page name:fungistor folder:"/specs/components" prio:1
|
||||
src:"mycollection:mycelium.md"
|
||||
title:"fungistor as Title"
|
||||
description:"...."
|
||||
```heroscript
|
||||
!!site.page src: "tech:introduction"
|
||||
description: "Introduction to ThreeFold Technology"
|
||||
slug: "/"
|
||||
|
||||
!!site.page name:fungistor folder:"/specs/components" prio:1
|
||||
src:"mycollection:mycelium" //can be without .md
|
||||
title:"fungistor as Title"
|
||||
description:"..."
|
||||
!!site.page src: "vision"
|
||||
description: "Our Vision for the Future Internet"
|
||||
|
||||
!!site.page src: "what"
|
||||
description: "What ThreeFold is Building"
|
||||
|
||||
!!site.page src: "presentation"
|
||||
description: "ThreeFold Technology Presentation"
|
||||
|
||||
!!site.page src: "status"
|
||||
description: "Current Development Status"
|
||||
```
|
||||
|
||||
## factory
|
||||
**Key Points:**
|
||||
- First page specifies collection as `tech:introduction` (collection:page_name format)
|
||||
- Subsequent pages only need the page name (e.g., `vision`) - the `tech` collection is reused
|
||||
- If `title` is not specified, it will be extracted from the markdown file itself
|
||||
- Pages are ordered by their appearance in the HeroScript file
|
||||
- `slug` can be used to customize the URL path (e.g., `"/"` for homepage)
|
||||
|
||||
### Example 2: Pages with Categories
|
||||
|
||||
Categories (sections) help organize pages into logical groups with their own navigation structure.
|
||||
|
||||
```heroscript
|
||||
!!site.page_category
|
||||
name: "first_principle_thinking"
|
||||
label: "First Principle Thinking"
|
||||
|
||||
!!site.page src: "first_principle_thinking:hardware_badly_used"
|
||||
description: "Hardware is not used properly, why it is important to understand hardware"
|
||||
|
||||
!!site.page src: "internet_risk"
|
||||
description: "Internet risk, how to mitigate it, and why it is important"
|
||||
|
||||
!!site.page src: "onion_analogy"
|
||||
description: "Compare onion with a computer, layers of abstraction"
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- `!!site.page_category` creates a new section/category
|
||||
- `name` is the internal identifier (snake_case)
|
||||
- `label` is the display name (automatically derived from `name` if not specified)
|
||||
- Category name is converted to title case: `first_principle_thinking` → "First Principle Thinking"
|
||||
- Once a category is defined, all subsequent pages belong to it until a new category is declared
|
||||
- Collection persistence works the same: specify once (e.g., `first_principle_thinking:hardware_badly_used`), then reuse
|
||||
|
||||
### Example 3: Advanced Page Configuration
|
||||
|
||||
```heroscript
|
||||
!!site.page_category
|
||||
name: "components"
|
||||
label: "System Components"
|
||||
position: 100
|
||||
|
||||
!!site.page src: "tech:mycelium"
|
||||
title: "Mycelium Network"
|
||||
description: "Peer-to-peer overlay network"
|
||||
slug: "mycelium-network"
|
||||
position: 1
|
||||
draft: false
|
||||
hide_title: false
|
||||
|
||||
!!site.page src: "fungistor"
|
||||
title: "Fungistor Storage"
|
||||
description: "Distributed storage system"
|
||||
position: 2
|
||||
```
|
||||
|
||||
**Available Page Parameters:**
|
||||
- `src`: Source reference as `collection:page_name` (required for first page in collection)
|
||||
- `title`: Page title (optional, extracted from markdown if not provided)
|
||||
- `description`: Page description for metadata
|
||||
- `slug`: Custom URL slug
|
||||
- `position`: Manual ordering (auto-incremented if not specified)
|
||||
- `draft`: Mark page as draft (default: false)
|
||||
- `hide_title`: Hide the page title in rendering (default: false)
|
||||
- `path`: Custom path for the page (defaults to category name)
|
||||
- `category`: Override the current category for this page
|
||||
|
||||
## File Organization
|
||||
|
||||
HeroScript files should be organized with numeric prefixes to control execution order:
|
||||
|
||||
```
|
||||
docs/
|
||||
├── 0_config.heroscript # Site configuration
|
||||
├── 1_menu.heroscript # Navigation and footer
|
||||
├── 2_intro_pages.heroscript # Introduction pages
|
||||
├── 3_tech_pages.heroscript # Technical documentation
|
||||
└── 4_api_pages.heroscript # API reference
|
||||
```
|
||||
|
||||
**Important:** Files are processed in alphabetical order, so use numeric prefixes (0_, 1_, 2_, etc.) to ensure correct execution sequence.
|
||||
|
||||
## Import External Content
|
||||
|
||||
```heroscript
|
||||
!!site.import
|
||||
url: "https://github.com/example/external-docs"
|
||||
dest: "external"
|
||||
replace: "PROJECT_NAME:My Project,VERSION:1.0.0"
|
||||
visible: true
|
||||
```
|
||||
|
||||
## Publish Destinations
|
||||
|
||||
```heroscript
|
||||
!!site.publish
|
||||
path: "/var/www/html/docs"
|
||||
ssh_name: "production_server"
|
||||
|
||||
!!site.publish_dev
|
||||
path: "/tmp/docs-preview"
|
||||
```
|
||||
|
||||
## Factory Methods
|
||||
|
||||
### Create or Get a Site
|
||||
|
||||
```v
|
||||
import incubaid.herolib.web.site
|
||||
mut mysite := site.new()!
|
||||
|
||||
// Create a new site
|
||||
mut mysite := site.new(name: 'my_docs')!
|
||||
|
||||
// Get an existing site
|
||||
mut mysite := site.get(name: 'my_docs')!
|
||||
|
||||
// Get default site
|
||||
mut mysite := site.default()!
|
||||
|
||||
// Check if site exists
|
||||
if site.exists(name: 'my_docs') {
|
||||
println('Site exists')
|
||||
}
|
||||
|
||||
// List all sites
|
||||
sites := site.list()
|
||||
println(sites)
|
||||
```
|
||||
|
||||
## how to use with plbook
|
||||
### Using with PlayBook
|
||||
|
||||
```v
|
||||
import incubaid.herolib.core.playbook
|
||||
import incubaid.herolib.web.site
|
||||
// path string
|
||||
// text string
|
||||
// git_url string
|
||||
// git_pull bool
|
||||
// git_branch string
|
||||
// git_reset bool
|
||||
// session ?&base.Session is optional
|
||||
mut plbook := playbook.new( "....")!
|
||||
|
||||
// Create playbook from path
|
||||
mut plbook := playbook.new(path: '/path/to/heroscripts')!
|
||||
|
||||
// Process site configuration
|
||||
site.play(mut plbook)!
|
||||
|
||||
// Access the configured site
|
||||
mut mysite := site.get(name: 'my_site')!
|
||||
```
|
||||
|
||||
## example json
|
||||
## Data Structures
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "depin",
|
||||
"title": "Documentation Site",
|
||||
"description": "ThreeFold is laying the foundation for a geo aware Web 4, the next generation of the Internet.",
|
||||
"tagline": "Geo Aware Internet Platform",
|
||||
"favicon": "img/favicon.png",
|
||||
"image": "img/tf_graph.png",
|
||||
"copyright": "ThreeFold",
|
||||
"footer": {
|
||||
"style": "dark",
|
||||
"links": [
|
||||
{
|
||||
"title": "Docs",
|
||||
"items": [
|
||||
{
|
||||
"label": "Introduction",
|
||||
"to": "intro",
|
||||
"href": ""
|
||||
},
|
||||
{
|
||||
"label": "Litepaper",
|
||||
"to": "",
|
||||
"href": "https://docs.threefold.io/docs/litepaper/"
|
||||
},
|
||||
{
|
||||
"label": "Roadmap",
|
||||
"to": "",
|
||||
"href": "https://docs.threefold.io/docs/roadmap"
|
||||
},
|
||||
{
|
||||
"label": "Manual",
|
||||
"to": "",
|
||||
"href": "https://manual.grid.tf/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Features",
|
||||
"items": [
|
||||
{
|
||||
"label": "Become a Farmer",
|
||||
"to": "",
|
||||
"href": "https://docs.threefold.io/docs/category/become-a-farmer"
|
||||
},
|
||||
{
|
||||
"label": "Components",
|
||||
"to": "",
|
||||
"href": "https://docs.threefold.io/docs/category/components"
|
||||
},
|
||||
{
|
||||
"label": "Technology",
|
||||
"to": "",
|
||||
"href": "https://threefold.info/tech/"
|
||||
},
|
||||
{
|
||||
"label": "Tokenomics",
|
||||
"to": "",
|
||||
"href": "https://docs.threefold.io/docs/tokens/tokenomics"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Web",
|
||||
"items": [
|
||||
{
|
||||
"label": "ThreeFold.io",
|
||||
"to": "",
|
||||
"href": "https://threefold.io"
|
||||
},
|
||||
{
|
||||
"label": "Dashboard",
|
||||
"to": "",
|
||||
"href": "https://dashboard.grid.tf"
|
||||
},
|
||||
{
|
||||
"label": "GitHub",
|
||||
"to": "",
|
||||
"href": "https://github.com/threefoldtech/home"
|
||||
},
|
||||
{
|
||||
"label": "Mycelium Network",
|
||||
"to": "",
|
||||
"href": "https://mycelium.threefold.io/"
|
||||
},
|
||||
{
|
||||
"label": "AI Box",
|
||||
"to": "",
|
||||
"href": "https://www2.aibox.threefold.io/"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"menu": {
|
||||
"title": "ThreeFold DePIN Tech",
|
||||
"items": [
|
||||
{
|
||||
"href": "https://threefold.io",
|
||||
"to": "",
|
||||
"label": "ThreeFold.io",
|
||||
"position": "right"
|
||||
},
|
||||
{
|
||||
"href": "https://mycelium.threefold.io/",
|
||||
"to": "",
|
||||
"label": "Mycelium Network",
|
||||
"position": "right"
|
||||
},
|
||||
{
|
||||
"href": "https://aibox.threefold.io/",
|
||||
"to": "",
|
||||
"label": "AI Box",
|
||||
"position": "right"
|
||||
}
|
||||
]
|
||||
},
|
||||
"import_collections": [
|
||||
{
|
||||
"url": "https://github.com/example/external-docs",
|
||||
"path": "",
|
||||
"dest": "",
|
||||
"replace": {
|
||||
"PROJECT_NAME": "My Project",
|
||||
"VERSION": "1.0.0"
|
||||
},
|
||||
"visible": false
|
||||
}
|
||||
],
|
||||
"pages": [
|
||||
{
|
||||
"name": "intro",
|
||||
"content": "",
|
||||
"title": "",
|
||||
"description": "ThreeFold is laying the foundation for a geo aware Web 4, the next generation of the Internet.",
|
||||
"draft": false,
|
||||
"folder": "",
|
||||
"prio": 0,
|
||||
"src": ""
|
||||
},
|
||||
{
|
||||
"name": "mycelium",
|
||||
"content": "the page content itself, only for small pages",
|
||||
"title": "Mycelium as Title",
|
||||
"description": "...",
|
||||
"draft": true,
|
||||
"folder": "/specs/components",
|
||||
"prio": 4,
|
||||
"src": ""
|
||||
},
|
||||
{
|
||||
"name": "fungistor",
|
||||
"content": "",
|
||||
"title": "fungistor as Title",
|
||||
"description": "....",
|
||||
"draft": false,
|
||||
"folder": "/specs/components",
|
||||
"prio": 1,
|
||||
"src": "mycollection:mycelium.md"
|
||||
},
|
||||
{
|
||||
"name": "fungistor",
|
||||
"content": "",
|
||||
"title": "fungistor as Title",
|
||||
"description": "...",
|
||||
"draft": false,
|
||||
"folder": "/specs/components",
|
||||
"prio": 1,
|
||||
"src": "mycollection:mycelium"
|
||||
}
|
||||
]
|
||||
### Site
|
||||
|
||||
```v
|
||||
pub struct Site {
|
||||
pub mut:
|
||||
pages []Page
|
||||
sections []Section
|
||||
siteconfig SiteConfig
|
||||
}
|
||||
```
|
||||
|
||||
### Page
|
||||
|
||||
```v
|
||||
pub struct Page {
|
||||
pub mut:
|
||||
name string // Page identifier
|
||||
title string // Display title
|
||||
description string // Page description
|
||||
draft bool // Draft status
|
||||
position int // Sort order
|
||||
hide_title bool // Hide title in rendering
|
||||
src string // Source as collection:page_name
|
||||
path string // URL path (without page name)
|
||||
section_name string // Category/section name
|
||||
title_nr int // Title numbering level
|
||||
slug string // Custom URL slug
|
||||
}
|
||||
```
|
||||
|
||||
### Section
|
||||
|
||||
```v
|
||||
pub struct Section {
|
||||
pub mut:
|
||||
name string // Internal identifier
|
||||
position int // Sort order
|
||||
path string // URL path
|
||||
label string // Display name
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **File Naming**: Use numeric prefixes (0_, 1_, 2_) to control execution order
|
||||
2. **Collection Reuse**: Specify collection once, then reuse for subsequent pages
|
||||
3. **Category Organization**: Group related pages under categories for better navigation
|
||||
4. **Title Extraction**: Let titles be extracted from markdown files when possible
|
||||
5. **Position Management**: Use automatic positioning unless you need specific ordering
|
||||
6. **Description**: Always provide descriptions for better SEO and navigation
|
||||
7. **Draft Status**: Use `draft: true` for work-in-progress pages
|
||||
|
||||
## Complete Example
|
||||
|
||||
See `examples/web/site/site_example.vsh` for a complete working example.
|
||||
|
||||
For a real-world example, check: https://git.ourworld.tf/tfgrid/docs_tfgrid4/src/branch/main/ebooks/tech
|
||||
@@ -1,91 +0,0 @@
|
||||
module site
|
||||
|
||||
import os
|
||||
|
||||
fn test_play_collections() ! {
|
||||
mypath := '${os.dir(@FILE)}/example'
|
||||
|
||||
mut sc := new(mypath)!
|
||||
|
||||
// Add assertions here based on the expected site config structure
|
||||
assert sc.name == 'depin'
|
||||
assert sc.description == 'ThreeFold is laying the foundation for a geo aware Web 4, the next generation of the Internet.'
|
||||
assert sc.tagline == 'Geo Aware Internet Platform'
|
||||
assert sc.favicon == 'img/favicon.png'
|
||||
assert sc.image == 'img/tf_graph.png'
|
||||
assert sc.copyright == 'ThreeFold'
|
||||
|
||||
// Assertions for menu
|
||||
assert sc.menu.title == 'ThreeFold DePIN Tech'
|
||||
assert sc.menu.items.len == 3 // Based on the three !!site.menu_item entries
|
||||
assert sc.menu.items[0].label == 'ThreeFold.io'
|
||||
assert sc.menu.items[0].href == 'https://threefold.io'
|
||||
assert sc.menu.items[0].position == 'right'
|
||||
assert sc.menu.items[1].label == 'Mycelium Network'
|
||||
assert sc.menu.items[1].href == 'https://mycelium.threefold.io/'
|
||||
assert sc.menu.items[1].position == 'right'
|
||||
assert sc.menu.items[2].label == 'AI Box'
|
||||
assert sc.menu.items[2].href == 'https://aibox.threefold.io/'
|
||||
assert sc.menu.items[2].position == 'right'
|
||||
|
||||
// Assertions for footer
|
||||
assert sc.footer.style == 'dark'
|
||||
assert sc.footer.links.len == 3 // Based on the three unique titles in !!site.footer_item
|
||||
assert sc.footer.links[0].title == 'Docs'
|
||||
assert sc.footer.links[0].items.len == 4
|
||||
assert sc.footer.links[1].title == 'Features'
|
||||
assert sc.footer.links[1].items.len == 4
|
||||
assert sc.footer.links[2].title == 'Web'
|
||||
assert sc.footer.links[2].items.len == 5
|
||||
|
||||
// Assertions for collections
|
||||
assert sc.import_collections.len == 1 // Based on the !!site.collections entry
|
||||
assert sc.import_collections[0].url == 'https://github.com/example/external-docs'
|
||||
assert sc.import_collections[0].replace.len == 2
|
||||
assert sc.import_collections[0].replace['PROJECT_NAME'] == 'My Project'
|
||||
assert sc.import_collections[0].replace['VERSION'] == '1.0.0'
|
||||
assert sc.import_collections[0].visible == false // Default value
|
||||
|
||||
// Assertions for pages
|
||||
assert sc.pages.len == 4 // Based on the four !!site.page entries in site.heroscript
|
||||
|
||||
// Assertions for the first page (intro)
|
||||
assert sc.pages[0].name == 'intro'
|
||||
assert sc.pages[0].description == 'ThreeFold is laying the foundation for a geo aware Web 4, the next generation of the Internet.'
|
||||
assert sc.pages[0].title == '' // No title specified in site.heroscript
|
||||
assert sc.pages[0].draft == false // Default value
|
||||
assert sc.pages[0].folder == '' // Default value
|
||||
assert sc.pages[0].prio == 0 // Default value
|
||||
assert sc.pages[0].src == '' // No src specified
|
||||
assert sc.pages[0].content == '' // No content specified
|
||||
|
||||
// Assertions for the second page (mycelium)
|
||||
assert sc.pages[1].name == 'mycelium'
|
||||
assert sc.pages[1].description == '...'
|
||||
assert sc.pages[1].title == 'Mycelium as Title'
|
||||
assert sc.pages[1].draft == true
|
||||
assert sc.pages[1].folder == '/specs/components'
|
||||
assert sc.pages[1].prio == 4
|
||||
assert sc.pages[1].src == '' // No src specified
|
||||
assert sc.pages[1].content == 'the page content itself, only for small pages'
|
||||
|
||||
// Assertions for the third page (fungistor with .md)
|
||||
assert sc.pages[2].name == 'fungistor'
|
||||
assert sc.pages[2].description == '....'
|
||||
assert sc.pages[2].title == 'fungistor as Title'
|
||||
assert sc.pages[2].draft == false // Default value
|
||||
assert sc.pages[2].folder == '/specs/components'
|
||||
assert sc.pages[2].prio == 1
|
||||
assert sc.pages[2].src == 'mycollection:mycelium.md'
|
||||
assert sc.pages[2].content == '' // No content specified
|
||||
|
||||
// Assertions for the fourth page (fungistor without .md)
|
||||
assert sc.pages[3].name == 'fungistor'
|
||||
assert sc.pages[3].description == '...'
|
||||
assert sc.pages[3].title == 'fungistor as Title'
|
||||
assert sc.pages[3].draft == false // Default value
|
||||
assert sc.pages[3].folder == '/specs/components'
|
||||
assert sc.pages[3].prio == 1
|
||||
assert sc.pages[3].src == 'mycollection:mycelium'
|
||||
assert sc.pages[3].content == '' // No content specified
|
||||
}
|
||||
Reference in New Issue
Block a user