feat: initial draft of drawings, still missing some features from my other site
This commit is contained in:
parent
2044616aeb
commit
9521fc4aef
7 changed files with 50 additions and 6 deletions
|
|
@ -59,7 +59,7 @@
|
||||||
|
|
||||||
# Custom CSS injected into a style tag.
|
# Custom CSS injected into a style tag.
|
||||||
# Classes: .guestbook-form, .guestbook-prompt, .guestbook-label, .guestbook-input,
|
# Classes: .guestbook-form, .guestbook-prompt, .guestbook-label, .guestbook-input,
|
||||||
# .guestbook-textarea, .guestbook-button, .entry-header, .entry-name,
|
# .guestbook-textarea, .guestbook-button, .entry-header, .entry-date, .entry-name,
|
||||||
# .entry-website, .entry-body, .entry-separator
|
# .entry-website, .entry-body, .entry-separator
|
||||||
# BOOK_STYLE=
|
# BOOK_STYLE=
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ Running `guestbook` with no env vars will give you a working guestbook on `local
|
||||||
|
|
||||||
# Custom CSS injected into a style tag.
|
# Custom CSS injected into a style tag.
|
||||||
# Classes: .guestbook-form, .guestbook-prompt, .guestbook-label, .guestbook-input,
|
# Classes: .guestbook-form, .guestbook-prompt, .guestbook-label, .guestbook-input,
|
||||||
# .guestbook-textarea, .guestbook-button, .entry-header, .entry-name,
|
# .guestbook-textarea, .guestbook-button, .entry-header, .entry-date, .entry-name,
|
||||||
# .entry-website, .entry-body, .entry-separator
|
# .entry-website, .entry-body, .entry-separator
|
||||||
# BOOK_STYLE=
|
# BOOK_STYLE=
|
||||||
|
|
||||||
|
|
@ -360,6 +360,7 @@ entries
|
||||||
|
|
||||||
/* Entries */
|
/* Entries */
|
||||||
.entry-header {}
|
.entry-header {}
|
||||||
|
.entry-date {}
|
||||||
.entry-name {}
|
.entry-name {}
|
||||||
.entry-website {}
|
.entry-website {}
|
||||||
.entry-body {}
|
.entry-body {}
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,7 @@ in
|
||||||
css = mkOption {
|
css = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
default = "";
|
default = "";
|
||||||
description = "Custom CSS injected into a style tag. Use class names: .guestbook-form, .guestbook-prompt, .guestbook-label, .guestbook-input, .guestbook-textarea, .guestbook-button, .guestbook-canvas, .entry-header, .entry-name, .entry-website, .entry-body, .entry-drawing, .entry-separator";
|
description = "Custom CSS injected into a style tag. Use class names: .guestbook-form, .guestbook-prompt, .guestbook-label, .guestbook-input, .guestbook-textarea, .guestbook-button, .guestbook-canvas, .entry-header, .entry-date, .entry-name, .entry-website, .entry-body, .entry-drawing, .entry-separator";
|
||||||
};
|
};
|
||||||
|
|
||||||
cssFile = mkOption {
|
cssFile = mkOption {
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ fn render_entry(entry: &Entry, config: &Config) -> String {
|
||||||
escape_html(&entry.meta.name)
|
escape_html(&entry.meta.name)
|
||||||
};
|
};
|
||||||
let mut header = format!(
|
let mut header = format!(
|
||||||
"<span class=\"entry-header\">{} - <span class=\"entry-name\">{}</span>",
|
"<span class=\"entry-header\"><span class=\"entry-date\">{}</span> - <span class=\"entry-name\">{}</span>",
|
||||||
&entry.meta.date[..10], name
|
&entry.meta.date[..10], name
|
||||||
);
|
);
|
||||||
if config.enable_website_links && !entry.meta.website.is_empty() {
|
if config.enable_website_links && !entry.meta.website.is_empty() {
|
||||||
|
|
@ -152,7 +152,7 @@ fn render_entry(entry: &Entry, config: &Config) -> String {
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
format!(
|
format!(
|
||||||
"\n{header}\n\n<span class=\"entry-body\">{body}</span>{drawing_html}\n\n<span class=\"entry-separator\">{}</span>\n",
|
"\n{header}\n{drawing_html}\n<span class=\"entry-body\">{body}</span>\n\n<span class=\"entry-separator\">{}</span>\n",
|
||||||
config.separator
|
config.separator
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use crate::entries::{self, Entry, Status};
|
||||||
|
|
||||||
/// Send a notification to Telegram about a new entry.
|
/// Send a notification to Telegram about a new entry.
|
||||||
async fn notify(bot: &Bot, chat_id: ChatId, entry: &Entry) {
|
async fn notify(bot: &Bot, chat_id: ChatId, entry: &Entry) {
|
||||||
let short_id = entry.id.split('-').last().unwrap_or(&entry.id);
|
let short_id = entry.id.split('_').last().unwrap_or(&entry.id);
|
||||||
let text = format!(
|
let text = format!(
|
||||||
"New guestbook entry:\n\nName: {}\nWebsite: {}\n\n{}\n\n/allow_{}\n/deny_{}",
|
"New guestbook entry:\n\nName: {}\nWebsite: {}\n\n{}\n\n/allow_{}\n/deny_{}",
|
||||||
entry.meta.name, entry.meta.website, entry.body, short_id, short_id
|
entry.meta.name, entry.meta.website, entry.body, short_id, short_id
|
||||||
|
|
|
||||||
42
src/web.rs
42
src/web.rs
|
|
@ -742,4 +742,46 @@ mod tests {
|
||||||
let content = std::fs::read_to_string(entries[0].as_ref().unwrap().path()).unwrap();
|
let content = std::fs::read_to_string(entries[0].as_ref().unwrap().path()).unwrap();
|
||||||
assert!(content.contains("drawing = \"\""));
|
assert!(content.contains("drawing = \"\""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_drawing_full_roundtrip() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let mut config = test_config(dir.path());
|
||||||
|
config.enable_drawings = true;
|
||||||
|
config.canvas_width = 400;
|
||||||
|
config.canvas_height = 200;
|
||||||
|
let (app, _rx) = test_app(config);
|
||||||
|
|
||||||
|
// Submit with a drawing
|
||||||
|
let png = fake_png(400, 200);
|
||||||
|
let drawing_data = base64::engine::general_purpose::STANDARD.encode(&png);
|
||||||
|
let data_url = format!("data:image/png;base64,{drawing_data}");
|
||||||
|
let body = format!(
|
||||||
|
"name=alice&message=hello&drawing={}",
|
||||||
|
urlencoding::encode(&data_url)
|
||||||
|
);
|
||||||
|
post_form(&app, &body).await;
|
||||||
|
|
||||||
|
// Approve the entry
|
||||||
|
let entries_dir = dir.path().join("entries");
|
||||||
|
let entry_file = std::fs::read_dir(&entries_dir).unwrap().next().unwrap().unwrap();
|
||||||
|
let content = std::fs::read_to_string(entry_file.path()).unwrap();
|
||||||
|
let id = entry_file.path().file_stem().unwrap().to_str().unwrap().to_string();
|
||||||
|
let mut entry = entries::Entry::parse(&id, &content).unwrap();
|
||||||
|
entry.meta.status = entries::Status::Approved;
|
||||||
|
std::fs::write(entry_file.path(), entry.to_file_contents()).unwrap();
|
||||||
|
|
||||||
|
let drawing_filename = entry.meta.drawing.clone();
|
||||||
|
assert!(!drawing_filename.is_empty(), "entry should have a drawing filename");
|
||||||
|
|
||||||
|
// Verify index shows the drawing
|
||||||
|
let html = get_index(&app).await;
|
||||||
|
assert!(html.contains("entry-drawing"));
|
||||||
|
assert!(html.contains(&format!("/drawings/{drawing_filename}")));
|
||||||
|
|
||||||
|
// Verify the drawing file is served
|
||||||
|
let (status, bytes) = get_path(&app, &format!("/drawings/{drawing_filename}")).await;
|
||||||
|
assert_eq!(status, StatusCode::OK);
|
||||||
|
assert_eq!(bytes, png);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
|
|
||||||
/* Entries */
|
/* Entries */
|
||||||
.entry-header {}
|
.entry-header {}
|
||||||
|
.entry-date {}
|
||||||
.entry-name {}
|
.entry-name {}
|
||||||
.entry-website {}
|
.entry-website {}
|
||||||
.entry-body {}
|
.entry-body {}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue