Skip to content

Commit 1dca8e9

Browse files
committed
added recent conversations
1 parent 23a773c commit 1dca8e9

File tree

8 files changed

+402
-67
lines changed

8 files changed

+402
-67
lines changed

zulipterminal/cli/run.py

+1
Original file line numberDiff line numberDiff line change
@@ -696,3 +696,4 @@ def print_setting(setting: str, data: SettingData, suffix: str = "") -> None:
696696

697697
if __name__ == "__main__":
698698
main()
699+

zulipterminal/config/keys.py

+10
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ class KeyBinding(TypedDict):
152152
'help_text': 'Send a message',
153153
'key_category': 'compose_box',
154154
},
155+
'OPEN_RECENT_CONVERSATIONS': {
156+
'keys': ['^'],
157+
'help_text': 'Open recent conversations',
158+
'key_category': 'navigation',
159+
},
155160
'SAVE_AS_DRAFT': {
156161
'keys': ['meta s'],
157162
'help_text': 'Save current message as a draft',
@@ -209,6 +214,11 @@ class KeyBinding(TypedDict):
209214
'help_text': 'Toggle topics in a stream',
210215
'key_category': 'stream_list',
211216
},
217+
"SEARCH_RECENT_CONVERSATIONS": {
218+
"keys": ["ctrl+f"],
219+
"help_text": "Search recent conversations",
220+
"key_category": "navigation"
221+
},
212222
'ALL_MESSAGES': {
213223
'keys': ['a', 'esc'],
214224
'help_text': 'View all messages',

zulipterminal/config/ui_sizes.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44

55
TAB_WIDTH = 3
6-
LEFT_WIDTH = 31
6+
LEFT_WIDTH = 32
77
RIGHT_WIDTH = 23
88

99
# These affect popup width-scaling, dependent upon window width

zulipterminal/core.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
StreamInfoView,
4848
StreamMembersView,
4949
UserInfoView,
50+
5051
)
5152
from zulipterminal.version import ZT_VERSION
5253

@@ -593,10 +594,11 @@ def copy_to_clipboard(self, text: str, text_category: str) -> None:
593594

594595
def _narrow_to(self, anchor: Optional[int], **narrow: Any) -> None:
595596
already_narrowed = self.model.set_narrow(**narrow)
597+
self.view.middle_column.set_view("messages")
596598

597599
if already_narrowed and anchor is None:
598600
return
599-
601+
600602
msg_id_list = self.model.get_message_ids_in_current_narrow()
601603

602604
# If no messages are found in the current narrow

zulipterminal/model.py

+54-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from collections import defaultdict
99
from concurrent.futures import Future, ThreadPoolExecutor, wait
1010
from copy import deepcopy
11-
from datetime import datetime
11+
from datetime import datetime,timezone
1212
from typing import (
1313
Any,
1414
Callable,
@@ -116,7 +116,6 @@ def __init__(self, controller: Any) -> None:
116116
self.recipients: FrozenSet[Any] = frozenset()
117117
self.index = initial_index
118118
self.last_unread_pm = None
119-
120119
self.user_id = -1
121120
self.user_email = ""
122121
self.user_full_name = ""
@@ -315,6 +314,7 @@ def set_narrow(
315314
frozenset(["pm_with"]): [["pm-with", pm_with]],
316315
frozenset(["starred"]): [["is", "starred"]],
317316
frozenset(["mentioned"]): [["is", "mentioned"]],
317+
318318
}
319319
for narrow_param, narrow in valid_narrows.items():
320320
if narrow_param == selected_params:
@@ -856,6 +856,7 @@ def modernize_message_response(message: Message) -> Message:
856856
message["topic_links"] = topic_links
857857

858858
return message
859+
859860

860861
def fetch_message_history(
861862
self, message_id: int
@@ -1094,7 +1095,56 @@ def get_other_subscribers_in_stream(
10941095
if stream["name"] == stream_name
10951096
if sub != self.user_id
10961097
]
1098+
def group_recent_conversations(self) -> List[Dict[str, Any]]:
1099+
"""Return the 10 most recent stream conversations."""
1100+
# Filter for stream messages
1101+
stream_msgs = [m for m in self.index["messages"].values() if m["type"] == "stream"]
1102+
if not stream_msgs:
1103+
return []
1104+
1105+
# Sort messages by timestamp (most recent first)
1106+
stream_msgs.sort(key=lambda x: x["timestamp"], reverse=True)
1107+
1108+
# Group messages by stream and topic
1109+
convos = defaultdict(list)
1110+
for msg in stream_msgs[:50]: # Limit to 50 recent messages
1111+
convos[(msg["stream_id"], msg["subject"])].append(msg)
1112+
1113+
# Process conversations into the desired format
1114+
processed_conversations = []
1115+
now = datetime.now(timezone.utc)
1116+
for (stream_id, topic), msg_list in sorted(
1117+
convos.items(), key=lambda x: max(m["timestamp"] for m in x[1]), reverse=True
1118+
)[:10]:
1119+
# Map stream_id to stream name
1120+
1121+
stream_name=self.stream_name_from_id(stream_id)
1122+
topic_name = topic if topic else "(no topic)"
1123+
1124+
# Extract participants
1125+
participants = set()
1126+
for msg in msg_list:
1127+
participants.add(msg["sender_full_name"])
1128+
1129+
# Format timestamp (using the most recent message in the conversation)
1130+
most_recent_msg = max(msg_list, key=lambda x: x["timestamp"])
1131+
timestamp = most_recent_msg["timestamp"]
1132+
conv_time = datetime.fromtimestamp(timestamp, tz=timezone.utc)
1133+
delta = now - conv_time
1134+
if delta.days > 0:
1135+
time_str = f"{delta.days} days ago"
1136+
else:
1137+
hours = delta.seconds // 3600
1138+
time_str = f"{hours} hours ago" if hours > 0 else "just now"
1139+
1140+
processed_conversations.append({
1141+
"stream": stream_name,
1142+
"topic": topic_name,
1143+
"participants": list(participants),
1144+
"time": time_str,
1145+
})
10971146

1147+
return processed_conversations
10981148
def _clean_and_order_custom_profile_data(
10991149
self, custom_profile_data: Dict[str, CustomFieldValue]
11001150
) -> List[CustomProfileData]:
@@ -1417,6 +1467,8 @@ def stream_id_from_name(self, stream_name: str) -> int:
14171467
if stream["name"] == stream_name:
14181468
return stream_id
14191469
raise RuntimeError("Invalid stream name.")
1470+
def stream_name_from_id(self,stream_id:int)->str:
1471+
return self.stream_dict[stream_id]["name"]
14201472

14211473
def stream_access_type(self, stream_id: int) -> StreamAccessType:
14221474
if stream_id not in self.stream_dict:

zulipterminal/ui.py

+3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def middle_column_view(self) -> Any:
7272
self.middle_column = MiddleColumnView(
7373
self, self.model, self.write_box, self.search_box
7474
)
75+
7576
return urwid.LineBox(
7677
self.middle_column,
7778
title="Messages",
@@ -273,6 +274,8 @@ def keypress(self, size: urwid_Box, key: str) -> Optional[str]:
273274
self.pm_button.activate(key)
274275
elif is_command_key("ALL_STARRED", key):
275276
self.starred_button.activate(key)
277+
elif is_command_key("OPEN_RECENT_CONVERSATIONS", key):
278+
self.time_button.activate(key)
276279
elif is_command_key("ALL_MENTIONS", key):
277280
self.mentioned_button.activate(key)
278281
elif is_command_key("SEARCH_PEOPLE", key):

zulipterminal/ui_tools/buttons.py

+21-4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
MENTIONED_MESSAGES_MARKER,
2525
MUTE_MARKER,
2626
STARRED_MESSAGES_MARKER,
27+
TIME_MENTION_MARKER
2728
)
2829
from zulipterminal.config.ui_mappings import EDIT_MODE_CAPTIONS, STREAM_ACCESS_TYPE
2930
from zulipterminal.helper import StreamData, hash_util_decode, process_media
@@ -130,7 +131,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
130131
class HomeButton(TopButton):
131132
def __init__(self, *, controller: Any, count: int) -> None:
132133
button_text = (
133-
f"All messages [{primary_display_key_for_command('ALL_MESSAGES')}]"
134+
f"All messages [{primary_display_key_for_command('ALL_MESSAGES')}]"
134135
)
135136

136137
super().__init__(
@@ -145,7 +146,7 @@ def __init__(self, *, controller: Any, count: int) -> None:
145146

146147
class PMButton(TopButton):
147148
def __init__(self, *, controller: Any, count: int) -> None:
148-
button_text = f"Direct messages [{primary_display_key_for_command('ALL_PM')}]"
149+
button_text = f"Direct messages [{primary_display_key_for_command('ALL_PM')}]"
149150

150151
super().__init__(
151152
controller=controller,
@@ -160,7 +161,7 @@ def __init__(self, *, controller: Any, count: int) -> None:
160161
class MentionedButton(TopButton):
161162
def __init__(self, *, controller: Any, count: int) -> None:
162163
button_text = (
163-
f"Mentions [{primary_display_key_for_command('ALL_MENTIONS')}]"
164+
f"Mentions [{primary_display_key_for_command('ALL_MENTIONS')}]"
164165
)
165166

166167
super().__init__(
@@ -171,12 +172,28 @@ def __init__(self, *, controller: Any, count: int) -> None:
171172
show_function=controller.narrow_to_all_mentions,
172173
count=count,
173174
)
175+
class TimeMentionedButton(TopButton):
176+
def __init__(self, *, controller: Any, count: int) -> None:
177+
button_text = (
178+
f"Recent Conversations [{primary_display_key_for_command('OPEN_RECENT_CONVERSATIONS')}]"
179+
)
180+
super().__init__(
181+
controller=controller,
182+
prefix_markup=("title", TIME_MENTION_MARKER),
183+
label_markup=(None, button_text),
184+
suffix_markup=("unread_count", f" ({count})" if count > 0 else ""),
185+
show_function=self.show_recent_conversations,
186+
count=count,
187+
)
188+
189+
def show_recent_conversations(self) -> None:
190+
self.controller.view.middle_column.set_view("recent")
174191

175192

176193
class StarredButton(TopButton):
177194
def __init__(self, *, controller: Any, count: int) -> None:
178195
button_text = (
179-
f"Starred messages [{primary_display_key_for_command('ALL_STARRED')}]"
196+
f"Starred messages [{primary_display_key_for_command('ALL_STARRED')}]"
180197
)
181198

182199
super().__init__(

0 commit comments

Comments
 (0)