diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index a75b7ef5..a43a190c 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -198,9 +198,10 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent)
if (eventInDb) {
if (auto newRules =
std::get_if<mtx::events::AccountDataEvent<mtx::pushrules::GlobalRuleset>>(
- &*eventInDb))
+ &*eventInDb)) {
pushrules =
std::make_unique<mtx::pushrules::PushRuleEvaluator>(newRules->content.global);
+ }
}
}
if (pushrules) {
diff --git a/src/EventAccessors.cpp b/src/EventAccessors.cpp
index ca730a75..a43d62c6 100644
--- a/src/EventAccessors.cpp
+++ b/src/EventAccessors.cpp
@@ -241,6 +241,18 @@ struct EventRelations
}
};
+struct EventMentions
+{
+ template<class T>
+ std::optional<mtx::common::Mentions> operator()(const mtx::events::Event<T> &e)
+ {
+ if constexpr (requires { T::mentions; }) {
+ return e.content.mentions;
+ }
+ return std::nullopt;
+ }
+};
+
struct SetEventRelations
{
mtx::common::Relations new_relations;
@@ -447,6 +459,11 @@ mtx::accessors::relations(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventRelations{}, event);
}
+std::optional<mtx::common::Mentions>
+mtx::accessors::mentions(const mtx::events::collections::TimelineEvents &event)
+{
+ return std::visit(EventMentions{}, event);
+}
void
mtx::accessors::set_relations(mtx::events::collections::TimelineEvents &event,
diff --git a/src/EventAccessors.h b/src/EventAccessors.h
index 4128f681..3651e941 100644
--- a/src/EventAccessors.h
+++ b/src/EventAccessors.h
@@ -107,6 +107,8 @@ std::string
mimetype(const mtx::events::collections::TimelineEvents &event);
const mtx::common::Relations &
relations(const mtx::events::collections::TimelineEvents &event);
+std::optional<mtx::common::Mentions>
+mentions(const mtx::events::collections::TimelineEvents &event);
void
set_relations(mtx::events::collections::TimelineEvents &event, mtx::common::Relations relations);
std::string
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index 62d38cf5..f8b57b81 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -179,31 +179,74 @@ InputBar::insertMimeData(const QMimeData *md)
}
void
-InputBar::updateTextContentProperties(const QString &t)
+InputBar::addMention(QString mention, QString text)
{
- // check for @room
- bool roomMention = false;
-
- if (t.size() > 4) {
- QTextBoundaryFinder finder(QTextBoundaryFinder::BoundaryType::Word, t);
-
- finder.toStart();
- do {
- auto start = finder.position();
- finder.toNextBoundary();
- auto end = finder.position();
- if (start > 0 && end - start >= 4 &&
- t.mid(start, end - start) == QLatin1String("room") &&
- t.at(start - 1) == QChar('@')) {
- roomMention = true;
- break;
+ if (!mentions_.contains(mention)) {
+ mentions_.push_back(mention);
+ mentionTexts_.push_back(text);
+
+ emit mentionsChanged();
+ if (mention == u"@room") {
+ this->containsAtRoom_ = true;
+ }
+ }
+}
+
+void
+InputBar::removeMention(QString mention)
+{
+ if (auto idx = mentions_.indexOf(mention); idx != -1) {
+ mentions_.removeAt(idx);
+ mentionTexts_.removeAt(idx);
+ emit mentionsChanged();
+ if (mention == u"@room") {
+ this->containsAtRoom_ = false;
+ }
+ }
+}
+
+void
+InputBar::updateTextContentProperties(const QString &t, bool charDeleted)
+{
+ auto containsRoomMention = [](QStringView text) {
+ // check for @room
+ bool roomMention = false;
+ if (text.size() > 4) {
+ QTextBoundaryFinder finder(QTextBoundaryFinder::BoundaryType::Word, text);
+
+ finder.toStart();
+ do {
+ auto start = finder.position();
+ finder.toNextBoundary();
+ auto end = finder.position();
+ if (start > 0 && end - start >= 4 &&
+ text.mid(start, end - start) == QStringView(u"room") &&
+ text.at(start - 1) == QChar('@')) {
+ roomMention = true;
+ break;
+ }
+ } while (finder.position() < text.size());
+ }
+ return roomMention;
+ };
+
+ if (charDeleted) {
+ for (qsizetype idx = 0; idx < mentions_.size();) {
+ if (!t.contains(mentionTexts_.at(idx))) {
+ removeMention(mentions_.at(idx));
+ } else {
+ idx++;
}
- } while (finder.position() < t.size());
+ }
}
+ auto roomMention = containsRoomMention(t);
+
if (roomMention != this->containsAtRoom_) {
- this->containsAtRoom_ = roomMention;
- emit containsAtRoomChanged();
+ if (roomMention)
+ addMention(QStringLiteral(u"@room"), QStringLiteral(u"@room"));
+ else
+ removeMention(QStringLiteral(u"@room"));
}
// check for invalid commands
@@ -280,7 +323,7 @@ InputBar::setText(const QString &newText)
if (history_.size() == INPUT_HISTORY_SIZE)
history_.pop_back();
- updateTextContentProperties(QLatin1String(""));
+ updateTextContentProperties(newText, true);
emit textChanged(newText);
}
void
@@ -294,14 +337,15 @@ InputBar::updateState(int selectionStart_,
else
startTyping();
- if (text_ != text()) {
+ auto oldText = text();
+ if (text_ != oldText) {
if (history_.empty())
history_.push_front(text_);
else
history_.front() = text_;
history_index_ = 0;
- updateTextContentProperties(text_);
+ updateTextContentProperties(text_, text_.size() < oldText.size());
// disabled, as it moves the cursor to the end
// emit textChanged(text_);
}
@@ -452,6 +496,24 @@ InputBar::generateRelations() const
return relations;
}
+mtx::common::Mentions
+InputBar::generateMentions()
+{
+ std::vector<std::string> userMentions;
+ for (const auto &m : mentions_)
+ if (m != u"@room")
+ userMentions.push_back(m.toStdString());
+ auto mention = mtx::common::Mentions{
+ .user_ids = userMentions,
+ .room = containsAtRoom_,
+ };
+
+ // this->containsAtRoom_ = false;
+ // this->mentions_.clear();
+ // this->mentionTexts_.clear();
+ return mention;
+}
+
void
InputBar::message(const QString &msg, MarkdownOverride useMarkdown, bool rainbowify)
{
@@ -484,6 +546,7 @@ InputBar::message(const QString &msg, MarkdownOverride useMarkdown, bool rainbow
text.format = "org.matrix.custom.html";
}
+ text.mentions = generateMentions();
text.relations = generateRelations();
if (!room->reply().isEmpty() && room->thread().isEmpty() && room->edit().isEmpty()) {
auto related = room->relatedInfo(room->reply());
@@ -540,6 +603,7 @@ InputBar::emote(const QString &msg, bool rainbowify)
emote.body = replaceMatrixToMarkdownLink(msg.trimmed()).toStdString();
}
+ emote.mentions = generateMentions();
emote.relations = generateRelations();
room->sendMessageEvent(emote, mtx::events::EventType::RoomMessage);
@@ -560,6 +624,7 @@ InputBar::notice(const QString &msg, bool rainbowify)
notice.body = replaceMatrixToMarkdownLink(msg.trimmed()).toStdString();
}
+ notice.mentions = generateMentions();
notice.relations = generateRelations();
room->sendMessageEvent(notice, mtx::events::EventType::RoomMessage);
@@ -582,6 +647,7 @@ InputBar::confetti(const QString &body, bool rainbowify)
confetti.body = replaceMatrixToMarkdownLink(body.trimmed()).toStdString();
}
+ confetti.mentions = generateMentions();
confetti.relations = generateRelations();
room->sendMessageEvent(confetti, mtx::events::EventType::RoomMessage);
@@ -606,6 +672,7 @@ InputBar::rainfall(const QString &body)
rain.body = replaceMatrixToMarkdownLink(body.trimmed()).toStdString();
}
+ rain.mentions = generateMentions();
rain.relations = generateRelations();
room->sendMessageEvent(rain, mtx::events::EventType::RoomMessage);
@@ -630,6 +697,7 @@ InputBar::customMsgtype(const QString &msgtype, const QString &body)
msg.body = replaceMatrixToMarkdownLink(body.trimmed()).toStdString();
}
+ msg.mentions = generateMentions();
msg.relations = generateRelations();
room->sendMessageEvent(msg, mtx::events::EventType::RoomMessage);
@@ -673,6 +741,7 @@ InputBar::image(const QString &filename,
image.info.thumbnail_info.mimetype = "image/png";
}
+ image.mentions = generateMentions();
image.relations = generateRelations();
room->sendMessageEvent(image, mtx::events::EventType::RoomMessage);
@@ -695,6 +764,7 @@ InputBar::file(const QString &filename,
else
file.url = url.toStdString();
+ file.mentions = generateMentions();
file.relations = generateRelations();
room->sendMessageEvent(file, mtx::events::EventType::RoomMessage);
@@ -722,6 +792,7 @@ InputBar::audio(const QString &filename,
else
audio.url = url.toStdString();
+ audio.mentions = generateMentions();
audio.relations = generateRelations();
room->sendMessageEvent(audio, mtx::events::EventType::RoomMessage);
@@ -771,6 +842,7 @@ InputBar::video(const QString &filename,
video.info.thumbnail_info.mimetype = "image/png";
}
+ video.mentions = generateMentions();
video.relations = generateRelations();
room->sendMessageEvent(video, mtx::events::EventType::RoomMessage);
@@ -825,6 +897,7 @@ InputBar::sticker(QStringList descriptor)
sticker.info.thumbnail_info.h = sticker.info.h;
sticker.info.thumbnail_info.w = sticker.info.w;
+ sticker.mentions = generateMentions();
sticker.relations = generateRelations();
room->sendMessageEvent(sticker, mtx::events::EventType::Sticker);
diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h
index fbf08343..c38de662 100644
--- a/src/timeline/InputBar.h
+++ b/src/timeline/InputBar.h
@@ -158,12 +158,12 @@ class InputBar final : public QObject
{
Q_OBJECT
Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged)
- Q_PROPERTY(bool containsAtRoom READ containsAtRoom NOTIFY containsAtRoomChanged)
Q_PROPERTY(
bool containsInvalidCommand READ containsInvalidCommand NOTIFY containsInvalidCommandChanged)
Q_PROPERTY(bool containsIncompleteCommand READ containsIncompleteCommand NOTIFY
containsIncompleteCommandChanged)
Q_PROPERTY(QString currentCommand READ currentCommand NOTIFY currentCommandChanged)
+ Q_PROPERTY(QStringList mentions READ mentions NOTIFY mentionsChanged)
Q_PROPERTY(QString text READ text NOTIFY textChanged)
Q_PROPERTY(QVariantList uploads READ uploads NOTIFY uploadsChanged)
@@ -188,7 +188,37 @@ public slots:
QString nextText();
void setText(const QString &newText);
- [[nodiscard]] bool containsAtRoom() const { return containsAtRoom_; }
+ [[nodiscard]] QStringList mentions() const { return mentions_; }
+ void addMention(QString m, QString text);
+ void removeMention(QString m);
+
+ void storeForEdit()
+ {
+ textBeforeEdit = text();
+ mentionsBefore = mentions_;
+ mentionTextsBefore = mentionTexts_;
+ emit mentionsChanged();
+ }
+ void restoreAfterEdit()
+ {
+ mentions_ = mentionsBefore;
+ mentionTexts_ = mentionTextsBefore;
+ mentionsBefore.clear();
+ mentionTextsBefore.clear();
+ setText(textBeforeEdit);
+ textBeforeEdit.clear();
+ emit mentionsChanged();
+ }
+ void replaceMentions(QStringList newMentions, QStringList newMentionTexts)
+ {
+ if (newMentions.size() != newMentionTexts.size())
+ return;
+
+ mentions_ = newMentions;
+ mentionTexts_ = newMentionTexts;
+ emit mentionsChanged();
+ }
+
bool containsInvalidCommand() const { return containsInvalidCommand_; }
bool containsIncompleteCommand() const { return containsIncompleteCommand_; }
QString currentCommand() const { return currentCommand_; }
@@ -218,8 +248,8 @@ private slots:
signals:
void textChanged(QString newText);
void uploadingChanged(bool value);
- void containsAtRoomChanged();
void containsInvalidCommandChanged();
+ void mentionsChanged();
void containsIncompleteCommandChanged();
void currentCommandChanged();
void uploadsChanged();
@@ -269,6 +299,7 @@ private:
QPair<QString, QString> getCommandAndArgs() const { return getCommandAndArgs(text()); }
QPair<QString, QString> getCommandAndArgs(const QString ¤tText) const;
mtx::common::Relations generateRelations() const;
+ mtx::common::Mentions generateMentions();
void startUploadFromPath(const QString &path);
void startUploadFromMimeData(const QMimeData &source, const QString &format);
@@ -281,7 +312,7 @@ private:
}
}
- void updateTextContentProperties(const QString &t);
+ void updateTextContentProperties(const QString &t, bool textDeleted = false);
void toggleIgnore(const QString &user, const bool ignored);
@@ -296,6 +327,10 @@ private:
bool containsInvalidCommand_ = false;
bool containsIncompleteCommand_ = false;
QString currentCommand_;
+ QStringList mentions_, mentionTexts_;
+ // store stuff during edits
+ QStringList mentionsBefore, mentionTextsBefore;
+ QString textBeforeEdit;
using UploadHandle = std::unique_ptr<MediaUpload, DeleteLaterDeleter>;
std::vector<UploadHandle> unconfirmedUploads;
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 05a3c45c..e7fb31f5 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -3069,9 +3069,7 @@ TimelineModel::setEdit(const QString &newEdit)
}
if (edit_.isEmpty()) {
- this->textBeforeEdit = input()->text();
- this->replyBeforeEdit = reply_;
- nhlog::ui()->debug("Stored: {}", textBeforeEdit.toStdString());
+ input()->storeForEdit();
}
auto quoted = [](QString in) { return in.replace("[", "\\[").replace("]", "\\]"); };
@@ -3083,6 +3081,24 @@ TimelineModel::setEdit(const QString &newEdit)
setReply(QString::fromStdString(mtx::accessors::relations(e).reply_to().value_or("")));
setThread(QString::fromStdString(mtx::accessors::relations(e).thread().value_or("")));
+ auto mentionsList = mtx::accessors::mentions(e);
+ QStringList mentions, mentionTexts;
+ if (mentionsList) {
+ if (mentionsList->room) {
+ mentions.append(QStringLiteral(u"@room"));
+ mentionTexts.append(QStringLiteral(u"@room"));
+ }
+
+ for (const auto &user : mentionsList->user_ids) {
+ auto userid = QString::fromStdString(user);
+ mentions.append(userid);
+ mentionTexts.append(
+ QStringLiteral("[%1](https://matrix.to/#/%2)")
+ .arg(displayName(userid).replace("[", "\\[").replace("]", "\\]"),
+ QString(QUrl::toPercentEncoding(userid))));
+ }
+ }
+
auto msgType = mtx::accessors::msg_type(e);
if (msgType == mtx::events::MessageType::Text ||
msgType == mtx::events::MessageType::Notice ||
@@ -3130,6 +3146,7 @@ TimelineModel::setEdit(const QString &newEdit)
} else {
input()->setText(QLatin1String(""));
}
+ input()->replaceMentions(std::move(mentions), std::move(mentionTexts));
edit_ = newEdit;
} else {
@@ -3148,9 +3165,7 @@ TimelineModel::resetEdit()
if (!edit_.isEmpty()) {
edit_ = QLatin1String("");
emit editChanged(edit_);
- nhlog::ui()->debug("Restoring: {}", textBeforeEdit.toStdString());
- input()->setText(textBeforeEdit);
- textBeforeEdit.clear();
+ input()->restoreAfterEdit();
if (replyBeforeEdit.isEmpty())
resetReply();
else
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 08c776f8..c7f3ebb6 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -529,7 +529,7 @@ private:
QString currentId, currentReadId;
QString reply_, edit_, thread_;
- QString textBeforeEdit, replyBeforeEdit;
+ QString replyBeforeEdit;
QStringList typingUsers_;
TimelineViewManager *manager_;
|