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