2025-07-21 22:28:43 -04:00
|
|
|
use std::collections::HashSet;
|
|
|
|
|
2025-07-20 18:29:43 -04:00
|
|
|
pub fn render_markdown(input: &str) -> String {
|
2025-07-21 22:28:43 -04:00
|
|
|
let html = tetratto_shared::markdown::render_markdown_dirty(&parse_text_color(
|
|
|
|
&parse_highlight(input),
|
|
|
|
));
|
|
|
|
|
|
|
|
let mut allowed_attributes = HashSet::new();
|
|
|
|
allowed_attributes.insert("id");
|
|
|
|
allowed_attributes.insert("class");
|
|
|
|
allowed_attributes.insert("ref");
|
|
|
|
allowed_attributes.insert("aria-label");
|
|
|
|
allowed_attributes.insert("lang");
|
|
|
|
allowed_attributes.insert("title");
|
|
|
|
allowed_attributes.insert("align");
|
|
|
|
allowed_attributes.insert("src");
|
|
|
|
allowed_attributes.insert("style");
|
|
|
|
|
|
|
|
tetratto_shared::markdown::clean_html(html, allowed_attributes)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_text_color_line(output: &mut String, buffer: &mut String, line: &str) {
|
|
|
|
let mut in_color_buffer = false;
|
|
|
|
let mut in_main_buffer = false;
|
|
|
|
let mut color_buffer = String::new();
|
|
|
|
let mut close_1 = false;
|
|
|
|
|
|
|
|
for char in line.chars() {
|
|
|
|
if close_1 && char != '%' {
|
|
|
|
// we expected to see another percentage to close the main buffer,
|
|
|
|
// not getting that means this wasn't meant to be a color
|
|
|
|
buffer.push('%');
|
|
|
|
in_main_buffer = false;
|
|
|
|
close_1 = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
match char {
|
|
|
|
'%' => {
|
|
|
|
if in_color_buffer {
|
|
|
|
in_color_buffer = false;
|
|
|
|
in_main_buffer = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if in_main_buffer {
|
|
|
|
// ending
|
|
|
|
if !close_1 {
|
|
|
|
close_1 = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// by this point, we have: !
|
|
|
|
// %color_buffer%main_buffer%%
|
|
|
|
output.push_str(&format!(
|
|
|
|
"<span style=\"color: {color_buffer}\">{buffer}</span>\n"
|
|
|
|
));
|
|
|
|
|
|
|
|
color_buffer.clear();
|
|
|
|
buffer.clear();
|
|
|
|
|
|
|
|
// ...
|
|
|
|
in_main_buffer = false;
|
|
|
|
close_1 = false;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// start
|
|
|
|
// flush buffer
|
|
|
|
output.push_str(&buffer);
|
|
|
|
buffer.clear();
|
|
|
|
|
|
|
|
// toggle open
|
|
|
|
in_color_buffer = true;
|
|
|
|
}
|
|
|
|
' ' => {
|
|
|
|
if in_color_buffer == true {
|
|
|
|
buffer.push_str(&color_buffer);
|
|
|
|
color_buffer.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer.push(char);
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
if in_color_buffer {
|
|
|
|
color_buffer.push(char)
|
|
|
|
} else {
|
|
|
|
buffer.push(char)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_highlight_line(output: &mut String, buffer: &mut String, line: &str) {
|
|
|
|
let mut open_1 = false;
|
|
|
|
let mut open_2 = false;
|
|
|
|
let mut close_1 = false;
|
|
|
|
let mut is_open = false;
|
|
|
|
|
|
|
|
for char in line.chars() {
|
|
|
|
if close_1 && char != '=' {
|
|
|
|
buffer.push('=');
|
|
|
|
close_1 = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
match char {
|
|
|
|
'=' => {
|
|
|
|
if !is_open {
|
|
|
|
// flush buffer
|
|
|
|
output.push_str(&buffer);
|
|
|
|
buffer.clear();
|
|
|
|
|
|
|
|
// toggle open
|
|
|
|
open_1 = true;
|
|
|
|
is_open = true;
|
|
|
|
} else {
|
|
|
|
if open_1 {
|
|
|
|
// this is the second open we've recieved
|
|
|
|
open_2 = true;
|
|
|
|
open_1 = false;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if close_1 {
|
|
|
|
// this is the second close we've received
|
|
|
|
output.push_str(&format!("<mark>{buffer}</mark>\n"));
|
|
|
|
buffer.clear();
|
|
|
|
open_1 = false;
|
|
|
|
open_2 = false;
|
|
|
|
close_1 = false;
|
|
|
|
is_open = false;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
close_1 = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
if open_1 {
|
|
|
|
open_1 = false;
|
|
|
|
buffer.push('=');
|
|
|
|
}
|
|
|
|
|
|
|
|
if open_2 && is_open {
|
|
|
|
open_2 = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer.push(char);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Helper macro to quickly allow parsers to ignore fenced code blocks.
|
|
|
|
macro_rules! parser_ignores_pre {
|
|
|
|
($body:ident, $input:ident) => {{
|
|
|
|
let mut in_pre_block = false;
|
|
|
|
let mut output = String::new();
|
|
|
|
let mut buffer = String::new();
|
|
|
|
|
|
|
|
for line in $input.split("\n") {
|
|
|
|
if line.starts_with("```") {
|
|
|
|
in_pre_block = !in_pre_block;
|
|
|
|
output.push_str(&format!("{line}\n"));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if in_pre_block {
|
|
|
|
output.push_str(&format!("{line}\n"));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$body(&mut output, &mut buffer, line);
|
|
|
|
output.push_str(&format!("{buffer}\n"));
|
|
|
|
buffer.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
output
|
|
|
|
}};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_text_color(input: &str) -> String {
|
|
|
|
parser_ignores_pre!(parse_text_color_line, input)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_highlight(input: &str) -> String {
|
|
|
|
parser_ignores_pre!(parse_highlight_line, input)
|
2025-07-20 18:29:43 -04:00
|
|
|
}
|