Render inline images/custom emotes

<img> tags that either have a width and height (needed for QML to render
correctly) or the data-mx-emote attribute (if they have no width/height,
we assume 32x32) will be rendered inline.

QML's support for img tags with remote src URLs seems buggy on Qt 5.15
especially, not working sometimes. We need to download and cache these
images (like done for matrix media events), and use the local path in
the img's src.
This commit is contained in:
miruka 2020-06-27 08:35:11 -04:00
parent 1a32c26b4d
commit 9dc0688557
2 changed files with 33 additions and 5 deletions

View File

@ -1,5 +1,8 @@
# TODO # TODO
- issue templates
- ctrl-tab highlight
## Refactoring ## Refactoring
- General change/upload avatar component for account and room settings - General change/upload avatar component for account and room settings

View File

@ -12,6 +12,8 @@ import mistune
from html_sanitizer.sanitizer import Sanitizer from html_sanitizer.sanitizer import Sanitizer
from lxml.html import HtmlElement, etree # nosec from lxml.html import HtmlElement, etree # nosec
import nio
from .svg_colors import SVG_COLORS from .svg_colors import SVG_COLORS
@ -111,7 +113,7 @@ class HTMLProcessor:
block_tags = { block_tags = {
"h1", "h2", "h3", "h4", "h5", "h6","blockquote", "h1", "h2", "h3", "h4", "h5", "h6","blockquote",
"p", "ul", "ol", "li", "hr", "br", "p", "ul", "ol", "li", "hr", "br", "img",
"table", "thead", "tbody", "tr", "th", "td", "pre", "table", "thead", "tbody", "tr", "th", "td", "pre",
"mx-reply", "mx-reply",
} }
@ -283,7 +285,6 @@ class HTMLProcessor:
) )
def sanitize_settings( def sanitize_settings(
self, inline: bool = False, outgoing: bool = False, room_id: str = "", self, inline: bool = False, outgoing: bool = False, room_id: str = "",
) -> dict: ) -> dict:
@ -303,6 +304,9 @@ class HTMLProcessor:
"ol": {"start"}, "ol": {"start"},
"hr": {"width"}, "hr": {"width"},
"span": {"data-mx-color"}, "span": {"data-mx-color"},
"img": {
"data-mx-emote", "src", "alt", "title", "width", "height",
},
}} }}
username_link_regexes = [] username_link_regexes = []
@ -316,9 +320,9 @@ class HTMLProcessor:
return { return {
"tags": inline_tags if inline else all_tags, "tags": inline_tags if inline else all_tags,
"attributes": inlines_attributes if inline else attributes, "attributes": inlines_attributes if inline else attributes,
"empty": {} if inline else {"hr", "br"}, "empty": {} if inline else {"hr", "br", "img"},
"separate": {"a"} if inline else { "separate": {"a"} if inline else {
"a", "p", "li", "table", "tr", "th", "td", "br", "hr", "a", "p", "li", "table", "tr", "th", "td", "br", "hr", "img",
}, },
"whitespace": {}, "whitespace": {},
"keep_typographic_whitespace": True, "keep_typographic_whitespace": True,
@ -388,7 +392,28 @@ class HTMLProcessor:
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>`."""
if el.tag == "img": if el.tag != "img":
return el
src = el.attrib.get("src")
width = el.attrib.get("width")
height = el.attrib.get("height")
is_emote = "data-mx-emote" in el.attrib
if src.startswith("mxc://"):
el.attrib["src"] = nio.Api.mxc_to_http(src)
if is_emote and not width and not height:
el.attrib["width"] = 32
el.attrib["height"] = 32
elif is_emote and width and not height:
el.attrib["height"] = width
elif is_emote and height and not width:
el.attrib["width"] = height
elif not is_emote and (not width or not height):
el.tag = "a" el.tag = "a"
el.attrib["href"] = el.attrib.pop("src", "") el.attrib["href"] = el.attrib.pop("src", "")
el.text = el.attrib.pop("alt", None) or el.attrib["href"] el.text = el.attrib.pop("alt", None) or el.attrib["href"]