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 { getOwner } from "@ember/application";
|
||||||
import { htmlSafe } from "@ember/template";
|
import { htmlSafe } from "@ember/template";
|
||||||
import { isEmpty } from "@ember/utils";
|
import { isEmpty } from "@ember/utils";
|
||||||
|
import { hbs } from "ember-cli-htmlbars";
|
||||||
import { h } from "virtual-dom";
|
import { h } from "virtual-dom";
|
||||||
import SearchAdvancedOptions from "discourse/components/search-advanced-options";
|
import SearchAdvancedOptions from "discourse/components/search-advanced-options";
|
||||||
import { renderAvatar } from "discourse/helpers/user-avatar";
|
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 { registerTopicFooterDropdown } from "discourse/lib/register-topic-footer-dropdown";
|
||||||
import { escapeExpression } from "discourse/lib/utilities";
|
import { escapeExpression } from "discourse/lib/utilities";
|
||||||
import RawHtml from "discourse/widgets/raw-html";
|
import RawHtml from "discourse/widgets/raw-html";
|
||||||
|
import RenderGlimmer from "discourse/widgets/render-glimmer";
|
||||||
import getURL from "discourse-common/lib/get-url";
|
import getURL from "discourse-common/lib/get-url";
|
||||||
import { iconHTML, iconNode } from "discourse-common/lib/icon-library";
|
import { iconHTML, iconNode } from "discourse-common/lib/icon-library";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
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) {
|
html(attrs) {
|
||||||
let { assignedToUser, assignedToGroup, href } = attrs;
|
return new RenderGlimmer(
|
||||||
|
this,
|
||||||
return h("p.assigned-to", [
|
"p.assigned-to",
|
||||||
assignedToUser ? iconNode("user-plus") : iconNode("group-plus"),
|
hbs`<AssignedToPost @assignedToUser={{@data.assignedToUser}} @assignedToGroup={{@data.assignedToGroup}} @href={{@data.href}} @post={{@data.post}} />`,
|
||||||
h("span.assign-text", I18n.t("discourse_assign.assigned_to")),
|
{
|
||||||
h(
|
assignedToUser: attrs.post.assigned_to_user,
|
||||||
"a",
|
assignedToGroup: attrs.post.assigned_to_group,
|
||||||
{ attributes: { class: "assigned-to-username", href } },
|
href: attrs.href,
|
||||||
assignedToUser ? assignedToUser.username : assignedToGroup.name
|
post: attrs.post,
|
||||||
),
|
}
|
||||||
]);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -792,6 +794,7 @@ function initialize(api) {
|
||||||
if (data.type === "unassigned") {
|
if (data.type === "unassigned") {
|
||||||
delete topic.indirectly_assigned_to[data.post_number];
|
delete topic.indirectly_assigned_to[data.post_number];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.appEvents.trigger("post-stream:refresh", {
|
this.appEvents.trigger("post-stream:refresh", {
|
||||||
id: topic.postStream.posts[0].id,
|
id: topic.postStream.posts[0].id,
|
||||||
});
|
});
|
||||||
|
|
@ -841,11 +844,13 @@ function initialize(api) {
|
||||||
? assignedToUserPath(assignedToUser)
|
? assignedToUserPath(assignedToUser)
|
||||||
: assignedToGroupPath(assignedToGroup);
|
: assignedToGroupPath(assignedToGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (href) {
|
if (href) {
|
||||||
return dec.widget.attach("assigned-to", {
|
return dec.widget.attach("assigned-to-post", {
|
||||||
assignedToUser,
|
assignedToUser,
|
||||||
assignedToGroup,
|
assignedToGroup,
|
||||||
href,
|
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(
|
showAssignModal(
|
||||||
target,
|
target,
|
||||||
{ isAssigned = false, targetType = "Topic", onSuccess }
|
{ 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") {
|
reassignUserToTopic(user, target, targetType = "Topic") {
|
||||||
return ajax("/assign/assign", {
|
return ajax("/assign/assign", {
|
||||||
type: "PUT",
|
type: "PUT",
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,12 @@
|
||||||
.assignee:not(:last-child):after {
|
.assignee:not(:last-child):after {
|
||||||
content: ", ";
|
content: ", ";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.more-button {
|
||||||
|
padding-left: 0.3em;
|
||||||
|
padding-right: 0.3em;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic-body {
|
.topic-body {
|
||||||
|
|
@ -44,19 +50,6 @@
|
||||||
align-items: center;
|
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 {
|
.topic-assigned-to {
|
||||||
min-width: 15%;
|
min-width: 15%;
|
||||||
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