` with a `quote` class.
This allows them to be styled appropriately from QML.
- Some methods have `inline` counterparts, which return text appropriate
+ Some methods take an `inline` argument, which return text appropriate
for UI elements restricted to display a single line, e.g. the room
last message subtitles in QML or notifications.
In inline filtered HTML, block tags are stripped or substituted and
newlines are turned into ⏎ symbols (U+23CE).
"""
- inline_tags = {"font", "a", "sup", "sub", "b", "i", "s", "u", "code"}
+ inline_tags = {
+ "span", "font", "a", "sup", "sub", "b", "i", "s", "u", "code",
+ }
block_tags = {
"h1", "h2", "h3", "h4", "h5", "h6","blockquote",
@@ -126,8 +128,16 @@ class HTMLProcessor:
def __init__(self) -> None:
- self._sanitizer = Sanitizer(self.sanitize_settings())
+ self._sanitizers = {
+ (False, False): Sanitizer(self.sanitize_settings(False, False)),
+ (True, False): Sanitizer(self.sanitize_settings(True, False)),
+ (False, True): Sanitizer(self.sanitize_settings(False, True)),
+ (True, True): Sanitizer(self.sanitize_settings(True, True)),
+ }
+
self._inline_sanitizer = Sanitizer(self.sanitize_settings(inline=True))
+ self._inline_outgoing_sanitizer = \
+ Sanitizer(self.sanitize_settings(inline=True))
# The whitespace remover doesn't take into account
sanitizer.normalize_overall_whitespace = lambda html, *args, **kw: html
@@ -149,44 +159,36 @@ class HTMLProcessor:
]
- def from_markdown(self, text: str, outgoing: bool = False) -> str:
+ def from_markdown(
+ self, text: str, inline: bool = False, outgoing: bool = False,
+ ) -> str:
"""Return filtered HTML from Markdown text."""
- return self.filter(self._markdown_to_html(text), outgoing)
+ return self.filter(self._markdown_to_html(text), inline, outgoing)
- def from_markdown_inline(self, text: str, outgoing: bool = False) -> str:
- """Return single-line filtered HTML from Markdown text."""
+ def filter(
+ self, html: str, inline: bool = False, outgoing: bool = False,
+ ) -> str:
+ """Filter and return HTML."""
- return self.filter_inline(self._markdown_to_html(text), outgoing)
-
-
- def filter_inline(self, html: str, outgoing: bool = False) -> str:
- """Filter and return HTML with block tags stripped or substituted."""
-
- html = self._inline_sanitizer.sanitize(html)
+ html = self._sanitizers[inline, outgoing].sanitize(html).rstrip("\n")
if outgoing:
return html
# Client-side modifications
- return self.inline_quote_regex.sub(
- r'\1\2', html,
- )
-
-
- def filter(self, html: str, outgoing: bool = False) -> str:
- """Filter and return HTML."""
-
- html = self._sanitizer.sanitize(html).rstrip("\n")
-
- if outgoing:
- return html
+ if inline:
+ return self.inline_quote_regex.sub(
+ r'\1\2', html,
+ )
return self.quote_regex.sub(r'\1\2\3', html)
- def sanitize_settings(self, inline: bool = False) -> dict:
+ def sanitize_settings(
+ self, inline: bool = False, outgoing: bool = False,
+ ) -> dict:
"""Return an html_sanitizer configuration."""
# https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes
@@ -203,6 +205,7 @@ class HTMLProcessor:
attributes = {**inlines_attributes, **{
"ol": {"start"},
"hr": {"width"},
+ "span": {"data-mx-color"},
}}
return {
@@ -231,18 +234,22 @@ class HTMLProcessor:
sanitizer.tag_replacer("div", "p"),
sanitizer.tag_replacer("caption", "p"),
sanitizer.target_blank_noopener,
- self._process_span_font,
+
+ self._span_color_to_font if not outgoing else lambda el: el,
+
self._img_to_a,
self._remove_extra_newlines,
self._newlines_to_return_symbol if inline else lambda el: el,
],
- "element_postprocessors": [],
+ "element_postprocessors": [
+ self._font_color_to_span if outgoing else lambda el: el,
+ ],
"is_mergeable": lambda e1, e2: e1.attrib == e2.attrib,
}
@staticmethod
- def _process_span_font(el: HtmlElement) -> HtmlElement:
+ def _span_color_to_font(el: HtmlElement) -> HtmlElement:
"""Convert HTML ``."""
if el.tag not in ("span", "font"):
@@ -256,6 +263,21 @@ class HTMLProcessor:
return el
+ @staticmethod
+ def _font_color_to_span(el: HtmlElement) -> HtmlElement:
+ """Convert HTML `` to ` HtmlElement:
"""Linkify images by wrapping `` tags in ``."""
diff --git a/src/backend/matrix_client.py b/src/backend/matrix_client.py
index 4cde9f49..e5e8c06e 100644
--- a/src/backend/matrix_client.py
+++ b/src/backend/matrix_client.py
@@ -244,8 +244,8 @@ class MatrixClient(nio.AsyncClient):
event_type = nio.RoomMessageEmote
text = text[len("/me "): ]
content = {"body": text, "msgtype": "m.emote"}
- to_html = HTML.from_markdown_inline(text, outgoing=True)
- echo_body = HTML.from_markdown_inline(text)
+ to_html = HTML.from_markdown(text, inline=True, outgoing=True)
+ echo_body = HTML.from_markdown(text, inline=True)
else:
event_type = nio.RoomMessageText
content = {"body": text, "msgtype": "m.text"}
@@ -965,7 +965,7 @@ class MatrixClient(nio.AsyncClient):
display_name = room.display_name or "",
avatar_url = room.gen_avatar_url or "",
plain_topic = room.topic or "",
- topic = HTML.filter_inline(room.topic or ""),
+ topic = HTML.filter(room.topic or "", inline=True),
inviter_id = inviter,
inviter_name = room.user_name(inviter) if inviter else "",
inviter_avatar =
diff --git a/src/backend/models/items.py b/src/backend/models/items.py
index f9852c5c..a9684016 100644
--- a/src/backend/models/items.py
+++ b/src/backend/models/items.py
@@ -239,7 +239,8 @@ class Event(ModelItem):
def __post_init__(self) -> None:
if not self.inline_content:
- self.inline_content = HTML_PROCESSOR.filter_inline(self.content)
+ self.inline_content = \
+ HTML_PROCESSOR.filter(self.content, inline=True)
def __lt__(self, other: "Event") -> bool:
diff --git a/src/backend/nio_callbacks.py b/src/backend/nio_callbacks.py
index 8ec0b17f..063e5a47 100644
--- a/src/backend/nio_callbacks.py
+++ b/src/backend/nio_callbacks.py
@@ -340,7 +340,7 @@ class NioCallbacks:
async def onRoomTopicEvent(self, room, ev) -> None:
if ev.topic:
- topic = HTML_PROCESSOR.filter_inline(ev.topic)
+ topic = HTML_PROCESSOR.filter(ev.topic, inline=True)
co = f"%1 changed the room's topic to \"{topic}\""
else:
co = "%1 removed the room's topic"