diff --git a/Cargo.lock b/Cargo.lock
index 2f7bf7d..079561e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2551,9 +2551,9 @@ dependencies = [
[[package]]
name = "tetratto-core"
-version = "12.0.0"
+version = "12.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2446a581866039e72a9203a472b97390991cb2ef51c5c29dad3aa69dd41edc2d"
+checksum = "2c1b01499f7471ee6f05299c06ebb18760440452714c6f9a6c0c9e0cf9a663bd"
dependencies = [
"async-recursion",
"base16ct",
diff --git a/Cargo.toml b/Cargo.toml
index 0f1e264..de66da2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,7 +8,7 @@ license = "AGPL-3.0-or-later"
homepage = "https://tetratto.com"
[dependencies]
-tetratto-core = "12.0.0"
+tetratto-core = "12.0.1"
tetratto-shared = "12.0.5"
tokio = { version = "1.46.1", features = ["macros", "rt-multi-thread"] }
pathbufd = "0.1.4"
diff --git a/app/public/app.js b/app/public/app.js
index 987273b..c5305b3 100644
--- a/app/public/app.js
+++ b/app/public/app.js
@@ -144,6 +144,14 @@ globalThis.tab_editor = () => {
};
globalThis.tab_preview = async () => {
+ if (
+ !document
+ .getElementById("preview_tab_button")
+ .classList.contains("camo")
+ ) {
+ return;
+ }
+
// render
const res = await (
await fetch("/api/v1/render", {
@@ -151,6 +159,7 @@ globalThis.tab_preview = async () => {
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
content: globalThis.editor.getValue(),
+ metadata: globalThis.metadata_editor.getValue(),
}),
})
).text();
diff --git a/app/public/style.css b/app/public/style.css
index a61c225..6fd2537 100644
--- a/app/public/style.css
+++ b/app/public/style.css
@@ -111,7 +111,7 @@ article {
}
}
-.container:not(#preview_tab) {
+.container:not(#preview_tab):not(#tabs_group) {
margin: 10px auto 0;
width: 100%;
}
@@ -375,7 +375,7 @@ h6 {
h1 {
text-align: center;
- margin-bottom: 2rem;
+ margin: 2rem 0;
width: 100%;
}
diff --git a/app/templates_src/edit.lisp b/app/templates_src/edit.lisp
index 9de0787..1840dcd 100644
--- a/app/templates_src/edit.lisp
+++ b/app/templates_src/edit.lisp
@@ -21,13 +21,14 @@
("onclick" "tab_metadata()")
(text "Metadata")))
(div
- ("class" "card tab tabs")
+ ("class" "card tab tabs container")
+ ("id" "tabs_group")
(div
("id" "editor_tab")
("class" "tab fadein"))
(div
("id" "preview_tab")
- ("class" "tab fadein hidden container"))
+ ("class" "tab fadein hidden"))
(div
("id" "metadata_tab")
("class" "tab fadein hidden")))
diff --git a/app/templates_src/index.lisp b/app/templates_src/index.lisp
index f5ee66f..f928864 100644
--- a/app/templates_src/index.lisp
+++ b/app/templates_src/index.lisp
@@ -21,13 +21,14 @@
("onclick" "tab_metadata()")
(text "Metadata")))
(div
- ("class" "card tab tabs")
+ ("class" "card tab tabs container")
+ ("id" "tabs_group")
(div
("id" "editor_tab")
("class" "tab fadein"))
(div
("id" "preview_tab")
- ("class" "tab fadein hidden container"))
+ ("class" "tab fadein hidden"))
(div
("id" "metadata_tab")
("class" "tab fadein hidden")))
diff --git a/src/markdown.rs b/src/markdown.rs
index 62ba846..e209cd2 100644
--- a/src/markdown.rs
+++ b/src/markdown.rs
@@ -2,7 +2,9 @@ use std::collections::HashSet;
pub fn render_markdown(input: &str) -> String {
let html = tetratto_shared::markdown::render_markdown_dirty(&parse_text_color(
- &parse_highlight(&parse_underline(&parse_comment(&parse_image_size(input)))),
+ &parse_highlight(&parse_link(&parse_image(&parse_image_size(
+ &parse_underline(&parse_comment(input)),
+ )))),
));
let mut allowed_attributes = HashSet::new();
@@ -16,7 +18,13 @@ pub fn render_markdown(input: &str) -> String {
allowed_attributes.insert("src");
allowed_attributes.insert("style");
- tetratto_shared::markdown::clean_html(html, allowed_attributes)
+ tetratto_shared::markdown::clean_html(
+ html.replace("", ":temp_style"),
+ allowed_attributes,
+ )
+ .replace(":temp_style", "")
}
fn parse_text_color_line(output: &mut String, buffer: &mut String, line: &str) {
@@ -258,6 +266,11 @@ fn parse_comment_line(output: &mut String, _: &mut String, line: &str) {
return;
}
+ if line == "[..]" {
+ output.push_str(" ");
+ return;
+ }
+
output.push_str(line);
}
@@ -323,17 +336,7 @@ fn parse_image_size_line(output: &mut String, buffer: &mut String, line: &str) {
if in_size && in_size_rhs {
// end
output.push_str(&format!(
- "![{buffer}",
- if size_lhs == "auto" {
- size_lhs
- } else {
- format!("{size_lhs}px")
- },
- if size_rhs == "auto" {
- size_rhs
- } else {
- format!("{size_rhs}px")
- }
+ "![{buffer}"
));
size_lhs = String::new();
@@ -380,6 +383,174 @@ fn parse_image_size_line(output: &mut String, buffer: &mut String, line: &str) {
}
}
+fn parse_image_line(output: &mut String, buffer: &mut String, line: &str) {
+ let mut image_possible = false;
+ let mut in_image = false;
+ let mut in_alt = false;
+ let mut in_src = false;
+ let mut alt = String::new();
+
+ 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;
+ in_alt = true;
+ continue;
+ }
+
+ if in_image {
+ buffer.push(char);
+ } else {
+ output.push(char);
+ }
+ }
+ ']' => {
+ if in_alt {
+ in_alt = false;
+ in_src = true;
+ continue;
+ }
+
+ output.push(char);
+ }
+ '(' => {
+ if in_src {
+ continue;
+ }
+
+ if in_image {
+ buffer.push(char);
+ } else {
+ output.push(char);
+ }
+ }
+ ')' => {
+ if in_image {
+ // end
+ output.push_str(&format!(
+ "
"
+ ));
+
+ alt = String::new();
+ in_alt = false;
+ in_src = false;
+ in_image = false;
+ image_possible = false;
+
+ buffer.clear();
+ continue;
+ }
+
+ output.push(char);
+ }
+ '!' => {
+ // flush buffer
+ output.push_str(&buffer);
+ buffer.clear();
+
+ // ...
+ image_possible = true;
+ }
+ _ => {
+ if in_image {
+ if in_alt {
+ alt.push(char)
+ } else {
+ buffer.push(char);
+ }
+ } else {
+ output.push(char)
+ }
+ }
+ }
+ }
+}
+
+fn parse_link_line(output: &mut String, buffer: &mut String, line: &str) {
+ let mut in_link = false;
+ let mut in_text = false;
+ let mut in_src = false;
+ let mut text = String::new();
+
+ for (i, char) in line.chars().enumerate() {
+ match char {
+ '[' => {
+ // flush buffer
+ output.push_str(&buffer);
+ buffer.clear();
+
+ // scan for closing, otherwise quit
+ let haystack = &line[i..];
+
+ if !haystack.contains("]") {
+ output.push('[');
+ continue;
+ }
+
+ // ...
+ in_link = true;
+ in_text = true;
+ }
+ ']' => {
+ if in_text {
+ in_text = false;
+ in_src = true;
+ continue;
+ }
+
+ output.push(char);
+ }
+ '(' => {
+ if in_src {
+ continue;
+ }
+
+ if in_link {
+ buffer.push(char);
+ } else {
+ output.push(char);
+ }
+ }
+ ')' => {
+ if in_link {
+ // end
+ output.push_str(&format!(
+ "{text}"
+ ));
+
+ text = String::new();
+ in_text = false;
+ in_src = false;
+ in_link = false;
+
+ buffer.clear();
+ continue;
+ }
+
+ output.push(char);
+ }
+ _ => {
+ if in_link {
+ if in_text {
+ text.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) => {{
@@ -427,3 +598,11 @@ pub fn parse_comment(input: &str) -> String {
pub fn parse_image_size(input: &str) -> String {
parser_ignores_pre!(parse_image_size_line, input)
}
+
+pub fn parse_image(input: &str) -> String {
+ parser_ignores_pre!(parse_image_line, input)
+}
+
+pub fn parse_link(input: &str) -> String {
+ parser_ignores_pre!(parse_link_line, input)
+}
diff --git a/src/model.rs b/src/model.rs
index 8beb0fb..c743fc1 100644
--- a/src/model.rs
+++ b/src/model.rs
@@ -364,7 +364,7 @@ impl EntryMetadata {
"
",
- self.content_font.replace(" ", "+").replace(" ", "_"),
+ self.content_font.replace(" ", "+").replace("_", "+"),
));
}
@@ -416,6 +416,15 @@ impl EntryMetadata {
}
for (element, size) in &self.content_text_size {
+ if element == "*" {
+ output.push_str(&format!(
+ ".container, .container * {{ font-size: {}; }}\n",
+ EntryMetadata::css_escape(&size)
+ ));
+
+ continue;
+ }
+
output.push_str(&format!(
".container {} {{ font-size: {}; }}\n",
element,
@@ -426,7 +435,7 @@ impl EntryMetadata {
if !self.content_font.is_empty() {
output.push_str(&format!(
".container {{ font-family: \"{}\", system-ui; }}",
- self.content_font
+ self.content_font.replace("_", " ")
));
}
diff --git a/src/routes.rs b/src/routes.rs
index 6a42fb1..e4db1e5 100644
--- a/src/routes.rs
+++ b/src/routes.rs
@@ -208,10 +208,20 @@ async fn editor_request(
#[derive(Deserialize)]
struct RenderMarkdown {
content: String,
+ metadata: String,
}
async fn render_request(Json(req): Json) -> impl IntoResponse {
- crate::markdown::render_markdown(&req.content)
+ let metadata: EntryMetadata = match toml::from_str(&EntryMetadata::ini_to_toml(&req.metadata)) {
+ Ok(x) => x,
+ Err(e) => return Html(e.to_string()),
+ };
+
+ if let Err(e) = metadata.validate() {
+ return Html(e.to_string());
+ }
+
+ Html(crate::markdown::render_markdown(&req.content) + &metadata.css())
}
async fn exists_request(