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:
SleeplessOne1917 2024-03-26 19:03:02 -04:00 committed by GitHub
parent 610789242b
commit 579aea40d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 6293 additions and 3069 deletions

@ -1 +1 @@
Subproject commit 62c8418021bc39543c87b4ae3dcf2419d13f61e0
Subproject commit b4c63029e598c022a04fc21acb45855645bd6794

View file

@ -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}": [

File diff suppressed because it is too large Load diff

View file

@ -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 {

View file

@ -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 = {

View file

@ -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;

View file

@ -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">

View file

@ -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);
}
}

View file

@ -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 (

View file

@ -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}

View file

@ -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;
}

View file

@ -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) {