dioxus_mdx/components/
renderer.rs1use std::sync::LazyLock;
4
5use dioxus::prelude::*;
6
7use super::slugify;
8use crate::components::{
9 DocAccordionGroup, DocCallout, DocCardGroup, DocCodeBlock, DocCodeGroup, DocExpandable,
10 DocParamField, DocRequestExample, DocResponseExample, DocResponseField, DocSteps, DocTabs,
11 DocUpdate, OpenApiViewer,
12};
13use crate::parser::{CardGroupNode, DocNode, parse_mdx};
14
15static HEADING_RE: LazyLock<regex::Regex> =
16 LazyLock::new(|| regex::Regex::new(r"<(h[2-4])>(.*?)</h[2-4]>").unwrap());
17static HTML_TAG_RE: LazyLock<regex::Regex> =
18 LazyLock::new(|| regex::Regex::new(r"<[^>]+>").unwrap());
19
20fn inject_heading_ids(html: &str) -> String {
22 HEADING_RE
23 .replace_all(html, |caps: ®ex::Captures| {
24 let tag = &caps[1];
25 let inner = &caps[2];
26 let plain = HTML_TAG_RE.replace_all(inner, "");
28 let id = slugify(&plain);
29 format!("<{tag} id=\"{id}\">{inner}</{tag}>")
30 })
31 .into_owned()
32}
33
34#[derive(Props, Clone, PartialEq)]
36pub struct DocNodeRendererProps {
37 pub node: DocNode,
39}
40
41#[component]
43pub fn DocNodeRenderer(props: DocNodeRendererProps) -> Element {
44 match &props.node {
45 DocNode::Markdown(md) => {
46 let html = markdown::to_html_with_options(md, &markdown::Options::gfm())
47 .unwrap_or_else(|_| md.clone());
48 let html = inject_heading_ids(&html);
49 rsx! {
50 div {
51 class: "prose-content",
52 dangerous_inner_html: html,
53 }
54 }
55 }
56 DocNode::Callout(callout) => {
57 rsx! {
58 DocCallout {
59 callout_type: callout.callout_type,
60 content: callout.content.clone(),
61 }
62 }
63 }
64 DocNode::Card(card) => {
65 rsx! {
67 DocCardGroup {
68 group: CardGroupNode {
69 cols: 1,
70 cards: vec![card.clone()],
71 }
72 }
73 }
74 }
75 DocNode::CardGroup(group) => {
76 rsx! {
77 DocCardGroup { group: group.clone() }
78 }
79 }
80 DocNode::Tabs(tabs) => {
81 rsx! {
82 DocTabs { tabs: tabs.clone() }
83 }
84 }
85 DocNode::Steps(steps) => {
86 rsx! {
87 DocSteps { steps: steps.clone() }
88 }
89 }
90 DocNode::AccordionGroup(group) => {
91 rsx! {
92 DocAccordionGroup { group: group.clone() }
93 }
94 }
95 DocNode::CodeBlock(block) => {
96 rsx! {
97 DocCodeBlock { block: block.clone() }
98 }
99 }
100 DocNode::CodeGroup(group) => {
101 rsx! {
102 DocCodeGroup { group: group.clone() }
103 }
104 }
105 DocNode::ParamField(field) => {
106 rsx! {
107 DocParamField { field: field.clone() }
108 }
109 }
110 DocNode::ResponseField(field) => {
111 rsx! {
112 DocResponseField { field: field.clone() }
113 }
114 }
115 DocNode::Expandable(expandable) => {
116 rsx! {
117 DocExpandable { expandable: expandable.clone() }
118 }
119 }
120 DocNode::RequestExample(example) => {
121 rsx! {
122 DocRequestExample { example: example.clone() }
123 }
124 }
125 DocNode::ResponseExample(example) => {
126 rsx! {
127 DocResponseExample { example: example.clone() }
128 }
129 }
130 DocNode::Update(update) => {
131 rsx! {
132 DocUpdate { update: update.clone() }
133 }
134 }
135 DocNode::OpenApi(openapi) => {
136 rsx! {
137 OpenApiViewer {
138 spec: openapi.spec.clone(),
139 tags: openapi.tags.clone(),
140 show_schemas: openapi.show_schemas,
141 }
142 }
143 }
144 }
145}
146
147#[derive(Props, Clone, PartialEq)]
149pub struct DocContentProps {
150 pub nodes: Vec<DocNode>,
152}
153
154#[component]
156pub fn DocContent(props: DocContentProps) -> Element {
157 rsx! {
158 div { class: "doc-content",
159 for (i, node) in props.nodes.iter().enumerate() {
160 DocNodeRenderer { key: "{i}", node: node.clone() }
161 }
162 }
163 }
164}
165
166#[derive(Props, Clone, PartialEq)]
168pub struct MdxContentProps {
169 pub content: String,
171}
172
173#[component]
191pub fn MdxContent(props: MdxContentProps) -> Element {
192 let nodes = parse_mdx(&props.content);
193
194 rsx! {
195 DocContent { nodes: nodes }
196 }
197}
198
199#[component]
201pub fn MdxRenderer(content: String) -> Element {
202 let nodes = parse_mdx(&content);
203
204 rsx! {
205 DocContent { nodes: nodes }
206 }
207}