From 347ebed5ea28b14f5b13e84a027e3af79117b91d Mon Sep 17 00:00:00 2001 From: Mahmoud-Emad Date: Thu, 6 Nov 2025 10:51:10 +0200 Subject: [PATCH] feat: Improve export self-containment and link handling - Use absolute paths for path_relative calculations - Validate links before export to populate page.links - Copy cross-collection referenced pages for self-contained export - Update export_link_path to generate local links for self-contained exports - Remove page from visited map to allow re-inclusion in other contexts --- lib/data/atlas/collection.v | 10 ++++++-- lib/data/atlas/export.v | 51 ++++++++++++++++++++++--------------- lib/data/atlas/link.v | 13 +++------- lib/data/atlas/page.v | 4 +++ 4 files changed, 46 insertions(+), 32 deletions(-) diff --git a/lib/data/atlas/collection.v b/lib/data/atlas/collection.v index b25367d2..9028c9cb 100644 --- a/lib/data/atlas/collection.v +++ b/lib/data/atlas/collection.v @@ -53,7 +53,10 @@ fn (mut c Collection) add_page(mut path pathlib.Path) ! { if name in c.pages { return error('Page ${name} already exists in collection ${c.name}') } - relativepath := path.path_relative(c.path()!.path)! + // Use absolute paths for path_relative to work correctly + mut col_path := pathlib.get(c.path) + mut page_abs_path := pathlib.get(path.absolute()) + relativepath := page_abs_path.path_relative(col_path.absolute())! mut p_new := Page{ name: name @@ -71,7 +74,10 @@ fn (mut c Collection) add_file(mut p pathlib.Path) ! { if name in c.files { return error('Page ${name} already exists in collection ${c.name}') } - relativepath := p.path_relative(c.path()!.path)! + // Use absolute paths for path_relative to work correctly + mut col_path := pathlib.get(c.path) + mut file_abs_path := pathlib.get(p.absolute()) + relativepath := file_abs_path.path_relative(col_path.absolute())! mut file_new := File{ name: name diff --git a/lib/data/atlas/export.v b/lib/data/atlas/export.v index ea0017c3..3fbb36f5 100644 --- a/lib/data/atlas/export.v +++ b/lib/data/atlas/export.v @@ -22,8 +22,8 @@ pub fn (mut a Atlas) export(args ExportArgs) ! { dest.empty()! } - // Validate links before export - // a.validate_links()! + // Validate links before export to populate page.links + a.validate_links()! for _, mut col in a.collections { col.export( @@ -67,6 +67,10 @@ pub fn (mut c Collection) export(args CollectionExportArgs) ! { )! json_file.write(meta)! + // Track cross-collection pages that need to be copied for self-contained export + mut cross_collection_pages := map[string]&Page{} // key: page.name, value: &Page + + // First pass: export all pages in this collection and collect cross-collection references for _, mut page in c.pages { // Get content with includes processed and links transformed for export content := page.content_with_fixed_links( @@ -78,6 +82,19 @@ pub fn (mut c Collection) export(args CollectionExportArgs) ! { mut dest_file := pathlib.get_file(path: '${col_dir.path}/${page.name}.md', create: true)! dest_file.write(content)! + // Collect cross-collection page references for copying + // IMPORTANT: Use cached links from validation (before transformation) to preserve collection info + for mut link in page.links { + // Only process valid page links (not files/images) from other collections + if link.status == .found && !link.is_file_link && !link.is_local_in_collection() { + mut target_page := link.target_page() or { continue } + // Use page name as key to avoid duplicates + if target_page.name !in cross_collection_pages { + cross_collection_pages[target_page.name] = target_page + } + } + } + // Redis operations... if args.redis { mut context := base.context()! @@ -86,23 +103,17 @@ pub fn (mut c Collection) export(args CollectionExportArgs) ! { } } - // // Export files - // if c.files.len > 0 { - // files_dir := pathlib.get_dir( - // path: '${col_dir.path}/files' - // create: true - // )! + // Second pass: copy cross-collection referenced pages to make collection self-contained + for _, mut ref_page in cross_collection_pages { + // Get the referenced page content with includes processed + ref_content := ref_page.content_with_fixed_links( + include: args.include + cross_collection: true + export_mode: true + )! - // for _, mut file in c.files { - // dest_path := '${files_dir.path}/${file.file_name()}' - // mut p2 := file.path()! - // p2.copy(dest: col_dir.path)! - - // if args.redis { - // mut context := base.context()! - // mut redis := context.redis()! - // redis.hset('atlas:${c.name}', file.file_name(), file.path()!.path)! - // } - // } - // } + // Write the referenced page to this collection's directory + mut dest_file := pathlib.get_file(path: '${col_dir.path}/${ref_page.name}.md', create: true)! + dest_file.write(ref_content)! + } } diff --git a/lib/data/atlas/link.v b/lib/data/atlas/link.v index 8637c69c..e2683fe5 100644 --- a/lib/data/atlas/link.v +++ b/lib/data/atlas/link.v @@ -242,27 +242,20 @@ fn (mut p Page) calculate_link_path(mut link Link, args FixLinksArgs) !string { return p.filesystem_link_path(mut link)! } -// export_link_path calculates path for export (flat structure: collection/file.md) +// export_link_path calculates path for export (self-contained: all references are local) fn (mut p Page) export_link_path(mut link Link) !string { - mut target_collection := '' mut target_filename := '' if link.is_file_link { mut tf := link.target_file()! - target_collection = tf.collection.name target_filename = tf.name } else { mut tp := link.target_page()! - target_collection = tp.collection.name target_filename = '${tp.name}.md' } - // Same collection: just filename, different collection: ../collection/filename - return if link.is_local_in_collection() { - target_filename - } else { - '../${target_collection}/${target_filename}' - } + // For self-contained exports, all links are local (cross-collection pages are copied) + return target_filename } // filesystem_link_path calculates path using actual filesystem paths diff --git a/lib/data/atlas/page.v b/lib/data/atlas/page.v index 1c42c4b0..3c739ad9 100644 --- a/lib/data/atlas/page.v +++ b/lib/data/atlas/page.v @@ -128,6 +128,10 @@ fn (mut p Page) process_includes(content string, mut visited map[string]bool) !s } } + // Remove this page from visited map to allow it to be included again in other contexts + // This prevents false positives when a page is included multiple times (which is valid) + visited.delete(page_key) + return processed_lines.join_lines() }