Compare commits

..

18 Commits

Author SHA1 Message Date
09fb14a796 chore: Release
All checks were successful
Continuous integration / Check (push) Successful in 37s
Continuous integration / Test Suite (push) Successful in 43s
Continuous integration / Trunk (push) Successful in 37s
Continuous integration / Rustfmt (push) Successful in 52s
Continuous integration / build (push) Successful in 50s
Continuous integration / Disallow unused dependencies (push) Successful in 2m28s
2025-02-25 20:08:44 -08:00
58a7936bba web: address lint 2025-02-25 20:08:31 -08:00
cd0ee361f5 chore: Release 2025-02-25 20:06:18 -08:00
77bd5abe0d Don't do incremental builds when release 2025-02-25 20:06:11 -08:00
450c5496b3 chore: Release 2025-02-25 20:04:01 -08:00
4411e45a3c Don't allow warnings when publishing 2025-02-25 20:03:40 -08:00
e7d20896d5 web: remove unnecessary Msg variant 2025-02-25 16:20:32 -08:00
32a1115abd chore: Release
Some checks failed
Continuous integration / Check (push) Successful in 38s
Continuous integration / Test Suite (push) Successful in 45s
Continuous integration / Trunk (push) Failing after 36s
Continuous integration / Rustfmt (push) Successful in 30s
Continuous integration / Disallow unused dependencies (push) Successful in 54s
Continuous integration / build (push) Successful in 2m44s
2025-02-25 15:58:46 -08:00
4982057500 web: more scroll to top improvements by reworking URL changes 2025-02-25 15:58:24 -08:00
8977f8bab5 chore: Release
All checks were successful
Continuous integration / Check (push) Successful in 40s
Continuous integration / Test Suite (push) Successful in 43s
Continuous integration / Trunk (push) Successful in 38s
Continuous integration / Rustfmt (push) Successful in 30s
Continuous integration / build (push) Successful in 49s
Continuous integration / Disallow unused dependencies (push) Successful in 2m39s
2025-02-25 13:51:38 -08:00
0962a6b3cf web: improve scroll-to-top behavior 2025-02-25 13:51:11 -08:00
3c72929a4f web: enable properly styled buttons 2025-02-25 10:26:16 -08:00
e4eb495a70 web: properly exit catchup mode when done 2025-02-25 10:25:28 -08:00
00e8b0342e chore: Release
All checks were successful
Continuous integration / Check (push) Successful in 37s
Continuous integration / Test Suite (push) Successful in 41s
Continuous integration / Trunk (push) Successful in 38s
Continuous integration / Rustfmt (push) Successful in 30s
Continuous integration / Disallow unused dependencies (push) Successful in 54s
Continuous integration / build (push) Successful in 2m46s
2025-02-24 18:41:19 -08:00
b1f9867c06 web: remove debug statement 2025-02-24 18:41:00 -08:00
77943b3570 web: scroll to top on page changes 2025-02-24 18:39:47 -08:00
45e4edb1dd web: add icons to catchup controls 2025-02-24 17:09:16 -08:00
9bf53afebf server: sort catchup ids by timestamp across all sources 2025-02-24 17:08:57 -08:00
10 changed files with 153 additions and 91 deletions

46
Cargo.lock generated
View File

