From 7c968d6a133b590d57c9c7982338140c2623a79f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Lo=CC=81pez=20Guevara?= Date: Thu, 24 Apr 2025 19:37:16 -0300 Subject: [PATCH 1/2] feat(hashtag-column): allow multiple hashtags --- crates/notedeck_columns/src/actionbar.rs | 2 +- crates/notedeck_columns/src/route.rs | 2 +- crates/notedeck_columns/src/timeline/kind.rs | 32 ++++++++++++++------ crates/notedeck_columns/src/timeline/mod.rs | 21 +++++++------ crates/notedeck_columns/src/ui/add_column.rs | 11 +++++-- 5 files changed, 44 insertions(+), 24 deletions(-) diff --git a/crates/notedeck_columns/src/actionbar.rs b/crates/notedeck_columns/src/actionbar.rs index 77349ef2..08363b3f 100644 --- a/crates/notedeck_columns/src/actionbar.rs +++ b/crates/notedeck_columns/src/actionbar.rs @@ -64,7 +64,7 @@ fn execute_note_action( } NoteAction::Hashtag(htag) => { - let kind = TimelineKind::Hashtag(htag.clone()); + let kind = TimelineKind::Hashtag(vec![htag.to_string()]); router.route_to(Route::Timeline(kind.clone())); timeline_cache.open(ndb, note_cache, txn, pool, &kind) } diff --git a/crates/notedeck_columns/src/route.rs b/crates/notedeck_columns/src/route.rs index e56623a7..c0ac01d6 100644 --- a/crates/notedeck_columns/src/route.rs +++ b/crates/notedeck_columns/src/route.rs @@ -338,7 +338,7 @@ impl fmt::Display for Route { TimelineKind::Universe => write!(f, "Universe"), TimelineKind::Generic(_) => write!(f, "Custom"), TimelineKind::Search(_) => write!(f, "Search"), - TimelineKind::Hashtag(ht) => write!(f, "Hashtag ({})", ht), + TimelineKind::Hashtag(ht) => write!(f, "Hashtag ({})", ht.join(" ")), TimelineKind::Thread(_id) => write!(f, "Thread"), TimelineKind::Profile(_id) => write!(f, "Profile"), }, diff --git a/crates/notedeck_columns/src/timeline/kind.rs b/crates/notedeck_columns/src/timeline/kind.rs index d20264f6..0c578303 100644 --- a/crates/notedeck_columns/src/timeline/kind.rs +++ b/crates/notedeck_columns/src/timeline/kind.rs @@ -216,7 +216,7 @@ pub enum TimelineKind { /// Generic filter, references a hash of a filter Generic(u64), - Hashtag(String), + Hashtag(Vec), } const NOTIFS_TOKEN_DEPRECATED: &str = "notifs"; @@ -335,7 +335,7 @@ impl TimelineKind { } TimelineKind::Hashtag(ht) => { writer.write_token("hashtag"); - writer.write_token(ht); + writer.write_token(&ht.join(" ")); } } } @@ -395,7 +395,12 @@ impl TimelineKind { }, |p| { p.parse_token("hashtag")?; - Ok(TimelineKind::Hashtag(p.pull_token()?.to_string())) + Ok(TimelineKind::Hashtag( + p.pull_token()? + .split_whitespace() + .map(|s| s.to_string()) + .collect(), + )) }, |p| { p.parse_token("search")?; @@ -457,12 +462,19 @@ impl TimelineKind { .build()]), TimelineKind::Hashtag(hashtag) => { - let url: &str = &hashtag.to_lowercase(); - FilterState::ready(vec![Filter::new() - .kinds([1]) - .limit(filter::default_limit()) - .tags([url], 't') - .build()]) + let filters = hashtag + .iter() + .filter(|tag| !tag.is_empty()) + .map(|tag| { + Filter::new() + .kinds([1]) + .limit(filter::default_limit()) + .tags([tag.to_lowercase().as_str()], 't') + .build() + }) + .collect::>(); + + FilterState::ready(filters) } TimelineKind::Algo(algo_timeline) => match algo_timeline { @@ -613,7 +625,7 @@ impl TimelineKind { TimelineKind::Thread(_root_id) => ColumnTitle::simple("Thread"), TimelineKind::Universe => ColumnTitle::simple("Universe"), TimelineKind::Generic(_) => ColumnTitle::simple("Custom"), - TimelineKind::Hashtag(hashtag) => ColumnTitle::formatted(hashtag.to_string()), + TimelineKind::Hashtag(hashtag) => ColumnTitle::formatted(hashtag.join(" ").to_string()), } } } diff --git a/crates/notedeck_columns/src/timeline/mod.rs b/crates/notedeck_columns/src/timeline/mod.rs index 4bedebc6..a387ad7a 100644 --- a/crates/notedeck_columns/src/timeline/mod.rs +++ b/crates/notedeck_columns/src/timeline/mod.rs @@ -243,18 +243,21 @@ impl Timeline { )) } - pub fn hashtag(hashtag: String) -> Self { - let hashtag = hashtag.to_lowercase(); - let htag: &str = &hashtag; - let filter = Filter::new() - .kinds([1]) - .limit(filter::default_limit()) - .tags([htag], 't') - .build(); + pub fn hashtag(hashtag: Vec) -> Self { + let filters = hashtag + .iter() + .map(|tag| { + Filter::new() + .kinds([1]) + .limit(filter::default_limit()) + .tags([tag.as_str()], 't') + .build() + }) + .collect::>(); Timeline::new( TimelineKind::Hashtag(hashtag), - FilterState::ready(vec![filter]), + FilterState::ready(filters), TimelineTab::only_notes_and_replies(), ) } diff --git a/crates/notedeck_columns/src/ui/add_column.rs b/crates/notedeck_columns/src/ui/add_column.rs index f8ec5091..4c7b9448 100644 --- a/crates/notedeck_columns/src/ui/add_column.rs +++ b/crates/notedeck_columns/src/ui/add_column.rs @@ -753,7 +753,7 @@ pub fn hashtag_ui( let text_edit = egui::TextEdit::singleline(text_buffer) .hint_text( - RichText::new("Enter the desired hashtag here") + RichText::new("Enter the desired hashtags here (for multiple space-separated)") .text_style(NotedeckTextStyle::Body.text_style()), ) .vertical_align(Align::Center) @@ -767,8 +767,13 @@ pub fn hashtag_ui( .add_sized(egui::vec2(50.0, 40.0), add_column_button()) .clicked() { - let resp = - AddColumnResponse::Timeline(TimelineKind::Hashtag(sanitize_hashtag(text_buffer))); + let resp = AddColumnResponse::Timeline(TimelineKind::Hashtag( + sanitize_hashtag(text_buffer) + .split_whitespace() + .filter(|s| !s.is_empty()) + .map(|s| s.to_string()) + .collect::>(), + )); id_string_map.remove(&id); Some(resp) } else { From 3b7f67ae0731d81823ff0573e563122fa487a8be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Lo=CC=81pez=20Guevara?= Date: Wed, 4 Jun 2025 15:08:04 -0300 Subject: [PATCH 2/2] feat(hashtag-column): update copies --- crates/notedeck_columns/src/actionbar.rs | 2 +- crates/notedeck_columns/src/route.rs | 2 +- crates/notedeck_columns/src/timeline/kind.rs | 5 +++-- crates/notedeck_columns/src/timeline/mod.rs | 1 + crates/notedeck_columns/src/ui/add_column.rs | 4 ++-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/notedeck_columns/src/actionbar.rs b/crates/notedeck_columns/src/actionbar.rs index 3636fdee..fba6952d 100644 --- a/crates/notedeck_columns/src/actionbar.rs +++ b/crates/notedeck_columns/src/actionbar.rs @@ -69,7 +69,7 @@ fn execute_note_action( timeline_res = timeline_cache.open(ndb, note_cache, txn, pool, &kind); } NoteAction::Hashtag(htag) => { - let kind = TimelineKind::Hashtag(vec![htag.to_string()]); + let kind = TimelineKind::Hashtag(vec![htag.clone()]); router_action = Some(RouterAction::route_to(Route::Timeline(kind.clone()))); timeline_res = timeline_cache.open(ndb, note_cache, txn, pool, &kind); } diff --git a/crates/notedeck_columns/src/route.rs b/crates/notedeck_columns/src/route.rs index 379a79aa..f12feb1e 100644 --- a/crates/notedeck_columns/src/route.rs +++ b/crates/notedeck_columns/src/route.rs @@ -338,7 +338,7 @@ impl fmt::Display for Route { TimelineKind::Universe => write!(f, "Universe"), TimelineKind::Generic(_) => write!(f, "Custom"), TimelineKind::Search(_) => write!(f, "Search"), - TimelineKind::Hashtag(ht) => write!(f, "Hashtag ({})", ht.join(" ")), + TimelineKind::Hashtag(ht) => write!(f, "Hashtags ({})", ht.join(" ")), TimelineKind::Thread(_id) => write!(f, "Thread"), TimelineKind::Profile(_id) => write!(f, "Profile"), }, diff --git a/crates/notedeck_columns/src/timeline/kind.rs b/crates/notedeck_columns/src/timeline/kind.rs index 64450c13..12301aee 100644 --- a/crates/notedeck_columns/src/timeline/kind.rs +++ b/crates/notedeck_columns/src/timeline/kind.rs @@ -265,7 +265,7 @@ impl Display for TimelineKind { TimelineKind::Notifications(_) => f.write_str("Notifications"), TimelineKind::Profile(_) => f.write_str("Profile"), TimelineKind::Universe => f.write_str("Universe"), - TimelineKind::Hashtag(_) => f.write_str("Hashtag"), + TimelineKind::Hashtag(_) => f.write_str("Hashtags"), TimelineKind::Thread(_) => f.write_str("Thread"), TimelineKind::Search(_) => f.write_str("Search"), } @@ -397,7 +397,8 @@ impl TimelineKind { Ok(TimelineKind::Hashtag( p.pull_token()? .split_whitespace() - .map(|s| s.to_string()) + .filter(|s| !s.is_empty()) + .map(|s| s.to_lowercase().to_string()) .collect(), )) }, diff --git a/crates/notedeck_columns/src/timeline/mod.rs b/crates/notedeck_columns/src/timeline/mod.rs index 4db20123..3dd451fe 100644 --- a/crates/notedeck_columns/src/timeline/mod.rs +++ b/crates/notedeck_columns/src/timeline/mod.rs @@ -246,6 +246,7 @@ impl Timeline { pub fn hashtag(hashtag: Vec) -> Self { let filters = hashtag .iter() + .filter(|tag| !tag.is_empty()) .map(|tag| { Filter::new() .kinds([1]) diff --git a/crates/notedeck_columns/src/ui/add_column.rs b/crates/notedeck_columns/src/ui/add_column.rs index 1b92f397..6395910f 100644 --- a/crates/notedeck_columns/src/ui/add_column.rs +++ b/crates/notedeck_columns/src/ui/add_column.rs @@ -474,7 +474,7 @@ impl<'a> AddColumnView<'a> { option: AddColumnOption::UndecidedNotification, }); vec.push(ColumnOptionData { - title: "Hashtag", + title: "Hashtags", description: "Stay up to date with a certain hashtag", icon: egui::include_image!("../../../../assets/icons/hashtag_icon_4x.png"), option: AddColumnOption::UndecidedHashtag, @@ -779,7 +779,7 @@ pub fn hashtag_ui( sanitize_hashtag(text_buffer) .split_whitespace() .filter(|s| !s.is_empty()) - .map(|s| s.to_string()) + .map(|s| s.to_lowercase().to_string()) .collect::>(), )); id_string_map.remove(&id);