diff --git a/assets/javascripts/discourse-assign/connectors/topic-footer-main-buttons-before-create/assign-button.js.es6 b/assets/javascripts/discourse-assign/connectors/topic-footer-main-buttons-before-create/assign-button.js.es6 index 04fa018..ea78d64 100644 --- a/assets/javascripts/discourse-assign/connectors/topic-footer-main-buttons-before-create/assign-button.js.es6 +++ b/assets/javascripts/discourse-assign/connectors/topic-footer-main-buttons-before-create/assign-button.js.es6 @@ -1,5 +1,4 @@ -import showModal from "discourse/lib/show-modal"; -import { ajax } from "discourse/lib/ajax"; +import { getOwner } from "discourse-common/lib/get-owner"; export default { shouldRender(args, component) { @@ -11,22 +10,18 @@ export default { ); }, + setupComponent(args, component) { + const taskActions = getOwner(this).lookup("service:task-actions"); + component.set("taskActions", taskActions); + }, + actions: { unassign() { this.set("topic.assigned_to_user", null); - - return ajax("/assign/unassign", { - type: "PUT", - data: { topic_id: this.get("topic.id") } - }); + this.get("taskActions").unassign(this.get("topic.id")); }, assign() { - showModal("assign-user", { - model: { - topic: this.topic, - username: this.topic.get("assigned_to_user.username") - } - }); + this.get("taskActions").assign(this.topic); } } }; diff --git a/assets/javascripts/discourse-assign/controllers/assign-user.js.es6 b/assets/javascripts/discourse-assign/controllers/assign-user.js.es6 index 1661570..da6bcae 100644 --- a/assets/javascripts/discourse-assign/controllers/assign-user.js.es6 +++ b/assets/javascripts/discourse-assign/controllers/assign-user.js.es6 @@ -44,7 +44,9 @@ export default Ember.Controller.extend({ } }) .then(() => { - // done + if (this.get("model.onSuccess")) { + this.get("model.onSuccess")(); + } }) .catch(popupAjaxError); } diff --git a/assets/javascripts/discourse-assign/controllers/user-activity-assigned.js.es6 b/assets/javascripts/discourse-assign/controllers/user-activity-assigned.js.es6 index cd1c8aa..b87b23d 100644 --- a/assets/javascripts/discourse-assign/controllers/user-activity-assigned.js.es6 +++ b/assets/javascripts/discourse-assign/controllers/user-activity-assigned.js.es6 @@ -1,8 +1,10 @@ import { ajax } from "discourse/lib/ajax"; import computed from "ember-addons/ember-computed-decorators"; +import UserTopicsList from "discourse/controllers/user-topics-list"; -export default Ember.Controller.extend({ +export default UserTopicsList.extend({ user: Ember.inject.controller(), + taskActions: Ember.inject.service(), @computed("model.topics") canUnassignAll(topics) { @@ -21,10 +23,19 @@ export default Ember.Controller.extend({ ajax("/assign/unassign-all", { type: "PUT", data: { user_id: user.get("id") } - }).then(() => this.send("unassignedAll")); + }).then(() => this.send("changeAssigned")); } } ); + }, + unassign(topic) { + this.get("taskActions") + .unassign(topic.get("id")) + .then(() => this.send("changeAssigned")); + }, + reassign(topic) { + const controller = this.get("taskActions").assign(topic); + controller.set("model.onSuccess", () => this.send("changeAssigned")); } } }); 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 1fddd87..7ad9b70 100644 --- a/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js.es6 +++ b/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js.es6 @@ -8,6 +8,9 @@ import { iconNode } from "discourse-common/lib/icon-library"; import { h } from "virtual-dom"; import { iconHTML } from "discourse-common/lib/icon-library"; +// TODO: This has to be removed when 2.3 becomes the new stable version. +import { ListItemDefaults } from "discourse/components/topic-list-item"; + const ACTION_ID = "assign"; function modifySelectKit(api) { @@ -138,9 +141,18 @@ function initialize(api) { const assignedTo = topic.get("assigned_to_user.username"); if (assignedTo) { const assignedPath = topic.get("assignedToUserPath"); - return `${iconHTML( + let assignLabels = `${iconHTML( "user-plus" )}${assignedTo}`; + + if ( + ListItemDefaults === undefined && + topic.get("archetype") === "private_message" + ) { + assignLabels += `
${iconHTML("envelope")} Message
`; + } + + return assignLabels; } }); diff --git a/assets/javascripts/discourse-assign/routes/user-activity-assigned.js.es6 b/assets/javascripts/discourse-assign/routes/user-activity-assigned.js.es6 index ef89462..72f43dd 100644 --- a/assets/javascripts/discourse-assign/routes/user-activity-assigned.js.es6 +++ b/assets/javascripts/discourse-assign/routes/user-activity-assigned.js.es6 @@ -1,4 +1,5 @@ import UserTopicListRoute from "discourse/routes/user-topic-list"; +import { ListItemDefaults } from "discourse/components/topic-list-item"; export default UserTopicListRoute.extend({ userActionType: 16, @@ -6,9 +7,10 @@ export default UserTopicListRoute.extend({ model() { return this.store.findFiltered("topicList", { - filter: "latest", + filter: `topics/messages-assigned/${this.modelFor("user").get( + "username_lower" + )}`, params: { - assigned: this.modelFor("user").get("username_lower"), // core is a bit odd here and is not sending an array, should be fixed exclude_category_ids: [-1] } @@ -16,8 +18,12 @@ export default UserTopicListRoute.extend({ }, renderTemplate() { + // TODO: This has to be removed when 2.3 becomes the new stable version. + const template = ListItemDefaults + ? "user-assigned-topics" + : "user-topics-list"; this.render("user-activity-assigned"); - this.render("user-topics-list", { into: "user-activity-assigned" }); + this.render(template, { into: "user-activity-assigned" }); }, setupController(controller, model) { @@ -26,7 +32,7 @@ export default UserTopicListRoute.extend({ }, actions: { - unassignedAll() { + changeAssigned() { this.refresh(); } } diff --git a/assets/javascripts/discourse/components/assign-actions-dropdown.js.es6 b/assets/javascripts/discourse/components/assign-actions-dropdown.js.es6 new file mode 100644 index 0000000..88ed3e4 --- /dev/null +++ b/assets/javascripts/discourse/components/assign-actions-dropdown.js.es6 @@ -0,0 +1,39 @@ +import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; + +export default DropdownSelectBoxComponent.extend({ + classNames: ["assign-actions-dropdown"], + headerIcon: null, + title: "...", + allowInitialValueMutation: false, + showFullTitle: true, + + computeContent() { + return [ + { + id: "unassign", + icon: "user-times", + name: I18n.t("discourse_assign.unassign.title"), + description: I18n.t("discourse_assign.unassign.help") + }, + { + id: "reassign", + icon: "users", + name: I18n.t("discourse_assign.reassign.title"), + description: I18n.t("discourse_assign.reassign.help") + } + ]; + }, + + actions: { + onSelect(id) { + switch (id) { + case "unassign": + this.unassign(this.get("topic"), this.get("user")); + break; + case "reassign": + this.reassign(this.get("topic"), this.get("user")); + break; + } + } + } +}); diff --git a/assets/javascripts/discourse/components/assigned-topic-list-item.js.es6 b/assets/javascripts/discourse/components/assigned-topic-list-item.js.es6 new file mode 100644 index 0000000..a9eaa15 --- /dev/null +++ b/assets/javascripts/discourse/components/assigned-topic-list-item.js.es6 @@ -0,0 +1,11 @@ +import computed from "ember-addons/ember-computed-decorators"; +import { ListItemDefaults } from "discourse/components/topic-list-item"; + +const privateMessageHelper = { + @computed("topic.archetype") + isPrivateMessage(archetype) { + return archetype === "private_message"; + } +}; + +export default Ember.Component.extend(ListItemDefaults, privateMessageHelper); diff --git a/assets/javascripts/discourse/components/assigned-topic-list.js.es6 b/assets/javascripts/discourse/components/assigned-topic-list.js.es6 new file mode 100644 index 0000000..0bbb097 --- /dev/null +++ b/assets/javascripts/discourse/components/assigned-topic-list.js.es6 @@ -0,0 +1,3 @@ +import TopicList from "discourse/components/topic-list"; + +export default TopicList; diff --git a/assets/javascripts/discourse/components/basic-assigned-topic-list.js.es6 b/assets/javascripts/discourse/components/basic-assigned-topic-list.js.es6 new file mode 100644 index 0000000..7a21ef3 --- /dev/null +++ b/assets/javascripts/discourse/components/basic-assigned-topic-list.js.es6 @@ -0,0 +1,3 @@ +import BasicTopicList from "discourse/components/basic-topic-list"; + +export default BasicTopicList; diff --git a/assets/javascripts/discourse/services/task-actions.js.es6 b/assets/javascripts/discourse/services/task-actions.js.es6 new file mode 100644 index 0000000..41bf387 --- /dev/null +++ b/assets/javascripts/discourse/services/task-actions.js.es6 @@ -0,0 +1,20 @@ +import { ajax } from "discourse/lib/ajax"; +import showModal from "discourse/lib/show-modal"; + +export default Ember.Service.extend({ + unassign(topicId) { + return ajax("/assign/unassign", { + type: "PUT", + data: { topic_id: topicId } + }); + }, + + assign(topic) { + return showModal("assign-user", { + model: { + topic: topic, + username: topic.get("assigned_to_user.username") + } + }); + } +}); diff --git a/assets/javascripts/discourse/templates/components/assigned-topic-list-item.hbs b/assets/javascripts/discourse/templates/components/assigned-topic-list-item.hbs new file mode 100644 index 0000000..97dfd4f --- /dev/null +++ b/assets/javascripts/discourse/templates/components/assigned-topic-list-item.hbs @@ -0,0 +1,50 @@ +{{!-- + The `~` syntax strip spaces between the elements, making it produce + `Some text`, + with no space between them. + This causes the topic-post-badge to be considered the same word as "text" + at the end of the link, preventing it from line wrapping onto its own line. +--}} + + + {{~raw "topic-status" topic=topic}} + {{~#if isPrivateMessage}} + {{~d-icon "envelope" class="private-message-icon"}} + {{~/if}} + {{~topic-link topic class="raw-link raw-topic-link"}} + {{~#if topic.featured_link}} + {{~topic-featured-link topic}} + {{~/if}} + {{~#if showTopicPostBadges}} + {{~raw "topic-post-badges" unread=topic.unread newPosts=topic.displayNewPosts unseen=topic.unseen url=topic.lastUnreadUrl newDotText=newDotText}} + {{~/if}} + + + {{#if expandPinned}} + {{raw "list/topic-excerpt" topic=topic}} + {{/if}} + + +{{#if showPosters}} + {{raw "list/posters-column" posters=topic.featuredUsers}} +{{/if}} + +{{raw "list/posts-count-column" topic=topic}} + +{{number topic.views numberKey="views_long"}} +{{raw "list/activity-column" topic=topic class="num" tagName="td"}} + + + {{assign-actions-dropdown topic=topic + user=username + unassign=unassign + reassign=reassign}} + diff --git a/assets/javascripts/discourse/templates/components/assigned-topic-list.hbs b/assets/javascripts/discourse/templates/components/assigned-topic-list.hbs new file mode 100644 index 0000000..0ab572b --- /dev/null +++ b/assets/javascripts/discourse/templates/components/assigned-topic-list.hbs @@ -0,0 +1,34 @@ +{{#unless skipHeader}} + + {{raw "topic-list-header" + toggleInTitle=toggleInTitle + hideCategory=hideCategory + showPosters=showPosters + showLikes=showLikes + showOpLikes=showOpLikes + order=order + ascending=ascending + sortable=sortable + listTitle=listTitle + bulkSelectEnabled=bulkSelectEnabled}} + +{{/unless}} + + + {{#each filteredTopics as |topic|}} + {{assigned-topic-list-item topic=topic + showTopicPostBadges=showTopicPostBadges + hideCategory=hideCategory + showPosters=showPosters + showLikes=showLikes + showOpLikes=showOpLikes + expandGloballyPinned=expandGloballyPinned + expandAllPinned=expandAllPinned + lastVisitedTopic=lastVisitedTopic + selected=selected + tagsForUser=tagsForUser + unassign=unassign + reassign=reassign}} + {{raw "list/visited-line" lastVisitedTopic=lastVisitedTopic topic=topic}} + {{/each}} + diff --git a/assets/javascripts/discourse/templates/components/basic-assigned-topic-list.hbs b/assets/javascripts/discourse/templates/components/basic-assigned-topic-list.hbs new file mode 100644 index 0000000..d1a881f --- /dev/null +++ b/assets/javascripts/discourse/templates/components/basic-assigned-topic-list.hbs @@ -0,0 +1,29 @@ +{{#conditional-loading-spinner condition=loading}} + {{#if hasIncoming}} +
+ +
+ {{/if}} + + {{#if topics}} + {{assigned-topic-list showPosters=showPosters + hideCategory=hideCategory + topics=topics + expandExcerpts=expandExcerpts + selected=selected + skipHeader=skipHeader + tagsForUser=tagsForUser + unassign=unassign + reassign=reassign}} + {{else}} + {{#unless loadingMore}} +
+ {{i18n 'choose_topic.none_found'}} +
+ {{/unless}} + {{/if}} +{{/conditional-loading-spinner}} \ No newline at end of file diff --git a/assets/javascripts/discourse/templates/user-activity-assigned.hbs b/assets/javascripts/discourse/templates/user-activity-assigned.hbs index 5f6be7d..8728c39 100644 --- a/assets/javascripts/discourse/templates/user-activity-assigned.hbs +++ b/assets/javascripts/discourse/templates/user-activity-assigned.hbs @@ -1,11 +1,4 @@
- {{#if model.topic_list.assigned_messages_count}} - {{#link-to 'userPrivateMessages.assigned' user.model class="btn btn-default assign-messages-assigned"}} - {{d-icon "envelope" class="glyph"}} - {{i18n 'user.messages.assigned_title' count=model.topic_list.assigned_messages_count}} - {{/link-to}} - {{/if}} - {{#if canUnassignAll}} {{d-button action=(action "unassignAll") diff --git a/assets/javascripts/discourse/templates/user-assigned-topics.hbs b/assets/javascripts/discourse/templates/user-assigned-topics.hbs new file mode 100644 index 0000000..fc9b17d --- /dev/null +++ b/assets/javascripts/discourse/templates/user-assigned-topics.hbs @@ -0,0 +1,17 @@ +{{#load-more class="paginated-topics-list" selector=".paginated-topics-list .topic-list tr" action=(action "loadMore")}} + {{basic-assigned-topic-list topicList=model + hideCategory=hideCategory + showPosters=showPosters + bulkSelectEnabled=bulkSelectEnabled + selected=selected + hasIncoming=hasIncoming + incomingCount=incomingCount + showInserted=(action "showInserted") + tagsForUser=tagsForUser + unassign=(action 'unassign') + reassign=(action 'reassign')}} + + {{conditional-loading-spinner condition=model.loadingMore}} +{{/load-more}} + + diff --git a/assets/stylesheets/assigns.scss b/assets/stylesheets/assigns.scss index 9b756fc..262194d 100644 --- a/assets/stylesheets/assigns.scss +++ b/assets/stylesheets/assigns.scss @@ -74,3 +74,7 @@ cursor: pointer; } } + +.private-message-icon { + margin-right: 2px; +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 633c349..e7a6e11 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -18,6 +18,9 @@ en: assign: title: "Assign" help: "Assign Topic to User" + reassign: + title: "Re-assign" + help: "Re-assign Topic to a different user" assign_modal: title: "Assign Topic" description: "Enter the username of the person you'd like to assign this topic" diff --git a/plugin.rb b/plugin.rb index fbcf71a..238b2e5 100644 --- a/plugin.rb +++ b/plugin.rb @@ -18,6 +18,7 @@ load File.expand_path('../lib/discourse_assign/helpers.rb', __FILE__) Discourse::Application.routes.append do mount ::DiscourseAssign::Engine, at: "/assign" get "topics/private-messages-assigned/:username" => "list#private_messages_assigned", as: "topics_private_messages_assigned", constraints: { username: /[\w.\-]+?/ } + get "topics/messages-assigned/:username" => "list#messages_assigned", as: "topics_messages_assigned", constraints: { username: /[\w.\-]+?/ } end after_initialize do @@ -139,6 +140,17 @@ after_initialize do require_dependency 'list_controller' class ::ListController generate_message_route(:private_messages_assigned) + generate_message_route(:messages_assigned) + end + + add_to_class(:topic_query, :list_messages_assigned) do |user| + list = joined_topic_user.where(" + topics.id IN ( + SELECT topic_id FROM topic_custom_fields + WHERE name = 'assigned_to_id' + AND value = ?) + ", user.id.to_s) + create_list(:assigned, {}, list) end add_to_class(:topic_query, :list_private_messages_assigned) do |user| diff --git a/spec/components/topic_query_spec.rb b/spec/components/topic_query_spec.rb index 8d1a163..f5f5402 100644 --- a/spec/components/topic_query_spec.rb +++ b/spec/components/topic_query_spec.rb @@ -1,10 +1,36 @@ require 'rails_helper' describe TopicQuery do - describe '#list_private_messages_assigned' do - let(:user) { Fabricate(:user) } - let(:user2) { Fabricate(:user) } + before do + SiteSetting.assign_enabled = true + end + let(:user) { Fabricate(:user) } + let(:user2) { Fabricate(:user) } + + describe '#list_messages_assigned' do + before do + @private_message = Fabricate(:private_message_topic, user: user) + @topic = Fabricate(:topic, user: user) + + assign_to(@private_message, user) + assign_to(@topic, user) + end + + it 'Includes topics and PMs assigned to user' do + assigned_messages = TopicQuery.new(user).list_messages_assigned(user).topics + + expect(assigned_messages).to contain_exactly(@private_message, @topic) + end + + it 'Excludes topics and PMs not assigned to user' do + assigned_messages = TopicQuery.new(user2).list_messages_assigned(user2).topics + + expect(assigned_messages).to be_empty + end + end + + describe '#list_private_messages_assigned' do let(:user_topic) do topic = Fabricate(:private_message_topic, topic_allowed_users: [ @@ -24,10 +50,7 @@ describe TopicQuery do Fabricate.build(:topic_allowed_user, user: user2) ], ) - - topic.posts << Fabricate(:post) - TopicAssigner.new(topic, user).assign(user) - topic + assign_to(topic, user) end let(:group) { Fabricate(:group).add(user) } @@ -42,13 +65,10 @@ describe TopicQuery do ], ) - topic.posts << Fabricate(:post) - TopicAssigner.new(topic, user).assign(user) - topic + assign_to(topic, user) end before do - SiteSetting.assign_enabled = true user_topic assigned_topic group_assigned_topic @@ -78,4 +98,11 @@ describe TopicQuery do ).to contain_exactly(assigned_topic, group_assigned_topic) end end + + def assign_to(topic, user) + topic.tap do |t| + t.posts << Fabricate(:post) + TopicAssigner.new(t, user).assign(user) + end + end end