Compare commits
3 Commits
331fb4f11b
...
831466ddda
| Author | SHA1 | Date | |
|---|---|---|---|
| 831466ddda | |||
| 4ee34444ae | |||
| 879ddb112e |
15
server/.sqlx/query-b39147b9d06171cb742141eda4675688cb702fb284758b1224ed3aa2d7f3b3d9.json
generated
Normal file
15
server/.sqlx/query-b39147b9d06171cb742141eda4675688cb702fb284758b1224ed3aa2d7f3b3d9.json
generated
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE\n post\nSET\n is_read = $1\nWHERE\n uid = $2\n",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Bool",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b39147b9d06171cb742141eda4675688cb702fb284758b1224ed3aa2d7f3b3d9"
|
||||
}
|
||||
6
server/sql/set_unread.sql
Normal file
6
server/sql/set_unread.sql
Normal file
@ -0,0 +1,6 @@
|
||||
UPDATE
|
||||
post
|
||||
SET
|
||||
is_read = $1
|
||||
WHERE
|
||||
uid = $2
|
||||
@ -1,7 +1,6 @@
|
||||
|
||||
use async_graphql::{
|
||||
connection::{Connection},
|
||||
Context, EmptySubscription, Enum, Error, FieldResult, Object, Schema, SimpleObject, Union,
|
||||
connection::Connection, Context, EmptySubscription, Enum, Error, FieldResult, Object, Schema,
|
||||
SimpleObject, Union,
|
||||
};
|
||||
use log::info;
|
||||
use notmuch::Notmuch;
|
||||
@ -273,11 +272,14 @@ impl Mutation {
|
||||
unread: bool,
|
||||
) -> Result<bool, Error> {
|
||||
let nm = ctx.data_unchecked::<Notmuch>();
|
||||
info!("set_read_status({unread})");
|
||||
if unread {
|
||||
nm.tag_add("unread", &format!("{query}"))?;
|
||||
} else {
|
||||
nm.tag_remove("unread", &format!("{query}"))?;
|
||||
let pool = ctx.data_unchecked::<PgPool>();
|
||||
|
||||
for q in query.split_whitespace() {
|
||||
if newsreader::is_newsreader_thread(&q) {
|
||||
newsreader::set_read_status(pool, &q, unread).await?;
|
||||
} else {
|
||||
nm::set_read_status(nm, q, unread).await?;
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@ -44,7 +44,6 @@ pub async fn search(
|
||||
query: String,
|
||||
) -> Result<Connection<usize, ThreadSummary>, async_graphql::Error> {
|
||||
let query: Query = query.parse()?;
|
||||
info!("news search query {query:?}");
|
||||
let site = query.site.expect("search has no site");
|
||||
connection::query(
|
||||
after,
|
||||
@ -52,7 +51,6 @@ pub async fn search(
|
||||
first,
|
||||
last,
|
||||
|after: Option<usize>, before: Option<usize>, first, last| async move {
|
||||
info!("search page info {after:#?}, {before:#?}, {first:#?}, {last:#?}");
|
||||
let default_page_size = 100;
|
||||
let (offset, limit) = match (after, before, first, last) {
|
||||
// Reasonable defaults
|
||||
@ -86,7 +84,6 @@ pub async fn search(
|
||||
// The +1 is to see if there are more pages of data available.
|
||||
let limit = limit + 1;
|
||||
|
||||
info!("search page offset {offset} limit {limit}");
|
||||
let rows = sqlx::query_file!(
|
||||
"sql/threads.sql",
|
||||
site,
|
||||
@ -214,7 +211,7 @@ pub async fn thread(pool: &PgPool, thread_id: String) -> Result<Thread, ServerEr
|
||||
let html = r.summary.unwrap_or("NO SUMMARY".to_string());
|
||||
// TODO: add site specific cleanups. For example:
|
||||
// * 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. imperialviolet
|
||||
// * Some sites appear to be HTML encoded, unencode them, i.e. imperialviolent
|
||||
let html = sanitize_html(&html, "", &link)?;
|
||||
let body = Body::Html(Html {
|
||||
html,
|
||||
@ -253,6 +250,7 @@ pub async fn thread(pool: &PgPool, thread_id: String) -> Result<Thread, ServerEr
|
||||
struct Query {
|
||||
unread_only: bool,
|
||||
site: Option<String>,
|
||||
uid: Option<String>,
|
||||
remainder: Vec<String>,
|
||||
}
|
||||
|
||||
@ -261,6 +259,7 @@ impl FromStr for Query {
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut unread_only = false;
|
||||
let mut site = None;
|
||||
let mut uid = None;
|
||||
let mut remainder = Vec::new();
|
||||
let site_prefix = format!("tag:{TAG_PREFIX}");
|
||||
for word in s.split_whitespace() {
|
||||
@ -268,6 +267,8 @@ impl FromStr for Query {
|
||||
unread_only = true
|
||||
} else if word.starts_with(&site_prefix) {
|
||||
site = Some(word[site_prefix.len()..].to_string())
|
||||
} else if word.starts_with(THREAD_PREFIX) {
|
||||
uid = Some(word[THREAD_PREFIX.len()..].to_string())
|
||||
} else {
|
||||
remainder.push(word.to_string());
|
||||
}
|
||||
@ -275,7 +276,20 @@ impl FromStr for Query {
|
||||
Ok(Query {
|
||||
unread_only,
|
||||
site,
|
||||
uid,
|
||||
remainder,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_read_status<'ctx>(
|
||||
pool: &PgPool,
|
||||
query: &str,
|
||||
unread: bool,
|
||||
) -> Result<bool, ServerError> {
|
||||
let query: Query = query.parse()?;
|
||||
sqlx::query_file!("sql/set_unread.sql", !unread, query.uid)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@ -80,7 +80,7 @@ pub async fn search(
|
||||
.0
|
||||
.into_iter()
|
||||
.map(|ts| ThreadSummary {
|
||||
thread: ts.thread,
|
||||
thread: format!("thread:{}", ts.thread),
|
||||
timestamp: ts.timestamp,
|
||||
date_relative: ts.date_relative,
|
||||
matched: ts.matched,
|
||||
@ -248,7 +248,7 @@ pub async fn thread(
|
||||
// TODO(wathiede): parse message and fill out attachments
|
||||
let attachments = extract_attachments(&m, &id)?;
|
||||
messages.push(Message {
|
||||
id,
|
||||
id: format!("id:{id}"),
|
||||
from,
|
||||
to,
|
||||
cc,
|
||||
@ -752,3 +752,16 @@ fn render_content_type_tree(m: &ParsedMail) -> String {
|
||||
SKIP_HEADERS.join("\n ")
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn set_read_status<'ctx>(
|
||||
nm: &Notmuch,
|
||||
query: &str,
|
||||
unread: bool,
|
||||
) -> Result<bool, ServerError> {
|
||||
if unread {
|
||||
nm.tag_add("unread", &format!("{query}"))?;
|
||||
} else {
|
||||
nm.tag_remove("unread", &format!("{query}"))?;
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@ -372,7 +372,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
{
|
||||
let threads = selected_threads
|
||||
.iter()
|
||||
.map(|tid| format!("thread:{tid}"))
|
||||
.map(|tid| tid.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
orders
|
||||
@ -387,7 +387,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
{
|
||||
let threads = selected_threads
|
||||
.iter()
|
||||
.map(|tid| format!("thread:{tid}"))
|
||||
.map(|tid| tid.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
orders
|
||||
@ -402,7 +402,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
{
|
||||
let threads = selected_threads
|
||||
.iter()
|
||||
.map(|tid| format!("thread:{tid}"))
|
||||
.map(|tid| tid.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
orders
|
||||
@ -417,7 +417,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
{
|
||||
let threads = selected_threads
|
||||
.iter()
|
||||
.map(|tid| format!("thread:{tid}"))
|
||||
.map(|tid| tid.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
orders
|
||||
|
||||
@ -73,6 +73,7 @@ fn removable_tags_chiclet<'a>(
|
||||
"is-grouped-multiline"
|
||||
],
|
||||
tags.iter().map(move |tag| {
|
||||
let thread_id = thread_id.to_string();
|
||||
let hex = compute_color(tag);
|
||||
let style = style! {St::BackgroundColor=>hex};
|
||||
let classes = C!["tag", IF!(is_mobile => "is-small")];
|
||||
@ -81,7 +82,6 @@ fn removable_tags_chiclet<'a>(
|
||||
};
|
||||
let tag = tag.clone();
|
||||
let rm_tag = tag.clone();
|
||||
let thread_id = format!("thread:{thread_id}");
|
||||
div![
|
||||
C!["control"],
|
||||
div![
|
||||
@ -592,7 +592,7 @@ fn render_open_header(msg: &ShowThreadQueryThreadMessages) -> Node<Msg> {
|
||||
],
|
||||
ev(Ev::Click, move |e| {
|
||||
e.stop_propagation();
|
||||
Msg::SetUnread(format!("id:{id}"), !is_unread)
|
||||
Msg::SetUnread(id, !is_unread)
|
||||
})
|
||||
]
|
||||
]
|
||||
@ -664,7 +664,7 @@ fn render_closed_header(msg: &ShowThreadQueryThreadMessages) -> Node<Msg> {
|
||||
],
|
||||
ev(Ev::Click, move |e| {
|
||||
e.stop_propagation();
|
||||
Msg::SetUnread(format!("id:{id}"), !is_unread)
|
||||
Msg::SetUnread(id, !is_unread)
|
||||
})
|
||||
]
|
||||
]
|
||||
@ -808,7 +808,8 @@ fn thread(
|
||||
});
|
||||
let read_thread_id = thread.thread_id.clone();
|
||||
let unread_thread_id = thread.thread_id.clone();
|
||||
let spam_thread_id = thread.thread_id.clone();
|
||||
let spam_add_thread_id = thread.thread_id.clone();
|
||||
let spam_unread_thread_id = thread.thread_id.clone();
|
||||
div![
|
||||
C!["thread"],
|
||||
h3![C!["is-size-5"], subject],
|
||||
@ -827,20 +828,14 @@ fn thread(
|
||||
attrs! {At::Title => "Mark as read"},
|
||||
span![C!["icon", "is-small"], i![C!["far", "fa-envelope-open"]]],
|
||||
IF!(show_icon_text=>span!["Read"]),
|
||||
ev(Ev::Click, move |_| Msg::SetUnread(
|
||||
format!("thread:{read_thread_id}"),
|
||||
false
|
||||
)),
|
||||
ev(Ev::Click, move |_| Msg::SetUnread(read_thread_id, false)),
|
||||
],
|
||||
button![
|
||||
C!["button", "mark-unread"],
|
||||
attrs! {At::Title => "Mark as unread"},
|
||||
span![C!["icon", "is-small"], i![C!["far", "fa-envelope"]]],
|
||||
IF!(show_icon_text=>span!["Unread"]),
|
||||
ev(Ev::Click, move |_| Msg::SetUnread(
|
||||
format!("thread:{unread_thread_id}"),
|
||||
true
|
||||
)),
|
||||
ev(Ev::Click, move |_| Msg::SetUnread(unread_thread_id, true)),
|
||||
],
|
||||
],
|
||||
],
|
||||
@ -854,8 +849,8 @@ fn thread(
|
||||
span![C!["icon", "is-small"], i![C!["far", "fa-hand"]]],
|
||||
IF!(show_icon_text=>span!["Spam"]),
|
||||
ev(Ev::Click, move |_| Msg::MultiMsg(vec![
|
||||
Msg::AddTag(format!("thread:{spam_thread_id}"), "Spam".to_string()),
|
||||
Msg::SetUnread(format!("thread:{spam_thread_id}"), false)
|
||||
Msg::AddTag(spam_add_thread_id, "Spam".to_string()),
|
||||
Msg::SetUnread(spam_unread_thread_id, false)
|
||||
])),
|
||||
],
|
||||
],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user