mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-22 20:31:13 +00:00
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 <insomnia-void@protonmail.com>
This commit is contained in:
parent
39f86d421e
commit
8594a30a53
34 changed files with 2381 additions and 2324 deletions
|
@ -1 +1 @@
|
||||||
Subproject commit 45d478e44fb12f0748640cf0416724e42e37d9a6
|
Subproject commit b3343aef72e5a7e5df34cf328b910ed798027270
|
|
@ -90,7 +90,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
display: inline-grid;
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
width: 1em;
|
width: 1em;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 67 KiB |
File diff suppressed because it is too large
Load diff
|
@ -8,6 +8,7 @@ import {
|
||||||
BanPerson,
|
BanPerson,
|
||||||
BlockPerson,
|
BlockPerson,
|
||||||
CommentId,
|
CommentId,
|
||||||
|
CommentResponse,
|
||||||
CommunityModeratorView,
|
CommunityModeratorView,
|
||||||
CreateComment,
|
CreateComment,
|
||||||
CreateCommentLike,
|
CreateCommentLike,
|
||||||
|
@ -28,6 +29,7 @@ import {
|
||||||
} from "lemmy-js-client";
|
} from "lemmy-js-client";
|
||||||
import { CommentNodeI, CommentViewType } from "../../interfaces";
|
import { CommentNodeI, CommentViewType } from "../../interfaces";
|
||||||
import { CommentNode } from "./comment-node";
|
import { CommentNode } from "./comment-node";
|
||||||
|
import { RequestState } from "../../services/HttpService";
|
||||||
|
|
||||||
interface CommentNodesProps {
|
interface CommentNodesProps {
|
||||||
nodes: CommentNodeI[];
|
nodes: CommentNodeI[];
|
||||||
|
@ -49,25 +51,29 @@ interface CommentNodesProps {
|
||||||
isChild?: boolean;
|
isChild?: boolean;
|
||||||
depth?: number;
|
depth?: number;
|
||||||
finished: Map<CommentId, boolean | undefined>;
|
finished: Map<CommentId, boolean | undefined>;
|
||||||
onSaveComment(form: SaveComment): void;
|
onSaveComment(form: SaveComment): Promise<void>;
|
||||||
onCommentReplyRead(form: MarkCommentReplyAsRead): void;
|
onCommentReplyRead(form: MarkCommentReplyAsRead): void;
|
||||||
onPersonMentionRead(form: MarkPersonMentionAsRead): void;
|
onPersonMentionRead(form: MarkPersonMentionAsRead): void;
|
||||||
onCreateComment(form: EditComment | CreateComment): void;
|
onCreateComment(
|
||||||
onEditComment(form: EditComment | CreateComment): void;
|
form: EditComment | CreateComment,
|
||||||
onCommentVote(form: CreateCommentLike): void;
|
): Promise<RequestState<CommentResponse>>;
|
||||||
onBlockPerson(form: BlockPerson): void;
|
onEditComment(
|
||||||
onDeleteComment(form: DeleteComment): void;
|
form: EditComment | CreateComment,
|
||||||
onRemoveComment(form: RemoveComment): void;
|
): Promise<RequestState<CommentResponse>>;
|
||||||
onDistinguishComment(form: DistinguishComment): void;
|
onCommentVote(form: CreateCommentLike): Promise<void>;
|
||||||
onAddModToCommunity(form: AddModToCommunity): void;
|
onBlockPerson(form: BlockPerson): Promise<void>;
|
||||||
onAddAdmin(form: AddAdmin): void;
|
onDeleteComment(form: DeleteComment): Promise<void>;
|
||||||
onBanPersonFromCommunity(form: BanFromCommunity): void;
|
onRemoveComment(form: RemoveComment): Promise<void>;
|
||||||
onBanPerson(form: BanPerson): void;
|
onDistinguishComment(form: DistinguishComment): Promise<void>;
|
||||||
onTransferCommunity(form: TransferCommunity): void;
|
onAddModToCommunity(form: AddModToCommunity): Promise<void>;
|
||||||
|
onAddAdmin(form: AddAdmin): Promise<void>;
|
||||||
|
onBanPersonFromCommunity(form: BanFromCommunity): Promise<void>;
|
||||||
|
onBanPerson(form: BanPerson): Promise<void>;
|
||||||
|
onTransferCommunity(form: TransferCommunity): Promise<void>;
|
||||||
onFetchChildren?(form: GetComments): void;
|
onFetchChildren?(form: GetComments): void;
|
||||||
onCommentReport(form: CreateCommentReport): void;
|
onCommentReport(form: CreateCommentReport): Promise<void>;
|
||||||
onPurgePerson(form: PurgePerson): void;
|
onPurgePerson(form: PurgePerson): Promise<void>;
|
||||||
onPurgeComment(form: PurgeComment): void;
|
onPurgeComment(form: PurgeComment): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CommentNodes extends Component<CommentNodesProps, any> {
|
export class CommentNodes extends Component<CommentNodesProps, any> {
|
||||||
|
|
|
@ -84,23 +84,23 @@ export class CommentReport extends Component<
|
||||||
hideImages
|
hideImages
|
||||||
// All of these are unused, since its viewonly
|
// All of these are unused, since its viewonly
|
||||||
finished={new Map()}
|
finished={new Map()}
|
||||||
onSaveComment={() => {}}
|
onSaveComment={async () => {}}
|
||||||
onBlockPerson={() => {}}
|
onBlockPerson={async () => {}}
|
||||||
onDeleteComment={() => {}}
|
onDeleteComment={async () => {}}
|
||||||
onRemoveComment={() => {}}
|
onRemoveComment={async () => {}}
|
||||||
onCommentVote={() => {}}
|
onCommentVote={async () => {}}
|
||||||
onCommentReport={() => {}}
|
onCommentReport={async () => {}}
|
||||||
onDistinguishComment={() => {}}
|
onDistinguishComment={async () => {}}
|
||||||
onAddModToCommunity={() => {}}
|
onAddModToCommunity={async () => {}}
|
||||||
onAddAdmin={() => {}}
|
onAddAdmin={async () => {}}
|
||||||
onTransferCommunity={() => {}}
|
onTransferCommunity={async () => {}}
|
||||||
onPurgeComment={() => {}}
|
onPurgeComment={async () => {}}
|
||||||
onPurgePerson={() => {}}
|
onPurgePerson={async () => {}}
|
||||||
onCommentReplyRead={() => {}}
|
onCommentReplyRead={() => {}}
|
||||||
onPersonMentionRead={() => {}}
|
onPersonMentionRead={() => {}}
|
||||||
onBanPersonFromCommunity={() => {}}
|
onBanPersonFromCommunity={async () => {}}
|
||||||
onBanPerson={() => {}}
|
onBanPerson={async () => {}}
|
||||||
onCreateComment={() => Promise.resolve(EMPTY_REQUEST)}
|
onCreateComment={async () => Promise.resolve(EMPTY_REQUEST)}
|
||||||
onEditComment={() => Promise.resolve(EMPTY_REQUEST)}
|
onEditComment={() => Promise.resolve(EMPTY_REQUEST)}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
|
|
140
src/shared/components/common/confirmation-modal.tsx
Normal file
140
src/shared/components/common/confirmation-modal.tsx
Normal file
|
@ -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<void>;
|
||||||
|
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<HTMLDivElement>;
|
||||||
|
readonly yesButtonRef: RefObject<HTMLButtonElement>;
|
||||||
|
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 (
|
||||||
|
<div
|
||||||
|
className="modal fade"
|
||||||
|
id="confirmModal"
|
||||||
|
tabIndex={-1}
|
||||||
|
aria-hidden
|
||||||
|
aria-labelledby="#confirmationModalTitle"
|
||||||
|
data-bs-backdrop="static"
|
||||||
|
ref={this.modalDivRef}
|
||||||
|
>
|
||||||
|
<div className="modal-dialog modal-fullscreen-sm-down">
|
||||||
|
<div className="modal-content">
|
||||||
|
<header className="modal-header">
|
||||||
|
<h3 className="modal-title" id="confirmationModalTitle">
|
||||||
|
{I18NextService.i18n.t("confirmation_required")}
|
||||||
|
</h3>
|
||||||
|
</header>
|
||||||
|
<div className="modal-body text-center align-middle text-body">
|
||||||
|
{loading ? (
|
||||||
|
<>
|
||||||
|
<Spinner large />
|
||||||
|
<div>
|
||||||
|
{loadingMessage}
|
||||||
|
<LoadingEllipses />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
message
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<footer className="modal-footer">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-success"
|
||||||
|
onClick={linkEvent(this, handleYes)}
|
||||||
|
ref={this.yesButtonRef}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{I18NextService.i18n.t("yes")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-danger"
|
||||||
|
onClick={onNo}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{I18NextService.i18n.t("no")}
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleShow() {
|
||||||
|
this.yesButtonRef.current?.focus();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<void>;
|
||||||
|
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 (
|
||||||
|
<button
|
||||||
|
className={classNames(
|
||||||
|
"btn btn-link btn-sm",
|
||||||
|
inline
|
||||||
|
? "btn-animate text-muted py-0"
|
||||||
|
: "d-flex align-items-center rounded-0 dropdown-item",
|
||||||
|
)}
|
||||||
|
onClick={linkEvent(this, handleClick)}
|
||||||
|
aria-label={label}
|
||||||
|
data-tippy-content={inline ? label : undefined}
|
||||||
|
disabled={this.state.loading}
|
||||||
|
>
|
||||||
|
{this.state.loading ? (
|
||||||
|
<Spinner />
|
||||||
|
) : (
|
||||||
|
<Icon classes={classNames("me-2", iconClass)} icon={icon} inline />
|
||||||
|
)}
|
||||||
|
{!inline && label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionButton.defaultProps = {
|
||||||
|
inline: false,
|
||||||
|
noLoading: false,
|
||||||
|
};
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { InfernoNode } from "inferno";
|
||||||
|
import ContentActionDropdown, {
|
||||||
|
ContentCommentProps,
|
||||||
|
} from "./content-action-dropdown";
|
||||||
|
|
||||||
|
export default (
|
||||||
|
props: Omit<ContentCommentProps, "type" | "postView">,
|
||||||
|
): InfernoNode => <ContentActionDropdown type="comment" {...props} />;
|
|
@ -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<void>;
|
||||||
|
onEdit: () => void;
|
||||||
|
onDelete: () => Promise<void>;
|
||||||
|
onReport: (reason: string) => Promise<void>;
|
||||||
|
onBlock: () => Promise<void>;
|
||||||
|
onRemove: (reason: string) => Promise<void>;
|
||||||
|
onBanFromCommunity: (form: BanUpdateForm) => Promise<void>;
|
||||||
|
onAppointCommunityMod: () => Promise<void>;
|
||||||
|
onTransferCommunity: () => Promise<void>;
|
||||||
|
onBanFromSite: (form: BanUpdateForm) => Promise<void>;
|
||||||
|
onPurgeContent: (reason: string) => Promise<void>;
|
||||||
|
onPurgeUser: (reason: string) => Promise<void>;
|
||||||
|
onAppointAdmin: () => Promise<void>;
|
||||||
|
moderators?: CommunityModeratorView[];
|
||||||
|
admins?: PersonView[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ContentCommentProps = {
|
||||||
|
type: "comment";
|
||||||
|
commentView: CommentView;
|
||||||
|
onReply: () => void;
|
||||||
|
onDistinguish: () => Promise<void>;
|
||||||
|
} & ContentActionDropdownPropsBase;
|
||||||
|
|
||||||
|
export type ContentPostProps = {
|
||||||
|
type: "post";
|
||||||
|
postView: PostView;
|
||||||
|
crossPostParams: CrossPostParams;
|
||||||
|
onLock: () => Promise<void>;
|
||||||
|
onFeatureLocal: () => Promise<void>;
|
||||||
|
onFeatureCommunity: () => Promise<void>;
|
||||||
|
} & 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" && (
|
||||||
|
<ActionButton
|
||||||
|
onClick={this.props.onReply}
|
||||||
|
icon="reply1"
|
||||||
|
inline
|
||||||
|
label={I18NextService.i18n.t("reply")}
|
||||||
|
noLoading
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<ActionButton
|
||||||
|
onClick={onSave}
|
||||||
|
inline
|
||||||
|
icon="star"
|
||||||
|
label={I18NextService.i18n.t(saved ? "unsave" : "save")}
|
||||||
|
iconClass={classNames({ "text-warning": saved })}
|
||||||
|
/>
|
||||||
|
{type === "post" && (
|
||||||
|
<CrossPostButton {...this.props.crossPostParams!} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="dropdown">
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-link btn-animate text-muted py-0 dropdown-toggle"
|
||||||
|
data-tippy-content={I18NextService.i18n.t("more")}
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls={dropdownId}
|
||||||
|
aria-label={I18NextService.i18n.t("more")}
|
||||||
|
>
|
||||||
|
<Icon icon="more-vertical" inline />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<ul className="dropdown-menu" id={dropdownId}>
|
||||||
|
{this.amCreator ? (
|
||||||
|
<>
|
||||||
|
<li>
|
||||||
|
<ActionButton
|
||||||
|
icon="edit"
|
||||||
|
label={I18NextService.i18n.t("edit")}
|
||||||
|
noLoading
|
||||||
|
onClick={onEdit}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ActionButton
|
||||||
|
onClick={onDelete}
|
||||||
|
icon={deleted ? "undo-trash" : "trash"}
|
||||||
|
label={I18NextService.i18n.t(
|
||||||
|
deleted ? "undelete" : "delete",
|
||||||
|
)}
|
||||||
|
iconClass={`text-${deleted ? "success" : "danger"}`}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{type === "comment" && (
|
||||||
|
<li>
|
||||||
|
<Link
|
||||||
|
className="btn btn-link d-flex align-items-center rounded-0 dropdown-item"
|
||||||
|
to={`/create_private_message/${creator.id}`}
|
||||||
|
title={I18NextService.i18n.t("message")}
|
||||||
|
aria-label={I18NextService.i18n.t("message")}
|
||||||
|
data-tippy-content={I18NextService.i18n.t("message")}
|
||||||
|
>
|
||||||
|
<Icon icon="mail" inline classes="me-2" />
|
||||||
|
{I18NextService.i18n.t("message")}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
<li>
|
||||||
|
<ActionButton
|
||||||
|
icon="flag"
|
||||||
|
label={I18NextService.i18n.t("create_report")}
|
||||||
|
onClick={this.toggleReportDialogShow}
|
||||||
|
noLoading
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ActionButton
|
||||||
|
icon="slash"
|
||||||
|
label={I18NextService.i18n.t("block_user")}
|
||||||
|
onClick={onBlock}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(amMod(community.id) || amAdmin()) && (
|
||||||
|
<>
|
||||||
|
<li>
|
||||||
|
<hr className="dropdown-divider" />
|
||||||
|
</li>
|
||||||
|
{type === "post" && (
|
||||||
|
<>
|
||||||
|
<li>
|
||||||
|
<ActionButton
|
||||||
|
onClick={this.props.onLock}
|
||||||
|
label={I18NextService.i18n.t(
|
||||||
|
locked ? "unlock" : "lock",
|
||||||
|
)}
|
||||||
|
icon={locked ? "unlock" : "lock"}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ActionButton
|
||||||
|
onClick={this.props.onFeatureCommunity}
|
||||||
|
label={I18NextService.i18n.t(
|
||||||
|
this.props.postView.post.featured_community
|
||||||
|
? "unfeature_from_community"
|
||||||
|
: "feature_in_community",
|
||||||
|
)}
|
||||||
|
icon={
|
||||||
|
this.props.postView.post.featured_community
|
||||||
|
? "pin-off"
|
||||||
|
: "pin"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
{amAdmin() && (
|
||||||
|
<li>
|
||||||
|
<ActionButton
|
||||||
|
onClick={this.props.onFeatureLocal}
|
||||||
|
label={I18NextService.i18n.t(
|
||||||
|
this.props.postView.post.featured_local
|
||||||
|
? "unfeature_from_local"
|
||||||
|
: "feature_in_local",
|
||||||
|
)}
|
||||||
|
icon={
|
||||||
|
this.props.postView.post.featured_local
|
||||||
|
? "pin-off"
|
||||||
|
: "pin"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{type === "comment" &&
|
||||||
|
this.amCreator &&
|
||||||
|
(this.canModOnSelf || this.canAdminOnSelf) && (
|
||||||
|
<li>
|
||||||
|
<ActionButton
|
||||||
|
onClick={this.props.onDistinguish}
|
||||||
|
icon={
|
||||||
|
this.props.commentView.comment.distinguished
|
||||||
|
? "shield-off"
|
||||||
|
: "shield"
|
||||||
|
}
|
||||||
|
label={I18NextService.i18n.t(
|
||||||
|
this.props.commentView.comment.distinguished
|
||||||
|
? "undistinguish"
|
||||||
|
: "distinguish",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
{(this.canMod || this.canAdmin) && (
|
||||||
|
<li>
|
||||||
|
<ActionButton
|
||||||
|
label={
|
||||||
|
removed
|
||||||
|
? `${I18NextService.i18n.t(
|
||||||
|
"restore",
|
||||||
|
)} ${I18NextService.i18n.t(
|
||||||
|
type === "post" ? "post" : "comment",
|
||||||
|
)}`
|
||||||
|
: I18NextService.i18n.t(
|
||||||
|
type === "post" ? "remove_post" : "remove_comment",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
icon={removed ? "restore" : "x"}
|
||||||
|
noLoading
|
||||||
|
onClick={this.toggleRemoveShow}
|
||||||
|
iconClass={`text-${removed ? "success" : "danger"}`}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
{this.canMod &&
|
||||||
|
(!creator_is_moderator || canAppointCommunityMod) && (
|
||||||
|
<>
|
||||||
|
<li>
|
||||||
|
<hr className="dropdown-divider" />
|
||||||
|
</li>
|
||||||
|
{!creator_is_moderator && (
|
||||||
|
<li>
|
||||||
|
<ActionButton
|
||||||
|
onClick={this.toggleBanFromCommunityShow}
|
||||||
|
label={I18NextService.i18n.t(
|
||||||
|
creator_banned_from_community
|
||||||
|
? "unban_from_community"
|
||||||
|
: "ban_from_community",
|
||||||
|
)}
|
||||||
|
icon={creator_banned_from_community ? "unban" : "ban"}
|
||||||
|
noLoading
|
||||||
|
iconClass={`text-${
|
||||||
|
creator_banned_from_community ? "success" : "danger"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
{canAppointCommunityMod && (
|
||||||
|
<li>
|
||||||
|
<ActionButton
|
||||||
|
onClick={this.toggleAppointModShow}
|
||||||
|
label={I18NextService.i18n.t(
|
||||||
|
`${
|
||||||
|
creator_is_moderator ? "remove" : "appoint"
|
||||||
|
}_as_mod`,
|
||||||
|
)}
|
||||||
|
icon={creator_is_moderator ? "demote" : "promote"}
|
||||||
|
iconClass={`text-${
|
||||||
|
creator_is_moderator ? "danger" : "success"
|
||||||
|
}`}
|
||||||
|
noLoading
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{(amCommunityCreator(this.id, moderators) || this.canAdmin) &&
|
||||||
|
creator_is_moderator && (
|
||||||
|
<li>
|
||||||
|
<ActionButton
|
||||||
|
label={I18NextService.i18n.t("transfer_community")}
|
||||||
|
onClick={this.toggleTransferCommunityShow}
|
||||||
|
icon="transfer"
|
||||||
|
noLoading
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{this.canAdmin && (showToggleAdmin || !creator_is_admin) && (
|
||||||
|
<>
|
||||||
|
<li>
|
||||||
|
<hr className="dropdown-divider" />
|
||||||
|
</li>
|
||||||
|
{!creator_is_admin && (
|
||||||
|
<>
|
||||||
|
<li>
|
||||||
|
<ActionButton
|
||||||
|
label={I18NextService.i18n.t(
|
||||||
|
creatorBannedFromLocal
|
||||||
|
? "unban_from_site"
|
||||||
|
: "ban_from_site",
|
||||||
|
)}
|
||||||
|
onClick={this.toggleBanFromSiteShow}
|
||||||
|
icon={creatorBannedFromLocal ? "unban" : "ban"}
|
||||||
|
iconClass={`text-${
|
||||||
|
creatorBannedFromLocal ? "success" : "danger"
|
||||||
|
}`}
|
||||||
|
noLoading
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ActionButton
|
||||||
|
label={I18NextService.i18n.t("purge_user")}
|
||||||
|
onClick={this.togglePurgePersonShow}
|
||||||
|
icon="purge"
|
||||||
|
noLoading
|
||||||
|
iconClass="text-danger"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ActionButton
|
||||||
|
label={I18NextService.i18n.t(
|
||||||
|
`purge_${type === "post" ? "post" : "comment"}`,
|
||||||
|
)}
|
||||||
|
onClick={this.togglePurgeContentShow}
|
||||||
|
icon="purge"
|
||||||
|
noLoading
|
||||||
|
iconClass="text-danger"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{showToggleAdmin && (
|
||||||
|
<li>
|
||||||
|
<ActionButton
|
||||||
|
label={I18NextService.i18n.t(
|
||||||
|
`${creator_is_admin ? "remove" : "appoint"}_as_admin`,
|
||||||
|
)}
|
||||||
|
onClick={this.toggleAppointAdminShow}
|
||||||
|
icon={creator_is_admin ? "demote" : "promote"}
|
||||||
|
iconClass={`text-${
|
||||||
|
creator_is_admin ? "danger" : "success"
|
||||||
|
}`}
|
||||||
|
noLoading
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{this.moderationDialogs}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleModDialogShow(
|
||||||
|
dialogType: DialogType,
|
||||||
|
stateOverride: Partial<ContentActionDropdownState> = {},
|
||||||
|
) {
|
||||||
|
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 && (
|
||||||
|
<>
|
||||||
|
<ModActionFormModal
|
||||||
|
onSubmit={this.wrapHandler(onRemove)}
|
||||||
|
modActionType={
|
||||||
|
type === "comment" ? "remove-comment" : "remove-post"
|
||||||
|
}
|
||||||
|
isRemoved={removed}
|
||||||
|
onCancel={this.hideAllDialogs}
|
||||||
|
show={showRemoveDialog}
|
||||||
|
/>
|
||||||
|
<ModActionFormModal
|
||||||
|
onSubmit={this.wrapHandler(
|
||||||
|
banType === BanType.Community
|
||||||
|
? onBanFromCommunity
|
||||||
|
: onBanFromSite,
|
||||||
|
)}
|
||||||
|
modActionType={
|
||||||
|
banType === BanType.Community ? "community-ban" : "site-ban"
|
||||||
|
}
|
||||||
|
creator={creator}
|
||||||
|
onCancel={this.hideAllDialogs}
|
||||||
|
isBanned={
|
||||||
|
banType === BanType.Community
|
||||||
|
? creator_banned_from_community
|
||||||
|
: banType === BanType.Site
|
||||||
|
? creator.banned
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
community={community}
|
||||||
|
show={showBanDialog}
|
||||||
|
/>
|
||||||
|
<ModActionFormModal
|
||||||
|
onSubmit={this.wrapHandler(onReport)}
|
||||||
|
modActionType={
|
||||||
|
type === "comment" ? "report-comment" : "report-post"
|
||||||
|
}
|
||||||
|
onCancel={this.hideAllDialogs}
|
||||||
|
show={showReportDialog}
|
||||||
|
/>
|
||||||
|
<ModActionFormModal
|
||||||
|
onSubmit={this.wrapHandler(
|
||||||
|
purgeType === PurgeType.Person ? onPurgeUser : onPurgeContent,
|
||||||
|
)}
|
||||||
|
modActionType={
|
||||||
|
purgeType === PurgeType.Post
|
||||||
|
? "purge-post"
|
||||||
|
: purgeType === PurgeType.Comment
|
||||||
|
? "purge-comment"
|
||||||
|
: "purge-person"
|
||||||
|
}
|
||||||
|
creator={creator}
|
||||||
|
onCancel={this.hideAllDialogs}
|
||||||
|
show={showPurgeDialog}
|
||||||
|
/>
|
||||||
|
<ConfirmationModal
|
||||||
|
show={showTransferCommunityDialog}
|
||||||
|
message={I18NextService.i18n.t("transfer_community_are_you_sure", {
|
||||||
|
user: getApubName(creator),
|
||||||
|
community: getApubName(community),
|
||||||
|
})}
|
||||||
|
loadingMessage={I18NextService.i18n.t("transferring_community")}
|
||||||
|
onNo={this.hideAllDialogs}
|
||||||
|
onYes={this.wrapHandler(onTransferCommunity)}
|
||||||
|
/>
|
||||||
|
<ConfirmationModal
|
||||||
|
show={showAppointModDialog}
|
||||||
|
message={I18NextService.i18n.t(
|
||||||
|
creator_is_moderator
|
||||||
|
? "remove_as_mod_are_you_sure"
|
||||||
|
: "appoint_as_mod_are_you_sure",
|
||||||
|
{
|
||||||
|
user: getApubName(creator),
|
||||||
|
community: getApubName(creator),
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
loadingMessage={I18NextService.i18n.t(
|
||||||
|
creator_is_moderator ? "removing_mod" : "appointing_mod",
|
||||||
|
)}
|
||||||
|
onNo={this.hideAllDialogs}
|
||||||
|
onYes={this.wrapHandler(onAppointCommunityMod)}
|
||||||
|
/>
|
||||||
|
<ConfirmationModal
|
||||||
|
show={showAppointAdminDialog}
|
||||||
|
message={I18NextService.i18n.t(
|
||||||
|
creator_is_admin
|
||||||
|
? "removing_as_admin_are_you_sure"
|
||||||
|
: "appoint_as_admin_are_you_sure",
|
||||||
|
{
|
||||||
|
user: getApubName(creator),
|
||||||
|
instance: hostname(creator.actor_id),
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
loadingMessage={I18NextService.i18n.t(
|
||||||
|
creator_is_admin ? "removing_admin" : "appointing_admin",
|
||||||
|
)}
|
||||||
|
onNo={this.hideAllDialogs}
|
||||||
|
onYes={this.wrapHandler(onAppointAdmin)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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<void>) {
|
||||||
|
return async (arg?: any) => {
|
||||||
|
await handler(arg);
|
||||||
|
this.hideAllDialogs();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 (
|
||||||
|
<Link
|
||||||
|
className="btn btn-sm btn-link btn-animate text-muted py-0"
|
||||||
|
to={{
|
||||||
|
pathname: "/create_post",
|
||||||
|
state: props,
|
||||||
|
}}
|
||||||
|
title={I18NextService.i18n.t("cross_post")}
|
||||||
|
data-tippy-content={I18NextService.i18n.t("cross_post")}
|
||||||
|
aria-label={I18NextService.i18n.t("cross_post")}
|
||||||
|
>
|
||||||
|
<Icon icon="copy" inline />
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { InfernoNode } from "inferno";
|
||||||
|
import ContentActionDropdown, {
|
||||||
|
ContentPostProps,
|
||||||
|
} from "./content-action-dropdown";
|
||||||
|
|
||||||
|
export default (
|
||||||
|
props: Omit<ContentPostProps, "type" | "commentView">,
|
||||||
|
): InfernoNode => <ContentActionDropdown type="post" {...props} />;
|
464
src/shared/components/common/mod-action-form-modal.tsx
Normal file
464
src/shared/components/common/mod-action-form-modal.tsx
Normal file
|
@ -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<void>;
|
||||||
|
creator: Person;
|
||||||
|
isBanned: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModActionFormModalPropsCommunityBan {
|
||||||
|
modActionType: "community-ban";
|
||||||
|
onSubmit: (form: BanUpdateForm) => Promise<void>;
|
||||||
|
creator: Person;
|
||||||
|
community: Community;
|
||||||
|
isBanned: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModActionFormModalPropsPurgePerson {
|
||||||
|
modActionType: "purge-person";
|
||||||
|
onSubmit: (reason: string) => Promise<void>;
|
||||||
|
creator: Person;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModActionFormModalPropsRemove {
|
||||||
|
modActionType: "remove-post" | "remove-comment";
|
||||||
|
onSubmit: (reason: string) => Promise<void>;
|
||||||
|
isRemoved: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModActionFormModalPropsRest {
|
||||||
|
modActionType:
|
||||||
|
| "report-post"
|
||||||
|
| "report-comment"
|
||||||
|
| "report-message"
|
||||||
|
| "purge-post"
|
||||||
|
| "purge-comment";
|
||||||
|
onSubmit: (reason: string) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<HTMLDivElement>;
|
||||||
|
private reasonRef: RefObject<HTMLInputElement>;
|
||||||
|
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 (
|
||||||
|
<div
|
||||||
|
className={classNames("modal fade", {
|
||||||
|
"modal-lg": this.isBanModal,
|
||||||
|
})}
|
||||||
|
data-bs-backdrop="static"
|
||||||
|
id="moderationModal"
|
||||||
|
tabIndex={-1}
|
||||||
|
aria-hidden
|
||||||
|
aria-labelledby="#moderationModalTitle"
|
||||||
|
ref={this.modalDivRef}
|
||||||
|
>
|
||||||
|
<div className="modal-dialog modal-dialog-centered">
|
||||||
|
<div className="modal-content">
|
||||||
|
<header className="modal-header">
|
||||||
|
<h3 className="modal-title" id="moderationModalTitle">
|
||||||
|
{this.headerText}
|
||||||
|
</h3>
|
||||||
|
</header>
|
||||||
|
<div
|
||||||
|
className={classNames("modal-body text-body", {
|
||||||
|
"text-center": loading,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<>
|
||||||
|
<Spinner large />
|
||||||
|
<div>
|
||||||
|
{this.loadingText}
|
||||||
|
<LoadingEllipses />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<form
|
||||||
|
onSubmit={linkEvent(this, handleSubmit)}
|
||||||
|
className="p-3 w-100 container"
|
||||||
|
id={formId}
|
||||||
|
>
|
||||||
|
<div className="row mb-3">
|
||||||
|
<div
|
||||||
|
className={classNames("col-12", {
|
||||||
|
"col-lg-6 col-xl-7": showExpiresField,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{modActionType.includes("purge") && <PurgeWarning />}
|
||||||
|
<label className="visually-hidden" htmlFor={reasonId}>
|
||||||
|
{I18NextService.i18n.t("reason")}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id={reasonId}
|
||||||
|
className="form-control my-2 my-lg-0"
|
||||||
|
placeholder={I18NextService.i18n.t("reason")}
|
||||||
|
required
|
||||||
|
value={reason}
|
||||||
|
onInput={linkEvent(this, handleReasonChange)}
|
||||||
|
ref={this.reasonRef}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{showExpiresField && (
|
||||||
|
<div className="col-12 col-lg-6 col-xl-5">
|
||||||
|
<label className="visually-hidden" htmlFor={expiresId}>
|
||||||
|
{I18NextService.i18n.t("expires")}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id={expiresId}
|
||||||
|
className="form-control my-2 my-lg-0"
|
||||||
|
placeholder={I18NextService.i18n.t(
|
||||||
|
"days_until_expiration",
|
||||||
|
)}
|
||||||
|
min={1}
|
||||||
|
value={daysUntilExpire}
|
||||||
|
onInput={linkEvent(this, handleExpiryChange)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="row">
|
||||||
|
{this.isBanModal && !isBanned && (
|
||||||
|
<div className="mb-2 col-12 col-lg-6 col-xxl-7">
|
||||||
|
<div className="form-check m2-3">
|
||||||
|
<label
|
||||||
|
className="form-check-label me-3 user-select-none"
|
||||||
|
title={I18NextService.i18n.t("remove_content_more")}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="form-check-input user-select-none"
|
||||||
|
type="checkbox"
|
||||||
|
checked={shouldRemoveData}
|
||||||
|
onChange={linkEvent(this, handleToggleRemove)}
|
||||||
|
/>
|
||||||
|
{I18NextService.i18n.t("remove_content")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="form-check mt-2">
|
||||||
|
<label
|
||||||
|
className="form-check-label"
|
||||||
|
title={I18NextService.i18n.t("remove_content_more")}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
onChange={linkEvent(this, handleTogglePermaBan)}
|
||||||
|
checked={shouldPermaBan}
|
||||||
|
/>
|
||||||
|
{I18NextService.i18n.t("permanently_ban")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<footer className="modal-footer">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-secondary me-3"
|
||||||
|
form={formId}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{this.buttonText}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-light"
|
||||||
|
onClick={onCancel}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{I18NextService.i18n.t("cancel")}
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 (
|
|
||||||
<form
|
|
||||||
className="form-inline"
|
|
||||||
onSubmit={linkEvent(this, handleReportSubmit)}
|
|
||||||
>
|
|
||||||
<label className="visually-hidden" htmlFor={id}>
|
|
||||||
{I18NextService.i18n.t("reason")}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id={id}
|
|
||||||
className="form-control me-2"
|
|
||||||
placeholder={I18NextService.i18n.t("reason")}
|
|
||||||
required
|
|
||||||
value={reason}
|
|
||||||
onInput={linkEvent(this, handleReportReasonChange)}
|
|
||||||
/>
|
|
||||||
<button type="submit" className="btn btn-secondary">
|
|
||||||
{loading ? <Spinner /> : I18NextService.i18n.t("create_report")}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -172,7 +172,7 @@ class RemoteFetchModal extends Component<
|
||||||
aria-hidden
|
aria-hidden
|
||||||
aria-labelledby="#remoteFetchModalTitle"
|
aria-labelledby="#remoteFetchModalTitle"
|
||||||
>
|
>
|
||||||
<div className="modal-dialog modal-fullscreen-sm-down">
|
<div className="modal-dialog modal-dialog-centered modal-fullscreen-sm-down">
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<header className="modal-header">
|
<header className="modal-header">
|
||||||
<h3 className="modal-title" id="remoteFetchModalTitle">
|
<h3 className="modal-title" id="remoteFetchModalTitle">
|
||||||
|
|
|
@ -143,7 +143,7 @@ export default class TotpModal extends Component<
|
||||||
data-bs-backdrop="static"
|
data-bs-backdrop="static"
|
||||||
ref={this.modalDivRef}
|
ref={this.modalDivRef}
|
||||||
>
|
>
|
||||||
<div className="modal-dialog modal-fullscreen-sm-down">
|
<div className="modal-dialog modal-dialog-centered modal-fullscreen-sm-down">
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<header className="modal-header">
|
<header className="modal-header">
|
||||||
<h3 className="modal-title" id="totpModalTitle">
|
<h3 className="modal-title" id="totpModalTitle">
|
||||||
|
|
|
@ -447,7 +447,7 @@ export class Community extends Component<
|
||||||
onAddAdmin={this.handleAddAdmin}
|
onAddAdmin={this.handleAddAdmin}
|
||||||
onTransferCommunity={this.handleTransferCommunity}
|
onTransferCommunity={this.handleTransferCommunity}
|
||||||
onFeaturePost={this.handleFeaturePost}
|
onFeaturePost={this.handleFeaturePost}
|
||||||
onMarkPostAsRead={() => {}}
|
onMarkPostAsRead={async () => {}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -758,11 +758,13 @@ export class Community extends Component<
|
||||||
async handlePostEdit(form: EditPost) {
|
async handlePostEdit(form: EditPost) {
|
||||||
const res = await HttpService.client.editPost(form);
|
const res = await HttpService.client.editPost(form);
|
||||||
this.findAndUpdatePost(res);
|
this.findAndUpdatePost(res);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handlePostVote(form: CreatePostLike) {
|
async handlePostVote(form: CreatePostLike) {
|
||||||
const voteRes = await HttpService.client.likePost(form);
|
const voteRes = await HttpService.client.likePost(form);
|
||||||
this.findAndUpdatePost(voteRes);
|
this.findAndUpdatePost(voteRes);
|
||||||
|
return voteRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCommentReport(form: CreateCommentReport) {
|
async handleCommentReport(form: CreateCommentReport) {
|
||||||
|
|
|
@ -716,7 +716,7 @@ export class Home extends Component<any, HomeState> {
|
||||||
onAddAdmin={this.handleAddAdmin}
|
onAddAdmin={this.handleAddAdmin}
|
||||||
onTransferCommunity={this.handleTransferCommunity}
|
onTransferCommunity={this.handleTransferCommunity}
|
||||||
onFeaturePost={this.handleFeaturePost}
|
onFeaturePost={this.handleFeaturePost}
|
||||||
onMarkPostAsRead={() => {}}
|
onMarkPostAsRead={async () => {}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -970,11 +970,13 @@ export class Home extends Component<any, HomeState> {
|
||||||
async handlePostEdit(form: EditPost) {
|
async handlePostEdit(form: EditPost) {
|
||||||
const res = await HttpService.client.editPost(form);
|
const res = await HttpService.client.editPost(form);
|
||||||
this.findAndUpdatePost(res);
|
this.findAndUpdatePost(res);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handlePostVote(form: CreatePostLike) {
|
async handlePostVote(form: CreatePostLike) {
|
||||||
const voteRes = await HttpService.client.likePost(form);
|
const voteRes = await HttpService.client.likePost(form);
|
||||||
this.findAndUpdatePost(voteRes);
|
this.findAndUpdatePost(voteRes);
|
||||||
|
return voteRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCommentReport(form: CreateCommentReport) {
|
async handleCommentReport(form: CreateCommentReport) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
BanPerson,
|
BanPerson,
|
||||||
BlockPerson,
|
BlockPerson,
|
||||||
CommentId,
|
CommentId,
|
||||||
|
CommentResponse,
|
||||||
CommentView,
|
CommentView,
|
||||||
CreateComment,
|
CreateComment,
|
||||||
CreateCommentLike,
|
CreateCommentLike,
|
||||||
|
@ -27,6 +28,7 @@ import {
|
||||||
MarkPersonMentionAsRead,
|
MarkPersonMentionAsRead,
|
||||||
MarkPostAsRead,
|
MarkPostAsRead,
|
||||||
PersonView,
|
PersonView,
|
||||||
|
PostResponse,
|
||||||
PostView,
|
PostView,
|
||||||
PurgeComment,
|
PurgeComment,
|
||||||
PurgePerson,
|
PurgePerson,
|
||||||
|
@ -43,6 +45,7 @@ import { setupTippy } from "../../tippy";
|
||||||
import { CommentNodes } from "../comment/comment-nodes";
|
import { CommentNodes } from "../comment/comment-nodes";
|
||||||
import { Paginator } from "../common/paginator";
|
import { Paginator } from "../common/paginator";
|
||||||
import { PostListing } from "../post/post-listing";
|
import { PostListing } from "../post/post-listing";
|
||||||
|
import { RequestState } from "../../services/HttpService";
|
||||||
|
|
||||||
interface PersonDetailsProps {
|
interface PersonDetailsProps {
|
||||||
personRes: GetPersonDetailsResponse;
|
personRes: GetPersonDetailsResponse;
|
||||||
|
@ -57,34 +60,34 @@ interface PersonDetailsProps {
|
||||||
enableNsfw: boolean;
|
enableNsfw: boolean;
|
||||||
view: PersonDetailsView;
|
view: PersonDetailsView;
|
||||||
onPageChange(page: number): number | any;
|
onPageChange(page: number): number | any;
|
||||||
onSaveComment(form: SaveComment): void;
|
onSaveComment(form: SaveComment): Promise<void>;
|
||||||
onCommentReplyRead(form: MarkCommentReplyAsRead): void;
|
onCommentReplyRead(form: MarkCommentReplyAsRead): void;
|
||||||
onPersonMentionRead(form: MarkPersonMentionAsRead): void;
|
onPersonMentionRead(form: MarkPersonMentionAsRead): void;
|
||||||
onCreateComment(form: CreateComment): void;
|
onCreateComment(form: CreateComment): Promise<RequestState<CommentResponse>>;
|
||||||
onEditComment(form: EditComment): void;
|
onEditComment(form: EditComment): Promise<RequestState<CommentResponse>>;
|
||||||
onCommentVote(form: CreateCommentLike): void;
|
onCommentVote(form: CreateCommentLike): Promise<void>;
|
||||||
onBlockPerson(form: BlockPerson): void;
|
onBlockPerson(form: BlockPerson): Promise<void>;
|
||||||
onDeleteComment(form: DeleteComment): void;
|
onDeleteComment(form: DeleteComment): Promise<void>;
|
||||||
onRemoveComment(form: RemoveComment): void;
|
onRemoveComment(form: RemoveComment): Promise<void>;
|
||||||
onDistinguishComment(form: DistinguishComment): void;
|
onDistinguishComment(form: DistinguishComment): Promise<void>;
|
||||||
onAddModToCommunity(form: AddModToCommunity): void;
|
onAddModToCommunity(form: AddModToCommunity): Promise<void>;
|
||||||
onAddAdmin(form: AddAdmin): void;
|
onAddAdmin(form: AddAdmin): Promise<void>;
|
||||||
onBanPersonFromCommunity(form: BanFromCommunity): void;
|
onBanPersonFromCommunity(form: BanFromCommunity): Promise<void>;
|
||||||
onBanPerson(form: BanPerson): void;
|
onBanPerson(form: BanPerson): Promise<void>;
|
||||||
onTransferCommunity(form: TransferCommunity): void;
|
onTransferCommunity(form: TransferCommunity): Promise<void>;
|
||||||
onFetchChildren?(form: GetComments): void;
|
onFetchChildren?(form: GetComments): void;
|
||||||
onCommentReport(form: CreateCommentReport): void;
|
onCommentReport(form: CreateCommentReport): Promise<void>;
|
||||||
onPurgePerson(form: PurgePerson): void;
|
onPurgePerson(form: PurgePerson): Promise<void>;
|
||||||
onPurgeComment(form: PurgeComment): void;
|
onPurgeComment(form: PurgeComment): Promise<void>;
|
||||||
onPostEdit(form: EditPost): void;
|
onPostEdit(form: EditPost): Promise<RequestState<PostResponse>>;
|
||||||
onPostVote(form: CreatePostLike): void;
|
onPostVote(form: CreatePostLike): Promise<RequestState<PostResponse>>;
|
||||||
onPostReport(form: CreatePostReport): void;
|
onPostReport(form: CreatePostReport): Promise<void>;
|
||||||
onLockPost(form: LockPost): void;
|
onLockPost(form: LockPost): Promise<void>;
|
||||||
onDeletePost(form: DeletePost): void;
|
onDeletePost(form: DeletePost): Promise<void>;
|
||||||
onRemovePost(form: RemovePost): void;
|
onRemovePost(form: RemovePost): Promise<void>;
|
||||||
onSavePost(form: SavePost): void;
|
onSavePost(form: SavePost): Promise<void>;
|
||||||
onFeaturePost(form: FeaturePost): void;
|
onFeaturePost(form: FeaturePost): Promise<void>;
|
||||||
onPurgePost(form: PurgePost): void;
|
onPurgePost(form: PurgePost): Promise<void>;
|
||||||
onMarkPostAsRead(form: MarkPostAsRead): void;
|
onMarkPostAsRead(form: MarkPostAsRead): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -897,11 +897,13 @@ export class Profile extends Component<
|
||||||
async handlePostVote(form: CreatePostLike) {
|
async handlePostVote(form: CreatePostLike) {
|
||||||
const voteRes = await HttpService.client.likePost(form);
|
const voteRes = await HttpService.client.likePost(form);
|
||||||
this.findAndUpdatePost(voteRes);
|
this.findAndUpdatePost(voteRes);
|
||||||
|
return voteRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handlePostEdit(form: EditPost) {
|
async handlePostEdit(form: EditPost) {
|
||||||
const res = await HttpService.client.editPost(form);
|
const res = await HttpService.client.editPost(form);
|
||||||
this.findAndUpdatePost(res);
|
this.findAndUpdatePost(res);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCommentReport(form: CreateCommentReport) {
|
async handleCommentReport(form: CreateCommentReport) {
|
||||||
|
|
|
@ -444,23 +444,23 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
siteLanguages={this.props.siteLanguages}
|
siteLanguages={this.props.siteLanguages}
|
||||||
viewOnly
|
viewOnly
|
||||||
// All of these are unused, since its view only
|
// All of these are unused, since its view only
|
||||||
onPostEdit={() => {}}
|
onPostEdit={async () => EMPTY_REQUEST}
|
||||||
onPostVote={() => {}}
|
onPostVote={async () => EMPTY_REQUEST}
|
||||||
onPostReport={() => {}}
|
onPostReport={async () => {}}
|
||||||
onBlockPerson={() => {}}
|
onBlockPerson={async () => {}}
|
||||||
onLockPost={() => {}}
|
onLockPost={async () => {}}
|
||||||
onDeletePost={() => {}}
|
onDeletePost={async () => {}}
|
||||||
onRemovePost={() => {}}
|
onRemovePost={async () => {}}
|
||||||
onSavePost={() => {}}
|
onSavePost={async () => {}}
|
||||||
onFeaturePost={() => {}}
|
onFeaturePost={async () => {}}
|
||||||
onPurgePerson={() => {}}
|
onPurgePerson={async () => {}}
|
||||||
onPurgePost={() => {}}
|
onPurgePost={async () => {}}
|
||||||
onBanPersonFromCommunity={() => {}}
|
onBanPersonFromCommunity={async () => {}}
|
||||||
onBanPerson={() => {}}
|
onBanPerson={async () => {}}
|
||||||
onAddModToCommunity={() => {}}
|
onAddModToCommunity={async () => {}}
|
||||||
onAddAdmin={() => {}}
|
onAddAdmin={async () => {}}
|
||||||
onTransferCommunity={() => {}}
|
onTransferCommunity={async () => {}}
|
||||||
onMarkPostAsRead={() => {}}
|
onMarkPostAsRead={async () => {}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -615,23 +615,23 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
siteLanguages={this.props.siteLanguages}
|
siteLanguages={this.props.siteLanguages}
|
||||||
viewOnly
|
viewOnly
|
||||||
// All of these are unused, since its view only
|
// All of these are unused, since its view only
|
||||||
onPostEdit={() => {}}
|
onPostEdit={async () => EMPTY_REQUEST}
|
||||||
onPostVote={() => {}}
|
onPostVote={async () => EMPTY_REQUEST}
|
||||||
onPostReport={() => {}}
|
onPostReport={async () => {}}
|
||||||
onBlockPerson={() => {}}
|
onBlockPerson={async () => {}}
|
||||||
onLockPost={() => {}}
|
onLockPost={async () => {}}
|
||||||
onDeletePost={() => {}}
|
onDeletePost={async () => {}}
|
||||||
onRemovePost={() => {}}
|
onRemovePost={async () => {}}
|
||||||
onSavePost={() => {}}
|
onSavePost={async () => {}}
|
||||||
onFeaturePost={() => {}}
|
onFeaturePost={async () => {}}
|
||||||
onPurgePerson={() => {}}
|
onPurgePerson={async () => {}}
|
||||||
onPurgePost={() => {}}
|
onPurgePost={async () => {}}
|
||||||
onBanPersonFromCommunity={() => {}}
|
onBanPersonFromCommunity={async () => {}}
|
||||||
onBanPerson={() => {}}
|
onBanPerson={async () => {}}
|
||||||
onAddModToCommunity={() => {}}
|
onAddModToCommunity={async () => {}}
|
||||||
onAddAdmin={() => {}}
|
onAddAdmin={async () => {}}
|
||||||
onTransferCommunity={() => {}}
|
onTransferCommunity={async () => {}}
|
||||||
onMarkPostAsRead={() => {}}
|
onMarkPostAsRead={async () => {}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -15,6 +15,7 @@ import {
|
||||||
Language,
|
Language,
|
||||||
LockPost,
|
LockPost,
|
||||||
MarkPostAsRead,
|
MarkPostAsRead,
|
||||||
|
PostResponse,
|
||||||
PostView,
|
PostView,
|
||||||
PurgePerson,
|
PurgePerson,
|
||||||
PurgePost,
|
PurgePost,
|
||||||
|
@ -24,6 +25,7 @@ import {
|
||||||
} from "lemmy-js-client";
|
} from "lemmy-js-client";
|
||||||
import { I18NextService } from "../../services";
|
import { I18NextService } from "../../services";
|
||||||
import { PostListing } from "./post-listing";
|
import { PostListing } from "./post-listing";
|
||||||
|
import { RequestState } from "../../services/HttpService";
|
||||||
|
|
||||||
interface PostListingsProps {
|
interface PostListingsProps {
|
||||||
posts: PostView[];
|
posts: PostView[];
|
||||||
|
@ -34,23 +36,23 @@ interface PostListingsProps {
|
||||||
enableDownvotes?: boolean;
|
enableDownvotes?: boolean;
|
||||||
enableNsfw?: boolean;
|
enableNsfw?: boolean;
|
||||||
viewOnly?: boolean;
|
viewOnly?: boolean;
|
||||||
onPostEdit(form: EditPost): void;
|
onPostEdit(form: EditPost): Promise<RequestState<PostResponse>>;
|
||||||
onPostVote(form: CreatePostLike): void;
|
onPostVote(form: CreatePostLike): Promise<RequestState<PostResponse>>;
|
||||||
onPostReport(form: CreatePostReport): void;
|
onPostReport(form: CreatePostReport): Promise<void>;
|
||||||
onBlockPerson(form: BlockPerson): void;
|
onBlockPerson(form: BlockPerson): Promise<void>;
|
||||||
onLockPost(form: LockPost): void;
|
onLockPost(form: LockPost): Promise<void>;
|
||||||
onDeletePost(form: DeletePost): void;
|
onDeletePost(form: DeletePost): Promise<void>;
|
||||||
onRemovePost(form: RemovePost): void;
|
onRemovePost(form: RemovePost): Promise<void>;
|
||||||
onSavePost(form: SavePost): void;
|
onSavePost(form: SavePost): Promise<void>;
|
||||||
onFeaturePost(form: FeaturePost): void;
|
onFeaturePost(form: FeaturePost): Promise<void>;
|
||||||
onPurgePerson(form: PurgePerson): void;
|
onPurgePerson(form: PurgePerson): Promise<void>;
|
||||||
onPurgePost(form: PurgePost): void;
|
onPurgePost(form: PurgePost): Promise<void>;
|
||||||
onBanPersonFromCommunity(form: BanFromCommunity): void;
|
onBanPersonFromCommunity(form: BanFromCommunity): Promise<void>;
|
||||||
onBanPerson(form: BanPerson): void;
|
onBanPerson(form: BanPerson): Promise<void>;
|
||||||
onAddModToCommunity(form: AddModToCommunity): void;
|
onAddModToCommunity(form: AddModToCommunity): Promise<void>;
|
||||||
onAddAdmin(form: AddAdmin): void;
|
onAddAdmin(form: AddAdmin): Promise<void>;
|
||||||
onTransferCommunity(form: TransferCommunity): void;
|
onTransferCommunity(form: TransferCommunity): Promise<void>;
|
||||||
onMarkPostAsRead(form: MarkPostAsRead): void;
|
onMarkPostAsRead(form: MarkPostAsRead): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PostListings extends Component<PostListingsProps, any> {
|
export class PostListings extends Component<PostListingsProps, any> {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { I18NextService } from "../../services";
|
||||||
import { Icon, Spinner } from "../common/icon";
|
import { Icon, Spinner } from "../common/icon";
|
||||||
import { PersonListing } from "../person/person-listing";
|
import { PersonListing } from "../person/person-listing";
|
||||||
import { PostListing } from "./post-listing";
|
import { PostListing } from "./post-listing";
|
||||||
|
import { EMPTY_REQUEST } from "../../services/HttpService";
|
||||||
|
|
||||||
interface PostReportProps {
|
interface PostReportProps {
|
||||||
report: PostReportView;
|
report: PostReportView;
|
||||||
|
@ -72,23 +73,23 @@ export class PostReport extends Component<PostReportProps, PostReportState> {
|
||||||
siteLanguages={[]}
|
siteLanguages={[]}
|
||||||
hideImage
|
hideImage
|
||||||
// All of these are unused, since its view only
|
// All of these are unused, since its view only
|
||||||
onPostEdit={() => {}}
|
onPostEdit={async () => EMPTY_REQUEST}
|
||||||
onPostVote={() => {}}
|
onPostVote={async () => EMPTY_REQUEST}
|
||||||
onPostReport={() => {}}
|
onPostReport={async () => {}}
|
||||||
onBlockPerson={() => {}}
|
onBlockPerson={async () => {}}
|
||||||
onLockPost={() => {}}
|
onLockPost={async () => {}}
|
||||||
onDeletePost={() => {}}
|
onDeletePost={async () => {}}
|
||||||
onRemovePost={() => {}}
|
onRemovePost={async () => {}}
|
||||||
onSavePost={() => {}}
|
onSavePost={async () => {}}
|
||||||
onFeaturePost={() => {}}
|
onFeaturePost={async () => {}}
|
||||||
onPurgePerson={() => {}}
|
onPurgePerson={async () => {}}
|
||||||
onPurgePost={() => {}}
|
onPurgePost={async () => {}}
|
||||||
onBanPersonFromCommunity={() => {}}
|
onBanPersonFromCommunity={async () => {}}
|
||||||
onBanPerson={() => {}}
|
onBanPerson={async () => {}}
|
||||||
onAddModToCommunity={() => {}}
|
onAddModToCommunity={async () => {}}
|
||||||
onAddAdmin={() => {}}
|
onAddAdmin={async () => {}}
|
||||||
onTransferCommunity={() => {}}
|
onTransferCommunity={async () => {}}
|
||||||
onMarkPostAsRead={() => {}}
|
onMarkPostAsRead={async () => {}}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
{I18NextService.i18n.t("reporter")}:{" "}
|
{I18NextService.i18n.t("reporter")}:{" "}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {
|
||||||
restoreScrollPosition,
|
restoreScrollPosition,
|
||||||
saveScrollPosition,
|
saveScrollPosition,
|
||||||
} from "@utils/browser";
|
} from "@utils/browser";
|
||||||
import { debounce, randomStr } from "@utils/helpers";
|
import { debounce, getApubName, randomStr } from "@utils/helpers";
|
||||||
import { isImage } from "@utils/media";
|
import { isImage } from "@utils/media";
|
||||||
import { RouteDataResponse } from "@utils/types";
|
import { RouteDataResponse } from "@utils/types";
|
||||||
import autosize from "autosize";
|
import autosize from "autosize";
|
||||||
|
@ -752,6 +752,11 @@ export class Post extends Component<any, PostState> {
|
||||||
async handleAddModToCommunity(form: AddModToCommunity) {
|
async handleAddModToCommunity(form: AddModToCommunity) {
|
||||||
const addModRes = await HttpService.client.addModToCommunity(form);
|
const addModRes = await HttpService.client.addModToCommunity(form);
|
||||||
this.updateModerators(addModRes);
|
this.updateModerators(addModRes);
|
||||||
|
if (addModRes.state === "success") {
|
||||||
|
toast(
|
||||||
|
I18NextService.i18n.t(form.added ? "appointed_mod" : "removed_mod"),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleFollow(form: FollowCommunity) {
|
async handleFollow(form: FollowCommunity) {
|
||||||
|
@ -837,21 +842,45 @@ export class Post extends Component<any, PostState> {
|
||||||
async handleDeleteComment(form: DeleteComment) {
|
async handleDeleteComment(form: DeleteComment) {
|
||||||
const deleteCommentRes = await HttpService.client.deleteComment(form);
|
const deleteCommentRes = await HttpService.client.deleteComment(form);
|
||||||
this.findAndUpdateComment(deleteCommentRes);
|
this.findAndUpdateComment(deleteCommentRes);
|
||||||
|
if (deleteCommentRes.state === "success") {
|
||||||
|
toast(
|
||||||
|
I18NextService.i18n.t(
|
||||||
|
form.deleted ? "deleted_comment" : "undeleted_comment",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDeletePost(form: DeletePost) {
|
async handleDeletePost(form: DeletePost) {
|
||||||
const deleteRes = await HttpService.client.deletePost(form);
|
const deleteRes = await HttpService.client.deletePost(form);
|
||||||
this.updatePost(deleteRes);
|
this.updatePost(deleteRes);
|
||||||
|
if (deleteRes.state === "success") {
|
||||||
|
toast(
|
||||||
|
I18NextService.i18n.t(form.deleted ? "deleted_post" : "undeleted_post"),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleRemovePost(form: RemovePost) {
|
async handleRemovePost(form: RemovePost) {
|
||||||
const removeRes = await HttpService.client.removePost(form);
|
const removeRes = await HttpService.client.removePost(form);
|
||||||
this.updatePost(removeRes);
|
this.updatePost(removeRes);
|
||||||
|
if (removeRes.state === "success") {
|
||||||
|
toast(
|
||||||
|
I18NextService.i18n.t(form.removed ? "removed_post" : "restored_post"),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleRemoveComment(form: RemoveComment) {
|
async handleRemoveComment(form: RemoveComment) {
|
||||||
const removeCommentRes = await HttpService.client.removeComment(form);
|
const removeCommentRes = await HttpService.client.removeComment(form);
|
||||||
this.findAndUpdateComment(removeCommentRes);
|
this.findAndUpdateComment(removeCommentRes);
|
||||||
|
if (removeCommentRes.state === "success") {
|
||||||
|
toast(
|
||||||
|
I18NextService.i18n.t(
|
||||||
|
form.removed ? "removed_comment" : "restored_comment",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSaveComment(form: SaveComment) {
|
async handleSaveComment(form: SaveComment) {
|
||||||
|
@ -867,6 +896,13 @@ export class Post extends Component<any, PostState> {
|
||||||
async handleFeaturePost(form: FeaturePost) {
|
async handleFeaturePost(form: FeaturePost) {
|
||||||
const featureRes = await HttpService.client.featurePost(form);
|
const featureRes = await HttpService.client.featurePost(form);
|
||||||
this.updatePost(featureRes);
|
this.updatePost(featureRes);
|
||||||
|
if (featureRes.state === "success") {
|
||||||
|
toast(
|
||||||
|
I18NextService.i18n.t(
|
||||||
|
form.featured ? "featured_post" : "unfeatured_post",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCommentVote(form: CreateCommentLike) {
|
async handleCommentVote(form: CreateCommentLike) {
|
||||||
|
@ -877,11 +913,13 @@ export class Post extends Component<any, PostState> {
|
||||||
async handlePostVote(form: CreatePostLike) {
|
async handlePostVote(form: CreatePostLike) {
|
||||||
const voteRes = await HttpService.client.likePost(form);
|
const voteRes = await HttpService.client.likePost(form);
|
||||||
this.updatePost(voteRes);
|
this.updatePost(voteRes);
|
||||||
|
return voteRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handlePostEdit(form: EditPost) {
|
async handlePostEdit(form: EditPost) {
|
||||||
const res = await HttpService.client.editPost(form);
|
const res = await HttpService.client.editPost(form);
|
||||||
this.updatePost(res);
|
this.updatePost(res);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCommentReport(form: CreateCommentReport) {
|
async handleCommentReport(form: CreateCommentReport) {
|
||||||
|
@ -901,11 +939,25 @@ export class Post extends Component<any, PostState> {
|
||||||
async handleLockPost(form: LockPost) {
|
async handleLockPost(form: LockPost) {
|
||||||
const lockRes = await HttpService.client.lockPost(form);
|
const lockRes = await HttpService.client.lockPost(form);
|
||||||
this.updatePost(lockRes);
|
this.updatePost(lockRes);
|
||||||
|
if (lockRes.state === "success") {
|
||||||
|
toast(
|
||||||
|
I18NextService.i18n.t(form.locked ? "locked_post" : "unlocked_post"),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDistinguishComment(form: DistinguishComment) {
|
async handleDistinguishComment(form: DistinguishComment) {
|
||||||
const distinguishRes = await HttpService.client.distinguishComment(form);
|
const distinguishRes = await HttpService.client.distinguishComment(form);
|
||||||
this.findAndUpdateComment(distinguishRes);
|
this.findAndUpdateComment(distinguishRes);
|
||||||
|
if (distinguishRes.state === "success") {
|
||||||
|
toast(
|
||||||
|
I18NextService.i18n.t(
|
||||||
|
form.distinguished
|
||||||
|
? "distinguished_comment"
|
||||||
|
: "undistinguished_comment",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleAddAdmin(form: AddAdmin) {
|
async handleAddAdmin(form: AddAdmin) {
|
||||||
|
@ -913,6 +965,9 @@ export class Post extends Component<any, PostState> {
|
||||||
|
|
||||||
if (addAdminRes.state === "success") {
|
if (addAdminRes.state === "success") {
|
||||||
this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
|
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<any, PostState> {
|
||||||
const transferCommunityRes =
|
const transferCommunityRes =
|
||||||
await HttpService.client.transferCommunity(form);
|
await HttpService.client.transferCommunity(form);
|
||||||
this.updateCommunityFull(transferCommunityRes);
|
this.updateCommunityFull(transferCommunityRes);
|
||||||
|
if (transferCommunityRes.state === "success") {
|
||||||
|
toast(I18NextService.i18n.t("transferred_community"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleFetchChildren(form: GetComments) {
|
async handleFetchChildren(form: GetComments) {
|
||||||
|
@ -944,12 +1002,33 @@ export class Post extends Component<any, PostState> {
|
||||||
|
|
||||||
async handleBanFromCommunity(form: BanFromCommunity) {
|
async handleBanFromCommunity(form: BanFromCommunity) {
|
||||||
const banRes = await HttpService.client.banFromCommunity(form);
|
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) {
|
async handleBanPerson(form: BanPerson) {
|
||||||
const banRes = await HttpService.client.banPerson(form);
|
const banRes = await HttpService.client.banPerson(form);
|
||||||
this.updateBan(banRes);
|
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<BanFromCommunityResponse>) {
|
updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { Icon, Spinner } from "../common/icon";
|
||||||
import { MomentTime } from "../common/moment-time";
|
import { MomentTime } from "../common/moment-time";
|
||||||
import { PersonListing } from "../person/person-listing";
|
import { PersonListing } from "../person/person-listing";
|
||||||
import { PrivateMessageForm } from "./private-message-form";
|
import { PrivateMessageForm } from "./private-message-form";
|
||||||
import ReportForm from "../common/report-form";
|
import ModActionFormModal from "../common/mod-action-form-modal";
|
||||||
|
|
||||||
interface PrivateMessageState {
|
interface PrivateMessageState {
|
||||||
showReply: boolean;
|
showReply: boolean;
|
||||||
|
@ -53,6 +53,7 @@ export class PrivateMessage extends Component<
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.handleReplyCancel = this.handleReplyCancel.bind(this);
|
this.handleReplyCancel = this.handleReplyCancel.bind(this);
|
||||||
this.handleReportSubmit = this.handleReportSubmit.bind(this);
|
this.handleReportSubmit = this.handleReportSubmit.bind(this);
|
||||||
|
this.hideReportDialog = this.hideReportDialog.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get mine(): boolean {
|
get mine(): boolean {
|
||||||
|
@ -247,9 +248,12 @@ export class PrivateMessage extends Component<
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{this.state.showReportDialog && (
|
<ModActionFormModal
|
||||||
<ReportForm onSubmit={this.handleReportSubmit} />
|
onSubmit={this.handleReportSubmit}
|
||||||
)}
|
modActionType="report-message"
|
||||||
|
onCancel={this.hideReportDialog}
|
||||||
|
show={this.state.showReportDialog}
|
||||||
|
/>
|
||||||
{this.state.showReply && (
|
{this.state.showReply && (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
|
@ -327,17 +331,21 @@ export class PrivateMessage extends Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
handleShowReportDialog(i: PrivateMessage) {
|
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({
|
this.props.onReport({
|
||||||
private_message_id: this.props.private_message_view.private_message.id,
|
private_message_id: this.props.private_message_view.private_message.id,
|
||||||
reason,
|
reason,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
this.hideReportDialog();
|
||||||
showReportDialog: false,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -704,23 +704,23 @@ export class Search extends Component<any, SearchState> {
|
||||||
siteLanguages={this.state.siteRes.discussion_languages}
|
siteLanguages={this.state.siteRes.discussion_languages}
|
||||||
viewOnly
|
viewOnly
|
||||||
// All of these are unused, since its view only
|
// All of these are unused, since its view only
|
||||||
onPostEdit={() => {}}
|
onPostEdit={async () => EMPTY_REQUEST}
|
||||||
onPostVote={() => {}}
|
onPostVote={async () => EMPTY_REQUEST}
|
||||||
onPostReport={() => {}}
|
onPostReport={async () => {}}
|
||||||
onBlockPerson={() => {}}
|
onBlockPerson={async () => {}}
|
||||||
onLockPost={() => {}}
|
onLockPost={async () => {}}
|
||||||
onDeletePost={() => {}}
|
onDeletePost={async () => {}}
|
||||||
onRemovePost={() => {}}
|
onRemovePost={async () => {}}
|
||||||
onSavePost={() => {}}
|
onSavePost={async () => {}}
|
||||||
onFeaturePost={() => {}}
|
onFeaturePost={async () => {}}
|
||||||
onPurgePerson={() => {}}
|
onPurgePerson={async () => {}}
|
||||||
onPurgePost={() => {}}
|
onPurgePost={async () => {}}
|
||||||
onBanPersonFromCommunity={() => {}}
|
onBanPersonFromCommunity={async () => {}}
|
||||||
onBanPerson={() => {}}
|
onBanPerson={async () => {}}
|
||||||
onAddModToCommunity={() => {}}
|
onAddModToCommunity={async () => {}}
|
||||||
onAddAdmin={() => {}}
|
onAddAdmin={async () => {}}
|
||||||
onTransferCommunity={() => {}}
|
onTransferCommunity={async () => {}}
|
||||||
onMarkPostAsRead={() => {}}
|
onMarkPostAsRead={async () => {}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{i.type_ === "comments" && (
|
{i.type_ === "comments" && (
|
||||||
|
@ -742,24 +742,24 @@ export class Search extends Component<any, SearchState> {
|
||||||
siteLanguages={this.state.siteRes.discussion_languages}
|
siteLanguages={this.state.siteRes.discussion_languages}
|
||||||
// All of these are unused, since its viewonly
|
// All of these are unused, since its viewonly
|
||||||
finished={new Map()}
|
finished={new Map()}
|
||||||
onSaveComment={() => {}}
|
onSaveComment={async () => {}}
|
||||||
onBlockPerson={() => {}}
|
onBlockPerson={async () => {}}
|
||||||
onDeleteComment={() => {}}
|
onDeleteComment={async () => {}}
|
||||||
onRemoveComment={() => {}}
|
onRemoveComment={async () => {}}
|
||||||
onCommentVote={() => {}}
|
onCommentVote={async () => {}}
|
||||||
onCommentReport={() => {}}
|
onCommentReport={async () => {}}
|
||||||
onDistinguishComment={() => {}}
|
onDistinguishComment={async () => {}}
|
||||||
onAddModToCommunity={() => {}}
|
onAddModToCommunity={async () => {}}
|
||||||
onAddAdmin={() => {}}
|
onAddAdmin={async () => {}}
|
||||||
onTransferCommunity={() => {}}
|
onTransferCommunity={async () => {}}
|
||||||
onPurgeComment={() => {}}
|
onPurgeComment={async () => {}}
|
||||||
onPurgePerson={() => {}}
|
onPurgePerson={async () => {}}
|
||||||
onCommentReplyRead={() => {}}
|
onCommentReplyRead={() => {}}
|
||||||
onPersonMentionRead={() => {}}
|
onPersonMentionRead={() => {}}
|
||||||
onBanPersonFromCommunity={() => {}}
|
onBanPersonFromCommunity={async () => {}}
|
||||||
onBanPerson={() => {}}
|
onBanPerson={async () => {}}
|
||||||
onCreateComment={() => Promise.resolve(EMPTY_REQUEST)}
|
onCreateComment={async () => EMPTY_REQUEST}
|
||||||
onEditComment={() => Promise.resolve(EMPTY_REQUEST)}
|
onEditComment={async () => EMPTY_REQUEST}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{i.type_ === "communities" && (
|
{i.type_ === "communities" && (
|
||||||
|
@ -803,24 +803,24 @@ export class Search extends Component<any, SearchState> {
|
||||||
siteLanguages={siteRes.discussion_languages}
|
siteLanguages={siteRes.discussion_languages}
|
||||||
// All of these are unused, since its viewonly
|
// All of these are unused, since its viewonly
|
||||||
finished={new Map()}
|
finished={new Map()}
|
||||||
onSaveComment={() => {}}
|
onSaveComment={async () => {}}
|
||||||
onBlockPerson={() => {}}
|
onBlockPerson={async () => {}}
|
||||||
onDeleteComment={() => {}}
|
onDeleteComment={async () => {}}
|
||||||
onRemoveComment={() => {}}
|
onRemoveComment={async () => {}}
|
||||||
onCommentVote={() => {}}
|
onCommentVote={async () => {}}
|
||||||
onCommentReport={() => {}}
|
onCommentReport={async () => {}}
|
||||||
onDistinguishComment={() => {}}
|
onDistinguishComment={async () => {}}
|
||||||
onAddModToCommunity={() => {}}
|
onAddModToCommunity={async () => {}}
|
||||||
onAddAdmin={() => {}}
|
onAddAdmin={async () => {}}
|
||||||
onTransferCommunity={() => {}}
|
onTransferCommunity={async () => {}}
|
||||||
onPurgeComment={() => {}}
|
onPurgeComment={async () => {}}
|
||||||
onPurgePerson={() => {}}
|
onPurgePerson={async () => {}}
|
||||||
onCommentReplyRead={() => {}}
|
onCommentReplyRead={() => {}}
|
||||||
onPersonMentionRead={() => {}}
|
onPersonMentionRead={() => {}}
|
||||||
onBanPersonFromCommunity={() => {}}
|
onBanPersonFromCommunity={async () => {}}
|
||||||
onBanPerson={() => {}}
|
onBanPerson={async () => {}}
|
||||||
onCreateComment={() => Promise.resolve(EMPTY_REQUEST)}
|
onCreateComment={async () => EMPTY_REQUEST}
|
||||||
onEditComment={() => Promise.resolve(EMPTY_REQUEST)}
|
onEditComment={async () => EMPTY_REQUEST}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -855,22 +855,22 @@ export class Search extends Component<any, SearchState> {
|
||||||
siteLanguages={siteRes.discussion_languages}
|
siteLanguages={siteRes.discussion_languages}
|
||||||
viewOnly
|
viewOnly
|
||||||
// All of these are unused, since its view only
|
// All of these are unused, since its view only
|
||||||
onPostEdit={() => {}}
|
onPostEdit={async () => EMPTY_REQUEST}
|
||||||
onPostVote={() => {}}
|
onPostVote={async () => EMPTY_REQUEST}
|
||||||
onPostReport={() => {}}
|
onPostReport={async () => {}}
|
||||||
onBlockPerson={() => {}}
|
onBlockPerson={async () => {}}
|
||||||
onLockPost={() => {}}
|
onLockPost={async () => {}}
|
||||||
onDeletePost={() => {}}
|
onDeletePost={async () => {}}
|
||||||
onRemovePost={() => {}}
|
onRemovePost={async () => {}}
|
||||||
onSavePost={() => {}}
|
onSavePost={async () => {}}
|
||||||
onFeaturePost={() => {}}
|
onFeaturePost={async () => {}}
|
||||||
onPurgePerson={() => {}}
|
onPurgePerson={async () => {}}
|
||||||
onPurgePost={() => {}}
|
onPurgePost={async () => {}}
|
||||||
onBanPersonFromCommunity={() => {}}
|
onBanPersonFromCommunity={async () => {}}
|
||||||
onBanPerson={() => {}}
|
onBanPerson={async () => {}}
|
||||||
onAddModToCommunity={() => {}}
|
onAddModToCommunity={async () => {}}
|
||||||
onAddAdmin={() => {}}
|
onAddAdmin={async () => {}}
|
||||||
onTransferCommunity={() => {}}
|
onTransferCommunity={async () => {}}
|
||||||
onMarkPostAsRead={() => {}}
|
onMarkPostAsRead={() => {}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,13 +1,32 @@
|
||||||
import { WithComment } from "@utils/types";
|
import { WithComment } from "@utils/types";
|
||||||
|
|
||||||
export default function editWith<D extends WithComment, L extends WithComment>(
|
export default function editWith<D extends WithComment, L extends WithComment>(
|
||||||
{ 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[],
|
list: L[],
|
||||||
) {
|
) {
|
||||||
return [
|
return [
|
||||||
...list.map(c =>
|
...list.map(c =>
|
||||||
c.comment.id === comment.id
|
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,
|
: c,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
11
src/shared/utils/helpers/apub-name.ts
Normal file
11
src/shared/utils/helpers/apub-name.ts
Normal file
|
@ -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)}`;
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import validInstanceTLD from "./valid-instance-tld";
|
||||||
import validTitle from "./valid-title";
|
import validTitle from "./valid-title";
|
||||||
import validURL from "./valid-url";
|
import validURL from "./valid-url";
|
||||||
import dedupByProperty from "./dedup-by-property";
|
import dedupByProperty from "./dedup-by-property";
|
||||||
|
import getApubName from "./apub-name";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
capitalizeFirstLetter,
|
capitalizeFirstLetter,
|
||||||
|
@ -50,4 +51,5 @@ export {
|
||||||
validTitle,
|
validTitle,
|
||||||
validURL,
|
validURL,
|
||||||
dedupByProperty,
|
dedupByProperty,
|
||||||
|
getApubName,
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,6 +6,6 @@ export default function amMod(
|
||||||
myUserInfo = UserService.Instance.myUserInfo,
|
myUserInfo = UserService.Instance.myUserInfo,
|
||||||
): boolean {
|
): boolean {
|
||||||
return myUserInfo
|
return myUserInfo
|
||||||
? myUserInfo.moderates.map(cmv => cmv.community.id).includes(communityId)
|
? myUserInfo.moderates.some(cmv => cmv.community.id === communityId)
|
||||||
: false;
|
: false;
|
||||||
}
|
}
|
||||||
|
|
5
src/shared/utils/types/cross-post-params.ts
Normal file
5
src/shared/utils/types/cross-post-params.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export default interface CrossPostParams {
|
||||||
|
name: string;
|
||||||
|
url?: string;
|
||||||
|
body?: string;
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import { QueryParams } from "./query-params";
|
||||||
import { RouteDataResponse } from "./route-data-response";
|
import { RouteDataResponse } from "./route-data-response";
|
||||||
import { ThemeColor } from "./theme-color";
|
import { ThemeColor } from "./theme-color";
|
||||||
import WithComment from "./with-comment";
|
import WithComment from "./with-comment";
|
||||||
|
import CrossPostParams from "./cross-post-params";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Choice,
|
Choice,
|
||||||
|
@ -16,4 +17,5 @@ export {
|
||||||
RouteDataResponse,
|
RouteDataResponse,
|
||||||
ThemeColor,
|
ThemeColor,
|
||||||
WithComment,
|
WithComment,
|
||||||
|
CrossPostParams,
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,4 +5,8 @@ export default interface WithComment {
|
||||||
counts: CommentAggregates;
|
counts: CommentAggregates;
|
||||||
my_vote?: number;
|
my_vote?: number;
|
||||||
saved: boolean;
|
saved: boolean;
|
||||||
|
creator_is_moderator: boolean;
|
||||||
|
creator_is_admin: boolean;
|
||||||
|
creator_blocked: boolean;
|
||||||
|
creator_banned_from_community: boolean;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue