From fb35a6ec14650da8747a7fa0913b364f53776e24 Mon Sep 17 00:00:00 2001 From: miruka Date: Wed, 20 May 2020 06:17:14 -0400 Subject: [PATCH] Implement replying to event in backend --- src/backend/matrix_client.py | 57 ++++++++++++++++++- src/backend/models/items.py | 12 ++-- src/gui/Pages/Chat/Chat.qml | 1 + src/gui/Pages/Chat/ChatPage.qml | 2 + src/gui/Pages/Chat/Composer.qml | 2 +- src/gui/Pages/Chat/ReplyBar.qml | 6 +- src/gui/Pages/Chat/Timeline/EventDelegate.qml | 1 + 7 files changed, 68 insertions(+), 13 deletions(-) diff --git a/src/backend/matrix_client.py b/src/backend/matrix_client.py index bf457c80..e82405ca 100644 --- a/src/backend/matrix_client.py +++ b/src/backend/matrix_client.py @@ -53,6 +53,18 @@ else: CryptDict = Dict[str, Any] +REPLY_FALLBACK = ( +"" + "
" + 'In reply to ' + '{user_id}' + "
" + "{content}" + "
" +"
" +"{reply_content}" +) + class UploadReturn(NamedTuple): """Details for an uploaded file.""" @@ -380,7 +392,9 @@ class MatrixClient(nio.AsyncClient): return {**self.invited_rooms, **self.rooms} - async def send_text(self, room_id: str, text: str) -> None: + async def send_text( + self, room_id: str, text: str, reply_to_event_id: Optional[str] = None, + ) -> None: """Send a markdown `m.text` or `m.notice` (with `/me`) message .""" from_md = partial(HTML.from_markdown, room_id=room_id) @@ -390,6 +404,8 @@ class MatrixClient(nio.AsyncClient): escape = True text = text[1:] + content: Dict[str, Any] + if text.startswith("/me ") and not escape: event_type = nio.RoomMessageEmote text = text[len("/me "): ] @@ -406,6 +422,30 @@ class MatrixClient(nio.AsyncClient): content["format"] = "org.matrix.custom.html" content["formatted_body"] = to_html + if reply_to_event_id: + to: Event = \ + self.models[self.user_id, room_id, "events"][reply_to_event_id] + + content["format"] = "org.matrix.custom.html" + content["body"] = f"> <{to.sender_id}> {to.origin_body}" + + to_html = REPLY_FALLBACK.format( + room_id = room_id, + event_id = reply_to_event_id, + user_id = to.sender_id, + content = + to.origin_formatted_body or html.escape(to.origin_body), + + reply_content = to_html, + ) + + echo_body = HTML.filter(to_html) + content["formatted_body"] = HTML.filter(to_html, outgoing=True) + + content["m.relates_to"] = { + "m.in_reply_to": { "event_id": reply_to_event_id }, + } + # Can't use the standard Matrix transaction IDs; they're only visible # to the sender so our other accounts wouldn't be able to replace # local echoes by real messages. @@ -414,7 +454,13 @@ class MatrixClient(nio.AsyncClient): mentions = HTML.mentions_in_html(echo_body) await self._local_echo( - room_id, tx_id, event_type, content=echo_body, mentions=mentions, + room_id, + tx_id, + event_type, + content = echo_body, + mentions = mentions, + origin_body = content["body"], + origin_formatted_body = content.get("formatted_body") or "", ) await self._send_message(room_id, content, tx_id) @@ -650,7 +696,8 @@ class MatrixClient(nio.AsyncClient): room_id, transaction_id, event_type, - inline_content = path.name, + inline_content = content["body"], + origin_body = content["body"], media_url = url, media_title = path.name, media_width = content["info"].get("w", 0), @@ -1389,6 +1436,10 @@ class MatrixClient(nio.AsyncClient): target_name = target_name, target_avatar = target_avatar, links = Event.parse_links(content), + + origin_body = getattr(ev, "body", "") or "", + origin_formatted_body = getattr(ev, "formatted_body", "") or "", + fetch_profile = (must_fetch_sender or must_fetch_target) if override_fetch_profile is None else diff --git a/src/backend/models/items.py b/src/backend/models/items.py index 665454dd..f15a4fd5 100644 --- a/src/backend/models/items.py +++ b/src/backend/models/items.py @@ -219,11 +219,13 @@ class Event(ModelItem): sender_avatar: str = field() fetch_profile: bool = False - content: str = "" - inline_content: str = "" - reason: str = "" - links: List[str] = field(default_factory=list) - mentions: List[Tuple[str, str]] = field(default_factory=list) + origin_body: str = "" + origin_formatted_body: str = "" + content: str = "" + inline_content: str = "" + reason: str = "" + links: List[str] = field(default_factory=list) + mentions: List[Tuple[str, str]] = field(default_factory=list) type_specifier: TypeSpecifier = TypeSpecifier.Unset diff --git a/src/gui/Pages/Chat/Chat.qml b/src/gui/Pages/Chat/Chat.qml index b1ff1b2a..0415c17f 100644 --- a/src/gui/Pages/Chat/Chat.qml +++ b/src/gui/Pages/Chat/Chat.qml @@ -22,6 +22,7 @@ Item { property bool ready: Boolean(userInfo && roomInfo) property bool longLoading: false + property string replyToEventId: "" property string replyToUserId: "" property string replyToDisplayName: "" diff --git a/src/gui/Pages/Chat/ChatPage.qml b/src/gui/Pages/Chat/ChatPage.qml index 52b225a9..644a2757 100644 --- a/src/gui/Pages/Chat/ChatPage.qml +++ b/src/gui/Pages/Chat/ChatPage.qml @@ -69,9 +69,11 @@ HColumnPage { } ReplyBar { + replyToEventId: chat.replyToEventId replyToUserId: chat.replyToUserId replyToDisplayName: chat.replyToDisplayName onCancel: { + chat.replyToEventId = "" chat.replyToUserId = "" chat.replyToDisplayName = "" } diff --git a/src/gui/Pages/Chat/Composer.qml b/src/gui/Pages/Chat/Composer.qml index 306aa134..2c0e56c5 100644 --- a/src/gui/Pages/Chat/Composer.qml +++ b/src/gui/Pages/Chat/Composer.qml @@ -188,7 +188,7 @@ Rectangle { if (textArea.text === "") { return } - const args = [chat.roomId, toSend] + const args = [chat.roomId, toSend, chat.replyToEventId] py.callClientCoro(writingUserId, "send_text", args) area.clear() diff --git a/src/gui/Pages/Chat/ReplyBar.qml b/src/gui/Pages/Chat/ReplyBar.qml index b036c5cf..30cedfde 100644 --- a/src/gui/Pages/Chat/ReplyBar.qml +++ b/src/gui/Pages/Chat/ReplyBar.qml @@ -9,7 +9,7 @@ InfoBar { icon.svgName: "reply-to" label.textFormat: Text.StyledText label.text: - replyToUserId ? + replyToEventId ? utils.coloredNameHtml(replyToDisplayName, replyToUserId) : "" @@ -17,6 +17,7 @@ InfoBar { signal cancel() + property string replyToEventId: "" property string replyToUserId: "" property string replyToDisplayName: "" @@ -25,9 +26,6 @@ InfoBar { backgroundColor: "transparent" icon.name: "reply-cancel" icon.color: theme.colors.negativeBackground - // iconItem.small: true - // topPadding: 0 - // bottomPadding: topPadding onClicked: cancel() Layout.fillHeight: true diff --git a/src/gui/Pages/Chat/Timeline/EventDelegate.qml b/src/gui/Pages/Chat/Timeline/EventDelegate.qml index dd88aa8f..c173400b 100644 --- a/src/gui/Pages/Chat/Timeline/EventDelegate.qml +++ b/src/gui/Pages/Chat/Timeline/EventDelegate.qml @@ -233,6 +233,7 @@ HColumnLayout { text: qsTr("Reply") onTriggered: { + chat.replyToEventId = model.id chat.replyToUserId = model.sender_id chat.replyToDisplayName = model.sender_name }