Temp bans (#524)

* Updating translations.

* Adding temp bans.

* Using new lemmy-js-client with temp bans

* Fixing some lint and dep issues
This commit is contained in:
Dessalines 2022-01-09 12:53:11 -05:00 committed by GitHub
parent 9abe811021
commit 4915193ae5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 1362 additions and 2006 deletions

@ -1 +1 @@
Subproject commit 1e0bb9920cda13bb128c87e85125b98ab8f319b6 Subproject commit 27a29ad5411438e4792e8e298aa5f4a3b60bb908

View file

@ -20,7 +20,7 @@
"@typescript-eslint/parser": "^5.6.0", "@typescript-eslint/parser": "^5.6.0",
"autosize": "^5.0.1", "autosize": "^5.0.1",
"check-password-strength": "^2.0.3", "check-password-strength": "^2.0.3",
"choices.js": "^9.0.1", "choices.js": "^10.0.0",
"emoji-short-name": "^1.0.0", "emoji-short-name": "^1.0.0",
"express": "~4.17.1", "express": "~4.17.1",
"i18next": "^21.5.4", "i18next": "^21.5.4",
@ -41,6 +41,7 @@
"moment": "^2.29.1", "moment": "^2.29.1",
"register-service-worker": "^1.7.2", "register-service-worker": "^1.7.2",
"rxjs": "^7.4.0", "rxjs": "^7.4.0",
"sass": "^1.47.0",
"serialize-javascript": "^6.0.0", "serialize-javascript": "^6.0.0",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"toastify-js": "^1.11.2", "toastify-js": "^1.11.2",
@ -51,12 +52,12 @@
"@babel/core": "^7.16.0", "@babel/core": "^7.16.0",
"@babel/plugin-transform-runtime": "^7.16.4", "@babel/plugin-transform-runtime": "^7.16.4",
"@babel/plugin-transform-typescript": "^7.16.1", "@babel/plugin-transform-typescript": "^7.16.1",
"@babel/preset-env": "7.16.4", "@babel/preset-env": "7.16.7",
"@babel/preset-typescript": "^7.16.0", "@babel/preset-typescript": "^7.16.0",
"@babel/runtime": "^7.16.3", "@babel/runtime": "^7.16.3",
"@types/autosize": "^4.0.0", "@types/autosize": "^4.0.0",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/node": "^16.11.11", "@types/node": "^17.0.8",
"@types/node-fetch": "^2.5.11", "@types/node-fetch": "^2.5.11",
"@types/serialize-javascript": "^5.0.1", "@types/serialize-javascript": "^5.0.1",
"@typescript-eslint/eslint-plugin": "^5.6.0", "@typescript-eslint/eslint-plugin": "^5.6.0",
@ -72,11 +73,10 @@
"husky": "^7.0.4", "husky": "^7.0.4",
"import-sort-style-module": "^6.0.0", "import-sort-style-module": "^6.0.0",
"iso-639-1": "^2.1.10", "iso-639-1": "^2.1.10",
"lemmy-js-client": "0.15.0-rc.6", "lemmy-js-client": "0.15.0-rc.34",
"lint-staged": "^12.1.2", "lint-staged": "^12.1.2",
"mini-css-extract-plugin": "^2.4.5", "mini-css-extract-plugin": "^2.4.5",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"node-sass": "^7.0.0",
"prettier": "^2.5.1", "prettier": "^2.5.1",
"prettier-plugin-import-sort": "^0.0.7", "prettier-plugin-import-sort": "^0.0.7",
"prettier-plugin-organize-imports": "^2.3.4", "prettier-plugin-organize-imports": "^2.3.4",
@ -90,7 +90,7 @@
"typescript": "^4.5.2", "typescript": "^4.5.2",
"webpack": "5.65.0", "webpack": "5.65.0",
"webpack-cli": "^4.9.1", "webpack-cli": "^4.9.1",
"webpack-dev-server": "4.6.0", "webpack-dev-server": "4.7.2",
"webpack-node-externals": "^3.0.0" "webpack-node-externals": "^3.0.0"
}, },
"engines": { "engines": {

View file

@ -28,7 +28,8 @@ import {
authField, authField,
canMod, canMod,
colorList, colorList,
getUnixTime, futureDaysToUnixTime,
isBanned,
isMod, isMod,
mdToHtml, mdToHtml,
numToSI, numToSI,
@ -51,7 +52,7 @@ interface CommentNodeState {
showBanDialog: boolean; showBanDialog: boolean;
removeData: boolean; removeData: boolean;
banReason: string; banReason: string;
banExpires: string; banExpireDays: number;
banType: BanType; banType: BanType;
showConfirmTransferSite: boolean; showConfirmTransferSite: boolean;
showConfirmTransferCommunity: boolean; showConfirmTransferCommunity: boolean;
@ -96,7 +97,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
showBanDialog: false, showBanDialog: false,
removeData: false, removeData: false,
banReason: null, banReason: null,
banExpires: null, banExpireDays: null,
banType: BanType.Community, banType: BanType.Community,
collapsed: false, collapsed: false,
viewSource: false, viewSource: false,
@ -187,7 +188,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{i18n.t("bot_account").toLowerCase()} {i18n.t("bot_account").toLowerCase()}
</div> </div>
)} )}
{(cv.creator_banned_from_community || cv.creator.banned) && ( {(cv.creator_banned_from_community || isBanned(cv.creator)) && (
<div className="badge badge-danger mr-2"> <div className="badge badge-danger mr-2">
{i18n.t("banned")} {i18n.t("banned")}
</div> </div>
@ -614,7 +615,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{this.canAdmin && ( {this.canAdmin && (
<> <>
{!this.isAdmin && {!this.isAdmin &&
(!cv.creator.banned ? ( (!isBanned(cv.creator) ? (
<button <button
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
onClick={linkEvent( onClick={linkEvent(
@ -637,7 +638,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{i18n.t("unban_from_site")} {i18n.t("unban_from_site")}
</button> </button>
))} ))}
{!cv.creator.banned && {!isBanned(cv.creator) &&
cv.creator.local && cv.creator.local &&
(!this.state.showConfirmAppointAsAdmin ? ( (!this.state.showConfirmAppointAsAdmin ? (
<button <button
@ -797,7 +798,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
)} )}
{this.state.showBanDialog && ( {this.state.showBanDialog && (
<form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}> <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
<div class="form-group row"> <div class="form-group row col-12">
<label <label
class="col-form-label" class="col-form-label"
htmlFor={`mod-ban-reason-${cv.comment.id}`} htmlFor={`mod-ban-reason-${cv.comment.id}`}
@ -812,6 +813,20 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
value={this.state.banReason} value={this.state.banReason}
onInput={linkEvent(this, this.handleModBanReasonChange)} onInput={linkEvent(this, this.handleModBanReasonChange)}
/> />
<label
class="col-form-label"
htmlFor={`mod-ban-expires-${cv.comment.id}`}
>
{i18n.t("expires")}
</label>
<input
type="number"
id={`mod-ban-expires-${cv.comment.id}`}
class="form-control mr-2"
placeholder={i18n.t("number_of_days")}
value={this.state.banExpireDays}
onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
/>
<div class="form-group"> <div class="form-group">
<div class="form-check"> <div class="form-check">
<input <input
@ -1191,8 +1206,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
i.setState(i.state); i.setState(i.state);
} }
handleModBanExpiresChange(i: CommentNode, event: any) { handleModBanExpireDaysChange(i: CommentNode, event: any) {
i.state.banExpires = event.target.value; i.state.banExpireDays = event.target.value;
i.setState(i.state); i.setState(i.state);
} }
@ -1223,7 +1238,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
ban, ban,
remove_data: i.state.removeData, remove_data: i.state.removeData,
reason: i.state.banReason, reason: i.state.banReason,
expires: getUnixTime(i.state.banExpires), expires: futureDaysToUnixTime(i.state.banExpireDays),
auth: authField(), auth: authField(),
}; };
WebSocketService.Instance.send(wsClient.banFromCommunity(form)); WebSocketService.Instance.send(wsClient.banFromCommunity(form));
@ -1238,7 +1253,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
ban, ban,
remove_data: i.state.removeData, remove_data: i.state.removeData,
reason: i.state.banReason, reason: i.state.banReason,
expires: getUnixTime(i.state.banExpires), expires: futureDaysToUnixTime(i.state.banExpireDays),
auth: authField(), auth: authField(),
}; };
WebSocketService.Instance.send(wsClient.banPerson(form)); WebSocketService.Instance.send(wsClient.banPerson(form));

View file

@ -1,9 +1,12 @@
import autosize from "autosize"; import autosize from "autosize";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { import {
BannedPersonsResponse,
GetBannedPersons,
GetSiteConfig, GetSiteConfig,
GetSiteConfigResponse, GetSiteConfigResponse,
GetSiteResponse, GetSiteResponse,
PersonViewSafe,
SaveSiteConfig, SaveSiteConfig,
SiteResponse, SiteResponse,
UserOperation, UserOperation,
@ -34,6 +37,7 @@ interface AdminSettingsState {
siteConfigRes: GetSiteConfigResponse; siteConfigRes: GetSiteConfigResponse;
siteConfigForm: SaveSiteConfig; siteConfigForm: SaveSiteConfig;
loading: boolean; loading: boolean;
banned: PersonViewSafe[];
siteConfigLoading: boolean; siteConfigLoading: boolean;
} }
@ -45,11 +49,12 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
siteRes: this.isoData.site_res, siteRes: this.isoData.site_res,
siteConfigForm: { siteConfigForm: {
config_hjson: null, config_hjson: null,
auth: authField(), auth: null,
}, },
siteConfigRes: { siteConfigRes: {
config_hjson: null, config_hjson: null,
}, },
banned: [],
loading: true, loading: true,
siteConfigLoading: null, siteConfigLoading: null,
}; };
@ -67,20 +72,34 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
this.state.siteConfigRes = this.isoData.routeData[0]; this.state.siteConfigRes = this.isoData.routeData[0];
this.state.siteConfigForm.config_hjson = this.state.siteConfigForm.config_hjson =
this.state.siteConfigRes.config_hjson; this.state.siteConfigRes.config_hjson;
this.state.banned = this.isoData.routeData[1].banned;
this.state.siteConfigLoading = false; this.state.siteConfigLoading = false;
this.state.loading = false; this.state.loading = false;
} else { } else {
this.state.siteConfigForm.auth = authField();
WebSocketService.Instance.send( WebSocketService.Instance.send(
wsClient.getSiteConfig({ wsClient.getSiteConfig({
auth: authField(), auth: authField(),
}) })
); );
WebSocketService.Instance.send(
wsClient.getBannedPersons({
auth: authField(),
})
);
} }
} }
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
let form: GetSiteConfig = { auth: req.auth }; let promises: Promise<any>[] = [];
return [req.client.getSiteConfig(form)];
let siteConfigForm: GetSiteConfig = { auth: req.auth };
promises.push(req.client.getSiteConfig(siteConfigForm));
let bannedPersonsForm: GetBannedPersons = { auth: req.auth };
promises.push(req.client.getBannedPersons(bannedPersonsForm));
return promises;
} }
componentDidMount() { componentDidMount() {
@ -105,10 +124,6 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
render() { render() {
return ( return (
<div class="container"> <div class="container">
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
/>
{this.state.loading ? ( {this.state.loading ? (
<h5> <h5>
<Spinner large /> <Spinner large />
@ -116,6 +131,10 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
) : ( ) : (
<div class="row"> <div class="row">
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
/>
{this.state.siteRes.site_view.site.id && ( {this.state.siteRes.site_view.site.id && (
<SiteForm site={this.state.siteRes.site_view.site} /> <SiteForm site={this.state.siteRes.site_view.site} />
)} )}
@ -149,7 +168,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
<> <>
<h5>{i18n.t("banned_users")}</h5> <h5>{i18n.t("banned_users")}</h5>
<ul class="list-unstyled"> <ul class="list-unstyled">
{this.state.siteRes.banned.map(banned => ( {this.state.banned.map(banned => (
<li class="list-inline-item"> <li class="list-inline-item">
<PersonListing person={banned.person} /> <PersonListing person={banned.person} />
</li> </li>
@ -225,6 +244,10 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
this.state.siteRes.site_view = data.site_view; this.state.siteRes.site_view = data.site_view;
this.setState(this.state); this.setState(this.state);
toast(i18n.t("site_saved")); toast(i18n.t("site_saved"));
} else if (op == UserOperation.GetBannedPersons) {
let data = wsJsonToRes<BannedPersonsResponse>(msg).data;
this.state.banned = data.banned;
this.setState(this.state);
} else if (op == UserOperation.GetSiteConfig) { } else if (op == UserOperation.GetSiteConfig) {
let data = wsJsonToRes<GetSiteConfigResponse>(msg).data; let data = wsJsonToRes<GetSiteConfigResponse>(msg).data;
this.state.siteConfigRes = data; this.state.siteConfigRes = data;

View file

@ -896,19 +896,6 @@ export class Home extends Component<any, HomeState> {
this.setState(this.state); this.setState(this.state);
} else if (op == UserOperation.BanPerson) { } else if (op == UserOperation.BanPerson) {
let data = wsJsonToRes<BanPersonResponse>(msg).data; let data = wsJsonToRes<BanPersonResponse>(msg).data;
let found = this.state.siteRes.banned.find(
p => (p.person.id = data.person_view.person.id)
);
// Remove the banned if its found in the list, and the action is an unban
if (found && !data.banned) {
this.state.siteRes.banned = this.state.siteRes.banned.filter(
i => i.person.id !== data.person_view.person.id
);
} else {
this.state.siteRes.banned.push(data.person_view);
}
this.state.posts this.state.posts
.filter(p => p.creator.id == data.person_view.person.id) .filter(p => p.creator.id == data.person_view.person.id)
.forEach(p => (p.creator.banned = data.banned)); .forEach(p => (p.creator.banned = data.banned));

View file

@ -26,8 +26,9 @@ import { UserService, WebSocketService } from "../../services";
import { import {
authField, authField,
canMod, canMod,
getUnixTime, futureDaysToUnixTime,
hostname, hostname,
isBanned,
isImage, isImage,
isMod, isMod,
isVideo, isVideo,
@ -54,7 +55,7 @@ interface PostListingState {
showBanDialog: boolean; showBanDialog: boolean;
removeData: boolean; removeData: boolean;
banReason: string; banReason: string;
banExpires: string; banExpireDays: number;
banType: BanType; banType: BanType;
showConfirmTransferSite: boolean; showConfirmTransferSite: boolean;
showConfirmTransferCommunity: boolean; showConfirmTransferCommunity: boolean;
@ -90,7 +91,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
showBanDialog: false, showBanDialog: false,
removeData: false, removeData: false,
banReason: null, banReason: null,
banExpires: null, banExpireDays: null,
banType: BanType.Community, banType: BanType.Community,
showConfirmTransferSite: false, showConfirmTransferSite: false,
showConfirmTransferCommunity: false, showConfirmTransferCommunity: false,
@ -294,7 +295,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</span> </span>
)} )}
{(post_view.creator_banned_from_community || {(post_view.creator_banned_from_community ||
post_view.creator.banned) && ( isBanned(post_view.creator)) && (
<span className="mx-1 badge badge-danger">{i18n.t("banned")}</span> <span className="mx-1 badge badge-danger">{i18n.t("banned")}</span>
)} )}
{post_view.creator_blocked && ( {post_view.creator_blocked && (
@ -900,7 +901,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{this.canAdmin && ( {this.canAdmin && (
<> <>
{!this.isAdmin && {!this.isAdmin &&
(!post_view.creator.banned ? ( (!isBanned(post_view.creator) ? (
<button <button
class="btn btn-link btn-animate text-muted py-0" class="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleModBanShow)} onClick={linkEvent(this, this.handleModBanShow)}
@ -917,7 +918,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{i18n.t("unban_from_site")} {i18n.t("unban_from_site")}
</button> </button>
))} ))}
{!post_view.creator.banned && post_view.creator.local && ( {!isBanned(post_view.creator) && post_view.creator.local && (
<button <button
class="btn btn-link btn-animate text-muted py-0" class="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleAddAdmin)} onClick={linkEvent(this, this.handleAddAdmin)}
@ -1013,7 +1014,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
)} )}
{this.state.showBanDialog && ( {this.state.showBanDialog && (
<form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}> <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
<div class="form-group row"> <div class="form-group row col-12">
<label class="col-form-label" htmlFor="post-listing-ban-reason"> <label class="col-form-label" htmlFor="post-listing-ban-reason">
{i18n.t("reason")} {i18n.t("reason")}
</label> </label>
@ -1025,6 +1026,17 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
value={this.state.banReason} value={this.state.banReason}
onInput={linkEvent(this, this.handleModBanReasonChange)} onInput={linkEvent(this, this.handleModBanReasonChange)}
/> />
<label class="col-form-label" htmlFor={`mod-ban-expires`}>
{i18n.t("expires")}
</label>
<input
type="number"
id={`mod-ban-expires`}
class="form-control mr-2"
placeholder={i18n.t("number_of_days")}
value={this.state.banExpireDays}
onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
/>
<div class="form-group"> <div class="form-group">
<div class="form-check"> <div class="form-check">
<input <input
@ -1496,8 +1508,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
i.setState(i.state); i.setState(i.state);
} }
handleModBanExpiresChange(i: PostListing, event: any) { handleModBanExpireDaysChange(i: PostListing, event: any) {
i.state.banExpires = event.target.value; i.state.banExpireDays = event.target.value;
i.setState(i.state); i.setState(i.state);
} }
@ -1528,7 +1540,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
ban, ban,
remove_data: i.state.removeData, remove_data: i.state.removeData,
reason: i.state.banReason, reason: i.state.banReason,
expires: getUnixTime(i.state.banExpires), expires: futureDaysToUnixTime(i.state.banExpireDays),
auth: authField(), auth: authField(),
}; };
WebSocketService.Instance.send(wsClient.banFromCommunity(form)); WebSocketService.Instance.send(wsClient.banFromCommunity(form));
@ -1543,7 +1555,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
ban, ban,
remove_data: i.state.removeData, remove_data: i.state.removeData,
reason: i.state.banReason, reason: i.state.banReason,
expires: getUnixTime(i.state.banExpires), expires: futureDaysToUnixTime(i.state.banExpireDays),
auth: authField(), auth: authField(),
}; };
WebSocketService.Instance.send(wsClient.banPerson(form)); WebSocketService.Instance.send(wsClient.banPerson(form));

View file

@ -14,6 +14,7 @@ import {
ListingType, ListingType,
MyUserInfo, MyUserInfo,
PersonBlockView, PersonBlockView,
PersonSafe,
PersonViewSafe, PersonViewSafe,
PostReportView, PostReportView,
PostView, PostView,
@ -285,6 +286,14 @@ export function getUnixTime(text: string): number {
return text ? new Date(text).getTime() / 1000 : undefined; return text ? new Date(text).getTime() / 1000 : undefined;
} }
export function futureDaysToUnixTime(days: number): number {
return days
? Math.trunc(
new Date(Date.now() + 1000 * 60 * 60 * 24 * days).getTime() / 1000
)
: undefined;
}
export function canMod( export function canMod(
myUserInfo: MyUserInfo, myUserInfo: MyUserInfo,
modIds: number[], modIds: number[],
@ -1517,3 +1526,16 @@ const SHORTNUM_SI_FORMAT = new Intl.NumberFormat("en-US", {
export function numToSI(value: number): string { export function numToSI(value: number): string {
return SHORTNUM_SI_FORMAT.format(value); return SHORTNUM_SI_FORMAT.format(value);
} }
export function isBanned(ps: PersonSafe): boolean {
// Add Z to convert from UTC date
if (ps.ban_expires) {
if (ps.banned && new Date(ps.ban_expires + "Z") > new Date()) {
return true;
} else {
return false;
}
} else {
return ps.banned;
}
}

3209
yarn.lock

File diff suppressed because it is too large Load diff