stamail

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 952baf777eb0b9dafd69967af166c1df4c001279
parent 76340912c8aa23b13a3f4893f93d2bd7430b1beb
Author: Nathaniel Chappelle <nathaniel@chappelle.dev>
Date:   Thu, 29 Jan 2026 23:30:22 -0800

Details and summaries working well to provide threading and dropdowns

Diffstat:
Msetup-mock-maildir | 4++--
Mstamail.c | 83+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mstyle.css | 6++++++
3 files changed, 58 insertions(+), 35 deletions(-)

diff --git a/setup-mock-maildir b/setup-mock-maildir @@ -333,5 +333,5 @@ echo " - Standalone announcements" echo " - Very deep thread (10 levels)" echo "" echo "To index with notmuch:" -echo " notmuch new --maildir=$MAILDIR" -echo " notmuch show --format=json '*' | ./stamail -o archive" +echo " notmuch new " +echo " notmuch show --format=sexp '*' | ./stamail " diff --git a/stamail.c b/stamail.c @@ -380,43 +380,57 @@ static int write_message(const char *output_dir, struct message *m) { return 0; } +static void render_message_line(FILE *fp, + struct message *m) { + unsigned long hash = hash_msgid(m->id, m->id_len); + + /* subject link */ + fprintf(fp, "<a href=\"msg-%08lx.html\">", hash); + fprinthtml(fp, m->subject, m->subject_len); + fprinthtml(fp, " <", 2); + fprinthtml(fp, m->from, m->from_len); + fprinthtml(fp, ">", 1); + fprintf(fp, "</a>"); + + /* inline details for body */ + if (m->body && m->body_len > 0) { + fprintf(fp, " "); + fprintf(fp, "<details>"); + fprintf(fp, "<summary>+</summary>"); + fprintf(fp, "<div class=\"body\">"); + fprinthtml(fp, m->body, m->body_len); + fprintf(fp, "</div>"); + fprintf(fp, "</details>"); + } +} + static void render_thread_ascii(FILE *fp, struct thread_node *n, const char *prefix, - int is_last) -{ + int is_last) { char next_prefix[1024]; - /* branch marker */ - fprintf(fp, "%s%s ", prefix, is_last ? "\\-" : "+-"); - - /* link to message */ - unsigned long hash = - hash_msgid(n->msg->id, n->msg->id_len); + fprintf(fp, "%s%s ", + prefix, + is_last ? "└─" : "├─"); - fprintf(fp, "<a href=\"msg-%08lx.html\">", hash); - fprinthtml(fp, - n->msg->subject, - n->msg->subject_len); - fprintf(fp, "</a>\n"); + render_message_line(fp, n->msg); + fputc('\n', fp); - /* extend prefix */ snprintf(next_prefix, sizeof(next_prefix), - "%s%s ", + "%s%s", prefix, - is_last ? " " : "| "); + is_last ? " " : "│ "); - /* walk children */ - struct thread_node *c = n->child; - while (c) { + for (struct thread_node *c = n->child; c; c = c->sibling) { render_thread_ascii(fp, c, next_prefix, c->sibling == NULL); - c = c->sibling; } } + static int write_threads(const char *output_dir, const char *archive_name, struct thread_node **threads, @@ -441,8 +455,7 @@ static int write_threads(const char *output_dir, fprintf(fp, "</nav>\n"); fprintf(fp, - "<pre style=\"font-family: monospace;" - "white-space: pre; line-height: 1.3;\">\n"); + "<pre class=\"threads\">\n"); for (int i = 0; i < num_threads; i++) { struct thread_node *r = *threads++; @@ -451,22 +464,26 @@ static int write_threads(const char *output_dir, unsigned long hash = hash_msgid(r->msg->id, r->msg->id_len); - fprintf(fp, "<a href=\"msg-%08lx.html\">", hash); - fprinthtml(fp, - r->msg->subject, - r->msg->subject_len); - fprintf(fp, "</a>\n"); - - /* children */ - struct thread_node *c = r->child; - while (c) { + fprintf(fp, "<details open class=\"threads\">\n"); + fprintf(fp, "<summary>"); + + /* root rendered as tree head */ + fprintf(fp, "└─ "); + render_message_line(fp, r->msg); + + fprintf(fp, "</summary>\n"); + + /* children share the same tree */ + for (struct thread_node *c = r->child; c; c = c->sibling) { render_thread_ascii(fp, c, - "", + " ", c->sibling == NULL); - c = c->sibling; } + fprintf(fp, "</details>\n"); + + fprintf(fp, "\n"); } diff --git a/style.css b/style.css @@ -12,3 +12,9 @@ td.from { width: 250px; } td.subject a { color: #0066cc; text-decoration: none; } td.subject a:hover { text-decoration: underline; } tr:hover { background: #f5f5f5; } +details { display: inline; } +summary { display: inline; cursor: pointer; color: #666; } +summary::marker { display: none; } +.body { margin-top: 0.5em; margin-left: 4ch; white-space: pre-wrap; color: #222; } +pre.threads { font-family: monospace; white-space: pre; line-height: 1.0; margin: 0; } +