Skip to main content

dioxus_docs_kit/components/blog/
blog_meta.rs

1use dioxus::prelude::*;
2
3use crate::BlogContext;
4use crate::blog::registry::BlogRegistry;
5
6fn join_site_url(site_url: &str, base_path: &str, slug: Option<&str>) -> String {
7    let mut url = site_url.trim_end_matches('/').to_string();
8
9    if !base_path.is_empty() {
10        if !base_path.starts_with('/') {
11            url.push('/');
12        }
13        url.push_str(base_path.trim_end_matches('/'));
14    }
15
16    if let Some(slug) = slug
17        && !slug.is_empty()
18    {
19        url.push('/');
20        url.push_str(slug.trim_start_matches('/'));
21    }
22
23    url
24}
25
26/// Injects Open Graph / SEO meta tags and document title for a single blog post.
27#[component]
28pub fn BlogPostMeta(slug: String, site_url: String) -> Element {
29    let registry = use_context::<&'static BlogRegistry>();
30    let ctx = use_context::<BlogContext>();
31
32    let post = match registry.get_post(&slug) {
33        Some(p) => p,
34        None => return rsx! {},
35    };
36
37    let title = &post.frontmatter.title;
38    let description = post.frontmatter.description.as_deref().unwrap_or("");
39    let url = join_site_url(&site_url, &ctx.base_path, Some(&slug));
40    let date = &post.frontmatter.date;
41    let author_name = registry
42        .get_author(&post.frontmatter.author)
43        .map(|a| a.name.as_str())
44        .unwrap_or("");
45
46    rsx! {
47        document::Title { "{title}" }
48        document::Meta { name: "description", content: "{description}" }
49
50        // Open Graph
51        document::Meta { property: "og:title", content: "{title}" }
52        document::Meta { property: "og:description", content: "{description}" }
53        document::Meta { property: "og:type", content: "article" }
54        document::Meta { property: "og:url", content: "{url}" }
55        if let Some(ref cover) = post.frontmatter.cover_image {
56            document::Meta { property: "og:image", content: "{cover}" }
57        }
58
59        // Twitter Card
60        document::Meta { name: "twitter:card", content: "summary_large_image" }
61        document::Meta { name: "twitter:title", content: "{title}" }
62        document::Meta { name: "twitter:description", content: "{description}" }
63        if let Some(ref cover) = post.frontmatter.cover_image {
64            document::Meta { name: "twitter:image", content: "{cover}" }
65        }
66
67        // Article metadata
68        document::Meta { property: "article:published_time", content: "{date}" }
69        if !author_name.is_empty() {
70            document::Meta { property: "article:author", content: "{author_name}" }
71        }
72        for tag in post.frontmatter.tags.iter() {
73            document::Meta { property: "article:tag", content: "{tag}" }
74        }
75    }
76}
77
78/// Injects basic SEO meta tags for the blog index/listing page.
79#[component]
80pub fn BlogIndexMeta(title: String, description: String, site_url: String) -> Element {
81    let ctx = use_context::<BlogContext>();
82    let url = join_site_url(&site_url, &ctx.base_path, None);
83
84    rsx! {
85        document::Title { "{title}" }
86        document::Meta { name: "description", content: "{description}" }
87        document::Meta { property: "og:title", content: "{title}" }
88        document::Meta { property: "og:description", content: "{description}" }
89        document::Meta { property: "og:type", content: "website" }
90        document::Meta { property: "og:url", content: "{url}" }
91        document::Meta { name: "twitter:card", content: "summary" }
92        document::Meta { name: "twitter:title", content: "{title}" }
93        document::Meta { name: "twitter:description", content: "{description}" }
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::join_site_url;
100
101    #[test]
102    fn joins_site_url_without_duplicate_slashes() {
103        assert_eq!(
104            join_site_url("https://example.com/", "/blog/", Some("hello-world")),
105            "https://example.com/blog/hello-world"
106        );
107        assert_eq!(
108            join_site_url("https://example.com", "blog", Some("/hello-world")),
109            "https://example.com/blog/hello-world"
110        );
111        assert_eq!(
112            join_site_url("https://example.com/", "/blog/", None),
113            "https://example.com/blog"
114        );
115    }
116}