]> Untitled Git - lemmy-ui.git/blob - src/shared/components/person/profile.tsx
Adding site ban from profile page. Fixes #588 (#627)
[lemmy-ui.git] / src / shared / components / person / profile.tsx
1 import { Component, linkEvent } from "inferno";
2 import { Link } from "inferno-router";
3 import {
4   AddAdminResponse,
5   BanPerson,
6   BanPersonResponse,
7   BlockPerson,
8   BlockPersonResponse,
9   CommentResponse,
10   GetPersonDetails,
11   GetPersonDetailsResponse,
12   GetSiteResponse,
13   PostResponse,
14   SortType,
15   UserOperation,
16 } from "lemmy-js-client";
17 import moment from "moment";
18 import { Subscription } from "rxjs";
19 import { i18n } from "../../i18next";
20 import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
21 import { UserService, WebSocketService } from "../../services";
22 import {
23   authField,
24   canMod,
25   capitalizeFirstLetter,
26   createCommentLikeRes,
27   createPostLikeFindRes,
28   editCommentRes,
29   editPostFindRes,
30   fetchLimit,
31   futureDaysToUnixTime,
32   getUsernameFromProps,
33   isBanned,
34   isMod,
35   mdToHtml,
36   numToSI,
37   previewLines,
38   relTags,
39   restoreScrollPosition,
40   routeSortTypeToEnum,
41   saveCommentRes,
42   saveScrollPosition,
43   setIsoData,
44   setOptionalAuth,
45   setupTippy,
46   toast,
47   updatePersonBlock,
48   wsClient,
49   wsJsonToRes,
50   wsSubscribe,
51   wsUserOp,
52 } from "../../utils";
53 import { BannerIconHeader } from "../common/banner-icon-header";
54 import { HtmlTags } from "../common/html-tags";
55 import { Icon, Spinner } from "../common/icon";
56 import { MomentTime } from "../common/moment-time";
57 import { SortSelect } from "../common/sort-select";
58 import { CommunityLink } from "../community/community-link";
59 import { PersonDetails } from "./person-details";
60 import { PersonListing } from "./person-listing";
61
62 interface ProfileState {
63   personRes: GetPersonDetailsResponse;
64   userName: string;
65   view: PersonDetailsView;
66   sort: SortType;
67   page: number;
68   loading: boolean;
69   personBlocked: boolean;
70   siteRes: GetSiteResponse;
71   showBanDialog: boolean;
72   banReason: string;
73   banExpireDays: number;
74   removeData: boolean;
75 }
76
77 interface ProfileProps {
78   view: PersonDetailsView;
79   sort: SortType;
80   page: number;
81   person_id: number | null;
82   username: string;
83 }
84
85 interface UrlParams {
86   view?: string;
87   sort?: SortType;
88   page?: number;
89 }
90
91 export class Profile extends Component<any, ProfileState> {
92   private isoData = setIsoData(this.context);
93   private subscription: Subscription;
94   private emptyState: ProfileState = {
95     personRes: undefined,
96     userName: getUsernameFromProps(this.props),
97     loading: true,
98     view: Profile.getViewFromProps(this.props.match.view),
99     sort: Profile.getSortTypeFromProps(this.props.match.sort),
100     page: Profile.getPageFromProps(this.props.match.page),
101     personBlocked: false,
102     siteRes: this.isoData.site_res,
103     showBanDialog: false,
104     banReason: null,
105     banExpireDays: null,
106     removeData: false,
107   };
108
109   constructor(props: any, context: any) {
110     super(props, context);
111
112     this.state = this.emptyState;
113     this.handleSortChange = this.handleSortChange.bind(this);
114     this.handlePageChange = this.handlePageChange.bind(this);
115
116     this.parseMessage = this.parseMessage.bind(this);
117     this.subscription = wsSubscribe(this.parseMessage);
118
119     // Only fetch the data if coming from another route
120     if (this.isoData.path == this.context.router.route.match.url) {
121       this.state.personRes = this.isoData.routeData[0];
122       this.state.loading = false;
123     } else {
124       this.fetchUserData();
125     }
126
127     this.setPersonBlock();
128   }
129
130   fetchUserData() {
131     let form: GetPersonDetails = {
132       username: this.state.userName,
133       sort: this.state.sort,
134       saved_only: this.state.view === PersonDetailsView.Saved,
135       page: this.state.page,
136       limit: fetchLimit,
137       auth: authField(false),
138     };
139     WebSocketService.Instance.send(wsClient.getPersonDetails(form));
140   }
141
142   get isCurrentUser() {
143     return (
144       UserService.Instance.myUserInfo?.local_user_view.person.id ==
145       this.state.personRes?.person_view.person.id
146     );
147   }
148
149   setPersonBlock() {
150     this.state.personBlocked = UserService.Instance.myUserInfo?.person_blocks
151       .map(a => a.target.id)
152       .includes(this.state.personRes?.person_view.person.id);
153   }
154
155   static getViewFromProps(view: string): PersonDetailsView {
156     return view ? PersonDetailsView[view] : PersonDetailsView.Overview;
157   }
158
159   static getSortTypeFromProps(sort: string): SortType {
160     return sort ? routeSortTypeToEnum(sort) : SortType.New;
161   }
162
163   static getPageFromProps(page: number): number {
164     return page ? Number(page) : 1;
165   }
166
167   static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
168     let pathSplit = req.path.split("/");
169     let promises: Promise<any>[] = [];
170
171     let username = pathSplit[2];
172     let view = this.getViewFromProps(pathSplit[4]);
173     let sort = this.getSortTypeFromProps(pathSplit[6]);
174     let page = this.getPageFromProps(Number(pathSplit[8]));
175
176     let form: GetPersonDetails = {
177       sort,
178       saved_only: view === PersonDetailsView.Saved,
179       page,
180       limit: fetchLimit,
181       username: username,
182     };
183     setOptionalAuth(form, req.auth);
184     promises.push(req.client.getPersonDetails(form));
185     return promises;
186   }
187
188   componentDidMount() {
189     setupTippy();
190   }
191
192   componentWillUnmount() {
193     this.subscription.unsubscribe();
194     saveScrollPosition(this.context);
195   }
196
197   static getDerivedStateFromProps(props: any): ProfileProps {
198     return {
199       view: this.getViewFromProps(props.match.params.view),
200       sort: this.getSortTypeFromProps(props.match.params.sort),
201       page: this.getPageFromProps(props.match.params.page),
202       person_id: Number(props.match.params.id) || null,
203       username: props.match.params.username,
204     };
205   }
206
207   componentDidUpdate(lastProps: any) {
208     // Necessary if you are on a post and you click another post (same route)
209     if (
210       lastProps.location.pathname.split("/")[2] !==
211       lastProps.history.location.pathname.split("/")[2]
212     ) {
213       // Couldnt get a refresh working. This does for now.
214       location.reload();
215     }
216   }
217
218   get documentTitle(): string {
219     return `@${this.state.personRes.person_view.person.name} - ${this.state.siteRes.site_view.site.name}`;
220   }
221
222   get bioTag(): string {
223     return this.state.personRes.person_view.person.bio
224       ? previewLines(this.state.personRes.person_view.person.bio)
225       : undefined;
226   }
227
228   render() {
229     return (
230       <div class="container">
231         {this.state.loading ? (
232           <h5>
233             <Spinner large />
234           </h5>
235         ) : (
236           <div class="row">
237             <div class="col-12 col-md-8">
238               <>
239                 <HtmlTags
240                   title={this.documentTitle}
241                   path={this.context.router.route.match.url}
242                   description={this.bioTag}
243                   image={this.state.personRes.person_view.person.avatar}
244                 />
245                 {this.userInfo()}
246                 <hr />
247               </>
248               {!this.state.loading && this.selects()}
249               <PersonDetails
250                 personRes={this.state.personRes}
251                 admins={this.state.siteRes.admins}
252                 sort={this.state.sort}
253                 page={this.state.page}
254                 limit={fetchLimit}
255                 enableDownvotes={
256                   this.state.siteRes.site_view.site.enable_downvotes
257                 }
258                 enableNsfw={this.state.siteRes.site_view.site.enable_nsfw}
259                 view={this.state.view}
260                 onPageChange={this.handlePageChange}
261               />
262             </div>
263
264             {!this.state.loading && (
265               <div class="col-12 col-md-4">
266                 {this.moderates()}
267                 {this.isCurrentUser && this.follows()}
268               </div>
269             )}
270           </div>
271         )}
272       </div>
273     );
274   }
275
276   viewRadios() {
277     return (
278       <div class="btn-group btn-group-toggle flex-wrap mb-2">
279         <label
280           className={`btn btn-outline-secondary pointer
281             ${this.state.view == PersonDetailsView.Overview && "active"}
282           `}
283         >
284           <input
285             type="radio"
286             value={PersonDetailsView.Overview}
287             checked={this.state.view === PersonDetailsView.Overview}
288             onChange={linkEvent(this, this.handleViewChange)}
289           />
290           {i18n.t("overview")}
291         </label>
292         <label
293           className={`btn btn-outline-secondary pointer
294             ${this.state.view == PersonDetailsView.Comments && "active"}
295           `}
296         >
297           <input
298             type="radio"
299             value={PersonDetailsView.Comments}
300             checked={this.state.view == PersonDetailsView.Comments}
301             onChange={linkEvent(this, this.handleViewChange)}
302           />
303           {i18n.t("comments")}
304         </label>
305         <label
306           className={`btn btn-outline-secondary pointer
307             ${this.state.view == PersonDetailsView.Posts && "active"}
308           `}
309         >
310           <input
311             type="radio"
312             value={PersonDetailsView.Posts}
313             checked={this.state.view == PersonDetailsView.Posts}
314             onChange={linkEvent(this, this.handleViewChange)}
315           />
316           {i18n.t("posts")}
317         </label>
318         <label
319           className={`btn btn-outline-secondary pointer
320             ${this.state.view == PersonDetailsView.Saved && "active"}
321           `}
322         >
323           <input
324             type="radio"
325             value={PersonDetailsView.Saved}
326             checked={this.state.view == PersonDetailsView.Saved}
327             onChange={linkEvent(this, this.handleViewChange)}
328           />
329           {i18n.t("saved")}
330         </label>
331       </div>
332     );
333   }
334
335   selects() {
336     let profileRss = `/feeds/u/${this.state.userName}.xml?sort=${this.state.sort}`;
337
338     return (
339       <div className="mb-2">
340         <span class="mr-3">{this.viewRadios()}</span>
341         <SortSelect
342           sort={this.state.sort}
343           onChange={this.handleSortChange}
344           hideHot
345           hideMostComments
346         />
347         <a href={profileRss} rel={relTags} title="RSS">
348           <Icon icon="rss" classes="text-muted small mx-2" />
349         </a>
350         <link rel="alternate" type="application/atom+xml" href={profileRss} />
351       </div>
352     );
353   }
354   handleBlockPerson(personId: number) {
355     if (personId != 0) {
356       let blockUserForm: BlockPerson = {
357         person_id: personId,
358         block: true,
359         auth: authField(),
360       };
361       WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
362     }
363   }
364   handleUnblockPerson(recipientId: number) {
365     let blockUserForm: BlockPerson = {
366       person_id: recipientId,
367       block: false,
368       auth: authField(),
369     };
370     WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
371   }
372
373   userInfo() {
374     let pv = this.state.personRes?.person_view;
375
376     return (
377       <div>
378         <BannerIconHeader banner={pv.person.banner} icon={pv.person.avatar} />
379         <div class="mb-3">
380           <div class="">
381             <div class="mb-0 d-flex flex-wrap">
382               <div>
383                 {pv.person.display_name && (
384                   <h5 class="mb-0">{pv.person.display_name}</h5>
385                 )}
386                 <ul class="list-inline mb-2">
387                   <li className="list-inline-item">
388                     <PersonListing
389                       person={pv.person}
390                       realLink
391                       useApubName
392                       muted
393                       hideAvatar
394                     />
395                   </li>
396                   {isBanned(pv.person) && (
397                     <li className="list-inline-item badge badge-danger">
398                       {i18n.t("banned")}
399                     </li>
400                   )}
401                   {pv.person.admin && (
402                     <li className="list-inline-item badge badge-light">
403                       {i18n.t("admin")}
404                     </li>
405                   )}
406                   {pv.person.bot_account && (
407                     <li className="list-inline-item badge badge-light">
408                       {i18n.t("bot_account").toLowerCase()}
409                     </li>
410                   )}
411                 </ul>
412               </div>
413               {this.banDialog()}
414               <div className="flex-grow-1 unselectable pointer mx-2"></div>
415               {!this.isCurrentUser && UserService.Instance.myUserInfo && (
416                 <>
417                   <a
418                     className={`d-flex align-self-start btn btn-secondary mr-2 ${
419                       !pv.person.matrix_user_id && "invisible"
420                     }`}
421                     rel={relTags}
422                     href={`https://matrix.to/#/${pv.person.matrix_user_id}`}
423                   >
424                     {i18n.t("send_secure_message")}
425                   </a>
426                   <Link
427                     className={"d-flex align-self-start btn btn-secondary mr-2"}
428                     to={`/create_private_message/recipient/${pv.person.id}`}
429                   >
430                     {i18n.t("send_message")}
431                   </Link>
432                   {this.state.personBlocked ? (
433                     <button
434                       className={
435                         "d-flex align-self-start btn btn-secondary mr-2"
436                       }
437                       onClick={linkEvent(
438                         pv.person.id,
439                         this.handleUnblockPerson
440                       )}
441                     >
442                       {i18n.t("unblock_user")}
443                     </button>
444                   ) : (
445                     <button
446                       className={
447                         "d-flex align-self-start btn btn-secondary mr-2"
448                       }
449                       onClick={linkEvent(pv.person.id, this.handleBlockPerson)}
450                     >
451                       {i18n.t("block_user")}
452                     </button>
453                   )}
454                 </>
455               )}
456
457               {this.canAdmin &&
458                 !this.personIsAdmin &&
459                 !this.state.showBanDialog &&
460                 (!isBanned(pv.person) ? (
461                   <button
462                     className={"d-flex align-self-start btn btn-secondary mr-2"}
463                     onClick={linkEvent(this, this.handleModBanShow)}
464                     aria-label={i18n.t("ban")}
465                   >
466                     {capitalizeFirstLetter(i18n.t("ban"))}
467                   </button>
468                 ) : (
469                   <button
470                     className={"d-flex align-self-start btn btn-secondary mr-2"}
471                     onClick={linkEvent(this, this.handleModBanSubmit)}
472                     aria-label={i18n.t("unban")}
473                   >
474                     {capitalizeFirstLetter(i18n.t("unban"))}
475                   </button>
476                 ))}
477             </div>
478             {pv.person.bio && (
479               <div className="d-flex align-items-center mb-2">
480                 <div
481                   className="md-div"
482                   dangerouslySetInnerHTML={mdToHtml(pv.person.bio)}
483                 />
484               </div>
485             )}
486             <div>
487               <ul class="list-inline mb-2">
488                 <li className="list-inline-item badge badge-light">
489                   {i18n.t("number_of_posts", {
490                     count: pv.counts.post_count,
491                     formattedCount: numToSI(pv.counts.post_count),
492                   })}
493                 </li>
494                 <li className="list-inline-item badge badge-light">
495                   {i18n.t("number_of_comments", {
496                     count: pv.counts.comment_count,
497                     formattedCount: numToSI(pv.counts.comment_count),
498                   })}
499                 </li>
500               </ul>
501             </div>
502             <div class="text-muted">
503               {i18n.t("joined")}{" "}
504               <MomentTime data={pv.person} showAgo ignoreUpdated />
505             </div>
506             <div className="d-flex align-items-center text-muted mb-2">
507               <Icon icon="cake" />
508               <span className="ml-2">
509                 {i18n.t("cake_day_title")}{" "}
510                 {moment.utc(pv.person.published).local().format("MMM DD, YYYY")}
511               </span>
512             </div>
513           </div>
514         </div>
515       </div>
516     );
517   }
518
519   banDialog() {
520     let pv = this.state.personRes?.person_view;
521     return (
522       <>
523         {this.state.showBanDialog && (
524           <form onSubmit={linkEvent(this, this.handleModBanSubmit)}>
525             <div class="form-group row col-12">
526               <label class="col-form-label" htmlFor="profile-ban-reason">
527                 {i18n.t("reason")}
528               </label>
529               <input
530                 type="text"
531                 id="profile-ban-reason"
532                 class="form-control mr-2"
533                 placeholder={i18n.t("reason")}
534                 value={this.state.banReason}
535                 onInput={linkEvent(this, this.handleModBanReasonChange)}
536               />
537               <label class="col-form-label" htmlFor={`mod-ban-expires`}>
538                 {i18n.t("expires")}
539               </label>
540               <input
541                 type="number"
542                 id={`mod-ban-expires`}
543                 class="form-control mr-2"
544                 placeholder={i18n.t("number_of_days")}
545                 value={this.state.banExpireDays}
546                 onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
547               />
548               <div class="form-group">
549                 <div class="form-check">
550                   <input
551                     class="form-check-input"
552                     id="mod-ban-remove-data"
553                     type="checkbox"
554                     checked={this.state.removeData}
555                     onChange={linkEvent(this, this.handleModRemoveDataChange)}
556                   />
557                   <label
558                     class="form-check-label"
559                     htmlFor="mod-ban-remove-data"
560                     title={i18n.t("remove_content_more")}
561                   >
562                     {i18n.t("remove_content")}
563                   </label>
564                 </div>
565               </div>
566             </div>
567             {/* TODO hold off on expires until later */}
568             {/* <div class="form-group row"> */}
569             {/*   <label class="col-form-label">Expires</label> */}
570             {/*   <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
571             {/* </div> */}
572             <div class="form-group row">
573               <button
574                 type="cancel"
575                 class="btn btn-secondary mr-2"
576                 aria-label={i18n.t("cancel")}
577                 onClick={linkEvent(this, this.handleModBanSubmitCancel)}
578               >
579                 {i18n.t("cancel")}
580               </button>
581               <button
582                 type="submit"
583                 class="btn btn-secondary"
584                 aria-label={i18n.t("ban")}
585               >
586                 {i18n.t("ban")} {pv.person.name}
587               </button>
588             </div>
589           </form>
590         )}
591       </>
592     );
593   }
594
595   moderates() {
596     return (
597       <div>
598         {this.state.personRes.moderates.length > 0 && (
599           <div class="card border-secondary mb-3">
600             <div class="card-body">
601               <h5>{i18n.t("moderates")}</h5>
602               <ul class="list-unstyled mb-0">
603                 {this.state.personRes.moderates.map(cmv => (
604                   <li>
605                     <CommunityLink community={cmv.community} />
606                   </li>
607                 ))}
608               </ul>
609             </div>
610           </div>
611         )}
612       </div>
613     );
614   }
615
616   follows() {
617     let follows = UserService.Instance.myUserInfo.follows;
618     return (
619       <div>
620         {follows.length > 0 && (
621           <div class="card border-secondary mb-3">
622             <div class="card-body">
623               <h5>{i18n.t("subscribed")}</h5>
624               <ul class="list-unstyled mb-0">
625                 {follows.map(cfv => (
626                   <li>
627                     <CommunityLink community={cfv.community} />
628                   </li>
629                 ))}
630               </ul>
631             </div>
632           </div>
633         )}
634       </div>
635     );
636   }
637
638   updateUrl(paramUpdates: UrlParams) {
639     const page = paramUpdates.page || this.state.page;
640     const viewStr = paramUpdates.view || PersonDetailsView[this.state.view];
641     const sortStr = paramUpdates.sort || this.state.sort;
642
643     let typeView = `/u/${this.state.userName}`;
644
645     this.props.history.push(
646       `${typeView}/view/${viewStr}/sort/${sortStr}/page/${page}`
647     );
648     this.state.loading = true;
649     this.setState(this.state);
650     this.fetchUserData();
651   }
652
653   get canAdmin(): boolean {
654     return (
655       this.state.siteRes?.admins &&
656       canMod(
657         UserService.Instance.myUserInfo,
658         this.state.siteRes.admins.map(a => a.person.id),
659         this.state.personRes?.person_view.person.id
660       )
661     );
662   }
663
664   get personIsAdmin(): boolean {
665     return (
666       this.state.siteRes?.admins &&
667       isMod(
668         this.state.siteRes.admins.map(a => a.person.id),
669         this.state.personRes?.person_view.person.id
670       )
671     );
672   }
673
674   handlePageChange(page: number) {
675     this.updateUrl({ page });
676   }
677
678   handleSortChange(val: SortType) {
679     this.updateUrl({ sort: val, page: 1 });
680   }
681
682   handleViewChange(i: Profile, event: any) {
683     i.updateUrl({
684       view: PersonDetailsView[Number(event.target.value)],
685       page: 1,
686     });
687   }
688
689   handleModBanShow(i: Profile) {
690     i.state.showBanDialog = true;
691     i.setState(i.state);
692   }
693
694   handleModBanReasonChange(i: Profile, event: any) {
695     i.state.banReason = event.target.value;
696     i.setState(i.state);
697   }
698
699   handleModBanExpireDaysChange(i: Profile, event: any) {
700     i.state.banExpireDays = event.target.value;
701     i.setState(i.state);
702   }
703
704   handleModRemoveDataChange(i: Profile, event: any) {
705     i.state.removeData = event.target.checked;
706     i.setState(i.state);
707   }
708
709   handleModBanSubmitCancel(i: Profile, event?: any) {
710     event.preventDefault();
711     i.state.showBanDialog = false;
712     i.setState(i.state);
713   }
714
715   handleModBanSubmit(i: Profile, event?: any) {
716     if (event) event.preventDefault();
717
718     let pv = i.state.personRes.person_view;
719     // If its an unban, restore all their data
720     let ban = !pv.person.banned;
721     if (ban == false) {
722       i.state.removeData = false;
723     }
724     let form: BanPerson = {
725       person_id: pv.person.id,
726       ban,
727       remove_data: i.state.removeData,
728       reason: i.state.banReason,
729       expires: futureDaysToUnixTime(i.state.banExpireDays),
730       auth: authField(),
731     };
732     WebSocketService.Instance.send(wsClient.banPerson(form));
733
734     i.state.showBanDialog = false;
735     i.setState(i.state);
736   }
737
738   parseMessage(msg: any) {
739     let op = wsUserOp(msg);
740     console.log(msg);
741     if (msg.error) {
742       toast(i18n.t(msg.error), "danger");
743       if (msg.error == "couldnt_find_that_username_or_email") {
744         this.context.router.history.push("/");
745       }
746       return;
747     } else if (msg.reconnect) {
748       this.fetchUserData();
749     } else if (op == UserOperation.GetPersonDetails) {
750       // Since the PersonDetails contains posts/comments as well as some general user info we listen here as well
751       // and set the parent state if it is not set or differs
752       // TODO this might need to get abstracted
753       let data = wsJsonToRes<GetPersonDetailsResponse>(msg).data;
754       this.state.personRes = data;
755       console.log(data);
756       this.state.loading = false;
757       this.setPersonBlock();
758       this.setState(this.state);
759       restoreScrollPosition(this.context);
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.CreateCommentLike) {
765       let data = wsJsonToRes<CommentResponse>(msg).data;
766       createCommentLikeRes(data.comment_view, this.state.personRes.comments);
767       this.setState(this.state);
768     } else if (
769       op == UserOperation.EditComment ||
770       op == UserOperation.DeleteComment ||
771       op == UserOperation.RemoveComment
772     ) {
773       let data = wsJsonToRes<CommentResponse>(msg).data;
774       editCommentRes(data.comment_view, this.state.personRes.comments);
775       this.setState(this.state);
776     } else if (op == UserOperation.CreateComment) {
777       let data = wsJsonToRes<CommentResponse>(msg).data;
778       if (
779         UserService.Instance.myUserInfo &&
780         data.comment_view.creator.id ==
781           UserService.Instance.myUserInfo?.local_user_view.person.id
782       ) {
783         toast(i18n.t("reply_sent"));
784       }
785     } else if (op == UserOperation.SaveComment) {
786       let data = wsJsonToRes<CommentResponse>(msg).data;
787       saveCommentRes(data.comment_view, this.state.personRes.comments);
788       this.setState(this.state);
789     } else if (
790       op == UserOperation.EditPost ||
791       op == UserOperation.DeletePost ||
792       op == UserOperation.RemovePost ||
793       op == UserOperation.LockPost ||
794       op == UserOperation.StickyPost ||
795       op == UserOperation.SavePost
796     ) {
797       let data = wsJsonToRes<PostResponse>(msg).data;
798       editPostFindRes(data.post_view, this.state.personRes.posts);
799       this.setState(this.state);
800     } else if (op == UserOperation.CreatePostLike) {
801       let data = wsJsonToRes<PostResponse>(msg).data;
802       createPostLikeFindRes(data.post_view, this.state.personRes.posts);
803       this.setState(this.state);
804     } else if (op == UserOperation.BanPerson) {
805       let data = wsJsonToRes<BanPersonResponse>(msg).data;
806       this.state.personRes.comments
807         .filter(c => c.creator.id == data.person_view.person.id)
808         .forEach(c => (c.creator.banned = data.banned));
809       this.state.personRes.posts
810         .filter(c => c.creator.id == data.person_view.person.id)
811         .forEach(c => (c.creator.banned = data.banned));
812       let pv = this.state.personRes.person_view;
813
814       if (pv.person.id == data.person_view.person.id) {
815         pv.person.banned = data.banned;
816       }
817       this.setState(this.state);
818     } else if (op == UserOperation.BlockPerson) {
819       let data = wsJsonToRes<BlockPersonResponse>(msg).data;
820       updatePersonBlock(data);
821       this.setPersonBlock();
822       this.setState(this.state);
823     }
824   }
825 }