Skip to content

Commit 90d26df

Browse files
committed
(feat) add emoji reactions
1 parent 8728e70 commit 90d26df

File tree

5 files changed

+179
-18
lines changed

5 files changed

+179
-18
lines changed

demo/src/ChatContainer.vue

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
@addRoom="addRoom"
6262
@menuActionHandler="menuActionHandler"
6363
@messageActionHandler="messageActionHandler"
64+
@sendMessageReaction="sendMessageReaction"
6465
/>
6566
</div>
6667
</template>
@@ -452,6 +453,26 @@ export default {
452453
// do something
453454
},
454455
456+
async sendMessageReaction({ reaction, messageId, roomId }) {
457+
await this.messagesRef(roomId)
458+
.doc(messageId)
459+
.update({
460+
[`reactions.${reaction.name}`]: firebase.firestore.FieldValue.arrayUnion(
461+
this.currentUserId
462+
)
463+
})
464+
},
465+
466+
async removeMessageReaction({ reaction, messageId, roomId }) {
467+
await this.messagesRef(roomId)
468+
.doc(messageId)
469+
.update({
470+
[`reactions.${reaction.name}`]: firebase.firestore.FieldValue.arrayRemove(
471+
this.currentUserId
472+
)
473+
})
474+
},
475+
455476
addRoom() {
456477
this.resetForms()
457478
this.addNewRoom = true

src/ChatWindow/ChatMessage.vue

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,18 @@
122122
</div>
123123
</transition>
124124

125+
<emoji-picker
126+
class="message-reactions"
127+
v-if="isMessageReactions"
128+
v-click-outside="closeEmoji"
129+
:emojiOpened="emojiOpened"
130+
:emojiReaction="true"
131+
:roomFooterRef="roomFooterRef"
132+
:positionRight="message.sender_id === 'me'"
133+
@addEmoji="sendMessageReaction"
134+
@openEmoji="emojiOpened = $event"
135+
></emoji-picker>
136+
125137
<transition
126138
:name="message.sender_id === 'me' ? 'slide-left' : 'slide-right'"
127139
v-if="filteredMessageActions.length"
@@ -147,6 +159,15 @@
147159
</div>
148160
</transition>
149161
</div>
162+
<button
163+
v-for="(reaction, key) in message.reactions"
164+
:key="key"
165+
class="button-reaction"
166+
:class="{ 'reaction-me': reaction[message.sender_id] }"
167+
@click="sendMessageReaction({ name: key })"
168+
>
169+
{{ getEmojiByName(key) }}<span>{{ reaction.length }}</span>
170+
</button>
150171
</div>
151172
</div>
152173
</div>
@@ -156,10 +177,11 @@
156177
import SvgIcon from './SvgIcon'
157178
import vClickOutside from 'v-click-outside'
158179
import ChatLoader from './ChatLoader'
180+
import EmojiPicker from './EmojiPicker'
159181
160182
export default {
161183
name: 'chat-message',
162-
components: { SvgIcon, ChatLoader },
184+
components: { SvgIcon, ChatLoader, EmojiPicker },
163185
164186
directives: {
165187
clickOutside: vClickOutside.directive
@@ -174,18 +196,22 @@ export default {
174196
textMessages: { type: Object, required: true },
175197
messageActions: { type: Array, required: true },
176198
roomFooterRef: { type: HTMLDivElement },
177-
newMessages: { type: Array }
199+
newMessages: { type: Array },
200+
showReactionEmojis: { type: Boolean, required: true },
201+
emojisList: { type: Object, required: true }
178202
},
179203
180204
data() {
181205
return {
182206
hoverMessageId: null,
183207
imageLoading: false,
184208
imageHover: false,
185-
messageReply: false,
209+
messageHover: false,
186210
optionsOpened: false,
187211
menuOptionsHeight: 0,
188-
newMessage: {}
212+
messageReaction: '',
213+
newMessage: {},
214+
emojiOpened: false
189215
}
190216
},
191217
@@ -249,10 +275,15 @@ export default {
249275
isMessageActions() {
250276
return (
251277
this.filteredMessageActions.length &&
252-
this.messageReply &&
278+
this.messageHover &&
253279
!this.message.deleted
254280
)
255281
},
282+
isMessageReactions() {
283+
return (
284+
this.showReactionEmojis && this.messageHover && !this.message.deleted
285+
)
286+
},
256287
filteredMessageActions() {
257288
return this.message.sender_id === 'me'
258289
? this.messageActions
@@ -269,15 +300,15 @@ export default {
269300
},
270301
onHoverMessage() {
271302
this.imageHover = true
272-
this.messageReply = true
303+
this.messageHover = true
273304
if (this.canEditMessage()) this.hoverMessageId = this.message._id
274305
},
275306
canEditMessage() {
276307
return this.message.sender_id === 'me' && !this.message.deleted
277308
},
278309
onLeaveMessage() {
279310
this.imageHover = false
280-
if (!this.optionsOpened) this.messageReply = false
311+
if (!this.optionsOpened && !this.emojiOpened) this.messageHover = false
281312
this.hoverMessageId = null
282313
},
283314
openFile() {
@@ -330,7 +361,21 @@ export default {
330361
},
331362
closeOptions() {
332363
this.optionsOpened = false
333-
if (this.hoverMessageId !== this.message._id) this.messageReply = false
364+
if (this.hoverMessageId !== this.message._id) this.messageHover = false
365+
},
366+
closeEmoji() {
367+
this.emojiOpened = false
368+
if (this.hoverMessageId !== this.message._id) this.messageHover = false
369+
},
370+
getEmojiByName(emojiName) {
371+
return this.emojisList[emojiName]
372+
},
373+
sendMessageReaction(emoji) {
374+
this.closeEmoji()
375+
this.$emit('sendMessageReaction', {
376+
messageId: this.message._id,
377+
reaction: emoji
378+
})
334379
}
335380
}
336381
}
@@ -586,6 +631,12 @@ export default {
586631
}
587632
}
588633
634+
.message-reactions {
635+
position: absolute;
636+
top: 6px;
637+
right: 37px;
638+
}
639+
589640
.menu-options {
590641
right: 15px;
591642
}
@@ -600,4 +651,37 @@ export default {
600651
vertical-align: middle;
601652
margin: -3px -3px 0 3px;
602653
}
654+
655+
.button-reaction {
656+
display: inline-flex;
657+
align-items: center;
658+
border: 1px solid #eee;
659+
outline: none;
660+
background: #eee;
661+
border-radius: 4px;
662+
margin: 4px 2px 0;
663+
transition: 0.3s;
664+
padding: 0 5px;
665+
font-size: 18px;
666+
line-height: 23px;
667+
668+
span {
669+
font-size: 12px;
670+
}
671+
672+
&:hover {
673+
border: 1px solid #ddd;
674+
background: #fff;
675+
}
676+
}
677+
678+
.reaction-me {
679+
border: 1px solid #3b98b8;
680+
background: #cfecf5;
681+
682+
&:hover {
683+
border: 1px solid #3b98b8;
684+
background: #cfecf5;
685+
}
686+
}
603687
</style>

src/ChatWindow/ChatWindow.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
:messageActions="messageActions"
1919
:showFiles="showFiles"
2020
:showEmojis="showEmojis"
21+
:showReactionEmojis="showReactionEmojis"
2122
:textMessages="t"
2223
@fetchMessages="fetchMessages"
2324
@sendMessage="sendMessage"
@@ -26,6 +27,7 @@
2627
@openFile="openFile"
2728
@menuActionHandler="menuActionHandler"
2829
@messageActionHandler="messageActionHandler"
30+
@sendMessageReaction="sendMessageReaction"
2931
>
3032
</messages-list>
3133
</div>
@@ -63,6 +65,7 @@ export default {
6365
},
6466
showFiles: { type: Boolean, default: true },
6567
showEmojis: { type: Boolean, default: true },
68+
showReactionEmojis: { type: Boolean, default: true },
6669
textMessages: { type: Object, default: null },
6770
theme: { type: String, default: 'light' },
6871
colors: { type: Object, default: null }
@@ -142,6 +145,12 @@ export default {
142145
action: ev,
143146
roomId: this.room.roomId
144147
})
148+
},
149+
sendMessageReaction(messageReaction) {
150+
this.$emit('sendMessageReaction', {
151+
...messageReaction,
152+
roomId: this.room.roomId
153+
})
145154
}
146155
}
147156
}

