diff --git a/src/timeline/DelegateChooser.cpp b/src/timeline/DelegateChooser.cpp
index 1f5fae7e..8598fa77 100644
--- a/src/timeline/DelegateChooser.cpp
+++ b/src/timeline/DelegateChooser.cpp
@@ -123,10 +123,6 @@ DelegateChooser::DelegateIncubator::statusChanged(QQmlIncubator::Status status)
}
chooser.child_->setParentItem(&chooser);
- connect(chooser.child_, &QQuickItem::heightChanged, &chooser, [this]() {
- chooser.setHeight(chooser.child_->height());
- });
- chooser.setHeight(chooser.child_->height());
QQmlEngine::setObjectOwnership(chooser.child_,
QQmlEngine::ObjectOwnership::JavaScriptOwnership);
emit chooser.childChanged();
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index be4bc09e..94d43a83 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -293,16 +293,16 @@ EventStore::handleSync(const mtx::responses::Timeline &events)
}
for (const auto &event : events.events) {
- std::string relates_to;
+ std::set<std::string> relates_to;
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);
+ auto id = mtx::accessors::relations(*redacted);
+ if (id.annotates()) {
+ auto idx = idToIndex(id.annotates()->event_id);
if (idx) {
events_by_id_.remove(
{room_id_, redaction->redacts});
@@ -312,20 +312,17 @@ EventStore::handleSync(const mtx::responses::Timeline &events)
}
}
- relates_to = redaction->redacts;
- } else if (auto reaction =
- std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(
- &event)) {
- relates_to = reaction->content.relates_to.event_id;
+ relates_to.insert(redaction->redacts);
} else {
- relates_to = mtx::accessors::in_reply_to_event(event);
+ for (const auto &r : mtx::accessors::relations(event).relations)
+ relates_to.insert(r.event_id);
}
- if (!relates_to.empty()) {
- auto idx = cache::client()->getTimelineIndex(room_id_, relates_to);
+ for (const auto &relates_to_id : relates_to) {
+ auto idx = cache::client()->getTimelineIndex(room_id_, relates_to_id);
if (idx) {
- events_by_id_.remove({room_id_, relates_to});
- decryptedEvents_.remove({room_id_, relates_to});
+ events_by_id_.remove({room_id_, relates_to_id});
+ decryptedEvents_.remove({room_id_, relates_to_id});
events_.remove({room_id_, *idx});
emit dataChanged(toExternalIdx(*idx), toExternalIdx(*idx));
}
@@ -408,6 +405,52 @@ EventStore::handle_room_verification(mtx::events::collections::TimelineEvents ev
event);
}
+std::vector<mtx::events::collections::TimelineEvents>
+EventStore::edits(const std::string &event_id)
+{
+ auto event_ids = cache::client()->relatedEvents(room_id_, event_id);
+
+ auto original_event = get(event_id, "", false, false);
+ if (!original_event)
+ return {};
+
+ auto original_sender = mtx::accessors::sender(*original_event);
+ auto original_relations = mtx::accessors::relations(*original_event);
+
+ std::vector<mtx::events::collections::TimelineEvents> edits;
+ for (const auto &id : event_ids) {
+ auto related_event = get(id, event_id, false, false);
+ if (!related_event)
+ continue;
+
+ auto related_ev = *related_event;
+
+ auto edit_rel = mtx::accessors::relations(related_ev);
+ if (edit_rel.replaces() == event_id &&
+ original_sender == mtx::accessors::sender(related_ev)) {
+ if (edit_rel.synthesized && original_relations.reply_to() &&
+ !edit_rel.reply_to()) {
+ edit_rel.relations.push_back(
+ {mtx::common::RelationType::InReplyTo,
+ original_relations.reply_to().value()});
+ mtx::accessors::set_relations(related_ev, std::move(edit_rel));
+ }
+ edits.push_back(std::move(related_ev));
+ }
+ }
+
+ auto c = cache::client();
+ std::sort(edits.begin(),
+ edits.end(),
+ [this, c](const mtx::events::collections::TimelineEvents &a,
+ const mtx::events::collections::TimelineEvents &b) {
+ return c->getArrivalIndex(this->room_id_, mtx::accessors::event_id(a)) <
+ c->getArrivalIndex(this->room_id_, mtx::accessors::event_id(b));
+ });
+
+ return edits;
+}
+
QVariantList
EventStore::reactions(const std::string &event_id)
{
@@ -430,13 +473,14 @@ EventStore::reactions(const std::string &event_id)
if (auto reaction = std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(
related_event);
- reaction && reaction->content.relates_to.key) {
- auto &agg = aggregation[reaction->content.relates_to.key.value()];
+ reaction && reaction->content.relations.annotates() &&
+ reaction->content.relations.annotates()->key) {
+ auto key = reaction->content.relations.annotates()->key.value();
+ auto &agg = aggregation[key];
if (agg.count == 0) {
Reaction temp{};
- temp.key_ =
- QString::fromStdString(reaction->content.relates_to.key.value());
+ temp.key_ = QString::fromStdString(key);
reactions.push_back(temp);
}
@@ -489,7 +533,13 @@ EventStore::get(int idx, bool decrypt)
if (!event_id)
return nullptr;
- auto event = cache::client()->getEvent(room_id_, *event_id);
+ std::optional<mtx::events::collections::TimelineEvent> event;
+ auto edits_ = edits(*event_id);
+ if (edits_.empty())
+ event = cache::client()->getEvent(room_id_, *event_id);
+ else
+ event = {edits_.back()};
+
if (!event)
return nullptr;
else
@@ -691,8 +741,7 @@ EventStore::decryptEvent(const IdIndex &idx,
body["unsigned"] = e.unsigned_data;
// relations are unencrypted in content...
- if (json old_ev = e; old_ev["content"].count("m.relates_to") != 0)
- body["content"]["m.relates_to"] = old_ev["content"]["m.relates_to"];
+ mtx::common::add_relations(body["content"], e.content.relations);
json event_array = json::array();
event_array.push_back(body);
@@ -717,7 +766,7 @@ EventStore::decryptEvent(const IdIndex &idx,
}
mtx::events::collections::TimelineEvents *
-EventStore::get(std::string_view id, std::string_view related_to, bool decrypt)
+EventStore::get(std::string_view id, std::string_view related_to, bool decrypt, bool resolve_edits)
{
if (this->thread() != QThread::currentThread())
nhlog::db()->warn("{} called from a different thread!", __func__);
@@ -725,7 +774,16 @@ EventStore::get(std::string_view id, std::string_view related_to, bool decrypt)
if (id.empty())
return nullptr;
- IdIndex index{room_id_, std::string(id.data(), id.size())};
+ IdIndex index{room_id_, std::string(id)};
+ if (resolve_edits) {
+ auto edits_ = edits(index.id);
+ if (!edits_.empty()) {
+ index.id = mtx::accessors::event_id(edits_.back());
+ auto event_ptr =
+ new mtx::events::collections::TimelineEvents(std::move(edits_.back()));
+ events_by_id_.insert(index, event_ptr);
+ }
+ }
auto event_ptr = events_by_id_.object(index);
if (!event_ptr) {
diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h
index f8eff9a9..ced7bdc0 100644
--- a/src/timeline/EventStore.h
+++ b/src/timeline/EventStore.h
@@ -66,7 +66,8 @@ public:
// relatedFetched event
mtx::events::collections::TimelineEvents *get(std::string_view id,
std::string_view related_to,
- bool decrypt = true);
+ bool decrypt = true,
+ bool resolve_edits = true);
// always returns a proper event as long as the idx is valid
mtx::events::collections::TimelineEvents *get(int idx, bool decrypt = true);
@@ -110,6 +111,7 @@ public slots:
void clearTimeline();
private:
+ std::vector<mtx::events::collections::TimelineEvents> edits(const std::string &event_id);
mtx::events::collections::TimelineEvents *decryptEvent(
const IdIndex &idx,
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e);
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index b31c1f76..08cbd15b 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -268,7 +268,18 @@ InputBar::message(QString msg, MarkdownOverride useMarkdown)
text.format = "org.matrix.custom.html";
}
- if (!room->reply().isEmpty()) {
+ if (!room->edit().isEmpty()) {
+ if (!room->reply().isEmpty()) {
+ text.relations.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
+ room->resetReply();
+ }
+
+ text.relations.relations.push_back(
+ {mtx::common::RelationType::Replace, room->edit().toStdString()});
+ room->resetEdit();
+
+ } else if (!room->reply().isEmpty()) {
auto related = room->relatedInfo(room->reply());
QString body;
@@ -294,7 +305,8 @@ InputBar::message(QString msg, MarkdownOverride useMarkdown)
text.formatted_body =
utils::getFormattedQuoteBody(related, msg.toHtmlEscaped()).toStdString();
- text.relates_to.in_reply_to.event_id = related.related_event;
+ text.relations.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, related.related_event});
room->resetReply();
}
@@ -316,9 +328,15 @@ InputBar::emote(QString msg)
}
if (!room->reply().isEmpty()) {
- emote.relates_to.in_reply_to.event_id = room->reply().toStdString();
+ emote.relations.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
room->resetReply();
}
+ if (!room->edit().isEmpty()) {
+ emote.relations.relations.push_back(
+ {mtx::common::RelationType::Replace, room->edit().toStdString()});
+ room->resetEdit();
+ }
room->sendMessageEvent(emote, mtx::events::EventType::RoomMessage);
}
@@ -346,9 +364,15 @@ InputBar::image(const QString &filename,
image.url = url.toStdString();
if (!room->reply().isEmpty()) {
- image.relates_to.in_reply_to.event_id = room->reply().toStdString();
+ image.relations.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
room->resetReply();
}
+ if (!room->edit().isEmpty()) {
+ image.relations.relations.push_back(
+ {mtx::common::RelationType::Replace, room->edit().toStdString()});
+ room->resetEdit();
+ }
room->sendMessageEvent(image, mtx::events::EventType::RoomMessage);
}
@@ -371,9 +395,15 @@ InputBar::file(const QString &filename,
file.url = url.toStdString();
if (!room->reply().isEmpty()) {
- file.relates_to.in_reply_to.event_id = room->reply().toStdString();
+ file.relations.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
room->resetReply();
}
+ if (!room->edit().isEmpty()) {
+ file.relations.relations.push_back(
+ {mtx::common::RelationType::Replace, room->edit().toStdString()});
+ room->resetEdit();
+ }
room->sendMessageEvent(file, mtx::events::EventType::RoomMessage);
}
@@ -397,9 +427,15 @@ InputBar::audio(const QString &filename,
audio.url = url.toStdString();
if (!room->reply().isEmpty()) {
- audio.relates_to.in_reply_to.event_id = room->reply().toStdString();
+ audio.relations.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
room->resetReply();
}
+ if (!room->edit().isEmpty()) {
+ audio.relations.relations.push_back(
+ {mtx::common::RelationType::Replace, room->edit().toStdString()});
+ room->resetEdit();
+ }
room->sendMessageEvent(audio, mtx::events::EventType::RoomMessage);
}
@@ -422,9 +458,15 @@ InputBar::video(const QString &filename,
video.url = url.toStdString();
if (!room->reply().isEmpty()) {
- video.relates_to.in_reply_to.event_id = room->reply().toStdString();
+ video.relations.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
room->resetReply();
}
+ if (!room->edit().isEmpty()) {
+ video.relations.relations.push_back(
+ {mtx::common::RelationType::Replace, room->edit().toStdString()});
+ room->resetEdit();
+ }
room->sendMessageEvent(video, mtx::events::EventType::RoomMessage);
}
@@ -518,6 +560,8 @@ InputBar::showPreview(const QMimeData &source, QString path, const QStringList &
[this](const QByteArray data, const QString &mime, const QString &fn) {
setUploading(true);
+ setText("");
+
auto payload = std::string(data.data(), data.size());
std::optional<mtx::crypto::EncryptedFile> encryptedFile;
if (cache::isRoomEncrypted(room->roomId().toStdString())) {
diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h
index f173bbc0..696a0dd9 100644
--- a/src/timeline/InputBar.h
+++ b/src/timeline/InputBar.h
@@ -41,6 +41,7 @@ public slots:
QString text() const;
QString previousText();
QString nextText();
+ void setText(QString newText) { emit textChanged(newText); }
void send();
void paste(bool fromMouse);
@@ -58,6 +59,7 @@ private slots:
signals:
void insertText(QString text);
+ void textChanged(QString newText);
void uploadingChanged(bool value);
private:
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index efeba146..6caac132 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -288,6 +288,8 @@ TimelineModel::roleNames() const
{ProportionalHeight, "proportionalHeight"},
{Id, "id"},
{State, "state"},
+ {IsEdited, "isEdited"},
+ {IsEditable, "isEditable"},
{IsEncrypted, "isEncrypted"},
{IsRoomEncrypted, "isRoomEncrypted"},
{ReplyTo, "replyTo"},
@@ -360,7 +362,7 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
const static QRegularExpression replyFallback(
"<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption);
- bool isReply = !in_reply_to_event(event).empty();
+ bool isReply = relations(event).reply_to().has_value();
auto formattedBody_ = QString::fromStdString(formatted_body(event));
if (formattedBody_.isEmpty()) {
@@ -409,8 +411,12 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
return QVariant(prop > 0 ? prop : 1.);
}
- case Id:
- return QVariant(QString::fromStdString(event_id(event)));
+ case Id: {
+ if (auto replaces = relations(event).replaces())
+ return QVariant(QString::fromStdString(replaces.value()));
+ else
+ return QVariant(QString::fromStdString(event_id(event)));
+ }
case State: {
auto id = QString::fromStdString(event_id(event));
auto containsOthers = [](const auto &vec) {
@@ -430,6 +436,11 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
else
return qml_mtx_events::Received;
}
+ case IsEdited:
+ return QVariant(relations(event).replaces().has_value());
+ case IsEditable:
+ return QVariant(!is_state_event(event) && mtx::accessors::sender(event) ==
+ http::client()->user_id().to_string());
case IsEncrypted: {
auto id = event_id(event);
auto encrypted_event = events.get(id, id, false);
@@ -442,9 +453,9 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
return cache::isRoomEncrypted(room_id_.toStdString());
}
case ReplyTo:
- return QVariant(QString::fromStdString(in_reply_to_event(event)));
+ return QVariant(QString::fromStdString(relations(event).reply_to().value_or("")));
case Reactions: {
- auto id = event_id(event);
+ auto id = relations(event).replaces().value_or(event_id(event));
return QVariant::fromValue(events.reactions(id));
}
case RoomId:
@@ -724,15 +735,30 @@ TimelineModel::updateLastMessage()
void
TimelineModel::setCurrentIndex(int index)
{
- if (!ChatPage::instance()->isActiveWindow())
- return;
-
auto oldIndex = idToIndex(currentId);
currentId = indexToId(index);
- emit currentIndexChanged(index);
+ if (index != oldIndex)
+ emit currentIndexChanged(index);
+
+ if (!ChatPage::instance()->isActiveWindow())
+ return;
- if ((oldIndex > index || oldIndex == -1) && !currentId.startsWith("m")) {
- readEvent(currentId.toStdString());
+ if (!currentId.startsWith("m")) {
+ auto oldReadIndex =
+ cache::getEventIndex(roomId().toStdString(), currentReadId.toStdString());
+ auto nextEventIndexAndId =
+ cache::lastInvisibleEventAfter(roomId().toStdString(), currentId.toStdString());
+
+ if (nextEventIndexAndId &&
+ (!oldReadIndex || *oldReadIndex < nextEventIndexAndId->first)) {
+ readEvent(nextEventIndexAndId->second);
+ currentReadId = QString::fromStdString(nextEventIndexAndId->second);
+
+ nhlog::net()->info("Marked as read {}, index {}, oldReadIndex {}",
+ nextEventIndexAndId->second,
+ nextEventIndexAndId->first,
+ *oldReadIndex);
+ }
}
}
@@ -821,6 +847,12 @@ TimelineModel::replyAction(QString id)
setReply(id);
}
+void
+TimelineModel::editAction(QString id)
+{
+ setEdit(id);
+}
+
RelatedInfo
TimelineModel::relatedInfo(QString id)
{
@@ -1509,6 +1541,51 @@ TimelineModel::formatMemberEvent(QString id)
return rendered;
}
+void
+TimelineModel::setEdit(QString newEdit)
+{
+ if (edit_.startsWith('m'))
+ return;
+
+ if (edit_ != newEdit) {
+ auto ev = events.get(newEdit.toStdString(), "");
+ if (ev && mtx::accessors::sender(*ev) == http::client()->user_id().to_string()) {
+ auto e = *ev;
+ setReply(QString::fromStdString(
+ mtx::accessors::relations(e).reply_to().value_or("")));
+
+ auto msgType = mtx::accessors::msg_type(e);
+ if (msgType == mtx::events::MessageType::Text ||
+ msgType == mtx::events::MessageType::Notice) {
+ input()->setText(relatedInfo(newEdit).quoted_body);
+ } else if (msgType == mtx::events::MessageType::Emote) {
+ input()->setText("/me " + relatedInfo(newEdit).quoted_body);
+ } else {
+ input()->setText("");
+ }
+
+ edit_ = newEdit;
+ } else {
+ resetReply();
+
+ input()->setText("");
+ edit_ = "";
+ }
+ emit editChanged(edit_);
+ }
+}
+
+void
+TimelineModel::resetEdit()
+{
+ if (!edit_.isEmpty()) {
+ edit_ = "";
+ emit editChanged(edit_);
+ input()->setText("");
+ resetReply();
+ }
+}
+
QString
TimelineModel::roomName() const
{
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index df067fd4..5f599741 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -146,6 +146,7 @@ class TimelineModel : public QAbstractListModel
Q_PROPERTY(std::vector<QString> typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY
typingUsersChanged)
Q_PROPERTY(QString reply READ reply WRITE setReply NOTIFY replyChanged RESET resetReply)
+ Q_PROPERTY(QString edit READ edit WRITE setEdit NOTIFY editChanged RESET resetEdit)
Q_PROPERTY(
bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged)
Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
@@ -182,6 +183,8 @@ public:
ProportionalHeight,
Id,
State,
+ IsEdited,
+ IsEditable,
IsEncrypted,
IsRoomEncrypted,
ReplyTo,
@@ -215,6 +218,7 @@ public:
Q_INVOKABLE void viewDecryptedRawMessage(QString id) const;
Q_INVOKABLE void openUserProfile(QString userid, bool global = false);
Q_INVOKABLE void openRoomSettings();
+ Q_INVOKABLE void editAction(QString id);
Q_INVOKABLE void replyAction(QString id);
Q_INVOKABLE void readReceiptsAction(QString id) const;
Q_INVOKABLE void redactEvent(QString id);
@@ -258,6 +262,9 @@ public slots:
QString reply() const { return reply_; }
void setReply(QString newReply)
{
+ if (edit_.startsWith('m'))
+ return;
+
if (reply_ != newReply) {
reply_ = newReply;
emit replyChanged(reply_);
@@ -270,6 +277,9 @@ public slots:
emit replyChanged(reply_);
}
}
+ QString edit() const { return edit_; }
+ void setEdit(QString newEdit);
+ void resetEdit();
void setDecryptDescription(bool decrypt) { decryptDescription = decrypt; }
void clearTimeline() { events.clearTimeline(); }
void receivedSessionKey(const std::string &session_key)
@@ -294,6 +304,7 @@ signals:
void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo);
void typingUsersChanged(std::vector<QString> users);
void replyChanged(QString reply);
+ void editChanged(QString reply);
void paginationInProgressChanged(const bool);
void newCallEvent(const mtx::events::collections::TimelineEvents &event);
@@ -324,8 +335,8 @@ private:
bool decryptDescription = true;
bool m_paginationInProgress = false;
- QString currentId;
- QString reply_;
+ QString currentId, currentReadId;
+ QString reply_, edit_;
std::vector<QString> typingUsers_;
TimelineViewManager *manager_;
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 0ed680f8..f2e6d571 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -21,6 +21,7 @@
#include "dialogs/ImageOverlay.h"
#include "emoji/EmojiModel.h"
#include "emoji/Provider.h"
+#include "ui/NhekoCursorShape.h"
#include "ui/NhekoDropArea.h"
#include <iostream> //only for debugging
@@ -118,6 +119,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice");
qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser");
qmlRegisterType<NhekoDropArea>("im.nheko", 1, 0, "NhekoDropArea");
+ qmlRegisterType<NhekoCursorShape>("im.nheko", 1, 0, "CursorShape");
qmlRegisterUncreatableType<DeviceVerificationFlow>(
"im.nheko", 1, 0, "DeviceVerificationFlow", "Can't create verification flow from QML!");
qmlRegisterUncreatableType<UserProfile>(
@@ -176,10 +178,6 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
view->setResizeMode(QQuickWidget::SizeRootObjectToView);
container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
- view->quickWindow()->setTextRenderType(QQuickWindow::NativeTextRendering);
-#endif
-
connect(view, &QQuickWidget::statusChanged, this, [](QQuickWidget::Status status) {
nhlog::ui()->debug("Status changed to {}", status);
});
@@ -508,9 +506,11 @@ TimelineViewManager::queueReactionMessage(const QString &reactedEvent, const QSt
// If selfReactedEvent is empty, that means we haven't previously reacted
if (selfReactedEvent.isEmpty()) {
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();
+ mtx::common::Relation rel;
+ rel.rel_type = mtx::common::RelationType::Annotation;
+ rel.event_id = reactedEvent.toStdString();
+ rel.key = reactionKey.toStdString();
+ reaction.relations.relations.push_back(rel);
timeline_->sendMessageEvent(reaction, mtx::events::EventType::Reaction);
// Otherwise, we have previously reacted and the reaction should be redacted
@@ -546,3 +546,9 @@ TimelineViewManager::queueCallMessage(const QString &roomid,
{
models.value(roomid)->sendMessageEvent(callHangUp, mtx::events::EventType::CallHangUp);
}
+
+void
+TimelineViewManager::focusMessageInput()
+{
+ emit focusInput();
+}
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 3e58bb43..61fce574 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -66,6 +66,7 @@ public:
Q_INVOKABLE void openLink(QString link) const;
+ Q_INVOKABLE void focusMessageInput();
Q_INVOKABLE void openInviteUsersDialog();
Q_INVOKABLE void openMemberListDialog() const;
Q_INVOKABLE void openLeaveRoomDialog() const;
@@ -86,6 +87,7 @@ signals:
void showRoomList();
void narrowViewChanged();
void focusChanged();
+ void focusInput();
public slots:
void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
|