mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-25 22:01: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 {
|
||||
display: inline-grid;
|
||||
display: inline-flex;
|
||||
width: 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,
|
||||
BlockPerson,
|
||||
CommentId,
|
||||
CommentResponse,
|
||||
CommunityModeratorView,
|
||||
CreateComment,
|
||||
CreateCommentLike,
|
||||
|
@ -28,6 +29,7 @@ import {
|
|||
} from "lemmy-js-client";
|
||||
import { CommentNodeI, CommentViewType } from "../../interfaces";
|
||||
import { CommentNode } from "./comment-node";
|
||||
import { RequestState } from "../../services/HttpService";
|
||||
|
||||
interface CommentNodesProps {
|
||||
nodes: CommentNodeI[];
|
||||
|
@ -49,25 +51,29 @@ interface CommentNodesProps {
|
|||
isChild?: boolean;
|
||||
depth?: number;
|
||||
finished: Map<CommentId, boolean | undefined>;
|
||||
onSaveComment(form: SaveComment): void;
|
||||
onSaveComment(form: SaveComment): Promise<void>;
|
||||
onCommentReplyRead(form: MarkCommentReplyAsRead): void;
|
||||
onPersonMentionRead(form: MarkPersonMentionAsRead): void;
|
||||
onCreateComment(form: EditComment | CreateComment): void;
|
||||
onEditComment(form: EditComment | CreateComment): void;
|
||||
onCommentVote(form: CreateCommentLike): void;
|
||||
onBlockPerson(form: BlockPerson): void;
|
||||
onDeleteComment(form: DeleteComment): void;
|
||||
onRemoveComment(form: RemoveComment): void;
|
||||
onDistinguishComment(form: DistinguishComment): void;
|
||||
onAddModToCommunity(form: AddModToCommunity): void;
|
||||
onAddAdmin(form: AddAdmin): void;
|
||||
onBanPersonFromCommunity(form: BanFromCommunity): void;
|
||||
onBanPerson(form: BanPerson): void;
|
||||
onTransferCommunity(form: TransferCommunity): void;
|
||||
onCreateComment(
|
||||
form: EditComment | CreateComment,
|
||||
): Promise<RequestState<CommentResponse>>;
|
||||
onEditComment(
|
||||
form: EditComment | CreateComment,
|
||||
): Promise<RequestState<CommentResponse>>;
|
||||
onCommentVote(form: CreateCommentLike): Promise<void>;
|
||||
onBlockPerson(form: BlockPerson): Promise<void>;
|
||||
onDeleteComment(form: DeleteComment): Promise<void>;
|
||||
onRemoveComment(form: RemoveComment): Promise<void>;
|
||||
onDistinguishComment(form: DistinguishComment): Promise<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;
|
||||
onCommentReport(form: CreateCommentReport): void;
|
||||
onPurgePerson(form: PurgePerson): void;
|
||||
onPurgeComment(form: PurgeComment): void;
|
||||
onCommentReport(form: CreateCommentReport): Promise<void>;
|
||||
onPurgePerson(form: PurgePerson): Promise<void>;
|
||||
onPurgeComment(form: PurgeComment): Promise<void>;
|
||||
}
|
||||
|
||||
export class CommentNodes extends Component<CommentNodesProps, any> {
|
||||
|
|
|
@ -84,23 +84,23 @@ export class CommentReport extends Component<
|
|||
hideImages
|
||||
// All of these are unused, since its viewonly
|
||||
finished={new Map()}
|
||||
onSaveComment={() => {}}
|
||||
onBlockPerson={() => {}}
|
||||
onDeleteComment={() => {}}
|
||||
onRemoveComment={() => {}}
|
||||
onCommentVote={() => {}}
|
||||
onCommentReport={() => {}}
|
||||
onDistinguishComment={() => {}}
|
||||
onAddModToCommunity={() => {}}
|
||||
onAddAdmin={() => {}}
|
||||
onTransferCommunity={() => {}}
|
||||
onPurgeComment={() => {}}
|
||||
onPurgePerson={() => {}}
|
||||
onSaveComment={async () => {}}
|
||||
onBlockPerson={async () => {}}
|
||||
onDeleteComment={async () => {}}
|
||||
onRemoveComment={async () => {}}
|
||||
onCommentVote={async () => {}}
|
||||
onCommentReport={async () => {}}
|
||||
onDistinguishComment={async () => {}}
|
||||
onAddModToCommunity={async () => {}}
|
||||
onAddAdmin={async () => {}}
|
||||
onTransferCommunity={async () => {}}
|
||||
onPurgeComment={async () => {}}
|
||||
onPurgePerson={async () => {}}
|
||||
onCommentReplyRead={() => {}}
|
||||
onPersonMentionRead={() => {}}
|
||||
onBanPersonFromCommunity={() => {}}
|
||||
onBanPerson={() => {}}
|
||||
onCreateComment={() => Promise.resolve(EMPTY_REQUEST)}
|
||||
onBanPersonFromCommunity={async () => {}}
|
||||
onBanPerson={async () => {}}
|
||||
onCreateComment={async () => Promise.resolve(EMPTY_REQUEST)}
|
||||
onEditComment={() => Promise.resolve(EMPTY_REQUEST)}
|
||||
/>
|
||||
<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-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">
|
||||
<header className="modal-header">
|
||||
<h3 className="modal-title" id="remoteFetchModalTitle">
|
||||
|
|
|
@ -143,7 +143,7 @@ export default class TotpModal extends Component<
|
|||
data-bs-backdrop="static"
|
||||
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">
|
||||
<header className="modal-header">
|
||||
<h3 className="modal-title" id="totpModalTitle">
|
||||
|
|
|
@ -447,7 +447,7 @@ export class Community extends Component<
|
|||
onAddAdmin={this.handleAddAdmin}
|
||||
onTransferCommunity={this.handleTransferCommunity}
|
||||
onFeaturePost={this.handleFeaturePost}
|
||||
onMarkPostAsRead={() => {}}
|
||||
onMarkPostAsRead={async () => {}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -758,11 +758,13 @@ export class Community extends Component<
|
|||
async handlePostEdit(form: EditPost) {
|
||||
const res = await HttpService.client.editPost(form);
|
||||
this.findAndUpdatePost(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
async handlePostVote(form: CreatePostLike) {
|
||||
const voteRes = await HttpService.client.likePost(form);
|
||||
this.findAndUpdatePost(voteRes);
|
||||
return voteRes;
|
||||
}
|
||||
|
||||
async handleCommentReport(form: CreateCommentReport) {
|
||||
|
|
|
@ -716,7 +716,7 @@ export class Home extends Component<any, HomeState> {
|
|||
onAddAdmin={this.handleAddAdmin}
|
||||
onTransferCommunity={this.handleTransferCommunity}
|
||||
onFeaturePost={this.handleFeaturePost}
|
||||
onMarkPostAsRead={() => {}}
|
||||
onMarkPostAsRead={async () => {}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -970,11 +970,13 @@ export class Home extends Component<any, HomeState> {
|
|||
async handlePostEdit(form: EditPost) {
|
||||
const res = await HttpService.client.editPost(form);
|
||||
this.findAndUpdatePost(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
async handlePostVote(form: CreatePostLike) {
|
||||
const voteRes = await HttpService.client.likePost(form);
|
||||
this.findAndUpdatePost(voteRes);
|
||||
return voteRes;
|
||||
}
|
||||
|
||||
async handleCommentReport(form: CreateCommentReport) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
BanPerson,
|
||||
BlockPerson,
|
||||
CommentId,
|
||||
CommentResponse,
|
||||
CommentView,
|
||||
CreateComment,
|
||||
CreateCommentLike,
|
||||
|
@ -27,6 +28,7 @@ import {
|
|||
MarkPersonMentionAsRead,
|
||||
MarkPostAsRead,
|
||||
PersonView,
|
||||
PostResponse,
|
||||
PostView,
|
||||
PurgeComment,
|
||||
PurgePerson,
|
||||
|
@ -43,6 +45,7 @@ import { setupTippy } from "../../tippy";
|
|||
import { CommentNodes } from "../comment/comment-nodes";
|
||||
import { Paginator } from "../common/paginator";
|
||||
import { PostListing } from "../post/post-listing";
|
||||
import { RequestState } from "../../services/HttpService";
|
||||
|
||||
interface PersonDetailsProps {
|
||||
personRes: GetPersonDetailsResponse;
|
||||
|
@ -57,34 +60,34 @@ interface PersonDetailsProps {
|
|||
enableNsfw: boolean;
|
||||
view: PersonDetailsView;
|
||||
onPageChange(page: number): number | any;
|
||||
onSaveComment(form: SaveComment): void;
|
||||
onSaveComment(form: SaveComment): Promise<void>;
|
||||
onCommentReplyRead(form: MarkCommentReplyAsRead): void;
|
||||
onPersonMentionRead(form: MarkPersonMentionAsRead): void;
|
||||
onCreateComment(form: CreateComment): void;
|
||||
onEditComment(form: EditComment): void;
|
||||
onCommentVote(form: CreateCommentLike): void;
|
||||
onBlockPerson(form: BlockPerson): void;
|
||||
onDeleteComment(form: DeleteComment): void;
|
||||
onRemoveComment(form: RemoveComment): void;
|
||||
onDistinguishComment(form: DistinguishComment): void;
|
||||
onAddModToCommunity(form: AddModToCommunity): void;
|
||||
onAddAdmin(form: AddAdmin): void;
|
||||
onBanPersonFromCommunity(form: BanFromCommunity): void;
|
||||
onBanPerson(form: BanPerson): void;
|
||||
onTransferCommunity(form: TransferCommunity): void;
|
||||
onCreateComment(form: CreateComment): Promise<RequestState<CommentResponse>>;
|
||||
onEditComment(form: EditComment): Promise<RequestState<CommentResponse>>;
|
||||
onCommentVote(form: CreateCommentLike): Promise<void>;
|
||||
onBlockPerson(form: BlockPerson): Promise<void>;
|
||||
onDeleteComment(form: DeleteComment): Promise<void>;
|
||||
onRemoveComment(form: RemoveComment): Promise<void>;
|
||||
onDistinguishComment(form: DistinguishComment): Promise<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;
|
||||
onCommentReport(form: CreateCommentReport): void;
|
||||
onPurgePerson(form: PurgePerson): void;
|
||||
onPurgeComment(form: PurgeComment): void;
|
||||
onPostEdit(form: EditPost): void;
|
||||
onPostVote(form: CreatePostLike): void;
|
||||
onPostReport(form: CreatePostReport): void;
|
||||
onLockPost(form: LockPost): void;
|
||||
onDeletePost(form: DeletePost): void;
|
||||
onRemovePost(form: RemovePost): void;
|
||||
onSavePost(form: SavePost): void;
|
||||
onFeaturePost(form: FeaturePost): void;
|
||||
onPurgePost(form: PurgePost): void;
|
||||
onCommentReport(form: CreateCommentReport): Promise<void>;
|
||||
onPurgePerson(form: PurgePerson): Promise<void>;
|
||||
onPurgeComment(form: PurgeComment): Promise<void>;
|
||||
onPostEdit(form: EditPost): Promise<RequestState<PostResponse>>;
|
||||
onPostVote(form: CreatePostLike): Promise<RequestState<PostResponse>>;
|
||||
onPostReport(form: CreatePostReport): Promise<void>;
|
||||
onLockPost(form: LockPost): Promise<void>;
|
||||
onDeletePost(form: DeletePost): Promise<void>;
|
||||
onRemovePost(form: RemovePost): Promise<void>;
|
||||
onSavePost(form: SavePost): Promise<void>;
|
||||
onFeaturePost(form: FeaturePost): Promise<void>;
|
||||
onPurgePost(form: PurgePost): Promise<void>;
|
||||
onMarkPostAsRead(form: MarkPostAsRead): void;
|
||||
}
|
||||
|
||||
|
|
|
@ -897,11 +897,13 @@ export class Profile extends Component<
|
|||
async handlePostVote(form: CreatePostLike) {
|
||||
const voteRes = await HttpService.client.likePost(form);
|
||||
this.findAndUpdatePost(voteRes);
|
||||
return voteRes;
|
||||
}
|
||||
|
||||
async handlePostEdit(form: EditPost) {
|
||||
const res = await HttpService.client.editPost(form);
|
||||
this.findAndUpdatePost(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
async handleCommentReport(form: CreateCommentReport) {
|
||||
|
|
|
@ -444,23 +444,23 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
siteLanguages={this.props.siteLanguages}
|
||||
viewOnly
|
||||
// All of these are unused, since its view only
|
||||
onPostEdit={() => {}}
|
||||
onPostVote={() => {}}
|
||||
onPostReport={() => {}}
|
||||
onBlockPerson={() => {}}
|
||||
onLockPost={() => {}}
|
||||
onDeletePost={() => {}}
|
||||
onRemovePost={() => {}}
|
||||
onSavePost={() => {}}
|
||||
onFeaturePost={() => {}}
|
||||
onPurgePerson={() => {}}
|
||||
onPurgePost={() => {}}
|
||||
onBanPersonFromCommunity={() => {}}
|
||||
onBanPerson={() => {}}
|
||||
onAddModToCommunity={() => {}}
|
||||
onAddAdmin={() => {}}
|
||||
onTransferCommunity={() => {}}
|
||||
onMarkPostAsRead={() => {}}
|
||||
onPostEdit={async () => EMPTY_REQUEST}
|
||||
onPostVote={async () => EMPTY_REQUEST}
|
||||
onPostReport={async () => {}}
|
||||
onBlockPerson={async () => {}}
|
||||
onLockPost={async () => {}}
|
||||
onDeletePost={async () => {}}
|
||||
onRemovePost={async () => {}}
|
||||
onSavePost={async () => {}}
|
||||
onFeaturePost={async () => {}}
|
||||
onPurgePerson={async () => {}}
|
||||
onPurgePost={async () => {}}
|
||||
onBanPersonFromCommunity={async () => {}}
|
||||
onBanPerson={async () => {}}
|
||||
onAddModToCommunity={async () => {}}
|
||||
onAddAdmin={async () => {}}
|
||||
onTransferCommunity={async () => {}}
|
||||
onMarkPostAsRead={async () => {}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
@ -615,23 +615,23 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
siteLanguages={this.props.siteLanguages}
|
||||
viewOnly
|
||||
// All of these are unused, since its view only
|
||||
onPostEdit={() => {}}
|
||||
onPostVote={() => {}}
|
||||
onPostReport={() => {}}
|
||||
onBlockPerson={() => {}}
|
||||
onLockPost={() => {}}
|
||||
onDeletePost={() => {}}
|
||||
onRemovePost={() => {}}
|
||||
onSavePost={() => {}}
|
||||
onFeaturePost={() => {}}
|
||||
onPurgePerson={() => {}}
|
||||
onPurgePost={() => {}}
|
||||
onBanPersonFromCommunity={() => {}}
|
||||
onBanPerson={() => {}}
|
||||
onAddModToCommunity={() => {}}
|
||||
onAddAdmin={() => {}}
|
||||
onTransferCommunity={() => {}}
|
||||
onMarkPostAsRead={() => {}}
|
||||
onPostEdit={async () => EMPTY_REQUEST}
|
||||
onPostVote={async () => EMPTY_REQUEST}
|
||||
onPostReport={async () => {}}
|
||||
onBlockPerson={async () => {}}
|
||||
onLockPost={async () => {}}
|
||||
onDeletePost={async () => {}}
|
||||
onRemovePost={async () => {}}
|
||||
onSavePost={async () => {}}
|
||||
onFeaturePost={async () => {}}
|
||||
onPurgePerson={async () => {}}
|
||||
onPurgePost={async () => {}}
|
||||
onBanPersonFromCommunity={async () => {}}
|
||||
onBanPerson={async () => {}}
|
||||
onAddModToCommunity={async () => {}}
|
||||
onAddAdmin={async () => {}}
|
||||
onTransferCommunity={async () => {}}
|
||||
onMarkPostAsRead={async () => {}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -15,6 +15,7 @@ import {
|
|||
Language,
|
||||
LockPost,
|
||||
MarkPostAsRead,
|
||||
PostResponse,
|
||||
PostView,
|
||||
PurgePerson,
|
||||
PurgePost,
|
||||
|
@ -24,6 +25,7 @@ import {
|
|||
} from "lemmy-js-client";
|
||||
import { I18NextService } from "../../services";
|
||||
import { PostListing } from "./post-listing";
|
||||
import { RequestState } from "../../services/HttpService";
|
||||
|
||||
interface PostListingsProps {
|
||||
posts: PostView[];
|
||||
|
@ -34,23 +36,23 @@ interface PostListingsProps {
|
|||
enableDownvotes?: boolean;
|
||||
enableNsfw?: boolean;
|
||||
viewOnly?: boolean;
|
||||
onPostEdit(form: EditPost): void;
|
||||
onPostVote(form: CreatePostLike): void;
|
||||
onPostReport(form: CreatePostReport): void;
|
||||
onBlockPerson(form: BlockPerson): void;
|
||||
onLockPost(form: LockPost): void;
|
||||
onDeletePost(form: DeletePost): void;
|
||||
onRemovePost(form: RemovePost): void;
|
||||
onSavePost(form: SavePost): void;
|
||||
onFeaturePost(form: FeaturePost): void;
|
||||
onPurgePerson(form: PurgePerson): void;
|
||||
onPurgePost(form: PurgePost): void;
|
||||
onBanPersonFromCommunity(form: BanFromCommunity): void;
|
||||
onBanPerson(form: BanPerson): void;
|
||||
onAddModToCommunity(form: AddModToCommunity): void;
|
||||
onAddAdmin(form: AddAdmin): void;
|
||||
onTransferCommunity(form: TransferCommunity): void;
|
||||
onMarkPostAsRead(form: MarkPostAsRead): void;
|
||||
onPostEdit(form: EditPost): Promise<RequestState<PostResponse>>;
|
||||
onPostVote(form: CreatePostLike): Promise<RequestState<PostResponse>>;
|
||||
onPostReport(form: CreatePostReport): Promise<void>;
|
||||
onBlockPerson(form: BlockPerson): Promise<void>;
|
||||
onLockPost(form: LockPost): Promise<void>;
|
||||
onDeletePost(form: DeletePost): Promise<void>;
|
||||
onRemovePost(form: RemovePost): Promise<void>;
|
||||
onSavePost(form: SavePost): Promise<void>;
|
||||
onFeaturePost(form: FeaturePost): Promise<void>;
|
||||
onPurgePerson(form: PurgePerson): Promise<void>;
|
||||
onPurgePost(form: PurgePost): Promise<void>;
|
||||
onBanPersonFromCommunity(form: BanFromCommunity): Promise<void>;
|
||||
onBanPerson(form: BanPerson): Promise<void>;
|
||||
onAddModToCommunity(form: AddModToCommunity): Promise<void>;
|
||||
onAddAdmin(form: AddAdmin): Promise<void>;
|
||||
onTransferCommunity(form: TransferCommunity): Promise<void>;
|
||||
onMarkPostAsRead(form: MarkPostAsRead): Promise<void>;
|
||||
}
|
||||
|
||||
export class PostListings extends Component<PostListingsProps, any> {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { I18NextService } from "../../services";
|
|||
import { Icon, Spinner } from "../common/icon";
|
||||
import { PersonListing } from "../person/person-listing";
|
||||
import { PostListing } from "./post-listing";
|
||||
import { EMPTY_REQUEST } from "../../services/HttpService";
|
||||
|
||||
interface PostReportProps {
|
||||
report: PostReportView;
|
||||
|
@ -72,23 +73,23 @@ export class PostReport extends Component<PostReportProps, PostReportState> {
|
|||
siteLanguages={[]}
|
||||
hideImage
|
||||
// All of these are unused, since its view only
|
||||
onPostEdit={() => {}}
|
||||
onPostVote={() => {}}
|
||||
onPostReport={() => {}}
|
||||
onBlockPerson={() => {}}
|
||||
onLockPost={() => {}}
|
||||
onDeletePost={() => {}}
|
||||
onRemovePost={() => {}}
|
||||
onSavePost={() => {}}
|
||||
onFeaturePost={() => {}}
|
||||
onPurgePerson={() => {}}
|
||||
onPurgePost={() => {}}
|
||||
onBanPersonFromCommunity={() => {}}
|
||||
onBanPerson={() => {}}
|
||||
onAddModToCommunity={() => {}}
|
||||
onAddAdmin={() => {}}
|
||||
onTransferCommunity={() => {}}
|
||||
onMarkPostAsRead={() => {}}
|
||||
onPostEdit={async () => EMPTY_REQUEST}
|
||||
onPostVote={async () => EMPTY_REQUEST}
|
||||
onPostReport={async () => {}}
|
||||
onBlockPerson={async () => {}}
|
||||
onLockPost={async () => {}}
|
||||
onDeletePost={async () => {}}
|
||||
onRemovePost={async () => {}}
|
||||
onSavePost={async () => {}}
|
||||
onFeaturePost={async () => {}}
|
||||
onPurgePerson={async () => {}}
|
||||
onPurgePost={async () => {}}
|
||||
onBanPersonFromCommunity={async () => {}}
|
||||
onBanPerson={async () => {}}
|
||||
onAddModToCommunity={async () => {}}
|
||||
onAddAdmin={async () => {}}
|
||||
onTransferCommunity={async () => {}}
|
||||
onMarkPostAsRead={async () => {}}
|
||||
/>
|
||||
<div>
|
||||
{I18NextService.i18n.t("reporter")}:{" "}
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
restoreScrollPosition,
|
||||
saveScrollPosition,
|
||||
} from "@utils/browser";
|
||||
import { debounce, randomStr } from "@utils/helpers";
|
||||
import { debounce, getApubName, randomStr } from "@utils/helpers";
|
||||
import { isImage } from "@utils/media";
|
||||
import { RouteDataResponse } from "@utils/types";
|
||||
import autosize from "autosize";
|
||||
|
@ -752,6 +752,11 @@ export class Post extends Component<any, PostState> {
|
|||
async handleAddModToCommunity(form: AddModToCommunity) {
|
||||
const addModRes = await HttpService.client.addModToCommunity(form);
|
||||
this.updateModerators(addModRes);
|
||||
if (addModRes.state === "success") {
|
||||
toast(
|
||||
I18NextService.i18n.t(form.added ? "appointed_mod" : "removed_mod"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async handleFollow(form: FollowCommunity) {
|
||||
|
@ -837,21 +842,45 @@ export class Post extends Component<any, PostState> {
|
|||
async handleDeleteComment(form: DeleteComment) {
|
||||
const deleteCommentRes = await HttpService.client.deleteComment(form);
|
||||
this.findAndUpdateComment(deleteCommentRes);
|
||||
if (deleteCommentRes.state === "success") {
|
||||
toast(
|
||||
I18NextService.i18n.t(
|
||||
form.deleted ? "deleted_comment" : "undeleted_comment",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async handleDeletePost(form: DeletePost) {
|
||||
const deleteRes = await HttpService.client.deletePost(form);
|
||||
this.updatePost(deleteRes);
|
||||
if (deleteRes.state === "success") {
|
||||
toast(
|
||||
I18NextService.i18n.t(form.deleted ? "deleted_post" : "undeleted_post"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async handleRemovePost(form: RemovePost) {
|
||||
const removeRes = await HttpService.client.removePost(form);
|
||||
this.updatePost(removeRes);
|
||||
if (removeRes.state === "success") {
|
||||
toast(
|
||||
I18NextService.i18n.t(form.removed ? "removed_post" : "restored_post"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async handleRemoveComment(form: RemoveComment) {
|
||||
const removeCommentRes = await HttpService.client.removeComment(form);
|
||||
this.findAndUpdateComment(removeCommentRes);
|
||||
if (removeCommentRes.state === "success") {
|
||||
toast(
|
||||
I18NextService.i18n.t(
|
||||
form.removed ? "removed_comment" : "restored_comment",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async handleSaveComment(form: SaveComment) {
|
||||
|
@ -867,6 +896,13 @@ export class Post extends Component<any, PostState> {
|
|||
async handleFeaturePost(form: FeaturePost) {
|
||||
const featureRes = await HttpService.client.featurePost(form);
|
||||
this.updatePost(featureRes);
|
||||
if (featureRes.state === "success") {
|
||||
toast(
|
||||
I18NextService.i18n.t(
|
||||
form.featured ? "featured_post" : "unfeatured_post",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async handleCommentVote(form: CreateCommentLike) {
|
||||
|
@ -877,11 +913,13 @@ export class Post extends Component<any, PostState> {
|
|||
async handlePostVote(form: CreatePostLike) {
|
||||
const voteRes = await HttpService.client.likePost(form);
|
||||
this.updatePost(voteRes);
|
||||
return voteRes;
|
||||
}
|
||||
|
||||
async handlePostEdit(form: EditPost) {
|
||||
const res = await HttpService.client.editPost(form);
|
||||
this.updatePost(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
async handleCommentReport(form: CreateCommentReport) {
|
||||
|
@ -901,11 +939,25 @@ export class Post extends Component<any, PostState> {
|
|||
async handleLockPost(form: LockPost) {
|
||||
const lockRes = await HttpService.client.lockPost(form);
|
||||
this.updatePost(lockRes);
|
||||
if (lockRes.state === "success") {
|
||||
toast(
|
||||
I18NextService.i18n.t(form.locked ? "locked_post" : "unlocked_post"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async handleDistinguishComment(form: DistinguishComment) {
|
||||
const distinguishRes = await HttpService.client.distinguishComment(form);
|
||||
this.findAndUpdateComment(distinguishRes);
|
||||
if (distinguishRes.state === "success") {
|
||||
toast(
|
||||
I18NextService.i18n.t(
|
||||
form.distinguished
|
||||
? "distinguished_comment"
|
||||
: "undistinguished_comment",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async handleAddAdmin(form: AddAdmin) {
|
||||
|
@ -913,6 +965,9 @@ export class Post extends Component<any, PostState> {
|
|||
|
||||
if (addAdminRes.state === "success") {
|
||||
this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
|
||||
toast(
|
||||
I18NextService.i18n.t(form.added ? "appointed_admin" : "removed_admin"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -920,6 +975,9 @@ export class Post extends Component<any, PostState> {
|
|||
const transferCommunityRes =
|
||||
await HttpService.client.transferCommunity(form);
|
||||
this.updateCommunityFull(transferCommunityRes);
|
||||
if (transferCommunityRes.state === "success") {
|
||||
toast(I18NextService.i18n.t("transferred_community"));
|
||||
}
|
||||
}
|
||||
|
||||
async handleFetchChildren(form: GetComments) {
|
||||
|
@ -944,12 +1002,33 @@ export class Post extends Component<any, PostState> {
|
|||
|
||||
async handleBanFromCommunity(form: BanFromCommunity) {
|
||||
const banRes = await HttpService.client.banFromCommunity(form);
|
||||
this.updateBan(banRes);
|
||||
this.updateBanFromCommunity(banRes);
|
||||
if (banRes.state === "success" && this.state.postRes.state === "success") {
|
||||
toast(
|
||||
I18NextService.i18n.t(
|
||||
form.ban ? "banned_from_community" : "unbanned_from_community",
|
||||
{
|
||||
user: getApubName(this.state.postRes.data.post_view.creator),
|
||||
community: getApubName(this.state.postRes.data.post_view.community),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async handleBanPerson(form: BanPerson) {
|
||||
const banRes = await HttpService.client.banPerson(form);
|
||||
this.updateBan(banRes);
|
||||
if (banRes.state === "success" && this.state.postRes.state === "success") {
|
||||
toast(
|
||||
I18NextService.i18n.t(
|
||||
form.ban ? "banned_from_site" : "unbanned_from_site",
|
||||
{
|
||||
user: getApubName(this.state.postRes.data.post_view.creator),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
|
||||
|
|
|
@ -14,7 +14,7 @@ import { Icon, Spinner } from "../common/icon";
|
|||
import { MomentTime } from "../common/moment-time";
|
||||
import { PersonListing } from "../person/person-listing";
|
||||
import { PrivateMessageForm } from "./private-message-form";
|
||||
import ReportForm from "../common/report-form";
|
||||
import ModActionFormModal from "../common/mod-action-form-modal";
|
||||
|
||||
interface PrivateMessageState {
|
||||
showReply: boolean;
|
||||
|
@ -53,6 +53,7 @@ export class PrivateMessage extends Component<
|
|||
super(props, context);
|
||||
this.handleReplyCancel = this.handleReplyCancel.bind(this);
|
||||
this.handleReportSubmit = this.handleReportSubmit.bind(this);
|
||||
this.hideReportDialog = this.hideReportDialog.bind(this);
|
||||
}
|
||||
|
||||
get mine(): boolean {
|
||||
|
@ -247,9 +248,12 @@ export class PrivateMessage extends Component<
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
{this.state.showReportDialog && (
|
||||
<ReportForm onSubmit={this.handleReportSubmit} />
|
||||
)}
|
||||
<ModActionFormModal
|
||||
onSubmit={this.handleReportSubmit}
|
||||
modActionType="report-message"
|
||||
onCancel={this.hideReportDialog}
|
||||
show={this.state.showReportDialog}
|
||||
/>
|
||||
{this.state.showReply && (
|
||||
<div className="row">
|
||||
<div className="col-sm-6">
|
||||
|
@ -327,17 +331,21 @@ export class PrivateMessage extends Component<
|
|||
}
|
||||
|
||||
handleShowReportDialog(i: PrivateMessage) {
|
||||
i.setState({ showReportDialog: !i.state.showReportDialog });
|
||||
i.setState({ showReportDialog: true });
|
||||
}
|
||||
|
||||
handleReportSubmit(reason: string) {
|
||||
hideReportDialog() {
|
||||
this.setState({
|
||||
showReportDialog: false,
|
||||
});
|
||||
}
|
||||
|
||||
async handleReportSubmit(reason: string) {
|
||||
this.props.onReport({
|
||||
private_message_id: this.props.private_message_view.private_message.id,
|
||||
reason,
|
||||
});
|
||||
|
||||
this.setState({
|
||||
showReportDialog: false,
|
||||
});
|
||||
this.hideReportDialog();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -704,23 +704,23 @@ export class Search extends Component<any, SearchState> {
|
|||
siteLanguages={this.state.siteRes.discussion_languages}
|
||||
viewOnly
|
||||
// All of these are unused, since its view only
|
||||
onPostEdit={() => {}}
|
||||
onPostVote={() => {}}
|
||||
onPostReport={() => {}}
|
||||
onBlockPerson={() => {}}
|
||||
onLockPost={() => {}}
|
||||
onDeletePost={() => {}}
|
||||
onRemovePost={() => {}}
|
||||
onSavePost={() => {}}
|
||||
onFeaturePost={() => {}}
|
||||
onPurgePerson={() => {}}
|
||||
onPurgePost={() => {}}
|
||||
onBanPersonFromCommunity={() => {}}
|
||||
onBanPerson={() => {}}
|
||||
onAddModToCommunity={() => {}}
|
||||
onAddAdmin={() => {}}
|
||||
onTransferCommunity={() => {}}
|
||||
onMarkPostAsRead={() => {}}
|
||||
onPostEdit={async () => EMPTY_REQUEST}
|
||||
onPostVote={async () => EMPTY_REQUEST}
|
||||
onPostReport={async () => {}}
|
||||
onBlockPerson={async () => {}}
|
||||
onLockPost={async () => {}}
|
||||
onDeletePost={async () => {}}
|
||||
onRemovePost={async () => {}}
|
||||
onSavePost={async () => {}}
|
||||
onFeaturePost={async () => {}}
|
||||
onPurgePerson={async () => {}}
|
||||
onPurgePost={async () => {}}
|
||||
onBanPersonFromCommunity={async () => {}}
|
||||
onBanPerson={async () => {}}
|
||||
onAddModToCommunity={async () => {}}
|
||||
onAddAdmin={async () => {}}
|
||||
onTransferCommunity={async () => {}}
|
||||
onMarkPostAsRead={async () => {}}
|
||||
/>
|
||||
)}
|
||||
{i.type_ === "comments" && (
|
||||
|
@ -742,24 +742,24 @@ export class Search extends Component<any, SearchState> {
|
|||
siteLanguages={this.state.siteRes.discussion_languages}
|
||||
// All of these are unused, since its viewonly
|
||||
finished={new Map()}
|
||||
onSaveComment={() => {}}
|
||||
onBlockPerson={() => {}}
|
||||
onDeleteComment={() => {}}
|
||||
onRemoveComment={() => {}}
|
||||
onCommentVote={() => {}}
|
||||
onCommentReport={() => {}}
|
||||
onDistinguishComment={() => {}}
|
||||
onAddModToCommunity={() => {}}
|
||||
onAddAdmin={() => {}}
|
||||
onTransferCommunity={() => {}}
|
||||
onPurgeComment={() => {}}
|
||||
onPurgePerson={() => {}}
|
||||
onSaveComment={async () => {}}
|
||||
onBlockPerson={async () => {}}
|
||||
onDeleteComment={async () => {}}
|
||||
onRemoveComment={async () => {}}
|
||||
onCommentVote={async () => {}}
|
||||
onCommentReport={async () => {}}
|
||||
onDistinguishComment={async () => {}}
|
||||
onAddModToCommunity={async () => {}}
|
||||
onAddAdmin={async () => {}}
|
||||
onTransferCommunity={async () => {}}
|
||||
onPurgeComment={async () => {}}
|
||||
onPurgePerson={async () => {}}
|
||||
onCommentReplyRead={() => {}}
|
||||
onPersonMentionRead={() => {}}
|
||||
onBanPersonFromCommunity={() => {}}
|
||||
onBanPerson={() => {}}
|
||||
onCreateComment={() => Promise.resolve(EMPTY_REQUEST)}
|
||||
onEditComment={() => Promise.resolve(EMPTY_REQUEST)}
|
||||
onBanPersonFromCommunity={async () => {}}
|
||||
onBanPerson={async () => {}}
|
||||
onCreateComment={async () => EMPTY_REQUEST}
|
||||
onEditComment={async () => EMPTY_REQUEST}
|
||||
/>
|
||||
)}
|
||||
{i.type_ === "communities" && (
|
||||
|
@ -803,24 +803,24 @@ export class Search extends Component<any, SearchState> {
|
|||
siteLanguages={siteRes.discussion_languages}
|
||||
// All of these are unused, since its viewonly
|
||||
finished={new Map()}
|
||||
onSaveComment={() => {}}
|
||||
onBlockPerson={() => {}}
|
||||
onDeleteComment={() => {}}
|
||||
onRemoveComment={() => {}}
|
||||
onCommentVote={() => {}}
|
||||
onCommentReport={() => {}}
|
||||
onDistinguishComment={() => {}}
|
||||
onAddModToCommunity={() => {}}
|
||||
onAddAdmin={() => {}}
|
||||
onTransferCommunity={() => {}}
|
||||
onPurgeComment={() => {}}
|
||||
onPurgePerson={() => {}}
|
||||
onSaveComment={async () => {}}
|
||||
onBlockPerson={async () => {}}
|
||||
onDeleteComment={async () => {}}
|
||||
onRemoveComment={async () => {}}
|
||||
onCommentVote={async () => {}}
|
||||
onCommentReport={async () => {}}
|
||||
onDistinguishComment={async () => {}}
|
||||
onAddModToCommunity={async () => {}}
|
||||
onAddAdmin={async () => {}}
|
||||
onTransferCommunity={async () => {}}
|
||||
onPurgeComment={async () => {}}
|
||||
onPurgePerson={async () => {}}
|
||||
onCommentReplyRead={() => {}}
|
||||
onPersonMentionRead={() => {}}
|
||||
onBanPersonFromCommunity={() => {}}
|
||||
onBanPerson={() => {}}
|
||||
onCreateComment={() => Promise.resolve(EMPTY_REQUEST)}
|
||||
onEditComment={() => Promise.resolve(EMPTY_REQUEST)}
|
||||
onBanPersonFromCommunity={async () => {}}
|
||||
onBanPerson={async () => {}}
|
||||
onCreateComment={async () => EMPTY_REQUEST}
|
||||
onEditComment={async () => EMPTY_REQUEST}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -855,22 +855,22 @@ export class Search extends Component<any, SearchState> {
|
|||
siteLanguages={siteRes.discussion_languages}
|
||||
viewOnly
|
||||
// All of these are unused, since its view only
|
||||
onPostEdit={() => {}}
|
||||
onPostVote={() => {}}
|
||||
onPostReport={() => {}}
|
||||
onBlockPerson={() => {}}
|
||||
onLockPost={() => {}}
|
||||
onDeletePost={() => {}}
|
||||
onRemovePost={() => {}}
|
||||
onSavePost={() => {}}
|
||||
onFeaturePost={() => {}}
|
||||
onPurgePerson={() => {}}
|
||||
onPurgePost={() => {}}
|
||||
onBanPersonFromCommunity={() => {}}
|
||||
onBanPerson={() => {}}
|
||||
onAddModToCommunity={() => {}}
|
||||
onAddAdmin={() => {}}
|
||||
onTransferCommunity={() => {}}
|
||||
onPostEdit={async () => EMPTY_REQUEST}
|
||||
onPostVote={async () => EMPTY_REQUEST}
|
||||
onPostReport={async () => {}}
|
||||
onBlockPerson={async () => {}}
|
||||
onLockPost={async () => {}}
|
||||
onDeletePost={async () => {}}
|
||||
onRemovePost={async () => {}}
|
||||
onSavePost={async () => {}}
|
||||
onFeaturePost={async () => {}}
|
||||
onPurgePerson={async () => {}}
|
||||
onPurgePost={async () => {}}
|
||||
onBanPersonFromCommunity={async () => {}}
|
||||
onBanPerson={async () => {}}
|
||||
onAddModToCommunity={async () => {}}
|
||||
onAddAdmin={async () => {}}
|
||||
onTransferCommunity={async () => {}}
|
||||
onMarkPostAsRead={() => {}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,13 +1,32 @@
|
|||
import { WithComment } from "@utils/types";
|
||||
|
||||
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[],
|
||||
) {
|
||||
return [
|
||||
...list.map(c =>
|
||||
c.comment.id === comment.id
|
||||
? { ...c, comment, counts, saved, my_vote }
|
||||
? {
|
||||
...c,
|
||||
comment,
|
||||
counts,
|
||||
saved,
|
||||
my_vote,
|
||||
creator_banned_from_community,
|
||||
creator_blocked,
|
||||
creator_is_admin,
|
||||
creator_is_moderator,
|
||||
}
|
||||
: c,
|
||||
),
|
||||
];
|
||||
|
|
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 validURL from "./valid-url";
|
||||
import dedupByProperty from "./dedup-by-property";
|
||||
import getApubName from "./apub-name";
|
||||
|
||||
export {
|
||||
capitalizeFirstLetter,
|
||||
|
@ -50,4 +51,5 @@ export {
|
|||
validTitle,
|
||||
validURL,
|
||||
dedupByProperty,
|
||||
getApubName,
|
||||
};
|
||||
|
|
|
@ -6,6 +6,6 @@ export default function amMod(
|
|||
myUserInfo = UserService.Instance.myUserInfo,
|
||||
): boolean {
|
||||
return myUserInfo
|
||||
? myUserInfo.moderates.map(cmv => cmv.community.id).includes(communityId)
|
||||
? myUserInfo.moderates.some(cmv => cmv.community.id === communityId)
|
||||
: false;
|
||||
}
|
||||
|
|
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 { ThemeColor } from "./theme-color";
|
||||
import WithComment from "./with-comment";
|
||||
import CrossPostParams from "./cross-post-params";
|
||||
|
||||
export {
|
||||
Choice,
|
||||
|
@ -16,4 +17,5 @@ export {
|
|||
RouteDataResponse,
|
||||
ThemeColor,
|
||||
WithComment,
|
||||
CrossPostParams,
|
||||
};
|
||||
|
|
|
@ -5,4 +5,8 @@ export default interface WithComment {
|
|||
counts: CommentAggregates;
|
||||
my_vote?: number;
|
||||
saved: boolean;
|
||||
creator_is_moderator: boolean;
|
||||
creator_is_admin: boolean;
|
||||
creator_blocked: boolean;
|
||||
creator_banned_from_community: boolean;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue