diff --git a/app/assets/javascripts/discourse/controllers/user_topics_list_controller.js b/app/assets/javascripts/discourse/controllers/user_topics_list_controller.js index 0d19c9925ba..98be7c7b295 100644 --- a/app/assets/javascripts/discourse/controllers/user_topics_list_controller.js +++ b/app/assets/javascripts/discourse/controllers/user_topics_list_controller.js @@ -8,6 +8,7 @@ **/ Discourse.UserTopicsListController = Discourse.ObjectController.extend({ hideCategory: false, + showParticipants: false, actions: { loadMore: function() { diff --git a/app/assets/javascripts/discourse/models/topic_list.js b/app/assets/javascripts/discourse/models/topic_list.js index dbbaa112f82..5b1647d92aa 100644 --- a/app/assets/javascripts/discourse/models/topic_list.js +++ b/app/assets/javascripts/discourse/models/topic_list.js @@ -163,6 +163,11 @@ Discourse.TopicList.reopenClass({ t.posters.forEach(function(p) { p.user = users[p.user_id]; }); + if (t.participants) { + t.participants.forEach(function(p) { + p.user = users[p.user_id]; + }); + } return Discourse.Topic.create(t); }); }, diff --git a/app/assets/javascripts/discourse/routes/user_topic_list_routes.js b/app/assets/javascripts/discourse/routes/user_topic_list_routes.js index 47c1c8b38e6..0b3a5f1c34b 100644 --- a/app/assets/javascripts/discourse/routes/user_topic_list_routes.js +++ b/app/assets/javascripts/discourse/routes/user_topic_list_routes.js @@ -8,7 +8,8 @@ Discourse.UserTopicListRoute = Discourse.Route.extend({ this.controllerFor('user_activity').set('userActionType', this.get('userActionType')); this.controllerFor('user_topics_list').setProperties({ model: model, - hideCategory: false + hideCategory: false, + showParticipants: false }); } }); @@ -23,7 +24,10 @@ function createPMRoute(viewName, path) { setupController: function() { this._super.apply(this, arguments); - this.controllerFor('user_topics_list').set('hideCategory', true); + this.controllerFor('user_topics_list').setProperties({ + hideCategory: true, + showParticipants: true + }); this.controllerFor('user').setProperties({ pmView: viewName, indexStream: false diff --git a/app/assets/javascripts/discourse/templates/components/basic-topic-list.js.handlebars b/app/assets/javascripts/discourse/templates/components/basic-topic-list.js.handlebars index 9be1e407e8c..55c562edabb 100644 --- a/app/assets/javascripts/discourse/templates/components/basic-topic-list.js.handlebars +++ b/app/assets/javascripts/discourse/templates/components/basic-topic-list.js.handlebars @@ -3,26 +3,18 @@ - {{#sortable-heading sortBy="default"}} - {{i18n topic.title}} - {{/sortable-heading}} + {{#unless controller.hideCategory}} - {{#sortable-heading sortBy="category"}} - {{i18n category_title}} - {{/sortable-heading}} + {{/unless}} - {{#sortable-heading sortBy="posts" number=true}} - {{i18n posts}} - {{/sortable-heading}} - {{#sortable-heading sortBy="likes" number=true}} - {{i18n likes}} - {{/sortable-heading}} - {{#sortable-heading sortBy="views" number=true}} - {{i18n views}} - {{/sortable-heading}} - {{#sortable-heading sortBy="activity" number=true colspan="2"}} - {{i18n activity}} - {{/sortable-heading}} + + {{#if controller.showParticipants}} + + {{else}} + + {{/if}} + + @@ -44,20 +36,33 @@ {{#unless controller.hideCategory}} - + {{/unless}} - - - + + {{#if controller.showParticipants}} + + {{else}} + + {{/if}} + + - {{#if topic.bumped}}
{{i18n topic.title}}{{i18n category_title}}{{i18n posts}}{{i18n users}}{{i18n likes}}{{i18n views}}{{i18n activity}}
- {{categoryLink topic.category showParent=true}} - + {{categoryLink topic.category showParent=true}} + {{number topic.posts_count numberKey="posts_long"}} + {{number topic.posts_count numberKey="posts_long"}} + + {{#each topic.participants}} + {{avatar this usernamePath="user.username" imageSize="small"}} + {{/each}} + + {{number topic.views numberKey="views_long"}} {{number topic.views numberKey="views_long"}} {{unboundAge topic.created_at}} @@ -78,9 +83,11 @@
{{else}}
- {{i18n choose_topic.none_found}} + {{i18n choose_topic.none_found}}
{{/if}} {{else}} -
{{i18n loading}}
+
+ {{i18n loading}} +
{{/if}} diff --git a/app/assets/javascripts/discourse/templates/list/user_topics_list.js.handlebars b/app/assets/javascripts/discourse/templates/list/user_topics_list.js.handlebars index 05b91a64780..44836b7c277 100644 --- a/app/assets/javascripts/discourse/templates/list/user_topics_list.js.handlebars +++ b/app/assets/javascripts/discourse/templates/list/user_topics_list.js.handlebars @@ -1 +1 @@ -{{basic-topic-list topicList=model hideCategory=hideCategory}} +{{basic-topic-list topicList=model hideCategory=hideCategory showParticipants=showParticipants}} diff --git a/app/assets/javascripts/discourse/templates/mobile/components/basic-topic-list.js.handlebars b/app/assets/javascripts/discourse/templates/mobile/components/basic-topic-list.js.handlebars index c1b1acfedb9..c84bedb98cb 100644 --- a/app/assets/javascripts/discourse/templates/mobile/components/basic-topic-list.js.handlebars +++ b/app/assets/javascripts/discourse/templates/mobile/components/basic-topic-list.js.handlebars @@ -29,9 +29,6 @@ {{/if}}
-
- {{categoryLink category showParent=true}} -
{{/if}}
+ {{#unless controller.hideCategory}} +
+ {{categoryLink category showParent=true}} +
+ {{/unless}} + {{#if controller.showParticipants}} +
+ {{#each topic.participants}} + {{avatar this usernamePath="user.username" imageSize="small"}} + {{/each}} +
+ {{/if}}
diff --git a/app/assets/stylesheets/desktop/topic-list.scss b/app/assets/stylesheets/desktop/topic-list.scss index 4ad1ec802cc..2ceb8179dd8 100644 --- a/app/assets/stylesheets/desktop/topic-list.scss +++ b/app/assets/stylesheets/desktop/topic-list.scss @@ -183,6 +183,11 @@ } .posters { min-width: 150px; + } + .participants { + min-width: 85px; + } + .posters, .participants { > a { float: left; margin-right: 4px; diff --git a/app/models/topic.rb b/app/models/topic.rb index fdb723be9bc..7afb405ca4e 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -22,6 +22,8 @@ class Topic < ActiveRecord::Base def_delegator :notifier, :muted!, :notify_muted! def_delegator :notifier, :toggle_mute, :toggle_mute + attr_accessor :allowed_user_ids + def self.max_sort_order 2**31 - 1 end @@ -103,6 +105,7 @@ class Topic < ActiveRecord::Base # When we want to temporarily attach some data to a forum topic (usually before serialization) attr_accessor :user_data attr_accessor :posters # TODO: can replace with posters_summary once we remove old list code + attr_accessor :participants attr_accessor :topic_list attr_accessor :meta_data attr_accessor :include_last_poster @@ -594,11 +597,14 @@ class Topic < ActiveRecord::Base end end - def posters_summary(options = {}) @posters_summary ||= TopicPostersSummary.new(self, options).summary end + def participants_summary(options = {}) + @participants_summary ||= TopicParticipantsSummary.new(self, options).summary + end + # Enable/disable the star on the topic def toggle_star(user, starred) Topic.transaction do diff --git a/app/models/topic_list.rb b/app/models/topic_list.rb index 54d847d3653..92c8e3f6602 100644 --- a/app/models/topic_list.rb +++ b/app/models/topic_list.rb @@ -20,6 +20,11 @@ class TopicList def topics return @topics if @topics.present? + # copy side-loaded data (allowed users) before dumping it with the .to_a + @topics_input.each do |t| + t.allowed_user_ids = t.allowed_users.map { |u| u.id }.to_a + end + @topics = @topics_input.to_a # Attach some data for serialization to each topic @@ -28,7 +33,7 @@ class TopicList # Create a lookup for all the user ids we need user_ids = [] @topics.each do |ft| - user_ids << ft.user_id << ft.last_post_user_id << ft.featured_user_ids + user_ids << ft.user_id << ft.last_post_user_id << ft.featured_user_ids << ft.allowed_user_ids end avatar_lookup = AvatarLookup.new(user_ids) @@ -36,10 +41,11 @@ class TopicList @topics.each do |ft| ft.user_data = @topic_lookup[ft.id] if @topic_lookup.present? ft.posters = ft.posters_summary(avatar_lookup: avatar_lookup) + ft.participants = ft.participants_summary(avatar_lookup: avatar_lookup, user: @current_user) ft.topic_list = self end - return @topics + @topics end def topic_ids diff --git a/app/models/topic_participants_summary.rb b/app/models/topic_participants_summary.rb new file mode 100644 index 00000000000..f72a5fb6cdd --- /dev/null +++ b/app/models/topic_participants_summary.rb @@ -0,0 +1,37 @@ +class TopicParticipantsSummary + attr_reader :topic, :options + + def initialize(topic, options = {}) + @topic = topic + @options = options + @user = options[:user] + end + + def summary + top_participants.compact.map(&method(:new_topic_poster_for)) + end + + def new_topic_poster_for(user) + TopicPoster.new.tap do |topic_poster| + topic_poster.user = user + topic_poster.extras = 'latest' if is_latest_poster?(user) + end + end + + def is_latest_poster?(user) + topic.last_post_user_id == user.id + end + + def top_participants + user_ids.map { |id| avatar_lookup[id] }.compact.uniq.take(3) + end + + def user_ids + return [] unless @user + [topic.user_id] + topic.allowed_user_ids - [@user.id] + end + + def avatar_lookup + @avatar_lookup ||= options[:avatar_lookup] || AvatarLookup.new(user_ids) + end +end diff --git a/app/models/topic_posters_summary.rb b/app/models/topic_posters_summary.rb index 8ab031fd2df..f39c32b1e99 100644 --- a/app/models/topic_posters_summary.rb +++ b/app/models/topic_posters_summary.rb @@ -49,7 +49,7 @@ class TopicPostersSummary :frequent_poster, :frequent_poster, :frequent_poster - ].map { |description| I18n.t(description) }) + ].map { |description| I18n.t(description) }) end def last_poster_is_topic_creator? diff --git a/app/serializers/topic_list_item_serializer.rb b/app/serializers/topic_list_item_serializer.rb index 96e00be81a6..e0bfd1a697e 100644 --- a/app/serializers/topic_list_item_serializer.rb +++ b/app/serializers/topic_list_item_serializer.rb @@ -9,10 +9,12 @@ class TopicListItemSerializer < ListableTopicSerializer :category_id has_many :posters, serializer: TopicPosterSerializer, embed: :objects + has_many :participants, serializer: TopicPosterSerializer, embed: :objects def starred object.user_data.starred? end + alias :include_starred? :has_user_data def posters @@ -23,4 +25,12 @@ class TopicListItemSerializer < ListableTopicSerializer object.posters.find { |poster| poster.user.id == object.last_post_user_id }.try(:user).try(:username) end + def participants + object.participants_summary || [] + end + + def include_participants? + object.private_message? + end + end diff --git a/lib/avatar_lookup.rb b/lib/avatar_lookup.rb index aef3196922a..147c4fe06df 100644 --- a/lib/avatar_lookup.rb +++ b/lib/avatar_lookup.rb @@ -13,11 +13,11 @@ class AvatarLookup def self.lookup_columns @lookup_columns ||= [:id, - :email, - :username, - :use_uploaded_avatar, - :uploaded_avatar_template, - :uploaded_avatar_id] + :email, + :username, + :use_uploaded_avatar, + :uploaded_avatar_template, + :uploaded_avatar_id] end def users @@ -27,12 +27,9 @@ class AvatarLookup def user_lookup_hash # adding tap here is a personal taste thing hash = {} - User - .where(:id => @user_ids) - .select(AvatarLookup.lookup_columns) - .each{|user| - hash[user.id] = user - } + User.where(:id => @user_ids) + .select(AvatarLookup.lookup_columns) + .each{ |user| hash[user.id] = user } hash end end diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 6a14e37ee01..318c53d6ab9 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -159,7 +159,8 @@ class TopicQuery options.reverse_merge!(per_page: SiteSetting.topics_per_page) # Start with a list of all topics - result = Topic.where("topics.id IN (SELECT topic_id FROM topic_allowed_users WHERE user_id = #{user.id.to_i})") + result = Topic.includes(:allowed_users) + .where("topics.id IN (SELECT topic_id FROM topic_allowed_users WHERE user_id = #{user.id.to_i})") .joins("LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{user.id.to_i})") .order(TopicQuerySQL.order_nocategory_basic_bumped) .private_messages diff --git a/spec/models/topic_participants_summary_spec.rb b/spec/models/topic_participants_summary_spec.rb new file mode 100644 index 00000000000..58863e61421 --- /dev/null +++ b/spec/models/topic_participants_summary_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe TopicParticipantsSummary do + describe '#summary' do + let(:summary) { described_class.new(topic, user: topic_creator).summary } + + let(:topic) do + Fabricate(:topic, + user: topic_creator, + archetype: Archetype::private_message + ) + end + + let(:topic_creator) { Fabricate(:user) } + let(:user1) { Fabricate(:user) } + let(:user2) { Fabricate(:user) } + let(:user3) { Fabricate(:user) } + let(:user4) { Fabricate(:user) } + + it "must never contains the user and at most 3 participants" do + topic.allowed_user_ids = [user1.id, user2.id, user3.id, user4.id] + expect(summary.map(&:user)).to eq([user1, user2, user3]) + end + + end +end