Skip to content

Commit d79dfa1

Browse files
author
Subhasish-Behera
committed
messages: Added a method transform_content_alert_words.
1 parent a027b65 commit d79dfa1

File tree

1 file changed

+58
-3
lines changed

1 file changed

+58
-3
lines changed

zulipterminal/ui_tools/messages.py

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
UI to render a Zulip message for display, and respond contextually to actions
33
"""
44

5+
import re
56
import typing
67
from collections import OrderedDict, defaultdict
78
from datetime import date, datetime
89
from time import time
9-
from typing import Any, Dict, List, NamedTuple, Optional, Tuple, Union
10+
from typing import Any, Dict, List, Match, NamedTuple, Optional, Tuple, Union
1011
from urllib.parse import urljoin, urlparse
1112

1213
import dateutil.parser
@@ -394,6 +395,9 @@ def soup2markup(
394395
metadata["bq_len"] -= 1
395396
continue
396397
markup.append(element)
398+
elif tag == "span" and "alert-word" in element.get("class", []):
399+
if tag_text:
400+
markup.append(("msg_code", tag_text))
397401
elif tag == "div" and (set(tag_classes) & set(unrendered_div_classes)):
398402
# UNRENDERED DIV CLASSES
399403
# NOTE: Though `matches` is generalized for multiple
@@ -628,6 +632,8 @@ def main_view(self) -> List[Any]:
628632
else:
629633
recipient_header = None
630634

635+
self.alerted_words: Optional[Any] = self.model.get_alert_words()
636+
631637
# Content Header
632638
message = {
633639
key: {
@@ -716,7 +722,11 @@ def main_view(self) -> List[Any]:
716722

717723
# Transform raw message content into markup (As needed by urwid.Text)
718724
content, self.message_links, self.time_mentions = self.transform_content(
719-
self.message["content"], self.model.server_url
725+
self.message["content"],
726+
self.model.server_url,
727+
self.alerted_words,
728+
"has_alert_word" in self.message["flags"]
729+
# self.message["flags"],
720730
)
721731
self.content.set_text(content)
722732

@@ -797,14 +807,59 @@ def update_message_author_status(self) -> bool:
797807

798808
return author_is_present
799809

810+
@staticmethod
811+
def transform_content_alert_words(content: str, alerted_list: List[str]) -> Any:
812+
alert_regex_replacements = {
813+
"&": "&",
814+
"<": "&lt;",
815+
">": "&gt;",
816+
# Accept quotes with or without HTML escaping
817+
'"': r"(?:\"|&quot;)",
818+
"'": r"(?:\'|&#39;)",
819+
}
820+
821+
before_punctuation = r"\s|^|>|[\\(\".,';\\[]"
822+
823+
after_punctuation = r"(?=\s|$|<|[\\)\"?!:.,';\]!])"
824+
825+
clean: str
826+
827+
def replace_callback(match: Match[Union[str, str, str]]) -> str:
828+
before = match.group(1)
829+
word = match.group(2)
830+
after = match.group(3)
831+
offset = match.start()
832+
matched_content = match.string
833+
pre_match = matched_content[:offset]
834+
check_string = pre_match + match.group()
835+
in_tag = check_string.rfind("<") > check_string.rfind(">")
836+
if in_tag:
837+
return f"{before}{word}{after}"
838+
return f"{before}<span class='alert-word'>{word}</span>{after}"
839+
840+
for word in alerted_list:
841+
clean = "".join(alert_regex_replacements.get(c, c) for c in word)
842+
regex = f"({before_punctuation})({clean})({after_punctuation})"
843+
regex1 = re.compile(regex, re.IGNORECASE)
844+
content = re.sub(regex1, replace_callback, content)
845+
846+
return content
847+
800848
@classmethod
801849
def transform_content(
802-
cls, content: Any, server_url: str
850+
cls,
851+
content: Any,
852+
server_url: str,
853+
alerted_list: List[str] = list(),
854+
alert_word_present: bool = False,
803855
) -> Tuple[
804856
Tuple[None, Any],
805857
"OrderedDict[str, Tuple[str, int, bool]]",
806858
List[Tuple[str, str]],
807859
]:
860+
if alert_word_present:
861+
content = cls.transform_content_alert_words(content, alerted_list)
862+
808863
soup = BeautifulSoup(content, "lxml")
809864
body = soup.find(name="body")
810865

0 commit comments

Comments
 (0)