diff --git a/lib/web/docusaurus/dsite_configuration.v b/lib/web/docusaurus/dsite_configuration.v index 4e9ff270..8d067b2f 100644 --- a/lib/web/docusaurus/dsite_configuration.v +++ b/lib/web/docusaurus/dsite_configuration.v @@ -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)! } diff --git a/lib/web/docusaurus/dsite_generate.v b/lib/web/docusaurus/dsite_generate.v index ace41759..a6ab603a 100644 --- a/lib/web/docusaurus/dsite_generate.v +++ b/lib/web/docusaurus/dsite_generate.v @@ -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()! diff --git a/lib/web/docusaurus/dsite_generate_docs.v b/lib/web/docusaurus/dsite_generate_docs.v index a5e2ac67..34749165 100644 --- a/lib/web/docusaurus/dsite_generate_docs.v +++ b/lib/web/docusaurus/dsite_generate_docs.v @@ -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) } } diff --git a/lib/web/site/model_siteconfig.v b/lib/web/site/model_siteconfig.v index 297d5a87..c7a3d04c 100644 --- a/lib/web/site/model_siteconfig.v +++ b/lib/web/site/model_siteconfig.v @@ -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 diff --git a/lib/web/site/play.v b/lib/web/site/play.v index 170f27df..907185ad 100644 --- a/lib/web/site/play.v +++ b/lib/web/site/play.v @@ -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 {