]> Untitled Git - lemmy-ui.git/blob - src/shared/components/person/settings.tsx
Private instances (#523)
[lemmy-ui.git] / src / shared / components / person / settings.tsx
1 import { Component, linkEvent } from "inferno";
2 import {
3   BlockCommunity,
4   BlockCommunityResponse,
5   BlockPerson,
6   BlockPersonResponse,
7   ChangePassword,
8   CommunityBlockView,
9   CommunityView,
10   DeleteAccount,
11   GetSiteResponse,
12   ListingType,
13   LoginResponse,
14   PersonBlockView,
15   PersonViewSafe,
16   SaveUserSettings,
17   SortType,
18   UserOperation,
19 } from "lemmy-js-client";
20 import { Subscription } from "rxjs";
21 import { i18n } from "../../i18next";
22 import { UserService, WebSocketService } from "../../services";
23 import {
24   authField,
25   capitalizeFirstLetter,
26   choicesConfig,
27   communitySelectName,
28   communityToChoice,
29   debounce,
30   elementUrl,
31   fetchCommunities,
32   fetchUsers,
33   getLanguage,
34   getNativeLanguageName,
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
527                   .sort((a, b) => a.code.localeCompare(b.code))
528                   .map(lang => (
529                     <option value={lang.code}>
530                       {getNativeLanguageName(lang.code)}
531                     </option>
532                   ))}
533               </select>
534             </div>
535           </div>
536           <div class="form-group row">
537             <label class="col-sm-3" htmlFor="user-theme">
538               {i18n.t("theme")}
539             </label>
540             <div class="col-sm-9">
541               <select
542                 id="user-theme"
543                 value={this.state.saveUserSettingsForm.theme}
544                 onChange={linkEvent(this, this.handleThemeChange)}
545                 class="custom-select w-auto"
546               >
547                 <option disabled aria-hidden="true">
548                   {i18n.t("theme")}
549                 </option>
550                 <option value="browser">{i18n.t("browser_default")}</option>
551                 {themes.map(theme => (
552                   <option value={theme}>{theme}</option>
553                 ))}
554               </select>
555             </div>
556           </div>
557           <form className="form-group row">
558             <label class="col-sm-3">{i18n.t("type")}</label>
559             <div class="col-sm-9">
560               <ListingTypeSelect
561                 type_={
562                   Object.values(ListingType)[
563                     this.state.saveUserSettingsForm.default_listing_type
564                   ]
565                 }
566                 showLocal={showLocal(this.isoData)}
567                 onChange={this.handleListingTypeChange}
568               />
569             </div>
570           </form>
571           <form className="form-group row">
572             <label class="col-sm-3">{i18n.t("sort_type")}</label>
573             <div class="col-sm-9">
574               <SortSelect
575                 sort={
576                   Object.values(SortType)[
577                     this.state.saveUserSettingsForm.default_sort_type
578                   ]
579                 }
580                 onChange={this.handleSortTypeChange}
581               />
582             </div>
583           </form>
584           {this.state.siteRes.site_view.site.enable_nsfw && (
585             <div class="form-group">
586               <div class="form-check">
587                 <input
588                   class="form-check-input"
589                   id="user-show-nsfw"
590                   type="checkbox"
591                   checked={this.state.saveUserSettingsForm.show_nsfw}
592                   onChange={linkEvent(this, this.handleShowNsfwChange)}
593                 />
594                 <label class="form-check-label" htmlFor="user-show-nsfw">
595                   {i18n.t("show_nsfw")}
596                 </label>
597               </div>
598             </div>
599           )}
600           <div class="form-group">
601             <div class="form-check">
602               <input
603                 class="form-check-input"
604                 id="user-show-scores"
605                 type="checkbox"
606                 checked={this.state.saveUserSettingsForm.show_scores}
607                 onChange={linkEvent(this, this.handleShowScoresChange)}
608               />
609               <label class="form-check-label" htmlFor="user-show-scores">
610                 {i18n.t("show_scores")}
611               </label>
612             </div>
613           </div>
614           <div class="form-group">
615             <div class="form-check">
616               <input
617                 class="form-check-input"
618                 id="user-show-avatars"
619                 type="checkbox"
620                 checked={this.state.saveUserSettingsForm.show_avatars}
621                 onChange={linkEvent(this, this.handleShowAvatarsChange)}
622               />
623               <label class="form-check-label" htmlFor="user-show-avatars">
624                 {i18n.t("show_avatars")}
625               </label>
626             </div>
627           </div>
628           <div class="form-group">
629             <div class="form-check">
630               <input
631                 class="form-check-input"
632                 id="user-bot-account"
633                 type="checkbox"
634                 checked={this.state.saveUserSettingsForm.bot_account}
635                 onChange={linkEvent(this, this.handleBotAccount)}
636               />
637               <label class="form-check-label" htmlFor="user-bot-account">
638                 {i18n.t("bot_account")}
639               </label>
640             </div>
641           </div>
642           <div class="form-group">
643             <div class="form-check">
644               <input
645                 class="form-check-input"
646                 id="user-show-bot-accounts"
647                 type="checkbox"
648                 checked={this.state.saveUserSettingsForm.show_bot_accounts}
649                 onChange={linkEvent(this, this.handleShowBotAccounts)}
650               />
651               <label class="form-check-label" htmlFor="user-show-bot-accounts">
652                 {i18n.t("show_bot_accounts")}
653               </label>
654             </div>
655           </div>
656           <div class="form-group">
657             <div class="form-check">
658               <input
659                 class="form-check-input"
660                 id="user-show-read-posts"
661                 type="checkbox"
662                 checked={this.state.saveUserSettingsForm.show_read_posts}
663                 onChange={linkEvent(this, this.handleReadPosts)}
664               />
665               <label class="form-check-label" htmlFor="user-show-read-posts">
666                 {i18n.t("show_read_posts")}
667               </label>
668             </div>
669           </div>
670           <div class="form-group">
671             <div class="form-check">
672               <input
673                 class="form-check-input"
674                 id="user-show-new-post-notifs"
675                 type="checkbox"
676                 checked={this.state.saveUserSettingsForm.show_new_post_notifs}
677                 onChange={linkEvent(this, this.handleShowNewPostNotifs)}
678               />
679               <label
680                 class="form-check-label"
681                 htmlFor="user-show-new-post-notifs"
682               >
683                 {i18n.t("show_new_post_notifs")}
684               </label>
685             </div>
686           </div>
687           <div class="form-group">
688             <div class="form-check">
689               <input
690                 class="form-check-input"
691                 id="user-send-notifications-to-email"
692                 type="checkbox"
693                 disabled={!this.state.saveUserSettingsForm.email}
694                 checked={
695                   this.state.saveUserSettingsForm.send_notifications_to_email
696                 }
697                 onChange={linkEvent(
698                   this,
699                   this.handleSendNotificationsToEmailChange
700                 )}
701               />
702               <label
703                 class="form-check-label"
704                 htmlFor="user-send-notifications-to-email"
705               >
706                 {i18n.t("send_notifications_to_email")}
707               </label>
708             </div>
709           </div>
710           <div class="form-group">
711             <button type="submit" class="btn btn-block btn-secondary mr-4">
712               {this.state.saveUserSettingsLoading ? (
713                 <Spinner />
714               ) : (
715                 capitalizeFirstLetter(i18n.t("save"))
716               )}
717             </button>
718           </div>
719           <hr />
720           <div class="form-group">
721             <button
722               class="btn btn-block btn-danger"
723               onClick={linkEvent(
724                 this,
725                 this.handleDeleteAccountShowConfirmToggle
726               )}
727             >
728               {i18n.t("delete_account")}
729             </button>
730             {this.state.deleteAccountShowConfirm && (
731               <>
732                 <div class="my-2 alert alert-danger" role="alert">
733                   {i18n.t("delete_account_confirm")}
734                 </div>
735                 <input
736                   type="password"
737                   value={this.state.deleteAccountForm.password}
738                   autoComplete="new-password"
739                   maxLength={60}
740                   onInput={linkEvent(
741                     this,
742                     this.handleDeleteAccountPasswordChange
743                   )}
744                   class="form-control my-2"
745                 />
746                 <button
747                   class="btn btn-danger mr-4"
748                   disabled={!this.state.deleteAccountForm.password}
749                   onClick={linkEvent(this, this.handleDeleteAccount)}
750                 >
751                   {this.state.deleteAccountLoading ? (
752                     <Spinner />
753                   ) : (
754                     capitalizeFirstLetter(i18n.t("delete"))
755                   )}
756                 </button>
757                 <button
758                   class="btn btn-secondary"
759                   onClick={linkEvent(
760                     this,
761                     this.handleDeleteAccountShowConfirmToggle
762                   )}
763                 >
764                   {i18n.t("cancel")}
765                 </button>
766               </>
767             )}
768           </div>
769         </form>
770       </>
771     );
772   }
773
774   setupBlockPersonChoices() {
775     if (isBrowser()) {
776       let selectId: any = document.getElementById("block-person-filter");
777       if (selectId) {
778         this.blockPersonChoices = new Choices(selectId, choicesConfig);
779         this.blockPersonChoices.passedElement.element.addEventListener(
780           "choice",
781           (e: any) => {
782             this.handleBlockPerson(Number(e.detail.choice.value));
783           },
784           false
785         );
786         this.blockPersonChoices.passedElement.element.addEventListener(
787           "search",
788           debounce(async (e: any) => {
789             try {
790               let persons = (await fetchUsers(e.detail.value)).users;
791               let choices = persons.map(pvs => personToChoice(pvs));
792               this.blockPersonChoices.setChoices(
793                 choices,
794                 "value",
795                 "label",
796                 true
797               );
798             } catch (err) {
799               console.error(err);
800             }
801           }, 400),
802           false
803         );
804       }
805     }
806   }
807
808   setupBlockCommunityChoices() {
809     if (isBrowser()) {
810       let selectId: any = document.getElementById("block-community-filter");
811       if (selectId) {
812         this.blockCommunityChoices = new Choices(selectId, choicesConfig);
813         this.blockCommunityChoices.passedElement.element.addEventListener(
814           "choice",
815           (e: any) => {
816             this.handleBlockCommunity(Number(e.detail.choice.value));
817           },
818           false
819         );
820         this.blockCommunityChoices.passedElement.element.addEventListener(
821           "search",
822           debounce(async (e: any) => {
823             try {
824               let communities = (await fetchCommunities(e.detail.value))
825                 .communities;
826               let choices = communities.map(cv => communityToChoice(cv));
827               this.blockCommunityChoices.setChoices(
828                 choices,
829                 "value",
830                 "label",
831                 true
832               );
833             } catch (err) {
834               console.log(err);
835             }
836           }, 400),
837           false
838         );
839       }
840     }
841   }
842
843   handleBlockPerson(personId: number) {
844     if (personId != 0) {
845       let blockUserForm: BlockPerson = {
846         person_id: personId,
847         block: true,
848         auth: authField(),
849       };
850       WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
851     }
852   }
853
854   handleUnblockPerson(i: { ctx: Settings; recipientId: number }) {
855     let blockUserForm: BlockPerson = {
856       person_id: i.recipientId,
857       block: false,
858       auth: authField(),
859     };
860     WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
861   }
862
863   handleBlockCommunity(community_id: number) {
864     if (community_id != 0) {
865       let blockCommunityForm: BlockCommunity = {
866         community_id,
867         block: true,
868         auth: authField(),
869       };
870       WebSocketService.Instance.send(
871         wsClient.blockCommunity(blockCommunityForm)
872       );
873     }
874   }
875
876   handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
877     let blockCommunityForm: BlockCommunity = {
878       community_id: i.communityId,
879       block: false,
880       auth: authField(),
881     };
882     WebSocketService.Instance.send(wsClient.blockCommunity(blockCommunityForm));
883   }
884
885   handleShowNsfwChange(i: Settings, event: any) {
886     i.state.saveUserSettingsForm.show_nsfw = event.target.checked;
887     i.setState(i.state);
888   }
889
890   handleShowAvatarsChange(i: Settings, event: any) {
891     i.state.saveUserSettingsForm.show_avatars = event.target.checked;
892     UserService.Instance.myUserInfo.local_user_view.local_user.show_avatars =
893       event.target.checked; // Just for instant updates
894     i.setState(i.state);
895   }
896
897   handleBotAccount(i: Settings, event: any) {
898     i.state.saveUserSettingsForm.bot_account = event.target.checked;
899     i.setState(i.state);
900   }
901
902   handleShowBotAccounts(i: Settings, event: any) {
903     i.state.saveUserSettingsForm.show_bot_accounts = event.target.checked;
904     i.setState(i.state);
905   }
906
907   handleReadPosts(i: Settings, event: any) {
908     i.state.saveUserSettingsForm.show_read_posts = event.target.checked;
909     i.setState(i.state);
910   }
911
912   handleShowNewPostNotifs(i: Settings, event: any) {
913     i.state.saveUserSettingsForm.show_new_post_notifs = event.target.checked;
914     i.setState(i.state);
915   }
916
917   handleShowScoresChange(i: Settings, event: any) {
918     i.state.saveUserSettingsForm.show_scores = event.target.checked;
919     UserService.Instance.myUserInfo.local_user_view.local_user.show_scores =
920       event.target.checked; // Just for instant updates
921     i.setState(i.state);
922   }
923
924   handleSendNotificationsToEmailChange(i: Settings, event: any) {
925     i.state.saveUserSettingsForm.send_notifications_to_email =
926       event.target.checked;
927     i.setState(i.state);
928   }
929
930   handleThemeChange(i: Settings, event: any) {
931     i.state.saveUserSettingsForm.theme = event.target.value;
932     setTheme(event.target.value, true);
933     i.setState(i.state);
934   }
935
936   handleLangChange(i: Settings, event: any) {
937     i.state.saveUserSettingsForm.lang = event.target.value;
938     i18n.changeLanguage(getLanguage(i.state.saveUserSettingsForm.lang));
939     i.setState(i.state);
940   }
941
942   handleSortTypeChange(val: SortType) {
943     this.state.saveUserSettingsForm.default_sort_type =
944       Object.keys(SortType).indexOf(val);
945     this.setState(this.state);
946   }
947
948   handleListingTypeChange(val: ListingType) {
949     this.state.saveUserSettingsForm.default_listing_type =
950       Object.keys(ListingType).indexOf(val);
951     this.setState(this.state);
952   }
953
954   handleEmailChange(i: Settings, event: any) {
955     i.state.saveUserSettingsForm.email = event.target.value;
956     i.setState(i.state);
957   }
958
959   handleBioChange(val: string) {
960     this.state.saveUserSettingsForm.bio = val;
961     this.setState(this.state);
962   }
963
964   handleAvatarUpload(url: string) {
965     this.state.saveUserSettingsForm.avatar = url;
966     this.setState(this.state);
967   }
968
969   handleAvatarRemove() {
970     this.state.saveUserSettingsForm.avatar = "";
971     this.setState(this.state);
972   }
973
974   handleBannerUpload(url: string) {
975     this.state.saveUserSettingsForm.banner = url;
976     this.setState(this.state);
977   }
978
979   handleBannerRemove() {
980     this.state.saveUserSettingsForm.banner = "";
981     this.setState(this.state);
982   }
983
984   handleDisplayNameChange(i: Settings, event: any) {
985     i.state.saveUserSettingsForm.display_name = event.target.value;
986     i.setState(i.state);
987   }
988
989   handleMatrixUserIdChange(i: Settings, event: any) {
990     i.state.saveUserSettingsForm.matrix_user_id = event.target.value;
991     if (
992       i.state.saveUserSettingsForm.matrix_user_id == "" &&
993       !UserService.Instance.myUserInfo.local_user_view.person.matrix_user_id
994     ) {
995       i.state.saveUserSettingsForm.matrix_user_id = undefined;
996     }
997     i.setState(i.state);
998   }
999
1000   handleNewPasswordChange(i: Settings, event: any) {
1001     i.state.changePasswordForm.new_password = event.target.value;
1002     if (i.state.changePasswordForm.new_password == "") {
1003       i.state.changePasswordForm.new_password = undefined;
1004     }
1005     i.setState(i.state);
1006   }
1007
1008   handleNewPasswordVerifyChange(i: Settings, event: any) {
1009     i.state.changePasswordForm.new_password_verify = event.target.value;
1010     if (i.state.changePasswordForm.new_password_verify == "") {
1011       i.state.changePasswordForm.new_password_verify = undefined;
1012     }
1013     i.setState(i.state);
1014   }
1015
1016   handleOldPasswordChange(i: Settings, event: any) {
1017     i.state.changePasswordForm.old_password = event.target.value;
1018     if (i.state.changePasswordForm.old_password == "") {
1019       i.state.changePasswordForm.old_password = undefined;
1020     }
1021     i.setState(i.state);
1022   }
1023
1024   handleSaveSettingsSubmit(i: Settings, event: any) {
1025     event.preventDefault();
1026     i.state.saveUserSettingsLoading = true;
1027     i.setState(i.state);
1028
1029     WebSocketService.Instance.send(
1030       wsClient.saveUserSettings(i.state.saveUserSettingsForm)
1031     );
1032   }
1033
1034   handleChangePasswordSubmit(i: Settings, event: any) {
1035     event.preventDefault();
1036     i.state.changePasswordLoading = true;
1037     i.setState(i.state);
1038
1039     WebSocketService.Instance.send(
1040       wsClient.changePassword(i.state.changePasswordForm)
1041     );
1042   }
1043
1044   handleDeleteAccountShowConfirmToggle(i: Settings, event: any) {
1045     event.preventDefault();
1046     i.state.deleteAccountShowConfirm = !i.state.deleteAccountShowConfirm;
1047     i.setState(i.state);
1048   }
1049
1050   handleDeleteAccountPasswordChange(i: Settings, event: any) {
1051     i.state.deleteAccountForm.password = event.target.value;
1052     i.setState(i.state);
1053   }
1054
1055   handleDeleteAccount(i: Settings, event: any) {
1056     event.preventDefault();
1057     i.state.deleteAccountLoading = true;
1058     i.setState(i.state);
1059
1060     WebSocketService.Instance.send(
1061       wsClient.deleteAccount(i.state.deleteAccountForm)
1062     );
1063   }
1064
1065   handleSwitchTab(i: { ctx: Settings; tab: string }) {
1066     i.ctx.setState({ currentTab: i.tab });
1067
1068     if (i.ctx.state.currentTab == "blocks") {
1069       i.ctx.setupBlockPersonChoices();
1070       i.ctx.setupBlockCommunityChoices();
1071     }
1072   }
1073
1074   setUserInfo() {
1075     let luv = UserService.Instance.myUserInfo.local_user_view;
1076     this.state.saveUserSettingsForm.show_nsfw = luv.local_user.show_nsfw;
1077     this.state.saveUserSettingsForm.theme = luv.local_user.theme
1078       ? luv.local_user.theme
1079       : "browser";
1080     this.state.saveUserSettingsForm.default_sort_type =
1081       luv.local_user.default_sort_type;
1082     this.state.saveUserSettingsForm.default_listing_type =
1083       luv.local_user.default_listing_type;
1084     this.state.saveUserSettingsForm.lang = luv.local_user.lang;
1085     this.state.saveUserSettingsForm.avatar = luv.person.avatar;
1086     this.state.saveUserSettingsForm.banner = luv.person.banner;
1087     this.state.saveUserSettingsForm.display_name = luv.person.display_name;
1088     this.state.saveUserSettingsForm.show_avatars = luv.local_user.show_avatars;
1089     this.state.saveUserSettingsForm.bot_account = luv.person.bot_account;
1090     this.state.saveUserSettingsForm.show_bot_accounts =
1091       luv.local_user.show_bot_accounts;
1092     this.state.saveUserSettingsForm.show_scores = luv.local_user.show_scores;
1093     this.state.saveUserSettingsForm.show_read_posts =
1094       luv.local_user.show_read_posts;
1095     this.state.saveUserSettingsForm.show_new_post_notifs =
1096       luv.local_user.show_new_post_notifs;
1097     this.state.saveUserSettingsForm.email = luv.local_user.email;
1098     this.state.saveUserSettingsForm.bio = luv.person.bio;
1099     this.state.saveUserSettingsForm.send_notifications_to_email =
1100       luv.local_user.send_notifications_to_email;
1101     this.state.saveUserSettingsForm.matrix_user_id = luv.person.matrix_user_id;
1102     this.state.personBlocks = UserService.Instance.myUserInfo.person_blocks;
1103     this.state.communityBlocks =
1104       UserService.Instance.myUserInfo.community_blocks;
1105   }
1106
1107   parseMessage(msg: any) {
1108     let op = wsUserOp(msg);
1109     console.log(msg);
1110     if (msg.error) {
1111       this.setState({
1112         saveUserSettingsLoading: false,
1113         changePasswordLoading: false,
1114         deleteAccountLoading: false,
1115       });
1116       toast(i18n.t(msg.error), "danger");
1117       return;
1118     } else if (op == UserOperation.SaveUserSettings) {
1119       let data = wsJsonToRes<LoginResponse>(msg).data;
1120       UserService.Instance.login(data);
1121       this.state.saveUserSettingsLoading = false;
1122       this.setState(this.state);
1123
1124       window.scrollTo(0, 0);
1125     } else if (op == UserOperation.ChangePassword) {
1126       let data = wsJsonToRes<LoginResponse>(msg).data;
1127       UserService.Instance.login(data);
1128       this.state.changePasswordLoading = false;
1129       this.setState(this.state);
1130       window.scrollTo(0, 0);
1131       toast(i18n.t("password_changed"));
1132     } else if (op == UserOperation.DeleteAccount) {
1133       this.setState({
1134         deleteAccountLoading: false,
1135         deleteAccountShowConfirm: false,
1136       });
1137       UserService.Instance.logout();
1138       window.location.href = "/";
1139       location.reload();
1140     } else if (op == UserOperation.BlockPerson) {
1141       let data = wsJsonToRes<BlockPersonResponse>(msg).data;
1142       this.setState({ personBlocks: updatePersonBlock(data) });
1143     } else if (op == UserOperation.BlockCommunity) {
1144       let data = wsJsonToRes<BlockCommunityResponse>(msg).data;
1145       this.setState({ communityBlocks: updateCommunityBlock(data) });
1146     }
1147   }
1148 }