vue-advanced-chat usage guide (socket.io+node+vue implements chat)

demo address: –
Git address of vue-advanced-chat: https://github.com/advanced-chat/vue-advanced-chat

1. Build demo

After cloning the demo address, install the dependencies in the demo directory and start it.

The page after startup is as follows:

2. Front-end code analysis

2.1 Analysis of key APIs

current-user-id: current user id

room-id: Can a specific room be loaded at any time?

rooms:?

messages: message list

room-actions: Can be used to display your own button when clicking the dropdown icon for each room in the room list

Used with event room-action-handler

roomActions: [
{
   <!-- --> name: 'inviteUser', title: 'Test drop-down' },
{
   <!-- --> name: 'removeUser', title: 'Remove User' },
{
   <!-- --> name: 'deleteRoom', title: 'Delete Room' }
]

menu-actions: Can be used to display your own buttons when the vertical dot icon in the room is clicked

Used with event menu-action-handler

menuActions: [
{
   <!-- --> name: 'inviteUser', title: 'Test Menu' },
{
   <!-- --> name: 'removeUser', title: 'Remove User' },
{
   <!-- --> name: 'deleteRoom', title: 'Delete Room' }
],

message-selection-actions: You can use this to display custom action buttons in the room header when selecting a message

messageSelectionActions: [{
   <!-- --> name: 'deleteMessages', title: 'Are you sure you want to delete it? ' }]

templates-text: Can be used to add autocomplete template text when typing in the room text area

templatesText: [
{
   <!-- -->
tag: 'Eat well',
text: 'Eat well, drink well, and have a good trip! '
},
{
   <!-- -->
tag: 'action',
text: 'This is the action'
},
{
   <!-- -->
tag: 'action 2',
text: 'This is the second action'
}
],

text-messages: can be used to replace the default i18n text

textMessages: {
   <!-- -->
ROOMS_EMPTY: 'No chat',
ROOM_EMPTY: 'Chat not selected',
NEW_MESSAGES: 'New messages',
MESSAGE_DELETED: 'Message has been deleted',
MESSAGES_EMPTY: 'No messages',
CONVERSATION_STARTED: 'Chat started on:',
TYPE_MESSAGE: 'Please enter',
SEARCH: 'Search',
IS_ONLINE: 'Online',
LAST_SEEN: 'last seen ',
IS_TYPING: 'Entering...',
CANCEL_SELECT_MESSAGE: 'Cancel'
}

2.2 Analysis of key events

fetch-more-rooms ?

Triggered when scrolling down the room list, should be the way to implement the paging system

fetch-messages

Fires every time a room is opened. If the room is opened for the first time, the options parameter will remain reset: true. (The purpose is to load the conversation’s older messages when the user scrolls to the top)

fetchMessages({
    <!-- --> room, options = {
    <!-- -->} }) {
   <!-- -->
this.$emit('show-demo-options', false)

  //If it is opened for the first time, initialize the parameters of the room
if (options.reset) {
   <!-- -->
this.resetMessages()
}

if (this.previousLastLoadedMessage & amp; & amp; !this.lastLoadedMessage) {
   <!-- -->
this.messagesLoaded = true
return
}

  //Set the ID value of the currently selected room
this.selectedRoom = room.roomId

console.info('[fetchMessages] Selected Room (selected room id):' + this.selectedRoom)
console.info('[fetchMessages] Selected Room messages per page (selected room message page number):' + this.messagesPerPage)
console.info('[fetchMessages] Selected Room last loaded message (selected room last loaded message):' + this.lastLoadedMessage)
firestoreService
.getMessages(room.roomId, this.messagesPerPage, this.lastLoadedMessage)
.then(({
    <!-- --> data, docs }) => {
   <!-- -->
// this.incrementDbCounter('Fetch Room Messages', messages.length)

      //Short call
if (this.selectedRoom !== room.roomId) return

      //The message data obtained from the interface is empty, or the data length obtained is less than the local message length, then loading is displayed.
if (data.length === 0 || data.length <this.messagesPerPage) {
   <!-- -->
setTimeout(() => {
   <!-- -->
this.messagesLoaded = true
}, 0)
}
      
      //If it is opened for the first time, set the message variable messages to empty.
if (options.reset) this.messages = []

data.forEach(message => {
   <!-- -->
        //Format the message as a template message
const formattedMessage = this.formatMessage(room, message)
console.info('[fetchMessages] Formatted Message:' + JSON.stringify(formattedMessage))
this.messages.unshift(formattedMessage)
})
console.info('[fetchMessages] Fetch Room Messages Length:' + this.messages.length)
      
if (this.lastLoadedMessage) {
   <!-- -->
this.previousLastLoadedMessage = this.lastLoadedMessage
}
this.lastLoadedMessage = docs[docs.length - 1]

this.listenMessages(room)
})
}

send-message

Send a message, triggered when you click send or enter

async sendMessage({
    <!-- --> content, roomId, files, replyMessage }) {
   <!-- -->
  //content message content, roomId room id, files file, replyMessage (WeChat reference) reply message
const message = {
   <!-- -->
sender_id: this.currentUserId,
content,
timestamp: new Date()
}

if (files) {
   <!-- -->
message.files = this.formattedFiles(files)
}

if (replyMessage) {
   <!-- -->
message.replyMessage = {
   <!-- -->
_id: replyMessage._id,
content: replyMessage.content,
sender_id: replyMessage.senderId
}

if (replyMessage.files) {
   <!-- -->
message.replyMessage.files = replyMessage.files
}
}
console.info('[sendMessage] room id is (the room id that sent the message):' + roomId)
console.info('[sendMessage] message is:' + JSON.stringify(message))

const {
   <!-- --> id } = await firestoreService.addMessage(roomId, message)

console.info('[sendMessage] message id is:' + id)

if (files) {
   <!-- -->
for (let index = 0; index < files.length; index + + ) {
   <!-- -->
await this.uploadFile({
   <!-- --> file: files[index], messageId: id, roomId })
}
}
console.info('[sendMessage] update room with id:' + roomId)
firestoreService.updateRoom(roomId, {
   <!-- --> lastUpdated: new Date() })
},

edit-message

Edit message

async editMessage({
    <!-- --> messageId, newContent, roomId, files }) {
   <!-- -->
const newMessage = {
   <!-- --> edited: new Date() }
newMessage.content = newContent

if (files) {
   <!-- -->
newMessage.files = this.formattedFiles(files)
} else {
   <!-- -->
newMessage.files = firestoreService.deleteDbField
}

await firestoreService.updateMessage(roomId, messageId, newMessage)

if (files) {
   <!-- -->
for (let index = 0; index < files.length; index + + ) {
   <!-- -->
if (files[index]?.blob) {
   <!-- -->
await this.uploadFile({
   <!-- --> file: files[index], messageId, roomId })
}
}
}
},

delete-message

Delete message

async deleteMessage({
    <!-- --> message, roomId }) {
   <!-- -->
await firestoreService.updateMessage(roomId, message._id, {
   <!-- -->
deleted: new Date()
})

const {
   <!-- --> files } = message

if (files) {
   <!-- -->
files.forEach(file => {
   <!-- -->
storageService.deleteFile(this.currentUserId, message._id, file)
})
}
},

open-file

download file

openFile({
    <!-- --> file }) {
   <!-- -->
window.open(file.file.url, '_blank')
},

open-user-tag ?This should not be used

Fired when a user tag within a message is clicked. Create user tags by entering @ in the footer text area and sending a message

add-room

add room

addRoom() {
   <!-- -->
this.resetForms()
this.addNewRoom = true
},

room-action-handler, menu-action-handler

Used together with room-actions and menu-actions in the API

menuActionHandler({
    <!-- --> action, roomId }) {
   <!-- -->
switch (action.name) {
   <!-- -->
case 'inviteUser':
return this.inviteUser(roomId)
case 'removeUser':
return this.removeUser(roomId)
case 'deleteRoom':
return this.deleteRoom(roomId)
}
},

message-selection-action-handler

Used with message-selection-actions in api

messageSelectionActionHandler({
    <!-- --> action, messages, roomId }) {
   <!-- -->
switch (action.name) {
   <!-- -->
case 'deleteMessages':
messages.forEach(message => {
   <!-- -->
this.deleteMessage({
   <!-- --> message, roomId })
})
}
},

send-message-reaction

Click on the emoji icon within the message

async sendMessageReaction({
    <!-- --> reaction, remove, messageId, roomId }) {
   <!-- -->
firestoreService.updateMessageReactions(
roomId,
messageId,
this.currentUserId,
reaction.unicode,
remove ? 'remove' : 'add'
)
},

typing-message

Start typing the message (the other party can see the prompt “Inputting…”)

typingMessage({
    <!-- --> message, roomId }) {
   <!-- -->
if (roomId) {
   <!-- -->
if (message?.length > 1) {
   <!-- -->
this.typingMessageCache = message
return
}

if (message?.length === 1 & amp; & amp; this.typingMessageCache) {
   <!-- -->
this.typingMessageCache = message
return
}

this.typingMessageCache = message

firestoreService.updateRoomTypingUsers(
roomId,
this.currentUserId,
message ? 'add' : 'remove'
)
}
},

toggle-rooms-list

Click the toggle icon within the room title

2.3 Public functions

resetMessages()

Reset the room’s message base variables

resetMessages() {
   <!-- -->
this.messages = []
this.messagesLoaded = false
this.lastLoadedMessage = null
this.previousLastLoadedMessage = null
this.listeners.forEach(listener => listener())
this.listeners = []
},

formatMessage()

Format message template

formatMessage(room, message) {
   <!-- -->
// const senderUser = room.users.find(user => user._id === message.sender_id)
const formattedMessage = {
   <!-- -->
...message,
...{
   <!-- -->
senderId: message.sender_id,
_id: message.id,
seconds: message.timestamp.seconds,
timestamp: parseTimestamp(message.timestamp, 'HH:mm'),
date: parseTimestamp(message.timestamp, 'DD MMMM YYYY'),
username: room.users.find(user => message.sender_id === user._id)
?.username,
// avatar: senderUser ? senderUser.avatar : null,
distributed: true
}
}

if (message.replyMessage) {
   <!-- -->
formattedMessage.replyMessage = {
   <!-- -->
...message.replyMessage,
...{
   <!-- -->
senderId: message.replyMessage.sender_id
}
}
}

return formattedMessage
},

listenMessages() ?

listenMessages(room) {
   <!-- -->
const listener = firestoreService.listenMessages(
room.roomId,
this.lastLoadedMessage,
this.previousLastLoadedMessage,
messages => {
   <!-- -->
messages.forEach(message => {
   <!-- -->
const formattedMessage = this.formatMessage(room, message)
const messageIndex = this.messages.findIndex(
m => m._id === message.id
)

if (messageIndex === -1) {
   <!-- -->
console.log('[listenMessages] new formatted message:' + JSON.stringify(formattedMessage))
this.messages = this.messages.concat([formattedMessage]