add: parse_underline, parse_comment, parse_image_size

This commit is contained in:
trisua 2025-07-22 14:48:55 -04:00
parent f8dac8f491
commit d8167aa06f
6 changed files with 275 additions and 4 deletions

1
Cargo.lock generated
View file

@ -92,6 +92,7 @@ dependencies = [
"glob", "glob",
"nanoneo", "nanoneo",
"pathbufd", "pathbufd",
"regex",
"serde", "serde",
"serde_json", "serde_json",
"serde_valid", "serde_valid",

View file

@ -30,3 +30,4 @@ glob = "0.3.2"
serde_json = "1.0.141" serde_json = "1.0.141"
toml = "0.9.2" toml = "0.9.2"
serde_valid = { version = "1.0.5", features = ["toml"] } serde_valid = { version = "1.0.5", features = ["toml"] }
regex = "1.11.1"

View file

@ -111,7 +111,7 @@ article {
} }
} }
.container { .container:not(#preview_tab) {
margin: 10px auto 0; margin: 10px auto 0;
width: 100%; width: 100%;
} }
@ -322,6 +322,10 @@ hr.margin {
margin: var(--pad-4) 0; margin: var(--pad-4) 0;
} }
span.img_sizer {
display: block;
}
p, p,
li, li,
span, span,

View file

