diff options
author | Nicolas Werner <nicolas.werner@hotmail.de> | 2024-03-08 18:43:59 +0100 |
---|---|---|
committer | Nicolas Werner <nicolas.werner@hotmail.de> | 2024-03-08 18:45:18 +0100 |
commit | 7c2a152cfbc2197989f7d722deb961ac80269019 (patch) | |
tree | 1ff5bc7c938ceb2242a0d769f9f64ef5e980f683 /src | |
parent | Fix buttons vanishing on the kde themes in the settings page (diff) | |
download | nheko-7c2a152cfbc2197989f7d722deb961ac80269019.tar.xz |
Add support for intentional mentions
This is still a bit flaky around when to remove a mention, but it should work in most cases. Might add a toggle in the future to disable these though.
Diffstat (limited to 'src')
-rw-r--r-- | src/ChatPage.cpp | 3 | ||||
-rw-r--r-- | src/EventAccessors.cpp | 17 | ||||
-rw-r--r-- | src/EventAccessors.h | 2 | ||||
-rw-r--r-- | src/timeline/InputBar.cpp | 119 | ||||
-rw-r--r-- | src/timeline/InputBar.h | 43 | ||||
-rw-r--r-- | src/timeline/TimelineModel.cpp | 27 | ||||
-rw-r--r-- | src/timeline/TimelineModel.h | 2 |
7 files changed, 178 insertions, 35 deletions
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_; |