Allow editing text and also hide edits from the timeline
This commit is contained in:
@@ -22,8 +22,10 @@ ToolBar {
|
|||||||
property alias isReply: replyItem.visible
|
property alias isReply: replyItem.visible
|
||||||
property bool isReaction: false
|
property bool isReaction: false
|
||||||
property var replyUser
|
property var replyUser
|
||||||
property string replyEventID
|
property string replyEventID: ""
|
||||||
property string replyContent
|
property string replyContent: ""
|
||||||
|
|
||||||
|
property string editEventId
|
||||||
|
|
||||||
property alias isAutoCompleting: autoCompleteListView.visible
|
property alias isAutoCompleting: autoCompleteListView.visible
|
||||||
property var autoCompleteModel
|
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 {
|
Kirigami.Separator {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 1
|
Layout.preferredHeight: 1
|
||||||
@@ -280,7 +295,7 @@ ToolBar {
|
|||||||
|
|
||||||
icon.name: "dialog-cancel"
|
icon.name: "dialog-cancel"
|
||||||
|
|
||||||
onClicked: clearReply()
|
onClicked: clearEditReply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -359,13 +374,13 @@ ToolBar {
|
|||||||
} else {
|
} else {
|
||||||
postMessage()
|
postMessage()
|
||||||
text = ""
|
text = ""
|
||||||
clearReply()
|
clearEditReply()
|
||||||
closeAll()
|
closeAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onEscapePressed: {
|
Keys.onEscapePressed: {
|
||||||
clearReply();
|
clearEditReply();
|
||||||
closeAll();
|
closeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,7 +465,7 @@ ToolBar {
|
|||||||
// lines.
|
// lines.
|
||||||
const updatedText = inputField.text.trim()
|
const updatedText = inputField.text.trim()
|
||||||
.replace(/@([^: ]*):([^ ]*\.[^ ]*)/, "[@$1:$2](https://matrix.to/#/@$1:$2)");
|
.replace(/@([^: ]*):([^ ]*\.[^ ]*)/, "[@$1:$2](https://matrix.to/#/@$1:$2)");
|
||||||
documentHandler.postMessage(updatedText, attachmentPath, replyEventID);
|
documentHandler.postMessage(updatedText, attachmentPath, replyEventID, editEventId);
|
||||||
clearAttachment();
|
clearAttachment();
|
||||||
currentRoom.markAllMessagesAsRead();
|
currentRoom.markAllMessagesAsRead();
|
||||||
clear();
|
clear();
|
||||||
@@ -521,7 +536,7 @@ ToolBar {
|
|||||||
onClicked: {
|
onClicked: {
|
||||||
inputField.postMessage()
|
inputField.postMessage()
|
||||||
inputField.text = ""
|
inputField.text = ""
|
||||||
root.clearReply()
|
root.clearEditReply()
|
||||||
root.closeAll()
|
root.closeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -553,17 +568,23 @@ ToolBar {
|
|||||||
inputField.clear()
|
inputField.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearReply() {
|
function clearEditReply() {
|
||||||
isReply = false
|
isReply = false;
|
||||||
replyUser = null;
|
replyUser = null;
|
||||||
replyContent = "";
|
root.replyContent = "";
|
||||||
replyEventID = ""
|
root.replyEventID = "";
|
||||||
|
root.editEventId = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function focus() {
|
function focus() {
|
||||||
inputField.forceActiveFocus()
|
inputField.forceActiveFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function edit(editContent, editEventId) {
|
||||||
|
inputField.text = editContent;
|
||||||
|
root.editEventId = editEventId
|
||||||
|
}
|
||||||
|
|
||||||
function closeAll() {
|
function closeAll() {
|
||||||
replyItem.visible = false
|
replyItem.visible = false
|
||||||
autoCompleteListView.visible = false
|
autoCompleteListView.visible = false
|
||||||
|
|||||||
@@ -134,6 +134,13 @@ RowLayout {
|
|||||||
onReact: currentRoom.toggleReaction(eventId, emoji)
|
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.Button {
|
||||||
QQC2.ToolTip.text: i18n("Reply")
|
QQC2.ToolTip.text: i18n("Reply")
|
||||||
QQC2.ToolTip.visible: hovered
|
QQC2.ToolTip.visible: hovered
|
||||||
|
|||||||
@@ -198,12 +198,14 @@ Kirigami.ScrollablePage {
|
|||||||
filterRowCallback: Config.showLeaveJoinEvent ? dontFilterLeaveJoin : filterLeaveJoin
|
filterRowCallback: Config.showLeaveJoinEvent ? dontFilterLeaveJoin : filterLeaveJoin
|
||||||
|
|
||||||
function dontFilterLeaveJoin(row, parent) {
|
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";
|
&& messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.EventTypeRole) !== "other";
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterLeaveJoin(row, parent) {
|
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) !== "other"
|
||||||
&& messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.EventTypeRole) !== "state";
|
&& messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.EventTypeRole) !== "state";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
if (!m_room || !m_document) {
|
||||||
return;
|
return;
|
||||||
@@ -200,7 +200,7 @@ void ChatDocumentHandler::postMessage(const QString &text, const QString &attach
|
|||||||
for (int i = 0; i < cleanedText.length(); i++) {
|
for (int i = 0; i < cleanedText.length(); i++) {
|
||||||
rainbowText = rainbowText % QStringLiteral("<font color='") % rainbowColors.at(i % rainbowColors.length()) % "'>" % cleanedText.at(i) % "</font>";
|
rainbowText = rainbowText % QStringLiteral("<font color='") % rainbowColors.at(i % rainbowColors.length()) % "'>" % cleanedText.at(i) % "</font>";
|
||||||
}
|
}
|
||||||
m_room->postHtmlMessage(cleanedText, rainbowText, messageEventType, replyEventId);
|
m_room->postHtmlMessage(cleanedText, rainbowText, messageEventType, replyEventId, editEventId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ void ChatDocumentHandler::postMessage(const QString &text, const QString &attach
|
|||||||
cleanedText = cleanedText.remove(0, noticePrefix.length());
|
cleanedText = cleanedText.remove(0, noticePrefix.length());
|
||||||
messageEventType = RoomMessageEvent::MsgType::Notice;
|
messageEventType = RoomMessageEvent::MsgType::Notice;
|
||||||
}
|
}
|
||||||
m_room->postArbitaryMessage(cleanedText, messageEventType, replyEventId);
|
m_room->postMessage(cleanedText, messageEventType, replyEventId, editEventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatDocumentHandler::replaceAutoComplete(const QString &word)
|
void ChatDocumentHandler::replaceAutoComplete(const QString &word)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public:
|
|||||||
[[nodiscard]] NeoChatRoom *room() const;
|
[[nodiscard]] NeoChatRoom *room() const;
|
||||||
void setRoom(NeoChatRoom *room);
|
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
|
/// This function will look at the current QTextCursor and determine if there
|
||||||
/// is the posibility to autocomplete it.
|
/// is the posibility to autocomplete it.
|
||||||
|
|||||||
@@ -415,6 +415,11 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isReplacement?
|
||||||
|
if (auto e = eventCast<const RoomMessageEvent>(&evt))
|
||||||
|
if (!e->replacedEvent().isEmpty())
|
||||||
|
return EventStatus::Hidden;
|
||||||
|
|
||||||
if (is<RedactionEvent>(evt) || is<ReactionEvent>(evt)) {
|
if (is<RedactionEvent>(evt) || is<ReactionEvent>(evt)) {
|
||||||
return EventStatus::Hidden;
|
return EventStatus::Hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -479,18 +479,6 @@ QString NeoChatRoom::markdownToHTML(const QString &markdown)
|
|||||||
return result;
|
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)
|
QString msgTypeToString(MessageEventType msgType)
|
||||||
{
|
{
|
||||||
switch (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 html = markdownToHTML(text);
|
||||||
const auto replyIt = findInTimeline(replyEventId);
|
postHtmlMessage(text, html, type, replyEventId, relateToEventId);
|
||||||
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",
|
|
||||||
"<mx-reply><blockquote><a href=\"https://matrix.to/#/" +
|
|
||||||
id() + "/" +
|
|
||||||
replyEventId +
|
|
||||||
"\">In reply to</a> <a href=\"https://matrix.to/#/" +
|
|
||||||
replyEvt.senderId() + "\">" + replyEvt.senderId() +
|
|
||||||
"</a><br>" + eventToString(replyEvt, Qt::RichText) +
|
|
||||||
"</blockquote></mx-reply>" + text
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
postJson("m.room.message", json);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Room::postMessage(text, type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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("@([^: ]*):([^ ]*\\.[^ ]*)"), "<a href=\"https://matrix.to/#/@$1:$2\">@$1:$2</a>");
|
||||||
|
bool isRichText = Qt::mightBeRichText(htmlWithLinks);
|
||||||
bool isReply = !replyEventId.isEmpty();
|
bool isReply = !replyEventId.isEmpty();
|
||||||
|
bool isEdit = !relateToEventId.isEmpty();
|
||||||
const auto replyIt = findInTimeline(replyEventId);
|
const auto replyIt = findInTimeline(replyEventId);
|
||||||
if (replyIt == timelineEdge()) {
|
if (replyIt == timelineEdge()) {
|
||||||
isReply = false;
|
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) {
|
if (isReply) {
|
||||||
const auto &replyEvt = **replyIt;
|
const auto &replyEvt = **replyIt;
|
||||||
|
|
||||||
@@ -592,7 +569,7 @@ void NeoChatRoom::postHtmlMessage(const QString &text, const QString &html, Mess
|
|||||||
"\">In reply to</a> <a href=\"https://matrix.to/#/" +
|
"\">In reply to</a> <a href=\"https://matrix.to/#/" +
|
||||||
replyEvt.senderId() + "\">" + replyEvt.senderId() +
|
replyEvt.senderId() + "\">" + replyEvt.senderId() +
|
||||||
"</a><br>" + eventToString(replyEvt, Qt::RichText) +
|
"</a><br>" + eventToString(replyEvt, Qt::RichText) +
|
||||||
"</blockquote></mx-reply>" + html
|
"</blockquote></mx-reply>" + (isRichText ? htmlWithLinks : text)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
@@ -602,7 +579,11 @@ void NeoChatRoom::postHtmlMessage(const QString &text, const QString &html, Mess
|
|||||||
return;
|
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)
|
void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction)
|
||||||
|
|||||||
@@ -130,13 +130,12 @@ Q_SIGNALS:
|
|||||||
void lastActiveTimeChanged();
|
void lastActiveTimeChanged();
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void uploadFile(const QUrl &url, const QString &body = "");
|
void uploadFile(const QUrl &url, const QString &body = QString());
|
||||||
void acceptInvitation();
|
void acceptInvitation();
|
||||||
void forget();
|
void forget();
|
||||||
void sendTypingNotification(bool isTyping);
|
void sendTypingNotification(bool isTyping);
|
||||||
void postArbitaryMessage(const QString &text, Quotient::RoomMessageEvent::MsgType type, const QString &replyEventId);
|
void postMessage(const QString &text, Quotient::MessageEventType type = Quotient::MessageEventType::Text, const QString &replyEventId = QString(), const QString &relateToEventId = QString());
|
||||||
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 = QString(), const QString &relateToEventId = QString());
|
||||||
void postHtmlMessage(const QString &text, const QString &html, Quotient::MessageEventType type = Quotient::MessageEventType::Text, const QString &replyEventId = "");
|
|
||||||
void changeAvatar(const QUrl &localFile);
|
void changeAvatar(const QUrl &localFile);
|
||||||
void addLocalAlias(const QString &alias);
|
void addLocalAlias(const QString &alias);
|
||||||
void removeLocalAlias(const QString &alias);
|
void removeLocalAlias(const QString &alias);
|
||||||
|
|||||||
Reference in New Issue
Block a user