Compare commits

..

No commits in common. "eba362a7f2f1799a7c3bb56881897426ca72958a" and "e7a0e5b66201260e47a67fa86953e3d3ec135d30" have entirely different histories.

11 changed files with 36 additions and 164 deletions

10
Cargo.lock generated
View File

@ -1185,7 +1185,6 @@ dependencies = [
"seed", "seed",
"serde", "serde",
"serde_json", "serde_json",
"shared",
"wasm-bindgen-test", "wasm-bindgen-test",
"wasm-timer", "wasm-timer",
"web-sys", "web-sys",
@ -2322,7 +2321,6 @@ dependencies = [
"rocket_cors", "rocket_cors",
"serde", "serde",
"serde_json", "serde_json",
"shared",
"thiserror", "thiserror",
"tokio", "tokio",
] ]
@ -2357,14 +2355,6 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "shared"
version = "0.1.0"
dependencies = [
"notmuch",
"serde",
]
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.0" version = "1.4.0"

View File

@ -4,7 +4,6 @@ members = [
"server", "server",
"notmuch", "notmuch",
"procmail2notmuch", "procmail2notmuch",
"shared"
] ]
[profile.release] [profile.release]

4
dev.sh
View File

@ -1,6 +1,6 @@
tmux new-session -d -s letterbox-dev tmux new-session -d -s letterbox-dev
tmux rename-window web tmux rename-window web
tmux send-keys "cd web; trunk serve --release --address 0.0.0.0 --port 6758 --proxy-backend http://localhost:9345/ --proxy-rewrite=/api/ -w ../shared -w ../notmuch -w ./" C-m tmux send-keys "cd web; trunk serve --release --address 0.0.0.0 --port 6758 --proxy-backend http://localhost:9345/ --proxy-rewrite=/api/" C-m
tmux new-window -n server tmux new-window -n server
tmux send-keys "cd server; cargo watch -x run -w ../shared -w ../notmuch -w ./" C-m tmux send-keys "cd server; cargo watch -x run" C-m
tmux attach -d -t letterbox-dev tmux attach -d -t letterbox-dev

View File

@ -478,19 +478,8 @@ impl Notmuch {
self.run_notmuch(std::iter::empty::<&str>()) self.run_notmuch(std::iter::empty::<&str>())
} }
pub fn search( pub fn search(&self, query: &str) -> Result<SearchSummary, NotmuchError> {
&self, let res = self.run_notmuch(["search", "--format=json", "--limit=20", query])?;
query: &str,
offset: usize,
limit: usize,
) -> Result<SearchSummary, NotmuchError> {
let res = self.run_notmuch([
"search",
"--format=json",
&format!("--offset={offset}"),
&format!("--limit={limit}"),
query,
])?;
Ok(serde_json::from_slice(&res)?) Ok(serde_json::from_slice(&res)?)
} }

View File

@ -9,7 +9,6 @@ edition = "2021"
rocket = { version = "0.5.0-rc.2", features = [ "json" ] } rocket = { version = "0.5.0-rc.2", features = [ "json" ] }
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", branch = "master" } rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", branch = "master" }
notmuch = { path = "../notmuch" } notmuch = { path = "../notmuch" }
shared = { path = "../shared" }
serde_json = "1.0.87" serde_json = "1.0.87"
thiserror = "1.0.37" thiserror = "1.0.37"
serde = { version = "1.0.147", features = ["derive"] } serde = { version = "1.0.147", features = ["derive"] }

View File

@ -22,30 +22,17 @@ fn hello() -> &'static str {
async fn refresh(nm: &State<Notmuch>) -> Result<Json<String>, Debug<NotmuchError>> { async fn refresh(nm: &State<Notmuch>) -> Result<Json<String>, Debug<NotmuchError>> {
Ok(Json(String::from_utf8_lossy(&nm.new()?).to_string())) Ok(Json(String::from_utf8_lossy(&nm.new()?).to_string()))
} }
#[get("/search")] #[get("/search")]
async fn search_all( async fn search_all(nm: &State<Notmuch>) -> Result<Json<SearchSummary>, Debug<NotmuchError>> {
nm: &State<Notmuch>, search(nm, "*").await
) -> Result<Json<shared::SearchResult>, Debug<NotmuchError>> {
search(nm, "*", None, None).await
} }
#[get("/search/<query>?<page>&<results_per_page>")] #[get("/search/<query>")]
async fn search( async fn search(
nm: &State<Notmuch>, nm: &State<Notmuch>,
query: &str, query: &str,
page: Option<usize>, ) -> Result<Json<SearchSummary>, Debug<NotmuchError>> {
results_per_page: Option<usize>, let res = nm.search(query)?;
) -> Result<Json<shared::SearchResult>, Debug<NotmuchError>> {
let page = page.unwrap_or(0);
let results_per_page = results_per_page.unwrap_or(10);
let res = shared::SearchResult {
summary: nm.search(query, page * results_per_page, results_per_page)?,
query: query.to_string(),
page,
results_per_page,
total: nm.count(query)?,
};
Ok(Json(res)) Ok(Json(res))
} }

