X-Git-Url: http://these/git/?a=blobdiff_plain;f=src%2Fshared%2Fcomponents%2Fperson%2Fprofile.tsx;h=f80d5b907a2f7f1972dc0b62d1bebb51d06df1fe;hb=2b1af707c3df6126b3e6890106c03c60ad49b1be;hp=81186504eac2312f0b25d9bbef887f816ee37259;hpb=f61037f5d89f12818c8100f907a98b74e980112a;p=lemmy-ui.git diff --git a/src/shared/components/person/profile.tsx b/src/shared/components/person/profile.tsx index 8118650..f80d5b9 100644 --- a/src/shared/components/person/profile.tsx +++ b/src/shared/components/person/profile.tsx @@ -4,41 +4,66 @@ import { Component, linkEvent } from "inferno"; import { Link } from "inferno-router"; import { RouteComponentProps } from "inferno-router/dist/Route"; import { - AddAdminResponse, + AddAdmin, + AddModToCommunity, + BanFromCommunity, + BanFromCommunityResponse, BanPerson, BanPersonResponse, BlockPerson, - BlockPersonResponse, + CommentId, + CommentReplyResponse, CommentResponse, Community, CommunityModeratorView, + CreateComment, + CreateCommentLike, + CreateCommentReport, + CreatePostLike, + CreatePostReport, + DeleteComment, + DeletePost, + DistinguishComment, + EditComment, + EditPost, + FeaturePost, GetPersonDetails, GetPersonDetailsResponse, GetSiteResponse, + LockPost, + MarkCommentReplyAsRead, + MarkPersonMentionAsRead, + PersonView, PostResponse, + PurgeComment, PurgeItemResponse, + PurgePerson, + PurgePost, + RemoveComment, + RemovePost, + SaveComment, + SavePost, SortType, - UserOperation, - wsJsonToRes, - wsUserOp, + TransferCommunity, } 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 { UserService } from "../../services"; +import { FirstLoadService } from "../../services/FirstLoadService"; +import { HttpService, RequestState } from "../../services/HttpService"; import { QueryParams, canMod, capitalizeFirstLetter, - createCommentLikeRes, - createPostLikeFindRes, - editCommentRes, - editPostFindRes, + editComment, + editPost, + editWith, enableDownvotes, enableNsfw, fetchLimit, futureDaysToUnixTime, + getCommentParentId, getPageFromString, getQueryParams, getQueryString, @@ -46,17 +71,15 @@ import { isBanned, mdToHtml, myAuth, + myAuthRequired, numToSI, relTags, restoreScrollPosition, - saveCommentRes, saveScrollPosition, setIsoData, setupTippy, toast, updatePersonBlock, - wsClient, - wsSubscribe, } from "../../utils"; import { BannerIconHeader } from "../common/banner-icon-header"; import { HtmlTags } from "../common/html-tags"; @@ -68,14 +91,15 @@ import { PersonDetails } from "./person-details"; import { PersonListing } from "./person-listing"; interface ProfileState { - personRes?: GetPersonDetailsResponse; - loading: boolean; + personRes: RequestState; personBlocked: boolean; banReason?: string; banExpireDays?: number; showBanDialog: boolean; removeData: boolean; siteRes: GetSiteResponse; + finished: Map; + isIsomorphic: boolean; } interface ProfileProps { @@ -102,26 +126,6 @@ function getViewFromProps(view?: string): PersonDetailsView { : PersonDetailsView.Overview; } -function toggleBlockPerson(recipientId: number, block: boolean) { - const auth = myAuth(); - - if (auth) { - const blockUserForm: BlockPerson = { - person_id: recipientId, - block, - auth, - }; - - WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); - } -} - -const handleUnblockPerson = (personId: number) => - toggleBlockPerson(personId, false); - -const handleBlockPerson = (personId: number) => - toggleBlockPerson(personId, true); - const getCommunitiesListing = ( translationKey: NoOptionI18nKeys, communityViews?: { community: Community }[] @@ -153,13 +157,14 @@ export class Profile extends Component< ProfileState > { private isoData = setIsoData(this.context); - private subscription?: Subscription; state: ProfileState = { - loading: true, + personRes: { state: "empty" }, personBlocked: false, siteRes: this.isoData.site_res, showBanDialog: false, removeData: false, + finished: new Map(), + isIsomorphic: false, }; constructor(props: RouteComponentProps<{ username: string }>, context: any) { @@ -168,51 +173,95 @@ export class Profile extends Component< this.handleSortChange = this.handleSortChange.bind(this); this.handlePageChange = this.handlePageChange.bind(this); - this.parseMessage = this.parseMessage.bind(this); - this.subscription = wsSubscribe(this.parseMessage); + this.handleBlockPerson = this.handleBlockPerson.bind(this); + this.handleUnblockPerson = this.handleUnblockPerson.bind(this); + + this.handleCreateComment = this.handleCreateComment.bind(this); + this.handleEditComment = this.handleEditComment.bind(this); + this.handleSaveComment = this.handleSaveComment.bind(this); + this.handleBlockPersonAlt = this.handleBlockPersonAlt.bind(this); + this.handleDeleteComment = this.handleDeleteComment.bind(this); + this.handleRemoveComment = this.handleRemoveComment.bind(this); + this.handleCommentVote = this.handleCommentVote.bind(this); + this.handleAddModToCommunity = this.handleAddModToCommunity.bind(this); + this.handleAddAdmin = this.handleAddAdmin.bind(this); + this.handlePurgePerson = this.handlePurgePerson.bind(this); + this.handlePurgeComment = this.handlePurgeComment.bind(this); + this.handleCommentReport = this.handleCommentReport.bind(this); + this.handleDistinguishComment = this.handleDistinguishComment.bind(this); + this.handleTransferCommunity = this.handleTransferCommunity.bind(this); + this.handleCommentReplyRead = this.handleCommentReplyRead.bind(this); + this.handlePersonMentionRead = this.handlePersonMentionRead.bind(this); + this.handleBanFromCommunity = this.handleBanFromCommunity.bind(this); + this.handleBanPerson = this.handleBanPerson.bind(this); + this.handlePostVote = this.handlePostVote.bind(this); + this.handlePostEdit = this.handlePostEdit.bind(this); + this.handlePostReport = this.handlePostReport.bind(this); + this.handleLockPost = this.handleLockPost.bind(this); + this.handleDeletePost = this.handleDeletePost.bind(this); + this.handleRemovePost = this.handleRemovePost.bind(this); + this.handleSavePost = this.handleSavePost.bind(this); + this.handlePurgePost = this.handlePurgePost.bind(this); + this.handleFeaturePost = this.handleFeaturePost.bind(this); // Only fetch the data if coming from another route - if (this.isoData.path === this.context.router.route.match.url) { + if (FirstLoadService.isFirstLoad) { this.state = { ...this.state, - personRes: this.isoData.routeData[0] as GetPersonDetailsResponse, - loading: false, + personRes: this.isoData.routeData[0], + isIsomorphic: true, }; - } else { - this.fetchUserData(); } } - fetchUserData() { - const { page, sort, view } = getProfileQueryParams(); + async componentDidMount() { + if (!this.state.isIsomorphic) { + await this.fetchUserData(); + } + setupTippy(); + } - const form: GetPersonDetails = { - username: this.props.match.params.username, - sort, - saved_only: view === PersonDetailsView.Saved, - page, - limit: fetchLimit, - auth: myAuth(false), - }; + componentWillUnmount() { + saveScrollPosition(this.context); + } + + async fetchUserData() { + const { page, sort, view } = getProfileQueryParams(); - WebSocketService.Instance.send(wsClient.getPersonDetails(form)); + this.setState({ personRes: { state: "empty" } }); + this.setState({ + personRes: await HttpService.client.getPersonDetails({ + username: this.props.match.params.username, + sort, + saved_only: view === PersonDetailsView.Saved, + page, + limit: fetchLimit, + auth: myAuth(), + }), + }); + restoreScrollPosition(this.context); + this.setPersonBlock(); } get amCurrentUser() { - return ( - UserService.Instance.myUserInfo?.local_user_view.person.id === - this.state.personRes?.person_view.person.id - ); + if (this.state.personRes.state === "success") { + return ( + UserService.Instance.myUserInfo?.local_user_view.person.id === + this.state.personRes.data.person_view.person.id + ); + } else { + return false; + } } setPersonBlock() { const mui = UserService.Instance.myUserInfo; const res = this.state.personRes; - if (mui && res) { + if (mui && res.state === "success") { this.setState({ personBlocked: mui.person_blocks.some( - ({ target: { id } }) => id === res.person_view.person.id + ({ target: { id } }) => id === res.data.person_view.person.id ), }); } @@ -223,7 +272,9 @@ export class Profile extends Component< path, query: { page, sort, view: urlView }, auth, - }: InitialFetchRequest>): Promise[] { + }: InitialFetchRequest>): Promise< + RequestState + >[] { const pathSplit = path.split("/"); const username = pathSplit[2]; @@ -241,74 +292,99 @@ export class Profile extends Component< return [client.getPersonDetails(form)]; } - componentDidMount() { - this.setPersonBlock(); - setupTippy(); - } - - componentWillUnmount() { - this.subscription?.unsubscribe(); - saveScrollPosition(this.context); - } - get documentTitle(): string { + const siteName = this.state.siteRes.site_view.site.name; const res = this.state.personRes; - return res - ? `@${res.person_view.person.name} - ${this.state.siteRes.site_view.site.name}` - : ""; + return res.state == "success" + ? `@${res.data.person_view.person.name} - ${siteName}` + : siteName; } - render() { - const { personRes, loading, siteRes } = this.state; - const { page, sort, view } = getProfileQueryParams(); - - return ( -
- {loading ? ( + renderPersonRes() { + switch (this.state.personRes.state) { + case "loading": + return (
- ) : ( - personRes && ( -
-
- - - {this.userInfo} - -
- - {this.selects} - - -
+ ); + case "success": { + const siteRes = this.state.siteRes; + const personRes = this.state.personRes.data; + const { page, sort, view } = getProfileQueryParams(); + + return ( +
+
+ + + {this.userInfo(personRes.person_view)} + +
+ + {this.selects} + + +
-
- - {this.amCurrentUser && } -
+
+ + {this.amCurrentUser && }
- ) - )} -
- ); +
+ ); + } + } + } + + render() { + return
{this.renderPersonRes()}
; } get viewRadios() { @@ -366,8 +442,7 @@ export class Profile extends Component< ); } - get userInfo() { - const pv = this.state.personRes?.person_view; + userInfo(pv: PersonView) { const { personBlocked, siteRes: { admins }, @@ -422,7 +497,7 @@ export class Profile extends Component< )}
- {this.banDialog} + {this.banDialog(pv)}
{!this.amCurrentUser && UserService.Instance.myUserInfo && ( <> @@ -448,7 +523,10 @@ export class Profile extends Component< className={ "d-flex align-self-start btn btn-secondary mr-2" } - onClick={linkEvent(pv.person.id, handleUnblockPerson)} + onClick={linkEvent( + pv.person.id, + this.handleUnblockPerson + )} > {i18n.t("unblock_user")} @@ -457,7 +535,10 @@ export class Profile extends Component< className={ "d-flex align-self-start btn btn-secondary mr-2" } - onClick={linkEvent(pv.person.id, handleBlockPerson)} + onClick={linkEvent( + pv.person.id, + this.handleBlockPerson + )} > {i18n.t("block_user")} @@ -544,87 +625,82 @@ export class Profile extends Component< ); } - get banDialog() { - const pv = this.state.personRes?.person_view; + banDialog(pv: PersonView) { const { showBanDialog } = this.state; return ( - pv && ( - <> - {showBanDialog && ( -
-
- - - + showBanDialog && ( + +
+ + + + +
+
-
-
- - -
-
-
- {/* TODO hold off on expires until later */} - {/*
*/} - {/* */} - {/* */} - {/*
*/} -
- - + {i18n.t("remove_content")} +
- - )} - +
+
+ {/* TODO hold off on expires until later */} + {/*
*/} + {/* */} + {/* */} + {/*
*/} +
+ + +
+ ) ); } - updateUrl({ page, sort, view }: Partial) { + async updateUrl({ page, sort, view }: Partial) { const { page: urlPage, sort: urlSort, @@ -640,9 +716,7 @@ export class Profile extends Component< const { username } = this.props.match.params; this.props.history.push(`/u/${username}${getQueryString(queryParams)}`); - - this.setState({ loading: true }); - this.fetchUserData(); + await this.fetchUserData(); } handlePageChange(page: number) { @@ -676,19 +750,18 @@ export class Profile extends Component< i.setState({ removeData: event.target.checked }); } - handleModBanSubmitCancel(i: Profile, event?: any) { - event.preventDefault(); + handleModBanSubmitCancel(i: Profile) { i.setState({ showBanDialog: false }); } - handleModBanSubmit(i: Profile, event?: any) { - if (event) event.preventDefault(); - const { personRes, removeData, banReason, banExpireDays } = i.state; + async handleModBanSubmit(i: Profile, event: any) { + event.preventDefault(); + const { removeData, banReason, banExpireDays } = i.state; - const person = personRes?.person_view.person; - const auth = myAuth(); + const personRes = i.state.personRes; - if (person && auth) { + if (personRes.state == "success") { + const person = personRes.data.person_view.person; const ban = !person.banned; // If its an unban, restore all their data @@ -696,154 +769,281 @@ export class Profile extends Component< i.setState({ removeData: false }); } - const form: BanPerson = { + const res = await HttpService.client.banPerson({ person_id: person.id, ban, remove_data: removeData, reason: banReason, expires: futureDaysToUnixTime(banExpireDays), - auth, - }; - WebSocketService.Instance.send(wsClient.banPerson(form)); - + auth: myAuthRequired(), + }); + // TODO + this.updateBan(res); i.setState({ showBanDialog: false }); } } - parseMessage(msg: any) { - const op = wsUserOp(msg); - console.log(msg); + async toggleBlockPerson(recipientId: number, block: boolean) { + const res = await HttpService.client.blockPerson({ + person_id: recipientId, + block, + auth: myAuthRequired(), + }); + if (res.state == "success") { + updatePersonBlock(res.data); + } + } + + handleUnblockPerson(personId: number) { + this.toggleBlockPerson(personId, false); + } - if (msg.error) { - toast(i18n.t(msg.error), "danger"); + handleBlockPerson(personId: number) { + this.toggleBlockPerson(personId, true); + } - if (msg.error === "couldnt_find_that_username_or_email") { - this.context.router.history.push("/"); - } - } else if (msg.reconnect) { - this.fetchUserData(); - } else { - switch (op) { - case 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 - const data = wsJsonToRes(msg); - this.setState({ personRes: data, loading: false }); - this.setPersonBlock(); - restoreScrollPosition(this.context); - - break; - } + async handleAddModToCommunity(form: AddModToCommunity) { + // TODO not sure what to do here + await HttpService.client.addModToCommunity(form); + } - case UserOperation.AddAdmin: { - const { admins } = wsJsonToRes(msg); - this.setState(s => ((s.siteRes.admins = admins), s)); + async handlePurgePerson(form: PurgePerson) { + const purgePersonRes = await HttpService.client.purgePerson(form); + this.purgeItem(purgePersonRes); + } - break; - } + async handlePurgeComment(form: PurgeComment) { + const purgeCommentRes = await HttpService.client.purgeComment(form); + this.purgeItem(purgeCommentRes); + } - case UserOperation.CreateCommentLike: { - const { comment_view } = wsJsonToRes(msg); - createCommentLikeRes(comment_view, this.state.personRes?.comments); - this.setState(this.state); + async handlePurgePost(form: PurgePost) { + const purgeRes = await HttpService.client.purgePost(form); + this.purgeItem(purgeRes); + } - break; - } + async handleBlockPersonAlt(form: BlockPerson) { + const blockPersonRes = await HttpService.client.blockPerson(form); + if (blockPersonRes.state === "success") { + updatePersonBlock(blockPersonRes.data); + } + } - case UserOperation.EditComment: - case UserOperation.DeleteComment: - case UserOperation.RemoveComment: { - const { comment_view } = wsJsonToRes(msg); - editCommentRes(comment_view, this.state.personRes?.comments); - this.setState(this.state); + async handleCreateComment(form: CreateComment) { + const createCommentRes = await HttpService.client.createComment(form); + this.createAndUpdateComments(createCommentRes); - break; - } + return createCommentRes; + } - case UserOperation.CreateComment: { - const { - comment_view: { - creator: { id }, - }, - } = wsJsonToRes(msg); - const mui = UserService.Instance.myUserInfo; + async handleEditComment(form: EditComment) { + const editCommentRes = await HttpService.client.editComment(form); + this.findAndUpdateComment(editCommentRes); - if (id === mui?.local_user_view.person.id) { - toast(i18n.t("reply_sent")); - } + return editCommentRes; + } - break; - } + async handleDeleteComment(form: DeleteComment) { + const deleteCommentRes = await HttpService.client.deleteComment(form); + this.findAndUpdateComment(deleteCommentRes); + } - case UserOperation.SaveComment: { - const { comment_view } = wsJsonToRes(msg); - saveCommentRes(comment_view, this.state.personRes?.comments); - this.setState(this.state); + async handleDeletePost(form: DeletePost) { + const deleteRes = await HttpService.client.deletePost(form); + this.findAndUpdatePost(deleteRes); + } - break; - } + async handleRemovePost(form: RemovePost) { + const removeRes = await HttpService.client.removePost(form); + this.findAndUpdatePost(removeRes); + } - case UserOperation.EditPost: - case UserOperation.DeletePost: - case UserOperation.RemovePost: - case UserOperation.LockPost: - case UserOperation.FeaturePost: - case UserOperation.SavePost: { - const { post_view } = wsJsonToRes(msg); - editPostFindRes(post_view, this.state.personRes?.posts); - this.setState(this.state); - - break; - } + async handleRemoveComment(form: RemoveComment) { + const removeCommentRes = await HttpService.client.removeComment(form); + this.findAndUpdateComment(removeCommentRes); + } + + async handleSaveComment(form: SaveComment) { + const saveCommentRes = await HttpService.client.saveComment(form); + this.findAndUpdateComment(saveCommentRes); + } + + async handleSavePost(form: SavePost) { + const saveRes = await HttpService.client.savePost(form); + this.findAndUpdatePost(saveRes); + } + + async handleFeaturePost(form: FeaturePost) { + const featureRes = await HttpService.client.featurePost(form); + this.findAndUpdatePost(featureRes); + } + + async handleCommentVote(form: CreateCommentLike) { + const voteRes = await HttpService.client.likeComment(form); + this.findAndUpdateComment(voteRes); + } + + async handlePostVote(form: CreatePostLike) { + const voteRes = await HttpService.client.likePost(form); + this.findAndUpdatePost(voteRes); + } + + async handlePostEdit(form: EditPost) { + const res = await HttpService.client.editPost(form); + this.findAndUpdatePost(res); + } + + async handleCommentReport(form: CreateCommentReport) { + const reportRes = await HttpService.client.createCommentReport(form); + if (reportRes.state === "success") { + toast(i18n.t("report_created")); + } + } - case UserOperation.CreatePostLike: { - const { post_view } = wsJsonToRes(msg); - createPostLikeFindRes(post_view, this.state.personRes?.posts); - this.setState(this.state); + async handlePostReport(form: CreatePostReport) { + const reportRes = await HttpService.client.createPostReport(form); + if (reportRes.state === "success") { + toast(i18n.t("report_created")); + } + } + + async handleLockPost(form: LockPost) { + const lockRes = await HttpService.client.lockPost(form); + this.findAndUpdatePost(lockRes); + } + + async handleDistinguishComment(form: DistinguishComment) { + const distinguishRes = await HttpService.client.distinguishComment(form); + this.findAndUpdateComment(distinguishRes); + } - break; + async handleAddAdmin(form: AddAdmin) { + const addAdminRes = await HttpService.client.addAdmin(form); + + if (addAdminRes.state == "success") { + this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s)); + } + } + + async handleTransferCommunity(form: TransferCommunity) { + await HttpService.client.transferCommunity(form); + toast(i18n.t("transfer_community")); + } + + async handleCommentReplyRead(form: MarkCommentReplyAsRead) { + const readRes = await HttpService.client.markCommentReplyAsRead(form); + this.findAndUpdateCommentReply(readRes); + } + + async handlePersonMentionRead(form: MarkPersonMentionAsRead) { + // TODO not sure what to do here. Maybe it is actually optional, because post doesn't need it. + await HttpService.client.markPersonMentionAsRead(form); + } + + async handleBanFromCommunity(form: BanFromCommunity) { + const banRes = await HttpService.client.banFromCommunity(form); + this.updateBanFromCommunity(banRes); + } + + async handleBanPerson(form: BanPerson) { + const banRes = await HttpService.client.banPerson(form); + this.updateBan(banRes); + } + + updateBanFromCommunity(banRes: RequestState) { + // Maybe not necessary + if (banRes.state === "success") { + this.setState(s => { + if (s.personRes.state == "success") { + s.personRes.data.posts + .filter(c => c.creator.id === banRes.data.person_view.person.id) + .forEach( + c => (c.creator_banned_from_community = banRes.data.banned) + ); + + s.personRes.data.comments + .filter(c => c.creator.id === banRes.data.person_view.person.id) + .forEach( + c => (c.creator_banned_from_community = banRes.data.banned) + ); } + return s; + }); + } + } - case UserOperation.BanPerson: { - const data = wsJsonToRes(msg); - const 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)); - const pv = res?.person_view; - - if (pv?.person.id === data.person_view.person.id) { - pv.person.banned = data.banned; - } - this.setState(this.state); - - break; + updateBan(banRes: RequestState) { + // Maybe not necessary + if (banRes.state == "success") { + this.setState(s => { + if (s.personRes.state == "success") { + s.personRes.data.posts + .filter(c => c.creator.id == banRes.data.person_view.person.id) + .forEach(c => (c.creator.banned = banRes.data.banned)); + s.personRes.data.comments + .filter(c => c.creator.id == banRes.data.person_view.person.id) + .forEach(c => (c.creator.banned = banRes.data.banned)); } + return s; + }); + } + } - case UserOperation.BlockPerson: { - const data = wsJsonToRes(msg); - updatePersonBlock(data); - this.setPersonBlock(); + purgeItem(purgeRes: RequestState) { + if (purgeRes.state == "success") { + toast(i18n.t("purge_success")); + this.context.router.history.push(`/`); + } + } - break; - } + findAndUpdateComment(res: RequestState) { + this.setState(s => { + if (s.personRes.state == "success" && res.state == "success") { + s.personRes.data.comments = editComment( + res.data.comment_view, + s.personRes.data.comments + ); + s.finished.set(res.data.comment_view.comment.id, true); + } + return s; + }); + } - case UserOperation.PurgePerson: - case UserOperation.PurgePost: - case UserOperation.PurgeComment: - case UserOperation.PurgeCommunity: { - const { success } = wsJsonToRes(msg); + createAndUpdateComments(res: RequestState) { + this.setState(s => { + if (s.personRes.state == "success" && res.state == "success") { + s.personRes.data.comments.unshift(res.data.comment_view); + // Set finished for the parent + s.finished.set( + getCommentParentId(res.data.comment_view.comment) ?? 0, + true + ); + } + return s; + }); + } - if (success) { - toast(i18n.t("purge_success")); - this.context.router.history.push(`/`); - } - } + findAndUpdateCommentReply(res: RequestState) { + this.setState(s => { + if (s.personRes.state == "success" && res.state == "success") { + s.personRes.data.comments = editWith( + res.data.comment_reply_view, + s.personRes.data.comments + ); } - } + return s; + }); + } + + findAndUpdatePost(res: RequestState) { + this.setState(s => { + if (s.personRes.state == "success" && res.state == "success") { + s.personRes.data.posts = editPost( + res.data.post_view, + s.personRes.data.posts + ); + } + return s; + }); } }