From 6bbdb50ab8c49bac5f31020f175be85f723f421b Mon Sep 17 00:00:00 2001 From: lew Date: Tue, 28 Apr 2026 00:30:08 +0100 Subject: [PATCH] ui: update to defaults, less assumptions about layout --- .env.example | 12 ++++---- README.md | 62 ++++++++++++++++++++++-------------------- module.nix | 18 ++++++------ src/config.rs | 18 ++++++------ src/render.rs | 61 ++++++++++++++++++++++------------------- templates/default.css | 38 ++++++++++++++++---------- templates/default.html | 6 ++-- 7 files changed, 113 insertions(+), 102 deletions(-) diff --git a/.env.example b/.env.example index 670640c..0d05b58 100644 --- a/.env.example +++ b/.env.example @@ -69,23 +69,23 @@ # .entry-name, .entry-website, .entry-body # BOOK_STYLE= -# Text shown above the form. +# Text shown above the form. Empty by default. # BOOK_FORM_PROMPT=Thanks for visiting. Sign the guestbook! # Submit button text. # BOOK_BUTTON_TEXT=sign # Label for the name field. -# BOOK_LABEL_NAME=Your name: +# BOOK_LABEL_NAME=name # Label for the website field. -# BOOK_LABEL_WEBSITE=Your website (optional): +# BOOK_LABEL_WEBSITE=website (optional) # Label for the message field. -# BOOK_LABEL_MESSAGE=Your message: +# BOOK_LABEL_MESSAGE=message # Message textarea width in pixels. -# BOOK_TEXTAREA_WIDTH=400 +# BOOK_TEXTAREA_WIDTH=320 # Message textarea height in pixels. # BOOK_TEXTAREA_HEIGHT=150 @@ -103,7 +103,7 @@ # BOOK_ENABLE_DRAWINGS=false # Drawing canvas width in pixels. -# BOOK_CANVAS_WIDTH=400 +# BOOK_CANVAS_WIDTH=320 # Drawing canvas height in pixels. # BOOK_CANVAS_HEIGHT=200 diff --git a/README.md b/README.md index cee5552..ed5afe4 100644 --- a/README.md +++ b/README.md @@ -156,27 +156,28 @@ Running `guestbook` with no env vars will give you a working guestbook on `local # Custom CSS injected into a style tag. # Classes: .guestbook-form, .guestbook-prompt, .guestbook-label, .guestbook-input, -# .guestbook-textarea, .guestbook-button, .entry, .entry-header, .entry-date, -# .entry-name, .entry-website, .entry-body +# .guestbook-textarea, .guestbook-button, .entries, .entry-header, .entry-date, +# .entry-name, .entry-website, .entry-body, .entry-drawing-wrap, .entry-drawing, +# .entry-voice-note-wrap # BOOK_STYLE= -# Text shown above the form. +# Text shown above the form. Empty by default. # BOOK_FORM_PROMPT=Thanks for visiting. Sign the guestbook! # Submit button text. # BOOK_BUTTON_TEXT=sign # Label for the name field. -# BOOK_LABEL_NAME=Your name: +# BOOK_LABEL_NAME=name # Label for the website field. -# BOOK_LABEL_WEBSITE=Your website (optional): +# BOOK_LABEL_WEBSITE=website (optional) # Label for the message field. -# BOOK_LABEL_MESSAGE=Your message: +# BOOK_LABEL_MESSAGE=message # Message textarea width in pixels. -# BOOK_TEXTAREA_WIDTH=400 +# BOOK_TEXTAREA_WIDTH=320 # Message textarea height in pixels. # BOOK_TEXTAREA_HEIGHT=150 @@ -194,7 +195,7 @@ Running `guestbook` with no env vars will give you a working guestbook on `local # BOOK_ENABLE_DRAWINGS=false # Drawing canvas width in pixels. -# BOOK_CANVAS_WIDTH=400 +# BOOK_CANVAS_WIDTH=320 # Drawing canvas height in pixels. # BOOK_CANVAS_HEIGHT=200 @@ -234,7 +235,7 @@ services.guestbook = { websites.enable = true; drawing = { enable = false; - canvasWidth = 400; + canvasWidth = 320; canvasHeight = 200; }; voiceNote = { @@ -275,15 +276,15 @@ services.guestbook = { cssFile = null; templateFile = null; successTemplateFile = null; - greeting = "Thanks for visiting. Sign the guestbook!"; + greeting = ""; labels = { submit = "sign"; - name = "Your name:"; - website = "Your website (optional):"; - message = "Your message:"; + name = "name"; + website = "website (optional)"; + message = "message"; }; message = { - width = 400; + width = 320; height = 150; }; }; @@ -412,14 +413,12 @@ entered into the 'message' field.
-{{title}} -========= +

