Skip to content

Commit 5abe7c6

Browse files
added an option to follow the poster or the mentioned users
in a post, bound to alt+f closes #58.
1 parent c639050 commit 5abe7c6

File tree

6 files changed

+134
-9
lines changed

6 files changed

+134
-9
lines changed

src/commands.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ pub enum UiCommand {
110110
ApplyPending(Option<usize>),
111111
HomePressed,
112112
ToggleContentWarning,
113+
ToggleFollow,
113114
ToggleWindowVisibility,
114115
SetQuickActionKeysEnabled(bool),
115116
SwitchTimelineByIndex(usize),
@@ -1437,6 +1438,76 @@ pub fn handle_ui_command(cmd: UiCommand, ctx: &mut UiCommandContext<'_>) {
14371438
let _ = launch_default_browser(&url, BrowserLaunchFlags::Default);
14381439
}
14391440
}
1441+
UiCommand::ToggleFollow => {
1442+
let Some(status) = get_selected_status(state) else {
1443+
live_region::announce(live_region, "No post selected");
1444+
return;
1445+
};
1446+
let target = status.reblog.as_ref().map_or(status, std::convert::AsRef::as_ref);
1447+
1448+
let mut all_users: Vec<crate::mastodon::Account> = Vec::new();
1449+
1450+
if status.reblog.is_some() {
1451+
all_users.push(status.account.clone());
1452+
}
1453+
all_users.push(target.account.clone());
1454+
1455+
let mut all_mentions: Vec<crate::mastodon::Mention> = target.mentions.clone();
1456+
for (url, text) in crate::html::extract_mention_links(&target.content) {
1457+
if all_mentions.iter().any(|m| m.url == url) {
1458+
continue;
1459+
}
1460+
let acct = acct_from_mention_link(&text, &url);
1461+
let username = acct.split('@').next().unwrap_or("").to_string();
1462+
all_mentions.push(crate::mastodon::Mention { id: String::new(), username, acct, url });
1463+
}
1464+
for mention in all_mentions {
1465+
if !all_users.iter().any(|u| u.acct == mention.acct) {
1466+
all_users.push(crate::mastodon::Account {
1467+
id: mention.id.clone(),
1468+
username: mention.username.clone(),
1469+
acct: mention.acct.clone(),
1470+
display_name: String::new(),
1471+
url: mention.url,
1472+
note: String::new(),
1473+
followers_count: 0,
1474+
following_count: 0,
1475+
statuses_count: 0,
1476+
fields: Vec::new(),
1477+
created_at: String::new(),
1478+
locked: false,
1479+
bot: false,
1480+
discoverable: None,
1481+
source: None,
1482+
});
1483+
}
1484+
}
1485+
1486+
let selected_user = if all_users.len() == 1 {
1487+
all_users[0].clone()
1488+
} else {
1489+
if let Some((acc, _)) = dialogs::prompt_for_account_list(
1490+
frame,
1491+
"Select User",
1492+
"Select user to follow/unfollow:",
1493+
&all_users,
1494+
) {
1495+
acc
1496+
} else {
1497+
return;
1498+
}
1499+
};
1500+
1501+
if let Some(net) = &state.network_handle {
1502+
net.send(NetworkCommand::ToggleFollow {
1503+
account_id: if selected_user.id.is_empty() { None } else { Some(selected_user.id) },
1504+
acct: selected_user.acct.clone(),
1505+
target_name: selected_user.username.clone(),
1506+
});
1507+
} else {
1508+
live_region::announce(live_region, "Network not available");
1509+
}
1510+
}
14401511
UiCommand::ViewInBrowser => {
14411512
let Some(status) = get_selected_status(state) else {
14421513
live_region::announce(live_region, "No post selected");

src/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ pub(crate) use crate::ui::ids::{
3838
ID_DELETE_POST, ID_DIRECT_TIMELINE, ID_EDIT_POST, ID_EDIT_PROFILE, ID_FAVORITE, ID_FAVORITES_TIMELINE,
3939
ID_FEDERATED_TIMELINE, ID_LOAD_MORE, ID_LOCAL_TIMELINE, ID_MANAGE_ACCOUNTS, ID_NEW_POST, ID_OPEN_LINKS,
4040
ID_OPEN_USER_TIMELINE_BY_INPUT, ID_OPTIONS, ID_PIN_POST, ID_QUOTE, ID_REFRESH, ID_REPLY, ID_REPLY_AUTHOR,
41-
ID_SEARCH, ID_TRAY_EXIT, ID_TRAY_TOGGLE, ID_UI_WAKE, ID_VIEW_BOOSTS, ID_VIEW_FAVORITES, ID_VIEW_HASHTAGS,
42-
ID_VIEW_HELP, ID_VIEW_IN_BROWSER, ID_VIEW_MENTIONS, ID_VIEW_POST, ID_VIEW_PROFILE, ID_VIEW_QUOTED_THREAD,
43-
ID_VIEW_THREAD, ID_VIEW_USER_TIMELINE, ID_VOTE, KEY_DELETE,
41+
ID_SEARCH, ID_TOGGLE_FOLLOW, ID_TRAY_EXIT, ID_TRAY_TOGGLE, ID_UI_WAKE, ID_VIEW_BOOSTS, ID_VIEW_FAVORITES,
42+
ID_VIEW_HASHTAGS, ID_VIEW_HELP, ID_VIEW_IN_BROWSER, ID_VIEW_MENTIONS, ID_VIEW_POST, ID_VIEW_PROFILE,
43+
ID_VIEW_QUOTED_THREAD, ID_VIEW_THREAD, ID_VIEW_USER_TIMELINE, ID_VOTE, KEY_DELETE,
4444
};
4545
use crate::{
4646
accounts::{start_add_account_flow, switch_to_account},

src/network.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ pub enum NetworkCommand {
114114
reblogs: bool,
115115
action: RelationshipAction,
116116
},
117+
ToggleFollow {
118+
account_id: Option<String>,
119+
acct: String,
120+
target_name: String,
121+
},
117122
UnfollowAccount {
118123
account_id: String,
119124
target_name: String,
@@ -803,6 +808,37 @@ fn network_loop(
803808
NetworkResponse::RelationshipUpdated { _account_id: account_id, target_name, action, result },
804809
);
805810
}
811+
Ok(NetworkCommand::ToggleFollow { account_id, acct, target_name }) => {
812+
let resolved_id = if let Some(id) = account_id {
813+
Some(id)
814+
} else if let Ok(account) = client.lookup_account(access_token, &acct) {
815+
Some(account.id)
816+
} else {
817+
None
818+
};
819+
820+
if let Some(id) = resolved_id {
821+
if let Ok(mut rels) = client.get_relationships(access_token, slice::from_ref(&id)) {
822+
if let Some(rel) = rels.pop() {
823+
let (action, result) = if rel.following {
824+
(RelationshipAction::Unfollow, client.unfollow_account(access_token, &id))
825+
} else if rel.requested {
826+
(RelationshipAction::CancelFollowRequest, client.unfollow_account(access_token, &id))
827+
} else {
828+
(
829+
RelationshipAction::Follow,
830+
client.follow_account_with_options(access_token, &id, true),
831+
)
832+
};
833+
send_response(
834+
responses,
835+
ui_waker,
836+
NetworkResponse::RelationshipUpdated { _account_id: id, target_name, action, result },
837+
);
838+
}
839+
}
840+
}
841+
}
806842
Ok(NetworkCommand::UnfollowAccount { account_id, target_name, action }) => {
807843
let result = client.unfollow_account(access_token, &account_id);
808844
send_response(

src/ui/ids.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ define_ids! {
3838
ID_COPY_POST,
3939
ID_VIEW_POST,
4040
// User actions
41+
ID_TOGGLE_FOLLOW,
4142
ID_VIEW_PROFILE,
4243
ID_VIEW_USER_TIMELINE,
4344
ID_OPEN_USER_TIMELINE_BY_INPUT,

src/ui/menu.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use crate::{
44
AppState, ContextMenuState, ID_BOOKMARK, ID_BOOST, ID_CHECK_FOR_UPDATES, ID_CLOSE_TIMELINE, ID_COPY_POST,
55
ID_DELETE_POST, ID_DIRECT_TIMELINE, ID_EDIT_POST, ID_EDIT_PROFILE, ID_FAVORITE, ID_FEDERATED_TIMELINE,
66
ID_LOAD_MORE, ID_LOCAL_TIMELINE, ID_MANAGE_ACCOUNTS, ID_NEW_POST, ID_OPEN_LINKS, ID_OPEN_USER_TIMELINE_BY_INPUT,
7-
ID_OPTIONS, ID_PIN_POST, ID_QUOTE, ID_REFRESH, ID_REPLY, ID_REPLY_AUTHOR, ID_SEARCH, ID_VIEW_BOOSTS,
8-
ID_VIEW_FAVORITES, ID_VIEW_HASHTAGS, ID_VIEW_HELP, ID_VIEW_IN_BROWSER, ID_VIEW_MENTIONS, ID_VIEW_PROFILE,
9-
ID_VIEW_QUOTED_THREAD, ID_VIEW_THREAD, ID_VIEW_USER_TIMELINE, commands::get_selected_status,
7+
ID_OPTIONS, ID_PIN_POST, ID_QUOTE, ID_REFRESH, ID_REPLY, ID_REPLY_AUTHOR, ID_SEARCH, ID_TOGGLE_FOLLOW,
8+
ID_VIEW_BOOSTS, ID_VIEW_FAVORITES, ID_VIEW_HASHTAGS, ID_VIEW_HELP, ID_VIEW_IN_BROWSER, ID_VIEW_MENTIONS,
9+
ID_VIEW_PROFILE, ID_VIEW_QUOTED_THREAD, ID_VIEW_THREAD, ID_VIEW_USER_TIMELINE, commands::get_selected_status,
1010
};
1111

1212
pub fn build_menu_bar() -> MenuBar {
@@ -45,6 +45,9 @@ pub fn build_menu_bar() -> MenuBar {
4545
post_menu
4646
.append(ID_QUOTE, "&Quote...\tCtrl+Q", "Quote this post", ItemKind::Normal)
4747
.expect("Failed to append quote menu item");
48+
post_menu
49+
.append(ID_TOGGLE_FOLLOW, "Toggle &Follow\tAlt+F", "Follow or unfollow the author", ItemKind::Normal)
50+
.expect("Failed to append toggle follow menu item");
4851
post_menu
4952
.append(ID_VIEW_PROFILE, "View &Profile\tCtrl+P", "View profile of selected post's author", ItemKind::Normal)
5053
.expect("Failed to append view profile menu item");

src/ui/window.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use crate::{
66
ContextMenuState, ID_BOOKMARK, ID_BOOST, ID_CHECK_FOR_UPDATES, ID_CLOSE_TIMELINE, ID_COPY_POST, ID_DELETE_POST,
77
ID_DIRECT_TIMELINE, ID_EDIT_POST, ID_EDIT_PROFILE, ID_FAVORITE, ID_FEDERATED_TIMELINE, ID_LOAD_MORE,
88
ID_LOCAL_TIMELINE, ID_MANAGE_ACCOUNTS, ID_NEW_POST, ID_OPEN_LINKS, ID_OPEN_USER_TIMELINE_BY_INPUT, ID_OPTIONS,
9-
ID_PIN_POST, ID_QUOTE, ID_REFRESH, ID_REPLY, ID_REPLY_AUTHOR, ID_SEARCH, ID_VIEW_BOOSTS, ID_VIEW_FAVORITES,
10-
ID_VIEW_HASHTAGS, ID_VIEW_HELP, ID_VIEW_IN_BROWSER, ID_VIEW_MENTIONS, ID_VIEW_POST, ID_VIEW_PROFILE,
11-
ID_VIEW_QUOTED_THREAD, ID_VIEW_THREAD, ID_VIEW_USER_TIMELINE, KEY_DELETE, UiCommand,
9+
ID_PIN_POST, ID_QUOTE, ID_REFRESH, ID_REPLY, ID_REPLY_AUTHOR, ID_SEARCH, ID_TOGGLE_FOLLOW, ID_VIEW_BOOSTS,
10+
ID_VIEW_FAVORITES, ID_VIEW_HASHTAGS, ID_VIEW_HELP, ID_VIEW_IN_BROWSER, ID_VIEW_MENTIONS, ID_VIEW_POST,
11+
ID_VIEW_PROFILE, ID_VIEW_QUOTED_THREAD, ID_VIEW_THREAD, ID_VIEW_USER_TIMELINE, KEY_DELETE, UiCommand,
1212
config::{AutoloadMode, SortOrder},
1313
ui::menu::build_menu_bar,
1414
ui_wake::UiCommandSender,
@@ -207,6 +207,14 @@ pub fn bind_input_handlers(
207207
}
208208
}
209209

210+
if !ctrl && !shift && alt {
211+
if k == 70 {
212+
let _ = ui_tx_list_key.send(UiCommand::ToggleFollow);
213+
event.skip(false);
214+
return;
215+
}
216+
}
217+
210218
if ctrl && shift && !alt {
211219
match k {
212220
314 => {
@@ -659,6 +667,12 @@ pub fn bind_input_handlers(
659667
let shutdown_menu = is_shutting_down;
660668
let frame_menu = parts.frame;
661669
frame_menu.on_menu_selected(move |event| match event.get_id() {
670+
ID_TOGGLE_FOLLOW => {
671+
if shutdown_menu.get() {
672+
return;
673+
}
674+
let _ = ui_tx_menu.send(UiCommand::ToggleFollow);
675+
}
662676
ID_VIEW_PROFILE => {
663677
if shutdown_menu.get() {
664678
return;

0 commit comments

Comments
 (0)