From 807446c70003c5d9d05e05f0457b2baa73fff625 Mon Sep 17 00:00:00 2001 From: Elijah Date: Thu, 26 Mar 2026 05:43:46 +0000 Subject: [PATCH 01/10] Support route with section --- src/lib.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index ba4030d..eb78e7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,6 +136,16 @@ async fn guide_page( page_response(base_context, search_index, &version, &page, pages).into_response() } +async fn guide_section_page( + base_context: BaseContext, + search_index: SearchIndex, + Path((version, section, page)): Path<(String, String, String)>, + pages: Arc, +) -> cot::Result { + let page = format!("{section}/{page}"); + page_response(base_context, search_index, &version, &page, pages).into_response() +} + fn page_response( base_context: BaseContext, search_index: SearchIndex, @@ -265,6 +275,7 @@ impl App for CotSiteApp { fn router(&self) -> Router { let pages_guide_version = self.pages.clone(); let pages_guide_page = self.pages.clone(); + let pages_guide_section_page = self.pages.clone(); Router::with_urls([ Route::with_handler_and_name("/", index, "index"), @@ -303,6 +314,21 @@ impl App for CotSiteApp { }, "guide_page", ), + Route::with_handler_and_name( + "/guide/{version}/{section}/{page}/", + async move |base_context: BaseContext, + search_index: SearchIndex, + path: Path<(String, String, String)>| { + guide_section_page( + base_context, + search_index, + path, + Arc::clone(&pages_guide_section_page), + ) + .await + }, + "guide_section_page", + ), ]) } From e13abfea5a651ceb1ebc2371ca4260da605f18c7 Mon Sep 17 00:00:00 2001 From: Elijah Date: Thu, 2 Apr 2026 16:27:32 +0000 Subject: [PATCH 02/10] Add support for Sub categories in Guide --- src/guides.rs | 177 ++++++++++++++------------- src/lib.rs | 50 ++++++-- static/static/css/guide_chapters.css | 56 +++++++++ templates/_base.html | 1 + templates/_guide_chapters.html | 45 ++++++- 5 files changed, 235 insertions(+), 94 deletions(-) create mode 100644 static/static/css/guide_chapters.css diff --git a/src/guides.rs b/src/guides.rs index cdcce05..06af8dd 100644 --- a/src/guides.rs +++ b/src/guides.rs @@ -3,20 +3,34 @@ use std::collections::HashMap; use cot_site_common::md_pages::{MdPage, MdPageLink}; use cot_site_macros::md_page; -use crate::GuideLinkCategory; +use crate::{GuideCategoryItem, GuideItem, GuideLinkCategory}; -pub fn parse_guides(categories: Vec<(&'static str, Vec)>) -> ParsedPagesForVersion { +pub fn parse_guides(categories: Vec<(&'static str, Vec)>) -> ParsedPagesForVersion { let categories_links = categories .iter() - .map(|(title, guides)| GuideLinkCategory { + .map(|(title, items)| GuideLinkCategory { title, - guides: guides.iter().map(MdPageLink::from).collect(), + guides: items + .iter() + .map(|item| match item { + GuideItem::Page(page) => GuideCategoryItem::Page(MdPageLink::from(page)), + GuideItem::SubCategory { title, pages } => GuideCategoryItem::SubCategory { + title, + pages: pages.iter().map(MdPageLink::from).collect(), + }, + }) + .collect(), }) .collect(); + let guide_map = categories .into_iter() - .flat_map(|(_title, guides)| guides) - .map(|guide| (guide.link.clone(), guide)) + .flat_map(|(_title, items)| items) + .flat_map(|item| match item { + GuideItem::Page(page) => vec![page], + GuideItem::SubCategory { pages, .. } => pages, + }) + .map(|page| (page.link.clone(), page)) .collect(); ParsedPagesForVersion { @@ -40,39 +54,46 @@ pub fn get_prev_next_link<'a>( guides: &'a [GuideLinkCategory], current_id: &str, ) -> (Option<&'a MdPageLink>, Option<&'a MdPageLink>) { + let all_links: Vec<&MdPageLink> = guides + .iter() + .flat_map(|category| category.guides.iter()) + .flat_map(|item| match item { + GuideCategoryItem::Page(link) => vec![link], + GuideCategoryItem::SubCategory { pages, .. } => pages.iter().collect(), + }) + .collect(); + let mut prev = None; let mut has_found = false; - for category in guides { - for guide in &category.guides { - if has_found { - return (prev, Some(guide)); - } else if guide.link == current_id { - has_found = true; - } else { - prev = Some(guide); - } + for link in all_links { + if has_found { + return (prev, Some(link)); + } else if link.link == current_id { + has_found = true; + } else { + prev = Some(link); } } (prev, None) } -pub(crate) fn get_categories(master_version: Vec<(&'static str, Vec)>) -> ParsedPages { +pub fn get_categories(master_version: Vec<(&'static str, Vec)>) -> ParsedPages { let version_map = HashMap::from([ ( "v0.1", vec![( "Getting started", vec![ - md_page!("v0.1", "introduction"), - md_page!("v0.1", "templates"), - md_page!("v0.1", "forms"), - md_page!("v0.1", "db-models"), - md_page!("v0.1", "admin-panel"), - md_page!("v0.1", "static-files"), - md_page!("v0.1", "error-pages"), - md_page!("v0.1", "testing"), + GuideItem::Page(md_page!("v0.1", "introduction")), + GuideItem::Page(md_page!("v0.1", "templates")), + GuideItem::Page(md_page!("v0.1", "forms")), + GuideItem::Page(md_page!("v0.1", "db-models")), + GuideItem::Page(md_page!("v0.1", "admin-panel")), + GuideItem::Page(md_page!("v0.1", "static-files")), + GuideItem::Page(md_page!("v0.1", "error-pages")), + GuideItem::Page(md_page!("v0.1", "testing")), ], )], ), @@ -81,14 +102,14 @@ pub(crate) fn get_categories(master_version: Vec<(&'static str, Vec)>) - vec![( "Getting started", vec![ - md_page!("v0.2", "introduction"), - md_page!("v0.2", "templates"), - md_page!("v0.2", "forms"), - md_page!("v0.2", "db-models"), - md_page!("v0.2", "admin-panel"), - md_page!("v0.2", "static-files"), - md_page!("v0.2", "error-pages"), - md_page!("v0.2", "testing"), + GuideItem::Page(md_page!("v0.2", "introduction")), + GuideItem::Page(md_page!("v0.2", "templates")), + GuideItem::Page(md_page!("v0.2", "forms")), + GuideItem::Page(md_page!("v0.2", "db-models")), + GuideItem::Page(md_page!("v0.2", "admin-panel")), + GuideItem::Page(md_page!("v0.2", "static-files")), + GuideItem::Page(md_page!("v0.2", "error-pages")), + GuideItem::Page(md_page!("v0.2", "testing")), ], )], ), @@ -97,15 +118,15 @@ pub(crate) fn get_categories(master_version: Vec<(&'static str, Vec)>) - vec![( "Getting started", vec![ - md_page!("v0.3", "introduction"), - md_page!("v0.3", "templates"), - md_page!("v0.3", "forms"), - md_page!("v0.3", "db-models"), - md_page!("v0.3", "admin-panel"), - md_page!("v0.3", "static-files"), - md_page!("v0.3", "error-pages"), - md_page!("v0.3", "openapi"), - md_page!("v0.3", "testing"), + GuideItem::Page(md_page!("v0.3", "introduction")), + GuideItem::Page(md_page!("v0.3", "templates")), + GuideItem::Page(md_page!("v0.3", "forms")), + GuideItem::Page(md_page!("v0.3", "db-models")), + GuideItem::Page(md_page!("v0.3", "admin-panel")), + GuideItem::Page(md_page!("v0.3", "static-files")), + GuideItem::Page(md_page!("v0.3", "error-pages")), + GuideItem::Page(md_page!("v0.3", "openapi")), + GuideItem::Page(md_page!("v0.3", "testing")), ], )], ), @@ -115,18 +136,21 @@ pub(crate) fn get_categories(master_version: Vec<(&'static str, Vec)>) - ( "Getting started", vec![ - md_page!("v0.4", "introduction"), - md_page!("v0.4", "templates"), - md_page!("v0.4", "forms"), - md_page!("v0.4", "db-models"), - md_page!("v0.4", "admin-panel"), - md_page!("v0.4", "static-files"), - md_page!("v0.4", "error-pages"), - md_page!("v0.4", "openapi"), - md_page!("v0.4", "testing"), + GuideItem::Page(md_page!("v0.4", "introduction")), + GuideItem::Page(md_page!("v0.4", "templates")), + GuideItem::Page(md_page!("v0.4", "forms")), + GuideItem::Page(md_page!("v0.4", "db-models")), + GuideItem::Page(md_page!("v0.4", "admin-panel")), + GuideItem::Page(md_page!("v0.4", "static-files")), + GuideItem::Page(md_page!("v0.4", "error-pages")), + GuideItem::Page(md_page!("v0.4", "openapi")), + GuideItem::Page(md_page!("v0.4", "testing")), ], ), - ("Upgrading", vec![md_page!("v0.4", "upgrade-guide")]), + ( + "Upgrading", + vec![GuideItem::Page(md_page!("v0.4", "upgrade-guide"))], + ), ], ), ( @@ -135,44 +159,27 @@ pub(crate) fn get_categories(master_version: Vec<(&'static str, Vec)>) - ( "Getting started", vec![ - md_page!("v0.5", "introduction"), - md_page!("v0.5", "templates"), - md_page!("v0.5", "forms"), - md_page!("v0.5", "db-models"), - md_page!("v0.5", "admin-panel"), - md_page!("v0.5", "static-files"), - md_page!("v0.5", "sending-emails"), - md_page!("v0.5", "caching"), - md_page!("v0.5", "error-pages"), - md_page!("v0.5", "openapi"), - md_page!("v0.5", "testing"), + GuideItem::Page(md_page!("v0.5", "introduction")), + GuideItem::Page(md_page!("v0.5", "templates")), + GuideItem::Page(md_page!("v0.5", "forms")), + GuideItem::Page(md_page!("v0.5", "db-models")), + GuideItem::Page(md_page!("v0.5", "admin-panel")), + GuideItem::Page(md_page!("v0.5", "static-files")), + GuideItem::Page(md_page!("v0.5", "sending-emails")), + GuideItem::Page(md_page!("v0.5", "caching")), + GuideItem::Page(md_page!("v0.5", "error-pages")), + GuideItem::Page(md_page!("v0.5", "openapi")), + GuideItem::Page(md_page!("v0.5", "testing")), ], ), - ("Upgrading", vec![md_page!("v0.5", "upgrade-guide")]), - ("About", vec![md_page!("v0.5", "framework-comparison")]), - ], - ), - ( - "v0.6", - vec![ ( - "Getting started", - vec![ - md_page!("v0.6", "introduction"), - md_page!("v0.6", "templates"), - md_page!("v0.6", "forms"), - md_page!("v0.6", "db-models"), - md_page!("v0.6", "admin-panel"), - md_page!("v0.6", "static-files"), - md_page!("v0.6", "sending-emails"), - md_page!("v0.6", "caching"), - md_page!("v0.6", "error-pages"), - md_page!("v0.6", "openapi"), - md_page!("v0.6", "testing"), - ], + "Upgrading", + vec![GuideItem::Page(md_page!("v0.5", "upgrade-guide"))], + ), + ( + "About", + vec![GuideItem::Page(md_page!("v0.5", "framework-comparison"))], ), - ("Upgrading", vec![md_page!("v0.6", "upgrade-guide")]), - ("About", vec![md_page!("v0.6", "framework-comparison")]), ], ), ("master", master_version), diff --git a/src/lib.rs b/src/lib.rs index eb78e7c..a38442e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,6 +64,47 @@ async fn index(base_context: BaseContext) -> cot::Result { Ok(Html::new(rendered)) } +#[derive(Debug, Clone)] +struct GuideLinkCategory { + title: &'static str, + guides: Vec, +} + +#[derive(Debug, Clone)] +pub enum GuideCategoryItem { + Page(MdPageLink), + SubCategory { + title: &'static str, + pages: Vec, + }, +} + +impl GuideCategoryItem { + pub fn contains_active_page(&self, current_link: &str) -> bool { + match self { + GuideCategoryItem::SubCategory { pages, .. } => { + pages.iter().any(|p| p.link == current_link) + } + GuideCategoryItem::Page(_) => false, + } + } + + pub fn collapse_id(&self) -> String { + match self { + GuideCategoryItem::SubCategory { title, .. } => title.to_lowercase().replace(' ', "-"), + GuideCategoryItem::Page(_) => String::new(), + } + } +} + +pub enum GuideItem { + Page(MdPage), + SubCategory { + title: &'static str, + pages: Vec, + }, +} + #[derive(Debug, Template)] #[template(path = "guide.html")] struct GuideTemplate<'a> { @@ -79,12 +120,6 @@ struct GuideTemplate<'a> { next: Option<&'a MdPageLink>, } -#[derive(Debug, Clone)] -struct GuideLinkCategory { - title: &'static str, - guides: Vec, -} - fn render_section(section: &Section) -> Safe { #[derive(Debug, Clone, Template)] #[template(path = "_md_page_toc_item.html")] @@ -257,7 +292,7 @@ impl CotSiteApp { /// The `master_pages` parameter should contain a list of sections, where /// each section is a tuple containing the name of the section and list /// of pages inside it. - pub fn new(master_pages: Vec<(&'static str, Vec)>) -> Self { + pub fn new(master_pages: Vec<(&'static str, Vec)>) -> Self { let pages = get_categories(master_pages); Self { @@ -335,6 +370,7 @@ impl App for CotSiteApp { fn static_files(&self) -> Vec { static_files!( "favicon.ico", + "static/css/guide_chapters.css", "static/css/main.css", "static/js/color-modes.js", "static/js/search.js", diff --git a/static/static/css/guide_chapters.css b/static/static/css/guide_chapters.css new file mode 100644 index 0000000..a6ed7c8 --- /dev/null +++ b/static/static/css/guide_chapters.css @@ -0,0 +1,56 @@ +/* Subcategory toggle button */ +.guide-subcategory-toggle { + background: none; + border: none; + padding: 0.2rem 0; + font-size: inherit; + font-weight: 600; + color: var(--bs-secondary-color); + cursor: pointer; + transition: color 0.15s ease; +} + +.guide-subcategory-toggle:hover, +.guide-subcategory-toggle.active { + color: var(--bs-emphasis-color); +} + +/* Chevron rotation when open */ +.guide-subcategory-chevron { + flex-shrink: 0; + transition: transform 0.2s ease; +} + +.guide-subcategory-toggle[aria-expanded="true"] .guide-subcategory-chevron { + transform: rotate(180deg); +} + +/* Indented pages inside the subcategory */ +.guide-subcategory-pages li { + padding: 0.1rem 0; +} + + +.guide-subcategory-pages { + list-style: none; + padding: 0.25rem 0 0.25rem 0.75rem !important; + margin: 0; + border-left: 2px solid var(--bs-border-color); + +} + +/* Shared link styles */ +.guide-link { + color: var(--bs-secondary-color); + text-decoration: none; + transition: color 0.15s ease; +} + +.guide-link:hover { + color: var(--bs-emphasis-color); +} + +.guide-link.active { + color: var(--bs-primary); + font-weight: 500; +} diff --git a/templates/_base.html b/templates/_base.html index cf245e6..50eeb20 100644 --- a/templates/_base.html +++ b/templates/_base.html @@ -22,6 +22,7 @@ + diff --git a/templates/_guide_chapters.html b/templates/_guide_chapters.html index 3b81f34..069f638 100644 --- a/templates/_guide_chapters.html +++ b/templates/_guide_chapters.html @@ -23,8 +23,49 @@
    - {%- for guide_link in category.guides -%} -
  • {{ guide_link.title }}
  • + {%- for item in category.guides -%} + {%- let is_active = item.contains_active_page(&guide.link) -%} + {%- match item -%} + + {%- when GuideCategoryItem::Page(link) -%} +
  • + + {{ link.title }} + +
  • + + {%- when GuideCategoryItem::SubCategory { title, pages } -%} + {%- let collapse_id = item.collapse_id() -%} +
  • + +
    + +
    +
  • + + {%- endmatch -%} {%- endfor -%}
From 6de3cd94017f98e5b301dfa7e58802c22756d063 Mon Sep 17 00:00:00 2001 From: Elijah Date: Thu, 2 Apr 2026 16:40:39 +0000 Subject: [PATCH 03/10] Revert "Add support for Sub categories in Guide" This reverts commit e13abfea5a651ceb1ebc2371ca4260da605f18c7. --- src/guides.rs | 177 +++++++++++++-------------- src/lib.rs | 50 ++------ static/static/css/guide_chapters.css | 56 --------- templates/_base.html | 1 - templates/_guide_chapters.html | 45 +------ 5 files changed, 94 insertions(+), 235 deletions(-) delete mode 100644 static/static/css/guide_chapters.css diff --git a/src/guides.rs b/src/guides.rs index 06af8dd..cdcce05 100644 --- a/src/guides.rs +++ b/src/guides.rs @@ -3,34 +3,20 @@ use std::collections::HashMap; use cot_site_common::md_pages::{MdPage, MdPageLink}; use cot_site_macros::md_page; -use crate::{GuideCategoryItem, GuideItem, GuideLinkCategory}; +use crate::GuideLinkCategory; -pub fn parse_guides(categories: Vec<(&'static str, Vec)>) -> ParsedPagesForVersion { +pub fn parse_guides(categories: Vec<(&'static str, Vec)>) -> ParsedPagesForVersion { let categories_links = categories .iter() - .map(|(title, items)| GuideLinkCategory { + .map(|(title, guides)| GuideLinkCategory { title, - guides: items - .iter() - .map(|item| match item { - GuideItem::Page(page) => GuideCategoryItem::Page(MdPageLink::from(page)), - GuideItem::SubCategory { title, pages } => GuideCategoryItem::SubCategory { - title, - pages: pages.iter().map(MdPageLink::from).collect(), - }, - }) - .collect(), + guides: guides.iter().map(MdPageLink::from).collect(), }) .collect(); - let guide_map = categories .into_iter() - .flat_map(|(_title, items)| items) - .flat_map(|item| match item { - GuideItem::Page(page) => vec![page], - GuideItem::SubCategory { pages, .. } => pages, - }) - .map(|page| (page.link.clone(), page)) + .flat_map(|(_title, guides)| guides) + .map(|guide| (guide.link.clone(), guide)) .collect(); ParsedPagesForVersion { @@ -54,46 +40,39 @@ pub fn get_prev_next_link<'a>( guides: &'a [GuideLinkCategory], current_id: &str, ) -> (Option<&'a MdPageLink>, Option<&'a MdPageLink>) { - let all_links: Vec<&MdPageLink> = guides - .iter() - .flat_map(|category| category.guides.iter()) - .flat_map(|item| match item { - GuideCategoryItem::Page(link) => vec![link], - GuideCategoryItem::SubCategory { pages, .. } => pages.iter().collect(), - }) - .collect(); - let mut prev = None; let mut has_found = false; - for link in all_links { - if has_found { - return (prev, Some(link)); - } else if link.link == current_id { - has_found = true; - } else { - prev = Some(link); + for category in guides { + for guide in &category.guides { + if has_found { + return (prev, Some(guide)); + } else if guide.link == current_id { + has_found = true; + } else { + prev = Some(guide); + } } } (prev, None) } -pub fn get_categories(master_version: Vec<(&'static str, Vec)>) -> ParsedPages { +pub(crate) fn get_categories(master_version: Vec<(&'static str, Vec)>) -> ParsedPages { let version_map = HashMap::from([ ( "v0.1", vec![( "Getting started", vec![ - GuideItem::Page(md_page!("v0.1", "introduction")), - GuideItem::Page(md_page!("v0.1", "templates")), - GuideItem::Page(md_page!("v0.1", "forms")), - GuideItem::Page(md_page!("v0.1", "db-models")), - GuideItem::Page(md_page!("v0.1", "admin-panel")), - GuideItem::Page(md_page!("v0.1", "static-files")), - GuideItem::Page(md_page!("v0.1", "error-pages")), - GuideItem::Page(md_page!("v0.1", "testing")), + md_page!("v0.1", "introduction"), + md_page!("v0.1", "templates"), + md_page!("v0.1", "forms"), + md_page!("v0.1", "db-models"), + md_page!("v0.1", "admin-panel"), + md_page!("v0.1", "static-files"), + md_page!("v0.1", "error-pages"), + md_page!("v0.1", "testing"), ], )], ), @@ -102,14 +81,14 @@ pub fn get_categories(master_version: Vec<(&'static str, Vec)>) -> Pa vec![( "Getting started", vec![ - GuideItem::Page(md_page!("v0.2", "introduction")), - GuideItem::Page(md_page!("v0.2", "templates")), - GuideItem::Page(md_page!("v0.2", "forms")), - GuideItem::Page(md_page!("v0.2", "db-models")), - GuideItem::Page(md_page!("v0.2", "admin-panel")), - GuideItem::Page(md_page!("v0.2", "static-files")), - GuideItem::Page(md_page!("v0.2", "error-pages")), - GuideItem::Page(md_page!("v0.2", "testing")), + md_page!("v0.2", "introduction"), + md_page!("v0.2", "templates"), + md_page!("v0.2", "forms"), + md_page!("v0.2", "db-models"), + md_page!("v0.2", "admin-panel"), + md_page!("v0.2", "static-files"), + md_page!("v0.2", "error-pages"), + md_page!("v0.2", "testing"), ], )], ), @@ -118,15 +97,15 @@ pub fn get_categories(master_version: Vec<(&'static str, Vec)>) -> Pa vec![( "Getting started", vec![ - GuideItem::Page(md_page!("v0.3", "introduction")), - GuideItem::Page(md_page!("v0.3", "templates")), - GuideItem::Page(md_page!("v0.3", "forms")), - GuideItem::Page(md_page!("v0.3", "db-models")), - GuideItem::Page(md_page!("v0.3", "admin-panel")), - GuideItem::Page(md_page!("v0.3", "static-files")), - GuideItem::Page(md_page!("v0.3", "error-pages")), - GuideItem::Page(md_page!("v0.3", "openapi")), - GuideItem::Page(md_page!("v0.3", "testing")), + md_page!("v0.3", "introduction"), + md_page!("v0.3", "templates"), + md_page!("v0.3", "forms"), + md_page!("v0.3", "db-models"), + md_page!("v0.3", "admin-panel"), + md_page!("v0.3", "static-files"), + md_page!("v0.3", "error-pages"), + md_page!("v0.3", "openapi"), + md_page!("v0.3", "testing"), ], )], ), @@ -136,21 +115,18 @@ pub fn get_categories(master_version: Vec<(&'static str, Vec)>) -> Pa ( "Getting started", vec![ - GuideItem::Page(md_page!("v0.4", "introduction")), - GuideItem::Page(md_page!("v0.4", "templates")), - GuideItem::Page(md_page!("v0.4", "forms")), - GuideItem::Page(md_page!("v0.4", "db-models")), - GuideItem::Page(md_page!("v0.4", "admin-panel")), - GuideItem::Page(md_page!("v0.4", "static-files")), - GuideItem::Page(md_page!("v0.4", "error-pages")), - GuideItem::Page(md_page!("v0.4", "openapi")), - GuideItem::Page(md_page!("v0.4", "testing")), + md_page!("v0.4", "introduction"), + md_page!("v0.4", "templates"), + md_page!("v0.4", "forms"), + md_page!("v0.4", "db-models"), + md_page!("v0.4", "admin-panel"), + md_page!("v0.4", "static-files"), + md_page!("v0.4", "error-pages"), + md_page!("v0.4", "openapi"), + md_page!("v0.4", "testing"), ], ), - ( - "Upgrading", - vec![GuideItem::Page(md_page!("v0.4", "upgrade-guide"))], - ), + ("Upgrading", vec![md_page!("v0.4", "upgrade-guide")]), ], ), ( @@ -159,27 +135,44 @@ pub fn get_categories(master_version: Vec<(&'static str, Vec)>) -> Pa ( "Getting started", vec![ - GuideItem::Page(md_page!("v0.5", "introduction")), - GuideItem::Page(md_page!("v0.5", "templates")), - GuideItem::Page(md_page!("v0.5", "forms")), - GuideItem::Page(md_page!("v0.5", "db-models")), - GuideItem::Page(md_page!("v0.5", "admin-panel")), - GuideItem::Page(md_page!("v0.5", "static-files")), - GuideItem::Page(md_page!("v0.5", "sending-emails")), - GuideItem::Page(md_page!("v0.5", "caching")), - GuideItem::Page(md_page!("v0.5", "error-pages")), - GuideItem::Page(md_page!("v0.5", "openapi")), - GuideItem::Page(md_page!("v0.5", "testing")), + md_page!("v0.5", "introduction"), + md_page!("v0.5", "templates"), + md_page!("v0.5", "forms"), + md_page!("v0.5", "db-models"), + md_page!("v0.5", "admin-panel"), + md_page!("v0.5", "static-files"), + md_page!("v0.5", "sending-emails"), + md_page!("v0.5", "caching"), + md_page!("v0.5", "error-pages"), + md_page!("v0.5", "openapi"), + md_page!("v0.5", "testing"), ], ), + ("Upgrading", vec![md_page!("v0.5", "upgrade-guide")]), + ("About", vec![md_page!("v0.5", "framework-comparison")]), + ], + ), + ( + "v0.6", + vec![ ( - "Upgrading", - vec![GuideItem::Page(md_page!("v0.5", "upgrade-guide"))], - ), - ( - "About", - vec![GuideItem::Page(md_page!("v0.5", "framework-comparison"))], + "Getting started", + vec![ + md_page!("v0.6", "introduction"), + md_page!("v0.6", "templates"), + md_page!("v0.6", "forms"), + md_page!("v0.6", "db-models"), + md_page!("v0.6", "admin-panel"), + md_page!("v0.6", "static-files"), + md_page!("v0.6", "sending-emails"), + md_page!("v0.6", "caching"), + md_page!("v0.6", "error-pages"), + md_page!("v0.6", "openapi"), + md_page!("v0.6", "testing"), + ], ), + ("Upgrading", vec![md_page!("v0.6", "upgrade-guide")]), + ("About", vec![md_page!("v0.6", "framework-comparison")]), ], ), ("master", master_version), diff --git a/src/lib.rs b/src/lib.rs index a38442e..eb78e7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,47 +64,6 @@ async fn index(base_context: BaseContext) -> cot::Result { Ok(Html::new(rendered)) } -#[derive(Debug, Clone)] -struct GuideLinkCategory { - title: &'static str, - guides: Vec, -} - -#[derive(Debug, Clone)] -pub enum GuideCategoryItem { - Page(MdPageLink), - SubCategory { - title: &'static str, - pages: Vec, - }, -} - -impl GuideCategoryItem { - pub fn contains_active_page(&self, current_link: &str) -> bool { - match self { - GuideCategoryItem::SubCategory { pages, .. } => { - pages.iter().any(|p| p.link == current_link) - } - GuideCategoryItem::Page(_) => false, - } - } - - pub fn collapse_id(&self) -> String { - match self { - GuideCategoryItem::SubCategory { title, .. } => title.to_lowercase().replace(' ', "-"), - GuideCategoryItem::Page(_) => String::new(), - } - } -} - -pub enum GuideItem { - Page(MdPage), - SubCategory { - title: &'static str, - pages: Vec, - }, -} - #[derive(Debug, Template)] #[template(path = "guide.html")] struct GuideTemplate<'a> { @@ -120,6 +79,12 @@ struct GuideTemplate<'a> { next: Option<&'a MdPageLink>, } +#[derive(Debug, Clone)] +struct GuideLinkCategory { + title: &'static str, + guides: Vec, +} + fn render_section(section: &Section) -> Safe { #[derive(Debug, Clone, Template)] #[template(path = "_md_page_toc_item.html")] @@ -292,7 +257,7 @@ impl CotSiteApp { /// The `master_pages` parameter should contain a list of sections, where /// each section is a tuple containing the name of the section and list /// of pages inside it. - pub fn new(master_pages: Vec<(&'static str, Vec)>) -> Self { + pub fn new(master_pages: Vec<(&'static str, Vec)>) -> Self { let pages = get_categories(master_pages); Self { @@ -370,7 +335,6 @@ impl App for CotSiteApp { fn static_files(&self) -> Vec { static_files!( "favicon.ico", - "static/css/guide_chapters.css", "static/css/main.css", "static/js/color-modes.js", "static/js/search.js", diff --git a/static/static/css/guide_chapters.css b/static/static/css/guide_chapters.css deleted file mode 100644 index a6ed7c8..0000000 --- a/static/static/css/guide_chapters.css +++ /dev/null @@ -1,56 +0,0 @@ -/* Subcategory toggle button */ -.guide-subcategory-toggle { - background: none; - border: none; - padding: 0.2rem 0; - font-size: inherit; - font-weight: 600; - color: var(--bs-secondary-color); - cursor: pointer; - transition: color 0.15s ease; -} - -.guide-subcategory-toggle:hover, -.guide-subcategory-toggle.active { - color: var(--bs-emphasis-color); -} - -/* Chevron rotation when open */ -.guide-subcategory-chevron { - flex-shrink: 0; - transition: transform 0.2s ease; -} - -.guide-subcategory-toggle[aria-expanded="true"] .guide-subcategory-chevron { - transform: rotate(180deg); -} - -/* Indented pages inside the subcategory */ -.guide-subcategory-pages li { - padding: 0.1rem 0; -} - - -.guide-subcategory-pages { - list-style: none; - padding: 0.25rem 0 0.25rem 0.75rem !important; - margin: 0; - border-left: 2px solid var(--bs-border-color); - -} - -/* Shared link styles */ -.guide-link { - color: var(--bs-secondary-color); - text-decoration: none; - transition: color 0.15s ease; -} - -.guide-link:hover { - color: var(--bs-emphasis-color); -} - -.guide-link.active { - color: var(--bs-primary); - font-weight: 500; -} diff --git a/templates/_base.html b/templates/_base.html index 50eeb20..cf245e6 100644 --- a/templates/_base.html +++ b/templates/_base.html @@ -22,7 +22,6 @@ - diff --git a/templates/_guide_chapters.html b/templates/_guide_chapters.html index 069f638..3b81f34 100644 --- a/templates/_guide_chapters.html +++ b/templates/_guide_chapters.html @@ -23,49 +23,8 @@
    - {%- for item in category.guides -%} - {%- let is_active = item.contains_active_page(&guide.link) -%} - {%- match item -%} - - {%- when GuideCategoryItem::Page(link) -%} -
  • - - {{ link.title }} - -
  • - - {%- when GuideCategoryItem::SubCategory { title, pages } -%} - {%- let collapse_id = item.collapse_id() -%} -
  • - -
    - -
    -
  • - - {%- endmatch -%} + {%- for guide_link in category.guides -%} +
  • {{ guide_link.title }}
  • {%- endfor -%}
From 87c2b0a46f196962efd3a0ef425faf86a1d65a73 Mon Sep 17 00:00:00 2001 From: Elijah Date: Thu, 2 Apr 2026 16:40:39 +0000 Subject: [PATCH 04/10] Revert "Support route with section" This reverts commit 807446c70003c5d9d05e05f0457b2baa73fff625. --- src/lib.rs | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index eb78e7c..ba4030d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,16 +136,6 @@ async fn guide_page( page_response(base_context, search_index, &version, &page, pages).into_response() } -async fn guide_section_page( - base_context: BaseContext, - search_index: SearchIndex, - Path((version, section, page)): Path<(String, String, String)>, - pages: Arc, -) -> cot::Result { - let page = format!("{section}/{page}"); - page_response(base_context, search_index, &version, &page, pages).into_response() -} - fn page_response( base_context: BaseContext, search_index: SearchIndex, @@ -275,7 +265,6 @@ impl App for CotSiteApp { fn router(&self) -> Router { let pages_guide_version = self.pages.clone(); let pages_guide_page = self.pages.clone(); - let pages_guide_section_page = self.pages.clone(); Router::with_urls([ Route::with_handler_and_name("/", index, "index"), @@ -314,21 +303,6 @@ impl App for CotSiteApp { }, "guide_page", ), - Route::with_handler_and_name( - "/guide/{version}/{section}/{page}/", - async move |base_context: BaseContext, - search_index: SearchIndex, - path: Path<(String, String, String)>| { - guide_section_page( - base_context, - search_index, - path, - Arc::clone(&pages_guide_section_page), - ) - .await - }, - "guide_section_page", - ), ]) } From fe74c7883f04308de99c25287abb731b4876f0fc Mon Sep 17 00:00:00 2001 From: Elijah Date: Thu, 2 Apr 2026 16:45:01 +0000 Subject: [PATCH 05/10] clean up commits --- src/guides.rs | 177 ++++++++++++++------------- src/lib.rs | 50 ++++++-- static/static/css/guide_chapters.css | 56 +++++++++ templates/_base.html | 1 + templates/_guide_chapters.html | 45 ++++++- 5 files changed, 235 insertions(+), 94 deletions(-) create mode 100644 static/static/css/guide_chapters.css diff --git a/src/guides.rs b/src/guides.rs index cdcce05..06af8dd 100644 --- a/src/guides.rs +++ b/src/guides.rs @@ -3,20 +3,34 @@ use std::collections::HashMap; use cot_site_common::md_pages::{MdPage, MdPageLink}; use cot_site_macros::md_page; -use crate::GuideLinkCategory; +use crate::{GuideCategoryItem, GuideItem, GuideLinkCategory}; -pub fn parse_guides(categories: Vec<(&'static str, Vec)>) -> ParsedPagesForVersion { +pub fn parse_guides(categories: Vec<(&'static str, Vec)>) -> ParsedPagesForVersion { let categories_links = categories .iter() - .map(|(title, guides)| GuideLinkCategory { + .map(|(title, items)| GuideLinkCategory { title, - guides: guides.iter().map(MdPageLink::from).collect(), + guides: items + .iter() + .map(|item| match item { + GuideItem::Page(page) => GuideCategoryItem::Page(MdPageLink::from(page)), + GuideItem::SubCategory { title, pages } => GuideCategoryItem::SubCategory { + title, + pages: pages.iter().map(MdPageLink::from).collect(), + }, + }) + .collect(), }) .collect(); + let guide_map = categories .into_iter() - .flat_map(|(_title, guides)| guides) - .map(|guide| (guide.link.clone(), guide)) + .flat_map(|(_title, items)| items) + .flat_map(|item| match item { + GuideItem::Page(page) => vec![page], + GuideItem::SubCategory { pages, .. } => pages, + }) + .map(|page| (page.link.clone(), page)) .collect(); ParsedPagesForVersion { @@ -40,39 +54,46 @@ pub fn get_prev_next_link<'a>( guides: &'a [GuideLinkCategory], current_id: &str, ) -> (Option<&'a MdPageLink>, Option<&'a MdPageLink>) { + let all_links: Vec<&MdPageLink> = guides + .iter() + .flat_map(|category| category.guides.iter()) + .flat_map(|item| match item { + GuideCategoryItem::Page(link) => vec![link], + GuideCategoryItem::SubCategory { pages, .. } => pages.iter().collect(), + }) + .collect(); + let mut prev = None; let mut has_found = false; - for category in guides { - for guide in &category.guides { - if has_found { - return (prev, Some(guide)); - } else if guide.link == current_id { - has_found = true; - } else { - prev = Some(guide); - } + for link in all_links { + if has_found { + return (prev, Some(link)); + } else if link.link == current_id { + has_found = true; + } else { + prev = Some(link); } } (prev, None) } -pub(crate) fn get_categories(master_version: Vec<(&'static str, Vec)>) -> ParsedPages { +pub fn get_categories(master_version: Vec<(&'static str, Vec)>) -> ParsedPages { let version_map = HashMap::from([ ( "v0.1", vec![( "Getting started", vec![ - md_page!("v0.1", "introduction"), - md_page!("v0.1", "templates"), - md_page!("v0.1", "forms"), - md_page!("v0.1", "db-models"), - md_page!("v0.1", "admin-panel"), - md_page!("v0.1", "static-files"), - md_page!("v0.1", "error-pages"), - md_page!("v0.1", "testing"), + GuideItem::Page(md_page!("v0.1", "introduction")), + GuideItem::Page(md_page!("v0.1", "templates")), + GuideItem::Page(md_page!("v0.1", "forms")), + GuideItem::Page(md_page!("v0.1", "db-models")), + GuideItem::Page(md_page!("v0.1", "admin-panel")), + GuideItem::Page(md_page!("v0.1", "static-files")), + GuideItem::Page(md_page!("v0.1", "error-pages")), + GuideItem::Page(md_page!("v0.1", "testing")), ], )], ), @@ -81,14 +102,14 @@ pub(crate) fn get_categories(master_version: Vec<(&'static str, Vec)>) - vec![( "Getting started", vec![ - md_page!("v0.2", "introduction"), - md_page!("v0.2", "templates"), - md_page!("v0.2", "forms"), - md_page!("v0.2", "db-models"), - md_page!("v0.2", "admin-panel"), - md_page!("v0.2", "static-files"), - md_page!("v0.2", "error-pages"), - md_page!("v0.2", "testing"), + GuideItem::Page(md_page!("v0.2", "introduction")), + GuideItem::Page(md_page!("v0.2", "templates")), + GuideItem::Page(md_page!("v0.2", "forms")), + GuideItem::Page(md_page!("v0.2", "db-models")), + GuideItem::Page(md_page!("v0.2", "admin-panel")), + GuideItem::Page(md_page!("v0.2", "static-files")), + GuideItem::Page(md_page!("v0.2", "error-pages")), + GuideItem::Page(md_page!("v0.2", "testing")), ], )], ), @@ -97,15 +118,15 @@ pub(crate) fn get_categories(master_version: Vec<(&'static str, Vec)>) - vec![( "Getting started", vec![ - md_page!("v0.3", "introduction"), - md_page!("v0.3", "templates"), - md_page!("v0.3", "forms"), - md_page!("v0.3", "db-models"), - md_page!("v0.3", "admin-panel"), - md_page!("v0.3", "static-files"), - md_page!("v0.3", "error-pages"), - md_page!("v0.3", "openapi"), - md_page!("v0.3", "testing"), + GuideItem::Page(md_page!("v0.3", "introduction")), + GuideItem::Page(md_page!("v0.3", "templates")), + GuideItem::Page(md_page!("v0.3", "forms")), + GuideItem::Page(md_page!("v0.3", "db-models")), + GuideItem::Page(md_page!("v0.3", "admin-panel")), + GuideItem::Page(md_page!("v0.3", "static-files")), + GuideItem::Page(md_page!("v0.3", "error-pages")), + GuideItem::Page(md_page!("v0.3", "openapi")), + GuideItem::Page(md_page!("v0.3", "testing")), ], )], ), @@ -115,18 +136,21 @@ pub(crate) fn get_categories(master_version: Vec<(&'static str, Vec)>) - ( "Getting started", vec![ - md_page!("v0.4", "introduction"), - md_page!("v0.4", "templates"), - md_page!("v0.4", "forms"), - md_page!("v0.4", "db-models"), - md_page!("v0.4", "admin-panel"), - md_page!("v0.4", "static-files"), - md_page!("v0.4", "error-pages"), - md_page!("v0.4", "openapi"), - md_page!("v0.4", "testing"), + GuideItem::Page(md_page!("v0.4", "introduction")), + GuideItem::Page(md_page!("v0.4", "templates")), + GuideItem::Page(md_page!("v0.4", "forms")), + GuideItem::Page(md_page!("v0.4", "db-models")), + GuideItem::Page(md_page!("v0.4", "admin-panel")), + GuideItem::Page(md_page!("v0.4", "static-files")), + GuideItem::Page(md_page!("v0.4", "error-pages")), + GuideItem::Page(md_page!("v0.4", "openapi")), + GuideItem::Page(md_page!("v0.4", "testing")), ], ), - ("Upgrading", vec![md_page!("v0.4", "upgrade-guide")]), + ( + "Upgrading", + vec![GuideItem::Page(md_page!("v0.4", "upgrade-guide"))], + ), ], ), ( @@ -135,44 +159,27 @@ pub(crate) fn get_categories(master_version: Vec<(&'static str, Vec)>) - ( "Getting started", vec![ - md_page!("v0.5", "introduction"), - md_page!("v0.5", "templates"), - md_page!("v0.5", "forms"), - md_page!("v0.5", "db-models"), - md_page!("v0.5", "admin-panel"), - md_page!("v0.5", "static-files"), - md_page!("v0.5", "sending-emails"), - md_page!("v0.5", "caching"), - md_page!("v0.5", "error-pages"), - md_page!("v0.5", "openapi"), - md_page!("v0.5", "testing"), + GuideItem::Page(md_page!("v0.5", "introduction")), + GuideItem::Page(md_page!("v0.5", "templates")), + GuideItem::Page(md_page!("v0.5", "forms")), + GuideItem::Page(md_page!("v0.5", "db-models")), + GuideItem::Page(md_page!("v0.5", "admin-panel")), + GuideItem::Page(md_page!("v0.5", "static-files")), + GuideItem::Page(md_page!("v0.5", "sending-emails")), + GuideItem::Page(md_page!("v0.5", "caching")), + GuideItem::Page(md_page!("v0.5", "error-pages")), + GuideItem::Page(md_page!("v0.5", "openapi")), + GuideItem::Page(md_page!("v0.5", "testing")), ], ), - ("Upgrading", vec![md_page!("v0.5", "upgrade-guide")]), - ("About", vec![md_page!("v0.5", "framework-comparison")]), - ], - ), - ( - "v0.6", - vec![ ( - "Getting started", - vec![ - md_page!("v0.6", "introduction"), - md_page!("v0.6", "templates"), - md_page!("v0.6", "forms"), - md_page!("v0.6", "db-models"), - md_page!("v0.6", "admin-panel"), - md_page!("v0.6", "static-files"), - md_page!("v0.6", "sending-emails"), - md_page!("v0.6", "caching"), - md_page!("v0.6", "error-pages"), - md_page!("v0.6", "openapi"), - md_page!("v0.6", "testing"), - ], + "Upgrading", + vec![GuideItem::Page(md_page!("v0.5", "upgrade-guide"))], + ), + ( + "About", + vec![GuideItem::Page(md_page!("v0.5", "framework-comparison"))], ), - ("Upgrading", vec![md_page!("v0.6", "upgrade-guide")]), - ("About", vec![md_page!("v0.6", "framework-comparison")]), ], ), ("master", master_version), diff --git a/src/lib.rs b/src/lib.rs index ba4030d..a180115 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,6 +64,47 @@ async fn index(base_context: BaseContext) -> cot::Result { Ok(Html::new(rendered)) } +#[derive(Debug, Clone)] +struct GuideLinkCategory { + title: &'static str, + guides: Vec, +} + +#[derive(Debug, Clone)] +pub enum GuideCategoryItem { + Page(MdPageLink), + SubCategory { + title: &'static str, + pages: Vec, + }, +} + +impl GuideCategoryItem { + pub fn contains_active_page(&self, current_link: &str) -> bool { + match self { + GuideCategoryItem::SubCategory { pages, .. } => { + pages.iter().any(|p| p.link == current_link) + } + GuideCategoryItem::Page(_) => false, + } + } + + pub fn collapse_id(&self) -> String { + match self { + GuideCategoryItem::SubCategory { title, .. } => title.to_lowercase().replace(' ', "-"), + GuideCategoryItem::Page(_) => String::new(), + } + } +} + +pub enum GuideItem { + Page(MdPage), + SubCategory { + title: &'static str, + pages: Vec, + }, +} + #[derive(Debug, Template)] #[template(path = "guide.html")] struct GuideTemplate<'a> { @@ -79,12 +120,6 @@ struct GuideTemplate<'a> { next: Option<&'a MdPageLink>, } -#[derive(Debug, Clone)] -struct GuideLinkCategory { - title: &'static str, - guides: Vec, -} - fn render_section(section: &Section) -> Safe { #[derive(Debug, Clone, Template)] #[template(path = "_md_page_toc_item.html")] @@ -247,7 +282,7 @@ impl CotSiteApp { /// The `master_pages` parameter should contain a list of sections, where /// each section is a tuple containing the name of the section and list /// of pages inside it. - pub fn new(master_pages: Vec<(&'static str, Vec)>) -> Self { + pub fn new(master_pages: Vec<(&'static str, Vec)>) -> Self { let pages = get_categories(master_pages); Self { @@ -309,6 +344,7 @@ impl App for CotSiteApp { fn static_files(&self) -> Vec { static_files!( "favicon.ico", + "static/css/guide_chapters.css", "static/css/main.css", "static/js/color-modes.js", "static/js/search.js", diff --git a/static/static/css/guide_chapters.css b/static/static/css/guide_chapters.css new file mode 100644 index 0000000..a6ed7c8 --- /dev/null +++ b/static/static/css/guide_chapters.css @@ -0,0 +1,56 @@ +/* Subcategory toggle button */ +.guide-subcategory-toggle { + background: none; + border: none; + padding: 0.2rem 0; + font-size: inherit; + font-weight: 600; + color: var(--bs-secondary-color); + cursor: pointer; + transition: color 0.15s ease; +} + +.guide-subcategory-toggle:hover, +.guide-subcategory-toggle.active { + color: var(--bs-emphasis-color); +} + +/* Chevron rotation when open */ +.guide-subcategory-chevron { + flex-shrink: 0; + transition: transform 0.2s ease; +} + +.guide-subcategory-toggle[aria-expanded="true"] .guide-subcategory-chevron { + transform: rotate(180deg); +} + +/* Indented pages inside the subcategory */ +.guide-subcategory-pages li { + padding: 0.1rem 0; +} + + +.guide-subcategory-pages { + list-style: none; + padding: 0.25rem 0 0.25rem 0.75rem !important; + margin: 0; + border-left: 2px solid var(--bs-border-color); + +} + +/* Shared link styles */ +.guide-link { + color: var(--bs-secondary-color); + text-decoration: none; + transition: color 0.15s ease; +} + +.guide-link:hover { + color: var(--bs-emphasis-color); +} + +.guide-link.active { + color: var(--bs-primary); + font-weight: 500; +} diff --git a/templates/_base.html b/templates/_base.html index cf245e6..50eeb20 100644 --- a/templates/_base.html +++ b/templates/_base.html @@ -22,6 +22,7 @@ + diff --git a/templates/_guide_chapters.html b/templates/_guide_chapters.html index 3b81f34..069f638 100644 --- a/templates/_guide_chapters.html +++ b/templates/_guide_chapters.html @@ -23,8 +23,49 @@
    - {%- for guide_link in category.guides -%} -
  • {{ guide_link.title }}
  • + {%- for item in category.guides -%} + {%- let is_active = item.contains_active_page(&guide.link) -%} + {%- match item -%} + + {%- when GuideCategoryItem::Page(link) -%} +
  • + + {{ link.title }} + +
  • + + {%- when GuideCategoryItem::SubCategory { title, pages } -%} + {%- let collapse_id = item.collapse_id() -%} +
  • + +
    + +
    +
  • + + {%- endmatch -%} {%- endfor -%}
From 5a1bb6abc23b429b53fd172218b89392cffb9cbb Mon Sep 17 00:00:00 2001 From: Elijah Date: Sat, 4 Apr 2026 22:33:04 +0000 Subject: [PATCH 06/10] add docs and fix struct scope --- src/lib.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a180115..66b4a3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,7 +71,7 @@ struct GuideLinkCategory { } #[derive(Debug, Clone)] -pub enum GuideCategoryItem { +enum GuideCategoryItem { Page(MdPageLink), SubCategory { title: &'static str, @@ -97,8 +97,38 @@ impl GuideCategoryItem { } } +/// Represents an item in a documentation guide. Each item can either be a +/// single markdown page or a subcategory containing a collection of related +/// pages. pub enum GuideItem { + /// A single markdown page to be rendered as part of the guide. + /// + /// # Examples + /// ``` + /// use cot_site::GuideItem; + /// use cot_site_macros::md_page; + /// + /// let page = GuideItem::Page(md_page!("guide", "introduction")); + /// ``` Page(MdPage), + /// A subcategory containing a collection of related pages. + /// + /// # Examples + /// ``` + /// use cot_site::GuideItem; + /// use cot_site_macros::md_page; + /// + /// let subcategory = GuideItem::SubCategory( + /// { + /// title: "Database", + /// pages: vec![ + /// md_page!("guide/databases/overview"), + /// md_page!("guide/databases/queries"), + /// md_page!("guide/databases/migrations"), + /// ] + /// } + /// ) + /// ``` SubCategory { title: &'static str, pages: Vec, From f416ac96a8fdaf532ef0a6068f95f495458ce782 Mon Sep 17 00:00:00 2001 From: Elijah Date: Sat, 4 Apr 2026 23:05:37 +0000 Subject: [PATCH 07/10] more docs --- src/lib.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 66b4a3c..eb88d1e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,7 +80,10 @@ enum GuideCategoryItem { } impl GuideCategoryItem { - pub fn contains_active_page(&self, current_link: &str) -> bool { + /// Takes a link and checks whether any of the pages in that subcategory + /// matches it. We call this inside the templates to decide whether a + /// subcategory accordion should start open or closed. + fn contains_active_page(&self, current_link: &str) -> bool { match self { GuideCategoryItem::SubCategory { pages, .. } => { pages.iter().any(|p| p.link == current_link) @@ -88,8 +91,9 @@ impl GuideCategoryItem { GuideCategoryItem::Page(_) => false, } } - - pub fn collapse_id(&self) -> String { + /// Returns a unique ID for the category which bootstrap uses to + /// control the open/close behavior of the accordion + fn collapse_id(&self) -> String { match self { GuideCategoryItem::SubCategory { title, .. } => title.to_lowercase().replace(' ', "-"), GuideCategoryItem::Page(_) => String::new(), From eafaa3f4693b5af0d7ad9da3d61688c528d08329 Mon Sep 17 00:00:00 2001 From: Elijah Date: Thu, 26 Mar 2026 05:43:46 +0000 Subject: [PATCH 08/10] Support route with section --- src/lib.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index eb88d1e..a5f7e50 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -205,6 +205,16 @@ async fn guide_page( page_response(base_context, search_index, &version, &page, pages).into_response() } +async fn guide_section_page( + base_context: BaseContext, + search_index: SearchIndex, + Path((version, section, page)): Path<(String, String, String)>, + pages: Arc, +) -> cot::Result { + let page = format!("{section}/{page}"); + page_response(base_context, search_index, &version, &page, pages).into_response() +} + fn page_response( base_context: BaseContext, search_index: SearchIndex, @@ -334,6 +344,7 @@ impl App for CotSiteApp { fn router(&self) -> Router { let pages_guide_version = self.pages.clone(); let pages_guide_page = self.pages.clone(); + let pages_guide_section_page = self.pages.clone(); Router::with_urls([ Route::with_handler_and_name("/", index, "index"), @@ -372,6 +383,21 @@ impl App for CotSiteApp { }, "guide_page", ), + Route::with_handler_and_name( + "/guide/{version}/{section}/{page}/", + async move |base_context: BaseContext, + search_index: SearchIndex, + path: Path<(String, String, String)>| { + guide_section_page( + base_context, + search_index, + path, + Arc::clone(&pages_guide_section_page), + ) + .await + }, + "guide_section_page", + ), ]) } From dbfcae94f46ae939767af45e9718245a83bc34e3 Mon Sep 17 00:00:00 2001 From: Elijah Date: Tue, 7 Apr 2026 00:37:20 +0000 Subject: [PATCH 09/10] fix ci failures --- src/lib.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a5f7e50..8e8ca13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,9 +108,8 @@ pub enum GuideItem { /// A single markdown page to be rendered as part of the guide. /// /// # Examples - /// ``` - /// use cot_site::GuideItem; - /// use cot_site_macros::md_page; + /// ```ignore + /// use cot_site::{GuideItem, md_page}; /// /// let page = GuideItem::Page(md_page!("guide", "introduction")); /// ``` @@ -118,20 +117,17 @@ pub enum GuideItem { /// A subcategory containing a collection of related pages. /// /// # Examples - /// ``` - /// use cot_site::GuideItem; - /// use cot_site_macros::md_page; + /// ```ignore + /// use cot_site::{GuideItem, md_page}; /// - /// let subcategory = GuideItem::SubCategory( - /// { + /// let subcategory = GuideItem::SubCategory{ /// title: "Database", /// pages: vec![ /// md_page!("guide/databases/overview"), /// md_page!("guide/databases/queries"), /// md_page!("guide/databases/migrations"), /// ] - /// } - /// ) + /// }; /// ``` SubCategory { title: &'static str, From 411c6a5d900f9f8677fdfa06c7c1e6779d319527 Mon Sep 17 00:00:00 2001 From: Elijah Date: Fri, 10 Apr 2026 15:37:28 +0000 Subject: [PATCH 10/10] move css for guide chapters to custom.scss --- scss/custom.scss | 57 ++++++++++++++++++++++++++++ src/lib.rs | 2 +- static/static/css/guide_chapters.css | 56 --------------------------- templates/_base.html | 1 - 4 files changed, 58 insertions(+), 58 deletions(-) delete mode 100644 static/static/css/guide_chapters.css diff --git a/scss/custom.scss b/scss/custom.scss index 5de3ffa..e8b93b3 100644 --- a/scss/custom.scss +++ b/scss/custom.scss @@ -271,3 +271,60 @@ button.navbar-toggler { padding-left: 1rem; position: relative; } + +/* Subcategory toggle button */ +.guide-subcategory-toggle { + background: none; + border: none; + padding: 0.2rem 0; + font-size: inherit; + font-weight: 600; + color: var(--bs-secondary-color); + cursor: pointer; + transition: color 0.15s ease; +} + +.guide-subcategory-toggle:hover, +.guide-subcategory-toggle.active { + color: var(--bs-emphasis-color); +} + +/* Chevron rotation when open */ +.guide-subcategory-chevron { + flex-shrink: 0; + transition: transform 0.2s ease; +} + +.guide-subcategory-toggle[aria-expanded="true"] .guide-subcategory-chevron { + transform: rotate(180deg); +} + +/* Indented pages inside the subcategory */ +.guide-subcategory-pages li { + padding: 0.1rem 0; +} + + +.guide-subcategory-pages { + list-style: none; + padding: 0.25rem 0 0.25rem 0.75rem !important; + margin: 0; + border-left: 2px solid var(--bs-border-color); + +} + +/* Shared link styles */ +.guide-link { + color: var(--bs-secondary-color); + text-decoration: none; + transition: color 0.15s ease; +} + +.guide-link:hover { + color: var(--bs-emphasis-color); +} + +.guide-link.active { + color: var(--bs-primary); + font-weight: 500; +} diff --git a/src/lib.rs b/src/lib.rs index 8e8ca13..2f9902d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,6 +70,7 @@ struct GuideLinkCategory { guides: Vec, } +/// Internal representation of a guide item that gets rendered in the templates. #[derive(Debug, Clone)] enum GuideCategoryItem { Page(MdPageLink), @@ -400,7 +401,6 @@ impl App for CotSiteApp { fn static_files(&self) -> Vec { static_files!( "favicon.ico", - "static/css/guide_chapters.css", "static/css/main.css", "static/js/color-modes.js", "static/js/search.js", diff --git a/static/static/css/guide_chapters.css b/static/static/css/guide_chapters.css deleted file mode 100644 index a6ed7c8..0000000 --- a/static/static/css/guide_chapters.css +++ /dev/null @@ -1,56 +0,0 @@ -/* Subcategory toggle button */ -.guide-subcategory-toggle { - background: none; - border: none; - padding: 0.2rem 0; - font-size: inherit; - font-weight: 600; - color: var(--bs-secondary-color); - cursor: pointer; - transition: color 0.15s ease; -} - -.guide-subcategory-toggle:hover, -.guide-subcategory-toggle.active { - color: var(--bs-emphasis-color); -} - -/* Chevron rotation when open */ -.guide-subcategory-chevron { - flex-shrink: 0; - transition: transform 0.2s ease; -} - -.guide-subcategory-toggle[aria-expanded="true"] .guide-subcategory-chevron { - transform: rotate(180deg); -} - -/* Indented pages inside the subcategory */ -.guide-subcategory-pages li { - padding: 0.1rem 0; -} - - -.guide-subcategory-pages { - list-style: none; - padding: 0.25rem 0 0.25rem 0.75rem !important; - margin: 0; - border-left: 2px solid var(--bs-border-color); - -} - -/* Shared link styles */ -.guide-link { - color: var(--bs-secondary-color); - text-decoration: none; - transition: color 0.15s ease; -} - -.guide-link:hover { - color: var(--bs-emphasis-color); -} - -.guide-link.active { - color: var(--bs-primary); - font-weight: 500; -} diff --git a/templates/_base.html b/templates/_base.html index 50eeb20..cf245e6 100644 --- a/templates/_base.html +++ b/templates/_base.html @@ -22,7 +22,6 @@ -