summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--default.html45
-rw-r--r--html.c263
2 files changed, 143 insertions, 165 deletions
diff --git a/default.html b/default.html
index 36736a6..7300f7c 100644
--- a/default.html
+++ b/default.html
@@ -12,11 +12,9 @@ body {
 nav ul {
 	padding: 0;
 	list-style-type: none;
-	display: flex;
-	flex-wrap: wrap;
 }
 nav ul li {
-	margin-right: 1.5ch;
+	display: inline;
 }
 
 main.index ol {
@@ -30,15 +28,12 @@ main.index h2 {
 	font-size: 1em;
 	margin: 0;
 }
-main.index data.replies, main.index time {
+main.index time, main.index data.replies {
 	display: block;
 }
 main.index data.replies[value="0"] {
 	display: none;
 }
-main.index data.replies::before {
-	content: '+';
-}
 
 article.message header {
 	background-color: gainsboro;
@@ -55,41 +50,14 @@ article.message header nav ul {
 	margin: 0;
 }
 
-address {
-	display: inline;
-	font-style: inherit;
-}
-ul.recipient {
+ul.address {
 	margin: 0;
 	padding: 0;
 	list-style-type: none;
-	display: flex;
-	flex-wrap: wrap;
-}
-ul.recipient li:not(:last-child)::after {
-	content: ', ';
-	margin-right: 1ch;
-}
-ul.recipient li.group > address::after {
-	content: ': ';
-}
-ul.recipient li.group::after {
-	content: '; ';
-	margin-right: 1ch;
-}
-address.from::before {
-	content: 'From: ';
-}
-address.from {
-	margin-right: 1ch;
-}
-ul.to::before {
-	content: 'To: ';
-	margin-right: 1ch;
+	display: inline;
 }
-ul.cc::before {
-	content: 'Cc: ';
-	margin-right: 1ch;
+ul.address li {
+	display: inline;
 }
 
 pre {
@@ -133,6 +101,7 @@ details.subthread summary {
 
 footer {
 	margin: 1em 0;
+	font-size: x-small;
 	text-align: center;
 	color: gray;
 }
diff --git a/html.c b/html.c
index d55e5bf..4269871 100644
--- a/html.c
+++ b/html.c
@@ -28,6 +28,7 @@
 #include <assert.h>
 #include <err.h>
 #include <regex.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -36,46 +37,64 @@
 
 #include "archive.h"
 
-static int htmlAddress(FILE *file, const char *class, struct Address addr) {
+static char *htmlMailto(struct Address addr) {
+	struct Variable vars[] = {
+		{ "mailbox", addr.mailbox },
+		{ "host", addr.host },
+		{0},
+	};
+	return templateString("mailto:[mailbox]@[host]", vars, escapeURL);
+}
+
+static int htmlAddress(FILE *file, struct Address addr, bool last) {
+	char *mailto = htmlMailto(addr);
 	const char *template;
 	if (addr.host) {
-		template = Q(<li><address class="[class]">[name]</address></li>);
+		template = Q(
+			<li><a href="[mailto]">[name]</a>[,]</li>
+		);
 	} else if (addr.mailbox) {
-		template = (const char *) {
-			Q(<li class="group">)
-				Q(<address class="[class]">[mailbox]</address>)
-				Q(<ul>)
-		};
+		template = Q(
+			<li>[mailbox]:
+				<ul>
+		);
 	} else {
-		template = (const char *) {
-				Q(</ul>)
-			Q(</li>)
-		};
+		template = Q(
+				</ul>;
+			</li>
+		);
 	}
 	struct Variable vars[] = {
-		{ "class", class },
+		{ "mailto", mailto },
 		{ "name", addressName(addr) },
 		{ "mailbox", addr.mailbox },
+		{ ",", (last ? "" : ", ") },
 		{0},
 	};
-	return templateRender(file, template, vars, escapeXML);
+	int error = templateRender(file, template, vars, escapeXML);
+	free(mailto);
+	return error;
 }
 
 static int
-htmlAddressList(FILE *file, const char *class, struct AddressList list) {
+htmlAddressList(FILE *file, const char *name, struct AddressList list) {
 	if (!list.len) return 0;
-	const char *template = Q(<ul class="recipient [class]">);
+	const char *template = Q(
+		<div class="[name]">
+			[name]:
+			<ul class="address">
+	);
 	struct Variable vars[] = {
-		{ "class", class },
+		{ "name", name },
 		{0},
 	};
 	int error = templateRender(file, template, vars, escapeXML);
 	if (error) return error;
 	for (size_t i = 0; i < list.len; ++i) {
-		error = htmlAddress(file, class, list.addrs[i]);
+		error = htmlAddress(file, list.addrs[i], i == list.len - 1);
 		if (error) return error;
 	}
-	return templateRender(file, Q(</ul>), NULL, NULL);
+	return templateRender(file, Q(</ul></div>), NULL, NULL);
 }
 
 static char *htmlReply(const struct Envelope *envelope) {
@@ -118,7 +137,7 @@ static char *htmlMbox(const char *messageID) {
 
 static int
 htmlNavItem(FILE *file, const char *name, const char *base, const char *url) {
-	const char *template = Q(<li><a href="[base][url]">[name]</a></li>);
+	const char *template = Q(<li><a href="[base][url]">[name]</a> </li>);
 	struct Variable vars[] = {
 		{ "name", name },
 		{ "base", base },
@@ -129,57 +148,51 @@ htmlNavItem(FILE *file, const char *name, const char *base, const char *url) {
 }
 
 int htmlMessageNav(FILE *file, const struct Envelope *envelope) {
-	int error = templateRender(file, Q(<nav><ul>), NULL, NULL);
+	char *mbox = htmlMbox(envelope->messageID);
+	int error = 0
+		|| templateRender(file, Q(<nav><ul>), NULL, NULL)
+		|| htmlNavItem(file, "download", "", mbox);
 	if (error) return error;
+	free(mbox);
 	if (envelope->inReplyTo) {
 		char *fragment = htmlFragment(envelope->inReplyTo);
 		error = htmlNavItem(file, "parent", "", fragment);
 		free(fragment);
 		if (error) return error;
 	}
-	char *mbox = htmlMbox(envelope->messageID);
-	error = htmlNavItem(file, "download", "", mbox);
-	free(mbox);
-	if (error) return error;
 	return templateRender(file, Q(</ul></nav>), NULL, NULL);
 }
 
-static const char *htmlUTC(time_t time) {
-	static char buf[sizeof("0000-00-00T00:00:00Z")];
-	strftime(buf, sizeof(buf), "%FT%TZ", gmtime(&time));
-	return buf;
-}
-
 int htmlMessageOpen(FILE *file, const struct Envelope *envelope) {
-	const char *template = {
-		Q(<article class="message" id="[messageID]">)
-		Q(<header>)
-			Q(<h2 class="subject"><a href="[fragment]">[subject]</a></h2>)
-			Q(<address class="from">)
-				Q(<a href="[reply]">[from]</a>)
-			Q(</address>)
-			Q(<time datetime="[utc]">[date]</time>)
-	};
 	char *fragment = htmlFragment(envelope->messageID);
 	char *reply = htmlReply(envelope);
+	const char *template = Q(
+		<article class="message" id="[messageID]">
+		<header>
+			<h2 class="Subject"><a href="[fragment]">[subject]</a></h2>
+			<div class="From">
+				From: <a href="[reply]">[from]</a>
+				<time datetime="[utc]">[date]</time>
+			</div>
+	);
 	struct Variable vars[] = {
 		{ "messageID", envelope->messageID },
 		{ "fragment", fragment },
 		{ "subject", envelope->subject },
 		{ "reply", reply },
 		{ "from", addressName(envelope->from) },
-		{ "utc", htmlUTC(envelope->time) },
+		{ "utc", iso8601(envelope->time).s },
 		{ "date", envelope->date },
 		{0},
 	};
 	int error = 0
 		|| templateRender(file, template, vars, escapeXML)
-		|| htmlAddressList(file, "to", envelope->to)
-		|| htmlAddressList(file, "cc", envelope->cc)
+		|| htmlAddressList(file, "To", envelope->to)
+		|| htmlAddressList(file, "Cc", envelope->cc)
 		|| htmlMessageNav(file, envelope)
 		|| templateRender(file, Q(</header>), NULL, NULL);
-	free(reply);
 	free(fragment);
+	free(reply);
 	return error;
 }
 
@@ -350,12 +363,12 @@ int htmlAttachmentOpen(FILE *file) {
 int htmlAttachment(
 	FILE *file, const struct BodyPart *part, const struct Variable *path
 ) {
-	const char *template = {
-		Q(<li><a href="[url]">[name][type][/][subtype]</a></li>)
-	};
 	char *url = templateString("../" PATH_ATTACHMENT, path, escapeURL);
 	const char *name = paramGet(part->disposition.params, "filename");
 	if (!name) name = paramGet(part->params, "name");
+	const char *template = Q(
+		<li><a href="[url]">[name][type][/][subtype]</a> </li>
+	);
 	struct Variable vars[] = {
 		{ "url", url },
 		{ "name", (name ? name : "") },
@@ -387,20 +400,19 @@ static char *htmlThreadURL(const struct Envelope *envelope, const char *type) {
 }
 
 int htmlThreadHead(FILE *file, const struct Envelope *envelope) {
-	const char *template = {
-		Q(<!DOCTYPE html>)
-		Q(<meta charset="utf-8">)
-		Q(<meta name="generator" content="[generator]">)
-		Q(<title>[subject]</title>)
-		Q(<link rel="alternate" type="application/atom+xml" href="[atom]">)
-		Q(<link rel="alternate" type="application/mbox" href="[mbox]">)
-	};
 	char *atom = htmlThreadURL(envelope, "atom");
 	char *mbox = htmlThreadURL(envelope, "mbox");
+	const char *template = Q(
+		<!DOCTYPE html>
+		<meta charset="utf-8">
+		<meta name="generator" content="[generator]">
+		<title>[subject]</title>
+		<link rel="alternate" type="application/atom+xml" href="[atom]">
+		<link rel="alternate" type="application/mbox" href="[mbox]">
+	);
 	struct Variable vars[] = {
 		{ "generator", GENERATOR_URL },
 		{ "subject", envelope->subject },
-		{ "title", baseTitle },
 		{ "atom", atom },
 		{ "mbox", mbox },
 		{0},
@@ -412,21 +424,21 @@ int htmlThreadHead(FILE *file, const struct Envelope *envelope) {
 }
 
 int htmlThreadOpen(FILE *file, const struct Envelope *envelope) {
-	const char *template = {
-		Q(<header class="thread">)
-			Q(<h1>[subject]</h1>)
-			Q(<nav>)
-				Q(<ul>)
-					Q(<li><a href="../index.html">index</a></li>)
-					Q(<li><a href="[atom]">follow</a></li>)
-					Q(<li><a href="[mbox]">download</a></li>)
-				Q(</ul>)
-			Q(</nav>)
-		Q(</header>)
-		Q(<main class="thread">)
-	};
 	char *atom = htmlThreadURL(envelope, "atom");
 	char *mbox = htmlThreadURL(envelope, "mbox");
+	const char *template = Q(
+		<header class="thread">
+			<h1>[subject]</h1>
+			<nav>
+				<ul>
+					<li><a href="../index.html">index</a> </li>
+					<li><a href="[atom]">follow</a> </li>
+					<li><a href="[mbox]">download</a> </li>
+				</ul>
+			</nav>
+		</header>
+		<main class="thread">
+	);
 	struct Variable vars[] = {
 		{ "subject", envelope->subject },
 		{ "atom", atom },
@@ -439,8 +451,8 @@ int htmlThreadOpen(FILE *file, const struct Envelope *envelope) {
 	return error;
 }
 
-static size_t threadCount(struct List thread) {
-	size_t count = 0;
+static uint32_t threadCount(struct List thread) {
+	uint32_t count = 0;
 	for (size_t i = 0; i < thread.len; ++i) {
 		if (thread.ptr[i].type == List) {
 			count += threadCount(thread.ptr[i].list);
@@ -451,41 +463,43 @@ static size_t threadCount(struct List thread) {
 	return count;
 }
 
-int htmlSubthreadOpen(FILE *file, struct List thread) {
-	const char *template = {
-		Q(<details class="subthread" open>)
-		Q(<summary>)
-		Q(<data class="replies" value="[replies]">[replies] repl[ies]</data>)
-		Q(</summary>)
-	};
-	size_t count = threadCount(thread);
-	char replies[32];
-	snprintf(replies, sizeof(replies), "%zu", count);
+static int htmlReplies(FILE *file, uint32_t replies) {
+	const char *template = Q(
+		<data class="replies" value="[replies]">[replies] repl[ies]</data>
+	);
 	struct Variable vars[] = {
-		{ "replies", replies },
-		{ "ies", (count > 1 ? "ies" : "y") },
+		{ "replies", u32(replies).s },
+		{ "ies", (replies != 1 ? "ies" : "y") },
 		{0},
 	};
 	return templateRender(file, template, vars, escapeXML);
 }
 
+int htmlSubthreadOpen(FILE *file, struct List thread) {
+	const char *template = Q(
+		<details class="subthread" open>
+			<summary>
+	);
+	return 0
+		|| templateRender(file, template, NULL, NULL)
+		|| htmlReplies(file, threadCount(thread))
+		|| templateRender(file, Q(</summary>), NULL, NULL);
+}
+
 int htmlSubthreadClose(FILE *file) {
 	return templateRender(file, Q(</details>), NULL, NULL);
 }
 
 static int htmlFooter(FILE *file) {
-	const char *template = {
-		Q(<footer>)
-			Q(<small>)
-			Q(<a href="[generator]">generated</a>)
-			" "
-			Q(<time datetime="[time]">[time]</time>)
-			Q(</small>)
-		Q(</footer>)
-	};
+	const char *template = Q(
+		<footer>
+			<a href="[generator]">generated</a>
+			<time datetime="[time]">[time]</time>
+		</footer>
+	);
 	struct Variable vars[] = {
 		{ "generator", GENERATOR_URL },
-		{ "time", htmlUTC(time(NULL)) },
+		{ "time", iso8601(time(NULL)).s },
 		{0},
 	};
 	return templateRender(file, template, vars, escapeXML);
@@ -498,13 +512,13 @@ int htmlThreadClose(FILE *file) {
 }
 
 int htmlIndexHead(FILE *file) {
-	const char *template = {
-		Q(<!DOCTYPE html>)
-		Q(<meta charset="utf-8">)
-		Q(<meta name="generator" content="[generator]">)
-		Q(<title>[title]</title>)
-		Q(<link rel="alternate" type="application/atom+xml" href="index.atom">)
-	};
+	const char *template = Q(
+		<!DOCTYPE html>
+		<meta charset="utf-8">
+		<meta name="generator" content="[generator]">
+		<title>[title]</title>
+		<link rel="alternate" type="application/atom+xml" href="index.atom">
+	);
 	struct Variable vars[] = {
 		{ "generator", GENERATOR_URL },
 		{ "title", baseTitle },
@@ -528,23 +542,23 @@ static int htmlIndexNav(FILE *file) {
 }
 
 int htmlIndexOpen(FILE *file) {
-	const char *head = {
-		Q(<header class="index">)
-			Q(<h1>[title]</h1>)
-	};
+	const char *head = Q(
+		<header class="index">
+			<h1>[title]</h1>
+	);
+	const char *tail = Q(
+		</header>
+		<main class="index">
+			<ol>
+	);
 	struct Variable vars[] = {
 		{ "title", baseTitle },
 		{0},
 	};
-	const char *tail = {
-		Q(</header>)
-		Q(<main class="index">)
-			Q(<ol>)
-	};
 	return 0
 		|| templateRender(file, head, vars, escapeXML)
 		|| htmlIndexNav(file)
-		|| templateRender(file, tail, NULL, NULL);
+		|| templateRender(file, tail, vars, escapeXML);
 }
 
 static char *htmlIndexURL(const struct Envelope *envelope) {
@@ -559,32 +573,27 @@ static char *htmlIndexURL(const struct Envelope *envelope) {
 int htmlIndexThread(
 	FILE *file, const struct Envelope *envelope, struct List thread
 ) {
-	const char *template = {
-		Q(<li>)
-			Q(<h2 class="subject"><a href="[url]">[subject]</a></h2>)
-			Q(<address class="from">[from]</address>)
-			Q(<time datetime="[utc]">[date]</time>)
-			" "
-			Q(<data class="replies" value="[replies]">)
-				Q([replies] repl[ies])
-			Q(</data>)
-		Q(</li>)
-	};
 	char *url = htmlIndexURL(envelope);
-	size_t count = threadCount(thread) - 1;
-	char replies[32];
-	snprintf(replies, sizeof(replies), "%zu", count);
+	const char *template = Q(
+		<li>
+			<h2 class="Subject"><a href="[url]">[subject]</a></h2>
+			<div class="From">
+				From: [from]
+				<time datetime="[utc]">[date]</time>
+			</div>
+	);
 	struct Variable vars[] = {
 		{ "url", url },
 		{ "subject", envelope->subject },
 		{ "from", addressName(envelope->from) },
-		{ "utc", htmlUTC(envelope->time) },
+		{ "utc", iso8601(envelope->time).s },
 		{ "date", envelope->date },
-		{ "replies", replies },
-		{ "ies", (count == 1 ? "y" : "ies") },
 		{0},
 	};
-	int error = templateRender(file, template, vars, escapeXML);
+	int error = 0
+		|| templateRender(file, template, vars, escapeXML)
+		|| htmlReplies(file, threadCount(thread) - 1)
+		|| templateRender(file, Q(</li>), NULL, NULL);
 	free(url);
 	return error;
 }