Timeline.vue 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819
  1. <template>
  2. <div class="timeline-section-component">
  3. <div v-if="!isLoaded">
  4. <status-placeholder />
  5. <status-placeholder />
  6. <status-placeholder />
  7. <status-placeholder />
  8. </div>
  9. <div v-else>
  10. <transition name="fade">
  11. <div v-if="showReblogBanner && getScope() === 'home'" class="card bg-g-amin card-body shadow-sm mb-3" style="border-radius: 15px;">
  12. <div class="d-flex justify-content-around align-items-center">
  13. <div class="flex-grow-1 ft-std">
  14. <h2 class="font-weight-bold text-white mb-0">Introducing Reblogs in feeds</h2>
  15. <hr />
  16. <p class="lead text-white mb-0">
  17. See reblogs from accounts you follow in your home feed!
  18. </p>
  19. <p class="text-white small mb-1" style="opacity:0.6">
  20. You can disable reblogs in feeds on the Timeline Settings page.
  21. </p>
  22. <hr />
  23. <div class="d-flex">
  24. <button class="btn btn-light rounded-pill font-weight-bold btn-block mr-2" @click.prevent="enableReblogs()">
  25. <template v-if="!enablingReblogs">Show reblogs in home feed</template>
  26. <b-spinner small v-else />
  27. </button>
  28. <button class="btn btn-outline-light rounded-pill font-weight-bold px-5" @click.prevent="hideReblogs()">Hide</button>
  29. </div>
  30. </div>
  31. </div>
  32. </div>
  33. </transition>
  34. <status
  35. v-for="(status, index) in feed"
  36. :key="'pf_feed:' + status.id + ':idx:' + index + ':fui:' + forceUpdateIdx"
  37. :status="status"
  38. :profile="profile"
  39. v-on:like="likeStatus(index)"
  40. v-on:unlike="unlikeStatus(index)"
  41. v-on:share="shareStatus(index)"
  42. v-on:unshare="unshareStatus(index)"
  43. v-on:menu="openContextMenu(index)"
  44. v-on:counter-change="counterChange(index, $event)"
  45. v-on:likes-modal="openLikesModal(index)"
  46. v-on:shares-modal="openSharesModal(index)"
  47. v-on:follow="follow(index)"
  48. v-on:unfollow="unfollow(index)"
  49. v-on:comment-likes-modal="openCommentLikesModal"
  50. v-on:handle-report="handleReport"
  51. v-on:bookmark="handleBookmark(index)"
  52. v-on:mod-tools="handleModTools(index)"
  53. />
  54. <div v-if="showLoadMore" class="text-center">
  55. <button
  56. class="btn btn-primary rounded-pill font-weight-bold"
  57. @click="tryToLoadMore">
  58. Load more
  59. </button>
  60. </div>
  61. <div v-if="canLoadMore">
  62. <intersect @enter="enterIntersect">
  63. <status-placeholder style="margin-bottom: 10rem;"/>
  64. </intersect>
  65. </div>
  66. <div v-if="!isLoaded && feed.length && endFeedReached" style="margin-bottom: 50vh">
  67. <div class="card card-body shadow-sm mb-3" style="border-radius: 15px;">
  68. <p class="display-4 text-center">✨</p>
  69. <p class="lead mb-0 text-center">You have reached the end of this feed</p>
  70. </div>
  71. </div>
  72. <timeline-onboarding
  73. v-if="scope == 'home' && !feed.length"
  74. :profile="profile"
  75. v-on:update-profile="updateProfile" />
  76. <empty-timeline v-if="isLoaded && scope !== 'home' && !feed.length" />
  77. </div>
  78. <context-menu
  79. v-if="showMenu"
  80. ref="contextMenu"
  81. :status="feed[postIndex]"
  82. :profile="profile"
  83. v-on:moderate="commitModeration"
  84. v-on:delete="deletePost"
  85. v-on:report-modal="handleReport"
  86. v-on:edit="handleEdit"
  87. v-on:muted="handleMuted"
  88. v-on:unfollow="handleUnfollow"
  89. />
  90. <likes-modal
  91. v-if="showLikesModal"
  92. ref="likesModal"
  93. :status="likesModalPost"
  94. :profile="profile"
  95. />
  96. <shares-modal
  97. v-if="showSharesModal"
  98. ref="sharesModal"
  99. :status="sharesModalPost"
  100. :profile="profile"
  101. />
  102. <report-modal
  103. ref="reportModal"
  104. :key="reportedStatusId"
  105. :status="reportedStatus"
  106. />
  107. <post-edit-modal
  108. ref="editModal"
  109. v-on:update="mergeUpdatedPost"
  110. />
  111. </div>
  112. </template>
  113. <script type="text/javascript">
  114. import StatusPlaceholder from './../partials/StatusPlaceholder.vue';
  115. import Status from './../partials/TimelineStatus.vue';
  116. import Intersect from 'vue-intersect';
  117. import ContextMenu from './../partials/post/ContextMenu.vue';
  118. import LikesModal from './../partials/post/LikeModal.vue';
  119. import SharesModal from './../partials/post/ShareModal.vue';
  120. import ReportModal from './../partials/modal/ReportPost.vue';
  121. import EmptyTimeline from './../partials/placeholders/EmptyTimeline.vue'
  122. import TimelineOnboarding from './../partials/placeholders/TimelineOnboarding.vue'
  123. import PostEditModal from './../partials/post/PostEditModal.vue';
  124. export default {
  125. props: {
  126. scope: {
  127. type: String,
  128. default: "home"
  129. },
  130. profile: {
  131. type: Object
  132. },
  133. refresh: {
  134. type: Boolean,
  135. default: false
  136. }
  137. },
  138. components: {
  139. "intersect": Intersect,
  140. "status-placeholder": StatusPlaceholder,
  141. "status": Status,
  142. "context-menu": ContextMenu,
  143. "likes-modal": LikesModal,
  144. "shares-modal": SharesModal,
  145. "report-modal": ReportModal,
  146. "empty-timeline": EmptyTimeline,
  147. "timeline-onboarding": TimelineOnboarding,
  148. "post-edit-modal": PostEditModal
  149. },
  150. data() {
  151. return {
  152. settings: [],
  153. isLoaded: false,
  154. feed: [],
  155. ids: [],
  156. max_id: 0,
  157. canLoadMore: true,
  158. showLoadMore: false,
  159. loadMoreTimeout: undefined,
  160. loadMoreAttempts: 0,
  161. isFetchingMore: false,
  162. endFeedReached: false,
  163. postIndex: 0,
  164. showMenu: false,
  165. showLikesModal: false,
  166. likesModalPost: {},
  167. showReportModal: false,
  168. reportedStatus: {},
  169. reportedStatusId: 0,
  170. showSharesModal: false,
  171. sharesModalPost: {},
  172. forceUpdateIdx: 0,
  173. showReblogBanner: false,
  174. enablingReblogs: false,
  175. baseApi: '/api/v1/timelines/',
  176. }
  177. },
  178. mounted() {
  179. if(window.App.config.features.hasOwnProperty('timelines')) {
  180. if(this.scope == 'local' && !window.App.config.features.timelines.local) {
  181. swal('Error', 'Cannot load this timeline', 'error');
  182. return;
  183. };
  184. if(this.scope == 'network' && !window.App.config.features.timelines.network) {
  185. swal('Error', 'Cannot load this timeline', 'error');
  186. return;
  187. };
  188. }
  189. if(window.App.config.ab.hasOwnProperty('cached_home_timeline')) {
  190. const cht = window.App.config.ab.cached_home_timeline == true;
  191. this.baseApi = cht ? '/api/v1/timelines/' : '/api/v1/timelines/';
  192. }
  193. this.fetchSettings();
  194. },
  195. methods: {
  196. getScope() {
  197. switch(this.scope) {
  198. case 'local':
  199. return 'public'
  200. break;
  201. case 'global':
  202. return 'network'
  203. break;
  204. default:
  205. return 'home';
  206. break;
  207. }
  208. },
  209. fetchSettings() {
  210. axios.get('/api/pixelfed/v1/web/settings')
  211. .then(res => {
  212. this.settings = res.data;
  213. if(!res.data) {
  214. this.showReblogBanner = true;
  215. } else {
  216. if(res.data.hasOwnProperty('hide_reblog_banner')) {
  217. } else if(res.data.hasOwnProperty('enable_reblogs')) {
  218. if(!res.data.enable_reblogs) {
  219. this.showReblogBanner = true;
  220. }
  221. } else {
  222. this.showReblogBanner = true;
  223. }
  224. }
  225. })
  226. .finally(() => {
  227. this.fetchTimeline();
  228. })
  229. },
  230. fetchTimeline(scrollToTop = false) {
  231. let url, params;
  232. if(this.getScope() === 'home' && this.settings && this.settings.hasOwnProperty('enable_reblogs') && this.settings.enable_reblogs) {
  233. url = this.baseApi + `home`;
  234. params = {
  235. '_pe': 1,
  236. max_id: this.max_id,
  237. limit: 6,
  238. include_reblogs: true,
  239. }
  240. } else {
  241. url = this.baseApi + this.getScope();
  242. if(this.max_id === 0) {
  243. params = {
  244. min_id: 1,
  245. limit: 6,
  246. '_pe': 1,
  247. }
  248. } else {
  249. params = {
  250. max_id: this.max_id,
  251. limit: 6,
  252. '_pe': 1,
  253. }
  254. }
  255. }
  256. if(this.getScope() === 'network') {
  257. params.remote = true;
  258. url = this.baseApi + `public`;
  259. }
  260. axios.get(url, {
  261. params: params
  262. }).then(res => {
  263. let ids = res.data.map(p => {
  264. if(p && p.hasOwnProperty('relationship')) {
  265. this.$store.commit('updateRelationship', [p.relationship]);
  266. }
  267. return p.id
  268. });
  269. this.isLoaded = true;
  270. if(res.data.length == 0) {
  271. return;
  272. }
  273. this.ids = ids;
  274. this.max_id = Math.min(...ids);
  275. this.feed = res.data;
  276. if(res.data.length < 4) {
  277. this.canLoadMore = false;
  278. this.showLoadMore = true;
  279. }
  280. })
  281. .then(() => {
  282. if(scrollToTop) {
  283. this.$nextTick(() => {
  284. window.scrollTo({
  285. top: 0,
  286. left: 0,
  287. behavior: 'smooth'
  288. });
  289. this.$emit('refreshed');
  290. });
  291. }
  292. })
  293. },
  294. enterIntersect() {
  295. if(this.isFetchingMore) {
  296. return;
  297. }
  298. this.isFetchingMore = true;
  299. let url, params;
  300. if(this.getScope() === 'home' && this.settings && this.settings.hasOwnProperty('enable_reblogs') && this.settings.enable_reblogs) {
  301. url = this.baseApi + `home`;
  302. params = {
  303. '_pe': 1,
  304. max_id: this.max_id,
  305. limit: 6,
  306. include_reblogs: true,
  307. }
  308. } else {
  309. url = this.baseApi + this.getScope();
  310. params = {
  311. max_id: this.max_id,
  312. limit: 6,
  313. '_pe': 1,
  314. }
  315. }
  316. if(this.getScope() === 'network') {
  317. params.remote = true;
  318. url = this.baseApi + `public`;
  319. }
  320. axios.get(url, {
  321. params: params
  322. }).then(res => {
  323. if(!res.data.length) {
  324. this.endFeedReached = true;
  325. this.canLoadMore = false;
  326. this.isFetchingMore = false;
  327. }
  328. setTimeout(() => {
  329. res.data.forEach(p => {
  330. if(this.ids.indexOf(p.id) == -1) {
  331. if(this.max_id > p.id) {
  332. this.max_id = p.id;
  333. }
  334. this.ids.push(p.id);
  335. this.feed.push(p);
  336. if(p && p.hasOwnProperty('relationship')) {
  337. this.$store.commit('updateRelationship', [p.relationship]);
  338. }
  339. }
  340. });
  341. this.isFetchingMore = false;
  342. }, 100);
  343. });
  344. },
  345. tryToLoadMore() {
  346. this.loadMoreAttempts++;
  347. if(this.loadMoreAttempts >= 3) {
  348. this.showLoadMore = false;
  349. }
  350. this.showLoadMore = false;
  351. this.canLoadMore = true;
  352. this.loadMoreTimeout = setTimeout(() => {
  353. this.canLoadMore = false;
  354. this.showLoadMore = true;
  355. }, 5000);
  356. },
  357. likeStatus(index) {
  358. let status = this.feed[index];
  359. if(status.reblog) {
  360. status = status.reblog;
  361. let state = status.favourited;
  362. let count = status.favourites_count;
  363. this.feed[index].reblog.favourites_count = count + 1;
  364. this.feed[index].reblog.favourited = !status.favourited;
  365. } else {
  366. let state = status.favourited;
  367. let count = status.favourites_count;
  368. this.feed[index].favourites_count = count + 1;
  369. this.feed[index].favourited = !status.favourited;
  370. }
  371. axios.post('/api/v1/statuses/' + status.id + '/favourite')
  372. .then(res => {
  373. //
  374. }).catch(err => {
  375. if(status.reblog) {
  376. this.feed[index].reblog.favourites_count = count;
  377. this.feed[index].reblog.favourited = false;
  378. } else {
  379. this.feed[index].favourites_count = count;
  380. this.feed[index].favourited = false;
  381. }
  382. let el = document.createElement('p');
  383. el.classList.add('text-left');
  384. el.classList.add('mb-0');
  385. el.innerHTML = '<span class="lead">We limit certain interactions to keep our community healthy and it appears that you have reached that limit. <span class="font-weight-bold">Please try again later.</span></span>';
  386. let wrapper = document.createElement('div');
  387. wrapper.appendChild(el);
  388. if(err.response.status === 429) {
  389. swal({
  390. title: 'Too many requests',
  391. content: wrapper,
  392. icon: 'warning',
  393. buttons: {
  394. // moreInfo: {
  395. // text: "Contact a human",
  396. // visible: true,
  397. // value: "more",
  398. // className: "text-lighter bg-transparent border"
  399. // },
  400. confirm: {
  401. text: "OK",
  402. value: false,
  403. visible: true,
  404. className: "bg-transparent primary",
  405. closeModal: true
  406. }
  407. }
  408. })
  409. .then((val) => {
  410. if(val == 'more') {
  411. location.href = '/site/contact'
  412. }
  413. return;
  414. });
  415. }
  416. })
  417. },
  418. unlikeStatus(index) {
  419. let status = this.feed[index];
  420. if(status.reblog) {
  421. status = status.reblog;
  422. let state = status.favourited;
  423. let count = status.favourites_count;
  424. this.feed[index].reblog.favourites_count = count - 1;
  425. this.feed[index].reblog.favourited = !status.favourited;
  426. } else {
  427. let state = status.favourited;
  428. let count = status.favourites_count;
  429. this.feed[index].favourites_count = count - 1;
  430. this.feed[index].favourited = !status.favourited;
  431. }
  432. axios.post('/api/v1/statuses/' + status.id + '/unfavourite')
  433. .then(res => {
  434. //
  435. }).catch(err => {
  436. if(status.reblog && status.pf_type == 'share') {
  437. this.feed[index].reblog.favourites_count = count;
  438. this.feed[index].reblog.favourited = false;
  439. } else {
  440. this.feed[index].favourites_count = count;
  441. this.feed[index].favourited = false;
  442. }
  443. })
  444. },
  445. openContextMenu(idx) {
  446. this.postIndex = idx;
  447. this.showMenu = true;
  448. this.$nextTick(() => {
  449. this.$refs.contextMenu.open();
  450. });
  451. },
  452. handleModTools(idx) {
  453. this.postIndex = idx;
  454. this.showMenu = true;
  455. this.$nextTick(() => {
  456. this.$refs.contextMenu.openModMenu();
  457. });
  458. },
  459. openLikesModal(idx) {
  460. this.postIndex = idx;
  461. let post = this.feed[this.postIndex];
  462. this.likesModalPost = post.reblog ? post.reblog : post;
  463. this.showLikesModal = true;
  464. this.$nextTick(() => {
  465. this.$refs.likesModal.open();
  466. });
  467. },
  468. openSharesModal(idx) {
  469. this.postIndex = idx;
  470. let post = this.feed[this.postIndex];
  471. this.sharesModalPost = post.reblog ? post.reblog : post;
  472. this.showSharesModal = true;
  473. this.$nextTick(() => {
  474. this.$refs.sharesModal.open();
  475. });
  476. },
  477. commitModeration(type) {
  478. let idx = this.postIndex;
  479. switch(type) {
  480. case 'addcw':
  481. this.feed[idx].sensitive = true;
  482. break;
  483. case 'remcw':
  484. this.feed[idx].sensitive = false;
  485. break;
  486. case 'unlist':
  487. this.feed.splice(idx, 1);
  488. break;
  489. case 'spammer':
  490. let id = this.feed[idx].account.id;
  491. this.feed = this.feed.filter(post => {
  492. return post.account.id != id;
  493. });
  494. break;
  495. }
  496. },
  497. deletePost() {
  498. this.feed.splice(this.postIndex, 1);
  499. this.forceUpdateIdx++;
  500. },
  501. counterChange(index, type) {
  502. let post = this.feed[index];
  503. switch(type) {
  504. case 'comment-increment':
  505. if(post.reblog != null) {
  506. this.feed[index].reblog.reply_count = this.feed[index].reblog.reply_count + 1;
  507. } else {
  508. this.feed[index].reply_count = this.feed[index].reply_count + 1;
  509. }
  510. break;
  511. case 'comment-decrement':
  512. if(post.reblog != null) {
  513. this.feed[index].reblog.reply_count = this.feed[index].reblog.reply_count - 1;
  514. } else {
  515. this.feed[index].reply_count = this.feed[index].reply_count - 1;
  516. }
  517. break;
  518. }
  519. },
  520. openCommentLikesModal(post) {
  521. if(post.reblog != null) {
  522. this.likesModalPost = post.reblog;
  523. } else {
  524. this.likesModalPost = post;
  525. }
  526. this.showLikesModal = true;
  527. this.$nextTick(() => {
  528. this.$refs.likesModal.open();
  529. });
  530. },
  531. shareStatus(index) {
  532. let status = this.feed[index];
  533. if(status.reblog) {
  534. status = status.reblog;
  535. let state = status.reblogged;
  536. let count = status.reblogs_count;
  537. this.feed[index].reblog.reblogs_count = count + 1;
  538. this.feed[index].reblog.reblogged = !status.reblogged;
  539. } else {
  540. let state = status.reblogged;
  541. let count = status.reblogs_count;
  542. this.feed[index].reblogs_count = count + 1;
  543. this.feed[index].reblogged = !status.reblogged;
  544. }
  545. axios.post('/api/v1/statuses/' + status.id + '/reblog')
  546. .then(res => {
  547. //
  548. }).catch(err => {
  549. if(status.reblog) {
  550. this.feed[index].reblog.reblogs_count = count;
  551. this.feed[index].reblog.reblogged = false;
  552. } else {
  553. this.feed[index].reblogs_count = count;
  554. this.feed[index].reblogged = false;
  555. }
  556. })
  557. },
  558. unshareStatus(index) {
  559. let status = this.feed[index];
  560. if(status.reblog) {
  561. status = status.reblog;
  562. let state = status.reblogged;
  563. let count = status.reblogs_count;
  564. this.feed[index].reblog.reblogs_count = count - 1;
  565. this.feed[index].reblog.reblogged = !status.reblogged;
  566. } else {
  567. let state = status.reblogged;
  568. let count = status.reblogs_count;
  569. this.feed[index].reblogs_count = count - 1;
  570. this.feed[index].reblogged = !status.reblogged;
  571. }
  572. axios.post('/api/v1/statuses/' + status.id + '/unreblog')
  573. .then(res => {
  574. //
  575. }).catch(err => {
  576. if(status.reblog) {
  577. this.feed[index].reblog.reblogs_count = count;
  578. this.feed[index].reblog.reblogged = false;
  579. } else {
  580. this.feed[index].reblogs_count = count;
  581. this.feed[index].reblogged = false;
  582. }
  583. })
  584. },
  585. handleReport(post) {
  586. this.reportedStatusId = post.id;
  587. this.$nextTick(() => {
  588. this.reportedStatus = post;
  589. this.$refs.reportModal.open();
  590. });
  591. },
  592. handleBookmark(index) {
  593. let p = this.feed[index];
  594. if(p.reblog) {
  595. p = p.reblog;
  596. }
  597. axios.post('/i/bookmark', {
  598. item: p.id
  599. })
  600. .then(res => {
  601. if(this.feed[index].reblog) {
  602. this.feed[index].reblog.bookmarked = !p.bookmarked;
  603. } else {
  604. this.feed[index].bookmarked = !p.bookmarked;
  605. }
  606. })
  607. .catch(err => {
  608. // this.feed[index].bookmarked = false;
  609. this.$bvToast.toast('Cannot bookmark post at this time.', {
  610. title: 'Bookmark Error',
  611. variant: 'danger',
  612. autoHideDelay: 5000
  613. });
  614. });
  615. },
  616. follow(index) {
  617. if(this.feed[index].reblog) {
  618. axios.post('/api/v1/accounts/' + this.feed[index].reblog.account.id + '/follow')
  619. .then(res => {
  620. this.$store.commit('updateRelationship', [res.data]);
  621. this.updateProfile({ following_count: this.profile.following_count + 1 });
  622. this.feed[index].reblog.account.followers_count = this.feed[index].reblog.account.followers_count + 1;
  623. }).catch(err => {
  624. swal('Oops!', 'An error occured when attempting to follow this account.', 'error');
  625. this.feed[index].reblog.relationship.following = false;
  626. });
  627. } else {
  628. axios.post('/api/v1/accounts/' + this.feed[index].account.id + '/follow')
  629. .then(res => {
  630. this.$store.commit('updateRelationship', [res.data]);
  631. this.updateProfile({ following_count: this.profile.following_count + 1 });
  632. this.feed[index].account.followers_count = this.feed[index].account.followers_count + 1;
  633. }).catch(err => {
  634. swal('Oops!', 'An error occured when attempting to follow this account.', 'error');
  635. this.feed[index].relationship.following = false;
  636. });
  637. }
  638. },
  639. unfollow(index) {
  640. if(this.feed[index].reblog) {
  641. axios.post('/api/v1/accounts/' + this.feed[index].reblog.account.id + '/unfollow')
  642. .then(res => {
  643. this.$store.commit('updateRelationship', [res.data]);
  644. this.updateProfile({ following_count: this.profile.following_count - 1 });
  645. this.feed[index].reblog.account.followers_count = this.feed[index].reblog.account.followers_count - 1;
  646. }).catch(err => {
  647. swal('Oops!', 'An error occured when attempting to unfollow this account.', 'error');
  648. this.feed[index].reblog.relationship.following = true;
  649. });
  650. } else {
  651. axios.post('/api/v1/accounts/' + this.feed[index].account.id + '/unfollow')
  652. .then(res => {
  653. this.$store.commit('updateRelationship', [res.data]);
  654. this.updateProfile({ following_count: this.profile.following_count - 1 });
  655. this.feed[index].account.followers_count = this.feed[index].account.followers_count - 1;
  656. }).catch(err => {
  657. swal('Oops!', 'An error occured when attempting to unfollow this account.', 'error');
  658. this.feed[index].relationship.following = true;
  659. });
  660. }
  661. },
  662. updateProfile(delta) {
  663. this.$emit('update-profile', delta);
  664. },
  665. handleRefresh() {
  666. this.isLoaded = false;
  667. this.feed = [];
  668. this.ids = [];
  669. this.max_id = 0;
  670. this.canLoadMore = true;
  671. this.showLoadMore = false;
  672. this.loadMoreTimeout = undefined;
  673. this.loadMoreAttempts = 0;
  674. this.isFetchingMore = false;
  675. this.endFeedReached = false;
  676. this.postIndex = 0;
  677. this.showMenu = false;
  678. this.showLikesModal = false;
  679. this.likesModalPost = {};
  680. this.showReportModal = false;
  681. this.reportedStatus = {};
  682. this.reportedStatusId = 0;
  683. this.showSharesModal = false;
  684. this.sharesModalPost = {};
  685. this.$nextTick(() => {
  686. this.fetchTimeline(true);
  687. });
  688. },
  689. handleEdit(status) {
  690. this.$refs.editModal.show(status);
  691. },
  692. mergeUpdatedPost(post) {
  693. this.feed = this.feed.map(p => {
  694. if(p.id == post.id) {
  695. p = post;
  696. }
  697. return p;
  698. });
  699. this.$nextTick(() => {
  700. this.forceUpdateIdx++;
  701. });
  702. },
  703. enableReblogs() {
  704. this.enablingReblogs = true;
  705. axios.post('/api/pixelfed/v1/web/settings', {
  706. field: 'enable_reblogs',
  707. value: true
  708. })
  709. .then(res => {
  710. setTimeout(() => {
  711. window.location.reload();
  712. }, 1000);
  713. })
  714. },
  715. hideReblogs() {
  716. this.showReblogBanner = false;
  717. axios.post('/api/pixelfed/v1/web/settings', {
  718. field: 'hide_reblog_banner',
  719. value: true
  720. })
  721. .then(res => {
  722. })
  723. },
  724. handleMuted(post) {
  725. this.feed = this.feed.filter(p => {
  726. return p.account.id !== post.account.id;
  727. });
  728. },
  729. handleUnfollow(post) {
  730. if(this.scope === 'home') {
  731. this.feed = this.feed.filter(p => {
  732. return p.account.id !== post.account.id;
  733. });
  734. }
  735. this.updateProfile({ following_count: this.profile.following_count - 1 });
  736. },
  737. },
  738. watch: {
  739. 'refresh': 'handleRefresh'
  740. },
  741. beforeDestroy() {
  742. clearTimeout(this.loadMoreTimeout);
  743. }
  744. }
  745. </script>