mirror of
https://github.com/Cronocide/WebMessage.git
synced 2025-01-22 11:18:25 +00:00
say hello to reactions 🎉
and bug fixes...
This commit is contained in:
parent
3e11254592
commit
2779e8053a
@ -18,6 +18,9 @@
|
||||
},
|
||||
"main": "background.js",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.34",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.2",
|
||||
"@fortawesome/vue-fontawesome": "^2.0.2",
|
||||
"@kevinfaguiar/vue-twemoji-picker": "^5.7.4",
|
||||
"@trevoreyre/autocomplete-vue": "^2.2.0",
|
||||
"auto-launch": "^5.0.5",
|
||||
|
53
src/App.vue
53
src/App.vue
@ -304,13 +304,12 @@ export default {
|
||||
this.status = 2
|
||||
},
|
||||
fetchMessages (data) {
|
||||
if (data && data[0] && !this.$store.state.messagesCache[data[0].chatId]) {
|
||||
this.$store.commit('addMessages', { id: data[0].chatId, data: data })
|
||||
if (data && data[0] && !this.$store.state.messagesCache[data[0].personId]) {
|
||||
this.$store.commit('addMessages', { id: data[0].personId, data: data })
|
||||
}
|
||||
},
|
||||
newMessage (data) {
|
||||
var chatData = data.chat[0]
|
||||
console.log(data)
|
||||
|
||||
if (chatData && chatData.personId) {
|
||||
var chatIndex = this.chats.findIndex(obj => obj.personId == chatData.personId)
|
||||
@ -325,9 +324,13 @@ export default {
|
||||
var messageData = data.message[0]
|
||||
|
||||
if (messageData) {
|
||||
if (this.$store.state.messagesCache[messageData.chatId]) {
|
||||
if (this.$store.state.messagesCache[messageData.chatId].findIndex(obj => obj.id == messageData.id) != -1) return
|
||||
this.$store.state.messagesCache[messageData.chatId].unshift(messageData)
|
||||
if (this.$store.state.messagesCache[messageData.personId]) {
|
||||
let oldMsgIndex = this.$store.state.messagesCache[messageData.personId].findIndex(obj => obj.guid == messageData.guid)
|
||||
if (oldMsgIndex != -1) {
|
||||
this.$store.state.messagesCache[messageData.personId][oldMsgIndex] = messageData
|
||||
return
|
||||
}
|
||||
this.$store.state.messagesCache[messageData.personId].unshift(messageData)
|
||||
}
|
||||
|
||||
if (messageData.sender != 1 && remote.Notification.isSupported()) {
|
||||
@ -346,7 +349,7 @@ export default {
|
||||
notif.on('click', (event, arg) => {
|
||||
if (chatData && chatData.id) {
|
||||
ipcRenderer.send('show_win')
|
||||
this.$router.push('/message/'+messageData.chatId)
|
||||
this.$router.push('/message/'+messageData.personId)
|
||||
}
|
||||
})
|
||||
notif.show()
|
||||
@ -355,6 +358,26 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
newReaction (data) {
|
||||
let reactions = data.reactions
|
||||
if (reactions && reactions.length > 0 && this.$store.state.messagesCache[reactions[0].personId]) {
|
||||
let msgIndex = this.$store.state.messagesCache[reactions[0].personId].findIndex(obj => obj.guid == reactions[0].forGUID)
|
||||
if (msgIndex > -1) {
|
||||
this.$store.state.messagesCache[reactions[0].personId][msgIndex].reactions = reactions
|
||||
}
|
||||
}
|
||||
|
||||
let chatData = data.chat[0]
|
||||
|
||||
if (chatData && chatData.personId) {
|
||||
let chatIndex = this.chats.findIndex(obj => obj.personId == chatData.personId)
|
||||
|
||||
if (chatIndex > -1) {
|
||||
this.chats.splice(chatIndex, 1)
|
||||
}
|
||||
this.chats.unshift(chatData)
|
||||
}
|
||||
},
|
||||
setAsRead (data) {
|
||||
if (this.$store.state.messagesCache[data.chatId]) {
|
||||
let messageIndex = this.$store.state.messagesCache[data.chatId].findIndex(obj => obj.guid == data.guid)
|
||||
@ -364,6 +387,21 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
removeChat (data) {
|
||||
if (data.chatId) {
|
||||
let chatId = data.chatId
|
||||
var chatIndex = this.chats.findIndex(obj => obj.address == chatId)
|
||||
|
||||
if (chatIndex > -1) {
|
||||
let chat = this.chats[chatIndex]
|
||||
if (this.$route.path == '/message/'+chat.personId) {
|
||||
this.$router.push('/')
|
||||
}
|
||||
this.$store.state.messagesCache[chat.personId] = null
|
||||
this.chats.splice(chatIndex, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
onopen () {
|
||||
this.offset = 0
|
||||
this.requestChats()
|
||||
@ -378,7 +416,6 @@ export default {
|
||||
this.chats = []
|
||||
},
|
||||
onclose (e) {
|
||||
console.log(e)
|
||||
this.loading = false
|
||||
if (this.$socket && this.$socket.readyState == 1) return
|
||||
this.status = 0
|
||||
|
@ -18,16 +18,18 @@
|
||||
<img src="@/assets/loading.webp" style="height:18px;" />
|
||||
</div>
|
||||
<template v-else-if="$route.params.id != 'new' || this.receiver != ''">
|
||||
<simplebar class="messages" ref="messages" data-simplebar-auto-hide="false">
|
||||
<div v-for="(msg, i) in sortedMessages" :key="msg.id">
|
||||
<reactionMenu :target="reactingMessage" :reactions="reactingMessageReactions" :guid="reactingMessageGUID" @close="closeReactionMenu" @sendReaction="sendReaction"></reactionMenu>
|
||||
<simplebar class="messages" ref="messages">
|
||||
<div v-for="(msg, i) in sortedMessages" :key="'msg'+msg.id">
|
||||
<div class="timegroup" v-html="dateGroup(i-1, i)" v-if="dateGroup(i-1, i) != ''"></div>
|
||||
|
||||
<div :ref="'msg'+msg.id" :class="(msg.sender == 1 ? 'send ' : 'receive ') + msg.type" class="messageGroup">
|
||||
<div :ref="'msg'+msg.id" :class="(msg.sender == 1 ? 'send ' : 'receive ') + msg.type + (sortedMessages.length-1 == i ? ' last' : '')" class="messageGroup">
|
||||
<div v-if="msg.group && msg.sender != 1" class="senderName" v-html="$options.filters.twemoji(msg.author)"></div>
|
||||
|
||||
<template v-for="(text, i) in msg.texts">
|
||||
<div :key="'wrapper'+i" v-longclick="openReactionMenu" style="display: contents;">
|
||||
<div v-for="(attachment, index) in text.attachments" :key="`${i}-${index}`" class="attachment">
|
||||
<template v-for="(text, ii) in msg.texts">
|
||||
<div :key="'wrapper'+ii" :ref="'msg'+msg.id+'-text'+ii" :id="'msg'+msg.id+'-text'+ii" @mousedown.left="startInterval(msg.id, ii, text.guid, text.reactions)" @mouseup.left="stopInterval" @mouseleave="stopInterval" class="textWrapper">
|
||||
<reactions :click="() => openReactionMenu(msg.id, ii, text.guid, text.reactions)" :target="'#msg'+msg.id+'-text'+ii" :reactions="text.reactions" v-if="text.attachments && text.attachments.length > 0 && $options.filters.twemoji(text.text) == ''" :targetFromMe="msg.sender == 1"></reactions>
|
||||
<div v-for="(attachment, index) in text.attachments" :key="`${ii}-${index}`" class="attachment">
|
||||
<template v-if="attachment[0] != '' && !attachment[0].includes('.pluginPayloadAttachment')">
|
||||
<expandable-image v-if="isImage(attachment[1])" :loadedData="scrollToBottom" :path="attachment[0]" :type="attachment[1]" />
|
||||
<video-player v-else-if="isVideo(attachment[1])" :loadedData="scrollToBottom" :path="attachment[0]" :type="attachment[1]" />
|
||||
@ -35,16 +37,17 @@
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<reactions :click="() => openReactionMenu(msg.id, ii, text.guid, text.reactions)" :target="'#msg'+msg.id+'-text'+ii" :reactions="text.reactions" v-if="$options.filters.twemoji(text.text) != ''" :targetFromMe="msg.sender == 1"></reactions>
|
||||
<div
|
||||
class="message"
|
||||
:key="i"
|
||||
:class="(msg.texts.length-1 == i ? 'last ' : '') + (isEmojis(text.text) ? 'jumbo' : '')"
|
||||
:key="'msg'+msg.id+'-text'+ii"
|
||||
:class="(msg.texts.length-1 == ii ? 'last ' : '') + (isEmojis(text.text) ? 'jumbo' : '')"
|
||||
:style="msg.sender == 1 && text.showStamp && (text.read > 0 || text.delivered > 0) ? 'margin-bottom: 0px;' : ''"
|
||||
v-if="$options.filters.twemoji(text.text) != ''">
|
||||
<span style="white-space: pre-wrap;" v-html="$options.filters.twemoji(text.text)" v-linkified></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="receipt" :key="i+'receipt'" v-if="msg.sender == 1 && text.showStamp && (text.read > 0 || text.delivered > 0)">
|
||||
<div class="receipt" :key="'msg'+msg.id+'-text'+ii+'-receipt'" v-if="msg.sender == 1 && text.showStamp && (text.read > 0 || text.delivered > 0)">
|
||||
<span class="type">{{ text.read > 0 ? "Read" : "Delivered" }}</span> {{ humanReadableTimestamp(text.read > 0 ? text.read : text.delivered) }}
|
||||
</div>
|
||||
</template>
|
||||
@ -93,6 +96,8 @@ import VideoPlayer from './VideoPlayer'
|
||||
import ExpandableImage from './ExpandableImage'
|
||||
import DownloadAttachment from './DownloadAttachment'
|
||||
import UploadButton from './UploadButton'
|
||||
import ReactionMenu from './ReactionMenu'
|
||||
import Reactions from './Reactions'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
@ -104,7 +109,9 @@ export default {
|
||||
VideoPlayer,
|
||||
ExpandableImage,
|
||||
DownloadAttachment,
|
||||
UploadButton
|
||||
UploadButton,
|
||||
ReactionMenu,
|
||||
Reactions
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
@ -117,7 +124,12 @@ export default {
|
||||
ignoreNextScroll: false,
|
||||
loading: false,
|
||||
canSend: true,
|
||||
hasAttachments: false
|
||||
hasAttachments: false,
|
||||
interval: null,
|
||||
timeHolding: 0,
|
||||
reactingMessage: null,
|
||||
reactingMessageGUID: null,
|
||||
reactingMessageReactions: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -140,13 +152,13 @@ export default {
|
||||
const groupDates = (date1, date2) => (date2 - date1 < 6000)
|
||||
const groupAuthor = (author1, author2) => (author1 == author2)
|
||||
|
||||
const groupedMessages = messages.reduce((r, { text, dateRead, dateDelivered, guid, ...rest }, i, arr) => {
|
||||
const groupedMessages = messages.reduce((r, { text, dateRead, dateDelivered, guid, reactions, ...rest }, i, arr) => {
|
||||
const prev = arr[i +-1]
|
||||
|
||||
if (prev && groupAuthor(rest.author, prev.author) && groupAuthor(rest.sender, prev.sender) && groupDates(rest.date, prev.date))
|
||||
r[r.length - 1].texts.unshift({ text: text.trim(), date: rest.date, attachments: rest.attachments, read: dateRead, delivered: dateDelivered, guid: guid, showStamp: rest.sender == 1 && !lastSentMessageFound })
|
||||
r[r.length - 1].texts.unshift({ text: text, date: rest.date, attachments: rest.attachments, read: dateRead, delivered: dateDelivered, guid: guid, reactions: reactions, showStamp: rest.sender == 1 && !lastSentMessageFound })
|
||||
else
|
||||
r.push({ ...rest, texts: [{ text: text.trim(), date: rest.date, attachments: rest.attachments, read: dateRead, delivered: dateDelivered, guid: guid, showStamp: rest.sender == 1 && !lastSentMessageFound }] })
|
||||
r.push({ ...rest, texts: [{ text: text, date: rest.date, attachments: rest.attachments, read: dateRead, delivered: dateDelivered, guid: guid, reactions: reactions, showStamp: rest.sender == 1 && !lastSentMessageFound }] })
|
||||
|
||||
if (rest.sender == 1 && !lastSentMessageFound) lastSentMessageFound = true
|
||||
|
||||
@ -165,6 +177,9 @@ export default {
|
||||
this.ignoreNextScroll = false
|
||||
this.canSend = true
|
||||
this.hasAttachments = false
|
||||
this.reactingMessage = null
|
||||
this.reactingMessageGUID = null
|
||||
this.reactingMessageReactions = null
|
||||
if (this.$refs.uploadButton) {
|
||||
this.$refs.uploadButton.clear()
|
||||
}
|
||||
@ -183,6 +198,28 @@ export default {
|
||||
isVideo(type) {
|
||||
return type.includes('video/')
|
||||
},
|
||||
startInterval (msgId, textId, guid, reactions) {
|
||||
if (!this.interval) {
|
||||
this.interval = setInterval(() => {
|
||||
this.timeHolding++
|
||||
if (this.timeHolding > 7) { //> 0.7 seconds, will trigger at 0.8 seconds
|
||||
this.openReactionMenu(msgId, textId, guid, reactions)
|
||||
clearInterval(this.interval)
|
||||
this.interval = false
|
||||
this.timeHolding = 0
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
},
|
||||
stopInterval () {
|
||||
clearInterval(this.interval)
|
||||
this.interval = false
|
||||
this.timeHolding = 0
|
||||
},
|
||||
closeReactionMenu () {
|
||||
this.reactingMessage = null
|
||||
this.reactingMessageGUID = null
|
||||
},
|
||||
dateGroup(prev, current) {
|
||||
let prevstamp = this.sortedMessages[prev] ? this.sortedMessages[prev].date : 0
|
||||
let currstamp = this.sortedMessages[current].date
|
||||
@ -316,20 +353,23 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
openReactionMenu (e) {
|
||||
console.log(e)
|
||||
openReactionMenu (msgId, textId, guid, reactions) {
|
||||
let el = $(this.$refs['msg'+msgId+'-text'+textId])
|
||||
this.reactingMessageReactions = reactions
|
||||
this.reactingMessageGUID = guid
|
||||
this.reactingMessage = el
|
||||
},
|
||||
sendReaction (text) {
|
||||
sendReaction (reactionId, guid) {
|
||||
if (!this.messages[0]) return
|
||||
this.sendSocket({ action: 'sendReaction', data: {
|
||||
chatId: this.messages[0].chatId,
|
||||
guid: text.guid
|
||||
guid: guid,
|
||||
reactionId: reactionId
|
||||
}})
|
||||
},
|
||||
sendText () {
|
||||
let messageText = this.messageText[this.$route.params.id]
|
||||
if (!messageText) messageText = ''
|
||||
messageText = messageText.trim()
|
||||
if (messageText == '' && (!this.$refs.uploadButton.attachments || this.$refs.uploadButton.attachments.length == 0)) return
|
||||
if (!this.canSend) return
|
||||
this.canSend = false
|
||||
@ -389,8 +429,10 @@ export default {
|
||||
autoCompleteInput (input) {
|
||||
if (input && input.name) {
|
||||
this.receiver = input.phone
|
||||
this.hookPasteAndDrop()
|
||||
} else if (/^\+\d{11,16}/gi.test(input)) {
|
||||
this.receiver = input
|
||||
this.hookPasteAndDrop()
|
||||
}
|
||||
},
|
||||
previewFiles () {
|
||||
@ -434,9 +476,13 @@ export default {
|
||||
this.offset += this.limit
|
||||
this.loading = false
|
||||
|
||||
this.hookPasteAndDrop()
|
||||
},
|
||||
hookPasteAndDrop () {
|
||||
this.$nextTick(() => {
|
||||
var el = document.getElementById('twemoji-textarea')
|
||||
if (el) {
|
||||
setTimeout(() => { el.focus() }, 10)
|
||||
el.addEventListener('paste', e => {
|
||||
if (e.clipboardData.files && e.clipboardData.files.length > 0) {
|
||||
let text = e.clipboardData.getData('Text')
|
||||
@ -481,7 +527,6 @@ export default {
|
||||
|
||||
if (this.offset == 0 && !this.$store.state.messagesCache[this.$route.params.id]) {
|
||||
this.messages = data
|
||||
this.$store.commit('addMessages', { id: this.$route.params.id, data: data })
|
||||
}
|
||||
else this.messages.push(...data)
|
||||
|
||||
@ -497,6 +542,7 @@ export default {
|
||||
if (this.lastHeight == null) {
|
||||
this.$nextTick(() => { this.scrollToBottom() })
|
||||
}
|
||||
this.messages.__ob__.dep.notify()
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -516,7 +562,11 @@ export default {
|
||||
console.log("Received a message, but content was empty.")
|
||||
} else {
|
||||
if (this.messages && this.messages.length > 0 && message[0]['personId'] == this.$route.params.id) {
|
||||
if (this.messages.findIndex(obj => obj.id == message[0].id) != -1) return
|
||||
let oldMsgIndex = this.messages.findIndex(obj => obj.id == message[0].id)
|
||||
if (oldMsgIndex != -1) {
|
||||
this.messages[oldMsgIndex] = message[0]
|
||||
return
|
||||
}
|
||||
|
||||
this.messages.unshift(message[0])
|
||||
|
||||
@ -531,6 +581,22 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
newReaction (data) {
|
||||
let reactions = data.reactions
|
||||
if (reactions && reactions.length > 0) {
|
||||
let msgIndex = this.messages.findIndex(obj => obj.guid == reactions[0].forGUID)
|
||||
if (msgIndex > -1) {
|
||||
this.messages[msgIndex].reactions = reactions
|
||||
this.messages.__ob__.dep.notify()
|
||||
let intervalTime = 0
|
||||
let interval = setInterval(() => {
|
||||
if (this.lastHeight == null) this.scrollToBottom()
|
||||
if (intervalTime >= 300) clearInterval(interval)
|
||||
intervalTime += 1
|
||||
}, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
onerror () {
|
||||
this.loading = false
|
||||
this.messages = []
|
||||
@ -900,27 +966,32 @@ export default {
|
||||
}
|
||||
|
||||
.messageGroup {
|
||||
// margin-top: 30px;
|
||||
margin-bottom: 10px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.last {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.attachment {
|
||||
max-width: 75%;
|
||||
max-width: 60%;
|
||||
max-height: 60%;
|
||||
border-radius: 18px;
|
||||
height: fit-content;
|
||||
margin-bottom: -2px;
|
||||
|
||||
img {
|
||||
max-width: 280px;
|
||||
max-height: 700px;
|
||||
// max-width: 280px;
|
||||
// max-height: 700px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
video {
|
||||
max-width: 420px;
|
||||
max-height: 700px;
|
||||
// max-width: 420px;
|
||||
// max-height: 700px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
@ -948,6 +1019,13 @@ export default {
|
||||
.receive {
|
||||
align-items: flex-start;
|
||||
|
||||
.textWrapper {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.message {
|
||||
color: white;
|
||||
margin-right: 25%;
|
||||
@ -961,8 +1039,6 @@ export default {
|
||||
}
|
||||
|
||||
&.last {
|
||||
margin-bottom: 10px;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
@ -1005,6 +1081,13 @@ export default {
|
||||
.send {
|
||||
align-items: flex-end;
|
||||
|
||||
.textWrapper {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.message {
|
||||
color: white;
|
||||
margin-left: 25%;
|
||||
@ -1018,8 +1101,6 @@ export default {
|
||||
}
|
||||
|
||||
&.last {
|
||||
margin-bottom: 10px;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
195
src/components/ReactionMenu.vue
Normal file
195
src/components/ReactionMenu.vue
Normal file
@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<transition name="fade" mode="out-in">
|
||||
<div id="reactionMenu" v-if="target">
|
||||
<div class="backdrop" @click="closeMenu">
|
||||
</div>
|
||||
<div class="menu" :style="{ top: position.top, left: position.left, right: position.right, bottom: position.bottom }" :class=" direction ">
|
||||
<font-awesome-icon @click="sendReaction(icon)" v-for="icon in icons" :key="icon.name" :icon="icon.name" :class="{ active: activeReactions.includes(icon.id) }" size="3x" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "ReactionMenu",
|
||||
data() {
|
||||
return {
|
||||
icons: [
|
||||
{ name: 'heart', id: 2000 },
|
||||
{ name: 'thumbs-up', id: 2001 },
|
||||
{ name: 'thumbs-down', id: 2002 },
|
||||
{ name: 'laugh-squint', id: 2003 },
|
||||
{ name: 'exclamation', id: 2004 },
|
||||
{ name: 'question', id: 2005 },
|
||||
],
|
||||
position: { top: '50px', left: '20px' },
|
||||
direction: 'right',
|
||||
clone: null,
|
||||
activeReactions: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
target: { type: Object },
|
||||
guid: { type: String },
|
||||
reactions: { type: Array }
|
||||
},
|
||||
watch: {
|
||||
target(newTarget) {
|
||||
if (newTarget != null) {
|
||||
let target = newTarget.children().last()
|
||||
let parent = newTarget.parent()
|
||||
let p = target.offset()
|
||||
let w = target.width()
|
||||
let h = target.height()
|
||||
let clone = target.clone()
|
||||
|
||||
clone.css({
|
||||
position: 'fixed',
|
||||
top: p.top+'px',
|
||||
left: p.left+'px',
|
||||
width: w+'px',
|
||||
height: h+'px',
|
||||
margin: '0'
|
||||
})
|
||||
|
||||
let msgWrapper = $('<div class="messages" style="display:contents;z-index:-1;"></div>')
|
||||
$('<div></div>').attr('class', parent.attr('class')).appendTo(msgWrapper)
|
||||
clone.appendTo(msgWrapper.children().first())
|
||||
this.$nextTick(() => {
|
||||
msgWrapper.appendTo('#reactionMenu')
|
||||
})
|
||||
|
||||
this.clone = msgWrapper
|
||||
this.direction = parent.hasClass('send') ? 'right' : 'left'
|
||||
|
||||
this.position = { }
|
||||
this.position.top = (p.top - 45) + 'px'
|
||||
if (this.direction == 'right') {
|
||||
this.position.right = 6 + 'px'
|
||||
} else {
|
||||
this.position.left = (p.left - 15) + 'px'
|
||||
}
|
||||
|
||||
let activeReactionsList = this.reactions.filter(reaction => reaction.reactionType >= 2000 && reaction.reactionType < 3000 && reaction.sender == 1)
|
||||
this.activeReactions = []
|
||||
activeReactionsList.forEach((reaction) => {
|
||||
this.activeReactions.push(reaction.reactionType)
|
||||
})
|
||||
} else {
|
||||
this.activeReactions = []
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
sendReaction (icon) {
|
||||
let id = icon.id
|
||||
if (this.activeReactions.includes(id)) id += 1000 //Remove reaction
|
||||
this.$emit('sendReaction', id, this.guid)
|
||||
this.closeMenu()
|
||||
},
|
||||
closeMenu () {
|
||||
setTimeout(() => {
|
||||
if (this.clone) {
|
||||
this.clone.remove()
|
||||
this.clone = null
|
||||
this.activeReactions = []
|
||||
}
|
||||
}, 500)
|
||||
|
||||
this.$emit('close')
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#reactionMenu {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
|
||||
.message.last:after {
|
||||
background: #090909;
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.6);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.menu {
|
||||
position: fixed;
|
||||
background-color: rgb(70,70,70);
|
||||
padding: 5px 10px;
|
||||
border-radius: 20px;
|
||||
color: rgb(140,140,140);
|
||||
z-index: 1;
|
||||
|
||||
svg {
|
||||
padding: 8px;
|
||||
border-radius: 50%;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #2484FF;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -5px;
|
||||
right: 20px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: rgb(70,70,70);
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -12px;
|
||||
right: 19px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: rgb(70,70,70);
|
||||
}
|
||||
|
||||
&.left {
|
||||
&:after {
|
||||
left: 20px;
|
||||
right: unset;
|
||||
}
|
||||
|
||||
&:before {
|
||||
left: 19px;
|
||||
right: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
104
src/components/Reactions.vue
Normal file
104
src/components/Reactions.vue
Normal file
@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<transition name="slide-fade" mode="out-in">
|
||||
<div class="reactions" v-if="reactionList.length > 0" :class="{ left: !targetFromMe }">
|
||||
<div v-for="(reaction, i) in reactionList" @click="click" :key="reaction.guid" class="bubble" :class="{ isMe: reaction.sender == 1 }" :style="{ zIndex: 4-i, top: position.top+'px', left: (position.left+(targetFromMe ? (-i*3) : (i*3)))+'px' }">
|
||||
<font-awesome-icon v-if="reaction.icon" :icon="reaction.icon.name" size="lg" :style="reaction.icon.style" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Reactions",
|
||||
props: {
|
||||
reactions: { type: Array },
|
||||
targetFromMe: { type: Boolean },
|
||||
target: { type: String },
|
||||
click: { type: Function }
|
||||
},
|
||||
computed: {
|
||||
reactionList() {
|
||||
let reactions = this.reactions.filter(reaction => reaction.reactionType >= 2000 && reaction.reactionType < 3000)
|
||||
reactions.forEach((reaction) => {
|
||||
let reactionId = reaction.reactionType
|
||||
let icon = this.icons.find(icon => icon.id == reactionId)
|
||||
reaction.icon = icon
|
||||
})
|
||||
|
||||
return reactions.sort((a, b) => b.sender - a.sender).slice(0, 4)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
icons: [
|
||||
{ name: 'heart', id: 2000, style: { color: '#FA5E96' } },
|
||||
{ name: 'thumbs-up', id: 2001 },
|
||||
{ name: 'thumbs-down', id: 2002 },
|
||||
{ name: 'laugh-squint', id: 2003 },
|
||||
{ name: 'exclamation', id: 2004 },
|
||||
{ name: 'question', id: 2005 },
|
||||
],
|
||||
position: { top: -16, left: 0 }
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.adjustPostion()
|
||||
},
|
||||
beforeMount () {
|
||||
this.adjustPostion()
|
||||
},
|
||||
methods: {
|
||||
adjustPostion () {
|
||||
let target = $(this.target).children().last()
|
||||
|
||||
this.position.left = (target.width() + 10)
|
||||
if (this.targetFromMe) {
|
||||
this.position.left = (-target.width() - 36)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.reactions {
|
||||
position: relative;
|
||||
margin-top: 18px;
|
||||
|
||||
.bubble {
|
||||
background-color: #3A3A3C;
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
border: solid 1px #1D1D1D;
|
||||
border-radius: 50%;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
line-height: 12px;
|
||||
transition: 0.3s;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.isMe {
|
||||
background-color: #2284FF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.slide-fade-enter-active {
|
||||
transition: all .3s ease;
|
||||
}
|
||||
.slide-fade-leave-active {
|
||||
transition: all .3s ease;
|
||||
}
|
||||
.slide-fade-enter, .slide-fade-leave-to {
|
||||
transform: scaleY(0);
|
||||
margin-top: 0px;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
12
src/main.js
12
src/main.js
@ -13,6 +13,9 @@ import $ from 'jquery'
|
||||
import Popover from 'vue-js-popover'
|
||||
import VueConfirmDialog from 'vue-confirm-dialog'
|
||||
import { longClickDirective } from 'vue-long-click'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faHeart, faThumbsUp, faThumbsDown, faLaughSquint, faExclamation, faQuestion } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||
window.$ = $
|
||||
|
||||
const https = require('https')
|
||||
@ -29,6 +32,15 @@ Vue.use(VueNativeSock, 'ws://', {
|
||||
Vue.use(VueFeather)
|
||||
Vue.use(Popover, { tooltip: true })
|
||||
Vue.use(VueConfirmDialog)
|
||||
|
||||
library.add(faHeart)
|
||||
library.add(faThumbsUp)
|
||||
library.add(faThumbsDown)
|
||||
library.add(faLaughSquint)
|
||||
library.add(faExclamation)
|
||||
library.add(faQuestion)
|
||||
|
||||
Vue.component('font-awesome-icon', FontAwesomeIcon)
|
||||
Vue.component('vue-confirm-dialog', VueConfirmDialog.default)
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
24
yarn.lock
24
yarn.lock
@ -877,6 +877,30 @@
|
||||
global-agent "^2.0.2"
|
||||
global-tunnel-ng "^2.7.1"
|
||||
|
||||
"@fortawesome/fontawesome-common-types@^0.2.34":
|
||||
version "0.2.34"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.34.tgz#0a8c348bb23b7b760030f5b1d912e582be4ec915"
|
||||
integrity sha512-XcIn3iYbTEzGIxD0/dY5+4f019jIcEIWBiHc3KrmK/ROahwxmZ/s+tdj97p/5K0klz4zZUiMfUlYP0ajhSJjmA==
|
||||
|
||||
"@fortawesome/fontawesome-svg-core@^1.2.34":
|
||||
version "1.2.34"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.34.tgz#1d1a7c92537cbc2b8a83eef6b6d824b4b5b46b26"
|
||||
integrity sha512-0KNN0nc5eIzaJxlv43QcDmTkDY1CqeN6J7OCGSs+fwGPdtv0yOQqRjieopBCmw+yd7uD3N2HeNL3Zm5isDleLg==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "^0.2.34"
|
||||
|
||||
"@fortawesome/free-solid-svg-icons@^5.15.2":
|
||||
version "5.15.2"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.2.tgz#25bb035de57cf85aee8072965732368ccc8e8943"
|
||||
integrity sha512-ZfCU+QjaFsdNZmOGmfqEWhzI3JOe37x5dF4kz9GeXvKn/sTxhqMtZ7mh3lBf76SvcYY5/GKFuyG7p1r4iWMQqw==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "^0.2.34"
|
||||
|
||||
"@fortawesome/vue-fontawesome@^2.0.2":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/vue-fontawesome/-/vue-fontawesome-2.0.2.tgz#5b86cd2fb7b4c17e5dede722c1c2855c97eceaea"
|
||||
integrity sha512-ecpKSBUWXsxRJVi/dbOds4tkKwEcBQ1JSDZFzE2jTFpF8xIh3OgTX8POIor6bOltjibr3cdEyvnDjecMwUmxhQ==
|
||||
|
||||
"@hapi/address@2.x.x":
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
|
||||
|
Loading…
Reference in New Issue
Block a user