diff --git a/src/backend/html_markdown.py b/src/backend/html_markdown.py index cbcbea2e..37978ac4 100644 --- a/src/backend/html_markdown.py +++ b/src/backend/html_markdown.py @@ -7,6 +7,7 @@ from typing import DefaultDict, Dict from urllib.parse import unquote import html_sanitizer.sanitizer as sanitizer +import lxml.html # nosec import mistune from html_sanitizer.sanitizer import Sanitizer from lxml.html import HtmlElement, etree # nosec @@ -173,6 +174,19 @@ class HTMLProcessor: ] + def user_id_link_in_html(self, html: str, user_id: str) -> bool: + if not html.strip(): + return False + + regex = re.compile(rf"https?://matrix.to/#/{user_id}", re.IGNORECASE) + + for _, _, href, _ in lxml.html.iterlinks(html): + if regex.match(unquote(href.strip())): + return True + + return False + + def from_markdown( self, text: str, diff --git a/src/backend/matrix_client.py b/src/backend/matrix_client.py index 09dcd024..b360003a 100644 --- a/src/backend/matrix_client.py +++ b/src/backend/matrix_client.py @@ -1073,9 +1073,11 @@ class MatrixClient(nio.AsyncClient): registered = self.models[self.user_id, "rooms"][room.room_id] last_event_date = registered.last_event_date typing_members = registered.typing_members + mentions = registered.mentions except KeyError: last_event_date = datetime.fromtimestamp(0) typing_members = [] + mentions = 0 self.models[self.user_id, "rooms"][room.room_id] = Room( id = room.room_id, @@ -1108,6 +1110,7 @@ class MatrixClient(nio.AsyncClient): can_set_guest_access = can_send_state("m.room.guest_access"), last_event_date = last_event_date, + mentions = mentions, ) diff --git a/src/backend/models/items.py b/src/backend/models/items.py index b5ecb34a..7732b267 100644 --- a/src/backend/models/items.py +++ b/src/backend/models/items.py @@ -80,6 +80,8 @@ class Room(ModelItem): last_event_date: datetime = ZeroDate + mentions: int = 0 + def __lt__(self, other: "Room") -> bool: """Sort by join state, then descending last event date, then name. diff --git a/src/backend/nio_callbacks.py b/src/backend/nio_callbacks.py index ade38076..c9c7007f 100644 --- a/src/backend/nio_callbacks.py +++ b/src/backend/nio_callbacks.py @@ -100,6 +100,11 @@ class NioCallbacks: room_id = room.room_id, ) + + if HTML_PROCESSOR.user_id_link_in_html(co, self.client.user_id): + rooms = self.client.models[self.client.user_id, "rooms"] + rooms[room.room_id].mentions += 1 + await self.client.register_nio_event(room, ev, content=co) diff --git a/src/gui/Base/HTile.qml b/src/gui/Base/HTile.qml index df2ad60f..bb6bcccd 100644 --- a/src/gui/Base/HTile.qml +++ b/src/gui/Base/HTile.qml @@ -6,6 +6,7 @@ import QtQuick.Layouts 1.12 HButton { id: tile + signal leftClicked() signal rightClicked() @@ -24,6 +25,7 @@ HButton { property Component image + contentItem: HRowLayout { id: contentItem spacing: tile.spacing @@ -50,34 +52,37 @@ HButton { Layout.fillHeight: true } + HLabel { + id: rightInfo + font.pixelSize: theme.fontSize.small + verticalAlignment: Qt.AlignVCenter + color: theme.colors.halfDimText + visible: Layout.maximumWidth > 0 + + Layout.fillHeight: true + Layout.maximumWidth: + text && tile.width >= 200 * theme.uiScale ? + implicitWidth : 0 + + Behavior on Layout.maximumWidth { HNumberAnimation {} } + } + HRowLayout { id: additionalInfo visible: visibleChildren.length > 0 } - HLabel { - id: rightInfo - font.pixelSize: theme.fontSize.small - color: theme.colors.halfDimText - - visible: Layout.maximumWidth > 0 - Layout.fillHeight: true - Layout.maximumWidth: - text && tile.width >= 160 * theme.uiScale ? - implicitWidth : 0 - - Behavior on Layout.maximumWidth { HNumberAnimation {} } - } } HRichLabel { id: subtitle textFormat: Text.StyledText font.pixelSize: theme.fontSize.small + verticalAlignment: Qt.AlignVCenter elide: Text.ElideRight color: theme.colors.dimText - visible: Layout.maximumHeight > 0 + Layout.maximumHeight: ! compact && text ? implicitHeight : 0 Layout.fillWidth: true Layout.fillHeight: true diff --git a/src/gui/MainPane/Room.qml b/src/gui/MainPane/Room.qml index 4dc5d8d4..72e98d9c 100644 --- a/src/gui/MainPane/Room.qml +++ b/src/gui/MainPane/Room.qml @@ -33,14 +33,36 @@ HTileDelegate { title.color: theme.mainPane.listView.room.name title.text: model.display_name || qsTr("Empty room") - additionalInfo.children: HIcon { - svgName: "invite-received" - colorize: theme.colors.alertBackground + additionalInfo.children: [ + HLabel { + text: model.mentions + font.pixelSize: theme.fontSize.small + verticalAlignment: Qt.AlignVCenter + leftPadding: theme.spacing / 4 + rightPadding: leftPadding - Layout.maximumWidth: invited ? implicitWidth : 0 + scale: model.mentions === 0 ? 0 : 1 + visible: scale > 0 - Behavior on Layout.maximumWidth { HNumberAnimation {} } - } + background: Rectangle { + color: theme.colors.alertBackground + radius: theme.radius / 4 + } + + Behavior on scale { HNumberAnimation {} } + }, + + HIcon { + svgName: "invite-received" + colorize: theme.colors.alertBackground + small: room.compact + visible: invited + + Layout.maximumWidth: invited ? implicitWidth : 0 + + Behavior on Layout.maximumWidth { HNumberAnimation {} } + } + ] subtitle.color: theme.mainPane.listView.room.subtitle subtitle.textFormat: Text.StyledText