src/ChatWindow/EmojiPicker.vue

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<emoji-picker @emoji="append" :search="search">
44
<div
55
class="svg-button"
6+
:class="{ 'button-reaction': emojiReaction }"
67
slot="emoji-invoker"
78
slot-scope="{ events: { click: clickEvent } }"
89
@click.stop="clickEvent"
@@ -15,7 +16,15 @@
1516
slot-scope="{ emojis, insert }"
1617
v-if="emojiOpened"
1718
>
18-
<div class="emoji-picker">
19+
<div
20+
class="emoji-picker"
21+
:class="{ 'picker-reaction': emojiReaction }"
22+
:style="{
23+
top: `${emojiPickerHeight}px`,
24+
right: positionRight ? '85px' : '',
25+
display: emojiPickerHeight || !emojiReaction ? 'initial' : 'none'
26+
}"
27+
>
1928
<div class="emoji-picker__search">
2029
<input type="text" v-model="search" v-focus />
2130
</div>
@@ -26,10 +35,11 @@
2635
<span
2736
v-for="(emoji, emojiName) in emojiGroup"
2837
:key="emojiName"
29-
@click="insert(emoji)"
38+
@click="insert({ emoji, emojiName })"
3039
:title="emojiName"
31-
>{{ emoji }}</span
3240
>
41+
{{ emoji }}
42+
</span>
3343
</div>
3444
</div>
3545
</div>
@@ -48,18 +58,31 @@ export default {
4858
EmojiPicker,
4959
SvgIcon
5060
},
51-
props: ['emojiOpened'],
61+
props: ['emojiOpened', 'emojiReaction', 'roomFooterRef', 'positionRight'],
5262
data() {
5363
return {
54-
search: ''
64+
search: '',
65+
emojiPickerHeight: ''
5566
}
5667
},
5768
methods: {
58-
append(emoji) {
59-
this.$emit('addEmoji', emoji)
69+
append({ emoji, emojiName }) {
70+
this.$emit('addEmoji', { icon: emoji, name: emojiName })
6071
},
61-
openEmoji() {
72+
openEmoji(ev) {
6273
this.$emit('openEmoji', true)
74+
this.setEmojiPickerHeight(ev.clientY)
75+
},
76+
setEmojiPickerHeight(clientY) {
77+
setTimeout(() => {
78+
if (!this.roomFooterRef) return
79+
80+
const roomFooterTop = this.roomFooterRef.getBoundingClientRect().top
81+
const pickerTopPosition = roomFooterTop - clientY > 320
82+
83+
if (pickerTopPosition) this.emojiPickerHeight = clientY
84+
else this.emojiPickerHeight = clientY - 320
85+
}, 0)
6386
}
6487
},
6588
directives: {
@@ -89,6 +112,7 @@ export default {
89112
90113
.emoji-picker {
91114
position: absolute;
115+
z-index: 9999;
92116
bottom: 32px;
93117
right: 10px;
94118
border: var(--chat-border-style);
@@ -101,6 +125,12 @@ export default {
101125
background: var(--chat-emoji-bg-color);
102126
}
103127
128+
.picker-reaction {
129+
position: fixed;
130+
top: initial;
131+
right: initial;
132+
}
133+
104134
.emoji-picker__search {
105135
display: flex;
106136
}
@@ -144,4 +174,9 @@ export default {
144174
background: var(--chat-sidemenu-bg-color-hover);
145175
cursor: pointer;
146176
}
177+
178+
.button-reaction svg {
179+
height: 19px;
180+
width: 19px;
181+
}
147182
</style>

0 commit comments

Comments
 (0)