diff --git a/imports/NeoChat/Component/ChatTextInput.qml b/imports/NeoChat/Component/ChatTextInput.qml index a839d20ff..7f6dd24df 100644 --- a/imports/NeoChat/Component/ChatTextInput.qml +++ b/imports/NeoChat/Component/ChatTextInput.qml @@ -318,6 +318,11 @@ ToolBar { property real progress: 0 property bool autoAppeared: false + // store each user we autoComplete here, this will be helpful later to generate + // the matrix.to links. + // This use an hack to define: https://doc.qt.io/qt-5/qml-var.html#property-value-initialization-semantics + property var userAutocompleted: ({}) + ChatDocumentHandler { id: documentHandler document: inputField.textDocument @@ -471,7 +476,7 @@ ToolBar { function postMessage() { roomManager.actionsHandler.postMessage(inputField.text.trim(), attachmentPath, - replyEventID, editEventId); + replyEventID, editEventId, inputField.userAutocompleted); clearAttachment(); currentRoom.markAllMessagesAsRead(); clear(); @@ -482,6 +487,10 @@ ToolBar { function autoComplete() { documentHandler.replaceAutoComplete(autoCompleteListView.currentItem.displayText) + if (!autoCompleteListView.currentItem.isEmoji) { + inputField.userAutocompleted[autoCompleteListView.currentItem.displayText] = autoCompleteListView.currentItem.userId; + } + } } } @@ -573,15 +582,14 @@ ToolBar { } function clear() { - inputField.clear() + inputField.clear(); + inputField.userAutocompleted = {}; } function clearEditReply() { isReply = false; replyUser = null; - if (root.editEventId.length > 0) { - clear(); - } + clear(); root.replyContent = ""; root.replyEventID = ""; root.editEventId = ""; @@ -592,9 +600,26 @@ ToolBar { inputField.forceActiveFocus() } - function edit(editContent, editEventId) { + function edit(editContent, editFormatedContent, editEventId) { + console.log("Editing ", editContent, "html:", editFormatedContent) + // Set the input field in edit mode inputField.text = editContent; root.editEventId = editEventId + + // clean autocompletion list + inputField.userAutocompleted = {}; + + // Fill autocompletion list with values extracted from message. + // We can't just iterate on every user in the list and try to + // find matching display name since some users have display name + // matching frequent words and this will marks too many words as + // mentions. + const regex = /([^<]*)<\/a>/g; + + let match; + while ((match = regex.exec(editFormatedContent.toString())) !== null) { + inputField.userAutocompleted[match[2]] = match[1]; + } } function closeAll() { diff --git a/imports/NeoChat/Component/Timeline/MessageDelegate.qml b/imports/NeoChat/Component/Timeline/MessageDelegate.qml index 9042fda81..a6ff6f68b 100644 --- a/imports/NeoChat/Component/Timeline/MessageDelegate.qml +++ b/imports/NeoChat/Component/Timeline/MessageDelegate.qml @@ -139,7 +139,7 @@ RowLayout { 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) + onClicked: chatTextInput.edit(message, model.formattedBody, eventId) } QQC2.Button { QQC2.ToolTip.text: i18n("Reply") diff --git a/src/actionshandler.cpp b/src/actionshandler.cpp index ff433bebc..8d9067f0e 100644 --- a/src/actionshandler.cpp +++ b/src/actionshandler.cpp @@ -142,7 +142,6 @@ void ActionsHandler::joinRoom(const QString &alias) joinRoomJob->errorString())); }); Quotient::JoinRoomJob::connect(joinRoomJob, &JoinRoomJob::success, [this, joinRoomJob] { - qDebug() << "joined" << joinRoomJob->roomId(); Q_EMIT roomJoined(joinRoomJob->roomId()); }); } @@ -159,15 +158,16 @@ void ActionsHandler::createRoom(const QString &name, const QString &topic) } void ActionsHandler::postMessage(const QString &text, - const QString &attachementPath, const QString &replyEventId, const QString &editEventId) + const QString &attachementPath, const QString &replyEventId, const QString &editEventId, + const QVariantMap &usernames) { QString rawText = text; QString cleanedText = text; - for (const auto *user : m_room->users()) { - const auto displayName = user->displayname(m_room); - cleanedText = cleanedText.replace(displayName, - "[" + displayName + "](https://matrix.to/#/" + user->id() + ")"); + + for (auto it = usernames.constBegin(); it != usernames.constEnd(); it++) { + cleanedText = cleanedText.replace(it.key(), + "[" + it.key() + "](https://matrix.to/#/" + it.value().toString() + ")"); } if (attachementPath.length() > 0) { @@ -328,5 +328,5 @@ void ActionsHandler::postMessage(const QString &text, cleanedText = cleanedText.remove(0, noticePrefix.length()); messageEventType = RoomMessageEvent::MsgType::Notice; } - m_room->postMessage(cleanedText, messageEventType, replyEventId, editEventId); + m_room->postMessage(rawText, cleanedText, messageEventType, replyEventId, editEventId); } diff --git a/src/actionshandler.h b/src/actionshandler.h index 683969ae5..76cac73fd 100644 --- a/src/actionshandler.h +++ b/src/actionshandler.h @@ -72,7 +72,7 @@ public Q_SLOTS: /// /// This also interprets commands if any. void postMessage(const QString &text, const QString &attachementPath, - const QString &replyEventId, const QString &editEventId); + const QString &replyEventId, const QString &editEventId, const QVariantMap &usernames); private: Connection *m_connection = nullptr; diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index 636c95cad..482693210 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -46,6 +46,7 @@ QHash MessageEventModel::roleNames() const roles[ShowSectionRole] = "showSection"; roles[ReactionRole] = "reaction"; roles[IsEditedRole] = "isEdited"; + roles[FormattedBodyRole] = "formattedBody"; return roles; } @@ -334,6 +335,16 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const return m_currentRoom->eventToString(evt, Qt::RichText); } + if (role == FormattedBodyRole) { + if (auto e = eventCast(&evt)) { + if (e->hasTextContent() && e->mimeType().name() != "text/plain") { + return static_cast(e->content())->body; + } + } + + return {}; + } + if (role == MessageRole) { return m_currentRoom->eventToString(evt); } diff --git a/src/messageeventmodel.h b/src/messageeventmodel.h index e1db33d4c..c9aad2ee6 100644 --- a/src/messageeventmodel.h +++ b/src/messageeventmodel.h @@ -32,6 +32,7 @@ public: LongOperationRole, AnnotationRole, UserMarkerRole, + FormattedBodyRole, ReplyRole, diff --git a/src/neochatroom.cpp b/src/neochatroom.cpp index 778c3bf13..44a084f4c 100644 --- a/src/neochatroom.cpp +++ b/src/neochatroom.cpp @@ -538,12 +538,12 @@ QString msgTypeToString(MessageEventType msgType) } } -void NeoChatRoom::postMessage(const QString &text, MessageEventType type, const QString &replyEventId, const QString &relateToEventId) +void NeoChatRoom::postMessage(const QString &rawText, const QString &text, MessageEventType type, const QString &replyEventId, const QString &relateToEventId) { const auto html = markdownToHTML(text); QString cleanText(text); cleanText.replace(QRegularExpression("\\[(.+)\\]\\(.+\\)"), "\\1"); - postHtmlMessage(cleanText, html, type, replyEventId, relateToEventId); + postHtmlMessage(rawText, html, type, replyEventId, relateToEventId); } void NeoChatRoom::postHtmlMessage(const QString &text, const QString &html, MessageEventType type, const QString &replyEventId, const QString &relateToEventId) diff --git a/src/neochatroom.h b/src/neochatroom.h index b1a9372db..7284ba705 100644 --- a/src/neochatroom.h +++ b/src/neochatroom.h @@ -139,7 +139,9 @@ public Q_SLOTS: void acceptInvitation(); void forget(); void sendTypingNotification(bool isTyping); - void postMessage(const QString &text, Quotient::MessageEventType type = Quotient::MessageEventType::Text, const QString &replyEventId = QString(), const QString &relateToEventId = QString()); + /// @param rawText The text as it was typed. + /// @param cleanedText The text with link to the users. + void postMessage(const QString &rawText, const QString &cleanedText, 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);