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",
|
"glob",
|
||||||
"nanoneo",
|
"nanoneo",
|
||||||
"pathbufd",
|
"pathbufd",
|
||||||
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_valid",
|
"serde_valid",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
|
|
243
src/markdown.rs
243
src/markdown.rs
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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(" ", "_"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue