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 } }