FEATURE: show group mentions and topics in groups page

This commit is contained in:
Sam
2015-12-01 16:52:43 +11:00
parent 0cffbf87c3
commit d1a5d8ea62
25 changed files with 202 additions and 52 deletions

View File

@ -1,9 +1,49 @@
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
var Tab = Em.Object.extend({
@computed('name')
location(name) {
return 'group.' + name;
}
});
export default Ember.Controller.extend({ export default Ember.Controller.extend({
counts: null, counts: null,
showing: null, showing: 'posts',
// It would be nice if bootstrap marked action lists as selected when their links @observes('counts')
// were 'active' not the `li` tags. countsChanged() {
showingIndex: Em.computed.equal('showing', 'index'), const counts = this.get('counts');
showingMembers: Em.computed.equal('showing', 'members') this.get('tabs').forEach(tab => {
tab.set('count', counts.get(tab.get('name')));
});
},
@observes('showing')
showingChanged() {
const showing = this.get('showing');
this.get('tabs').forEach(tab => {
tab.set('active', showing === tab.get('name'));
});
},
tabs: [
Tab.create({
active: true,
name: 'posts',
'location': 'group.index'
}),
Tab.create({
name: 'topics'
}),
Tab.create({
name: 'mentions'
}),
Tab.create({
name: 'members'
})
]
}); });

View File

