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({ Discourse.UserTopicsListController = Discourse.ObjectController.extend({
hideCategory: false, hideCategory: false,
showParticipants: false,
actions: { actions: {
loadMore: function() { loadMore: function() {

View file

@ -163,6 +163,11 @@ Discourse.TopicList.reopenClass({
t.posters.forEach(function(p) { t.posters.forEach(function(p) {
p.user = users[p.user_id]; 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); 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_activity').set('userActionType', this.get('userActionType'));
this.controllerFor('user_topics_list').setProperties({ this.controllerFor('user_topics_list').setProperties({
model: model, model: model,
hideCategory: false hideCategory: false,
showParticipants: false
}); });
} }
}); });
@ -23,7 +24,10 @@ function createPMRoute(viewName, path) {
setupController: function() { setupController: function() {
this._super.apply(this, arguments); 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({ this.controllerFor('user').setProperties({
pmView: viewName, pmView: viewName,
indexStream: false indexStream: false

View file

@ -3,26 +3,18 @@
<table id="topic-list"> <table id="topic-list">
<thead> <thead>
<tr> <tr>
{{#sortable-heading sortBy="default"}} <th>{{i18n topic.title}}</th>
{{i18n topic.title}}
{{/sortable-heading}}
{{#unless controller.hideCategory}} {{#unless controller.hideCategory}}
{{#sortable-heading sortBy="category"}} <th>{{i18n category_title}}</th>
{{i18n category_title}}
{{/sortable-heading}}
{{/unless}} {{/unless}}
{{#sortable-heading sortBy="posts" number=true}} <th>{{i18n posts}}</th>
{{i18n posts}} {{#if controller.showParticipants}}
{{/sortable-heading}} <th>{{i18n users}}</th>
{{#sortable-heading sortBy="likes" number=true}} {{else}}
{{i18n likes}} <th>{{i18n likes}}</th>
{{/sortable-heading}} {{/if}}
{{#sortable-heading sortBy="views" number=true}} <th>{{i18n views}}</th>
{{i18n views}} <th>{{i18n activity}}</th>
{{/sortable-heading}}
{{#sortable-heading sortBy="activity" number=true colspan="2"}}
{{i18n activity}}
{{/sortable-heading}}
</tr> </tr>
</thead> </thead>
@ -44,20 +36,33 @@
</td> </td>
{{#unless controller.hideCategory}} {{#unless controller.hideCategory}}
<td class="category"> <td class="category">
{{categoryLink topic.category showParent=true}} {{categoryLink topic.category showParent=true}}
</td> </td>
{{/unless}} {{/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 class='num likes'> </td>
{{#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 controller.showParticipants}}
{{/if}} <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>
<td {{bind-attr class=":num :views topic.viewsHeat"}}>{{number topic.views numberKey="views_long"}}</td>
{{#if topic.bumped}} {{#if topic.bumped}}
<td class='num activity'> <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> <a href="{{unbound topic.url}}" class='{{coldAgeClass created_at}}' title='{{i18n first_post}}: {{{rawDate topic.created_at}}}' >{{unboundAge topic.created_at}}</a>
@ -78,9 +83,11 @@
</table> </table>
{{else}} {{else}}
<div class='alert alert-info'> <div class='alert alert-info'>
{{i18n choose_topic.none_found}} {{i18n choose_topic.none_found}}
</div> </div>
{{/if}} {{/if}}
{{else}} {{else}}
<div class='spinner'>{{i18n loading}}</div> <div class='spinner'>
{{i18n loading}}
</div>
{{/if}} {{/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}} {{/if}}
</div> </div>
<div class="topic-item-stats clearfix"> <div class="topic-item-stats clearfix">
<div class='category'>
{{categoryLink category showParent=true}}
</div>
<div class="pull-right"> <div class="pull-right">
<div class='num posts'> <div class='num posts'>
<a href="{{lastUnreadUrl}}">{{number posts_count numberKey="posts_long"}}</a> <a href="{{lastUnreadUrl}}">{{number posts_count numberKey="posts_long"}}</a>
@ -53,6 +50,18 @@
</div> </div>
{{/if}} {{/if}}
</div> </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 class="clearfix"></div>
</div> </div>
</td> </td>

View file

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

View file

@ -22,6 +22,8 @@ class Topic < ActiveRecord::Base
def_delegator :notifier, :muted!, :notify_muted! def_delegator :notifier, :muted!, :notify_muted!
def_delegator :notifier, :toggle_mute, :toggle_mute def_delegator :notifier, :toggle_mute, :toggle_mute
attr_accessor :allowed_user_ids
def self.max_sort_order def self.max_sort_order
2**31 - 1 2**31 - 1
end end
@ -103,6 +105,7 @@ class Topic < ActiveRecord::Base
# When we want to temporarily attach some data to a forum topic (usually before serialization) # When we want to temporarily attach some data to a forum topic (usually before serialization)
attr_accessor :user_data attr_accessor :user_data
attr_accessor :posters # TODO: can replace with posters_summary once we remove old list code attr_accessor :posters # TODO: can replace with posters_summary once we remove old list code
attr_accessor :participants
attr_accessor :topic_list attr_accessor :topic_list
attr_accessor :meta_data attr_accessor :meta_data
attr_accessor :include_last_poster attr_accessor :include_last_poster
@ -594,11 +597,14 @@ class Topic < ActiveRecord::Base
end end
end end
def posters_summary(options = {}) def posters_summary(options = {})
@posters_summary ||= TopicPostersSummary.new(self, options).summary @posters_summary ||= TopicPostersSummary.new(self, options).summary
end end
def participants_summary(options = {})
@participants_summary ||= TopicParticipantsSummary.new(self, options).summary
end
# Enable/disable the star on the topic # Enable/disable the star on the topic
def toggle_star(user, starred) def toggle_star(user, starred)
Topic.transaction do Topic.transaction do

View file

@ -20,6 +20,11 @@ class TopicList
def topics def topics
return @topics if @topics.present? 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 @topics = @topics_input.to_a
# Attach some data for serialization to each topic # Attach some data for serialization to each topic
@ -28,7 +33,7 @@ class TopicList
# Create a lookup for all the user ids we need # Create a lookup for all the user ids we need
user_ids = [] user_ids = []
@topics.each do |ft| @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 end
avatar_lookup = AvatarLookup.new(user_ids) avatar_lookup = AvatarLookup.new(user_ids)
@ -36,10 +41,11 @@ class TopicList
@topics.each do |ft| @topics.each do |ft|
ft.user_data = @topic_lookup[ft.id] if @topic_lookup.present? ft.user_data = @topic_lookup[ft.id] if @topic_lookup.present?
ft.posters = ft.posters_summary(avatar_lookup: avatar_lookup) 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 ft.topic_list = self
end end
return @topics @topics
end end
def topic_ids 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

@ -49,7 +49,7 @@ class TopicPostersSummary
:frequent_poster, :frequent_poster,
:frequent_poster, :frequent_poster,
:frequent_poster :frequent_poster
].map { |description| I18n.t(description) }) ].map { |description| I18n.t(description) })
end end
def last_poster_is_topic_creator? def last_poster_is_topic_creator?

View file

@ -9,10 +9,12 @@ class TopicListItemSerializer < ListableTopicSerializer
:category_id :category_id
has_many :posters, serializer: TopicPosterSerializer, embed: :objects has_many :posters, serializer: TopicPosterSerializer, embed: :objects
has_many :participants, serializer: TopicPosterSerializer, embed: :objects
def starred def starred
object.user_data.starred? object.user_data.starred?
end end
alias :include_starred? :has_user_data alias :include_starred? :has_user_data
def posters 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) object.posters.find { |poster| poster.user.id == object.last_post_user_id }.try(:user).try(:username)
end end
def participants
object.participants_summary || []
end
def include_participants?
object.private_message?
end
end end

View file

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

View file

@ -159,7 +159,8 @@ class TopicQuery
options.reverse_merge!(per_page: SiteSetting.topics_per_page) options.reverse_merge!(per_page: SiteSetting.topics_per_page)
# Start with a list of all topics # 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})") .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) .order(TopicQuerySQL.order_nocategory_basic_bumped)
.private_messages .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