styles: semantic html rather than all being pre-wrapped to generalise for others

This commit is contained in:
Lewis Wynne 2026-04-10 19:25:53 +01:00
parent 9f0e3aae6d
commit a7c74241a0
7 changed files with 52 additions and 38 deletions

View file

@ -51,16 +51,13 @@
# Maximum length for website URLs. 0 for unlimited.
# BOOK_MAX_WEBSITE_LENGTH=0
# Separator between guestbook entries.
# BOOK_SEPARATOR=------------------------------------------------------------
# Path to a CSS file. Takes precedence over BOOK_STYLE.
# BOOK_STYLE_FILE=./templates/default.css
# Custom CSS injected into a style tag.
# Classes: .guestbook-form, .guestbook-prompt, .guestbook-label, .guestbook-input,
# .guestbook-textarea, .guestbook-button, .entry-header, .entry-date, .entry-name,
# .entry-website, .entry-body, .entry-separator
# .guestbook-textarea, .guestbook-button, .entry, .entry-header, .entry-date,
# .entry-name, .entry-website, .entry-body
# BOOK_STYLE=
# Text shown above the form.

View file

@ -208,7 +208,7 @@ in
css = mkOption {
type = types.str;
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-date, .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, .entry-header, .entry-date, .entry-name, .entry-website, .entry-body, .entry-drawing";
};
cssFile = mkOption {
@ -229,12 +229,6 @@ in
description = "Custom success page template with {{title}} and {{style}} placeholders. Uses built-in default if null.";
};
separator = mkOption {
type = types.str;
default = "------------------------------------------------------------";
description = "Separator between guestbook entries.";
};
greeting = mkOption {
type = types.str;
default = "Thanks for visiting. Sign the guestbook!";
@ -308,7 +302,6 @@ in
BOOK_MAX_NAME_LENGTH = toString cfg.limits.name;
BOOK_MAX_MESSAGE_LENGTH = toString cfg.limits.message;
BOOK_MAX_WEBSITE_LENGTH = toString cfg.limits.website;
BOOK_SEPARATOR = cfg.styles.separator;
BOOK_STYLE = cfg.styles.css;
BOOK_FORM_PROMPT = cfg.styles.greeting;
BOOK_BUTTON_TEXT = cfg.styles.labels.submit;

View file

@ -30,7 +30,6 @@ pub struct Config {
pub voice_note_max_duration: u32,
pub template: Option<String>,
pub success_template: Option<String>,
pub separator: String,
pub style: String,
pub form_prompt: String,
pub button_text: String,
@ -131,8 +130,6 @@ impl Config {
.unwrap_or_else(|_| "20".into())
.parse()
.map_err(|_| "BOOK_VOICE_NOTE_MAX_DURATION must be a number")?,
separator: env::var("BOOK_SEPARATOR")
.unwrap_or_else(|_| "------------------------------------------------------------".into()),
template: env::var("BOOK_TEMPLATE").ok().map(|path| {
std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("failed to read template {path}: {e}"))

View file

@ -238,9 +238,8 @@ pub fn render_error_page(config: &Config, error: &str) -> String {
</head>
<body>
<div class="page-container">
{error}
<a href="/">&#8592; back</a>
<p>{error}</p>
<p><a href="/">&#8592; back</a></p>
</div>
</body>
</html>"#,
@ -271,7 +270,7 @@ fn render_entry(entry: &Entry, config: &Config) -> String {
escape_html(&entry.meta.name)
};
let mut header = format!(
"<span class=\"entry-header\"><span class=\"entry-date\">{}</span> - <span class=\"entry-name\">{}</span>",
"<header class=\"entry-header\"><span class=\"entry-date\">{}</span> - <span class=\"entry-name\">{}</span>",
&entry.meta.date[..10], name
);
if config.enable_website_links && !entry.meta.website.is_empty() {
@ -281,7 +280,7 @@ fn render_entry(entry: &Entry, config: &Config) -> String {
website, website
));
}
header.push_str("</span>");
header.push_str("</header>");
let body = if config.enable_html_injection {
entry.body.clone()
} else {
@ -305,8 +304,7 @@ fn render_entry(entry: &Entry, config: &Config) -> String {
String::new()
};
format!(
"\n{header}\n{drawing_html}{voice_note_html}\n<span class=\"entry-body\">{body}</span>\n\n<span class=\"entry-separator\">{}</span>\n",
config.separator
"<article class=\"entry\">{header}{drawing_html}{voice_note_html}<div class=\"entry-body\">{body}</div></article>"
)
}
@ -345,7 +343,6 @@ mod tests {
voice_note_max_duration: 20,
template: None,
success_template: None,
separator: "---".into(),
style: String::new(),
form_prompt: "Thanks for visiting. Sign the guestbook!".into(),
button_text: "sign".into(),
@ -400,7 +397,7 @@ mod tests {
assert!(html.contains("entry-header"));
assert!(html.contains("entry-name"));
assert!(html.contains("entry-body"));
assert!(html.contains("entry-separator"));
assert!(html.contains("<article class=\"entry\">"));
}
#[test]

View file

@ -368,7 +368,6 @@ mod tests {
voice_note_max_duration: 20,
template: None,
success_template: None,
separator: "---".into(),
style: String::new(),
form_prompt: "Thanks for visiting. Sign the guestbook!".into(),
button_text: "sign".into(),

View file

@ -3,22 +3,32 @@
max-width: 70ch;
margin: 0 auto;
padding: 1rem;
white-space: pre-wrap;
word-wrap: break-word;
}
/* Form */
.guestbook-prompt {}
.guestbook-prompt {
display: block;
margin-bottom: 1em;
}
.guestbook-form {}
.guestbook-label {}
.guestbook-input {}
.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;
}
/* Drawings */
@ -35,12 +45,19 @@
.guestbook-canvas-tools a {
cursor: pointer;
}
.guestbook-drawing-wrap {
display: block;
margin-bottom: 0.5em;
}
.guestbook-drawing-inline a {
cursor: pointer;
}
.guestbook-drawing-content:empty {
display: none;
}
.guestbook-drawing-content {
display: block;
margin-bottom: 1em;
margin-bottom: 0.5em;
}
.guestbook-swatch {
display: inline-block;
@ -65,6 +82,10 @@
}
/* Voice notes */
.guestbook-voice-wrap {
display: block;
margin-bottom: 0.5em;
}
.guestbook-voice-record.recording {
color: red;
}
@ -80,14 +101,25 @@
}
audio {
display: block;
margin-top: 0.6em;
margin-bottom: 0.3em;
height: 2em;
}
/* Entries */
.entry-header {}
.entry {
margin: 0.5em 0;
padding-bottom: 0.5em;
border-bottom: 1px solid #ccc;
}
.entry:last-child {
border-bottom: none;
}
.entry-header {
margin-bottom: 0.2em;
}
.entry-date {}
.entry-name {}
.entry-website {}
.entry-body {}
.entry-separator {}
.entry-body {
white-space: pre-wrap;
}

View file

@ -20,9 +20,8 @@
</head>
<body>
<div class="page-container">
Thanks! Your message is pending approval.
<a href="/">&#8592; back</a>
<p>Thanks! Your message is pending approval.</p>
<p><a href="/">&#8592; back</a></p>
</div>
</body>
</html>