@ -2,7 +2,7 @@ use std::collections::HashSet;
pub fn render_markdown(input: &str) -> String { pub fn render_markdown(input: &str) -> String {
let html = tetratto_shared::markdown::render_markdown_dirty(&parse_text_color( let html = tetratto_shared::markdown::render_markdown_dirty(&parse_text_color(
&parse_highlight(input), &parse_highlight(&parse_underline(&parse_comment(&parse_image_size(input)))),
)); ));
let mut allowed_attributes = HashSet::new(); let mut allowed_attributes = HashSet::new();
@ -151,6 +151,235 @@ fn parse_highlight_line(output: &mut String, buffer: &mut String, line: &str) {
} }
} }
fn parse_underline_line(output: &mut String, buffer: &mut String, line: &str) {
let mut open_1 = false;
let mut is_open = false;
let mut close_1 = false;
for char in line.chars() {
if open_1 && char != '~' {
is_open = false;
open_1 = false;
buffer.push('!');
}
if close_1 && char != '!' {
is_open = false;
close_1 = false;
buffer.push('~');
}
match char {
'~' => {
if open_1 {
open_1 = false;
is_open = true;
} else if is_open {
// open close
close_1 = true;
}
}
'!' => {
if close_1 {
// close
let mut s: Vec<&str> = buffer.split(";").collect();
let text = s.pop().unwrap_or(&"").trim();
let mut style = String::new();
for (i, mut x) in s.iter().enumerate() {
if i == 0 {
// color
if x == &"default" {
x = &"currentColor";
}
style.push_str(&format!("text-decoration-color: {x};"));
} else if i == 1 {
// style
if x == &"default" {
x = &"solid";
}
style.push_str(&format!("text-decoration-style: {x};"));
} else if i == 2 {
// line
if x == &"default" {
x = &"underline";
}
style.push_str(&format!("text-decoration-line: {x};"));
} else if i == 3 {
// thickness
if x == &"default" {
x = &"1px";
}
style.push_str(&format!("text-decoration-thickness: {x}px;"));
}
}
// defaults
if s.get(1).is_none() {
style.push_str(&format!("text-decoration-style: solid;"));
}
if s.get(2).is_none() {
style.push_str(&format!("text-decoration-line: underline;"));
}
if s.get(3).is_none() {
style.push_str(&format!("text-decoration-thickness: 1px;"));
}
// ...
output.push_str(&format!("<span style=\"{style}\">{text}</span>"));
buffer.clear();
open_1 = false;
is_open = false;
close_1 = false;
continue;
}
// open
open_1 = true;
// flush buffer
output.push_str(&buffer);
buffer.clear();
}
_ => buffer.push(char),
}
}
}
fn parse_comment_line(output: &mut String, _: &mut String, line: &str) {
if line.contains("]:") && line.starts_with("[") {
return;
}
output.push_str(line);
}
fn parse_image_size_line(output: &mut String, buffer: &mut String, line: &str) {
let mut image_possible = false;
let mut in_image = false;
let mut in_size = false;
let mut in_size_rhs = false;
let mut size_lhs = String::new();
let mut size_rhs = String::new();
if !line.contains("{") {
output.push_str(line);
return;
}
for char in line.chars() {
if image_possible && char != '[' {
image_possible = false;
output.push('!');
}
match char {
'[' => {
if image_possible {
in_image = true;
image_possible = false;
continue;
}
if in_image {
buffer.push(char);
} else {
output.push(char);
}
}
'{' => {
if in_image {
in_size = true;
continue;
}
if in_image {
buffer.push(char);
} else {
output.push(char);
}
}
':' => {
if in_size {
in_size_rhs = true;
continue;
}
if in_image {
buffer.push(char);
} else {
output.push(char);
}
}
'}' => {
if in_size && in_size_rhs {
// end
output.push_str(&format!(
"<span style=\"width: {}; height: {}\" class=\"img_sizer\">![{buffer}</span>",
if size_lhs == "auto" {
size_lhs
} else {
format!("{size_lhs}px")
},
if size_rhs == "auto" {
size_rhs
} else {
format!("{size_rhs}px")
}
));
size_lhs = String::new();
size_rhs = String::new();
in_image = false;
in_size = false;
in_size_rhs = false;
image_possible = false;
buffer.clear();
continue;
}
if in_image {
buffer.push(char);
} else {
output.push(char);
}
}
'!' => {
// flush buffer
output.push_str(&buffer);
buffer.clear();
// ...
image_possible = true
}
_ => {
if in_image {
if in_size {
if in_size_rhs {
size_rhs.push(char);
} else {
size_lhs.push(char);
}
} else {
buffer.push(char);
}
} else {
output.push(char)
}
}
}
}
}
/// Helper macro to quickly allow parsers to ignore fenced code blocks. /// Helper macro to quickly allow parsers to ignore fenced code blocks.
macro_rules! parser_ignores_pre { macro_rules! parser_ignores_pre {
($body:ident, $input:ident) => {{ ($body:ident, $input:ident) => {{
@ -186,3 +415,15 @@ pub fn parse_text_color(input: &str) -> String {
pub fn parse_highlight(input: &str) -> String { pub fn parse_highlight(input: &str) -> String {
parser_ignores_pre!(parse_highlight_line, input) parser_ignores_pre!(parse_highlight_line, input)
} }
pub fn parse_underline(input: &str) -> String {
parser_ignores_pre!(parse_underline_line, input)
}
pub fn parse_comment(input: &str) -> String {
parser_ignores_pre!(parse_comment_line, input)
}
pub fn parse_image_size(input: &str) -> String {
parser_ignores_pre!(parse_image_size_line, input)
}

View file

@ -363,8 +363,8 @@ impl EntryMetadata {
output.push_str(&format!( output.push_str(&format!(
"<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\"> "<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">
<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin> <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>
<link href=\"https://fonts.googleapis.com/css2?family={}:wght@100..900&display=swap\" rel=\"stylesheet\">", <link href=\"https://fonts.googleapis.com/css2?family={}&display=swap\" rel=\"stylesheet\">",
self.content_font.replace(" ", "+"), self.content_font.replace(" ", "+").replace(" ", "_"),
)); ));
} }

View file

@ -23,6 +23,8 @@ use tetratto_shared::{
unix_epoch_timestamp, unix_epoch_timestamp,
}; };
pub const NAME_REGEX: &str = r"[^\w_\-\.,!]+";
pub fn routes() -> Router { pub fn routes() -> Router {
Router::new() Router::new()
.nest_service( .nest_service(
@ -269,6 +271,16 @@ async fn create_request(
return Json(Error::DataTooLong("content".to_string()).into()); return Json(Error::DataTooLong("content".to_string()).into());
} }
// check slug
let regex = regex::RegexBuilder::new(NAME_REGEX)
.multi_line(true)
.build()
.unwrap();
if regex.captures(&req.slug).is_some() {
return Json(Error::MiscError("This slug contains invalid characters".to_string()).into());
}
// check metadata // check metadata
let metadata: EntryMetadata = match toml::from_str(&EntryMetadata::ini_to_toml(&req.metadata)) { let metadata: EntryMetadata = match toml::from_str(&EntryMetadata::ini_to_toml(&req.metadata)) {
Ok(x) => x, Ok(x) => x,
@ -428,6 +440,18 @@ async fn edit_request(
return Json(Error::DataTooLong("slug".to_string()).into()); return Json(Error::DataTooLong("slug".to_string()).into());
} }
// check slug
let regex = regex::RegexBuilder::new(NAME_REGEX)
.multi_line(true)
.build()
.unwrap();
if regex.captures(&new_slug).is_some() {
return Json(
Error::MiscError("This slug contains invalid characters".to_string()).into(),
);
}
// check for existing // check for existing
if data if data
.query(&SimplifiedQuery { .query(&SimplifiedQuery {