feat: Add announcement bar configuration

- Add AnnouncementBar struct and field to Configuration
- Add announcement.json file generation
- Implement play_announcement function for importing announcement config
- Improve fix_links to calculate relative paths dynamically
- Escape single quotes in YAML frontmatter fields
This commit is contained in:
Mahmoud-Emad
2025-10-16 17:38:18 +03:00
parent b2bc0d1b6a
commit b18c6824d6
5 changed files with 143 additions and 21 deletions

View File

@@ -6,9 +6,10 @@ import incubaid.herolib.web.site
pub struct Configuration {
pub mut:
main Main
navbar Navbar
footer Footer
main Main
navbar Navbar
footer Footer
announcement AnnouncementBar
}
pub struct Main {
@@ -75,6 +76,15 @@ pub mut:
to string @[omitempty]
}
pub struct AnnouncementBar {
pub mut:
id string @[json: 'id']
content string @[json: 'content']
background_color string @[json: 'backgroundColor']
text_color string @[json: 'textColor']
is_closeable bool @[json: 'isCloseable']
}
// ... (struct definitions remain the same) ...
// This function is now a pure transformer: site.SiteConfig -> docusaurus.Configuration
@@ -107,7 +117,7 @@ fn new_configuration(site_cfg site.SiteConfig) !Configuration {
}
cfg := Configuration{
main: Main{
main: Main{
title: site_cfg.title
tagline: site_cfg.tagline
favicon: site_cfg.favicon
@@ -137,7 +147,7 @@ fn new_configuration(site_cfg site.SiteConfig) !Configuration {
copyright: site_cfg.copyright
name: site_cfg.name
}
navbar: Navbar{
navbar: Navbar{
title: site_cfg.menu.title
logo: Logo{
alt: site_cfg.menu.logo_alt
@@ -146,10 +156,17 @@ fn new_configuration(site_cfg site.SiteConfig) !Configuration {
}
items: nav_items
}
footer: Footer{
footer: Footer{
style: site_cfg.footer.style
links: footer_links
}
announcement: AnnouncementBar{
id: site_cfg.announcement.id
content: site_cfg.announcement.content
background_color: site_cfg.announcement.background_color
text_color: site_cfg.announcement.text_color
is_closeable: site_cfg.announcement.is_closeable
}
}
return config_fix(cfg)!
}

View File

@@ -31,6 +31,9 @@ pub fn (mut docsite DocSite) generate() ! {
mut footer_file := pathlib.get_file(path: '${cfg_path}/footer.json', create: true)!
footer_file.write(json.encode_pretty(docsite.config.footer))!
mut announcement_file := pathlib.get_file(path: '${cfg_path}/announcement.json', create: true)!
announcement_file.write(json.encode_pretty(docsite.config.announcement))!
docsite.generate_docs()!
docsite.import()!

View File

@@ -86,14 +86,18 @@ fn (mut generator SiteGenerator) page_generate(args_ Page) ! {
args.title = page_name
}
}
content << "title: '${args.title}'"
// Escape single quotes in YAML by doubling them
escaped_title := args.title.replace("'", "''")
content << "title: '${escaped_title}'"
if args.description.len > 0 {
content << "description: '${args.description}'"
escaped_description := args.description.replace("'", "''")
content << "description: '${escaped_description}'"
}
if args.slug.len > 0 {
content << "slug: '${args.slug}'"
escaped_slug := args.slug.replace("'", "''")
content << "slug: '${escaped_slug}'"
}
if args.hide_title {
@@ -118,7 +122,7 @@ fn (mut generator SiteGenerator) page_generate(args_ Page) ! {
}
// Fix links to account for nested categories
page_content = generator.fix_links(page_content)
page_content = generator.fix_links(page_content, args.path)
c += '\n${page_content}\n'
@@ -190,10 +194,81 @@ fn strip_numeric_prefix(name string) string {
return name
}
// Calculate relative path from current directory to target directory
// current_dir: directory of the current page (e.g., '' for root, 'tokens' for tokens/, 'farming/advanced' for nested)
// target_dir: directory of the target page
// page_name: name of the target page
// Returns: relative path (e.g., './page', '../dir/page', '../../page')
fn calculate_relative_path(current_dir string, target_dir string, page_name string) string {
// Both at root level
if current_dir == '' && target_dir == '' {
return './${page_name}'
}
// Current at root, target in subdirectory
if current_dir == '' && target_dir != '' {
return './${target_dir}/${page_name}'
}
// Current in subdirectory, target at root
if current_dir != '' && target_dir == '' {
// Count directory levels to go up
levels := current_dir.split('/').len
up := '../'.repeat(levels)
return '${up}${page_name}'
}
// Both in subdirectories
current_parts := current_dir.split('/')
target_parts := target_dir.split('/')
// Find common prefix
mut common_len := 0
for i := 0; i < current_parts.len && i < target_parts.len; i++ {
if current_parts[i] == target_parts[i] {
common_len++
} else {
break
}
}
// Calculate how many levels to go up
up_levels := current_parts.len - common_len
mut path_parts := []string{}
// Add ../ for each level up
for _ in 0 .. up_levels {
path_parts << '..'
}
// Add remaining target path parts
for i in common_len .. target_parts.len {
path_parts << target_parts[i]
}
// Add page name
path_parts << page_name
return path_parts.join('/')
}
// Fix links to account for nested categories and Docusaurus URL conventions
fn (generator SiteGenerator) fix_links(content string) string {
fn (generator SiteGenerator) fix_links(content string, current_page_path string) string {
mut result := content
// Extract current page's directory path
mut current_dir := current_page_path.trim('/')
if current_dir.contains('/') && !current_dir.ends_with('/') {
last_part := current_dir.all_after_last('/')
if last_part.contains('.') {
current_dir = current_dir.all_before_last('/')
}
}
// If path is just a filename or empty, current_dir should be empty (root level)
if !current_dir.contains('/') && current_dir.contains('.') {
current_dir = ''
}
// Build maps for link fixing
mut collection_paths := map[string]string{} // collection -> directory path (for nested collections)
mut page_to_path := map[string]string{} // page_name -> full directory path in Docusaurus
@@ -275,25 +350,20 @@ fn (generator SiteGenerator) fix_links(content string) string {
// STEP 3: Fix same-collection links: ./page -> correct path based on Docusaurus structure
for page_name, target_dir in page_to_path {
old_link := './${page_name}'
if result.contains(old_link) && target_dir != '' {
new_link := '../${target_dir}/${page_name}'
if result.contains(old_link) {
new_link := calculate_relative_path(current_dir, target_dir, page_name)
result = result.replace(old_link, new_link)
}
}
// STEP 4: Convert collection:page format to proper relative paths
// Pattern: collection:page_name -> ../dir/page_name
// Calculate relative path from current page to target page
for collection_page, target_dir in collection_page_map {
old_pattern := collection_page
if result.contains(old_pattern) {
// Extract just the page name from "collection:page"
page_name := collection_page.all_after(':')
mut new_link := ''
if target_dir != '' {
new_link = '../${target_dir}/${page_name}'
} else {
new_link = './${page_name}'
}
new_link := calculate_relative_path(current_dir, target_dir, page_name)
result = result.replace(old_pattern, new_link)
}
}

View File

@@ -27,6 +27,18 @@ pub mut:
build_dest []BuildDest // Production build destinations (from !!site.build_dest)
build_dest_dev []BuildDest // Development build destinations (from !!site.build_dest_dev)
announcement AnnouncementBar // Announcement bar configuration (from !!site.announcement)
}
// Announcement bar config structure
pub struct AnnouncementBar {
pub mut:
id string @[json: 'id']
content string @[json: 'content']
background_color string @[json: 'backgroundColor']
text_color string @[json: 'textColor']
is_closeable bool @[json: 'isCloseable']
}
// Footer config structures
@@ -73,7 +85,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
// 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

View File

@@ -50,6 +50,7 @@ pub fn play(mut plbook PlayBook) ! {
play_import(mut plbook, mut config)!
play_menu(mut plbook, mut config)!
play_footer(mut plbook, mut config)!
play_announcement(mut plbook, mut config)!
play_publish(mut plbook, mut config)!
play_publish_dev(mut plbook, mut config)!
play_pages(mut plbook, mut website)!
@@ -178,6 +179,25 @@ fn play_footer(mut plbook PlayBook, mut config SiteConfig) ! {
}
}
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 {