FEATURE: add the first 3 participants in a private message

This commit is contained in:
Régis Hanol 2014-05-12 09:32:49 +02:00
parent 557dc76c07
commit 9125453628
15 changed files with 166 additions and 52 deletions

View file

@ -8,6 +8,7 @@
**/
Discourse.UserTopicsListController = Discourse.ObjectController.extend({
hideCategory: false,
showParticipants: false,
actions: {
loadMore: function() {

View file

@ -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);
});
},

View file

@ -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

View file

@ -3,26 +3,18 @@
<table id="topic-list">
<thead>
<tr>
{{#sortable-heading sortBy="default"}}
{{i18n topic.title}}
{{/sortable-heading}}
<th>{{i18n topic.title}}</th>
{{#unless controller.hideCategory}}
{{#sortable-heading sortBy="category"}}
{{i18n category_title}}
{{/sortable-heading}}
<th>{{i18n category_title}}</th>
{{/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}}
<th>{{i18n posts}}</th>
{{#if controller.showParticipants}}
<th>{{i18n users}}</th>
{{else}}
<th>{{i18n likes}}</th>
{{/if}}
<th>{{i18n views}}</th>
<th>{{i18n activity}}</th>
</tr>
</thead>
@ -49,15 +41,28 @@
</td>
{{/unless}}
<td class='num posts'><a href="{{unbound topic.lastUnreadUrl}}" class='badge-posts'>{{number topic.posts_count numberKey="posts_long"}}</a></td>
<td class='num posts'>
<a href="{{unbound topic.lastUnreadUrl}}" class='badge-posts'>{{number topic.posts_count numberKey="posts_long"}}</a>
</td>
{{#if controller.showParticipants}}
<td class='participants'>
{{#each topic.participants}}
<a href="{{user.path}}" class="{{extras}}">{{avatar this usernamePath="user.username" imageSize="small"}}</a>
{{/each}}
</td>
{{else}}
<td class='num likes'>
{{#if topic.like_count}}
<a href='{{unbound topic.url}}{{#if topic.has_summary}}?filter=summary{{/if}}'>{{unbound topic.like_count}} <i class='fa fa-heart'></i></a>
{{/if}}
</td>
{{/if}}
<td {{bind-attr class=":num :views topic.viewsHeat"}}>
{{number topic.views numberKey="views_long"}}
</td>
<td {{bind-attr class=":num :views topic.viewsHeat"}}>{{number topic.views numberKey="views_long"}}</td>
{{#if topic.bumped}}
<td class='num activity'>
<a href="{{unbound topic.url}}" class='{{coldAgeClass created_at}}' title='{{i18n first_post}}: {{{rawDate topic.created_at}}}' >{{unboundAge topic.created_at}}</a>
@ -82,5 +87,7 @@
</div>
{{/if}}
{{else}}
<div class='spinner'>{{i18n loading}}</div>
<div class='spinner'>
{{i18n loading}}
</div>
{{/if}}

View file

@ -1 +1 @@
{{basic-topic-list topicList=model hideCategory=hideCategory}}
{{basic-topic-list topicList=model hideCategory=hideCategory showParticipants=showParticipants}}

View file

@ -29,9 +29,6 @@
{{/if}}
</div>
<div class="topic-item-stats clearfix">
<div class='category'>
{{categoryLink category showParent=true}}
</div>
<div class="pull-right">
<div class='num posts'>
<a href="{{lastUnreadUrl}}">{{number posts_count numberKey="posts_long"}}</a>
@ -53,6 +50,18 @@
</div>
{{/if}}
</div>
{{#unless controller.hideCategory}}
<div class='category'>
{{categoryLink category showParent=true}}
</div>
{{/unless}}
{{#if controller.showParticipants}}
<div class='participants'>
{{#each topic.participants}}
<a href="{{user.path}}" class="{{extras}}">{{avatar this usernamePath="user.username" imageSize="small"}}</a>
{{/each}}
</div>
{{/if}}
<div class="clearfix"></div>
</div>
</td>

View file

@ -183,6 +183,11 @@
}
.posters {
min-width: 150px;
}
.participants {
min-width: 85px;
}
.posters, .participants {
> a {
float: left;
margin-right: 4px;

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -27,12 +27,9 @@ class AvatarLookup
def user_lookup_hash
# adding tap here is a personal taste thing
hash = {}
User
.where(:id => @user_ids)
User.where(:id => @user_ids)
.select(AvatarLookup.lookup_columns)
.each{|user|
hash[user.id] = user
}
.each{ |user| hash[user.id] = user }
hash
end
end

View file

@ -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

View file

@ -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