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