import { Component, linkEvent } from "inferno"; import { BlockCommunity, BlockCommunityResponse, BlockPerson, BlockPersonResponse, ChangePassword, CommunityBlockView, CommunityView, DeleteAccount, GetSiteResponse, ListingType, LoginResponse, PersonBlockView, PersonViewSafe, SaveUserSettings, SortType, UserOperation, wsJsonToRes, wsUserOp, } from "lemmy-js-client"; import { Subscription } from "rxjs"; import { i18n, languages } from "../../i18next"; import { UserService, WebSocketService } from "../../services"; import { capitalizeFirstLetter, choicesConfig, communitySelectName, communityToChoice, debounce, elementUrl, enableNsfw, fetchCommunities, fetchThemeList, fetchUsers, getLanguages, isBrowser, myAuth, personSelectName, personToChoice, relTags, setIsoData, setTheme, setupTippy, showLocal, toast, updateCommunityBlock, updatePersonBlock, wsClient, wsSubscribe, } from "../../utils"; import { HtmlTags } from "../common/html-tags"; import { Icon, Spinner } from "../common/icon"; import { ImageUploadForm } from "../common/image-upload-form"; import { LanguageSelect } from "../common/language-select"; import { ListingTypeSelect } from "../common/listing-type-select"; import { MarkdownTextArea } from "../common/markdown-textarea"; import { SortSelect } from "../common/sort-select"; import { CommunityLink } from "../community/community-link"; import { PersonListing } from "./person-listing"; var Choices: any; if (isBrowser()) { Choices = require("choices.js"); } interface SettingsState { // TODO redo these forms saveUserSettingsForm: { show_nsfw?: boolean; theme?: string; default_sort_type?: number; default_listing_type?: number; interface_language?: string; avatar?: string; banner?: string; display_name?: string; email?: string; bio?: string; matrix_user_id?: string; show_avatars?: boolean; show_scores?: boolean; send_notifications_to_email?: boolean; bot_account?: boolean; show_bot_accounts?: boolean; show_read_posts?: boolean; show_new_post_notifs?: boolean; discussion_languages?: number[]; generate_totp_2fa?: boolean; }; changePasswordForm: { new_password?: string; new_password_verify?: string; old_password?: string; }; deleteAccountForm: { password?: string; }; personBlocks: PersonBlockView[]; blockPerson?: PersonViewSafe; communityBlocks: CommunityBlockView[]; blockCommunityId: number; blockCommunity?: CommunityView; currentTab: string; themeList: string[]; saveUserSettingsLoading: boolean; changePasswordLoading: boolean; deleteAccountLoading: boolean; deleteAccountShowConfirm: boolean; siteRes: GetSiteResponse; } export class Settings extends Component { private isoData = setIsoData(this.context); private blockPersonChoices: any; private blockCommunityChoices: any; private subscription?: Subscription; state: SettingsState = { saveUserSettingsForm: {}, changePasswordForm: {}, saveUserSettingsLoading: false, changePasswordLoading: false, deleteAccountLoading: false, deleteAccountShowConfirm: false, deleteAccountForm: {}, personBlocks: [], communityBlocks: [], blockCommunityId: 0, currentTab: "settings", siteRes: this.isoData.site_res, themeList: [], }; constructor(props: any, context: any) { super(props, context); this.handleSortTypeChange = this.handleSortTypeChange.bind(this); this.handleListingTypeChange = this.handleListingTypeChange.bind(this); this.handleBioChange = this.handleBioChange.bind(this); this.handleDiscussionLanguageChange = this.handleDiscussionLanguageChange.bind(this); this.handleAvatarUpload = this.handleAvatarUpload.bind(this); this.handleAvatarRemove = this.handleAvatarRemove.bind(this); this.handleBannerUpload = this.handleBannerUpload.bind(this); this.handleBannerRemove = this.handleBannerRemove.bind(this); this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); let mui = UserService.Instance.myUserInfo; if (mui) { let luv = mui.local_user_view; this.state = { ...this.state, personBlocks: mui.person_blocks, communityBlocks: mui.community_blocks, saveUserSettingsForm: { ...this.state.saveUserSettingsForm, show_nsfw: luv.local_user.show_nsfw, theme: luv.local_user.theme ? luv.local_user.theme : "browser", default_sort_type: luv.local_user.default_sort_type, default_listing_type: luv.local_user.default_listing_type, interface_language: luv.local_user.interface_language, discussion_languages: mui.discussion_languages, avatar: luv.person.avatar, banner: luv.person.banner, display_name: luv.person.display_name, show_avatars: luv.local_user.show_avatars, bot_account: luv.person.bot_account, show_bot_accounts: luv.local_user.show_bot_accounts, show_scores: luv.local_user.show_scores, show_read_posts: luv.local_user.show_read_posts, show_new_post_notifs: luv.local_user.show_new_post_notifs, email: luv.local_user.email, bio: luv.person.bio, send_notifications_to_email: luv.local_user.send_notifications_to_email, matrix_user_id: luv.person.matrix_user_id, }, }; } } async componentDidMount() { setupTippy(); this.setState({ themeList: await fetchThemeList() }); } componentWillUnmount() { this.subscription?.unsubscribe(); } get documentTitle(): string { return i18n.t("settings"); } render() { return (
<>
{this.state.currentTab == "settings" && this.userSettings()} {this.state.currentTab == "blocks" && this.blockCards()}
); } userSettings() { return (
{this.saveUserSettingsHtmlForm()}
{this.changePasswordHtmlForm()}
); } blockCards() { return (
{this.blockUserCard()}
{this.blockCommunityCard()}
); } changePasswordHtmlForm() { return ( <>
{i18n.t("change_password")}
); } blockUserCard() { return (
{this.blockUserForm()} {this.blockedUsersList()}
); } blockedUsersList() { return ( <>
{i18n.t("blocked_users")}
); } blockUserForm() { let blockPerson = this.state.blockPerson; return (
); } blockCommunityCard() { return (
{this.blockCommunityForm()} {this.blockedCommunitiesList()}
); } blockedCommunitiesList() { return ( <>
{i18n.t("blocked_communities")}
); } blockCommunityForm() { return (
); } saveUserSettingsHtmlForm() { let selectedLangs = this.state.saveUserSettingsForm.discussion_languages; return ( <>
{i18n.t("settings")}
{enableNsfw(this.state.siteRes) && (
)}
{this.totpSection()}

{this.state.deleteAccountShowConfirm && ( <>
{i18n.t("delete_account_confirm")}
)}
); } totpSection() { let totpUrl = UserService.Instance.myUserInfo?.local_user_view.local_user.totp_2fa_url; return ( <> {!totpUrl && (
)} {totpUrl && ( <>
{i18n.t("two_factor_link")}
)} ); } setupBlockPersonChoices() { if (isBrowser()) { let selectId: any = document.getElementById("block-person-filter"); if (selectId) { this.blockPersonChoices = new Choices(selectId, choicesConfig); this.blockPersonChoices.passedElement.element.addEventListener( "choice", (e: any) => { this.handleBlockPerson(Number(e.detail.choice.value)); }, false ); this.blockPersonChoices.passedElement.element.addEventListener( "search", debounce(async (e: any) => { try { let persons = (await fetchUsers(e.detail.value)).users; let choices = persons.map(pvs => personToChoice(pvs)); this.blockPersonChoices.setChoices( choices, "value", "label", true ); } catch (err) { console.error(err); } }), false ); } } } setupBlockCommunityChoices() { if (isBrowser()) { let selectId: any = document.getElementById("block-community-filter"); if (selectId) { this.blockCommunityChoices = new Choices(selectId, choicesConfig); this.blockCommunityChoices.passedElement.element.addEventListener( "choice", (e: any) => { this.handleBlockCommunity(Number(e.detail.choice.value)); }, false ); this.blockCommunityChoices.passedElement.element.addEventListener( "search", debounce(async (e: any) => { try { let communities = (await fetchCommunities(e.detail.value)) .communities; let choices = communities.map(cv => communityToChoice(cv)); this.blockCommunityChoices.setChoices( choices, "value", "label", true ); } catch (err) { console.log(err); } }), false ); } } } handleBlockPerson(personId: number) { let auth = myAuth(); if (auth && personId != 0) { let blockUserForm: BlockPerson = { person_id: personId, block: true, auth, }; WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); } } handleUnblockPerson(i: { ctx: Settings; recipientId: number }) { let auth = myAuth(); if (auth) { let blockUserForm: BlockPerson = { person_id: i.recipientId, block: false, auth, }; WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); } } handleBlockCommunity(community_id: number) { let auth = myAuth(); if (auth && community_id != 0) { let blockCommunityForm: BlockCommunity = { community_id, block: true, auth, }; WebSocketService.Instance.send( wsClient.blockCommunity(blockCommunityForm) ); } } handleUnblockCommunity(i: { ctx: Settings; communityId: number }) { let auth = myAuth(); if (auth) { let blockCommunityForm: BlockCommunity = { community_id: i.communityId, block: false, auth, }; WebSocketService.Instance.send( wsClient.blockCommunity(blockCommunityForm) ); } } handleShowNsfwChange(i: Settings, event: any) { i.state.saveUserSettingsForm.show_nsfw = event.target.checked; i.setState(i.state); } handleShowAvatarsChange(i: Settings, event: any) { i.state.saveUserSettingsForm.show_avatars = event.target.checked; let mui = UserService.Instance.myUserInfo; if (mui) { mui.local_user_view.local_user.show_avatars = event.target.checked; } i.setState(i.state); } handleBotAccount(i: Settings, event: any) { i.state.saveUserSettingsForm.bot_account = event.target.checked; i.setState(i.state); } handleShowBotAccounts(i: Settings, event: any) { i.state.saveUserSettingsForm.show_bot_accounts = event.target.checked; i.setState(i.state); } handleReadPosts(i: Settings, event: any) { i.state.saveUserSettingsForm.show_read_posts = event.target.checked; i.setState(i.state); } handleShowNewPostNotifs(i: Settings, event: any) { i.state.saveUserSettingsForm.show_new_post_notifs = event.target.checked; i.setState(i.state); } handleShowScoresChange(i: Settings, event: any) { i.state.saveUserSettingsForm.show_scores = event.target.checked; let mui = UserService.Instance.myUserInfo; if (mui) { mui.local_user_view.local_user.show_scores = event.target.checked; } i.setState(i.state); } handleGenerateTotp(i: Settings, event: any) { // Coerce false to undefined here, so it won't generate it. let checked: boolean | undefined = event.target.checked || undefined; if (checked) { toast(i18n.t("two_factor_setup_instructions")); } i.state.saveUserSettingsForm.generate_totp_2fa = checked; i.setState(i.state); } handleRemoveTotp(i: Settings, event: any) { // Coerce true to undefined here, so it won't generate it. let checked: boolean | undefined = !event.target.checked && undefined; i.state.saveUserSettingsForm.generate_totp_2fa = checked; i.setState(i.state); } handleSendNotificationsToEmailChange(i: Settings, event: any) { i.state.saveUserSettingsForm.send_notifications_to_email = event.target.checked; i.setState(i.state); } handleThemeChange(i: Settings, event: any) { i.state.saveUserSettingsForm.theme = event.target.value; setTheme(event.target.value, true); i.setState(i.state); } handleInterfaceLangChange(i: Settings, event: any) { i.state.saveUserSettingsForm.interface_language = event.target.value; i18n.changeLanguage( getLanguages(i.state.saveUserSettingsForm.interface_language).at(0) ); i.setState(i.state); } handleDiscussionLanguageChange(val: number[]) { this.setState( s => ((s.saveUserSettingsForm.discussion_languages = val), s) ); } handleSortTypeChange(val: SortType) { this.setState( s => ( (s.saveUserSettingsForm.default_sort_type = Object.keys(SortType).indexOf(val)), s ) ); } handleListingTypeChange(val: ListingType) { this.setState( s => ( (s.saveUserSettingsForm.default_listing_type = Object.keys(ListingType).indexOf(val)), s ) ); } handleEmailChange(i: Settings, event: any) { i.state.saveUserSettingsForm.email = event.target.value; i.setState(i.state); } handleBioChange(val: string) { this.setState(s => ((s.saveUserSettingsForm.bio = val), s)); } handleAvatarUpload(url: string) { this.setState(s => ((s.saveUserSettingsForm.avatar = url), s)); } handleAvatarRemove() { this.setState(s => ((s.saveUserSettingsForm.avatar = ""), s)); } handleBannerUpload(url: string) { this.setState(s => ((s.saveUserSettingsForm.banner = url), s)); } handleBannerRemove() { this.setState(s => ((s.saveUserSettingsForm.banner = ""), s)); } handleDisplayNameChange(i: Settings, event: any) { i.state.saveUserSettingsForm.display_name = event.target.value; i.setState(i.state); } handleMatrixUserIdChange(i: Settings, event: any) { i.state.saveUserSettingsForm.matrix_user_id = event.target.value; i.setState(i.state); } handleNewPasswordChange(i: Settings, event: any) { i.state.changePasswordForm.new_password = event.target.value; if (i.state.changePasswordForm.new_password == "") { i.state.changePasswordForm.new_password = undefined; } i.setState(i.state); } handleNewPasswordVerifyChange(i: Settings, event: any) { i.state.changePasswordForm.new_password_verify = event.target.value; if (i.state.changePasswordForm.new_password_verify == "") { i.state.changePasswordForm.new_password_verify = undefined; } i.setState(i.state); } handleOldPasswordChange(i: Settings, event: any) { i.state.changePasswordForm.old_password = event.target.value; if (i.state.changePasswordForm.old_password == "") { i.state.changePasswordForm.old_password = undefined; } i.setState(i.state); } handleSaveSettingsSubmit(i: Settings, event: any) { event.preventDefault(); i.setState({ saveUserSettingsLoading: true }); let auth = myAuth(); if (auth) { let form: SaveUserSettings = { ...i.state.saveUserSettingsForm, auth }; WebSocketService.Instance.send(wsClient.saveUserSettings(form)); } } handleChangePasswordSubmit(i: Settings, event: any) { event.preventDefault(); i.setState({ changePasswordLoading: true }); let auth = myAuth(); let pForm = i.state.changePasswordForm; let new_password = pForm.new_password; let new_password_verify = pForm.new_password_verify; let old_password = pForm.old_password; if (auth && new_password && old_password && new_password_verify) { let form: ChangePassword = { new_password, new_password_verify, old_password, auth, }; WebSocketService.Instance.send(wsClient.changePassword(form)); } } handleDeleteAccountShowConfirmToggle(i: Settings, event: any) { event.preventDefault(); i.setState({ deleteAccountShowConfirm: !i.state.deleteAccountShowConfirm }); } handleDeleteAccountPasswordChange(i: Settings, event: any) { i.state.deleteAccountForm.password = event.target.value; i.setState(i.state); } handleDeleteAccount(i: Settings, event: any) { event.preventDefault(); i.setState({ deleteAccountLoading: true }); let auth = myAuth(); let password = i.state.deleteAccountForm.password; if (auth && password) { let form: DeleteAccount = { password, auth, }; WebSocketService.Instance.send(wsClient.deleteAccount(form)); } } handleSwitchTab(i: { ctx: Settings; tab: string }) { i.ctx.setState({ currentTab: i.tab }); if (i.ctx.state.currentTab == "blocks") { i.ctx.setupBlockPersonChoices(); i.ctx.setupBlockCommunityChoices(); } } parseMessage(msg: any) { let op = wsUserOp(msg); console.log(msg); if (msg.error) { this.setState({ saveUserSettingsLoading: false, changePasswordLoading: false, deleteAccountLoading: false, }); toast(i18n.t(msg.error), "danger"); return; } else if (op == UserOperation.SaveUserSettings) { let data = wsJsonToRes(msg); UserService.Instance.login(data); location.reload(); this.setState({ saveUserSettingsLoading: false }); toast(i18n.t("saved")); window.scrollTo(0, 0); } else if (op == UserOperation.ChangePassword) { let data = wsJsonToRes(msg); UserService.Instance.login(data); this.setState({ changePasswordLoading: false }); window.scrollTo(0, 0); toast(i18n.t("password_changed")); } else if (op == UserOperation.DeleteAccount) { this.setState({ deleteAccountLoading: false, deleteAccountShowConfirm: false, }); UserService.Instance.logout(); window.location.href = "/"; } else if (op == UserOperation.BlockPerson) { let data = wsJsonToRes(msg); updatePersonBlock(data); let mui = UserService.Instance.myUserInfo; if (mui) { this.setState({ personBlocks: mui.person_blocks }); } } else if (op == UserOperation.BlockCommunity) { let data = wsJsonToRes(msg); updateCommunityBlock(data); let mui = UserService.Instance.myUserInfo; if (mui) { this.setState({ communityBlocks: mui.community_blocks }); } } } }