Emoji reactions
This commit is contained in:
parent
5c2a1d6246
commit
5f4b14ecc4
|
@ -170,6 +170,17 @@ func (s *Server) OnTakeback(sub *Subscriber, msg Message) {
|
|||
})
|
||||
}
|
||||
|
||||
// OnReact handles emoji reactions for chat messages.
|
||||
func (s *Server) OnReact(sub *Subscriber, msg Message) {
|
||||
// Forward the reaction to everybody.
|
||||
s.Broadcast(Message{
|
||||
Action: ActionReact,
|
||||
Username: sub.Username,
|
||||
Message: msg.Message,
|
||||
MessageID: msg.MessageID,
|
||||
})
|
||||
}
|
||||
|
||||
// OnFile handles a picture shared in chat with a channel.
|
||||
func (s *Server) OnFile(sub *Subscriber, msg Message) {
|
||||
if sub.Username == "" {
|
||||
|
|
|
@ -57,6 +57,7 @@ const (
|
|||
ActionUnwatch = "unwatch" // user has closed your video
|
||||
ActionFile = "file" // image sharing in chat
|
||||
ActionTakeback = "takeback" // user takes back (deletes) their message for everybody
|
||||
ActionReact = "react" // emoji reaction to a chat message
|
||||
|
||||
// Actions sent by server only
|
||||
ActionPing = "ping"
|
||||
|
|
|
@ -105,6 +105,8 @@ func (sub *Subscriber) ReadLoop(s *Server) {
|
|||
s.OnUnwatch(sub, msg)
|
||||
case ActionTakeback:
|
||||
s.OnTakeback(sub, msg)
|
||||
case ActionReact:
|
||||
s.OnReact(sub, msg)
|
||||
default:
|
||||
sub.ChatServer("Unsupported message type.")
|
||||
}
|
||||
|
|
|
@ -274,3 +274,17 @@ div.feed.popped-out {
|
|||
.cursor-notallowed {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Emoji reaction button support */
|
||||
.position-relative {
|
||||
position: relative;
|
||||
}
|
||||
.emoji-button {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
}
|
||||
.emoji-button button {
|
||||
background-color: rgba(255, 255, 255, 0.5) !important;
|
||||
color: rgba(0, 0, 0, 0.5) !important;
|
||||
}
|
||||
|
|
|
@ -62,7 +62,18 @@ const app = Vue.createApp({
|
|||
ready: false,
|
||||
audioContext: null,
|
||||
audioTracks: {},
|
||||
}
|
||||
},
|
||||
reactions: [
|
||||
'❤️',
|
||||
'😂',
|
||||
// '😉',
|
||||
// '😢',
|
||||
// '😡',
|
||||
'🔥',
|
||||
// '😈',
|
||||
'🍑',
|
||||
'🍆',
|
||||
]
|
||||
},
|
||||
|
||||
// User JWT settings if available.
|
||||
|
@ -166,6 +177,14 @@ const app = Vue.createApp({
|
|||
autoscroll: true, // scroll to bottom on new messages
|
||||
fontSizeClass: "", // font size magnification
|
||||
DMs: {},
|
||||
messageReactions: {
|
||||
// Will look like:
|
||||
// "123": { (message ID)
|
||||
// "❤️": [ (reaction emoji)
|
||||
// "username" // users who reacted
|
||||
// ]
|
||||
// }
|
||||
},
|
||||
|
||||
// Responsive CSS controls for mobile.
|
||||
responsive: {
|
||||
|
@ -390,6 +409,35 @@ const app = Vue.createApp({
|
|||
// TODO
|
||||
},
|
||||
|
||||
// Emoji reactions
|
||||
sendReact(message, emoji) {
|
||||
this.ws.conn.send(JSON.stringify({
|
||||
action: 'react',
|
||||
msgID: message.msgID,
|
||||
message: emoji,
|
||||
}));
|
||||
},
|
||||
onReact(msg) {
|
||||
// Search all channels for this message ID and append the reaction.
|
||||
let msgID = msg.msgID,
|
||||
who = msg.username,
|
||||
emoji = msg.message;
|
||||
|
||||
if (this.messageReactions[msgID] == undefined) {
|
||||
this.messageReactions[msgID] = {};
|
||||
}
|
||||
if (this.messageReactions[msgID][emoji] == undefined) {
|
||||
this.messageReactions[msgID][emoji] = [];
|
||||
}
|
||||
|
||||
// don't count double reactions of same emoji from same chatter
|
||||
for (let reactor of this.messageReactions[msgID][emoji]) {
|
||||
if (reactor === who) return;
|
||||
}
|
||||
|
||||
this.messageReactions[msgID][emoji].push(who);
|
||||
},
|
||||
|
||||
// Sync the current user state (such as video broadcasting status) to
|
||||
// the backend, which will reload everybody's Who List.
|
||||
sendMe() {
|
||||
|
@ -641,6 +689,9 @@ const app = Vue.createApp({
|
|||
case "takeback":
|
||||
this.onTakeback(msg);
|
||||
break;
|
||||
case "react":
|
||||
this.onReact(msg);
|
||||
break;
|
||||
case "presence":
|
||||
this.onPresence(msg);
|
||||
break;
|
||||
|
@ -1001,6 +1052,15 @@ const app = Vue.createApp({
|
|||
})
|
||||
},
|
||||
|
||||
/* message reaction emojis */
|
||||
hasReactions(msg) {
|
||||
return this.messageReactions[msg.msgID] != undefined;
|
||||
},
|
||||
getReactions(msg) {
|
||||
if (!this.hasReactions(msg)) return [];
|
||||
return this.messageReactions[msg.msgID];
|
||||
},
|
||||
|
||||
activeChannels() {
|
||||
// List of current channels, unread indicators etc.
|
||||
let result = [];
|
||||
|
|
|
@ -777,7 +777,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Normal chat message: full size card w/ avatar -->
|
||||
<div v-else class="box mb-2 px-4 pt-3 pb-1">
|
||||
<div v-else class="box mb-2 px-4 pt-3 pb-1 position-relative">
|
||||
<div class="media mb-0">
|
||||
<div class="media-left">
|
||||
<a :href="profileURLForUsername(msg.username)" @click.prevent="openProfile({username: msg.username})"
|
||||
|
@ -882,10 +882,39 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Emoji reactions menu -->
|
||||
<div v-if="msg.msgID" class="dropdown is-up is-right emoji-button" onclick="this.classList.toggle('is-active')">
|
||||
<div class="dropdown-trigger">
|
||||
<button class="button is-small px-2" aria-haspopup="true" :aria-controls="`react-menu-${msg.msgID}`">
|
||||
<span>
|
||||
<i class="fa fa-heart has-text-grey"></i>
|
||||
<i class="fa fa-plus has-text-grey pl-1"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-menu" :id="`react-menu-${msg.msgID}`" role="menu">
|
||||
<div class="dropdown-content">
|
||||
<a v-for="i in config.reactions" href="#" class="dropdown-item" @click.prevent="sendReact(msg, i)">
|
||||
[[i]]
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message box -->
|
||||
<div class="content pl-5 py-3 mb-0">
|
||||
<em v-if="msg.action === 'presence'">[[msg.message]]</em>
|
||||
<div v-else v-html="msg.message"></div>
|
||||
|
||||
<!-- Reactions so far? -->
|
||||
<div v-if="hasReactions(msg)" class="mt-1">
|
||||
<span v-for="(users, emoji) in getReactions(msg)"
|
||||
class="tag is-secondary mr-1"
|
||||
:title="emoji + ' by: ' + users.join(', ')"
|
||||
onclick="window.alert(this.title)">
|
||||
[[emoji]] <small class="ml-1">[[users.length]]</small>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue
Block a user