@ -1,10 +1,5 @@
/** /**
Handles displaying posts within a group Handles displaying posts within a group
@class GroupIndexController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/ **/
export default Ember.ArrayController.extend({ export default Ember.ArrayController.extend({
needs: ['group'], needs: ['group'],
@ -21,7 +16,8 @@ export default Ember.ArrayController.extend({
var lastPostId = posts[posts.length-1].get('id'), var lastPostId = posts[posts.length-1].get('id'),
group = this.get('controllers.group.model'); group = this.get('controllers.group.model');
group.findPosts({beforePostId: lastPostId}).then(function(newPosts) { var opts = {beforePostId: lastPostId, type: this.get('type')};
group.findPosts(opts).then(function(newPosts) {
posts.addObjects(newPosts); posts.addObjects(newPosts);
self.set('loading', false); self.set('loading', false);
}); });

View File

@ -0,0 +1,3 @@
import IndexController from 'discourse/controllers/group/index';
export default IndexController.extend({type: 'mentions'});

View File

@ -0,0 +1,3 @@
import IndexController from 'discourse/controllers/group/index';
export default IndexController.extend({type: 'topics'});

View File

@ -121,10 +121,12 @@ const Group = Discourse.Model.extend({
findPosts(opts) { findPosts(opts) {
opts = opts || {}; opts = opts || {};
const type = opts['type'] || 'posts';
var data = {}; var data = {};
if (opts.beforePostId) { data.before_post_id = opts.beforePostId; } if (opts.beforePostId) { data.before_post_id = opts.beforePostId; }
return Discourse.ajax("/groups/" + this.get('name') + "/posts.json", { data: data }).then(function (posts) { return Discourse.ajax(`/groups/${this.get('name')}/${type}.json`, { data: data }).then(function (posts) {
return posts.map(function (p) { return posts.map(function (p) {
p.user = Discourse.User.create(p.user); p.user = Discourse.User.create(p.user);
return Em.Object.create(p); return Em.Object.create(p);

View File

@ -48,6 +48,8 @@ export default function() {
}); });
this.resource('group', { path: '/groups/:name' }, function() { this.resource('group', { path: '/groups/:name' }, function() {
this.route('topics');
this.route('mentions');
this.route('members'); this.route('members');
}); });

View File

@ -9,6 +9,6 @@ export default Discourse.Route.extend({
setupController(controller, model) { setupController(controller, model) {
controller.set("model", model); controller.set("model", model);
this.controllerFor("group").set("showing", "index"); this.controllerFor("group").set("showing", "posts");
} }
}); });

View File

@ -0,0 +1,11 @@
export default Discourse.Route.extend({
model() {
return this.modelFor("group").findPosts({type: 'mentions'});
},
setupController(controller, model) {
controller.set("model", model);
this.controllerFor("group").set("showing", "mentions");
}
});

View File

@ -0,0 +1,11 @@
export default Discourse.Route.extend({
model() {
return this.modelFor("group").findPosts({type: 'topics'});
},
setupController(controller, model) {
controller.set("model", model);
this.controllerFor("group").set("showing", "topics");
}
});

View File

@ -0,0 +1,18 @@
<div class='item'>
<div class='clearfix info'>
<a href="{{unbound post.user.userUrl}}" data-user-card="{{unbound post.user.username}}" class='avatar-link'><div class='avatar-wrapper'>{{avatar post.user imageSize="large" extraClasses="actor" ignoreTitle="true"}}</div></a>
<span class='time'>{{format-date post.created_at leaveAgo="true"}}</span>
<span class="title">
<a href="{{unbound post.url}}">{{unbound post.title}}</a>
</span>
<span class="category">{{category-link post.category}}</span>
<div class="group-member-info">
{{#if post.user_long_name}}
<span class="name">{{post.user_long_name}}</span>{{#if post.user_title}}<span class="title">, {{post.user_title}}</span>{{/if}}
{{/if}}
</div>
</div>
<p class='excerpt'>
{{{unbound post.cooked}}}
</p>
</div>

View File

@ -1,28 +1,26 @@
<div class="container"> <div class="container">
<section class='user-navigation'> <section class='user-navigation'>
<ul class='action-list nav-stacked'> <ul class='action-list nav-stacked'>
<li {{bind-attr class="showingIndex:active"}}> {{#each tabs as |tab|}}
{{#link-to 'group.index' model}}{{i18n 'groups.posts'}} <li class="{{if tab.active 'active'}}">
<span class='count'>({{counts.posts}})</span> {{#link-to tab.location model}}{{tab.name}}
{{/link-to}} {{#if tab.count}}
</li> <span class='count'>({{tab.count}})</span>
<li {{bind-attr class="showingMembers:active"}}> {{/if}}
{{#link-to 'group.members' model}}{{i18n 'groups.members'}}
<span class='count'>({{counts.members}})</span>
{{/link-to}} {{/link-to}}
</li> </li>
{{/each}}
</ul> </ul>
</section> </section>
<section class='user-main'> <section class='user-main'>
<section class='user-right groups'>
<section class='user-right groups'> <section class='about group'>
<section class='about group'> <div class='details'>
<div class='details'> <h1>{{model.name}}</h1>
<h1>{{model.name}}</h1> </div>
</div>
</section>
{{outlet}}
</section> </section>
{{outlet}}
</section> </section>
</section>
</div> </div>

View File

@ -1,22 +1,5 @@
<div class='user-stream'> <div class='user-stream'>
{{#each p in controller}} {{#each controller as |post|}}
<div class='item'> {{group-post post=post}}
<div class='clearfix info'>
<a href="{{unbound p.user.userUrl}}" data-user-card="{{unbound p.user.username}}" class='avatar-link'><div class='avatar-wrapper'>{{avatar p.user imageSize="large" extraClasses="actor" ignoreTitle="true"}}</div></a>
<span class='time'>{{format-date p.created_at leaveAgo="true"}}</span>
<span class="title">
<a href="{{unbound p.url}}">{{unbound p.title}}</a>
</span>
<span class="category">{{category-link p.category}}</span>
<div class="group-member-info">
{{#if p.user_long_name}}
<span class="name">{{p.user_long_name}}</span>{{#if p.user_title}}<span class="title">, {{p.user_title}}</span>{{/if}}
{{/if}}
</div>
</div>
<p class='excerpt'>
{{{unbound p.cooked}}}
</p>
</div>
{{/each}} {{/each}}
</div> </div>

View File

@ -0,0 +1,6 @@
<div class='user-stream'>
{{#each controller as |post|}}
{{group-post post=post}}
{{/each}}
</div>

View File

@ -0,0 +1,5 @@
<div class='user-stream'>
{{#each controller as |post|}}
{{group-post post=post}}
{{/each}}
</div>

View File

@ -0,0 +1,6 @@
import ScrollTop from 'discourse/mixins/scroll-top';
import LoadMore from "discourse/mixins/load-more";
export default Ember.View.extend(ScrollTop, LoadMore, {
eyelineSelector: '.user-stream .item',
});

View File

@ -0,0 +1,6 @@
import ScrollTop from 'discourse/mixins/scroll-top';
import LoadMore from "discourse/mixins/load-more";
export default Ember.View.extend(ScrollTop, LoadMore, {
eyelineSelector: '.user-stream .item',
});

View File

@ -7,6 +7,8 @@ class GroupsController < ApplicationController
def counts def counts
group = find_group(:group_id) group = find_group(:group_id)
render json: {counts: { posts: group.posts_for(guardian).count, render json: {counts: { posts: group.posts_for(guardian).count,
topics: group.posts_for(guardian).where(post_number: 1).count,
mentions: group.mentioned_posts_for(guardian).count,
members: group.users.count } } members: group.users.count } }
end end
@ -16,6 +18,19 @@ class GroupsController < ApplicationController
render_serialized posts.to_a, GroupPostSerializer render_serialized posts.to_a, GroupPostSerializer
end end
def topics
group = find_group(:group_id)
posts = group.posts_for(guardian, params[:before_post_id]).where(post_number: 1).limit(20)
render_serialized posts.to_a, GroupPostSerializer
end
def mentions
group = find_group(:group_id)
posts = group.mentioned_posts_for(guardian, params[:before_post_id]).limit(20)
render_serialized posts.to_a, GroupPostSerializer
end
def members def members
group = find_group(:group_id) group = find_group(:group_id)

View File

@ -3,6 +3,7 @@ class Group < ActiveRecord::Base
has_many :category_groups, dependent: :destroy has_many :category_groups, dependent: :destroy
has_many :group_users, dependent: :destroy has_many :group_users, dependent: :destroy
has_many :group_mentions, dependent: :destroy
has_many :categories, through: :category_groups has_many :categories, through: :category_groups
has_many :users, through: :group_users has_many :users, through: :group_users
@ -80,6 +81,18 @@ class Group < ActiveRecord::Base
result.order('posts.created_at desc') result.order('posts.created_at desc')
end end
def mentioned_posts_for(guardian, before_post_id=nil)
result = Post.joins(:group_mentions)
.includes(:user, :topic, :topic => :category)
.references(:posts, :topics, :category)
.where('topics.archetype <> ?', Archetype.private_message)
.where(post_type: Post.types[:regular])
result = guardian.filter_allowed_categories(result)
result = result.where('posts.id < ?', before_post_id) if before_post_id
result.order('posts.created_at desc')
end
def self.trust_group_ids def self.trust_group_ids
(10..19).to_a (10..19).to_a
end end

View File

@ -0,0 +1,4 @@
class GroupMention < ActiveRecord::Base
belongs_to :post
belongs_to :group
end

View File

@ -32,6 +32,7 @@ class Post < ActiveRecord::Base
has_many :replies, through: :post_replies has_many :replies, through: :post_replies
has_many :post_actions has_many :post_actions
has_many :topic_links has_many :topic_links
has_many :group_mentions, dependent: :destroy
has_many :post_uploads has_many :post_uploads
has_many :uploads, through: :post_uploads has_many :uploads, through: :post_uploads

View File

@ -64,6 +64,16 @@ class PostAlerter
notify_post_users(post, notified) notify_post_users(post, notified)
end end
sync_group_mentions(post, mentioned_groups)
end
def sync_group_mentions(post, mentioned_groups)
GroupMention.where(post_id: post.id).destroy_all
return if mentioned_groups.blank?
mentioned_groups.each do |group|
GroupMention.create(post_id: post.id, group_id: group.id)
end
end end
def unread_posts(user, topic) def unread_posts(user, topic)

View File

@ -342,6 +342,8 @@ Discourse::Application.routes.draw do
resources :groups do resources :groups do
get 'members' get 'members'
get 'posts' get 'posts'
get 'topics'
get 'mentions'
get 'counts' get 'counts'
member do member do

View File

@ -0,0 +1,12 @@
class AddGroupMentions < ActiveRecord::Migration
def change
create_table :group_mentions do |t|
t.integer :post_id
t.integer :group_id
t.timestamps
end
add_index :group_mentions, [:post_id, :group_id], unique: true
add_index :group_mentions, [:group_id, :post_id], unique: true
end
end

View File

@ -103,12 +103,15 @@ describe PostAlerter do
create_post_with_alerts(raw: "Hello @group how are you?") create_post_with_alerts(raw: "Hello @group how are you?")
}.to change(evil_trout.notifications, :count).by(1) }.to change(evil_trout.notifications, :count).by(1)
expect(GroupMention.count).to eq(1)
group.update_columns(alias_level: Group::ALIAS_LEVELS[:members_mods_and_admins]) group.update_columns(alias_level: Group::ALIAS_LEVELS[:members_mods_and_admins])
expect { expect {
create_post_with_alerts(raw: "Hello @group you are not mentionable") create_post_with_alerts(raw: "Hello @group you are not mentionable")
}.to change(evil_trout.notifications, :count).by(0) }.to change(evil_trout.notifications, :count).by(0)
expect(GroupMention.count).to eq(2)
end end
end end