FEATURE: add the first 3 participants in a private message
This commit is contained in:
parent
557dc76c07
commit
9125453628
|
@ -8,6 +8,7 @@
|
|||
**/
|
||||
Discourse.UserTopicsListController = Discourse.ObjectController.extend({
|
||||
hideCategory: false,
|
||||
showParticipants: false,
|
||||
|
||||
actions: {
|
||||
loadMore: function() {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
@ -44,20 +36,33 @@
|
|||
</td>
|
||||
|
||||
{{#unless controller.hideCategory}}
|
||||
<td class="category">
|
||||
{{categoryLink topic.category showParent=true}}
|
||||
</td>
|
||||
<td class="category">
|
||||
{{categoryLink topic.category showParent=true}}
|
||||
</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 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 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>
|
||||
|
@ -78,9 +83,11 @@
|
|||
</table>
|
||||
{{else}}
|
||||
<div class='alert alert-info'>
|
||||
{{i18n choose_topic.none_found}}
|
||||
{{i18n choose_topic.none_found}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<div class='spinner'>{{i18n loading}}</div>
|
||||
<div class='spinner'>
|
||||
{{i18n loading}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{{basic-topic-list topicList=model hideCategory=hideCategory}}
|
||||
{{basic-topic-list topicList=model hideCategory=hideCategory showParticipants=showParticipants}}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -183,6 +183,11 @@
|
|||
}
|
||||
.posters {
|
||||
min-width: 150px;
|
||||
}
|
||||
.participants {
|
||||
min-width: 85px;
|
||||
}
|
||||
.posters, .participants {
|
||||
> a {
|
||||
float: left;
|
||||
margin-right: 4px;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue