From 8abf9398e9e86ffe1b951b7e02b9252ca87c7d41 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Wed, 3 Apr 2024 21:07:56 -0700 Subject: [PATCH] web: add mark as spam buttons --- web/graphql/add_tag.graphql | 3 ++ web/graphql/remove_tag.graphql | 3 ++ web/graphql/schema.json | 90 ++++++++++++++++++++++++++++++++++ web/src/graphql.rs | 16 ++++++ web/src/state.rs | 80 ++++++++++++++++++++++++++++++ web/src/view/mod.rs | 19 ++++++- 6 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 web/graphql/add_tag.graphql create mode 100644 web/graphql/remove_tag.graphql diff --git a/web/graphql/add_tag.graphql b/web/graphql/add_tag.graphql new file mode 100644 index 0000000..0dda9da --- /dev/null +++ b/web/graphql/add_tag.graphql @@ -0,0 +1,3 @@ +mutation AddTagMutation($query: String!, $tag: String!) { + tagAdd(query:$query, tag:$tag) +} diff --git a/web/graphql/remove_tag.graphql b/web/graphql/remove_tag.graphql new file mode 100644 index 0000000..fcdd9ff --- /dev/null +++ b/web/graphql/remove_tag.graphql @@ -0,0 +1,3 @@ +mutation RemoveTagMutation($query: String!, $tag: String!) { + tagRemove(query:$query, tag:$tag) +} diff --git a/web/graphql/schema.json b/web/graphql/schema.json index a6f2c07..524bcac 100644 --- a/web/graphql/schema.json +++ b/web/graphql/schema.json @@ -693,6 +693,96 @@ "ofType": null } } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "query", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "tag", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "tagAdd", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "query", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "tag", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "tagRemove", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } } ], "inputFields": null, diff --git a/web/src/graphql.rs b/web/src/graphql.rs index e46baee..3f0ae16 100644 --- a/web/src/graphql.rs +++ b/web/src/graphql.rs @@ -28,6 +28,22 @@ pub struct ShowThreadQuery; )] pub struct MarkReadMutation; +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "graphql/schema.json", + query_path = "graphql/add_tag.graphql", + response_derives = "Debug" +)] +pub struct AddTagMutation; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "graphql/schema.json", + query_path = "graphql/remove_tag.graphql", + response_derives = "Debug" +)] +pub struct RemoveTagMutation; + pub async fn send_graphql(body: Body) -> Result, Error> where Body: Serialize, diff --git a/web/src/state.rs b/web/src/state.rs index 5352b91..8620fb4 100644 --- a/web/src/state.rs +++ b/web/src/state.rs @@ -184,6 +184,52 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders) { Msg::Noop }); } + Msg::AddTag(query, tag) => { + let search_url = urls::search(&model.query, 0).to_string(); + orders.skip().perform_cmd(async move { + let res: Result< + graphql_client::Response, + gloo_net::Error, + > = send_graphql(graphql::AddTagMutation::build_query( + graphql::add_tag_mutation::Variables { + query: query.clone(), + tag: tag.clone(), + }, + )) + .await; + if let Err(e) = res { + error!("Failed to add tag {tag} to {query}: {e}"); + } + seed::window() + .location() + .set_href(&search_url) + .expect("failed to change location"); + Msg::Noop + }); + } + Msg::RemoveTag(query, tag) => { + let search_url = urls::search(&model.query, 0).to_string(); + orders.skip().perform_cmd(async move { + let res: Result< + graphql_client::Response, + gloo_net::Error, + > = send_graphql(graphql::RemoveTagMutation::build_query( + graphql::remove_tag_mutation::Variables { + query: query.clone(), + tag: tag.clone(), + }, + )) + .await; + if let Err(e) = res { + error!("Failed to remove tag {tag} to {query}: {e}"); + } + seed::window() + .location() + .set_href(&search_url) + .expect("failed to change location"); + Msg::Noop + }); + } Msg::FrontPageRequest { query, @@ -311,6 +357,36 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders) { *selected_threads = results.iter().map(|node| node.thread.clone()).collect(); } } + Msg::SelectionAddTag(tag) => { + if let Context::SearchResult { + selected_threads, .. + } = &mut model.context + { + let threads = selected_threads + .iter() + .map(|tid| format!("thread:{tid}")) + .collect::>() + .join(" "); + orders + .skip() + .perform_cmd(async move { Msg::AddTag(threads, tag) }); + } + } + Msg::SelectionRemoveTag(tag) => { + if let Context::SearchResult { + selected_threads, .. + } = &mut model.context + { + let threads = selected_threads + .iter() + .map(|tid| format!("thread:{tid}")) + .collect::>() + .join(" "); + orders + .skip() + .perform_cmd(async move { Msg::RemoveTag(threads, tag) }); + } + } Msg::SelectionMarkAsRead => { if let Context::SearchResult { selected_threads, .. @@ -435,6 +511,8 @@ pub enum Msg { SearchQuery(String), SetUnread(String, bool), + AddTag(String, String), + RemoveTag(String, String), FrontPageRequest { query: String, @@ -455,6 +533,8 @@ pub enum Msg { SelectionSetNone, SelectionSetAll, + SelectionAddTag(String), + SelectionRemoveTag(String), SelectionMarkAsRead, SelectionMarkAsUnread, SelectionAddThread(String), diff --git a/web/src/view/mod.rs b/web/src/view/mod.rs index cbea814..70a8a80 100644 --- a/web/src/view/mod.rs +++ b/web/src/view/mod.rs @@ -261,6 +261,13 @@ fn search_toolbar( span![ // TODO(wathiede): add "Mark as spam" C!["level-item", "buttons", "has-addons"], + button![ + C!["button"], + attrs!{At::Title => "Mark as spam"}, + span![C!["icon", "is-small"], i![C!["far", "fa-hand"]]], + span!["Spam"], + ev(Ev::Click, |_| Msg::SelectionAddTag("Spam".to_string())) + ], button![ C!["button"], attrs!{At::Title => "Mark as read"}, @@ -721,13 +728,23 @@ fn thread(thread: &ShowThreadQueryThread, open_messages: &HashSet) -> No }); let read_thread_id = thread.thread_id.clone(); let unread_thread_id = thread.thread_id.clone(); + let spam_thread_id = thread.thread_id.clone(); div![ C!["thread"], h3![C!["is-size-5"], subject], span![C!["tags"], tags_chiclet(&tags, false)], span![ - // TODO(wathiede): add "Mark as spam" C!["level-item", "buttons", "has-addons"], + button![ + C!["button"], + attrs! {At::Title => "Spam"}, + span![C!["icon", "is-small"], i![C!["far", "fa-hand"]]], + span!["Spam"], + ev(Ev::Click, move |_| Msg::AddTag( + format!("thread:{spam_thread_id}"), + "Spam".to_string() + )), + ], button![ C!["button"], attrs! {At::Title => "Mark as read"},