1 import { getQueryParams, getQueryString } from "@utils/helpers";
2 import { canMod, isAdmin, isBanned } from "@utils/roles";
3 import type { QueryParams } from "@utils/types";
4 import classNames from "classnames";
5 import { NoOptionI18nKeys } from "i18next";
6 import { Component, linkEvent } from "inferno";
7 import { Link } from "inferno-router";
8 import { RouteComponentProps } from "inferno-router/dist/Route";
13 BanFromCommunityResponse,
21 CommunityModeratorView,
34 GetPersonDetailsResponse,
37 MarkCommentReplyAsRead,
38 MarkPersonMentionAsRead,
51 } from "lemmy-js-client";
52 import moment from "moment";
53 import { i18n } from "../../i18next";
54 import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
55 import { UserService } from "../../services";
56 import { FirstLoadService } from "../../services/FirstLoadService";
57 import { HttpService, RequestState } from "../../services/HttpService";
60 capitalizeFirstLetter,
75 restoreScrollPosition,
82 import { BannerIconHeader } from "../common/banner-icon-header";
83 import { HtmlTags } from "../common/html-tags";
84 import { Icon, Spinner } from "../common/icon";
85 import { MomentTime } from "../common/moment-time";
86 import { SortSelect } from "../common/sort-select";
87 import { CommunityLink } from "../community/community-link";
88 import { PersonDetails } from "./person-details";
89 import { PersonListing } from "./person-listing";
91 type ProfileData = RouteDataResponse<{
92 personResponse: GetPersonDetailsResponse;
95 interface ProfileState {
96 personRes: RequestState<GetPersonDetailsResponse>;
97 personBlocked: boolean;
99 banExpireDays?: number;
100 showBanDialog: boolean;
102 siteRes: GetSiteResponse;
103 finished: Map<CommentId, boolean | undefined>;
104 isIsomorphic: boolean;
107 interface ProfileProps {
108 view: PersonDetailsView;
113 function getProfileQueryParams() {
114 return getQueryParams<ProfileProps>({
115 view: getViewFromProps,
116 page: getPageFromString,
117 sort: getSortTypeFromQuery,
121 function getSortTypeFromQuery(sort?: string): SortType {
122 return sort ? (sort as SortType) : "New";
125 function getViewFromProps(view?: string): PersonDetailsView {
127 ? PersonDetailsView[view] ?? PersonDetailsView.Overview
128 : PersonDetailsView.Overview;
131 const getCommunitiesListing = (
132 translationKey: NoOptionI18nKeys,
133 communityViews?: { community: Community }[]
136 communityViews.length > 0 && (
137 <div className="card border-secondary mb-3">
138 <div className="card-body">
139 <h5>{i18n.t(translationKey)}</h5>
140 <ul className="list-unstyled mb-0">
141 {communityViews.map(({ community }) => (
142 <li key={community.id}>
143 <CommunityLink community={community} />
151 const Moderates = ({ moderates }: { moderates?: CommunityModeratorView[] }) =>
152 getCommunitiesListing("moderates", moderates);
154 const Follows = () =>
155 getCommunitiesListing("subscribed", UserService.Instance.myUserInfo?.follows);
157 export class Profile extends Component<
158 RouteComponentProps<{ username: string }>,
161 private isoData = setIsoData<ProfileData>(this.context);
162 state: ProfileState = {
163 personRes: { state: "empty" },
164 personBlocked: false,
165 siteRes: this.isoData.site_res,
166 showBanDialog: false,
172 constructor(props: RouteComponentProps<{ username: string }>, context: any) {
173 super(props, context);
175 this.handleSortChange = this.handleSortChange.bind(this);
176 this.handlePageChange = this.handlePageChange.bind(this);
178 this.handleBlockPerson = this.handleBlockPerson.bind(this);
179 this.handleUnblockPerson = this.handleUnblockPerson.bind(this);
181 this.handleCreateComment = this.handleCreateComment.bind(this);
182 this.handleEditComment = this.handleEditComment.bind(this);
183 this.handleSaveComment = this.handleSaveComment.bind(this);
184 this.handleBlockPersonAlt = this.handleBlockPersonAlt.bind(this);
185 this.handleDeleteComment = this.handleDeleteComment.bind(this);
186 this.handleRemoveComment = this.handleRemoveComment.bind(this);
187 this.handleCommentVote = this.handleCommentVote.bind(this);
188 this.handleAddModToCommunity = this.handleAddModToCommunity.bind(this);
189 this.handleAddAdmin = this.handleAddAdmin.bind(this);
190 this.handlePurgePerson = this.handlePurgePerson.bind(this);
191 this.handlePurgeComment = this.handlePurgeComment.bind(this);
192 this.handleCommentReport = this.handleCommentReport.bind(this);
193 this.handleDistinguishComment = this.handleDistinguishComment.bind(this);
194 this.handleTransferCommunity = this.handleTransferCommunity.bind(this);
195 this.handleCommentReplyRead = this.handleCommentReplyRead.bind(this);
196 this.handlePersonMentionRead = this.handlePersonMentionRead.bind(this);
197 this.handleBanFromCommunity = this.handleBanFromCommunity.bind(this);
198 this.handleBanPerson = this.handleBanPerson.bind(this);
199 this.handlePostVote = this.handlePostVote.bind(this);
200 this.handlePostEdit = this.handlePostEdit.bind(this);
201 this.handlePostReport = this.handlePostReport.bind(this);
202 this.handleLockPost = this.handleLockPost.bind(this);
203 this.handleDeletePost = this.handleDeletePost.bind(this);
204 this.handleRemovePost = this.handleRemovePost.bind(this);
205 this.handleSavePost = this.handleSavePost.bind(this);
206 this.handlePurgePost = this.handlePurgePost.bind(this);
207 this.handleFeaturePost = this.handleFeaturePost.bind(this);
209 // Only fetch the data if coming from another route
210 if (FirstLoadService.isFirstLoad) {
213 personRes: this.isoData.routeData.personResponse,
219 async componentDidMount() {
220 if (!this.state.isIsomorphic) {
221 await this.fetchUserData();
226 componentWillUnmount() {
227 saveScrollPosition(this.context);
230 async fetchUserData() {
231 const { page, sort, view } = getProfileQueryParams();
233 this.setState({ personRes: { state: "empty" } });
235 personRes: await HttpService.client.getPersonDetails({
236 username: this.props.match.params.username,
238 saved_only: view === PersonDetailsView.Saved,
244 restoreScrollPosition(this.context);
245 this.setPersonBlock();
248 get amCurrentUser() {
249 if (this.state.personRes.state === "success") {
251 UserService.Instance.myUserInfo?.local_user_view.person.id ===
252 this.state.personRes.data.person_view.person.id
260 const mui = UserService.Instance.myUserInfo;
261 const res = this.state.personRes;
263 if (mui && res.state === "success") {
265 personBlocked: mui.person_blocks.some(
266 ({ target: { id } }) => id === res.data.person_view.person.id
272 static async fetchInitialData({
275 query: { page, sort, view: urlView },
277 }: InitialFetchRequest<QueryParams<ProfileProps>>): Promise<ProfileData> {
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),
293 personResponse: await client.getPersonDetails(form),
297 get documentTitle(): string {
298 const siteName = this.state.siteRes.site_view.site.name;
299 const res = this.state.personRes;
300 return res.state == "success"
301 ? `@${res.data.person_view.person.name} - ${siteName}`
306 switch (this.state.personRes.state) {
314 const siteRes = this.state.siteRes;
315 const personRes = this.state.personRes.data;
316 const { page, sort, view } = getProfileQueryParams();
319 <div className="row">
320 <div className="col-12 col-md-8">
322 title={this.documentTitle}
323 path={this.context.router.route.match.url}
324 description={personRes.person_view.person.bio}
325 image={personRes.person_view.person.avatar}
328 {this.userInfo(personRes.person_view)}
335 personRes={personRes}
336 admins={siteRes.admins}
340 finished={this.state.finished}
341 enableDownvotes={enableDownvotes(siteRes)}
342 enableNsfw={enableNsfw(siteRes)}
344 onPageChange={this.handlePageChange}
345 allLanguages={siteRes.all_languages}
346 siteLanguages={siteRes.discussion_languages}
347 // TODO all the forms here
348 onSaveComment={this.handleSaveComment}
349 onBlockPerson={this.handleBlockPersonAlt}
350 onDeleteComment={this.handleDeleteComment}
351 onRemoveComment={this.handleRemoveComment}
352 onCommentVote={this.handleCommentVote}
353 onCommentReport={this.handleCommentReport}
354 onDistinguishComment={this.handleDistinguishComment}
355 onAddModToCommunity={this.handleAddModToCommunity}
356 onAddAdmin={this.handleAddAdmin}
357 onTransferCommunity={this.handleTransferCommunity}
358 onPurgeComment={this.handlePurgeComment}
359 onPurgePerson={this.handlePurgePerson}
360 onCommentReplyRead={this.handleCommentReplyRead}
361 onPersonMentionRead={this.handlePersonMentionRead}
362 onBanPersonFromCommunity={this.handleBanFromCommunity}
363 onBanPerson={this.handleBanPerson}
364 onCreateComment={this.handleCreateComment}
365 onEditComment={this.handleEditComment}
366 onPostEdit={this.handlePostEdit}
367 onPostVote={this.handlePostVote}
368 onPostReport={this.handlePostReport}
369 onLockPost={this.handleLockPost}
370 onDeletePost={this.handleDeletePost}
371 onRemovePost={this.handleRemovePost}
372 onSavePost={this.handleSavePost}
373 onPurgePost={this.handlePurgePost}
374 onFeaturePost={this.handleFeaturePost}
378 <div className="col-12 col-md-4">
379 <Moderates moderates={personRes.moderates} />
380 {this.amCurrentUser && <Follows />}
389 return <div className="container-lg">{this.renderPersonRes()}</div>;
394 <div className="btn-group btn-group-toggle flex-wrap mb-2">
395 {this.getRadio(PersonDetailsView.Overview)}
396 {this.getRadio(PersonDetailsView.Comments)}
397 {this.getRadio(PersonDetailsView.Posts)}
398 {this.amCurrentUser && this.getRadio(PersonDetailsView.Saved)}
403 getRadio(view: PersonDetailsView) {
404 const { view: urlView } = getProfileQueryParams();
405 const active = view === urlView;
409 className={classNames("btn btn-outline-secondary pointer", {
417 onChange={linkEvent(this, this.handleViewChange)}
419 {i18n.t(view.toLowerCase() as NoOptionI18nKeys)}
425 const { sort } = getProfileQueryParams();
426 const { username } = this.props.match.params;
428 const profileRss = `/feeds/u/${username}.xml?sort=${sort}`;
431 <div className="mb-2">
432 <span className="mr-3">{this.viewRadios}</span>
435 onChange={this.handleSortChange}
439 <a href={profileRss} rel={relTags} title="RSS">
440 <Icon icon="rss" classes="text-muted small mx-2" />
442 <link rel="alternate" type="application/atom+xml" href={profileRss} />
447 userInfo(pv: PersonView) {
457 {!isBanned(pv.person) && (
459 banner={pv.person.banner}
460 icon={pv.person.avatar}
463 <div className="mb-3">
465 <div className="mb-0 d-flex flex-wrap">
467 {pv.person.display_name && (
468 <h5 className="mb-0">{pv.person.display_name}</h5>
470 <ul className="list-inline mb-2">
471 <li className="list-inline-item">
480 {isBanned(pv.person) && (
481 <li className="list-inline-item badge badge-danger">
485 {pv.person.deleted && (
486 <li className="list-inline-item badge badge-danger">
490 {pv.person.admin && (
491 <li className="list-inline-item badge badge-light">
495 {pv.person.bot_account && (
496 <li className="list-inline-item badge badge-light">
497 {i18n.t("bot_account").toLowerCase()}
503 <div className="flex-grow-1 unselectable pointer mx-2"></div>
504 {!this.amCurrentUser && UserService.Instance.myUserInfo && (
507 className={`d-flex align-self-start btn btn-secondary mr-2 ${
508 !pv.person.matrix_user_id && "invisible"
511 href={`https://matrix.to/#/${pv.person.matrix_user_id}`}
513 {i18n.t("send_secure_message")}
517 "d-flex align-self-start btn btn-secondary mr-2"
519 to={`/create_private_message/${pv.person.id}`}
521 {i18n.t("send_message")}
526 "d-flex align-self-start btn btn-secondary mr-2"
530 this.handleUnblockPerson
533 {i18n.t("unblock_user")}
538 "d-flex align-self-start btn btn-secondary mr-2"
542 this.handleBlockPerson
545 {i18n.t("block_user")}
551 {canMod(pv.person.id, undefined, admins) &&
552 !isAdmin(pv.person.id, admins) &&
554 (!isBanned(pv.person) ? (
557 "d-flex align-self-start btn btn-secondary mr-2"
559 onClick={linkEvent(this, this.handleModBanShow)}
560 aria-label={i18n.t("ban")}
562 {capitalizeFirstLetter(i18n.t("ban"))}
567 "d-flex align-self-start btn btn-secondary mr-2"
569 onClick={linkEvent(this, this.handleModBanSubmit)}
570 aria-label={i18n.t("unban")}
572 {capitalizeFirstLetter(i18n.t("unban"))}
577 <div className="d-flex align-items-center mb-2">
580 dangerouslySetInnerHTML={mdToHtml(pv.person.bio)}
585 <ul className="list-inline mb-2">
586 <li className="list-inline-item badge badge-light">
587 {i18n.t("number_of_posts", {
588 count: Number(pv.counts.post_count),
589 formattedCount: numToSI(pv.counts.post_count),
592 <li className="list-inline-item badge badge-light">
593 {i18n.t("number_of_comments", {
594 count: Number(pv.counts.comment_count),
595 formattedCount: numToSI(pv.counts.comment_count),
600 <div className="text-muted">
601 {i18n.t("joined")}{" "}
603 published={pv.person.published}
608 <div className="d-flex align-items-center text-muted mb-2">
610 <span className="ml-2">
611 {i18n.t("cake_day_title")}{" "}
613 .utc(pv.person.published)
615 .format("MMM DD, YYYY")}
618 {!UserService.Instance.myUserInfo && (
619 <div className="alert alert-info" role="alert">
620 {i18n.t("profile_not_logged_in_alert")}
630 banDialog(pv: PersonView) {
631 const { showBanDialog } = this.state;
635 <form onSubmit={linkEvent(this, this.handleModBanSubmit)}>
636 <div className="form-group row col-12">
637 <label className="col-form-label" htmlFor="profile-ban-reason">
642 id="profile-ban-reason"
643 className="form-control mr-2"
644 placeholder={i18n.t("reason")}
645 value={this.state.banReason}
646 onInput={linkEvent(this, this.handleModBanReasonChange)}
648 <label className="col-form-label" htmlFor={`mod-ban-expires`}>
653 id={`mod-ban-expires`}
654 className="form-control mr-2"
655 placeholder={i18n.t("number_of_days")}
656 value={this.state.banExpireDays}
657 onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
659 <div className="form-group">
660 <div className="form-check">
662 className="form-check-input"
663 id="mod-ban-remove-data"
665 checked={this.state.removeData}
666 onChange={linkEvent(this, this.handleModRemoveDataChange)}
669 className="form-check-label"
670 htmlFor="mod-ban-remove-data"
671 title={i18n.t("remove_content_more")}
673 {i18n.t("remove_content")}
678 {/* TODO hold off on expires until later */}
679 {/* <div class="form-group row"> */}
680 {/* <label class="col-form-label">Expires</label> */}
681 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
683 <div className="form-group row">
686 className="btn btn-secondary mr-2"
687 aria-label={i18n.t("cancel")}
688 onClick={linkEvent(this, this.handleModBanSubmitCancel)}
694 className="btn btn-secondary"
695 aria-label={i18n.t("ban")}
697 {i18n.t("ban")} {pv.person.name}
705 async updateUrl({ page, sort, view }: Partial<ProfileProps>) {
710 } = getProfileQueryParams();
712 const queryParams: QueryParams<ProfileProps> = {
713 page: (page ?? urlPage).toString(),
714 sort: sort ?? urlSort,
715 view: view ?? urlView,
718 const { username } = this.props.match.params;
720 this.props.history.push(`/u/${username}${getQueryString(queryParams)}`);
721 await this.fetchUserData();
724 handlePageChange(page: number) {
725 this.updateUrl({ page });
728 handleSortChange(sort: SortType) {
729 this.updateUrl({ sort, page: 1 });
732 handleViewChange(i: Profile, event: any) {
734 view: PersonDetailsView[event.target.value],
739 handleModBanShow(i: Profile) {
740 i.setState({ showBanDialog: true });
743 handleModBanReasonChange(i: Profile, event: any) {
744 i.setState({ banReason: event.target.value });
747 handleModBanExpireDaysChange(i: Profile, event: any) {
748 i.setState({ banExpireDays: event.target.value });
751 handleModRemoveDataChange(i: Profile, event: any) {
752 i.setState({ removeData: event.target.checked });
755 handleModBanSubmitCancel(i: Profile) {
756 i.setState({ showBanDialog: false });
759 async handleModBanSubmit(i: Profile, event: any) {
760 event.preventDefault();
761 const { removeData, banReason, banExpireDays } = i.state;
763 const personRes = i.state.personRes;
765 if (personRes.state == "success") {
766 const person = personRes.data.person_view.person;
767 const ban = !person.banned;
769 // If its an unban, restore all their data
771 i.setState({ removeData: false });
774 const res = await HttpService.client.banPerson({
775 person_id: person.id,
777 remove_data: removeData,
779 expires: futureDaysToUnixTime(banExpireDays),
780 auth: myAuthRequired(),
784 i.setState({ showBanDialog: false });
788 async toggleBlockPerson(recipientId: number, block: boolean) {
789 const res = await HttpService.client.blockPerson({
790 person_id: recipientId,
792 auth: myAuthRequired(),
794 if (res.state == "success") {
795 updatePersonBlock(res.data);
799 handleUnblockPerson(personId: number) {
800 this.toggleBlockPerson(personId, false);
803 handleBlockPerson(personId: number) {
804 this.toggleBlockPerson(personId, true);
807 async handleAddModToCommunity(form: AddModToCommunity) {
808 // TODO not sure what to do here
809 await HttpService.client.addModToCommunity(form);
812 async handlePurgePerson(form: PurgePerson) {
813 const purgePersonRes = await HttpService.client.purgePerson(form);
814 this.purgeItem(purgePersonRes);
817 async handlePurgeComment(form: PurgeComment) {
818 const purgeCommentRes = await HttpService.client.purgeComment(form);
819 this.purgeItem(purgeCommentRes);
822 async handlePurgePost(form: PurgePost) {
823 const purgeRes = await HttpService.client.purgePost(form);
824 this.purgeItem(purgeRes);
827 async handleBlockPersonAlt(form: BlockPerson) {
828 const blockPersonRes = await HttpService.client.blockPerson(form);
829 if (blockPersonRes.state === "success") {
830 updatePersonBlock(blockPersonRes.data);
834 async handleCreateComment(form: CreateComment) {
835 const createCommentRes = await HttpService.client.createComment(form);
836 this.createAndUpdateComments(createCommentRes);
838 return createCommentRes;
841 async handleEditComment(form: EditComment) {
842 const editCommentRes = await HttpService.client.editComment(form);
843 this.findAndUpdateComment(editCommentRes);
845 return editCommentRes;
848 async handleDeleteComment(form: DeleteComment) {
849 const deleteCommentRes = await HttpService.client.deleteComment(form);
850 this.findAndUpdateComment(deleteCommentRes);
853 async handleDeletePost(form: DeletePost) {
854 const deleteRes = await HttpService.client.deletePost(form);
855 this.findAndUpdatePost(deleteRes);
858 async handleRemovePost(form: RemovePost) {
859 const removeRes = await HttpService.client.removePost(form);
860 this.findAndUpdatePost(removeRes);
863 async handleRemoveComment(form: RemoveComment) {
864 const removeCommentRes = await HttpService.client.removeComment(form);
865 this.findAndUpdateComment(removeCommentRes);
868 async handleSaveComment(form: SaveComment) {
869 const saveCommentRes = await HttpService.client.saveComment(form);
870 this.findAndUpdateComment(saveCommentRes);
873 async handleSavePost(form: SavePost) {
874 const saveRes = await HttpService.client.savePost(form);
875 this.findAndUpdatePost(saveRes);
878 async handleFeaturePost(form: FeaturePost) {
879 const featureRes = await HttpService.client.featurePost(form);
880 this.findAndUpdatePost(featureRes);
883 async handleCommentVote(form: CreateCommentLike) {
884 const voteRes = await HttpService.client.likeComment(form);
885 this.findAndUpdateComment(voteRes);
888 async handlePostVote(form: CreatePostLike) {
889 const voteRes = await HttpService.client.likePost(form);
890 this.findAndUpdatePost(voteRes);
893 async handlePostEdit(form: EditPost) {
894 const res = await HttpService.client.editPost(form);
895 this.findAndUpdatePost(res);
898 async handleCommentReport(form: CreateCommentReport) {
899 const reportRes = await HttpService.client.createCommentReport(form);
900 if (reportRes.state === "success") {
901 toast(i18n.t("report_created"));
905 async handlePostReport(form: CreatePostReport) {
906 const reportRes = await HttpService.client.createPostReport(form);
907 if (reportRes.state === "success") {
908 toast(i18n.t("report_created"));
912 async handleLockPost(form: LockPost) {
913 const lockRes = await HttpService.client.lockPost(form);
914 this.findAndUpdatePost(lockRes);
917 async handleDistinguishComment(form: DistinguishComment) {
918 const distinguishRes = await HttpService.client.distinguishComment(form);
919 this.findAndUpdateComment(distinguishRes);
922 async handleAddAdmin(form: AddAdmin) {
923 const addAdminRes = await HttpService.client.addAdmin(form);
925 if (addAdminRes.state == "success") {
926 this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
930 async handleTransferCommunity(form: TransferCommunity) {
931 await HttpService.client.transferCommunity(form);
932 toast(i18n.t("transfer_community"));
935 async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
936 const readRes = await HttpService.client.markCommentReplyAsRead(form);
937 this.findAndUpdateCommentReply(readRes);
940 async handlePersonMentionRead(form: MarkPersonMentionAsRead) {
941 // TODO not sure what to do here. Maybe it is actually optional, because post doesn't need it.
942 await HttpService.client.markPersonMentionAsRead(form);
945 async handleBanFromCommunity(form: BanFromCommunity) {
946 const banRes = await HttpService.client.banFromCommunity(form);
947 this.updateBanFromCommunity(banRes);
950 async handleBanPerson(form: BanPerson) {
951 const banRes = await HttpService.client.banPerson(form);
952 this.updateBan(banRes);
955 updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
956 // Maybe not necessary
957 if (banRes.state === "success") {
959 if (s.personRes.state == "success") {
960 s.personRes.data.posts
961 .filter(c => c.creator.id === banRes.data.person_view.person.id)
963 c => (c.creator_banned_from_community = banRes.data.banned)
966 s.personRes.data.comments
967 .filter(c => c.creator.id === banRes.data.person_view.person.id)
969 c => (c.creator_banned_from_community = banRes.data.banned)
977 updateBan(banRes: RequestState<BanPersonResponse>) {
978 // Maybe not necessary
979 if (banRes.state == "success") {
981 if (s.personRes.state == "success") {
982 s.personRes.data.posts
983 .filter(c => c.creator.id == banRes.data.person_view.person.id)
984 .forEach(c => (c.creator.banned = banRes.data.banned));
985 s.personRes.data.comments
986 .filter(c => c.creator.id == banRes.data.person_view.person.id)
987 .forEach(c => (c.creator.banned = banRes.data.banned));
994 purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
995 if (purgeRes.state == "success") {
996 toast(i18n.t("purge_success"));
997 this.context.router.history.push(`/`);
1001 findAndUpdateComment(res: RequestState<CommentResponse>) {
1002 this.setState(s => {
1003 if (s.personRes.state == "success" && res.state == "success") {
1004 s.personRes.data.comments = editComment(
1005 res.data.comment_view,
1006 s.personRes.data.comments
1008 s.finished.set(res.data.comment_view.comment.id, true);
1014 createAndUpdateComments(res: RequestState<CommentResponse>) {
1015 this.setState(s => {
1016 if (s.personRes.state == "success" && res.state == "success") {
1017 s.personRes.data.comments.unshift(res.data.comment_view);
1018 // Set finished for the parent
1020 getCommentParentId(res.data.comment_view.comment) ?? 0,
1028 findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
1029 this.setState(s => {
1030 if (s.personRes.state == "success" && res.state == "success") {
1031 s.personRes.data.comments = editWith(
1032 res.data.comment_reply_view,
1033 s.personRes.data.comments
1040 findAndUpdatePost(res: RequestState<PostResponse>) {
1041 this.setState(s => {
1042 if (s.personRes.state == "success" && res.state == "success") {
1043 s.personRes.data.posts = editPost(
1045 s.personRes.data.posts