You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Comments.vue 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. <!--
  2. - @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
  3. -
  4. - @author John Molakvoæ <skjnldsv@protonmail.com>
  5. -
  6. - @license GNU AGPL version 3 or any later version
  7. -
  8. - This program is free software: you can redistribute it and/or modify
  9. - it under the terms of the GNU Affero General Public License as
  10. - published by the Free Software Foundation, either version 3 of the
  11. - License, or (at your option) any later version.
  12. -
  13. - This program is distributed in the hope that it will be useful,
  14. - but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. - GNU Affero General Public License for more details.
  17. -
  18. - You should have received a copy of the GNU Affero General Public License
  19. - along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. -
  21. -->
  22. <template>
  23. <div class="comments" :class="{ 'icon-loading': isFirstLoading }">
  24. <!-- Editor -->
  25. <Comment v-bind="editorData"
  26. :auto-complete="autoComplete"
  27. :editor="true"
  28. :ressource-id="ressourceId"
  29. class="comments__writer"
  30. @new="onNewComment" />
  31. <template v-if="!isFirstLoading">
  32. <EmptyContent v-if="!hasComments && done" icon="icon-comment">
  33. {{ t('comments', 'No comments yet, start the conversation!') }}
  34. </EmptyContent>
  35. <!-- Comments -->
  36. <Comment v-for="comment in comments"
  37. v-else
  38. :key="comment.id"
  39. v-bind="comment"
  40. :auto-complete="autoComplete"
  41. :ressource-id="ressourceId"
  42. :message.sync="comment.message"
  43. class="comments__list"
  44. @delete="onDelete" />
  45. <!-- Loading more message -->
  46. <div v-if="loading && !isFirstLoading" class="comments__info icon-loading" />
  47. <div v-else-if="hasComments && done" class="comments__info">
  48. {{ t('comments', 'No more messages') }}
  49. </div>
  50. <!-- Error message -->
  51. <EmptyContent v-else-if="error" class="comments__error" icon="icon-error">
  52. {{ error }}
  53. <template #desc>
  54. <button icon="icon-history" @click="getComments">
  55. {{ t('comments', 'Retry') }}
  56. </button>
  57. </template>
  58. </EmptyContent>
  59. </template>
  60. </div>
  61. </template>
  62. <script>
  63. import { generateOcsUrl } from '@nextcloud/router'
  64. import { getCurrentUser } from '@nextcloud/auth'
  65. import axios from '@nextcloud/axios'
  66. import VTooltip from 'v-tooltip'
  67. import Vue from 'vue'
  68. import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
  69. import Comment from '../components/Comment'
  70. import getComments, { DEFAULT_LIMIT } from '../services/GetComments'
  71. import cancelableRequest from '../utils/cancelableRequest'
  72. Vue.use(VTooltip)
  73. export default {
  74. name: 'Comments',
  75. components: {
  76. // Avatar,
  77. Comment,
  78. EmptyContent,
  79. },
  80. data() {
  81. return {
  82. error: '',
  83. loading: false,
  84. done: false,
  85. ressourceId: null,
  86. offset: 0,
  87. comments: [],
  88. cancelRequest: () => {},
  89. editorData: {
  90. actorDisplayName: getCurrentUser().displayName,
  91. actorId: getCurrentUser().uid,
  92. key: 'editor',
  93. },
  94. Comment,
  95. }
  96. },
  97. computed: {
  98. hasComments() {
  99. return this.comments.length > 0
  100. },
  101. isFirstLoading() {
  102. return this.loading && this.offset === 0
  103. },
  104. },
  105. methods: {
  106. /**
  107. * Update current ressourceId and fetch new data
  108. * @param {Number} ressourceId the current ressourceId (fileId...)
  109. */
  110. async update(ressourceId) {
  111. this.ressourceId = ressourceId
  112. this.resetState()
  113. this.getComments()
  114. },
  115. /**
  116. * Ran when the bottom of the tab is reached
  117. */
  118. onScrollBottomReached() {
  119. /**
  120. * Do not fetch more if we:
  121. * - are showing an error
  122. * - already fetched everything
  123. * - are currently loading
  124. */
  125. if (this.error || this.done || this.loading) {
  126. return
  127. }
  128. this.getComments()
  129. },
  130. /**
  131. * Get the existing shares infos
  132. */
  133. async getComments() {
  134. // Cancel any ongoing request
  135. this.cancelRequest('cancel')
  136. try {
  137. this.loading = true
  138. this.error = ''
  139. // Init cancellable request
  140. const { request, cancel } = cancelableRequest(getComments)
  141. this.cancelRequest = cancel
  142. // Fetch comments
  143. const comments = await request({
  144. commentsType: this.commentsType,
  145. ressourceId: this.ressourceId,
  146. }, { offset: this.offset })
  147. this.logger.debug(`Processed ${comments.length} comments`, { comments })
  148. // We received less than the requested amount,
  149. // we're done fetching comments
  150. if (comments.length < DEFAULT_LIMIT) {
  151. this.done = true
  152. }
  153. // Insert results
  154. this.comments.push(...comments)
  155. // Increase offset for next fetch
  156. this.offset += DEFAULT_LIMIT
  157. } catch (error) {
  158. if (error.message === 'cancel') {
  159. return
  160. }
  161. this.error = t('comments', 'Unable to load the comments list')
  162. console.error('Error loading the comments list', error)
  163. } finally {
  164. this.loading = false
  165. }
  166. },
  167. /**
  168. * Autocomplete @mentions
  169. * @param {string} search the query
  170. * @param {Function} callback the callback to process the results with
  171. */
  172. async autoComplete(search, callback) {
  173. const results = await axios.get(generateOcsUrl('core', 2) + 'autocomplete/get', {
  174. params: {
  175. search,
  176. itemType: 'files',
  177. itemId: this.ressourceId,
  178. sorter: 'commenters|share-recipients',
  179. limit: OC.appConfig?.comments?.maxAutoCompleteResults || 25,
  180. },
  181. })
  182. return callback(results.data.ocs.data)
  183. },
  184. /**
  185. * Add newly created comment to the list
  186. * @param {Object} comment the new comment
  187. */
  188. onNewComment(comment) {
  189. this.comments.unshift(comment)
  190. },
  191. /**
  192. * Remove deleted comment from the list
  193. * @param {number} id the deleted comment
  194. */
  195. onDelete(id) {
  196. const index = this.comments.findIndex(comment => comment.id === id)
  197. if (index > -1) {
  198. this.comments.splice(index, 1)
  199. } else {
  200. console.error('Could not find the deleted comment in the list', id)
  201. }
  202. },
  203. /**
  204. * Reset the current view to its default state
  205. */
  206. resetState() {
  207. this.error = ''
  208. this.loading = false
  209. this.done = false
  210. this.offset = 0
  211. this.comments = []
  212. },
  213. },
  214. }
  215. </script>
  216. <style lang="scss" scoped>
  217. .comments {
  218. // Do not add emptycontent top margin
  219. &__error{
  220. margin-top: 0;
  221. }
  222. &__info {
  223. height: 60px;
  224. color: var(--color-text-maxcontrast);
  225. text-align: center;
  226. line-height: 60px;
  227. }
  228. }
  229. </style>