mirror of
https://github.com/discourse/discourse.git
synced 2025-05-24 03:36:18 +08:00
FEATURE: show group mentions and topics in groups page
This commit is contained in:
@ -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'
|
||||||
|
})
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -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) {
|
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);
|
||||||
|
@ -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');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
});
|
11
app/assets/javascripts/discourse/routes/group-topics.js.es6
Normal file
11
app/assets/javascripts/discourse/routes/group-topics.js.es6
Normal 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");
|
||||||
|
}
|
||||||
|
});
|
@ -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">
|
<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>
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
4
app/models/group_mention.rb
Normal file
4
app/models/group_mention.rb
Normal file
@ -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 :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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
12
db/migrate/20151201035631_add_group_mentions.rb
Normal file
12
db/migrate/20151201035631_add_group_mentions.rb
Normal 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
|
@ -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
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user