diff --git a/app/assets/javascripts/admin/addon/components/penalty-post-action.js b/app/assets/javascripts/admin/addon/components/admin-penalty-post-action.js
similarity index 100%
rename from app/assets/javascripts/admin/addon/components/penalty-post-action.js
rename to app/assets/javascripts/admin/addon/components/admin-penalty-post-action.js
index e0102724474..eb201ae0899 100644
--- a/app/assets/javascripts/admin/addon/components/penalty-post-action.js
+++ b/app/assets/javascripts/admin/addon/components/admin-penalty-post-action.js
@@ -1,9 +1,9 @@
+import Component from "@ember/component";
+import { equal } from "@ember/object/computed";
import discourseComputed, {
afterRender,
} from "discourse-common/utils/decorators";
-import Component from "@ember/component";
import I18n from "I18n";
-import { equal } from "@ember/object/computed";
const ACTIONS = ["delete", "delete_replies", "edit", "none"];
diff --git a/app/assets/javascripts/admin/addon/components/suspension-details.js b/app/assets/javascripts/admin/addon/components/admin-penalty-reason.js
similarity index 100%
rename from app/assets/javascripts/admin/addon/components/suspension-details.js
rename to app/assets/javascripts/admin/addon/components/admin-penalty-reason.js
index fe931ae12e7..ca6164e7630 100644
--- a/app/assets/javascripts/admin/addon/components/suspension-details.js
+++ b/app/assets/javascripts/admin/addon/components/admin-penalty-reason.js
@@ -1,8 +1,8 @@
import Component from "@ember/component";
-import I18n from "I18n";
import { action } from "@ember/object";
-import discourseComputed from "discourse-common/utils/decorators";
import { equal } from "@ember/object/computed";
+import discourseComputed from "discourse-common/utils/decorators";
+import I18n from "I18n";
const CUSTOM_REASON_KEY = "custom";
diff --git a/app/assets/javascripts/admin/addon/components/admin-penalty-similar-users.js b/app/assets/javascripts/admin/addon/components/admin-penalty-similar-users.js
index 0e0158e8684..241927a7b7f 100644
--- a/app/assets/javascripts/admin/addon/components/admin-penalty-similar-users.js
+++ b/app/assets/javascripts/admin/addon/components/admin-penalty-similar-users.js
@@ -5,7 +5,7 @@ import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
tagName: "",
- @discourseComputed("type")
+ @discourseComputed("penaltyType")
penaltyField(penaltyType) {
if (penaltyType === "suspend") {
return "can_be_suspended";
diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-penalize-user.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-penalize-user.js
new file mode 100644
index 00000000000..4120bc34ff4
--- /dev/null
+++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-penalize-user.js
@@ -0,0 +1,168 @@
+import Controller from "@ember/controller";
+import { action } from "@ember/object";
+import { next } from "@ember/runloop";
+import { inject as service } from "@ember/service";
+import { isEmpty } from "@ember/utils";
+import discourseComputed from "discourse-common/utils/decorators";
+import { extractError } from "discourse/lib/ajax-error";
+import ModalFunctionality from "discourse/mixins/modal-functionality";
+import I18n from "I18n";
+
+export default Controller.extend(ModalFunctionality, {
+ dialog: service(),
+
+ loadingUser: false,
+ errorMessage: null,
+ penaltyType: null,
+ penalizeUntil: null,
+ reason: null,
+ message: null,
+ postId: null,
+ postAction: null,
+ postEdit: null,
+ user: null,
+ otherUserIds: null,
+ loading: false,
+ confirmClose: false,
+
+ onShow() {
+ this.setProperties({
+ loadingUser: true,
+ errorMessage: null,
+ penaltyType: null,
+ penalizeUntil: null,
+ reason: null,
+ message: null,
+ postId: null,
+ postAction: "delete",
+ postEdit: null,
+ user: null,
+ otherUserIds: [],
+ loading: false,
+ errorMessage: null,
+ reason: null,
+ message: null,
+ confirmClose: false,
+ });
+ },
+
+ finishedSetup() {
+ this.set("penalizeUntil", this.user?.next_penalty);
+ },
+
+ beforeClose() {
+ if (this.confirmClose) {
+ return true;
+ }
+
+ if (
+ (this.reason && this.reason.length > 1) ||
+ (this.message && this.message.length > 1)
+ ) {
+ this.send("hideModal");
+ this.dialog.confirm({
+ message: I18n.t("admin.user.confirm_cancel_penalty"),
+ didConfirm: () => {
+ next(() => {
+ this.set("confirmClose", true);
+ this.send("closeModal");
+ });
+ },
+ didCancel: () => this.send("reopenModal"),
+ });
+
+ return false;
+ }
+ },
+
+ @discourseComputed("penaltyType")
+ modalTitle(penaltyType) {
+ if (penaltyType === "suspend") {
+ return "admin.user.suspend_modal_title";
+ } else if (penaltyType === "silence") {
+ return "admin.user.silence_modal_title";
+ }
+ },
+
+ @discourseComputed("penaltyType")
+ buttonLabel(penaltyType) {
+ if (penaltyType === "suspend") {
+ return "admin.user.suspend";
+ } else if (penaltyType === "silence") {
+ return "admin.user.silence";
+ }
+ },
+
+ @discourseComputed(
+ "user.penalty_counts.suspended",
+ "user.penalty_counts.silenced"
+ )
+ penaltyHistory(suspendedCount, silencedCount) {
+ return I18n.messageFormat("admin.user.penalty_history_MF", {
+ SUSPENDED: suspendedCount,
+ SILENCED: silencedCount,
+ });
+ },
+
+ @discourseComputed("penaltyType", "user.canSuspend", "user.canSilence")
+ canPenalize(penaltyType, canSuspend, canSilence) {
+ if (penaltyType === "suspend") {
+ return canSuspend;
+ } else if (penaltyType === "silence") {
+ return canSilence;
+ }
+
+ return false;
+ },
+
+ @discourseComputed("penalizing", "penalizeUntil", "reason")
+ submitDisabled(penalizing, penalizeUntil, reason) {
+ return penalizing || isEmpty(penalizeUntil) || !reason || reason.length < 1;
+ },
+
+ @action
+ async penalizeUser() {
+ if (this.submitDisabled) {
+ return;
+ }
+
+ this.set("penalizing", true);
+ this.set("confirmClose", true);
+
+ if (this.before) {
+ this.before();
+ }
+
+ let result;
+ try {
+ const opts = {
+ reason: this.reason,
+ message: this.message,
+ post_id: this.postId,
+ post_action: this.postAction,
+ post_edit: this.postEdit,
+ other_user_ids: this.otherUserIds,
+ };
+
+ if (this.penaltyType === "suspend") {
+ opts.suspend_until = this.penalizeUntil;
+ result = await this.user.suspend(opts);
+ } else if (this.penaltyType === "silence") {
+ opts.silenced_till = this.penalizeUntil;
+ result = await this.user.silence(opts);
+ } else {
+ // eslint-disable-next-line no-console
+ console.error("Unknown penalty type:", this.penaltyType);
+ }
+
+ this.send("closeModal");
+ if (this.successCallback) {
+ await this.successCallback(result);
+ }
+ } catch {
+ this.set("errorMessage", extractError(result));
+ } finally {
+ this.set("penalizing", false);
+ }
+ },
+});
diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-silence-user.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-silence-user.js
deleted file mode 100644
index 077a0863306..00000000000
--- a/app/assets/javascripts/admin/addon/controllers/modals/admin-silence-user.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import Controller from "@ember/controller";
-import PenaltyController from "admin/mixins/penalty-controller";
-import discourseComputed from "discourse-common/utils/decorators";
-import { isEmpty } from "@ember/utils";
-
-export default Controller.extend(PenaltyController, {
- silenceUntil: null,
- silencing: false,
-
- onShow() {
- this.resetModal();
- this.setProperties({
- silenceUntil: null,
- silencing: false,
- otherUserIds: [],
- });
- },
-
- finishedSetup() {
- this.set("silenceUntil", this.user?.next_penalty);
- },
-
- @discourseComputed("silenceUntil", "reason", "silencing")
- submitDisabled(silenceUntil, reason, silencing) {
- return silencing || isEmpty(silenceUntil) || !reason || reason.length < 1;
- },
-
- actions: {
- silence() {
- if (this.submitDisabled) {
- return;
- }
-
- this.set("silencing", true);
- this.penalize(() => {
- return this.user.silence({
- silenced_till: this.silenceUntil,
- reason: this.reason,
- message: this.message,
- post_id: this.postId,
- post_action: this.postAction,
- post_edit: this.postEdit,
- other_user_ids: this.otherUserIds,
- });
- }).finally(() => this.set("silencing", false));
- },
- },
-});
diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-suspend-user.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-suspend-user.js
deleted file mode 100644
index c9f34d89577..00000000000
--- a/app/assets/javascripts/admin/addon/controllers/modals/admin-suspend-user.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import Controller from "@ember/controller";
-import PenaltyController from "admin/mixins/penalty-controller";
-import discourseComputed from "discourse-common/utils/decorators";
-import { isEmpty } from "@ember/utils";
-
-export default Controller.extend(PenaltyController, {
- suspendUntil: null,
- suspending: false,
-
- onShow() {
- this.resetModal();
- this.setProperties({
- suspendUntil: null,
- suspending: false,
- otherUserIds: [],
- });
- },
-
- finishedSetup() {
- this.set("suspendUntil", this.user?.next_penalty);
- },
-
- @discourseComputed("suspendUntil", "reason", "suspending")
- submitDisabled(suspendUntil, reason, suspending) {
- return suspending || isEmpty(suspendUntil) || !reason || reason.length < 1;
- },
-
- actions: {
- suspend() {
- if (this.submitDisabled) {
- return;
- }
-
- this.set("suspending", true);
- this.penalize(() => {
- return this.user.suspend({
- suspend_until: this.suspendUntil,
- reason: this.reason,
- message: this.message,
- post_id: this.postId,
- post_action: this.postAction,
- post_edit: this.postEdit,
- other_user_ids: this.otherUserIds,
- });
- }).finally(() => this.set("suspending", false));
- },
- },
-});
diff --git a/app/assets/javascripts/admin/addon/mixins/penalty-controller.js b/app/assets/javascripts/admin/addon/mixins/penalty-controller.js
deleted file mode 100644
index eb5d56e8cfc..00000000000
--- a/app/assets/javascripts/admin/addon/mixins/penalty-controller.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import I18n from "I18n";
-import Mixin from "@ember/object/mixin";
-import ModalFunctionality from "discourse/mixins/modal-functionality";
-import { Promise } from "rsvp";
-import { extractError } from "discourse/lib/ajax-error";
-import { next } from "@ember/runloop";
-import { inject as service } from "@ember/service";
-
-export default Mixin.create(ModalFunctionality, {
- dialog: service(),
- errorMessage: null,
- reason: null,
- message: null,
- postEdit: null,
- postAction: null,
- user: null,
- postId: null,
- successCallback: null,
- confirmClose: false,
-
- resetModal() {
- this.setProperties({
- errorMessage: null,
- reason: null,
- message: null,
- loadingUser: true,
- postId: null,
- postEdit: null,
- postAction: "delete",
- before: null,
- successCallback: null,
- confirmClose: false,
- });
- },
-
- beforeClose() {
- // prompt a confirmation if we have unsaved content
- if (
- !this.confirmClose &&
- ((this.reason && this.reason.length > 1) ||
- (this.message && this.message.length > 1))
- ) {
- this.send("hideModal");
- this.dialog.confirm({
- message: I18n.t("admin.user.confirm_cancel_penalty"),
- didConfirm: () => {
- next(() => {
- this.set("confirmClose", true);
- this.send("closeModal");
- });
- },
- didCancel: () => this.send("reopenModal"),
- });
- return false;
- }
- },
-
- penalize(cb) {
- let before = this.before;
- let promise = before ? before() : Promise.resolve();
-
- return promise
- .then(() => cb())
- .then((result) => {
- this.set("confirmClose", true);
- this.send("closeModal");
- let callback = this.successCallback;
- if (callback) {
- callback(result);
- }
- })
- .catch((error) => {
- this.set("errorMessage", extractError(error));
- });
- },
-});
diff --git a/app/assets/javascripts/admin/addon/models/admin-user.js b/app/assets/javascripts/admin/addon/models/admin-user.js
index 3fb3e28dad4..a61980bc61a 100644
--- a/app/assets/javascripts/admin/addon/models/admin-user.js
+++ b/app/assets/javascripts/admin/addon/models/admin-user.js
@@ -197,6 +197,7 @@ const AdminUser = User.extend({
canLockTrustLevel: lt("trust_level", 4),
canSuspend: not("staff"),
+ canSilence: not("staff"),
@discourseComputed("suspended_till", "suspended_at")
suspendDuration(suspendedTill, suspendedAt) {
diff --git a/app/assets/javascripts/admin/addon/services/admin-tools.js b/app/assets/javascripts/admin/addon/services/admin-tools.js
index 5fce78cb33d..59d3a432d14 100644
--- a/app/assets/javascripts/admin/addon/services/admin-tools.js
+++ b/app/assets/javascripts/admin/addon/services/admin-tools.js
@@ -41,11 +41,17 @@ export default Service.extend({
_showControlModal(type, user, opts) {
opts = opts || {};
- let controller = showModal(`admin-${type}-user`, {
+
+ const controller = showModal(`admin-penalize-user`, {
admin: true,
modalClass: `${type}-user-modal`,
});
- controller.setProperties({ postId: opts.postId, postEdit: opts.postEdit });
+
+ controller.setProperties({
+ penaltyType: type,
+ postId: opts.postId,
+ postEdit: opts.postEdit,
+ });
return (
user.adminUserView
diff --git a/app/assets/javascripts/admin/addon/templates/components/penalty-post-action.hbs b/app/assets/javascripts/admin/addon/templates/components/admin-penalty-post-action.hbs
similarity index 100%
rename from app/assets/javascripts/admin/addon/templates/components/penalty-post-action.hbs
rename to app/assets/javascripts/admin/addon/templates/components/admin-penalty-post-action.hbs
diff --git a/app/assets/javascripts/admin/addon/templates/components/admin-penalty-reason.hbs b/app/assets/javascripts/admin/addon/templates/components/admin-penalty-reason.hbs
new file mode 100644
index 00000000000..eb2af20a75e
--- /dev/null
+++ b/app/assets/javascripts/admin/addon/templates/components/admin-penalty-reason.hbs
@@ -0,0 +1,18 @@
+
+ {{#if (eq @penaltyType "suspend")}}
+
+
+
+ {{#if this.isCustomReason}}
+
+ {{/if}}
+ {{else if (eq @penaltyType "silence")}}
+
+
+ {{/if}}
+
+
+
+
+
+
diff --git a/app/assets/javascripts/admin/addon/templates/components/admin-penalty-similar-users.hbs b/app/assets/javascripts/admin/addon/templates/components/admin-penalty-similar-users.hbs
index ccb35da4d87..8e18d8271df 100644
--- a/app/assets/javascripts/admin/addon/templates/components/admin-penalty-similar-users.hbs
+++ b/app/assets/javascripts/admin/addon/templates/components/admin-penalty-similar-users.hbs
@@ -1,6 +1,6 @@
-
- {{i18n "admin.user.other_matches" (hash count=this.user.similar_users_count username=this.user.username)}}
+
+ {{html-safe (i18n "admin.user.other_matches" (hash count=this.user.similar_users_count username=this.user.username))}}
diff --git a/app/assets/javascripts/admin/addon/templates/components/silence-details.hbs b/app/assets/javascripts/admin/addon/templates/components/silence-details.hbs
index b68c80d4def..423d26da044 100644
--- a/app/assets/javascripts/admin/addon/templates/components/silence-details.hbs
+++ b/app/assets/javascripts/admin/addon/templates/components/silence-details.hbs
@@ -1,4 +1,4 @@
-
+