зеркало из https://github.com/nextcloud/spreed.git
Merge pull request #2296 from nextcloud/receive-new-messages
Add ability to receive new messages.
This commit is contained in:
Коммит
b88aa92aa5
|
@ -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)
|
||||
|
|
Загрузка…
Ссылка в новой задаче