mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-22 06:36:17 +00:00
Notify users that they are banned from a community (#2397)
* Add banned blurb to community sidebar * Hide interactable parts of posts and comments when banned from community * Add translation * Fix some typescript errors * Fix typescript errors * PR feedback
This commit is contained in:
parent
610789242b
commit
579aea40d0
|
@ -1 +1 @@
|
|||
Subproject commit 62c8418021bc39543c87b4ae3dcf2419d13f61e0
|
||||
Subproject commit b4c63029e598c022a04fc21acb45855645bd6794
|
52
package.json
52
package.json
|
@ -25,33 +25,33 @@
|
|||
"node": ">=8.9.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-proposal-decorators": "^7.23.9",
|
||||
"@babel/plugin-transform-class-properties": "^7.23.3",
|
||||
"@babel/plugin-transform-runtime": "^7.23.9",
|
||||
"@babel/plugin-transform-typescript": "^7.23.6",
|
||||
"@babel/preset-env": "^7.23.9",
|
||||
"@babel/preset-typescript": "^7.23.3",
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@babel/plugin-proposal-decorators": "^7.24.1",
|
||||
"@babel/plugin-transform-class-properties": "^7.24.1",
|
||||
"@babel/plugin-transform-runtime": "^7.24.3",
|
||||
"@babel/plugin-transform-typescript": "^7.24.1",
|
||||
"@babel/preset-env": "^7.24.3",
|
||||
"@babel/preset-typescript": "^7.24.1",
|
||||
"@babel/runtime": "^7.24.1",
|
||||
"@emoji-mart/data": "^1.1.2",
|
||||
"@shortcm/qr-image": "^9.0.4",
|
||||
"autosize": "^6.0.1",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-plugin-inferno": "^6.7.1",
|
||||
"bootstrap": "^5.3.3",
|
||||
"check-password-strength": "^2.0.7",
|
||||
"check-password-strength": "^2.0.10",
|
||||
"classnames": "^2.5.1",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"cookie": "^0.6.0",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"css-loader": "^6.10.0",
|
||||
"date-fns": "^3.3.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"emoji-mart": "^5.5.2",
|
||||
"emoji-short-name": "^2.0.0",
|
||||
"express": "~4.18.2",
|
||||
"express": "~4.19.2",
|
||||
"highlight.js": "^11.9.0",
|
||||
"history": "^5.3.0",
|
||||
"html-to-text": "^9.0.5",
|
||||
"i18next": "^23.10.0",
|
||||
"i18next": "^23.10.1",
|
||||
"inferno": "^8.2.3",
|
||||
"inferno-create-element": "^8.2.3",
|
||||
"inferno-helmet": "^5.2.1",
|
||||
|
@ -60,9 +60,9 @@
|
|||
"inferno-router": "^8.2.3",
|
||||
"inferno-server": "^8.2.3",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lemmy-js-client": "0.19.4-alpha.4",
|
||||
"lemmy-js-client": "0.19.4-alpha.14",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"markdown-it": "^14.0.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"markdown-it-bidi": "^0.1.0",
|
||||
"markdown-it-container": "^4.0.0",
|
||||
"markdown-it-emoji": "^3.0.0",
|
||||
|
@ -72,25 +72,25 @@
|
|||
"markdown-it-ruby": "^0.1.1",
|
||||
"markdown-it-sub": "^2.0.0",
|
||||
"markdown-it-sup": "^2.0.0",
|
||||
"mini-css-extract-plugin": "^2.8.0",
|
||||
"mini-css-extract-plugin": "^2.8.1",
|
||||
"register-service-worker": "^1.7.2",
|
||||
"run-node-webpack-plugin": "^1.3.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"sanitize-html": "^2.12.1",
|
||||
"sass": "^1.71.1",
|
||||
"sanitize-html": "^2.13.0",
|
||||
"sass": "^1.72.0",
|
||||
"sass-loader": "^14.1.1",
|
||||
"serialize-javascript": "^6.0.2",
|
||||
"service-worker-webpack": "^1.0.0",
|
||||
"sharp": "0.33.2",
|
||||
"sharp": "0.33.3",
|
||||
"tippy.js": "^6.3.7",
|
||||
"toastify-js": "^1.12.0",
|
||||
"tributejs": "^5.1.3",
|
||||
"webpack": "^5.90.3",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-node-externals": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.9",
|
||||
"@babel/core": "^7.24.3",
|
||||
"@types/autosize": "^4.0.3",
|
||||
"@types/bootstrap": "^5.2.10",
|
||||
"@types/cookie": "^0.6.0",
|
||||
|
@ -99,13 +99,13 @@
|
|||
"@types/lodash.isequal": "^4.5.8",
|
||||
"@types/markdown-it": "^13.0.7",
|
||||
"@types/markdown-it-container": "^2.0.9",
|
||||
"@types/node": "^20.11.20",
|
||||
"@types/node": "^20.11.30",
|
||||
"@types/path-browserify": "^1.0.2",
|
||||
"@types/sanitize-html": "^2.11.0",
|
||||
"@types/serialize-javascript": "^5.0.4",
|
||||
"@types/toastify-js": "^1.12.3",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.0",
|
||||
"@typescript-eslint/parser": "^7.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
||||
"@typescript-eslint/parser": "^7.4.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-inferno": "^7.33.3",
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
|
@ -117,15 +117,15 @@
|
|||
"prettier-plugin-import-sort": "^0.0.7",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"prettier-plugin-packagejson": "^2.4.12",
|
||||
"qs": "^6.11.2",
|
||||
"qs": "^6.12.0",
|
||||
"rimraf": "^5.0.5",
|
||||
"sortpack": "^2.4.0",
|
||||
"style-loader": "^3.3.4",
|
||||
"terser": "^5.28.1",
|
||||
"typescript": "^5.3.3",
|
||||
"terser": "^5.29.2",
|
||||
"typescript": "^5.4.3",
|
||||
"typescript-language-server": "^4.3.3",
|
||||
"webpack-bundle-analyzer": "^4.10.1",
|
||||
"webpack-dev-server": "5.0.2"
|
||||
"webpack-dev-server": "5.0.4"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{css, scss}": [
|
||||
|
|
9097
pnpm-lock.yaml
9097
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -13,7 +13,6 @@ import {
|
|||
CommentId,
|
||||
CommentReplyView,
|
||||
CommentResponse,
|
||||
CommentView,
|
||||
CommunityModeratorView,
|
||||
CreateComment,
|
||||
CreateCommentLike,
|
||||
|
@ -37,6 +36,7 @@ import deepEqual from "lodash.isequal";
|
|||
import { commentTreeMaxDepth } from "../../config";
|
||||
import {
|
||||
CommentNodeI,
|
||||
CommentNodeView,
|
||||
CommentViewType,
|
||||
VoteContentType,
|
||||
} from "../../interfaces";
|
||||
|
@ -152,7 +152,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
this.handleTransferCommunity = this.handleTransferCommunity.bind(this);
|
||||
}
|
||||
|
||||
get commentView(): CommentView {
|
||||
get commentView(): CommentNodeView {
|
||||
return this.props.node.comment_view;
|
||||
}
|
||||
|
||||
|
@ -188,6 +188,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
post,
|
||||
counts,
|
||||
my_vote,
|
||||
banned_from_community,
|
||||
} = this.commentView;
|
||||
|
||||
const moreRepliesBorderColor = this.props.node.depth
|
||||
|
@ -344,54 +345,55 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
)}
|
||||
</button>
|
||||
)}
|
||||
{UserService.Instance.myUserInfo && !this.props.viewOnly && (
|
||||
<>
|
||||
<VoteButtonsCompact
|
||||
voteContentType={VoteContentType.Comment}
|
||||
id={id}
|
||||
onVote={this.props.onCommentVote}
|
||||
enableDownvotes={this.props.enableDownvotes}
|
||||
counts={counts}
|
||||
my_vote={my_vote}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-link btn-animate text-muted"
|
||||
onClick={linkEvent(this, handleToggleViewSource)}
|
||||
data-tippy-content={I18NextService.i18n.t(
|
||||
"view_source",
|
||||
)}
|
||||
aria-label={I18NextService.i18n.t("view_source")}
|
||||
>
|
||||
<Icon
|
||||
icon="file-text"
|
||||
classes={`icon-inline ${
|
||||
this.state.viewSource && "text-success"
|
||||
}`}
|
||||
{UserService.Instance.myUserInfo &&
|
||||
!(this.props.viewOnly || banned_from_community) && (
|
||||
<>
|
||||
<VoteButtonsCompact
|
||||
voteContentType={VoteContentType.Comment}
|
||||
id={id}
|
||||
onVote={this.props.onCommentVote}
|
||||
enableDownvotes={this.props.enableDownvotes}
|
||||
counts={counts}
|
||||
my_vote={my_vote}
|
||||
/>
|
||||
</button>
|
||||
<CommentActionDropdown
|
||||
commentView={this.commentView}
|
||||
admins={this.props.admins}
|
||||
moderators={this.props.moderators}
|
||||
onReply={this.handleReplyClick}
|
||||
onReport={this.handleReportComment}
|
||||
onBlock={this.handleBlockPerson}
|
||||
onSave={this.handleSaveComment}
|
||||
onEdit={this.handleEditClick}
|
||||
onDelete={this.handleDeleteComment}
|
||||
onDistinguish={this.handleDistinguishComment}
|
||||
onRemove={this.handleRemoveComment}
|
||||
onBanFromCommunity={this.handleBanFromCommunity}
|
||||
onAppointCommunityMod={this.handleAppointCommunityMod}
|
||||
onTransferCommunity={this.handleTransferCommunity}
|
||||
onPurgeUser={this.handlePurgePerson}
|
||||
onPurgeContent={this.handlePurgeComment}
|
||||
onBanFromSite={this.handleBanFromSite}
|
||||
onAppointAdmin={this.handleAppointAdmin}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-link btn-animate text-muted"
|
||||
onClick={linkEvent(this, handleToggleViewSource)}
|
||||
data-tippy-content={I18NextService.i18n.t(
|
||||
"view_source",
|
||||
)}
|
||||
aria-label={I18NextService.i18n.t("view_source")}
|
||||
>
|
||||
<Icon
|
||||
icon="file-text"
|
||||
classes={`icon-inline ${
|
||||
this.state.viewSource && "text-success"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
<CommentActionDropdown
|
||||
commentView={this.commentView}
|
||||
admins={this.props.admins}
|
||||
moderators={this.props.moderators}
|
||||
onReply={this.handleReplyClick}
|
||||
onReport={this.handleReportComment}
|
||||
onBlock={this.handleBlockPerson}
|
||||
onSave={this.handleSaveComment}
|
||||
onEdit={this.handleEditClick}
|
||||
onDelete={this.handleDeleteComment}
|
||||
onDistinguish={this.handleDistinguishComment}
|
||||
onRemove={this.handleRemoveComment}
|
||||
onBanFromCommunity={this.handleBanFromCommunity}
|
||||
onAppointCommunityMod={this.handleAppointCommunityMod}
|
||||
onTransferCommunity={this.handleTransferCommunity}
|
||||
onPurgeUser={this.handlePurgePerson}
|
||||
onPurgeContent={this.handlePurgeComment}
|
||||
onBanFromSite={this.handleBanFromSite}
|
||||
onAppointAdmin={this.handleAppointAdmin}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
@ -589,16 +591,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
this.setState({ showReply: false, showEdit: false });
|
||||
}
|
||||
|
||||
isPersonMentionType(
|
||||
item: CommentView | PersonMentionView | CommentReplyView,
|
||||
): item is PersonMentionView {
|
||||
return (item as PersonMentionView).person_mention?.id !== undefined;
|
||||
isPersonMentionType(item: CommentNodeView): item is PersonMentionView {
|
||||
return item.person_mention?.id !== undefined;
|
||||
}
|
||||
|
||||
isCommentReplyType(
|
||||
item: CommentView | PersonMentionView | CommentReplyView,
|
||||
): item is CommentReplyView {
|
||||
return (item as CommentReplyView).comment_reply?.id !== undefined;
|
||||
isCommentReplyType(item: CommentNodeView): item is CommentReplyView {
|
||||
return item.comment_reply?.id !== undefined;
|
||||
}
|
||||
|
||||
get isCommentNew(): boolean {
|
||||
|
|
|
@ -63,6 +63,7 @@ export class CommentReport extends Component<
|
|||
saved: false,
|
||||
creator_blocked: false,
|
||||
my_vote: r.my_vote,
|
||||
banned_from_community: false,
|
||||
};
|
||||
|
||||
const node: CommentNodeI = {
|
||||
|
|
|
@ -3,12 +3,7 @@ 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 { CommunityModeratorView, PersonView, PostView } from "lemmy-js-client";
|
||||
import {
|
||||
amAdmin,
|
||||
amCommunityCreator,
|
||||
|
@ -23,7 +18,7 @@ import { Link } from "inferno-router";
|
|||
import ConfirmationModal from "../confirmation-modal";
|
||||
import ViewVotesModal from "../view-votes-modal";
|
||||
import ModActionFormModal, { BanUpdateForm } from "../mod-action-form-modal";
|
||||
import { BanType, PurgeType } from "../../../interfaces";
|
||||
import { BanType, CommentNodeView, PurgeType } from "../../../interfaces";
|
||||
import { getApubName, hostname } from "@utils/helpers";
|
||||
|
||||
interface ContentActionDropdownPropsBase {
|
||||
|
@ -46,7 +41,7 @@ interface ContentActionDropdownPropsBase {
|
|||
|
||||
export type ContentCommentProps = {
|
||||
type: "comment";
|
||||
commentView: CommentView;
|
||||
commentView: CommentNodeView;
|
||||
onReply: () => void;
|
||||
onDistinguish: () => Promise<void>;
|
||||
} & ContentActionDropdownPropsBase;
|
||||
|
|
|
@ -126,6 +126,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
const {
|
||||
community: { name, actor_id, id, posting_restricted_to_mods, visibility },
|
||||
counts,
|
||||
banned_from_community,
|
||||
} = this.props.community_view;
|
||||
return (
|
||||
<aside className="mb-3">
|
||||
|
@ -134,14 +135,18 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
<div className="card-body">
|
||||
{this.communityTitle()}
|
||||
{this.props.editable && this.adminButtons()}
|
||||
<SubscribeButton
|
||||
communityView={this.props.community_view}
|
||||
onFollow={linkEvent(this, this.handleFollowCommunity)}
|
||||
onUnFollow={linkEvent(this, this.handleUnfollowCommunity)}
|
||||
loading={this.state.followCommunityLoading}
|
||||
/>
|
||||
{this.canPost && this.createPost()}
|
||||
{myUserInfo && this.blockCommunity()}
|
||||
{!banned_from_community && (
|
||||
<>
|
||||
<SubscribeButton
|
||||
communityView={this.props.community_view}
|
||||
onFollow={linkEvent(this, this.handleFollowCommunity)}
|
||||
onUnFollow={linkEvent(this, this.handleUnfollowCommunity)}
|
||||
loading={this.state.followCommunityLoading}
|
||||
/>
|
||||
{this.canPost && this.createPost()}
|
||||
{myUserInfo && this.blockCommunity()}
|
||||
</>
|
||||
)}
|
||||
{!myUserInfo && (
|
||||
<div className="alert alert-info" role="alert">
|
||||
<T
|
||||
|
@ -174,6 +179,21 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
</T>
|
||||
</div>
|
||||
)}
|
||||
{banned_from_community && (
|
||||
<div
|
||||
className="alert alert-danger text-sm-start text-xs-center"
|
||||
role="alert"
|
||||
>
|
||||
<Icon
|
||||
icon="ban"
|
||||
inline
|
||||
classes="me-sm-2 mx-auto d-sm-inline d-block"
|
||||
/>
|
||||
<T i18nKey="banned_from_community_blurb" className="d-inline">
|
||||
#<strong className="fw-bold">#</strong>#
|
||||
</T>
|
||||
</div>
|
||||
)}
|
||||
{this.description()}
|
||||
<div>
|
||||
<div className="fw-semibold mb-1">
|
||||
|
|
|
@ -547,14 +547,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
}
|
||||
|
||||
commentsLine(mobile = false) {
|
||||
const {
|
||||
admins,
|
||||
moderators,
|
||||
viewOnly,
|
||||
showBody,
|
||||
onPostVote,
|
||||
enableDownvotes,
|
||||
} = this.props;
|
||||
const { admins, moderators, showBody, onPostVote, enableDownvotes } =
|
||||
this.props;
|
||||
const {
|
||||
post: { ap_id, id, body },
|
||||
counts,
|
||||
|
@ -580,7 +574,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
>
|
||||
<Icon icon="fedilink" inline />
|
||||
</a>
|
||||
{mobile && !viewOnly && (
|
||||
{mobile && this.isInteractable && (
|
||||
<VoteButtonsCompact
|
||||
voteContentType={VoteContentType.Post}
|
||||
id={id}
|
||||
|
@ -593,7 +587,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
|
||||
{showBody && body && this.viewSourceButton}
|
||||
|
||||
{UserService.Instance.myUserInfo && !viewOnly && (
|
||||
{UserService.Instance.myUserInfo && this.isInteractable && (
|
||||
<PostActionDropdown
|
||||
postView={this.postView}
|
||||
admins={admins}
|
||||
|
@ -734,7 +728,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
{/* The larger view*/}
|
||||
<div className="d-none d-sm-block">
|
||||
<article className="row post-container">
|
||||
{!this.props.viewOnly && (
|
||||
{this.isInteractable && (
|
||||
<div className="col flex-grow-0">
|
||||
<VoteButtons
|
||||
voteContentType={VoteContentType.Post}
|
||||
|
@ -1047,4 +1041,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
get canAdmin(): boolean {
|
||||
return canAdmin(this.postView.creator.id, this.props.admins);
|
||||
}
|
||||
|
||||
get isInteractable() {
|
||||
const {
|
||||
viewOnly,
|
||||
post_view: { banned_from_community },
|
||||
} = this.props;
|
||||
|
||||
return !(viewOnly || banned_from_community);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,8 @@ export class PostReport extends Component<PostReportProps, PostReportState> {
|
|||
unread_comments: 0,
|
||||
creator_is_moderator: false,
|
||||
creator_is_admin: false,
|
||||
banned_from_community: false,
|
||||
hidden: false,
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -399,7 +399,9 @@ export class Post extends Component<any, PostState> {
|
|||
<div ref={this.state.commentSectionRef} className="mb-2" />
|
||||
|
||||
{/* Only show the top level comment form if its not a context view */}
|
||||
{!this.state.commentId && (
|
||||
{!(
|
||||
this.state.commentId || res.post_view.banned_from_community
|
||||
) && (
|
||||
<CommentForm
|
||||
node={res.post_view.post.id}
|
||||
disabled={res.post_view.post.locked}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import { ErrorPageData } from "@utils/types";
|
||||
import { CommentView, GetSiteResponse } from "lemmy-js-client";
|
||||
import {
|
||||
CommentReply,
|
||||
CommentView,
|
||||
GetSiteResponse,
|
||||
PersonMention,
|
||||
} from "lemmy-js-client";
|
||||
import type { ParsedQs } from "qs";
|
||||
import { RequestState } from "./services/HttpService";
|
||||
|
||||
|
@ -77,8 +82,14 @@ export enum VoteContentType {
|
|||
Comment,
|
||||
}
|
||||
|
||||
export type CommentNodeView = Omit<CommentView, "banned_from_community"> &
|
||||
Partial<Pick<CommentView, "banned_from_community">> & {
|
||||
person_mention?: PersonMention;
|
||||
comment_reply?: CommentReply;
|
||||
};
|
||||
|
||||
export interface CommentNodeI {
|
||||
comment_view: CommentView;
|
||||
comment_view: CommentNodeView;
|
||||
children: Array<CommentNodeI>;
|
||||
depth: number;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { CommentView } from "lemmy-js-client";
|
||||
import { CommentNodeI } from "../../interfaces";
|
||||
import { CommentNodeI, CommentNodeView } from "../../interfaces";
|
||||
|
||||
export default function commentsToFlatNodes(
|
||||
comments: CommentView[],
|
||||
comments: CommentNodeView[],
|
||||
): CommentNodeI[] {
|
||||
const nodes: CommentNodeI[] = [];
|
||||
for (const comment of comments) {
|
||||
|
|
Loading…
Reference in a new issue