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