karaty-template 0.2.2

default karaty template
Documentation
use std::collections::HashMap;

use dioxus::prelude::*;
use dioxus_retrouter::Link;
use karaty_blueprint::TemplateProps;
use markdown::mdast;

use crate::blog::PostInfo;

#[allow(non_snake_case)]
pub fn DocsPreset(cx: Scope<TemplateProps>) -> Element {
    let _404 = cx.props.utility._404;
    let Navbar = cx.props.utility.navbar;
    let Footer = cx.props.utility.footer;
    let Markdown = *cx.props.utility.renderers.get("markdown").unwrap();
    let Giscus = cx.props.utility.giscus;

    let data = &cx.props.data;
    let config = cx.props.config.clone();

    let segment_name = if let Some(karaty_blueprint::Value::String(v)) = config.get("file-segment")
    {
        v.to_string()
    } else {
        "path".to_string()
    };

    let file = cx.props.route.segments.get(&segment_name);
    if file.is_none() {
        return cx.render(rsx! {
            _404 {}
        });
    }
    let file = file.unwrap();

    let mut file_path = file.split(".").map(String::from).collect::<Vec<String>>();
    file_path.last_mut().map(|v| *v = format!("{v}.md"));
    let index = data.get(vec!["_index.md".to_string()]);
    let index = {
        if let Some(karaty_blueprint::TemplateData::File(index)) = index {
            let index = markdown::to_mdast(&index, &markdown::ParseOptions::default()).ok();
            if index.is_none() {
                vec![]
            } else {
                let index = index.unwrap();
                let mut index_nodes = vec![];
                if let mdast::Node::Root(root) = index {
                    index_nodes = root.clone().children;
                }
                index_nodes
            }
        } else {
            vec![]
        }
    };
    let data = data.get(file_path);

    match data {
        Some(karaty_blueprint::TemplateData::File(data)) => {
            let data = to_info(data);
            if data.is_none() {
                return cx.render(rsx! {
                    _404 {}
                });
            }
            let data = data.unwrap();

            let date = if data.date.is_empty() {
                "Unknown".to_string()
            } else {
                data.date
            };

            cx.render(rsx! {
                div { class: "bg-cover bg-white dark:bg-gray-900 dark:text-white",
                    Navbar {}
                    div { class: "container mx-auto px-8 max-w-7xl",
                        div { class: "grid grid-cols-12 gap-6",
                            div {
                                class: "row-span-3 max-h-[34rem] col-span-12 sm:col-span-3 bg-gray-50 dark:bg-gray-800 rounded-md",
                                div {
                                    class: "px-3 py-2",
                                    DocsSideBar {
                                        index: index.clone(),
                                        path: cx.props.route.bound_path.clone(),
                                        file_sign: segment_name.clone(),
                                    }
                                }
                            }
                            div {
                                class: "col-span-12 sm:col-span-8",
                                span {
                                    class: "font-bold text-3xl sm:text-4xl text-gray-600 dark:text-gray-200",
                                    "{data.title}"
                                }
                                span {
                                    class: "hidden sm:block float-right text-gray-400 dark:text-gray-300",
                                    "Updated on {date}"
                                }
                                p {
                                    class: "sm:hidden text-gray-400 dark:text-gray-300",
                                    "Updated on {date}"
                                }
                            }
                            div {
                                class:"row-span-2 col-span-12 sm:col-span-8",
                                div {
                                    class: "prose prose-sm sm:prose-base mt-4 dark:text-white dark:prose-invert",
                                    Markdown {
                                        content: data.content.clone(),
                                        config: Default::default(),
                                    }
                                }
                            }
                        }
                    }
                    Giscus {}
                    div { class: "giscus flex justify-center container mx-auto my-12" }
                    div {
                        class: "flex justify-center container mx-auto my-14",
                        Footer {}
                    }
                }
            })
        }
        _ => cx.render(rsx! {
            _404 {}
        }),
    }
}

#[derive(PartialEq, Props)]
pub struct SideBarProps {
    index: Vec<mdast::Node>,
    path: String,
    file_sign: String,
}

#[allow(non_snake_case)]
pub fn DocsSideBar(cx: Scope<SideBarProps>) -> Element {
    let node = cx.props.index.clone();

    let display = node.iter().map(|node| {
        let child = if let Some(v) = node.children() {
            v.clone()
        } else {
            vec![]
        };
        let embedd = rsx! {
            DocsSideBar {
                index: child,
                path: cx.props.path.clone(),
                file_sign: cx.props.file_sign.clone(),
            }
        };
        if let mdast::Node::ListItem(_) = node {
            return rsx! {
                li {
                    class: "text-xs font-mono dark:text-white",
                    embedd
                }
            };
        } else if let mdast::Node::List(_) = node {
            return rsx! {
                ul {
                    class: "px-5 py-1",
                    embedd
                }
            };
        } else if let mdast::Node::Paragraph(_) = node {
            return rsx! {
                embedd
            };
        } else if let mdast::Node::Link(link) = node {
            let class =
                "text-gray-600 dark:text-gray-200 hover:text-blue-700 dark:hover:text-blue-300";
            if &link.url[0..1] == "@" {
                let url = cx
                    .props
                    .path
                    .replace(&format!(":{}", cx.props.file_sign), &link.url[1..]);
                return rsx! {
                    Link {
                        class: "{class}",
                        to: "{url}",
                        embedd
                    }
                };
            } else {
                return rsx! {
                    a {
                        class: "{class}",
                        href: "{link.url}",
                        embedd
                    }
                };
            }
        } else if let mdast::Node::Text(text) = node {
            return rsx! {
                "{text.value}"
            };
        }
        rsx! { div { "{node:?}" } }
    });

    cx.render(rsx! {
        display
    })
}

fn to_info(meta_info: String) -> Option<PostInfo> {
    let mut type_mark = HashMap::new();

    type_mark.insert("title".into(), "string");
    type_mark.insert("tags".into(), "array");
    type_mark.insert("category".into(), "string");
    type_mark.insert("date".into(), "string");
    type_mark.insert("released".into(), "bool");

    let temp = markdown_meta_parser::MetaData {
        content: meta_info,
        required: vec!["title".to_string()],
        type_mark,
    }
    .parse()
    .ok();

    temp.as_ref()?;
    let (meta_info, content) = temp.unwrap();

    if meta_info.contains_key("released")
        && !meta_info
            .get("released")
            .unwrap()
            .clone()
            .as_bool()
            .unwrap()
    {
        return None;
    }

    let title = meta_info.get("title").unwrap().clone();

    let date = meta_info.get("date");
    let date = if let Some(d) = date {
        d.clone().as_string().unwrap()
    } else {
        "".to_string()
    };

    let tags = meta_info.get("tags");
    let tags = if let Some(v) = tags {
        v.clone().as_array().unwrap()
    } else {
        vec![]
    };

    let category = meta_info.get("category");
    let category = if let Some(v) = category {
        v.clone().as_string()
    } else {
        None
    };

    let title = title.as_string().unwrap();

    let blog_info = PostInfo {
        title,
        tags,
        category,
        date,
        path: String::new(),
        content,
        sub_group: Default::default(),
    };
    Some(blog_info)
}