diff --git a/src/Cache.cpp b/src/Cache.cpp index 0d75ac51..2178bbfb 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -1681,6 +1681,27 @@ Cache::storeEvent(const std::string &room_id, txn.commit(); } +void +Cache::replaceEvent(const std::string &room_id, + const std::string &event_id, + const mtx::events::collections::TimelineEvent &event) +{ + auto txn = lmdb::txn::begin(env_); + auto eventsDb = getEventsDb(txn, room_id); + auto relationsDb = getRelationsDb(txn, room_id); + auto event_json = mtx::accessors::serialize_event(event.data).dump(); + + { + eventsDb.del(txn, event_id); + eventsDb.put(txn, event_id, event_json); + for (auto relation : mtx::accessors::relations(event.data).relations) { + relationsDb.put(txn, relation.event_id, event_id); + } + } + + txn.commit(); +} + std::vector Cache::relatedEvents(const std::string &room_id, const std::string &event_id) { diff --git a/src/Cache_p.h b/src/Cache_p.h index f2911622..669f1895 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -184,6 +184,9 @@ public: void storeEvent(const std::string &room_id, const std::string &event_id, const mtx::events::collections::TimelineEvent &event); + void replaceEvent(const std::string &room_id, + const std::string &event_id, + const mtx::events::collections::TimelineEvent &event); std::vector relatedEvents(const std::string &room_id, const std::string &event_id); diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index 4a9f0fff..04f7ef76 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -185,6 +185,48 @@ EventStore::EventStore(std::string room_id, QObject *) [this](std::string txn_id, std::string event_id) { nhlog::ui()->debug("sent {}", txn_id); + // Replace the event_id in pending edits/replies/redactions with the actual + // event_id of this event. This allows one to edit and reply to events that are + // currently pending. + + // FIXME (introduced by balsoft): this doesn't work for encrypted events, but + // allegedly it's hard to fix so I'll leave my first contribution at that + for (auto related_event_id : cache::client()->relatedEvents(room_id_, txn_id)) { + if (cache::client()->getEvent(room_id_, related_event_id)) { + auto related_event = + cache::client()->getEvent(room_id_, related_event_id).value(); + auto relations = mtx::accessors::relations(related_event.data); + + // Replace the blockquote in fallback reply + auto related_text = + std::get_if>( + &related_event.data); + if (related_text && relations.reply_to() == txn_id) { + size_t index = + related_text->content.formatted_body.find(txn_id); + if (index != std::string::npos) { + related_text->content.formatted_body.replace( + index, event_id.length(), event_id); + } + } + + for (mtx::common::Relation &rel : relations.relations) { + if (rel.event_id == txn_id) + rel.event_id = event_id; + } + + mtx::accessors::set_relations(related_event.data, relations); + + cache::client()->replaceEvent( + room_id_, related_event_id, related_event); + + auto idx = idToIndex(related_event_id); + + events_by_id_.remove({room_id_, related_event_id}); + events_.remove({room_id_, toInternalIdx(*idx)}); + } + } + http::client()->read_event( room_id_, event_id, [this, event_id](mtx::http::RequestErr err) { if (err) { @@ -193,6 +235,11 @@ EventStore::EventStore(std::string room_id, QObject *) } }); + auto idx = idToIndex(event_id); + + if (idx) + emit dataChanged(*idx, *idx); + cache::client()->removePendingStatus(room_id_, txn_id); this->current_txn = ""; this->current_txn_error_count = 0; diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index f29f929e..99547b15 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -375,6 +375,21 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj connect(&events, &EventStore::updateFlowEventId, this, [this](std::string event_id) { this->updateFlowEventId(event_id); }); + // When a message is sent, check if the current edit/reply relates to that message, + // and update the event_id so that it points to the sent message and not the pending one. + connect(&events, + &EventStore::messageSent, + this, + [this](std::string txn_id, std::string event_id) { + if (edit_.toStdString() == txn_id) { + edit_ = QString::fromStdString(event_id); + emit editChanged(edit_); + } + if (reply_.toStdString() == txn_id) { + reply_ = QString::fromStdString(event_id); + emit replyChanged(reply_); + } + }); showEventTimer.callOnTimeout(this, &TimelineModel::scrollTimerEvent); } @@ -568,10 +583,8 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r 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() && - !event_id(event).empty() && event_id(event).front() == '$'); + 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, "", false); @@ -1796,9 +1809,6 @@ TimelineModel::formatMemberEvent(QString id) void TimelineModel::setEdit(QString newEdit) { - if (edit_.startsWith('m')) - return; - if (newEdit.isEmpty()) { resetEdit(); return;