mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-23 23:58:31 -05:00
FEATURE: add the first 3 participants in a private message
This commit is contained in:
parent
557dc76c07
commit
9125453628
15 changed files with 166 additions and 52 deletions
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{{basic-topic-list topicList=model hideCategory=hideCategory}}
|
{{basic-topic-list topicList=model hideCategory=hideCategory showParticipants=showParticipants}}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
37
app/models/topic_participants_summary.rb
Normal file
37
app/models/topic_participants_summary.rb
Normal 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
|
|
@ -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?
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
26
spec/models/topic_participants_summary_spec.rb
Normal file
26
spec/models/topic_participants_summary_spec.rb
Normal 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
|
Loading…
Reference in a new issue