Add ability to receive new messages.

Signed-off-by: Marco Ambrosini <marcoambrosini@pm.me>
This commit is contained in:
Marco Ambrosini 2019-10-10 17:12:34 +02:00
Родитель 7a9c0021b1
Коммит 85390c5194
10 изменённых файлов: 162 добавлений и 69 удалений

Просмотреть файл

@ -19,11 +19,16 @@
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<docs>
This component displays the text inside the message component and can be used for
the main body of the message as well as a quote.
</docs>
<template>
<div v-show="message"
:class="{ 'message-main--quote' : isQuote }"
class="message-main">
<div class="message-main-header">
<div v-if="showAuthor" class="message-main-header">
<h6>{{ actorDisplayName }}</h6>
</div>
<slot />
@ -37,23 +42,34 @@
export default {
inheritAttrs: false,
props: {
/**
* The sender of the message.
*/
actorDisplayName: {
type: String,
required: true
},
/**
* The message or quote text.
*/
message: {
type: String,
required: true
},
isFirstMessage: {
/**
* if true, it displays the message author on top of the message.
*/
showAuthor: {
type: Boolean,
default: true
},
/**
* Style the message as a quote.
*/
isQuote: {
type: Boolean,
default: false
}
},
computed: {
isQuote() {
return !!this.$parent.messageText
}
}
}
</script>
@ -74,13 +90,13 @@ export default {
flex-direction: column;
font-size: 20;
&-header {
color: #989898;
color: var(--color-text-maxcontrast);
}
&-text {
color: #444444;
color: var(--color-text-light);
}
&--quote {
border-left: 4px solid rgb(59, 59, 59);
border-left: 4px solid var(--color-primary);
padding: 4px 0 0 8px;
}
}

Просмотреть файл

@ -18,6 +18,14 @@
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<docs>
This component is a wrapper for the list of messages. It's main purpose it to
get the messagesList array and loop through the list to generate the messages.
In order not to render each and every messages that is in the store, we use
the DynamicScroller component, whose docs you can find [here.](https://github.com/Akryum/vue-virtual-scroller#dynamicscroller)
</docs>
<template>
<DynamicScroller
@ -47,7 +55,7 @@ import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller/dist/
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import Message from './Message/Message'
import MessageBody from './MessageBody/MessageBody'
import { fetchMessages } from '../../services/messagesService'
import { fetchMessages, lookForNewMessges } from '../../services/messagesService'
export default {
name: 'MessagesList',
@ -57,6 +65,7 @@ export default {
Message,
MessageBody
},
props: {
/**
* The conversation token.
@ -66,34 +75,45 @@ export default {
required: true
}
},
data: function() {
return {
/**
* Keeps track of the state of the component in order to trigger the scroll to
* bottom.
*/
isInitiated: false
}
},
computed: {
/**
* Gets the messages array.
* Gets the messages array. We need this because the DynamicScroller needs an array to
* loop through.
*
* @returns {Array}
* @returns {array}
*/
messagesList() {
return this.$store.getters.messagesList(this.token)
},
/**
* Gets the messages object.
* Gets the messages object, which is structured so that the key of each message element
* corresponds to the id of the message, and makes it easy and efficient to access the
* individual message object.
*
* @returns {Object}
* @returns {object}
*/
messages() {
return this.$store.getters.messages(this.token)
}
},
watch: {
token: function() {
this.onTokenChange()
}
},
/**
* Fetches the messages when the MessageList is mounted for the
* first time. The router mounts this component only if the token
@ -102,37 +122,71 @@ export default {
beforeMount() {
this.onTokenChange()
},
beforeUpdate() {
/**
* If the component is not initiated, scroll to the bottom of the message list.
*/
if (!this.isInitiated) {
this.scrollToBottom()
this.isInitiated = true
}
},
// Scrolls to the bottom of the message list.
methods: {
/**
* Fetches the messaes of a conversation given the
* conversation token.
*/
async onTokenChange() {
this.isInitiated = false
const messages = await fetchMessages(this.token)
// Process each messages and adds it to the store
messages.data.ocs.data.forEach(message => {
this.$store.dispatch('processMessage', message)
})
// After loading the old messages to the store, we start looking for new mwssages.
this.getNewMessages()
},
/**
* Creates a long polling request for a new message.
*/
async getNewMessages() {
const lastKnownMessageId = this.messagesList[this.messagesList.length - 1].id
const messages = await lookForNewMessges(this.token, lastKnownMessageId)
// If there are no new messages, the variable messages will be undefined.
if (messages !== undefined) {
// Process each messages and adds it to the store
messages.data.ocs.data.forEach(message => {
this.$store.dispatch('processMessage', message)
})
this.scrollToBottom()
}
/**
* This method recursively call itself after a response, so we're always
* looking for new messages.
*/
this.getNewMessages()
},
/**
* Dispatches the deleteMessages action.
* @param {object} event The deleteMessage event emitted by the Message component.
*/
handleDeleteMessage(event) {
this.$store.dispatch('deleteMessage', event.message)
},
/**
* Scrolls to the bottom of the list.
*/
scrollToBottom() {
this.$nextTick(function() {
document.querySelector('.scroller').scrollTop = document.querySelector('.scroller').scrollHeight
})
this.isInitiated = true
}
},
methods: {
async onTokenChange() {
this.isInitiated = false
/**
* Fetches the messaes of a conversation given the
* conversation token.
*/
const messages = await fetchMessages(this.token)
messages.data.ocs.data.forEach(message => {
// Process each messages and adds it to the store
this.$store.dispatch('processMessage', message)
})
},
scrollToEnd: function() {
this.$el.scrollTop = this.$el.scrollHeight
},
handleDeleteMessage(event) {
this.$store.dispatch('deleteMessage', event.message)
}
}
}
</script>

