diff options
Diffstat (limited to 'apps/comments/src/views/Comments.vue')
-rw-r--r-- | apps/comments/src/views/Comments.vue | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/apps/comments/src/views/Comments.vue b/apps/comments/src/views/Comments.vue new file mode 100644 index 00000000000..8c3ec66c323 --- /dev/null +++ b/apps/comments/src/views/Comments.vue @@ -0,0 +1,264 @@ +<!-- + - @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com> + - + - @author John Molakvoæ <skjnldsv@protonmail.com> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - 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/>. + - + --> + +<template> + <div class="comments" :class="{ 'icon-loading': isFirstLoading }"> + <!-- Editor --> + <Comment v-bind="editorData" + :auto-complete="autoComplete" + :editor="true" + :ressource-id="ressourceId" + class="comments__writer" + @new="onNewComment" /> + + <template v-if="!isFirstLoading"> + <EmptyContent v-if="!hasComments && done" icon="icon-comment"> + {{ t('comments', 'No comments yet, start the conversation!') }} + </EmptyContent> + + <!-- Comments --> + <Comment v-for="comment in comments" + v-else + :key="comment.id" + v-bind="comment" + :auto-complete="autoComplete" + :ressource-id="ressourceId" + :message.sync="comment.message" + class="comments__list" + @delete="onDelete" /> + + <!-- Loading more message --> + <div v-if="loading && !isFirstLoading" class="comments__info icon-loading" /> + + <div v-else-if="hasComments && done" class="comments__info"> + {{ t('comments', 'No more messages') }} + </div> + + <!-- Error message --> + <EmptyContent v-else-if="error" class="comments__error" icon="icon-error"> + {{ error }} + <template #desc> + <button icon="icon-history" @click="getComments"> + {{ t('comments', 'Retry') }} + </button> + </template> + </EmptyContent> + </template> + </div> +</template> + +<script> +import { generateOcsUrl } from '@nextcloud/router' +import { getCurrentUser } from '@nextcloud/auth' +import axios from '@nextcloud/axios' +import VTooltip from 'v-tooltip' +import Vue from 'vue' + +import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent' + +import Comment from '../components/Comment' +import getComments, { DEFAULT_LIMIT } from '../services/GetComments' +import cancelableRequest from '../utils/cancelableRequest' + +Vue.use(VTooltip) + +export default { + name: 'Comments', + + components: { + // Avatar, + Comment, + EmptyContent, + }, + + data() { + return { + error: '', + loading: false, + done: false, + + ressourceId: null, + offset: 0, + comments: [], + + cancelRequest: () => {}, + + editorData: { + actorDisplayName: getCurrentUser().displayName, + actorId: getCurrentUser().uid, + key: 'editor', + }, + + Comment, + } + }, + + computed: { + hasComments() { + return this.comments.length > 0 + }, + isFirstLoading() { + return this.loading && this.offset === 0 + }, + }, + + methods: { + /** + * Update current ressourceId and fetch new data + * @param {Number} ressourceId the current ressourceId (fileId...) + */ + async update(ressourceId) { + this.ressourceId = ressourceId + this.resetState() + this.getComments() + }, + + /** + * Ran when the bottom of the tab is reached + */ + onScrollBottomReached() { + /** + * Do not fetch more if we: + * - are showing an error + * - already fetched everything + * - are currently loading + */ + if (this.error || this.done || this.loading) { + return + } + this.getComments() + }, + + /** + * Get the existing shares infos + */ + async getComments() { + // Cancel any ongoing request + this.cancelRequest('cancel') + + try { + this.loading = true + this.error = '' + + // Init cancellable request + const { request, cancel } = cancelableRequest(getComments) + this.cancelRequest = cancel + + // Fetch comments + const comments = await request({ + commentsType: this.commentsType, + ressourceId: this.ressourceId, + }, { offset: this.offset }) + + this.logger.debug(`Processed ${comments.length} comments`, { comments }) + + // We received less than the requested amount, + // we're done fetching comments + if (comments.length < DEFAULT_LIMIT) { + this.done = true + } + + // Insert results + this.comments.push(...comments) + + // Increase offset for next fetch + this.offset += DEFAULT_LIMIT + } catch (error) { + if (error.message === 'cancel') { + return + } + // Reverting offset + this.error = t('comments', 'Unable to load the comments list') + console.error('Error loading the comments list', error) + } finally { + this.loading = false + } + }, + + /** + * Autocomplete @mentions + * @param {string} search the query + * @param {Function} callback the callback to process the results with + */ + async autoComplete(search, callback) { + const results = await axios.get(generateOcsUrl('core', 2) + 'autocomplete/get', { + params: { + search, + itemType: 'files', + itemId: this.ressourceId, + sorter: 'commenters|share-recipients', + limit: OC.appConfig?.comments?.maxAutoCompleteResults || 25, + }, + }) + return callback(results.data.ocs.data) + }, + + /** + * Add newly created comment to the list + * @param {Object} comment the new comment + */ + onNewComment(comment) { + this.comments.unshift(comment) + }, + + /** + * Remove deleted comment from the list + * @param {number} id the deleted comment + */ + onDelete(id) { + const index = this.comments.findIndex(comment => comment.id === id) + if (index > -1) { + this.comments.splice(index, 1) + } else { + console.error('Could not find the deleted comment in the list', id) + } + }, + + /** + * Reset the current view to its default state + */ + resetState() { + this.error = '' + this.loading = false + this.done = false + this.offset = 0 + this.comments = [] + }, + }, +} +</script> + +<style lang="scss" scoped> +.comments { + // Do not add emptycontent top margin + &__error{ + margin-top: 0; + } + + &__info { + height: 60px; + color: var(--color-text-maxcontrast); + text-align: center; + line-height: 60px; + } +} +</style> |