diff --git a/imports/NeoChat/Component/ChatTextInput.qml b/imports/NeoChat/Component/ChatTextInput.qml
index 2275c5356..621f4ddf5 100644
--- a/imports/NeoChat/Component/ChatTextInput.qml
+++ b/imports/NeoChat/Component/ChatTextInput.qml
@@ -22,8 +22,10 @@ ToolBar {
property alias isReply: replyItem.visible
property bool isReaction: false
property var replyUser
- property string replyEventID
- property string replyContent
+ property string replyEventID: ""
+ property string replyContent: ""
+
+ property string editEventId
property alias isAutoCompleting: autoCompleteListView.visible
property var autoCompleteModel
@@ -262,6 +264,19 @@ ToolBar {
}
}
+ RowLayout {
+ visible: editEventId.length > 0
+ ToolButton {
+ icon.name: "dialog-cancel"
+ onClicked: clearEditReply();
+ }
+
+ Label {
+ Layout.alignment: Qt.AlignVCenter
+ text: i18n("Edit Message")
+ }
+ }
+
Kirigami.Separator {
Layout.fillWidth: true
Layout.preferredHeight: 1
@@ -280,7 +295,7 @@ ToolBar {
icon.name: "dialog-cancel"
- onClicked: clearReply()
+ onClicked: clearEditReply()
}
@@ -359,13 +374,13 @@ ToolBar {
} else {
postMessage()
text = ""
- clearReply()
+ clearEditReply()
closeAll()
}
}
Keys.onEscapePressed: {
- clearReply();
+ clearEditReply();
closeAll();
}
@@ -450,7 +465,7 @@ ToolBar {
// lines.
const updatedText = inputField.text.trim()
.replace(/@([^: ]*):([^ ]*\.[^ ]*)/, "[@$1:$2](https://matrix.to/#/@$1:$2)");
- documentHandler.postMessage(updatedText, attachmentPath, replyEventID);
+ documentHandler.postMessage(updatedText, attachmentPath, replyEventID, editEventId);
clearAttachment();
currentRoom.markAllMessagesAsRead();
clear();
@@ -521,7 +536,7 @@ ToolBar {
onClicked: {
inputField.postMessage()
inputField.text = ""
- root.clearReply()
+ root.clearEditReply()
root.closeAll()
}
@@ -553,17 +568,23 @@ ToolBar {
inputField.clear()
}
- function clearReply() {
- isReply = false
+ function clearEditReply() {
+ isReply = false;
replyUser = null;
- replyContent = "";
- replyEventID = ""
+ root.replyContent = "";
+ root.replyEventID = "";
+ root.editEventId = "";
}
function focus() {
inputField.forceActiveFocus()
}
+ function edit(editContent, editEventId) {
+ inputField.text = editContent;
+ root.editEventId = editEventId
+ }
+
function closeAll() {
replyItem.visible = false
autoCompleteListView.visible = false
diff --git a/imports/NeoChat/Component/Timeline/MessageDelegate.qml b/imports/NeoChat/Component/Timeline/MessageDelegate.qml
index 6c90cffcf..49f620b07 100644
--- a/imports/NeoChat/Component/Timeline/MessageDelegate.qml
+++ b/imports/NeoChat/Component/Timeline/MessageDelegate.qml
@@ -134,6 +134,13 @@ RowLayout {
onReact: currentRoom.toggleReaction(eventId, emoji)
}
}
+ QQC2.Button {
+ QQC2.ToolTip.text: i18n("Edit")
+ QQC2.ToolTip.visible: hovered
+ visible: controlContainer.hovered && author.id === Controller.activeConnection.localUserId && (model.eventType === "emote" || model.eventType === "message")
+ icon.name: "document-edit"
+ onClicked: chatTextInput.edit(message, eventId)
+ }
QQC2.Button {
QQC2.ToolTip.text: i18n("Reply")
QQC2.ToolTip.visible: hovered
diff --git a/imports/NeoChat/Page/RoomPage.qml b/imports/NeoChat/Page/RoomPage.qml
index 9e7d4797b..ff0332eff 100644
--- a/imports/NeoChat/Page/RoomPage.qml
+++ b/imports/NeoChat/Page/RoomPage.qml
@@ -198,12 +198,14 @@ Kirigami.ScrollablePage {
filterRowCallback: Config.showLeaveJoinEvent ? dontFilterLeaveJoin : filterLeaveJoin
function dontFilterLeaveJoin(row, parent) {
- return messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.MessageRole) !== 0x10
+ return messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.SpecialMarksRole) !== EventStatus.Hidden
+ && messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.MessageRole) !== 0x10
&& messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.EventTypeRole) !== "other";
}
function filterLeaveJoin(row, parent) {
- return messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.MessageRole) !== 0x10
+ return messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.SpecialMarksRole) !== EventStatus.Hidden
+ && messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.MessageRole) !== 0x10
&& messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.EventTypeRole) !== "other"
&& messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.EventTypeRole) !== "state";
}
diff --git a/src/chatdocumenthandler.cpp b/src/chatdocumenthandler.cpp
index 35927a4aa..74d50c07f 100644
--- a/src/chatdocumenthandler.cpp
+++ b/src/chatdocumenthandler.cpp
@@ -167,7 +167,7 @@ QVariantMap ChatDocumentHandler::getAutocompletionInfo()
};
}
-void ChatDocumentHandler::postMessage(const QString &text, const QString &attachementPath, const QString &replyEventId) const
+void ChatDocumentHandler::postMessage(const QString &text, const QString &attachementPath, const QString &replyEventId, const QString &editEventId) const
{
if (!m_room || !m_document) {
return;
@@ -200,7 +200,7 @@ void ChatDocumentHandler::postMessage(const QString &text, const QString &attach
for (int i = 0; i < cleanedText.length(); i++) {
rainbowText = rainbowText % QStringLiteral("" % cleanedText.at(i) % "";
}
- m_room->postHtmlMessage(cleanedText, rainbowText, messageEventType, replyEventId);
+ m_room->postHtmlMessage(cleanedText, rainbowText, messageEventType, replyEventId, editEventId);
return;
}
@@ -211,7 +211,7 @@ void ChatDocumentHandler::postMessage(const QString &text, const QString &attach
cleanedText = cleanedText.remove(0, noticePrefix.length());
messageEventType = RoomMessageEvent::MsgType::Notice;
}
- m_room->postArbitaryMessage(cleanedText, messageEventType, replyEventId);
+ m_room->postMessage(cleanedText, messageEventType, replyEventId, editEventId);
}
void ChatDocumentHandler::replaceAutoComplete(const QString &word)
diff --git a/src/chatdocumenthandler.h b/src/chatdocumenthandler.h
index 5e7c413e6..dcfd2a538 100644
--- a/src/chatdocumenthandler.h
+++ b/src/chatdocumenthandler.h
@@ -51,7 +51,7 @@ public:
[[nodiscard]] NeoChatRoom *room() const;
void setRoom(NeoChatRoom *room);
- Q_INVOKABLE void postMessage(const QString &text, const QString &attachementPath, const QString &replyEventId) const;
+ Q_INVOKABLE void postMessage(const QString &text, const QString &attachementPath, const QString &replyEventId, const QString &editEventId) const;
/// This function will look at the current QTextCursor and determine if there
/// is the posibility to autocomplete it.
diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp
index 3753d6d9a..8c21575f7 100644
--- a/src/messageeventmodel.cpp
+++ b/src/messageeventmodel.cpp
@@ -415,6 +415,11 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
}
+ // isReplacement?
+ if (auto e = eventCast(&evt))
+ if (!e->replacedEvent().isEmpty())
+ return EventStatus::Hidden;
+
if (is(evt) || is(evt)) {
return EventStatus::Hidden;
}
diff --git a/src/neochatroom.cpp b/src/neochatroom.cpp
index 0e2ffa2fb..7f30a59c4 100644
--- a/src/neochatroom.cpp
+++ b/src/neochatroom.cpp
@@ -479,18 +479,6 @@ QString NeoChatRoom::markdownToHTML(const QString &markdown)
return result;
}
-void NeoChatRoom::postArbitaryMessage(const QString &text, Quotient::RoomMessageEvent::MsgType type, const QString &replyEventId)
-{
- const auto parsedHTML = markdownToHTML(text);
- const bool isRichText = Qt::mightBeRichText(parsedHTML);
-
- if (isRichText) { // Markdown
- postHtmlMessage(text, parsedHTML, type, replyEventId);
- } else { // Plain text
- postPlainMessage(text, type, replyEventId);
- }
-}
-
QString msgTypeToString(MessageEventType msgType)
{
switch (msgType) {
@@ -515,59 +503,48 @@ QString msgTypeToString(MessageEventType msgType)
}
}
-void NeoChatRoom::postPlainMessage(const QString &text, MessageEventType type, const QString &replyEventId)
+void NeoChatRoom::postMessage(const QString &text, MessageEventType type, const QString &replyEventId, const QString &relateToEventId)
{
- bool isReply = !replyEventId.isEmpty();
- const auto replyIt = findInTimeline(replyEventId);
- if (replyIt == timelineEdge()) {
- isReply = false;
- }
-
- if (isReply) {
- const auto &replyEvt = **replyIt;
-
- // clang-format off
- QJsonObject json{
- {"msgtype", msgTypeToString(type)},
- {"body", "> <" + replyEvt.senderId() + "> " + eventToString(replyEvt) + "\n\n" + text},
- {"format", "org.matrix.custom.html"},
- {"m.relates_to",
- QJsonObject {
- {"m.in_reply_to",
- QJsonObject {
- {"event_id", replyEventId}
- }
- }
- }
- },
- {"formatted_body",
- "In reply to " + replyEvt.senderId() +
- "
" + eventToString(replyEvt, Qt::RichText) +
- "
" + text
- }
- };
- // clang-format on
-
- postJson("m.room.message", json);
-
- return;
- }
-
- Room::postMessage(text, type);
+ const auto html = markdownToHTML(text);
+ postHtmlMessage(text, html, type, replyEventId, relateToEventId);
}
-void NeoChatRoom::postHtmlMessage(const QString &text, const QString &html, MessageEventType type, const QString &replyEventId)
+void NeoChatRoom::postHtmlMessage(const QString &text, const QString &html, MessageEventType type, const QString &replyEventId, const QString &relateToEventId)
{
+ QString htmlWithLinks = html;
+ htmlWithLinks = htmlWithLinks.replace(QRegularExpression("@([^: ]*):([^ ]*\\.[^ ]*)"), "@$1:$2");
+ bool isRichText = Qt::mightBeRichText(htmlWithLinks);
bool isReply = !replyEventId.isEmpty();
+ bool isEdit = !relateToEventId.isEmpty();
const auto replyIt = findInTimeline(replyEventId);
if (replyIt == timelineEdge()) {
isReply = false;
}
+
+ if (isEdit) {
+ QJsonObject json {
+ {"type", "m.room.message"},
+ {"msgtype", msgTypeToString(type)},
+ {"body", "* " + (isRichText ? text : htmlWithLinks)},
+ {"m.new_content",
+ QJsonObject {
+ {"body", (isRichText ? text : htmlWithLinks)},
+ {"msgtype", msgTypeToString(type)}
+ }
+ },
+ {"m.relates_to",
+ QJsonObject {
+ {"rel_type", "m.replace"},
+ {"event_id", relateToEventId}
+ }
+ }
+ };
+
+ postJson("m.room.message", json);
+ return;
+ }
+
if (isReply) {
const auto &replyEvt = **replyIt;
@@ -592,7 +569,7 @@ void NeoChatRoom::postHtmlMessage(const QString &text, const QString &html, Mess
"\">In reply to " + replyEvt.senderId() +
"
" + eventToString(replyEvt, Qt::RichText) +
- "" + html
+ "" + (isRichText ? htmlWithLinks : text)
}
};
// clang-format on
@@ -602,7 +579,11 @@ void NeoChatRoom::postHtmlMessage(const QString &text, const QString &html, Mess
return;
}
- Room::postHtmlMessage(text, html, type);
+ if (isRichText) {
+ Room::postHtmlMessage(text, html, type);
+ } else {
+ Room::postMessage(text, type);
+ }
}
void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction)
diff --git a/src/neochatroom.h b/src/neochatroom.h
index 847a750b8..827d8f1b5 100644
--- a/src/neochatroom.h
+++ b/src/neochatroom.h
@@ -130,13 +130,12 @@ Q_SIGNALS:
void lastActiveTimeChanged();
public Q_SLOTS:
- void uploadFile(const QUrl &url, const QString &body = "");
+ void uploadFile(const QUrl &url, const QString &body = QString());
void acceptInvitation();
void forget();
void sendTypingNotification(bool isTyping);
- void postArbitaryMessage(const QString &text, Quotient::RoomMessageEvent::MsgType type, const QString &replyEventId);
- void postPlainMessage(const QString &text, Quotient::RoomMessageEvent::MsgType type = Quotient::MessageEventType::Text, const QString &replyEventId = "");
- void postHtmlMessage(const QString &text, const QString &html, Quotient::MessageEventType type = Quotient::MessageEventType::Text, const QString &replyEventId = "");
+ void postMessage(const QString &text, Quotient::MessageEventType type = Quotient::MessageEventType::Text, const QString &replyEventId = QString(), const QString &relateToEventId = QString());
+ void postHtmlMessage(const QString &text, const QString &html, Quotient::MessageEventType type = Quotient::MessageEventType::Text, const QString &replyEventId = QString(), const QString &relateToEventId = QString());
void changeAvatar(const QUrl &localFile);
void addLocalAlias(const QString &alias);
void removeLocalAlias(const QString &alias);