mirror of
https://github.com/discourse/discourse.git
synced 2025-02-16 18:24:52 -06:00
DEV: Convert flag
modal to component-based API (#23279)
# Topic Flag <img width="587" alt="Screenshot 2023-08-28 at 10 53 12 AM" src="https://github.com/discourse/discourse/assets/50783505/6ffe4e47-05a6-406c-9d1b-899ff4d5c2c9"> # Post Flag <img width="620" alt="Screenshot 2023-08-28 at 10 52 44 AM" src="https://github.com/discourse/discourse/assets/50783505/1f893916-b62f-4825-a337-4c0e0e4ce3af"> # Chat Flag <img width="648" alt="Screenshot 2023-08-28 at 10 52 31 AM" src="https://github.com/discourse/discourse/assets/50783505/e79444e8-a8b1-4f05-9b47-03d425bc9085">
This commit is contained in:
parent
cf71f8358e
commit
026cd3e532
@ -20,14 +20,21 @@
|
||||
@value={{this.message}}
|
||||
/>
|
||||
<div
|
||||
class="custom-message-length {{this.customMessageLengthClasses}}"
|
||||
>{{this.customMessageLength}}</div>
|
||||
class={{concat-class
|
||||
"custom-message-length"
|
||||
this.customMessageLengthClasses
|
||||
}}
|
||||
>
|
||||
{{this.customMessageLength}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{{#if this.staffFlagsAvailable}}
|
||||
{{#if @includeSeparator}}
|
||||
<hr />
|
||||
{{/if}}
|
||||
{{#if this.staffFlagsAvailable}}
|
||||
<h3>{{i18n "flagging.notify_staff"}}</h3>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
@ -53,8 +60,13 @@
|
||||
@value={{this.message}}
|
||||
/>
|
||||
<div
|
||||
class="custom-message-length {{this.customMessageLengthClasses}}"
|
||||
>{{this.customMessageLength}}</div>
|
||||
class={{concat-class
|
||||
"custom-message-length"
|
||||
this.customMessageLengthClasses
|
||||
}}
|
||||
>
|
||||
{{this.customMessageLength}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</label>
|
||||
|
@ -0,0 +1,80 @@
|
||||
<DModal
|
||||
class="flag-modal"
|
||||
@bodyClass="flag-modal-body"
|
||||
@title={{i18n this.title}}
|
||||
@submitOnEnter={{false}}
|
||||
@closeModal={{@closeModal}}
|
||||
{{on "keydown" this.onKeydown}}
|
||||
>
|
||||
<:body>
|
||||
<form>
|
||||
<FlagSelection
|
||||
@nameKey={{this.selected.name_key}}
|
||||
@flags={{this.flagsAvailable}}
|
||||
as |f|
|
||||
>
|
||||
<FlagActionType
|
||||
@flag={{f}}
|
||||
@message={{this.message}}
|
||||
@isWarning={{this.isWarning}}
|
||||
@selectedFlag={{this.selected}}
|
||||
@username={{@model.flagModel.username}}
|
||||
@staffFlagsAvailable={{this.staffFlagsAvailable}}
|
||||
@changePostActionType={{this.changePostActionType}}
|
||||
@includeSeparator={{this.includeSeparator}}
|
||||
/>
|
||||
</FlagSelection>
|
||||
</form>
|
||||
<PluginOutlet
|
||||
@name="flag-modal-bottom"
|
||||
@connectorTagName="div"
|
||||
@outletArgs={{hash post=@model.flagModel}}
|
||||
/>
|
||||
</:body>
|
||||
<:footer>
|
||||
<DButton
|
||||
class="btn-primary"
|
||||
@action={{this.createFlag}}
|
||||
@disabled={{not this.submitEnabled}}
|
||||
@title="flagging.submit_tooltip"
|
||||
@icon={{if this.selected.is_custom_flag "envelope" "flag"}}
|
||||
@label={{this.submitLabel}}
|
||||
/>
|
||||
|
||||
{{#if this.canSendWarning}}
|
||||
<DButton
|
||||
class="btn-danger"
|
||||
@action={{this.createFlagAsWarning}}
|
||||
@disabled={{not this.submitEnabled}}
|
||||
@icon="exclamation-triangle"
|
||||
@label="flagging.official_warning"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.canTakeAction}}
|
||||
<ReviewableBundledAction
|
||||
@bundle={{this.flagActions}}
|
||||
@performAction={{this.takeAction}}
|
||||
@reviewableUpdating={{not this.submitEnabled}}
|
||||
/>
|
||||
|
||||
<DButton
|
||||
class="btn-danger"
|
||||
@action={{this.flagForReview}}
|
||||
@disabled={{not this.submitEnabled this.notifyModeratorsFlag}}
|
||||
@icon="exclamation-triangle"
|
||||
@label="flagging.flag_for_review"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showDeleteSpammer}}
|
||||
<DButton
|
||||
class="btn-danger"
|
||||
@action={{this.deleteSpammer}}
|
||||
@disabled={{not this.submitEnabled}}
|
||||
@icon="exclamation-triangle"
|
||||
@label="flagging.delete_spammer"
|
||||
/>
|
||||
{{/if}}
|
||||
</:footer>
|
||||
</DModal>
|
208
app/assets/javascripts/discourse/app/components/modal/flag.js
Normal file
208
app/assets/javascripts/discourse/app/components/modal/flag.js
Normal file
@ -0,0 +1,208 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import I18n from "I18n";
|
||||
import { MAX_MESSAGE_LENGTH } from "discourse/models/post-action-type";
|
||||
import User from "discourse/models/user";
|
||||
import { reload } from "discourse/helpers/page-reloader";
|
||||
|
||||
const NOTIFY_MODERATORS_ID = 7;
|
||||
|
||||
export default class Flag extends Component {
|
||||
@service adminTools;
|
||||
@service currentUser;
|
||||
@service siteSettings;
|
||||
@service site;
|
||||
@service appEvents;
|
||||
|
||||
@tracked userDetails;
|
||||
@tracked selected;
|
||||
@tracked message;
|
||||
@tracked isWarning = false;
|
||||
@tracked spammerDetails;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
this.adminTools
|
||||
?.checkSpammer(this.args.model.flagModel.user_id)
|
||||
.then((result) => (this.spammerDetails = result));
|
||||
}
|
||||
|
||||
get flagActions() {
|
||||
return {
|
||||
icon: "gavel",
|
||||
label: I18n.t("flagging.take_action"),
|
||||
actions: [
|
||||
{
|
||||
id: "agree_and_keep",
|
||||
icon: "thumbs-up",
|
||||
label: I18n.t("flagging.take_action_options.default.title"),
|
||||
description: I18n.t("flagging.take_action_options.default.details"),
|
||||
},
|
||||
{
|
||||
id: "agree_and_suspend",
|
||||
icon: "ban",
|
||||
label: I18n.t("flagging.take_action_options.suspend.title"),
|
||||
description: I18n.t("flagging.take_action_options.suspend.details"),
|
||||
client_action: "suspend",
|
||||
},
|
||||
{
|
||||
id: "agree_and_silence",
|
||||
icon: "microphone-slash",
|
||||
label: I18n.t("flagging.take_action_options.silence.title"),
|
||||
description: I18n.t("flagging.take_action_options.silence.details"),
|
||||
client_action: "silence",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
get canSendWarning() {
|
||||
return (
|
||||
!this.args.model.flagTarget.targetsTopic() &&
|
||||
this.currentUser.staff &&
|
||||
this.selected?.name_key === "notify_user"
|
||||
);
|
||||
}
|
||||
|
||||
get submitLabel() {
|
||||
if (this.selected?.is_custom_flag) {
|
||||
return this.args.model.flagTarget.customSubmitLabel();
|
||||
}
|
||||
|
||||
return this.args.model.flagTarget.submitLabel();
|
||||
}
|
||||
|
||||
get includeSeparator() {
|
||||
return (
|
||||
this.staffFlagsAvailable ||
|
||||
this.args.model.flagTarget.includeSeparator?.()
|
||||
);
|
||||
}
|
||||
|
||||
get title() {
|
||||
return this.args.model.flagTarget.title();
|
||||
}
|
||||
|
||||
get flagsAvailable() {
|
||||
return this.args.model.flagTarget.flagsAvailable(this);
|
||||
}
|
||||
|
||||
get staffFlagsAvailable() {
|
||||
return this.args.model.flagModel.flagsAvailable?.length > 1;
|
||||
}
|
||||
|
||||
get submitEnabled() {
|
||||
if (!this.selected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.selected.is_custom_flag) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const len = this.message?.length || 0;
|
||||
return (
|
||||
len >= this.siteSettings.min_personal_message_post_length &&
|
||||
len <= MAX_MESSAGE_LENGTH
|
||||
);
|
||||
}
|
||||
|
||||
get notifyModeratorsFlag() {
|
||||
return this.flagsAvailable.find((f) => f.id === NOTIFY_MODERATORS_ID);
|
||||
}
|
||||
|
||||
get canTakeAction() {
|
||||
return (
|
||||
!this.args.model.flagTarget.targetsTopic() &&
|
||||
!this.selected?.is_custom_flag &&
|
||||
this.currentUser.staff
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
onKeydown(event) {
|
||||
if (
|
||||
this.submitEnabled &&
|
||||
event.key === "Enter" &&
|
||||
(event.ctrlKey || event.metaKey)
|
||||
) {
|
||||
this.createFlag();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
async penalize(adminToolMethod, performAction) {
|
||||
if (!this.adminTools) {
|
||||
return;
|
||||
}
|
||||
|
||||
const createdBy = await User.findByUsername(
|
||||
this.args.model.flagModel.username
|
||||
);
|
||||
const opts = { before: performAction };
|
||||
|
||||
if (this.args.model.flagTarget.editable()) {
|
||||
opts.postId = this.args.model.flagModel.id;
|
||||
opts.postEdit = this.args.model.flagModel.cooked;
|
||||
}
|
||||
|
||||
return this.adminTools[adminToolMethod](createdBy, opts);
|
||||
}
|
||||
|
||||
@action
|
||||
async deleteSpammer() {
|
||||
if (this.spammerDetails) {
|
||||
await this.spammerDetails.deleteUser();
|
||||
reload();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
async takeAction(actionable) {
|
||||
if (actionable.client_action === "suspend") {
|
||||
await this.penalize("showSuspendModal", () =>
|
||||
this.createFlag({ takeAction: true, skipClose: true })
|
||||
);
|
||||
} else if (actionable.client_action === "silence") {
|
||||
await this.penalize("showSilenceModal", () =>
|
||||
this.createFlag({ takeAction: true, skipClose: true })
|
||||
);
|
||||
} else if (actionable.client_action) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`No handler for ${actionable.client_action} found`);
|
||||
} else {
|
||||
this.args.model.setHidden();
|
||||
this.createFlag({ takeAction: true });
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
createFlag(opts = {}) {
|
||||
if (this.selected.is_custom_flag) {
|
||||
opts.message = this.message;
|
||||
}
|
||||
this.args.model.flagTarget.create(this, opts);
|
||||
}
|
||||
|
||||
@action
|
||||
createFlagAsWarning() {
|
||||
this.createFlag({ isWarning: true });
|
||||
this.args.model.setHidden();
|
||||
}
|
||||
|
||||
@action
|
||||
flagForReview() {
|
||||
this.selected ||= this.notifyModeratorsFlag;
|
||||
this.createFlag({ queue_for_review: true });
|
||||
this.args.model.setHidden();
|
||||
}
|
||||
|
||||
@action
|
||||
changePostActionType(actionType) {
|
||||
this.selected = actionType;
|
||||
}
|
||||
}
|
@ -17,13 +17,12 @@
|
||||
/>
|
||||
{{else}}
|
||||
<DButton
|
||||
@class={{concat
|
||||
"reviewable-action "
|
||||
@class={{concat-class
|
||||
"reviewable-action"
|
||||
(dasherize this.first.id)
|
||||
" "
|
||||
this.first.button_class
|
||||
}}
|
||||
@action={{action "perform" this.first}}
|
||||
@action={{action "performFirst"}}
|
||||
@translatedLabel={{this.first.label}}
|
||||
@disabled={{this.reviewableUpdating}}
|
||||
/>
|
||||
|
@ -18,11 +18,11 @@ export default Component.extend({
|
||||
|
||||
actions: {
|
||||
performById(id) {
|
||||
this.attrs.performAction(this.get("bundle.actions").findBy("id", id));
|
||||
this.performAction(this.get("bundle.actions").findBy("id", id));
|
||||
},
|
||||
|
||||
perform(action) {
|
||||
this.attrs.performAction(action);
|
||||
performFirst() {
|
||||
this.performAction(this.first);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1,261 +0,0 @@
|
||||
import { schedule } from "@ember/runloop";
|
||||
import Controller from "@ember/controller";
|
||||
import I18n from "I18n";
|
||||
import { MAX_MESSAGE_LENGTH } from "discourse/models/post-action-type";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import { Promise } from "rsvp";
|
||||
import User from "discourse/models/user";
|
||||
import discourseComputed, { bind } from "discourse-common/utils/decorators";
|
||||
import { not } from "@ember/object/computed";
|
||||
import optionalService from "discourse/lib/optional-service";
|
||||
import { classify } from "@ember/string";
|
||||
|
||||
export default Controller.extend(ModalFunctionality, {
|
||||
adminTools: optionalService(),
|
||||
userDetails: null,
|
||||
selected: null,
|
||||
message: null,
|
||||
isWarning: false,
|
||||
topicActionByName: null,
|
||||
spammerDetails: null,
|
||||
flagActions: null,
|
||||
flagTarget: null,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.flagActions = {
|
||||
icon: "gavel",
|
||||
label: I18n.t("flagging.take_action"),
|
||||
actions: [
|
||||
{
|
||||
id: "agree_and_keep",
|
||||
icon: "thumbs-up",
|
||||
label: I18n.t("flagging.take_action_options.default.title"),
|
||||
description: I18n.t("flagging.take_action_options.default.details"),
|
||||
},
|
||||
{
|
||||
id: "agree_and_suspend",
|
||||
icon: "ban",
|
||||
label: I18n.t("flagging.take_action_options.suspend.title"),
|
||||
description: I18n.t("flagging.take_action_options.suspend.details"),
|
||||
client_action: "suspend",
|
||||
},
|
||||
{
|
||||
id: "agree_and_silence",
|
||||
icon: "microphone-slash",
|
||||
label: I18n.t("flagging.take_action_options.silence.title"),
|
||||
description: I18n.t("flagging.take_action_options.silence.details"),
|
||||
client_action: "silence",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
|
||||
@bind
|
||||
keyDown(event) {
|
||||
// CTRL+ENTER or CMD+ENTER
|
||||
if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
|
||||
if (this.submitEnabled) {
|
||||
this.send("createFlag");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
clientSuspend(performAction) {
|
||||
this._penalize("showSuspendModal", performAction);
|
||||
},
|
||||
|
||||
clientSilence(performAction) {
|
||||
this._penalize("showSilenceModal", performAction);
|
||||
},
|
||||
|
||||
_penalize(adminToolMethod, performAction) {
|
||||
if (this.adminTools) {
|
||||
return User.findByUsername(this.model.username).then((createdBy) => {
|
||||
const opts = { before: performAction };
|
||||
|
||||
if (this.flagTarget.editable()) {
|
||||
opts.postId = this.model.id;
|
||||
opts.postEdit = this.model.cooked;
|
||||
}
|
||||
|
||||
return this.adminTools[adminToolMethod](createdBy, opts);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.setProperties({
|
||||
selected: null,
|
||||
spammerDetails: null,
|
||||
});
|
||||
|
||||
if (this.adminTools) {
|
||||
this.adminTools.checkSpammer(this.get("model.user_id")).then((result) => {
|
||||
this.set("spammerDetails", result);
|
||||
});
|
||||
}
|
||||
|
||||
schedule("afterRender", () => {
|
||||
const element = document.querySelector(".flag-modal");
|
||||
element.addEventListener("keydown", this.keyDown);
|
||||
});
|
||||
},
|
||||
|
||||
onClose() {
|
||||
const element = document.querySelector(".flag-modal");
|
||||
element.removeEventListener("keydown", this.keyDown);
|
||||
},
|
||||
|
||||
@discourseComputed("spammerDetails.canDelete", "selected.name_key")
|
||||
showDeleteSpammer(canDeleteSpammer, nameKey) {
|
||||
return canDeleteSpammer && nameKey === "spam";
|
||||
},
|
||||
|
||||
@discourseComputed("flagTarget")
|
||||
title(flagTarget) {
|
||||
return flagTarget.title();
|
||||
},
|
||||
|
||||
@discourseComputed(
|
||||
"post",
|
||||
"flagTarget",
|
||||
"model.actions_summary.@each.can_act"
|
||||
)
|
||||
flagsAvailable() {
|
||||
return this.flagTarget.flagsAvailable(this, this.site, this.model);
|
||||
},
|
||||
|
||||
@discourseComputed(
|
||||
"post",
|
||||
"flagTarget",
|
||||
"model.actions_summary.@each.can_act"
|
||||
)
|
||||
staffFlagsAvailable() {
|
||||
return (
|
||||
this.get("model.flagsAvailable") &&
|
||||
this.get("model.flagsAvailable").length > 1
|
||||
);
|
||||
},
|
||||
|
||||
@discourseComputed("selected.is_custom_flag", "message.length")
|
||||
submitEnabled() {
|
||||
const selected = this.selected;
|
||||
if (!selected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (selected.get("is_custom_flag")) {
|
||||
const len = this.get("message.length") || 0;
|
||||
return (
|
||||
len >= this.siteSettings.min_personal_message_post_length &&
|
||||
len <= MAX_MESSAGE_LENGTH
|
||||
);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
submitDisabled: not("submitEnabled"),
|
||||
cantFlagForReview: not("notifyModeratorsFlag"),
|
||||
|
||||
@discourseComputed("flagsAvailable")
|
||||
notifyModeratorsFlag(flagsAvailable) {
|
||||
const notifyModeratorsID = 7;
|
||||
return flagsAvailable.find((f) => f.id === notifyModeratorsID);
|
||||
},
|
||||
|
||||
// Staff accounts can "take action"
|
||||
@discourseComputed("flagTarget", "selected.is_custom_flag")
|
||||
canTakeAction(flagTarget, isCustomFlag) {
|
||||
return (
|
||||
!flagTarget.targetsTopic() &&
|
||||
!isCustomFlag &&
|
||||
this.currentUser.get("staff")
|
||||
);
|
||||
},
|
||||
|
||||
@discourseComputed("selected.is_custom_flag")
|
||||
submitIcon(isCustomFlag) {
|
||||
return isCustomFlag ? "envelope" : "flag";
|
||||
},
|
||||
|
||||
@discourseComputed("selected.is_custom_flag", "flagTarget")
|
||||
submitLabel(isCustomFlag, flagTarget) {
|
||||
if (isCustomFlag) {
|
||||
return flagTarget.customSubmitLabel();
|
||||
}
|
||||
|
||||
return flagTarget.submitLabel();
|
||||
},
|
||||
|
||||
actions: {
|
||||
deleteSpammer() {
|
||||
let details = this.spammerDetails;
|
||||
if (details) {
|
||||
details.deleteUser().then(() => window.location.reload());
|
||||
}
|
||||
},
|
||||
|
||||
takeAction(action) {
|
||||
let performAction = (o = {}) => {
|
||||
o.takeAction = true;
|
||||
this.send("createFlag", o);
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
if (action.client_action) {
|
||||
let actionMethod = this[`client${classify(action.client_action)}`];
|
||||
if (actionMethod) {
|
||||
return actionMethod.call(this, () =>
|
||||
performAction({ skipClose: true })
|
||||
);
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`No handler for ${action.client_action} found`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.set("model.hidden", true);
|
||||
return performAction();
|
||||
}
|
||||
},
|
||||
|
||||
createFlag(opts) {
|
||||
const params = opts || {};
|
||||
|
||||
if (this.get("selected.is_custom_flag")) {
|
||||
params.message = this.message;
|
||||
}
|
||||
|
||||
this.flagTarget.create(this, params);
|
||||
},
|
||||
|
||||
createFlagAsWarning() {
|
||||
this.send("createFlag", { isWarning: true });
|
||||
this.set("model.hidden", true);
|
||||
},
|
||||
|
||||
flagForReview() {
|
||||
if (!this.selected) {
|
||||
this.set("selected", this.get("notifyModeratorsFlag"));
|
||||
}
|
||||
|
||||
this.send("createFlag", { queue_for_review: true });
|
||||
this.set("model.hidden", true);
|
||||
},
|
||||
|
||||
changePostActionType(action) {
|
||||
this.set("selected", action);
|
||||
},
|
||||
},
|
||||
|
||||
@discourseComputed("flagTarget", "selected.name_key")
|
||||
canSendWarning(flagTarget, nameKey) {
|
||||
return (
|
||||
!flagTarget.targetsTopic() &&
|
||||
this.currentUser.get("staff") &&
|
||||
nameKey === "notify_user"
|
||||
);
|
||||
},
|
||||
});
|
@ -9,41 +9,24 @@ export default class Flag {
|
||||
return true;
|
||||
}
|
||||
|
||||
create(controller, opts) {
|
||||
create(flagModal, opts) {
|
||||
// an instance of ActionSummary
|
||||
let postAction = this.postActionFor(controller);
|
||||
|
||||
controller.appEvents.trigger(
|
||||
const postAction = this.postActionFor(flagModal);
|
||||
flagModal.appEvents.trigger(
|
||||
this.flagCreatedEvent,
|
||||
controller.model,
|
||||
flagModal.args.model.flagModel,
|
||||
postAction,
|
||||
opts
|
||||
);
|
||||
|
||||
controller.send("hideModal");
|
||||
|
||||
flagModal.args.closeModal();
|
||||
postAction
|
||||
.act(controller.model, opts)
|
||||
.act(flagModal.args.model.flagModel, opts)
|
||||
.then(() => {
|
||||
if (controller.isDestroying || controller.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!opts.skipClose) {
|
||||
controller.send("closeModal");
|
||||
}
|
||||
if (opts.message) {
|
||||
controller.set("message", "");
|
||||
}
|
||||
controller.appEvents.trigger("post-stream:refresh", {
|
||||
id: controller.get("model.id"),
|
||||
flagModal.appEvents.trigger("post-stream:refresh", {
|
||||
id: flagModal.args.model.flagModel.id,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!controller.isDestroying && !controller.isDestroyed) {
|
||||
controller.send("closeModal");
|
||||
}
|
||||
popupAjaxError(error);
|
||||
});
|
||||
.catch((error) => popupAjaxError(error));
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ export default class PostFlag extends Flag {
|
||||
return "post:flag-created";
|
||||
}
|
||||
|
||||
flagsAvailable(_flagController, _site, model) {
|
||||
let flagsAvailable = model.flagsAvailable;
|
||||
flagsAvailable(flagModal) {
|
||||
let flagsAvailable = flagModal.args.model.flagModel.flagsAvailable;
|
||||
|
||||
// "message user" option should be at the top
|
||||
const notifyUserIndex = flagsAvailable.indexOf(
|
||||
@ -34,9 +34,10 @@ export default class PostFlag extends Flag {
|
||||
return flagsAvailable;
|
||||
}
|
||||
|
||||
postActionFor(controller) {
|
||||
return controller
|
||||
.get("model.actions_summary")
|
||||
.findBy("id", controller.get("selected.id"));
|
||||
postActionFor(flagModal) {
|
||||
return flagModal.args.model.flagModel.actions_summary.findBy(
|
||||
"id",
|
||||
flagModal.selected.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -23,24 +23,24 @@ export default class TopicFlag extends Flag {
|
||||
return "topic:flag-created";
|
||||
}
|
||||
|
||||
flagsAvailable(flagController, site, model) {
|
||||
flagsAvailable(flagModal) {
|
||||
let lookup = EmberObject.create();
|
||||
|
||||
model.actions_summary.forEach((a) => {
|
||||
a.flagTopic = model;
|
||||
a.actionType = site.topicFlagTypeById(a.id);
|
||||
flagModal.args.model.flagModel.actions_summary.forEach((a) => {
|
||||
a.flagTopic = flagModal.args.model.flagModel;
|
||||
a.actionType = flagModal.site.topicFlagTypeById(a.id);
|
||||
lookup.set(a.actionType.name_key, ActionSummary.create(a));
|
||||
});
|
||||
flagController.set("topicActionByName", lookup);
|
||||
flagModal.topicActionByName = lookup;
|
||||
|
||||
return site.topic_flag_types.filter((item) => {
|
||||
return model.actions_summary.some((a) => {
|
||||
return flagModal.site.topic_flag_types.filter((item) => {
|
||||
return flagModal.args.model.flagModel.actions_summary.some((a) => {
|
||||
return a.id === item.id && a.can_act;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
postActionFor(controller) {
|
||||
return controller.get(`topicActionByName.${controller.selected.name_key}`);
|
||||
postActionFor(flagModal) {
|
||||
return flagModal.topicActionByName[flagModal.selected.name_key];
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import EditSlowModeModal from "discourse/components/modal/edit-slow-mode";
|
||||
import ChangeTimestampModal from "discourse/components/modal/change-timestamp";
|
||||
import EditTopicTimerModal from "discourse/components/modal/edit-topic-timer";
|
||||
import FeatureTopicModal from "discourse/components/modal/feature-topic";
|
||||
import FlagModal from "discourse/components/modal/flag";
|
||||
|
||||
const SCROLL_DELAY = 500;
|
||||
|
||||
@ -103,15 +104,25 @@ const TopicRoute = DiscourseRoute.extend({
|
||||
|
||||
@action
|
||||
showFlags(model) {
|
||||
let controller = showModal("flag", { model });
|
||||
controller.setProperties({ flagTarget: new PostFlag() });
|
||||
this.modal.show(FlagModal, {
|
||||
model: {
|
||||
flagTarget: new PostFlag(),
|
||||
flagModel: model,
|
||||
setHidden: () => model.set("hidden", true),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@action
|
||||
showFlagTopic() {
|
||||
const model = this.modelFor("topic");
|
||||
let controller = showModal("flag", { model });
|
||||
controller.setProperties({ flagTarget: new TopicFlag() });
|
||||
this.modal.show(FlagModal, {
|
||||
model: {
|
||||
flagTarget: new TopicFlag(),
|
||||
flagModel: model,
|
||||
setHidden: () => model.set("hidden", true),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@action
|
||||
|
@ -18,7 +18,6 @@ const KNOWN_LEGACY_MODALS = [
|
||||
"create-account",
|
||||
"create-invite-bulk",
|
||||
"create-invite",
|
||||
"flag",
|
||||
"grant-badge",
|
||||
"group-default-notifications",
|
||||
"login",
|
||||
|
@ -1,76 +0,0 @@
|
||||
<DModalBody
|
||||
@class="flag-modal-body"
|
||||
@title={{this.title}}
|
||||
@submitOnEnter={{false}}
|
||||
>
|
||||
<form>
|
||||
<FlagSelection
|
||||
@nameKey={{this.selected.name_key}}
|
||||
@flags={{this.flagsAvailable}}
|
||||
as |f|
|
||||
>
|
||||
<FlagActionType
|
||||
@flag={{f}}
|
||||
@message={{this.message}}
|
||||
@isWarning={{this.isWarning}}
|
||||
@selectedFlag={{this.selected}}
|
||||
@username={{this.model.username}}
|
||||
@staffFlagsAvailable={{this.staffFlagsAvailable}}
|
||||
@changePostActionType={{action "changePostActionType"}}
|
||||
/>
|
||||
</FlagSelection>
|
||||
</form>
|
||||
|
||||
<PluginOutlet
|
||||
@name="flag-modal-bottom"
|
||||
@connectorTagName="div"
|
||||
@outletArgs={{hash post=this.model}}
|
||||
/>
|
||||
</DModalBody>
|
||||
|
||||
<div class="modal-footer">
|
||||
<DButton
|
||||
@class="btn-primary"
|
||||
@action={{action "createFlag"}}
|
||||
@disabled={{this.submitDisabled}}
|
||||
@title="flagging.submit_tooltip"
|
||||
@icon={{this.submitIcon}}
|
||||
@label={{this.submitLabel}}
|
||||
/>
|
||||
|
||||
{{#if this.canSendWarning}}
|
||||
<DButton
|
||||
@class="btn-danger"
|
||||
@action={{action "createFlagAsWarning"}}
|
||||
@disabled={{this.submitDisabled}}
|
||||
@icon="exclamation-triangle"
|
||||
@label="flagging.official_warning"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.canTakeAction}}
|
||||
<ReviewableBundledAction
|
||||
@bundle={{this.flagActions}}
|
||||
@performAction={{action "takeAction"}}
|
||||
@reviewableUpdating={{this.submitDisabled}}
|
||||
/>
|
||||
|
||||
<DButton
|
||||
@class="btn-danger"
|
||||
@action={{action "flagForReview"}}
|
||||
@disabled={{or this.submitDisabled this.cantFlagForReview}}
|
||||
@icon="exclamation-triangle"
|
||||
@label="flagging.flag_for_review"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showDeleteSpammer}}
|
||||
<DButton
|
||||
@class="btn-danger"
|
||||
@action={{action "deleteSpammer"}}
|
||||
@disabled={{this.submitDisabled}}
|
||||
@icon="exclamation-triangle"
|
||||
@label="flagging.delete_spammer"
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
@ -158,31 +158,31 @@ acceptance("flagging", function (needs) {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await openFlagModal();
|
||||
|
||||
const modal = query("#discourse-modal");
|
||||
const modal = query(".d-modal");
|
||||
await pressEnter(modal, "ctrlKey");
|
||||
assert.ok(
|
||||
exists("#discourse-modal:visible"),
|
||||
exists(".d-modal:visible"),
|
||||
"The modal wasn't closed because the accept button was disabled"
|
||||
);
|
||||
|
||||
await click("#radio_inappropriate"); // this enables the accept button
|
||||
await pressEnter(modal, "ctrlKey");
|
||||
assert.ok(!exists("#discourse-modal:visible"), "The modal was closed");
|
||||
assert.ok(!exists(".d-modal:visible"), "The modal was closed");
|
||||
});
|
||||
|
||||
test("CMD or WINDOWS-KEY + ENTER accepts the modal", async function (assert) {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await openFlagModal();
|
||||
|
||||
const modal = query("#discourse-modal");
|
||||
const modal = query(".d-modal");
|
||||
await pressEnter(modal, "metaKey");
|
||||
assert.ok(
|
||||
exists("#discourse-modal:visible"),
|
||||
exists(".d-modal:visible"),
|
||||
"The modal wasn't closed because the accept button was disabled"
|
||||
);
|
||||
|
||||
await click("#radio_inappropriate"); // this enables the accept button
|
||||
await pressEnter(modal, "ctrlKey");
|
||||
assert.ok(!exists("#discourse-modal:visible"), "The modal was closed");
|
||||
assert.ok(!exists(".d-modal:visible"), "The modal was closed");
|
||||
});
|
||||
});
|
||||
|
@ -24,6 +24,10 @@ export default class ChatMessageFlag {
|
||||
return false;
|
||||
}
|
||||
|
||||
includeSeparator() {
|
||||
return true;
|
||||
}
|
||||
|
||||
_rewriteFlagDescriptions(flags) {
|
||||
return flags.map((flag) => {
|
||||
flag.set(
|
||||
@ -34,11 +38,13 @@ export default class ChatMessageFlag {
|
||||
});
|
||||
}
|
||||
|
||||
flagsAvailable(_controller, site, model) {
|
||||
let flagsAvailable = site.flagTypes;
|
||||
flagsAvailable(flagModal) {
|
||||
let flagsAvailable = flagModal.site.flagTypes;
|
||||
|
||||
flagsAvailable = flagsAvailable.filter((flag) => {
|
||||
return model.availableFlags.includes(flag.name_key);
|
||||
return flagModal.args.model.flagModel.availableFlags.includes(
|
||||
flag.name_key
|
||||
);
|
||||
});
|
||||
|
||||
// "message user" option should be at the top
|
||||
@ -55,37 +61,19 @@ export default class ChatMessageFlag {
|
||||
return this._rewriteFlagDescriptions(flagsAvailable);
|
||||
}
|
||||
|
||||
create(controller, opts) {
|
||||
controller.send("hideModal");
|
||||
create(flagModal, opts) {
|
||||
flagModal.args.closeModal();
|
||||
|
||||
return ajax("/chat/flag", {
|
||||
method: "PUT",
|
||||
data: {
|
||||
chat_message_id: controller.get("model.id"),
|
||||
flag_type_id: controller.get("selected.id"),
|
||||
chat_message_id: flagModal.args.model.flagModel.id,
|
||||
flag_type_id: flagModal.selected.id,
|
||||
message: opts.message,
|
||||
is_warning: opts.isWarning,
|
||||
take_action: opts.takeAction,
|
||||
queue_for_review: opts.queue_for_review,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
if (controller.isDestroying || controller.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!opts.skipClose) {
|
||||
controller.send("closeModal");
|
||||
}
|
||||
if (opts.message) {
|
||||
controller.set("message", "");
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!controller.isDestroying && !controller.isDestroyed) {
|
||||
controller.send("closeModal");
|
||||
}
|
||||
popupAjaxError(error);
|
||||
});
|
||||
}).catch((error) => popupAjaxError(error));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import getURL from "discourse-common/lib/get-url";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import ChatMessageFlag from "discourse/plugins/chat/discourse/lib/chat-message-flag";
|
||||
import Bookmark from "discourse/models/bookmark";
|
||||
import BookmarkModal from "discourse/components/modal/bookmark";
|
||||
@ -18,6 +17,7 @@ import { tracked } from "@glimmer/tracking";
|
||||
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
|
||||
import { MESSAGE_CONTEXT_THREAD } from "discourse/plugins/chat/discourse/components/chat-message";
|
||||
import I18n from "I18n";
|
||||
import FlagModal from "discourse/components/modal/flag";
|
||||
|
||||
const removedSecondaryActions = new Set();
|
||||
|
||||
@ -338,8 +338,13 @@ export default class ChatMessageInteractor {
|
||||
const model = new ChatMessage(this.message.channel, this.message);
|
||||
model.username = this.message.user?.username;
|
||||
model.user_id = this.message.user?.id;
|
||||
const controller = showModal("flag", { model });
|
||||
controller.set("flagTarget", new ChatMessageFlag());
|
||||
this.modal.show(FlagModal, {
|
||||
model: {
|
||||
flagTarget: new ChatMessageFlag(),
|
||||
flagModel: model,
|
||||
setHidden: () => model.set("hidden", true),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
|
Loading…
Reference in New Issue
Block a user