add: parse_underline, parse_comment, parse_image_size
This commit is contained in:
parent
f8dac8f491
commit
d8167aa06f
6 changed files with 275 additions and 4 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -92,6 +92,7 @@ dependencies = [
|
|||
"glob",
|
||||
"nanoneo",
|
||||
"pathbufd",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_valid",
|
||||
|
|
|
@ -30,3 +30,4 @@ glob = "0.3.2"
|
|||
serde_json = "1.0.141"
|
||||
toml = "0.9.2"
|
||||
serde_valid = { version = "1.0.5", features = ["toml"] }
|
||||
regex = "1.11.1"
|
||||
|
|
|
@ -111,7 +111,7 @@ article {
|
|||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
.container:not(#preview_tab) {
|
||||
margin: 10px auto 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -322,6 +322,10 @@ hr.margin {
|
|||
margin: var(--pad-4) 0;
|
||||
}
|
||||
|
||||
span.img_sizer {
|
||||
display: block;
|
||||
}
|
||||
|
||||
p,
|
||||
li,
|
||||
span,
|
||||
|
|
243
src/markdown.rs
243
src/markdown.rs
|
@ -2,7 +2,7 @@ use std::collections::HashSet;
|
|||
|
||||
pub fn render_markdown(input: &str) -> String {
|
||||
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();
|
||||
|
@ -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.
|
||||
macro_rules! parser_ignores_pre {
|
||||
($body:ident, $input:ident) => {{
|
||||
|
@ -186,3 +415,15 @@ pub fn parse_text_color(input: &str) -> String {
|
|||
pub fn parse_highlight(input: &str) -> String {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -363,8 +363,8 @@ impl EntryMetadata {
|
|||
output.push_str(&format!(
|
||||
"<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">
|
||||
<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>
|
||||
<link href=\"https://fonts.googleapis.com/css2?family={}:wght@100..900&display=swap\" rel=\"stylesheet\">",
|
||||
self.content_font.replace(" ", "+"),
|
||||
<link href=\"https://fonts.googleapis.com/css2?family={}&display=swap\" rel=\"stylesheet\">",
|
||||
self.content_font.replace(" ", "+").replace(" ", "_"),
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ use tetratto_shared::{
|
|||
unix_epoch_timestamp,
|
||||
};
|
||||
|
||||
pub const NAME_REGEX: &str = r"[^\w_\-\.,!]+";
|
||||
|
||||
pub fn routes() -> Router {
|
||||
Router::new()
|
||||
.nest_service(
|
||||
|
@ -269,6 +271,16 @@ async fn create_request(
|
|||
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
|
||||
let metadata: EntryMetadata = match toml::from_str(&EntryMetadata::ini_to_toml(&req.metadata)) {
|
||||
Ok(x) => x,
|
||||
|
@ -428,6 +440,18 @@ async fn edit_request(
|
|||
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
|
||||
if data
|
||||
.query(&SimplifiedQuery {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue