diff --git a/docs/hotkeys.md b/docs/hotkeys.md
index 43bbf6125a..61433c8d14 100644
--- a/docs/hotkeys.md
+++ b/docs/hotkeys.md
@@ -136,4 +136,5 @@
|View current message in browser|v|
|Show/hide full rendered message|f|
|Show/hide full raw message|r|
+|Show/hide poll voter list|M|
diff --git a/zulipterminal/config/keys.py b/zulipterminal/config/keys.py
index a86420a364..77e8cb639a 100644
--- a/zulipterminal/config/keys.py
+++ b/zulipterminal/config/keys.py
@@ -457,6 +457,11 @@ class KeyBinding(TypedDict):
'help_text': 'Show/hide full raw message',
'key_category': 'msg_info',
},
+ 'SHOW_POLL_VOTES': {
+ 'keys': ['M'],
+ 'help_text': 'Show/hide poll voter list',
+ 'key_category': 'msg_info',
+ },
'NEW_HINT': {
'keys': ['tab'],
'help_text': 'New footer hotkey hint',
diff --git a/zulipterminal/core.py b/zulipterminal/core.py
index 61c5f79922..3540f527cb 100644
--- a/zulipterminal/core.py
+++ b/zulipterminal/core.py
@@ -43,6 +43,7 @@
MarkdownHelpView,
MsgInfoView,
NoticeView,
+ PollResultsView,
PopUpConfirmationView,
StreamInfoView,
StreamMembersView,
@@ -281,6 +282,34 @@ def show_msg_info(
)
self.show_pop_up(msg_info_view, "area:msg")
+ def show_poll_vote(
+ self,
+ poll_question: str,
+ options: Dict[str, Dict[str, Any]],
+ ) -> None:
+ options_with_names = {}
+ for option_key, option_data in options.items():
+ option_text = option_data["option"]
+ voter_ids = option_data["votes"]
+
+ voter_names = []
+ for voter_id in voter_ids:
+ voter_names.append(self.model.user_name_from_id(voter_id))
+
+ options_with_names[option_key] = {
+ "option": option_text,
+ "votes": voter_names if voter_names else [],
+ }
+
+ self.show_pop_up(
+ PollResultsView(
+ self,
+ poll_question,
+ options_with_names,
+ ),
+ "area:msg",
+ )
+
def show_emoji_picker(self, message: Message) -> None:
all_emoji_units = [
(emoji_name, emoji["code"], emoji["aliases"])
diff --git a/zulipterminal/ui_tools/messages.py b/zulipterminal/ui_tools/messages.py
index 8fe2bb8f5c..f2b6785245 100644
--- a/zulipterminal/ui_tools/messages.py
+++ b/zulipterminal/ui_tools/messages.py
@@ -69,6 +69,7 @@ def __init__(self, message: Message, model: "Model", last_message: Any) -> None:
self.topic_links: Dict[str, Tuple[str, int, bool]] = dict()
self.time_mentions: List[Tuple[str, str]] = list()
self.last_message = last_message
+ self.widget_type: str = ""
# if this is the first message
if self.last_message is None:
self.last_message = defaultdict(dict)
@@ -733,9 +734,9 @@ def main_view(self) -> List[Any]:
)
if self.message.get("submessages"):
- widget_type = find_widget_type(self.message.get("submessages", []))
+ self.widget_type = find_widget_type(self.message.get("submessages", []))
- if widget_type == "todo":
+ if self.widget_type == "todo":
title, tasks = process_todo_widget(self.message.get("submessages", []))
todo_widget = "To-do\n" + f"{title}"
@@ -757,28 +758,28 @@ def main_view(self) -> List[Any]:
# though it's not very useful.
self.message["content"] = todo_widget
- elif widget_type == "poll":
- poll_question, poll_options = process_poll_widget(
+ elif self.widget_type == "poll":
+ self.poll_question, self.poll_options = process_poll_widget(
self.message.get("submessages", [])
)
# TODO: ZT doesn't yet support adding poll questions after the
# creation of the poll. So, if the poll question is not provided,
# we show a message to add one via the web app.
- if not poll_question:
- poll_question = (
+ if not self.poll_question:
+ self.poll_question = (
"No poll question is provided. Please add one via the web app."
)
- poll_widget = f"Poll\n{poll_question}"
+ poll_widget = f"Poll\n{self.poll_question}"
- if poll_options:
+ if self.poll_options:
max_votes_len = max(
len(str(len(option["votes"])))
- for option in poll_options.values()
+ for option in self.poll_options.values()
)
- for option_info in poll_options.values():
+ for option_info in self.poll_options.values():
padded_votes = f"{len(option_info['votes']):>{max_votes_len}}"
poll_widget += f"\n[ {padded_votes} ] {option_info['option']}"
else:
@@ -1188,4 +1189,6 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
self.model.controller.show_emoji_picker(self.message)
elif is_command_key("MSG_SENDER_INFO", key):
self.model.controller.show_msg_sender_info(self.message["sender_id"])
+ elif is_command_key("SHOW_POLL_VOTES", key) and self.widget_type == "poll":
+ self.model.controller.show_poll_vote(self.poll_question, self.poll_options)
return key
diff --git a/zulipterminal/ui_tools/views.py b/zulipterminal/ui_tools/views.py
index 02b3afbd0b..effba3b7f5 100644
--- a/zulipterminal/ui_tools/views.py
+++ b/zulipterminal/ui_tools/views.py
@@ -23,6 +23,7 @@
from zulipterminal.config.symbols import (
CHECK_MARK,
COLUMN_TITLE_BAR_LINE,
+ INVALID_MARKER,
PINNED_STREAMS_DIVIDER,
SECTION_DIVIDER_LINE,
)
@@ -2176,3 +2177,37 @@ def keypress(self, size: urwid_Size, key: str) -> str:
self.controller.exit_popup()
return key
return super().keypress(size, key)
+
+
+class PollResultsView(PopUpView):
+ def __init__(
+ self,
+ controller: Any,
+ poll_question: str,
+ poll_options: Dict[str, Dict[str, Any]],
+ ) -> None:
+ poll_results_content: List[Tuple[str, List[Tuple[str, str]]]] = [("", [])]
+
+ for option_key, option_data in poll_options.items():
+ option_text = option_data["option"]
+ if len(option_text) >= 13:
+ option_text = option_text[:10] + "…"
+ voter_names = option_data["votes"]
+
+ voters_display = (
+ "\n".join(map(str, voter_names))
+ if voter_names
+ else f"{INVALID_MARKER} No votes yet"
+ )
+
+ poll_results_content[0][1].append((option_text, voters_display))
+
+ popup_width, column_widths = self.calculate_table_widths(
+ poll_results_content, len(poll_question)
+ )
+
+ widgets = self.make_table_with_categories(poll_results_content, column_widths)
+
+ super().__init__(
+ controller, widgets, "SHOW_POLL_VOTES", popup_width, poll_question
+ )