Compare commits
1 Commits
f6c1835b18
...
pretty
| Author | SHA1 | Date | |
|---|---|---|---|
| 98df04527d |
120
Cargo.lock
generated
120
Cargo.lock
generated
@@ -50,6 +50,54 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
@@ -222,18 +270,65 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.31"
|
||||
version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
||||
checksum = "95ed24df0632f708f5f6d8082675bef2596f7084dee3dd55f632290bf35bfe0f"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"time 0.1.45",
|
||||
"wasm-bindgen",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2 1.0.66",
|
||||
"quote 1.0.33",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "console_error_panic_hook"
|
||||
version = "0.1.7"
|
||||
@@ -936,6 +1031,12 @@ version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
@@ -1242,7 +1343,6 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
name = "letterbox"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
"css-inline",
|
||||
@@ -1525,6 +1625,8 @@ dependencies = [
|
||||
name = "notmuch"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"itertools",
|
||||
"log 0.4.20",
|
||||
"pretty_assertions",
|
||||
@@ -2592,6 +2694,12 @@ dependencies = [
|
||||
"quote 1.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.15.44"
|
||||
@@ -3050,6 +3158,12 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.4.1"
|
||||
|
||||
@@ -6,6 +6,8 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
clap = { version = "4.4.7", features = ["derive"] }
|
||||
log = "0.4.14"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0", features = ["unbounded_depth"] }
|
||||
|
||||
46
notmuch/src/main.rs
Normal file
46
notmuch/src/main.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use notmuch::Notmuch;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(propagate_version = true)]
|
||||
struct Cli {
|
||||
/// Optional notmuch config file
|
||||
#[arg(short, long)]
|
||||
config: Option<PathBuf>,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Show given search term
|
||||
Show { search_term: String },
|
||||
/// Search for given search term
|
||||
Search { search_term: String },
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let nm = if let Some(config_path) = cli.config {
|
||||
Notmuch::with_config(config_path)
|
||||
} else {
|
||||
Notmuch::default()
|
||||
};
|
||||
|
||||
// You can check for the existence of subcommands, and if found use their
|
||||
// matches just as you would the top level cmd
|
||||
match &cli.command {
|
||||
Commands::Search { search_term } => {
|
||||
println!("{:#?}", nm.search(&search_term, 0, 10)?);
|
||||
}
|
||||
Commands::Show { search_term } => {
|
||||
println!("{:#?}", nm.show(&search_term)?);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -7,7 +7,9 @@ pub fn threadset_to_messages(
|
||||
thread_set: notmuch::ThreadSet,
|
||||
) -> Result<Vec<Message>, error::ServerError> {
|
||||
for t in thread_set.0 {
|
||||
for tn in t.0 {}
|
||||
for tn in t.0 {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
@@ -9,6 +9,5 @@ pub struct SearchResult {
|
||||
pub results_per_page: usize,
|
||||
pub total: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Message {}
|
||||
|
||||
@@ -27,7 +27,6 @@ itertools = "0.10.5"
|
||||
serde_json = { version = "1.0.93", features = ["unbounded_depth"] }
|
||||
wasm-timer = "0.2.5"
|
||||
css-inline = "0.8.5"
|
||||
chrono = "0.4.31"
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = ['-Os']
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<link rel="stylesheet", href="https://jenil.github.io/bulmaswatch/cyborg/bulmaswatch.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" />
|
||||
<link rel="icon" href="https://static.xinu.tv/favicon/letterbox.svg" />
|
||||
<style>
|
||||
|
||||
.message {
|
||||
padding: 0.5em;*/
|
||||
}
|
||||
@@ -39,7 +37,7 @@ iframe {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 10em;
|
||||
width: 15em;
|
||||
}
|
||||
.index .subject {
|
||||
overflow: hidden;
|
||||
@@ -47,9 +45,8 @@ iframe {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.index .date {
|
||||
width: 6em;
|
||||
width: 8em;
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
}
|
||||
.footer {
|
||||
background-color: #eee;
|
||||
@@ -94,30 +91,6 @@ input, .input {
|
||||
input::placeholder, .input::placeholder{
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.search-results .row {
|
||||
border-bottom: 1px #444 solid;
|
||||
padding-bottom: .5em;
|
||||
padding-top: .5em;
|
||||
}
|
||||
.search-results .row .subject {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.search-results .row .from {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.search-results .row .tag {
|
||||
height: 1.5em;
|
||||
padding-left: .5em;
|
||||
padding-right: .5em;
|
||||
}
|
||||
.float-right {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ use std::{
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
use chrono::{DateTime, Datelike, Duration, Local, Utc};
|
||||
use itertools::Itertools;
|
||||
use log::{debug, error, info, Level};
|
||||
use notmuch::{Content, Part, Thread, ThreadNode, ThreadSet};
|
||||
@@ -98,7 +97,7 @@ mod urls {
|
||||
enum Context {
|
||||
None,
|
||||
Search(shared::SearchResult),
|
||||
Thread(ThreadSet),
|
||||
Thread(Vec<shared::Message>),
|
||||
}
|
||||
|
||||
// `Model` describes our app state.
|
||||
@@ -137,7 +136,7 @@ enum Msg {
|
||||
ShowRequest(String),
|
||||
ShowResult(fetch::Result<ThreadSet>),
|
||||
ShowPrettyRequest(String),
|
||||
ShowPrettyResult(fetch::Result<ThreadSet>),
|
||||
ShowPrettyResult(fetch::Result<Vec<shared::Message>>),
|
||||
NextPage,
|
||||
PreviousPage,
|
||||
}
|
||||
@@ -186,9 +185,10 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
.skip()
|
||||
.perform_cmd(async move { Msg::ShowResult(show_request(&tid).await) });
|
||||
}
|
||||
// TODO(wathiede): remove
|
||||
Msg::ShowResult(Ok(response_data)) => {
|
||||
debug!("fetch ok {:#?}", response_data);
|
||||
model.context = Context::Thread(response_data);
|
||||
//model.context = Context::Thread(response_data);
|
||||
}
|
||||
Msg::ShowResult(Err(fetch_error)) => {
|
||||
error!("fetch failed {:?}", fetch_error);
|
||||
@@ -200,7 +200,7 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
.perform_cmd(async move { Msg::ShowPrettyResult(show_pretty_request(&tid).await) });
|
||||
}
|
||||
Msg::ShowPrettyResult(Ok(response_data)) => {
|
||||
debug!("fetch ok {:#?}", response_data);
|
||||
info!("fetch ok {:#?}", response_data);
|
||||
model.context = Context::Thread(response_data);
|
||||
}
|
||||
Msg::ShowPrettyResult(Err(fetch_error)) => {
|
||||
@@ -289,7 +289,7 @@ async fn show_request(tid: &str) -> fetch::Result<ThreadSet> {
|
||||
.map_err(|_| FetchError::JsonError(fetch::JsonError::Serde(JsValue::NULL)))?)
|
||||
}
|
||||
|
||||
async fn show_pretty_request(tid: &str) -> fetch::Result<ThreadSet> {
|
||||
async fn show_pretty_request(tid: &str) -> fetch::Result<Vec<shared::Message>> {
|
||||
Request::new(api::show_pretty(tid))
|
||||
.method(Method::Get)
|
||||
.fetch()
|
||||
@@ -482,28 +482,6 @@ fn pretty_authors(authors: &str) -> impl Iterator<Item = Node<Msg>> + '_ {
|
||||
)
|
||||
}
|
||||
|
||||
fn human_age(timestamp: i64) -> String {
|
||||
let now = Local::now();
|
||||
let ts = DateTime::<Utc>::from_timestamp(timestamp, 0)
|
||||
.unwrap()
|
||||
.with_timezone(&Local);
|
||||
let age = now - ts;
|
||||
let weekday = now.weekday();
|
||||
let datetime = if age < Duration::minutes(1) {
|
||||
format!("{} ago", age.num_seconds())
|
||||
} else if age < Duration::hours(1) {
|
||||
format!("{} ago", age.num_minutes())
|
||||
} else if age < Duration::days(1) {
|
||||
ts.format("%H:%M").to_string()
|
||||
} else if age < Duration::weeks(1) {
|
||||
ts.format("%a %H:%M").to_string()
|
||||
} else {
|
||||
ts.format("%b %e").to_string()
|
||||
};
|
||||
info!("dateime {datetime} TZ: {}", ts.offset());
|
||||
datetime
|
||||
}
|
||||
|
||||
fn view_mobile_search_results(query: &str, search_results: &shared::SearchResult) -> Node<Msg> {
|
||||
if query.is_empty() {
|
||||
set_title("all mail");
|
||||
@@ -526,24 +504,21 @@ fn view_mobile_search_results(query: &str, search_results: &shared::SearchResult
|
||||
]
|
||||
*/
|
||||
let tid = r.thread.clone();
|
||||
let datetime = human_age(r.timestamp as i64);
|
||||
div![
|
||||
C!["row"],
|
||||
div![
|
||||
C!["subject"],
|
||||
&r.subject,
|
||||
ev(Ev::Click, move |_| Msg::ShowPrettyRequest(tid)),
|
||||
],
|
||||
span![C!["from", "is-size-7"], pretty_authors(&r.authors)],
|
||||
div![
|
||||
span![C!["is-size-7"], tags_chiclet(&r.tags, true)],
|
||||
span![C!["is-size-7", "float-right", "date"], datetime]
|
||||
]
|
||||
span![C!["from"], pretty_authors(&r.authors)],
|
||||
span![C!["tags"], tags_chiclet(&r.tags, true)],
|
||||
],
|
||||
span![C!["date"], &r.date_relative],
|
||||
]
|
||||
});
|
||||
let first = search_results.page * search_results.results_per_page;
|
||||
div![
|
||||
C!["search-results"],
|
||||
h1!["Search results"],
|
||||
view_search_pager(first, summaries.len(), search_results.total),
|
||||
rows,
|
||||
@@ -560,7 +535,6 @@ fn view_search_results(query: &str, search_results: &shared::SearchResult) -> No
|
||||
let summaries = &search_results.summary.0;
|
||||
let rows = summaries.iter().map(|r| {
|
||||
let tid = r.thread.clone();
|
||||
let datetime = human_age(r.timestamp as i64);
|
||||
tr![
|
||||
td![
|
||||
C!["from"],
|
||||
@@ -579,7 +553,7 @@ fn view_search_results(query: &str, search_results: &shared::SearchResult) -> No
|
||||
&r.subject,
|
||||
]
|
||||
],
|
||||
td![C!["date"], datetime]
|
||||
td![C!["date"], &r.date_relative]
|
||||
]
|
||||
});
|
||||
let first = search_results.page * search_results.results_per_page;
|
||||
@@ -629,29 +603,32 @@ fn view_search_pager(start: usize, count: usize, total: usize) -> Node<Msg> {
|
||||
]
|
||||
}
|
||||
|
||||
fn view_thread(thread_set: &ThreadSet) -> Node<Msg> {
|
||||
assert_eq!(thread_set.0.len(), 1);
|
||||
let thread = &thread_set.0[0];
|
||||
assert_eq!(thread.0.len(), 1);
|
||||
let thread_node = &thread.0[0];
|
||||
let subject = first_subject(&thread_node).unwrap_or("<No subject>".to_string());
|
||||
set_title(&subject);
|
||||
fn view_thread(messages: &[shared::Message]) -> Node<Msg> {
|
||||
div!["TODO(wathiede): view_thread(messages)"]
|
||||
/*
|
||||
assert_eq!(thread_set.0.len(), 1);
|
||||
let thread = &thread_set.0[0];
|
||||
assert_eq!(thread.0.len(), 1);
|
||||
let thread_node = &thread.0[0];
|
||||
let subject = first_subject(&thread_node).unwrap_or("<No subject>".to_string());
|
||||
set_title(&subject);
|
||||
div![
|
||||
C!["container"],
|
||||
h1![C!["title"], subject],
|
||||
view_message(&thread_node),
|
||||
a![
|
||||
attrs! {At::Href=>api::original(&thread_node.0.as_ref().expect("message missing").id)},
|
||||
"Original"
|
||||
],
|
||||
/*
|
||||
div![
|
||||
C!["container"],
|
||||
h1![C!["title"], subject],
|
||||
view_message(&thread_node),
|
||||
a![
|
||||
attrs! {At::Href=>api::original(&thread_node.0.as_ref().expect("message missing").id)},
|
||||
"Original"
|
||||
],
|
||||
/*
|
||||
div![
|
||||
C!["debug"],
|
||||
"Add zippy for debug dump",
|
||||
view_debug_thread_set(thread_set)
|
||||
] /* pre![format!("Thread: {:#?}", thread_set).replace(" ", " ")] */
|
||||
*/
|
||||
C!["debug"],
|
||||
"Add zippy for debug dump",
|
||||
view_debug_thread_set(thread_set)
|
||||
] /* pre![format!("Thread: {:#?}", thread_set).replace(" ", " ")] */
|
||||
*/
|
||||
]
|
||||
*/
|
||||
}
|
||||
|
||||
fn view_debug_thread_set(thread_set: &ThreadSet) -> Node<Msg> {
|
||||
@@ -759,7 +736,7 @@ fn view_footer(render_time_ms: u128) -> Node<Msg> {
|
||||
fn view_desktop(model: &Model) -> Node<Msg> {
|
||||
let content = match &model.context {
|
||||
Context::None => div![h1!["Loading"]],
|
||||
Context::Thread(thread_set) => view_thread(thread_set),
|
||||
Context::Thread(messages) => view_thread(messages),
|
||||
Context::Search(search_results) => view_search_results(&model.query, search_results),
|
||||
};
|
||||
div![
|
||||
@@ -772,7 +749,7 @@ fn view_desktop(model: &Model) -> Node<Msg> {
|
||||
fn view_mobile(model: &Model) -> Node<Msg> {
|
||||
let content = match &model.context {
|
||||
Context::None => div![h1!["Loading"]],
|
||||
Context::Thread(thread_set) => view_thread(thread_set),
|
||||
Context::Thread(messages) => view_thread(messages),
|
||||
Context::Search(search_results) => view_mobile_search_results(&model.query, search_results),
|
||||
};
|
||||
div![
|
||||
|
||||
Reference in New Issue
Block a user