diff --git a/app/controllers/discourse_assign/assign_controller.rb b/app/controllers/discourse_assign/assign_controller.rb index 8dbcd7c..c9accfd 100644 --- a/app/controllers/discourse_assign/assign_controller.rb +++ b/app/controllers/discourse_assign/assign_controller.rb @@ -44,27 +44,11 @@ module DiscourseAssign end def assign - target_id = params.require(:target_id) - target_type = params.require(:target_type) - username = params.permit(:username)['username'] - group_name = params.permit(:group_name)['group_name'] + reassign_or_assign_target(action: "assign") + end - assign_to = username.present? ? User.find_by(username_lower: username.downcase) : Group.where("LOWER(name) = ?", group_name.downcase).first - - raise Discourse::NotFound unless assign_to - raise Discourse::NotFound if !Assignment.valid_type?(target_type) - target = target_type.constantize.where(id: target_id).first - raise Discourse::NotFound unless target - - # perhaps? - #Scheduler::Defer.later "assign topic" do - assign = Assigner.new(target, current_user).assign(assign_to) - - if assign[:success] - render json: success_json - else - render json: translate_failure(assign[:reason], assign_to), status: 400 - end + def reassign + reassign_or_assign_target(action: "reassign") end def assigned @@ -185,5 +169,29 @@ module DiscourseAssign def ensure_assign_allowed raise Discourse::InvalidAccess.new unless current_user.can_assign? end + + def reassign_or_assign_target(action:) + target_id = params.require(:target_id) + target_type = params.require(:target_type) + username = params.permit(:username)['username'] + group_name = params.permit(:group_name)['group_name'] + + assign_to = username.present? ? User.find_by(username_lower: username.downcase) : Group.where("LOWER(name) = ?", group_name.downcase).first + + raise Discourse::NotFound unless assign_to + raise Discourse::NotFound if !Assignment.valid_type?(target_type) + target = target_type.constantize.where(id: target_id).first + raise Discourse::NotFound unless target + + # perhaps? + #Scheduler::Defer.later "assign topic" do + assign = Assigner.new(target, current_user).send(action, assign_to) + + if assign[:success] + render json: success_json + else + render json: translate_failure(assign[:reason], assign_to), status: 400 + end + end end end diff --git a/assets/javascripts/discourse-assign/controllers/assign-user.js.es6 b/assets/javascripts/discourse-assign/controllers/assign-user.js.es6 index d89520b..841fef4 100644 --- a/assets/javascripts/discourse-assign/controllers/assign-user.js.es6 +++ b/assets/javascripts/discourse-assign/controllers/assign-user.js.es6 @@ -5,7 +5,6 @@ import { popupAjaxError } from "discourse/lib/ajax-error"; import { not, or } from "@ember/object/computed"; import { isEmpty } from "@ember/utils"; import { action } from "@ember/object"; -import discourseComputed from "discourse-common/utils/decorators"; export default Controller.extend({ topicBulkActions: controller(), @@ -39,14 +38,43 @@ export default Controller.extend({ }); }, - @discourseComputed("model.targetType") - i18nSuffix(targetType) { - switch (targetType) { - case "Post": - return "_post_modal"; - case "Topic": - return "_modal"; + reassignOrAssignTarget(assign_action) { + if (this.isBulkAction) { + this.bulkAction(this.model.username); + return; } + let path = "/assign/" + assign_action; + + if (isEmpty(this.get("model.username"))) { + this.model.target.set("assigned_to_user", null); + } + + if (isEmpty(this.get("model.group_name"))) { + this.model.target.set("assigned_to_group", null); + } + + if ( + isEmpty(this.get("model.username")) && + isEmpty(this.get("model.group_name")) + ) { + path = "/assign/unassign"; + } + + this.send("closeModal"); + + return ajax(path, { + type: "PUT", + data: { + username: this.get("model.username"), + group_name: this.get("model.group_name"), + target_id: this.get("model.target.id"), + target_type: this.get("model.targetType"), + }, + }) + .then(() => { + this.get("model.onSuccess")?.(); + }) + .catch(popupAjaxError); }, @action @@ -77,48 +105,47 @@ export default Controller.extend({ @action assign() { + this.reassignOrAssignTarget("assign"); + }, + + @action + reassign() { + this.reassignOrAssignTarget("reassign"); + }, + + @action + reassignUser(name) { if (this.isBulkAction) { - this.bulkAction(this.model.username); + this.bulkAction(name); return; } - let path = "/assign/assign"; - if (isEmpty(this.get("model.username"))) { - this.model.target.set("assigned_to_user", null); + if (this.allowedGroupsForAssignment.includes(name)) { + this.setProperties({ + "model.username": null, + "model.group_name": name, + "model.allowedGroups": this.taskActions.allowedGroups, + }); + } else { + this.setProperties({ + "model.username": name, + "model.group_name": null, + "model.allowedGroups": this.taskActions.allowedGroups, + }); } - if (isEmpty(this.get("model.group_name"))) { - this.model.target.set("assigned_to_group", null); + if (name) { + return this.reassign(); } - - if ( - isEmpty(this.get("model.username")) && - isEmpty(this.get("model.group_name")) - ) { - path = "/assign/unassign"; - } - - this.send("closeModal"); - - return ajax(path, { - type: "PUT", - data: { - username: this.get("model.username"), - group_name: this.get("model.group_name"), - target_id: this.get("model.target.id"), - target_type: this.get("model.targetType"), - }, - }) - .then(() => { - if (this.get("model.onSuccess")) { - this.get("model.onSuccess")(); - } - }) - .catch(popupAjaxError); }, @action assignUsername(selected) { this.assignUser(selected.firstObject); }, + + @action + reassignUsername(selected) { + this.reassignUser(selected.firstObject); + }, }); diff --git a/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js.es6 b/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js.es6 index c3f34ab..5268544 100644 --- a/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js.es6 +++ b/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js.es6 @@ -14,9 +14,17 @@ import TopicButtonAction, { import { inject } from "@ember/controller"; import I18n from "I18n"; import { isEmpty } from "@ember/utils"; +import { registerTopicFooterDropdown } from "discourse/lib/register-topic-footer-dropdown"; const PLUGIN_ID = "discourse-assign"; +const DEPENDENT_KEYS = [ + "topic.assigned_to_user", + "topic.assigned_to_group", + "currentUser.can_assign", + "topic.assigned_to_user.username", +]; + function titleForState(name) { if (name) { return I18n.t("discourse_assign.unassign.help", { @@ -27,14 +35,124 @@ function titleForState(name) { } } +function defaultTitle(topic) { + return titleForState( + topic.get("topic.assigned_to_user.username") || + topic.get("topic.assigned_to_group.name") + ); +} + +function includeIsAssignedOnTopic(api) { + api.modifyClass("model:topic", { + pluginId: PLUGIN_ID, + isAssigned() { + return this.assigned_to_user || this.assigned_to_group; + }, + }); +} + function registerTopicFooterButtons(api) { + registerTopicFooterDropdown({ + id: "reassign", + + action(id) { + if (!this.get("currentUser.can_assign")) { + return; + } + + const taskActions = getOwner(this).lookup("service:task-actions"); + + switch (id) { + case "unassign": { + this.set("topic.assigned_to_user", null); + this.set("topic.assigned_to_group", null); + taskActions.unassign(this.topic.id).then(() => { + this.appEvents.trigger("post-stream:refresh", { + id: this.topic.postStream.firstPostId, + }); + }); + break; + } + case "reassign-self": { + this.set("topic.assigned_to_user", null); + this.set("topic.assigned_to_group", null); + taskActions + .reassignUserToTopic(this.currentUser, this.topic) + .then(() => { + this.appEvents.trigger("post-stream:refresh", { + id: this.topic.postStream.firstPostId, + }); + }); + break; + } + case "reassign": { + taskActions.reassign(this.topic).set("model.onSuccess", () => { + this.appEvents.trigger("post-stream:refresh", { + id: this.topic.postStream.firstPostId, + }); + }); + break; + } + } + }, + + noneItem() { + const user = this.get("topic.assigned_to_user"); + const group = this.get("topic.assigned_to_group"); + const label = I18n.t("discourse_assign.unassign.title_w_ellipsis"); + const groupLabel = I18n.t("discourse_assign.unassign.title"); + + if (user) { + return { + id: null, + name: htmlSafe( + `${renderAvatar(user, { + imageSize: "tiny", + ignoreTitle: true, + })}${label}` + ), + }; + } else if (group) { + return { + id: null, + name: htmlSafe( + `${groupLabel} @${group.name}...` + ), + }; + } + }, + dependentKeys: DEPENDENT_KEYS, + classNames: ["reassign"], + content() { + const content = [ + { id: "unassign", name: I18n.t("discourse_assign.unassign.title") }, + ]; + if ( + this.topic.isAssigned() && + this.get("topic.assigned_to_user")?.username !== + this.currentUser.username + ) { + content.push({ + id: "reassign-self", + name: I18n.t("discourse_assign.reassign.to_self"), + }); + } + content.push({ + id: "reassign", + name: I18n.t("discourse_assign.reassign.title_w_ellipsis"), + }); + return content; + }, + + displayed() { + return !this.site.mobileView && this.topic.isAssigned(); + }, + }); + api.registerTopicFooterButton({ id: "assign", icon() { - const hasAssignement = - this.get("topic.assigned_to_user") || - this.get("topic.assigned_to_group"); - return hasAssignement + return this.topic.isAssigned() ? this.site.mobileView ? "user-times" : null @@ -42,47 +160,13 @@ function registerTopicFooterButtons(api) { }, priority: 250, translatedTitle() { - return titleForState( - this.get("topic.assigned_to_user.username") || - this.get("topic.assigned_to_group.name") - ); + defaultTitle(this); }, translatedAriaLabel() { - return titleForState( - this.get("topic.assigned_to_user.username") || - this.get("topic.assigned_to_group.name") - ); + defaultTitle(this); }, translatedLabel() { - const user = this.get("topic.assigned_to_user"); - const group = this.get("topic.assigned_to_group"); - const label = I18n.t("discourse_assign.unassign.title"); - - if (user) { - if (this.site.mobileView) { - return htmlSafe( - `${label}${ - user.username - }${renderAvatar(user, { - imageSize: "small", - ignoreTitle: true, - })}` - ); - } else { - return htmlSafe( - `${renderAvatar(user, { - imageSize: "tiny", - ignoreTitle: true, - })}${label}` - ); - } - } else if (group) { - return htmlSafe( - `${label} @${group.name}` - ); - } else { - return I18n.t("discourse_assign.assign.title"); - } + return I18n.t("discourse_assign.assign.title"); }, action() { if (!this.get("currentUser.can_assign")) { @@ -90,20 +174,19 @@ function registerTopicFooterButtons(api) { } const taskActions = getOwner(this).lookup("service:task-actions"); - const topic = this.topic; - if (topic.assigned_to_user || topic.assigned_to_group) { + if (this.topic.isAssigned()) { this.set("topic.assigned_to_user", null); this.set("topic.assigned_to_group", null); - taskActions.unassign(topic.id, "Topic").then(() => { + taskActions.unassign(this.topic.id, "Topic").then(() => { this.appEvents.trigger("post-stream:refresh", { - id: topic.postStream.firstPostId, + id: this.topic.postStream.firstPostId, }); }); } else { - taskActions.assign(topic).set("model.onSuccess", () => { + taskActions.assign(this.topic).set("model.onSuccess", () => { this.appEvents.trigger("post-stream:refresh", { - id: topic.postStream.firstPostId, + id: this.topic.postStream.firstPostId, }); }); } @@ -112,14 +195,183 @@ function registerTopicFooterButtons(api) { return this.site.mobileView; }, classNames: ["assign"], - dependentKeys: [ - "topic.assigned_to_user", - "topic.assigned_to_group", - "currentUser.can_assign", - "topic.assigned_to_user.username", - ], + dependentKeys: DEPENDENT_KEYS, displayed() { - return this.currentUser && this.currentUser.can_assign; + return this.currentUser?.can_assign && !this.topic.isAssigned(); + }, + }); + + api.registerTopicFooterButton({ + id: "unassign-mobile-header", + translatedTitle() { + defaultTitle(this); + }, + translatedAriaLabel() { + defaultTitle(this); + }, + translatedLabel() { + const user = this.get("topic.assigned_to_user"); + const group = this.get("topic.assigned_to_group"); + const label = I18n.t("discourse_assign.assigned_to_w_ellipsis"); + + if (user) { + return htmlSafe( + `${label}${ + user.username + }${renderAvatar(user, { + imageSize: "small", + ignoreTitle: true, + })}` + ); + } else if (group) { + return htmlSafe( + `${label} @${group.name}` + ); + } + }, + dropdown() { + return this.currentUser?.can_assign && this.topic.isAssigned(); + }, + classNames: ["assign"], + dependentKeys: DEPENDENT_KEYS, + displayed() { + // only display the button in the mobile view + return this.site.mobileView; + }, + }); + + api.registerTopicFooterButton({ + id: "unassign-mobile", + icon() { + return "user-times"; + }, + translatedTitle() { + defaultTitle(this); + }, + translatedAriaLabel() { + defaultTitle(this); + }, + translatedLabel() { + const label = I18n.t("discourse_assign.unassign.title"); + + return htmlSafe( + `${label}` + ); + }, + action() { + if (!this.get("currentUser.can_assign")) { + return; + } + + const taskActions = getOwner(this).lookup("service:task-actions"); + + this.set("topic.assigned_to_user", null); + this.set("topic.assigned_to_group", null); + taskActions.unassign(this.topic.id).then(() => { + this.appEvents.trigger("post-stream:refresh", { + id: this.topic.postStream.firstPostId, + }); + }); + }, + dropdown() { + return this.currentUser?.can_assign && this.topic.isAssigned(); + }, + classNames: ["assign"], + dependentKeys: DEPENDENT_KEYS, + displayed() { + // only display the button in the mobile view + return this.site.mobileView && this.topic.isAssigned(); + }, + }); + + api.registerTopicFooterButton({ + id: "reassign-self-mobile", + icon() { + return "user-plus"; + }, + translatedTitle() { + defaultTitle(this); + }, + translatedAriaLabel() { + defaultTitle(this); + }, + translatedLabel() { + const label = I18n.t("discourse_assign.reassign.to_self"); + + return htmlSafe( + `${label}` + ); + }, + action() { + if (!this.get("currentUser.can_assign")) { + return; + } + + const taskActions = getOwner(this).lookup("service:task-actions"); + + this.set("topic.assigned_to_user", null); + this.set("topic.assigned_to_group", null); + taskActions.reassignUserToTopic(this.currentUser, this.topic).then(() => { + this.appEvents.trigger("post-stream:refresh", { + id: this.topic.postStream.firstPostId, + }); + }); + }, + dropdown() { + return this.currentUser?.can_assign && this.topic.isAssigned(); + }, + classNames: ["assign"], + dependentKeys: DEPENDENT_KEYS, + displayed() { + return ( + // only display the button in the mobile view + this.site.mobileView && + this.topic.isAssigned() && + this.get("topic.assigned_to_user")?.username !== + this.currentUser.username + ); + }, + }); + + api.registerTopicFooterButton({ + id: "reassign-mobile", + icon() { + return "user-plus"; + }, + translatedTitle() { + defaultTitle(this); + }, + translatedAriaLabel() { + defaultTitle(this); + }, + translatedLabel() { + const label = I18n.t("discourse_assign.reassign.title_w_ellipsis"); + + return htmlSafe( + `${label}` + ); + }, + action() { + if (!this.get("currentUser.can_assign")) { + return; + } + + const taskActions = getOwner(this).lookup("service:task-actions"); + + taskActions.reassign(this.topic).set("model.onSuccess", () => { + this.appEvents.trigger("post-stream:refresh", { + id: this.topic.postStream.firstPostId, + }); + }); + }, + dropdown() { + return this.currentUser?.can_assign && this.topic.isAssigned(); + }, + classNames: ["assign"], + dependentKeys: DEPENDENT_KEYS, + displayed() { + // only display the button in the mobile view + return this.site.mobileView; }, }); } @@ -240,16 +492,19 @@ function initialize(api) { api.addPostSmallActionIcon("unassigned_group", "group-times"); api.addPostSmallActionIcon("unassigned_from_post", "user-times"); api.addPostSmallActionIcon("unassigned_group_from_post", "group-times"); - api.includePostAttributes("assigned_to_user", "assigned_to_group"); + api.addPostSmallActionIcon("reassigned", "user-plus"); + api.addPostSmallActionIcon("reassigned_group", "group-plus"); api.addPostTransformCallback((transformed) => { if ( [ "assigned", "unassigned", + "reassigned", "assigned_group", "unassigned_group", + "reassigned_group", "assigned_to_post", "assigned_group_to_post", "unassigned_from_post", @@ -592,6 +847,7 @@ export default { }); } + withPluginApi("0.13.0", (api) => includeIsAssignedOnTopic(api)); withPluginApi("0.11.0", (api) => initialize(api)); withPluginApi("0.8.28", (api) => registerTopicFooterButtons(api)); diff --git a/assets/javascripts/discourse/services/task-actions.js.es6 b/assets/javascripts/discourse/services/task-actions.js.es6 index 90d1e57..b3e9f9d 100644 --- a/assets/javascripts/discourse/services/task-actions.js.es6 +++ b/assets/javascripts/discourse/services/task-actions.js.es6 @@ -3,6 +3,15 @@ import { ajax } from "discourse/lib/ajax"; import showModal from "discourse/lib/show-modal"; export default Service.extend({ + i18nSuffix(targetType) { + switch (targetType) { + case "Post": + return "_post_modal"; + case "Topic": + return "_modal"; + } + }, + unassign(targetId, targetType = "Topic") { return ajax("/assign/unassign", { type: "PUT", @@ -15,22 +24,47 @@ export default Service.extend({ assign(target, targetType = "Topic") { return showModal("assign-user", { + title: "discourse_assign.assign" + this.i18nSuffix(targetType) + ".title", model: { - username: target.get("assigned_to_user.username"), - group_name: target.get("assigned_to_group.name"), + description: + "discourse_assign.assign" + + this.i18nSuffix(targetType) + + ".description", + username: target.assigned_to_user?.username, + group_name: target.assigned_to_group?.name, target, targetType, }, }); }, - assignUserToTopic(user, topic) { - return ajax("/assign/assign", { + reassignUserToTopic(user, target, targetType = "Topic") { + return ajax("/assign/reassign", { type: "PUT", data: { username: user.username, - target_id: topic.id, - target_type: "Topic", + target_id: target.id, + target_type: targetType, + }, + }); + }, + + reassign(target, targetType = "Topic") { + return showModal("assign-user", { + title: + "discourse_assign.assign" + + this.i18nSuffix(targetType) + + ".reassign_title", + model: { + description: + "discourse_assign.reassign" + + this.i18nSuffix(targetType) + + ".description", + reassign: true, + username: target.assigned_to_user?.username, + group_name: target.assigned_to_group?.name, + target, + targetType, }, }); }, diff --git a/assets/javascripts/discourse/templates/modal/assign-user.hbs b/assets/javascripts/discourse/templates/modal/assign-user.hbs index 3891b40..96dcbb5 100644 --- a/assets/javascripts/discourse/templates/modal/assign-user.hbs +++ b/assets/javascripts/discourse/templates/modal/assign-user.hbs @@ -1,10 +1,10 @@ -{{#d-modal-body title=(concat "discourse_assign.assign" i18nSuffix ".title") class="assign"}} +{{#d-modal-body class="assign"}}
-

{{i18n (concat "discourse_assign.assign" i18nSuffix ".description")}}

+

{{i18n model.description}}

{{email-group-user-chooser autocomplete="off" value=assigneeName - onChange=(action "assignUsername") + onChange=(action (if model.reassign "reassignUsername" "assignUsername")) autofocus="autofocus" options=(hash placementStrategy="absolute" @@ -19,7 +19,7 @@ }}
{{#each assignSuggestions as |user|}} - + {{avatar user imageSize="small"}} {{/each}} @@ -29,9 +29,9 @@