import { NoOptionI18nKeys } from "i18next"; import { Component, linkEvent } from "inferno"; import { BlockCommunity, BlockCommunityResponse, BlockPerson, BlockPersonResponse, ChangePassword, CommunityBlockView, DeleteAccount, GetSiteResponse, ListingType, LoginResponse, PersonBlockView, SaveUserSettings, SortType, UserOperation, wsJsonToRes, wsUserOp, } from "lemmy-js-client"; import { Subscription } from "rxjs"; import { i18n, languages } from "../../i18next"; import { UserService, WebSocketService } from "../../services"; import { Choice, capitalizeFirstLetter, communityToChoice, debounce, elementUrl, emDash, enableNsfw, fetchCommunities, fetchThemeList, fetchUsers, getLanguages, myAuth, 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 { SearchableSelect } from "../common/searchable-select"; import { SortSelect } from "../common/sort-select"; import Tabs from "../common/tabs"; import { CommunityLink } from "../community/community-link"; import { PersonListing } from "./person-listing"; interface SettingsState { // TODO redo these forms saveUserSettingsForm: { show_nsfw?: boolean; theme?: string; default_sort_type?: SortType; default_listing_type?: ListingType; 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[]; communityBlocks: CommunityBlockView[]; currentTab: string; themeList: string[]; saveUserSettingsLoading: boolean; changePasswordLoading: boolean; deleteAccountLoading: boolean; deleteAccountShowConfirm: boolean; siteRes: GetSiteResponse; searchCommunityLoading: boolean; searchCommunityOptions: Choice[]; searchPersonLoading: boolean; searchPersonOptions: Choice[]; } type FilterType = "user" | "community"; const Filter = ({ filterType, options, onChange, onSearch, loading, }: { filterType: FilterType; options: Choice[]; onSearch: (text: string) => void; onChange: (choice: Choice) => void; loading: boolean; }) => (
); export class Settings extends Component { private isoData = setIsoData(this.context); private subscription?: Subscription; state: SettingsState = { saveUserSettingsForm: {}, changePasswordForm: {}, saveUserSettingsLoading: false, changePasswordLoading: false, deleteAccountLoading: false, deleteAccountShowConfirm: false, deleteAccountForm: {}, personBlocks: [], communityBlocks: [], currentTab: "settings", siteRes: this.isoData.site_res, themeList: [], searchCommunityLoading: false, searchCommunityOptions: [], searchPersonLoading: false, searchPersonOptions: [], }; 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.userSettings = this.userSettings.bind(this); this.blockCards = this.blockCards.bind(this); this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); const mui = UserService.Instance.myUserInfo; if (mui) { const { local_user: { show_nsfw, theme, default_sort_type, default_listing_type, interface_language, show_avatars, show_bot_accounts, show_scores, show_read_posts, show_new_post_notifs, send_notifications_to_email, email, }, person: { avatar, banner, display_name, bot_account, bio, matrix_user_id, }, } = mui.local_user_view; this.state = { ...this.state, personBlocks: mui.person_blocks, communityBlocks: mui.community_blocks, saveUserSettingsForm: { ...this.state.saveUserSettingsForm, show_nsfw, theme: theme ?? "browser", default_sort_type, default_listing_type, interface_language, discussion_languages: mui.discussion_languages, avatar, banner, display_name, show_avatars, bot_account, show_bot_accounts, show_scores, show_read_posts, show_new_post_notifs, email, bio, send_notifications_to_email, matrix_user_id, }, }; } } async componentDidMount() { setupTippy(); this.setState({ themeList: await fetchThemeList() }); } componentWillUnmount() { this.subscription?.unsubscribe(); } get documentTitle(): string { return i18n.t("settings"); } render() { return (
); } userSettings() { return (
{this.saveUserSettingsHtmlForm()}
{this.changePasswordHtmlForm()}
); } blockCards() { return (
{this.blockUserCard()}
{this.blockCommunityCard()}
); } changePasswordHtmlForm() { return ( <>
{i18n.t("change_password")}
); } blockUserCard() { const { searchPersonLoading, searchPersonOptions } = this.state; return (
{this.blockedUsersList()}
); } blockedUsersList() { return ( <>
{i18n.t("blocked_users")}
); } blockCommunityCard() { const { searchCommunityLoading, searchCommunityOptions } = this.state; return (
{this.blockedCommunitiesList()}
); } blockedCommunitiesList() { return ( <>
{i18n.t("blocked_communities")}
); } 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")}
)} ); } handlePersonSearch = debounce(async (text: string) => { this.setState({ searchPersonLoading: true }); const searchPersonOptions: Choice[] = []; if (text.length > 0) { searchPersonOptions.push( ...(await fetchUsers(text)).users.map(personToChoice) ); } this.setState({ searchPersonLoading: false, searchPersonOptions, }); }); handleCommunitySearch = debounce(async (text: string) => { this.setState({ searchCommunityLoading: true }); const searchCommunityOptions: Choice[] = []; if (text.length > 0) { searchCommunityOptions.push( ...(await fetchCommunities(text)).communities.map(communityToChoice) ); } this.setState({ searchCommunityLoading: false, searchCommunityOptions, }); }); handleBlockPerson({ value }: Choice) { const auth = myAuth(); if (auth && value !== "0") { const blockUserForm: BlockPerson = { person_id: Number(value), block: true, auth, }; WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); } } handleUnblockPerson(i: { ctx: Settings; recipientId: number }) { const auth = myAuth(); if (auth) { const blockUserForm: BlockPerson = { person_id: i.recipientId, block: false, auth, }; WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); } } handleBlockCommunity({ value }: Choice) { const auth = myAuth(); if (auth && value !== "0") { const blockCommunityForm: BlockCommunity = { community_id: Number(value), block: true, auth, }; WebSocketService.Instance.send( wsClient.blockCommunity(blockCommunityForm) ); } } handleUnblockCommunity(i: { ctx: Settings; communityId: number }) { const auth = myAuth(); if (auth) { const 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 = val), s)); } handleListingTypeChange(val: ListingType) { this.setState( s => ((s.saveUserSettingsForm.default_listing_type = 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 }); } 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 }); } } } }