diff --git a/src/components/card/CardSidebarTabComments.vue b/src/components/card/CardSidebarTabComments.vue index 9646dbab9..841fb272d 100644 --- a/src/components/card/CardSidebarTabComments.vue +++ b/src/components/card/CardSidebarTabComments.vue @@ -36,14 +36,16 @@ -
@@ -59,9 +61,10 @@ import tippy from 'tippy.js' import { Editor, EditorContent } from 'tiptap' import { Mention } from 'tiptap-extensions' -import { mapState } from 'vuex' +import { mapState, mapGetters } from 'vuex' import { Avatar } from '@nextcloud/vue' import CommentItem from './CommentItem' +import InfiniteLoading from 'vue-infinite-loading' export default { name: 'CardSidebarTabComments', @@ -69,6 +72,7 @@ export default { Avatar, CommentItem, EditorContent, + InfiniteLoading, }, props: { card: { @@ -80,9 +84,6 @@ export default { return { newComment: '', isLoading: false, - limit: 20, - offset: 0, - editor: new Editor({ extensions: [ new Mention({ @@ -176,7 +177,7 @@ export default { comments: state => state.comment.comments, currentBoard: state => state.currentBoard, }), - + ...mapGetters(['getCommentsForCard', 'hasMoreComments']), hasResults() { return this.filteredUsers.length }, @@ -193,17 +194,19 @@ export default { }, }, }, - created() { - }, - methods: { - loadComments() { + async infiniteHandler($state) { + await this.loadMore() + if (this.hasMoreComments(this.card.id)) { + $state.loaded() + } else { + $state.complete() + } + }, + async loadComments() { this.isLoading = true - this.card.limit = this.limit - this.card.offset = this.offset - this.$store.dispatch('listComments', this.card).then(response => { - this.isLoading = false - }) + await this.$store.dispatch('fetchComments', { cardId: this.card.id }) + this.isLoading = false }, createComment() { const commentObj = { @@ -215,9 +218,10 @@ export default { this.newComment = '' this.editor.setContent('') }, - loadMore() { - this.offset = this.offset + this.limit - this.loadComments() + async loadMore() { + this.isLoading = true + await this.$store.dispatch('fetchMore', { cardId: this.card.id }) + this.isLoading = false }, // navigate to the previous item diff --git a/src/services/BoardApi.js b/src/services/BoardApi.js index a148de137..164ac1716 100644 --- a/src/services/BoardApi.js +++ b/src/services/BoardApi.js @@ -21,6 +21,7 @@ */ import axios from '@nextcloud/axios' +import './../models' /** * This class handles all the api communication with the Deck backend. diff --git a/src/services/CommentApi.js b/src/services/CommentApi.js index 33fb0c1a6..775beae63 100644 --- a/src/services/CommentApi.js +++ b/src/services/CommentApi.js @@ -22,6 +22,7 @@ import axios from '@nextcloud/axios' import { getCurrentUser } from '@nextcloud/auth' +import xmlToTagList from '../helpers/xml' export class CommentApi { @@ -30,97 +31,62 @@ export class CommentApi { return OC.linkToRemote(url) } - listComments(card) { - return axios({ + async loadComments({ cardId, limit, offset }) { + const response = await axios({ method: 'REPORT', - url: this.url(`${card.id}`), + url: this.url(`${cardId}`), data: ` - ${card.limit} - ${card.offset} + ${limit} + ${offset} `, - }).then( - (response) => { - return Promise.resolve(response.data) - }, - (err) => { - return Promise.reject(err) - } - ) - .catch((err) => { - return Promise.reject(err) - }) + }) + return xmlToTagList(response.data) } - createComment(commentObj) { - return axios({ + async createComment({ cardId, comment }) { + const response = await axios({ method: 'POST', - url: this.url(`${commentObj.cardId}`), - data: { actorType: 'users', message: `${commentObj.comment}`, verb: 'comment' }, - }).then( - (response) => { - const header = response.headers['content-location'] - const headerArray = header.split('/') - const id = headerArray[headerArray.length - 1] + url: this.url(`${cardId}`), + data: { actorType: 'users', message: `${comment}`, verb: 'comment' }, + }) - const ret = { - cardId: (commentObj.cardId).toString(), - id: id, - uId: getCurrentUser().uid, - creationDateTime: (new Date()).toString(), - message: commentObj.comment, - } - return Promise.resolve(ret) - }, - (err) => { - return Promise.reject(err) - } - ) - .catch((err) => { - return Promise.reject(err) - }) + const header = response.headers['content-location'] + const headerArray = header.split('/') + const id = headerArray[headerArray.length - 1] + + const ret = { + cardId: (cardId).toString(), + id: id, + uId: getCurrentUser().uid, + creationDateTime: (new Date()).toString(), + message: comment, + } + return ret } - updateComment(data) { - return axios({ + async updateComment({ cardId, commentId, comment }) { + const response = await axios({ method: 'PROPPATCH', - url: this.url(`${data.cardId}/${data.commentId}`), + url: this.url(`${cardId}/${commentId}`), data: ` - ${data.comment} + ${comment} `, - }).then( - (response) => { - return Promise.resolve(response.data) - }, - (err) => { - return Promise.reject(err) - } - ) - .catch((err) => { - return Promise.reject(err) - }) + }) + return response.data } - deleteComment(data) { - return axios({ + async deleteComment({ cardId, commentId }) { + const response = await axios({ method: 'DELETE', - url: this.url(`${data.cardId}/${data.commentId}`), - }).then( - (response) => { - return Promise.resolve(response.data) - }, - (err) => { - return Promise.reject(err) - } - ) - .catch((err) => { - return Promise.reject(err) - }) + url: this.url(`${cardId}/${commentId}`), + }) + return response.data } } diff --git a/src/services/StackApi.js b/src/services/StackApi.js index 782088661..9cf343e9a 100644 --- a/src/services/StackApi.js +++ b/src/services/StackApi.js @@ -21,6 +21,7 @@ */ import axios from '@nextcloud/axios' +import './../models' export class StackApi { diff --git a/src/store/comment.js b/src/store/comment.js index 0aac6239e..991255a40 100644 --- a/src/store/comment.js +++ b/src/store/comment.js @@ -21,73 +21,93 @@ */ import { CommentApi } from '../services/CommentApi' -import xmlToTagList from '../helpers/xml' import Vue from 'vue' const apiClient = new CommentApi() +const COMMENT_FETCH_LIMIT = 3 + export default { state: { comments: {}, }, + getters: { + getCommentsForCard: (state) => (id) => { + if (state.comments[id]) { + return [...state.comments[id].comments].sort((a, b) => b.id - a.id) + } + return [] + }, + hasMoreComments: (state) => (cardId) => { + return state.comments[cardId] && state.comments[cardId].hasMore + }, + }, mutations: { - addComments(state, commentObj) { - if (state.comments[commentObj.cardId] === undefined) { - Vue.set(state.comments, commentObj.cardId, commentObj.comments) - } else { - // FIXME append comments once incremental fetching is implemented - // state.comments[commentObj.cardId].push(...commentObj.comments) - Vue.set(state.comments, commentObj.cardId, commentObj.comments) + endReached(state, { cardId }) { + if (state.comments[cardId]) { + state.comments[cardId].hasMore = false } }, - createComment(state, newComment) { - if (state.comments[newComment.cardId] === undefined) { - state.comments[newComment.cardId] = [] + addComments(state, { comments, cardId }) { + if (state.comments[cardId] === undefined) { + Vue.set(state.comments, cardId, { + hasMore: comments.length > 0, + comments, + }) + } else { + const newComments = comments.filter((comment) => { + return state.comments[cardId].comments.findIndex((item) => item.id === comment.id) === -1 + }) + state.comments[cardId].comments.push(...newComments) } - state.comments[newComment.cardId].push(newComment) }, updateComment(state, comment) { - const existingIndex = state.comments[comment.cardId].findIndex(_comment => _comment.id === comment.commentId) + const existingIndex = state.comments[comment.cardId].comments.findIndex(_comment => _comment.id === comment.commentId) if (existingIndex !== -1) { - state.comments[comment.cardId][existingIndex].message = comment.comment + state.comments[comment.cardId].comments[existingIndex].message = comment.comment } }, deleteComment(state, comment) { - const existingIndex = state.comments[comment.cardId].findIndex(_comment => _comment.id === comment.commentId) + const existingIndex = state.comments[comment.cardId].comments.findIndex(_comment => _comment.id === comment.commentId) if (existingIndex !== -1) { - state.comments[comment.cardId].splice(existingIndex, 1) + state.comments[comment.cardId].comments.splice(existingIndex, 1) } }, }, actions: { - listComments({ commit }, card) { - apiClient.listComments(card) - .then((comments) => { - const commentsJson = xmlToTagList(comments) - const returnObj = { - cardId: card.id, - comments: commentsJson, - } - commit('addComments', returnObj) - }) + async fetchComments({ commit }, { cardId, offset }) { + const comments = await apiClient.loadComments({ + cardId, + limit: COMMENT_FETCH_LIMIT, + offset: offset || 0, + }) + + if (comments.length < COMMENT_FETCH_LIMIT) { + commit('endReached', { cardId }) + return + } + commit('addComments', { + cardId, + comments, + }) }, - createComment({ commit }, newComment) { - apiClient.createComment(newComment) - .then((newComment) => { - commit('createComment', newComment) - }) + async fetchMore({ commit, dispatch, getters }, { cardId }) { + // fetch newer comments first + await dispatch('fetchComments', { cardId }) + await dispatch('fetchComments', { cardId, offset: getters.getCommentsForCard(cardId).length }) + }, - deleteComment({ commit }, data) { - apiClient.deleteComment(data) - .then((retVal) => { - commit('deleteComment', data) - }) + async createComment({ commit, dispatch }, { cardId, comment }) { + await apiClient.createComment({ cardId, comment }) + await dispatch('fetchComments', { cardId }) }, - updateComment({ commit }, data) { - apiClient.updateComment(data) - .then((retVal) => { - commit('updateComment', data) - }) + async deleteComment({ commit }, data) { + await apiClient.deleteComment(data) + commit('deleteComment', data) + }, + async updateComment({ commit }, data) { + await apiClient.updateComment(data) + commit('updateComment', data) }, }, } diff --git a/src/store/main.js b/src/store/main.js index fc0f27804..3440f2638 100644 --- a/src/store/main.js +++ b/src/store/main.js @@ -110,12 +110,9 @@ export default new Vuex.Store({ toggleShowArchived(state) { state.showArchived = !state.showArchived }, - /** + /* * Adds or replaces a board in the store. * Matches a board by it's id. - * - * @param state - * @param board */ addBoard(state, board) { const indexExisting = state.boards.findIndex((b) => { @@ -141,11 +138,8 @@ export default new Vuex.Store({ } }, - /** + /* * Removes the board from the store. - * - * @param state - * @param board */ removeBoard(state, board) { state.boards = state.boards.filter((b) => { @@ -207,7 +201,6 @@ export default new Vuex.Store({ } }, updateLabelFromCurrentBoard(state, newLabel) { - const labelToUpdate = state.currentBoard.labels.find((l) => { return newLabel.id === l.id }) @@ -264,6 +257,7 @@ export default new Vuex.Store({ toggleShowArchived({ commit }) { commit('toggleShowArchived') }, + /** * @param commit * @param state