{{title}}

{{prompt}} {{form}} -entries -======= +

entries

{{entries}}
@@ -462,9 +461,12 @@ entries ```css /* Page container */ +body { + margin: 0; + line-height: 1.5; +} .page-container { max-width: 70ch; - margin: 0 auto; padding: 1rem; word-wrap: break-word; } @@ -472,37 +474,37 @@ entries /* Form */ .guestbook-prompt { display: block; margin-bottom: 1em; } .guestbook-form {} -.guestbook-label { display: block; } -.guestbook-input { display: block; margin-bottom: 0.5em; } -.guestbook-textarea { display: block; box-sizing: border-box; max-width: 100%; margin-bottom: 0.5em; } -.guestbook-button { display: block; margin-top: 1em; margin-bottom: 1.5em; } +.guestbook-label { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } +.guestbook-input { display: block; margin-bottom: 0.2em; } +.guestbook-textarea { display: block; box-sizing: border-box; max-width: 100%; margin-bottom: 0.2em; } +.guestbook-button { display: block; } /* Drawings */ .guestbook-canvas { border: 1px solid #000; cursor: crosshair; display: block; max-width: 100%; height: auto; } .guestbook-canvas-tools { display: block; } .guestbook-canvas-tools a { cursor: pointer; } -.guestbook-drawing-wrap { display: block; margin-bottom: 0.5em; } +.guestbook-drawing-wrap { display: block; } .guestbook-drawing-inline a { cursor: pointer; } .guestbook-drawing-content:empty { display: none; } -.guestbook-drawing-content { display: block; margin-bottom: 0.5em; } +.guestbook-drawing-content { display: block; } .guestbook-swatch { display: inline-block; width: 0.85em; height: 0.85em; border: 1px solid #000; cursor: pointer; vertical-align: middle; box-sizing: border-box; margin: 0 1px; } .guestbook-swatch.active { border: 1px solid #000; outline: 1px solid #000; } .guestbook-size-slider { width: 4em; vertical-align: middle; } .entry-drawing { max-width: 100%; } /* Voice notes */ -.guestbook-voice-wrap { display: block; margin-bottom: 0.5em; } +.guestbook-voice-wrap { display: block; } .guestbook-voice-record.recording { color: red; } .guestbook-voice-timer { font-variant-numeric: tabular-nums; } .guestbook-voice-playback:empty { display: none; } .guestbook-voice-playback { display: block; white-space: normal; } -audio { display: block; margin-bottom: 0.3em; height: 2em; } +audio { display: block; height: 2em; } /* Entries */ -.entry { margin: 0.5em 0; } -.entry-header { margin-bottom: 0.2em; } +.entries { margin: 0; line-height: 1; } +.entries dt:not(:first-child) { margin-top: 0.5rem; } .entry-date {} -.entry-name {} +.entry-name { font-weight: bold; } .entry-website {} .entry-body { white-space: pre-wrap; } ``` diff --git a/module.nix b/module.nix index 7d54bb7..5e24e52 100644 --- a/module.nix +++ b/module.nix @@ -98,7 +98,7 @@ in canvasWidth = mkOption { type = types.int; - default = 400; + default = 320; description = "Drawing canvas width in pixels."; }; @@ -251,7 +251,7 @@ in greeting = mkOption { type = types.str; - default = "Thanks for visiting. Sign the guestbook!"; + default = ""; description = "Text shown above the form."; }; @@ -264,27 +264,27 @@ in name = mkOption { type = types.str; - default = "Your name:"; - description = "Label for the name field."; + default = "name"; + description = "Label for the name field (used as both screen-reader label and placeholder)."; }; website = mkOption { type = types.str; - default = "Your website (optional):"; - description = "Label for the website field."; + default = "website (optional)"; + description = "Label for the website field (used as both screen-reader label and placeholder)."; }; message = mkOption { type = types.str; - default = "Your message:"; - description = "Label for the message field."; + default = "message"; + description = "Label for the message field (used as both screen-reader label and placeholder)."; }; }; message = { width = mkOption { type = types.int; - default = 400; + default = 320; description = "Message textarea width in pixels."; }; diff --git a/src/config.rs b/src/config.rs index 35752bc..f9b23c5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -137,7 +137,7 @@ impl Config { .map(|v| v != "false") .unwrap_or(false), canvas_width: env::var("BOOK_CANVAS_WIDTH") - .unwrap_or_else(|_| "400".into()) + .unwrap_or_else(|_| "320".into()) .parse() .map_err(|_| "BOOK_CANVAS_WIDTH must be a number")?, canvas_height: env::var("BOOK_CANVAS_HEIGHT") @@ -167,18 +167,16 @@ impl Config { }) .or_else(|| env::var("BOOK_STYLE").ok()) .unwrap_or_default(), - form_prompt: env::var("BOOK_FORM_PROMPT") - .unwrap_or_else(|_| "Thanks for visiting. Sign the guestbook!".into()), + form_prompt: env::var("BOOK_FORM_PROMPT").unwrap_or_default(), button_text: env::var("BOOK_BUTTON_TEXT") .unwrap_or_else(|_| "sign".into()), - label_name: env::var("BOOK_LABEL_NAME") - .unwrap_or_else(|_| "Your name:".into()), + label_name: env::var("BOOK_LABEL_NAME").unwrap_or_else(|_| "name".into()), label_website: env::var("BOOK_LABEL_WEBSITE") - .unwrap_or_else(|_| "Your website (optional):".into()), + .unwrap_or_else(|_| "website (optional)".into()), label_message: env::var("BOOK_LABEL_MESSAGE") - .unwrap_or_else(|_| "Your message:".into()), + .unwrap_or_else(|_| "message".into()), textarea_width: env::var("BOOK_TEXTAREA_WIDTH") - .unwrap_or_else(|_| "400".into()) + .unwrap_or_else(|_| "320".into()) .parse() .map_err(|_| "BOOK_TEXTAREA_WIDTH must be a number")?, textarea_height: env::var("BOOK_TEXTAREA_HEIGHT") @@ -318,9 +316,9 @@ mod tests { let config = Config::from_env().unwrap(); assert!(!config.enable_drawings); - assert_eq!(config.canvas_width, 400); + assert_eq!(config.canvas_width, 320); assert_eq!(config.canvas_height, 200); - assert_eq!(config.max_drawing_bytes(), 400 * 200 * 4); + assert_eq!(config.max_drawing_bytes(), 320 * 200 * 4); } #[test] diff --git a/src/render.rs b/src/render.rs index 6de00f8..8c00226 100644 --- a/src/render.rs +++ b/src/render.rs @@ -32,8 +32,8 @@ pub fn render_page(template: &str, config: &Config, entries: &[Entry], form_html pub fn render_form(config: &Config) -> String { let website_section = if config.enable_website_links { format!( - "\n\n\n", - config.label_website + "\n\n\n", + label = config.label_website ) } else { String::new() @@ -41,8 +41,8 @@ pub fn render_form(config: &Config) -> String { let captcha_section = if config.enable_captcha { format!( - "\n\n\n", - config.captcha_question + "\n\n\n", + label = config.captcha_question ) } else { String::new() @@ -186,10 +186,10 @@ pub fn render_form(config: &Config) -> String { format!( r#"
- + {website_section} - + {captcha_section} {drawing_section}{voice_note_section}
"#, @@ -256,13 +256,14 @@ fn escape_html(s: &str) -> String { } fn render_entries(entries: &[Entry], config: &Config) -> String { - let mut html = String::new(); - for (i, entry) in entries.iter().enumerate() { - if i > 0 { - html.push_str("
"); - } + if entries.is_empty() { + return String::new(); + } + let mut html = String::from("
"); + for entry in entries { html.push_str(&render_entry(entry, config)); } + html.push_str("
"); html } @@ -272,18 +273,15 @@ fn render_entry(entry: &Entry, config: &Config) -> String { } else { escape_html(&entry.meta.name) }; - let mut header = format!( - "
{} - {}", - &entry.meta.date[..10], name - ); - if config.enable_website_links && !entry.meta.website.is_empty() { - let website = escape_html(&entry.meta.website); - header.push_str(&format!( - " ({})", - website, website - )); - } - header.push_str("
"); + let name_html = if config.enable_website_links && !entry.meta.website.is_empty() { + format!( + "{}", + escape_html(&entry.meta.website), + name + ) + } else { + name + }; let body = if config.enable_html_injection { entry.body.clone() } else { @@ -291,7 +289,7 @@ fn render_entry(entry: &Entry, config: &Config) -> String { }; let drawing_html = if !entry.meta.drawing.is_empty() { format!( - "\"Drawing", + "
\"Drawing
", escape_html(&entry.meta.drawing), escape_html(&entry.meta.name) ) @@ -300,15 +298,21 @@ fn render_entry(entry: &Entry, config: &Config) -> String { }; let voice_note_html = if !entry.meta.voice_note.is_empty() { format!( - "", + "
", escape_html(&entry.meta.voice_note) ) } else { String::new() }; + let body_html = if body.is_empty() { + String::new() + } else { + format!("
{body}
") + }; + let date = &entry.meta.date[..10]; format!( - "
{header}{drawing_html}{voice_note_html}
{body}
", - id = escape_html(&entry.id) + "
{date} {name_html}
{body_html}{drawing_html}{voice_note_html}", + id = escape_html(&entry.id), ) } @@ -404,7 +408,8 @@ mod tests { assert!(html.contains("entry-header")); assert!(html.contains("entry-name")); assert!(html.contains("entry-body")); - assert!(html.contains("
")); + assert!(html.contains("id=\"test\"")); + assert!(html.contains("
")); } #[test] diff --git a/templates/default.css b/templates/default.css index 4a5542b..5e6004c 100644 --- a/templates/default.css +++ b/templates/default.css @@ -1,7 +1,10 @@ /* Page container */ +body { + margin: 0; + line-height: 1.5; +} .page-container { max-width: 70ch; - margin: 0 auto; padding: 1rem; word-wrap: break-word; } @@ -13,22 +16,28 @@ } .guestbook-form {} .guestbook-label { - display: block; + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; } .guestbook-input { display: block; - margin-bottom: 0.5em; + margin-bottom: 0.2em; } .guestbook-textarea { display: block; box-sizing: border-box; max-width: 100%; - margin-bottom: 0.5em; + margin-bottom: 0.2em; } .guestbook-button { display: block; - margin-top: 1em; - margin-bottom: 1.5em; } /* Drawings */ @@ -47,7 +56,6 @@ } .guestbook-drawing-wrap { display: block; - margin-bottom: 0.5em; } .guestbook-drawing-inline a { cursor: pointer; @@ -57,7 +65,6 @@ } .guestbook-drawing-content { display: block; - margin-bottom: 0.5em; } .guestbook-swatch { display: inline-block; @@ -84,7 +91,6 @@ /* Voice notes */ .guestbook-voice-wrap { display: block; - margin-bottom: 0.5em; } .guestbook-voice-record.recording { color: red; @@ -101,19 +107,21 @@ } audio { display: block; - margin-bottom: 0.3em; height: 2em; } /* Entries */ -.entry { - margin: 0.5em 0; +.entries { + margin: 0; + line-height: 1; } -.entry-header { - margin-bottom: 0.2em; +.entries dt:not(:first-child) { + margin-top: 0.5rem; } .entry-date {} -.entry-name {} +.entry-name { + font-weight: bold; +} .entry-website {} .entry-body { white-space: pre-wrap; diff --git a/templates/default.html b/templates/default.html index a517a98..870ddc1 100644 --- a/templates/default.html +++ b/templates/default.html @@ -30,14 +30,12 @@
-{{title}} -========= +

{{title}}

{{prompt}} {{form}} -entries -======= +

entries

{{entries}}