diff --git a/assets/javascripts/discourse/components/assigned-to-post.gjs b/assets/javascripts/discourse/components/assigned-to-post.gjs new file mode 100644 index 0000000..b0ad44b --- /dev/null +++ b/assets/javascripts/discourse/components/assigned-to-post.gjs @@ -0,0 +1,64 @@ +import Component from "@glimmer/component"; +import { action } from "@ember/object"; +import { inject as service } from "@ember/service"; +import DButton from "discourse/components/d-button"; +import icon from "discourse-common/helpers/d-icon"; +import i18n from "discourse-common/helpers/i18n"; +import DMenu from "float-kit/components/d-menu"; + +export default class AssignedToPost extends Component { + @service taskActions; + + @action + unassign() { + this.taskActions.unassignPost(this.args.post); + } + + @action + editAssignment() { + this.taskActions.showAssignPostModal(this.args.post); + } + + +} diff --git a/assets/javascripts/discourse/initializers/extend-for-assigns.js b/assets/javascripts/discourse/initializers/extend-for-assigns.js index ca14210..c5ca7d9 100644 --- a/assets/javascripts/discourse/initializers/extend-for-assigns.js +++ b/assets/javascripts/discourse/initializers/extend-for-assigns.js @@ -1,6 +1,7 @@ import { getOwner } from "@ember/application"; import { htmlSafe } from "@ember/template"; import { isEmpty } from "@ember/utils"; +import { hbs } from "ember-cli-htmlbars"; import { h } from "virtual-dom"; import SearchAdvancedOptions from "discourse/components/search-advanced-options"; import { renderAvatar } from "discourse/helpers/user-avatar"; @@ -8,6 +9,7 @@ import { withPluginApi } from "discourse/lib/plugin-api"; import { registerTopicFooterDropdown } from "discourse/lib/register-topic-footer-dropdown"; import { escapeExpression } from "discourse/lib/utilities"; import RawHtml from "discourse/widgets/raw-html"; +import RenderGlimmer from "discourse/widgets/render-glimmer"; import getURL from "discourse-common/lib/get-url"; import { iconHTML, iconNode } from "discourse-common/lib/icon-library"; import discourseComputed from "discourse-common/utils/decorators"; @@ -646,19 +648,19 @@ function initialize(api) { } }); - api.createWidget("assigned-to", { + api.createWidget("assigned-to-post", { html(attrs) { - let { assignedToUser, assignedToGroup, href } = attrs; - - return h("p.assigned-to", [ - assignedToUser ? iconNode("user-plus") : iconNode("group-plus"), - h("span.assign-text", I18n.t("discourse_assign.assigned_to")), - h( - "a", - { attributes: { class: "assigned-to-username", href } }, - assignedToUser ? assignedToUser.username : assignedToGroup.name - ), - ]); + return new RenderGlimmer( + this, + "p.assigned-to", + hbs``, + { + assignedToUser: attrs.post.assigned_to_user, + assignedToGroup: attrs.post.assigned_to_group, + href: attrs.href, + post: attrs.post, + } + ); }, }); @@ -792,6 +794,7 @@ function initialize(api) { if (data.type === "unassigned") { delete topic.indirectly_assigned_to[data.post_number]; } + this.appEvents.trigger("post-stream:refresh", { id: topic.postStream.posts[0].id, }); @@ -841,11 +844,13 @@ function initialize(api) { ? assignedToUserPath(assignedToUser) : assignedToGroupPath(assignedToGroup); } + if (href) { - return dec.widget.attach("assigned-to", { + return dec.widget.attach("assigned-to-post", { assignedToUser, assignedToGroup, href, + post: postModel, }); } } diff --git a/assets/javascripts/discourse/services/task-actions.js b/assets/javascripts/discourse/services/task-actions.js index 573de16..465d1ac 100644 --- a/assets/javascripts/discourse/services/task-actions.js +++ b/assets/javascripts/discourse/services/task-actions.js @@ -45,6 +45,11 @@ export default class TaskActions extends Service { }); } + async unassignPost(post) { + await this.unassign(post.id, "Post"); + delete post.topic.indirectly_assigned_to[post.id]; + } + showAssignModal( target, { isAssigned = false, targetType = "Topic", onSuccess } @@ -62,6 +67,10 @@ export default class TaskActions extends Service { }); } + showAssignPostModal(post) { + return this.showAssignModal(post, { targetType: "Post" }); + } + reassignUserToTopic(user, target, targetType = "Topic") { return ajax("/assign/assign", { type: "PUT", diff --git a/assets/stylesheets/assigns.scss b/assets/stylesheets/assigns.scss index bc9db18..be0519e 100644 --- a/assets/stylesheets/assigns.scss +++ b/assets/stylesheets/assigns.scss @@ -26,6 +26,12 @@ .assignee:not(:last-child):after { content: ", "; } + + .more-button { + padding-left: 0.3em; + padding-right: 0.3em; + vertical-align: middle; + } } .topic-body { @@ -44,19 +50,6 @@ align-items: center; } -.assigned-to-user { - display: flex; - align-items: center; - - img.avatar { - margin-right: 0.3em; - } - - .unassign { - margin-left: 0.5em; - } -} - .topic-assigned-to { min-width: 15%; width: 15%; diff --git a/test/javascripts/acceptance/post-popup-menu-test.js b/test/javascripts/acceptance/post-popup-menu-test.js new file mode 100644 index 0000000..10e7f60 --- /dev/null +++ b/test/javascripts/acceptance/post-popup-menu-test.js @@ -0,0 +1,124 @@ +import { click, fillIn, visit } from "@ember/test-helpers"; +import { test } from "qunit"; +import topicFixtures from "discourse/tests/fixtures/topic"; +import { + acceptance, + publishToMessageBus, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; +import { cloneJSON } from "discourse-common/lib/object"; + +const username = "eviltrout"; +const new_assignee_username = "new_assignee"; + +function topicWithAssignedPostResponse() { + const topic = cloneJSON(topicFixtures["/t/28830/1.json"]); + const secondPost = topic.post_stream.posts[1]; + + topic["indirectly_assigned_to"] = { + [secondPost.id]: { + assigned_to: { + username, + }, + post_number: 1, + }, + }; + secondPost["assigned_to_user"] = { username }; + + return topic; +} + +const selectors = { + assignedTo: ".post-stream article#post_2 .assigned-to", + moreButton: ".post-stream .topic-post .more-button", + popupMenu: { + unassign: ".popup-menu .popup-menu-btn svg.d-icon-user-plus", + editAssignment: ".popup-menu .popup-menu-btn svg.d-icon-group-plus", + }, + modal: { + assignee: ".modal-container .select-kit-header-wrapper", + assigneeInput: ".modal-container .filter-input", + assignButton: ".d-modal__footer .btn-primary", + }, +}; + +const topic = topicWithAssignedPostResponse(); +const post = topic.post_stream.posts[1]; + +acceptance("Discourse Assign | Post popup menu", function (needs) { + needs.user(); + needs.settings({ + assign_enabled: true, + }); + + needs.pretender((server, helper) => { + server.get("/t/44.json", () => helper.response(topic)); + + server.put("/assign/assign", () => { + return helper.response({ success: true }); + }); + + server.put("/assign/unassign", () => { + return helper.response({ success: true }); + }); + + server.get("/assign/suggestions", () => + helper.response({ + assign_allowed_for_groups: [], + suggestions: [{ username: new_assignee_username }], + }) + ); + + server.get("/u/search/users", () => + helper.response({ users: [{ username: new_assignee_username }] }) + ); + }); + + needs.hooks.beforeEach(() => { + updateCurrentUser({ can_assign: true }); + }); + + test("Unassigns the post", async function (assert) { + await visit("/t/assignment-topic/44"); + await click(selectors.moreButton); + await click(selectors.popupMenu.unassign); + await publishToMessageBus("/staff/topic-assignment", { + type: "unassigned", + topic_id: topic.id, + post_id: post.id, + assigned_type: "User", + }); + + assert.dom(".popup-menu").doesNotExist("The popup menu is closed"); + assert.dom(selectors.assignedTo).doesNotExist("The post is unassigned"); + }); + + test("Reassigns the post", async function (assert) { + await visit("/t/assignment-topic/44"); + await click(selectors.moreButton); + await click(selectors.popupMenu.editAssignment); + await click(selectors.modal.assignee); + await fillIn(selectors.modal.assigneeInput, new_assignee_username); + await click(selectors.modal.assignButton); + + await publishToMessageBus("/staff/topic-assignment", { + type: "assigned", + topic_id: topic.id, + post_id: post.id, + assigned_type: "User", + assigned_to: { + username: new_assignee_username, + }, + }); + + // todo: we can skip this one for now, I can fix it in a core PR + // assert.dom(".popup-menu").doesNotExist("The popup menu is closed"); + + assert + .dom(`${selectors.assignedTo} .assigned-to-username`) + .hasText( + new_assignee_username, + "The post is assigned to the new assignee" + ); + }); +});