1use leptos::prelude::*;
2
3#[derive(Clone, Debug, Default, PartialEq, Eq)]
4pub enum CodeBlockTheme {
5 #[default]
6 Default,
7 Dark,
8 Light,
9 GitHub,
10 Monokai,
11}
12
13#[derive(Clone, Debug)]
14pub struct MarkdownOptions {
15 pub enable_gfm: bool,
16 pub code_theme: Option<CodeBlockTheme>,
18 pub syntax_highlighting_language_classes: bool,
20 pub open_links_in_new_tab: bool,
21 pub allow_raw_html: bool,
22 pub use_explicit_classes: bool,
26}
27
28impl Default for MarkdownOptions {
29 fn default() -> Self {
30 Self {
31 enable_gfm: true,
32 code_theme: Some(CodeBlockTheme::default()),
33 syntax_highlighting_language_classes: true,
34 open_links_in_new_tab: true,
35 allow_raw_html: true,
36 use_explicit_classes: false,
37 }
38 }
39}
40
41impl MarkdownOptions {
42 #[must_use]
44 pub fn new() -> Self {
45 Self::default()
46 }
47
48 #[must_use]
50 pub fn with_gfm(mut self, enable: bool) -> Self {
51 self.enable_gfm = enable;
52 self
53 }
54
55 #[must_use]
57 pub fn with_code_theme(mut self, theme: CodeBlockTheme) -> Self {
58 self.code_theme = Some(theme);
59 self
60 }
61
62 #[must_use]
64 pub fn without_code_theme(mut self) -> Self {
65 self.code_theme = None;
66 self
67 }
68
69 #[must_use]
71 pub fn with_language_classes(mut self, enable: bool) -> Self {
72 self.syntax_highlighting_language_classes = enable;
73 self
74 }
75
76 #[must_use]
78 pub fn with_new_tab_links(mut self, enable: bool) -> Self {
79 self.open_links_in_new_tab = enable;
80 self
81 }
82
83 #[must_use]
85 pub fn with_allow_raw_html(mut self, enable: bool) -> Self {
86 self.allow_raw_html = enable;
87 self
88 }
89
90 #[must_use]
94 pub fn with_explicit_classes(mut self, enable: bool) -> Self {
95 self.use_explicit_classes = enable;
96 self
97 }
98}
99
100pub struct MarkdownClasses;
102
103impl MarkdownClasses {
104 pub const CONTENT: &'static str =
106 "leptos-mdx-content prose prose-gray max-w-none dark:prose-invert";
107
108 pub const H1: &'static str =
110 "text-3xl font-bold text-gray-900 dark:text-gray-100 mt-6 mb-4 first:mt-0";
111 pub const H2: &'static str =
112 "text-2xl font-semibold text-gray-900 dark:text-gray-100 mt-5 mb-3";
113 pub const H3: &'static str = "text-xl font-semibold text-gray-900 dark:text-gray-100 mt-4 mb-2";
114 pub const H4: &'static str = "text-lg font-medium text-gray-900 dark:text-gray-100 mt-3 mb-2";
115 pub const H5: &'static str = "text-base font-medium text-gray-900 dark:text-gray-100 mt-3 mb-2";
116 pub const H6: &'static str = "text-sm font-medium text-gray-600 dark:text-gray-400 mt-3 mb-2";
117
118 pub const PARAGRAPH: &'static str = "mb-4 leading-relaxed text-gray-700 dark:text-gray-300";
120 pub const BLOCKQUOTE: &'static str = "border-l-4 border-blue-500 pl-4 py-2 my-4 bg-blue-50 dark:bg-blue-950/30 text-gray-700 dark:text-gray-300 italic";
121
122 pub const INLINE_CODE: &'static str = "bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 px-1.5 py-0.5 rounded text-sm font-mono";
124 pub const CODE_BLOCK: &'static str = "bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-lg p-4 my-4 overflow-x-auto";
125 pub const CODE_BLOCK_CODE: &'static str =
126 "font-mono text-sm leading-relaxed text-gray-800 dark:text-gray-200";
127
128 pub const UL: &'static str =
130 "list-disc list-inside mb-4 space-y-1 text-gray-700 dark:text-gray-300";
131 pub const OL: &'static str =
132 "list-decimal list-inside mb-4 space-y-1 text-gray-700 dark:text-gray-300";
133 pub const LI: &'static str = "leading-relaxed";
134
135 pub const LINK: &'static str = "text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 underline underline-offset-2 hover:underline-offset-4 transition-all";
137 pub const IMAGE: &'static str = "max-w-full h-auto rounded-lg shadow-sm my-4";
138
139 pub const TABLE: &'static str = "min-w-full divide-y divide-gray-200 dark:divide-gray-700 my-4 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden";
141 pub const THEAD: &'static str = "bg-gray-50 dark:bg-gray-800";
142 pub const TR: &'static str =
143 "bg-white dark:bg-gray-900 even:bg-gray-50 dark:even:bg-gray-800/50";
144 pub const TD: &'static str = "px-6 py-4 text-sm text-gray-900 dark:text-gray-100";
145 pub const TH: &'static str = "px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider";
146
147 pub const HR: &'static str = "border-0 h-px bg-gradient-to-r from-transparent via-gray-300 dark:via-gray-600 to-transparent my-8";
149 pub const CHECKBOX: &'static str = "mr-2 accent-blue-600";
150
151 pub const MATH_INLINE: &'static str = "font-serif italic text-gray-800 dark:text-gray-200";
153 pub const MATH_DISPLAY: &'static str = "font-serif italic text-center my-4 p-3 bg-gray-50 dark:bg-gray-800 rounded-lg text-gray-800 dark:text-gray-200";
154
155 pub const DL: &'static str = "my-4";
157 pub const DT: &'static str = "font-semibold text-gray-900 dark:text-gray-100 mt-4 first:mt-0";
158 pub const DD: &'static str = "ml-6 mb-2 text-gray-700 dark:text-gray-300";
159
160 pub const SUP: &'static str = "text-xs align-super";
162 pub const SUB: &'static str = "text-xs align-sub";
163
164 pub const EM: &'static str = "italic";
166 pub const STRONG: &'static str = "font-bold";
167 pub const DEL: &'static str = "line-through text-gray-500 dark:text-gray-400";
168
169 pub const FOOTNOTE_REF: &'static str = "text-xs align-super text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300";
171 pub const FOOTNOTE_DEF: &'static str = "text-sm border-t border-gray-200 dark:border-gray-700 mt-8 pt-4 text-gray-600 dark:text-gray-400";
172 pub const RAW_HTML_BLOCK: &'static str = "bg-yellow-50 dark:bg-yellow-950/30 border border-yellow-200 dark:border-yellow-800 rounded-lg p-3 my-4 font-mono text-sm text-yellow-800 dark:text-yellow-200 whitespace-pre-wrap";
173 pub const INLINE_HTML: &'static str = "bg-yellow-100 dark:bg-yellow-900/50 text-yellow-800 dark:text-yellow-200 px-2 py-1 rounded text-xs font-mono border border-yellow-300 dark:border-yellow-700";
174
175 pub const THEME_DEFAULT: &'static str = "bg-gray-50 dark:bg-gray-900";
177 pub const THEME_DARK: &'static str = "bg-gray-900 text-gray-100";
178 pub const THEME_LIGHT: &'static str = "bg-white text-gray-900 border";
179 pub const THEME_GITHUB: &'static str =
180 "bg-[#f6f8fa] dark:bg-[#0d1117] text-[#24292f] dark:text-[#f0f6fc]";
181 pub const THEME_MONOKAI: &'static str = "bg-[#272822] text-[#f8f8f2]";
182}
183
184pub fn get_code_theme_classes(theme: &CodeBlockTheme) -> &'static str {
186 match theme {
187 CodeBlockTheme::Default => MarkdownClasses::THEME_DEFAULT,
188 CodeBlockTheme::Dark => MarkdownClasses::THEME_DARK,
189 CodeBlockTheme::Light => MarkdownClasses::THEME_LIGHT,
190 CodeBlockTheme::GitHub => MarkdownClasses::THEME_GITHUB,
191 CodeBlockTheme::Monokai => MarkdownClasses::THEME_MONOKAI,
192 }
193}
194
195pub fn get_enhanced_prose_classes() -> &'static str {
197 "leptos-mdx-content prose prose-gray max-w-none dark:prose-invert prose-headings:font-bold prose-headings:text-gray-900 dark:prose-headings:text-gray-100 prose-p:text-gray-700 dark:prose-p:text-gray-300 prose-a:text-blue-600 dark:prose-a:text-blue-400 prose-strong:text-gray-900 dark:prose-strong:text-gray-100 prose-code:text-gray-800 dark:prose-code:text-gray-200 prose-pre:bg-gray-50 dark:prose-pre:bg-gray-900"
198}
199
200#[component]
202pub fn MarkdownStyles() -> impl IntoView {
203 ""
205}