diff --git a/resources/qml/Reactions.qml b/resources/qml/Reactions.qml
index 5b3bbc20..c1091756 100644
--- a/resources/qml/Reactions.qml
+++ b/resources/qml/Reactions.qml
@@ -34,7 +34,7 @@ Flow {
onClicked: {
console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + " in room " + reactionFlow.roomId + ". selfReactedEvent: " + modelData.selfReactedEvent)
- timelineManager.reactToMessage(reactionFlow.roomId, reactionFlow.eventId, modelData.key, modelData.selfReactedEvent)
+ timelineManager.queueReactionMessage(reactionFlow.eventId, modelData.key)
}
diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml
index e87590f1..8186db8a 100644
--- a/resources/qml/TimelineRow.qml
+++ b/resources/qml/TimelineRow.qml
@@ -90,7 +90,6 @@ MouseArea {
ToolTip.visible: hovered
ToolTip.text: qsTr("React")
emojiPicker: emojiPopup
- room_id: model.roomId
event_id: model.id
}
ImageButton {
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index fd185bd9..1d7b4a4a 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -40,19 +40,20 @@ Page {
id: messageContextMenu
modal: true
- function show(eventId_, eventType_, isEncrypted_, showAt) {
+ function show(eventId_, eventType_, isEncrypted_, showAt_) {
eventId = eventId_
eventType = eventType_
isEncrypted = isEncrypted_
- popup(showAt)
+ popup(showAt_)
}
property string eventId
property int eventType
property bool isEncrypted
+
MenuItem {
text: qsTr("React")
- onClicked: chat.model.reactAction(messageContextMenu.eventId)
+ onClicked: emojiPopup.show(messageContextMenu.parent, messageContextMenu.eventId)
}
MenuItem {
text: qsTr("Reply")
diff --git a/resources/qml/emoji/EmojiButton.qml b/resources/qml/emoji/EmojiButton.qml
index f8f75e3e..c5eee4e4 100644
--- a/resources/qml/emoji/EmojiButton.qml
+++ b/resources/qml/emoji/EmojiButton.qml
@@ -8,11 +8,10 @@ import "../"
ImageButton {
property var colors: currentActivePalette
property var emojiPicker
- property string room_id
property string event_id
image: ":/icons/icons/ui/smile.png"
id: emojiButton
- onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, room_id, event_id)
+ onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, event_id)
}
diff --git a/resources/qml/emoji/EmojiPicker.qml b/resources/qml/emoji/EmojiPicker.qml
index ac67af2a..f75221d5 100644
--- a/resources/qml/emoji/EmojiPicker.qml
+++ b/resources/qml/emoji/EmojiPicker.qml
@@ -10,17 +10,17 @@ import "../"
Popup {
- function show(showAt, room_id, event_id) {
- console.debug("Showing emojiPicker for " + event_id + "in room " + room_id)
- parent = showAt
- x = Math.round((showAt.width - width) / 2)
- y = showAt.height
- emojiPopup.room_id = room_id
- emojiPopup.event_id = event_id
- open()
- }
+ function show(showAt, event_id) {
+ console.debug("Showing emojiPicker for " + event_id)
+ if (showAt){
+ parent = showAt
+ x = Math.round((showAt.width - width) / 2)
+ y = showAt.height
+ }
+ emojiPopup.event_id = event_id
+ open()
+ }
- property string room_id
property string event_id
property var colors
property alias model: gridView.model
@@ -102,9 +102,9 @@ Popup {
}
// TODO: maybe add favorites at some point?
onClicked: {
- console.debug("Picked " + model.unicode + "in response to " + emojiPopup.event_id + " in room " + emojiPopup.room_id)
+ console.debug("Picked " + model.unicode + "in response to " + emojiPopup.event_id)
emojiPopup.close()
- timelineManager.queueReactionMessage(emojiPopup.room_id, emojiPopup.event_id, model.unicode)
+ timelineManager.queueReactionMessage(emojiPopup.event_id, model.unicode)
}
}
diff --git a/src/EventAccessors.cpp b/src/EventAccessors.cpp
index 7071819b..0618206c 100644
--- a/src/EventAccessors.cpp
+++ b/src/EventAccessors.cpp
@@ -223,6 +223,20 @@ struct EventInReplyTo
}
};
+struct EventRelatesTo
+{
+ template<class Content>
+ using related_ev_id_t = decltype(Content::relates_to.event_id);
+ template<class T>
+ std::string operator()(const mtx::events::Event<T> &e)
+ {
+ if constexpr (is_detected<related_ev_id_t, T>::value) {
+ return e.content.relates_to.event_id;
+ }
+ return "";
+ }
+};
+
struct EventTransactionId
{
template<class T>
@@ -378,6 +392,11 @@ mtx::accessors::in_reply_to_event(const mtx::events::collections::TimelineEvents
{
return std::visit(EventInReplyTo{}, event);
}
+std::string
+mtx::accessors::relates_to_event_id(const mtx::events::collections::TimelineEvents &event)
+{
+ return std::visit(EventRelatesTo{}, event);
+}
std::string
mtx::accessors::transaction_id(const mtx::events::collections::TimelineEvents &event)
diff --git a/src/EventAccessors.h b/src/EventAccessors.h
index a7577d86..8f08ef1c 100644
--- a/src/EventAccessors.h
+++ b/src/EventAccessors.h
@@ -53,6 +53,8 @@ mimetype(const mtx::events::collections::TimelineEvents &event);
std::string
in_reply_to_event(const mtx::events::collections::TimelineEvents &event);
std::string
+relates_to_event_id(const mtx::events::collections::TimelineEvents &event);
+std::string
transaction_id(const mtx::events::collections::TimelineEvents &event);
int64_t
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index 0bd7a97e..eb1162cc 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -202,6 +202,20 @@ EventStore::handleSync(const mtx::responses::Timeline &events)
if (auto redaction =
std::get_if<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(
&event)) {
+ // fixup reactions
+ auto redacted = events_by_id_.object({room_id_, redaction->redacts});
+ if (redacted) {
+ auto id = mtx::accessors::relates_to_event_id(*redacted);
+ if (!id.empty()) {
+ auto idx = idToIndex(id);
+ if (idx) {
+ events_by_id_.remove(
+ {room_id_, redaction->redacts});
+ emit dataChanged(*idx, *idx);
+ }
+ }
+ }
+
relates_to = redaction->redacts;
} else if (auto reaction =
std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 85d2eb4e..8631eb83 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -1069,8 +1069,9 @@ struct SendMessageVisitor
// reactions need to have the relation outside of ciphertext, or synapse / the homeserver
// cannot handle it correctly. See the MSC for more details:
// https://github.com/matrix-org/matrix-doc/blob/matthew/msc1849/proposals/1849-aggregations.md#end-to-end-encryption
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::Reaction> &msg)
+ void operator()(mtx::events::RoomEvent<mtx::events::msg::Reaction> msg)
{
+ msg.type = mtx::events::EventType::Reaction;
emit model_->addPendingMessageToStore(msg);
}
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index cbe88fd2..f8a84f17 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -197,6 +197,15 @@ public:
Q_INVOKABLE void cacheMedia(QString eventId);
Q_INVOKABLE bool saveMedia(QString eventId) const;
+ std::vector<::Reaction> reactions(const std::string &event_id)
+ {
+ auto list = events.reactions(event_id);
+ std::vector<::Reaction> vec;
+ for (const auto &r : list)
+ vec.push_back(r.value<Reaction>());
+ return vec;
+ }
+
void updateLastMessage();
void addEvents(const mtx::responses::Timeline &events);
template<class T>
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 64af8afb..8cb72edd 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -314,36 +314,39 @@ TimelineViewManager::queueEmoteMessage(const QString &msg)
}
void
-TimelineViewManager::reactToMessage(const QString &roomId,
- const QString &reactedEvent,
- const QString &reactionKey,
- const QString &selfReactedEvent)
+TimelineViewManager::queueReactionMessage(const QString &reactedEvent, const QString &reactionKey)
{
+ if (!timeline_)
+ return;
+
+ auto reactions = timeline_->reactions(reactedEvent.toStdString());
+
+ QString selfReactedEvent;
+ for (const auto &reaction : reactions) {
+ if (reactionKey == reaction.key_) {
+ selfReactedEvent = reaction.selfReactedEvent_;
+ break;
+ }
+ }
+
+ if (selfReactedEvent.startsWith("m"))
+ return;
+
// If selfReactedEvent is empty, that means we haven't previously reacted
if (selfReactedEvent.isEmpty()) {
- queueReactionMessage(roomId, reactedEvent, reactionKey);
+ mtx::events::msg::Reaction reaction;
+ reaction.relates_to.rel_type = mtx::common::RelationType::Annotation;
+ reaction.relates_to.event_id = reactedEvent.toStdString();
+ reaction.relates_to.key = reactionKey.toStdString();
+
+ timeline_->sendMessage(reaction);
// Otherwise, we have previously reacted and the reaction should be redacted
} else {
- auto model = models.value(roomId);
- model->redactEvent(selfReactedEvent);
+ timeline_->redactEvent(selfReactedEvent);
}
}
void
-TimelineViewManager::queueReactionMessage(const QString &roomId,
- const QString &reactedEvent,
- const QString &reactionKey)
-{
- mtx::events::msg::Reaction reaction;
- reaction.relates_to.rel_type = mtx::common::RelationType::Annotation;
- reaction.relates_to.event_id = reactedEvent.toStdString();
- reaction.relates_to.key = reactionKey.toStdString();
-
- auto model = models.value(roomId);
- model->sendMessage(reaction);
-}
-
-void
TimelineViewManager::queueImageMessage(const QString &roomid,
const QString &filename,
const std::optional<mtx::crypto::EncryptedFile> &file,
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index ed095058..63106916 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -61,13 +61,7 @@ public slots:
void setHistoryView(const QString &room_id);
void updateColorPalette();
- void queueReactionMessage(const QString &roomId,
- const QString &reactedEvent,
- const QString &reaction);
- void reactToMessage(const QString &roomId,
- const QString &reactedEvent,
- const QString &reactionKey,
- const QString &selfReactedEvent);
+ void queueReactionMessage(const QString &reactedEvent, const QString &reactionKey);
void queueTextMessage(const QString &msg);
void queueEmoteMessage(const QString &msg);
void queueImageMessage(const QString &roomid,
|