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:
Isaac Janzen 2023-08-28 16:51:58 -05:00 committed by GitHub
parent cf71f8358e
commit 026cd3e532
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 379 additions and 430 deletions

View File

@ -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>

View File

@ -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>

View 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;
}
}

View File

@ -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}}
/>

View File

@ -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);
},
},
});

View File

@ -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"
);
},
});

View File

@ -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));
}
}

View File

@ -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
);
}
}

View File

@ -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];
}
}

View File

@ -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

View File

@ -18,7 +18,6 @@ const KNOWN_LEGACY_MODALS = [
"create-account",
"create-invite-bulk",
"create-invite",
"flag",
"grant-badge",
"group-default-notifications",
"login",

View File

@ -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>

View File

@ -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");
});
});

View File

@ -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));
}
}

View File

@ -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