import { None, Option, Some } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { Link } from "inferno-router"; import { AddAdminResponse, BanPerson, BanPersonResponse, BlockPerson, BlockPersonResponse, CommentResponse, GetPersonDetails, GetPersonDetailsResponse, GetSiteResponse, PostResponse, PurgeItemResponse, SortType, toUndefined, 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 { auth, canMod, capitalizeFirstLetter, createCommentLikeRes, createPostLikeFindRes, editCommentRes, editPostFindRes, enableDownvotes, enableNsfw, fetchLimit, futureDaysToUnixTime, getUsernameFromProps, isAdmin, isBanned, mdToHtml, 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: Option; userName: string; view: PersonDetailsView; sort: SortType; page: number; loading: boolean; personBlocked: boolean; banReason: Option; banExpireDays: Option; showBanDialog: boolean; removeData: 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, GetPersonDetailsResponse); private subscription: Subscription; private emptyState: ProfileState = { personRes: None, 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, banReason: null, banExpireDays: null, removeData: false, }; constructor(props: any, context: any) { super(props, context); this.state = this.emptyState; 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: Some(this.isoData.routeData[0] as GetPersonDetailsResponse), loading: false, }; } else { this.fetchUserData(); } } fetchUserData() { let form = new GetPersonDetails({ username: Some(this.state.userName), person_id: None, community_id: None, sort: Some(this.state.sort), saved_only: Some(this.state.view === PersonDetailsView.Saved), page: Some(this.state.page), limit: Some(fetchLimit), auth: auth(false).ok(), }); WebSocketService.Instance.send(wsClient.getPersonDetails(form)); } get amCurrentUser() { return UserService.Instance.myUserInfo.match({ some: mui => this.state.personRes.match({ some: res => mui.local_user_view.person.id == res.person_view.person.id, none: false, }), none: false, }); } setPersonBlock() { UserService.Instance.myUserInfo.match({ some: mui => this.state.personRes.match({ some: res => this.setState({ personBlocked: mui.person_blocks .map(a => a.target.id) .includes(res.person_view.person.id), }), none: void 0, }), none: void 0, }); } 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 = Some(this.getSortTypeFromProps(pathSplit[6])); let page = Some(this.getPageFromProps(Number(pathSplit[8]))); let form = new GetPersonDetails({ username: Some(username), person_id: None, community_id: None, sort, saved_only: Some(view === PersonDetailsView.Saved), page, limit: Some(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) || 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.match({ some: res => `@${res.person_view.person.name} - ${this.state.siteRes.site_view.site.name}`, none: "", }); } render() { return (
{this.state.loading ? (
) : ( this.state.personRes.match({ some: res => (
<> {this.userInfo()}
{!this.state.loading && this.selects()}
{!this.state.loading && (
{this.moderates()} {this.amCurrentUser && this.follows()}
)}
), none: <>, }) )}
); } viewRadios() { return (
); } selects() { let profileRss = `/feeds/u/${this.state.userName}.xml?sort=${this.state.sort}`; return (
{this.viewRadios()}
); } handleBlockPerson(personId: number) { if (personId != 0) { let blockUserForm = new BlockPerson({ person_id: personId, block: true, auth: auth().unwrap(), }); WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); } } handleUnblockPerson(recipientId: number) { let blockUserForm = new BlockPerson({ person_id: recipientId, block: false, auth: auth().unwrap(), }); WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); } userInfo() { return this.state.personRes .map(r => r.person_view) .match({ some: pv => (
{pv.person.display_name.match({ some: displayName => (
{displayName}
), none: <>, })}
  • {isBanned(pv.person) && (
  • {i18n.t("banned")}
  • )} {pv.person.admin && (
  • {i18n.t("admin")}
  • )} {pv.person.bot_account && (
  • {i18n.t("bot_account").toLowerCase()}
  • )}
{this.banDialog()}
{!this.amCurrentUser && UserService.Instance.myUserInfo.isSome() && ( <> {i18n.t("send_secure_message")} {i18n.t("send_message")} {this.state.personBlocked ? ( ) : ( )} )} {canMod( None, Some(this.state.siteRes.admins), pv.person.id ) && !isAdmin(Some(this.state.siteRes.admins), pv.person.id) && !this.state.showBanDialog && (!isBanned(pv.person) ? ( ) : ( ))}
{pv.person.bio.match({ some: bio => (
), none: <>, })}
  • {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")}
), none: <>, }); } banDialog() { return this.state.personRes .map(r => r.person_view) .match({ some: pv => ( <> {this.state.showBanDialog && (
{/* TODO hold off on expires until later */} {/*
*/} {/* */} {/* */} {/*
*/}
)} ), none: <>, }); } moderates() { return this.state.personRes .map(r => r.moderates) .match({ some: moderates => { if (moderates.length > 0) { return (
{i18n.t("moderates")}
    {moderates.map(cmv => (
  • ))}
); } else { return <>; } }, none: void 0, }); } follows() { return UserService.Instance.myUserInfo .map(m => m.follows) .match({ some: follows => { if (follows.length > 0) { return (
{i18n.t("subscribed")}
    {follows.map(cfv => (
  • ))}
); } else { return <>; } }, none: void 0, }); } 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(); i.state.personRes .map(r => r.person_view.person) .match({ some: person => { // If its an unban, restore all their data let ban = !person.banned; if (ban == false) { i.setState({ removeData: false }); } let form = new BanPerson({ person_id: person.id, ban, remove_data: Some(i.state.removeData), reason: i.state.banReason, expires: i.state.banExpireDays.map(futureDaysToUnixTime), auth: auth().unwrap(), }); WebSocketService.Instance.send(wsClient.banPerson(form)); i.setState({ showBanDialog: false }); }, none: void 0, }); } 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, GetPersonDetailsResponse ); this.setState({ personRes: Some(data), loading: false }); this.setPersonBlock(); restoreScrollPosition(this.context); } else if (op == UserOperation.AddAdmin) { let data = wsJsonToRes(msg, AddAdminResponse); this.setState(s => ((s.siteRes.admins = data.admins), s)); } else if (op == UserOperation.CreateCommentLike) { let data = wsJsonToRes(msg, CommentResponse); createCommentLikeRes( data.comment_view, this.state.personRes.map(r => r.comments).unwrapOr([]) ); this.setState(this.state); } else if ( op == UserOperation.EditComment || op == UserOperation.DeleteComment || op == UserOperation.RemoveComment ) { let data = wsJsonToRes(msg, CommentResponse); editCommentRes( data.comment_view, this.state.personRes.map(r => r.comments).unwrapOr([]) ); this.setState(this.state); } else if (op == UserOperation.CreateComment) { let data = wsJsonToRes(msg, CommentResponse); UserService.Instance.myUserInfo.match({ some: mui => { if (data.comment_view.creator.id == mui.local_user_view.person.id) { toast(i18n.t("reply_sent")); } }, none: void 0, }); } else if (op == UserOperation.SaveComment) { let data = wsJsonToRes(msg, CommentResponse); saveCommentRes( data.comment_view, this.state.personRes.map(r => r.comments).unwrapOr([]) ); 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, PostResponse); editPostFindRes( data.post_view, this.state.personRes.map(r => r.posts).unwrapOr([]) ); this.setState(this.state); } else if (op == UserOperation.CreatePostLike) { let data = wsJsonToRes(msg, PostResponse); createPostLikeFindRes( data.post_view, this.state.personRes.map(r => r.posts).unwrapOr([]) ); this.setState(this.state); } else if (op == UserOperation.BanPerson) { let data = wsJsonToRes(msg, BanPersonResponse); this.state.personRes.match({ some: res => { 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); }, none: void 0, }); } else if (op == UserOperation.BlockPerson) { let data = wsJsonToRes(msg, BlockPersonResponse); 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, PurgeItemResponse); if (data.success) { toast(i18n.t("purge_success")); this.context.router.history.push(`/`); } } } }