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