@@ -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.1"
version = "0.9.7"
dependencies = [
"itertools 0.14.0",
"log",
@@ -2980,14 +2980,14 @@ dependencies = [
[[package]]
name = "letterbox-procmail2notmuch"
version = "0.9.1"
version = "0.9.7"
dependencies = [
"anyhow",
]
[[package]]
name = "letterbox-server"
version = "0.9.1"
version = "0.9.7"
dependencies = [
"ammonia",
"anyhow",
@@ -3030,7 +3030,7 @@ dependencies = [
[[package]]
name = "letterbox-shared"
version = "0.9.1"
version = "0.9.7"
dependencies = [
"build-info",
"letterbox-notmuch",
@@ -3039,7 +3039,7 @@ dependencies = [
[[package]]
name = "letterbox-web"
version = "0.9.1"
version = "0.9.7"
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]]

View File

@@ -8,7 +8,7 @@ authors = ["Bill Thiede <git@xinu.tv>"]
edition = "2021"
license = "UNLICENSED"
publish = ["xinu"]
version = "0.9.1"
version = "0.9.7"
repository = "https://git.z.xinu.tv/wathiede/letterbox"
[profile.dev]

View File

@@ -1,3 +1,6 @@
export CARGO_INCREMENTAL := "0"
export RUSTFLAGS := "-D warnings"
default:
@echo "Run: just patch|minor|major"

View File

@@ -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.1", path = "../notmuch", registry = "xinu" }
letterbox-shared = { version = "0.9.1", path = "../shared", registry = "xinu" }
letterbox-notmuch = { version = "0.9.7", path = "../notmuch", registry = "xinu" }
letterbox-shared = { version = "0.9.7", path = "../shared", registry = "xinu" }
[build-dependencies]
build-info-build = "0.0.39"

View File

@@ -331,10 +331,15 @@ impl QueryRoot {
notmuch_results.len(),
);
let results: Vec<_> = newsreader_results
let mut results: Vec<_> = newsreader_results
.into_iter()
.chain(notmuch_results)
.collect();
// The leading '-' is to reverse sort
results.sort_by_key(|item| match item {
ThreadSummaryCursor::Newsreader(_, ts) => -ts.timestamp,
ThreadSummaryCursor::Notmuch(_, ts) => -ts.timestamp,
});
let ids = results
.into_iter()
.map(|r| match r {

View File

@@ -12,5 +12,5 @@ version.workspace = true
[dependencies]
build-info = "0.0.39"
letterbox-notmuch = { version = "0.9.1", path = "../notmuch", registry = "xinu" }
letterbox-notmuch = { version = "0.9.7", path = "../notmuch", registry = "xinu" }
serde = { version = "1.0.147", features = ["derive"] }

View File

@@ -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.1", path = "../shared", registry = "xinu" }
letterbox-notmuch = { version = "0.9.1", path = "../notmuch", registry = "xinu" }
letterbox-shared = { version = "0.9.7", path = "../shared", registry = "xinu" }
letterbox-notmuch = { version = "0.9.7", 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']

View File

@@ -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".

View File

@@ -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,18 +53,22 @@ 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;
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 '{}', history {}",
url,
"url changed\nold '{old}'\nnew '{new}', history {}",
history().length().unwrap_or(0)
);
let hpp = url.remaining_hash_path_parts();
match hpp.as_slice() {
let hpp = new.remaining_hash_path_parts();
let msg = match hpp.as_slice() {
["t", tid] => Msg::ShowThreadRequest {
thread_id: tid.to_string(),
},
@@ -102,11 +105,14 @@ fn on_url_changed(uc: subs::UrlChanged) -> Msg {
last: None,
}
}
}
};
messages.push(msg);
Msg::MultiMsg(messages)
}
// `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 => {
@@ -132,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()
@@ -140,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 {
@@ -182,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,
@@ -308,7 +314,6 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
})
.collect(),
);
info!("pager {:#?}", data.search.page_info);
let selected_threads = 'context: {
if let Context::SearchResult {
results,
@@ -536,6 +541,10 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
.expect("failed to copy to clipboard");
});
}
Msg::ScrollToTop => {
info!("scrolling to the top");
web_sys::window().unwrap().scroll_to_with_x_and_y(0., 0.);
}
Msg::WindowScrolled => {
if let Some(el) = model.content_el.get() {
let ih = window()
@@ -593,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 => {
@@ -608,24 +618,30 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
orders.send_msg(Msg::CatchupNext);
}
Msg::CatchupNext => {
orders.send_msg(Msg::ScrollToTop);
let Some(catchup) = &mut model.catchup else {
orders.send_msg(Msg::GoToSearchResults);
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;
}
}
@@ -659,6 +675,7 @@ pub struct Model {
pub content_el: ElRef<HtmlElement>,
pub versions: Version,
pub catchup: Option<Catchup>,
pub last_url: Url,
}
#[derive(Debug)]
@@ -717,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>),
@@ -777,6 +795,7 @@ pub enum Msg {
CopyToClipboard(String),
ScrollToTop,
WindowScrolled,
SetProgress(f64),
UpdateServerVersion(String),

View File

@@ -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, info};
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",
@@ -216,13 +226,15 @@ fn catchup_view(
"bg-black",
],
button![
C![&tw_classes::BUTTON],
"Keep unread",
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"],
"Mark as read",
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)
]
],
@@ -445,7 +457,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"],
@@ -474,14 +486,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"],
@@ -489,7 +503,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"],
@@ -503,13 +518,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))
@@ -1033,6 +1048,7 @@ fn render_attachements(
]
}
// TODO: add cathup_mode:bool and hide elements when true
#[topo::nested]
fn thread(
thread: &ShowThreadQueryThreadOnEmailThread,
@@ -1065,7 +1081,7 @@ fn thread(
let spam_add_thread_id = thread.thread_id.clone();
let spam_unread_thread_id = thread.thread_id.clone();
div![
C!["lg:p-4"],
C!["lg:p-4", "max-w-4xl"],
div![
C!["p-4", "lg:p-0"],
h3![C!["text-xl"], subject],
@@ -1074,7 +1090,8 @@ fn thread(
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"],
@@ -1084,7 +1101,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"],
@@ -1095,7 +1113,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"],
@@ -1161,7 +1180,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",
@@ -1170,7 +1189,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)
@@ -1178,7 +1197,7 @@ fn view_header(
"Unread",
],
a![
C![&tw_classes::BUTTON],
tw_classes::button(),
C!["px-4", "rounded-none"],
attrs! {
At::Href => urls::search("", 0)
@@ -1250,11 +1269,7 @@ pub fn view_tags(tags: &Option<Vec<Tag>>) -> Node<Msg> {
],
IF!(t.unread>0 => format!("{}", t.unread))
],
ev(Ev::Click, |_| {
// Scroll window to the top when searching for a tag.
info!("scrolling to the top because you clicked a tag");
web_sys::window().unwrap().scroll_to_with_x_and_y(0., 0.);
})
ev(Ev::Click, |_| { Msg::ScrollToTop })
]
}
fn matches(a: &[&str], b: &[&str]) -> usize {
@@ -1334,6 +1349,8 @@ pub fn view_tags(tags: &Option<Vec<Tag>>) -> Node<Msg> {
]
]
}
// TODO: add cathup_mode:bool and hide elements when true
fn news_post(post: &ShowThreadQueryThreadOnNewsPost, content_el: &ElRef<HtmlElement>) -> Node<Msg> {
let subject = &post.title;
set_title(subject);
@@ -1369,7 +1386,8 @@ fn news_post(post: &ShowThreadQueryThreadOnNewsPost, content_el: &ElRef<HtmlElem
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"],
@@ -1379,7 +1397,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"],
@@ -1454,7 +1473,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",
@@ -1463,7 +1482,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",
@@ -1472,7 +1491,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",
@@ -1524,7 +1543,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"],
@@ -1539,11 +1557,10 @@ 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, |_| web_sys::window()
.unwrap()
.scroll_to_with_x_and_y(0., 0.))
ev(Ev::Click, |_| Msg::ScrollToTop)
]
}