mdx/
theme.rs

1use std::collections::HashMap;
2
3/// Theme options
4#[derive(Debug, Clone)]
5pub struct ThemeOptions {
6    /// The name of the theme
7    pub name: String,
8    /// Custom CSS variables
9    pub variables: HashMap<String, String>,
10}
11
12impl Default for ThemeOptions {
13    fn default() -> Self {
14        Self {
15            name: "modern".to_string(),
16            variables: HashMap::new(),
17        }
18    }
19}
20
21/// Theme for styling rendered Markdown
22pub struct Theme {
23    name: String,
24    variables: HashMap<String, String>,
25}
26
27impl Theme {
28    /// Create a new theme with the given name
29    pub fn new(name: &str) -> Self {
30        let mut theme = Self {
31            name: name.to_string(),
32            variables: HashMap::new(),
33        };
34
35        // Set default variables
36        theme.set_default_variables();
37
38        theme
39    }
40
41    /// Set default CSS variables
42    fn set_default_variables(&mut self) {
43        let defaults = match self.name.as_str() {
44            "modern" => get_modern_theme_variables(),
45            "minimal" => get_minimal_theme_variables(),
46            "dark" => get_dark_theme_variables(),
47            "light" => get_light_theme_variables(),
48            _ => get_modern_theme_variables(), // Default to modern
49        };
50
51        for (key, value) in defaults {
52            self.variables.insert(key.to_string(), value.to_string());
53        }
54    }
55
56    /// Get the theme's CSS
57    pub fn get_css(&self) -> String {
58        let mut css = String::from(":root {\n");
59
60        // Add CSS variables
61        for (key, value) in &self.variables {
62            css.push_str(&format!("  --{}: {};\n", key, value));
63        }
64
65        css.push_str("}\n\n");
66
67        // Add theme CSS based on name
68        match self.name.as_str() {
69            "modern" => css.push_str(MODERN_THEME_CSS),
70            "minimal" => css.push_str(MINIMAL_THEME_CSS),
71            "dark" => css.push_str(DARK_THEME_CSS),
72            "light" => css.push_str(LIGHT_THEME_CSS),
73            _ => css.push_str(MODERN_THEME_CSS), // Default to modern
74        }
75
76        css
77    }
78
79    /// Set a CSS variable
80    pub fn set_variable(&mut self, key: &str, value: &str) {
81        self.variables.insert(key.to_string(), value.to_string());
82    }
83
84    /// Get a CSS variable
85    pub fn get_variable(&self, key: &str) -> Option<&String> {
86        self.variables.get(key)
87    }
88}
89
90// Default CSS variables for the Modern theme
91fn get_modern_theme_variables() -> Vec<(&'static str, &'static str)> {
92    vec![
93        ("primary-color", "#3b82f6"),
94        ("secondary-color", "#6b7280"),
95        ("background-color", "#ffffff"),
96        ("text-color", "#1f2937"),
97        ("muted-color", "#6b7280"),
98        ("border-color", "#e5e7eb"),
99        ("font-family", "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif"),
100        ("monospace-font", "'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace"),
101        ("content-width", "768px"),
102        ("info-color", "#3b82f6"),
103        ("info-light-color", "#eff6ff"),
104        ("warning-color", "#f59e0b"),
105        ("warning-light-color", "#fffbeb"),
106        ("error-color", "#ef4444"),
107        ("error-light-color", "#fee2e2"),
108        ("success-color", "#10b981"),
109        ("success-light-color", "#ecfdf5"),
110    ]
111}
112
113// Default CSS variables for the Minimal theme
114fn get_minimal_theme_variables() -> Vec<(&'static str, &'static str)> {
115    vec![
116        ("primary-color", "#000000"),
117        ("secondary-color", "#4b5563"),
118        ("background-color", "#ffffff"),
119        ("text-color", "#000000"),
120        ("muted-color", "#4b5563"),
121        ("border-color", "#e5e7eb"),
122        ("font-family", "Georgia, serif"),
123        (
124            "monospace-font",
125            "'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace",
126        ),
127        ("content-width", "700px"),
128        ("info-color", "#000000"),
129        ("info-light-color", "#f3f4f6"),
130        ("warning-color", "#000000"),
131        ("warning-light-color", "#f3f4f6"),
132        ("error-color", "#000000"),
133        ("error-light-color", "#f3f4f6"),
134        ("success-color", "#000000"),
135        ("success-light-color", "#f3f4f6"),
136    ]
137}
138
139// Default CSS variables for the Dark theme
140fn get_dark_theme_variables() -> Vec<(&'static str, &'static str)> {
141    vec![
142        ("primary-color", "#3b82f6"),
143        ("secondary-color", "#9ca3af"),
144        ("background-color", "#111827"),
145        ("text-color", "#f9fafb"),
146        ("muted-color", "#9ca3af"),
147        ("border-color", "#374151"),
148        ("font-family", "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif"),
149        ("monospace-font", "'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace"),
150        ("content-width", "768px"),
151        ("info-color", "#3b82f6"),
152        ("info-light-color", "#1e3a8a"),
153        ("warning-color", "#f59e0b"),
154        ("warning-light-color", "#78350f"),
155        ("error-color", "#ef4444"),
156        ("error-light-color", "#7f1d1d"),
157        ("success-color", "#10b981"),
158        ("success-light-color", "#064e3b"),
159    ]
160}
161
162// Default CSS variables for the Light theme
163fn get_light_theme_variables() -> Vec<(&'static str, &'static str)> {
164    vec![
165        ("primary-color", "#0284c7"),
166        ("secondary-color", "#64748b"),
167        ("background-color", "#f8fafc"),
168        ("text-color", "#334155"),
169        ("muted-color", "#64748b"),
170        ("border-color", "#e2e8f0"),
171        ("font-family", "system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif"),
172        ("monospace-font", "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace"),
173        ("content-width", "768px"),
174        ("info-color", "#0284c7"),
175        ("info-light-color", "#e0f2fe"),
176        ("warning-color", "#ea580c"),
177        ("warning-light-color", "#fff7ed"),
178        ("error-color", "#dc2626"),
179        ("error-light-color", "#fee2e2"),
180        ("success-color", "#16a34a"),
181        ("success-light-color", "#f0fdf4"),
182    ]
183}
184
185// CSS for the Modern theme
186const MODERN_THEME_CSS: &str = r#"
187/* Modern theme */
188body {
189  font-family: var(--font-family);
190  color: var(--text-color);
191  background-color: var(--background-color);
192  line-height: 1.6;
193  margin: 0;
194  padding: 0;
195}
196
197.markrust-container {
198  max-width: var(--content-width);
199  margin: 0 auto;
200  padding: 2rem 1rem;
201}
202
203.markrust-content {
204  margin-top: 2rem;
205}
206
207h1, h2, h3, h4, h5, h6 {
208  margin-top: 2rem;
209  margin-bottom: 1rem;
210  font-weight: 600;
211  line-height: 1.25;
212}
213
214h1 {
215  font-size: 2.25rem;
216  border-bottom: 1px solid var(--border-color);
217  padding-bottom: 0.5rem;
218}
219
220h2 {
221  font-size: 1.8rem;
222}
223
224h3 {
225  font-size: 1.5rem;
226}
227
228h4 {
229  font-size: 1.25rem;
230}
231
232h5 {
233  font-size: 1rem;
234}
235
236h6 {
237  font-size: 0.875rem;
238}
239
240a {
241  color: var(--primary-color);
242  text-decoration: none;
243}
244
245a:hover {
246  text-decoration: underline;
247}
248
249p {
250  margin-top: 0;
251  margin-bottom: 1rem;
252}
253
254ul, ol {
255  margin-top: 0;
256  margin-bottom: 1rem;
257  padding-left: 2rem;
258}
259
260li {
261  margin-bottom: 0.25rem;
262}
263
264blockquote {
265  margin: 1rem 0;
266  padding: 0.5rem 1rem;
267  border-left: 4px solid var(--primary-color);
268  background-color: rgba(0, 0, 0, 0.05);
269}
270
271hr {
272  border: 0;
273  height: 1px;
274  background-color: var(--border-color);
275  margin: 2rem 0;
276}
277
278code {
279  font-family: var(--monospace-font);
280  font-size: 0.9em;
281  background-color: rgba(0, 0, 0, 0.05);
282  padding: 0.2em 0.4em;
283  border-radius: 3px;
284}
285
286pre {
287  margin: 1rem 0;
288  padding: 1rem;
289  overflow-x: auto;
290  background-color: rgba(0, 0, 0, 0.05);
291  border-radius: 0.375rem;
292}
293
294pre code {
295  background: none;
296  padding: 0;
297  font-size: 0.9em;
298  color: inherit;
299}
300
301/* Code block with copy button */
302.markrust-code-block {
303  position: relative;
304}
305
306.markrust-copy-button {
307  position: absolute;
308  top: 0.5rem;
309  right: 0.5rem;
310  padding: 0.25rem;
311  background-color: rgba(255, 255, 255, 0.2);
312  border: none;
313  border-radius: 0.25rem;
314  color: var(--text-color);
315  cursor: pointer;
316  opacity: 0.6;
317  transition: opacity 0.2s;
318}
319
320.markrust-copy-button:hover {
321  opacity: 1;
322}
323
324/* Table styles */
325.markrust-table {
326  border-collapse: collapse;
327  width: 100%;
328  margin: 1rem 0;
329}
330
331.markrust-table th,
332.markrust-table td {
333  border: 1px solid var(--border-color);
334  padding: 0.5rem 0.75rem;
335  text-align: left;
336}
337
338.markrust-table th {
339  background-color: rgba(0, 0, 0, 0.05);
340  font-weight: 600;
341}
342
343.markrust-table .align-center {
344  text-align: center;
345}
346
347.markrust-table .align-right {
348  text-align: right;
349}
350
351/* Table of contents */
352.markrust-toc {
353  background-color: rgba(0, 0, 0, 0.03);
354  border-radius: 0.375rem;
355  padding: 1rem;
356  margin-bottom: 2rem;
357}
358
359.markrust-toc-header {
360  font-weight: 600;
361  margin-bottom: 0.5rem;
362}
363
364.markrust-toc ul {
365  list-style-type: none;
366  padding-left: 0;
367  margin-bottom: 0;
368}
369
370.markrust-toc ul ul {
371  padding-left: 1.5rem;
372}
373
374.markrust-toc li {
375  margin-bottom: 0.25rem;
376}
377
378.markrust-toc a {
379  text-decoration: none;
380  color: var(--text-color);
381}
382
383.markrust-toc a:hover {
384  color: var(--primary-color);
385  text-decoration: underline;
386}
387"#;
388
389// CSS for the Minimal theme
390const MINIMAL_THEME_CSS: &str = r#"
391/* Minimal theme */
392body {
393  font-family: var(--font-family);
394  color: var(--text-color);
395  background-color: var(--background-color);
396  line-height: 1.7;
397  margin: 0;
398  padding: 0;
399}
400
401.markrust-container {
402  max-width: var(--content-width);
403  margin: 0 auto;
404  padding: 2rem 1rem;
405}
406
407.markrust-content {
408  margin-top: 2rem;
409}
410
411h1, h2, h3, h4, h5, h6 {
412  margin-top: 2.5rem;
413  margin-bottom: 1rem;
414  font-weight: 400;
415  line-height: 1.25;
416  letter-spacing: -0.02em;
417}
418
419h1 {
420  font-size: 2.5rem;
421  border-bottom: 1px solid var(--border-color);
422  padding-bottom: 0.5rem;
423}
424
425h2 {
426  font-size: 2rem;
427}
428
429h3 {
430  font-size: 1.75rem;
431}
432
433h4 {
434  font-size: 1.5rem;
435}
436
437h5 {
438  font-size: 1.25rem;
439}
440
441h6 {
442  font-size: 1rem;
443}
444
445a {
446  color: var(--text-color);
447  text-decoration: underline;
448}
449
450a:hover {
451  color: var(--primary-color);
452}
453
454p {
455  margin-top: 0;
456  margin-bottom: 1.5rem;
457}
458
459ul, ol {
460  margin-top: 0;
461  margin-bottom: 1.5rem;
462  padding-left: 2rem;
463}
464
465li {
466  margin-bottom: 0.5rem;
467}
468
469blockquote {
470  margin: 1.5rem 0;
471  padding: 0 0 0 1.5rem;
472  border-left: 1px solid var(--text-color);
473  font-style: italic;
474}
475
476hr {
477  border: 0;
478  height: 1px;
479  background-color: var(--border-color);
480  margin: 3rem 0;
481}
482
483code {
484  font-family: var(--monospace-font);
485  font-size: 0.85em;
486  background-color: transparent;
487  padding: 0;
488  border-radius: 0;
489}
490
491pre {
492  margin: 1.5rem 0;
493  padding: 1rem;
494  overflow-x: auto;
495  background-color: rgb(245, 245, 245);
496  border-radius: 0;
497}
498
499pre code {
500  background: none;
501  padding: 0;
502  font-size: 0.85em;
503  color: inherit;
504}
505
506/* Table styles */
507.markrust-table {
508  border-collapse: collapse;
509  width: 100%;
510  margin: 1.5rem 0;
511}
512
513.markrust-table th,
514.markrust-table td {
515  border: 1px solid var(--border-color);
516  padding: 0.75rem 1rem;
517  text-align: left;
518}
519
520.markrust-table th {
521  font-weight: 400;
522  border-bottom: 2px solid var(--text-color);
523}
524
525/* Table of contents */
526.markrust-toc {
527  border: 1px solid var(--border-color);
528  padding: 1.5rem;
529  margin-bottom: 2.5rem;
530}
531
532.markrust-toc-header {
533  font-weight: 400;
534  margin-bottom: 1rem;
535  font-size: 1.25rem;
536}
537
538.markrust-toc ul {
539  list-style-type: none;
540  padding-left: 0;
541  margin-bottom: 0;
542}
543
544.markrust-toc ul ul {
545  padding-left: 1.5rem;
546}
547
548.markrust-toc li {
549  margin-bottom: 0.5rem;
550}
551
552.markrust-toc a {
553  text-decoration: none;
554  color: var(--text-color);
555}
556
557.markrust-toc a:hover {
558  text-decoration: underline;
559}
560"#;
561
562// CSS for the Dark theme
563const DARK_THEME_CSS: &str = r#"
564/* Dark theme */
565body {
566  font-family: var(--font-family);
567  color: var(--text-color);
568  background-color: var(--background-color);
569  line-height: 1.6;
570  margin: 0;
571  padding: 0;
572}
573
574.markrust-container {
575  max-width: var(--content-width);
576  margin: 0 auto;
577  padding: 2rem 1rem;
578}
579
580.markrust-content {
581  margin-top: 2rem;
582}
583
584h1, h2, h3, h4, h5, h6 {
585  margin-top: 2rem;
586  margin-bottom: 1rem;
587  font-weight: 600;
588  line-height: 1.25;
589  color: var(--primary-color);
590}
591
592h1 {
593  font-size: 2.25rem;
594  border-bottom: 1px solid var(--border-color);
595  padding-bottom: 0.5rem;
596}
597
598h2 {
599  font-size: 1.8rem;
600}
601
602h3 {
603  font-size: 1.5rem;
604}
605
606h4 {
607  font-size: 1.25rem;
608}
609
610h5 {
611  font-size: 1rem;
612}
613
614h6 {
615  font-size: 0.875rem;
616}
617
618a {
619  color: var(--primary-color);
620  text-decoration: none;
621}
622
623a:hover {
624  text-decoration: underline;
625}
626
627p {
628  margin-top: 0;
629  margin-bottom: 1rem;
630}
631
632ul, ol {
633  margin-top: 0;
634  margin-bottom: 1rem;
635  padding-left: 2rem;
636}
637
638li {
639  margin-bottom: 0.25rem;
640}
641
642blockquote {
643  margin: 1rem 0;
644  padding: 0.5rem 1rem;
645  border-left: 4px solid var(--primary-color);
646  background-color: rgba(255, 255, 255, 0.05);
647}
648
649hr {
650  border: 0;
651  height: 1px;
652  background-color: var(--border-color);
653  margin: 2rem 0;
654}
655
656code {
657  font-family: var(--monospace-font);
658  font-size: 0.9em;
659  background-color: rgba(255, 255, 255, 0.1);
660  padding: 0.2em 0.4em;
661  border-radius: 3px;
662}
663
664pre {
665  margin: 1rem 0;
666  padding: 1rem;
667  overflow-x: auto;
668  background-color: rgba(255, 255, 255, 0.1);
669  border-radius: 0.375rem;
670}
671
672pre code {
673  background: none;
674  padding: 0;
675  font-size: 0.9em;
676  color: inherit;
677}
678
679/* Code block with copy button */
680.markrust-code-block {
681  position: relative;
682}
683
684.markrust-copy-button {
685  position: absolute;
686  top: 0.5rem;
687  right: 0.5rem;
688  padding: 0.25rem;
689  background-color: rgba(255, 255, 255, 0.1);
690  border: none;
691  border-radius: 0.25rem;
692  color: var(--text-color);
693  cursor: pointer;
694  opacity: 0.6;
695  transition: opacity 0.2s;
696}
697
698.markrust-copy-button:hover {
699  opacity: 1;
700}
701
702/* Table styles */
703.markrust-table {
704  border-collapse: collapse;
705  width: 100%;
706  margin: 1rem 0;
707}
708
709.markrust-table th,
710.markrust-table td {
711  border: 1px solid var(--border-color);
712  padding: 0.5rem 0.75rem;
713  text-align: left;
714}
715
716.markrust-table th {
717  background-color: rgba(255, 255, 255, 0.05);
718  font-weight: 600;
719}
720
721.markrust-table .align-center {
722  text-align: center;
723}
724
725.markrust-table .align-right {
726  text-align: right;
727}
728
729/* Table of contents */
730.markrust-toc {
731  background-color: rgba(255, 255, 255, 0.05);
732  border-radius: 0.375rem;
733  padding: 1rem;
734  margin-bottom: 2rem;
735}
736
737.markrust-toc-header {
738  font-weight: 600;
739  margin-bottom: 0.5rem;
740  color: var(--primary-color);
741}
742
743.markrust-toc ul {
744  list-style-type: none;
745  padding-left: 0;
746  margin-bottom: 0;
747}
748
749.markrust-toc ul ul {
750  padding-left: 1.5rem;
751}
752
753.markrust-toc li {
754  margin-bottom: 0.25rem;
755}
756
757.markrust-toc a {
758  text-decoration: none;
759  color: var(--text-color);
760}
761
762.markrust-toc a:hover {
763  color: var(--primary-color);
764  text-decoration: underline;
765}
766"#;
767
768// CSS for the Light theme
769const LIGHT_THEME_CSS: &str = r#"
770/* Light theme */
771body {
772  font-family: var(--font-family);
773  color: var(--text-color);
774  background-color: var(--background-color);
775  line-height: 1.6;
776  margin: 0;
777  padding: 0;
778}
779
780.markrust-container {
781  max-width: var(--content-width);
782  margin: 0 auto;
783  padding: 2rem 1rem;
784}
785
786.markrust-content {
787  margin-top: 2rem;
788}
789
790h1, h2, h3, h4, h5, h6 {
791  margin-top: 2rem;
792  margin-bottom: 1rem;
793  font-weight: 600;
794  line-height: 1.25;
795  color: var(--primary-color);
796}
797
798h1 {
799  font-size: 2.25rem;
800  border-bottom: 1px solid var(--border-color);
801  padding-bottom: 0.5rem;
802}
803
804h2 {
805  font-size: 1.8rem;
806}
807
808h3 {
809  font-size: 1.5rem;
810}
811
812h4 {
813  font-size: 1.25rem;
814}
815
816h5 {
817  font-size: 1rem;
818}
819
820h6 {
821  font-size: 0.875rem;
822}
823
824a {
825  color: var(--primary-color);
826  text-decoration: none;
827}
828
829a:hover {
830  text-decoration: underline;
831}
832
833p {
834  margin-top: 0;
835  margin-bottom: 1rem;
836}
837
838ul, ol {
839  margin-top: 0;
840  margin-bottom: 1rem;
841  padding-left: 2rem;
842}
843
844li {
845  margin-bottom: 0.25rem;
846}
847
848blockquote {
849  margin: 1rem 0;
850  padding: 0.5rem 1rem;
851  border-left: 4px solid var(--primary-color);
852  background-color: var(--info-light-color);
853}
854
855hr {
856  border: 0;
857  height: 1px;
858  background-color: var(--border-color);
859  margin: 2rem 0;
860}
861
862code {
863  font-family: var(--monospace-font);
864  font-size: 0.9em;
865  background-color: rgba(0, 0, 0, 0.05);
866  padding: 0.2em 0.4em;
867  border-radius: 3px;
868}
869
870pre {
871  margin: 1rem 0;
872  padding: 1rem;
873  overflow-x: auto;
874  background-color: rgba(0, 0, 0, 0.05);
875  border-radius: 0.375rem;
876}
877
878pre code {
879  background: none;
880  padding: 0;
881  font-size: 0.9em;
882  color: inherit;
883}
884
885/* Code block with copy button */
886.markrust-code-block {
887  position: relative;
888}
889
890.markrust-copy-button {
891  position: absolute;
892  top: 0.5rem;
893  right: 0.5rem;
894  padding: 0.25rem;
895  background-color: rgba(255, 255, 255, 0.7);
896  border: none;
897  border-radius: 0.25rem;
898  color: var(--text-color);
899  cursor: pointer;
900  opacity: 0.6;
901  transition: opacity 0.2s;
902}
903
904.markrust-copy-button:hover {
905  opacity: 1;
906}
907
908/* Table styles */
909.markrust-table {
910  border-collapse: collapse;
911  width: 100%;
912  margin: 1rem 0;
913}
914
915.markrust-table th,
916.markrust-table td {
917  border: 1px solid var(--border-color);
918  padding: 0.5rem 0.75rem;
919  text-align: left;
920}
921
922.markrust-table th {
923  background-color: rgba(0, 0, 0, 0.03);
924  font-weight: 600;
925}
926
927.markrust-table .align-center {
928  text-align: center;
929}
930
931.markrust-table .align-right {
932  text-align: right;
933}
934
935/* Table of contents */
936.markrust-toc {
937  background-color: rgba(0, 0, 0, 0.02);
938  border-radius: 0.375rem;
939  padding: 1rem;
940  margin-bottom: 2rem;
941}
942
943.markrust-toc-header {
944  font-weight: 600;
945  margin-bottom: 0.5rem;
946  color: var(--primary-color);
947}
948
949.markrust-toc ul {
950  list-style-type: none;
951  padding-left: 0;
952  margin-bottom: 0;
953}
954
955.markrust-toc ul ul {
956  padding-left: 1.5rem;
957}
958
959.markrust-toc li {
960  margin-bottom: 0.25rem;
961}
962
963.markrust-toc a {
964  text-decoration: none;
965  color: var(--text-color);
966}
967
968.markrust-toc a:hover {
969  color: var(--primary-color);
970  text-decoration: underline;
971}
972"#;