]> Untitled Git - lemmy-ui.git/blob - src/shared/components/main.tsx
Merge remote-tracking branch 'origin/drone-ci' into drone-ci-dess
[lemmy-ui.git] / src / shared / components / main.tsx
1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
3 import { Subscription } from 'rxjs';
4 import {
5   UserOperation,
6   CommunityFollowerView,
7   GetFollowedCommunitiesResponse,
8   ListCommunities,
9   ListCommunitiesResponse,
10   CommunityView,
11   SortType,
12   GetSiteResponse,
13   ListingType,
14   SiteResponse,
15   GetPostsResponse,
16   PostResponse,
17   PostView,
18   GetPosts,
19   CommentView,
20   GetComments,
21   GetCommentsResponse,
22   CommentResponse,
23   AddAdminResponse,
24   BanUserResponse,
25 } from 'lemmy-js-client';
26 import { DataType, InitialFetchRequest } from '../interfaces';
27 import { WebSocketService, UserService } from '../services';
28 import { PostListings } from './post-listings';
29 import { CommentNodes } from './comment-nodes';
30 import { SortSelect } from './sort-select';
31 import { ListingTypeSelect } from './listing-type-select';
32 import { DataTypeSelect } from './data-type-select';
33 import { SiteForm } from './site-form';
34 import { UserListing } from './user-listing';
35 import { CommunityLink } from './community-link';
36 import { BannerIconHeader } from './banner-icon-header';
37 import {
38   wsJsonToRes,
39   mdToHtml,
40   fetchLimit,
41   toast,
42   getListingTypeFromProps,
43   getPageFromProps,
44   getSortTypeFromProps,
45   getDataTypeFromProps,
46   editCommentRes,
47   saveCommentRes,
48   createCommentLikeRes,
49   createPostLikeFindRes,
50   editPostFindRes,
51   commentsToFlatNodes,
52   setupTippy,
53   notifyPost,
54   setIsoData,
55   wsSubscribe,
56   isBrowser,
57   wsUserOp,
58   setOptionalAuth,
59   wsClient,
60   authField,
61 } from '../utils';
62 import { i18n } from '../i18next';
63 import { T } from 'inferno-i18next';
64 import { HtmlTags } from './html-tags';
65
66 interface MainState {
67   subscribedCommunities: CommunityFollowerView[];
68   trendingCommunities: CommunityView[];
69   siteRes: GetSiteResponse;
70   showEditSite: boolean;
71   loading: boolean;
72   posts: PostView[];
73   comments: CommentView[];
74   listingType: ListingType;
75   dataType: DataType;
76   sort: SortType;
77   page: number;
78 }
79
80 interface MainProps {
81   listingType: ListingType;
82   dataType: DataType;
83   sort: SortType;
84   page: number;
85 }
86
87 interface UrlParams {
88   listingType?: ListingType;
89   dataType?: string;
90   sort?: SortType;
91   page?: number;
92 }
93
94 export class Main extends Component<any, MainState> {
95   private isoData = setIsoData(this.context);
96   private subscription: Subscription;
97   private emptyState: MainState = {
98     subscribedCommunities: [],
99     trendingCommunities: [],
100     siteRes: this.isoData.site_res,
101     showEditSite: false,
102     loading: true,
103     posts: [],
104     comments: [],
105     listingType: getListingTypeFromProps(this.props),
106     dataType: getDataTypeFromProps(this.props),
107     sort: getSortTypeFromProps(this.props),
108     page: getPageFromProps(this.props),
109   };
110
111   constructor(props: any, context: any) {
112     super(props, context);
113
114     this.state = this.emptyState;
115     this.handleEditCancel = this.handleEditCancel.bind(this);
116     this.handleSortChange = this.handleSortChange.bind(this);
117     this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
118     this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
119
120     this.parseMessage = this.parseMessage.bind(this);
121     this.subscription = wsSubscribe(this.parseMessage);
122
123     // Only fetch the data if coming from another route
124     if (this.isoData.path == this.context.router.route.match.url) {
125       if (this.state.dataType == DataType.Post) {
126         this.state.posts = this.isoData.routeData[0].posts;
127       } else {
128         this.state.comments = this.isoData.routeData[0].comments;
129       }
130       this.state.trendingCommunities = this.isoData.routeData[1].communities;
131       if (UserService.Instance.user) {
132         this.state.subscribedCommunities = this.isoData.routeData[2].communities;
133       }
134       this.state.loading = false;
135     } else {
136       this.fetchTrendingCommunities();
137       this.fetchData();
138       if (UserService.Instance.user) {
139         WebSocketService.Instance.send(
140           wsClient.getFollowedCommunities({
141             auth: authField(),
142           })
143         );
144       }
145     }
146
147     setupTippy();
148   }
149
150   fetchTrendingCommunities() {
151     let listCommunitiesForm: ListCommunities = {
152       sort: SortType.Hot,
153       limit: 6,
154       auth: authField(false),
155     };
156     WebSocketService.Instance.send(
157       wsClient.listCommunities(listCommunitiesForm)
158     );
159   }
160
161   componentDidMount() {
162     // This means it hasn't been set up yet
163     if (!this.state.siteRes.site_view) {
164       this.context.router.history.push('/setup');
165     }
166
167     WebSocketService.Instance.send(wsClient.communityJoin({ community_id: 0 }));
168   }
169
170   componentWillUnmount() {
171     if (isBrowser()) {
172       this.subscription.unsubscribe();
173       window.isoData.path = undefined;
174     }
175   }
176
177   static getDerivedStateFromProps(props: any): MainProps {
178     return {
179       listingType: getListingTypeFromProps(props),
180       dataType: getDataTypeFromProps(props),
181       sort: getSortTypeFromProps(props),
182       page: getPageFromProps(props),
183     };
184   }
185
186   static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
187     let pathSplit = req.path.split('/');
188     let dataType: DataType = pathSplit[3]
189       ? DataType[pathSplit[3]]
190       : DataType.Post;
191
192     // TODO figure out auth default_listingType, default_sort_type
193     let type_: ListingType = pathSplit[5]
194       ? ListingType[pathSplit[5]]
195       : UserService.Instance.user
196       ? Object.values(ListingType)[
197           UserService.Instance.user.default_listing_type
198         ]
199       : ListingType.Local;
200     let sort: SortType = pathSplit[7]
201       ? SortType[pathSplit[7]]
202       : UserService.Instance.user
203       ? Object.values(SortType)[UserService.Instance.user.default_sort_type]
204       : SortType.Active;
205
206     let page = pathSplit[9] ? Number(pathSplit[9]) : 1;
207
208     let promises: Promise<any>[] = [];
209
210     if (dataType == DataType.Post) {
211       let getPostsForm: GetPosts = {
212         page,
213         limit: fetchLimit,
214         sort,
215         type_,
216       };
217       setOptionalAuth(getPostsForm, req.auth);
218       promises.push(req.client.getPosts(getPostsForm));
219     } else {
220       let getCommentsForm: GetComments = {
221         page,
222         limit: fetchLimit,
223         sort,
224         type_,
225       };
226       setOptionalAuth(getCommentsForm, req.auth);
227       promises.push(req.client.getComments(getCommentsForm));
228     }
229
230     let trendingCommunitiesForm: ListCommunities = {
231       sort: SortType.Hot,
232       limit: 6,
233     };
234     promises.push(req.client.listCommunities(trendingCommunitiesForm));
235
236     if (req.auth) {
237       promises.push(req.client.getFollowedCommunities({ auth: req.auth }));
238     }
239
240     return promises;
241   }
242
243   componentDidUpdate(_: any, lastState: MainState) {
244     if (
245       lastState.listingType !== this.state.listingType ||
246       lastState.dataType !== this.state.dataType ||
247       lastState.sort !== this.state.sort ||
248       lastState.page !== this.state.page
249     ) {
250       this.setState({ loading: true });
251       this.fetchData();
252     }
253   }
254
255   get documentTitle(): string {
256     return `${
257       this.state.siteRes.site_view
258         ? this.state.siteRes.site_view.site.name
259         : 'Lemmy'
260     }`;
261   }
262
263   render() {
264     return (
265       <div class="container">
266         <HtmlTags
267           title={this.documentTitle}
268           path={this.context.router.route.match.url}
269         />
270         {this.state.siteRes.site_view.site && (
271           <div class="row">
272             <main role="main" class="col-12 col-md-8">
273               {this.posts()}
274             </main>
275             <aside class="col-12 col-md-4">{this.mySidebar()}</aside>
276           </div>
277         )}
278       </div>
279     );
280   }
281
282   mySidebar() {
283     return (
284       <div>
285         {!this.state.loading && (
286           <div>
287             <div class="card border-secondary mb-3">
288               <div class="card-body">
289                 {this.trendingCommunities()}
290                 {this.createCommunityButton()}
291               </div>
292             </div>
293
294             {UserService.Instance.user &&
295               this.state.subscribedCommunities.length > 0 && (
296                 <div class="card border-secondary mb-3">
297                   <div class="card-body">{this.subscribedCommunities()}</div>
298                 </div>
299               )}
300
301             <div class="card border-secondary mb-3">
302               <div class="card-body">{this.sidebar()}</div>
303             </div>
304           </div>
305         )}
306       </div>
307     );
308   }
309
310   createCommunityButton() {
311     return (
312       <Link className="btn btn-secondary btn-block" to="/create_community">
313         {i18n.t('create_a_community')}
314       </Link>
315     );
316   }
317
318   trendingCommunities() {
319     return (
320       <div>
321         <h5>
322           <T i18nKey="trending_communities">
323             #
324             <Link className="text-body" to="/communities">
325               #
326             </Link>
327           </T>
328         </h5>
329         <ul class="list-inline">
330           {this.state.trendingCommunities.map(cv => (
331             <li class="list-inline-item d-inline">
332               <CommunityLink community={cv.community} />
333             </li>
334           ))}
335         </ul>
336       </div>
337     );
338   }
339
340   subscribedCommunities() {
341     return (
342       <div>
343         <h5>
344           <T i18nKey="subscribed_to_communities">
345             #
346             <Link className="text-body" to="/communities">
347               #
348             </Link>
349           </T>
350         </h5>
351         <ul class="list-inline mb-0">
352           {this.state.subscribedCommunities.map(cfv => (
353             <li class="list-inline-item d-inline">
354               <CommunityLink community={cfv.community} />
355             </li>
356           ))}
357         </ul>
358       </div>
359     );
360   }
361
362   sidebar() {
363     let site = this.state.siteRes.site_view.site;
364     return (
365       <div>
366         {!this.state.showEditSite ? (
367           <div>
368             <div class="mb-2">
369               {this.siteName()}
370               {this.adminButtons()}
371             </div>
372             <BannerIconHeader banner={site.banner} />
373             {this.siteInfo()}
374           </div>
375         ) : (
376           <SiteForm site={site} onCancel={this.handleEditCancel} />
377         )}
378       </div>
379     );
380   }
381
382   updateUrl(paramUpdates: UrlParams) {
383     const listingTypeStr = paramUpdates.listingType || this.state.listingType;
384     const dataTypeStr = paramUpdates.dataType || DataType[this.state.dataType];
385     const sortStr = paramUpdates.sort || this.state.sort;
386     const page = paramUpdates.page || this.state.page;
387     this.props.history.push(
388       `/home/data_type/${dataTypeStr}/listing_type/${listingTypeStr}/sort/${sortStr}/page/${page}`
389     );
390   }
391
392   siteInfo() {
393     return (
394       <div>
395         {this.state.siteRes.site_view.site.description &&
396           this.siteDescription()}
397         {this.badges()}
398         {this.admins()}
399       </div>
400     );
401   }
402
403   siteName() {
404     return <h5 class="mb-0">{`${this.documentTitle}`}</h5>;
405   }
406
407   admins() {
408     return (
409       <ul class="mt-1 list-inline small mb-0">
410         <li class="list-inline-item">{i18n.t('admins')}:</li>
411         {this.state.siteRes.admins.map(av => (
412           <li class="list-inline-item">
413             <UserListing user={av.user} />
414           </li>
415         ))}
416       </ul>
417     );
418   }
419
420   badges() {
421     let site_view = this.state.siteRes.site_view;
422     return (
423       <ul class="my-2 list-inline">
424         <li className="list-inline-item badge badge-secondary">
425           {i18n.t('number_online', { count: this.state.siteRes.online })}
426         </li>
427         <li className="list-inline-item badge badge-secondary">
428           {i18n.t('number_of_users', {
429             count: site_view.counts.users,
430           })}
431         </li>
432         <li className="list-inline-item badge badge-secondary">
433           {i18n.t('number_of_communities', {
434             count: site_view.counts.communities,
435           })}
436         </li>
437         <li className="list-inline-item badge badge-secondary">
438           {i18n.t('number_of_posts', {
439             count: site_view.counts.posts,
440           })}
441         </li>
442         <li className="list-inline-item badge badge-secondary">
443           {i18n.t('number_of_comments', {
444             count: site_view.counts.comments,
445           })}
446         </li>
447         <li className="list-inline-item">
448           <Link className="badge badge-secondary" to="/modlog">
449             {i18n.t('modlog')}
450           </Link>
451         </li>
452       </ul>
453     );
454   }
455
456   adminButtons() {
457     return (
458       this.canAdmin && (
459         <ul class="list-inline mb-1 text-muted font-weight-bold">
460           <li className="list-inline-item-action">
461             <span
462               class="pointer"
463               onClick={linkEvent(this, this.handleEditClick)}
464               data-tippy-content={i18n.t('edit')}
465             >
466               <svg class="icon icon-inline">
467                 <use xlinkHref="#icon-edit"></use>
468               </svg>
469             </span>
470           </li>
471         </ul>
472       )
473     );
474   }
475
476   siteDescription() {
477     return (
478       <div
479         className="md-div"
480         dangerouslySetInnerHTML={mdToHtml(
481           this.state.siteRes.site_view.site.description
482         )}
483       />
484     );
485   }
486
487   posts() {
488     return (
489       <div class="main-content-wrapper">
490         {this.state.loading ? (
491           <h5>
492             <svg class="icon icon-spinner spin">
493               <use xlinkHref="#icon-spinner"></use>
494             </svg>
495           </h5>
496         ) : (
497           <div>
498             {this.selects()}
499             {this.listings()}
500             {this.paginator()}
501           </div>
502         )}
503       </div>
504     );
505   }
506
507   listings() {
508     let site = this.state.siteRes.site_view.site;
509     return this.state.dataType == DataType.Post ? (
510       <PostListings
511         posts={this.state.posts}
512         showCommunity
513         removeDuplicates
514         sort={this.state.sort}
515         enableDownvotes={site.enable_downvotes}
516         enableNsfw={site.enable_nsfw}
517       />
518     ) : (
519       <CommentNodes
520         nodes={commentsToFlatNodes(this.state.comments)}
521         noIndent
522         showCommunity
523         sortType={this.state.sort}
524         showContext
525         enableDownvotes={site.enable_downvotes}
526       />
527     );
528   }
529
530   selects() {
531     return (
532       <div className="mb-3">
533         <span class="mr-3">
534           <DataTypeSelect
535             type_={this.state.dataType}
536             onChange={this.handleDataTypeChange}
537           />
538         </span>
539         <span class="mr-3">
540           <ListingTypeSelect
541             type_={this.state.listingType}
542             showLocal={this.showLocal}
543             onChange={this.handleListingTypeChange}
544           />
545         </span>
546         <span class="mr-2">
547           <SortSelect sort={this.state.sort} onChange={this.handleSortChange} />
548         </span>
549         {this.state.listingType == ListingType.All && (
550           <a
551             href={`/feeds/all.xml?sort=${this.state.sort}`}
552             target="_blank"
553             rel="noopener"
554             title="RSS"
555           >
556             <svg class="icon text-muted small">
557               <use xlinkHref="#icon-rss">#</use>
558             </svg>
559           </a>
560         )}
561         {this.state.listingType == ListingType.Local && (
562           <a
563             href={`/feeds/local.xml?sort=${this.state.sort}`}
564             target="_blank"
565             rel="noopener"
566             title="RSS"
567           >
568             <svg class="icon text-muted small">
569               <use xlinkHref="#icon-rss">#</use>
570             </svg>
571           </a>
572         )}
573         {UserService.Instance.user &&
574           this.state.listingType == ListingType.Subscribed && (
575             <a
576               href={`/feeds/front/${UserService.Instance.auth}.xml?sort=${this.state.sort}`}
577               target="_blank"
578               title="RSS"
579               rel="noopener"
580             >
581               <svg class="icon text-muted small">
582                 <use xlinkHref="#icon-rss">#</use>
583               </svg>
584             </a>
585           )}
586       </div>
587     );
588   }
589
590   paginator() {
591     return (
592       <div class="my-2">
593         {this.state.page > 1 && (
594           <button
595             class="btn btn-secondary mr-1"
596             onClick={linkEvent(this, this.prevPage)}
597           >
598             {i18n.t('prev')}
599           </button>
600         )}
601         {this.state.posts.length > 0 && (
602           <button
603             class="btn btn-secondary"
604             onClick={linkEvent(this, this.nextPage)}
605           >
606             {i18n.t('next')}
607           </button>
608         )}
609       </div>
610     );
611   }
612
613   get showLocal(): boolean {
614     return (
615       this.isoData.site_res.federated_instances !== null &&
616       this.isoData.site_res.federated_instances.length > 0
617     );
618   }
619
620   get canAdmin(): boolean {
621     return (
622       UserService.Instance.user &&
623       this.state.siteRes.admins
624         .map(a => a.user.id)
625         .includes(UserService.Instance.user.id)
626     );
627   }
628
629   handleEditClick(i: Main) {
630     i.state.showEditSite = true;
631     i.setState(i.state);
632   }
633
634   handleEditCancel() {
635     this.state.showEditSite = false;
636     this.setState(this.state);
637   }
638
639   nextPage(i: Main) {
640     i.updateUrl({ page: i.state.page + 1 });
641     window.scrollTo(0, 0);
642   }
643
644   prevPage(i: Main) {
645     i.updateUrl({ page: i.state.page - 1 });
646     window.scrollTo(0, 0);
647   }
648
649   handleSortChange(val: SortType) {
650     this.updateUrl({ sort: val, page: 1 });
651     window.scrollTo(0, 0);
652   }
653
654   handleListingTypeChange(val: ListingType) {
655     this.updateUrl({ listingType: val, page: 1 });
656     window.scrollTo(0, 0);
657   }
658
659   handleDataTypeChange(val: DataType) {
660     this.updateUrl({ dataType: DataType[val], page: 1 });
661     window.scrollTo(0, 0);
662   }
663
664   fetchData() {
665     if (this.state.dataType == DataType.Post) {
666       let getPostsForm: GetPosts = {
667         page: this.state.page,
668         limit: fetchLimit,
669         sort: this.state.sort,
670         type_: this.state.listingType,
671         auth: authField(false),
672       };
673       WebSocketService.Instance.send(wsClient.getPosts(getPostsForm));
674     } else {
675       let getCommentsForm: GetComments = {
676         page: this.state.page,
677         limit: fetchLimit,
678         sort: this.state.sort,
679         type_: this.state.listingType,
680         auth: authField(false),
681       };
682       WebSocketService.Instance.send(wsClient.getComments(getCommentsForm));
683     }
684   }
685
686   parseMessage(msg: any) {
687     let op = wsUserOp(msg);
688     if (msg.error) {
689       toast(i18n.t(msg.error), 'danger');
690       return;
691     } else if (msg.reconnect) {
692       WebSocketService.Instance.send(
693         wsClient.communityJoin({ community_id: 0 })
694       );
695       this.fetchData();
696     } else if (op == UserOperation.GetFollowedCommunities) {
697       let data = wsJsonToRes<GetFollowedCommunitiesResponse>(msg).data;
698       this.state.subscribedCommunities = data.communities;
699       this.setState(this.state);
700     } else if (op == UserOperation.ListCommunities) {
701       let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
702       this.state.trendingCommunities = data.communities;
703       this.setState(this.state);
704     } else if (op == UserOperation.EditSite) {
705       let data = wsJsonToRes<SiteResponse>(msg).data;
706       this.state.siteRes.site_view = data.site_view;
707       this.state.showEditSite = false;
708       this.setState(this.state);
709       toast(i18n.t('site_saved'));
710     } else if (op == UserOperation.GetPosts) {
711       let data = wsJsonToRes<GetPostsResponse>(msg).data;
712       this.state.posts = data.posts;
713       this.state.loading = false;
714       this.setState(this.state);
715       setupTippy();
716     } else if (op == UserOperation.CreatePost) {
717       let data = wsJsonToRes<PostResponse>(msg).data;
718
719       // If you're on subscribed, only push it if you're subscribed.
720       if (this.state.listingType == ListingType.Subscribed) {
721         if (
722           this.state.subscribedCommunities
723             .map(c => c.community.id)
724             .includes(data.post_view.community.id)
725         ) {
726           this.state.posts.unshift(data.post_view);
727           notifyPost(data.post_view, this.context.router);
728         }
729       } else {
730         // NSFW posts
731         let nsfw = data.post_view.post.nsfw || data.post_view.community.nsfw;
732
733         // Don't push the post if its nsfw, and don't have that setting on
734         if (
735           !nsfw ||
736           (nsfw &&
737             UserService.Instance.user &&
738             UserService.Instance.user.show_nsfw)
739         ) {
740           this.state.posts.unshift(data.post_view);
741           notifyPost(data.post_view, this.context.router);
742         }
743       }
744       this.setState(this.state);
745     } else if (
746       op == UserOperation.EditPost ||
747       op == UserOperation.DeletePost ||
748       op == UserOperation.RemovePost ||
749       op == UserOperation.LockPost ||
750       op == UserOperation.StickyPost ||
751       op == UserOperation.SavePost
752     ) {
753       let data = wsJsonToRes<PostResponse>(msg).data;
754       editPostFindRes(data.post_view, this.state.posts);
755       this.setState(this.state);
756     } else if (op == UserOperation.CreatePostLike) {
757       let data = wsJsonToRes<PostResponse>(msg).data;
758       createPostLikeFindRes(data.post_view, this.state.posts);
759       this.setState(this.state);
760     } else if (op == UserOperation.AddAdmin) {
761       let data = wsJsonToRes<AddAdminResponse>(msg).data;
762       this.state.siteRes.admins = data.admins;
763       this.setState(this.state);
764     } else if (op == UserOperation.BanUser) {
765       let data = wsJsonToRes<BanUserResponse>(msg).data;
766       let found = this.state.siteRes.banned.find(
767         u => (u.user.id = data.user_view.user.id)
768       );
769
770       // Remove the banned if its found in the list, and the action is an unban
771       if (found && !data.banned) {
772         this.state.siteRes.banned = this.state.siteRes.banned.filter(
773           i => i.user.id !== data.user_view.user.id
774         );
775       } else {
776         this.state.siteRes.banned.push(data.user_view);
777       }
778
779       this.state.posts
780         .filter(p => p.creator.id == data.user_view.user.id)
781         .forEach(p => (p.creator.banned = data.banned));
782
783       this.setState(this.state);
784     } else if (op == UserOperation.GetComments) {
785       let data = wsJsonToRes<GetCommentsResponse>(msg).data;
786       this.state.comments = data.comments;
787       this.state.loading = false;
788       this.setState(this.state);
789     } else if (
790       op == UserOperation.EditComment ||
791       op == UserOperation.DeleteComment ||
792       op == UserOperation.RemoveComment
793     ) {
794       let data = wsJsonToRes<CommentResponse>(msg).data;
795       editCommentRes(data.comment_view, this.state.comments);
796       this.setState(this.state);
797     } else if (op == UserOperation.CreateComment) {
798       let data = wsJsonToRes<CommentResponse>(msg).data;
799
800       // Necessary since it might be a user reply
801       if (data.recipient_ids.length == 0) {
802         // If you're on subscribed, only push it if you're subscribed.
803         if (this.state.listingType == ListingType.Subscribed) {
804           if (
805             this.state.subscribedCommunities
806               .map(c => c.community.id)
807               .includes(data.comment_view.community.id)
808           ) {
809             this.state.comments.unshift(data.comment_view);
810           }
811         } else {
812           this.state.comments.unshift(data.comment_view);
813         }
814         this.setState(this.state);
815       }
816     } else if (op == UserOperation.SaveComment) {
817       let data = wsJsonToRes<CommentResponse>(msg).data;
818       saveCommentRes(data.comment_view, this.state.comments);
819       this.setState(this.state);
820     } else if (op == UserOperation.CreateCommentLike) {
821       let data = wsJsonToRes<CommentResponse>(msg).data;
822       createCommentLikeRes(data.comment_view, this.state.comments);
823       this.setState(this.state);
824     }
825   }
826 }