font color → span mx color for outgoing HTML
Also remove HTML_PROCESSOR `filter_inline` and `from_markdown_inline` methods. `filter` and `from_markdown` now take an `inline` argument.
This commit is contained in:
parent
335d931b0a
commit
44e5de02f8
|
@ -92,14 +92,16 @@ class HTMLProcessor:
|
||||||
- Wrap text lines starting with a `>` in `<span>` with a `quote` class.
|
- Wrap text lines starting with a `>` in `<span>` with a `quote` class.
|
||||||
This allows them to be styled appropriately from QML.
|
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
|
for UI elements restricted to display a single line, e.g. the room
|
||||||
last message subtitles in QML or notifications.
|
last message subtitles in QML or notifications.
|
||||||
In inline filtered HTML, block tags are stripped or substituted and
|
In inline filtered HTML, block tags are stripped or substituted and
|
||||||
newlines are turned into ⏎ symbols (U+23CE).
|
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 = {
|
block_tags = {
|
||||||
"h1", "h2", "h3", "h4", "h5", "h6","blockquote",
|
"h1", "h2", "h3", "h4", "h5", "h6","blockquote",
|
||||||
|
@ -126,8 +128,16 @@ class HTMLProcessor:
|
||||||
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
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_sanitizer = Sanitizer(self.sanitize_settings(inline=True))
|
||||||
|
self._inline_outgoing_sanitizer = \
|
||||||
|
Sanitizer(self.sanitize_settings(inline=True))
|
||||||
|
|
||||||
# The whitespace remover doesn't take <pre> into account
|
# The whitespace remover doesn't take <pre> into account
|
||||||
sanitizer.normalize_overall_whitespace = lambda html, *args, **kw: html
|
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 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:
|
def filter(
|
||||||
"""Return single-line filtered HTML from Markdown text."""
|
self, html: str, inline: bool = False, outgoing: bool = False,
|
||||||
|
) -> str:
|
||||||
|
"""Filter and return HTML."""
|
||||||
|
|
||||||
return self.filter_inline(self._markdown_to_html(text), outgoing)
|
html = self._sanitizers[inline, outgoing].sanitize(html).rstrip("\n")
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if outgoing:
|
if outgoing:
|
||||||
return html
|
return html
|
||||||
|
|
||||||
# Client-side modifications
|
# Client-side modifications
|
||||||
return self.inline_quote_regex.sub(
|
if inline:
|
||||||
r'\1<span class="quote">\2</span>', html,
|
return self.inline_quote_regex.sub(
|
||||||
)
|
r'\1<span class="quote">\2</span>', 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
|
|
||||||
|
|
||||||
return self.quote_regex.sub(r'\1<span class="quote">\2</span>\3', html)
|
return self.quote_regex.sub(r'\1<span class="quote">\2</span>\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."""
|
"""Return an html_sanitizer configuration."""
|
||||||
|
|
||||||
# https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes
|
# https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes
|
||||||
|
@ -203,6 +205,7 @@ class HTMLProcessor:
|
||||||
attributes = {**inlines_attributes, **{
|
attributes = {**inlines_attributes, **{
|
||||||
"ol": {"start"},
|
"ol": {"start"},
|
||||||
"hr": {"width"},
|
"hr": {"width"},
|
||||||
|
"span": {"data-mx-color"},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -231,18 +234,22 @@ class HTMLProcessor:
|
||||||
sanitizer.tag_replacer("div", "p"),
|
sanitizer.tag_replacer("div", "p"),
|
||||||
sanitizer.tag_replacer("caption", "p"),
|
sanitizer.tag_replacer("caption", "p"),
|
||||||
sanitizer.target_blank_noopener,
|
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._img_to_a,
|
||||||
self._remove_extra_newlines,
|
self._remove_extra_newlines,
|
||||||
self._newlines_to_return_symbol if inline else lambda el: el,
|
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,
|
"is_mergeable": lambda e1, e2: e1.attrib == e2.attrib,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _process_span_font(el: HtmlElement) -> HtmlElement:
|
def _span_color_to_font(el: HtmlElement) -> HtmlElement:
|
||||||
"""Convert HTML `<span data-mx-color=...` to `<font color=...>`."""
|
"""Convert HTML `<span data-mx-color=...` to `<font color=...>`."""
|
||||||
|
|
||||||
if el.tag not in ("span", "font"):
|
if el.tag not in ("span", "font"):
|
||||||
|
@ -256,6 +263,21 @@ class HTMLProcessor:
|
||||||
return el
|
return el
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _font_color_to_span(el: HtmlElement) -> HtmlElement:
|
||||||
|
"""Convert HTML `<font color=...>` to `<span data-mx-color=...`."""
|
||||||
|
|
||||||
|
if el.tag not in ("span", "font"):
|
||||||
|
return el
|
||||||
|
|
||||||
|
color = el.attrib.pop("color", None)
|
||||||
|
if color:
|
||||||
|
el.tag = "span"
|
||||||
|
el.attrib["data-mx-color"] = color
|
||||||
|
|
||||||
|
return el
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _img_to_a(el: HtmlElement) -> HtmlElement:
|
def _img_to_a(el: HtmlElement) -> HtmlElement:
|
||||||
"""Linkify images by wrapping `<img>` tags in `<a>`."""
|
"""Linkify images by wrapping `<img>` tags in `<a>`."""
|
||||||
|
|
|
@ -244,8 +244,8 @@ class MatrixClient(nio.AsyncClient):
|
||||||
event_type = nio.RoomMessageEmote
|
event_type = nio.RoomMessageEmote
|
||||||
text = text[len("/me "): ]
|
text = text[len("/me "): ]
|
||||||
content = {"body": text, "msgtype": "m.emote"}
|
content = {"body": text, "msgtype": "m.emote"}
|
||||||
to_html = HTML.from_markdown_inline(text, outgoing=True)
|
to_html = HTML.from_markdown(text, inline=True, outgoing=True)
|
||||||
echo_body = HTML.from_markdown_inline(text)
|
echo_body = HTML.from_markdown(text, inline=True)
|
||||||
else:
|
else:
|
||||||
event_type = nio.RoomMessageText
|
event_type = nio.RoomMessageText
|
||||||
content = {"body": text, "msgtype": "m.text"}
|
content = {"body": text, "msgtype": "m.text"}
|
||||||
|
@ -965,7 +965,7 @@ class MatrixClient(nio.AsyncClient):
|
||||||
display_name = room.display_name or "",
|
display_name = room.display_name or "",
|
||||||
avatar_url = room.gen_avatar_url or "",
|
avatar_url = room.gen_avatar_url or "",
|
||||||
plain_topic = room.topic 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_id = inviter,
|
||||||
inviter_name = room.user_name(inviter) if inviter else "",
|
inviter_name = room.user_name(inviter) if inviter else "",
|
||||||
inviter_avatar =
|
inviter_avatar =
|
||||||
|
|
|
@ -239,7 +239,8 @@ class Event(ModelItem):
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
if not self.inline_content:
|
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:
|
def __lt__(self, other: "Event") -> bool:
|
||||||
|
|
|
@ -340,7 +340,7 @@ class NioCallbacks:
|
||||||
|
|
||||||
async def onRoomTopicEvent(self, room, ev) -> None:
|
async def onRoomTopicEvent(self, room, ev) -> None:
|
||||||
if ev.topic:
|
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}\""
|
co = f"%1 changed the room's topic to \"{topic}\""
|
||||||
else:
|
else:
|
||||||
co = "%1 removed the room's topic"
|
co = "%1 removed the room's topic"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user