mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-24 03:58:49 +03:00
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.
This commit is contained in:
parent
7ec56d8df5
commit
7c2a152cfb
15 changed files with 242 additions and 51 deletions
|
@ -603,7 +603,7 @@ if(USE_BUNDLED_MTXCLIENT)
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
MatrixClient
|
MatrixClient
|
||||||
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
|
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
|
||||||
GIT_TAG 03bb6fbd665260faec0148b5bb0bfe484e88581a
|
GIT_TAG 188ecb899744e55842c1debaa4597cdc5184be8a
|
||||||
)
|
)
|
||||||
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
|
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
|
||||||
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
||||||
|
|
|
@ -223,7 +223,7 @@ modules:
|
||||||
buildsystem: cmake-ninja
|
buildsystem: cmake-ninja
|
||||||
name: mtxclient
|
name: mtxclient
|
||||||
sources:
|
sources:
|
||||||
- commit: 03bb6fbd665260faec0148b5bb0bfe484e88581a
|
- commit: 188ecb899744e55842c1debaa4597cdc5184be8a
|
||||||
#tag: v0.9.2
|
#tag: v0.9.2
|
||||||
type: git
|
type: git
|
||||||
url: https://github.com/Nheko-Reborn/mtxclient.git
|
url: https://github.com/Nheko-Reborn/mtxclient.git
|
||||||
|
|
|
@ -42,6 +42,13 @@ Control {
|
||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
function currentUserid() {
|
||||||
|
if (popup.completerName == "user") {
|
||||||
|
return listView.itemAtIndex(currentIndex).modelData.userid;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
function down() {
|
function down() {
|
||||||
if (bottomToTop)
|
if (bottomToTop)
|
||||||
up_();
|
up_();
|
||||||
|
|
|
@ -114,6 +114,10 @@ Rectangle {
|
||||||
function insertCompletion(completion) {
|
function insertCompletion(completion) {
|
||||||
messageInput.remove(completerTriggeredAt, cursorPosition);
|
messageInput.remove(completerTriggeredAt, cursorPosition);
|
||||||
messageInput.insert(cursorPosition, completion);
|
messageInput.insert(cursorPosition, completion);
|
||||||
|
let userid = completer.currentUserid();
|
||||||
|
if (userid) {
|
||||||
|
room.input.addMention(userid, completion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function openCompleter(pos, type) {
|
function openCompleter(pos, type) {
|
||||||
if (popup.opened)
|
if (popup.opened)
|
||||||
|
@ -176,10 +180,17 @@ Rectangle {
|
||||||
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
|
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
|
||||||
if (popup.opened) {
|
if (popup.opened) {
|
||||||
var currentCompletion = completer.currentCompletion();
|
var currentCompletion = completer.currentCompletion();
|
||||||
|
let userid = completer.currentUserid();
|
||||||
|
|
||||||
completer.completerName = "";
|
completer.completerName = "";
|
||||||
popup.close();
|
popup.close();
|
||||||
|
|
||||||
if (currentCompletion) {
|
if (currentCompletion) {
|
||||||
messageInput.insertCompletion(currentCompletion);
|
messageInput.insertCompletion(currentCompletion);
|
||||||
|
if (userid) {
|
||||||
|
console.log(userid);
|
||||||
|
room.input.addMention(userid, currentCompletion);
|
||||||
|
}
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,9 @@ Rectangle {
|
||||||
|
|
||||||
property color bubbleColor: Nheko.theme.error
|
property color bubbleColor: Nheko.theme.error
|
||||||
required property string text
|
required property string text
|
||||||
|
property bool showRemove: false
|
||||||
|
|
||||||
|
signal removeClicked();
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
color: palette.window // required to hide the timeline behind this warning
|
color: palette.window // required to hide the timeline behind this warning
|
||||||
|
@ -35,10 +38,30 @@ Rectangle {
|
||||||
id: warningDisplay
|
id: warningDisplay
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
anchors.margins: Nheko.paddingSmall
|
anchors.margins: Nheko.paddingSmall
|
||||||
|
anchors.rightMargin: warningRoot.showRemove ? (Nheko.paddingSmall*3 + removeButton.width) : Nheko.paddingSmall
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: warningRoot.text
|
text: warningRoot.text
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageButton {
|
||||||
|
id: removeButton
|
||||||
|
|
||||||
|
visible: warningRoot.showRemove
|
||||||
|
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.margins: Nheko.paddingSmall
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
image: ":/icons/icons/ui/dismiss.svg"
|
||||||
|
hoverEnabled: true
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: qsTr("Don't mention them in this message")
|
||||||
|
onClicked: {
|
||||||
|
warningRoot.removeClicked();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,18 +128,6 @@ TimelineEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Rectangle {
|
|
||||||
anchors.top: gridContainer.top
|
|
||||||
anchors.left: gridContainer.left
|
|
||||||
anchors.topMargin: -2
|
|
||||||
anchors.leftMargin: -2 + (stateEventSpacing.visible ? (stateEventSpacing.width + gridContainer.spacing) : 0)
|
|
||||||
color: "transparent"
|
|
||||||
border.color: Nheko.theme.red
|
|
||||||
border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0
|
|
||||||
radius: 4
|
|
||||||
height: contentColumn.implicitHeight + 4
|
|
||||||
width: contentColumn.implicitWidth + 4 + (wrapper.threadId ? (4 + gridContainer.spacing) : 0)
|
|
||||||
},
|
|
||||||
Row {
|
Row {
|
||||||
id: gridContainer
|
id: gridContainer
|
||||||
|
|
||||||
|
@ -292,6 +280,18 @@ TimelineEvent {
|
||||||
TapHandler {
|
TapHandler {
|
||||||
onDoubleTapped: wrapper.room.reply = wrapper.eventId
|
onDoubleTapped: wrapper.room.reply = wrapper.eventId
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Rectangle {
|
||||||
|
anchors.top: gridContainer.top
|
||||||
|
anchors.left: gridContainer.left
|
||||||
|
anchors.topMargin: -2
|
||||||
|
anchors.leftMargin: -2 + (stateEventSpacing.visible ? (stateEventSpacing.width + gridContainer.spacing) : 0)
|
||||||
|
color: "transparent"
|
||||||
|
border.color: Nheko.theme.red
|
||||||
|
border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0
|
||||||
|
radius: 4
|
||||||
|
height: contentColumn.implicitHeight + 4
|
||||||
|
width: contentColumn.implicitWidth + 4 + (wrapper.threadId ? (4 + gridContainer.spacing) : 0)
|
||||||
},
|
},
|
||||||
TimelineMetadata {
|
TimelineMetadata {
|
||||||
id: metadata
|
id: metadata
|
||||||
|
|
|
@ -148,9 +148,16 @@ Item {
|
||||||
}
|
}
|
||||||
UploadBox {
|
UploadBox {
|
||||||
}
|
}
|
||||||
MessageInputWarning {
|
Repeater {
|
||||||
text: qsTr("You are about to notify the whole room")
|
model: room ? room.input.mentions : null
|
||||||
visible: (room && room.permissions.canPingRoom() && room.input.containsAtRoom)
|
|
||||||
|
MessageInputWarning {
|
||||||
|
required property string modelData
|
||||||
|
bubbleColor: modelData == "@room" ? Nheko.theme.error : Nheko.theme.orange
|
||||||
|
text: modelData == "@room" ? qsTr("You are about to notify the whole room") : qsTr("You will be mentioning %1").arg(modelData)
|
||||||
|
showRemove: true
|
||||||
|
onRemoveClicked: room.input.removeMention(modelData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MessageInputWarning {
|
MessageInputWarning {
|
||||||
text: qsTr("The command /%1 is not recognized and will be sent as part of your message").arg(room ? room.input.currentCommand : "")
|
text: qsTr("The command /%1 is not recognized and will be sent as part of your message").arg(room ? room.input.currentCommand : "")
|
||||||
|
|
|
@ -273,7 +273,7 @@ ApplicationWindow {
|
||||||
ComboBox {
|
ComboBox {
|
||||||
model: [qsTr("Muted"), qsTr("Mentions only"), qsTr("All messages")]
|
model: [qsTr("Muted"), qsTr("Mentions only"), qsTr("All messages")]
|
||||||
currentIndex: roomSettings.notifications
|
currentIndex: roomSettings.notifications
|
||||||
onActivated: {
|
onActivated: (index) => {
|
||||||
roomSettings.changeNotifications(index);
|
roomSettings.changeNotifications(index);
|
||||||
}
|
}
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
|
@ -198,9 +198,10 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent)
|
||||||
if (eventInDb) {
|
if (eventInDb) {
|
||||||
if (auto newRules =
|
if (auto newRules =
|
||||||
std::get_if<mtx::events::AccountDataEvent<mtx::pushrules::GlobalRuleset>>(
|
std::get_if<mtx::events::AccountDataEvent<mtx::pushrules::GlobalRuleset>>(
|
||||||
&*eventInDb))
|
&*eventInDb)) {
|
||||||
pushrules =
|
pushrules =
|
||||||
std::make_unique<mtx::pushrules::PushRuleEvaluator>(newRules->content.global);
|
std::make_unique<mtx::pushrules::PushRuleEvaluator>(newRules->content.global);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pushrules) {
|
if (pushrules) {
|
||||||
|
|
|
@ -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
|
struct SetEventRelations
|
||||||
{
|
{
|
||||||
mtx::common::Relations new_relations;
|
mtx::common::Relations new_relations;
|
||||||
|
@ -447,6 +459,11 @@ mtx::accessors::relations(const mtx::events::collections::TimelineEvents &event)
|
||||||
{
|
{
|
||||||
return std::visit(EventRelations{}, 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
|
void
|
||||||
mtx::accessors::set_relations(mtx::events::collections::TimelineEvents &event,
|
mtx::accessors::set_relations(mtx::events::collections::TimelineEvents &event,
|
||||||
|
|
|
@ -107,6 +107,8 @@ std::string
|
||||||
mimetype(const mtx::events::collections::TimelineEvents &event);
|
mimetype(const mtx::events::collections::TimelineEvents &event);
|
||||||
const mtx::common::Relations &
|
const mtx::common::Relations &
|
||||||
relations(const mtx::events::collections::TimelineEvents &event);
|
relations(const mtx::events::collections::TimelineEvents &event);
|
||||||
|
std::optional<mtx::common::Mentions>
|
||||||
|
mentions(const mtx::events::collections::TimelineEvents &event);
|
||||||
void
|
void
|
||||||
set_relations(mtx::events::collections::TimelineEvents &event, mtx::common::Relations relations);
|
set_relations(mtx::events::collections::TimelineEvents &event, mtx::common::Relations relations);
|
||||||
std::string
|
std::string
|
||||||
|
|
|
@ -179,31 +179,74 @@ InputBar::insertMimeData(const QMimeData *md)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
InputBar::updateTextContentProperties(const QString &t)
|
InputBar::addMention(QString mention, QString text)
|
||||||
{
|
{
|
||||||
// check for @room
|
if (!mentions_.contains(mention)) {
|
||||||
bool roomMention = false;
|
mentions_.push_back(mention);
|
||||||
|
mentionTexts_.push_back(text);
|
||||||
|
|
||||||
if (t.size() > 4) {
|
emit mentionsChanged();
|
||||||
QTextBoundaryFinder finder(QTextBoundaryFinder::BoundaryType::Word, t);
|
if (mention == u"@room") {
|
||||||
|
this->containsAtRoom_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
finder.toStart();
|
void
|
||||||
do {
|
InputBar::removeMention(QString mention)
|
||||||
auto start = finder.position();
|
{
|
||||||
finder.toNextBoundary();
|
if (auto idx = mentions_.indexOf(mention); idx != -1) {
|
||||||
auto end = finder.position();
|
mentions_.removeAt(idx);
|
||||||
if (start > 0 && end - start >= 4 &&
|
mentionTexts_.removeAt(idx);
|
||||||
t.mid(start, end - start) == QLatin1String("room") &&
|
emit mentionsChanged();
|
||||||
t.at(start - 1) == QChar('@')) {
|
if (mention == u"@room") {
|
||||||
roomMention = true;
|
this->containsAtRoom_ = false;
|
||||||
break;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_) {
|
if (roomMention != this->containsAtRoom_) {
|
||||||
this->containsAtRoom_ = roomMention;
|
if (roomMention)
|
||||||
emit containsAtRoomChanged();
|
addMention(QStringLiteral(u"@room"), QStringLiteral(u"@room"));
|
||||||
|
else
|
||||||
|
removeMention(QStringLiteral(u"@room"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for invalid commands
|
// check for invalid commands
|
||||||
|
@ -280,7 +323,7 @@ InputBar::setText(const QString &newText)
|
||||||
if (history_.size() == INPUT_HISTORY_SIZE)
|
if (history_.size() == INPUT_HISTORY_SIZE)
|
||||||
history_.pop_back();
|
history_.pop_back();
|
||||||
|
|
||||||
updateTextContentProperties(QLatin1String(""));
|
updateTextContentProperties(newText, true);
|
||||||
emit textChanged(newText);
|
emit textChanged(newText);
|
||||||
}
|
}
|
||||||
void
|
void
|
||||||
|
@ -294,14 +337,15 @@ InputBar::updateState(int selectionStart_,
|
||||||
else
|
else
|
||||||
startTyping();
|
startTyping();
|
||||||
|
|
||||||
if (text_ != text()) {
|
auto oldText = text();
|
||||||
|
if (text_ != oldText) {
|
||||||
if (history_.empty())
|
if (history_.empty())
|
||||||
history_.push_front(text_);
|
history_.push_front(text_);
|
||||||
else
|
else
|
||||||
history_.front() = text_;
|
history_.front() = text_;
|
||||||
history_index_ = 0;
|
history_index_ = 0;
|
||||||
|
|
||||||
updateTextContentProperties(text_);
|
updateTextContentProperties(text_, text_.size() < oldText.size());
|
||||||
// disabled, as it moves the cursor to the end
|
// disabled, as it moves the cursor to the end
|
||||||
// emit textChanged(text_);
|
// emit textChanged(text_);
|
||||||
}
|
}
|
||||||
|
@ -452,6 +496,24 @@ InputBar::generateRelations() const
|
||||||
return relations;
|
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
|
void
|
||||||
InputBar::message(const QString &msg, MarkdownOverride useMarkdown, bool rainbowify)
|
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.format = "org.matrix.custom.html";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
text.mentions = generateMentions();
|
||||||
text.relations = generateRelations();
|
text.relations = generateRelations();
|
||||||
if (!room->reply().isEmpty() && room->thread().isEmpty() && room->edit().isEmpty()) {
|
if (!room->reply().isEmpty() && room->thread().isEmpty() && room->edit().isEmpty()) {
|
||||||
auto related = room->relatedInfo(room->reply());
|
auto related = room->relatedInfo(room->reply());
|
||||||
|
@ -540,6 +603,7 @@ InputBar::emote(const QString &msg, bool rainbowify)
|
||||||
emote.body = replaceMatrixToMarkdownLink(msg.trimmed()).toStdString();
|
emote.body = replaceMatrixToMarkdownLink(msg.trimmed()).toStdString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emote.mentions = generateMentions();
|
||||||
emote.relations = generateRelations();
|
emote.relations = generateRelations();
|
||||||
|
|
||||||
room->sendMessageEvent(emote, mtx::events::EventType::RoomMessage);
|
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.body = replaceMatrixToMarkdownLink(msg.trimmed()).toStdString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notice.mentions = generateMentions();
|
||||||
notice.relations = generateRelations();
|
notice.relations = generateRelations();
|
||||||
|
|
||||||
room->sendMessageEvent(notice, mtx::events::EventType::RoomMessage);
|
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.body = replaceMatrixToMarkdownLink(body.trimmed()).toStdString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
confetti.mentions = generateMentions();
|
||||||
confetti.relations = generateRelations();
|
confetti.relations = generateRelations();
|
||||||
|
|
||||||
room->sendMessageEvent(confetti, mtx::events::EventType::RoomMessage);
|
room->sendMessageEvent(confetti, mtx::events::EventType::RoomMessage);
|
||||||
|
@ -606,6 +672,7 @@ InputBar::rainfall(const QString &body)
|
||||||
rain.body = replaceMatrixToMarkdownLink(body.trimmed()).toStdString();
|
rain.body = replaceMatrixToMarkdownLink(body.trimmed()).toStdString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rain.mentions = generateMentions();
|
||||||
rain.relations = generateRelations();
|
rain.relations = generateRelations();
|
||||||
|
|
||||||
room->sendMessageEvent(rain, mtx::events::EventType::RoomMessage);
|
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.body = replaceMatrixToMarkdownLink(body.trimmed()).toStdString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
msg.mentions = generateMentions();
|
||||||
msg.relations = generateRelations();
|
msg.relations = generateRelations();
|
||||||
|
|
||||||
room->sendMessageEvent(msg, mtx::events::EventType::RoomMessage);
|
room->sendMessageEvent(msg, mtx::events::EventType::RoomMessage);
|
||||||
|
@ -673,6 +741,7 @@ InputBar::image(const QString &filename,
|
||||||
image.info.thumbnail_info.mimetype = "image/png";
|
image.info.thumbnail_info.mimetype = "image/png";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
image.mentions = generateMentions();
|
||||||
image.relations = generateRelations();
|
image.relations = generateRelations();
|
||||||
|
|
||||||
room->sendMessageEvent(image, mtx::events::EventType::RoomMessage);
|
room->sendMessageEvent(image, mtx::events::EventType::RoomMessage);
|
||||||
|
@ -695,6 +764,7 @@ InputBar::file(const QString &filename,
|
||||||
else
|
else
|
||||||
file.url = url.toStdString();
|
file.url = url.toStdString();
|
||||||
|
|
||||||
|
file.mentions = generateMentions();
|
||||||
file.relations = generateRelations();
|
file.relations = generateRelations();
|
||||||
|
|
||||||
room->sendMessageEvent(file, mtx::events::EventType::RoomMessage);
|
room->sendMessageEvent(file, mtx::events::EventType::RoomMessage);
|
||||||
|
@ -722,6 +792,7 @@ InputBar::audio(const QString &filename,
|
||||||
else
|
else
|
||||||
audio.url = url.toStdString();
|
audio.url = url.toStdString();
|
||||||
|
|
||||||
|
audio.mentions = generateMentions();
|
||||||
audio.relations = generateRelations();
|
audio.relations = generateRelations();
|
||||||
|
|
||||||
room->sendMessageEvent(audio, mtx::events::EventType::RoomMessage);
|
room->sendMessageEvent(audio, mtx::events::EventType::RoomMessage);
|
||||||
|
@ -771,6 +842,7 @@ InputBar::video(const QString &filename,
|
||||||
video.info.thumbnail_info.mimetype = "image/png";
|
video.info.thumbnail_info.mimetype = "image/png";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
video.mentions = generateMentions();
|
||||||
video.relations = generateRelations();
|
video.relations = generateRelations();
|
||||||
|
|
||||||
room->sendMessageEvent(video, mtx::events::EventType::RoomMessage);
|
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.h = sticker.info.h;
|
||||||
sticker.info.thumbnail_info.w = sticker.info.w;
|
sticker.info.thumbnail_info.w = sticker.info.w;
|
||||||
|
|
||||||
|
sticker.mentions = generateMentions();
|
||||||
sticker.relations = generateRelations();
|
sticker.relations = generateRelations();
|
||||||
|
|
||||||
room->sendMessageEvent(sticker, mtx::events::EventType::Sticker);
|
room->sendMessageEvent(sticker, mtx::events::EventType::Sticker);
|
||||||
|
|
|
@ -158,12 +158,12 @@ class InputBar final : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged)
|
Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged)
|
||||||
Q_PROPERTY(bool containsAtRoom READ containsAtRoom NOTIFY containsAtRoomChanged)
|
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(
|
||||||
bool containsInvalidCommand READ containsInvalidCommand NOTIFY containsInvalidCommandChanged)
|
bool containsInvalidCommand READ containsInvalidCommand NOTIFY containsInvalidCommandChanged)
|
||||||
Q_PROPERTY(bool containsIncompleteCommand READ containsIncompleteCommand NOTIFY
|
Q_PROPERTY(bool containsIncompleteCommand READ containsIncompleteCommand NOTIFY
|
||||||
containsIncompleteCommandChanged)
|
containsIncompleteCommandChanged)
|
||||||
Q_PROPERTY(QString currentCommand READ currentCommand NOTIFY currentCommandChanged)
|
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(QString text READ text NOTIFY textChanged)
|
||||||
Q_PROPERTY(QVariantList uploads READ uploads NOTIFY uploadsChanged)
|
Q_PROPERTY(QVariantList uploads READ uploads NOTIFY uploadsChanged)
|
||||||
|
|
||||||
|
@ -188,7 +188,37 @@ public slots:
|
||||||
QString nextText();
|
QString nextText();
|
||||||
void setText(const QString &newText);
|
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 containsInvalidCommand() const { return containsInvalidCommand_; }
|
||||||
bool containsIncompleteCommand() const { return containsIncompleteCommand_; }
|
bool containsIncompleteCommand() const { return containsIncompleteCommand_; }
|
||||||
QString currentCommand() const { return currentCommand_; }
|
QString currentCommand() const { return currentCommand_; }
|
||||||
|
@ -218,8 +248,8 @@ private slots:
|
||||||
signals:
|
signals:
|
||||||
void textChanged(QString newText);
|
void textChanged(QString newText);
|
||||||
void uploadingChanged(bool value);
|
void uploadingChanged(bool value);
|
||||||
void containsAtRoomChanged();
|
|
||||||
void containsInvalidCommandChanged();
|
void containsInvalidCommandChanged();
|
||||||
|
void mentionsChanged();
|
||||||
void containsIncompleteCommandChanged();
|
void containsIncompleteCommandChanged();
|
||||||
void currentCommandChanged();
|
void currentCommandChanged();
|
||||||
void uploadsChanged();
|
void uploadsChanged();
|
||||||
|
@ -269,6 +299,7 @@ private:
|
||||||
QPair<QString, QString> getCommandAndArgs() const { return getCommandAndArgs(text()); }
|
QPair<QString, QString> getCommandAndArgs() const { return getCommandAndArgs(text()); }
|
||||||
QPair<QString, QString> getCommandAndArgs(const QString ¤tText) const;
|
QPair<QString, QString> getCommandAndArgs(const QString ¤tText) const;
|
||||||
mtx::common::Relations generateRelations() const;
|
mtx::common::Relations generateRelations() const;
|
||||||
|
mtx::common::Mentions generateMentions();
|
||||||
|
|
||||||
void startUploadFromPath(const QString &path);
|
void startUploadFromPath(const QString &path);
|
||||||
void startUploadFromMimeData(const QMimeData &source, const QString &format);
|
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);
|
void toggleIgnore(const QString &user, const bool ignored);
|
||||||
|
|
||||||
|
@ -296,6 +327,10 @@ private:
|
||||||
bool containsInvalidCommand_ = false;
|
bool containsInvalidCommand_ = false;
|
||||||
bool containsIncompleteCommand_ = false;
|
bool containsIncompleteCommand_ = false;
|
||||||
QString currentCommand_;
|
QString currentCommand_;
|
||||||
|
QStringList mentions_, mentionTexts_;
|
||||||
|
// store stuff during edits
|
||||||
|
QStringList mentionsBefore, mentionTextsBefore;
|
||||||
|
QString textBeforeEdit;
|
||||||
|
|
||||||
using UploadHandle = std::unique_ptr<MediaUpload, DeleteLaterDeleter>;
|
using UploadHandle = std::unique_ptr<MediaUpload, DeleteLaterDeleter>;
|
||||||
std::vector<UploadHandle> unconfirmedUploads;
|
std::vector<UploadHandle> unconfirmedUploads;
|
||||||
|
|
|
@ -3069,9 +3069,7 @@ TimelineModel::setEdit(const QString &newEdit)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (edit_.isEmpty()) {
|
if (edit_.isEmpty()) {
|
||||||
this->textBeforeEdit = input()->text();
|
input()->storeForEdit();
|
||||||
this->replyBeforeEdit = reply_;
|
|
||||||
nhlog::ui()->debug("Stored: {}", textBeforeEdit.toStdString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto quoted = [](QString in) { return in.replace("[", "\\[").replace("]", "\\]"); };
|
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("")));
|
setReply(QString::fromStdString(mtx::accessors::relations(e).reply_to().value_or("")));
|
||||||
setThread(QString::fromStdString(mtx::accessors::relations(e).thread().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);
|
auto msgType = mtx::accessors::msg_type(e);
|
||||||
if (msgType == mtx::events::MessageType::Text ||
|
if (msgType == mtx::events::MessageType::Text ||
|
||||||
msgType == mtx::events::MessageType::Notice ||
|
msgType == mtx::events::MessageType::Notice ||
|
||||||
|
@ -3130,6 +3146,7 @@ TimelineModel::setEdit(const QString &newEdit)
|
||||||
} else {
|
} else {
|
||||||
input()->setText(QLatin1String(""));
|
input()->setText(QLatin1String(""));
|
||||||
}
|
}
|
||||||
|
input()->replaceMentions(std::move(mentions), std::move(mentionTexts));
|
||||||
|
|
||||||
edit_ = newEdit;
|
edit_ = newEdit;
|
||||||
} else {
|
} else {
|
||||||
|
@ -3148,9 +3165,7 @@ TimelineModel::resetEdit()
|
||||||
if (!edit_.isEmpty()) {
|
if (!edit_.isEmpty()) {
|
||||||
edit_ = QLatin1String("");
|
edit_ = QLatin1String("");
|
||||||
emit editChanged(edit_);
|
emit editChanged(edit_);
|
||||||
nhlog::ui()->debug("Restoring: {}", textBeforeEdit.toStdString());
|
input()->restoreAfterEdit();
|
||||||
input()->setText(textBeforeEdit);
|
|
||||||
textBeforeEdit.clear();
|
|
||||||
if (replyBeforeEdit.isEmpty())
|
if (replyBeforeEdit.isEmpty())
|
||||||
resetReply();
|
resetReply();
|
||||||
else
|
else
|
||||||
|
|
|
@ -529,7 +529,7 @@ private:
|
||||||
|
|
||||||
QString currentId, currentReadId;
|
QString currentId, currentReadId;
|
||||||
QString reply_, edit_, thread_;
|
QString reply_, edit_, thread_;
|
||||||
QString textBeforeEdit, replyBeforeEdit;
|
QString replyBeforeEdit;
|
||||||
QStringList typingUsers_;
|
QStringList typingUsers_;
|
||||||
|
|
||||||
TimelineViewManager *manager_;
|
TimelineViewManager *manager_;
|
||||||
|
|
Loading…
Reference in a new issue