From 760cec01a8682271002abd5825cee9c95ca8f148 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Mon, 26 Aug 2024 21:48:53 -0700 Subject: [PATCH] Refactor thread responses into an enum. Lays ground work for different types of views, i.e. email, news, docs, etc. --- server/src/graphql.rs | 19 +++-- server/src/newsreader.rs | 7 +- server/src/nm.rs | 8 +- web/graphql/schema.json | 140 ++++++++++++++++++-------------- web/graphql/show_thread.graphql | 82 ++++++++++--------- web/src/state.rs | 39 +++++---- web/src/view/desktop.rs | 3 +- web/src/view/mobile.rs | 4 +- web/src/view/mod.rs | 46 +++++------ web/src/view/tablet.rs | 3 +- 10 files changed, 190 insertions(+), 161 deletions(-) diff --git a/server/src/graphql.rs b/server/src/graphql.rs index 30cb032..d166c19 100644 --- a/server/src/graphql.rs +++ b/server/src/graphql.rs @@ -32,8 +32,13 @@ pub struct ThreadSummary { pub tags: Vec, } +#[derive(Debug, Union)] +pub enum Thread { + Email(EmailThread), +} + #[derive(Debug, SimpleObject)] -pub struct Thread { +pub struct EmailThread { pub thread_id: String, pub subject: String, pub messages: Vec, @@ -369,11 +374,13 @@ impl QueryRoot { .field("contentTree") .exists(); // TODO: look at thread_id and conditionally load newsreader - if newsreader::is_newsreader_thread(&thread_id) { - Ok(newsreader::thread(pool, thread_id).await?) - } else { - Ok(nm::thread(nm, thread_id, debug_content_tree).await?) - } + Ok(Thread::Email( + if newsreader::is_newsreader_thread(&thread_id) { + newsreader::thread(pool, thread_id).await? + } else { + nm::thread(nm, thread_id, debug_content_tree).await? + }, + )) } } diff --git a/server/src/newsreader.rs b/server/src/newsreader.rs index 082d380..b1d2ec1 100644 --- a/server/src/newsreader.rs +++ b/server/src/newsreader.rs @@ -14,7 +14,7 @@ const THREAD_PREFIX: &'static str = "news:"; use crate::{ compute_offset_limit, error::ServerError, - graphql::{Body, Email, Html, Message, Tag, Thread, ThreadSummary}, + graphql::{Body, Email, EmailThread, Html, Message, Tag, ThreadSummary}, AddOutlink, EscapeHtml, InlineStyle, SanitizeHtml, SlurpContents, StripHtml, Transformer, }; @@ -143,7 +143,7 @@ pub async fn tags(pool: &PgPool, _needs_unread: bool) -> Result, Server Ok(tags) } -pub async fn thread(pool: &PgPool, thread_id: String) -> Result { +pub async fn thread(pool: &PgPool, thread_id: String) -> Result { let id = thread_id .strip_prefix(THREAD_PREFIX) .expect("news thread doesn't start with '{THREAD_PREFIX}'") @@ -198,7 +198,6 @@ pub async fn thread(pool: &PgPool, thread_id: String) -> Result // * Some sites appear to be HTML encoded, unencode them, i.e. imperialviolent let body_tranformers: Vec> = vec![ - // TODO: add a map of urls and selectors Box::new(SlurpContents { site_selectors: hashmap![ "hackaday.com".to_string() => vec![ @@ -237,7 +236,7 @@ pub async fn thread(pool: &PgPool, thread_id: String) -> Result Result { +) -> Result { // TODO(wathiede): normalize all email addresses through an address book with preferred // display names (that default to the most commonly seen name). let mut messages = Vec::new(); @@ -246,7 +246,7 @@ pub async fn thread( .next() .and_then(|m| m.subject.clone()) .unwrap_or("(NO SUBJECT)".to_string()); - Ok(Thread { + Ok(EmailThread { thread_id, subject, messages, diff --git a/web/graphql/schema.json b/web/graphql/schema.json index 38ef135..f9c3bae 100644 --- a/web/graphql/schema.json +++ b/web/graphql/schema.json @@ -290,6 +290,73 @@ "name": "Email", "possibleTypes": null }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "threadId", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "subject", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "messages", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Message", + "ofType": null + } + } + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "EmailThread", + "possibleTypes": null + }, { "description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).", "enumValues": null, @@ -1056,7 +1123,7 @@ "kind": "NON_NULL", "name": null, "ofType": { - "kind": "OBJECT", + "kind": "UNION", "name": "Thread", "ofType": null } @@ -1157,69 +1224,18 @@ { "description": null, "enumValues": null, - "fields": [ - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "threadId", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "subject", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "messages", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "Message", - "ofType": null - } - } - } - } - } - ], + "fields": null, "inputFields": null, - "interfaces": [], - "kind": "OBJECT", + "interfaces": null, + "kind": "UNION", "name": "Thread", - "possibleTypes": null + "possibleTypes": [ + { + "kind": "OBJECT", + "name": "EmailThread", + "ofType": null + } + ] }, { "description": null, diff --git a/web/graphql/show_thread.graphql b/web/graphql/show_thread.graphql index 2cab65b..61cb0b3 100644 --- a/web/graphql/show_thread.graphql +++ b/web/graphql/show_thread.graphql @@ -1,47 +1,49 @@ query ShowThreadQuery($threadId: String!) { thread(threadId: $threadId) { - threadId, - subject - messages { - id + __typename ... on EmailThread{ + threadId, subject - tags - from { - name - addr - } - to { - name - addr - } - cc { - name - addr - } - timestamp - body { - __typename - ... on UnhandledContentType { - contents - contentTree - } - ... on PlainText { - contents - contentTree - } - ... on Html { - contents - contentTree - } - } - path - attachments { + messages { id - idx - filename - contentType - contentId - size + subject + tags + from { + name + addr + } + to { + name + addr + } + cc { + name + addr + } + timestamp + body { + __typename + ... on UnhandledContentType { + contents + contentTree + } + ... on PlainText { + contents + contentTree + } + ... on Html { + contents + contentTree + } + } + path + attachments { + id + idx + filename + contentType + contentId + size + } } } } diff --git a/web/src/state.rs b/web/src/state.rs index 5250c8e..cc89f83 100644 --- a/web/src/state.rs +++ b/web/src/state.rs @@ -341,25 +341,28 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders) { }) .collect(), ); - let mut open_messages: HashSet<_> = data - .thread - .messages - .iter() - .filter(|msg| msg.tags.iter().any(|t| t == "unread")) - .map(|msg| msg.id.clone()) - .collect(); - if open_messages.is_empty() { - open_messages = data - .thread - .messages - .iter() - .map(|msg| msg.id.clone()) - .collect(); + match &data.thread { + graphql::show_thread_query::ShowThreadQueryThread::EmailThread( + ShowThreadQueryThreadOnEmailThread { + thread_id, + subject, + messages, + }, + ) => { + let mut open_messages: HashSet<_> = messages + .iter() + .filter(|msg| msg.tags.iter().any(|t| t == "unread")) + .map(|msg| msg.id.clone()) + .collect(); + if open_messages.is_empty() { + open_messages = messages.iter().map(|msg| msg.id.clone()).collect(); + } + model.context = Context::ThreadResult { + thread: data.thread, + open_messages, + }; + } } - model.context = Context::ThreadResult { - thread: data.thread, - open_messages, - }; } Msg::ShowThreadResult(bad) => { error!("show_thread_query error: {bad:#?}"); diff --git a/web/src/view/desktop.rs b/web/src/view/desktop.rs index 66dfc2f..9281c75 100644 --- a/web/src/view/desktop.rs +++ b/web/src/view/desktop.rs @@ -3,6 +3,7 @@ use seed_hooks::{state_access::CloneState, topo, use_state}; use crate::{ api::urls, + graphql::show_thread_query::*, state::{Context, Model, Msg}, view::{self, view_header, view_search_results, view_tags}, }; @@ -15,7 +16,7 @@ pub(super) fn view(model: &Model) -> Node { let content = match &model.context { Context::None => div![h1!["Loading"]], Context::ThreadResult { - thread, + thread: ShowThreadQueryThread::EmailThread(thread), open_messages, } => view::thread(thread, open_messages, show_icon_text), Context::SearchResult { diff --git a/web/src/view/mobile.rs b/web/src/view/mobile.rs index b9b7c48..2a4a70d 100644 --- a/web/src/view/mobile.rs +++ b/web/src/view/mobile.rs @@ -4,7 +4,7 @@ use seed::{prelude::*, *}; use crate::{ api::urls, - graphql::front_page_query::*, + graphql::{front_page_query::*, show_thread_query::*}, state::{Context, Model, Msg}, view::{ self, human_age, pretty_authors, search_toolbar, set_title, tags_chiclet, view_header, @@ -18,7 +18,7 @@ pub(super) fn view(model: &Model) -> Node { let content = match &model.context { Context::None => div![h1!["Loading"]], Context::ThreadResult { - thread, + thread: ShowThreadQueryThread::EmailThread(thread), open_messages, } => view::thread(thread, open_messages, show_icon_text), Context::SearchResult { diff --git a/web/src/view/mod.rs b/web/src/view/mod.rs index f3c451d..fda614a 100644 --- a/web/src/view/mod.rs +++ b/web/src/view/mod.rs @@ -394,9 +394,9 @@ macro_rules! implement_email { } implement_email!( - ShowThreadQueryThreadMessagesTo, - ShowThreadQueryThreadMessagesCc, - ShowThreadQueryThreadMessagesFrom + ShowThreadQueryThreadOnEmailThreadMessagesTo, + ShowThreadQueryThreadOnEmailThreadMessagesCc, + ShowThreadQueryThreadOnEmailThreadMessagesFrom ); fn raw_text_message(contents: &str) -> Node { @@ -467,13 +467,13 @@ fn render_avatar(avatar: Option, from: &str) -> Node { } } -fn render_open_header(msg: &ShowThreadQueryThreadMessages) -> Node { +fn render_open_header(msg: &ShowThreadQueryThreadOnEmailThreadMessages) -> Node { let (from, from_detail) = match &msg.from { - Some(ShowThreadQueryThreadMessagesFrom { + Some(ShowThreadQueryThreadOnEmailThreadMessagesFrom { name: Some(name), addr, }) => (name.to_string(), addr.clone()), - Some(ShowThreadQueryThreadMessagesFrom { + Some(ShowThreadQueryThreadOnEmailThreadMessagesFrom { addr: Some(addr), .. }) => (addr.to_string(), None), _ => (String::from("UNKNOWN"), None), @@ -516,15 +516,15 @@ fn render_open_header(msg: &ShowThreadQueryThreadMessages) -> Node { if i>0 { ", " }else { "" }, { let to = match to { - ShowThreadQueryThreadMessagesTo { + ShowThreadQueryThreadOnEmailThreadMessagesTo { name: Some(name), addr:Some(addr), } => format!("{name} <{addr}>"), - ShowThreadQueryThreadMessagesTo { + ShowThreadQueryThreadOnEmailThreadMessagesTo { name: Some(name), addr:None } => format!("{name}"), - ShowThreadQueryThreadMessagesTo { + ShowThreadQueryThreadOnEmailThreadMessagesTo { addr: Some(addr), .. } => format!("{addr}"), _ => String::from("UNKNOWN"), @@ -553,15 +553,15 @@ fn render_open_header(msg: &ShowThreadQueryThreadMessages) -> Node { if i>0 { ", " }else { "" }, { let cc = match cc { - ShowThreadQueryThreadMessagesCc { + ShowThreadQueryThreadOnEmailThreadMessagesCc { name: Some(name), addr:Some(addr), } => format!("{name} <{addr}>"), - ShowThreadQueryThreadMessagesCc { + ShowThreadQueryThreadOnEmailThreadMessagesCc { name: Some(name), addr:None } => format!("{name}"), - ShowThreadQueryThreadMessagesCc { + ShowThreadQueryThreadOnEmailThreadMessagesCc { addr: Some(addr), .. } => format!("<{addr}>"), _ => String::from("UNKNOWN"), @@ -609,12 +609,12 @@ fn render_open_header(msg: &ShowThreadQueryThreadMessages) -> Node { ] } -fn render_closed_header(msg: &ShowThreadQueryThreadMessages) -> Node { +fn render_closed_header(msg: &ShowThreadQueryThreadOnEmailThreadMessages) -> Node { let from: String = match &msg.from { - Some(ShowThreadQueryThreadMessagesFrom { + Some(ShowThreadQueryThreadOnEmailThreadMessagesFrom { name: Some(name), .. }) => name.to_string(), - Some(ShowThreadQueryThreadMessagesFrom { + Some(ShowThreadQueryThreadOnEmailThreadMessagesFrom { addr: Some(addr), .. }) => addr.to_string(), _ => String::from("UNKNOWN"), @@ -683,7 +683,7 @@ fn render_closed_header(msg: &ShowThreadQueryThreadMessages) -> Node { ] } -fn message_render(msg: &ShowThreadQueryThreadMessages, open: bool) -> Node { +fn message_render(msg: &ShowThreadQueryThreadOnEmailThreadMessages, open: bool) -> Node { let expand_id = msg.id.clone(); div![ C!["message"], @@ -707,16 +707,16 @@ fn message_render(msg: &ShowThreadQueryThreadMessages, open: bool) -> Node div![ C!["body"], match &msg.body { - ShowThreadQueryThreadMessagesBody::UnhandledContentType( - ShowThreadQueryThreadMessagesBodyOnUnhandledContentType { contents ,content_tree}, + ShowThreadQueryThreadOnEmailThreadMessagesBody::UnhandledContentType( + ShowThreadQueryThreadOnEmailThreadMessagesBodyOnUnhandledContentType { contents ,content_tree}, ) => div![ raw_text_message(&contents), div![C!["error"], view_content_tree(&content_tree), ] ], - ShowThreadQueryThreadMessagesBody::PlainText( - ShowThreadQueryThreadMessagesBodyOnPlainText { + ShowThreadQueryThreadOnEmailThreadMessagesBody::PlainText( + ShowThreadQueryThreadOnEmailThreadMessagesBodyOnPlainText { contents, content_tree, }, @@ -724,8 +724,8 @@ fn message_render(msg: &ShowThreadQueryThreadMessages, open: bool) -> Node raw_text_message(&contents), view_content_tree(&content_tree), ], - ShowThreadQueryThreadMessagesBody::Html( - ShowThreadQueryThreadMessagesBodyOnHtml { + ShowThreadQueryThreadOnEmailThreadMessagesBody::Html( + ShowThreadQueryThreadOnEmailThreadMessagesBodyOnHtml { contents, content_tree, }, @@ -792,7 +792,7 @@ fn message_render(msg: &ShowThreadQueryThreadMessages, open: bool) -> Node #[topo::nested] fn thread( - thread: &ShowThreadQueryThread, + thread: &ShowThreadQueryThreadOnEmailThread, open_messages: &HashSet, show_icon_text: bool, ) -> Node { diff --git a/web/src/view/tablet.rs b/web/src/view/tablet.rs index b9a6354..ead555d 100644 --- a/web/src/view/tablet.rs +++ b/web/src/view/tablet.rs @@ -1,6 +1,7 @@ use seed::{prelude::*, *}; use crate::{ + graphql::show_thread_query::*, state::{Context, Model, Msg}, view::{self, view_header, view_search_results, view_tags}, }; @@ -12,7 +13,7 @@ pub(super) fn view(model: &Model) -> Node { let content = match &model.context { Context::None => div![h1!["Loading"]], Context::ThreadResult { - thread, + thread: ShowThreadQueryThread::EmailThread(thread), open_messages, } => view::thread(thread, open_messages, show_icon_text), Context::SearchResult {