From 5ca03fdea868d475ea3e683d74585629239a7b5c Mon Sep 17 00:00:00 2001 From: Black Hat Date: Mon, 17 Sep 2018 11:58:02 +0800 Subject: [PATCH] Some basic reply support. --- qml/component/MessageDelegate.qml | 3 +- qml/form/RoomForm.qml | 187 ++++++++++++++++-------------- qml/menu/MessageContextMenu.qml | 10 ++ src/matriqueroom.cpp | 15 +++ src/matriqueroom.h | 1 + src/messageeventmodel.cpp | 3 + src/messageeventmodel.h | 1 + 7 files changed, 131 insertions(+), 89 deletions(-) diff --git a/qml/component/MessageDelegate.qml b/qml/component/MessageDelegate.qml index 13d7ef8..7cd4c1d 100644 --- a/qml/component/MessageDelegate.qml +++ b/qml/component/MessageDelegate.qml @@ -82,7 +82,8 @@ RowLayout { selectByMouse: true readOnly: true wrapMode: Label.Wrap - selectionColor: Material.accent + selectedTextColor: highlighted ? Material.accent : "white" + selectionColor: highlighted ? "white" : Material.accent textFormat: Text.RichText onLinkActivated: Qt.openUrlExternally(link) diff --git a/qml/form/RoomForm.qml b/qml/form/RoomForm.qml index da45545..f40bc10 100644 --- a/qml/form/RoomForm.qml +++ b/qml/form/RoomForm.qml @@ -332,108 +332,119 @@ Item { onClicked: currentRoom.chooseAndUploadFile() } - TextField { - property real progress: 0 - + ScrollView { Layout.fillWidth: true Layout.preferredHeight: 48 - id: inputField + clip: true - placeholderText: "Send a Message" - leftPadding: 16 - topPadding: 0 - bottomPadding: 0 - selectByMouse: true + TextArea { + property real progress: 0 - text: currentRoom ? currentRoom.cachedInput : "" + id: inputField - onTextChanged: { - timeoutTimer.restart() - repeatTimer.start() - currentRoom.cachedInput = text - } + placeholderText: "Send a Message" + leftPadding: 16 + topPadding: 0 + bottomPadding: 0 + selectByMouse: true + verticalAlignment: TextEdit.AlignVCenter - Keys.onReturnPressed: { - if (inputField.text) { - inputField.postMessage(inputField.text) - inputField.text = "" + text: currentRoom ? currentRoom.cachedInput : "" + + onTextChanged: { + timeoutTimer.restart() + repeatTimer.start() + currentRoom.cachedInput = text } - } - background: Rectangle { - color: MSettings.darkTheme ? "#282828" : "#eaeaea" - } + background: Rectangle { color: MSettings.darkTheme ? "#282828" : "#eaeaea" } - ToolTip.visible: currentRoom && currentRoom.hasUsersTyping - ToolTip.text: currentRoom ? currentRoom.usersTyping : "" + ToolTip.visible: currentRoom && currentRoom.hasUsersTyping + ToolTip.text: currentRoom ? currentRoom.usersTyping : "" - Timer { - id: timeoutTimer - - repeat: false - interval: 2000 - onTriggered: { - repeatTimer.stop() - currentRoom.sendTypingNotification(false) - } - } - - Timer { - id: repeatTimer - - repeat: true - interval: 5000 - triggeredOnStart: true - onTriggered: currentRoom.sendTypingNotification(true) - } - - function postMessage(text) { - if (text.trim().length === 0) { return } - if(!currentRoom) { return } - - var PREFIX_ME = '/me ' - var PREFIX_NOTICE = '/notice ' - var PREFIX_RAINBOW = '/rainbow ' - var PREFIX_HTML = '/html ' - var PREFIX_MARKDOWN = '/md ' - - if (text.indexOf(PREFIX_ME) === 0) { - text = text.substr(PREFIX_ME.length) - currentRoom.postMessage(text, RoomMessageEvent.Emote) - return - } - if (text.indexOf(PREFIX_NOTICE) === 0) { - text = text.substr(PREFIX_NOTICE.length) - currentRoom.postMessage(text, RoomMessageEvent.Notice) - return - } - if (text.indexOf(PREFIX_RAINBOW) === 0) { - text = text.substr(PREFIX_RAINBOW.length) - - var parsedText = "" - var rainbowColor = ["#ff2b00", "#ff5500", "#ff8000", "#ffaa00", "#ffd500", "#ffff00", "#d4ff00", "#aaff00", "#80ff00", "#55ff00", "#2bff00", "#00ff00", "#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff", "#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"] - for (var i = 0; i < text.length; i++) { - parsedText = parsedText + "" + text.charAt(i) + "" + Shortcut { + sequence: "Ctrl+Return" + onActivated: { + inputField.postMessage(inputField.text) + inputField.text = "" } - currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text) - return - } - if (text.indexOf(PREFIX_HTML) === 0) { - text = text.substr(PREFIX_HTML.length) - var re = new RegExp("<.*?>") - var plainText = text.replace(re, "") - currentRoom.postHtmlMessage(plainText, text, RoomMessageEvent.Text) - return - } - if (text.indexOf(PREFIX_MARKDOWN) === 0) { - text = text.substr(PREFIX_MARKDOWN.length) - var parsedText = Markdown.markdown_parser(text) - currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text) - return } - currentRoom.postPlainText(text) + Timer { + id: timeoutTimer + + repeat: false + interval: 2000 + onTriggered: { + repeatTimer.stop() + currentRoom.sendTypingNotification(false) + } + } + + Timer { + id: repeatTimer + + repeat: true + interval: 5000 + triggeredOnStart: true + onTriggered: currentRoom.sendTypingNotification(true) + } + + function postMessage(text) { + if (text.trim().length === 0) { return } + if(!currentRoom) { return } + + var PREFIX_ME = '/me ' + var PREFIX_NOTICE = '/notice ' + var PREFIX_RAINBOW = '/rainbow ' + var PREFIX_HTML = '/html ' + var PREFIX_MARKDOWN = '/md ' + + var replyRe = new RegExp("^> <(.*)><(.*)> (.*)\n\n(.*)") + if (text.match(replyRe)) { + var matches = text.match(replyRe) + currentRoom.sendReply(matches[1], matches[2], matches[3], matches[4]) + return + } + + if (text.indexOf(PREFIX_ME) === 0) { + text = text.substr(PREFIX_ME.length) + currentRoom.postMessage(text, RoomMessageEvent.Emote) + return + } + if (text.indexOf(PREFIX_NOTICE) === 0) { + text = text.substr(PREFIX_NOTICE.length) + currentRoom.postMessage(text, RoomMessageEvent.Notice) + return + } + if (text.indexOf(PREFIX_RAINBOW) === 0) { + text = text.substr(PREFIX_RAINBOW.length) + + var parsedText = "" + var rainbowColor = ["#ff2b00", "#ff5500", "#ff8000", "#ffaa00", "#ffd500", "#ffff00", "#d4ff00", "#aaff00", "#80ff00", "#55ff00", "#2bff00", "#00ff00", "#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff", "#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"] + for (var i = 0; i < text.length; i++) { + parsedText = parsedText + "" + text.charAt(i) + "" + } + currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text) + return + } + if (text.indexOf(PREFIX_HTML) === 0) { + text = text.substr(PREFIX_HTML.length) + var re = new RegExp("<.*?>") + var plainText = text.replace(re, "") + currentRoom.postHtmlMessage(plainText, text, RoomMessageEvent.Text) + return + } + if (text.indexOf(PREFIX_MARKDOWN) === 0) { + text = text.substr(PREFIX_MARKDOWN.length) + var parsedText = Markdown.markdown_parser(text) + currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text) + return + } + + currentRoom.postPlainText(text) + } } } diff --git a/qml/menu/MessageContextMenu.qml b/qml/menu/MessageContextMenu.qml index 24b0124..a173c85 100644 --- a/qml/menu/MessageContextMenu.qml +++ b/qml/menu/MessageContextMenu.qml @@ -41,6 +41,16 @@ Menu { onTriggered: row.saveFileAs() } + MenuItem { + visible: model && model.author !== currentRoom.localUser + height: visible ? undefined : 0 + text: "Reply" + + onTriggered: { + inputField.clear() + inputField.insert(0, "> <" + model.author.id + "><" + model.eventId + "> " + model.message + "\n\n") + } + } MenuItem { visible: model && model.author === currentRoom.localUser height: visible ? undefined : 0 diff --git a/src/matriqueroom.cpp b/src/matriqueroom.cpp index d11da12..e6976c9 100644 --- a/src/matriqueroom.cpp +++ b/src/matriqueroom.cpp @@ -124,3 +124,18 @@ void MatriqueRoom::countChanged() { resetHighlightCount(); } } + +void MatriqueRoom::sendReply(QString userId, QString eventId, + QString replyContent, QString sendContent) { + QJsonObject json{ + {"msgtype", "m.text"}, + {"body", "> <" + userId + "> " + replyContent + "\n\n" + sendContent}, + {"format", "org.matrix.custom.html"}, + {"m.relates_to", QJsonObject{{"m.in_reply_to", QJsonObject{{"event_id", eventId}}}}}, + {"formatted_body", + "
In reply to " + userId + "
" + replyContent + + "
" + sendContent}}; + postJson("m.room.message", json); +} diff --git a/src/matriqueroom.h b/src/matriqueroom.h index 3bfcebc..349d8eb 100644 --- a/src/matriqueroom.h +++ b/src/matriqueroom.h @@ -59,6 +59,7 @@ class MatriqueRoom : public Room { void acceptInvitation(); void forget(); void sendTypingNotification(bool isTyping); + void sendReply(QString userId, QString eventId, QString replyContent, QString sendContent); }; #endif // MATRIQUEROOM_H diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index ef169ea..65531a9 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -15,6 +15,7 @@ QHash MessageEventModel::roleNames() const { QHash roles = QAbstractItemModel::roleNames(); roles[EventTypeRole] = "eventType"; + roles[MessageRole] = "message"; roles[AboveEventTypeRole] = "aboveEventType"; roles[EventIdRole] = "eventId"; roles[TimeRole] = "time"; @@ -415,6 +416,8 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { tr("Unknown Event")); } + if (role == MessageRole) return evt.contentJson().value("body"); + if (role == Qt::ToolTipRole) { return evt.originalJson(); } diff --git a/src/messageeventmodel.h b/src/messageeventmodel.h index 5045681..7b54207 100644 --- a/src/messageeventmodel.h +++ b/src/messageeventmodel.h @@ -13,6 +13,7 @@ class MessageEventModel : public QAbstractListModel { public: enum EventRoles { EventTypeRole = Qt::UserRole + 1, + MessageRole, AboveEventTypeRole, EventIdRole, TimeRole,