FEATURE: Penalty history improvements (#13359)
* FEATURE: add penalty history when silencing a user Display penalty history (last 6 months) when silencing/suspending a user * FEATURE: allow default penalty values to be chosen Adds a site setting that designates default penalty values in hours. Silence/suspend modals will auto-fill in the default values, but otherwise will still allow moderators to pick and overwrite values as normal. First silence/suspend: first value Second silence/suspend: second value etc. Penalty counts are forgiven at the same rate as tl3 promotion requirements do. Co-authored-by: jjaffeux <j.jaffeux@gmail.com>
This commit is contained in:
parent
729a9856f8
commit
d87a0216bb
|
@ -0,0 +1,22 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
classNames: ["penalty-history"],
|
||||||
|
|
||||||
|
@discourseComputed("user.penalty_counts.suspended")
|
||||||
|
suspendedCountClass(count) {
|
||||||
|
if (count > 0) {
|
||||||
|
return "danger";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("user.penalty_counts.silenced")
|
||||||
|
silencedCountClass(count) {
|
||||||
|
if (count > 0) {
|
||||||
|
return "danger";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
});
|
|
@ -12,6 +12,10 @@ export default Controller.extend(PenaltyController, {
|
||||||
this.setProperties({ silenceUntil: null, silencing: false });
|
this.setProperties({ silenceUntil: null, silencing: false });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
finishedSetup() {
|
||||||
|
this.set("silenceUntil", this.user?.next_penalty);
|
||||||
|
},
|
||||||
|
|
||||||
@discourseComputed("silenceUntil", "reason", "silencing")
|
@discourseComputed("silenceUntil", "reason", "silencing")
|
||||||
submitDisabled(silenceUntil, reason, silencing) {
|
submitDisabled(silenceUntil, reason, silencing) {
|
||||||
return silencing || isEmpty(silenceUntil) || !reason || reason.length < 1;
|
return silencing || isEmpty(silenceUntil) || !reason || reason.length < 1;
|
||||||
|
|
|
@ -12,6 +12,10 @@ export default Controller.extend(PenaltyController, {
|
||||||
this.setProperties({ suspendUntil: null, suspending: false });
|
this.setProperties({ suspendUntil: null, suspending: false });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
finishedSetup() {
|
||||||
|
this.set("suspendUntil", this.user?.next_penalty);
|
||||||
|
},
|
||||||
|
|
||||||
@discourseComputed("suspendUntil", "reason", "suspending")
|
@discourseComputed("suspendUntil", "reason", "suspending")
|
||||||
submitDisabled(suspendUntil, reason, suspending) {
|
submitDisabled(suspendUntil, reason, suspending) {
|
||||||
return suspending || isEmpty(suspendUntil) || !reason || reason.length < 1;
|
return suspending || isEmpty(suspendUntil) || !reason || reason.length < 1;
|
||||||
|
|
|
@ -48,7 +48,6 @@ export default Service.extend({
|
||||||
|
|
||||||
_showControlModal(type, user, opts) {
|
_showControlModal(type, user, opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
|
||||||
let controller = showModal(`admin-${type}-user`, {
|
let controller = showModal(`admin-${type}-user`, {
|
||||||
admin: true,
|
admin: true,
|
||||||
modalClass: `${type}-user-modal`,
|
modalClass: `${type}-user-modal`,
|
||||||
|
@ -65,6 +64,8 @@ export default Service.extend({
|
||||||
before: opts.before,
|
before: opts.before,
|
||||||
successCallback: opts.successCallback,
|
successCallback: opts.successCallback,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
controller.finishedSetup();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div class="suspended-count {{suspendedCountClass}}" title={{i18n "admin.user.last_six_months"}}>
|
||||||
|
<label>{{i18n "admin.user.suspended_count"}}</label>
|
||||||
|
<span>{{user.penalty_counts.suspended}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="silenced-count {{silencedCountClass}}" title={{i18n "admin.user.last_six_months"}}>
|
||||||
|
<label>{{i18n "admin.user.silenced_count"}}</label>
|
||||||
|
<span>{{user.penalty_counts.silenced}}</span>
|
||||||
|
</div>
|
|
@ -5,6 +5,8 @@
|
||||||
<div class="alert alert-error">{{errorMessage}}</div>
|
<div class="alert alert-error">{{errorMessage}}</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{admin-penalty-history user=user}}
|
||||||
|
|
||||||
<div class="until-controls">
|
<div class="until-controls">
|
||||||
<label>
|
<label>
|
||||||
{{future-date-input
|
{{future-date-input
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if user.canSuspend}}
|
{{#if user.canSuspend}}
|
||||||
|
{{admin-penalty-history user=user}}
|
||||||
|
|
||||||
<div class="until-controls">
|
<div class="until-controls">
|
||||||
<label>
|
<label>
|
||||||
{{future-date-input
|
{{future-date-input
|
||||||
|
|
|
@ -14,9 +14,10 @@ export default Component.extend({
|
||||||
displayLabel: null,
|
displayLabel: null,
|
||||||
labelClasses: null,
|
labelClasses: null,
|
||||||
|
|
||||||
|
timeInputDisabled: empty("date"),
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
|
|
||||||
if (this.input) {
|
if (this.input) {
|
||||||
const datetime = moment(this.input);
|
const datetime = moment(this.input);
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
|
@ -27,8 +28,6 @@ export default Component.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
timeInputDisabled: empty("date"),
|
|
||||||
|
|
||||||
@observes("date", "time")
|
@observes("date", "time")
|
||||||
_updateInput() {
|
_updateInput() {
|
||||||
if (!this.date) {
|
if (!this.date) {
|
||||||
|
|
|
@ -35,4 +35,24 @@
|
||||||
height: 10em;
|
height: 10em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.penalty-history {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
border-bottom: 1px solid var(--primary-low);
|
||||||
|
display: flex;
|
||||||
|
& > * {
|
||||||
|
flex-basis: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 1em 0;
|
||||||
|
font-weight: 600;
|
||||||
|
label {
|
||||||
|
font-weight: 600;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.danger {
|
||||||
|
background-color: var(--danger);
|
||||||
|
color: var(--secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ class AdminDetailedUserSerializer < AdminUserSerializer
|
||||||
:full_suspend_reason,
|
:full_suspend_reason,
|
||||||
:suspended_till,
|
:suspended_till,
|
||||||
:silence_reason,
|
:silence_reason,
|
||||||
|
:penalty_counts,
|
||||||
|
:next_penalty,
|
||||||
:primary_group_id,
|
:primary_group_id,
|
||||||
:badge_count,
|
:badge_count,
|
||||||
:warnings_received_count,
|
:warnings_received_count,
|
||||||
|
@ -96,6 +98,20 @@ class AdminDetailedUserSerializer < AdminUserSerializer
|
||||||
object.silence_reason
|
object.silence_reason
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def penalty_counts
|
||||||
|
TrustLevel3Requirements.new(object).penalty_counts
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_penalty
|
||||||
|
step_number = penalty_counts.total
|
||||||
|
steps = SiteSetting.penalty_step_hours.split('|')
|
||||||
|
step_number = [step_number, steps.length].min
|
||||||
|
penalty_hours = steps[step_number]
|
||||||
|
Integer(penalty_hours, 10).hours.from_now
|
||||||
|
rescue
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
def silenced_by
|
def silenced_by
|
||||||
object.silenced_record.try(:acting_user)
|
object.silenced_record.try(:acting_user)
|
||||||
end
|
end
|
||||||
|
|
|
@ -4858,6 +4858,7 @@ en:
|
||||||
penalty_post_edit: "Edit the post"
|
penalty_post_edit: "Edit the post"
|
||||||
penalty_post_none: "Do nothing"
|
penalty_post_none: "Do nothing"
|
||||||
penalty_count: "Penalty Count"
|
penalty_count: "Penalty Count"
|
||||||
|
penalty_history: "Penalty History"
|
||||||
clear_penalty_history:
|
clear_penalty_history:
|
||||||
title: "Clear Penalty History"
|
title: "Clear Penalty History"
|
||||||
description: "users with penalties cannot reach TL3"
|
description: "users with penalties cannot reach TL3"
|
||||||
|
@ -4997,6 +4998,9 @@ en:
|
||||||
trust_level_unlocked_tip: "trust level is unlocked, system will may promote or demote user"
|
trust_level_unlocked_tip: "trust level is unlocked, system will may promote or demote user"
|
||||||
lock_trust_level: "Lock Trust Level"
|
lock_trust_level: "Lock Trust Level"
|
||||||
unlock_trust_level: "Unlock Trust Level"
|
unlock_trust_level: "Unlock Trust Level"
|
||||||
|
silenced_count: "Silenced"
|
||||||
|
suspended_count: "Suspended"
|
||||||
|
last_six_months: "Last 6 months"
|
||||||
tl3_requirements:
|
tl3_requirements:
|
||||||
title: "Requirements for Trust Level 3"
|
title: "Requirements for Trust Level 3"
|
||||||
table_title:
|
table_title:
|
||||||
|
|
|
@ -2160,6 +2160,7 @@ en:
|
||||||
share_anonymized_statistics: "Share anonymized usage statistics."
|
share_anonymized_statistics: "Share anonymized usage statistics."
|
||||||
|
|
||||||
auto_handle_queued_age: "Automatically handle records that are waiting to be reviewed after this many days. Flags will be ignored. Queued posts and users will be rejected. Set to 0 to disable this feature."
|
auto_handle_queued_age: "Automatically handle records that are waiting to be reviewed after this many days. Flags will be ignored. Queued posts and users will be rejected. Set to 0 to disable this feature."
|
||||||
|
penalty_step_hours: "Default penalties for silencing or suspending users in hours. First offense defaults to the first value, second offense defaults to the second value, etc."
|
||||||
svg_icon_subset: "Add additional FontAwesome 5 icons that you would like to include in your assets. Use prefix 'fa-' for solid icons, 'far-' for regular icons and 'fab-' for brand icons."
|
svg_icon_subset: "Add additional FontAwesome 5 icons that you would like to include in your assets. Use prefix 'fa-' for solid icons, 'far-' for regular icons and 'fab-' for brand icons."
|
||||||
max_prints_per_hour_per_user: "Maximum number of /print page impressions (set to 0 to disable)"
|
max_prints_per_hour_per_user: "Maximum number of /print page impressions (set to 0 to disable)"
|
||||||
|
|
||||||
|
|
|
@ -2270,6 +2270,11 @@ uncategorized:
|
||||||
default: 60
|
default: 60
|
||||||
min: 0
|
min: 0
|
||||||
|
|
||||||
|
penalty_step_hours:
|
||||||
|
default: "24|72|168|720"
|
||||||
|
type: "list"
|
||||||
|
list_type: "compact"
|
||||||
|
|
||||||
svg_icon_subset:
|
svg_icon_subset:
|
||||||
default: ""
|
default: ""
|
||||||
type: "list"
|
type: "list"
|
||||||
|
|
Loading…
Reference in New Issue