import { Component, linkEvent } from "inferno"; import { Link } from "inferno-router"; import { AddAdminResponse, BanPerson, BanPersonResponse, BlockPerson, BlockPersonResponse, CommentResponse, GetPersonDetails, GetPersonDetailsResponse, GetSiteResponse, PostResponse, PurgeItemResponse, SortType, UserOperation, wsJsonToRes, wsUserOp, } 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 { canMod, capitalizeFirstLetter, createCommentLikeRes, createPostLikeFindRes, editCommentRes, editPostFindRes, enableDownvotes, enableNsfw, fetchLimit, futureDaysToUnixTime, getUsernameFromProps, isAdmin, isBanned, mdToHtml, myAuth, numToSI, relTags, restoreScrollPosition, routeSortTypeToEnum, saveCommentRes, saveScrollPosition, setIsoData, setupTippy, toast, updatePersonBlock, wsClient, wsSubscribe, } 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; personBlocked: boolean; banReason?: string; banExpireDays?: number; showBanDialog: boolean; removeData: boolean; siteRes: GetSiteResponse; } interface ProfileProps { view: PersonDetailsView; sort: SortType; page: number; person_id?: number; username: string; } interface UrlParams { view?: string; sort?: SortType; page?: number; } export class Profile extends Component { private isoData = setIsoData(this.context); private subscription?: Subscription; state: ProfileState = { 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), personBlocked: false, siteRes: this.isoData.site_res, showBanDialog: false, removeData: false, }; constructor(props: any, context: any) { super(props, context); this.handleSortChange = this.handleSortChange.bind(this); this.handlePageChange = this.handlePageChange.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 = { ...this.state, personRes: this.isoData.routeData[0] as GetPersonDetailsResponse, loading: false, }; } else { this.fetchUserData(); } } 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: myAuth(false), }; WebSocketService.Instance.send(wsClient.getPersonDetails(form)); } get amCurrentUser() { return ( UserService.Instance.myUserInfo?.local_user_view.person.id == this.state.personRes?.person_view.person.id ); } setPersonBlock() { let mui = UserService.Instance.myUserInfo; let res = this.state.personRes; if (mui && res) { this.setState({ personBlocked: mui.person_blocks .map(a => a.target.id) .includes(res.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 username = pathSplit[2]; let view = this.getViewFromProps(pathSplit[4]); let sort = this.getSortTypeFromProps(pathSplit[6]); let page = this.getPageFromProps(Number(pathSplit[8])); let form: GetPersonDetails = { username: username, sort, saved_only: view === PersonDetailsView.Saved, page, limit: fetchLimit, auth: req.auth, }; return [req.client.getPersonDetails(form)]; } componentDidMount() { this.setPersonBlock(); setupTippy(); } 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), 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 { let res = this.state.personRes; return res ? `@${res.person_view.person.name} - ${this.state.siteRes.site_view.site.name}` : ""; } render() { let res = this.state.personRes; return (
{this.state.loading ? (
) : ( res && (
<> {this.userInfo()}
{!this.state.loading && this.selects()}
{!this.state.loading && (
{this.moderates()} {this.amCurrentUser && this.follows()}
)}
) )}
); } viewRadios() { return (
); } selects() { let profileRss = `/feeds/u/${this.state.userName}.xml?sort=${this.state.sort}`; return (
{this.viewRadios()}
); } handleBlockPerson(personId: number) { let auth = myAuth(); if (auth) { if (personId != 0) { let blockUserForm: BlockPerson = { person_id: personId, block: true, auth, }; WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); } } } handleUnblockPerson(recipientId: number) { let auth = myAuth(); if (auth) { let blockUserForm: BlockPerson = { person_id: recipientId, block: false, auth, }; WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); } } userInfo() { let pv = this.state.personRes?.person_view; return ( pv && (
{!isBanned(pv.person) && ( )}
{pv.person.display_name && (
{pv.person.display_name}
)}
  • {isBanned(pv.person) && (
  • {i18n.t("banned")}
  • )} {pv.person.deleted && (
  • {i18n.t("deleted")}
  • )} {pv.person.admin && (
  • {i18n.t("admin")}
  • )} {pv.person.bot_account && (
  • {i18n.t("bot_account").toLowerCase()}
  • )}
{this.banDialog()}
{!this.amCurrentUser && UserService.Instance.myUserInfo && ( <> {i18n.t("send_secure_message")} {i18n.t("send_message")} {this.state.personBlocked ? ( ) : ( )} )} {canMod(pv.person.id, undefined, this.state.siteRes.admins) && !isAdmin(pv.person.id, this.state.siteRes.admins) && !this.state.showBanDialog && (!isBanned(pv.person) ? ( ) : ( ))}
{pv.person.bio && (
)}
  • {i18n.t("number_of_posts", { count: pv.counts.post_count, formattedCount: numToSI(pv.counts.post_count), })}
  • {i18n.t("number_of_comments", { count: pv.counts.comment_count, formattedCount: numToSI(pv.counts.comment_count), })}
{i18n.t("joined")}{" "}
{i18n.t("cake_day_title")}{" "} {moment .utc(pv.person.published) .local() .format("MMM DD, YYYY")}
) ); } banDialog() { let pv = this.state.personRes?.person_view; return ( pv && ( <> {this.state.showBanDialog && (
{/* TODO hold off on expires until later */} {/*
*/} {/* */} {/* */} {/*
*/}
)} ) ); } moderates() { let moderates = this.state.personRes?.moderates; return ( moderates && moderates.length > 0 && (
{i18n.t("moderates")}
    {moderates.map(cmv => (
  • ))}
) ); } follows() { let follows = UserService.Instance.myUserInfo?.follows; return ( follows && 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.setState({ loading: true }); this.fetchUserData(); } handlePageChange(page: number) { this.updateUrl({ page: 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, }); } handleModBanShow(i: Profile) { i.setState({ showBanDialog: true }); } handleModBanReasonChange(i: Profile, event: any) { i.setState({ banReason: event.target.value }); } handleModBanExpireDaysChange(i: Profile, event: any) { i.setState({ banExpireDays: event.target.value }); } handleModRemoveDataChange(i: Profile, event: any) { i.setState({ removeData: event.target.checked }); } handleModBanSubmitCancel(i: Profile, event?: any) { event.preventDefault(); i.setState({ showBanDialog: false }); } handleModBanSubmit(i: Profile, event?: any) { if (event) event.preventDefault(); let person = i.state.personRes?.person_view.person; let auth = myAuth(); if (person && auth) { // If its an unban, restore all their data let ban = !person.banned; if (ban == false) { i.setState({ removeData: false }); } let form: BanPerson = { person_id: person.id, ban, remove_data: i.state.removeData, reason: i.state.banReason, expires: futureDaysToUnixTime(i.state.banExpireDays), auth, }; WebSocketService.Instance.send(wsClient.banPerson(form)); i.setState({ showBanDialog: false }); } } 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); this.setState({ personRes: data, loading: false }); this.setPersonBlock(); restoreScrollPosition(this.context); } else if (op == UserOperation.AddAdmin) { let data = wsJsonToRes(msg); this.setState(s => ((s.siteRes.admins = data.admins), s)); } else if (op == UserOperation.CreateCommentLike) { let data = wsJsonToRes(msg); 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); editCommentRes(data.comment_view, this.state.personRes?.comments); this.setState(this.state); } else if (op == UserOperation.CreateComment) { let data = wsJsonToRes(msg); let mui = UserService.Instance.myUserInfo; if (data.comment_view.creator.id == mui?.local_user_view.person.id) { toast(i18n.t("reply_sent")); } } else if (op == UserOperation.SaveComment) { let data = wsJsonToRes(msg); 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.FeaturePost || op == UserOperation.SavePost ) { let data = wsJsonToRes(msg); editPostFindRes(data.post_view, this.state.personRes?.posts); this.setState(this.state); } else if (op == UserOperation.CreatePostLike) { let data = wsJsonToRes(msg); createPostLikeFindRes(data.post_view, this.state.personRes?.posts); this.setState(this.state); } else if (op == UserOperation.BanPerson) { let data = wsJsonToRes(msg); let res = this.state.personRes; res?.comments .filter(c => c.creator.id == data.person_view.person.id) .forEach(c => (c.creator.banned = data.banned)); res?.posts .filter(c => c.creator.id == data.person_view.person.id) .forEach(c => (c.creator.banned = data.banned)); let pv = res?.person_view; if (pv?.person.id == data.person_view.person.id) { pv.person.banned = data.banned; } this.setState(this.state); } else if (op == UserOperation.BlockPerson) { let data = wsJsonToRes(msg); updatePersonBlock(data); this.setPersonBlock(); this.setState(this.state); } else if ( op == UserOperation.PurgePerson || op == UserOperation.PurgePost || op == UserOperation.PurgeComment || op == UserOperation.PurgeCommunity ) { let data = wsJsonToRes(msg); if (data.success) { toast(i18n.t("purge_success")); this.context.router.history.push(`/`); } } } }