]> Untitled Git - lemmy-ui.git/blob - src/shared/components/person/settings.tsx
Fix tippy on component mount. Fixes #509 (#511)
[lemmy-ui.git] / src / shared / components / person / settings.tsx
1 import { Component, linkEvent } from "inferno";
2 import ISO6391 from "iso-639-1";
3 import {
4   BlockCommunity,
5   BlockCommunityResponse,
6   BlockPerson,
7   BlockPersonResponse,
8   ChangePassword,
9   CommunityBlockView,
10   CommunityView,
11   DeleteAccount,
12   GetSiteResponse,
13   ListingType,
14   LoginResponse,
15   PersonBlockView,
16   PersonViewSafe,
17   SaveUserSettings,
18   SortType,
19   UserOperation,
20 } from "lemmy-js-client";
21 import { Subscription } from "rxjs";
22 import { i18n } from "../../i18next";
23 import { UserService, WebSocketService } from "../../services";
24 import {
25   authField,
26   capitalizeFirstLetter,
27   choicesConfig,
28   communitySelectName,
29   communityToChoice,
30   debounce,
31   elementUrl,
32   fetchCommunities,
33   fetchUsers,
34   getLanguage,
35   isBrowser,
36   languages,
37   personSelectName,
38   personToChoice,
39   setIsoData,
40   setTheme,
41   setupTippy,
42   showLocal,
43   themes,
44   toast,
45   updateCommunityBlock,
46   updatePersonBlock,
47   wsClient,
48   wsJsonToRes,
49   wsSubscribe,
50   wsUserOp,
51 } from "../../utils";
52 import { HtmlTags } from "../common/html-tags";
53 import { Icon, Spinner } from "../common/icon";
54 import { ImageUploadForm } from "../common/image-upload-form";
55 import { ListingTypeSelect } from "../common/listing-type-select";
56 import { MarkdownTextArea } from "../common/markdown-textarea";
57 import { SortSelect } from "../common/sort-select";
58 import { CommunityLink } from "../community/community-link";
59 import { PersonListing } from "./person-listing";
60
61 var Choices: any;
62 if (isBrowser()) {
63   Choices = require("choices.js");
64 }
65
66 interface SettingsState {
67   saveUserSettingsForm: SaveUserSettings;
68   changePasswordForm: ChangePassword;
69   saveUserSettingsLoading: boolean;
70   changePasswordLoading: boolean;
71   deleteAccountLoading: boolean;
72   deleteAccountShowConfirm: boolean;
73   deleteAccountForm: DeleteAccount;
74   personBlocks: PersonBlockView[];
75   blockPersonId: number;
76   blockPerson?: PersonViewSafe;
77   communityBlocks: CommunityBlockView[];
78   blockCommunityId: number;
79   blockCommunity?: CommunityView;
80   currentTab: string;
81   siteRes: GetSiteResponse;
82 }
83
84 export class Settings extends Component<any, SettingsState> {
85   private isoData = setIsoData(this.context);
86   private blockPersonChoices: any;
87   private blockCommunityChoices: any;
88   private subscription: Subscription;
89   private emptyState: SettingsState = {
90     saveUserSettingsForm: {
91       auth: authField(false),
92     },
93     changePasswordForm: {
94       new_password: null,
95       new_password_verify: null,
96       old_password: null,
97       auth: authField(false),
98     },
99     saveUserSettingsLoading: null,
100     changePasswordLoading: false,
101     deleteAccountLoading: null,
102     deleteAccountShowConfirm: false,
103     deleteAccountForm: {
104       password: null,
105       auth: authField(false),
106     },
107     personBlocks: [],
108     blockPersonId: 0,
109     communityBlocks: [],
110     blockCommunityId: 0,
111     currentTab: "settings",
112     siteRes: this.isoData.site_res,
113   };
114
115   constructor(props: any, context: any) {
116     super(props, context);
117
118     this.state = this.emptyState;
119     this.handleSortTypeChange = this.handleSortTypeChange.bind(this);
120     this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
121     this.handleBioChange = this.handleBioChange.bind(this);
122
123     this.handleAvatarUpload = this.handleAvatarUpload.bind(this);
124     this.handleAvatarRemove = this.handleAvatarRemove.bind(this);
125
126     this.handleBannerUpload = this.handleBannerUpload.bind(this);
127     this.handleBannerRemove = this.handleBannerRemove.bind(this);
128
129     this.parseMessage = this.parseMessage.bind(this);
130     this.subscription = wsSubscribe(this.parseMessage);
131
132     this.setUserInfo();
133   }
134
135   componentDidMount() {
136     setupTippy();
137   }
138
139   componentWillUnmount() {
140     this.subscription.unsubscribe();
141   }
142
143   get documentTitle(): string {
144     return i18n.t("settings");
145   }
146
147   render() {
148     return (
149       <div class="container">
150         <>
151           <HtmlTags
152             title={this.documentTitle}
153             path={this.context.router.route.match.url}
154             description={this.documentTitle}
155             image={this.state.saveUserSettingsForm.avatar}
156           />
157           <ul class="nav nav-tabs mb-2">
158             <li class="nav-item">
159               <button
160                 class={`nav-link btn ${
161                   this.state.currentTab == "settings" && "active"
162                 }`}
163                 onClick={linkEvent(
164                   { ctx: this, tab: "settings" },
165                   this.handleSwitchTab
166                 )}
167               >
168                 {i18n.t("settings")}
169               </button>
170             </li>
171             <li class="nav-item">
172               <button
173                 class={`nav-link btn ${
174                   this.state.currentTab == "blocks" && "active"
175                 }`}
176                 onClick={linkEvent(
177                   { ctx: this, tab: "blocks" },
178                   this.handleSwitchTab
179                 )}
180               >
181                 {i18n.t("blocks")}
182               </button>
183             </li>
184           </ul>
185           {this.state.currentTab == "settings" && this.userSettings()}
186           {this.state.currentTab == "blocks" && this.blockCards()}
187         </>
188       </div>
189     );
190   }
191
192   userSettings() {
193     return (
194       <div class="row">
195         <div class="col-12 col-md-6">
196           <div class="card border-secondary mb-3">
197             <div class="card-body">{this.saveUserSettingsHtmlForm()}</div>
198           </div>
199         </div>
200         <div class="col-12 col-md-6">
201           <div class="card border-secondary mb-3">
202             <div class="card-body">{this.changePasswordHtmlForm()}</div>
203           </div>
204         </div>
205       </div>
206     );
207   }
208
209   blockCards() {
210     return (
211       <div class="row">
212         <div class="col-12 col-md-6">
213           <div class="card border-secondary mb-3">
214             <div class="card-body">{this.blockUserCard()}</div>
215           </div>
216         </div>
217         <div class="col-12 col-md-6">
218           <div class="card border-secondary mb-3">
219             <div class="card-body">{this.blockCommunityCard()}</div>
220           </div>
221         </div>
222       </div>
223     );
224   }
225
226   changePasswordHtmlForm() {
227     return (
228       <>
229         <h5>{i18n.t("change_password")}</h5>
230         <form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
231           <div class="form-group row">
232             <label class="col-sm-5 col-form-label" htmlFor="user-password">
233               {i18n.t("new_password")}
234             </label>
235             <div class="col-sm-7">
236               <input
237                 type="password"
238                 id="user-password"
239                 class="form-control"
240                 value={this.state.changePasswordForm.new_password}
241                 autoComplete="new-password"
242                 maxLength={60}
243                 onInput={linkEvent(this, this.handleNewPasswordChange)}
244               />
245             </div>
246           </div>
247           <div class="form-group row">
248             <label
249               class="col-sm-5 col-form-label"
250               htmlFor="user-verify-password"
251             >
252               {i18n.t("verify_password")}
253             </label>
254             <div class="col-sm-7">
255               <input
256                 type="password"
257                 id="user-verify-password"
258                 class="form-control"
259                 value={this.state.changePasswordForm.new_password_verify}
260                 autoComplete="new-password"
261                 maxLength={60}
262                 onInput={linkEvent(this, this.handleNewPasswordVerifyChange)}
263               />
264             </div>
265           </div>
266           <div class="form-group row">
267             <label class="col-sm-5 col-form-label" htmlFor="user-old-password">
268               {i18n.t("old_password")}
269             </label>
270             <div class="col-sm-7">
271               <input
272                 type="password"
273                 id="user-old-password"
274                 class="form-control"
275                 value={this.state.changePasswordForm.old_password}
276                 autoComplete="new-password"
277                 maxLength={60}
278                 onInput={linkEvent(this, this.handleOldPasswordChange)}
279               />
280             </div>
281           </div>
282           <div class="form-group">
283             <button type="submit" class="btn btn-block btn-secondary mr-4">
284               {this.state.changePasswordLoading ? (
285                 <Spinner />
286               ) : (
287                 capitalizeFirstLetter(i18n.t("save"))
288               )}
289             </button>
290           </div>
291         </form>
292       </>
293     );
294   }
295
296   blockUserCard() {
297     return (
298       <div>
299         {this.blockUserForm()}
300         {this.blockedUsersList()}
301       </div>
302     );
303   }
304
305   blockedUsersList() {
306     return (
307       <>
308         <h5>{i18n.t("blocked_users")}</h5>
309         <ul class="list-unstyled mb-0">
310           {this.state.personBlocks.map(pb => (
311             <li>
312               <span>
313                 <PersonListing person={pb.target} />
314                 <button
315                   className="btn btn-sm"
316                   onClick={linkEvent(
317                     { ctx: this, recipientId: pb.target.id },
318                     this.handleUnblockPerson
319                   )}
320                   data-tippy-content={i18n.t("unblock_user")}
321                 >
322                   <Icon icon="x" classes="icon-inline" />
323                 </button>
324               </span>
325             </li>
326           ))}
327         </ul>
328       </>
329     );
330   }
331
332   blockUserForm() {
333     return (
334       <div class="form-group row">
335         <label class="col-md-4 col-form-label" htmlFor="block-person-filter">
336           {i18n.t("block_user")}
337         </label>
338         <div class="col-md-8">
339           <select
340             class="form-control"
341             id="block-person-filter"
342             value={this.state.blockPersonId}
343           >
344             <option value="0">—</option>
345             {this.state.blockPerson && (
346               <option value={this.state.blockPerson.person.id}>
347                 {personSelectName(this.state.blockPerson)}
348               </option>
349             )}
350           </select>
351         </div>
352       </div>
353     );
354   }
355
356   blockCommunityCard() {
357     return (
358       <div>
359         {this.blockCommunityForm()}
360         {this.blockedCommunitiesList()}
361       </div>
362     );
363   }
364
365   blockedCommunitiesList() {
366     return (
367       <>
368         <h5>{i18n.t("blocked_communities")}</h5>
369         <ul class="list-unstyled mb-0">
370           {this.state.communityBlocks.map(cb => (
371             <li>
372               <span>
373                 <CommunityLink community={cb.community} />
374                 <button
375                   className="btn btn-sm"
376                   onClick={linkEvent(
377                     { ctx: this, communityId: cb.community.id },
378                     this.handleUnblockCommunity
379                   )}
380                   data-tippy-content={i18n.t("unblock_community")}
381                 >
382                   <Icon icon="x" classes="icon-inline" />
383                 </button>
384               </span>
385             </li>
386           ))}
387         </ul>
388       </>
389     );
390   }
391
392   blockCommunityForm() {
393     return (
394       <div class="form-group row">
395         <label class="col-md-4 col-form-label" htmlFor="block-community-filter">
396           {i18n.t("block_community")}
397         </label>
398         <div class="col-md-8">
399           <select
400             class="form-control"
401             id="block-community-filter"
402             value={this.state.blockCommunityId}
403           >
404             <option value="0">—</option>
405             {this.state.blockCommunity && (
406               <option value={this.state.blockCommunity.community.id}>
407                 {communitySelectName(this.state.blockCommunity)}
408               </option>
409             )}
410           </select>
411         </div>
412       </div>
413     );
414   }
415
416   saveUserSettingsHtmlForm() {
417     return (
418       <>
419         <h5>{i18n.t("settings")}</h5>
420         <form onSubmit={linkEvent(this, this.handleSaveSettingsSubmit)}>
421           <div class="form-group row">
422             <label class="col-sm-5 col-form-label" htmlFor="display-name">
423               {i18n.t("display_name")}
424             </label>
425             <div class="col-sm-7">
426               <input
427                 id="display-name"
428                 type="text"
429                 class="form-control"
430                 placeholder={i18n.t("optional")}
431                 value={this.state.saveUserSettingsForm.display_name}
432                 onInput={linkEvent(this, this.handleDisplayNameChange)}
433                 pattern="^(?!@)(.+)$"
434                 minLength={3}
435               />
436             </div>
437           </div>
438           <div class="form-group row">
439             <label class="col-sm-3 col-form-label" htmlFor="user-bio">
440               {i18n.t("bio")}
441             </label>
442             <div class="col-sm-9">
443               <MarkdownTextArea
444                 initialContent={this.state.saveUserSettingsForm.bio}
445                 onContentChange={this.handleBioChange}
446                 maxLength={300}
447                 hideNavigationWarnings
448               />
449             </div>
450           </div>
451           <div class="form-group row">
452             <label class="col-sm-3 col-form-label" htmlFor="user-email">
453               {i18n.t("email")}
454             </label>
455             <div class="col-sm-9">
456               <input
457                 type="email"
458                 id="user-email"
459                 class="form-control"
460                 placeholder={i18n.t("optional")}
461                 value={this.state.saveUserSettingsForm.email}
462                 onInput={linkEvent(this, this.handleEmailChange)}
463                 minLength={3}
464               />
465             </div>
466           </div>
467           <div class="form-group row">
468             <label class="col-sm-5 col-form-label" htmlFor="matrix-user-id">
469               <a href={elementUrl} rel="noopener">
470                 {i18n.t("matrix_user_id")}
471               </a>
472             </label>
473             <div class="col-sm-7">
474               <input
475                 id="matrix-user-id"
476                 type="text"
477                 class="form-control"
478                 placeholder="@user:example.com"
479                 value={this.state.saveUserSettingsForm.matrix_user_id}
480                 onInput={linkEvent(this, this.handleMatrixUserIdChange)}
481                 pattern="^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
482               />
483             </div>
484           </div>
485           <div class="form-group row">
486             <label class="col-sm-3">{i18n.t("avatar")}</label>
487             <div class="col-sm-9">
488               <ImageUploadForm
489                 uploadTitle={i18n.t("upload_avatar")}
490                 imageSrc={this.state.saveUserSettingsForm.avatar}
491                 onUpload={this.handleAvatarUpload}
492                 onRemove={this.handleAvatarRemove}
493                 rounded
494               />
495             </div>
496           </div>
497           <div class="form-group row">
498             <label class="col-sm-3">{i18n.t("banner")}</label>
499             <div class="col-sm-9">
500               <ImageUploadForm
501                 uploadTitle={i18n.t("upload_banner")}
502                 imageSrc={this.state.saveUserSettingsForm.banner}
503                 onUpload={this.handleBannerUpload}
504                 onRemove={this.handleBannerRemove}
505               />
506             </div>
507           </div>
508           <div class="form-group row">
509             <label class="col-sm-3" htmlFor="user-language">
510               {i18n.t("language")}
511             </label>
512             <div class="col-sm-9">
513               <select
514                 id="user-language"
515                 value={this.state.saveUserSettingsForm.lang}
516                 onChange={linkEvent(this, this.handleLangChange)}
517                 class="custom-select w-auto"
518               >
519                 <option disabled aria-hidden="true">
520                   {i18n.t("language")}
521                 </option>
522                 <option value="browser">{i18n.t("browser_default")}</option>
523                 <option disabled aria-hidden="true">
524                   â”€â”€
525                 </option>
526                 {languages.sort().map(lang => (
527                   <option value={lang.code}>
528                     {ISO6391.getNativeName(lang.code) || lang.code}
529                   </option>
530                 ))}
531               </select>
532             </div>
533           </div>
534           <div class="form-group row">
535             <label class="col-sm-3" htmlFor="user-theme">
536               {i18n.t("theme")}
537             </label>
538             <div class="col-sm-9">
539               <select
540                 id="user-theme"
541                 value={this.state.saveUserSettingsForm.theme}
542                 onChange={linkEvent(this, this.handleThemeChange)}
543                 class="custom-select w-auto"
544               >
545                 <option disabled aria-hidden="true">
546                   {i18n.t("theme")}
547                 </option>
548                 <option value="browser">{i18n.t("browser_default")}</option>
549                 {themes.map(theme => (
550                   <option value={theme}>{theme}</option>
551                 ))}
552               </select>
553             </div>
554           </div>
555           <form className="form-group row">
556             <label class="col-sm-3">{i18n.t("type")}</label>
557             <div class="col-sm-9">
558               <ListingTypeSelect
559                 type_={
560                   Object.values(ListingType)[
561                     this.state.saveUserSettingsForm.default_listing_type
562                   ]
563                 }
564                 showLocal={showLocal(this.isoData)}
565                 onChange={this.handleListingTypeChange}
566               />
567             </div>
568           </form>
569           <form className="form-group row">
570             <label class="col-sm-3">{i18n.t("sort_type")}</label>
571             <div class="col-sm-9">
572               <SortSelect
573                 sort={
574                   Object.values(SortType)[
575                     this.state.saveUserSettingsForm.default_sort_type
576                   ]
577                 }
578                 onChange={this.handleSortTypeChange}
579               />
580             </div>
581           </form>
582           {this.state.siteRes.site_view.site.enable_nsfw && (
583             <div class="form-group">
584               <div class="form-check">
585                 <input
586                   class="form-check-input"
587                   id="user-show-nsfw"
588                   type="checkbox"
589                   checked={this.state.saveUserSettingsForm.show_nsfw}
590                   onChange={linkEvent(this, this.handleShowNsfwChange)}
591                 />
592                 <label class="form-check-label" htmlFor="user-show-nsfw">
593                   {i18n.t("show_nsfw")}
594                 </label>
595               </div>
596             </div>
597           )}
598           <div class="form-group">
599             <div class="form-check">
600               <input
601                 class="form-check-input"
602                 id="user-show-scores"
603                 type="checkbox"
604                 checked={this.state.saveUserSettingsForm.show_scores}
605                 onChange={linkEvent(this, this.handleShowScoresChange)}
606               />
607               <label class="form-check-label" htmlFor="user-show-scores">
608                 {i18n.t("show_scores")}
609               </label>
610             </div>
611           </div>
612           <div class="form-group">
613             <div class="form-check">
614               <input
615                 class="form-check-input"
616                 id="user-show-avatars"
617                 type="checkbox"
618                 checked={this.state.saveUserSettingsForm.show_avatars}
619                 onChange={linkEvent(this, this.handleShowAvatarsChange)}
620               />
621               <label class="form-check-label" htmlFor="user-show-avatars">
622                 {i18n.t("show_avatars")}
623               </label>
624             </div>
625           </div>
626           <div class="form-group">
627             <div class="form-check">
628               <input
629                 class="form-check-input"
630                 id="user-bot-account"
631                 type="checkbox"
632                 checked={this.state.saveUserSettingsForm.bot_account}
633                 onChange={linkEvent(this, this.handleBotAccount)}
634               />
635               <label class="form-check-label" htmlFor="user-bot-account">
636                 {i18n.t("bot_account")}
637               </label>
638             </div>
639           </div>
640           <div class="form-group">
641             <div class="form-check">
642               <input
643                 class="form-check-input"
644                 id="user-show-bot-accounts"
645                 type="checkbox"
646                 checked={this.state.saveUserSettingsForm.show_bot_accounts}
647                 onChange={linkEvent(this, this.handleShowBotAccounts)}
648               />
649               <label class="form-check-label" htmlFor="user-show-bot-accounts">
650                 {i18n.t("show_bot_accounts")}
651               </label>
652             </div>
653           </div>
654           <div class="form-group">
655             <div class="form-check">
656               <input
657                 class="form-check-input"
658                 id="user-show-read-posts"
659                 type="checkbox"
660                 checked={this.state.saveUserSettingsForm.show_read_posts}
661                 onChange={linkEvent(this, this.handleReadPosts)}
662               />
663               <label class="form-check-label" htmlFor="user-show-read-posts">
664                 {i18n.t("show_read_posts")}
665               </label>
666             </div>
667           </div>
668           <div class="form-group">
669             <div class="form-check">
670               <input
671                 class="form-check-input"
672                 id="user-show-new-post-notifs"
673                 type="checkbox"
674                 checked={this.state.saveUserSettingsForm.show_new_post_notifs}
675                 onChange={linkEvent(this, this.handleShowNewPostNotifs)}
676               />
677               <label
678                 class="form-check-label"
679                 htmlFor="user-show-new-post-notifs"
680               >
681                 {i18n.t("show_new_post_notifs")}
682               </label>
683             </div>
684           </div>
685           <div class="form-group">
686             <div class="form-check">
687               <input
688                 class="form-check-input"
689                 id="user-send-notifications-to-email"
690                 type="checkbox"
691                 disabled={!this.state.saveUserSettingsForm.email}
692                 checked={
693                   this.state.saveUserSettingsForm.send_notifications_to_email
694                 }
695                 onChange={linkEvent(
696                   this,
697                   this.handleSendNotificationsToEmailChange
698                 )}
699               />
700               <label
701                 class="form-check-label"
702                 htmlFor="user-send-notifications-to-email"
703               >
704                 {i18n.t("send_notifications_to_email")}
705               </label>
706             </div>
707           </div>
708           <div class="form-group">
709             <button type="submit" class="btn btn-block btn-secondary mr-4">
710               {this.state.saveUserSettingsLoading ? (
711                 <Spinner />
712               ) : (
713                 capitalizeFirstLetter(i18n.t("save"))
714               )}
715             </button>
716           </div>
717           <hr />
718           <div class="form-group">
719             <button
720               class="btn btn-block btn-danger"
721               onClick={linkEvent(
722                 this,
723                 this.handleDeleteAccountShowConfirmToggle
724               )}
725             >
726               {i18n.t("delete_account")}
727             </button>
728             {this.state.deleteAccountShowConfirm && (
729               <>
730                 <div class="my-2 alert alert-danger" role="alert">
731                   {i18n.t("delete_account_confirm")}
732                 </div>
733                 <input
734                   type="password"
735                   value={this.state.deleteAccountForm.password}
736                   autoComplete="new-password"
737                   maxLength={60}
738                   onInput={linkEvent(
739                     this,
740                     this.handleDeleteAccountPasswordChange
741                   )}
742                   class="form-control my-2"
743                 />
744                 <button
745                   class="btn btn-danger mr-4"
746                   disabled={!this.state.deleteAccountForm.password}
747                   onClick={linkEvent(this, this.handleDeleteAccount)}
748                 >
749                   {this.state.deleteAccountLoading ? (
750                     <Spinner />
751                   ) : (
752                     capitalizeFirstLetter(i18n.t("delete"))
753                   )}
754                 </button>
755                 <button
756                   class="btn btn-secondary"
757                   onClick={linkEvent(
758                     this,
759                     this.handleDeleteAccountShowConfirmToggle
760                   )}
761                 >
762                   {i18n.t("cancel")}
763                 </button>
764               </>
765             )}
766           </div>
767         </form>
768       </>
769     );
770   }
771
772   setupBlockPersonChoices() {
773     if (isBrowser()) {
774       let selectId: any = document.getElementById("block-person-filter");
775       if (selectId) {
776         this.blockPersonChoices = new Choices(selectId, choicesConfig);
777         this.blockPersonChoices.passedElement.element.addEventListener(
778           "choice",
779           (e: any) => {
780             this.handleBlockPerson(Number(e.detail.choice.value));
781           },
782           false
783         );
784         this.blockPersonChoices.passedElement.element.addEventListener(
785           "search",
786           debounce(async (e: any) => {
787             try {
788               let persons = (await fetchUsers(e.detail.value)).users;
789               let choices = persons.map(pvs => personToChoice(pvs));
790               this.blockPersonChoices.setChoices(
791                 choices,
792                 "value",
793                 "label",
794                 true
795               );
796             } catch (err) {
797               console.log(err);
798             }
799           }, 400),
800           false
801         );
802       }
803     }
804   }
805
806   setupBlockCommunityChoices() {
807     if (isBrowser()) {
808       let selectId: any = document.getElementById("block-community-filter");
809       if (selectId) {
810         this.blockCommunityChoices = new Choices(selectId, choicesConfig);
811         this.blockCommunityChoices.passedElement.element.addEventListener(
812           "choice",
813           (e: any) => {
814             this.handleBlockCommunity(Number(e.detail.choice.value));
815           },
816           false
817         );
818         this.blockCommunityChoices.passedElement.element.addEventListener(
819           "search",
820           debounce(async (e: any) => {
821             try {
822               let communities = (await fetchCommunities(e.detail.value))
823                 .communities;
824               let choices = communities.map(cv => communityToChoice(cv));
825               this.blockCommunityChoices.setChoices(
826                 choices,
827                 "value",
828                 "label",
829                 true
830               );
831             } catch (err) {
832               console.log(err);
833             }
834           }, 400),
835           false
836         );
837       }
838     }
839   }
840
841   handleBlockPerson(personId: number) {
842     if (personId != 0) {
843       let blockUserForm: BlockPerson = {
844         person_id: personId,
845         block: true,
846         auth: authField(),
847       };
848       WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
849     }
850   }
851
852   handleUnblockPerson(i: { ctx: Settings; recipientId: number }) {
853     let blockUserForm: BlockPerson = {
854       person_id: i.recipientId,
855       block: false,
856       auth: authField(),
857     };
858     WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
859   }
860
861   handleBlockCommunity(community_id: number) {
862     if (community_id != 0) {
863       let blockCommunityForm: BlockCommunity = {
864         community_id,
865         block: true,
866         auth: authField(),
867       };
868       WebSocketService.Instance.send(
869         wsClient.blockCommunity(blockCommunityForm)
870       );
871     }
872   }
873
874   handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
875     let blockCommunityForm: BlockCommunity = {
876       community_id: i.communityId,
877       block: false,
878       auth: authField(),
879     };
880     WebSocketService.Instance.send(wsClient.blockCommunity(blockCommunityForm));
881   }
882
883   handleShowNsfwChange(i: Settings, event: any) {
884     i.state.saveUserSettingsForm.show_nsfw = event.target.checked;
885     i.setState(i.state);
886   }
887
888   handleShowAvatarsChange(i: Settings, event: any) {
889     i.state.saveUserSettingsForm.show_avatars = event.target.checked;
890     UserService.Instance.myUserInfo.local_user_view.local_user.show_avatars =
891       event.target.checked; // Just for instant updates
892     i.setState(i.state);
893   }
894
895   handleBotAccount(i: Settings, event: any) {
896     i.state.saveUserSettingsForm.bot_account = event.target.checked;
897     i.setState(i.state);
898   }
899
900   handleShowBotAccounts(i: Settings, event: any) {
901     i.state.saveUserSettingsForm.show_bot_accounts = event.target.checked;
902     i.setState(i.state);
903   }
904
905   handleReadPosts(i: Settings, event: any) {
906     i.state.saveUserSettingsForm.show_read_posts = event.target.checked;
907     i.setState(i.state);
908   }
909
910   handleShowNewPostNotifs(i: Settings, event: any) {
911     i.state.saveUserSettingsForm.show_new_post_notifs = event.target.checked;
912     i.setState(i.state);
913   }
914
915   handleShowScoresChange(i: Settings, event: any) {
916     i.state.saveUserSettingsForm.show_scores = event.target.checked;
917     UserService.Instance.myUserInfo.local_user_view.local_user.show_scores =
918       event.target.checked; // Just for instant updates
919     i.setState(i.state);
920   }
921
922   handleSendNotificationsToEmailChange(i: Settings, event: any) {
923     i.state.saveUserSettingsForm.send_notifications_to_email =
924       event.target.checked;
925     i.setState(i.state);
926   }
927
928   handleThemeChange(i: Settings, event: any) {
929     i.state.saveUserSettingsForm.theme = event.target.value;
930     setTheme(event.target.value, true);
931     i.setState(i.state);
932   }
933
934   handleLangChange(i: Settings, event: any) {
935     i.state.saveUserSettingsForm.lang = event.target.value;
936     i18n.changeLanguage(getLanguage(i.state.saveUserSettingsForm.lang));
937     i.setState(i.state);
938   }
939
940   handleSortTypeChange(val: SortType) {
941     this.state.saveUserSettingsForm.default_sort_type =
942       Object.keys(SortType).indexOf(val);
943     this.setState(this.state);
944   }
945
946   handleListingTypeChange(val: ListingType) {
947     this.state.saveUserSettingsForm.default_listing_type =
948       Object.keys(ListingType).indexOf(val);
949     this.setState(this.state);
950   }
951
952   handleEmailChange(i: Settings, event: any) {
953     i.state.saveUserSettingsForm.email = event.target.value;
954     i.setState(i.state);
955   }
956
957   handleBioChange(val: string) {
958     this.state.saveUserSettingsForm.bio = val;
959     this.setState(this.state);
960   }
961
962   handleAvatarUpload(url: string) {
963     this.state.saveUserSettingsForm.avatar = url;
964     this.setState(this.state);
965   }
966
967   handleAvatarRemove() {
968     this.state.saveUserSettingsForm.avatar = "";
969     this.setState(this.state);
970   }
971
972   handleBannerUpload(url: string) {
973     this.state.saveUserSettingsForm.banner = url;
974     this.setState(this.state);
975   }
976
977   handleBannerRemove() {
978     this.state.saveUserSettingsForm.banner = "";
979     this.setState(this.state);
980   }
981
982   handleDisplayNameChange(i: Settings, event: any) {
983     i.state.saveUserSettingsForm.display_name = event.target.value;
984     i.setState(i.state);
985   }
986
987   handleMatrixUserIdChange(i: Settings, event: any) {
988     i.state.saveUserSettingsForm.matrix_user_id = event.target.value;
989     if (
990       i.state.saveUserSettingsForm.matrix_user_id == "" &&
991       !UserService.Instance.myUserInfo.local_user_view.person.matrix_user_id
992     ) {
993       i.state.saveUserSettingsForm.matrix_user_id = undefined;
994     }
995     i.setState(i.state);
996   }
997
998   handleNewPasswordChange(i: Settings, event: any) {
999     i.state.changePasswordForm.new_password = event.target.value;
1000     if (i.state.changePasswordForm.new_password == "") {
1001       i.state.changePasswordForm.new_password = undefined;
1002     }
1003     i.setState(i.state);
1004   }
1005
1006   handleNewPasswordVerifyChange(i: Settings, event: any) {
1007     i.state.changePasswordForm.new_password_verify = event.target.value;
1008     if (i.state.changePasswordForm.new_password_verify == "") {
1009       i.state.changePasswordForm.new_password_verify = undefined;
1010     }
1011     i.setState(i.state);
1012   }
1013
1014   handleOldPasswordChange(i: Settings, event: any) {
1015     i.state.changePasswordForm.old_password = event.target.value;
1016     if (i.state.changePasswordForm.old_password == "") {
1017       i.state.changePasswordForm.old_password = undefined;
1018     }
1019     i.setState(i.state);
1020   }
1021
1022   handleSaveSettingsSubmit(i: Settings, event: any) {
1023     event.preventDefault();
1024     i.state.saveUserSettingsLoading = true;
1025     i.setState(i.state);
1026
1027     WebSocketService.Instance.send(
1028       wsClient.saveUserSettings(i.state.saveUserSettingsForm)
1029     );
1030   }
1031
1032   handleChangePasswordSubmit(i: Settings, event: any) {
1033     event.preventDefault();
1034     i.state.changePasswordLoading = true;
1035     i.setState(i.state);
1036
1037     WebSocketService.Instance.send(
1038       wsClient.changePassword(i.state.changePasswordForm)
1039     );
1040   }
1041
1042   handleDeleteAccountShowConfirmToggle(i: Settings, event: any) {
1043     event.preventDefault();
1044     i.state.deleteAccountShowConfirm = !i.state.deleteAccountShowConfirm;
1045     i.setState(i.state);
1046   }
1047
1048   handleDeleteAccountPasswordChange(i: Settings, event: any) {
1049     i.state.deleteAccountForm.password = event.target.value;
1050     i.setState(i.state);
1051   }
1052
1053   handleDeleteAccount(i: Settings, event: any) {
1054     event.preventDefault();
1055     i.state.deleteAccountLoading = true;
1056     i.setState(i.state);
1057
1058     WebSocketService.Instance.send(
1059       wsClient.deleteAccount(i.state.deleteAccountForm)
1060     );
1061   }
1062
1063   handleSwitchTab(i: { ctx: Settings; tab: string }) {
1064     i.ctx.setState({ currentTab: i.tab });
1065
1066     if (i.ctx.state.currentTab == "blocks") {
1067       i.ctx.setupBlockPersonChoices();
1068       i.ctx.setupBlockCommunityChoices();
1069     }
1070   }
1071
1072   setUserInfo() {
1073     let luv = UserService.Instance.myUserInfo.local_user_view;
1074     this.state.saveUserSettingsForm.show_nsfw = luv.local_user.show_nsfw;
1075     this.state.saveUserSettingsForm.theme = luv.local_user.theme
1076       ? luv.local_user.theme
1077       : "browser";
1078     this.state.saveUserSettingsForm.default_sort_type =
1079       luv.local_user.default_sort_type;
1080     this.state.saveUserSettingsForm.default_listing_type =
1081       luv.local_user.default_listing_type;
1082     this.state.saveUserSettingsForm.lang = luv.local_user.lang;
1083     this.state.saveUserSettingsForm.avatar = luv.person.avatar;
1084     this.state.saveUserSettingsForm.banner = luv.person.banner;
1085     this.state.saveUserSettingsForm.display_name = luv.person.display_name;
1086     this.state.saveUserSettingsForm.show_avatars = luv.local_user.show_avatars;
1087     this.state.saveUserSettingsForm.bot_account = luv.person.bot_account;
1088     this.state.saveUserSettingsForm.show_bot_accounts =
1089       luv.local_user.show_bot_accounts;
1090     this.state.saveUserSettingsForm.show_scores = luv.local_user.show_scores;
1091     this.state.saveUserSettingsForm.show_read_posts =
1092       luv.local_user.show_read_posts;
1093     this.state.saveUserSettingsForm.show_new_post_notifs =
1094       luv.local_user.show_new_post_notifs;
1095     this.state.saveUserSettingsForm.email = luv.local_user.email;
1096     this.state.saveUserSettingsForm.bio = luv.person.bio;
1097     this.state.saveUserSettingsForm.send_notifications_to_email =
1098       luv.local_user.send_notifications_to_email;
1099     this.state.saveUserSettingsForm.matrix_user_id = luv.person.matrix_user_id;
1100     this.state.personBlocks = UserService.Instance.myUserInfo.person_blocks;
1101     this.state.communityBlocks =
1102       UserService.Instance.myUserInfo.community_blocks;
1103   }
1104
1105   parseMessage(msg: any) {
1106     let op = wsUserOp(msg);
1107     console.log(msg);
1108     if (msg.error) {
1109       toast(i18n.t(msg.error), "danger");
1110       return;
1111     } else if (op == UserOperation.SaveUserSettings) {
1112       let data = wsJsonToRes<LoginResponse>(msg).data;
1113       UserService.Instance.login(data);
1114       this.state.saveUserSettingsLoading = false;
1115       this.setState(this.state);
1116
1117       window.scrollTo(0, 0);
1118     } else if (op == UserOperation.ChangePassword) {
1119       let data = wsJsonToRes<LoginResponse>(msg).data;
1120       UserService.Instance.login(data);
1121       this.state.changePasswordLoading = false;
1122       this.setState(this.state);
1123       window.scrollTo(0, 0);
1124       toast(i18n.t("password_changed"));
1125     } else if (op == UserOperation.DeleteAccount) {
1126       this.setState({
1127         deleteAccountLoading: false,
1128         deleteAccountShowConfirm: false,
1129       });
1130       UserService.Instance.logout();
1131       window.location.href = "/";
1132       location.reload();
1133     } else if (op == UserOperation.BlockPerson) {
1134       let data = wsJsonToRes<BlockPersonResponse>(msg).data;
1135       this.setState({ personBlocks: updatePersonBlock(data) });
1136     } else if (op == UserOperation.BlockCommunity) {
1137       let data = wsJsonToRes<BlockCommunityResponse>(msg).data;
1138       this.setState({ communityBlocks: updateCommunityBlock(data) });
1139     }
1140   }
1141 }