View File

@ -1,10 +0,0 @@
[package]
name = "shared"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
notmuch = { path = "../notmuch" }
serde = { version = "1.0.147", features = ["derive"] }

View File

@ -1,11 +0,0 @@
use notmuch::SearchSummary;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct SearchResult {
pub summary: SearchSummary,
pub query: String,
pub page: usize,
pub results_per_page: usize,
pub total: usize,
}

View File

@ -22,7 +22,6 @@ seed = "0.9.2"
console_log = {git = "http://git-private.h.xinu.tv/wathiede/console_log.git"} console_log = {git = "http://git-private.h.xinu.tv/wathiede/console_log.git"}
serde = { version = "1.0.147", features = ["derive"] } serde = { version = "1.0.147", features = ["derive"] }
notmuch = {path = "../notmuch"} notmuch = {path = "../notmuch"}
shared = {path = "../shared"}
itertools = "0.10.5" itertools = "0.10.5"
serde_json = { version = "1.0.93", features = ["unbounded_depth"] } serde_json = { version = "1.0.93", features = ["unbounded_depth"] }
wasm-timer = "0.2.5" wasm-timer = "0.2.5"

View File

@ -37,7 +37,6 @@ iframe {
} }
.footer { .footer {
background-color: #eee; background-color: #eee;
color: #222;
position: fixed; position: fixed;
bottom: 0; bottom: 0;
left: 0; left: 0;
@ -72,12 +71,6 @@ iframe {
padding: 1.5em; padding: 1.5em;
} }
} }
input, .input {
color: #000;
}
input::placeholder, .input::placeholder{
color: #555;
}
</style> </style>
</head> </head>

View File

