Compare commits
15 Commits
letterbox-
...
letterbox-
| Author | SHA1 | Date | |
|---|---|---|---|
| a96693004c | |||
| ed9fe11fbf | |||
| 09fb14a796 | |||
| 58a7936bba | |||
| cd0ee361f5 | |||
| 77bd5abe0d | |||
| 450c5496b3 | |||
| 4411e45a3c | |||
| e7d20896d5 | |||
| 32a1115abd | |||
| 4982057500 | |||
| 8977f8bab5 | |||
| 0962a6b3cf | |||
| 3c72929a4f | |||
| e4eb495a70 |
46
Cargo.lock
generated
46
Cargo.lock
generated
@@ -910,9 +910,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "console_log"
|
||||
version = "0.1.2"
|
||||
version = "0.1.4"
|
||||
source = "sparse+https://git.z.xinu.tv/api/packages/wathiede/cargo/"
|
||||
checksum = "e628484ff9348e6c256644436f215c0a9766867820da8cf161c567db1c877e32"
|
||||
checksum = "d36495b7586d34322c3ffcff0e0d9d0b70f3a4ce88a9c199b3d8a01afb1debd7"
|
||||
dependencies = [
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
@@ -1552,7 +1552,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1620,9 +1620,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.35"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
|
||||
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
@@ -2886,7 +2886,7 @@ checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37"
|
||||
dependencies = [
|
||||
"hermit-abi 0.4.0",
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2965,7 +2965,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "letterbox-notmuch"
|
||||
version = "0.9.2"
|
||||
version = "0.9.8"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
@@ -2980,14 +2980,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "letterbox-procmail2notmuch"
|
||||
version = "0.9.2"
|
||||
version = "0.9.8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "letterbox-server"
|
||||
version = "0.9.2"
|
||||
version = "0.9.8"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
@@ -3030,7 +3030,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "letterbox-shared"
|
||||
version = "0.9.2"
|
||||
version = "0.9.8"
|
||||
dependencies = [
|
||||
"build-info",
|
||||
"letterbox-notmuch",
|
||||
@@ -3039,7 +3039,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "letterbox-web"
|
||||
version = "0.9.2"
|
||||
version = "0.9.8"
|
||||
dependencies = [
|
||||
"build-info",
|
||||
"build-info-build",
|
||||
@@ -3057,6 +3057,7 @@ dependencies = [
|
||||
"seed_hooks",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum_macros 0.27.1",
|
||||
"thiserror 2.0.11",
|
||||
"uuid",
|
||||
"wasm-bindgen",
|
||||
@@ -4446,7 +4447,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5129,7 +5130,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5962,7 +5963,7 @@ version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
"strum_macros 0.26.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5978,6 +5979,19 @@ dependencies = [
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "1.0.0"
|
||||
@@ -6205,7 +6219,7 @@ dependencies = [
|
||||
"getrandom 0.3.1",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7281,7 +7295,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -8,7 +8,7 @@ authors = ["Bill Thiede <git@xinu.tv>"]
|
||||
edition = "2021"
|
||||
license = "UNLICENSED"
|
||||
publish = ["xinu"]
|
||||
version = "0.9.2"
|
||||
version = "0.9.8"
|
||||
repository = "https://git.z.xinu.tv/wathiede/letterbox"
|
||||
|
||||
[profile.dev]
|
||||
|
||||
3
Justfile
3
Justfile
@@ -1,3 +1,6 @@
|
||||
export CARGO_INCREMENTAL := "0"
|
||||
export RUSTFLAGS := "-D warnings"
|
||||
|
||||
default:
|
||||
@echo "Run: just patch|minor|major"
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ urlencoding = "2.1.3"
|
||||
#xtracing = { path = "../../xtracing" }
|
||||
#xtracing = { git = "http://git-private.h.xinu.tv/wathiede/xtracing.git" }
|
||||
xtracing = { version = "0.3.0", registry = "xinu" }
|
||||
letterbox-notmuch = { version = "0.9.2", path = "../notmuch", registry = "xinu" }
|
||||
letterbox-shared = { version = "0.9.2", path = "../shared", registry = "xinu" }
|
||||
letterbox-notmuch = { version = "0.9.8", path = "../notmuch", registry = "xinu" }
|
||||
letterbox-shared = { version = "0.9.8", path = "../shared", registry = "xinu" }
|
||||
|
||||
[build-dependencies]
|
||||
build-info-build = "0.0.39"
|
||||
|
||||
@@ -12,5 +12,5 @@ version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
build-info = "0.0.39"
|
||||
letterbox-notmuch = { version = "0.9.2", path = "../notmuch", registry = "xinu" }
|
||||
letterbox-notmuch = { version = "0.9.8", path = "../notmuch", registry = "xinu" }
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
|
||||
@@ -33,9 +33,10 @@ wasm-bindgen = "=0.2.100"
|
||||
uuid = { version = "1.13.1", features = [
|
||||
"js",
|
||||
] } # direct dep to set js feature, prevents Rng issues
|
||||
letterbox-shared = { version = "0.9.2", path = "../shared", registry = "xinu" }
|
||||
letterbox-notmuch = { version = "0.9.2", path = "../notmuch", registry = "xinu" }
|
||||
letterbox-shared = { version = "0.9.8", path = "../shared", registry = "xinu" }
|
||||
letterbox-notmuch = { version = "0.9.8", path = "../notmuch", registry = "xinu" }
|
||||
seed_hooks = { version = "0.4.0", registry = "xinu" }
|
||||
strum_macros = "0.27.1"
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = ['-Os']
|
||||
|
||||
@@ -18,6 +18,9 @@ fn main() {
|
||||
#[cfg(debug_assertions)]
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let lvl = Level::Debug;
|
||||
#[cfg(not(debug_assertions))]
|
||||
let lvl = Level::Info;
|
||||
console_log::init_with_level(lvl).expect("failed to initialize console logging");
|
||||
// Mount the `app` to the element with the `id` "app".
|
||||
|
||||
@@ -32,13 +32,12 @@ pub fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
||||
if url.hash().is_none() {
|
||||
orders.request_url(urls::search(unread_query(), 0));
|
||||
} else {
|
||||
orders.notify(subs::UrlRequested::new(url));
|
||||
orders.request_url(url);
|
||||
};
|
||||
orders.stream(streams::window_event(Ev::Resize, |_| Msg::OnResize));
|
||||
// TODO(wathiede): only do this while viewing the index? Or maybe add a new message that force
|
||||
// 'notmuch new' on the server periodically?
|
||||
orders.stream(streams::interval(30_000, || Msg::RefreshStart));
|
||||
orders.subscribe(on_url_changed);
|
||||
orders.subscribe(Msg::OnUrlChanged);
|
||||
orders.stream(streams::window_event(Ev::Scroll, |_| Msg::WindowScrolled));
|
||||
|
||||
build_info::build_info!(fn bi);
|
||||
@@ -54,24 +53,21 @@ pub fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
||||
server: None,
|
||||
},
|
||||
catchup: None,
|
||||
last_url: Url::current(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_url_changed(uc: subs::UrlChanged) -> Msg {
|
||||
let mut url = uc.0;
|
||||
let href = document().location().unwrap().href().unwrap();
|
||||
let origin = document().location().unwrap().origin().unwrap();
|
||||
let current_url = &href[origin.len()..];
|
||||
let did_change = current_url != url.to_string();
|
||||
fn on_url_changed(old: &Url, mut new: Url) -> Msg {
|
||||
let did_change = *old != new;
|
||||
let mut messages = Vec::new();
|
||||
if did_change {
|
||||
messages.push(Msg::ScrollToTop)
|
||||
}
|
||||
info!(
|
||||
"url changed\nold '{current_url}'\nnew '{url}', history {}",
|
||||
"url changed\nold '{old}'\nnew '{new}', history {}",
|
||||
history().length().unwrap_or(0)
|
||||
);
|
||||
let hpp = url.remaining_hash_path_parts();
|
||||
let hpp = new.remaining_hash_path_parts();
|
||||
let msg = match hpp.as_slice() {
|
||||
["t", tid] => Msg::ShowThreadRequest {
|
||||
thread_id: tid.to_string(),
|
||||
@@ -116,6 +112,7 @@ fn on_url_changed(uc: subs::UrlChanged) -> Msg {
|
||||
|
||||
// `update` describes how to handle each `Msg`.
|
||||
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
debug!("update({})", msg);
|
||||
match msg {
|
||||
Msg::Noop => {}
|
||||
Msg::RefreshStart => {
|
||||
@@ -141,7 +138,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
orders.perform_cmd(async move { Msg::Refresh });
|
||||
}
|
||||
Msg::Refresh => {
|
||||
orders.perform_cmd(async move { on_url_changed(subs::UrlChanged(Url::current())) });
|
||||
orders.request_url(Url::current());
|
||||
}
|
||||
Msg::Reload => {
|
||||
window()
|
||||
@@ -149,7 +146,10 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
.reload()
|
||||
.expect("failed to reload window");
|
||||
}
|
||||
Msg::OnResize => (),
|
||||
Msg::OnUrlChanged(new_url) => {
|
||||
orders.send_msg(on_url_changed(&model.last_url, new_url.0.clone()));
|
||||
model.last_url = new_url.0;
|
||||
}
|
||||
|
||||
Msg::NextPage => {
|
||||
match &model.context {
|
||||
@@ -191,10 +191,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
};
|
||||
}
|
||||
Msg::GoToSearchResults => {
|
||||
let url = urls::search(&model.query, 0);
|
||||
info!("GoToSearchResults Start");
|
||||
orders.request_url(url);
|
||||
info!("GoToSearchResults End");
|
||||
orders.send_msg(Msg::SearchQuery(model.query.clone()));
|
||||
}
|
||||
|
||||
Msg::UpdateQuery(query) => model.query = query,
|
||||
@@ -605,6 +602,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
format!("{} is:unread", model.query)
|
||||
};
|
||||
info!("starting catchup mode w/ {}", query);
|
||||
orders.send_msg(Msg::ScrollToTop);
|
||||
orders.send_msg(Msg::CatchupRequest { query });
|
||||
}
|
||||
Msg::CatchupKeepUnread => {
|
||||
@@ -626,19 +624,24 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
return;
|
||||
};
|
||||
let Some(idx) = catchup.items.iter().position(|i| !i.seen) else {
|
||||
// All items have been seen
|
||||
orders.send_msg(Msg::CatchupExit);
|
||||
orders.send_msg(Msg::GoToSearchResults);
|
||||
return;
|
||||
};
|
||||
catchup.items[idx].seen = true;
|
||||
if idx < catchup.items.len() - 1 {
|
||||
// Reached last item
|
||||
orders.request_url(urls::thread(&catchup.items[idx + 1].id));
|
||||
return;
|
||||
} else {
|
||||
orders.send_msg(Msg::CatchupExit);
|
||||
orders.send_msg(Msg::GoToSearchResults);
|
||||
return;
|
||||
};
|
||||
}
|
||||
Msg::CatchupExit => {
|
||||
orders.send_msg(Msg::ScrollToTop);
|
||||
model.catchup = None;
|
||||
}
|
||||
}
|
||||
@@ -672,6 +675,7 @@ pub struct Model {
|
||||
pub content_el: ElRef<HtmlElement>,
|
||||
pub versions: Version,
|
||||
pub catchup: Option<Catchup>,
|
||||
pub last_url: Url,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -730,14 +734,15 @@ pub enum RefreshingState {
|
||||
Error(String),
|
||||
}
|
||||
// `Msg` describes the different events you can modify state with.
|
||||
#[derive(strum_macros::Display)]
|
||||
pub enum Msg {
|
||||
Noop,
|
||||
// Tell the client to refresh its state
|
||||
Refresh,
|
||||
// Tell the client to reload whole page from server
|
||||
Reload,
|
||||
// Window has changed size
|
||||
OnResize,
|
||||
// TODO: add GoToUrl
|
||||
OnUrlChanged(subs::UrlChanged),
|
||||
// Tell the server to update state
|
||||
RefreshStart,
|
||||
RefreshDone(Option<gloo_net::Error>),
|
||||
|
||||
@@ -4,7 +4,7 @@ use chrono::{DateTime, Datelike, Duration, Local, Utc};
|
||||
use human_format::{Formatter, Scales};
|
||||
use itertools::Itertools;
|
||||
use letterbox_shared::compute_color;
|
||||
use log::{debug, error};
|
||||
use log::error;
|
||||
use seed::{prelude::*, *};
|
||||
use seed_hooks::{state_access::CloneState, topo, use_state, StateAccessEventHandlers};
|
||||
use web_sys::{HtmlElement, HtmlInputElement};
|
||||
@@ -21,6 +21,8 @@ use crate::{
|
||||
const MAX_RAW_MESSAGE_SIZE: usize = 100_000;
|
||||
|
||||
mod tw_classes {
|
||||
use seed::{prelude::*, *};
|
||||
|
||||
pub const TAG: &[&str] = &[
|
||||
"rounded-md",
|
||||
"px-2",
|
||||
@@ -37,22 +39,30 @@ mod tw_classes {
|
||||
"text-xs",
|
||||
"[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]",
|
||||
];
|
||||
pub const BUTTON: &[&str] = &[
|
||||
"bg-neutral-900",
|
||||
"rounded-md",
|
||||
"p-2",
|
||||
"border",
|
||||
"border-neutral-700",
|
||||
"text-center",
|
||||
"text-sm",
|
||||
"transition-all",
|
||||
"shadow-md",
|
||||
"hover:shadow-lg",
|
||||
"hover:bg-neutral-700",
|
||||
"disabled:pointer-events-none",
|
||||
"disabled:opacity-50",
|
||||
"disabled:shadow-none",
|
||||
];
|
||||
|
||||
// TODO: should this be a builder pattern?
|
||||
pub fn button() -> seed::Attrs {
|
||||
button_with_color("bg-neutral-900", "hover:bg-neutral-700")
|
||||
}
|
||||
|
||||
pub fn button_with_color<T: ToClasses>(bg: T, hover: T) -> seed::Attrs {
|
||||
C![
|
||||
"rounded-md",
|
||||
"p-2",
|
||||
"border",
|
||||
"border-neutral-700",
|
||||
"text-center",
|
||||
"text-sm",
|
||||
"transition-all",
|
||||
"shadow-md",
|
||||
"hover:shadow-lg",
|
||||
"disabled:pointer-events-none",
|
||||
"disabled:opacity-50",
|
||||
"disabled:shadow-none",
|
||||
bg,
|
||||
hover,
|
||||
]
|
||||
}
|
||||
|
||||
pub const CHECKBOX: &[&str] = &[
|
||||
"w-8",
|
||||
@@ -83,13 +93,13 @@ pub fn view(model: &Model) -> Node<Msg> {
|
||||
} => {
|
||||
if let Some(catchup) = &model.catchup {
|
||||
catchup_view(
|
||||
thread(thread_data, open_messages, &model.content_el),
|
||||
thread(thread_data, open_messages, &model.content_el, true),
|
||||
&catchup.items,
|
||||
model.read_completion_ratio,
|
||||
)
|
||||
} else {
|
||||
normal_view(
|
||||
thread(thread_data, open_messages, &model.content_el),
|
||||
thread(thread_data, open_messages, &model.content_el, false),
|
||||
&model.versions,
|
||||
&model.query,
|
||||
&model.refreshing_state,
|
||||
@@ -104,13 +114,13 @@ pub fn view(model: &Model) -> Node<Msg> {
|
||||
} => {
|
||||
if let Some(catchup) = &model.catchup {
|
||||
catchup_view(
|
||||
news_post(post, &model.content_el),
|
||||
news_post(post, &model.content_el, true),
|
||||
&catchup.items,
|
||||
model.read_completion_ratio,
|
||||
)
|
||||
} else {
|
||||
normal_view(
|
||||
news_post(post, &model.content_el),
|
||||
news_post(post, &model.content_el, false),
|
||||
&model.versions,
|
||||
&model.query,
|
||||
&model.refreshing_state,
|
||||
@@ -189,9 +199,10 @@ fn catchup_view(
|
||||
"border-b",
|
||||
"border-gray-500",
|
||||
"bg-black",
|
||||
"opacity-50",
|
||||
],
|
||||
div![
|
||||
C!["absolute", "right-4", "text-gray-500"],
|
||||
C!["absolute", "top-0", "right-4", "text-gray-500", "p-4"],
|
||||
span![i![C!["fas", "fa-x"]]],
|
||||
ev(Ev::Click, move |_| Msg::CatchupExit)
|
||||
],
|
||||
@@ -200,7 +211,7 @@ fn catchup_view(
|
||||
format!("{} left ", items.iter().filter(|i| !i.seen).count(),)
|
||||
]
|
||||
],
|
||||
div![C!["mt-12", "mb-4"], content],
|
||||
div![C!["mt-12", "mb-20"], content],
|
||||
div![
|
||||
C![
|
||||
"fixed",
|
||||
@@ -214,15 +225,16 @@ fn catchup_view(
|
||||
"border-t",
|
||||
"border-gray-500",
|
||||
"bg-black",
|
||||
"opacity-50",
|
||||
],
|
||||
button![
|
||||
C![&tw_classes::BUTTON],
|
||||
tw_classes::button(),
|
||||
span![i![C!["far", "fa-envelope"]]],
|
||||
span![C!["pl-2"], "Keep unread"],
|
||||
ev(Ev::Click, move |_| Msg::CatchupKeepUnread)
|
||||
],
|
||||
button![
|
||||
C![&tw_classes::BUTTON, "bg-green-500"],
|
||||
tw_classes::button_with_color("bg-green-800", "hover:bg-neutral-700"),
|
||||
span![i![C!["far", "fa-envelope-open"]]],
|
||||
span![C!["pl-2"], "Mark as read"],
|
||||
ev(Ev::Click, move |_| Msg::CatchupMarkAsRead)
|
||||
@@ -447,7 +459,7 @@ fn search_toolbar(
|
||||
div![
|
||||
C!["gap-2", "flex", IF!(show_bulk_edit => "hidden")],
|
||||
div![button![
|
||||
C![&tw_classes::BUTTON],
|
||||
tw_classes::button(),
|
||||
attrs! {At::Title => "Mark as read"},
|
||||
span![i![C!["far", "fa-eye"]]],
|
||||
span![C!["pl-2", "hidden", "md:inline"], "Catch-up"],
|
||||
@@ -476,14 +488,16 @@ fn search_toolbar(
|
||||
],
|
||||
div![
|
||||
button![
|
||||
C![&tw_classes::BUTTON, "rounded-r-none"],
|
||||
tw_classes::button(),
|
||||
C!["rounded-r-none"],
|
||||
attrs! {At::Title => "Mark as read"},
|
||||
span![i![C!["far", "fa-envelope-open"]]],
|
||||
span![C!["pl-2", "hidden", "md:inline"], "Read"],
|
||||
ev(Ev::Click, |_| Msg::SelectionMarkAsRead)
|
||||
],
|
||||
button![
|
||||
C![&tw_classes::BUTTON, "rounded-l-none"],
|
||||
tw_classes::button(),
|
||||
C!["rounded-l-none"],
|
||||
attrs! {At::Title => "Mark as unread"},
|
||||
span![i![C!["far", "fa-envelope"]]],
|
||||
span![C!["pl-2", "hidden", "md:inline"], "Unread"],
|
||||
@@ -491,7 +505,8 @@ fn search_toolbar(
|
||||
]
|
||||
],
|
||||
div![button![
|
||||
C![&tw_classes::BUTTON, "text-red-500"],
|
||||
tw_classes::button(),
|
||||
C!["text-red-500"],
|
||||
attrs! {At::Title => "Mark as spam"},
|
||||
span![i![C!["far", "fa-hand"]]],
|
||||
span![C!["pl-2", "hidden", "md:inline"], "Spam"],
|
||||
@@ -505,13 +520,13 @@ fn search_toolbar(
|
||||
C!["flex", "gap-2", "items-center"],
|
||||
p![format!("{count} results")],
|
||||
button![
|
||||
C![&tw_classes::BUTTON],
|
||||
tw_classes::button(),
|
||||
IF!(!pager.has_previous_page => attrs!{ At::Disabled=>true }),
|
||||
"<",
|
||||
IF!(pager.has_previous_page => ev(Ev::Click, |_| Msg::PreviousPage)),
|
||||
],
|
||||
button![
|
||||
C![&tw_classes::BUTTON],
|
||||
tw_classes::button(),
|
||||
IF!(!pager.has_next_page => attrs!{ At::Disabled=>true }),
|
||||
">",
|
||||
IF!(pager.has_next_page => ev(Ev::Click, |_| Msg::NextPage))
|
||||
@@ -1035,11 +1050,13 @@ fn render_attachements(
|
||||
]
|
||||
}
|
||||
|
||||
// TODO: add cathup_mode:bool and hide elements when true
|
||||
#[topo::nested]
|
||||
fn thread(
|
||||
thread: &ShowThreadQueryThreadOnEmailThread,
|
||||
open_messages: &HashSet<String>,
|
||||
content_el: &ElRef<HtmlElement>,
|
||||
catchup_mode: bool,
|
||||
) -> Node<Msg> {
|
||||
// TODO(wathiede): show per-message subject if it changes significantly from top-level subject
|
||||
let subject = if thread.subject.is_empty() {
|
||||
@@ -1072,11 +1089,12 @@ fn thread(
|
||||
C!["p-4", "lg:p-0"],
|
||||
h3![C!["text-xl"], subject],
|
||||
span![removable_tags_chiclet(&thread.thread_id, &tags)],
|
||||
div![
|
||||
IF!(!catchup_mode => div![
|
||||
C!["pt-4", "gap-2", "flex", "justify-around"],
|
||||
div![
|
||||
button![
|
||||
C![&tw_classes::BUTTON, "rounded-r-none"],
|
||||
tw_classes::button(),
|
||||
C!["rounded-r-none"],
|
||||
attrs! {At::Title => "Mark as read"},
|
||||
span![i![C!["far", "fa-envelope-open"]]],
|
||||
span![C!["pl-2"], "Read"],
|
||||
@@ -1086,7 +1104,8 @@ fn thread(
|
||||
])),
|
||||
],
|
||||
button![
|
||||
C![&tw_classes::BUTTON, "rounded-l-none"],
|
||||
tw_classes::button(),
|
||||
C!["rounded-l-none"],
|
||||
attrs! {At::Title => "Mark as unread"},
|
||||
span![i![C!["far", "fa-envelope"]]],
|
||||
span![C!["pl-2"], "Unread"],
|
||||
@@ -1097,7 +1116,8 @@ fn thread(
|
||||
],
|
||||
],
|
||||
div![button![
|
||||
C![&tw_classes::BUTTON, "text-red-500"],
|
||||
tw_classes::button(),
|
||||
C!["text-red-500"],
|
||||
attrs! {At::Title => "Spam"},
|
||||
span![i![C!["far", "fa-hand"]]],
|
||||
span![C!["pl-2"], "Spam"],
|
||||
@@ -1107,13 +1127,13 @@ fn thread(
|
||||
Msg::GoToSearchResults
|
||||
])),
|
||||
]]
|
||||
],
|
||||
]),
|
||||
],
|
||||
div![
|
||||
C!["lg:mt-4", "mail-thread"],
|
||||
el_ref(content_el),
|
||||
messages,
|
||||
click_to_top()
|
||||
IF!(!catchup_mode => click_to_top())
|
||||
],
|
||||
/* TODO(wathiede): plumb in orignal id
|
||||
a![
|
||||
@@ -1163,7 +1183,7 @@ fn view_header(
|
||||
C!["flex", "px-4", "pt-4", "overflow-hidden"],
|
||||
a![
|
||||
C![IF![is_error => "bg-red-500"], "rounded-r-none"],
|
||||
C![&tw_classes::BUTTON],
|
||||
tw_classes::button(),
|
||||
span![i![C![
|
||||
"fa-solid",
|
||||
"fa-arrow-rotate-right",
|
||||
@@ -1172,7 +1192,7 @@ fn view_header(
|
||||
ev(Ev::Click, |_| Msg::RefreshStart),
|
||||
],
|
||||
a![
|
||||
C![&tw_classes::BUTTON],
|
||||
tw_classes::button(),
|
||||
C!["px-4", "rounded-none"],
|
||||
attrs! {
|
||||
At::Href => urls::search(unread_query(), 0)
|
||||
@@ -1180,7 +1200,7 @@ fn view_header(
|
||||
"Unread",
|
||||
],
|
||||
a![
|
||||
C![&tw_classes::BUTTON],
|
||||
tw_classes::button(),
|
||||
C!["px-4", "rounded-none"],
|
||||
attrs! {
|
||||
At::Href => urls::search("", 0)
|
||||
@@ -1332,7 +1352,13 @@ pub fn view_tags(tags: &Option<Vec<Tag>>) -> Node<Msg> {
|
||||
]
|
||||
]
|
||||
}
|
||||
fn news_post(post: &ShowThreadQueryThreadOnNewsPost, content_el: &ElRef<HtmlElement>) -> Node<Msg> {
|
||||
|
||||
// TODO: add cathup_mode:bool and hide elements when true
|
||||
fn news_post(
|
||||
post: &ShowThreadQueryThreadOnNewsPost,
|
||||
content_el: &ElRef<HtmlElement>,
|
||||
catchup_mode: bool,
|
||||
) -> Node<Msg> {
|
||||
let subject = &post.title;
|
||||
set_title(subject);
|
||||
let read_thread_id = post.thread_id.clone();
|
||||
@@ -1363,11 +1389,12 @@ fn news_post(post: &ShowThreadQueryThreadOnNewsPost, content_el: &ElRef<HtmlElem
|
||||
C!["p-4", "lg:p-0"],
|
||||
h3![C!["text-xl"], subject],
|
||||
span![tag(format!("News/{}", post.slug))],
|
||||
div![
|
||||
IF!(!catchup_mode => div![
|
||||
C!["pt-4", "gap-2", "flex", "justify-around"],
|
||||
div![
|
||||
button![
|
||||
C![&tw_classes::BUTTON, "rounded-r-none"],
|
||||
tw_classes::button(),
|
||||
C!["rounded-r-none"],
|
||||
attrs! {At::Title => "Mark as read"},
|
||||
span![i![C!["far", "fa-envelope-open"]]],
|
||||
span![C!["pl-2"], "Read"],
|
||||
@@ -1377,7 +1404,8 @@ fn news_post(post: &ShowThreadQueryThreadOnNewsPost, content_el: &ElRef<HtmlElem
|
||||
])),
|
||||
],
|
||||
button![
|
||||
C![&tw_classes::BUTTON, "rounded-l-none"],
|
||||
tw_classes::button(),
|
||||
C!["rounded-l-none"],
|
||||
attrs! {At::Title => "Mark as unread"},
|
||||
span![i![C!["far", "fa-envelope"]]],
|
||||
span![C!["pl-2"], "Unread"],
|
||||
@@ -1389,7 +1417,7 @@ fn news_post(post: &ShowThreadQueryThreadOnNewsPost, content_el: &ElRef<HtmlElem
|
||||
],
|
||||
// Placeholder for symmetry with email view that has Spam button
|
||||
div![],
|
||||
],
|
||||
]),
|
||||
],
|
||||
div![
|
||||
C!["lg:mt-4"],
|
||||
@@ -1409,12 +1437,11 @@ fn news_post(post: &ShowThreadQueryThreadOnNewsPost, content_el: &ElRef<HtmlElem
|
||||
raw![&post.body]
|
||||
]
|
||||
],
|
||||
click_to_top(),
|
||||
IF!(!catchup_mode => click_to_top()),
|
||||
]
|
||||
}
|
||||
fn render_news_post_header(post: &ShowThreadQueryThreadOnNewsPost) -> Node<Msg> {
|
||||
let from = &post.site;
|
||||
// TODO: move avatar/favicon stuff to the server side and and come up with a solution for emails
|
||||
let id = post.thread_id.clone();
|
||||
let is_unread = !post.is_read;
|
||||
let url = &post.url;
|
||||
@@ -1452,7 +1479,7 @@ fn render_news_post_header(post: &ShowThreadQueryThreadOnNewsPost) -> Node<Msg>
|
||||
div![
|
||||
C!["flex", "gap-2", "pt-2", "text-sm"],
|
||||
a![
|
||||
C![&tw_classes::BUTTON],
|
||||
tw_classes::button(),
|
||||
attrs! {
|
||||
At::Href => post.url,
|
||||
At::Target => "_blank",
|
||||
@@ -1461,7 +1488,7 @@ fn render_news_post_header(post: &ShowThreadQueryThreadOnNewsPost) -> Node<Msg>
|
||||
i![C!["fas", "fa-up-right-from-square"]],
|
||||
],
|
||||
a![
|
||||
C![&tw_classes::BUTTON],
|
||||
tw_classes::button(),
|
||||
attrs! {
|
||||
At::Href => add_archive_url,
|
||||
At::Target => "_blank",
|
||||
@@ -1470,7 +1497,7 @@ fn render_news_post_header(post: &ShowThreadQueryThreadOnNewsPost) -> Node<Msg>
|
||||
i![C!["fas", "fa-plus"]],
|
||||
],
|
||||
a![
|
||||
C![&tw_classes::BUTTON],
|
||||
tw_classes::button(),
|
||||
attrs! {
|
||||
At::Href => view_archive_url,
|
||||
At::Target => "_blank",
|
||||
@@ -1522,7 +1549,6 @@ fn reading_progress(ratio: f64) -> Node<Msg> {
|
||||
]
|
||||
}
|
||||
pub fn view_versions(versions: &Version) -> Node<Msg> {
|
||||
debug!("versions {versions:?}");
|
||||
aside![
|
||||
C!["p-2"],
|
||||
p![C!["uppercase", "font-bold"], "Versions"],
|
||||
@@ -1537,7 +1563,8 @@ pub fn view_versions(versions: &Version) -> Node<Msg> {
|
||||
|
||||
fn click_to_top() -> Node<Msg> {
|
||||
button![
|
||||
C![&tw_classes::BUTTON, "bg-red-500", "lg:m-0", "m-4"],
|
||||
tw_classes::button_with_color("bg-red-500", "hover:bg-neutral-700"),
|
||||
C!["lg:m-0", "m-4"],
|
||||
span!["Top"],
|
||||
span![i![C!["fas", "fa-arrow-turn-up"]]],
|
||||
ev(Ev::Click, |_| Msg::ScrollToTop)
|
||||
|
||||
Reference in New Issue
Block a user