diff options
-rw-r--r-- | server/migrations/2019-09-05-230317_add_mod_ban_views/down.sql | 44 | ||||
-rw-r--r-- | server/migrations/2019-09-05-230317_add_mod_ban_views/up.sql | 47 | ||||
-rw-r--r-- | server/src/db/post_view.rs | 8 | ||||
-rw-r--r-- | ui/src/components/comment-node.tsx | 27 | ||||
-rw-r--r-- | ui/src/components/post-listing.tsx | 245 | ||||
-rw-r--r-- | ui/src/components/post.tsx | 6 | ||||
-rw-r--r-- | ui/src/components/user.tsx | 10 | ||||
-rw-r--r-- | ui/src/interfaces.ts | 5 | ||||
-rw-r--r-- | ui/src/translations/en.ts | 1 |
9 files changed, 371 insertions, 22 deletions
diff --git a/server/migrations/2019-09-05-230317_add_mod_ban_views/down.sql b/server/migrations/2019-09-05-230317_add_mod_ban_views/down.sql new file mode 100644 index 00000000..c60b672c --- /dev/null +++ b/server/migrations/2019-09-05-230317_add_mod_ban_views/down.sql @@ -0,0 +1,44 @@ +-- Post view +drop view post_view; +create view post_view as +with all_post as +( + select + p.*, + (select name from user_ where p.creator_id = user_.id) as creator_name, + (select name from community where p.community_id = community.id) as community_name, + (select removed from community c where p.community_id = c.id) as community_removed, + (select deleted from community c where p.community_id = c.id) as community_deleted, + (select nsfw from community c where p.community_id = c.id) as community_nsfw, + (select count(*) from comment where comment.post_id = p.id) as number_of_comments, + coalesce(sum(pl.score), 0) as score, + count (case when pl.score = 1 then 1 else null end) as upvotes, + count (case when pl.score = -1 then 1 else null end) as downvotes, + hot_rank(coalesce(sum(pl.score) , 0), p.published) as hot_rank + from post p + left join post_like pl on p.id = pl.post_id + group by p.id +) + +select +ap.*, +u.id as user_id, +coalesce(pl.score, 0) as my_vote, +(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, +(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, +(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved +from user_ u +cross join all_post ap +left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id + +union all + +select +ap.*, +null as user_id, +null as my_vote, +null as subscribed, +null as read, +null as saved +from all_post ap +; diff --git a/server/migrations/2019-09-05-230317_add_mod_ban_views/up.sql b/server/migrations/2019-09-05-230317_add_mod_ban_views/up.sql new file mode 100644 index 00000000..d73b3720 --- /dev/null +++ b/server/migrations/2019-09-05-230317_add_mod_ban_views/up.sql @@ -0,0 +1,47 @@ +-- Create post view, adding banned_from_community + +drop view post_view; +create view post_view as +with all_post as +( + select + p.*, + (select u.banned from user_ u where p.creator_id = u.id) as banned, + (select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community, + (select name from user_ where p.creator_id = user_.id) as creator_name, + (select name from community where p.community_id = community.id) as community_name, + (select removed from community c where p.community_id = c.id) as community_removed, + (select deleted from community c where p.community_id = c.id) as community_deleted, + (select nsfw from community c where p.community_id = c.id) as community_nsfw, + (select count(*) from comment where comment.post_id = p.id) as number_of_comments, + coalesce(sum(pl.score), 0) as score, + count (case when pl.score = 1 then 1 else null end) as upvotes, + count (case when pl.score = -1 then 1 else null end) as downvotes, + hot_rank(coalesce(sum(pl.score) , 0), p.published) as hot_rank + from post p + left join post_like pl on p.id = pl.post_id + group by p.id +) + +select +ap.*, +u.id as user_id, +coalesce(pl.score, 0) as my_vote, +(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, +(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, +(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved +from user_ u +cross join all_post ap +left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id + +union all + +select +ap.*, +null as user_id, +null as my_vote, +null as subscribed, +null as read, +null as saved +from all_post ap +; diff --git a/server/src/db/post_view.rs b/server/src/db/post_view.rs index c9d8cff7..a3d327dc 100644 --- a/server/src/db/post_view.rs +++ b/server/src/db/post_view.rs @@ -20,6 +20,8 @@ table! { updated -> Nullable<Timestamp>, deleted -> Bool, nsfw -> Bool, + banned -> Bool, + banned_from_community -> Bool, creator_name -> Varchar, community_name -> Varchar, community_removed -> Bool, @@ -54,6 +56,8 @@ pub struct PostView { pub updated: Option<chrono::NaiveDateTime>, pub deleted: bool, pub nsfw: bool, + pub banned: bool, + pub banned_from_community: bool, pub creator_name: String, pub community_name: String, pub community_removed: bool, @@ -279,6 +283,8 @@ mod tests { body: None, creator_id: inserted_user.id, creator_name: user_name.to_owned(), + banned: false, + banned_from_community: false, community_id: inserted_community.id, removed: false, deleted: false, @@ -312,6 +318,8 @@ mod tests { locked: false, creator_id: inserted_user.id, creator_name: user_name.to_owned(), + banned: false, + banned_from_community: false, community_id: inserted_community.id, community_name: community_name.to_owned(), community_removed: false, diff --git a/ui/src/components/comment-node.tsx b/ui/src/components/comment-node.tsx index 3eff8c79..7dbaafdc 100644 --- a/ui/src/components/comment-node.tsx +++ b/ui/src/components/comment-node.tsx @@ -1,6 +1,6 @@ import { Component, linkEvent } from 'inferno'; import { Link } from 'inferno-router'; -import { CommentNode as CommentNodeI, CommentLikeForm, CommentForm as CommentFormI, SaveCommentForm, BanFromCommunityForm, BanUserForm, CommunityUser, UserView, AddModToCommunityForm, AddAdminForm, TransferCommunityForm, TransferSiteForm } from '../interfaces'; +import { CommentNode as CommentNodeI, CommentLikeForm, CommentForm as CommentFormI, SaveCommentForm, BanFromCommunityForm, BanUserForm, CommunityUser, UserView, AddModToCommunityForm, AddAdminForm, TransferCommunityForm, TransferSiteForm, BanType } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { mdToHtml, getUnixTime, canMod, isMod } from '../utils'; import * as moment from 'moment'; @@ -10,8 +10,6 @@ import { CommentNodes } from './comment-nodes'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; -enum BanType {Community, Site}; - interface CommentNodeState { showReply: boolean; showEdit: boolean; @@ -21,9 +19,9 @@ interface CommentNodeState { banReason: string; banExpires: string; banType: BanType; - collapsed: boolean; showConfirmTransferSite: boolean; showConfirmTransferCommunity: boolean; + collapsed: boolean; } interface CommentNodeProps { @@ -87,6 +85,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { {this.isAdmin && <li className="list-inline-item badge badge-light"><T i18nKey="admin">#</T></li> } + {(node.comment.banned_from_community || node.comment.banned) && + <li className="list-inline-item badge badge-danger"><T i18nKey="banned">#</T></li> + } <li className="list-inline-item"> <span>( <span className="text-info">+{node.comment.upvotes}</span> @@ -122,7 +123,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { </li> <li className="list-inline-item"> <span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}> - {!this.props.node.comment.deleted ? i18n.t('delete') : i18n.t('restore')} + {!node.comment.deleted ? i18n.t('delete') : i18n.t('restore')} </span> </li> </> @@ -130,7 +131,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { {/* Admins and mods can remove comments */} {this.canMod && <li className="list-inline-item"> - {!this.props.node.comment.removed ? + {!node.comment.removed ? <span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}><T i18nKey="remove">#</T></span> : <span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}><T i18nKey="restore">#</T></span> } @@ -141,13 +142,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { <> {!this.isMod && <li className="list-inline-item"> - {!this.props.node.comment.banned_from_community ? + {!node.comment.banned_from_community ? <span class="pointer" onClick={linkEvent(this, this.handleModBanFromCommunityShow)}><T i18nKey="ban">#</T></span> : <span class="pointer" onClick={linkEvent(this, this.handleModBanFromCommunitySubmit)}><T i18nKey="unban">#</T></span> } </li> } - {!this.props.node.comment.banned_from_community && + {!node.comment.banned_from_community && <li className="list-inline-item"> <span class="pointer" onClick={linkEvent(this, this.handleAddModToCommunity)}>{this.isMod ? i18n.t('remove_as_mod') : i18n.t('appoint_as_mod')}</span> </li> @@ -172,13 +173,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { <> {!this.isAdmin && <li className="list-inline-item"> - {!this.props.node.comment.banned ? + {!node.comment.banned ? <span class="pointer" onClick={linkEvent(this, this.handleModBanShow)}><T i18nKey="ban_from_site">#</T></span> : <span class="pointer" onClick={linkEvent(this, this.handleModBanSubmit)}><T i18nKey="unban_from_site">#</T></span> } </li> } - {!this.props.node.comment.banned && + {!node.comment.banned && <li className="list-inline-item"> <span class="pointer" onClick={linkEvent(this, this.handleAddAdmin)}>{this.isAdmin ? i18n.t('remove_as_admin') : i18n.t('appoint_as_admin')}</span> </li> @@ -230,7 +231,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */} {/* </div> */} <div class="form-group row"> - <button type="submit" class="btn btn-secondary">{i18n.t('ban')} {this.props.node.comment.creator_name}</button> + <button type="submit" class="btn btn-secondary">{i18n.t('ban')} {node.comment.creator_name}</button> </div> </form> } @@ -241,9 +242,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { disabled={this.props.locked} /> } - {this.props.node.children && !this.state.collapsed && + {node.children && !this.state.collapsed && <CommentNodes - nodes={this.props.node.children} + nodes={node.children} locked={this.props.locked} moderators={this.props.moderators} admins={this.props.admins} diff --git a/ui/src/components/post-listing.tsx b/ui/src/components/post-listing.tsx index 4a3b744a..ccd3ce45 100644 --- a/ui/src/components/post-listing.tsx +++ b/ui/src/components/post-listing.tsx @@ -1,10 +1,10 @@ import { Component, linkEvent } from 'inferno'; import { Link } from 'inferno-router'; import { WebSocketService, UserService } from '../services'; -import { Post, CreatePostLikeForm, PostForm as PostFormI, SavePostForm, CommunityUser, UserView } from '../interfaces'; +import { Post, CreatePostLikeForm, PostForm as PostFormI, SavePostForm, CommunityUser, UserView, BanType, BanFromCommunityForm, BanUserForm, AddModToCommunityForm, AddAdminForm, TransferSiteForm, TransferCommunityForm } from '../interfaces'; import { MomentTime } from './moment-time'; import { PostForm } from './post-form'; -import { mdToHtml, canMod, isMod, isImage } from '../utils'; +import { mdToHtml, canMod, isMod, isImage, getUnixTime } from '../utils'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; @@ -12,6 +12,12 @@ interface PostListingState { showEdit: boolean; showRemoveDialog: boolean; removeReason: string; + showBanDialog: boolean; + banReason: string; + banExpires: string; + banType: BanType; + showConfirmTransferSite: boolean; + showConfirmTransferCommunity: boolean; imageExpanded: boolean; } @@ -31,7 +37,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> { showEdit: false, showRemoveDialog: false, removeReason: null, - imageExpanded: false + showBanDialog: false, + banReason: null, + banExpires: null, + banType: BanType.Community, + showConfirmTransferSite: false, + showConfirmTransferCommunity: false, + imageExpanded: false, } constructor(props: any, context: any) { @@ -126,6 +138,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> { {this.isAdmin && <span className="mx-1 badge badge-light"><T i18nKey="admin">#</T></span> } + {(post.banned_from_community || post.banned) && + <span className="mx-1 badge badge-danger"><T i18nKey="banned">#</T></span> + } {this.props.showCommunity && <span> <span> {i18n.t('to')} </span> @@ -169,17 +184,79 @@ export class PostListing extends Component<PostListingProps, PostListingState> { </> } {this.canMod && - <span> + <> <li className="list-inline-item"> - {!this.props.post.removed ? + {!post.removed ? <span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}><T i18nKey="remove">#</T></span> : <span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}><T i18nKey="restore">#</T></span> } </li> <li className="list-inline-item"> - <span class="pointer" onClick={linkEvent(this, this.handleModLock)}>{this.props.post.locked ? i18n.t('unlock') : i18n.t('lock')}</span> + <span class="pointer" onClick={linkEvent(this, this.handleModLock)}>{post.locked ? i18n.t('unlock') : i18n.t('lock')}</span> </li> - </span> + </> + } + {/* Mods can ban from community, and appoint as mods to community */} + {this.canMod && + <> + {!this.isMod && + <li className="list-inline-item"> + {!post.banned_from_community ? + <span class="pointer" onClick={linkEvent(this, this.handleModBanFromCommunityShow)}><T i18nKey="ban">#</T></span> : + <span class="pointer" onClick={linkEvent(this, this.handleModBanFromCommunitySubmit)}><T i18nKey="unban">#</T></span> + } + </li> + } + {!post.banned_from_community && + <li className="list-inline-item"> + <span class="pointer" onClick={linkEvent(this, this.handleAddModToCommunity)}>{this.isMod ? i18n.t('remove_as_mod') : i18n.t('appoint_as_mod')}</span> + </li> + } + </> + } + {/* Community creators and admins can transfer community to another mod */} + {(this.amCommunityCreator || this.canAdmin) && this.isMod && + <li className="list-inline-item"> + {!this.state.showConfirmTransferCommunity ? + <span class="pointer" onClick={linkEvent(this, this.handleShowConfirmTransferCommunity)}><T i18nKey="transfer_community">#</T> + </span> : <> + <span class="d-inline-block mr-1"><T i18nKey="are_you_sure">#</T></span> + <span class="pointer d-inline-block mr-1" onClick={linkEvent(this, this.handleTransferCommunity)}><T i18nKey="yes">#</T></span> + <span class="pointer d-inline-block" onClick={linkEvent(this, this.handleCancelShowConfirmTransferCommunity)}><T i18nKey="no">#</T></span> + </> + } + </li> + } + {/* Admins can ban from all, and appoint other admins */} + {this.canAdmin && + <> + {!this.isAdmin && + <li className="list-inline-item"> + {!post.banned ? + <span class="pointer" onClick={linkEvent(this, this.handleModBanShow)}><T i18nKey="ban_from_site">#</T></span> : + <span class="pointer" onClick={linkEvent(this, this.handleModBanSubmit)}><T i18nKey="unban_from_site">#</T></span> + } + </li> + } + {!post.banned && + <li className="list-inline-item"> + <span class="pointer" onClick={linkEvent(this, this.handleAddAdmin)}>{this.isAdmin ? i18n.t('remove_as_admin') : i18n.t('appoint_as_admin')}</span> + </li> + } + </> + } + {/* Site Creator can transfer to another admin */} + {this.amSiteCreator && this.isAdmin && + <li className="list-inline-item"> + {!this.state.showConfirmTransferSite ? + <span class="pointer" onClick={linkEvent(this, this.handleShowConfirmTransferSite)}><T i18nKey="transfer_site">#</T> + </span> : <> + <span class="d-inline-block mr-1"><T i18nKey="are_you_sure">#</T></span> + <span class="pointer d-inline-block mr-1" onClick={linkEvent(this, this.handleTransferSite)}><T i18nKey="yes">#</T></span> + <span class="pointer d-inline-block" onClick={linkEvent(this, this.handleCancelShowConfirmTransferSite)}><T i18nKey="no">#</T></span> + </> + } + </li> } </ul> } @@ -189,7 +266,23 @@ export class PostListing extends Component<PostListingProps, PostListingState> { <button type="submit" class="btn btn-secondary"><T i18nKey="remove_post">#</T></button> </form> } - {this.props.showBody && this.props.post.body && <div className="md-div" dangerouslySetInnerHTML={mdToHtml(post.body)} />} + {this.state.showBanDialog && + <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}> + <div class="form-group row"> + <label class="col-form-label"><T i18nKey="reason">#</T></label> + <input type="text" class="form-control mr-2" placeholder={i18n.t('reason')} value={this.state.banReason} onInput={linkEvent(this, this.handleModBanReasonChange)} /> + </div> + {/* TODO hold off on expires until later */} + {/* <div class="form-group row"> */} + {/* <label class="col-form-label">Expires</label> */} + {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */} + {/* </div> */} + <div class="form-group row"> + <button type="submit" class="btn btn-secondary">{i18n.t('ban')} {post.creator_name}</button> + </div> + </form> + } + {this.props.showBody && post.body && <div className="md-div" dangerouslySetInnerHTML={mdToHtml(post.body)} />} </div> </div> ) @@ -218,6 +311,24 @@ export class PostListing extends Component<PostListingProps, PostListingState> { } else return false; } + get canAdmin(): boolean { + return this.props.admins && canMod(UserService.Instance.user, this.props.admins.map(a => a.id), this.props.post.creator_id); + } + + get amCommunityCreator(): boolean { + return this.props.moderators && + UserService.Instance.user && + (this.props.post.creator_id != UserService.Instance.user.id) && + (UserService.Instance.user.id == this.props.moderators[0].user_id); + } + + get amSiteCreator(): boolean { + return this.props.admins && + UserService.Instance.user && + (this.props.post.creator_id != UserService.Instance.user.id) && + (UserService.Instance.user.id == this.props.admins[0].id); + } + handlePostLike(i: PostListing) { let form: CreatePostLikeForm = { @@ -328,6 +439,124 @@ export class PostListing extends Component<PostListingProps, PostListingState> { WebSocketService.Instance.editPost(form); } + handleModBanFromCommunityShow(i: PostListing) { + i.state.showBanDialog = true; + i.state.banType = BanType.Community; + i.setState(i.state); + } + + handleModBanShow(i: PostListing) { + i.state.showBanDialog = true; + i.state.banType = BanType.Site; + i.setState(i.state); + } + + handleModBanReasonChange(i: PostListing, event: any) { + i.state.banReason = event.target.value; + i.setState(i.state); + } + + handleModBanExpiresChange(i: PostListing, event: any) { + i.state.banExpires = event.target.value; + i.setState(i.state); + } + + handleModBanFromCommunitySubmit(i: PostListing) { + i.state.banType = BanType.Community; + i.setState(i.state); + i.handleModBanBothSubmit(i); + } + + handleModBanSubmit(i: PostListing) { + i.state.banType = BanType.Site; + i.setState(i.state); + i.handleModBanBothSubmit(i); + } + + handleModBanBothSubmit(i: PostListing) { + event.preventDefault(); + + if (i.state.banType == BanType.Community) { + let form: BanFromCommunityForm = { + user_id: i.props.post.creator_id, + community_id: i.props.post.community_id, + ban: !i.props.post.banned_from_community, + reason: i.state.banReason, + expires: getUnixTime(i.state.banExpires), + }; + WebSocketService.Instance.banFromCommunity(form); + } else { + let form: BanUserForm = { + user_id: i.props.post.creator_id, + ban: !i.props.post.banned, + reason: i.state.banReason, + expires: getUnixTime(i.state.banExpires), + }; + WebSocketService.Instance.banUser(form); + } + + i.state.showBanDialog = false; + i.setState(i.state); + } + + handleAddModToCommunity(i: PostListing) { + let form: AddModToCommunityForm = { + user_id: i.props.post.creator_id, + community_id: i.props.post.community_id, + added: !i.isMod, + }; + WebSocketService.Instance.addModToCommunity(form); + i.setState(i.state); + } + + handleAddAdmin(i: PostListing) { + let form: AddAdminForm = { + user_id: i.props.post.creator_id, + added: !i.isAdmin, + }; + WebSocketService.Instance.addAdmin(form); + i.setState(i.state); + } + + handleShowConfirmTransferCommunity(i: PostListing) { + i.state.showConfirmTransferCommunity = true; + i.setState(i.state); + } + + handleCancelShowConfirmTransferCommunity(i: PostListing) { + i.state.showConfirmTransferCommunity = false; + i.setState(i.state); + } + + handleTransferCommunity(i: PostListing) { + let form: TransferCommunityForm = { + community_id: i.props.post.community_id, + user_id: i.props.post.creator_id, + }; + WebSocketService.Instance.transferCommunity(form); + i.state.showConfirmTransferCommunity = false; + i.setState(i.state); + } + + handleShowConfirmTransferSite(i: PostListing) { + i.state.showConfirmTransferSite = true; + i.setState(i.state); + } + + handleCancelShowConfirmTransferSite(i: PostListing) { + i.state.showConfirmTransferSite = false; + i.setState(i.state); + } + + handleTransferSite(i: PostListing) { + let form: TransferSiteForm = { + user_id: i.props.post.creator_id, + }; + WebSocketService.Instance.transferSite(form); + i.state.showConfirmTransferSite = false; + i.setState(i.state); + } + handleImageExpandClick(i: PostListing) { i.state.imageExpanded = !i.state.imageExpanded; i.setState(i.state); diff --git a/ui/src/components/post.tsx b/ui/src/components/post.tsx index 91f8f4db..7e2dbd62 100644 --- a/ui/src/components/post.tsx +++ b/ui/src/components/post.tsx @@ -351,6 +351,9 @@ export class Post extends Component<any, PostState> { let res: BanFromCommunityResponse = msg; this.state.comments.filter(c => c.creator_id == res.user.id) .forEach(c => c.banned_from_community = res.banned); + if (this.state.post.creator_id == res.user.id) { + this.state.post.banned_from_community = res.banned; + } this.setState(this.state); } else if (op == UserOperation.AddModToCommunity) { let res: AddModToCommunityResponse = msg; @@ -360,6 +363,9 @@ export class Post extends Component<any, PostState> { let res: BanUserResponse = msg; this.state.comments.filter(c => c.creator_id == res.user.id) .forEach(c => c.banned = res.banned); + if (this.state.post.creator_id == res.user.id) { + this.state.post.banned = res.banned; + } this.setState(this.state); } else if (op == UserOperation.AddAdmin) { let res: AddAdminResponse = msg; diff --git a/ui/src/components/user.tsx b/ui/src/components/user.tsx index 8b78917e..c5ba974f 100644 --- a/ui/src/components/user.tsx +++ b/ui/src/components/user.tsx @@ -45,6 +45,7 @@ export class User extends Component<any, UserState> { post_score: null, number_of_comments: null, comment_score: null, + banned: null, }, user_id: null, username: null, @@ -234,7 +235,14 @@ export class User extends Component<any, UserState> { <div> <div class="card border-secondary mb-3"> <div class="card-body"> - <h5>{user.name}</h5> + <h5> + <ul class="list-inline mb-0"> + <li className="list-inline-item">{user.name}</li> + {user.banned && + <li className="list-inline-item badge badge-danger"><T i18nKey="banned">#</T></li> + } + </ul> + </h5> <div>{i18n.t('joined')} <MomentTime data={user} /></div> <div class="table-responsive"> <table class="table table-bordered table-sm mt-2 mb-0"> diff --git a/ui/src/interfaces.ts b/ui/src/interfaces.ts index c9a647d6..f2675eb3 100644 --- a/ui/src/interfaces.ts +++ b/ui/src/interfaces.ts @@ -34,6 +34,7 @@ export interface UserView { post_score: number; number_of_comments: number; comment_score: number; + banned: boolean; } export interface CommunityUser { @@ -77,6 +78,8 @@ export interface Post { deleted: boolean; locked: boolean; nsfw: boolean; + banned: boolean; + banned_from_community: boolean; published: string; updated?: string; creator_name: string; @@ -138,6 +141,8 @@ export interface Site { number_of_communities: number; } +export enum BanType {Community, Site}; + export interface FollowCommunityForm { community_id: number; follow: boolean; diff --git a/ui/src/translations/en.ts b/ui/src/translations/en.ts index 1ddf087d..2d3523e9 100644 --- a/ui/src/translations/en.ts +++ b/ui/src/translations/en.ts @@ -56,6 +56,7 @@ export const en = { ban_from_site: 'ban from site', unban: 'unban', unban_from_site: 'unban from site', + banned: 'banned', save: 'save', unsave: 'unsave', create: 'create', |