CommentFeed.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. <template>
  2. <div>
  3. <div v-if="loaded">
  4. <div v-if="showReplyForm" class="card card-body shadow-none border bg-light">
  5. <div class="media">
  6. <img :src="profile.avatar" class="rounded-circle border mr-3" width="32px" height="32px">
  7. <div class="media-body">
  8. <div class="reply-form form-group mb-0">
  9. <input v-if="!composeText || composeText.length < 40" class="form-control rounded-pill" placeholder="Add a comment..." v-model="composeText">
  10. <textarea v-else class="form-control" placeholder="Add a comment..." v-model="composeText" rows="4"></textarea>
  11. <div v-if="composeText && composeText.length" class="btn btn-primary btn-sm rounded-pill font-weight-bold px-3" @click="submitComment">
  12. <span v-if="postingComment">
  13. <div class="spinner-border spinner-border-sm" role="status">
  14. <span class="sr-only">Loading...</span>
  15. </div>
  16. </span>
  17. <span v-else>Post</span>
  18. </div>
  19. </div>
  20. <div v-if="composeText" class="reply-options">
  21. <select class="form-control form-control-sm rounded-pill font-weight-bold" v-model="visibility">
  22. <option value="public">Public</option>
  23. <option value="private">Followers Only</option>
  24. </select>
  25. <div class="custom-control custom-switch">
  26. <input type="checkbox" class="custom-control-input" id="sensitive" v-model="sensitive">
  27. <label class="custom-control-label font-weight-bold text-lighter" for="sensitive">
  28. <span class="d-none d-md-inline-block">Sensitive/</span>NSFW
  29. </label>
  30. </div>
  31. <span class="text-muted font-weight-bold small">
  32. {{ composeText.length }} / 500
  33. </span>
  34. </div>
  35. </div>
  36. </div>
  37. </div>
  38. <div class="d-none card card-body shadow-none border rounded-0 border-top-0 bg-light">
  39. <div class="d-flex justify-content-between align-items-center">
  40. <p class="font-weight-bold text-muted mb-0 mr-md-5">
  41. <i class="fas fa-comment mr-1"></i>
  42. {{ formatCount(pagination.total) }}
  43. </p>
  44. <h4 class="font-weight-bold mb-0 text-lighter">Comments</h4>
  45. <div class="form-group mb-0">
  46. <select class="form-control form-control-sm">
  47. <option>New</option>
  48. <option>Oldest</option>
  49. </select>
  50. </div>
  51. </div>
  52. </div>
  53. <status-card v-for="(reply, index) in feed" :key="'replies:'+index" :status="reply" size="small" />
  54. <div v-if="pagination.links.hasOwnProperty('next')" class="card card-body shadow-none rounded-0 border border-top-0 py-3">
  55. <button v-if="loadingMoreComments" class="btn btn-primary" disabled>
  56. <div class="spinner-border spinner-border-sm" role="status">
  57. <span class="sr-only">Loading...</span>
  58. </div>
  59. </button>
  60. <button v-else class="btn btn-primary font-weight-bold" @click="loadMoreComments">Load more comments</button>
  61. </div>
  62. <context-menu
  63. v-if="ctxStatus && profile"
  64. ref="cMenu"
  65. :status="ctxStatus"
  66. :profile="profile"
  67. v-on:status-delete="statusDeleted" />
  68. </div>
  69. <div v-else>
  70. </div>
  71. </div>
  72. </template>
  73. <script type="text/javascript">
  74. import ContextMenu from './ContextMenu.vue';
  75. import StatusCard from './StatusCard.vue';
  76. export default {
  77. props: {
  78. status: {
  79. type: Object,
  80. },
  81. currentProfile: {
  82. type: Object
  83. },
  84. showReplyForm: {
  85. type: Boolean,
  86. default: true
  87. }
  88. },
  89. components: {
  90. "context-menu": ContextMenu,
  91. "status-card": StatusCard
  92. },
  93. data() {
  94. return {
  95. loaded: false,
  96. profile: undefined,
  97. feed: [],
  98. pagination: undefined,
  99. ctxStatus: false,
  100. composeText: null,
  101. visibility: 'public',
  102. sensitive: false,
  103. postingComment: false,
  104. loadingMoreComments: false,
  105. page: 2
  106. }
  107. },
  108. beforeMount() {
  109. this.fetchProfile();
  110. },
  111. mounted() {
  112. // if(this.currentProfile && !this.currentProfile.hasOwnProperty('id')) {
  113. // } else {
  114. // this.profile = this.currentProfile;
  115. // }
  116. },
  117. methods: {
  118. fetchProfile() {
  119. axios.get('/api/pixelfed/v1/accounts/verify_credentials').then(res => {
  120. this.profile = res.data;
  121. });
  122. this.fetchComments();
  123. },
  124. fetchComments() {
  125. let url = '/api/v2/comments/'+this.status.account.id+'/status/'+this.status.id;
  126. axios.get(url)
  127. .then(res => {
  128. this.feed = res.data.data;
  129. this.pagination = res.data.meta.pagination;
  130. this.loaded = true;
  131. }).catch(error => {
  132. this.loaded = true;
  133. if(!error.response) {
  134. } else {
  135. switch(error.response.status) {
  136. case 401:
  137. $('.postCommentsLoader .lds-ring')
  138. .attr('style','width:100%')
  139. .addClass('pt-4 font-weight-bold text-muted')
  140. .text('Please login to view.');
  141. break;
  142. default:
  143. $('.postCommentsLoader .lds-ring')
  144. .attr('style','width:100%')
  145. .addClass('pt-4 font-weight-bold text-muted')
  146. .text('An error occurred, cannot fetch comments. Please try again later.');
  147. break;
  148. }
  149. }
  150. });
  151. },
  152. trimCaption(caption) {
  153. return caption;
  154. },
  155. profileUrl(status) {
  156. return status.url;
  157. },
  158. statusUrl(status) {
  159. return status.url;
  160. },
  161. replyFocus() {
  162. },
  163. likeReply() {
  164. },
  165. timeAgo(ts) {
  166. return App.util.format.timeAgo(ts);
  167. },
  168. statusDeleted() {
  169. },
  170. ctxMenu(index) {
  171. this.ctxStatus = this.feed[index];
  172. setTimeout(() => {
  173. this.$refs.cMenu.open();
  174. }, 300);
  175. },
  176. submitComment() {
  177. this.postingComment = true;
  178. let data = {
  179. item: this.status.id,
  180. comment: this.composeText,
  181. sensitive: this.sensitive
  182. }
  183. let self = this;
  184. axios.post('/i/comment', data)
  185. .then(res => {
  186. self.composeText = null;
  187. let entity = res.data.entity;
  188. self.postingComment = false;
  189. self.feed.unshift(entity);
  190. self.pagination.total++;
  191. }).catch(err => {
  192. swal('Oops!', 'An error occured, please try again later.', 'error');
  193. self.postingComment = false;
  194. })
  195. },
  196. formatCount(i) {
  197. return App.util.format.count(i);
  198. },
  199. loadMoreComments() {
  200. let self = this;
  201. this.loadingMoreComments = true;
  202. let url = '/api/v2/comments/'+this.status.account.id+'/status/'+this.status.id;
  203. axios.get(url, {
  204. params: {
  205. page: this.page
  206. }
  207. }).then(res => {
  208. self.feed.push(...res.data.data);
  209. self.pagination = res.data.meta.pagination;
  210. self.loadingMoreComments = false;
  211. self.page++;
  212. }).catch(error => {
  213. self.loadingMoreComments = false;
  214. });
  215. }
  216. }
  217. }
  218. </script>
  219. <style lang="scss" scoped>
  220. .reply-form {
  221. position:relative;
  222. input {
  223. padding-right: 90px;
  224. }
  225. textarea {
  226. padding-right: 80px;
  227. align-items: center;
  228. }
  229. .btn {
  230. position:absolute;
  231. top: 50%;
  232. transform: translateY(-50%);
  233. right: 6px;
  234. }
  235. }
  236. .reply-options {
  237. display: flex;
  238. justify-content: space-between;
  239. align-items: center;
  240. margin-top: 15px;
  241. .form-control {
  242. max-width: 140px;
  243. }
  244. }
  245. </style>