diff --git a/crates/rari-doc/src/helpers/subpages.rs b/crates/rari-doc/src/helpers/subpages.rs index 37ad3ec1..9b06a044 100644 --- a/crates/rari-doc/src/helpers/subpages.rs +++ b/crates/rari-doc/src/helpers/subpages.rs @@ -137,7 +137,7 @@ pub struct ListSubPagesContext<'a> { pub include_parent: bool, } -pub fn list_sub_pages_internal( +pub fn list_sub_pages_flattened_internal( out: &mut String, url: &str, locale: Locale, @@ -149,8 +149,7 @@ pub fn list_sub_pages_internal( include_parent, }: ListSubPagesContext<'_>, ) -> Result<(), DocError> { - let sub_pages = get_sub_pages(url, Some(1), sorter.unwrap_or_default())?; - let depth = depth.map(|i| i.saturating_sub(1)); + let sub_pages = get_sub_pages(url, depth, sorter.unwrap_or_default())?; if include_parent { let page = Page::from_url_with_locale_and_fallback(url, locale)?; write_parent_li(out, &page, locale)?; @@ -159,15 +158,46 @@ pub fn list_sub_pages_internal( if !page_types.is_empty() && !page_types.contains(&sub_page.page_type()) { continue; } + write_li_with_badges(out, &sub_page, locale, code, true)?; + } + Ok(()) +} +pub fn list_sub_pages_nested_internal( + out: &mut String, + url: &str, + locale: Locale, + depth: Option, + ListSubPagesContext { + sorter, + page_types, + code, + include_parent, + }: ListSubPagesContext<'_>, +) -> Result<(), DocError> { + if depth == Some(0) { + return Ok(()); + } + let sub_pages = get_sub_pages(url, Some(1), sorter.unwrap_or_default())?; + let depth = depth.map(|i| i.saturating_sub(1)); + if include_parent { + let page = Page::from_url_with_locale_and_fallback(url, locale)?; + write_parent_li(out, &page, locale)?; + } + for sub_page in sub_pages { + let page_type_match = page_types.is_empty() || page_types.contains(&sub_page.page_type()); let sub_sub_pages = get_sub_pages(sub_page.url(), depth, sorter.unwrap_or_default())?; if sub_sub_pages.is_empty() { - write_li_with_badges(out, &sub_page, locale, code, true)?; + if page_type_match { + write_li_with_badges(out, &sub_page, locale, code, true)?; + } } else { - write_li_with_badges(out, &sub_page, locale, code, false)?; - out.push_str("
    "); + if page_type_match { + write_li_with_badges(out, &sub_page, locale, code, false)?; + } + let mut sub_pages_out = String::new(); - list_sub_pages_internal( - out, + list_sub_pages_nested_internal( + &mut sub_pages_out, sub_page.url(), locale, depth, @@ -178,17 +208,24 @@ pub fn list_sub_pages_internal( include_parent, }, )?; - out.push_str("
"); - out.push_str(""); + if !sub_pages_out.is_empty() { + out.push_str("
    "); + out.push_str(&sub_pages_out); + out.push_str("
"); + } + if page_type_match { + out.push_str(""); + } } } Ok(()) } -pub fn list_sub_pages_grouped_internal( +pub fn list_sub_pages_flattened_grouped_internal( out: &mut String, url: &str, locale: Locale, + depth: Option, ListSubPagesContext { sorter, page_types, @@ -196,7 +233,7 @@ pub fn list_sub_pages_grouped_internal( include_parent, }: ListSubPagesContext<'_>, ) -> Result<(), DocError> { - let sub_pages = get_sub_pages(url, None, sorter.unwrap_or_default())?; + let sub_pages = get_sub_pages(url, depth, sorter.unwrap_or_default())?; let mut grouped = BTreeMap::new(); for sub_page in sub_pages.iter() { diff --git a/crates/rari-doc/src/html/sidebar.rs b/crates/rari-doc/src/html/sidebar.rs index ac54becb..9a7c3e13 100644 --- a/crates/rari-doc/src/html/sidebar.rs +++ b/crates/rari-doc/src/html/sidebar.rs @@ -21,7 +21,8 @@ use crate::cached_readers::read_sidebar; use crate::error::DocError; use crate::helpers; use crate::helpers::subpages::{ - list_sub_pages_grouped_internal, list_sub_pages_internal, ListSubPagesContext, + list_sub_pages_flattened_grouped_internal, list_sub_pages_flattened_internal, + list_sub_pages_nested_internal, ListSubPagesContext, }; use crate::pages::page::{Page, PageLike}; use crate::pages::types::doc::Doc; @@ -253,9 +254,25 @@ fn details_is_none(details: &Details) -> bool { matches!(details, Details::None) } +const fn depth_is_default(depth: &usize) -> bool { + *depth == 1 +} +const fn default_depth() -> usize { + 1 +} + +/// depth == 0 => None which means infinite otherwise Some(depth). +const fn depth_to_option(depth: usize) -> Option { + if depth == 0 { + None + } else { + Some(depth) + } +} + #[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone)] #[serde(rename_all = "camelCase")] -pub struct BasicEntry { +pub struct CoreEntry { #[serde(skip_serializing_if = "Option::is_none")] pub link: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -266,6 +283,13 @@ pub struct BasicEntry { pub details: Details, #[serde(default, skip_serializing_if = "is_default")] pub code: bool, +} + +#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct BasicEntry { + #[serde(flatten)] + pub core: CoreEntry, #[serde( default, skip_serializing_if = "sidebar_entries_are_empty", @@ -278,12 +302,6 @@ pub struct BasicEntry { #[serde(rename_all = "camelCase")] pub struct SubPageEntry { pub path: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub link: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub hash: Option, #[serde( default, deserialize_with = "t_or_vec", @@ -291,12 +309,33 @@ pub struct SubPageEntry { skip_serializing_if = "Vec::is_empty" )] pub tags: Vec, - #[serde(default, skip_serializing_if = "details_is_none")] - pub details: Details, #[serde(default, skip_serializing_if = "is_default")] - pub code: bool, + pub include_parent: bool, + #[serde(default = "default_depth", skip_serializing_if = "depth_is_default")] + pub depth: usize, + #[serde(default, skip_serializing_if = "is_default")] + pub nested: bool, + #[serde(flatten)] + pub core: CoreEntry, +} + +#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct SubPageGroupedEntry { + pub path: String, + #[serde( + default, + deserialize_with = "t_or_vec", + serialize_with = "serialize_t_or_vec", + skip_serializing_if = "Vec::is_empty" + )] + pub tags: Vec, #[serde(default, skip_serializing_if = "is_default")] pub include_parent: bool, + #[serde(default = "default_depth", skip_serializing_if = "depth_is_default")] + pub depth: usize, + #[serde(flatten)] + pub core: CoreEntry, } #[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone)] @@ -310,7 +349,7 @@ pub struct WebExtApiEntry { pub enum SidebarEntry { Section(BasicEntry), ListSubPages(SubPageEntry), - ListSubPagesGrouped(SubPageEntry), + ListSubPagesGrouped(SubPageGroupedEntry), WebExtApi(WebExtApiEntry), #[serde(untagged)] Default(BasicEntry), @@ -323,12 +362,20 @@ pub enum SidebarEntry { #[derive(Debug, Default)] pub enum MetaChildren { Children(Vec), - ListSubPages(String, Vec, bool, bool), + ListSubPages { + path: String, + tags: Vec, + code: bool, + include_parent: bool, + depth: Option, + nested: bool, + }, ListSubPagesGrouped { path: String, tags: Vec, code: bool, include_parent: bool, + depth: Option, }, WebExtApi, #[default] @@ -404,12 +451,15 @@ impl TryFrom for SidebarMetaEntry { fn try_from(value: SidebarEntry) -> Result { let res = match value { SidebarEntry::Section(BasicEntry { - link, - hash, - title, - code, + core: + CoreEntry { + link, + hash, + title, + details, + code, + }, children, - details, }) => SidebarMetaEntry { section: true, details, @@ -426,50 +476,74 @@ impl TryFrom for SidebarMetaEntry { ) }, }, + SidebarEntry::ListSubPages(SubPageEntry { - details, - tags, - link, - hash, - title, - path, - code, - include_parent, - }) => SidebarMetaEntry { - section: false, - details, - code: false, - content: SidebarMetaEntryContent::from_link_title_hash(link, title, hash), - children: MetaChildren::ListSubPages(path, tags, code, include_parent), - }, - SidebarEntry::ListSubPagesGrouped(SubPageEntry { - details, + core: + CoreEntry { + link, + hash, + title, + details, + code, + }, tags, - link, - hash, - title, path, - code, include_parent, + depth, + nested, }) => SidebarMetaEntry { section: false, details, code: false, content: SidebarMetaEntryContent::from_link_title_hash(link, title, hash), - children: MetaChildren::ListSubPagesGrouped { + children: MetaChildren::ListSubPages { path, tags, code, include_parent, + depth: depth_to_option(depth), + nested, }, }, + SidebarEntry::ListSubPagesGrouped(sub_page_entry) => { + let SubPageGroupedEntry { + core: + CoreEntry { + link, + hash, + title, + details, + code, + }, + tags, + path, + include_parent, + depth, + } = sub_page_entry; + SidebarMetaEntry { + section: false, + details, + code: false, + content: SidebarMetaEntryContent::from_link_title_hash(link, title, hash), + children: MetaChildren::ListSubPagesGrouped { + path, + tags, + code, + include_parent, + depth: depth_to_option(depth), + }, + } + } SidebarEntry::Default(BasicEntry { - link, - hash, - title, - code, + core: + CoreEntry { + link, + hash, + title, + details, + code, + }, children, - details, }) => SidebarMetaEntry { section: false, details, @@ -581,35 +655,43 @@ impl SidebarMetaEntry { child.render(out, locale, slug, l10n)?; } } - MetaChildren::ListSubPages(url, page_types, code, include_parent) => { - let url = if url.starts_with(concat!("/", default_locale().as_url_str(), "/")) { - Cow::Borrowed(url) + MetaChildren::ListSubPages { + path, + tags, + code, + include_parent, + depth, + nested, + } => { + let url = if path.starts_with(concat!("/", default_locale().as_url_str(), "/")) { + Cow::Borrowed(path) } else { Cow::Owned(concat_strs!( "/", Locale::default().as_url_str(), "/docs", - url + path )) }; - list_sub_pages_internal( - out, - &url, - locale, - Some(1), - ListSubPagesContext { - sorter: None, - page_types, - code: *code, - include_parent: *include_parent, - }, - )? + let ctx = ListSubPagesContext { + sorter: None, + page_types: tags, + code: *code, + include_parent: *include_parent, + }; + if *nested { + list_sub_pages_nested_internal(out, &url, locale, *depth, ctx)? + } else { + list_sub_pages_flattened_internal(out, &url, locale, *depth, ctx)? + } } + MetaChildren::ListSubPagesGrouped { path, tags, code, include_parent, + depth, } => { let url = if path.starts_with(concat!("/", default_locale().as_url_str(), "/")) { Cow::Borrowed(path) @@ -621,10 +703,11 @@ impl SidebarMetaEntry { path )) }; - list_sub_pages_grouped_internal( + list_sub_pages_flattened_grouped_internal( out, &url, locale, + *depth, ListSubPagesContext { sorter: None, page_types: tags, @@ -663,6 +746,6 @@ mod test { fn test_details_ser() { let yaml_str = r#"details: closed"#; let entry: BasicEntry = serde_yaml_ng::from_str(yaml_str).unwrap(); - assert_eq!(entry.details, Details::Closed); + assert_eq!(entry.core.details, Details::Closed); } } diff --git a/crates/rari-doc/src/templ/templs/listsubpages.rs b/crates/rari-doc/src/templ/templs/listsubpages.rs index 8b3c8343..069f885a 100644 --- a/crates/rari-doc/src/templ/templs/listsubpages.rs +++ b/crates/rari-doc/src/templ/templs/listsubpages.rs @@ -33,7 +33,7 @@ pub fn list_sub_pages( false, )?; } else { - subpages::list_sub_pages_internal( + subpages::list_sub_pages_nested_internal( &mut out, url, env.locale, diff --git a/crates/rari-tools/src/sidebars.rs b/crates/rari-tools/src/sidebars.rs index 20697953..bc3d55fe 100644 --- a/crates/rari-tools/src/sidebars.rs +++ b/crates/rari-tools/src/sidebars.rs @@ -5,7 +5,9 @@ use std::path::Path; use const_format::concatcp; use pretty_yaml::config::{FormatOptions, LanguageOptions}; -use rari_doc::html::sidebar::{BasicEntry, Sidebar, SidebarEntry, SubPageEntry, WebExtApiEntry}; +use rari_doc::html::sidebar::{ + BasicEntry, CoreEntry, Sidebar, SidebarEntry, SubPageEntry, SubPageGroupedEntry, WebExtApiEntry, +}; use rari_types::globals::content_root; use rari_types::locale::{default_locale, Locale}; use rari_utils::concat_strs; @@ -45,26 +47,26 @@ pub fn sync_sidebars() -> Result<(), ToolError> { fn traverse_and_extract_l10nable<'a, 'b: 'a>(entry: &'a SidebarEntry) -> HashSet<&'a str> { let (title, hash, children) = match entry { SidebarEntry::Section(basic_entry) => ( - basic_entry.title.as_deref(), - basic_entry.hash.as_deref(), + basic_entry.core.title.as_deref(), + basic_entry.core.hash.as_deref(), Some(&basic_entry.children), ), SidebarEntry::ListSubPages(sub_page_entry) => ( - sub_page_entry.title.as_deref(), - sub_page_entry.hash.as_deref(), + sub_page_entry.core.title.as_deref(), + sub_page_entry.core.hash.as_deref(), None, ), SidebarEntry::ListSubPagesGrouped(sub_page_entry) => ( - sub_page_entry.title.as_deref(), - sub_page_entry.hash.as_deref(), + sub_page_entry.core.title.as_deref(), + sub_page_entry.core.hash.as_deref(), None, ), SidebarEntry::WebExtApi(web_ext_api_entry) => { (Some(web_ext_api_entry.title.as_str()), None, None) } SidebarEntry::Default(basic_entry) => ( - basic_entry.title.as_deref(), - basic_entry.hash.as_deref(), + basic_entry.core.title.as_deref(), + basic_entry.core.hash.as_deref(), Some(&basic_entry.children), ), _ => (None, None, None), @@ -228,106 +230,137 @@ fn replace_pairs(link: Option, pairs: Pairs<'_>) -> Option { } } -fn process_entry(entry: SidebarEntry, pairs: Pairs<'_>) -> SidebarEntry { - match entry { - SidebarEntry::Section(BasicEntry { - link, - hash, - title, - code, - children, - details, - }) => { - let new_link: Option = replace_pairs(link.clone(), pairs); - if link.is_some() && new_link.is_none() { - return SidebarEntry::None; - } - SidebarEntry::Section(BasicEntry { - link: new_link, +fn process_basic_entry( + BasicEntry { + core: + CoreEntry { + link, hash, title, - code, - children: children - .into_iter() - .map(|c| process_entry(c, pairs)) - .collect(), details, - }) - } - SidebarEntry::ListSubPages(SubPageEntry { - details, - tags, - link, + code, + }, + children, + }: BasicEntry, + pairs: Pairs<'_>, +) -> Option { + let new_link: Option = replace_pairs(link.clone(), pairs); + if link.is_some() && new_link.is_none() { + return None; + } + Some(BasicEntry { + core: CoreEntry { + link: new_link, hash, title, - path, - include_parent, + details, code, - }) => { - let new_path: Option = replace_pairs(Some(path), pairs); - if new_path.is_none() { - return SidebarEntry::None; - } - SidebarEntry::ListSubPages(SubPageEntry { - details, - tags, - link: replace_pairs(link.clone(), pairs), + }, + children: children + .into_iter() + .map(|c| process_entry(c, pairs)) + .collect(), + }) +} + +fn process_sub_page_grouped_entry( + SubPageGroupedEntry { + core: + CoreEntry { + link, hash, title, - path: new_path.unwrap(), - include_parent, + details, code, - }) - } - SidebarEntry::ListSubPagesGrouped(SubPageEntry { - details, - tags, - link, + }, + tags, + path, + include_parent, + depth, + }: SubPageGroupedEntry, + pairs: Pairs<'_>, +) -> Option { + let new_path: String = replace_pairs(Some(path), pairs)?; + Some(SubPageGroupedEntry { + core: CoreEntry { + link: replace_pairs(link.clone(), pairs), hash, title, - path, - include_parent, + details, code, - }) => { - let new_path: Option = replace_pairs(Some(path), pairs); - if new_path.is_none() { - return SidebarEntry::None; - } - SidebarEntry::ListSubPagesGrouped(SubPageEntry { - details, - tags, - link: replace_pairs(link.clone(), pairs), + }, + tags, + path: new_path, + include_parent, + depth, + }) +} +fn process_sub_page_entry( + SubPageEntry { + core: + CoreEntry { + link, hash, title, - path: new_path.unwrap(), - include_parent, + details, code, - }) - } - SidebarEntry::Default(BasicEntry { - link, + }, + tags, + path, + include_parent, + depth, + nested, + }: SubPageEntry, + pairs: Pairs<'_>, +) -> Option { + let new_path: String = replace_pairs(Some(path), pairs)?; + Some(SubPageEntry { + core: CoreEntry { + link: replace_pairs(link.clone(), pairs), hash, title, - code, - children, details, - }) => { - let new_link: Option = replace_pairs(link.clone(), pairs); - if link.is_some() && new_link.is_none() { - return SidebarEntry::None; + code, + }, + tags, + path: new_path, + include_parent, + depth, + nested, + }) +} + +fn process_entry(entry: SidebarEntry, pairs: Pairs<'_>) -> SidebarEntry { + match entry { + SidebarEntry::Section(basic_entry) => { + if let Some(entry) = process_basic_entry(basic_entry, pairs) { + SidebarEntry::Section(entry) + } else { + SidebarEntry::None + } + } + SidebarEntry::Default(basic_entry) => { + if let Some(entry) = process_basic_entry(basic_entry, pairs) { + SidebarEntry::Default(entry) + } else { + SidebarEntry::None } - SidebarEntry::Default(BasicEntry { - link: replace_pairs(link.clone(), pairs), - hash, - title, - code, - children: children - .into_iter() - .map(|c| process_entry(c, pairs)) - .collect(), - details, - }) } + SidebarEntry::ListSubPages(sub_page_entry) => { + if let Some(entry) = process_sub_page_entry(sub_page_entry, pairs) { + SidebarEntry::ListSubPages(entry) + } else { + SidebarEntry::None + } + } + SidebarEntry::ListSubPagesGrouped(sub_page_entry) => { + if let Some(entry) = process_sub_page_grouped_entry(sub_page_entry, pairs) { + SidebarEntry::ListSubPagesGrouped(entry) + } else { + SidebarEntry::None + } + } + SidebarEntry::Link(link) => { let new_link: Option = replace_pairs(Some(link), pairs); if new_link.is_none() { @@ -444,7 +477,10 @@ mod test { } else { panic!("Expected a Section entry with children"); }; - let link = if let SidebarEntry::Default(BasicEntry { link: l, .. }) = third_item_first_child + let link = if let SidebarEntry::Default(BasicEntry { + core: CoreEntry { link: l, .. }, + .. + }) = third_item_first_child { l.clone().unwrap() } else {