Compare commits

..

No commits in common. "d8275debdc544cf3641abe3508a9c209068b7f2f" and "19ee6f338dd0ef96c3671609517692af60177b63" have entirely different histories.

5 changed files with 38 additions and 126 deletions

6
Cargo.lock generated
View File

@ -975,12 +975,10 @@ version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"console_log",
"itertools",
"log 0.4.17",
"notmuch",
"seed",
"serde",
"serde_json",
"wasm-bindgen-test",
]
@ -1783,9 +1781,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.93"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
dependencies = [
"itoa",
"ryu",

View File

@ -425,14 +425,14 @@ pub struct SearchTags(pub Vec<String>);
pub struct ThreadSummary {
pub thread: ThreadId,
pub timestamp: UnixTime,
/// user-friendly timestamp
pub date_relative: String,
/// number of matched messages
/// user-friendly timestamp
pub matched: isize,
/// total messages in thread
/// number of matched messages
pub total: isize,
/// comma-separated names with | between matched and unmatched
/// total messages in thread
pub authors: String,
/// comma-separated names with | between matched and unmatched
pub subject: String,
pub tags: Vec<String>,
@ -541,8 +541,6 @@ impl Notmuch {
Ok(BufReader::new(child.stdout.take().unwrap()).lines())
}
// TODO(wathiede): implement tags() based on "notmuch search --output=tags '*'"
fn run_notmuch<I, S>(&self, args: I) -> Result<Vec<u8>, NotmuchError>
where
I: IntoIterator<Item = S>,

View File

@ -22,8 +22,6 @@ seed = "0.9.2"
console_log = {git = "http://git-private.h.xinu.tv/wathiede/console_log.git"}
serde = { version = "1.0.147", features = ["derive"] }
notmuch = {path = "../notmuch"}
itertools = "0.10.5"
serde_json = { version = "1.0.93", features = ["unbounded_depth"] }
[package.metadata.wasm-pack.profile.release]
wasm-opt = ['-Os']

View File

@ -6,15 +6,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="modulepreload" href="/pkg/package.js" as="script" type="text/javascript">
<link rel="preload" href="/pkg/package_bg.wasm" as="fetch" type="application/wasm" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css" integrity="sha512-SzlrxWUlpfuzQ+pcUCosxcglQRNAq/DZjVsC0lE40xsADsfeQoEypE+enwcOiGjk/bSuGGKHEyjSoQ1zVisanQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style>
.message {
padding-left: 0.5em;
}
.body {
padding-bottom: 1em;
border: 1px red solid;
}
.error {
background-color: red;
@ -26,14 +23,6 @@ iframe {
height: 100%;
width: 100%;
}
.index .from {
width: 200px;
}
.index .subject {
}
.index .date {
white-space: nowrap;
}
</style>
</head>

View File

@ -3,11 +3,9 @@
// but some rules are too "annoying" or are not applicable for your case.)
#![allow(clippy::wildcard_imports)]
use itertools::Itertools;
use log::{debug, error, info, warn, Level};
use notmuch::{Content, Part, SearchSummary, Thread, ThreadNode, ThreadSet};
use seed::{prelude::*, *};
use serde::de::Deserialize;
// ------ ------
// Init
@ -114,6 +112,7 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
}
async fn search_request(query: &str) -> fetch::Result<SearchSummary> {
info!("making search request for '{}'", query);
Request::new(api::search(query))
.method(Method::Get)
.fetch()
@ -137,17 +136,13 @@ mod api {
}
async fn show_request(tid: &str) -> fetch::Result<ThreadSet> {
let b = Request::new(api::show(tid))
Request::new(api::show(tid))
.method(Method::Get)
.fetch()
.await?
.check_status()?
.bytes()
.await?;
let mut deserializer = serde_json::Deserializer::from_slice(&b);
deserializer.disable_recursion_limit();
Ok(ThreadSet::deserialize(&mut deserializer)
.map_err(|_| FetchError::JsonError(fetch::JsonError::Serde(JsValue::NULL)))?)
.json()
.await
}
// ------ ------
@ -272,42 +267,6 @@ fn set_title(title: &str) {
seed::document().set_title(&format!("lb: {}", title));
}
fn tags_chiclet(tags: &[String]) -> impl Iterator<Item = Node<Msg>> + '_ {
tags.iter().map(|tag| match tag.as_str() {
"attachment" => span![C!["tag"], "📎"],
"replied" => span![C!["tag"], i![C!["fa-solid", "fa-reply"]]],
_ => span![C!["tag"], tag],
})
}
fn pretty_authors(authors: &str) -> impl Iterator<Item = Node<Msg>> + '_ {
let authors = authors.split(',');
/*
if authors.len() == 1 {
return authors.iter().filter_map(|author| {
Some(span![
attrs! {
At::Title => author.trim()},
author
])
});
}
*/
authors
.filter_map(|author| {
author.split_whitespace().nth(0).map(|first| {
span![
attrs! {
At::Title => author.trim()},
first
]
})
})
.intersperse(span![", "])
}
fn view_search_results(query: &str, search_results: &SearchSummary) -> Node<Msg> {
if query.is_empty() {
set_title("all mail");
@ -317,31 +276,20 @@ fn view_search_results(query: &str, search_results: &SearchSummary) -> Node<Msg>
let rows = search_results.0.iter().map(|r| {
let tid = r.thread.clone();
tr![
td![],
td![
C!["from"],
pretty_authors(&r.authors),
&r.authors,
IF!(r.total>1 => small![" ", r.total.to_string()]),
IF!(r.tags.contains(&"attachment".to_string()) => "📎"),
],
td![C!["subject"], tags_chiclet(&r.tags), " ", &r.subject],
td![C!["date"], &r.date_relative],
td![&r.subject],
td![&r.date_relative],
ev(Ev::Click, move |_| Msg::ShowRequest(tid)),
]
});
div![table![
C![
"table",
"index",
"is-fullwidth",
"is-hoverable",
"is-narrow",
"is-striped",
],
thead![tr![
th![C!["from"], "From"],
th![C!["subject"], "Subject"],
th![C!["date"], "Date"]
]],
tbody![rows]
tr![th!["tid"], th!["From"], th!["Subject"], th!["Date"]],
rows
]]
}
@ -395,25 +343,13 @@ fn view_debug_thread_node(thread_node: &ThreadNode) -> Node<Msg> {
fn view_header(query: &str) -> Node<Msg> {
let query = query.to_string();
nav![
C!["navbar"],
attrs! {At::Role=>"navigation"},
div![
C!["navbar-menu"],
div![
C!["navbar-start"],
a![
C!["navbar-item", "button",],
button![
"Unread",
ev(Ev::Click, |_| Msg::SearchRequest("is:unread".to_string())),
],
a![
C!["navbar-item", "button"],
"All",
ev(Ev::Click, |_| Msg::SearchRequest("".to_string())),
],
button!["All", ev(Ev::Click, |_| Msg::SearchRequest("".to_string())),],
input![
C!["navbar-item", "input"],
attrs! {
At::Placeholder => "Search";
At::AutoFocus => true.as_at_value();
@ -426,25 +362,18 @@ fn view_header(query: &str) -> Node<Msg> {
} else {
Msg::Noop
}),
]
]
]
],
]
}
// `view` describes what to display.
fn view(model: &Model) -> Node<Msg> {
info!("view called");
let content = match &model.context {
Context::None => div![h1!["Loading"]],
Context::Thread(thread_set) => view_thread(thread_set),
Context::Search(search_results) => view_search_results(&model.query, search_results),
};
div![section![
C!["section"],
view_header(&model.query),
div![C!["container"], content]
]]
div![view_header(&model.query), content]
}
// ------ ------