From 8594a30a535ab11edef24ee0716ebe4a1b3803d6 Mon Sep 17 00:00:00 2001 From: SleeplessOne1917 Date: Wed, 6 Dec 2023 23:17:02 +0000 Subject: [PATCH] Moderation/content action overhaul (#2258) * Fix remove post dialog * Consolidate mod action logic * Make mod action form less janky * Move content action dropdown to its own component * Make reusable component for content action buttons * Finish up mod dropdown * Introduce new content dropdown component to post listing * Fix cancel moderation button bug * Add icons, tweak UI * Handle delete/undelete icons * The thing * Fix some of the banning related bugs * Fix mod form ban bugs * Fix some more bugs * Make comments use dropdown menu * Use mod action form with comments * Make confirmation modal * Make all the mod action dialogs modals * Tweak modal * Fix bug with mod form submit * Tweak modal more * More modal tweaking and some feedback toasts * Use icon pairs for on/off * Make modals auto focus input * Implement PR suggestions * Make UI use async functions where needed * Make loading state for context action modals * Hide context actions that users should not be able to do * Add loading state to confirmation modals * Use updated translations * PR feedback * Add forgotten trnslations * Fix scrolling bug --------- Co-authored-by: SleeplessOne --- lemmy-translations | 2 +- src/assets/css/main.css | 1 - src/assets/symbols.svg | 288 +++-- .../components/comment/comment-node.tsx | 1148 +++-------------- .../components/comment/comment-nodes.tsx | 38 +- .../components/comment/comment-report.tsx | 30 +- .../components/common/confirmation-modal.tsx | 140 ++ .../common/content-actions/action-button.tsx | 79 ++ .../comment-action-dropdown.tsx | 8 + .../content-action-dropdown.tsx | 766 +++++++++++ .../content-actions/cross-post-button.tsx | 22 + .../content-actions/post-action-dropdown.tsx | 8 + .../common/mod-action-form-modal.tsx | 464 +++++++ src/shared/components/common/report-form.tsx | 69 - .../components/common/subscribe-button.tsx | 2 +- src/shared/components/common/totp-modal.tsx | 2 +- src/shared/components/community/community.tsx | 4 +- src/shared/components/home/home.tsx | 4 +- .../components/person/person-details.tsx | 53 +- src/shared/components/person/profile.tsx | 2 + src/shared/components/post/post-form.tsx | 68 +- src/shared/components/post/post-listing.tsx | 1148 +++-------------- src/shared/components/post/post-listings.tsx | 36 +- src/shared/components/post/post-report.tsx | 35 +- src/shared/components/post/post.tsx | 83 +- .../private_message/private-message.tsx | 26 +- src/shared/components/search.tsx | 130 +- src/shared/utils/app/edit-with.ts | 23 +- src/shared/utils/helpers/apub-name.ts | 11 + src/shared/utils/helpers/index.ts | 2 + src/shared/utils/roles/am-mod.ts | 2 +- src/shared/utils/types/cross-post-params.ts | 5 + src/shared/utils/types/index.ts | 2 + src/shared/utils/types/with-comment.ts | 4 + 34 files changed, 2381 insertions(+), 2324 deletions(-) create mode 100644 src/shared/components/common/confirmation-modal.tsx create mode 100644 src/shared/components/common/content-actions/action-button.tsx create mode 100644 src/shared/components/common/content-actions/comment-action-dropdown.tsx create mode 100644 src/shared/components/common/content-actions/content-action-dropdown.tsx create mode 100644 src/shared/components/common/content-actions/cross-post-button.tsx create mode 100644 src/shared/components/common/content-actions/post-action-dropdown.tsx create mode 100644 src/shared/components/common/mod-action-form-modal.tsx delete mode 100644 src/shared/components/common/report-form.tsx create mode 100644 src/shared/utils/helpers/apub-name.ts create mode 100644 src/shared/utils/types/cross-post-params.ts diff --git a/lemmy-translations b/lemmy-translations index 45d478e4..b3343aef 160000 --- a/lemmy-translations +++ b/lemmy-translations @@ -1 +1 @@ -Subproject commit 45d478e44fb12f0748640cf0416724e42e37d9a6 +Subproject commit b3343aef72e5a7e5df34cf328b910ed798027270 diff --git a/src/assets/css/main.css b/src/assets/css/main.css index bad10ec5..656b0548 100644 --- a/src/assets/css/main.css +++ b/src/assets/css/main.css @@ -90,7 +90,6 @@ } .icon { - display: inline-grid; display: inline-flex; width: 1em; height: 1em; diff --git a/src/assets/symbols.svg b/src/assets/symbols.svg index dba5dbe6..5019ed1e 100644 --- a/src/assets/symbols.svg +++ b/src/assets/symbols.svg @@ -1,255 +1,220 @@ -
  • @@ -288,7 +218,7 @@ export class CommentNode extends Component { /> - + {cv.comment.distinguished && ( @@ -297,29 +227,29 @@ export class CommentNode extends Component { {this.props.showCommunity && ( <> {I18NextService.i18n.t("to")} - + - {cv.post.name} + {post.name} )} {this.getLinkButton(true)} - {cv.comment.language_id !== 0 && ( + {language_id !== 0 && ( { this.props.allLanguages.find( - lang => lang.id === cv.comment.language_id, + lang => lang.id === language_id, )?.name } @@ -332,20 +262,17 @@ export class CommentNode extends Component { - {numToSI(this.commentView.counts.score)} + {numToSI(counts.score)} )} - +
    {/* end of user row */} @@ -355,9 +282,7 @@ export class CommentNode extends Component { edit onReplyCancel={this.handleReplyCancel} disabled={this.props.locked} - finished={this.props.finished.get( - this.props.node.comment_view.comment.id, - )} + finished={this.props.finished.get(id)} focus allLanguages={this.props.allLanguages} siteLanguages={this.props.siteLanguages} @@ -414,491 +339,35 @@ export class CommentNode extends Component { <> + - - {!this.state.showAdvanced ? ( - - ) : ( - <> - {!this.myComment && ( - <> - - - - - - - )} - - - {this.myComment && ( - <> - - - - {(canModOnSelf || canAdminOnSelf) && ( - - )} - - )} - {/* Admins and mods can remove comments */} - {(canMod_ || canAdmin_) && ( - <> - {!cv.comment.removed ? ( - - ) : ( - - )} - - )} - {/* Mods can ban from community, and appoint as mods to community */} - {canMod_ && ( - <> - {!isMod_ && - (!cv.creator_banned_from_community ? ( - - ) : ( - - ))} - {!cv.creator_banned_from_community && - (!this.state.showConfirmAppointAsMod ? ( - - ) : ( - <> - - - - - ))} - - )} - {/* Community creators and admins can transfer community to another mod */} - {(amCommunityCreator_ || canAdmin_) && - isMod_ && - cv.creator.local && - (!this.state.showConfirmTransferCommunity ? ( - - ) : ( - <> - - - - - ))} - {/* Admins can ban from all, and appoint other admins */} - {canAdmin_ && ( - <> - {!isAdmin_ && ( - <> - - - - {!isBanned(cv.creator) ? ( - - ) : ( - - )} - - )} - {!isBanned(cv.creator) && - cv.creator.local && - (!this.state.showConfirmAppointAsAdmin ? ( - - ) : ( - <> - - - - - ))} - - )} - - )} )} - {/* end of button group */} )} @@ -919,10 +388,8 @@ export class CommentNode extends Component { ) : ( <> {I18NextService.i18n.t("x_more_replies", { - count: node.comment_view.counts.child_count, - formattedCount: numToSI( - node.comment_view.counts.child_count, - ), + count: counts.child_count, + formattedCount: numToSI(counts.child_count), })}{" "} ➔ @@ -930,148 +397,12 @@ export class CommentNode extends Component { )} - {/* end of details */} - {this.state.showRemoveDialog && ( -
    - - - -
    - )} - {this.state.showReportDialog && ( - - )} - {this.state.showBanDialog && ( -
    -
    - - - - -
    -
    - - -
    -
    -
    - {/* TODO hold off on expires until later */} - {/*
    */} - {/* */} - {/* */} - {/*
    */} -
    - -
    -
    - )} - - {this.state.showPurgeDialog && ( -
    - - - -
    - {this.state.purgeLoading ? ( - - ) : ( - - )} -
    - - )} {this.state.showReply && ( { : comment.content; } - handleReplyClick(i: CommentNode) { - i.setState({ showReply: true }); + handleReplyClick() { + this.setState({ showReply: true }); } - handleEditClick(i: CommentNode) { - i.setState({ showEdit: true }); + handleEditClick() { + this.setState({ showEdit: true }); } handleReplyCancel() { this.setState({ showReply: false, showEdit: false }); } - handleShowReportDialog(i: CommentNode) { - i.setState({ showReportDialog: !i.state.showReportDialog }); - } - - handleModRemoveShow(i: CommentNode) { - i.setState({ - showRemoveDialog: !i.state.showRemoveDialog, - showBanDialog: false, - }); - } - - handleModRemoveReasonChange(i: CommentNode, event: any) { - i.setState({ removeReason: event.target.value }); - } - - handleModRemoveDataChange(i: CommentNode, event: any) { - i.setState({ removeData: event.target.checked }); - } - isPersonMentionType( item: CommentView | PersonMentionView | CommentReplyView, ): item is PersonMentionView { @@ -1262,82 +574,6 @@ export class CommentNode extends Component { return (item as CommentReplyView).comment_reply?.id !== undefined; } - handleModBanFromCommunityShow(i: CommentNode) { - i.setState({ - showBanDialog: true, - banType: BanType.Community, - showRemoveDialog: false, - }); - } - - handleModBanShow(i: CommentNode) { - i.setState({ - showBanDialog: true, - banType: BanType.Site, - showRemoveDialog: false, - }); - } - - handleModBanReasonChange(i: CommentNode, event: any) { - i.setState({ banReason: event.target.value }); - } - - handleModBanExpireDaysChange(i: CommentNode, event: any) { - i.setState({ banExpireDays: event.target.value }); - } - - handlePurgePersonShow(i: CommentNode) { - i.setState({ - showPurgeDialog: true, - purgeType: PurgeType.Person, - showRemoveDialog: false, - }); - } - - handlePurgeCommentShow(i: CommentNode) { - i.setState({ - showPurgeDialog: true, - purgeType: PurgeType.Comment, - showRemoveDialog: false, - }); - } - - handlePurgeReasonChange(i: CommentNode, event: any) { - i.setState({ purgeReason: event.target.value }); - } - - handleShowConfirmAppointAsMod(i: CommentNode) { - i.setState({ showConfirmAppointAsMod: true }); - } - - handleCancelConfirmAppointAsMod(i: CommentNode) { - i.setState({ showConfirmAppointAsMod: false }); - } - - handleShowConfirmAppointAsAdmin(i: CommentNode) { - i.setState({ showConfirmAppointAsAdmin: true }); - } - - handleCancelConfirmAppointAsAdmin(i: CommentNode) { - i.setState({ showConfirmAppointAsAdmin: false }); - } - - handleShowConfirmTransferCommunity(i: CommentNode) { - i.setState({ showConfirmTransferCommunity: true }); - } - - handleCancelShowConfirmTransferCommunity(i: CommentNode) { - i.setState({ showConfirmTransferCommunity: false }); - } - - handleShowConfirmTransferSite(i: CommentNode) { - i.setState({ showConfirmTransferSite: true }); - } - - handleCancelShowConfirmTransferSite(i: CommentNode) { - i.setState({ showConfirmTransferSite: false }); - } - get isCommentNew(): boolean { const now = subMinutes(new Date(), 10); const then = parseISO(this.commentView.comment.published); @@ -1349,28 +585,21 @@ export class CommentNode extends Component { setupTippy(); } - handleViewSource(i: CommentNode) { - i.setState({ viewSource: !i.state.viewSource }); - } - handleShowAdvanced(i: CommentNode) { i.setState({ showAdvanced: !i.state.showAdvanced }); setupTippy(); } - handleSaveComment(i: CommentNode) { - i.setState({ saveLoading: true }); - - i.props.onSaveComment({ - comment_id: i.commentView.comment.id, - save: !i.commentView.saved, + async handleSaveComment() { + this.props.onSaveComment({ + comment_id: this.commentView.comment.id, + save: !this.commentView.saved, }); } - handleBlockPerson(i: CommentNode) { - i.setState({ blockPersonLoading: true }); - i.props.onBlockPerson({ - person_id: i.commentView.creator.id, + async handleBlockPerson() { + this.props.onBlockPerson({ + person_id: this.commentView.creator.id, block: true, }); } @@ -1391,119 +620,124 @@ export class CommentNode extends Component { } } - handleDeleteComment(i: CommentNode) { - i.setState({ deleteLoading: true }); - i.props.onDeleteComment({ - comment_id: i.commentId, - deleted: !i.commentView.comment.deleted, + async handleDeleteComment() { + this.props.onDeleteComment({ + comment_id: this.commentId, + deleted: !this.commentView.comment.deleted, }); } - handleRemoveComment(i: CommentNode, event: any) { - event.preventDefault(); - i.setState({ removeLoading: true }); - i.props.onRemoveComment({ - comment_id: i.commentId, - removed: !i.commentView.comment.removed, - reason: i.state.removeReason, + async handleRemoveComment(reason: string) { + this.props.onRemoveComment({ + comment_id: this.commentId, + removed: !this.commentView.comment.removed, + reason, }); } - handleDistinguishComment(i: CommentNode) { - i.setState({ distinguishLoading: true }); - i.props.onDistinguishComment({ - comment_id: i.commentId, - distinguished: !i.commentView.comment.distinguished, + async handleDistinguishComment() { + this.props.onDistinguishComment({ + comment_id: this.commentId, + distinguished: !this.commentView.comment.distinguished, }); } - handleBanPersonFromCommunity(i: CommentNode) { - i.setState({ banLoading: true }); - i.props.onBanPersonFromCommunity({ - community_id: i.commentView.community.id, - person_id: i.commentView.creator.id, - ban: !i.commentView.creator_banned_from_community, - reason: i.state.banReason, - remove_data: i.state.removeData, - expires: futureDaysToUnixTime(i.state.banExpireDays), - }); - } + async handleBanFromCommunity({ + daysUntilExpires, + reason, + shouldRemove, + }: BanUpdateForm) { + const { + creator: { id: person_id }, + creator_banned_from_community, + community: { id: community_id }, + } = this.commentView; - handleBanPerson(i: CommentNode) { - i.setState({ banLoading: true }); - i.props.onBanPerson({ - person_id: i.commentView.creator.id, - ban: !i.commentView.creator_banned_from_community, - reason: i.state.banReason, - remove_data: i.state.removeData, - expires: futureDaysToUnixTime(i.state.banExpireDays), - }); - } + const ban = !creator_banned_from_community; - handleModBanBothSubmit(i: CommentNode, event: any) { - event.preventDefault(); - if (i.state.banType === BanType.Community) { - i.handleBanPersonFromCommunity(i); - } else { - i.handleBanPerson(i); + // If its an unban, restore all their data + if (ban === false) { + shouldRemove = false; } - } + const expires = futureDaysToUnixTime(daysUntilExpires); - handleAddModToCommunity(i: CommentNode) { - i.setState({ addModLoading: true }); - - const added = !i.commentView.creator_is_moderator; - i.props.onAddModToCommunity({ - community_id: i.commentView.community.id, - person_id: i.commentView.creator.id, - added, + this.props.onBanPersonFromCommunity({ + community_id, + person_id, + ban, + remove_data: shouldRemove, + reason, + expires, }); } - handleAddAdmin(i: CommentNode) { - i.setState({ addAdminLoading: true }); + async handleBanFromSite({ + daysUntilExpires, + reason, + shouldRemove, + }: BanUpdateForm) { + const { + creator: { id: person_id, banned }, + } = this.commentView; - const added = !i.commentView.creator_is_admin; - i.props.onAddAdmin({ - person_id: i.commentView.creator.id, - added, + const ban = !banned; + + // If its an unban, restore all their data + if (ban === false) { + shouldRemove = false; + } + const expires = futureDaysToUnixTime(daysUntilExpires); + + this.props.onBanPerson({ + person_id, + ban, + remove_data: shouldRemove, + reason, + expires, }); } - handleTransferCommunity(i: CommentNode) { - i.setState({ transferCommunityLoading: true }); - i.props.onTransferCommunity({ - community_id: i.commentView.community.id, - person_id: i.commentView.creator.id, - }); - } - - handleReportComment(reason: string) { + async handleReportComment(reason: string) { this.props.onCommentReport({ comment_id: this.commentId, reason, }); + } - this.setState({ - showReportDialog: false, + async handleAppointCommunityMod() { + this.props.onAddModToCommunity({ + community_id: this.commentView.community.id, + person_id: this.commentView.creator.id, + added: !this.commentView.creator_is_moderator, }); } - handlePurgeBothSubmit(i: CommentNode, event: any) { - event.preventDefault(); - i.setState({ purgeLoading: true }); + async handleAppointAdmin() { + this.props.onAddAdmin({ + person_id: this.commentView.creator.id, + added: !this.commentView.creator_is_admin, + }); + } - if (i.state.purgeType === PurgeType.Person) { - i.props.onPurgePerson({ - person_id: i.commentView.creator.id, - reason: i.state.purgeReason, - }); - } else { - i.props.onPurgeComment({ - comment_id: i.commentId, - reason: i.state.purgeReason, - }); - } + async handlePurgePerson(reason: string) { + this.props.onPurgePerson({ + person_id: this.commentView.creator.id, + reason, + }); + } + + async handlePurgeComment(reason: string) { + this.props.onPurgeComment({ + comment_id: this.commentId, + reason, + }); + } + + async handleTransferCommunity() { + this.props.onTransferCommunity({ + community_id: this.commentView.community.id, + person_id: this.commentView.creator.id, + }); } handleFetchChildren(i: CommentNode) { diff --git a/src/shared/components/comment/comment-nodes.tsx b/src/shared/components/comment/comment-nodes.tsx index 2e60309e..394a196c 100644 --- a/src/shared/components/comment/comment-nodes.tsx +++ b/src/shared/components/comment/comment-nodes.tsx @@ -8,6 +8,7 @@ import { BanPerson, BlockPerson, CommentId, + CommentResponse, CommunityModeratorView, CreateComment, CreateCommentLike, @@ -28,6 +29,7 @@ import { } from "lemmy-js-client"; import { CommentNodeI, CommentViewType } from "../../interfaces"; import { CommentNode } from "./comment-node"; +import { RequestState } from "../../services/HttpService"; interface CommentNodesProps { nodes: CommentNodeI[]; @@ -49,25 +51,29 @@ interface CommentNodesProps { isChild?: boolean; depth?: number; finished: Map; - onSaveComment(form: SaveComment): void; + onSaveComment(form: SaveComment): Promise; onCommentReplyRead(form: MarkCommentReplyAsRead): void; onPersonMentionRead(form: MarkPersonMentionAsRead): void; - onCreateComment(form: EditComment | CreateComment): void; - onEditComment(form: EditComment | CreateComment): void; - onCommentVote(form: CreateCommentLike): void; - onBlockPerson(form: BlockPerson): void; - onDeleteComment(form: DeleteComment): void; - onRemoveComment(form: RemoveComment): void; - onDistinguishComment(form: DistinguishComment): void; - onAddModToCommunity(form: AddModToCommunity): void; - onAddAdmin(form: AddAdmin): void; - onBanPersonFromCommunity(form: BanFromCommunity): void; - onBanPerson(form: BanPerson): void; - onTransferCommunity(form: TransferCommunity): void; + onCreateComment( + form: EditComment | CreateComment, + ): Promise>; + onEditComment( + form: EditComment | CreateComment, + ): Promise>; + onCommentVote(form: CreateCommentLike): Promise; + onBlockPerson(form: BlockPerson): Promise; + onDeleteComment(form: DeleteComment): Promise; + onRemoveComment(form: RemoveComment): Promise; + onDistinguishComment(form: DistinguishComment): Promise; + onAddModToCommunity(form: AddModToCommunity): Promise; + onAddAdmin(form: AddAdmin): Promise; + onBanPersonFromCommunity(form: BanFromCommunity): Promise; + onBanPerson(form: BanPerson): Promise; + onTransferCommunity(form: TransferCommunity): Promise; onFetchChildren?(form: GetComments): void; - onCommentReport(form: CreateCommentReport): void; - onPurgePerson(form: PurgePerson): void; - onPurgeComment(form: PurgeComment): void; + onCommentReport(form: CreateCommentReport): Promise; + onPurgePerson(form: PurgePerson): Promise; + onPurgeComment(form: PurgeComment): Promise; } export class CommentNodes extends Component { diff --git a/src/shared/components/comment/comment-report.tsx b/src/shared/components/comment/comment-report.tsx index 94fb35c0..359d1948 100644 --- a/src/shared/components/comment/comment-report.tsx +++ b/src/shared/components/comment/comment-report.tsx @@ -84,23 +84,23 @@ export class CommentReport extends Component< hideImages // All of these are unused, since its viewonly finished={new Map()} - onSaveComment={() => {}} - onBlockPerson={() => {}} - onDeleteComment={() => {}} - onRemoveComment={() => {}} - onCommentVote={() => {}} - onCommentReport={() => {}} - onDistinguishComment={() => {}} - onAddModToCommunity={() => {}} - onAddAdmin={() => {}} - onTransferCommunity={() => {}} - onPurgeComment={() => {}} - onPurgePerson={() => {}} + onSaveComment={async () => {}} + onBlockPerson={async () => {}} + onDeleteComment={async () => {}} + onRemoveComment={async () => {}} + onCommentVote={async () => {}} + onCommentReport={async () => {}} + onDistinguishComment={async () => {}} + onAddModToCommunity={async () => {}} + onAddAdmin={async () => {}} + onTransferCommunity={async () => {}} + onPurgeComment={async () => {}} + onPurgePerson={async () => {}} onCommentReplyRead={() => {}} onPersonMentionRead={() => {}} - onBanPersonFromCommunity={() => {}} - onBanPerson={() => {}} - onCreateComment={() => Promise.resolve(EMPTY_REQUEST)} + onBanPersonFromCommunity={async () => {}} + onBanPerson={async () => {}} + onCreateComment={async () => Promise.resolve(EMPTY_REQUEST)} onEditComment={() => Promise.resolve(EMPTY_REQUEST)} />
    diff --git a/src/shared/components/common/confirmation-modal.tsx b/src/shared/components/common/confirmation-modal.tsx new file mode 100644 index 00000000..d369863b --- /dev/null +++ b/src/shared/components/common/confirmation-modal.tsx @@ -0,0 +1,140 @@ +import { Component, RefObject, createRef, linkEvent } from "inferno"; +import { I18NextService } from "../../services"; +import type { Modal } from "bootstrap"; +import { Spinner } from "./icon"; +import { LoadingEllipses } from "./loading-ellipses"; + +interface ConfirmationModalProps { + onYes: () => Promise; + onNo: () => void; + message: string; + loadingMessage: string; + show: boolean; +} + +interface ConfirmationModalState { + loading: boolean; +} + +async function handleYes(i: ConfirmationModal) { + i.setState({ loading: true }); + await i.props.onYes(); + i.setState({ loading: false }); +} + +export default class ConfirmationModal extends Component< + ConfirmationModalProps, + ConfirmationModalState +> { + readonly modalDivRef: RefObject; + readonly yesButtonRef: RefObject; + modal: Modal; + state: ConfirmationModalState = { + loading: false, + }; + + constructor(props: ConfirmationModalProps, context: any) { + super(props, context); + + this.modalDivRef = createRef(); + this.yesButtonRef = createRef(); + + this.handleShow = this.handleShow.bind(this); + } + + async componentDidMount() { + this.modalDivRef.current?.addEventListener( + "shown.bs.modal", + this.handleShow, + ); + + const Modal = (await import("bootstrap/js/dist/modal")).default; + this.modal = new Modal(this.modalDivRef.current!); + + if (this.props.show) { + this.modal.show(); + } + } + + componentWillUnmount() { + this.modalDivRef.current?.removeEventListener( + "shown.bs.modal", + this.handleShow, + ); + + this.modal.dispose(); + } + + componentDidUpdate({ show: prevShow }: ConfirmationModalProps) { + if (!!prevShow !== !!this.props.show) { + if (this.props.show) { + this.modal.show(); + } else { + this.modal.hide(); + } + } + } + + render() { + const { message, onNo, loadingMessage } = this.props; + const { loading } = this.state; + + return ( +
    +
    +
    +
    +

    + {I18NextService.i18n.t("confirmation_required")} +

    +
    +
    + {loading ? ( + <> + +
    + {loadingMessage} + +
    + + ) : ( + message + )} +
    +
    + + +
    +
    +
    +
    + ); + } + + handleShow() { + this.yesButtonRef.current?.focus(); + } +} diff --git a/src/shared/components/common/content-actions/action-button.tsx b/src/shared/components/common/content-actions/action-button.tsx new file mode 100644 index 00000000..ee665969 --- /dev/null +++ b/src/shared/components/common/content-actions/action-button.tsx @@ -0,0 +1,79 @@ +import { Component, linkEvent } from "inferno"; +import { Icon, Spinner } from "../icon"; +import classNames from "classnames"; + +interface ActionButtonPropsBase { + label: string; + icon: string; + iconClass?: string; + inline?: boolean; + noLoading?: boolean; +} + +interface ActionButtonPropsLoading extends ActionButtonPropsBase { + onClick: () => Promise; + noLoading?: false; +} + +interface ActionButtonPropsNoLoading extends ActionButtonPropsBase { + onClick: () => void; + noLoading: true; +} + +type ActionButtonProps = ActionButtonPropsLoading | ActionButtonPropsNoLoading; + +interface ActionButtonState { + loading: boolean; +} + +async function handleClick(i: ActionButton) { + if (!i.props.noLoading) { + i.setState({ loading: true }); + } + await i.props.onClick(); + i.setState({ loading: false }); +} + +export default class ActionButton extends Component< + ActionButtonProps, + ActionButtonState +> { + state: ActionButtonState = { + loading: false, + }; + + constructor(props: ActionButtonProps, context: any) { + super(props, context); + } + + render() { + const { label, icon, iconClass, inline } = this.props; + + return ( + + ); + } +} + +ActionButton.defaultProps = { + inline: false, + noLoading: false, +}; diff --git a/src/shared/components/common/content-actions/comment-action-dropdown.tsx b/src/shared/components/common/content-actions/comment-action-dropdown.tsx new file mode 100644 index 00000000..0231288e --- /dev/null +++ b/src/shared/components/common/content-actions/comment-action-dropdown.tsx @@ -0,0 +1,8 @@ +import { InfernoNode } from "inferno"; +import ContentActionDropdown, { + ContentCommentProps, +} from "./content-action-dropdown"; + +export default ( + props: Omit, +): InfernoNode => ; diff --git a/src/shared/components/common/content-actions/content-action-dropdown.tsx b/src/shared/components/common/content-actions/content-action-dropdown.tsx new file mode 100644 index 00000000..7cda3737 --- /dev/null +++ b/src/shared/components/common/content-actions/content-action-dropdown.tsx @@ -0,0 +1,766 @@ +import { Component } from "inferno"; +import { I18NextService, UserService } from "../../../services"; +import { Icon } from "../icon"; +import { CrossPostParams } from "@utils/types"; +import CrossPostButton from "./cross-post-button"; +import { + CommentView, + CommunityModeratorView, + PersonView, + PostView, +} from "lemmy-js-client"; +import { + amAdmin, + amCommunityCreator, + amMod, + canAdmin, + canMod, + isBanned, +} from "@utils/roles"; +import ActionButton from "./action-button"; +import classNames from "classnames"; +import { Link } from "inferno-router"; +import ConfirmationModal from "../confirmation-modal"; +import ModActionFormModal, { BanUpdateForm } from "../mod-action-form-modal"; +import { BanType, PurgeType } from "../../../interfaces"; +import { getApubName, hostname } from "@utils/helpers"; + +interface ContentActionDropdownPropsBase { + onSave: () => Promise; + onEdit: () => void; + onDelete: () => Promise; + onReport: (reason: string) => Promise; + onBlock: () => Promise; + onRemove: (reason: string) => Promise; + onBanFromCommunity: (form: BanUpdateForm) => Promise; + onAppointCommunityMod: () => Promise; + onTransferCommunity: () => Promise; + onBanFromSite: (form: BanUpdateForm) => Promise; + onPurgeContent: (reason: string) => Promise; + onPurgeUser: (reason: string) => Promise; + onAppointAdmin: () => Promise; + moderators?: CommunityModeratorView[]; + admins?: PersonView[]; +} + +export type ContentCommentProps = { + type: "comment"; + commentView: CommentView; + onReply: () => void; + onDistinguish: () => Promise; +} & ContentActionDropdownPropsBase; + +export type ContentPostProps = { + type: "post"; + postView: PostView; + crossPostParams: CrossPostParams; + onLock: () => Promise; + onFeatureLocal: () => Promise; + onFeatureCommunity: () => Promise; +} & ContentActionDropdownPropsBase; + +type ContentActionDropdownProps = ContentCommentProps | ContentPostProps; + +const dialogTypes = [ + "showBanDialog", + "showRemoveDialog", + "showPurgeDialog", + "showReportDialog", + "showTransferCommunityDialog", + "showAppointModDialog", + "showAppointAdminDialog", +] as const; + +type DialogType = (typeof dialogTypes)[number]; + +type ContentActionDropdownState = { + banType?: BanType; + purgeType?: PurgeType; + mounted: boolean; +} & { [key in DialogType]: boolean }; + +export default class ContentActionDropdown extends Component< + ContentActionDropdownProps, + ContentActionDropdownState +> { + state: ContentActionDropdownState = { + showAppointAdminDialog: false, + showAppointModDialog: false, + showBanDialog: false, + showPurgeDialog: false, + showRemoveDialog: false, + showReportDialog: false, + showTransferCommunityDialog: false, + mounted: false, + }; + + constructor(props: ContentActionDropdownProps, context: any) { + super(props, context); + + this.toggleModDialogShow = this.toggleModDialogShow.bind(this); + this.hideAllDialogs = this.hideAllDialogs.bind(this); + this.toggleReportDialogShow = this.toggleReportDialogShow.bind(this); + this.toggleRemoveShow = this.toggleRemoveShow.bind(this); + this.toggleBanFromCommunityShow = + this.toggleBanFromCommunityShow.bind(this); + this.toggleBanFromSiteShow = this.toggleBanFromSiteShow.bind(this); + this.togglePurgePersonShow = this.togglePurgePersonShow.bind(this); + this.togglePurgeContentShow = this.togglePurgeContentShow.bind(this); + this.toggleTransferCommunityShow = + this.toggleTransferCommunityShow.bind(this); + this.toggleAppointModShow = this.toggleAppointModShow.bind(this); + this.toggleAppointAdminShow = this.toggleAppointAdminShow.bind(this); + this.wrapHandler = this.wrapHandler.bind(this); + } + + componentDidMount() { + this.setState({ mounted: true }); + } + + render() { + // Possible enhancement: Priority+ pattern instead of just hard coding which get hidden behind the show more button. + const { onSave, type, onDelete, onBlock, onEdit, moderators } = this.props; + const { + id, + saved, + deleted, + locked, + removed, + creator_banned_from_community, + creator, + community, + creator_is_admin, + creator_is_moderator, + } = this.contentInfo; + const dropdownId = + type === "post" + ? `post-actions-dropdown-${id}` + : `comment-actions-dropdown-${id}`; + const creatorBannedFromLocal = isBanned(creator); + const showToggleAdmin = !creatorBannedFromLocal && creator.local; + const canAppointCommunityMod = + (amMod(community.id) || (amAdmin() && community.local)) && + !creator_banned_from_community; + + return ( + <> + {type === "comment" && ( + + )} + + {type === "post" && ( + + )} + +
    + + +
      + {this.amCreator ? ( + <> +
    • + +
    • +
    • + +
    • + + ) : ( + <> + {type === "comment" && ( +
    • + + + {I18NextService.i18n.t("message")} + +
    • + )} +
    • + +
    • +
    • + +
    • + + )} + + {(amMod(community.id) || amAdmin()) && ( + <> +
    • +
      +
    • + {type === "post" && ( + <> +
    • + +
    • +
    • + +
    • + {amAdmin() && ( +
    • + +
    • + )} + + )} + + )} + {type === "comment" && + this.amCreator && + (this.canModOnSelf || this.canAdminOnSelf) && ( +
    • + +
    • + )} + {(this.canMod || this.canAdmin) && ( +
    • + +
    • + )} + {this.canMod && + (!creator_is_moderator || canAppointCommunityMod) && ( + <> +
    • +
      +
    • + {!creator_is_moderator && ( +
    • + +
    • + )} + {canAppointCommunityMod && ( +
    • + +
    • + )} + + )} + {(amCommunityCreator(this.id, moderators) || this.canAdmin) && + creator_is_moderator && ( +
    • + +
    • + )} + + {this.canAdmin && (showToggleAdmin || !creator_is_admin) && ( + <> +
    • +
      +
    • + {!creator_is_admin && ( + <> +
    • + +
    • +
    • + +
    • +
    • + +
    • + + )} + {showToggleAdmin && ( +
    • + +
    • + )} + + )} +
    +
    + {this.moderationDialogs} + + ); + } + + toggleModDialogShow( + dialogType: DialogType, + stateOverride: Partial = {}, + ) { + this.setState(prev => ({ + ...prev, + [dialogType]: !prev[dialogType], + ...dialogTypes + .filter(dt => dt !== dialogType) + .reduce( + (acc, dt) => ({ + ...acc, + [dt]: false, + }), + {}, + ), + ...stateOverride, + })); + } + + hideAllDialogs() { + this.setState({ + showBanDialog: false, + showPurgeDialog: false, + showRemoveDialog: false, + showReportDialog: false, + showAppointAdminDialog: false, + showAppointModDialog: false, + showTransferCommunityDialog: false, + }); + } + + toggleReportDialogShow() { + this.toggleModDialogShow("showReportDialog"); + } + + toggleRemoveShow() { + this.toggleModDialogShow("showRemoveDialog"); + } + + toggleBanFromCommunityShow() { + this.toggleModDialogShow("showBanDialog", { + banType: BanType.Community, + }); + } + + toggleBanFromSiteShow() { + this.toggleModDialogShow("showBanDialog", { + banType: BanType.Site, + }); + } + + togglePurgePersonShow() { + this.toggleModDialogShow("showPurgeDialog", { + purgeType: PurgeType.Person, + }); + } + + togglePurgeContentShow() { + this.toggleModDialogShow("showPurgeDialog", { + purgeType: + this.props.type === "post" ? PurgeType.Post : PurgeType.Comment, + }); + } + + toggleTransferCommunityShow() { + this.toggleModDialogShow("showTransferCommunityDialog"); + } + + toggleAppointModShow() { + this.toggleModDialogShow("showAppointModDialog"); + } + + toggleAppointAdminShow() { + this.toggleModDialogShow("showAppointAdminDialog"); + } + + get moderationDialogs() { + const { + showBanDialog, + showPurgeDialog, + showRemoveDialog, + showReportDialog, + banType, + purgeType, + showTransferCommunityDialog, + showAppointModDialog, + showAppointAdminDialog, + mounted, + } = this.state; + const { + removed, + creator, + creator_banned_from_community, + community, + creator_is_admin, + creator_is_moderator, + } = this.contentInfo; + const { + onReport, + onRemove, + onBanFromCommunity, + onBanFromSite, + onPurgeContent, + onPurgeUser, + onTransferCommunity, + onAppointCommunityMod, + onAppointAdmin, + type, + } = this.props; + + // Wait until componentDidMount runs (which only happens on the browser) to prevent sending over a gratuitous amount of markup + return ( + mounted && ( + <> + + + + + + + + + ) + ); + } + + get contentInfo() { + if (this.props.type === "post") { + const { + post: { id, deleted, locked, removed }, + saved, + creator, + creator_banned_from_community, + community, + creator_is_admin, + creator_is_moderator, + } = this.props.postView; + + return { + id, + saved, + deleted, + creator, + locked, + removed, + creator_banned_from_community, + community, + creator_is_admin, + creator_is_moderator, + }; + } else { + const { + comment: { id, deleted, removed }, + saved, + creator, + creator_banned_from_community, + community, + creator_is_admin, + creator_is_moderator, + } = this.props.commentView; + + return { + id, + saved, + deleted, + creator, + removed, + creator_banned_from_community, + community, + creator_is_admin, + creator_is_moderator, + }; + } + } + + get amCreator() { + const { creator } = this.contentInfo; + + return ( + creator.id === UserService.Instance.myUserInfo?.local_user_view.person.id + ); + } + + get canMod() { + const { creator } = this.contentInfo; + return canMod(creator.id, this.props.moderators, this.props.admins); + } + + get canAdmin() { + const { creator } = this.contentInfo; + return canAdmin(creator.id, this.props.admins); + } + + get canModOnSelf() { + const { creator } = this.contentInfo; + return canMod( + creator.id, + this.props.moderators, + this.props.admins, + UserService.Instance.myUserInfo, + true, + ); + } + + get canAdminOnSelf() { + const { creator } = this.contentInfo; + return canAdmin( + creator.id, + this.props.admins, + UserService.Instance.myUserInfo, + true, + ); + } + + get id() { + return this.props.type === "post" + ? this.props.postView.creator.id + : this.props.commentView.creator.id; + } + + wrapHandler(handler: (arg?: any) => Promise) { + return async (arg?: any) => { + await handler(arg); + this.hideAllDialogs(); + }; + } +} diff --git a/src/shared/components/common/content-actions/cross-post-button.tsx b/src/shared/components/common/content-actions/cross-post-button.tsx new file mode 100644 index 00000000..d8e226c0 --- /dev/null +++ b/src/shared/components/common/content-actions/cross-post-button.tsx @@ -0,0 +1,22 @@ +import { Link } from "inferno-router"; +import { I18NextService } from "../../../services"; +import { Icon } from "../icon"; +import { CrossPostParams } from "@utils/types"; +import { InfernoNode } from "inferno"; + +export default function CrossPostButton(props: CrossPostParams): InfernoNode { + return ( + + + + ); +} diff --git a/src/shared/components/common/content-actions/post-action-dropdown.tsx b/src/shared/components/common/content-actions/post-action-dropdown.tsx new file mode 100644 index 00000000..c3f4af41 --- /dev/null +++ b/src/shared/components/common/content-actions/post-action-dropdown.tsx @@ -0,0 +1,8 @@ +import { InfernoNode } from "inferno"; +import ContentActionDropdown, { + ContentPostProps, +} from "./content-action-dropdown"; + +export default ( + props: Omit, +): InfernoNode => ; diff --git a/src/shared/components/common/mod-action-form-modal.tsx b/src/shared/components/common/mod-action-form-modal.tsx new file mode 100644 index 00000000..24c8ecb5 --- /dev/null +++ b/src/shared/components/common/mod-action-form-modal.tsx @@ -0,0 +1,464 @@ +import { Component, RefObject, createRef, linkEvent } from "inferno"; +import { I18NextService } from "../../services/I18NextService"; +import { PurgeWarning, Spinner } from "./icon"; +import { getApubName, randomStr } from "@utils/helpers"; +import type { Modal } from "bootstrap"; +import classNames from "classnames"; +import { Community, Person } from "lemmy-js-client"; +import { LoadingEllipses } from "./loading-ellipses"; + +export interface BanUpdateForm { + reason?: string; + shouldRemove?: boolean; + daysUntilExpires?: number; +} + +interface ModActionFormModalPropsSiteBan { + modActionType: "site-ban"; + onSubmit: (form: BanUpdateForm) => Promise; + creator: Person; + isBanned: boolean; +} + +interface ModActionFormModalPropsCommunityBan { + modActionType: "community-ban"; + onSubmit: (form: BanUpdateForm) => Promise; + creator: Person; + community: Community; + isBanned: boolean; +} + +interface ModActionFormModalPropsPurgePerson { + modActionType: "purge-person"; + onSubmit: (reason: string) => Promise; + creator: Person; +} + +interface ModActionFormModalPropsRemove { + modActionType: "remove-post" | "remove-comment"; + onSubmit: (reason: string) => Promise; + isRemoved: boolean; +} + +interface ModActionFormModalPropsRest { + modActionType: + | "report-post" + | "report-comment" + | "report-message" + | "purge-post" + | "purge-comment"; + onSubmit: (reason: string) => Promise; +} + +type ModActionFormModalProps = ( + | ModActionFormModalPropsSiteBan + | ModActionFormModalPropsCommunityBan + | ModActionFormModalPropsRest + | ModActionFormModalPropsPurgePerson + | ModActionFormModalPropsRemove +) & { onCancel: () => void; show: boolean }; + +interface ModActionFormFormState { + loading: boolean; + reason: string; + daysUntilExpire?: number; + shouldRemoveData?: boolean; + shouldPermaBan?: boolean; +} + +function handleReasonChange(i: ModActionFormModal, event: any) { + i.setState({ reason: event.target.value }); +} + +function handleExpiryChange(i: ModActionFormModal, event: any) { + i.setState({ daysUntilExpire: parseInt(event.target.value, 10) }); +} + +function handleToggleRemove(i: ModActionFormModal) { + i.setState(prev => ({ + ...prev, + shouldRemoveData: !prev.shouldRemoveData, + })); +} + +function handleTogglePermaBan(i: ModActionFormModal) { + i.setState(prev => ({ + ...prev, + shouldPermaBan: !prev.shouldPermaBan, + daysUntilExpire: undefined, + })); +} + +async function handleSubmit(i: ModActionFormModal, event: any) { + event.preventDefault(); + i.setState({ loading: true }); + + if (i.isBanModal) { + await i.props.onSubmit({ + reason: i.state.reason, + daysUntilExpires: i.state.daysUntilExpire!, + shouldRemove: i.state.shouldRemoveData!, + } as BanUpdateForm & string); // Need to & string to handle type weirdness + } else { + await i.props.onSubmit(i.state.reason); + } + + i.setState({ + loading: false, + reason: "", + }); +} + +export default class ModActionFormModal extends Component< + ModActionFormModalProps, + ModActionFormFormState +> { + private modalDivRef: RefObject; + private reasonRef: RefObject; + modal: Modal; + state: ModActionFormFormState = { + loading: false, + reason: "", + }; + + constructor(props: ModActionFormModalProps, context: any) { + super(props, context); + this.modalDivRef = createRef(); + this.reasonRef = createRef(); + + if (this.isBanModal) { + this.state.shouldRemoveData = false; + } + + this.handleShow = this.handleShow.bind(this); + } + + async componentDidMount() { + this.modalDivRef.current?.addEventListener( + "shown.bs.modal", + this.handleShow, + ); + + const Modal = (await import("bootstrap/js/dist/modal")).default; + this.modal = new Modal(this.modalDivRef.current!); + + if (this.props.show) { + this.modal.show(); + } + } + + componentWillUnmount() { + this.modalDivRef.current?.removeEventListener( + "shown.bs.modal", + this.handleShow, + ); + + this.modal.dispose(); + } + + componentDidUpdate({ show: prevShow }: ModActionFormModalProps) { + if (!!prevShow !== !!this.props.show) { + if (this.props.show) { + this.modal.show(); + } else { + this.modal.hide(); + } + } + } + + render() { + const { + loading, + reason, + daysUntilExpire, + shouldRemoveData, + shouldPermaBan, + } = this.state; + const reasonId = `mod-form-reason-${randomStr()}`; + const expiresId = `mod-form-expires-${randomStr()}`; + const { modActionType, onCancel } = this.props; + + const formId = `mod-action-form-${randomStr()}`; + + const isBanned = + (modActionType === "site-ban" || modActionType === "community-ban") && + this.props.isBanned; + + const showExpiresField = this.isBanModal && !(isBanned || shouldPermaBan); + + return ( +
    +
    +
    +
    +

    + {this.headerText} +

    +
    +
    + {loading ? ( + <> + +
    + {this.loadingText} + +
    + + ) : ( +
    +
    +
    + {modActionType.includes("purge") && } + + +
    + {showExpiresField && ( +
    + + +
    + )} +
    +
    + {this.isBanModal && !isBanned && ( +
    +
    + +
    +
    + +
    +
    + )} +
    +
    + )} +
    +
    + + +
    +
    +
    +
    + ); + } + + handleShow() { + this.reasonRef.current?.focus(); + } + + get isBanModal() { + return ( + this.props.modActionType === "site-ban" || + this.props.modActionType === "community-ban" + ); + } + + get headerText() { + switch (this.props.modActionType) { + case "site-ban": { + return I18NextService.i18n.t( + this.props.isBanned ? "unban_with_name" : "ban_with_name", + { + user: getApubName(this.props.creator), + }, + ); + } + + case "community-ban": { + return I18NextService.i18n.t( + this.props.isBanned + ? "unban_from_community_with_name" + : "ban_from_community_with_name", + { + user: getApubName(this.props.creator), + community: getApubName(this.props.community), + }, + ); + } + + case "purge-post": { + return I18NextService.i18n.t("purge_post"); + } + + case "purge-comment": { + return I18NextService.i18n.t("purge_comment"); + } + + case "purge-person": { + return I18NextService.i18n.t("purge_user_with_name", { + user: getApubName(this.props.creator), + }); + } + + case "remove-post": { + return I18NextService.i18n.t( + this.props.isRemoved ? "restore_post" : "remove_post", + ); + } + + case "remove-comment": { + return I18NextService.i18n.t( + this.props.isRemoved ? "restore_comment" : "remove_comment", + ); + } + + case "report-post": { + return I18NextService.i18n.t("report_post"); + } + + case "report-comment": { + return I18NextService.i18n.t("report_comment"); + } + + case "report-message": { + return I18NextService.i18n.t("report_message"); + } + } + } + + get buttonText() { + switch (this.props.modActionType) { + case "site-ban": + case "community-ban": { + return I18NextService.i18n.t(this.props.isBanned ? "unban" : "ban"); + } + + case "purge-post": + case "purge-comment": + case "purge-person": { + return I18NextService.i18n.t("purge"); + } + + case "remove-post": + case "remove-comment": { + return I18NextService.i18n.t( + this.props.isRemoved ? "restore" : "remove", + ); + } + + case "report-post": + case "report-comment": + case "report-message": { + return I18NextService.i18n.t("create_report"); + } + } + } + + get loadingText() { + let translation: string; + + switch (this.props.modActionType) { + case "site-ban": + case "community-ban": { + translation = this.props.isBanned ? "unbanning" : "banning"; + break; + } + + case "purge-post": + case "purge-comment": + case "purge-person": { + translation = "purging"; + break; + } + + case "remove-post": + case "remove-comment": { + translation = this.props.isRemoved ? "restoring" : "removing"; + break; + } + + case "report-post": + case "report-comment": + case "report-message": { + translation = "creating_report"; + break; + } + } + + return I18NextService.i18n.t(translation); + } +} diff --git a/src/shared/components/common/report-form.tsx b/src/shared/components/common/report-form.tsx deleted file mode 100644 index b58da594..00000000 --- a/src/shared/components/common/report-form.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { Component, linkEvent } from "inferno"; -import { I18NextService } from "../../services/I18NextService"; -import { Spinner } from "./icon"; -import { randomStr } from "@utils/helpers"; - -interface ReportFormProps { - onSubmit: (reason: string) => void; -} - -interface ReportFormState { - loading: boolean; - reason: string; -} - -function handleReportReasonChange(i: ReportForm, event: any) { - i.setState({ reason: event.target.value }); -} - -function handleReportSubmit(i: ReportForm, event: any) { - event.preventDefault(); - i.setState({ loading: true }); - i.props.onSubmit(i.state.reason); - - i.setState({ - loading: false, - reason: "", - }); -} - -export default class ReportForm extends Component< - ReportFormProps, - ReportFormState -> { - state: ReportFormState = { - loading: false, - reason: "", - }; - constructor(props, context) { - super(props, context); - } - - render() { - const { loading, reason } = this.state; - const id = `report-form-${randomStr()}`; - - return ( -
    - - - -
    - ); - } -} diff --git a/src/shared/components/common/subscribe-button.tsx b/src/shared/components/common/subscribe-button.tsx index 58195391..67337323 100644 --- a/src/shared/components/common/subscribe-button.tsx +++ b/src/shared/components/common/subscribe-button.tsx @@ -172,7 +172,7 @@ class RemoteFetchModal extends Component< aria-hidden aria-labelledby="#remoteFetchModalTitle" > -
    +

    diff --git a/src/shared/components/common/totp-modal.tsx b/src/shared/components/common/totp-modal.tsx index e949b499..b07b4b86 100644 --- a/src/shared/components/common/totp-modal.tsx +++ b/src/shared/components/common/totp-modal.tsx @@ -143,7 +143,7 @@ export default class TotpModal extends Component< data-bs-backdrop="static" ref={this.modalDivRef} > -
    +

    diff --git a/src/shared/components/community/community.tsx b/src/shared/components/community/community.tsx index 265c0f57..e690fb12 100644 --- a/src/shared/components/community/community.tsx +++ b/src/shared/components/community/community.tsx @@ -447,7 +447,7 @@ export class Community extends Component< onAddAdmin={this.handleAddAdmin} onTransferCommunity={this.handleTransferCommunity} onFeaturePost={this.handleFeaturePost} - onMarkPostAsRead={() => {}} + onMarkPostAsRead={async () => {}} /> ); } @@ -758,11 +758,13 @@ export class Community extends Component< async handlePostEdit(form: EditPost) { const res = await HttpService.client.editPost(form); this.findAndUpdatePost(res); + return res; } async handlePostVote(form: CreatePostLike) { const voteRes = await HttpService.client.likePost(form); this.findAndUpdatePost(voteRes); + return voteRes; } async handleCommentReport(form: CreateCommentReport) { diff --git a/src/shared/components/home/home.tsx b/src/shared/components/home/home.tsx index 7493033d..8e758d38 100644 --- a/src/shared/components/home/home.tsx +++ b/src/shared/components/home/home.tsx @@ -716,7 +716,7 @@ export class Home extends Component { onAddAdmin={this.handleAddAdmin} onTransferCommunity={this.handleTransferCommunity} onFeaturePost={this.handleFeaturePost} - onMarkPostAsRead={() => {}} + onMarkPostAsRead={async () => {}} /> ); } @@ -970,11 +970,13 @@ export class Home extends Component { async handlePostEdit(form: EditPost) { const res = await HttpService.client.editPost(form); this.findAndUpdatePost(res); + return res; } async handlePostVote(form: CreatePostLike) { const voteRes = await HttpService.client.likePost(form); this.findAndUpdatePost(voteRes); + return voteRes; } async handleCommentReport(form: CreateCommentReport) { diff --git a/src/shared/components/person/person-details.tsx b/src/shared/components/person/person-details.tsx index 8e72005f..7b0498a9 100644 --- a/src/shared/components/person/person-details.tsx +++ b/src/shared/components/person/person-details.tsx @@ -7,6 +7,7 @@ import { BanPerson, BlockPerson, CommentId, + CommentResponse, CommentView, CreateComment, CreateCommentLike, @@ -27,6 +28,7 @@ import { MarkPersonMentionAsRead, MarkPostAsRead, PersonView, + PostResponse, PostView, PurgeComment, PurgePerson, @@ -43,6 +45,7 @@ import { setupTippy } from "../../tippy"; import { CommentNodes } from "../comment/comment-nodes"; import { Paginator } from "../common/paginator"; import { PostListing } from "../post/post-listing"; +import { RequestState } from "../../services/HttpService"; interface PersonDetailsProps { personRes: GetPersonDetailsResponse; @@ -57,34 +60,34 @@ interface PersonDetailsProps { enableNsfw: boolean; view: PersonDetailsView; onPageChange(page: number): number | any; - onSaveComment(form: SaveComment): void; + onSaveComment(form: SaveComment): Promise; onCommentReplyRead(form: MarkCommentReplyAsRead): void; onPersonMentionRead(form: MarkPersonMentionAsRead): void; - onCreateComment(form: CreateComment): void; - onEditComment(form: EditComment): void; - onCommentVote(form: CreateCommentLike): void; - onBlockPerson(form: BlockPerson): void; - onDeleteComment(form: DeleteComment): void; - onRemoveComment(form: RemoveComment): void; - onDistinguishComment(form: DistinguishComment): void; - onAddModToCommunity(form: AddModToCommunity): void; - onAddAdmin(form: AddAdmin): void; - onBanPersonFromCommunity(form: BanFromCommunity): void; - onBanPerson(form: BanPerson): void; - onTransferCommunity(form: TransferCommunity): void; + onCreateComment(form: CreateComment): Promise>; + onEditComment(form: EditComment): Promise>; + onCommentVote(form: CreateCommentLike): Promise; + onBlockPerson(form: BlockPerson): Promise; + onDeleteComment(form: DeleteComment): Promise; + onRemoveComment(form: RemoveComment): Promise; + onDistinguishComment(form: DistinguishComment): Promise; + onAddModToCommunity(form: AddModToCommunity): Promise; + onAddAdmin(form: AddAdmin): Promise; + onBanPersonFromCommunity(form: BanFromCommunity): Promise; + onBanPerson(form: BanPerson): Promise; + onTransferCommunity(form: TransferCommunity): Promise; onFetchChildren?(form: GetComments): void; - onCommentReport(form: CreateCommentReport): void; - onPurgePerson(form: PurgePerson): void; - onPurgeComment(form: PurgeComment): void; - onPostEdit(form: EditPost): void; - onPostVote(form: CreatePostLike): void; - onPostReport(form: CreatePostReport): void; - onLockPost(form: LockPost): void; - onDeletePost(form: DeletePost): void; - onRemovePost(form: RemovePost): void; - onSavePost(form: SavePost): void; - onFeaturePost(form: FeaturePost): void; - onPurgePost(form: PurgePost): void; + onCommentReport(form: CreateCommentReport): Promise; + onPurgePerson(form: PurgePerson): Promise; + onPurgeComment(form: PurgeComment): Promise; + onPostEdit(form: EditPost): Promise>; + onPostVote(form: CreatePostLike): Promise>; + onPostReport(form: CreatePostReport): Promise; + onLockPost(form: LockPost): Promise; + onDeletePost(form: DeletePost): Promise; + onRemovePost(form: RemovePost): Promise; + onSavePost(form: SavePost): Promise; + onFeaturePost(form: FeaturePost): Promise; + onPurgePost(form: PurgePost): Promise; onMarkPostAsRead(form: MarkPostAsRead): void; } diff --git a/src/shared/components/person/profile.tsx b/src/shared/components/person/profile.tsx index e779d334..c8710e27 100644 --- a/src/shared/components/person/profile.tsx +++ b/src/shared/components/person/profile.tsx @@ -897,11 +897,13 @@ export class Profile extends Component< async handlePostVote(form: CreatePostLike) { const voteRes = await HttpService.client.likePost(form); this.findAndUpdatePost(voteRes); + return voteRes; } async handlePostEdit(form: EditPost) { const res = await HttpService.client.editPost(form); this.findAndUpdatePost(res); + return res; } async handleCommentReport(form: CreateCommentReport) { diff --git a/src/shared/components/post/post-form.tsx b/src/shared/components/post/post-form.tsx index 9c9c8d28..3e69976d 100644 --- a/src/shared/components/post/post-form.tsx +++ b/src/shared/components/post/post-form.tsx @@ -444,23 +444,23 @@ export class PostForm extends Component { siteLanguages={this.props.siteLanguages} viewOnly // All of these are unused, since its view only - onPostEdit={() => {}} - onPostVote={() => {}} - onPostReport={() => {}} - onBlockPerson={() => {}} - onLockPost={() => {}} - onDeletePost={() => {}} - onRemovePost={() => {}} - onSavePost={() => {}} - onFeaturePost={() => {}} - onPurgePerson={() => {}} - onPurgePost={() => {}} - onBanPersonFromCommunity={() => {}} - onBanPerson={() => {}} - onAddModToCommunity={() => {}} - onAddAdmin={() => {}} - onTransferCommunity={() => {}} - onMarkPostAsRead={() => {}} + onPostEdit={async () => EMPTY_REQUEST} + onPostVote={async () => EMPTY_REQUEST} + onPostReport={async () => {}} + onBlockPerson={async () => {}} + onLockPost={async () => {}} + onDeletePost={async () => {}} + onRemovePost={async () => {}} + onSavePost={async () => {}} + onFeaturePost={async () => {}} + onPurgePerson={async () => {}} + onPurgePost={async () => {}} + onBanPersonFromCommunity={async () => {}} + onBanPerson={async () => {}} + onAddModToCommunity={async () => {}} + onAddAdmin={async () => {}} + onTransferCommunity={async () => {}} + onMarkPostAsRead={async () => {}} /> )} @@ -615,23 +615,23 @@ export class PostForm extends Component { siteLanguages={this.props.siteLanguages} viewOnly // All of these are unused, since its view only - onPostEdit={() => {}} - onPostVote={() => {}} - onPostReport={() => {}} - onBlockPerson={() => {}} - onLockPost={() => {}} - onDeletePost={() => {}} - onRemovePost={() => {}} - onSavePost={() => {}} - onFeaturePost={() => {}} - onPurgePerson={() => {}} - onPurgePost={() => {}} - onBanPersonFromCommunity={() => {}} - onBanPerson={() => {}} - onAddModToCommunity={() => {}} - onAddAdmin={() => {}} - onTransferCommunity={() => {}} - onMarkPostAsRead={() => {}} + onPostEdit={async () => EMPTY_REQUEST} + onPostVote={async () => EMPTY_REQUEST} + onPostReport={async () => {}} + onBlockPerson={async () => {}} + onLockPost={async () => {}} + onDeletePost={async () => {}} + onRemovePost={async () => {}} + onSavePost={async () => {}} + onFeaturePost={async () => {}} + onPurgePerson={async () => {}} + onPurgePost={async () => {}} + onBanPersonFromCommunity={async () => {}} + onBanPerson={async () => {}} + onAddModToCommunity={async () => {}} + onAddAdmin={async () => {}} + onTransferCommunity={async () => {}} + onMarkPostAsRead={async () => {}} /> ) diff --git a/src/shared/components/post/post-listing.tsx b/src/shared/components/post/post-listing.tsx index db3c785f..17c7e056 100644 --- a/src/shared/components/post/post-listing.tsx +++ b/src/shared/components/post/post-listing.tsx @@ -1,20 +1,9 @@ import { myAuth } from "@utils/app"; import { canShare, share } from "@utils/browser"; import { getExternalHost, getHttpBase } from "@utils/env"; -import { - capitalizeFirstLetter, - futureDaysToUnixTime, - hostname, -} from "@utils/helpers"; +import { futureDaysToUnixTime, hostname } from "@utils/helpers"; import { isImage, isVideo } from "@utils/media"; -import { - amAdmin, - amCommunityCreator, - amMod, - canAdmin, - canMod, - isBanned, -} from "@utils/roles"; +import { canAdmin, canMod } from "@utils/roles"; import classNames from "classnames"; import { Component, linkEvent } from "inferno"; import { Link } from "inferno-router"; @@ -34,6 +23,7 @@ import { LockPost, MarkPostAsRead, PersonView, + PostResponse, PostView, PurgePerson, PurgePost, @@ -42,16 +32,11 @@ import { TransferCommunity, } from "lemmy-js-client"; import { relTags } from "../../config"; -import { - BanType, - PostFormParams, - PurgeType, - VoteContentType, -} from "../../interfaces"; +import { VoteContentType } from "../../interfaces"; import { mdToHtml, mdToHtmlInline } from "../../markdown"; import { I18NextService, UserService } from "../../services"; import { setupTippy } from "../../tippy"; -import { Icon, PurgeWarning, Spinner } from "../common/icon"; +import { Icon } from "../common/icon"; import { MomentTime } from "../common/moment-time"; import { PictrsImage } from "../common/pictrs-image"; import { UserBadges } from "../common/user-badges"; @@ -60,41 +45,18 @@ import { CommunityLink } from "../community/community-link"; import { PersonListing } from "../person/person-listing"; import { MetadataCard } from "./metadata-card"; import { PostForm } from "./post-form"; -import ReportForm from "../common/report-form"; +import { BanUpdateForm } from "../common/mod-action-form-modal"; +import PostActionDropdown from "../common/content-actions/post-action-dropdown"; +import { CrossPostParams } from "@utils/types"; +import { RequestState } from "../../services/HttpService"; -interface PostListingState { +type PostListingState = { showEdit: boolean; - showRemoveDialog: boolean; - showPurgeDialog: boolean; - purgeReason?: string; - purgeType?: PurgeType; - purgeLoading: boolean; - removeReason?: string; - showBanDialog: boolean; - banReason?: string; - banExpireDays?: number; - banType?: BanType; - removeData?: boolean; - showConfirmTransferSite: boolean; - showConfirmTransferCommunity: boolean; imageExpanded: boolean; viewSource: boolean; showAdvanced: boolean; - showMoreMobile: boolean; showBody: boolean; - showReportDialog: boolean; - blockLoading: boolean; - lockLoading: boolean; - deleteLoading: boolean; - removeLoading: boolean; - saveLoading: boolean; - featureCommunityLoading: boolean; - featureLocalLoading: boolean; - banLoading: boolean; - addModLoading: boolean; - addAdminLoading: boolean; - transferLoading: boolean; -} +}; interface PostListingProps { post_view: PostView; @@ -112,54 +74,32 @@ interface PostListingProps { enableDownvotes?: boolean; enableNsfw?: boolean; viewOnly?: boolean; - onPostEdit(form: EditPost): void; - onPostVote(form: CreatePostLike): void; - onPostReport(form: CreatePostReport): void; - onBlockPerson(form: BlockPerson): void; - onLockPost(form: LockPost): void; - onDeletePost(form: DeletePost): void; - onRemovePost(form: RemovePost): void; - onSavePost(form: SavePost): void; - onFeaturePost(form: FeaturePost): void; - onPurgePerson(form: PurgePerson): void; - onPurgePost(form: PurgePost): void; - onBanPersonFromCommunity(form: BanFromCommunity): void; - onBanPerson(form: BanPerson): void; - onAddModToCommunity(form: AddModToCommunity): void; - onAddAdmin(form: AddAdmin): void; - onTransferCommunity(form: TransferCommunity): void; + onPostEdit(form: EditPost): Promise>; + onPostVote(form: CreatePostLike): Promise>; + onPostReport(form: CreatePostReport): Promise; + onBlockPerson(form: BlockPerson): Promise; + onLockPost(form: LockPost): Promise; + onDeletePost(form: DeletePost): Promise; + onRemovePost(form: RemovePost): Promise; + onSavePost(form: SavePost): Promise; + onFeaturePost(form: FeaturePost): Promise; + onPurgePerson(form: PurgePerson): Promise; + onPurgePost(form: PurgePost): Promise; + onBanPersonFromCommunity(form: BanFromCommunity): Promise; + onBanPerson(form: BanPerson): Promise; + onAddModToCommunity(form: AddModToCommunity): Promise; + onAddAdmin(form: AddAdmin): Promise; + onTransferCommunity(form: TransferCommunity): Promise; onMarkPostAsRead(form: MarkPostAsRead): void; } export class PostListing extends Component { state: PostListingState = { showEdit: false, - showRemoveDialog: false, - showPurgeDialog: false, - purgeType: PurgeType.Person, - showBanDialog: false, - banType: BanType.Community, - removeData: false, - showConfirmTransferSite: false, - showConfirmTransferCommunity: false, imageExpanded: false, viewSource: false, showAdvanced: false, - showMoreMobile: false, showBody: false, - showReportDialog: false, - purgeLoading: false, - blockLoading: false, - lockLoading: false, - deleteLoading: false, - removeLoading: false, - saveLoading: false, - featureCommunityLoading: false, - featureLocalLoading: false, - banLoading: false, - addModLoading: false, - addAdminLoading: false, - transferLoading: false, }; constructor(props: any, context: any) { @@ -167,7 +107,23 @@ export class PostListing extends Component { this.handleEditPost = this.handleEditPost.bind(this); this.handleEditCancel = this.handleEditCancel.bind(this); - this.handleReportSubmit = this.handleReportSubmit.bind(this); + this.handleEditClick = this.handleEditClick.bind(this); + this.handleReport = this.handleReport.bind(this); + this.handleRemove = this.handleRemove.bind(this); + this.handleSavePost = this.handleSavePost.bind(this); + this.handleBlockPerson = this.handleBlockPerson.bind(this); + this.handleDeletePost = this.handleDeletePost.bind(this); + this.handleModLock = this.handleModLock.bind(this); + this.handleModFeaturePostCommunity = + this.handleModFeaturePostCommunity.bind(this); + this.handleModFeaturePostLocal = this.handleModFeaturePostLocal.bind(this); + this.handleAppointCommunityMod = this.handleAppointCommunityMod.bind(this); + this.handleAppointAdmin = this.handleAppointAdmin.bind(this); + this.handleTransferCommunity = this.handleTransferCommunity.bind(this); + this.handleModBanFromCommunity = this.handleModBanFromCommunity.bind(this); + this.handleModBanFromSite = this.handleModBanFromSite.bind(this); + this.handlePurgePerson = this.handlePurgePerson.bind(this); + this.handlePurgePost = this.handlePurgePost.bind(this); } componentDidMount(): void { @@ -180,25 +136,6 @@ export class PostListing extends Component { } } - componentWillReceiveProps(nextProps: PostListingProps) { - if (this.props !== nextProps) { - this.setState({ - purgeLoading: false, - blockLoading: false, - lockLoading: false, - deleteLoading: false, - removeLoading: false, - saveLoading: false, - featureCommunityLoading: false, - featureLocalLoading: false, - banLoading: false, - addModLoading: false, - addAdminLoading: false, - transferLoading: false, - }); - } - } - get postView(): PostView { return this.props.post_view; } @@ -602,8 +539,19 @@ export class PostListing extends Component { } commentsLine(mobile = false) { - const pv = this.postView; - const post = pv.post; + const { + admins, + moderators, + viewOnly, + showBody, + onPostVote, + enableDownvotes, + } = this.props; + const { + post: { local, ap_id, id, body }, + counts, + my_vote, + } = this.postView; return (
    @@ -617,141 +565,56 @@ export class PostListing extends Component { )} - {!post.local && ( + {local && ( )} - {mobile && !this.props.viewOnly && ( + {mobile && !viewOnly && ( )} - {this.props.showBody && pv.post.body && this.viewSourceButton} + {showBody && body && this.viewSourceButton} - {UserService.Instance.myUserInfo && - !this.props.viewOnly && - this.postActions()} + {UserService.Instance.myUserInfo && !viewOnly && ( + + )}
    ); } - postActions() { - // Possible enhancement: Priority+ pattern instead of just hard coding which get hidden behind the show more button. - // Possible enhancement: Make each button a component. - const pv = this.postView; - const post = pv.post; - - return ( - <> - {this.saveButton} - {this.crossPostButton} - -
    - - -
      - {!this.myPost ? ( - <> -
    • {this.reportButton}
    • -
    • {this.blockButton}
    • - - ) : ( - <> -
    • {this.editButton}
    • -
    • {this.deleteButton}
    • - - )} - - {/* Any mod can do these, not limited to hierarchy*/} - {(amMod(this.postView.community.id) || amAdmin()) && ( - <> -
    • -
      -
    • -
    • {this.lockButton}
    • - {this.featureButtons} - - )} - - {(this.canMod_ || this.canAdmin_) && ( -
    • {this.modRemoveButton}
    • - )} - - {this.canMod_ && ( - <> -
    • -
      -
    • - {!pv.creator_is_moderator && - (!pv.creator_banned_from_community ? ( -
    • {this.modBanFromCommunityButton}
    • - ) : ( -
    • {this.modUnbanFromCommunityButton}
    • - ))} - {!pv.creator_banned_from_community && ( -
    • {this.addModToCommunityButton}
    • - )} - - )} - - {(amCommunityCreator(pv.creator.id, this.props.moderators) || - this.canAdmin_) && - pv.creator_is_moderator && ( -
    • {this.transferCommunityButton}
    • - )} - - {/* Admins can ban from all, and appoint other admins */} - {this.canAdmin_ && ( - <> -
    • -
      -
    • - {!pv.creator_is_admin && ( - <> - {!isBanned(pv.creator) ? ( -
    • {this.modBanButton}
    • - ) : ( -
    • {this.modUnbanButton}
    • - )} -
    • {this.purgePersonButton}
    • -
    • {this.purgePostButton}
    • - - )} - {!isBanned(pv.creator) && pv.creator.local && ( -
    • {this.toggleAdminButton}
    • - )} - - )} -
    -
    - - ); - } - public get linkTarget(): string { return UserService.Instance.myUserInfo?.local_user_view.local_user .open_links_in_new_tab @@ -796,121 +659,6 @@ export class PostListing extends Component { : pv.unread_comments; } - get saveButton() { - const saved = this.postView.saved; - const label = saved - ? I18NextService.i18n.t("unsave") - : I18NextService.i18n.t("save"); - return ( - - ); - } - - get crossPostButton() { - return ( - - - - ); - } - - get reportButton() { - return ( - - ); - } - - get blockButton() { - return ( - - ); - } - - get editButton() { - return ( - - ); - } - - get deleteButton() { - const deleted = this.postView.post.deleted; - const label = !deleted - ? I18NextService.i18n.t("delete") - : I18NextService.i18n.t("restore"); - return ( - - ); - } - get viewSourceButton() { return ( - ); - } - - get featureButtons() { - const featuredCommunity = this.postView.post.featured_community; - const labelCommunity = featuredCommunity - ? I18NextService.i18n.t("unfeature_from_community") - : I18NextService.i18n.t("feature_in_community"); - - const featuredLocal = this.postView.post.featured_local; - const labelLocal = featuredLocal - ? I18NextService.i18n.t("unfeature_from_local") - : I18NextService.i18n.t("feature_in_local"); - return ( - <> -
  • - -
  • -
  • - {amAdmin() && ( - - )} -
  • - - ); - } - - get modBanFromCommunityButton() { - return ( - - ); - } - - get modUnbanFromCommunityButton() { - return ( - - ); - } - - get addModToCommunityButton() { - return ( - - ); - } - - get modBanButton() { - return ( - - ); - } - - get modUnbanButton() { - return ( - - ); - } - - get purgePersonButton() { - return ( - - ); - } - - get purgePostButton() { - return ( - - ); - } - - get toggleAdminButton() { - return ( - - ); - } - - get transferCommunityButton() { - return ( - - ); - } - - get modRemoveButton() { - const removed = this.postView.post.removed; - return ( - - ); - } - - removeAndBanDialogs() { - const post = this.postView; - const purgeTypeText = - this.state.purgeType === PurgeType.Post - ? I18NextService.i18n.t("purge_post") - : `${I18NextService.i18n.t("purge")} ${post.creator.name}`; - return ( - <> - {this.state.showRemoveDialog && ( -
    - - - -
    - )} - {this.state.showConfirmTransferCommunity && ( - <> - - - - - )} - {this.state.showBanDialog && ( -
    -
    - - - - -
    -
    - - -
    -
    -
    - {/* TODO hold off on expires until later */} - {/*
    */} - {/* */} - {/* */} - {/*
    */} -
    - -
    -
    - )} - {this.state.showReportDialog && ( - - )} - {this.state.showPurgeDialog && ( -
    - - - - {this.state.purgeLoading ? ( - - ) : ( - - )} - - )} - - ); - } - mobileThumbnail() { const post = this.postView.post; return post.thumbnail_url || (post.url && isImage(post.url)) ? ( @@ -1366,7 +722,6 @@ export class PostListing extends Component { {this.commentsLine(true)} {this.duplicatesLine()} - {this.removeAndBanDialogs()}

    @@ -1396,7 +751,6 @@ export class PostListing extends Component { {this.createdLine()} {this.commentsLine()} {this.duplicatesLine()} - {this.removeAndBanDialogs()} @@ -1406,15 +760,8 @@ export class PostListing extends Component { ); } - private get myPost(): boolean { - return ( - this.postView.creator.id === - UserService.Instance.myUserInfo?.local_user_view.person.id - ); - } - - handleEditClick(i: PostListing) { - i.setState({ showEdit: true }); + handleEditClick() { + this.setState({ showEdit: true }); } handleEditCancel() { @@ -1424,7 +771,7 @@ export class PostListing extends Component { // The actual editing is done in the receive for post handleEditPost(form: EditPost) { this.setState({ showEdit: false }); - this.props.onPostEdit(form); + return this.props.onPostEdit(form); } handleShare(i: PostListing) { @@ -1436,61 +783,48 @@ export class PostListing extends Component { }); } - handleShowReportDialog(i: PostListing) { - i.setState({ showReportDialog: !i.state.showReportDialog }); - } - - handleReportSubmit(reason: string) { - this.props.onPostReport({ + handleReport(reason: string) { + return this.props.onPostReport({ post_id: this.postView.post.id, reason, }); - - this.setState({ - showReportDialog: false, - }); } - handleBlockPersonClick(i: PostListing) { - i.setState({ blockLoading: true }); - i.props.onBlockPerson({ - person_id: i.postView.creator.id, + handleBlockPerson() { + return this.props.onBlockPerson({ + person_id: this.postView.creator.id, block: true, }); } - handleDeleteClick(i: PostListing) { - i.setState({ deleteLoading: true }); - i.props.onDeletePost({ - post_id: i.postView.post.id, - deleted: !i.postView.post.deleted, + handleDeletePost() { + return this.props.onDeletePost({ + post_id: this.postView.post.id, + deleted: !this.postView.post.deleted, }); } - handleSavePostClick(i: PostListing) { - i.setState({ saveLoading: true }); - i.props.onSavePost({ - post_id: i.postView.post.id, - save: !i.postView.saved, + handleSavePost() { + return this.props.onSavePost({ + post_id: this.postView.post.id, + save: !this.postView.saved, }); } - get crossPostParams(): PostFormParams { - const queryParams: PostFormParams = {}; + get crossPostParams(): CrossPostParams { const { name, url } = this.postView.post; - - queryParams.name = name; + const crossPostParams: CrossPostParams = { name }; if (url) { - queryParams.url = url; + crossPostParams.url = url; } const crossPostBody = this.crossPostBody(); if (crossPostBody) { - queryParams.body = crossPostBody; + crossPostParams.body = crossPostBody; } - return queryParams; + return crossPostParams; } crossPostBody(): string | undefined { @@ -1508,201 +842,124 @@ export class PostListing extends Component { return this.props.showBody || this.state.showBody; } - handleModRemoveShow(i: PostListing) { - i.setState({ - showRemoveDialog: !i.state.showRemoveDialog, - showBanDialog: false, + handleRemove(reason: string) { + return this.props.onRemovePost({ + post_id: this.postView.post.id, + removed: !this.postView.post.removed, + reason, }); } - handleModRemoveReasonChange(i: PostListing, event: any) { - i.setState({ removeReason: event.target.value }); - } - - handleModRemoveDataChange(i: PostListing, event: any) { - i.setState({ removeData: event.target.checked }); - } - - handleModRemoveSubmit(i: PostListing, event: any) { - event.preventDefault(); - i.setState({ removeLoading: true }); - i.props.onRemovePost({ - post_id: i.postView.post.id, - removed: !i.postView.post.removed, - reason: i.state.removeReason, + handleModLock() { + return this.props.onLockPost({ + post_id: this.postView.post.id, + locked: !this.postView.post.locked, }); } - handleModLock(i: PostListing) { - i.setState({ lockLoading: true }); - i.props.onLockPost({ - post_id: i.postView.post.id, - locked: !i.postView.post.locked, - }); - } - - handleModFeaturePostLocal(i: PostListing) { - i.setState({ featureLocalLoading: true }); - i.props.onFeaturePost({ - post_id: i.postView.post.id, - featured: !i.postView.post.featured_local, + handleModFeaturePostLocal() { + return this.props.onFeaturePost({ + post_id: this.postView.post.id, + featured: !this.postView.post.featured_local, feature_type: "Local", }); } - handleModFeaturePostCommunity(i: PostListing) { - i.setState({ featureCommunityLoading: true }); - i.props.onFeaturePost({ - post_id: i.postView.post.id, - featured: !i.postView.post.featured_community, + handleModFeaturePostCommunity() { + return this.props.onFeaturePost({ + post_id: this.postView.post.id, + featured: !this.postView.post.featured_community, feature_type: "Community", }); } - handleModBanFromCommunityShow(i: PostListing) { - i.setState({ - showBanDialog: true, - banType: BanType.Community, - showRemoveDialog: false, + handlePurgePost(reason: string) { + return this.props.onPurgePost({ + post_id: this.postView.post.id, + reason, }); } - handleModBanShow(i: PostListing) { - i.setState({ - showBanDialog: true, - banType: BanType.Site, - showRemoveDialog: false, + handlePurgePerson(reason: string) { + return this.props.onPurgePerson({ + person_id: this.postView.creator.id, + reason, }); } - handlePurgePersonShow(i: PostListing) { - i.setState({ - showPurgeDialog: true, - purgeType: PurgeType.Person, - showRemoveDialog: false, - }); - } + handleModBanFromCommunity({ + daysUntilExpires, + reason, + shouldRemove, + }: BanUpdateForm) { + const { + creator: { id: person_id }, + creator_banned_from_community, + community: { id: community_id }, + } = this.postView; + const ban = !creator_banned_from_community; - handlePurgePostShow(i: PostListing) { - i.setState({ - showPurgeDialog: true, - purgeType: PurgeType.Post, - showRemoveDialog: false, - }); - } - - handlePurgeReasonChange(i: PostListing, event: any) { - i.setState({ purgeReason: event.target.value }); - } - - handlePurgeSubmit(i: PostListing, event: any) { - event.preventDefault(); - i.setState({ purgeLoading: true }); - if (i.state.purgeType === PurgeType.Person) { - i.props.onPurgePerson({ - person_id: i.postView.creator.id, - reason: i.state.purgeReason, - }); - } else if (i.state.purgeType === PurgeType.Post) { - i.props.onPurgePost({ - post_id: i.postView.post.id, - reason: i.state.purgeReason, - }); - } - } - - handleModBanReasonChange(i: PostListing, event: any) { - i.setState({ banReason: event.target.value }); - } - - handleModBanExpireDaysChange(i: PostListing, event: any) { - i.setState({ banExpireDays: event.target.value }); - } - - handleModBanFromCommunitySubmit(i: PostListing, event: any) { - i.setState({ banType: BanType.Community }); - i.handleModBanBothSubmit(i, event); - } - - handleModBanSubmit(i: PostListing, event: any) { - i.setState({ banType: BanType.Site }); - i.handleModBanBothSubmit(i, event); - } - - handleModBanBothSubmit(i: PostListing, event: any) { - event.preventDefault(); - i.setState({ banLoading: true }); - - const ban = !i.postView.creator_banned_from_community; // If its an unban, restore all their data if (ban === false) { - i.setState({ removeData: false }); + shouldRemove = false; } - const person_id = i.postView.creator.id; - const remove_data = i.state.removeData; - const reason = i.state.banReason; - const expires = futureDaysToUnixTime(i.state.banExpireDays); + const expires = futureDaysToUnixTime(daysUntilExpires); - if (i.state.banType === BanType.Community) { - const community_id = i.postView.community.id; - i.props.onBanPersonFromCommunity({ - community_id, - person_id, - ban, - remove_data, - reason, - expires, - }); - } else { - i.props.onBanPerson({ - person_id, - ban, - remove_data, - reason, - expires, - }); + return this.props.onBanPersonFromCommunity({ + community_id, + person_id, + ban, + remove_data: shouldRemove, + reason, + expires, + }); + } + + handleModBanFromSite({ + daysUntilExpires, + reason, + shouldRemove, + }: BanUpdateForm) { + const { + creator: { id: person_id, banned }, + } = this.postView; + const ban = !banned; + + // If its an unban, restore all their data + if (ban === false) { + shouldRemove = false; } - } + const expires = futureDaysToUnixTime(daysUntilExpires); - handleAddModToCommunity(i: PostListing) { - i.setState({ addModLoading: true }); - i.props.onAddModToCommunity({ - community_id: i.postView.community.id, - person_id: i.postView.creator.id, - added: !i.postView.creator_is_moderator, + return this.props.onBanPerson({ + person_id, + ban, + remove_data: shouldRemove, + reason, + expires, }); } - handleAddAdmin(i: PostListing) { - i.setState({ addAdminLoading: true }); - i.props.onAddAdmin({ - person_id: i.postView.creator.id, - added: !i.postView.creator_is_admin, + handleAppointCommunityMod() { + return this.props.onAddModToCommunity({ + community_id: this.postView.community.id, + person_id: this.postView.creator.id, + added: !this.postView.creator_is_moderator, }); } - handleShowConfirmTransferCommunity(i: PostListing) { - i.setState({ showConfirmTransferCommunity: true }); - } - - handleCancelShowConfirmTransferCommunity(i: PostListing) { - i.setState({ showConfirmTransferCommunity: false }); - } - - handleTransferCommunity(i: PostListing) { - i.setState({ transferLoading: true }); - i.props.onTransferCommunity({ - community_id: i.postView.community.id, - person_id: i.postView.creator.id, + handleAppointAdmin() { + return this.props.onAddAdmin({ + person_id: this.postView.creator.id, + added: !this.postView.creator_is_admin, }); } - handleShowConfirmTransferSite(i: PostListing) { - i.setState({ showConfirmTransferSite: true }); - } - - handleCancelShowConfirmTransferSite(i: PostListing) { - i.setState({ showConfirmTransferSite: false }); + handleTransferCommunity() { + return this.props.onTransferCommunity({ + community_id: this.postView.community.id, + person_id: this.postView.creator.id, + }); } handleImageExpandClick(i: PostListing, event: any) { @@ -1722,19 +979,6 @@ export class PostListing extends Component { i.setState({ viewSource: !i.state.viewSource }); } - handleShowAdvanced(i: PostListing) { - i.setState({ showAdvanced: !i.state.showAdvanced }); - setupTippy(); - } - - handleShowMoreMobile(i: PostListing) { - i.setState({ - showMoreMobile: !i.state.showMoreMobile, - showAdvanced: !i.state.showAdvanced, - }); - setupTippy(); - } - handleShowBody(i: PostListing) { i.setState({ showBody: !i.state.showBody }); setupTippy(); @@ -1759,7 +1003,7 @@ export class PostListing extends Component { return `${points} • ${upvotes} • ${downvotes}`; } - get canModOnSelf_(): boolean { + get canModOnSelf(): boolean { return canMod( this.postView.creator.id, this.props.moderators, @@ -1769,7 +1013,7 @@ export class PostListing extends Component { ); } - get canMod_(): boolean { + get canMod(): boolean { return canMod( this.postView.creator.id, this.props.moderators, @@ -1777,7 +1021,7 @@ export class PostListing extends Component { ); } - get canAdmin_(): boolean { + get canAdmin(): boolean { return canAdmin(this.postView.creator.id, this.props.admins); } } diff --git a/src/shared/components/post/post-listings.tsx b/src/shared/components/post/post-listings.tsx index fc77f505..aa350f53 100644 --- a/src/shared/components/post/post-listings.tsx +++ b/src/shared/components/post/post-listings.tsx @@ -15,6 +15,7 @@ import { Language, LockPost, MarkPostAsRead, + PostResponse, PostView, PurgePerson, PurgePost, @@ -24,6 +25,7 @@ import { } from "lemmy-js-client"; import { I18NextService } from "../../services"; import { PostListing } from "./post-listing"; +import { RequestState } from "../../services/HttpService"; interface PostListingsProps { posts: PostView[]; @@ -34,23 +36,23 @@ interface PostListingsProps { enableDownvotes?: boolean; enableNsfw?: boolean; viewOnly?: boolean; - onPostEdit(form: EditPost): void; - onPostVote(form: CreatePostLike): void; - onPostReport(form: CreatePostReport): void; - onBlockPerson(form: BlockPerson): void; - onLockPost(form: LockPost): void; - onDeletePost(form: DeletePost): void; - onRemovePost(form: RemovePost): void; - onSavePost(form: SavePost): void; - onFeaturePost(form: FeaturePost): void; - onPurgePerson(form: PurgePerson): void; - onPurgePost(form: PurgePost): void; - onBanPersonFromCommunity(form: BanFromCommunity): void; - onBanPerson(form: BanPerson): void; - onAddModToCommunity(form: AddModToCommunity): void; - onAddAdmin(form: AddAdmin): void; - onTransferCommunity(form: TransferCommunity): void; - onMarkPostAsRead(form: MarkPostAsRead): void; + onPostEdit(form: EditPost): Promise>; + onPostVote(form: CreatePostLike): Promise>; + onPostReport(form: CreatePostReport): Promise; + onBlockPerson(form: BlockPerson): Promise; + onLockPost(form: LockPost): Promise; + onDeletePost(form: DeletePost): Promise; + onRemovePost(form: RemovePost): Promise; + onSavePost(form: SavePost): Promise; + onFeaturePost(form: FeaturePost): Promise; + onPurgePerson(form: PurgePerson): Promise; + onPurgePost(form: PurgePost): Promise; + onBanPersonFromCommunity(form: BanFromCommunity): Promise; + onBanPerson(form: BanPerson): Promise; + onAddModToCommunity(form: AddModToCommunity): Promise; + onAddAdmin(form: AddAdmin): Promise; + onTransferCommunity(form: TransferCommunity): Promise; + onMarkPostAsRead(form: MarkPostAsRead): Promise; } export class PostListings extends Component { diff --git a/src/shared/components/post/post-report.tsx b/src/shared/components/post/post-report.tsx index db5ec83b..74826268 100644 --- a/src/shared/components/post/post-report.tsx +++ b/src/shared/components/post/post-report.tsx @@ -5,6 +5,7 @@ import { I18NextService } from "../../services"; import { Icon, Spinner } from "../common/icon"; import { PersonListing } from "../person/person-listing"; import { PostListing } from "./post-listing"; +import { EMPTY_REQUEST } from "../../services/HttpService"; interface PostReportProps { report: PostReportView; @@ -72,23 +73,23 @@ export class PostReport extends Component { siteLanguages={[]} hideImage // All of these are unused, since its view only - onPostEdit={() => {}} - onPostVote={() => {}} - onPostReport={() => {}} - onBlockPerson={() => {}} - onLockPost={() => {}} - onDeletePost={() => {}} - onRemovePost={() => {}} - onSavePost={() => {}} - onFeaturePost={() => {}} - onPurgePerson={() => {}} - onPurgePost={() => {}} - onBanPersonFromCommunity={() => {}} - onBanPerson={() => {}} - onAddModToCommunity={() => {}} - onAddAdmin={() => {}} - onTransferCommunity={() => {}} - onMarkPostAsRead={() => {}} + onPostEdit={async () => EMPTY_REQUEST} + onPostVote={async () => EMPTY_REQUEST} + onPostReport={async () => {}} + onBlockPerson={async () => {}} + onLockPost={async () => {}} + onDeletePost={async () => {}} + onRemovePost={async () => {}} + onSavePost={async () => {}} + onFeaturePost={async () => {}} + onPurgePerson={async () => {}} + onPurgePost={async () => {}} + onBanPersonFromCommunity={async () => {}} + onBanPerson={async () => {}} + onAddModToCommunity={async () => {}} + onAddAdmin={async () => {}} + onTransferCommunity={async () => {}} + onMarkPostAsRead={async () => {}} />
    {I18NextService.i18n.t("reporter")}:{" "} diff --git a/src/shared/components/post/post.tsx b/src/shared/components/post/post.tsx index 099f7bcb..3ff6c18e 100644 --- a/src/shared/components/post/post.tsx +++ b/src/shared/components/post/post.tsx @@ -18,7 +18,7 @@ import { restoreScrollPosition, saveScrollPosition, } from "@utils/browser"; -import { debounce, randomStr } from "@utils/helpers"; +import { debounce, getApubName, randomStr } from "@utils/helpers"; import { isImage } from "@utils/media"; import { RouteDataResponse } from "@utils/types"; import autosize from "autosize"; @@ -752,6 +752,11 @@ export class Post extends Component { async handleAddModToCommunity(form: AddModToCommunity) { const addModRes = await HttpService.client.addModToCommunity(form); this.updateModerators(addModRes); + if (addModRes.state === "success") { + toast( + I18NextService.i18n.t(form.added ? "appointed_mod" : "removed_mod"), + ); + } } async handleFollow(form: FollowCommunity) { @@ -837,21 +842,45 @@ export class Post extends Component { async handleDeleteComment(form: DeleteComment) { const deleteCommentRes = await HttpService.client.deleteComment(form); this.findAndUpdateComment(deleteCommentRes); + if (deleteCommentRes.state === "success") { + toast( + I18NextService.i18n.t( + form.deleted ? "deleted_comment" : "undeleted_comment", + ), + ); + } } async handleDeletePost(form: DeletePost) { const deleteRes = await HttpService.client.deletePost(form); this.updatePost(deleteRes); + if (deleteRes.state === "success") { + toast( + I18NextService.i18n.t(form.deleted ? "deleted_post" : "undeleted_post"), + ); + } } async handleRemovePost(form: RemovePost) { const removeRes = await HttpService.client.removePost(form); this.updatePost(removeRes); + if (removeRes.state === "success") { + toast( + I18NextService.i18n.t(form.removed ? "removed_post" : "restored_post"), + ); + } } async handleRemoveComment(form: RemoveComment) { const removeCommentRes = await HttpService.client.removeComment(form); this.findAndUpdateComment(removeCommentRes); + if (removeCommentRes.state === "success") { + toast( + I18NextService.i18n.t( + form.removed ? "removed_comment" : "restored_comment", + ), + ); + } } async handleSaveComment(form: SaveComment) { @@ -867,6 +896,13 @@ export class Post extends Component { async handleFeaturePost(form: FeaturePost) { const featureRes = await HttpService.client.featurePost(form); this.updatePost(featureRes); + if (featureRes.state === "success") { + toast( + I18NextService.i18n.t( + form.featured ? "featured_post" : "unfeatured_post", + ), + ); + } } async handleCommentVote(form: CreateCommentLike) { @@ -877,11 +913,13 @@ export class Post extends Component { async handlePostVote(form: CreatePostLike) { const voteRes = await HttpService.client.likePost(form); this.updatePost(voteRes); + return voteRes; } async handlePostEdit(form: EditPost) { const res = await HttpService.client.editPost(form); this.updatePost(res); + return res; } async handleCommentReport(form: CreateCommentReport) { @@ -901,11 +939,25 @@ export class Post extends Component { async handleLockPost(form: LockPost) { const lockRes = await HttpService.client.lockPost(form); this.updatePost(lockRes); + if (lockRes.state === "success") { + toast( + I18NextService.i18n.t(form.locked ? "locked_post" : "unlocked_post"), + ); + } } async handleDistinguishComment(form: DistinguishComment) { const distinguishRes = await HttpService.client.distinguishComment(form); this.findAndUpdateComment(distinguishRes); + if (distinguishRes.state === "success") { + toast( + I18NextService.i18n.t( + form.distinguished + ? "distinguished_comment" + : "undistinguished_comment", + ), + ); + } } async handleAddAdmin(form: AddAdmin) { @@ -913,6 +965,9 @@ export class Post extends Component { if (addAdminRes.state === "success") { this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s)); + toast( + I18NextService.i18n.t(form.added ? "appointed_admin" : "removed_admin"), + ); } } @@ -920,6 +975,9 @@ export class Post extends Component { const transferCommunityRes = await HttpService.client.transferCommunity(form); this.updateCommunityFull(transferCommunityRes); + if (transferCommunityRes.state === "success") { + toast(I18NextService.i18n.t("transferred_community")); + } } async handleFetchChildren(form: GetComments) { @@ -944,12 +1002,33 @@ export class Post extends Component { async handleBanFromCommunity(form: BanFromCommunity) { const banRes = await HttpService.client.banFromCommunity(form); - this.updateBan(banRes); + this.updateBanFromCommunity(banRes); + if (banRes.state === "success" && this.state.postRes.state === "success") { + toast( + I18NextService.i18n.t( + form.ban ? "banned_from_community" : "unbanned_from_community", + { + user: getApubName(this.state.postRes.data.post_view.creator), + community: getApubName(this.state.postRes.data.post_view.community), + }, + ), + ); + } } async handleBanPerson(form: BanPerson) { const banRes = await HttpService.client.banPerson(form); this.updateBan(banRes); + if (banRes.state === "success" && this.state.postRes.state === "success") { + toast( + I18NextService.i18n.t( + form.ban ? "banned_from_site" : "unbanned_from_site", + { + user: getApubName(this.state.postRes.data.post_view.creator), + }, + ), + ); + } } updateBanFromCommunity(banRes: RequestState) { diff --git a/src/shared/components/private_message/private-message.tsx b/src/shared/components/private_message/private-message.tsx index 035983f8..f2c9ad5f 100644 --- a/src/shared/components/private_message/private-message.tsx +++ b/src/shared/components/private_message/private-message.tsx @@ -14,7 +14,7 @@ import { Icon, Spinner } from "../common/icon"; import { MomentTime } from "../common/moment-time"; import { PersonListing } from "../person/person-listing"; import { PrivateMessageForm } from "./private-message-form"; -import ReportForm from "../common/report-form"; +import ModActionFormModal from "../common/mod-action-form-modal"; interface PrivateMessageState { showReply: boolean; @@ -53,6 +53,7 @@ export class PrivateMessage extends Component< super(props, context); this.handleReplyCancel = this.handleReplyCancel.bind(this); this.handleReportSubmit = this.handleReportSubmit.bind(this); + this.hideReportDialog = this.hideReportDialog.bind(this); } get mine(): boolean { @@ -247,9 +248,12 @@ export class PrivateMessage extends Component<
    )} - {this.state.showReportDialog && ( - - )} + {this.state.showReply && (
    @@ -327,17 +331,21 @@ export class PrivateMessage extends Component< } handleShowReportDialog(i: PrivateMessage) { - i.setState({ showReportDialog: !i.state.showReportDialog }); + i.setState({ showReportDialog: true }); } - handleReportSubmit(reason: string) { + hideReportDialog() { + this.setState({ + showReportDialog: false, + }); + } + + async handleReportSubmit(reason: string) { this.props.onReport({ private_message_id: this.props.private_message_view.private_message.id, reason, }); - this.setState({ - showReportDialog: false, - }); + this.hideReportDialog(); } } diff --git a/src/shared/components/search.tsx b/src/shared/components/search.tsx index 716b2de1..4c099ca1 100644 --- a/src/shared/components/search.tsx +++ b/src/shared/components/search.tsx @@ -704,23 +704,23 @@ export class Search extends Component { siteLanguages={this.state.siteRes.discussion_languages} viewOnly // All of these are unused, since its view only - onPostEdit={() => {}} - onPostVote={() => {}} - onPostReport={() => {}} - onBlockPerson={() => {}} - onLockPost={() => {}} - onDeletePost={() => {}} - onRemovePost={() => {}} - onSavePost={() => {}} - onFeaturePost={() => {}} - onPurgePerson={() => {}} - onPurgePost={() => {}} - onBanPersonFromCommunity={() => {}} - onBanPerson={() => {}} - onAddModToCommunity={() => {}} - onAddAdmin={() => {}} - onTransferCommunity={() => {}} - onMarkPostAsRead={() => {}} + onPostEdit={async () => EMPTY_REQUEST} + onPostVote={async () => EMPTY_REQUEST} + onPostReport={async () => {}} + onBlockPerson={async () => {}} + onLockPost={async () => {}} + onDeletePost={async () => {}} + onRemovePost={async () => {}} + onSavePost={async () => {}} + onFeaturePost={async () => {}} + onPurgePerson={async () => {}} + onPurgePost={async () => {}} + onBanPersonFromCommunity={async () => {}} + onBanPerson={async () => {}} + onAddModToCommunity={async () => {}} + onAddAdmin={async () => {}} + onTransferCommunity={async () => {}} + onMarkPostAsRead={async () => {}} /> )} {i.type_ === "comments" && ( @@ -742,24 +742,24 @@ export class Search extends Component { siteLanguages={this.state.siteRes.discussion_languages} // All of these are unused, since its viewonly finished={new Map()} - onSaveComment={() => {}} - onBlockPerson={() => {}} - onDeleteComment={() => {}} - onRemoveComment={() => {}} - onCommentVote={() => {}} - onCommentReport={() => {}} - onDistinguishComment={() => {}} - onAddModToCommunity={() => {}} - onAddAdmin={() => {}} - onTransferCommunity={() => {}} - onPurgeComment={() => {}} - onPurgePerson={() => {}} + onSaveComment={async () => {}} + onBlockPerson={async () => {}} + onDeleteComment={async () => {}} + onRemoveComment={async () => {}} + onCommentVote={async () => {}} + onCommentReport={async () => {}} + onDistinguishComment={async () => {}} + onAddModToCommunity={async () => {}} + onAddAdmin={async () => {}} + onTransferCommunity={async () => {}} + onPurgeComment={async () => {}} + onPurgePerson={async () => {}} onCommentReplyRead={() => {}} onPersonMentionRead={() => {}} - onBanPersonFromCommunity={() => {}} - onBanPerson={() => {}} - onCreateComment={() => Promise.resolve(EMPTY_REQUEST)} - onEditComment={() => Promise.resolve(EMPTY_REQUEST)} + onBanPersonFromCommunity={async () => {}} + onBanPerson={async () => {}} + onCreateComment={async () => EMPTY_REQUEST} + onEditComment={async () => EMPTY_REQUEST} /> )} {i.type_ === "communities" && ( @@ -803,24 +803,24 @@ export class Search extends Component { siteLanguages={siteRes.discussion_languages} // All of these are unused, since its viewonly finished={new Map()} - onSaveComment={() => {}} - onBlockPerson={() => {}} - onDeleteComment={() => {}} - onRemoveComment={() => {}} - onCommentVote={() => {}} - onCommentReport={() => {}} - onDistinguishComment={() => {}} - onAddModToCommunity={() => {}} - onAddAdmin={() => {}} - onTransferCommunity={() => {}} - onPurgeComment={() => {}} - onPurgePerson={() => {}} + onSaveComment={async () => {}} + onBlockPerson={async () => {}} + onDeleteComment={async () => {}} + onRemoveComment={async () => {}} + onCommentVote={async () => {}} + onCommentReport={async () => {}} + onDistinguishComment={async () => {}} + onAddModToCommunity={async () => {}} + onAddAdmin={async () => {}} + onTransferCommunity={async () => {}} + onPurgeComment={async () => {}} + onPurgePerson={async () => {}} onCommentReplyRead={() => {}} onPersonMentionRead={() => {}} - onBanPersonFromCommunity={() => {}} - onBanPerson={() => {}} - onCreateComment={() => Promise.resolve(EMPTY_REQUEST)} - onEditComment={() => Promise.resolve(EMPTY_REQUEST)} + onBanPersonFromCommunity={async () => {}} + onBanPerson={async () => {}} + onCreateComment={async () => EMPTY_REQUEST} + onEditComment={async () => EMPTY_REQUEST} /> ); } @@ -855,22 +855,22 @@ export class Search extends Component { siteLanguages={siteRes.discussion_languages} viewOnly // All of these are unused, since its view only - onPostEdit={() => {}} - onPostVote={() => {}} - onPostReport={() => {}} - onBlockPerson={() => {}} - onLockPost={() => {}} - onDeletePost={() => {}} - onRemovePost={() => {}} - onSavePost={() => {}} - onFeaturePost={() => {}} - onPurgePerson={() => {}} - onPurgePost={() => {}} - onBanPersonFromCommunity={() => {}} - onBanPerson={() => {}} - onAddModToCommunity={() => {}} - onAddAdmin={() => {}} - onTransferCommunity={() => {}} + onPostEdit={async () => EMPTY_REQUEST} + onPostVote={async () => EMPTY_REQUEST} + onPostReport={async () => {}} + onBlockPerson={async () => {}} + onLockPost={async () => {}} + onDeletePost={async () => {}} + onRemovePost={async () => {}} + onSavePost={async () => {}} + onFeaturePost={async () => {}} + onPurgePerson={async () => {}} + onPurgePost={async () => {}} + onBanPersonFromCommunity={async () => {}} + onBanPerson={async () => {}} + onAddModToCommunity={async () => {}} + onAddAdmin={async () => {}} + onTransferCommunity={async () => {}} onMarkPostAsRead={() => {}} />
    diff --git a/src/shared/utils/app/edit-with.ts b/src/shared/utils/app/edit-with.ts index 9003f761..36f4b074 100644 --- a/src/shared/utils/app/edit-with.ts +++ b/src/shared/utils/app/edit-with.ts @@ -1,13 +1,32 @@ import { WithComment } from "@utils/types"; export default function editWith( - { comment, counts, saved, my_vote }: D, + { + comment, + counts, + saved, + my_vote, + creator_banned_from_community, + creator_blocked, + creator_is_admin, + creator_is_moderator, + }: D, list: L[], ) { return [ ...list.map(c => c.comment.id === comment.id - ? { ...c, comment, counts, saved, my_vote } + ? { + ...c, + comment, + counts, + saved, + my_vote, + creator_banned_from_community, + creator_blocked, + creator_is_admin, + creator_is_moderator, + } : c, ), ]; diff --git a/src/shared/utils/helpers/apub-name.ts b/src/shared/utils/helpers/apub-name.ts new file mode 100644 index 00000000..441226a9 --- /dev/null +++ b/src/shared/utils/helpers/apub-name.ts @@ -0,0 +1,11 @@ +import hostname from "./hostname"; + +export default function getApubName({ + name, + actor_id, +}: { + name: string; + actor_id: string; +}) { + return `${name}@${hostname(actor_id)}`; +} diff --git a/src/shared/utils/helpers/index.ts b/src/shared/utils/helpers/index.ts index c519bfc1..9f862c55 100644 --- a/src/shared/utils/helpers/index.ts +++ b/src/shared/utils/helpers/index.ts @@ -23,6 +23,7 @@ import validInstanceTLD from "./valid-instance-tld"; import validTitle from "./valid-title"; import validURL from "./valid-url"; import dedupByProperty from "./dedup-by-property"; +import getApubName from "./apub-name"; export { capitalizeFirstLetter, @@ -50,4 +51,5 @@ export { validTitle, validURL, dedupByProperty, + getApubName, }; diff --git a/src/shared/utils/roles/am-mod.ts b/src/shared/utils/roles/am-mod.ts index 84ccddaa..46e01913 100644 --- a/src/shared/utils/roles/am-mod.ts +++ b/src/shared/utils/roles/am-mod.ts @@ -6,6 +6,6 @@ export default function amMod( myUserInfo = UserService.Instance.myUserInfo, ): boolean { return myUserInfo - ? myUserInfo.moderates.map(cmv => cmv.community.id).includes(communityId) + ? myUserInfo.moderates.some(cmv => cmv.community.id === communityId) : false; } diff --git a/src/shared/utils/types/cross-post-params.ts b/src/shared/utils/types/cross-post-params.ts new file mode 100644 index 00000000..9eb29b70 --- /dev/null +++ b/src/shared/utils/types/cross-post-params.ts @@ -0,0 +1,5 @@ +export default interface CrossPostParams { + name: string; + url?: string; + body?: string; +} diff --git a/src/shared/utils/types/index.ts b/src/shared/utils/types/index.ts index 2086b966..65926f79 100644 --- a/src/shared/utils/types/index.ts +++ b/src/shared/utils/types/index.ts @@ -6,6 +6,7 @@ import { QueryParams } from "./query-params"; import { RouteDataResponse } from "./route-data-response"; import { ThemeColor } from "./theme-color"; import WithComment from "./with-comment"; +import CrossPostParams from "./cross-post-params"; export { Choice, @@ -16,4 +17,5 @@ export { RouteDataResponse, ThemeColor, WithComment, + CrossPostParams, }; diff --git a/src/shared/utils/types/with-comment.ts b/src/shared/utils/types/with-comment.ts index 703f5e0c..27f3b841 100644 --- a/src/shared/utils/types/with-comment.ts +++ b/src/shared/utils/types/with-comment.ts @@ -5,4 +5,8 @@ export default interface WithComment { counts: CommentAggregates; my_vote?: number; saved: boolean; + creator_is_moderator: boolean; + creator_is_admin: boolean; + creator_blocked: boolean; + creator_banned_from_community: boolean; }