1#![allow(clippy::needless_pass_by_value)]
2
3use crate::template::{SpeechDetails, render_speech, speech};
4use crate::{
5 error::{AppError, Result},
6 template::{SpeechCharacter, SpeechEmotion},
7};
8use autumnus::{formatter::Formatter as _, languages::Language};
9use makup::Rewriter;
10use maud::{Markup, PreEscaped, html};
11use std::collections::HashMap;
12
13fn speech_box(contents: &str, attrs: HashMap<&str, &str>) -> String {
14 let char = attrs.get("character").unwrap_or(&"deer");
15 let emotion = attrs.get("emotion").unwrap_or(&"neutral");
16
17 speech(
18 &match *char {
19 "you" => SpeechCharacter::You,
20 _ => SpeechCharacter::Deer,
21 },
22 &match *emotion {
23 "worried" => SpeechEmotion::Worried,
24 "shocked" => SpeechEmotion::Shocked,
25 "happy" => SpeechEmotion::Happy,
26 _ => SpeechEmotion::Neutral,
27 },
28 &html! { (PreEscaped(contents)) },
29 )
30 .into_string()
31}
32
33fn speech_box_nocss(contents: &str, attrs: HashMap<&str, &str>) -> String {
34 let char = attrs.get("character").unwrap_or(&"deer");
35 let emotion = attrs.get("emotion").unwrap_or(&"neutral");
36
37 let SpeechDetails { class: _, alt, src } = render_speech(
38 &match *char {
39 "you" => SpeechCharacter::You,
40 _ => SpeechCharacter::Deer,
41 },
42 &match *emotion {
43 "worried" => SpeechEmotion::Worried,
44 "shocked" => SpeechEmotion::Shocked,
45 "happy" => SpeechEmotion::Happy,
46 _ => SpeechEmotion::Neutral,
47 },
48 );
49
50 html! {
51 table {
52 tbody {
53 td {
54 img width="120" height="120" src=(src) alt=(alt);
55 }
56 td {
57 span { (PreEscaped(contents))}
58 }
59 }
60 }
61 }
62 .into_string()
63}
64
65#[expect(
66 clippy::expect_used,
67 clippy::unwrap_used,
68 reason = "Function is not allowed to error, annoyingly."
69)]
70pub(crate) fn maud_code_block(text: &str, lang: &str) -> Markup {
71 let formatter = autumnus::HtmlInlineBuilder::new()
72 .lang(Language::guess(lang, text))
73 .source(text)
74 .theme(Some(
75 autumnus::themes::get("catppuccin_mocha").expect("Built in!"),
76 ))
77 .build()
78 .expect("i hope this doesnt crash!");
79
80 let mut output = Vec::new();
81 formatter.format(&mut output).unwrap();
82
83 let output = String::from_utf8(output).unwrap();
84 html! {
85 .code-block {
86 .code-language { (lang) }
87 (PreEscaped(output))
88 }
89 }
90}
91
92fn code_block(contents: &str, attrs: HashMap<&str, &str>) -> String {
93 let lang = attrs
94 .get("lang")
95 .or_else(|| attrs.get("language"))
96 .unwrap_or(&"plain");
97
98 maud_code_block(contents, lang).into_string()
99}
100
101fn code_block_nocss(contents: &str, attrs: HashMap<&str, &str>) -> String {
102 let lang = attrs
103 .get("lang")
104 .or_else(|| attrs.get("language"))
105 .unwrap_or(&"plain");
106
107 html! {
108 figure {
109 pre {
110 code {
111 (contents)
112 }
113 }
114 figcaption {
115 "Code block in " (lang) " language"
116 }
117 }
118 }
119 .into_string()
120}
121
122pub fn render(markup: &str) -> Result<String> {
123 let mut rewriter = Rewriter::default();
124 rewriter.add_component("speech-box", speech_box);
125 rewriter.add_component("code-block", code_block);
126
127 let contents = rewriter
128 .rewrite(markup)
129 .map_err(|e| AppError::internal_server_error(e.to_string()))?;
130
131 Ok(contents)
132}
133
134pub fn no_css(markup: &str) -> Result<String> {
135 let mut rewriter = Rewriter::default();
136 rewriter.add_component("speech-box", speech_box_nocss);
137 rewriter.add_component("code-block", code_block_nocss);
138
139 let contents = rewriter
140 .rewrite(markup)
141 .map_err(|e| AppError::internal_server_error(e.to_string()))?;
142
143 Ok(contents)
144}