web: rewrite frontend to use graphql for search results
This commit is contained in:
parent
3e3024dd5c
commit
0737f5aac5
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -97,6 +97,7 @@ dependencies = [
|
|||||||
"handlebars",
|
"handlebars",
|
||||||
"http",
|
"http",
|
||||||
"indexmap 2.0.0",
|
"indexmap 2.0.0",
|
||||||
|
"log 0.4.20",
|
||||||
"mime 0.3.17",
|
"mime 0.3.17",
|
||||||
"multer",
|
"multer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
@ -1545,6 +1546,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"shared",
|
"shared",
|
||||||
|
"thiserror",
|
||||||
"wasm-bindgen-test",
|
"wasm-bindgen-test",
|
||||||
"wasm-timer",
|
"wasm-timer",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
@ -3072,18 +3074,18 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.47"
|
version = "1.0.50"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
|
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.47"
|
version = "1.0.50"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
|
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.66",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.33",
|
"quote 1.0.33",
|
||||||
|
|||||||
@ -29,6 +29,7 @@ wasm-timer = "0.2.5"
|
|||||||
css-inline = "0.8.5"
|
css-inline = "0.8.5"
|
||||||
chrono = "0.4.31"
|
chrono = "0.4.31"
|
||||||
graphql_client = "0.13.0"
|
graphql_client = "0.13.0"
|
||||||
|
thiserror = "1.0.50"
|
||||||
|
|
||||||
[package.metadata.wasm-pack.profile.release]
|
[package.metadata.wasm-pack.profile.release]
|
||||||
wasm-opt = ['-Os']
|
wasm-opt = ['-Os']
|
||||||
|
|||||||
@ -9,3 +9,7 @@ port = 6758
|
|||||||
[[proxy]]
|
[[proxy]]
|
||||||
backend = "http://localhost:9345/"
|
backend = "http://localhost:9345/"
|
||||||
rewrite= "/api/"
|
rewrite= "/api/"
|
||||||
|
[[proxy]]
|
||||||
|
backend="http://localhost:9345/graphiql"
|
||||||
|
[[proxy]]
|
||||||
|
backend="http://localhost:9345/graphql"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
query FrontPageQuery($query: [String!], $first: [Int], $after: [String]) {
|
query FrontPageQuery($query: String!, $after: String $before: String, $first: Int, $last: Int) {
|
||||||
count(query: $query)
|
count(query: $query)
|
||||||
search(query: $query, first: $first, after: $after) {
|
search(query: $query, after: $after, before: $before, first: $first, last: $last) {
|
||||||
pageInfo {
|
pageInfo {
|
||||||
hasPreviousPage
|
hasPreviousPage
|
||||||
hasNextPage
|
hasNextPage
|
||||||
|
|||||||
@ -125,6 +125,19 @@ blockquote[type="cite"],
|
|||||||
background-color: red;
|
background-color: red;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.desktop-main-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 12rem 1fr;
|
||||||
|
}
|
||||||
|
.tags-menu {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
.tags-menu .menu-list a {
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
}
|
||||||
|
.navbar {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@ -15,14 +15,14 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
|||||||
)]
|
)]
|
||||||
pub struct FrontPageQuery;
|
pub struct FrontPageQuery;
|
||||||
|
|
||||||
async fn send_graphql<Body, Resp>(body: Body) -> fetch::Result<graphql_client::Response<Resp>>
|
pub async fn send_graphql<Body, Resp>(body: Body) -> fetch::Result<graphql_client::Response<Resp>>
|
||||||
where
|
where
|
||||||
Body: Serialize,
|
Body: Serialize,
|
||||||
Resp: DeserializeOwned + 'static,
|
Resp: DeserializeOwned + 'static,
|
||||||
{
|
{
|
||||||
use web_sys::RequestMode;
|
use web_sys::RequestMode;
|
||||||
|
|
||||||
Request::new("/graphql")
|
Request::new("/graphql/")
|
||||||
.method(Method::Post)
|
.method(Method::Post)
|
||||||
.header(Header::content_type("application/json"))
|
.header(Header::content_type("application/json"))
|
||||||
.mode(RequestMode::Cors)
|
.mode(RequestMode::Cors)
|
||||||
|
|||||||
348
web/src/lib.rs
348
web/src/lib.rs
@ -8,16 +8,33 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use chrono::{DateTime, Duration, Local, Utc};
|
use chrono::{DateTime, Duration, Local, Utc};
|
||||||
|
use graphql_client::GraphQLQuery;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::{debug, error, info, Level};
|
use log::{debug, error, info, Level};
|
||||||
use notmuch::{Content, Part, Thread, ThreadNode, ThreadSet};
|
use notmuch::{Content, Part, Thread, ThreadNode, ThreadSet};
|
||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
use serde::de::Deserialize;
|
use serde::de::Deserialize;
|
||||||
|
use thiserror::Error;
|
||||||
use wasm_timer::Instant;
|
use wasm_timer::Instant;
|
||||||
|
|
||||||
|
use crate::graphql::{front_page_query::*, send_graphql};
|
||||||
|
|
||||||
mod graphql;
|
mod graphql;
|
||||||
|
|
||||||
const SEARCH_RESULTS_PER_PAGE: usize = 20;
|
const SEARCH_RESULTS_PER_PAGE: usize = 20;
|
||||||
|
const USE_GRAPHQL: bool = true;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
enum UIError {
|
||||||
|
#[error("No error, this should never be presented to user")]
|
||||||
|
NoError,
|
||||||
|
#[error("failed to fetch {0}: {1:?}")]
|
||||||
|
FetchError(&'static str, FetchError),
|
||||||
|
#[error("{0} error decoding: {1:?}")]
|
||||||
|
FetchDecodeError(&'static str, Vec<graphql_client::Error>),
|
||||||
|
#[error("no data or errors for {0}")]
|
||||||
|
NoData(&'static str),
|
||||||
|
}
|
||||||
|
|
||||||
// ------ ------
|
// ------ ------
|
||||||
// Init
|
// Init
|
||||||
@ -36,6 +53,8 @@ fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
|||||||
context: Context::None,
|
context: Context::None,
|
||||||
query: "".to_string(),
|
query: "".to_string(),
|
||||||
refreshing_state: RefreshingState::None,
|
refreshing_state: RefreshingState::None,
|
||||||
|
ui_error: UIError::NoError,
|
||||||
|
tags: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,25 +70,54 @@ fn on_url_changed(uc: subs::UrlChanged) -> Msg {
|
|||||||
["t", tid] => Msg::ShowPrettyRequest(tid.to_string()),
|
["t", tid] => Msg::ShowPrettyRequest(tid.to_string()),
|
||||||
["s", query] => {
|
["s", query] => {
|
||||||
let query = Url::decode_uri_component(query).unwrap_or("".to_string());
|
let query = Url::decode_uri_component(query).unwrap_or("".to_string());
|
||||||
|
if USE_GRAPHQL {
|
||||||
|
Msg::FrontPageRequest {
|
||||||
|
query,
|
||||||
|
after: None,
|
||||||
|
before: None,
|
||||||
|
first: None,
|
||||||
|
last: None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
Msg::SearchRequest {
|
Msg::SearchRequest {
|
||||||
query,
|
query,
|
||||||
page: 0,
|
page: 0,
|
||||||
results_per_page: SEARCH_RESULTS_PER_PAGE,
|
results_per_page: SEARCH_RESULTS_PER_PAGE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
["s", query, page] => {
|
["s", query, page] => {
|
||||||
let query = Url::decode_uri_component(query).unwrap_or("".to_string());
|
let query = Url::decode_uri_component(query).unwrap_or("".to_string());
|
||||||
let page = page[1..].parse().unwrap_or(0);
|
let page = page[1..].parse().unwrap_or(0);
|
||||||
|
if USE_GRAPHQL {
|
||||||
|
Msg::FrontPageRequest {
|
||||||
|
query,
|
||||||
|
after: Some(page.to_string()),
|
||||||
|
before: None,
|
||||||
|
first: None,
|
||||||
|
last: None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
Msg::SearchRequest {
|
Msg::SearchRequest {
|
||||||
query,
|
query,
|
||||||
page,
|
page,
|
||||||
results_per_page: SEARCH_RESULTS_PER_PAGE,
|
results_per_page: SEARCH_RESULTS_PER_PAGE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
p => {
|
p => {
|
||||||
if !p.is_empty() {
|
if !p.is_empty() {
|
||||||
info!("Unhandled path '{p:?}'");
|
info!("Unhandled path '{p:?}'");
|
||||||
}
|
}
|
||||||
|
if USE_GRAPHQL {
|
||||||
|
Msg::FrontPageRequest {
|
||||||
|
query: "".to_string(),
|
||||||
|
after: None,
|
||||||
|
before: None,
|
||||||
|
first: None,
|
||||||
|
last: None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
Msg::SearchRequest {
|
Msg::SearchRequest {
|
||||||
query: "".to_string(),
|
query: "".to_string(),
|
||||||
page: 0,
|
page: 0,
|
||||||
@ -78,6 +126,7 @@ fn on_url_changed(uc: subs::UrlChanged) -> Msg {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod urls {
|
mod urls {
|
||||||
use seed::Url;
|
use seed::Url;
|
||||||
@ -100,6 +149,12 @@ mod urls {
|
|||||||
enum Context {
|
enum Context {
|
||||||
None,
|
None,
|
||||||
Search(shared::SearchResult),
|
Search(shared::SearchResult),
|
||||||
|
SearchResult {
|
||||||
|
query: String,
|
||||||
|
results: Vec<FrontPageQuerySearchNodes>,
|
||||||
|
count: usize,
|
||||||
|
pager: FrontPageQuerySearchPageInfo,
|
||||||
|
},
|
||||||
Thread(ThreadSet),
|
Thread(ThreadSet),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,6 +163,8 @@ struct Model {
|
|||||||
query: String,
|
query: String,
|
||||||
context: Context,
|
context: Context,
|
||||||
refreshing_state: RefreshingState,
|
refreshing_state: RefreshingState,
|
||||||
|
ui_error: UIError,
|
||||||
|
tags: Option<Vec<crate::graphql::front_page_query::FrontPageQueryTags>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@ -142,6 +199,17 @@ enum Msg {
|
|||||||
ShowPrettyResult(fetch::Result<ThreadSet>),
|
ShowPrettyResult(fetch::Result<ThreadSet>),
|
||||||
NextPage,
|
NextPage,
|
||||||
PreviousPage,
|
PreviousPage,
|
||||||
|
|
||||||
|
FrontPageRequest {
|
||||||
|
query: String,
|
||||||
|
after: Option<String>,
|
||||||
|
before: Option<String>,
|
||||||
|
first: Option<i64>,
|
||||||
|
last: Option<i64>,
|
||||||
|
},
|
||||||
|
FrontPageResult(
|
||||||
|
fetch::Result<graphql_client::Response<graphql::front_page_query::ResponseData>>,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
// `update` describes how to handle each `Msg`.
|
// `update` describes how to handle each `Msg`.
|
||||||
@ -213,6 +281,19 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
Context::Search(sr) => {
|
Context::Search(sr) => {
|
||||||
orders.request_url(urls::search(&sr.query, sr.page + 1));
|
orders.request_url(urls::search(&sr.query, sr.page + 1));
|
||||||
}
|
}
|
||||||
|
Context::SearchResult { query, pager, .. } => {
|
||||||
|
let query = query.to_string();
|
||||||
|
let after = pager.end_cursor.clone();
|
||||||
|
orders.perform_cmd(async move {
|
||||||
|
Msg::FrontPageRequest {
|
||||||
|
query,
|
||||||
|
after,
|
||||||
|
before: None,
|
||||||
|
first: Some(SEARCH_RESULTS_PER_PAGE as i64),
|
||||||
|
last: None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
Context::Thread(_) => (), // do nothing (yet?)
|
Context::Thread(_) => (), // do nothing (yet?)
|
||||||
Context::None => (), // do nothing (yet?)
|
Context::None => (), // do nothing (yet?)
|
||||||
};
|
};
|
||||||
@ -222,10 +303,75 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
Context::Search(sr) => {
|
Context::Search(sr) => {
|
||||||
orders.request_url(urls::search(&sr.query, sr.page.saturating_sub(1)));
|
orders.request_url(urls::search(&sr.query, sr.page.saturating_sub(1)));
|
||||||
}
|
}
|
||||||
|
Context::SearchResult { query, pager, .. } => {
|
||||||
|
let query = query.to_string();
|
||||||
|
let before = pager.start_cursor.clone();
|
||||||
|
orders.perform_cmd(async move {
|
||||||
|
Msg::FrontPageRequest {
|
||||||
|
query,
|
||||||
|
after: None,
|
||||||
|
before,
|
||||||
|
first: None,
|
||||||
|
last: Some(SEARCH_RESULTS_PER_PAGE as i64),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Context::Thread(_) => (), // do nothing (yet?)
|
Context::Thread(_) => (), // do nothing (yet?)
|
||||||
Context::None => (), // do nothing (yet?)
|
Context::None => (), // do nothing (yet?)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Msg::FrontPageRequest {
|
||||||
|
query,
|
||||||
|
after,
|
||||||
|
before,
|
||||||
|
first,
|
||||||
|
last,
|
||||||
|
} => {
|
||||||
|
info!("making FrontPageRequest: {query} after:{after:?} before:{before:?} first:{first:?} last:{last:?}");
|
||||||
|
model.query = query.clone();
|
||||||
|
orders.skip().perform_cmd(async move {
|
||||||
|
Msg::FrontPageResult(
|
||||||
|
send_graphql(graphql::FrontPageQuery::build_query(
|
||||||
|
graphql::front_page_query::Variables {
|
||||||
|
query,
|
||||||
|
after,
|
||||||
|
before,
|
||||||
|
first,
|
||||||
|
last,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.await,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Msg::FrontPageResult(Err(e)) => error!("error FrontPageResult: {e:?}"),
|
||||||
|
Msg::FrontPageResult(Ok(graphql_client::Response {
|
||||||
|
data: None,
|
||||||
|
errors: None,
|
||||||
|
..
|
||||||
|
})) => {
|
||||||
|
error!("FrontPageResult no data or errors, should not happen");
|
||||||
|
}
|
||||||
|
Msg::FrontPageResult(Ok(graphql_client::Response {
|
||||||
|
data: None,
|
||||||
|
errors: Some(e),
|
||||||
|
..
|
||||||
|
})) => {
|
||||||
|
error!("FrontPageResult error: {e:?}");
|
||||||
|
}
|
||||||
|
Msg::FrontPageResult(Ok(graphql_client::Response {
|
||||||
|
data: Some(data), ..
|
||||||
|
})) => {
|
||||||
|
model.tags = Some(data.tags);
|
||||||
|
model.context = Context::SearchResult {
|
||||||
|
query: model.query.clone(),
|
||||||
|
results: data.search.nodes,
|
||||||
|
count: data.count as usize,
|
||||||
|
pager: data.search.page_info,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -507,7 +653,49 @@ fn human_age(timestamp: i64) -> String {
|
|||||||
datetime
|
datetime
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_mobile_search_results(query: &str, search_results: &shared::SearchResult) -> Node<Msg> {
|
fn view_mobile_search_results(
|
||||||
|
query: &str,
|
||||||
|
results: &[FrontPageQuerySearchNodes],
|
||||||
|
count: usize,
|
||||||
|
pager: &FrontPageQuerySearchPageInfo,
|
||||||
|
) -> Node<Msg> {
|
||||||
|
if query.is_empty() {
|
||||||
|
set_title("all mail");
|
||||||
|
} else {
|
||||||
|
set_title(query);
|
||||||
|
}
|
||||||
|
let rows = results.iter().map(|r| {
|
||||||
|
let tid = r.thread.clone();
|
||||||
|
let datetime = human_age(r.timestamp as i64);
|
||||||
|
a![
|
||||||
|
C!["has-text-light"],
|
||||||
|
attrs! {
|
||||||
|
At::Href => urls::thread(&tid)
|
||||||
|
},
|
||||||
|
div![
|
||||||
|
C!["row"],
|
||||||
|
div![C!["subject"], &r.subject],
|
||||||
|
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]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
});
|
||||||
|
div![
|
||||||
|
C!["search-results"],
|
||||||
|
h1!["Search results"],
|
||||||
|
view_search_pager(count, pager),
|
||||||
|
rows,
|
||||||
|
view_search_pager(count, pager),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view_mobile_search_results_legacy(
|
||||||
|
query: &str,
|
||||||
|
search_results: &shared::SearchResult,
|
||||||
|
) -> Node<Msg> {
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
set_title("all mail");
|
set_title("all mail");
|
||||||
} else {
|
} else {
|
||||||
@ -550,13 +738,74 @@ fn view_mobile_search_results(query: &str, search_results: &shared::SearchResult
|
|||||||
div![
|
div![
|
||||||
C!["search-results"],
|
C!["search-results"],
|
||||||
h1!["Search results"],
|
h1!["Search results"],
|
||||||
view_search_pager(first, summaries.len(), search_results.total),
|
view_search_pager_legacy(first, summaries.len(), search_results.total),
|
||||||
rows,
|
rows,
|
||||||
view_search_pager(first, summaries.len(), search_results.total)
|
view_search_pager_legacy(first, summaries.len(), search_results.total)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_search_results(query: &str, search_results: &shared::SearchResult) -> Node<Msg> {
|
fn view_search_results(
|
||||||
|
query: &str,
|
||||||
|
results: &[FrontPageQuerySearchNodes],
|
||||||
|
count: usize,
|
||||||
|
pager: &FrontPageQuerySearchPageInfo,
|
||||||
|
) -> Node<Msg> {
|
||||||
|
info!("pager {pager:?}");
|
||||||
|
if query.is_empty() {
|
||||||
|
set_title("all mail");
|
||||||
|
} else {
|
||||||
|
set_title(query);
|
||||||
|
}
|
||||||
|
let rows = results.iter().map(|r| {
|
||||||
|
let tid = r.thread.clone();
|
||||||
|
let datetime = human_age(r.timestamp as i64);
|
||||||
|
tr![
|
||||||
|
td![
|
||||||
|
C!["from"],
|
||||||
|
pretty_authors(&r.authors),
|
||||||
|
// TODO(wathiede): visualize message count if more than one message is in the
|
||||||
|
// thread
|
||||||
|
//IF!(r.total>1 => small![" ", r.total.to_string()]),
|
||||||
|
],
|
||||||
|
td![
|
||||||
|
C!["subject"],
|
||||||
|
tags_chiclet(&r.tags, false),
|
||||||
|
" ",
|
||||||
|
a![
|
||||||
|
C!["has-text-light"],
|
||||||
|
attrs! {
|
||||||
|
At::Href => urls::thread(&tid)
|
||||||
|
},
|
||||||
|
&r.subject,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
td![C!["date"], datetime]
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
div![
|
||||||
|
view_search_pager(count, pager),
|
||||||
|
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]
|
||||||
|
],
|
||||||
|
view_search_pager(count, pager)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view_search_results_legacy(query: &str, search_results: &shared::SearchResult) -> Node<Msg> {
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
set_title("all mail");
|
set_title("all mail");
|
||||||
} else {
|
} else {
|
||||||
@ -589,7 +838,7 @@ fn view_search_results(query: &str, search_results: &shared::SearchResult) -> No
|
|||||||
});
|
});
|
||||||
let first = search_results.page * search_results.results_per_page;
|
let first = search_results.page * search_results.results_per_page;
|
||||||
div![
|
div![
|
||||||
view_search_pager(first, summaries.len(), search_results.total),
|
view_search_pager_legacy(first, summaries.len(), search_results.total),
|
||||||
table![
|
table![
|
||||||
C![
|
C![
|
||||||
"table",
|
"table",
|
||||||
@ -606,11 +855,51 @@ fn view_search_results(query: &str, search_results: &shared::SearchResult) -> No
|
|||||||
]],
|
]],
|
||||||
tbody![rows]
|
tbody![rows]
|
||||||
],
|
],
|
||||||
view_search_pager(first, summaries.len(), search_results.total)
|
view_search_pager_legacy(first, summaries.len(), search_results.total)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_search_pager(start: usize, count: usize, total: usize) -> Node<Msg> {
|
fn view_search_pager(count: usize, pager: &FrontPageQuerySearchPageInfo) -> Node<Msg> {
|
||||||
|
let start = pager
|
||||||
|
.start_cursor
|
||||||
|
.as_ref()
|
||||||
|
.map(|i| i.parse().unwrap_or(0))
|
||||||
|
.unwrap_or(0);
|
||||||
|
nav![
|
||||||
|
C!["pagination"],
|
||||||
|
a![
|
||||||
|
C![
|
||||||
|
"pagination-previous",
|
||||||
|
"button",
|
||||||
|
//IF!(!pager.has_previous_page => "is-static"),
|
||||||
|
],
|
||||||
|
IF!(!pager.has_previous_page => attrs!{ At::Disabled=>true }),
|
||||||
|
"<",
|
||||||
|
IF!(pager.has_previous_page => ev(Ev::Click, |_| Msg::PreviousPage)),
|
||||||
|
],
|
||||||
|
a![
|
||||||
|
C![
|
||||||
|
"pagination-next",
|
||||||
|
"button",
|
||||||
|
//IF!(!pager.has_next_page => "is-static")
|
||||||
|
],
|
||||||
|
IF!(!pager.has_next_page => attrs!{ At::Disabled=>true }),
|
||||||
|
">",
|
||||||
|
IF!(pager.has_next_page => ev(Ev::Click, |_| Msg::NextPage))
|
||||||
|
],
|
||||||
|
ul![
|
||||||
|
C!["pagination-list"],
|
||||||
|
li![format!(
|
||||||
|
"{} - {} of {}",
|
||||||
|
start,
|
||||||
|
count.min(start + SEARCH_RESULTS_PER_PAGE),
|
||||||
|
count
|
||||||
|
)],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view_search_pager_legacy(start: usize, count: usize, total: usize) -> Node<Msg> {
|
||||||
let is_first = start <= 0;
|
let is_first = start <= 0;
|
||||||
let is_last = (start + SEARCH_RESULTS_PER_PAGE) >= total;
|
let is_last = (start + SEARCH_RESULTS_PER_PAGE) >= total;
|
||||||
nav![
|
nav![
|
||||||
@ -732,7 +1021,11 @@ fn view_header(query: &str, refresh_request: &RefreshingState) -> Node<Msg> {
|
|||||||
At::Value => query,
|
At::Value => query,
|
||||||
},
|
},
|
||||||
input_ev(Ev::Input, |q| Msg::SearchRequest {
|
input_ev(Ev::Input, |q| Msg::SearchRequest {
|
||||||
query: Url::encode_uri_component(q),
|
query: Url::encode_uri_component(if q.is_empty() {
|
||||||
|
"*".to_string()
|
||||||
|
} else {
|
||||||
|
q
|
||||||
|
}),
|
||||||
page: 0,
|
page: 0,
|
||||||
results_per_page: SEARCH_RESULTS_PER_PAGE,
|
results_per_page: SEARCH_RESULTS_PER_PAGE,
|
||||||
}),
|
}),
|
||||||
@ -762,25 +1055,58 @@ fn view_footer(render_time_ms: u128) -> Node<Msg> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn view_desktop(model: &Model) -> Node<Msg> {
|
fn view_desktop(model: &Model) -> Node<Msg> {
|
||||||
// TODO(wathiede): add sidebar showing tags, use https://bulma.io/documentation/components/menu/#docsNav
|
|
||||||
// Do two queries, one without `unread` so it loads fast, then a second with unread.
|
// Do two queries, one without `unread` so it loads fast, then a second with unread.
|
||||||
let content = match &model.context {
|
let content = match &model.context {
|
||||||
Context::None => div![h1!["Loading"]],
|
Context::None => div![h1!["Loading"]],
|
||||||
Context::Thread(thread_set) => view_thread(thread_set),
|
Context::Thread(thread_set) => view_thread(thread_set),
|
||||||
Context::Search(search_results) => view_search_results(&model.query, search_results),
|
Context::Search(search_results) => view_search_results_legacy(&model.query, search_results),
|
||||||
|
Context::SearchResult {
|
||||||
|
query,
|
||||||
|
results,
|
||||||
|
count,
|
||||||
|
pager,
|
||||||
|
} => view_search_results(&query, results.as_slice(), *count, pager),
|
||||||
};
|
};
|
||||||
|
div![
|
||||||
|
C!["desktop-main-content"],
|
||||||
|
aside![
|
||||||
|
C!["tags-menu", "menu"],
|
||||||
|
p![C!["menu-label"], "Tags"],
|
||||||
|
ul![
|
||||||
|
C!["menu-list"],
|
||||||
|
model.tags.as_ref().map(|tags| tags.iter().map(|t| li![a![
|
||||||
|
attrs! {
|
||||||
|
At::Href => urls::search(&format!("tag:{}", t.name), 0)
|
||||||
|
},
|
||||||
|
style! {
|
||||||
|
St::BackgroundColor => t.bg_color,
|
||||||
|
St::Color => t.fg_color,
|
||||||
|
},
|
||||||
|
&t.name
|
||||||
|
]]))
|
||||||
|
]
|
||||||
|
],
|
||||||
div![
|
div![
|
||||||
view_header(&model.query, &model.refreshing_state),
|
view_header(&model.query, &model.refreshing_state),
|
||||||
section![C!["section"], content],
|
section![C!["section"], content],
|
||||||
view_header(&model.query, &model.refreshing_state),
|
view_header(&model.query, &model.refreshing_state),
|
||||||
]
|
]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_mobile(model: &Model) -> Node<Msg> {
|
fn view_mobile(model: &Model) -> Node<Msg> {
|
||||||
let content = match &model.context {
|
let content = match &model.context {
|
||||||
Context::None => div![h1!["Loading"]],
|
Context::None => div![h1!["Loading"]],
|
||||||
Context::Thread(thread_set) => view_thread(thread_set),
|
Context::Thread(thread_set) => view_thread(thread_set),
|
||||||
Context::Search(search_results) => view_mobile_search_results(&model.query, search_results),
|
Context::Search(search_results) => {
|
||||||
|
view_mobile_search_results_legacy(&model.query, search_results)
|
||||||
|
}
|
||||||
|
Context::SearchResult {
|
||||||
|
query,
|
||||||
|
results,
|
||||||
|
count,
|
||||||
|
pager,
|
||||||
|
} => view_mobile_search_results(&query, results.as_slice(), *count, pager),
|
||||||
};
|
};
|
||||||
div![
|
div![
|
||||||
view_header(&model.query, &model.refreshing_state),
|
view_header(&model.query, &model.refreshing_state),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user