FEATURE: show group mentions and topics in groups page
This commit is contained in:
parent
0cffbf87c3
commit
d1a5d8ea62
|
@ -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({
|
||||
counts: null,
|
||||
showing: null,
|
||||
showing: 'posts',
|
||||
|
||||
// It would be nice if bootstrap marked action lists as selected when their links
|
||||
// were 'active' not the `li` tags.
|
||||
showingIndex: Em.computed.equal('showing', 'index'),
|
||||
showingMembers: Em.computed.equal('showing', 'members')
|
||||
@observes('counts')
|
||||
countsChanged() {
|
||||
const counts = this.get('counts');
|
||||
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'
|
||||
})
|
||||
]
|
||||
});
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
/**
|
||||
Handles displaying posts within a group
|
||||
|
||||
@class GroupIndexController
|
||||
@extends Ember.ArrayController
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
export default Ember.ArrayController.extend({
|
||||
needs: ['group'],
|
||||
|
@ -21,7 +16,8 @@ export default Ember.ArrayController.extend({
|
|||
var lastPostId = posts[posts.length-1].get('id'),
|
||||
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);
|
||||
self.set('loading', false);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import IndexController from 'discourse/controllers/group/index';
|
||||
|
||||
export default IndexController.extend({type: 'mentions'});
|
|
@ -0,0 +1,3 @@
|
|||
import IndexController from 'discourse/controllers/group/index';
|
||||
|
||||
export default IndexController.extend({type: 'topics'});
|
|
@ -121,10 +121,12 @@ const Group = Discourse.Model.extend({
|
|||
findPosts(opts) {
|
||||
opts = opts || {};
|
||||
|
||||
const type = opts['type'] || 'posts';
|
||||
|
||||
var data = {};
|
||||
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) {
|
||||
p.user = Discourse.User.create(p.user);
|
||||
return Em.Object.create(p);
|
||||
|
|
|
@ -48,6 +48,8 @@ export default function() {
|
|||
});
|
||||
|
||||
this.resource('group', { path: '/groups/:name' }, function() {
|
||||
this.route('topics');
|
||||
this.route('mentions');
|
||||
this.route('members');
|
||||
});
|
||||
|
||||
|
|
|
@ -9,6 +9,6 @@ export default Discourse.Route.extend({
|
|||
|
||||
setupController(controller, model) {
|
||||
controller.set("model", model);
|
||||
this.controllerFor("group").set("showing", "index");
|
||||
this.controllerFor("group").set("showing", "posts");
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
});
|
|
@ -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");
|
||||
}
|
||||
});
|
|
@ -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>
|
|
@ -1,28 +1,26 @@
|
|||
<div class="container">
|
||||
<section class='user-navigation'>
|
||||
<ul class='action-list nav-stacked'>
|
||||
<li {{bind-attr class="showingIndex:active"}}>
|
||||
{{#link-to 'group.index' model}}{{i18n 'groups.posts'}}
|
||||
<span class='count'>({{counts.posts}})</span>
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li {{bind-attr class="showingMembers:active"}}>
|
||||
{{#link-to 'group.members' model}}{{i18n 'groups.members'}}
|
||||
<span class='count'>({{counts.members}})</span>
|
||||
<section class='user-navigation'>
|
||||
<ul class='action-list nav-stacked'>
|
||||
{{#each tabs as |tab|}}
|
||||
<li class="{{if tab.active 'active'}}">
|
||||
{{#link-to tab.location model}}{{tab.name}}
|
||||
{{#if tab.count}}
|
||||
<span class='count'>({{tab.count}})</span>
|
||||
{{/if}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class='user-main'>
|
||||
|
||||
<section class='user-right groups'>
|
||||
<section class='about group'>
|
||||
<div class='details'>
|
||||
<h1>{{model.name}}</h1>
|
||||
</div>
|
||||
</section>
|
||||
{{outlet}}
|
||||
<section class='user-main'>
|
||||
<section class='user-right groups'>
|
||||
<section class='about group'>
|
||||
<div class='details'>
|
||||
<h1>{{model.name}}</h1>
|
||||
</div>
|
||||
</section>
|
||||
{{outlet}}
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
@ -1,22 +1,5 @@
|
|||
<div class='user-stream'>
|
||||
{{#each p in controller}}
|
||||
<div class='item'>
|
||||
<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 controller as |post|}}
|
||||
{{group-post post=post}}
|
||||
{{/each}}
|
||||
</div>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<td class='avatar'>{{user-small user=m}}
|
||||
{{#if m.owner}}
|
||||
<span class='is-owner'>{{i18n "groups.owner"}}</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</td>
|
||||
<td>
|
||||
<span class="text">{{bound-date m.last_posted_at}}</span>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<div class='user-stream'>
|
||||
{{#each controller as |post|}}
|
||||
{{group-post post=post}}
|
||||
{{/each}}
|
||||
</div>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<div class='user-stream'>
|
||||
{{#each controller as |post|}}
|
||||
{{group-post post=post}}
|
||||
{{/each}}
|
||||
</div>
|
|
@ -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',
|
||||
});
|
|
@ -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',
|
||||
});
|
|
@ -7,6 +7,8 @@ class GroupsController < ApplicationController
|
|||
def counts
|
||||
group = find_group(:group_id)
|
||||
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 } }
|
||||
end
|
||||
|
||||
|
@ -16,6 +18,19 @@ class GroupsController < ApplicationController
|
|||
render_serialized posts.to_a, GroupPostSerializer
|
||||
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
|
||||
group = find_group(:group_id)
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ class Group < ActiveRecord::Base
|
|||
|
||||
has_many :category_groups, dependent: :destroy
|
||||
has_many :group_users, dependent: :destroy
|
||||
has_many :group_mentions, dependent: :destroy
|
||||
|
||||
has_many :categories, through: :category_groups
|
||||
has_many :users, through: :group_users
|
||||
|
@ -80,6 +81,18 @@ class Group < ActiveRecord::Base
|
|||
result.order('posts.created_at desc')
|
||||
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
|
||||
(10..19).to_a
|
||||
end
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
class GroupMention < ActiveRecord::Base
|
||||
belongs_to :post
|
||||
belongs_to :group
|
||||
end
|
|
@ -32,6 +32,7 @@ class Post < ActiveRecord::Base
|
|||
has_many :replies, through: :post_replies
|
||||
has_many :post_actions
|
||||
has_many :topic_links
|
||||
has_many :group_mentions, dependent: :destroy
|
||||
|
||||
has_many :post_uploads
|
||||
has_many :uploads, through: :post_uploads
|
||||
|
|
|
@ -64,6 +64,16 @@ class PostAlerter
|
|||
notify_post_users(post, notified)
|
||||
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
|
||||
|
||||
def unread_posts(user, topic)
|
||||
|
|
|
@ -342,6 +342,8 @@ Discourse::Application.routes.draw do
|
|||
resources :groups do
|
||||
get 'members'
|
||||
get 'posts'
|
||||
get 'topics'
|
||||
get 'mentions'
|
||||
get 'counts'
|
||||
|
||||
member do
|
||||
|
|
|
@ -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
|
|
@ -103,12 +103,15 @@ describe PostAlerter do
|
|||
create_post_with_alerts(raw: "Hello @group how are you?")
|
||||
}.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])
|
||||
|
||||
expect {
|
||||
create_post_with_alerts(raw: "Hello @group you are not mentionable")
|
||||
}.to change(evil_trout.notifications, :count).by(0)
|
||||
|
||||
expect(GroupMention.count).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue