use dioxus::prelude::*;
use super::slugify;
use crate::components::{
DocAccordionGroup, DocCallout, DocCardGroup, DocCodeBlock, DocCodeGroup, DocExpandable,
DocParamField, DocRequestExample, DocResponseExample, DocResponseField, DocSteps, DocTabs,
DocUpdate, OpenApiViewer,
};
use crate::parser::{CardGroupNode, DocNode, parse_mdx};
fn inject_heading_ids(html: &str) -> String {
let re = regex::Regex::new(r"<(h[2-4])>(.*?)</h[2-4]>").unwrap();
re.replace_all(html, |caps: ®ex::Captures| {
let tag = &caps[1];
let inner = &caps[2];
let plain = regex::Regex::new(r"<[^>]+>")
.unwrap()
.replace_all(inner, "");
let id = slugify(&plain);
format!("<{tag} id=\"{id}\">{inner}</{tag}>")
})
.into_owned()
}
#[derive(Props, Clone, PartialEq)]
pub struct DocNodeRendererProps {
pub node: DocNode,
}
#[component]
pub fn DocNodeRenderer(props: DocNodeRendererProps) -> Element {
match &props.node {
DocNode::Markdown(md) => {
let html = markdown::to_html_with_options(md, &markdown::Options::gfm())
.unwrap_or_else(|_| md.clone());
let html = inject_heading_ids(&html);
rsx! {
div {
class: "prose-content",
dangerous_inner_html: html,
}
}
}
DocNode::Callout(callout) => {
rsx! {
DocCallout {
callout_type: callout.callout_type,
content: callout.content.clone(),
}
}
}
DocNode::Card(card) => {
rsx! {
DocCardGroup {
group: CardGroupNode {
cols: 1,
cards: vec![card.clone()],
}
}
}
}
DocNode::CardGroup(group) => {
rsx! {
DocCardGroup { group: group.clone() }
}
}
DocNode::Tabs(tabs) => {
rsx! {
DocTabs { tabs: tabs.clone() }
}
}
DocNode::Steps(steps) => {
rsx! {
DocSteps { steps: steps.clone() }
}
}
DocNode::AccordionGroup(group) => {
rsx! {
DocAccordionGroup { group: group.clone() }
}
}
DocNode::CodeBlock(block) => {
rsx! {
DocCodeBlock { block: block.clone() }
}
}
DocNode::CodeGroup(group) => {
rsx! {
DocCodeGroup { group: group.clone() }
}
}
DocNode::ParamField(field) => {
rsx! {
DocParamField { field: field.clone() }
}
}
DocNode::ResponseField(field) => {
rsx! {
DocResponseField { field: field.clone() }
}
}
DocNode::Expandable(expandable) => {
rsx! {
DocExpandable { expandable: expandable.clone() }
}
}
DocNode::RequestExample(example) => {
rsx! {
DocRequestExample { example: example.clone() }
}
}
DocNode::ResponseExample(example) => {
rsx! {
DocResponseExample { example: example.clone() }
}
}
DocNode::Update(update) => {
rsx! {
DocUpdate { update: update.clone() }
}
}
DocNode::OpenApi(openapi) => {
rsx! {
OpenApiViewer {
spec: openapi.spec.clone(),
tags: openapi.tags.clone(),
show_schemas: openapi.show_schemas,
}
}
}
}
}
#[derive(Props, Clone, PartialEq)]
pub struct DocContentProps {
pub nodes: Vec<DocNode>,
}
#[component]
pub fn DocContent(props: DocContentProps) -> Element {
rsx! {
div { class: "doc-content",
for (i, node) in props.nodes.iter().enumerate() {
DocNodeRenderer { key: "{i}", node: node.clone() }
}
}
}
}
#[derive(Props, Clone, PartialEq)]
pub struct MdxContentProps {
pub content: String,
}
#[component]
pub fn MdxContent(props: MdxContentProps) -> Element {
let nodes = parse_mdx(&props.content);
rsx! {
DocContent { nodes: nodes }
}
}
#[component]
pub fn MdxRenderer(content: String) -> Element {
let nodes = parse_mdx(&content);
rsx! {
DocContent { nodes: nodes }
}
}