Просмотреть файл

@ -59,7 +59,7 @@ export default {
methods: {
/**
* Create a new conversation with the selected user.
* @param {String} userId the ID of the clicked user.
* @param {string} userId the ID of the clicked user.
*/
async createAndJoinConversation(userId) {
console.debug(userId)

Просмотреть файл

@ -101,7 +101,7 @@ export default {
},
/**
* Deletes the current user from the conversation.
* @param {String} token The token of the conversation to be left.
* @param {string} token The token of the conversation to be left.
*/
async deleteConversation(token) {
const response = await removeCurrentUserFromConversation(token)

Просмотреть файл

@ -74,7 +74,7 @@ export default {
* at the v-on in the template) unless shift is pressed:
* in this case a new line will be created.
*
* @param {Object} event the event object;
* @param {object} event the event object;
*/
handleKeydown(event) {
// TODO: add support for CTRL+ENTER new line

Просмотреть файл

@ -37,7 +37,7 @@ const fetchConversations = async function() {
/**
* Fetch possible conversations
* @param {String} searchText The string that will be used in the search query.
* @param {string} searchText The string that will be used in the search query.
*/
const searchPossibleConversations = async function(searchText) {
try {
@ -50,7 +50,7 @@ const searchPossibleConversations = async function(searchText) {
/**
* Create a new one to one conversation with the specified user.
* @param {String} userId The ID of the user with wich the new conversation will be opened.
* @param {string} userId The ID of the user with wich the new conversation will be opened.
*/
const createOneToOneConversation = async function(userId) {
try {
@ -63,7 +63,7 @@ const createOneToOneConversation = async function(userId) {
/**
* Create a new group conversation.
* @param {String} groupId The group ID, this parameter is optional.
* @param {string} groupId The group ID, this parameter is optional.
*/
const createGroupConversation = async function(groupId) {
try {
@ -76,7 +76,7 @@ const createGroupConversation = async function(groupId) {
/**
* Delete a conversation.
* @param {String} token The token of the conversation to be deleted.
* @param {string} token The token of the conversation to be deleted.
*/
const deleteConversation = async function(token) {
try {

Просмотреть файл

@ -38,12 +38,28 @@ const fetchMessages = async function(token) {
}
}
/**
* Fetches newly created messages that belong to a particular conversation
* specified with its token.
*
* @param {string} token The conversation token;
* @param {int} lastKnownMessageId The id of the last message in the store.
*/
const lookForNewMessges = async function(token, lastKnownMessageId) {
try {
const response = await axios.get(generateOcsUrl('apps/spreed/api/v1/chat', 2) + token + '?lookIntoFuture=1' + '&includeLastKnown=0' + `&lastKnownMessageId=${lastKnownMessageId}`)
return response
} catch (error) {
console.debug('Error while looking for new message: ', error)
}
}
/**
* Posts a new messageto the server.
*
* @param {Object} param0 The message object that is destructured;
* @param {String} token The conversation token;
* @param {Object} message The message object.
* @param {object} param0 The message object that is destructured;
* @param {string} token The conversation token;
* @param {object} message The message object.
*/
const postNewMessage = async function({ token, message }) {
try {
@ -54,4 +70,8 @@ const postNewMessage = async function({ token, message }) {
}
}
export { fetchMessages, postNewMessage }
export {
fetchMessages,
lookForNewMessges,
postNewMessage
}

Просмотреть файл

@ -27,7 +27,7 @@ import { generateOcsUrl } from 'nextcloud-router'
* Joins the current user to a conversation specified with
* the token.
*
* @param {String} token The conversation token;
* @param {string} token The conversation token;
*/
const joinConversation = async function(token) {
try {
@ -41,7 +41,7 @@ const joinConversation = async function(token) {
/**
* Leaves the conversation specified with the token.
*
* @param {String} token The conversation token;
* @param {string} token The conversation token;
*/
const leaveConversation = async function(token) {
try {
@ -55,7 +55,7 @@ const leaveConversation = async function(token) {
/**
* Removes the the current user from the conversation specified with the token.
*
* @param {String} token The conversation token;
* @param {string} token The conversation token;
*/
const removeCurrentUserFromConversation = async function(token) {
try {

Просмотреть файл

@ -37,8 +37,8 @@ const mutations = {
/**
* Adds a conversation to the store.
*
* @param {Object} state current store state;
* @param {Object} conversation the conversation;
* @param {object} state current store state;
* @param {object} conversation the conversation;
*/
addConversation(state, conversation) {
Vue.set(state.conversations, conversation.id, conversation)
@ -47,8 +47,8 @@ const mutations = {
* Creates a key-value pair with conversation id and name
* respectively.
*
* @param {Object} state current state object;
* @param {Object} object destructuring object;
* @param {object} state current state object;
* @param {object} object destructuring object;
* @param {int} object.id conversation id;
* @param {string} object.displayName conversation name;
*/
@ -61,8 +61,8 @@ const actions = {
/**
* Add a conversation to the store and index the displayname.
*
* @param {Object} context default store context;
* @param {Object} conversation the conversation;
* @param {object} context default store context;
* @param {object} conversation the conversation;
*/
addConversation(context, conversation) {
context.commit('addConversation', conversation)

Просмотреть файл

@ -44,8 +44,8 @@ const getters = {
const mutations = {
/**
* Adds a message to the store.
* @param {Object} state current store state;
* @param {Object} message the message;
* @param {object} state current store state;
* @param {object} message the message;
*/
addMessage(state, message) {
if (!state.messages[message.token]) {
@ -55,16 +55,16 @@ const mutations = {
},
/**
* Deletes a message from the store.
* @param {Object} state current store state;
* @param {Object} message the message;
* @param {object} state current store state;
* @param {object} message the message;
*/
deleteMessage(state, message) {
Vue.delete(state.messages[message.token], message.id)
},
/**
* Adds a temporary message to the store.
* @param {Object} state current store state;
* @param {Object} message the temporary message;
* @param {object} state current store state;
* @param {object} message the temporary message;
*/
addTemporaryMessage(state, message) {
Vue.set(state.messages[message.token], message.id, message)
@ -72,14 +72,15 @@ const mutations = {
}
const actions = {
/**
* Adds message to the store.
*
* If the message has a parent message object,
* first it adds the parent to the store.
*
* @param {Object} context default store context;
* @param {Object} message the message;
* @param {object} context default store context;
* @param {object} message the message;
*/
processMessage(context, message) {
if (message.parent) {
@ -88,22 +89,24 @@ const actions = {
}
context.commit('addMessage', message)
},
/**
* Delete a message
*
* @param {Object} context default store context;
* @param {String} message the message to be deleted;
* @param {object} context default store context;
* @param {string} message the message to be deleted;
*/
deleteMessage(context, message) {
context.commit('deleteMessage', message)
},
/**
* Add a temporary message generated in the client to
* the store, these messages are deleted once the full
* message object is recived from the server.
*
* @param {Object} context default store context;
* @param {Object} message the temporary message;
* @param {object} context default store context;
* @param {object} message the temporary message;
*/
addTemporaryMessage(context, message) {
context.commit('addTemporaryMessage', message)