From: Dessalines Date: Fri, 20 Aug 2021 02:56:18 +0000 (-0400) Subject: Feature/user community block (#362) X-Git-Url: http://these/git/%7B%60%24%7BwebArchiveUrl%7D/%22%7B%7D/%7Bthis.state.thumbnail%7D?a=commitdiff_plain;h=b27d982a7b149e08e7fad2686e12fe2dcd8e7eb0;p=lemmy-ui.git Feature/user community block (#362) * Extracting user settings and profile page. - Auto-collapsing dropdown and navbar on link clicks. - Fixes #180 * Adding User and Community blocking. Fixes #295 - Added a new settings page. - Switched to myUserInfo. - Removing GetFollowedCommunities endpoint * Fixing blocks --- diff --git a/package.json b/package.json index fa3bd73..0a0e85b 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "husky": "^7.0.1", "import-sort-style-module": "^6.0.0", "iso-639-1": "^2.1.9", - "lemmy-js-client": "0.11.4-rc.12", + "lemmy-js-client": "0.11.4-rc.14", "lint-staged": "^11.0.1", "mini-css-extract-plugin": "^2.1.0", "node-fetch": "^2.6.1", diff --git a/src/assets/css/main.css b/src/assets/css/main.css index 8861ebc..ef6b743 100644 --- a/src/assets/css/main.css +++ b/src/assets/css/main.css @@ -152,7 +152,11 @@ } } -.dropdown-menu { +.dropdown-content { + position: absolute; + background-color: var(--light); + min-width: 160px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); z-index: 2000; } diff --git a/src/shared/components/app/app.tsx b/src/shared/components/app/app.tsx index 689f798..ddf466b 100644 --- a/src/shared/components/app/app.tsx +++ b/src/shared/components/app/app.tsx @@ -26,7 +26,7 @@ export class App extends Component { <>
- + {siteRes && siteRes.site_view && this.props.siteRes.site_view.site.icon && ( diff --git a/src/shared/components/app/navbar.tsx b/src/shared/components/app/navbar.tsx index 114ffb7..06304a8 100644 --- a/src/shared/components/app/navbar.tsx +++ b/src/shared/components/app/navbar.tsx @@ -50,6 +50,7 @@ interface NavbarState { unreadCount: number; searchParam: string; toggleSearch: boolean; + showDropdown: boolean; onSiteBanner?(url: string): any; } @@ -67,6 +68,7 @@ export class Navbar extends Component { expanded: false, searchParam: "", toggleSearch: false, + showDropdown: false, }; subscription: any; @@ -122,15 +124,17 @@ export class Navbar extends Component { } } - handleSearchParam(i: Navbar, event: any) { - i.state.searchParam = event.target.value; - i.setState(i.state); + componentWillUnmount() { + this.wsSub.unsubscribe(); + this.userSub.unsubscribe(); + this.unreadCountSub.unsubscribe(); } updateUrl() { const searchParam = this.state.searchParam; this.setState({ searchParam: "" }); this.setState({ toggleSearch: false }); + this.setState({ showDropdown: false, expanded: false }); if (searchParam === "") { this.context.router.history.push(`/search/`); } else { @@ -141,54 +145,26 @@ export class Navbar extends Component { } } - handleSearchSubmit(i: Navbar, event: any) { - event.preventDefault(); - i.updateUrl(); - } - - handleSearchBtn(i: Navbar, event: any) { - event.preventDefault(); - i.setState({ toggleSearch: true }); - - i.searchTextField.current.focus(); - const offsetWidth = i.searchTextField.current.offsetWidth; - if (i.state.searchParam && offsetWidth > 100) { - i.updateUrl(); - } - } - - handleSearchBlur(i: Navbar, event: any) { - if (!(event.relatedTarget && event.relatedTarget.name !== "search-btn")) { - i.state.toggleSearch = false; - i.setState(i.state); - } - } - render() { return this.navbar(); } - componentWillUnmount() { - this.wsSub.unsubscribe(); - this.userSub.unsubscribe(); - this.unreadCountSub.unsubscribe(); - } - // TODO class active corresponding to current page navbar() { - let localUserView = - UserService.Instance.localUserView || this.props.site_res.my_user; + let myUserInfo = + UserService.Instance.myUserInfo || this.props.site_res.my_user; + let person = myUserInfo?.local_user_view.person; return (
- {UserService.Instance.localUserView && - this.state.subscribedCommunities.length > 0 && ( + {UserService.Instance.myUserInfo && + UserService.Instance.myUserInfo.follows.length > 0 && (
{this.subscribedCommunities()}
@@ -443,7 +428,7 @@ export class Home extends Component {
    - {this.state.subscribedCommunities.map(cfv => ( + {UserService.Instance.myUserInfo.follows.map(cfv => (
  • @@ -704,7 +689,7 @@ export class Home extends Component { )} - {UserService.Instance.localUserView && + {UserService.Instance.myUserInfo && this.state.listingType == ListingType.Subscribed && ( { get canAdmin(): boolean { return ( - UserService.Instance.localUserView && + UserService.Instance.myUserInfo && this.state.siteRes.admins .map(a => a.person.id) - .includes(UserService.Instance.localUserView.person.id) + .includes(UserService.Instance.myUserInfo.local_user_view.person.id) ); } @@ -807,10 +792,6 @@ export class Home extends Component { wsClient.communityJoin({ community_id: 0 }) ); this.fetchData(); - } else if (op == UserOperation.GetFollowedCommunities) { - let data = wsJsonToRes(msg).data; - this.state.subscribedCommunities = data.communities; - this.setState(this.state); } else if (op == UserOperation.ListCommunities) { let data = wsJsonToRes(msg).data; this.state.trendingCommunities = data.communities; @@ -836,21 +817,21 @@ export class Home extends Component { let nsfwCheck = !nsfw || (nsfw && - UserService.Instance.localUserView && - UserService.Instance.localUserView.local_user.show_nsfw); + UserService.Instance.myUserInfo && + UserService.Instance.myUserInfo.local_user_view.local_user.show_nsfw); // Only push these if you're on the first page, and you pass the nsfw check if (this.state.page == 1 && nsfwCheck) { // If you're on subscribed, only push it if you're subscribed. if (this.state.listingType == ListingType.Subscribed) { if ( - this.state.subscribedCommunities + UserService.Instance.myUserInfo.follows .map(c => c.community.id) .includes(data.post_view.community.id) ) { this.state.posts.unshift(data.post_view); if ( - UserService.Instance.localUserView?.local_user + UserService.Instance.myUserInfo?.local_user_view.local_user .show_new_post_notifs ) { notifyPost(data.post_view, this.context.router); @@ -861,7 +842,7 @@ export class Home extends Component { if (data.post_view.post.local) { this.state.posts.unshift(data.post_view); if ( - UserService.Instance.localUserView?.local_user + UserService.Instance.myUserInfo?.local_user_view.local_user .show_new_post_notifs ) { notifyPost(data.post_view, this.context.router); @@ -870,7 +851,8 @@ export class Home extends Component { } else { this.state.posts.unshift(data.post_view); if ( - UserService.Instance.localUserView?.local_user.show_new_post_notifs + UserService.Instance.myUserInfo?.local_user_view.local_user + .show_new_post_notifs ) { notifyPost(data.post_view, this.context.router); } @@ -937,7 +919,7 @@ export class Home extends Component { // If you're on subscribed, only push it if you're subscribed. if (this.state.listingType == ListingType.Subscribed) { if ( - this.state.subscribedCommunities + UserService.Instance.myUserInfo.follows .map(c => c.community.id) .includes(data.comment_view.community.id) ) { @@ -956,6 +938,9 @@ export class Home extends Component { let data = wsJsonToRes(msg).data; createCommentLikeRes(data.comment_view, this.state.comments); this.setState(this.state); + } else if (op == UserOperation.BlockPerson) { + let data = wsJsonToRes(msg).data; + updatePersonBlock(data); } } } diff --git a/src/shared/components/modlog.tsx b/src/shared/components/modlog.tsx index fc875f8..2e8527d 100644 --- a/src/shared/components/modlog.tsx +++ b/src/shared/components/modlog.tsx @@ -409,16 +409,16 @@ export class Modlog extends Component { get isAdminOrMod(): boolean { let isAdmin = - UserService.Instance.localUserView && + UserService.Instance.myUserInfo && this.isoData.site_res.admins .map(a => a.person.id) - .includes(UserService.Instance.localUserView.person.id); + .includes(UserService.Instance.myUserInfo.local_user_view.person.id); let isMod = - UserService.Instance.localUserView && + UserService.Instance.myUserInfo && this.state.communityMods && this.state.communityMods .map(m => m.moderator.id) - .includes(UserService.Instance.localUserView.person.id); + .includes(UserService.Instance.myUserInfo.local_user_view.person.id); return isAdmin || isMod; } diff --git a/src/shared/components/person/inbox.tsx b/src/shared/components/person/inbox.tsx index 1037587..c18dafa 100644 --- a/src/shared/components/person/inbox.tsx +++ b/src/shared/components/person/inbox.tsx @@ -1,5 +1,6 @@ import { Component, linkEvent } from "inferno"; import { + BlockPersonResponse, CommentResponse, CommentView, GetPersonMentions, @@ -31,6 +32,7 @@ import { setIsoData, setupTippy, toast, + updatePersonBlock, wsClient, wsJsonToRes, wsSubscribe, @@ -103,7 +105,7 @@ export class Inbox extends Component { this.handleSortChange = this.handleSortChange.bind(this); this.handlePageChange = this.handlePageChange.bind(this); - if (!UserService.Instance.localUserView && isBrowser()) { + if (!UserService.Instance.myUserInfo && isBrowser()) { toast(i18n.t("not_logged_in"), "danger"); this.context.router.history.push(`/login`); } @@ -130,9 +132,9 @@ export class Inbox extends Component { } get documentTitle(): string { - return `@${UserService.Instance.localUserView.person.name} ${i18n.t( - "inbox" - )} - ${this.state.site_view.site.name}`; + return `@${ + UserService.Instance.myUserInfo.local_user_view.person.name + } ${i18n.t("inbox")} - ${this.state.site_view.site.name}`; } render() { @@ -722,7 +724,7 @@ export class Inbox extends Component { if ( data.recipient_ids.includes( - UserService.Instance.localUserView.local_user.id + UserService.Instance.myUserInfo.local_user_view.local_user.id ) ) { this.state.replies.unshift(data.comment_view); @@ -730,7 +732,7 @@ export class Inbox extends Component { this.setState(this.state); } else if ( data.comment_view.creator.id == - UserService.Instance.localUserView.person.id + UserService.Instance.myUserInfo.local_user_view.person.id ) { // TODO this seems wrong, you should be using form_id toast(i18n.t("reply_sent")); @@ -739,7 +741,7 @@ export class Inbox extends Component { let data = wsJsonToRes(msg).data; if ( data.private_message_view.recipient.id == - UserService.Instance.localUserView.person.id + UserService.Instance.myUserInfo.local_user_view.person.id ) { this.state.messages.unshift(data.private_message_view); this.state.combined.unshift( @@ -756,6 +758,9 @@ export class Inbox extends Component { let data = wsJsonToRes(msg).data; createCommentLikeRes(data.comment_view, this.state.replies); this.setState(this.state); + } else if (op == UserOperation.BlockPerson) { + let data = wsJsonToRes(msg).data; + updatePersonBlock(data); } } @@ -769,10 +774,11 @@ export class Inbox extends Component { this.state.mentions.filter(r => !r.person_mention.read).length + this.state.messages.filter( r => - UserService.Instance.localUserView && + UserService.Instance.myUserInfo && !r.private_message.read && // TODO also seems very strange and wrong - r.creator.id !== UserService.Instance.localUserView.person.id + r.creator.id !== + UserService.Instance.myUserInfo.local_user_view.person.id ).length ); } diff --git a/src/shared/components/person/person.tsx b/src/shared/components/person/person.tsx deleted file mode 100644 index 2259367..0000000 --- a/src/shared/components/person/person.tsx +++ /dev/null @@ -1,1351 +0,0 @@ -import { Component, linkEvent } from "inferno"; -import { Link } from "inferno-router"; -import ISO6391 from "iso-639-1"; -import { - AddAdminResponse, - BanPersonResponse, - ChangePassword, - CommentResponse, - DeleteAccount, - GetPersonDetails, - GetPersonDetailsResponse, - GetSiteResponse, - ListingType, - LoginResponse, - PostResponse, - SaveUserSettings, - SortType, - UserOperation, -} from "lemmy-js-client"; -import moment from "moment"; -import { Subscription } from "rxjs"; -import { i18n } from "../../i18next"; -import { InitialFetchRequest, PersonDetailsView } from "../../interfaces"; -import { UserService, WebSocketService } from "../../services"; -import { - authField, - capitalizeFirstLetter, - createCommentLikeRes, - createPostLikeFindRes, - editCommentRes, - editPostFindRes, - elementUrl, - fetchLimit, - getLanguage, - getUsernameFromProps, - languages, - mdToHtml, - previewLines, - restoreScrollPosition, - routeSortTypeToEnum, - saveCommentRes, - saveScrollPosition, - setIsoData, - setOptionalAuth, - setTheme, - setupTippy, - showLocal, - themes, - toast, - wsClient, - wsJsonToRes, - wsSubscribe, - wsUserOp, -} from "../../utils"; -import { BannerIconHeader } from "../common/banner-icon-header"; -import { HtmlTags } from "../common/html-tags"; -import { Icon, Spinner } from "../common/icon"; -import { ImageUploadForm } from "../common/image-upload-form"; -import { ListingTypeSelect } from "../common/listing-type-select"; -import { MarkdownTextArea } from "../common/markdown-textarea"; -import { MomentTime } from "../common/moment-time"; -import { SortSelect } from "../common/sort-select"; -import { CommunityLink } from "../community/community-link"; -import { PersonDetails } from "./person-details"; -import { PersonListing } from "./person-listing"; - -interface PersonState { - personRes: GetPersonDetailsResponse; - userName: string; - view: PersonDetailsView; - sort: SortType; - page: number; - loading: boolean; - saveUserSettingsForm: SaveUserSettings; - changePasswordForm: ChangePassword; - saveUserSettingsLoading: boolean; - changePasswordLoading: boolean; - deleteAccountLoading: boolean; - deleteAccountShowConfirm: boolean; - deleteAccountForm: DeleteAccount; - siteRes: GetSiteResponse; -} - -interface PersonProps { - view: PersonDetailsView; - sort: SortType; - page: number; - person_id: number | null; - username: string; -} - -interface UrlParams { - view?: string; - sort?: SortType; - page?: number; -} - -export class Person extends Component { - private isoData = setIsoData(this.context); - private subscription: Subscription; - private emptyState: PersonState = { - personRes: undefined, - userName: getUsernameFromProps(this.props), - loading: true, - view: Person.getViewFromProps(this.props.match.view), - sort: Person.getSortTypeFromProps(this.props.match.sort), - page: Person.getPageFromProps(this.props.match.page), - saveUserSettingsForm: { - auth: authField(false), - }, - changePasswordForm: { - new_password: null, - new_password_verify: null, - old_password: null, - auth: authField(false), - }, - saveUserSettingsLoading: null, - changePasswordLoading: false, - deleteAccountLoading: null, - deleteAccountShowConfirm: false, - deleteAccountForm: { - password: null, - auth: authField(false), - }, - siteRes: this.isoData.site_res, - }; - - constructor(props: any, context: any) { - super(props, context); - - this.state = this.emptyState; - this.handleSortChange = this.handleSortChange.bind(this); - this.handleUserSettingsSortTypeChange = - this.handleUserSettingsSortTypeChange.bind(this); - this.handleUserSettingsListingTypeChange = - this.handleUserSettingsListingTypeChange.bind(this); - this.handlePageChange = this.handlePageChange.bind(this); - this.handleUserSettingsBioChange = - this.handleUserSettingsBioChange.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); - - // Only fetch the data if coming from another route - if (this.isoData.path == this.context.router.route.match.url) { - this.state.personRes = this.isoData.routeData[0]; - this.setUserInfo(); - this.state.loading = false; - } else { - this.fetchUserData(); - } - - setupTippy(); - } - - fetchUserData() { - let form: GetPersonDetails = { - username: this.state.userName, - sort: this.state.sort, - saved_only: this.state.view === PersonDetailsView.Saved, - page: this.state.page, - limit: fetchLimit, - auth: authField(false), - }; - WebSocketService.Instance.send(wsClient.getPersonDetails(form)); - } - - get isCurrentUser() { - return ( - UserService.Instance.localUserView?.person.id == - this.state.personRes.person_view.person.id - ); - } - - static getViewFromProps(view: string): PersonDetailsView { - return view ? PersonDetailsView[view] : PersonDetailsView.Overview; - } - - static getSortTypeFromProps(sort: string): SortType { - return sort ? routeSortTypeToEnum(sort) : SortType.New; - } - - static getPageFromProps(page: number): number { - return page ? Number(page) : 1; - } - - static fetchInitialData(req: InitialFetchRequest): Promise[] { - let pathSplit = req.path.split("/"); - let promises: Promise[] = []; - - // It can be /u/me, or /username/1 - let idOrName = pathSplit[2]; - let person_id: number; - let username: string; - if (isNaN(Number(idOrName))) { - username = idOrName; - } else { - person_id = Number(idOrName); - } - - let view = this.getViewFromProps(pathSplit[4]); - let sort = this.getSortTypeFromProps(pathSplit[6]); - let page = this.getPageFromProps(Number(pathSplit[8])); - - let form: GetPersonDetails = { - sort, - saved_only: view === PersonDetailsView.Saved, - page, - limit: fetchLimit, - }; - setOptionalAuth(form, req.auth); - this.setIdOrName(form, person_id, username); - promises.push(req.client.getPersonDetails(form)); - return promises; - } - - static setIdOrName(obj: any, id: number, name_: string) { - if (id) { - obj.person_id = id; - } else { - obj.username = name_; - } - } - - componentWillUnmount() { - this.subscription.unsubscribe(); - saveScrollPosition(this.context); - } - - static getDerivedStateFromProps(props: any): PersonProps { - return { - view: this.getViewFromProps(props.match.params.view), - sort: this.getSortTypeFromProps(props.match.params.sort), - page: this.getPageFromProps(props.match.params.page), - person_id: Number(props.match.params.id) || null, - username: props.match.params.username, - }; - } - - componentDidUpdate(lastProps: any) { - // Necessary if you are on a post and you click another post (same route) - if ( - lastProps.location.pathname.split("/")[2] !== - lastProps.history.location.pathname.split("/")[2] - ) { - // Couldnt get a refresh working. This does for now. - location.reload(); - } - } - - get documentTitle(): string { - return `@${this.state.personRes.person_view.person.name} - ${this.state.siteRes.site_view.site.name}`; - } - - get bioTag(): string { - return this.state.personRes.person_view.person.bio - ? previewLines(this.state.personRes.person_view.person.bio) - : undefined; - } - - render() { - return ( -
    - {this.state.loading ? ( -
    - -
    - ) : ( -
    -
    - <> - - {this.userInfo()} -
    - - {!this.state.loading && this.selects()} - -
    - - {!this.state.loading && ( -
    - {this.isCurrentUser && this.userSettings()} - {this.moderates()} - {this.follows()} -
    - )} -
    - )} -
    - ); - } - - viewRadios() { - return ( -
    - - - - -
    - ); - } - - selects() { - return ( -
    - ); - } - - userInfo() { - let pv = this.state.personRes?.person_view; - - return ( -
    - -
    -
    -
    -
    - {pv.person.display_name && ( -
    {pv.person.display_name}
    - )} -
      -
    • - -
    • - {pv.person.banned && ( -
    • - {i18n.t("banned")} -
    • - )} -
    -
    -
    - {this.isCurrentUser ? ( - - ) : ( - <> - - {i18n.t("send_secure_message")} - - - {i18n.t("send_message")} - - - )} -
    - {pv.person.bio && ( -
    -
    -
    - )} -
    -
      -
    • - {i18n.t("number_of_posts", { count: pv.counts.post_count })} -
    • -
    • - {i18n.t("number_of_comments", { - count: pv.counts.comment_count, - })} -
    • -
    -
    -
    - {i18n.t("joined")}{" "} - -
    -
    - - - {i18n.t("cake_day_title")}{" "} - {moment.utc(pv.person.published).local().format("MMM DD, YYYY")} - -
    -
    -
    -
    - ); - } - - userSettings() { - return ( -
    -
    -
    - {this.saveUserSettingsHtmlForm()} -
    - {this.changePasswordHtmlForm()} -
    -
    -
    - ); - } - - changePasswordHtmlForm() { - return ( - <> -
    {i18n.t("change_password")}
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    - - ); - } - - saveUserSettingsHtmlForm() { - return ( - <> -
    {i18n.t("settings")}
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    - - - - -
    - - - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    - - {this.state.siteRes.site_view.site.enable_nsfw && ( -
    -
    - - -
    -
    - )} -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    - -
    -
    -
    - - {this.state.deleteAccountShowConfirm && ( - <> - - - - - - )} -
    - - - ); - } - - moderates() { - return ( -
    - {this.state.personRes.moderates.length > 0 && ( -
    -
    -
    {i18n.t("moderates")}
    -
      - {this.state.personRes.moderates.map(cmv => ( -
    • - -
    • - ))} -
    -
    -
    - )} -
    - ); - } - - follows() { - return ( -
    - {this.state.personRes.follows.length > 0 && ( -
    -
    -
    {i18n.t("subscribed")}
    -
      - {this.state.personRes.follows.map(cfv => ( -
    • - -
    • - ))} -
    -
    -
    - )} -
    - ); - } - - updateUrl(paramUpdates: UrlParams) { - const page = paramUpdates.page || this.state.page; - const viewStr = paramUpdates.view || PersonDetailsView[this.state.view]; - const sortStr = paramUpdates.sort || this.state.sort; - - let typeView = `/u/${this.state.userName}`; - - this.props.history.push( - `${typeView}/view/${viewStr}/sort/${sortStr}/page/${page}` - ); - this.state.loading = true; - this.setState(this.state); - this.fetchUserData(); - } - - handlePageChange(page: number) { - this.updateUrl({ page }); - } - - handleSortChange(val: SortType) { - this.updateUrl({ sort: val, page: 1 }); - } - - handleViewChange(i: Person, event: any) { - i.updateUrl({ - view: PersonDetailsView[Number(event.target.value)], - page: 1, - }); - } - - handleUserSettingsShowNsfwChange(i: Person, event: any) { - i.state.saveUserSettingsForm.show_nsfw = event.target.checked; - i.setState(i.state); - } - - handleUserSettingsShowAvatarsChange(i: Person, event: any) { - i.state.saveUserSettingsForm.show_avatars = event.target.checked; - UserService.Instance.localUserView.local_user.show_avatars = - event.target.checked; // Just for instant updates - i.setState(i.state); - } - - handleUserSettingsBotAccount(i: Person, event: any) { - i.state.saveUserSettingsForm.bot_account = event.target.checked; - i.setState(i.state); - } - - handleUserSettingsShowBotAccounts(i: Person, event: any) { - i.state.saveUserSettingsForm.show_bot_accounts = event.target.checked; - i.setState(i.state); - } - - handleUserSettingsShowReadPosts(i: Person, event: any) { - i.state.saveUserSettingsForm.show_read_posts = event.target.checked; - i.setState(i.state); - } - - handleUserSettingsShowNewPostNotifs(i: Person, event: any) { - i.state.saveUserSettingsForm.show_new_post_notifs = event.target.checked; - i.setState(i.state); - } - - handleUserSettingsShowScoresChange(i: Person, event: any) { - i.state.saveUserSettingsForm.show_scores = event.target.checked; - UserService.Instance.localUserView.local_user.show_scores = - event.target.checked; // Just for instant updates - i.setState(i.state); - } - - handleUserSettingsSendNotificationsToEmailChange(i: Person, event: any) { - i.state.saveUserSettingsForm.send_notifications_to_email = - event.target.checked; - i.setState(i.state); - } - - handleUserSettingsThemeChange(i: Person, event: any) { - i.state.saveUserSettingsForm.theme = event.target.value; - setTheme(event.target.value, true); - i.setState(i.state); - } - - handleUserSettingsLangChange(i: Person, event: any) { - i.state.saveUserSettingsForm.lang = event.target.value; - i18n.changeLanguage(getLanguage(i.state.saveUserSettingsForm.lang)); - i.setState(i.state); - } - - handleUserSettingsSortTypeChange(val: SortType) { - this.state.saveUserSettingsForm.default_sort_type = - Object.keys(SortType).indexOf(val); - this.setState(this.state); - } - - handleUserSettingsListingTypeChange(val: ListingType) { - this.state.saveUserSettingsForm.default_listing_type = - Object.keys(ListingType).indexOf(val); - this.setState(this.state); - } - - handleUserSettingsEmailChange(i: Person, event: any) { - i.state.saveUserSettingsForm.email = event.target.value; - i.setState(i.state); - } - - handleUserSettingsBioChange(val: string) { - this.state.saveUserSettingsForm.bio = val; - this.setState(this.state); - } - - handleAvatarUpload(url: string) { - this.state.saveUserSettingsForm.avatar = url; - this.setState(this.state); - } - - handleAvatarRemove() { - this.state.saveUserSettingsForm.avatar = ""; - this.setState(this.state); - } - - handleBannerUpload(url: string) { - this.state.saveUserSettingsForm.banner = url; - this.setState(this.state); - } - - handleBannerRemove() { - this.state.saveUserSettingsForm.banner = ""; - this.setState(this.state); - } - - handleUserSettingsPreferredUsernameChange(i: Person, event: any) { - i.state.saveUserSettingsForm.display_name = event.target.value; - i.setState(i.state); - } - - handleUserSettingsMatrixUserIdChange(i: Person, event: any) { - i.state.saveUserSettingsForm.matrix_user_id = event.target.value; - if ( - i.state.saveUserSettingsForm.matrix_user_id == "" && - !UserService.Instance.localUserView.person.matrix_user_id - ) { - i.state.saveUserSettingsForm.matrix_user_id = undefined; - } - i.setState(i.state); - } - - handleNewPasswordChange(i: Person, 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: Person, 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: Person, 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); - } - - handleSaveUserSettingsSubmit(i: Person, event: any) { - event.preventDefault(); - i.state.saveUserSettingsLoading = true; - i.setState(i.state); - - WebSocketService.Instance.send( - wsClient.saveUserSettings(i.state.saveUserSettingsForm) - ); - } - - handleChangePasswordSubmit(i: Person, event: any) { - event.preventDefault(); - i.state.changePasswordLoading = true; - i.setState(i.state); - - WebSocketService.Instance.send( - wsClient.changePassword(i.state.changePasswordForm) - ); - } - - handleDeleteAccountShowConfirmToggle(i: Person, event: any) { - event.preventDefault(); - i.state.deleteAccountShowConfirm = !i.state.deleteAccountShowConfirm; - i.setState(i.state); - } - - handleDeleteAccountPasswordChange(i: Person, event: any) { - i.state.deleteAccountForm.password = event.target.value; - i.setState(i.state); - } - - handleLogoutClick(i: Person) { - UserService.Instance.logout(); - i.context.router.history.push("/"); - } - - handleDeleteAccount(i: Person, event: any) { - event.preventDefault(); - i.state.deleteAccountLoading = true; - i.setState(i.state); - - WebSocketService.Instance.send( - wsClient.deleteAccount(i.state.deleteAccountForm) - ); - } - - setUserInfo() { - if (this.isCurrentUser) { - this.state.saveUserSettingsForm.show_nsfw = - UserService.Instance.localUserView.local_user.show_nsfw; - this.state.saveUserSettingsForm.theme = UserService.Instance.localUserView - .local_user.theme - ? UserService.Instance.localUserView.local_user.theme - : "browser"; - this.state.saveUserSettingsForm.default_sort_type = - UserService.Instance.localUserView.local_user.default_sort_type; - this.state.saveUserSettingsForm.default_listing_type = - UserService.Instance.localUserView.local_user.default_listing_type; - this.state.saveUserSettingsForm.lang = - UserService.Instance.localUserView.local_user.lang; - this.state.saveUserSettingsForm.avatar = - UserService.Instance.localUserView.person.avatar; - this.state.saveUserSettingsForm.banner = - UserService.Instance.localUserView.person.banner; - this.state.saveUserSettingsForm.display_name = - UserService.Instance.localUserView.person.display_name; - this.state.saveUserSettingsForm.show_avatars = - UserService.Instance.localUserView.local_user.show_avatars; - this.state.saveUserSettingsForm.bot_account = - UserService.Instance.localUserView.person.bot_account; - this.state.saveUserSettingsForm.show_bot_accounts = - UserService.Instance.localUserView.local_user.show_bot_accounts; - this.state.saveUserSettingsForm.show_scores = - UserService.Instance.localUserView.local_user.show_scores; - this.state.saveUserSettingsForm.show_read_posts = - UserService.Instance.localUserView.local_user.show_read_posts; - this.state.saveUserSettingsForm.show_new_post_notifs = - UserService.Instance.localUserView.local_user.show_new_post_notifs; - this.state.saveUserSettingsForm.email = - UserService.Instance.localUserView.local_user.email; - this.state.saveUserSettingsForm.bio = - UserService.Instance.localUserView.person.bio; - this.state.saveUserSettingsForm.send_notifications_to_email = - UserService.Instance.localUserView.local_user.send_notifications_to_email; - this.state.saveUserSettingsForm.matrix_user_id = - UserService.Instance.localUserView.person.matrix_user_id; - } - } - - parseMessage(msg: any) { - let op = wsUserOp(msg); - console.log(msg); - if (msg.error) { - toast(i18n.t(msg.error), "danger"); - if (msg.error == "couldnt_find_that_username_or_email") { - this.context.router.history.push("/"); - } - this.setState({ - deleteAccountLoading: false, - saveUserSettingsLoading: false, - changePasswordLoading: false, - }); - return; - } else if (msg.reconnect) { - this.fetchUserData(); - } else if (op == UserOperation.GetPersonDetails) { - // Since the PersonDetails contains posts/comments as well as some general user info we listen here as well - // and set the parent state if it is not set or differs - // TODO this might need to get abstracted - let data = wsJsonToRes(msg).data; - this.state.personRes = data; - console.log(data); - this.setUserInfo(); - this.state.loading = false; - this.setState(this.state); - restoreScrollPosition(this.context); - } else if (op == UserOperation.SaveUserSettings) { - let data = wsJsonToRes(msg).data; - UserService.Instance.login(data); - this.state.personRes.person_view.person.bio = - this.state.saveUserSettingsForm.bio; - this.state.personRes.person_view.person.display_name = - this.state.saveUserSettingsForm.display_name; - this.state.personRes.person_view.person.banner = - this.state.saveUserSettingsForm.banner; - this.state.personRes.person_view.person.avatar = - this.state.saveUserSettingsForm.avatar; - this.state.saveUserSettingsLoading = false; - this.setState(this.state); - - window.scrollTo(0, 0); - } else if (op == UserOperation.ChangePassword) { - let data = wsJsonToRes(msg).data; - UserService.Instance.login(data); - this.state.changePasswordLoading = false; - this.setState(this.state); - 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.AddAdmin) { - let data = wsJsonToRes(msg).data; - this.state.siteRes.admins = data.admins; - this.setState(this.state); - } else if (op == UserOperation.CreateCommentLike) { - let data = wsJsonToRes(msg).data; - createCommentLikeRes(data.comment_view, this.state.personRes.comments); - this.setState(this.state); - } else if ( - op == UserOperation.EditComment || - op == UserOperation.DeleteComment || - op == UserOperation.RemoveComment - ) { - let data = wsJsonToRes(msg).data; - editCommentRes(data.comment_view, this.state.personRes.comments); - this.setState(this.state); - } else if (op == UserOperation.CreateComment) { - let data = wsJsonToRes(msg).data; - if ( - UserService.Instance.localUserView && - data.comment_view.creator.id == - UserService.Instance.localUserView.person.id - ) { - toast(i18n.t("reply_sent")); - } - } else if (op == UserOperation.SaveComment) { - let data = wsJsonToRes(msg).data; - saveCommentRes(data.comment_view, this.state.personRes.comments); - this.setState(this.state); - } else if ( - op == UserOperation.EditPost || - op == UserOperation.DeletePost || - op == UserOperation.RemovePost || - op == UserOperation.LockPost || - op == UserOperation.StickyPost || - op == UserOperation.SavePost - ) { - let data = wsJsonToRes(msg).data; - editPostFindRes(data.post_view, this.state.personRes.posts); - this.setState(this.state); - } else if (op == UserOperation.CreatePostLike) { - let data = wsJsonToRes(msg).data; - createPostLikeFindRes(data.post_view, this.state.personRes.posts); - this.setState(this.state); - } else if (op == UserOperation.BanPerson) { - let data = wsJsonToRes(msg).data; - this.state.personRes.comments - .filter(c => c.creator.id == data.person_view.person.id) - .forEach(c => (c.creator.banned = data.banned)); - this.state.personRes.posts - .filter(c => c.creator.id == data.person_view.person.id) - .forEach(c => (c.creator.banned = data.banned)); - this.setState(this.state); - } - } -} diff --git a/src/shared/components/person/profile.tsx b/src/shared/components/person/profile.tsx new file mode 100644 index 0000000..815f2fd --- /dev/null +++ b/src/shared/components/person/profile.tsx @@ -0,0 +1,583 @@ +import { Component, linkEvent } from "inferno"; +import { Link } from "inferno-router"; +import { + AddAdminResponse, + BanPersonResponse, + BlockPersonResponse, + CommentResponse, + GetPersonDetails, + GetPersonDetailsResponse, + GetSiteResponse, + PostResponse, + SortType, + UserOperation, +} from "lemmy-js-client"; +import moment from "moment"; +import { Subscription } from "rxjs"; +import { i18n } from "../../i18next"; +import { InitialFetchRequest, PersonDetailsView } from "../../interfaces"; +import { UserService, WebSocketService } from "../../services"; +import { + authField, + createCommentLikeRes, + createPostLikeFindRes, + editCommentRes, + editPostFindRes, + fetchLimit, + getUsernameFromProps, + mdToHtml, + previewLines, + restoreScrollPosition, + routeSortTypeToEnum, + saveCommentRes, + saveScrollPosition, + setIsoData, + setOptionalAuth, + setupTippy, + toast, + updatePersonBlock, + wsClient, + wsJsonToRes, + wsSubscribe, + wsUserOp, +} from "../../utils"; +import { BannerIconHeader } from "../common/banner-icon-header"; +import { HtmlTags } from "../common/html-tags"; +import { Icon, Spinner } from "../common/icon"; +import { MomentTime } from "../common/moment-time"; +import { SortSelect } from "../common/sort-select"; +import { CommunityLink } from "../community/community-link"; +import { PersonDetails } from "./person-details"; +import { PersonListing } from "./person-listing"; + +interface ProfileState { + personRes: GetPersonDetailsResponse; + userName: string; + view: PersonDetailsView; + sort: SortType; + page: number; + loading: boolean; + siteRes: GetSiteResponse; +} + +interface ProfileProps { + view: PersonDetailsView; + sort: SortType; + page: number; + person_id: number | null; + username: string; +} + +interface UrlParams { + view?: string; + sort?: SortType; + page?: number; +} + +export class Profile extends Component { + private isoData = setIsoData(this.context); + private subscription: Subscription; + private emptyState: ProfileState = { + personRes: undefined, + userName: getUsernameFromProps(this.props), + loading: true, + view: Profile.getViewFromProps(this.props.match.view), + sort: Profile.getSortTypeFromProps(this.props.match.sort), + page: Profile.getPageFromProps(this.props.match.page), + siteRes: this.isoData.site_res, + }; + + constructor(props: any, context: any) { + super(props, context); + + this.state = this.emptyState; + this.handleSortChange = this.handleSortChange.bind(this); + + this.parseMessage = this.parseMessage.bind(this); + this.subscription = wsSubscribe(this.parseMessage); + + // Only fetch the data if coming from another route + if (this.isoData.path == this.context.router.route.match.url) { + this.state.personRes = this.isoData.routeData[0]; + this.state.loading = false; + } else { + this.fetchUserData(); + } + + setupTippy(); + } + + fetchUserData() { + let form: GetPersonDetails = { + username: this.state.userName, + sort: this.state.sort, + saved_only: this.state.view === PersonDetailsView.Saved, + page: this.state.page, + limit: fetchLimit, + auth: authField(false), + }; + WebSocketService.Instance.send(wsClient.getPersonDetails(form)); + } + + get isCurrentUser() { + return ( + UserService.Instance.myUserInfo?.local_user_view.person.id == + this.state.personRes.person_view.person.id + ); + } + + static getViewFromProps(view: string): PersonDetailsView { + return view ? PersonDetailsView[view] : PersonDetailsView.Overview; + } + + static getSortTypeFromProps(sort: string): SortType { + return sort ? routeSortTypeToEnum(sort) : SortType.New; + } + + static getPageFromProps(page: number): number { + return page ? Number(page) : 1; + } + + static fetchInitialData(req: InitialFetchRequest): Promise[] { + let pathSplit = req.path.split("/"); + let promises: Promise[] = []; + + // It can be /u/me, or /username/1 + let idOrName = pathSplit[2]; + let person_id: number; + let username: string; + if (isNaN(Number(idOrName))) { + username = idOrName; + } else { + person_id = Number(idOrName); + } + + let view = this.getViewFromProps(pathSplit[4]); + let sort = this.getSortTypeFromProps(pathSplit[6]); + let page = this.getPageFromProps(Number(pathSplit[8])); + + let form: GetPersonDetails = { + sort, + saved_only: view === PersonDetailsView.Saved, + page, + limit: fetchLimit, + }; + setOptionalAuth(form, req.auth); + this.setIdOrName(form, person_id, username); + promises.push(req.client.getPersonDetails(form)); + return promises; + } + + static setIdOrName(obj: any, id: number, name_: string) { + if (id) { + obj.person_id = id; + } else { + obj.username = name_; + } + } + + componentWillUnmount() { + this.subscription.unsubscribe(); + saveScrollPosition(this.context); + } + + static getDerivedStateFromProps(props: any): ProfileProps { + return { + view: this.getViewFromProps(props.match.params.view), + sort: this.getSortTypeFromProps(props.match.params.sort), + page: this.getPageFromProps(props.match.params.page), + person_id: Number(props.match.params.id) || null, + username: props.match.params.username, + }; + } + + componentDidUpdate(lastProps: any) { + // Necessary if you are on a post and you click another post (same route) + if ( + lastProps.location.pathname.split("/")[2] !== + lastProps.history.location.pathname.split("/")[2] + ) { + // Couldnt get a refresh working. This does for now. + location.reload(); + } + } + + get documentTitle(): string { + return `@${this.state.personRes.person_view.person.name} - ${this.state.siteRes.site_view.site.name}`; + } + + get bioTag(): string { + return this.state.personRes.person_view.person.bio + ? previewLines(this.state.personRes.person_view.person.bio) + : undefined; + } + + render() { + return ( +
    + {this.state.loading ? ( +
    + +
    + ) : ( +
    +
    + <> + + {this.userInfo()} +
    + + {!this.state.loading && this.selects()} + +
    + + {!this.state.loading && ( +
    + {this.moderates()} + {UserService.Instance.myUserInfo && this.follows()} +
    + )} +
    + )} +
    + ); + } + + viewRadios() { + return ( +
    + + + + +
    + ); + } + + selects() { + return ( +
    + {this.viewRadios()} + + + + +
    + ); + } + + userInfo() { + let pv = this.state.personRes?.person_view; + + return ( +
    + +
    +
    +
    +
    + {pv.person.display_name && ( +
    {pv.person.display_name}
    + )} +
      +
    • + +
    • + {pv.person.banned && ( +
    • + {i18n.t("banned")} +
    • + )} +
    +
    +
    + {!this.isCurrentUser && ( + <> + + {i18n.t("send_secure_message")} + + + {i18n.t("send_message")} + + + )} +
    + {pv.person.bio && ( +
    +
    +
    + )} +
    +
      +
    • + {i18n.t("number_of_posts", { count: pv.counts.post_count })} +
    • +
    • + {i18n.t("number_of_comments", { + count: pv.counts.comment_count, + })} +
    • +
    +
    +
    + {i18n.t("joined")}{" "} + +
    +
    + + + {i18n.t("cake_day_title")}{" "} + {moment.utc(pv.person.published).local().format("MMM DD, YYYY")} + +
    +
    +
    +
    + ); + } + + moderates() { + return ( +
    + {this.state.personRes.moderates.length > 0 && ( +
    +
    +
    {i18n.t("moderates")}
    +
      + {this.state.personRes.moderates.map(cmv => ( +
    • + +
    • + ))} +
    +
    +
    + )} +
    + ); + } + + follows() { + let follows = UserService.Instance.myUserInfo.follows; + return ( +
    + {follows.length > 0 && ( +
    +
    +
    {i18n.t("subscribed")}
    +
      + {follows.map(cfv => ( +
    • + +
    • + ))} +
    +
    +
    + )} +
    + ); + } + + updateUrl(paramUpdates: UrlParams) { + const page = paramUpdates.page || this.state.page; + const viewStr = paramUpdates.view || PersonDetailsView[this.state.view]; + const sortStr = paramUpdates.sort || this.state.sort; + + let typeView = `/u/${this.state.userName}`; + + this.props.history.push( + `${typeView}/view/${viewStr}/sort/${sortStr}/page/${page}` + ); + this.state.loading = true; + this.setState(this.state); + this.fetchUserData(); + } + + handlePageChange(page: number) { + this.updateUrl({ page }); + } + + handleSortChange(val: SortType) { + this.updateUrl({ sort: val, page: 1 }); + } + + handleViewChange(i: Profile, event: any) { + i.updateUrl({ + view: PersonDetailsView[Number(event.target.value)], + page: 1, + }); + } + + parseMessage(msg: any) { + let op = wsUserOp(msg); + console.log(msg); + if (msg.error) { + toast(i18n.t(msg.error), "danger"); + if (msg.error == "couldnt_find_that_username_or_email") { + this.context.router.history.push("/"); + } + return; + } else if (msg.reconnect) { + this.fetchUserData(); + } else if (op == UserOperation.GetPersonDetails) { + // Since the PersonDetails contains posts/comments as well as some general user info we listen here as well + // and set the parent state if it is not set or differs + // TODO this might need to get abstracted + let data = wsJsonToRes(msg).data; + this.state.personRes = data; + console.log(data); + this.state.loading = false; + this.setState(this.state); + restoreScrollPosition(this.context); + } else if (op == UserOperation.AddAdmin) { + let data = wsJsonToRes(msg).data; + this.state.siteRes.admins = data.admins; + this.setState(this.state); + } else if (op == UserOperation.CreateCommentLike) { + let data = wsJsonToRes(msg).data; + createCommentLikeRes(data.comment_view, this.state.personRes.comments); + this.setState(this.state); + } else if ( + op == UserOperation.EditComment || + op == UserOperation.DeleteComment || + op == UserOperation.RemoveComment + ) { + let data = wsJsonToRes(msg).data; + editCommentRes(data.comment_view, this.state.personRes.comments); + this.setState(this.state); + } else if (op == UserOperation.CreateComment) { + let data = wsJsonToRes(msg).data; + if ( + UserService.Instance.myUserInfo && + data.comment_view.creator.id == + UserService.Instance.myUserInfo.local_user_view.person.id + ) { + toast(i18n.t("reply_sent")); + } + } else if (op == UserOperation.SaveComment) { + let data = wsJsonToRes(msg).data; + saveCommentRes(data.comment_view, this.state.personRes.comments); + this.setState(this.state); + } else if ( + op == UserOperation.EditPost || + op == UserOperation.DeletePost || + op == UserOperation.RemovePost || + op == UserOperation.LockPost || + op == UserOperation.StickyPost || + op == UserOperation.SavePost + ) { + let data = wsJsonToRes(msg).data; + editPostFindRes(data.post_view, this.state.personRes.posts); + this.setState(this.state); + } else if (op == UserOperation.CreatePostLike) { + let data = wsJsonToRes(msg).data; + createPostLikeFindRes(data.post_view, this.state.personRes.posts); + this.setState(this.state); + } else if (op == UserOperation.BanPerson) { + let data = wsJsonToRes(msg).data; + this.state.personRes.comments + .filter(c => c.creator.id == data.person_view.person.id) + .forEach(c => (c.creator.banned = data.banned)); + this.state.personRes.posts + .filter(c => c.creator.id == data.person_view.person.id) + .forEach(c => (c.creator.banned = data.banned)); + this.setState(this.state); + } else if (op == UserOperation.BlockPerson) { + let data = wsJsonToRes(msg).data; + updatePersonBlock(data); + } + } +} diff --git a/src/shared/components/person/settings.tsx b/src/shared/components/person/settings.tsx new file mode 100644 index 0000000..955cc30 --- /dev/null +++ b/src/shared/components/person/settings.tsx @@ -0,0 +1,1118 @@ +import { Component, linkEvent } from "inferno"; +import ISO6391 from "iso-639-1"; +import { + BlockCommunity, + BlockCommunityResponse, + BlockPerson, + BlockPersonResponse, + ChangePassword, + CommunityBlockView, + CommunityView, + DeleteAccount, + GetSiteResponse, + ListingType, + LoginResponse, + PersonBlockView, + PersonViewSafe, + SaveUserSettings, + SortType, + UserOperation, +} from "lemmy-js-client"; +import { Subscription } from "rxjs"; +import { i18n } from "../../i18next"; +import { UserService, WebSocketService } from "../../services"; +import { + authField, + capitalizeFirstLetter, + choicesConfig, + communitySelectName, + communityToChoice, + debounce, + elementUrl, + fetchCommunities, + fetchUsers, + getLanguage, + isBrowser, + languages, + personSelectName, + personToChoice, + setIsoData, + setTheme, + setupTippy, + showLocal, + themes, + toast, + updateCommunityBlock, + updatePersonBlock, + wsClient, + wsJsonToRes, + wsSubscribe, + wsUserOp, +} from "../../utils"; +import { HtmlTags } from "../common/html-tags"; +import { Icon, Spinner } from "../common/icon"; +import { ImageUploadForm } from "../common/image-upload-form"; +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 { + saveUserSettingsForm: SaveUserSettings; + changePasswordForm: ChangePassword; + saveUserSettingsLoading: boolean; + changePasswordLoading: boolean; + deleteAccountLoading: boolean; + deleteAccountShowConfirm: boolean; + deleteAccountForm: DeleteAccount; + personBlocks: PersonBlockView[]; + blockPersonId: number; + blockPerson?: PersonViewSafe; + communityBlocks: CommunityBlockView[]; + blockCommunityId: number; + blockCommunity?: CommunityView; + currentTab: string; + siteRes: GetSiteResponse; +} + +export class Settings extends Component { + private isoData = setIsoData(this.context); + private blockPersonChoices: any; + private blockCommunityChoices: any; + private subscription: Subscription; + private emptyState: SettingsState = { + saveUserSettingsForm: { + auth: authField(false), + }, + changePasswordForm: { + new_password: null, + new_password_verify: null, + old_password: null, + auth: authField(false), + }, + saveUserSettingsLoading: null, + changePasswordLoading: false, + deleteAccountLoading: null, + deleteAccountShowConfirm: false, + deleteAccountForm: { + password: null, + auth: authField(false), + }, + personBlocks: [], + blockPersonId: 0, + communityBlocks: [], + blockCommunityId: 0, + currentTab: "settings", + siteRes: this.isoData.site_res, + }; + + constructor(props: any, context: any) { + super(props, context); + + this.state = this.emptyState; + this.handleSortTypeChange = this.handleSortTypeChange.bind(this); + this.handleListingTypeChange = this.handleListingTypeChange.bind(this); + this.handleBioChange = this.handleBioChange.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); + + this.setUserInfo(); + + setupTippy(); + } + + 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")}
    +
      + {this.state.personBlocks.map(pb => ( +
    • + + + + +
    • + ))} +
    + + ); + } + + blockUserForm() { + return ( +
    + +
    + +
    +
    + ); + } + + blockCommunityCard() { + return ( +
    + {this.blockCommunityForm()} + {this.blockedCommunitiesList()} +
    + ); + } + + blockedCommunitiesList() { + return ( + <> +
    {i18n.t("blocked_communities")}
    +
      + {this.state.communityBlocks.map(cb => ( +
    • + + + + +
    • + ))} +
    + + ); + } + + blockCommunityForm() { + return ( +
    + +
    + +
    +
    + ); + } + + saveUserSettingsHtmlForm() { + return ( + <> +
    {i18n.t("settings")}
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + + + + +
    + + + + {this.state.siteRes.site_view.site.enable_nsfw && ( +
    +
    + + +
    +
    + )} +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    + +
    +
    +
    + + {this.state.deleteAccountShowConfirm && ( + <> + + + + + + )} +
    + + + ); + } + + 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) => { + let persons = (await fetchUsers(e.detail.value)).users; + let choices = persons.map(pvs => personToChoice(pvs)); + this.blockPersonChoices.setChoices(choices, "value", "label", true); + }, 400), + 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) => { + let communities = (await fetchCommunities(e.detail.value)) + .communities; + let choices = communities.map(cv => communityToChoice(cv)); + this.blockCommunityChoices.setChoices( + choices, + "value", + "label", + true + ); + }, 400), + false + ); + } + } + } + + handleBlockPerson(personId: number) { + if (personId != 0) { + let blockUserForm: BlockPerson = { + person_id: personId, + block: true, + auth: authField(), + }; + WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); + } + } + + handleUnblockPerson(i: { ctx: Settings; recipientId: number }) { + let blockUserForm: BlockPerson = { + person_id: i.recipientId, + block: false, + auth: authField(), + }; + WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); + } + + handleBlockCommunity(community_id: number) { + if (community_id != 0) { + let blockCommunityForm: BlockCommunity = { + community_id, + block: true, + auth: authField(), + }; + WebSocketService.Instance.send( + wsClient.blockCommunity(blockCommunityForm) + ); + } + } + + handleUnblockCommunity(i: { ctx: Settings; communityId: number }) { + let blockCommunityForm: BlockCommunity = { + community_id: i.communityId, + block: false, + auth: authField(), + }; + 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; + UserService.Instance.myUserInfo.local_user_view.local_user.show_avatars = + event.target.checked; // Just for instant updates + 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; + UserService.Instance.myUserInfo.local_user_view.local_user.show_scores = + event.target.checked; // Just for instant updates + 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); + } + + handleLangChange(i: Settings, event: any) { + i.state.saveUserSettingsForm.lang = event.target.value; + i18n.changeLanguage(getLanguage(i.state.saveUserSettingsForm.lang)); + i.setState(i.state); + } + + handleSortTypeChange(val: SortType) { + this.state.saveUserSettingsForm.default_sort_type = + Object.keys(SortType).indexOf(val); + this.setState(this.state); + } + + handleListingTypeChange(val: ListingType) { + this.state.saveUserSettingsForm.default_listing_type = + Object.keys(ListingType).indexOf(val); + this.setState(this.state); + } + + handleEmailChange(i: Settings, event: any) { + i.state.saveUserSettingsForm.email = event.target.value; + i.setState(i.state); + } + + handleBioChange(val: string) { + this.state.saveUserSettingsForm.bio = val; + this.setState(this.state); + } + + handleAvatarUpload(url: string) { + this.state.saveUserSettingsForm.avatar = url; + this.setState(this.state); + } + + handleAvatarRemove() { + this.state.saveUserSettingsForm.avatar = ""; + this.setState(this.state); + } + + handleBannerUpload(url: string) { + this.state.saveUserSettingsForm.banner = url; + this.setState(this.state); + } + + handleBannerRemove() { + this.state.saveUserSettingsForm.banner = ""; + this.setState(this.state); + } + + 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; + if ( + i.state.saveUserSettingsForm.matrix_user_id == "" && + !UserService.Instance.myUserInfo.local_user_view.person.matrix_user_id + ) { + i.state.saveUserSettingsForm.matrix_user_id = undefined; + } + 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.state.saveUserSettingsLoading = true; + i.setState(i.state); + + WebSocketService.Instance.send( + wsClient.saveUserSettings(i.state.saveUserSettingsForm) + ); + } + + handleChangePasswordSubmit(i: Settings, event: any) { + event.preventDefault(); + i.state.changePasswordLoading = true; + i.setState(i.state); + + WebSocketService.Instance.send( + wsClient.changePassword(i.state.changePasswordForm) + ); + } + + handleDeleteAccountShowConfirmToggle(i: Settings, event: any) { + event.preventDefault(); + i.state.deleteAccountShowConfirm = !i.state.deleteAccountShowConfirm; + i.setState(i.state); + } + + handleDeleteAccountPasswordChange(i: Settings, event: any) { + i.state.deleteAccountForm.password = event.target.value; + i.setState(i.state); + } + + handleLogoutClick(i: Settings) { + UserService.Instance.logout(); + i.context.router.history.push("/"); + } + + handleDeleteAccount(i: Settings, event: any) { + event.preventDefault(); + i.state.deleteAccountLoading = true; + i.setState(i.state); + + WebSocketService.Instance.send( + wsClient.deleteAccount(i.state.deleteAccountForm) + ); + } + + handleSwitchTab(i: { ctx: Settings; tab: string }) { + i.ctx.setState({ currentTab: i.tab }); + + if (i.ctx.state.currentTab == "blocks") { + i.ctx.setupBlockPersonChoices(); + i.ctx.setupBlockCommunityChoices(); + } + } + + setUserInfo() { + let luv = UserService.Instance.myUserInfo.local_user_view; + this.state.saveUserSettingsForm.show_nsfw = luv.local_user.show_nsfw; + this.state.saveUserSettingsForm.theme = luv.local_user.theme + ? luv.local_user.theme + : "browser"; + this.state.saveUserSettingsForm.default_sort_type = + luv.local_user.default_sort_type; + this.state.saveUserSettingsForm.default_listing_type = + luv.local_user.default_listing_type; + this.state.saveUserSettingsForm.lang = luv.local_user.lang; + this.state.saveUserSettingsForm.avatar = luv.person.avatar; + this.state.saveUserSettingsForm.banner = luv.person.banner; + this.state.saveUserSettingsForm.display_name = luv.person.display_name; + this.state.saveUserSettingsForm.show_avatars = luv.local_user.show_avatars; + this.state.saveUserSettingsForm.bot_account = luv.person.bot_account; + this.state.saveUserSettingsForm.show_bot_accounts = + luv.local_user.show_bot_accounts; + this.state.saveUserSettingsForm.show_scores = luv.local_user.show_scores; + this.state.saveUserSettingsForm.show_read_posts = + luv.local_user.show_read_posts; + this.state.saveUserSettingsForm.show_new_post_notifs = + luv.local_user.show_new_post_notifs; + this.state.saveUserSettingsForm.email = luv.local_user.email; + this.state.saveUserSettingsForm.bio = luv.person.bio; + this.state.saveUserSettingsForm.send_notifications_to_email = + luv.local_user.send_notifications_to_email; + this.state.saveUserSettingsForm.matrix_user_id = luv.person.matrix_user_id; + this.state.personBlocks = UserService.Instance.myUserInfo.person_blocks; + this.state.communityBlocks = + UserService.Instance.myUserInfo.community_blocks; + } + + parseMessage(msg: any) { + let op = wsUserOp(msg); + console.log(msg); + if (msg.error) { + toast(i18n.t(msg.error), "danger"); + return; + } else if (op == UserOperation.SaveUserSettings) { + let data = wsJsonToRes(msg).data; + UserService.Instance.login(data); + this.state.saveUserSettingsLoading = false; + this.setState(this.state); + + window.scrollTo(0, 0); + } else if (op == UserOperation.ChangePassword) { + let data = wsJsonToRes(msg).data; + UserService.Instance.login(data); + this.state.changePasswordLoading = false; + this.setState(this.state); + 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).data; + this.setState({ personBlocks: updatePersonBlock(data) }); + } else if (op == UserOperation.BlockCommunity) { + let data = wsJsonToRes(msg).data; + this.setState({ communityBlocks: updateCommunityBlock(data) }); + } + } +} diff --git a/src/shared/components/post/create-post.tsx b/src/shared/components/post/create-post.tsx index d2945b5..b5c95c4 100644 --- a/src/shared/components/post/create-post.tsx +++ b/src/shared/components/post/create-post.tsx @@ -51,7 +51,7 @@ export class CreatePost extends Component { this.handlePostCreate = this.handlePostCreate.bind(this); this.state = this.emptyState; - if (!UserService.Instance.localUserView && isBrowser()) { + if (!UserService.Instance.myUserInfo && isBrowser()) { toast(i18n.t("not_logged_in"), "danger"); this.context.router.history.push(`/login`); } diff --git a/src/shared/components/post/post-form.tsx b/src/shared/components/post/post-form.tsx index f3e2c27..10309fb 100644 --- a/src/shared/components/post/post-form.tsx +++ b/src/shared/components/post/post-form.tsx @@ -194,7 +194,7 @@ export class PostForm extends Component {