mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-22 20:31:13 +00:00
Adding admin view vote modal. (#2303)
Admins can now click post or comment dropdowns, and view their votes. Should help with vote-trolling.
This commit is contained in:
parent
107e512b7b
commit
d1bc165327
4 changed files with 259 additions and 6 deletions
|
@ -70,7 +70,7 @@
|
||||||
"inferno-router": "^8.2.2",
|
"inferno-router": "^8.2.2",
|
||||||
"inferno-server": "^8.2.2",
|
"inferno-server": "^8.2.2",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"lemmy-js-client": "0.19.0",
|
"lemmy-js-client": "0.19.2-alpha.1",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
"markdown-it-bidi": "^0.1.0",
|
"markdown-it-bidi": "^0.1.0",
|
||||||
|
|
|
@ -21,6 +21,7 @@ import ActionButton from "./action-button";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Link } from "inferno-router";
|
import { Link } from "inferno-router";
|
||||||
import ConfirmationModal from "../confirmation-modal";
|
import ConfirmationModal from "../confirmation-modal";
|
||||||
|
import ViewVotesModal from "../view-votes-modal";
|
||||||
import ModActionFormModal, { BanUpdateForm } from "../mod-action-form-modal";
|
import ModActionFormModal, { BanUpdateForm } from "../mod-action-form-modal";
|
||||||
import { BanType, PurgeType } from "../../../interfaces";
|
import { BanType, PurgeType } from "../../../interfaces";
|
||||||
import { getApubName, hostname } from "@utils/helpers";
|
import { getApubName, hostname } from "@utils/helpers";
|
||||||
|
@ -69,6 +70,7 @@ const dialogTypes = [
|
||||||
"showTransferCommunityDialog",
|
"showTransferCommunityDialog",
|
||||||
"showAppointModDialog",
|
"showAppointModDialog",
|
||||||
"showAppointAdminDialog",
|
"showAppointAdminDialog",
|
||||||
|
"showViewVotesDialog",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
type DialogType = (typeof dialogTypes)[number];
|
type DialogType = (typeof dialogTypes)[number];
|
||||||
|
@ -91,6 +93,7 @@ export default class ContentActionDropdown extends Component<
|
||||||
showRemoveDialog: false,
|
showRemoveDialog: false,
|
||||||
showReportDialog: false,
|
showReportDialog: false,
|
||||||
showTransferCommunityDialog: false,
|
showTransferCommunityDialog: false,
|
||||||
|
showViewVotesDialog: false,
|
||||||
mounted: false,
|
mounted: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -110,6 +113,7 @@ export default class ContentActionDropdown extends Component<
|
||||||
this.toggleTransferCommunityShow.bind(this);
|
this.toggleTransferCommunityShow.bind(this);
|
||||||
this.toggleAppointModShow = this.toggleAppointModShow.bind(this);
|
this.toggleAppointModShow = this.toggleAppointModShow.bind(this);
|
||||||
this.toggleAppointAdminShow = this.toggleAppointAdminShow.bind(this);
|
this.toggleAppointAdminShow = this.toggleAppointAdminShow.bind(this);
|
||||||
|
this.toggleViewVotesShow = this.toggleViewVotesShow.bind(this);
|
||||||
this.wrapHandler = this.wrapHandler.bind(this);
|
this.wrapHandler = this.wrapHandler.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +207,7 @@ export default class ContentActionDropdown extends Component<
|
||||||
{type === "comment" && (
|
{type === "comment" && (
|
||||||
<li>
|
<li>
|
||||||
<Link
|
<Link
|
||||||
className="btn btn-link d-flex align-items-center rounded-0 dropdown-item"
|
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
|
||||||
to={`/create_private_message/${creator.id}`}
|
to={`/create_private_message/${creator.id}`}
|
||||||
title={I18NextService.i18n.t("message")}
|
title={I18NextService.i18n.t("message")}
|
||||||
aria-label={I18NextService.i18n.t("message")}
|
aria-label={I18NextService.i18n.t("message")}
|
||||||
|
@ -231,6 +235,16 @@ export default class ContentActionDropdown extends Component<
|
||||||
</li>
|
</li>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{amAdmin() && (
|
||||||
|
<li>
|
||||||
|
<ActionButton
|
||||||
|
onClick={this.toggleViewVotesShow}
|
||||||
|
label={I18NextService.i18n.t("view_votes")}
|
||||||
|
icon={"arrow-up"}
|
||||||
|
noLoading
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
|
||||||
{(amMod(community.id) || amAdmin()) && (
|
{(amMod(community.id) || amAdmin()) && (
|
||||||
<>
|
<>
|
||||||
|
@ -475,6 +489,7 @@ export default class ContentActionDropdown extends Component<
|
||||||
showAppointAdminDialog: false,
|
showAppointAdminDialog: false,
|
||||||
showAppointModDialog: false,
|
showAppointModDialog: false,
|
||||||
showTransferCommunityDialog: false,
|
showTransferCommunityDialog: false,
|
||||||
|
showViewVotesDialog: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -523,6 +538,10 @@ export default class ContentActionDropdown extends Component<
|
||||||
this.toggleModDialogShow("showAppointAdminDialog");
|
this.toggleModDialogShow("showAppointAdminDialog");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleViewVotesShow() {
|
||||||
|
this.toggleModDialogShow("showViewVotesDialog");
|
||||||
|
}
|
||||||
|
|
||||||
get moderationDialogs() {
|
get moderationDialogs() {
|
||||||
const {
|
const {
|
||||||
showBanDialog,
|
showBanDialog,
|
||||||
|
@ -534,6 +553,7 @@ export default class ContentActionDropdown extends Component<
|
||||||
showTransferCommunityDialog,
|
showTransferCommunityDialog,
|
||||||
showAppointModDialog,
|
showAppointModDialog,
|
||||||
showAppointAdminDialog,
|
showAppointAdminDialog,
|
||||||
|
showViewVotesDialog,
|
||||||
mounted,
|
mounted,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const {
|
const {
|
||||||
|
@ -543,6 +563,7 @@ export default class ContentActionDropdown extends Component<
|
||||||
community,
|
community,
|
||||||
creator_is_admin,
|
creator_is_admin,
|
||||||
creator_is_moderator,
|
creator_is_moderator,
|
||||||
|
id,
|
||||||
} = this.contentInfo;
|
} = this.contentInfo;
|
||||||
const {
|
const {
|
||||||
onReport,
|
onReport,
|
||||||
|
@ -658,6 +679,12 @@ export default class ContentActionDropdown extends Component<
|
||||||
onNo={this.hideAllDialogs}
|
onNo={this.hideAllDialogs}
|
||||||
onYes={this.wrapHandler(onAppointAdmin)}
|
onYes={this.wrapHandler(onAppointAdmin)}
|
||||||
/>
|
/>
|
||||||
|
<ViewVotesModal
|
||||||
|
type={type}
|
||||||
|
id={id}
|
||||||
|
show={showViewVotesDialog}
|
||||||
|
onCancel={this.hideAllDialogs}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
226
src/shared/components/common/view-votes-modal.tsx
Normal file
226
src/shared/components/common/view-votes-modal.tsx
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
import { Component, RefObject, createRef, linkEvent } from "inferno";
|
||||||
|
import { I18NextService } from "../../services";
|
||||||
|
import type { Modal } from "bootstrap";
|
||||||
|
import { Icon, Spinner } from "./icon";
|
||||||
|
import { Paginator } from "../common/paginator";
|
||||||
|
import {
|
||||||
|
ListCommentLikesResponse,
|
||||||
|
ListPostLikesResponse,
|
||||||
|
VoteView,
|
||||||
|
} from "lemmy-js-client";
|
||||||
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
|
import { fetchLimit } from "../../config";
|
||||||
|
import { PersonListing } from "../person/person-listing";
|
||||||
|
|
||||||
|
interface ViewVotesModalProps {
|
||||||
|
type: "comment" | "post";
|
||||||
|
id: number;
|
||||||
|
show: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ViewVotesModalState {
|
||||||
|
postLikesRes: RequestState<ListPostLikesResponse>;
|
||||||
|
commentLikesRes: RequestState<ListCommentLikesResponse>;
|
||||||
|
page: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function voteViewTable(votes: VoteView[]) {
|
||||||
|
return (
|
||||||
|
<div className="table-responsive">
|
||||||
|
<table id="community_table" className="table table-sm table-hover">
|
||||||
|
<tbody>
|
||||||
|
{votes.map(v => (
|
||||||
|
<tr key={v.creator.id}>
|
||||||
|
<td className="text-start">
|
||||||
|
<PersonListing person={v.creator} useApubName />
|
||||||
|
</td>
|
||||||
|
<td className="text-end">{scoreToIcon(v.score)}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scoreToIcon(score: number) {
|
||||||
|
return score === 1 ? (
|
||||||
|
<Icon icon="arrow-up1" classes="icon-inline small text-info" />
|
||||||
|
) : (
|
||||||
|
<Icon icon="arrow-down1" classes="icon-inline small text-danger" />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ViewVotesModal extends Component<
|
||||||
|
ViewVotesModalProps,
|
||||||
|
ViewVotesModalState
|
||||||
|
> {
|
||||||
|
readonly modalDivRef: RefObject<HTMLDivElement>;
|
||||||
|
readonly yesButtonRef: RefObject<HTMLButtonElement>;
|
||||||
|
modal: Modal;
|
||||||
|
state: ViewVotesModalState = {
|
||||||
|
postLikesRes: EMPTY_REQUEST,
|
||||||
|
commentLikesRes: EMPTY_REQUEST,
|
||||||
|
page: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props: ViewVotesModalProps, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.modalDivRef = createRef();
|
||||||
|
this.yesButtonRef = createRef();
|
||||||
|
|
||||||
|
this.handleShow = this.handleShow.bind(this);
|
||||||
|
this.handleDismiss = this.handleDismiss.bind(this);
|
||||||
|
this.handlePageChange = this.handlePageChange.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();
|
||||||
|
await this.refetch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.modalDivRef.current?.removeEventListener(
|
||||||
|
"shown.bs.modal",
|
||||||
|
this.handleShow,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.modal.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidUpdate({ show: prevShow }: ViewVotesModalProps) {
|
||||||
|
if (!!prevShow !== !!this.props.show) {
|
||||||
|
if (this.props.show) {
|
||||||
|
this.modal.show();
|
||||||
|
await this.refetch();
|
||||||
|
} else {
|
||||||
|
this.modal.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="modal fade"
|
||||||
|
id="viewVotesModal"
|
||||||
|
tabIndex={-1}
|
||||||
|
aria-hidden
|
||||||
|
aria-labelledby="#viewVotesModalTitle"
|
||||||
|
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="viewVotesModalTitle">
|
||||||
|
{I18NextService.i18n.t("votes")}
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn-close"
|
||||||
|
onClick={linkEvent(this, this.handleDismiss)}
|
||||||
|
aria-label={I18NextService.i18n.t("cancel")}
|
||||||
|
></button>
|
||||||
|
</header>
|
||||||
|
<div className="modal-body text-center align-middle text-body">
|
||||||
|
{this.postLikes()}
|
||||||
|
{this.commentLikes()}
|
||||||
|
<Paginator
|
||||||
|
page={this.state.page}
|
||||||
|
onChange={this.handlePageChange}
|
||||||
|
nextDisabled={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
postLikes() {
|
||||||
|
switch (this.state.postLikesRes.state) {
|
||||||
|
case "loading":
|
||||||
|
return (
|
||||||
|
<h1 className="h4">
|
||||||
|
<Spinner large />
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
|
case "success": {
|
||||||
|
const likes = this.state.postLikesRes.data.post_likes;
|
||||||
|
return voteViewTable(likes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commentLikes() {
|
||||||
|
switch (this.state.commentLikesRes.state) {
|
||||||
|
case "loading":
|
||||||
|
return (
|
||||||
|
<h1 className="h4">
|
||||||
|
<Spinner large />
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
|
case "success": {
|
||||||
|
const likes = this.state.commentLikesRes.data.comment_likes;
|
||||||
|
return voteViewTable(likes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleShow() {
|
||||||
|
this.yesButtonRef.current?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDismiss() {
|
||||||
|
this.props.onCancel();
|
||||||
|
this.modal.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
async handlePageChange(page: number) {
|
||||||
|
this.setState({ page });
|
||||||
|
await this.refetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
async refetch() {
|
||||||
|
const page = this.state.page;
|
||||||
|
const limit = fetchLimit;
|
||||||
|
|
||||||
|
if (this.props.type === "post") {
|
||||||
|
this.setState({ postLikesRes: LOADING_REQUEST });
|
||||||
|
this.setState({
|
||||||
|
postLikesRes: await HttpService.client.listPostLikes({
|
||||||
|
post_id: this.props.id,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({ commentLikesRes: LOADING_REQUEST });
|
||||||
|
this.setState({
|
||||||
|
commentLikesRes: await HttpService.client.listCommentLikes({
|
||||||
|
comment_id: this.props.id,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5715,10 +5715,10 @@ leac@^0.6.0:
|
||||||
resolved "https://registry.yarnpkg.com/leac/-/leac-0.6.0.tgz#dcf136e382e666bd2475f44a1096061b70dc0912"
|
resolved "https://registry.yarnpkg.com/leac/-/leac-0.6.0.tgz#dcf136e382e666bd2475f44a1096061b70dc0912"
|
||||||
integrity sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==
|
integrity sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==
|
||||||
|
|
||||||
lemmy-js-client@0.19.0:
|
lemmy-js-client@0.19.2-alpha.1:
|
||||||
version "0.19.0"
|
version "0.19.2-alpha.1"
|
||||||
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.19.0.tgz#50098183264fa176784857f45665b06994b31e18"
|
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.19.2-alpha.1.tgz#2fc7b00b38ce8bf4be08d25ef5c25d2708bf59ca"
|
||||||
integrity sha512-h+E8wC9RKjlToWw9+kuGFAzk4Fiaf61KqAwzvoCDAfj2L1r+YNt5EDMOggGCoRx5PlqLuIVr7BNEU46KxJfmHA==
|
integrity sha512-XIQDfvULtaQuQMg7tIa0eRoUQFGb7y5NdJMVZaeQ2cT9q90IjB5WLE4wd/rOSk9aEt61iWanGsVDHt5aizOOiw==
|
||||||
dependencies:
|
dependencies:
|
||||||
cross-fetch "^3.1.5"
|
cross-fetch "^3.1.5"
|
||||||
form-data "^4.0.0"
|
form-data "^4.0.0"
|
||||||
|
|
Loading…
Reference in a new issue