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]