Add support for copying & deleting multiple messages at once

BUG: 496458
This commit is contained in:
Azhar Momin
2026-02-11 18:30:09 +05:30
committed by Joshua Goins
parent 0f634ff795
commit f5d726989f
10 changed files with 357 additions and 15 deletions

View File

@@ -59,6 +59,8 @@
#include <KJobTrackerInterface>
#include <KLocalizedString>
#include <ranges>
using namespace Quotient;
std::function<bool(const Quotient::RoomEvent *)> NeoChatRoom::m_hiddenFilter = [](const Quotient::RoomEvent *) -> bool {
@@ -630,7 +632,14 @@ bool NeoChatRoom::isUserBanned(const QString &user) const
void NeoChatRoom::deleteMessagesByUser(const QString &user, const QString &reason)
{
doDeleteMessagesByUser(user, reason);
QStringList events;
for (const auto &event : messageEvents()) {
if (event->senderId() == user && !event->isRedacted() && !event.viewAs<RedactionEvent>() && !event->isStateEvent()) {
events += event->id();
}
}
doDeleteMessageIds(events, reason);
}
QString NeoChatRoom::historyVisibility() const
@@ -761,16 +770,10 @@ void NeoChatRoom::setUserPowerLevel(const QString &userID, const int &powerLevel
}
}
QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QString reason)
QCoro::Task<void> NeoChatRoom::doDeleteMessageIds(const QStringList eventIds, QString reason)
{
QStringList events;
for (const auto &event : messageEvents()) {
if (event->senderId() == user && !event->isRedacted() && !event.viewAs<RedactionEvent>() && !event->isStateEvent()) {
events += event->id();
}
}
for (const auto &e : events) {
auto job = connection()->callApi<RedactEventJob>(id(), QString::fromLatin1(QUrl::toPercentEncoding(e)), connection()->generateTxnId(), reason);
for (const auto &eventId : eventIds) {
auto job = connection()->callApi<RedactEventJob>(id(), eventId, connection()->generateTxnId(), reason);
co_await qCoro(job.get(), &BaseJob::finished);
if (job->error() != BaseJob::Success) {
qWarning() << "Error: \"" << job->error() << "\" while deleting messages. Aborting";
@@ -1963,4 +1966,96 @@ QList<QString> NeoChatRoom::sortedMemberIds() const
return m_sortedMemberIds;
}
int NeoChatRoom::selectedMessageCount() const
{
return m_selectedMessageIds.size();
}
bool NeoChatRoom::canDeleteSelectedMessages() const
{
if (canSendState("redact"_L1)) {
return true;
}
const QString localUserId = connection()->userId();
return std::ranges::all_of(m_selectedMessageIds, [this, localUserId](const QString &eventId) {
const auto eventIt = findInTimeline(eventId);
if (eventIt == historyEdge()) {
return false;
}
const RoomEvent *event = eventIt->event();
return event && (event->senderId() == localUserId);
});
}
bool NeoChatRoom::isMessageSelected(const QString &eventId) const
{
return m_selectedMessageIds.contains(eventId);
}
void NeoChatRoom::toggleMessageSelection(const QString &eventId)
{
if (!m_selectedMessageIds.remove(eventId)) {
m_selectedMessageIds.insert(eventId);
}
Q_EMIT selectionChanged();
}
QString NeoChatRoom::getFormattedSelectedMessages() const
{
QVector<const RoomEvent *> events;
events.reserve(m_selectedMessageIds.size());
std::ranges::copy(m_selectedMessageIds | std::views::transform([this](const QString &eventId) -> const RoomEvent * {
const auto eventIt = findInTimeline(eventId);
return eventIt != historyEdge() ? eventIt->event() : nullptr;
}) | std::views::filter([](const RoomEvent *event) {
return event != nullptr;
}),
std::back_inserter(events));
std::ranges::sort(events, {}, &RoomEvent::originTimestamp);
QString formattedContent;
formattedContent.reserve(events.size() * 256); // estimate an average of 256 characters per message
for (const RoomEvent *event : events) {
formattedContent += EventHandler::authorDisplayName(this, event);
formattedContent += u""_s;
formattedContent += EventHandler::dateTime(this, event).shortDateTime();
formattedContent += u'\n';
formattedContent += EventHandler::plainBody(this, event);
formattedContent += u"\n\n"_s;
}
return formattedContent.trimmed();
}
void NeoChatRoom::deleteSelectedMessages(const QString &reason)
{
QStringList events;
for (const auto &eventId : m_selectedMessageIds) {
const auto eventIt = findInTimeline(eventId);
if (eventIt == historyEdge()) {
continue;
}
const RoomEvent *event = eventIt->event();
if (event && !event->isRedacted() && !is<RedactionEvent>(*event)) {
events += eventId;
}
}
doDeleteMessageIds(events, reason);
clearSelectedMessages();
}
void NeoChatRoom::clearSelectedMessages()
{
m_selectedMessageIds.clear();
Q_EMIT selectionChanged();
}
#include "moc_neochatroom.cpp"

View File

@@ -220,6 +220,16 @@ class NeoChatRoom : public Quotient::Room
*/
Q_PROPERTY(bool spaceHasUnreadMessages READ spaceHasUnreadMessages NOTIFY spaceHasUnreadMessagesChanged)
/**
* @brief The number of selected messages in the room.
*/
Q_PROPERTY(int selectedMessageCount READ selectedMessageCount NOTIFY selectionChanged)
/**
* @brief Whether the user can delete the selected messages.
*/
Q_PROPERTY(bool canDeleteSelectedMessages READ canDeleteSelectedMessages NOTIFY selectionChanged)
public:
explicit NeoChatRoom(Quotient::Connection *connection, QString roomId, Quotient::JoinState joinState = {});
@@ -676,6 +686,41 @@ public:
*/
QList<QString> sortedMemberIds() const;
/**
* @brief The number of selected messages in the room.
*/
int selectedMessageCount() const;
/**
* @brief Whether the user can delete the selected messages.
*/
bool canDeleteSelectedMessages() const;
/**
* @brief Whether the given message is selected.
*/
Q_INVOKABLE bool isMessageSelected(const QString &eventId) const;
/**
* @brief Toggle the selection state of the given message.
*/
Q_INVOKABLE void toggleMessageSelection(const QString &eventId);
/**
* @brief Get the content of the selected messages formatted as a single string.
*/
Q_INVOKABLE QString getFormattedSelectedMessages() const;
/**
* @brief Delete the selected messages with an optional reason.
*/
Q_INVOKABLE void deleteSelectedMessages(const QString &reason = QString());
/**
* @brief Clear the selection of messages.
*/
Q_INVOKABLE void clearSelectedMessages();
private:
bool m_visible = false;
@@ -693,7 +738,7 @@ private:
void onAddHistoricalTimelineEvents(rev_iter_t from) override;
void onRedaction(const Quotient::RoomEvent &prevEvent, const Quotient::RoomEvent &after) override;
QCoro::Task<void> doDeleteMessagesByUser(const QString &user, QString reason);
QCoro::Task<void> doDeleteMessageIds(const QStringList eventIds, QString reason);
QCoro::Task<void> doUploadFile(QUrl url, QString body = QString(), std::optional<Quotient::EventRelation> relatesTo = std::nullopt);
std::unique_ptr<Quotient::RoomEvent> m_cachedEvent;
@@ -713,6 +758,7 @@ private:
QString m_lastUnreadHighlightId;
QList<QString> m_sortedMemberIds;
QSet<QString> m_selectedMessageIds;
private Q_SLOTS:
void updatePushNotificationState(QString type);
@@ -752,6 +798,7 @@ Q_SIGNALS:
void pinnedMessageChanged();
void highlightCycleStartedChanged();
void spaceHasUnreadMessagesChanged();
void selectionChanged();
/**
* @brief Request a message be shown to the user of the given type.