@ -9,7 +9,7 @@ use std::{
use itertools::Itertools; use itertools::Itertools;
use log::{debug, error, info, warn, Level}; use log::{debug, error, info, warn, Level};
use notmuch::{Content, Part, Thread, ThreadNode, ThreadSet}; use notmuch::{Content, Part, SearchSummary, Thread, ThreadNode, ThreadSet};
use seed::{prelude::*, *}; use seed::{prelude::*, *};
use serde::de::Deserialize; use serde::de::Deserialize;
use wasm_timer::Instant; use wasm_timer::Instant;
@ -57,7 +57,7 @@ fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
// ------ ------ // ------ ------
enum Context { enum Context {
None, None,
Search(shared::SearchResult), Search(SearchSummary),
Thread(ThreadSet), Thread(ThreadSet),
} }
@ -86,13 +86,11 @@ enum Msg {
RefreshStart, RefreshStart,
RefreshDone(Option<FetchError>), RefreshDone(Option<FetchError>),
SearchRequest(String), SearchRequest(String),
SearchResult(fetch::Result<shared::SearchResult>), SearchResult(fetch::Result<SearchSummary>),
ShowRequest(String), ShowRequest(String),
ShowResult(fetch::Result<ThreadSet>), ShowResult(fetch::Result<ThreadSet>),
ShowPrettyRequest(String), ShowPrettyRequest(String),
ShowPrettyResult(fetch::Result<ThreadSet>), ShowPrettyResult(fetch::Result<ThreadSet>),
NextPage,
PreviousPage,
} }
// `update` describes how to handle each `Msg`. // `update` describes how to handle each `Msg`.
@ -157,38 +155,10 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
Msg::ShowPrettyResult(Err(fetch_error)) => { Msg::ShowPrettyResult(Err(fetch_error)) => {
error!("fetch failed {:?}", fetch_error); error!("fetch failed {:?}", fetch_error);
} }
Msg::NextPage => {
match &model.context {
Context::Search(sr) => {
orders.send_msg(Msg::SearchRequest(format!(
"{}?page={}&results_per_page={}",
Url::encode_uri_component(&sr.query),
sr.page + 1,
sr.results_per_page
)));
}
Context::Thread(_) => (), // do nothing (yet?)
Context::None => (), // do nothing (yet?)
};
}
Msg::PreviousPage => {
match &model.context {
Context::Search(sr) => {
orders.send_msg(Msg::SearchRequest(format!(
"{}?page={}&results_per_page={}",
Url::encode_uri_component(&sr.query),
sr.page.saturating_sub(1),
sr.results_per_page
)));
}
Context::Thread(_) => (), // do nothing (yet?)
Context::None => (), // do nothing (yet?)
};
}
} }
} }
async fn search_request(query: &str) -> fetch::Result<shared::SearchResult> { async fn search_request(query: &str) -> fetch::Result<SearchSummary> {
Request::new(api::search(query)) Request::new(api::search(query))
.method(Method::Get) .method(Method::Get)
.fetch() .fetch()
@ -432,14 +402,13 @@ fn pretty_authors(authors: &str) -> impl Iterator<Item = Node<Msg>> + '_ {
) )
} }
fn view_mobile_search_results(query: &str, search_results: &shared::SearchResult) -> Node<Msg> { fn view_mobile_search_results(query: &str, search_results: &SearchSummary) -> Node<Msg> {
if query.is_empty() { if query.is_empty() {
set_title("all mail"); set_title("all mail");
} else { } else {
set_title(query); set_title(query);
} }
let summaries = &search_results.summary.0; let rows = search_results.0.iter().map(|r| {
let rows = summaries.iter().map(|r| {
/* /*
let tid = r.thread.clone(); let tid = r.thread.clone();
tr![ tr![
@ -468,22 +437,16 @@ fn view_mobile_search_results(query: &str, search_results: &shared::SearchResult
hr![], hr![],
] ]
}); });
let first = search_results.page * search_results.results_per_page; div![h1!["Search results"], rows]
div![
h1!["Search results"],
view_search_pager(first, summaries.len(), search_results.total),
rows
]
} }
fn view_search_results(query: &str, search_results: &shared::SearchResult) -> Node<Msg> { fn view_search_results(query: &str, search_results: &SearchSummary) -> Node<Msg> {
if query.is_empty() { if query.is_empty() {
set_title("all mail"); set_title("all mail");
} else { } else {
set_title(query); set_title(query);
} }
let summaries = &search_results.summary.0; let rows = search_results.0.iter().map(|r| {
let rows = summaries.iter().map(|r| {
let tid = r.thread.clone(); let tid = r.thread.clone();
tr![ tr![
td![ td![
@ -503,10 +466,7 @@ fn view_search_results(query: &str, search_results: &shared::SearchResult) -> No
td![C!["date"], &r.date_relative] td![C!["date"], &r.date_relative]
] ]
}); });
let first = search_results.page * search_results.results_per_page; div![table![
div![
view_search_pager(first, summaries.len(), search_results.total),
table![
C![ C![
"table", "table",
"index", "index",
@ -521,28 +481,7 @@ fn view_search_results(query: &str, search_results: &shared::SearchResult) -> No
th![C!["date"], "Date"] th![C!["date"], "Date"]
]], ]],
tbody![rows] tbody![rows]
] ]]
]
}
fn view_search_pager(page: usize, count: usize, total: usize) -> Node<Msg> {
nav![
C!["pagination"],
a![
C!["pagination-previous", "button"],
"<",
ev(Ev::Click, |_| Msg::PreviousPage)
],
a![
C!["pagination-next", "button"],
">",
ev(Ev::Click, |_| Msg::NextPage)
],
ul![
C!["pagination-list"],
li![format!("{} - {} of {}", page, page + count, total)],
],
]
} }
fn view_thread(thread_set: &ThreadSet) -> Node<Msg> { fn view_thread(thread_set: &ThreadSet) -> Node<Msg> {
@ -602,7 +541,7 @@ fn view_header(query: &str, refresh_request: &RefreshingState) -> Node<Msg> {
} else { } else {
false false
}; };
let query = Url::decode_uri_component(query).unwrap_or("".to_string()); let query = query.to_string();
nav![ nav![
C!["navbar"], C!["navbar"],
attrs! {At::Role=>"navigation"}, attrs! {At::Role=>"navigation"},
@ -635,12 +574,10 @@ fn view_header(query: &str, refresh_request: &RefreshingState) -> Node<Msg> {
At::AutoFocus => true.as_at_value(); At::AutoFocus => true.as_at_value();
At::Value => query, At::Value => query,
}, },
input_ev(Ev::Input, |q| Msg::SearchRequest( input_ev(Ev::Input, Msg::SearchRequest),
Url::encode_uri_component(q)
)),
// Resend search on enter. // Resend search on enter.
keyboard_ev(Ev::KeyUp, move |e| if e.key_code() == 0x0d { keyboard_ev(Ev::KeyUp, move |e| if e.key_code() == 0x0d {
Msg::SearchRequest(Url::encode_uri_component(query)) Msg::SearchRequest(query)
} else { } else {
Msg::Noop Msg::Noop
}), }),