FEATURE: Add menu to assigned text on posts (#550)
This adds a popup menu to assigned posts that makes it easier to unassign or reassign them.
This commit is contained in:
parent
a1686e1ca9
commit
b09a6618fa
|
|
@ -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);
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if @assignedToUser}}
|
||||
{{icon "user-plus"}}
|
||||
{{else}}
|
||||
{{icon "group-plus"}}
|
||||
{{/if}}
|
||||
|
||||
<span class="assign-text">
|
||||
{{i18n "discourse_assign.assigned_to"}}
|
||||
</span>
|
||||
|
||||
<a href={{@href}} class="assigned-to-username">
|
||||
{{#if @assignedToUser}}
|
||||
{{@assignedToUser.username}}
|
||||
{{else}}
|
||||
{{@assignedToGroup.name}}
|
||||
{{/if}}
|
||||
</a>
|
||||
|
||||
<DMenu @icon="ellipsis-h" class="btn-flat more-button">
|
||||
<div class="popup-menu">
|
||||
<ul>
|
||||
<li>
|
||||
<DButton
|
||||
@action={{this.unassign}}
|
||||
@icon="user-plus"
|
||||
@label="discourse_assign.unassign.title"
|
||||
class="popup-menu-btn"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<DButton
|
||||
@action={{this.editAssignment}}
|
||||
@icon="group-plus"
|
||||
@label="discourse_assign.reassign.title_w_ellipsis"
|
||||
class="popup-menu-btn"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</DMenu>
|
||||
</template>
|
||||
}
|
||||
|
|
@ -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`<AssignedToPost @assignedToUser={{@data.assignedToUser}} @assignedToGroup={{@data.assignedToGroup}} @href={{@data.href}} @post={{@data.post}} />`,
|
||||
{
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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%;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue