Skip to main content

dioxus_docs_kit/blog/
types.rs

1use dioxus_mdx::DocNode;
2use serde::Deserialize;
3use std::collections::HashMap;
4
5/// Blog manifest parsed from `_blog.json`.
6#[derive(Debug, Clone, Deserialize)]
7pub struct BlogManifest {
8    #[serde(default)]
9    pub authors: HashMap<String, Author>,
10    pub posts: Vec<String>,
11}
12
13/// Author definition from the blog manifest.
14#[derive(Debug, Clone, PartialEq, Deserialize)]
15pub struct Author {
16    pub name: String,
17    #[serde(default)]
18    pub avatar: Option<String>,
19    #[serde(default)]
20    pub bio: Option<String>,
21    #[serde(default)]
22    pub url: Option<String>,
23}
24
25/// Blog post frontmatter extracted from MDX files.
26#[derive(Debug, Clone, PartialEq, Deserialize)]
27pub struct BlogFrontmatter {
28    pub title: String,
29    #[serde(default)]
30    pub description: Option<String>,
31    /// ISO 8601 date string, e.g. "2026-03-15"
32    pub date: String,
33    /// Author ID referencing `_blog.json` authors map
34    pub author: String,
35    #[serde(default)]
36    pub tags: Vec<String>,
37    /// Cover image path (relative to assets/)
38    #[serde(default, rename = "coverImage")]
39    pub cover_image: Option<String>,
40    /// Set to true to hide from listing
41    #[serde(default)]
42    pub draft: bool,
43    /// Set to true to pin this post to the featured section
44    #[serde(default)]
45    pub featured: bool,
46}
47
48/// A fully parsed blog post.
49#[derive(Debug, Clone, PartialEq)]
50pub struct BlogPost {
51    /// URL slug (from filename)
52    pub slug: String,
53    pub frontmatter: BlogFrontmatter,
54    /// Parsed MDX content nodes
55    pub content: Vec<DocNode>,
56    /// Raw markdown for search indexing and reading time calculation
57    pub raw_markdown: String,
58    /// Estimated reading time in minutes
59    pub reading_time_minutes: u32,
60}
61
62/// A searchable entry in the blog.
63#[derive(PartialEq)]
64pub struct BlogSearchEntry {
65    pub slug: String,
66    pub title: String,
67    pub description: String,
68    pub content_preview: String,
69    pub date: String,
70    pub tags: Vec<String>,
71}
72
73/// Extract blog frontmatter from MDX content.
74///
75/// Returns the parsed frontmatter and the remaining content after the frontmatter block.
76pub fn extract_blog_frontmatter(content: &str) -> Option<(BlogFrontmatter, &str)> {
77    let content = content.trim();
78
79    if !content.starts_with("---") {
80        return None;
81    }
82
83    let after_first_delim = &content[3..];
84    let end_idx = after_first_delim.find("\n---")?;
85    let yaml_content = after_first_delim[..end_idx].trim();
86    let remaining = after_first_delim[end_idx + 4..].trim_start();
87
88    let fm: BlogFrontmatter = serde_yaml::from_str(yaml_content).ok()?;
89    Some((fm, remaining))
90}
91
92/// Calculate reading time from raw text (words / 200 WPM, minimum 1 minute).
93pub fn calculate_reading_time(text: &str) -> u32 {
94    let word_count = text.split_whitespace().count();
95    ((word_count as f64 / 200.0).ceil() as u32).max(1)
96}