diff --git a/lib/web/docusaurus/config.v b/lib/web/docusaurus/config.v index 6418be43..636729cf 100644 --- a/lib/web/docusaurus/config.v +++ b/lib/web/docusaurus/config.v @@ -17,9 +17,7 @@ pub mut: reset bool template_update bool coderoot string - // Client configuration - use_atlas bool // true = atlas_client, false = doctreeclient - atlas_dir string // Required when use_atlas = true + atlas_dir string } @[params] @@ -31,9 +29,7 @@ pub mut: reset bool template_update bool coderoot string - // Client configuration - use_atlas bool // true = atlas_client, false = doctreeclient - atlas_dir string // Required when use_atlas = true + atlas_dir string } // return the last know config @@ -42,8 +38,8 @@ pub fn config() !DocusaurusConfig { docusaurus_config << DocusaurusConfigParams{} } mut args := docusaurus_config[0] or { panic('bug in docusaurus config') } - if args.use_atlas && args.atlas_dir == '' { - return error('use_atlas is true but atlas_dir is not set') + if args.atlas_dir == '' { + return error('atlas_dir is not set') } if args.path_build == '' { args.path_build = '${os.home_dir()}/hero/var/docusaurus/build' @@ -62,7 +58,6 @@ pub fn config() !DocusaurusConfig { install: args.install reset: args.reset template_update: args.template_update - use_atlas: args.use_atlas atlas_dir: args.atlas_dir } if c.install { diff --git a/lib/web/docusaurus/dsite_configuration.v b/lib/web/docusaurus/dsite_configuration.v index 8d067b2f..356cff78 100644 --- a/lib/web/docusaurus/dsite_configuration.v +++ b/lib/web/docusaurus/dsite_configuration.v @@ -9,6 +9,7 @@ pub mut: main Main navbar Navbar footer Footer + sidebar_json_txt string //will hold the sidebar.json content announcement AnnouncementBar } @@ -78,7 +79,7 @@ pub mut: pub struct AnnouncementBar { pub mut: - id string @[json: 'id'] + // id string @[json: 'id'] content string @[json: 'content'] background_color string @[json: 'backgroundColor'] text_color string @[json: 'textColor'] @@ -88,8 +89,9 @@ pub mut: // ... (struct definitions remain the same) ... // This function is now a pure transformer: site.SiteConfig -> docusaurus.Configuration -fn new_configuration(site_cfg site.SiteConfig) !Configuration { +fn new_configuration(mysite site.Site) !Configuration { // Transform site.SiteConfig to docusaurus.Configuration + mut site_cfg := mysite.siteconfig mut nav_items := []NavbarItem{} for item in site_cfg.menu.items { nav_items << NavbarItem{ @@ -116,6 +118,8 @@ fn new_configuration(site_cfg site.SiteConfig) !Configuration { } } + sidebar_json_txt := mysite.nav.sidebar_to_json()! + cfg := Configuration{ main: Main{ title: site_cfg.title @@ -161,13 +165,15 @@ fn new_configuration(site_cfg site.SiteConfig) !Configuration { links: footer_links } announcement: AnnouncementBar{ - id: site_cfg.announcement.id + // 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 } + sidebar_json_txt: sidebar_json_txt } + return config_fix(cfg)! } diff --git a/lib/web/docusaurus/dsite_generate.v b/lib/web/docusaurus/dsite_generate.v index fb25f61b..f3b40272 100644 --- a/lib/web/docusaurus/dsite_generate.v +++ b/lib/web/docusaurus/dsite_generate.v @@ -33,7 +33,11 @@ pub fn (mut docsite DocSite) generate() ! { 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()! + // generate sidebar.json, now new way to drive docusaurus navigation + mut sidebar_file := pathlib.get_file(path: '${cfg_path}/sidebar.json', create: true)! + sidebar_file.write(docsite.config.sidebar_json_txt)! + + docsite.link_docs()! docsite.import()! } diff --git a/lib/web/docusaurus/dsite_generate_docs.v b/lib/web/docusaurus/dsite_generate_docs.v deleted file mode 100644 index b5f20d25..00000000 --- a/lib/web/docusaurus/dsite_generate_docs.v +++ /dev/null @@ -1,442 +0,0 @@ -module docusaurus - -import incubaid.herolib.core.pathlib -import incubaid.herolib.data.atlas.client as atlas_client -import incubaid.herolib.web.site { Page, Section, Site } -import incubaid.herolib.data.markdown.tools as markdowntools -import incubaid.herolib.ui.console - -struct SiteGenerator { -mut: - siteconfig_name string - path pathlib.Path - client IDocClient - flat bool // if flat then won't use sitenames as subdir's - site Site - errors []string // collect errors here -} - -// Generate docs from site configuration -pub fn (mut docsite DocSite) generate_docs() ! { - c := config()! - - // we generate the docs in the build path - docs_path := '${c.path_build.path}/docs' - - // Create the appropriate client based on configuration - mut client_instance := atlas_client.new(export_dir: c.atlas_dir)! - mut client := IDocClient(client_instance) - - mut gen := SiteGenerator{ - path: pathlib.get_dir(path: docs_path, create: true)! - client: client - flat: true - site: docsite.website - } - - for section in gen.site.sections { - gen.section_generate(section)! - } - - for page in gen.site.pages { - gen.page_generate(page)! - } - - if gen.errors.len > 0 { - println('Page List: is header collection and page name per collection.\nAvailable pages:\n${gen.client.list_markdown()!}') - return error('Errors occurred during site generation:\n${gen.errors.join('\n\n')}\n') - } -} - -fn (mut generator SiteGenerator) error(msg string) ! { - console.print_stderr('Error: ${msg}') - generator.errors << msg -} - -fn (mut generator SiteGenerator) page_generate(args_ Page) ! { - mut args := args_ - - mut content := ['---'] - - mut parts := args.src.split(':') - if parts.len != 2 { - generator.error("Invalid src format for page '${args.src}', expected format: collection:page_name, TODO: fix in ${args.path}, check the collection & page_name exists in the pagelist")! - return - } - collection_name := parts[0] - page_name := parts[1] - - mut page_content := generator.client.get_page_content(collection_name, page_name) or { - generator.error("Couldn't find page '${collection_name}:${page_name}' is formatted as collectionname:pagename. TODO: fix in ${args.path}, check the collection & page_name exists in the pagelist. ")! - return - } - - if args.description.len == 0 { - descnew := markdowntools.extract_title(page_content) - if descnew != '' { - args.description = descnew - } else { - args.description = page_name - } - } - - if args.title.len == 0 { - descnew := markdowntools.extract_title(page_content) - if descnew != '' { - args.title = descnew - } else { - args.title = page_name - } - } - // Escape single quotes in YAML by doubling them - escaped_title := args.title.replace("'", "''") - content << "title: '${escaped_title}'" - - if args.description.len > 0 { - escaped_description := args.description.replace("'", "''") - content << "description: '${escaped_description}'" - } - - if args.slug.len > 0 { - escaped_slug := args.slug.replace("'", "''") - content << "slug: '${escaped_slug}'" - } - - if args.hide_title { - content << 'hide_title: ${args.hide_title}' - } - - if args.draft { - content << 'draft: ${args.draft}' - } - - if args.position > 0 { - content << 'sidebar_position: ${args.position}' - } - - content << '---' - - mut c := content.join('\n') - - if args.title_nr > 0 { - // Set the title number in the page content - page_content = markdowntools.set_titles(page_content, args.title_nr) - } - - // Fix links to account for nested categories - page_content = generator.fix_links(page_content, args.path) - - c += '\n${page_content}\n' - - if args.path.ends_with('/') || args.path.trim_space() == '' { - // means is dir - args.path += page_name - } - - if !args.path.ends_with('.md') { - args.path += '.md' - } - - mut pagepath := '${generator.path.path}/${args.path}' - mut pagefile := pathlib.get_file(path: pagepath, create: true)! - - pagefile.write(c)! - - generator.client.copy_pages(collection_name, page_name, pagefile.path_dir()) or { - generator.error("Couldn't copy pages for page:'${page_name}' in collection:'${collection_name}'\nERROR:${err}")! - return - } - generator.client.copy_images(collection_name, page_name, pagefile.path_dir()) or { - generator.error("Couldn't copy images for page:'${page_name}' in collection:'${collection_name}'\nERROR:${err}")! - return - } - generator.client.copy_files(collection_name, page_name, pagefile.path_dir()) or { - generator.error("Couldn't copy files for page:'${page_name}' in collection:'${collection_name}'\nERROR:${err}")! - return - } -} - -fn (mut generator SiteGenerator) section_generate(args_ Section) ! { - mut args := args_ - - mut c := '' - if args.description.len > 0 { - c = '{ - "label": "${args.label}", - "position": ${args.position}, - "link": { - "type": "generated-index", - "description": "${args.description}" - } - }' - } else { - c = '{ - "label": "${args.label}", - "position": ${args.position}, - "link": { - "type": "generated-index" - } - }' - } - - mut category_path := '${generator.path.path}/${args.path}/_category_.json' - mut catfile := pathlib.get_file(path: category_path, create: true)! - - catfile.write(c)! -} - -// Strip numeric prefix from filename (e.g., "03_linux_installation" -> "linux_installation") -// Docusaurus automatically strips these prefixes from URLs -fn strip_numeric_prefix(name string) string { - // Match pattern: digits followed by underscore at the start - if name.len > 2 && name[0].is_digit() { - for i := 1; i < name.len; i++ { - if name[i] == `_` { - // Found the underscore, return everything after it - return name[i + 1..] - } - if !name[i].is_digit() { - // Not a numeric prefix pattern, return as-is - return name - } - } - } - 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, 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 - mut collection_page_map := map[string]string{} // "collection:page" -> directory path - - for page in generator.site.pages { - parts := page.src.split(':') - if parts.len != 2 { - continue - } - collection := parts[0] - page_name := parts[1] - - // Extract directory path from page.path - mut dir_path := page.path.trim('/') - if dir_path.contains('/') && !dir_path.ends_with('/') { - last_part := dir_path.all_after_last('/') - if last_part.contains('.') || last_part == page_name { - dir_path = dir_path.all_before_last('/') - } - } - - // Store collection -> directory mapping for nested collections - if dir_path != collection && dir_path != '' { - collection_paths[collection] = dir_path - } - - // Store page_name -> directory path for fixing same-collection links - // Strip numeric prefix from page_name for the map key - clean_page_name := strip_numeric_prefix(page_name) - page_to_path[clean_page_name] = dir_path - - // Store collection:page -> directory path for fixing collection:page format links - collection_page_map['${collection}:${clean_page_name}'] = dir_path - } - - // STEP 1: Strip numeric prefixes from all page references in links FIRST - mut lines := result.split('\n') - for i, line in lines { - if !line.contains('](') { - continue - } - - mut new_line := line - parts := line.split('](') - if parts.len < 2 { - continue - } - - for j := 1; j < parts.len; j++ { - close_idx := parts[j].index(')') or { continue } - link_url := parts[j][..close_idx] - - mut new_url := link_url - if link_url.contains('/') { - path_part := link_url.all_before_last('/') - file_part := link_url.all_after_last('/') - new_file := strip_numeric_prefix(file_part) - if new_file != file_part { - new_url = '${path_part}/${new_file}' - } - } else { - new_url = strip_numeric_prefix(link_url) - } - - if new_url != link_url { - new_line = new_line.replace('](${link_url})', '](${new_url})') - } - } - lines[i] = new_line - } - result = lines.join('\n') - - // STEP 2: Replace ../collection/ with ../actual/nested/path/ for cross-collection links - for collection, actual_path in collection_paths { - result = result.replace('../${collection}/', '../${actual_path}/') - } - - // 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) { - 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 - // 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(':') - new_link := calculate_relative_path(current_dir, target_dir, page_name) - result = result.replace(old_pattern, new_link) - } - } - - // STEP 5: Fix bare page references (from atlas self-contained exports) - // Atlas exports convert cross-collection links to simple relative links like "token_system2.md" - // We need to transform these to proper relative paths based on Docusaurus structure - for page_name, target_dir in page_to_path { - // Match links in the format ](page_name) or ](page_name.md) - old_link_with_md := '](${page_name}.md)' - old_link_without_md := '](${page_name})' - - if result.contains(old_link_with_md) || result.contains(old_link_without_md) { - new_link := calculate_relative_path(current_dir, target_dir, page_name) - // Replace both .md and non-.md versions - result = result.replace(old_link_with_md, '](${new_link})') - result = result.replace(old_link_without_md, '](${new_link})') - } - } - - // STEP 6: Remove .md extensions from all remaining links (Docusaurus doesn't use them in URLs) - result = result.replace('.md)', ')') - - // STEP 7: Fix image links to point to img/ subdirectory - // Images are copied to img/ subdirectory by copy_images(), so we need to update the links - // Transform ![alt](image.png) to ![alt](img/image.png) for local images only - mut image_lines := result.split('\n') - for i, line in image_lines { - // Find image links: ![...](...) but skip external URLs - if line.contains('![') { - mut pos := 0 - for { - img_start := line.index_after('![', pos) or { break } - alt_end := line.index_after(']', img_start) or { break } - if alt_end + 1 >= line.len || line[alt_end + 1] != `(` { - pos = alt_end + 1 - continue - } - url_start := alt_end + 2 - url_end := line.index_after(')', url_start) or { break } - url := line[url_start..url_end] - - // Skip external URLs and already-prefixed img/ paths - if url.starts_with('http://') || url.starts_with('https://') - || url.starts_with('img/') || url.starts_with('./img/') { - pos = url_end + 1 - continue - } - - // Skip absolute paths and paths with ../ - if url.starts_with('/') || url.starts_with('../') { - pos = url_end + 1 - continue - } - - // This is a local image reference - add img/ prefix - new_url := 'img/${url}' - image_lines[i] = line[0..url_start] + new_url + line[url_end..] - break - } - } - } - result = image_lines.join('\n') - - return result -} diff --git a/lib/web/docusaurus/dsite_generate_docs__.v b/lib/web/docusaurus/dsite_generate_docs__.v new file mode 100644 index 00000000..855eea0b --- /dev/null +++ b/lib/web/docusaurus/dsite_generate_docs__.v @@ -0,0 +1,442 @@ +module docusaurus + +import incubaid.herolib.core.pathlib +// import incubaid.herolib.data.atlas.client as atlas_client +// import incubaid.herolib.web.site { Page, Section, Site } +// import incubaid.herolib.data.markdown.tools as markdowntools +// import incubaid.herolib.ui.console + +// struct SiteGenerator { +// mut: +// siteconfig_name string +// path pathlib.Path +// client IDocClient +// flat bool // if flat then won't use sitenames as subdir's +// site Site +// errors []string // collect errors here +// } + +// // Generate docs from site configuration +// pub fn (mut docsite DocSite) generate_docs() ! { +// c := config()! + +// // we generate the docs in the build path +// docs_path := '${c.path_build.path}/docs' + +// // Create the appropriate client based on configuration +// mut client_instance := atlas_client.new(export_dir: c.atlas_dir)! +// mut client := IDocClient(client_instance) + +// mut gen := SiteGenerator{ +// path: pathlib.get_dir(path: docs_path, create: true)! +// client: client +// flat: true +// site: docsite.website +// } + +// for section in gen.site.sections { +// gen.section_generate(section)! +// } + +// for page in gen.site.pages { +// gen.page_generate(page)! +// } + +// if gen.errors.len > 0 { +// println('Page List: is header collection and page name per collection.\nAvailable pages:\n${gen.client.list_markdown()!}') +// return error('Errors occurred during site generation:\n${gen.errors.join('\n\n')}\n') +// } +// } + +// fn (mut generator SiteGenerator) error(msg string) ! { +// console.print_stderr('Error: ${msg}') +// generator.errors << msg +// } + +// fn (mut generator SiteGenerator) page_generate(args_ Page) ! { +// mut args := args_ + +// mut content := ['---'] + +// mut parts := args.src.split(':') +// if parts.len != 2 { +// generator.error("Invalid src format for page '${args.src}', expected format: collection:page_name, TODO: fix in ${args.path}, check the collection & page_name exists in the pagelist")! +// return +// } +// collection_name := parts[0] +// page_name := parts[1] + +// mut page_content := generator.client.get_page_content(collection_name, page_name) or { +// generator.error("Couldn't find page '${collection_name}:${page_name}' is formatted as collectionname:pagename. TODO: fix in ${args.path}, check the collection & page_name exists in the pagelist. ")! +// return +// } + +// if args.description.len == 0 { +// descnew := markdowntools.extract_title(page_content) +// if descnew != '' { +// args.description = descnew +// } else { +// args.description = page_name +// } +// } + +// if args.title.len == 0 { +// descnew := markdowntools.extract_title(page_content) +// if descnew != '' { +// args.title = descnew +// } else { +// args.title = page_name +// } +// } +// // Escape single quotes in YAML by doubling them +// escaped_title := args.title.replace("'", "''") +// content << "title: '${escaped_title}'" + +// if args.description.len > 0 { +// escaped_description := args.description.replace("'", "''") +// content << "description: '${escaped_description}'" +// } + +// if args.slug.len > 0 { +// escaped_slug := args.slug.replace("'", "''") +// content << "slug: '${escaped_slug}'" +// } + +// if args.hide_title { +// content << 'hide_title: ${args.hide_title}' +// } + +// if args.draft { +// content << 'draft: ${args.draft}' +// } + +// if args.position > 0 { +// content << 'sidebar_position: ${args.position}' +// } + +// content << '---' + +// mut c := content.join('\n') + +// if args.title_nr > 0 { +// // Set the title number in the page content +// page_content = markdowntools.set_titles(page_content, args.title_nr) +// } + +// // Fix links to account for nested categories +// page_content = generator.fix_links(page_content, args.path) + +// c += '\n${page_content}\n' + +// if args.path.ends_with('/') || args.path.trim_space() == '' { +// // means is dir +// args.path += page_name +// } + +// if !args.path.ends_with('.md') { +// args.path += '.md' +// } + +// mut pagepath := '${generator.path.path}/${args.path}' +// mut pagefile := pathlib.get_file(path: pagepath, create: true)! + +// pagefile.write(c)! + +// generator.client.copy_pages(collection_name, page_name, pagefile.path_dir()) or { +// generator.error("Couldn't copy pages for page:'${page_name}' in collection:'${collection_name}'\nERROR:${err}")! +// return +// } +// generator.client.copy_images(collection_name, page_name, pagefile.path_dir()) or { +// generator.error("Couldn't copy images for page:'${page_name}' in collection:'${collection_name}'\nERROR:${err}")! +// return +// } +// generator.client.copy_files(collection_name, page_name, pagefile.path_dir()) or { +// generator.error("Couldn't copy files for page:'${page_name}' in collection:'${collection_name}'\nERROR:${err}")! +// return +// } +// } + +// fn (mut generator SiteGenerator) section_generate(args_ Section) ! { +// mut args := args_ + +// mut c := '' +// if args.description.len > 0 { +// c = '{ +// "label": "${args.label}", +// "position": ${args.position}, +// "link": { +// "type": "generated-index", +// "description": "${args.description}" +// } +// }' +// } else { +// c = '{ +// "label": "${args.label}", +// "position": ${args.position}, +// "link": { +// "type": "generated-index" +// } +// }' +// } + +// mut category_path := '${generator.path.path}/${args.path}/_category_.json' +// mut catfile := pathlib.get_file(path: category_path, create: true)! + +// catfile.write(c)! +// } + +// // Strip numeric prefix from filename (e.g., "03_linux_installation" -> "linux_installation") +// // Docusaurus automatically strips these prefixes from URLs +// fn strip_numeric_prefix(name string) string { +// // Match pattern: digits followed by underscore at the start +// if name.len > 2 && name[0].is_digit() { +// for i := 1; i < name.len; i++ { +// if name[i] == `_` { +// // Found the underscore, return everything after it +// return name[i + 1..] +// } +// if !name[i].is_digit() { +// // Not a numeric prefix pattern, return as-is +// return name +// } +// } +// } +// 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, 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 +// mut collection_page_map := map[string]string{} // "collection:page" -> directory path + +// for page in generator.site.pages { +// parts := page.src.split(':') +// if parts.len != 2 { +// continue +// } +// collection := parts[0] +// page_name := parts[1] + +// // Extract directory path from page.path +// mut dir_path := page.path.trim('/') +// if dir_path.contains('/') && !dir_path.ends_with('/') { +// last_part := dir_path.all_after_last('/') +// if last_part.contains('.') || last_part == page_name { +// dir_path = dir_path.all_before_last('/') +// } +// } + +// // Store collection -> directory mapping for nested collections +// if dir_path != collection && dir_path != '' { +// collection_paths[collection] = dir_path +// } + +// // Store page_name -> directory path for fixing same-collection links +// // Strip numeric prefix from page_name for the map key +// clean_page_name := strip_numeric_prefix(page_name) +// page_to_path[clean_page_name] = dir_path + +// // Store collection:page -> directory path for fixing collection:page format links +// collection_page_map['${collection}:${clean_page_name}'] = dir_path +// } + +// // STEP 1: Strip numeric prefixes from all page references in links FIRST +// mut lines := result.split('\n') +// for i, line in lines { +// if !line.contains('](') { +// continue +// } + +// mut new_line := line +// parts := line.split('](') +// if parts.len < 2 { +// continue +// } + +// for j := 1; j < parts.len; j++ { +// close_idx := parts[j].index(')') or { continue } +// link_url := parts[j][..close_idx] + +// mut new_url := link_url +// if link_url.contains('/') { +// path_part := link_url.all_before_last('/') +// file_part := link_url.all_after_last('/') +// new_file := strip_numeric_prefix(file_part) +// if new_file != file_part { +// new_url = '${path_part}/${new_file}' +// } +// } else { +// new_url = strip_numeric_prefix(link_url) +// } + +// if new_url != link_url { +// new_line = new_line.replace('](${link_url})', '](${new_url})') +// } +// } +// lines[i] = new_line +// } +// result = lines.join('\n') + +// // STEP 2: Replace ../collection/ with ../actual/nested/path/ for cross-collection links +// for collection, actual_path in collection_paths { +// result = result.replace('../${collection}/', '../${actual_path}/') +// } + +// // 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) { +// 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 +// // 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(':') +// new_link := calculate_relative_path(current_dir, target_dir, page_name) +// result = result.replace(old_pattern, new_link) +// } +// } + +// // STEP 5: Fix bare page references (from atlas self-contained exports) +// // Atlas exports convert cross-collection links to simple relative links like "token_system2.md" +// // We need to transform these to proper relative paths based on Docusaurus structure +// for page_name, target_dir in page_to_path { +// // Match links in the format ](page_name) or ](page_name.md) +// old_link_with_md := '](${page_name}.md)' +// old_link_without_md := '](${page_name})' + +// if result.contains(old_link_with_md) || result.contains(old_link_without_md) { +// new_link := calculate_relative_path(current_dir, target_dir, page_name) +// // Replace both .md and non-.md versions +// result = result.replace(old_link_with_md, '](${new_link})') +// result = result.replace(old_link_without_md, '](${new_link})') +// } +// } + +// // STEP 6: Remove .md extensions from all remaining links (Docusaurus doesn't use them in URLs) +// result = result.replace('.md)', ')') + +// // STEP 7: Fix image links to point to img/ subdirectory +// // Images are copied to img/ subdirectory by copy_images(), so we need to update the links +// // Transform ![alt](image.png) to ![alt](img/image.png) for local images only +// mut image_lines := result.split('\n') +// for i, line in image_lines { +// // Find image links: ![...](...) but skip external URLs +// if line.contains('![') { +// mut pos := 0 +// for { +// img_start := line.index_after('![', pos) or { break } +// alt_end := line.index_after(']', img_start) or { break } +// if alt_end + 1 >= line.len || line[alt_end + 1] != `(` { +// pos = alt_end + 1 +// continue +// } +// url_start := alt_end + 2 +// url_end := line.index_after(')', url_start) or { break } +// url := line[url_start..url_end] + +// // Skip external URLs and already-prefixed img/ paths +// if url.starts_with('http://') || url.starts_with('https://') +// || url.starts_with('img/') || url.starts_with('./img/') { +// pos = url_end + 1 +// continue +// } + +// // Skip absolute paths and paths with ../ +// if url.starts_with('/') || url.starts_with('../') { +// pos = url_end + 1 +// continue +// } + +// // This is a local image reference - add img/ prefix +// new_url := 'img/${url}' +// image_lines[i] = line[0..url_start] + new_url + line[url_end..] +// break +// } +// } +// } +// result = image_lines.join('\n') + +// return result +// } diff --git a/lib/web/docusaurus/dsite_link_docs.v b/lib/web/docusaurus/dsite_link_docs.v new file mode 100644 index 00000000..df88e9c1 --- /dev/null +++ b/lib/web/docusaurus/dsite_link_docs.v @@ -0,0 +1,29 @@ +module docusaurus + +import incubaid.herolib.core.pathlib +// import incubaid.herolib.data.atlas.client as atlas_client +// import incubaid.herolib.web.site { Page, Section, Site } +import incubaid.herolib.data.markdown.tools as markdowntools +import incubaid.herolib.ui.console +import os + +// Generate docs from site configuration +pub fn (mut docsite DocSite) link_docs() ! { + c := config()! + + // we generate the docs in the build path + docs_path := '${c.path_build.path}/docs' + + //reset it + os.rmdir_all(docs_path)! + os.mkdir(docs_path)! + + //TODO: now link all the collections to the docs folder + + println(c) + + if true{ + panic('link_docs is not yet implemented') + } + // $dbg; +} diff --git a/lib/web/docusaurus/dsite_store_structure.v b/lib/web/docusaurus/dsite_store_structure.v deleted file mode 100644 index ded27431..00000000 --- a/lib/web/docusaurus/dsite_store_structure.v +++ /dev/null @@ -1,40 +0,0 @@ -module docusaurus - -import incubaid.herolib.core.base -import incubaid.herolib.core.texttools - -// // Store the Docusaurus site structure in Redis for link processing -// // This maps collection:page to their actual Docusaurus paths -// pub fn (mut docsite DocSite) store_site_structure() ! { -// mut context := base.context()! -// mut redis := context.redis()! - -// // Store mapping of collection:page to docusaurus path (without .md extension) -// for page in docsite.website.pages { -// parts := page.src.split(':') -// if parts.len != 2 { -// continue -// } -// collection_name := texttools.name_fix(parts[0]) -// page_name := texttools.name_fix(parts[1]) - -// // Calculate the docusaurus path (without .md extension for URLs) -// mut doc_path := page.path - -// // Handle empty or root path -// if doc_path.trim_space() == '' || doc_path == '/' { -// doc_path = page_name -// } else if doc_path.ends_with('/') { -// doc_path += page_name -// } - -// // Remove .md extension if present for URL paths -// if doc_path.ends_with('.md') { -// doc_path = doc_path[..doc_path.len - 3] -// } - -// // Store in Redis with key format: collection:page.md -// key := '${collection_name}:${page_name}.md' -// redis.hset('doctree_docusaurus_paths', key, doc_path)! -// } -// } diff --git a/lib/web/docusaurus/factory.v b/lib/web/docusaurus/factory.v index 8a7562ae..3c88bf4c 100644 --- a/lib/web/docusaurus/factory.v +++ b/lib/web/docusaurus/factory.v @@ -26,7 +26,7 @@ pub fn dsite_define(sitename string) ! { name: sitename path_publish: pathlib.get_dir(path: '${path_build_}/build', create: true)! path_build: pathlib.get_dir(path: path_build_, create: true)! - config: new_configuration(website.siteconfig)! + config: new_configuration(website)! website: website } diff --git a/lib/web/docusaurus/model_configuration_test.v b/lib/web/docusaurus/model_configuration_test.v deleted file mode 100644 index 1bc61bdc..00000000 --- a/lib/web/docusaurus/model_configuration_test.v +++ /dev/null @@ -1,106 +0,0 @@ -module docusaurus - -import os -import incubaid.herolib.core.pathlib -import incubaid.herolib.core.base // For context and Redis, if test needs to manage it -import time - -const test_heroscript_content = '!!site.config\n name:"Kristof"\n title:"Internet Geek"\n tagline:"Internet Geek"\n url:"https://friends.threefold.info"\n url_home:"docs/"\n base_url:"/kristof/"\n favicon:"img/favicon.png"\n image:"img/tf_graph.png"\n copyright:"Kristof"\n\n!!site.config_meta\n description:"ThreeFold is laying the foundation for a geo aware Web 4, the next generation of the Internet."\n image:"https://threefold.info/kristof/img/tf_graph.png"\n title:"ThreeFold Technology Vision"\n\n!!site.build_dest\n ssh_name:"production"\n path:"/root/hero/www/info/kristof"\n\n!!site.navbar\n title:"Kristof = Chief Executive Geek"\n logo_alt:"Kristof Logo"\n logo_src:"img/logo.svg"\n logo_src_dark:"img/logo.svg"\n\n!!site.navbar_item\n label:"ThreeFold Technology"\n href:"https://threefold.info/kristof/"\n position:"right"\n\n!!site.navbar_item\n label:"ThreeFold.io"\n href:"https://threefold.io"\n position:"right"\n\n!!site.footer\n style:"dark"\n\n!!site.footer_item\n title:"Docs"\n label:"Introduction"\n href:"/docs"\n\n!!site.footer_item\n title:"Docs"\n label:"TFGrid V4 Docs"\n href:"https://docs.threefold.io/"\n\n!!site.footer_item\n title:"Community"\n label:"Telegram"\n href:"https://t.me/threefold"\n\n!!site.footer_item\n title:"Community"\n label:"X"\n href:"https://x.com/threefold_io"\n\n!!site.footer_item\n title:"Links"\n label:"ThreeFold.io"\n href:"https://threefold.io"\n' - -fn test_load_configuration_from_heroscript() ! { - // Ensure context is initialized for Redis connection if siteconfig.new() needs it implicitly - base.context()! - - temp_cfg_dir := os.join_path(os.temp_dir(), 'test_docusaurus_cfg_${time.ticks()}') - os.mkdir_all(temp_cfg_dir)! - defer { - os.rmdir_all(temp_cfg_dir) or { eprintln('Error removing temp dir.') } - } - - heroscript_path := os.join_path(temp_cfg_dir, 'config.heroscript') - os.write_file(heroscript_path, test_heroscript_content)! - - config := load_configuration(temp_cfg_dir)! - - // Main assertions - assert config.main.name == 'kristof' // texttools.name_fix converts to lowercase - assert config.main.title == 'Internet Geek' - assert config.main.tagline == 'Internet Geek' - assert config.main.url == 'https://friends.threefold.info' - assert config.main.url_home == 'docs/' - assert config.main.base_url == '/kristof/' - assert config.main.favicon == 'img/favicon.png' - assert config.main.image == 'img/tf_graph.png' - assert config.main.copyright == 'Kristof' - - // Metadata assertions - assert config.main.metadata.title == 'ThreeFold Technology Vision' - assert config.main.metadata.description == 'ThreeFold is laying the foundation for a geo aware Web 4, the next generation of the Internet.' - assert config.main.metadata.image == 'https://threefold.info/kristof/img/tf_graph.png' - - // Build Dest assertions - assert config.main.build_dest.len == 1 - assert config.main.build_dest[0] == '/root/hero/www/info/kristof' - - // Navbar assertions - assert config.navbar.title == 'Kristof = Chief Executive Geek' - assert config.navbar.logo.alt == 'Kristof Logo' - assert config.navbar.logo.src == 'img/logo.svg' - assert config.navbar.logo.src_dark == 'img/logo.svg' - assert config.navbar.items.len == 2 - assert config.navbar.items[0].label == 'ThreeFold Technology' - assert config.navbar.items[0].href == 'https://threefold.info/kristof/' - assert config.navbar.items[0].position == 'right' - assert config.navbar.items[1].label == 'ThreeFold.io' - assert config.navbar.items[1].href == 'https://threefold.io' - assert config.navbar.items[1].position == 'right' - - // Footer assertions - assert config.footer.style == 'dark' - assert config.footer.links.len == 3 // 'Docs', 'Community', 'Links' - - // Check 'Docs' footer links - mut docs_link_found := false - for link in config.footer.links { - if link.title == 'Docs' { - docs_link_found = true - assert link.items.len == 2 - assert link.items[0].label == 'Introduction' - assert link.items[0].href == '/docs' - assert link.items[1].label == 'TFGrid V4 Docs' - assert link.items[1].href == 'https://docs.threefold.io/' - break - } - } - assert docs_link_found - - // Check 'Community' footer links - mut community_link_found := false - for link in config.footer.links { - if link.title == 'Community' { - community_link_found = true - assert link.items.len == 2 - assert link.items[0].label == 'Telegram' - assert link.items[0].href == 'https://t.me/threefold' - assert link.items[1].label == 'X' - assert link.items[1].href == 'https://x.com/threefold_io' - break - } - } - assert community_link_found - - // Check 'Links' footer links - mut links_link_found := false - for link in config.footer.links { - if link.title == 'Links' { - links_link_found = true - assert link.items.len == 1 - assert link.items[0].label == 'ThreeFold.io' - assert link.items[0].href == 'https://threefold.io' - break - } - } - assert links_link_found - - println('test_load_configuration_from_heroscript passed successfully.') -} diff --git a/lib/web/docusaurus/play.v b/lib/web/docusaurus/play.v index 14b6f0f1..50aa57a8 100644 --- a/lib/web/docusaurus/play.v +++ b/lib/web/docusaurus/play.v @@ -19,7 +19,6 @@ pub fn play(mut plbook PlayBook) ! { template_update: param_define.get_default_false('template_update') install: param_define.get_default_false('install') atlas_dir: param_define.get_default('atlas_dir', '${os.home_dir()}/hero/var/atlas_export')! - use_atlas: param_define.get_default_false('use_atlas') )! site_name := param_define.get('name') or { diff --git a/lib/web/s/siteplay_test.v b/lib/web/s/siteplay_test.v deleted file mode 100644 index fcef77c8..00000000 --- a/lib/web/s/siteplay_test.v +++ /dev/null @@ -1,444 +0,0 @@ -module site - -import incubaid.herolib.core.playbook -import incubaid.herolib.ui.console -import os - -// Big comprehensive HeroScript for testing -const test_heroscript = ' -!!site.config - name: "test_docs" - title: "Test Documentation Site" - description: "A comprehensive test documentation site" - tagline: "Testing everything" - favicon: "img/favicon.png" - image: "img/test-og.png" - copyright: "© 2024 Test Organization" - url: "https://test.example.com" - base_url: "/" - url_home: "/docs" - -!!site.config_meta - title: "Test Docs - Advanced" - image: "img/test-og-alternative.png" - description: "Advanced test documentation" - -!!site.navbar - title: "Test Documentation" - logo_alt: "Test Logo" - logo_src: "img/logo.svg" - logo_src_dark: "img/logo-dark.svg" - -!!site.navbar_item - label: "Getting Started" - to: "intro" - position: "left" - -!!site.navbar_item - label: "API Reference" - to: "api" - position: "left" - -!!site.navbar_item - label: "GitHub" - href: "https://github.com/example/test" - position: "right" - -!!site.navbar_item - label: "Blog" - href: "https://blog.example.com" - position: "right" - -!!site.footer - style: "dark" - -!!site.footer_item - title: "Documentation" - label: "Introduction" - to: "intro" - -!!site.footer_item - title: "Documentation" - label: "Getting Started" - to: "getting-started" - -!!site.footer_item - title: "Documentation" - label: "Advanced Topics" - to: "advanced" - -!!site.footer_item - title: "Community" - label: "Discord" - href: "https://discord.gg/example" - -!!site.footer_item - title: "Community" - label: "Twitter" - href: "https://twitter.com/example" - -!!site.footer_item - title: "Legal" - label: "Privacy Policy" - href: "https://example.com/privacy" - -!!site.footer_item - title: "Legal" - label: "Terms of Service" - href: "https://example.com/terms" - -!!site.announcement - id: "v2-release" - content: "🎉 Version 2.0 is now available! Check out the new features." - background_color: "#1a472a" - text_color: "#fff" - is_closeable: true - -!!site.page_category - name: "getting_started" - label: "Getting Started" - position: 10 - -!!site.page src: "guides:introduction" - title: "Introduction to Test Docs" - description: "Learn what this project is about" - -!!site.page src: "installation" - title: "Installation Guide" - description: "How to install and setup" - -!!site.page src: "quick_start" - title: "Quick Start" - description: "5 minute quick start guide" - -!!site.page_category - name: "concepts" - label: "Core Concepts" - position: 20 - -!!site.page src: "concepts:architecture" - title: "Architecture Overview" - description: "Understanding the system architecture" - -!!site.page src: "components" - title: "Key Components" - description: "Learn about the main components" - -!!site.page src: "workflow" - title: "Typical Workflow" - description: "How to use the system" - -!!site.page_category - name: "api" - label: "API Reference" - position: 30 - -!!site.page src: "api:rest" - title: "REST API" - description: "Complete REST API reference" - -!!site.page src: "graphql" - title: "GraphQL API" - description: "GraphQL API documentation" - -!!site.page src: "webhooks" - title: "Webhooks" - description: "Webhook configuration and examples" - -!!site.page_category - name: "advanced" - label: "Advanced Topics" - position: 40 - -!!site.page src: "advanced:performance" - title: "Performance Optimization" - description: "Tips for optimal performance" - -!!site.page src: "scaling" - title: "Scaling Guide" - description: "How to scale the system" - -!!site.page src: "security" - title: "Security Best Practices" - description: "Security considerations and best practices" - -!!site.page src: "troubleshooting" - title: "Troubleshooting" - description: "Common issues and solutions" - draft: false - -!!site.publish - path: "/var/www/html/docs" - ssh_name: "production-server" - -!!site.publish_dev - path: "/tmp/docs-dev" -' - -fn test_site1() ! { - console.print_header('Site Module Comprehensive Test') - console.lf() - - // ======================================================== - // TEST 1: Create playbook from heroscript - // ======================================================== - console.print_item('TEST 1: Creating playbook from HeroScript') - mut plbook := playbook.new(text: test_heroscript)! - console.print_green('✓ Playbook created successfully') - console.lf() - - // ======================================================== - // TEST 2: Process site configuration - // ======================================================== - console.print_item('TEST 2: Processing site.play()') - site.play(mut plbook)! - console.print_green('✓ Site configuration processed successfully') - console.lf() - - // ======================================================== - // TEST 3: Retrieve site and validate - // ======================================================== - console.print_item('TEST 3: Retrieving configured site') - mut test_site := site.get(name: 'test_docs')! - console.print_green('✓ Site retrieved successfully') - console.lf() - - // ======================================================== - // TEST 4: Validate SiteConfig - // ======================================================== - console.print_header('Validating SiteConfig') - mut config := &test_site.siteconfig - - help_test_string('Site Name', config.name, 'test_docs') - help_test_string('Site Title', config.title, 'Test Documentation Site') - help_test_string('Site Description', config.description, 'A comprehensive test documentation site') - help_test_string('Site Tagline', config.tagline, 'Testing everything') - help_test_string('Copyright', config.copyright, '© 2024 Test Organization') - help_test_string('Base URL', config.base_url, '/') - help_test_string('URL Home', config.url_home, '/docs') - - help_test_string('Meta Title', config.meta_title, 'Test Docs - Advanced') - help_test_string('Meta Image', config.meta_image, 'img/test-og-alternative.png') - - assert config.build_dest.len == 1, 'Should have 1 production build destination' - console.print_green('✓ Production build dest: ${config.build_dest[0].path}') - - assert config.build_dest_dev.len == 1, 'Should have 1 dev build destination' - console.print_green('✓ Dev build dest: ${config.build_dest_dev[0].path}') - - console.lf() - - // ======================================================== - // TEST 5: Validate Menu Configuration - // ======================================================== - console.print_header('Validating Menu Configuration') - mut menu := config.menu - - help_test_string('Menu Title', menu.title, 'Test Documentation') - help_test_string('Menu Logo Alt', menu.logo_alt, 'Test Logo') - help_test_string('Menu Logo Src', menu.logo_src, 'img/logo.svg') - help_test_string('Menu Logo Src Dark', menu.logo_src_dark, 'img/logo-dark.svg') - - assert menu.items.len == 4, 'Should have 4 navbar items, got ${menu.items.len}' - console.print_green('✓ Menu has 4 navbar items') - - // Validate navbar items - help_test_navbar_item(menu.items[0], 'Getting Started', 'intro', '', 'left') - help_test_navbar_item(menu.items[1], 'API Reference', 'api', '', 'left') - help_test_navbar_item(menu.items[2], 'GitHub', '', 'https://github.com/example/test', - 'right') - help_test_navbar_item(menu.items[3], 'Blog', '', 'https://blog.example.com', 'right') - - console.lf() - - // ======================================================== - // TEST 6: Validate Footer Configuration - // ======================================================== - console.print_header('Validating Footer Configuration') - mut footer := config.footer - - help_test_string('Footer Style', footer.style, 'dark') - assert footer.links.len == 3, 'Should have 3 footer link groups, got ${footer.links.len}' - console.print_green('✓ Footer has 3 link groups') - - // Validate footer structure - for link_group in footer.links { - console.print_item('Footer group: "${link_group.title}" has ${link_group.items.len} items') - } - - // Detailed footer validation - mut doc_links := footer.links.filter(it.title == 'Documentation') - assert doc_links.len == 1, 'Should have 1 Documentation link group' - assert doc_links[0].items.len == 3, 'Documentation should have 3 items' - console.print_green('✓ Documentation footer: 3 items') - - mut community_links := footer.links.filter(it.title == 'Community') - assert community_links.len == 1, 'Should have 1 Community link group' - assert community_links[0].items.len == 2, 'Community should have 2 items' - console.print_green('✓ Community footer: 2 items') - - mut legal_links := footer.links.filter(it.title == 'Legal') - assert legal_links.len == 1, 'Should have 1 Legal link group' - assert legal_links[0].items.len == 2, 'Legal should have 2 items' - console.print_green('✓ Legal footer: 2 items') - - console.lf() - - // ======================================================== - // TEST 7: Validate Announcement Bar - // ======================================================== - console.print_header('Validating Announcement Bar') - mut announcement := config.announcement - - help_test_string('Announcement ID', announcement.id, 'v2-release') - help_test_string('Announcement Content', announcement.content, '🎉 Version 2.0 is now available! Check out the new features.') - help_test_string('Announcement BG Color', announcement.background_color, '#1a472a') - help_test_string('Announcement Text Color', announcement.text_color, '#fff') - assert announcement.is_closeable == true, 'Announcement should be closeable' - console.print_green('✓ Announcement bar configured correctly') - - console.lf() - - // ======================================================== - // TEST 8: Validate Pages - // ======================================================== - console.print_header('Validating Pages') - mut pages := test_site.pages.clone() - - assert pages.len == 13, 'Should have 13 pages, got ${pages.len}' - console.print_green('✓ Total pages: ${pages.len}') - - // List and validate pages - mut page_ids := pages.keys() - page_ids.sort() - - for page_id in page_ids { - mut page := pages[page_id] - console.print_debug(' Page: ${page_id} - "${page.title}"') - } - - // Validate specific pages - assert 'guides:introduction' in pages, 'guides:introduction page not found' - console.print_green('✓ Found guides:introduction') - - assert 'concepts:architecture' in pages, 'concepts:architecture page not found' - console.print_green('✓ Found concepts:architecture') - - assert 'api:rest' in pages, 'api:rest page not found' - console.print_green('✓ Found api:rest') - - console.lf() - - // ======================================================== - // TEST 9: Validate Navigation Structure - // ======================================================== - console.print_header('Validating Navigation Structure') - mut sidebar := unsafe { test_site.nav.my_sidebar.clone() } - - console.print_item('Navigation sidebar has ${sidebar.len} items') - - // Count categories - mut category_count := 0 - mut doc_count := 0 - - for item in sidebar { - match item { - site.NavCat { - category_count++ - console.print_debug(' Category: "${item.label}" with ${item.items.len} sub-items') - } - site.NavDoc { - doc_count++ - console.print_debug(' Doc: "${item.label}" (${item.id})') - } - site.NavLink { - console.print_debug(' Link: "${item.label}" -> ${item.href}') - } - } - } - - assert category_count == 4, 'Should have 4 categories, got ${category_count}' - console.print_green('✓ Navigation has 4 categories') - - // Validate category structure - for item in sidebar { - match item { - site.NavCat { - console.print_item('Category: "${item.label}"') - println(' Collapsible: ${item.collapsible}, Collapsed: ${item.collapsed}') - println(' Items: ${item.items.len}') - - // Validate sub-items - for sub_item in item.items { - match sub_item { - site.NavDoc { - println(' - ${sub_item.label} (${sub_item.id})') - } - else { - println(' - Unexpected item type') - } - } - } - } - else {} - } - } - - console.lf() - - // ======================================================== - // TEST 10: Validate Site Factory - // ======================================================== - console.print_header('Validating Site Factory') - - mut all_sites := site.list() - console.print_item('Total sites registered: ${all_sites.len}') - for site_name in all_sites { - console.print_debug(' - ${site_name}') - } - - assert all_sites.contains('test_docs'), 'test_docs should be in sites list' - console.print_green('✓ test_docs found in factory') - - assert site.exists(name: 'test_docs'), 'test_docs should exist' - console.print_green('✓ test_docs verified to exist') - - console.lf() - - // ======================================================== - // FINAL SUMMARY - // ======================================================== - console.print_header('Test Summary') - console.print_green('✓ All tests passed successfully!') - console.print_item('Site Name: ${config.name}') - console.print_item('Pages: ${pages.len}') - console.print_item('Navigation Categories: ${category_count}') - console.print_item('Navbar Items: ${menu.items.len}') - console.print_item('Footer Groups: ${footer.links.len}') - console.print_item('Announcement: Active') - console.print_item('Build Destinations: ${config.build_dest.len} prod, ${config.build_dest_dev.len} dev') - - console.lf() - console.print_green('All validations completed successfully!') -} - -// ============================================================ -// Helper Functions for Testing -// ============================================================ - -fn help_test_string(label string, actual string, expected string) { - if actual == expected { - console.print_green('✓ ${label}: "${actual}"') - } else { - console.print_stderr('✗ ${label}: expected "${expected}", got "${actual}"') - panic('Test failed: ${label}') - } -} - -fn help_test_navbar_item(item MenuItem, label string, to string, href string, position string) { - assert item.label == label, 'Expected label "${label}", got "${item.label}"' - assert item.to == to, 'Expected to "${to}", got "${item.to}"' - assert item.href == href, 'Expected href "${href}", got "${item.href}"' - assert item.position == position, 'Expected position "${position}", got "${item.position}"' - console.print_green('✓ Navbar item: "${label}"') -} diff --git a/lib/web/site/model_nav.v b/lib/web/site/model_nav.v index 28c23a05..d4fee447 100644 --- a/lib/web/site/model_nav.v +++ b/lib/web/site/model_nav.v @@ -94,7 +94,7 @@ fn nav_item_to_json(item NavItem) !NavItemJson { } // Convert entire NavConfig sidebar to JSON string -fn (nc NavConfig) sidebar_to_json() !string { +pub fn (nc NavConfig) sidebar_to_json() !string { mut result := []NavItemJson{} for item in nc.my_sidebar { result << nav_item_to_json(item)! diff --git a/lib/web/site/readme.md b/lib/web/site/readme.md index eda5ed03..19ac7b0f 100644 --- a/lib/web/site/readme.md +++ b/lib/web/site/readme.md @@ -38,9 +38,8 @@ site.play(mut plbook)! mut mysite := site.get(name: 'my_docs')! // Print available pages -pages_map := mysite.list_pages() -for page_id, _ in pages_map { - console.print_item('Page: ${page_id}') +for page_id, page in mysite.pages { + console.print_item('Page: ${page_id} - "${page.title}"') } println('Site has ${mysite.pages.len} pages') @@ -48,6 +47,156 @@ println('Site has ${mysite.pages.len} pages') --- +## API Reference + +### Site Factory + +Factory functions to create and retrieve site instances: + +```v +// Create a new site +mut mysite := site.new(name: 'my_docs')! + +// Get existing site +mut mysite := site.get(name: 'my_docs')! + +// Check if site exists +if site.exists(name: 'my_docs') { + println('Site exists') +} + +// Get all site names +site_names := site.list() // Returns []string + +// Get default site (creates if needed) +mut default := site.default()! +``` + +### Site Object Structure + +```v +pub struct Site { +pub mut: + pages map[string]Page // key: "collection:page_name" + nav NavConfig // Navigation sidebar + siteconfig SiteConfig // Full configuration +} +``` + +### Accessing Pages + +```v +// Access all pages +pages := mysite.pages // map[string]Page + +// Get specific page +page := mysite.pages['docs:introduction'] + +// Page structure +pub struct Page { +pub mut: + id string // "collection:page_name" + title string // Display title + description string // SEO metadata + draft bool // Hidden if true + hide_title bool // Don't show title in rendering + src string // Source reference +} +``` + +### Navigation Structure + +```v +// Access sidebar navigation +sidebar := mysite.nav.my_sidebar // []NavItem + +// NavItem is a sum type (can be one of three types): +pub type NavItem = NavDoc | NavCat | NavLink + +// Navigation items: + +pub struct NavDoc { +pub: + id string // page id + label string // display name +} + +pub struct NavCat { +pub mut: + label string + collapsible bool + collapsed bool + items []NavItem // nested NavDoc/NavCat/NavLink +} + +pub struct NavLink { +pub: + label string + href string + description string +} + +// Example: iterate navigation +for item in mysite.nav.my_sidebar { + match item { + NavDoc { + println('Page: ${item.label} (${item.id})') + } + NavCat { + println('Category: ${item.label} (${item.items.len} items)') + } + NavLink { + println('Link: ${item.label} -> ${item.href}') + } + } +} +``` + +### Site Configuration + +```v +pub struct SiteConfig { +pub mut: + // Core + name string + title string + description string + tagline string + favicon string + image string + copyright string + + // URLs (Docusaurus) + url string // Full site URL + base_url string // Base path (e.g., "/" or "/docs/") + url_home string // Home page path + + // SEO Metadata + meta_title string // SEO title override + meta_image string // OG image override + + // Publishing + build_dest []BuildDest // Production destinations + build_dest_dev []BuildDest // Development destinations + + // Navigation & Footer + footer Footer + menu Menu + announcement AnnouncementBar + + // Imports + imports []ImportItem +} + +pub struct BuildDest { +pub mut: + path string + ssh_name string +} +``` + +--- + ## Core Concepts ### Site @@ -179,7 +328,6 @@ Overrides specific metadata for SEO without changing core config. ```heroscript !!site.announcement - id: "new-release" content: "🎉 Version 2.0 is now available!" background_color: "#20232a" text_color: "#fff" @@ -237,6 +385,11 @@ Overrides specific metadata for SEO without changing core config. - `draft` - Hide from navigation (default: false) - `hide_title` - Don't show title in page (default: false) +**Category Parameters:** +- `name` - Category identifier (required) +- `label` - Display label (auto-generated from name if omitted) +- `position` - Sort order (auto-incremented if omitted) + ### 7. Content Imports ```heroscript @@ -377,19 +530,3 @@ Files are processed in alphabetical order. Numeric prefixes ensure: - Pages build the final structure - Publishing configured last ---- - -## Processing Order - -The Site module processes HeroScript in this strict order: - -1. Site Configuration -2. Metadata Overrides -3. Imports -4. Navigation -5. Footer -6. Announcement -7. Publishing -8. Pages & Categories - -Each stage depends on previous stages completing successfully.