From 52db70e2a104e01b288c5e7a403622a2484025de Mon Sep 17 00:00:00 2001 From: Preet Mishra Date: Wed, 5 Aug 2020 19:20:40 +0530 Subject: [PATCH 1/2] requirements: Add python-dateutil and tzlocal for time mentions. This adds python-dateutil and tzlocal to render time mentions in local time zone. The package python-dateutil would be used for parsing ISO time string sent by the server and the package tzlocal would be used for getting user's local time zone. --- Pipfile | 2 ++ requirements.txt | 2 ++ setup.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/Pipfile b/Pipfile index 7324be23d5..75909d115f 100644 --- a/Pipfile +++ b/Pipfile @@ -10,6 +10,8 @@ urwid-readline = ">=0.11" beautifulsoup4 = ">=4.9.0" lxml = ">=4.5.2" typing_extensions = ">=3.7" +python-dateutil = ">=2.8.1" +tzlocal = ">=2.1" [dev-packages] pytest = "==5.3.5" diff --git a/requirements.txt b/requirements.txt index 8678381daa..ab64989b5e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,5 +16,7 @@ isort==4.3.21 urwid_readline>=0.11 beautifulsoup4>=4.9.0 lxml>=4.5.2 +python-dateutil>=2.8.1 +tzlocal>=2.1 zipp==1.0.0 # To support Python 3.5 diff --git a/setup.py b/setup.py index 1a9519f2ab..c0540cf11e 100644 --- a/setup.py +++ b/setup.py @@ -111,5 +111,7 @@ def long_description(): 'beautifulsoup4>=4.9.0', 'lxml>=4.5.2', 'typing_extensions>=3.7', + 'python-dateutil>=2.8.1', + 'tzlocal>=2.1', ], ) From 44901d47627687dbfc14016f0261f35acb340cdb Mon Sep 17 00:00:00 2001 From: Preet Mishra Date: Mon, 10 Aug 2020 16:21:55 +0530 Subject: [PATCH 2/2] boxes/symbols/themes: Markup timezone-aware time mentions. This adds support for rendering timezone-aware time mentions which were recently added to the Zulip's markdown suite. See https://zulipchat.com/help/format-your-message-using-markdown#mention-a-time for markdown details. Complimentary additions: * msg_time style in themes.py. * TIME_MENTION_MARKER symbol in symbols.py. Test amended. Fixes #691. --- tests/ui/test_ui_tools.py | 17 +++++++++++++++-- zulipterminal/config/symbols.py | 3 +++ zulipterminal/config/themes.py | 7 +++++++ zulipterminal/ui_tools/boxes.py | 22 +++++++++++++++++++++- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/tests/ui/test_ui_tools.py b/tests/ui/test_ui_tools.py index a5b5b1cf39..0fd0f29d6a 100644 --- a/tests/ui/test_ui_tools.py +++ b/tests/ui/test_ui_tools.py @@ -3,12 +3,13 @@ from typing import Any, Dict import pytest +import pytz from bs4 import BeautifulSoup from urwid import Columns, Divider, Padding, Text from zulipterminal.config.keys import is_command_key, keys_for_command from zulipterminal.config.symbols import ( - QUOTED_TEXT_MARKER, STREAM_TOPIC_SEPARATOR, + QUOTED_TEXT_MARKER, STREAM_TOPIC_SEPARATOR, TIME_MENTION_MARKER, ) from zulipterminal.helper import powerset from zulipterminal.ui_tools.boxes import MessageBox @@ -1723,6 +1724,15 @@ def test_private_message_to_self(self, mocker): '│ ', (None, ' '), ' │\n', '└─', '───────', '─┘', ]), + ('', [( + 'msg_time', + ' {} Fri, Aug 7 2020, 10:00 (IST) '.format(TIME_MENTION_MARKER) + )]), + ('', [( + 'msg_time', + ' {} Tue, Aug 11 2020, 22:02 (IST) '.format(TIME_MENTION_MARKER) + )]), ('some-math', ['some-math']), ('some-math', ['some-math']), ('', ['', ' \N{BULLET} ', '', 'text']), @@ -1758,13 +1768,16 @@ def test_private_message_to_self(self, mocker): 'table_with_center_and_right_alignments', 'table_with_single_column', 'table_with_the_bare_minimum', + 'time_human_readable_input', 'time_UNIX_timestamp_input', 'math', 'math2', 'ul', 'ul_with_ul_li_newlines', 'ol', 'ol_with_ol_li_newlines', 'ol_starting_at_5', 'strikethrough_del', 'inline_image', 'inline_ref', 'emoji', 'preview-twitter', 'zulip_extra_emoji', 'custom_emoji' ]) - def test_soup2markup(self, content, markup): + def test_soup2markup(self, content, markup, mocker): + mocker.patch(BOXES + '.get_localzone', + return_value=pytz.timezone('Asia/Kolkata')) message = dict(display_recipient=['x'], stream_id=5, subject='hi', sender_email='foo@zulip.com', id=4, sender_id=4209, type='stream', # NOTE Output should not vary with PM diff --git a/zulipterminal/config/symbols.py b/zulipterminal/config/symbols.py index 9b907157bf..028a516a16 100644 --- a/zulipterminal/config/symbols.py +++ b/zulipterminal/config/symbols.py @@ -7,3 +7,6 @@ APPLICATION_TITLE_BAR_LINE = '═' PINNED_STREAMS_DIVIDER = '-' LIST_TITLE_BAR_LINE = '━' +# NOTE: The '⏱' emoji needs an extra space while rendering. Otherwise, it +# appears to overlap its subsequent text. +TIME_MENTION_MARKER = '⏱ ' # Other tested options are: '⧗' and '⧖'. diff --git a/zulipterminal/config/themes.py b/zulipterminal/config/themes.py index 143631018d..d5b40a2121 100644 --- a/zulipterminal/config/themes.py +++ b/zulipterminal/config/themes.py @@ -33,6 +33,7 @@ 'msg_quote': 'underline', 'msg_code': 'bold', 'msg_bold': 'bold', + 'msg_time': 'bold', 'footer': 'standout', 'starred': 'bold', 'popup_category': 'bold', @@ -139,6 +140,8 @@ None, DEF['black'], DEF['white']), ('msg_bold', 'white, bold', 'black', None, DEF['white:bold'], DEF['black']), + ('msg_time', 'black', 'white', + None, DEF['black'], DEF['white']), ('footer', 'white', 'dark red', None, DEF['white'], DEF['dark_red']), ('starred', 'light red, bold', 'black', @@ -205,6 +208,8 @@ None, BLACK, WHITE), ('msg_bold', 'white, bold', 'black', None, WHITEBOLD, BLACK), + ('msg_time', 'black', 'white', + None, BLACK, WHITE), ('footer', 'white', 'dark red', None, WHITE, DARKRED), ('starred', 'light red, bold', 'black', @@ -244,6 +249,7 @@ ('msg_quote', 'black', 'brown'), ('msg_code', 'black', 'light gray'), ('msg_bold', 'white, bold', 'dark gray'), + ('msg_time', 'white', 'dark gray'), ('footer', 'white', 'dark red'), ('starred', 'light red, bold', 'white'), ('popup_category', 'dark gray, bold', 'light gray'), @@ -277,6 +283,7 @@ ('msg_quote', 'brown', 'dark blue'), ('msg_code', 'dark blue', 'white'), ('msg_bold', 'white, bold', 'dark blue'), + ('msg_time', 'dark blue', 'white'), ('footer', 'white', 'dark red'), ('starred', 'light red, bold', 'light blue'), ('popup_category', 'light gray, bold', 'light blue'), diff --git a/zulipterminal/ui_tools/boxes.py b/zulipterminal/ui_tools/boxes.py index 7d0bd5a242..9ebb42f640 100644 --- a/zulipterminal/ui_tools/boxes.py +++ b/zulipterminal/ui_tools/boxes.py @@ -6,16 +6,18 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union from urllib.parse import urljoin, urlparse +import dateutil.parser import urwid from bs4 import BeautifulSoup from bs4.element import NavigableString +from tzlocal import get_localzone from urwid_readline import ReadlineEdit from zulipterminal import emoji_names from zulipterminal.config.keys import is_command_key, keys_for_command from zulipterminal.config.symbols import ( MESSAGE_CONTENT_MARKER, MESSAGE_HEADER_DIVIDER, QUOTED_TEXT_MARKER, - STREAM_TOPIC_SEPARATOR, + STREAM_TOPIC_SEPARATOR, TIME_MENTION_MARKER, ) from zulipterminal.helper import ( Message, format_string, match_emoji, match_group, match_stream, match_user, @@ -740,6 +742,24 @@ def soup2markup(self, soup: Any, **state: Any) -> List[Any]: markup.extend(self.soup2markup(element, **state)) elif element.name == 'table': markup.extend(render_table(element)) + elif element.name == 'time': + # New in feature level 16, server version 3.0. + # Render time in current user's local time zone. + timestamp = element.get('datetime') + + # This should not happen. Regardless, we are interested in + # debugging and reporting it to zulip/zulip if it does. + assert timestamp is not None, 'Could not find datetime attr' + + utc_time = dateutil.parser.parse(timestamp) + local_time = utc_time.astimezone(get_localzone()) + # TODO: Address 12-hour format support with application-wide + # support for different formats. + time_string = local_time.strftime('%a, %b %-d %Y, %-H:%M (%Z)') + markup.append(( + 'msg_time', + ' {} {} '.format(TIME_MENTION_MARKER, time_string) + )) else: markup.extend(self.soup2markup(element)) return markup