diff --git a/TODO.md b/TODO.md index 1ebc35d9..41b10499 100644 --- a/TODO.md +++ b/TODO.md @@ -28,3 +28,5 @@ - `
` scrollbar on overflow
 
 - Make links in room subtitle clickable, formatting?
+
+- Push instead of replacing in stack view
diff --git a/harmonyqml/backend/html_filter.py b/harmonyqml/backend/html_filter.py
index 73b1fad4..3f9bf9c3 100644
--- a/harmonyqml/backend/html_filter.py
+++ b/harmonyqml/backend/html_filter.py
@@ -4,7 +4,7 @@
 import re
 
 import html_sanitizer.sanitizer as sanitizer
-from lxml.html import HtmlElement
+from lxml.html import HtmlElement, etree
 from PyQt5.QtCore import QObject, pyqtProperty, pyqtSlot
 
 
@@ -39,8 +39,20 @@ class HtmlFilter(QObject):
 
 
     @pyqtSlot(str, result=str)
-    def sanitize(self, html: str) -> str:
-        return self._sanitizer.sanitize(html)
+    def filter(self, html: str) -> str:
+        html = self._sanitizer.sanitize(html)
+        if not html:
+            return ""
+
+        tree = etree.fromstring(html, parser=etree.HTMLParser())
+
+        for el in tree.iter("img"):
+            el = self._wrap_img_in_a(el)
+
+        for el in tree.iter("a"):
+            el = self._append_img_to_a(el)
+
+        return str(etree.tostring(tree[0][0], encoding="utf-8"), "utf-8")
 
 
     @pyqtProperty("QVariant")
@@ -73,7 +85,7 @@ class HtmlFilter(QObject):
                 "link_regexes": self.link_regexes,
                 "avoid_hosts": [],
             },
-            "sanitize_href": sanitizer.sanitize_href,
+            "sanitize_href": lambda href: href,
             "element_preprocessors": [
                 sanitizer.bold_span_to_strong,
                 sanitizer.italic_span_to_em,
@@ -101,3 +113,39 @@ class HtmlFilter(QObject):
             el.clear()
 
         return el
+
+
+    def _wrap_img_in_a(self, el: HtmlElement) -> HtmlElement:
+        link   = el.attrib.get("src", "")
+        width  = el.attrib.get("width", "256")
+        height = el.attrib.get("height", "256")
+
+        if el.getparent().tag == "a" or el.tag != "img" or \
+           not self._is_image_path(link):
+            return el
+
+        el.tag    = "a"
+        el.attrib.clear()
+        el.attrib["href"] = link
+        el.append(etree.Element("img", src=link, width=width, height=height))
+        return el
+
+
+    def _append_img_to_a(self, el: HtmlElement) -> HtmlElement:
+        link = el.attrib.get("href", "")
+
+        if not (el.tag == "a" and self._is_image_path(link)):
+            return el
+
+        for _ in el.iter("img"):  # if the  already has an  child
+            return el
+
+        el.append(etree.Element("img", src=link, width="256", height="256"))
+        return el
+
+
+    @staticmethod
+    def _is_image_path(link: str) -> bool:
+        return bool(re.match(
+            r".+\.(jpg|jpeg|png|gif|bmp|webp|tiff|svg)$", link, re.IGNORECASE
+        ))
diff --git a/harmonyqml/components/chat/MessageContent.qml b/harmonyqml/components/chat/MessageContent.qml
index dd94b010..cdcd2203 100644
--- a/harmonyqml/components/chat/MessageContent.qml
+++ b/harmonyqml/components/chat/MessageContent.qml
@@ -39,7 +39,7 @@ Row {
             //      (isOwn ? "  " + content : "")
 
             text: (dict.formatted_body ?
-                   Backend.htmlFilter.sanitize(dict.formatted_body) :
+                   Backend.htmlFilter.filter(dict.formatted_body) :
                    dict.body) +
                   "  " +
                   Qt.formatDateTime(date_time, "hh:mm:ss") +
diff --git a/harmonyqml/components/chat/MessageList.qml b/harmonyqml/components/chat/MessageList.qml
index 065638cf..baa3109e 100644
--- a/harmonyqml/components/chat/MessageList.qml
+++ b/harmonyqml/components/chat/MessageList.qml
@@ -20,5 +20,9 @@ Rectangle {
         clip: true
         topMargin: space
         bottomMargin: space
+
+        // Keep x scroll pages cached, to limit images having to be
+        // reloaded from network.
+        cacheBuffer: height * 6
     }
 }