From 3f1c58b743537df4c90c37e0a3f47781b22b203e Mon Sep 17 00:00:00 2001 From: N-Pex Date: Tue, 15 Dec 2020 17:06:26 +0100 Subject: [PATCH] Replies --- src/assets/css/chat.scss | 30 +++++++- src/components/Chat.vue | 34 +++++---- .../messages/MessageIncomingText.vue | 7 +- .../messages/MessageOutgoingText.vue | 7 +- src/components/messages/messageMixin.js | 69 +++++++++++++++++++ src/plugins/utils.js | 14 +++- src/services/matrix.service.js | 18 +++-- 7 files changed, 157 insertions(+), 22 deletions(-) diff --git a/src/assets/css/chat.scss b/src/assets/css/chat.scss index 54ac879..63416e7 100644 --- a/src/assets/css/chat.scss +++ b/src/assets/css/chat.scss @@ -150,7 +150,7 @@ $chat-text-size: 0.7pt; margin-left: 30% !important; text-align: right; .bubble { - background-color: #88eec0; + background-color: #e5e5e5; border-radius: 10px 10px 0 10px; padding: 8px; } @@ -243,12 +243,40 @@ $chat-text-size: 0.7pt; font-size: 14 * $chat-text-size; color: #000000; overflow-wrap: break-word; + white-space: pre; .edit-marker { font-size: 0.8rem; color: #888888; } } +.original-message { + background-color: white; + border: 1px solid black; + border-radius: 10px; + padding: 8px; + max-height: 200px; + overflow-x: hidden; + overflow-y: auto; + margin-bottom: 8px; + .original-message-sender { + font-family: 'Titillium Web', sans-serif; + font-weight: 700; + font-size: 13 * $chat-text-size; + color: #000000; + overflow-wrap: break-word; + white-space: pre; + } + .original-message-text { + font-family: 'Titillium Web', sans-serif; + font-weight: 400; + font-size: 11 * $chat-text-size; + color: #000000; + overflow-wrap: break-word; + white-space: pre; + } +} + .time { font-family: 'Titillium Web', sans-serif; font-weight: 300; diff --git a/src/components/Chat.vue b/src/components/Chat.vue index c0d604a..63cac97 100644 --- a/src/components/Chat.vue +++ b/src/components/Chat.vue @@ -14,6 +14,7 @@ v-on:close="showContextMenu = false" v-if="selectedEvent && showContextMenu" v-on:addreaction="addReaction" + v-on:addreply="addReply(selectedEvent)" v-on:edit="edit(selectedEvent)" :event="selectedEvent" :incoming="selectedEvent.getSender() != $matrix.currentUserId" @@ -55,16 +56,10 @@ 'm.reaction' ) " + :timelineSet="timelineWindow._timelineSet" v-on:send-quick-reaction="sendQuickReaction" v-on:context-menu="showContextMenuForEvent($event)" /> - @@ -73,6 +68,8 @@ +
REPLYING TO EVENT: {{ replyToEvent.getContent().body }}
+
{{ typingMembersString }} @@ -112,8 +109,8 @@ /> - - + + cancel @@ -261,6 +258,7 @@ export default { showEmojiPicker: false, selectedEvent: null, editedEvent: null, + replyToEvent: null, showContextMenu: false, /** * Current chat container size. We need to keep track of this so that if and when @@ -298,7 +296,7 @@ export default { return this.room.roomId; }, attachButtonDisabled() { - return this.editedEvent || this.currentInput.length > 0; + return this.editedEvent != null || this.replyToEvent != null || this.currentInput.length > 0; }, sendButtonDisabled() { return this.currentInput.length == 0; @@ -328,6 +326,7 @@ export default { watch: { room: { + immediate: true, handler(room, ignoredOldVal) { console.log("Chat: Current room changed"); @@ -351,8 +350,7 @@ export default { this.paginateBackIfNeeded(); }); }); - }, - immediate: true, + } }, }, @@ -524,7 +522,8 @@ export default { this.$matrix.matrixClient, this.roomId, this.currentInput, - this.editedEvent + this.editedEvent, + this.replyToEvent ) .then(() => { console.log("Sent message"); @@ -534,6 +533,7 @@ export default { }); this.currentInput = ""; this.editedEvent = null; //TODO - Is this a good place to reset this? + this.replyToEvent = null; } }, @@ -678,15 +678,21 @@ export default { this.showEmojiPicker = true; }, + addReply(event) { + this.replyToEvent = event; + this.$refs.messageInput.focus(); + }, + edit(event) { this.editedEvent = event; this.currentInput = event.getContent().body; this.$refs.messageInput.focus(); }, - cancelEdit() { + cancelEditReply() { this.currentInput = ""; this.editedEvent = null; + this.replyToEvent = null; }, emojiSelected(e) { diff --git a/src/components/messages/MessageIncomingText.vue b/src/components/messages/MessageIncomingText.vue index 6cf3f0c..4d3bc0b 100644 --- a/src/components/messages/MessageIncomingText.vue +++ b/src/components/messages/MessageIncomingText.vue @@ -8,13 +8,18 @@
+
+
{{ inReplyToSender || 'Someone' }} said:
+
{{ inReplyToText }}
+
- {{ event.getContent().body }} + {{ messageText }} (edited)
+
more_vert
+
+
{{ inReplyToSender || 'Someone' }} said:
+
{{ inReplyToText }}
+
+
- {{ event.getContent().body }} + {{ messageText }} (edited) diff --git a/src/components/messages/messageMixin.js b/src/components/messages/messageMixin.js index 2af57d9..17d921f 100644 --- a/src/components/messages/messageMixin.js +++ b/src/components/messages/messageMixin.js @@ -22,9 +22,78 @@ export default { default: function () { return null } + }, + timelineSet: { + type: Object, + default: function () { + return null + } + }, + }, + data() { + return { + inReplyToEvent: null, + inReplyToSender: null + } + }, + mounted() { + const relatesTo = this.event.getWireContent()['m.relates_to']; + if (relatesTo && relatesTo['m.in_reply_to']) + { + // Can we find the original message? + const originalEventId = relatesTo['m.in_reply_to'].event_id; + if (originalEventId) { + const originalEvent = this.timelineSet.findEventById(originalEventId); + this.inReplyToEvent = originalEvent; + this.inReplyToSender = this.messageEventDisplayName(originalEvent); + } } }, computed: { + inReplyToText() { + const relatesTo = this.event.getWireContent()['m.relates_to']; + if (relatesTo && relatesTo['m.in_reply_to']) + { + const content = this.event.getContent(); + + const lines = content.body.split('\n').reverse(); + while (lines.length && !lines[0].startsWith('> ')) lines.shift(); + // Reply fallback has a blank line after it, so remove it to prevent leading newline + if (lines[0] === '') lines.shift(); + const text = lines + .map((item) => { return item.replace(/^> (<.*> )?/g, ''); }) + .reverse() + .join('\n'); + if (text) { + return text; + } + + if (this.inReplyToEvent) { + var c = this.inReplyToEvent.getContent(); + return c.body; + } + + // We don't have the original text (at the moment at least) + return ""; + } + return null; + }, + + messageText() { + const relatesTo = this.event.getWireContent()['m.relates_to']; + if (relatesTo && relatesTo['m.in_reply_to']) + { + const content = this.event.getContent(); + + // Remove the new text and strip "> " from the old original text + const lines = content.body.split('\n'); + while (lines.length && lines[0].startsWith('> ')) lines.shift(); + // Reply fallback has a blank line after it, so remove it to prevent leading newline + if (lines[0] === '') lines.shift(); + return lines.join('\n'); + } + return this.event.getContent().body; + } }, methods: { showContextMenu() { diff --git a/src/plugins/utils.js b/src/plugins/utils.js index f12049b..e0afaa8 100644 --- a/src/plugins/utils.js +++ b/src/plugins/utils.js @@ -113,7 +113,7 @@ class Util { }); } - sendTextMessage(matrixClient, roomId, text, editedEvent) { + sendTextMessage(matrixClient, roomId, text, editedEvent, replyToEvent) { var content = ContentHelpers.makeTextMessage(text); if (editedEvent) { content['m.relates_to'] = { @@ -124,6 +124,18 @@ class Util { body: content.body, msgtype: content.msgtype } + } else if (replyToEvent) { + content['m.relates_to'] = { + 'm.in_reply_to': { + event_id: replyToEvent.getId() + } + } + + // Prefix the content with reply info (seems to be a legacy thing) + const prefix = replyToEvent.getContent().body.split('\n').map((item,index) => { + return "> " + (index == 0 ? ("<" + replyToEvent.getSender() + "> ") : "") + item; + }).join('\n'); + content.body = prefix + "\n\n" + content.body; } return this.sendMessage(matrixClient, roomId, "m.room.message", content); } diff --git a/src/services/matrix.service.js b/src/services/matrix.service.js index 78e6ec5..5d3cb4b 100644 --- a/src/services/matrix.service.js +++ b/src/services/matrix.service.js @@ -24,6 +24,7 @@ export default { matrixClient: null, matrixClientReady: false, rooms: [], + currentRoom: null, } }, mounted() { @@ -47,11 +48,15 @@ export default { currentRoomId() { return this.$store.state.currentRoomId; }, + }, - currentRoom() { - return this.getRoom(this.currentRoomId); - }, - + watch: { + currentRoomId: { + immediate: true, + handler(roomId) { + this.currentRoom = this.getRoom(roomId); + } + } }, methods: { @@ -120,6 +125,8 @@ export default { initClient() { this.reloadRooms(); this.matrixClientReady = true; + this.currentRoom = null; + this.currentRoom = this.getRoom(this.currentRoomId); this.matrixClient.emit('Matrix.initialized', this.matrixClient); }, @@ -259,6 +266,9 @@ export default { }, getRoom(roomId) { + if (!roomId) { + return null; + } var room = this.rooms.find(room => { if (roomId.startsWith("#")) { return room.getCanonicalAlias() == roomId;