Refactor thread responses into an enum.
Lays ground work for different types of views, i.e. email, news, docs, etc.
This commit is contained in:
parent
446fcfe37f
commit
760cec01a8
@ -32,8 +32,13 @@ pub struct ThreadSummary {
|
||||
pub tags: Vec<String>,
|
||||
}
|
||||
|
||||
#[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<Message>,
|
||||
@ -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?
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<Vec<Tag>, Server
|
||||
Ok(tags)
|
||||
}
|
||||
|
||||
pub async fn thread(pool: &PgPool, thread_id: String) -> Result<Thread, ServerError> {
|
||||
pub async fn thread(pool: &PgPool, thread_id: String) -> Result<EmailThread, ServerError> {
|
||||
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<Thread, ServerEr
|
||||
// * Grafana does <div class="image-wrapp"><img class="lazyload>"<img src="/media/...>"</img></div>
|
||||
// * Some sites appear to be HTML encoded, unencode them, i.e. imperialviolent
|
||||
let body_tranformers: Vec<Box<dyn Transformer>> = 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<Thread, ServerEr
|
||||
name: r.name,
|
||||
addr: addr.map(|a| a.to_string()),
|
||||
});
|
||||
Ok(Thread {
|
||||
Ok(EmailThread {
|
||||
thread_id,
|
||||
subject: title.clone(),
|
||||
messages: vec![Message {
|
||||
|
||||
@ -14,8 +14,8 @@ use crate::{
|
||||
compute_offset_limit,
|
||||
error::ServerError,
|
||||
graphql::{
|
||||
Attachment, Body, DispositionType, Email, Header, Html, Message, PlainText, Tag, Thread,
|
||||
ThreadSummary, UnhandledContentType,
|
||||
Attachment, Body, DispositionType, Email, EmailThread, Header, Html, Message, PlainText,
|
||||
Tag, ThreadSummary, UnhandledContentType,
|
||||
},
|
||||
linkify_html, sanitize_html,
|
||||
};
|
||||
@ -125,7 +125,7 @@ pub async fn thread(
|
||||
nm: &Notmuch,
|
||||
thread_id: String,
|
||||
debug_content_tree: bool,
|
||||
) -> Result<Thread, ServerError> {
|
||||
) -> Result<EmailThread, ServerError> {
|
||||
// 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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -341,25 +341,28 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
})
|
||||
.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:#?}");
|
||||
|
||||
@ -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<Msg> {
|
||||
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 {
|
||||
|
||||
@ -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<Msg> {
|
||||
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 {
|
||||
|
||||
@ -394,9 +394,9 @@ macro_rules! implement_email {
|
||||
}
|
||||
|
||||
implement_email!(
|
||||
ShowThreadQueryThreadMessagesTo,
|
||||
ShowThreadQueryThreadMessagesCc,
|
||||
ShowThreadQueryThreadMessagesFrom
|
||||
ShowThreadQueryThreadOnEmailThreadMessagesTo,
|
||||
ShowThreadQueryThreadOnEmailThreadMessagesCc,
|
||||
ShowThreadQueryThreadOnEmailThreadMessagesFrom
|
||||
);
|
||||
|
||||
fn raw_text_message(contents: &str) -> Node<Msg> {
|
||||
@ -467,13 +467,13 @@ fn render_avatar(avatar: Option<String>, from: &str) -> Node<Msg> {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_open_header(msg: &ShowThreadQueryThreadMessages) -> Node<Msg> {
|
||||
fn render_open_header(msg: &ShowThreadQueryThreadOnEmailThreadMessages) -> Node<Msg> {
|
||||
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<Msg> {
|
||||
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<Msg> {
|
||||
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<Msg> {
|
||||
]
|
||||
}
|
||||
|
||||
fn render_closed_header(msg: &ShowThreadQueryThreadMessages) -> Node<Msg> {
|
||||
fn render_closed_header(msg: &ShowThreadQueryThreadOnEmailThreadMessages) -> Node<Msg> {
|
||||
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<Msg> {
|
||||
]
|
||||
}
|
||||
|
||||
fn message_render(msg: &ShowThreadQueryThreadMessages, open: bool) -> Node<Msg> {
|
||||
fn message_render(msg: &ShowThreadQueryThreadOnEmailThreadMessages, open: bool) -> Node<Msg> {
|
||||
let expand_id = msg.id.clone();
|
||||
div![
|
||||
C!["message"],
|
||||
@ -707,16 +707,16 @@ fn message_render(msg: &ShowThreadQueryThreadMessages, open: bool) -> Node<Msg>
|
||||
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<Msg>
|
||||
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<Msg>
|
||||
|
||||
#[topo::nested]
|
||||
fn thread(
|
||||
thread: &ShowThreadQueryThread,
|
||||
thread: &ShowThreadQueryThreadOnEmailThread,
|
||||
open_messages: &HashSet<String>,
|
||||
show_icon_text: bool,
|
||||
) -> Node<Msg> {
|
||||
|
||||
@ -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<Msg> {
|
||||
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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user