//! Templates for public-facing forum pages. use askama::Template; use super::CsrfTokenOption; // ============================================================================ // View-model structs (lightweight data for templates, not domain models) // ============================================================================ /// Minimal user info for the site header. pub struct TemplateSessionUser { pub username: String, pub is_platform_admin: bool, } /// Row in the forum directory (home page). pub struct CommunityDirectoryRow { pub slug: String, pub name: String, pub description: Option, pub category_count: u32, pub thread_count: u32, } /// Row in the project detail page (category listing). pub struct CategoryRow { pub name: String, pub slug: String, pub description: Option, pub thread_count: u32, } /// Tag badge for display in templates. pub struct TagBadge { pub id: String, pub name: String, pub slug: String, } /// Row in the dense thread table. pub struct ThreadRow { pub id: String, pub title: String, pub author_name: String, pub author_username: String, pub reply_count: u32, pub last_activity: String, pub pinned: bool, pub locked: bool, pub has_mention: bool, pub tags: Vec, } /// Footnote on a post. pub struct FootnoteViewRow { pub author_name: String, pub body_html: String, pub timestamp: String, } /// Link preview card for a post. pub struct LinkPreviewViewRow { pub url: String, pub title: Option, pub description: Option, } /// Single post in a thread. pub struct PostRow { pub id: String, pub author_name: String, pub author_username: String, pub timestamp: String, pub body_html: String, pub is_op: bool, pub is_removed: bool, pub can_add_footnote: bool, pub can_remove: bool, pub can_flag: bool, pub footnotes: Vec, pub link_previews: Vec, pub endorsement_count: u32, pub is_endorsed: bool, pub can_endorse: bool, } // ============================================================================ // Pagination // ============================================================================ /// Pagination state passed to templates. pub struct Pagination { pub current_page: u32, pub total_pages: u32, pub has_prev: bool, pub has_next: bool, } impl Pagination { pub fn new(page: u32, total_items: i64, per_page: i64) -> Self { let total_pages = ((total_items as f64) / (per_page as f64)).ceil() as u32; let total_pages = total_pages.max(1); let current_page = page.min(total_pages); Self { current_page, total_pages, has_prev: current_page > 1, has_next: current_page < total_pages, } } } // ============================================================================ // Page templates // ============================================================================ /// Forum directory — lists local communities. #[derive(Template)] #[template(path = "pages/forum_directory.html")] pub struct ForumDirectoryTemplate { pub csrf_token: CsrfTokenOption, pub session_user: Option, pub mnw_base_url: std::sync::Arc, pub communities: Vec, pub pagination: Pagination, } /// Project forum — category table within a single project. #[derive(Template)] #[template(path = "pages/community.html")] pub struct CommunityTemplate { pub csrf_token: CsrfTokenOption, pub session_user: Option, pub mnw_base_url: std::sync::Arc, pub community_name: String, pub community_slug: String, pub community_description: Option, pub categories: Vec, pub is_owner: bool, pub is_mod_or_owner: bool, } /// Category view — dense thread table (the signature UI). #[derive(Template)] #[template(path = "pages/category.html")] pub struct CategoryTemplate { pub csrf_token: CsrfTokenOption, pub session_user: Option, pub mnw_base_url: std::sync::Arc, pub community_name: String, pub community_slug: String, pub category_name: String, pub category_slug: String, pub threads: Vec, pub pagination: Pagination, pub sort_column: String, pub sort_order: String, pub available_tags: Vec, pub active_tag: Option, } /// Thread view — post list with reply form. #[derive(Template)] #[template(path = "pages/thread.html")] pub struct ThreadTemplate { pub csrf_token: CsrfTokenOption, pub session_user: Option, pub mnw_base_url: std::sync::Arc, pub community_name: String, pub community_slug: String, pub category_name: String, pub category_slug: String, pub thread_id: String, pub thread_title: String, pub locked: bool, pub pinned: bool, pub is_mod: bool, pub can_mod_thread: bool, pub is_tracked: bool, pub posts: Vec, pub pagination: Pagination, } /// New thread creation form. #[derive(Template)] #[template(path = "pages/new_thread.html")] pub struct NewThreadTemplate { pub csrf_token: CsrfTokenOption, pub session_user: Option, pub mnw_base_url: std::sync::Arc, pub community_name: String, pub community_slug: String, pub category_name: String, pub category_slug: String, pub available_tags: Vec, } /// Edit thread title form. #[derive(Template)] #[template(path = "pages/edit_thread.html")] pub struct EditThreadTemplate { pub csrf_token: CsrfTokenOption, pub session_user: Option, pub mnw_base_url: std::sync::Arc, pub community_name: String, pub community_slug: String, pub category_name: String, pub category_slug: String, pub thread_id: String, pub current_title: String, } /// Category row for the settings page. pub struct SettingsCategoryRow { pub id: String, pub name: String, pub slug: String, pub description: Option, pub sort_order: i32, pub is_first: bool, pub is_last: bool, } /// Community settings page (owner only). #[derive(Template)] #[template(path = "pages/community_settings.html")] pub struct CommunitySettingsTemplate { pub csrf_token: CsrfTokenOption, pub session_user: Option, pub mnw_base_url: std::sync::Arc, pub community_name: String, pub community_slug: String, pub community_description: Option, pub auto_hide_threshold: Option, pub categories: Vec, pub tags: Vec, } /// Edit category form (owner only). #[derive(Template)] #[template(path = "pages/edit_category.html")] pub struct EditCategoryTemplate { pub csrf_token: CsrfTokenOption, pub session_user: Option, pub mnw_base_url: std::sync::Arc, pub community_name: String, pub community_slug: String, pub category_id: String, pub category_name: String, pub category_description: Option, } /// Row in the member list. pub struct MemberListRow { pub username: String, pub display_name: String, pub role: String, pub joined: String, } /// Community member list page. #[derive(Template)] #[template(path = "pages/members.html")] pub struct MembersTemplate { pub csrf_token: CsrfTokenOption, pub session_user: Option, pub mnw_base_url: std::sync::Arc, pub community_name: String, pub community_slug: String, pub members: Vec, } /// Activity row for user profile page. pub struct ProfileActivityRow { pub thread_id: String, pub thread_title: String, pub category_name: String, pub category_slug: String, pub timestamp: String, pub is_thread_author: bool, } /// User profile within a community. #[derive(Template)] #[template(path = "pages/user_profile.html")] pub struct UserProfileTemplate { pub csrf_token: CsrfTokenOption, pub session_user: Option, pub mnw_base_url: std::sync::Arc, pub community_name: String, pub community_slug: String, pub username: String, pub display_name: String, pub avatar_url: Option, pub role: String, pub joined: String, pub post_count: i64, pub endorsement_count: i64, pub activity: Vec, } /// Row in tracked threads list. pub struct TrackedThreadViewRow { pub thread_id: String, pub thread_title: String, pub community_name: String, pub community_slug: String, pub category_slug: String, pub unread_count: u32, pub has_mention: bool, } /// Tracked threads page. #[derive(Template)] #[template(path = "pages/tracked.html")] pub struct TrackedThreadsTemplate { pub csrf_token: CsrfTokenOption, pub session_user: Option, pub mnw_base_url: std::sync::Arc, pub threads: Vec, } // ============================================================================ // Search templates // ============================================================================ /// Single search result row. pub struct SearchResultViewRow { pub thread_id: String, pub thread_title: String, pub author_username: String, pub community_name: String, pub community_slug: String, pub category_name: String, pub category_slug: String, pub snippet: String, pub last_activity: String, } /// HTMX fragment — search results list. #[derive(Template)] #[template(path = "fragments/search_results.html")] pub struct SearchResultsFragment { pub results: Vec, } /// Privacy/tracking info page. #[derive(Template)] #[template(path = "pages/tracking_info.html")] pub struct TrackingInfoTemplate { pub csrf_token: CsrfTokenOption, pub session_user: Option, pub mnw_base_url: std::sync::Arc, } /// 404 error page. #[derive(Template)] #[template(path = "pages/error_404.html")] pub struct Error404Template { pub csrf_token: CsrfTokenOption, pub session_user: Option, pub mnw_base_url: std::sync::Arc, } /// 500 error page. #[derive(Template)] #[template(path = "pages/error_500.html")] pub struct Error500Template { pub csrf_token: CsrfTokenOption, pub session_user: Option, pub mnw_base_url: std::sync::Arc, } // ============================================================================ // Moderation templates // ============================================================================ /// Row in the active bans/mutes table. pub struct BanListRow { pub username: String, pub display_name: Option, pub ban_type: String, pub reason: Option, pub expires: Option, pub created: String, pub banned_by: String, } /// Row in the mod log. pub struct ModLogRow { pub actor: String, pub action: String, pub target: Option, pub reason: Option, pub timestamp: String, } /// Pending flag for moderation page. pub struct FlagViewRow { pub flag_id: String, pub post_id: String, pub thread_id: String, pub thread_title: String, pub category_slug: String, pub flagger_username: String, pub reason: String, pub detail: Option, pub created: String, } /// Community moderation page (mod/owner only). #[derive(Template)] #[template(path = "pages/moderation.html")] pub struct ModerationTemplate { pub csrf_token: CsrfTokenOption, pub session_user: Option, pub mnw_base_url: std::sync::Arc, pub community_name: String, pub community_slug: String, pub bans: Vec, pub pending_flags: Vec, pub is_owner: bool, } /// Mod log page (mod/owner only). #[derive(Template)] #[template(path = "pages/mod_log.html")] pub struct ModLogTemplate { pub csrf_token: CsrfTokenOption, pub session_user: Option, pub mnw_base_url: std::sync::Arc, pub community_name: String, pub community_slug: String, pub entries: Vec, pub pagination: Pagination, } // ============================================================================ // Admin templates // ============================================================================ /// Row for communities in admin dashboard. pub struct AdminCommunityViewRow { pub id: String, pub name: String, pub slug: String, pub is_suspended: bool, pub suspension_reason: Option, } /// Row for users in admin dashboard. pub struct AdminUserViewRow { pub id: String, pub username: String, pub display_name: Option, pub is_suspended: bool, pub suspension_reason: Option, } /// Platform admin dashboard. #[derive(Template)] #[template(path = "pages/admin.html")] pub struct AdminDashboardTemplate { pub csrf_token: CsrfTokenOption, pub session_user: Option, pub mnw_base_url: std::sync::Arc, pub communities: Vec, pub users: Vec, pub search_query: String, }