Replies
This commit is contained in:
parent
ad0e5788aa
commit
3f1c58b743
7 changed files with 157 additions and 22 deletions
|
|
@ -150,7 +150,7 @@ $chat-text-size: 0.7pt;
|
||||||
margin-left: 30% !important;
|
margin-left: 30% !important;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
.bubble {
|
.bubble {
|
||||||
background-color: #88eec0;
|
background-color: #e5e5e5;
|
||||||
border-radius: 10px 10px 0 10px;
|
border-radius: 10px 10px 0 10px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
@ -243,12 +243,40 @@ $chat-text-size: 0.7pt;
|
||||||
font-size: 14 * $chat-text-size;
|
font-size: 14 * $chat-text-size;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
|
white-space: pre;
|
||||||
.edit-marker {
|
.edit-marker {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
color: #888888;
|
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 {
|
.time {
|
||||||
font-family: 'Titillium Web', sans-serif;
|
font-family: 'Titillium Web', sans-serif;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
v-on:close="showContextMenu = false"
|
v-on:close="showContextMenu = false"
|
||||||
v-if="selectedEvent && showContextMenu"
|
v-if="selectedEvent && showContextMenu"
|
||||||
v-on:addreaction="addReaction"
|
v-on:addreaction="addReaction"
|
||||||
|
v-on:addreply="addReply(selectedEvent)"
|
||||||
v-on:edit="edit(selectedEvent)"
|
v-on:edit="edit(selectedEvent)"
|
||||||
:event="selectedEvent"
|
:event="selectedEvent"
|
||||||
:incoming="selectedEvent.getSender() != $matrix.currentUserId"
|
:incoming="selectedEvent.getSender() != $matrix.currentUserId"
|
||||||
|
|
@ -55,16 +56,10 @@
|
||||||
'm.reaction'
|
'm.reaction'
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
:timelineSet="timelineWindow._timelineSet"
|
||||||
v-on:send-quick-reaction="sendQuickReaction"
|
v-on:send-quick-reaction="sendQuickReaction"
|
||||||
v-on:context-menu="showContextMenuForEvent($event)"
|
v-on:context-menu="showContextMenuForEvent($event)"
|
||||||
/>
|
/>
|
||||||
<!-- <message-operations
|
|
||||||
v-on:close="showContextMenu = false"
|
|
||||||
v-if="selectedEvent == event && showContextMenu"
|
|
||||||
v-on:addreaction="addReaction"
|
|
||||||
:event="event"
|
|
||||||
:incoming="event.getSender() != $matrix.currentUserId"
|
|
||||||
/> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -73,6 +68,8 @@
|
||||||
<!-- Input area -->
|
<!-- Input area -->
|
||||||
<v-container v-if="room" fluid class="input-area-outer">
|
<v-container v-if="room" fluid class="input-area-outer">
|
||||||
<v-row class="ma-0 pa-0">
|
<v-row class="ma-0 pa-0">
|
||||||
|
<div v-if="replyToEvent">REPLYING TO EVENT: {{ replyToEvent.getContent().body }}</div>
|
||||||
|
|
||||||
<!-- CONTACT IS TYPING -->
|
<!-- CONTACT IS TYPING -->
|
||||||
<div class="typing">
|
<div class="typing">
|
||||||
{{ typingMembersString }}
|
{{ typingMembersString }}
|
||||||
|
|
@ -112,8 +109,8 @@
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1" v-if="editedEvent">
|
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1" v-if="editedEvent || replyToEvent">
|
||||||
<v-btn fab small elevation="0" color="black" @click.stop="cancelEdit">
|
<v-btn fab small elevation="0" color="black" @click.stop="cancelEditReply">
|
||||||
<v-icon color="white">cancel</v-icon>
|
<v-icon color="white">cancel</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
@ -261,6 +258,7 @@ export default {
|
||||||
showEmojiPicker: false,
|
showEmojiPicker: false,
|
||||||
selectedEvent: null,
|
selectedEvent: null,
|
||||||
editedEvent: null,
|
editedEvent: null,
|
||||||
|
replyToEvent: null,
|
||||||
showContextMenu: false,
|
showContextMenu: false,
|
||||||
/**
|
/**
|
||||||
* Current chat container size. We need to keep track of this so that if and when
|
* 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;
|
return this.room.roomId;
|
||||||
},
|
},
|
||||||
attachButtonDisabled() {
|
attachButtonDisabled() {
|
||||||
return this.editedEvent || this.currentInput.length > 0;
|
return this.editedEvent != null || this.replyToEvent != null || this.currentInput.length > 0;
|
||||||
},
|
},
|
||||||
sendButtonDisabled() {
|
sendButtonDisabled() {
|
||||||
return this.currentInput.length == 0;
|
return this.currentInput.length == 0;
|
||||||
|
|
@ -328,6 +326,7 @@ export default {
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
room: {
|
room: {
|
||||||
|
immediate: true,
|
||||||
handler(room, ignoredOldVal) {
|
handler(room, ignoredOldVal) {
|
||||||
console.log("Chat: Current room changed");
|
console.log("Chat: Current room changed");
|
||||||
|
|
||||||
|
|
@ -351,8 +350,7 @@ export default {
|
||||||
this.paginateBackIfNeeded();
|
this.paginateBackIfNeeded();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
immediate: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -524,7 +522,8 @@ export default {
|
||||||
this.$matrix.matrixClient,
|
this.$matrix.matrixClient,
|
||||||
this.roomId,
|
this.roomId,
|
||||||
this.currentInput,
|
this.currentInput,
|
||||||
this.editedEvent
|
this.editedEvent,
|
||||||
|
this.replyToEvent
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log("Sent message");
|
console.log("Sent message");
|
||||||
|
|
@ -534,6 +533,7 @@ export default {
|
||||||
});
|
});
|
||||||
this.currentInput = "";
|
this.currentInput = "";
|
||||||
this.editedEvent = null; //TODO - Is this a good place to reset this?
|
this.editedEvent = null; //TODO - Is this a good place to reset this?
|
||||||
|
this.replyToEvent = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -678,15 +678,21 @@ export default {
|
||||||
this.showEmojiPicker = true;
|
this.showEmojiPicker = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addReply(event) {
|
||||||
|
this.replyToEvent = event;
|
||||||
|
this.$refs.messageInput.focus();
|
||||||
|
},
|
||||||
|
|
||||||
edit(event) {
|
edit(event) {
|
||||||
this.editedEvent = event;
|
this.editedEvent = event;
|
||||||
this.currentInput = event.getContent().body;
|
this.currentInput = event.getContent().body;
|
||||||
this.$refs.messageInput.focus();
|
this.$refs.messageInput.focus();
|
||||||
},
|
},
|
||||||
|
|
||||||
cancelEdit() {
|
cancelEditReply() {
|
||||||
this.currentInput = "";
|
this.currentInput = "";
|
||||||
this.editedEvent = null;
|
this.editedEvent = null;
|
||||||
|
this.replyToEvent = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
emojiSelected(e) {
|
emojiSelected(e) {
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,18 @@
|
||||||
</v-avatar>
|
</v-avatar>
|
||||||
|
|
||||||
<div class="bubble">
|
<div class="bubble">
|
||||||
|
<div class="original-message" v-if="inReplyToText">
|
||||||
|
<div class="original-message-sender">{{ inReplyToSender || 'Someone' }} said:</div>
|
||||||
|
<div class="original-message-text">{{ inReplyToText }}</div>
|
||||||
|
</div>
|
||||||
<div class="message">
|
<div class="message">
|
||||||
{{ event.getContent().body }}
|
{{ messageText }}
|
||||||
<span class="edit-marker" v-if="event.replacingEventId()"
|
<span class="edit-marker" v-if="event.replacingEventId()"
|
||||||
>(edited)</span
|
>(edited)</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<QuickReactions :event="event" :reactions="reactions" />
|
<QuickReactions :event="event" :reactions="reactions" />
|
||||||
|
<!-- <div>{{ JSON.stringify(event) }}</div> -->
|
||||||
</div>
|
</div>
|
||||||
<v-btn icon class="op-button" @click.stop="showContextMenu"
|
<v-btn icon class="op-button" @click.stop="showContextMenu"
|
||||||
><v-icon>more_vert</v-icon></v-btn
|
><v-icon>more_vert</v-icon></v-btn
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="messageOut">
|
<div class="messageOut">
|
||||||
<div class="bubble">
|
<div class="bubble">
|
||||||
|
<div class="original-message" v-if="inReplyToText">
|
||||||
|
<div class="original-message-sender">{{ inReplyToSender || 'Someone' }} said:</div>
|
||||||
|
<div class="original-message-text">{{ inReplyToText }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="message">
|
<div class="message">
|
||||||
{{ event.getContent().body }}
|
{{ messageText }}
|
||||||
<span class="edit-marker" v-if="event.replacingEventId()"
|
<span class="edit-marker" v-if="event.replacingEventId()"
|
||||||
>(edited)</span
|
>(edited)</span
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,78 @@ export default {
|
||||||
default: function () {
|
default: function () {
|
||||||
return null
|
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: {
|
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 "<original text>";
|
||||||
|
}
|
||||||
|
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: {
|
methods: {
|
||||||
showContextMenu() {
|
showContextMenu() {
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ class Util {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTextMessage(matrixClient, roomId, text, editedEvent) {
|
sendTextMessage(matrixClient, roomId, text, editedEvent, replyToEvent) {
|
||||||
var content = ContentHelpers.makeTextMessage(text);
|
var content = ContentHelpers.makeTextMessage(text);
|
||||||
if (editedEvent) {
|
if (editedEvent) {
|
||||||
content['m.relates_to'] = {
|
content['m.relates_to'] = {
|
||||||
|
|
@ -124,6 +124,18 @@ class Util {
|
||||||
body: content.body,
|
body: content.body,
|
||||||
msgtype: content.msgtype
|
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);
|
return this.sendMessage(matrixClient, roomId, "m.room.message", content);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ export default {
|
||||||
matrixClient: null,
|
matrixClient: null,
|
||||||
matrixClientReady: false,
|
matrixClientReady: false,
|
||||||
rooms: [],
|
rooms: [],
|
||||||
|
currentRoom: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
@ -47,11 +48,15 @@ export default {
|
||||||
currentRoomId() {
|
currentRoomId() {
|
||||||
return this.$store.state.currentRoomId;
|
return this.$store.state.currentRoomId;
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
currentRoom() {
|
watch: {
|
||||||
return this.getRoom(this.currentRoomId);
|
currentRoomId: {
|
||||||
},
|
immediate: true,
|
||||||
|
handler(roomId) {
|
||||||
|
this.currentRoom = this.getRoom(roomId);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -120,6 +125,8 @@ export default {
|
||||||
initClient() {
|
initClient() {
|
||||||
this.reloadRooms();
|
this.reloadRooms();
|
||||||
this.matrixClientReady = true;
|
this.matrixClientReady = true;
|
||||||
|
this.currentRoom = null;
|
||||||
|
this.currentRoom = this.getRoom(this.currentRoomId);
|
||||||
this.matrixClient.emit('Matrix.initialized', this.matrixClient);
|
this.matrixClient.emit('Matrix.initialized', this.matrixClient);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -259,6 +266,9 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
getRoom(roomId) {
|
getRoom(roomId) {
|
||||||
|
if (!roomId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
var room = this.rooms.find(room => {
|
var room = this.rooms.find(room => {
|
||||||
if (roomId.startsWith("#")) {
|
if (roomId.startsWith("#")) {
|
||||||
return room.getCanonicalAlias() == roomId;
|
return room.getCanonicalAlias() == roomId;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue