1 import classNames from "classnames";
2 import { NoOptionI18nKeys } from "i18next";
3 import { Component, linkEvent } from "inferno";
4 import { Link } from "inferno-router";
5 import { RouteComponentProps } from "inferno-router/dist/Route";
10 BanFromCommunityResponse,
18 CommunityModeratorView,
31 GetPersonDetailsResponse,
34 MarkCommentReplyAsRead,
35 MarkPersonMentionAsRead,
48 } from "lemmy-js-client";
49 import moment from "moment";
50 import { i18n } from "../../i18next";
51 import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
52 import { UserService } from "../../services";
53 import { FirstLoadService } from "../../services/FirstLoadService";
54 import { HttpService, RequestState } from "../../services/HttpService";
58 capitalizeFirstLetter,
77 restoreScrollPosition,
84 import { BannerIconHeader } from "../common/banner-icon-header";
85 import { HtmlTags } from "../common/html-tags";
86 import { Icon, Spinner } from "../common/icon";
87 import { MomentTime } from "../common/moment-time";
88 import { SortSelect } from "../common/sort-select";
89 import { CommunityLink } from "../community/community-link";
90 import { PersonDetails } from "./person-details";
91 import { PersonListing } from "./person-listing";
93 interface ProfileState {
94 personRes: RequestState<GetPersonDetailsResponse>;
95 personBlocked: boolean;
97 banExpireDays?: number;
98 showBanDialog: boolean;
100 siteRes: GetSiteResponse;
101 finished: Map<CommentId, boolean | undefined>;
102 isIsomorphic: boolean;
105 interface ProfileProps {
106 view: PersonDetailsView;
111 function getProfileQueryParams() {
112 return getQueryParams<ProfileProps>({
113 view: getViewFromProps,
114 page: getPageFromString,
115 sort: getSortTypeFromQuery,
119 function getSortTypeFromQuery(sort?: string): SortType {
120 return sort ? (sort as SortType) : "New";
123 function getViewFromProps(view?: string): PersonDetailsView {
125 ? PersonDetailsView[view] ?? PersonDetailsView.Overview
126 : PersonDetailsView.Overview;
129 const getCommunitiesListing = (
130 translationKey: NoOptionI18nKeys,
131 communityViews?: { community: Community }[]
134 communityViews.length > 0 && (
135 <div className="card border-secondary mb-3">
136 <div className="card-body">
137 <h5>{i18n.t(translationKey)}</h5>
138 <ul className="list-unstyled mb-0">
139 {communityViews.map(({ community }) => (
140 <li key={community.id}>
141 <CommunityLink community={community} />
149 const Moderates = ({ moderates }: { moderates?: CommunityModeratorView[] }) =>
150 getCommunitiesListing("moderates", moderates);
152 const Follows = () =>
153 getCommunitiesListing("subscribed", UserService.Instance.myUserInfo?.follows);
155 export class Profile extends Component<
156 RouteComponentProps<{ username: string }>,
159 private isoData = setIsoData(this.context);
160 state: ProfileState = {
161 personRes: { state: "empty" },
162 personBlocked: false,
163 siteRes: this.isoData.site_res,
164 showBanDialog: false,
170 constructor(props: RouteComponentProps<{ username: string }>, context: any) {
171 super(props, context);
173 this.handleSortChange = this.handleSortChange.bind(this);
174 this.handlePageChange = this.handlePageChange.bind(this);
176 this.handleBlockPerson = this.handleBlockPerson.bind(this);
177 this.handleUnblockPerson = this.handleUnblockPerson.bind(this);
179 this.handleCreateComment = this.handleCreateComment.bind(this);
180 this.handleEditComment = this.handleEditComment.bind(this);
181 this.handleSaveComment = this.handleSaveComment.bind(this);
182 this.handleBlockPersonAlt = this.handleBlockPersonAlt.bind(this);
183 this.handleDeleteComment = this.handleDeleteComment.bind(this);
184 this.handleRemoveComment = this.handleRemoveComment.bind(this);
185 this.handleCommentVote = this.handleCommentVote.bind(this);
186 this.handleAddModToCommunity = this.handleAddModToCommunity.bind(this);
187 this.handleAddAdmin = this.handleAddAdmin.bind(this);
188 this.handlePurgePerson = this.handlePurgePerson.bind(this);
189 this.handlePurgeComment = this.handlePurgeComment.bind(this);
190 this.handleCommentReport = this.handleCommentReport.bind(this);
191 this.handleDistinguishComment = this.handleDistinguishComment.bind(this);
192 this.handleTransferCommunity = this.handleTransferCommunity.bind(this);
193 this.handleCommentReplyRead = this.handleCommentReplyRead.bind(this);
194 this.handlePersonMentionRead = this.handlePersonMentionRead.bind(this);
195 this.handleBanFromCommunity = this.handleBanFromCommunity.bind(this);
196 this.handleBanPerson = this.handleBanPerson.bind(this);
197 this.handlePostVote = this.handlePostVote.bind(this);
198 this.handlePostEdit = this.handlePostEdit.bind(this);
199 this.handlePostReport = this.handlePostReport.bind(this);
200 this.handleLockPost = this.handleLockPost.bind(this);
201 this.handleDeletePost = this.handleDeletePost.bind(this);
202 this.handleRemovePost = this.handleRemovePost.bind(this);
203 this.handleSavePost = this.handleSavePost.bind(this);
204 this.handlePurgePost = this.handlePurgePost.bind(this);
205 this.handleFeaturePost = this.handleFeaturePost.bind(this);
207 // Only fetch the data if coming from another route
208 if (FirstLoadService.isFirstLoad) {
211 personRes: this.isoData.routeData[0],
217 async componentDidMount() {
218 if (!this.state.isIsomorphic) {
219 await this.fetchUserData();
224 componentWillUnmount() {
225 saveScrollPosition(this.context);
228 async fetchUserData() {
229 const { page, sort, view } = getProfileQueryParams();
231 this.setState({ personRes: { state: "empty" } });
233 personRes: await HttpService.client.getPersonDetails({
234 username: this.props.match.params.username,
236 saved_only: view === PersonDetailsView.Saved,
242 restoreScrollPosition(this.context);
243 this.setPersonBlock();
246 get amCurrentUser() {
247 if (this.state.personRes.state === "success") {
249 UserService.Instance.myUserInfo?.local_user_view.person.id ===
250 this.state.personRes.data.person_view.person.id
258 const mui = UserService.Instance.myUserInfo;
259 const res = this.state.personRes;
261 if (mui && res.state === "success") {
263 personBlocked: mui.person_blocks.some(
264 ({ target: { id } }) => id === res.data.person_view.person.id
270 static fetchInitialData({
273 query: { page, sort, view: urlView },
275 }: InitialFetchRequest<QueryParams<ProfileProps>>): Promise<
278 const pathSplit = path.split("/");
280 const username = pathSplit[2];
281 const view = getViewFromProps(urlView);
283 const form: GetPersonDetails = {
285 sort: getSortTypeFromQuery(sort),
286 saved_only: view === PersonDetailsView.Saved,
287 page: getPageFromString(page),
292 return [client.getPersonDetails(form)];
295 get documentTitle(): string {
296 const siteName = this.state.siteRes.site_view.site.name;
297 const res = this.state.personRes;
298 return res.state == "success"
299 ? `@${res.data.person_view.person.name} - ${siteName}`
304 switch (this.state.personRes.state) {
312 const siteRes = this.state.siteRes;
313 const personRes = this.state.personRes.data;
314 const { page, sort, view } = getProfileQueryParams();
317 <div className="row">
318 <div className="col-12 col-md-8">
320 title={this.documentTitle}
321 path={this.context.router.route.match.url}
322 description={personRes.person_view.person.bio}
323 image={personRes.person_view.person.avatar}
326 {this.userInfo(personRes.person_view)}
333 personRes={personRes}
334 admins={siteRes.admins}
338 finished={this.state.finished}
339 enableDownvotes={enableDownvotes(siteRes)}
340 enableNsfw={enableNsfw(siteRes)}
342 onPageChange={this.handlePageChange}
343 allLanguages={siteRes.all_languages}
344 siteLanguages={siteRes.discussion_languages}
345 // TODO all the forms here
346 onSaveComment={this.handleSaveComment}
347 onBlockPerson={this.handleBlockPersonAlt}
348 onDeleteComment={this.handleDeleteComment}
349 onRemoveComment={this.handleRemoveComment}
350 onCommentVote={this.handleCommentVote}
351 onCommentReport={this.handleCommentReport}
352 onDistinguishComment={this.handleDistinguishComment}
353 onAddModToCommunity={this.handleAddModToCommunity}
354 onAddAdmin={this.handleAddAdmin}
355 onTransferCommunity={this.handleTransferCommunity}
356 onPurgeComment={this.handlePurgeComment}
357 onPurgePerson={this.handlePurgePerson}
358 onCommentReplyRead={this.handleCommentReplyRead}
359 onPersonMentionRead={this.handlePersonMentionRead}
360 onBanPersonFromCommunity={this.handleBanFromCommunity}
361 onBanPerson={this.handleBanPerson}
362 onCreateComment={this.handleCreateComment}
363 onEditComment={this.handleEditComment}
364 onPostEdit={this.handlePostEdit}
365 onPostVote={this.handlePostVote}
366 onPostReport={this.handlePostReport}
367 onLockPost={this.handleLockPost}
368 onDeletePost={this.handleDeletePost}
369 onRemovePost={this.handleRemovePost}
370 onSavePost={this.handleSavePost}
371 onPurgePost={this.handlePurgePost}
372 onFeaturePost={this.handleFeaturePost}
376 <div className="col-12 col-md-4">
377 <Moderates moderates={personRes.moderates} />
378 {this.amCurrentUser && <Follows />}
387 return <div className="container-lg">{this.renderPersonRes()}</div>;
392 <div className="btn-group btn-group-toggle flex-wrap mb-2">
393 {this.getRadio(PersonDetailsView.Overview)}
394 {this.getRadio(PersonDetailsView.Comments)}
395 {this.getRadio(PersonDetailsView.Posts)}
396 {this.amCurrentUser && this.getRadio(PersonDetailsView.Saved)}
401 getRadio(view: PersonDetailsView) {
402 const { view: urlView } = getProfileQueryParams();
403 const active = view === urlView;
407 className={classNames("btn btn-outline-secondary pointer", {
415 onChange={linkEvent(this, this.handleViewChange)}
417 {i18n.t(view.toLowerCase() as NoOptionI18nKeys)}
423 const { sort } = getProfileQueryParams();
424 const { username } = this.props.match.params;
426 const profileRss = `/feeds/u/${username}.xml?sort=${sort}`;
429 <div className="mb-2">
430 <span className="mr-3">{this.viewRadios}</span>
433 onChange={this.handleSortChange}
437 <a href={profileRss} rel={relTags} title="RSS">
438 <Icon icon="rss" classes="text-muted small mx-2" />
440 <link rel="alternate" type="application/atom+xml" href={profileRss} />
445 userInfo(pv: PersonView) {
455 {!isBanned(pv.person) && (
457 banner={pv.person.banner}
458 icon={pv.person.avatar}
461 <div className="mb-3">
463 <div className="mb-0 d-flex flex-wrap">
465 {pv.person.display_name && (
466 <h5 className="mb-0">{pv.person.display_name}</h5>
468 <ul className="list-inline mb-2">
469 <li className="list-inline-item">
478 {isBanned(pv.person) && (
479 <li className="list-inline-item badge badge-danger">
483 {pv.person.deleted && (
484 <li className="list-inline-item badge badge-danger">
488 {pv.person.admin && (
489 <li className="list-inline-item badge badge-light">
493 {pv.person.bot_account && (
494 <li className="list-inline-item badge badge-light">
495 {i18n.t("bot_account").toLowerCase()}
501 <div className="flex-grow-1 unselectable pointer mx-2"></div>
502 {!this.amCurrentUser && UserService.Instance.myUserInfo && (
505 className={`d-flex align-self-start btn btn-secondary mr-2 ${
506 !pv.person.matrix_user_id && "invisible"
509 href={`https://matrix.to/#/${pv.person.matrix_user_id}`}
511 {i18n.t("send_secure_message")}
515 "d-flex align-self-start btn btn-secondary mr-2"
517 to={`/create_private_message/${pv.person.id}`}
519 {i18n.t("send_message")}
524 "d-flex align-self-start btn btn-secondary mr-2"
528 this.handleUnblockPerson
531 {i18n.t("unblock_user")}
536 "d-flex align-self-start btn btn-secondary mr-2"
540 this.handleBlockPerson
543 {i18n.t("block_user")}
549 {canMod(pv.person.id, undefined, admins) &&
550 !isAdmin(pv.person.id, admins) &&
552 (!isBanned(pv.person) ? (
555 "d-flex align-self-start btn btn-secondary mr-2"
557 onClick={linkEvent(this, this.handleModBanShow)}
558 aria-label={i18n.t("ban")}
560 {capitalizeFirstLetter(i18n.t("ban"))}
565 "d-flex align-self-start btn btn-secondary mr-2"
567 onClick={linkEvent(this, this.handleModBanSubmit)}
568 aria-label={i18n.t("unban")}
570 {capitalizeFirstLetter(i18n.t("unban"))}
575 <div className="d-flex align-items-center mb-2">
578 dangerouslySetInnerHTML={mdToHtml(pv.person.bio)}
583 <ul className="list-inline mb-2">
584 <li className="list-inline-item badge badge-light">
585 {i18n.t("number_of_posts", {
586 count: Number(pv.counts.post_count),
587 formattedCount: numToSI(pv.counts.post_count),
590 <li className="list-inline-item badge badge-light">
591 {i18n.t("number_of_comments", {
592 count: Number(pv.counts.comment_count),
593 formattedCount: numToSI(pv.counts.comment_count),
598 <div className="text-muted">
599 {i18n.t("joined")}{" "}
601 published={pv.person.published}
606 <div className="d-flex align-items-center text-muted mb-2">
608 <span className="ml-2">
609 {i18n.t("cake_day_title")}{" "}
611 .utc(pv.person.published)
613 .format("MMM DD, YYYY")}
616 {!UserService.Instance.myUserInfo && (
617 <div className="alert alert-info" role="alert">
618 {i18n.t("profile_not_logged_in_alert")}
628 banDialog(pv: PersonView) {
629 const { showBanDialog } = this.state;
633 <form onSubmit={linkEvent(this, this.handleModBanSubmit)}>
634 <div className="form-group row col-12">
635 <label className="col-form-label" htmlFor="profile-ban-reason">
640 id="profile-ban-reason"
641 className="form-control mr-2"
642 placeholder={i18n.t("reason")}
643 value={this.state.banReason}
644 onInput={linkEvent(this, this.handleModBanReasonChange)}
646 <label className="col-form-label" htmlFor={`mod-ban-expires`}>
651 id={`mod-ban-expires`}
652 className="form-control mr-2"
653 placeholder={i18n.t("number_of_days")}
654 value={this.state.banExpireDays}
655 onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
657 <div className="form-group">
658 <div className="form-check">
660 className="form-check-input"
661 id="mod-ban-remove-data"
663 checked={this.state.removeData}
664 onChange={linkEvent(this, this.handleModRemoveDataChange)}
667 className="form-check-label"
668 htmlFor="mod-ban-remove-data"
669 title={i18n.t("remove_content_more")}
671 {i18n.t("remove_content")}
676 {/* TODO hold off on expires until later */}
677 {/* <div class="form-group row"> */}
678 {/* <label class="col-form-label">Expires</label> */}
679 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
681 <div className="form-group row">
684 className="btn btn-secondary mr-2"
685 aria-label={i18n.t("cancel")}
686 onClick={linkEvent(this, this.handleModBanSubmitCancel)}
692 className="btn btn-secondary"
693 aria-label={i18n.t("ban")}
695 {i18n.t("ban")} {pv.person.name}
703 async updateUrl({ page, sort, view }: Partial<ProfileProps>) {
708 } = getProfileQueryParams();
710 const queryParams: QueryParams<ProfileProps> = {
711 page: (page ?? urlPage).toString(),
712 sort: sort ?? urlSort,
713 view: view ?? urlView,
716 const { username } = this.props.match.params;
718 this.props.history.push(`/u/${username}${getQueryString(queryParams)}`);
719 await this.fetchUserData();
722 handlePageChange(page: number) {
723 this.updateUrl({ page });
726 handleSortChange(sort: SortType) {
727 this.updateUrl({ sort, page: 1 });
730 handleViewChange(i: Profile, event: any) {
732 view: PersonDetailsView[event.target.value],
737 handleModBanShow(i: Profile) {
738 i.setState({ showBanDialog: true });
741 handleModBanReasonChange(i: Profile, event: any) {
742 i.setState({ banReason: event.target.value });
745 handleModBanExpireDaysChange(i: Profile, event: any) {
746 i.setState({ banExpireDays: event.target.value });
749 handleModRemoveDataChange(i: Profile, event: any) {
750 i.setState({ removeData: event.target.checked });
753 handleModBanSubmitCancel(i: Profile) {
754 i.setState({ showBanDialog: false });
757 async handleModBanSubmit(i: Profile, event: any) {
758 event.preventDefault();
759 const { removeData, banReason, banExpireDays } = i.state;
761 const personRes = i.state.personRes;
763 if (personRes.state == "success") {
764 const person = personRes.data.person_view.person;
765 const ban = !person.banned;
767 // If its an unban, restore all their data
769 i.setState({ removeData: false });
772 const res = await HttpService.client.banPerson({
773 person_id: person.id,
775 remove_data: removeData,
777 expires: futureDaysToUnixTime(banExpireDays),
778 auth: myAuthRequired(),
782 i.setState({ showBanDialog: false });
786 async toggleBlockPerson(recipientId: number, block: boolean) {
787 const res = await HttpService.client.blockPerson({
788 person_id: recipientId,
790 auth: myAuthRequired(),
792 if (res.state == "success") {
793 updatePersonBlock(res.data);
797 handleUnblockPerson(personId: number) {
798 this.toggleBlockPerson(personId, false);
801 handleBlockPerson(personId: number) {
802 this.toggleBlockPerson(personId, true);
805 async handleAddModToCommunity(form: AddModToCommunity) {
806 // TODO not sure what to do here
807 await HttpService.client.addModToCommunity(form);
810 async handlePurgePerson(form: PurgePerson) {
811 const purgePersonRes = await HttpService.client.purgePerson(form);
812 this.purgeItem(purgePersonRes);
815 async handlePurgeComment(form: PurgeComment) {
816 const purgeCommentRes = await HttpService.client.purgeComment(form);
817 this.purgeItem(purgeCommentRes);
820 async handlePurgePost(form: PurgePost) {
821 const purgeRes = await HttpService.client.purgePost(form);
822 this.purgeItem(purgeRes);
825 async handleBlockPersonAlt(form: BlockPerson) {
826 const blockPersonRes = await HttpService.client.blockPerson(form);
827 if (blockPersonRes.state === "success") {
828 updatePersonBlock(blockPersonRes.data);
832 async handleCreateComment(form: CreateComment) {
833 const createCommentRes = await HttpService.client.createComment(form);
834 this.createAndUpdateComments(createCommentRes);
836 return createCommentRes;
839 async handleEditComment(form: EditComment) {
840 const editCommentRes = await HttpService.client.editComment(form);
841 this.findAndUpdateComment(editCommentRes);
843 return editCommentRes;
846 async handleDeleteComment(form: DeleteComment) {
847 const deleteCommentRes = await HttpService.client.deleteComment(form);
848 this.findAndUpdateComment(deleteCommentRes);
851 async handleDeletePost(form: DeletePost) {
852 const deleteRes = await HttpService.client.deletePost(form);
853 this.findAndUpdatePost(deleteRes);
856 async handleRemovePost(form: RemovePost) {
857 const removeRes = await HttpService.client.removePost(form);
858 this.findAndUpdatePost(removeRes);
861 async handleRemoveComment(form: RemoveComment) {
862 const removeCommentRes = await HttpService.client.removeComment(form);
863 this.findAndUpdateComment(removeCommentRes);
866 async handleSaveComment(form: SaveComment) {
867 const saveCommentRes = await HttpService.client.saveComment(form);
868 this.findAndUpdateComment(saveCommentRes);
871 async handleSavePost(form: SavePost) {
872 const saveRes = await HttpService.client.savePost(form);
873 this.findAndUpdatePost(saveRes);
876 async handleFeaturePost(form: FeaturePost) {
877 const featureRes = await HttpService.client.featurePost(form);
878 this.findAndUpdatePost(featureRes);
881 async handleCommentVote(form: CreateCommentLike) {
882 const voteRes = await HttpService.client.likeComment(form);
883 this.findAndUpdateComment(voteRes);
886 async handlePostVote(form: CreatePostLike) {
887 const voteRes = await HttpService.client.likePost(form);
888 this.findAndUpdatePost(voteRes);
891 async handlePostEdit(form: EditPost) {
892 const res = await HttpService.client.editPost(form);
893 this.findAndUpdatePost(res);
896 async handleCommentReport(form: CreateCommentReport) {
897 const reportRes = await HttpService.client.createCommentReport(form);
898 if (reportRes.state === "success") {
899 toast(i18n.t("report_created"));
903 async handlePostReport(form: CreatePostReport) {
904 const reportRes = await HttpService.client.createPostReport(form);
905 if (reportRes.state === "success") {
906 toast(i18n.t("report_created"));
910 async handleLockPost(form: LockPost) {
911 const lockRes = await HttpService.client.lockPost(form);
912 this.findAndUpdatePost(lockRes);
915 async handleDistinguishComment(form: DistinguishComment) {
916 const distinguishRes = await HttpService.client.distinguishComment(form);
917 this.findAndUpdateComment(distinguishRes);
920 async handleAddAdmin(form: AddAdmin) {
921 const addAdminRes = await HttpService.client.addAdmin(form);
923 if (addAdminRes.state == "success") {
924 this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
928 async handleTransferCommunity(form: TransferCommunity) {
929 await HttpService.client.transferCommunity(form);
930 toast(i18n.t("transfer_community"));
933 async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
934 const readRes = await HttpService.client.markCommentReplyAsRead(form);
935 this.findAndUpdateCommentReply(readRes);
938 async handlePersonMentionRead(form: MarkPersonMentionAsRead) {
939 // TODO not sure what to do here. Maybe it is actually optional, because post doesn't need it.
940 await HttpService.client.markPersonMentionAsRead(form);
943 async handleBanFromCommunity(form: BanFromCommunity) {
944 const banRes = await HttpService.client.banFromCommunity(form);
945 this.updateBanFromCommunity(banRes);
948 async handleBanPerson(form: BanPerson) {
949 const banRes = await HttpService.client.banPerson(form);
950 this.updateBan(banRes);
953 updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
954 // Maybe not necessary
955 if (banRes.state === "success") {
957 if (s.personRes.state == "success") {
958 s.personRes.data.posts
959 .filter(c => c.creator.id === banRes.data.person_view.person.id)
961 c => (c.creator_banned_from_community = banRes.data.banned)
964 s.personRes.data.comments
965 .filter(c => c.creator.id === banRes.data.person_view.person.id)
967 c => (c.creator_banned_from_community = banRes.data.banned)
975 updateBan(banRes: RequestState<BanPersonResponse>) {
976 // Maybe not necessary
977 if (banRes.state == "success") {
979 if (s.personRes.state == "success") {
980 s.personRes.data.posts
981 .filter(c => c.creator.id == banRes.data.person_view.person.id)
982 .forEach(c => (c.creator.banned = banRes.data.banned));
983 s.personRes.data.comments
984 .filter(c => c.creator.id == banRes.data.person_view.person.id)
985 .forEach(c => (c.creator.banned = banRes.data.banned));
992 purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
993 if (purgeRes.state == "success") {
994 toast(i18n.t("purge_success"));
995 this.context.router.history.push(`/`);
999 findAndUpdateComment(res: RequestState<CommentResponse>) {
1000 this.setState(s => {
1001 if (s.personRes.state == "success" && res.state == "success") {
1002 s.personRes.data.comments = editComment(
1003 res.data.comment_view,
1004 s.personRes.data.comments
1006 s.finished.set(res.data.comment_view.comment.id, true);
1012 createAndUpdateComments(res: RequestState<CommentResponse>) {
1013 this.setState(s => {
1014 if (s.personRes.state == "success" && res.state == "success") {
1015 s.personRes.data.comments.unshift(res.data.comment_view);
1016 // Set finished for the parent
1018 getCommentParentId(res.data.comment_view.comment) ?? 0,
1026 findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
1027 this.setState(s => {
1028 if (s.personRes.state == "success" && res.state == "success") {
1029 s.personRes.data.comments = editWith(
1030 res.data.comment_reply_view,
1031 s.personRes.data.comments
1038 findAndUpdatePost(res: RequestState<PostResponse>) {
1039 this.setState(s => {
1040 if (s.personRes.state == "success" && res.state == "success") {
1041 s.personRes.data.posts = editPost(
1043 s.personRes.data.posts