mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
work in progress wide category list
This commit is contained in:
@@ -34,6 +34,7 @@ Discourse.Category = Discourse.Model.extend({
|
|||||||
return Discourse.getURL("/category/") + (this.get('slug'));
|
return Discourse.getURL("/category/") + (this.get('slug'));
|
||||||
}.property('name'),
|
}.property('name'),
|
||||||
|
|
||||||
|
|
||||||
style: function() {
|
style: function() {
|
||||||
return "background-color: #" + (this.get('category.color')) + "; color: #" + (this.get('category.text_color')) + ";";
|
return "background-color: #" + (this.get('category.color')) + "; color: #" + (this.get('category.text_color')) + ";";
|
||||||
}.property('color', 'text_color'),
|
}.property('color', 'text_color'),
|
||||||
@@ -101,7 +102,15 @@ Discourse.Category = Discourse.Model.extend({
|
|||||||
|
|
||||||
latestTopic: function(){
|
latestTopic: function(){
|
||||||
return this.get("topics")[0];
|
return this.get("topics")[0];
|
||||||
}.property("topics")
|
}.property("topics"),
|
||||||
|
|
||||||
|
unreadTopics: function(){
|
||||||
|
return Discourse.TopicTrackingState.current().countUnread(this.get('name'));
|
||||||
|
}.property('Discourse.TopicTrackingState.current.messageCount'),
|
||||||
|
|
||||||
|
newTopics: function(){
|
||||||
|
return Discourse.TopicTrackingState.current().countNew(this.get('name'));
|
||||||
|
}.property('Discourse.TopicTrackingState.current.messageCount')
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ Discourse.Topic = Discourse.Model.extend({
|
|||||||
return this.urlForPostNumber(this.get('highest_post_number'));
|
return this.urlForPostNumber(this.get('highest_post_number'));
|
||||||
}.property('url', 'highest_post_number'),
|
}.property('url', 'highest_post_number'),
|
||||||
|
|
||||||
|
lastPosterUrl: function() {
|
||||||
|
return Discourse.getURL("/users/") + this.get("last_poster.username");
|
||||||
|
}.property('last_poster'),
|
||||||
|
|
||||||
// The amount of new posts to display. It might be different than what the server
|
// The amount of new posts to display. It might be different than what the server
|
||||||
// tells us if we are still asynchronously flushing our "recently read" data.
|
// tells us if we are still asynchronously flushing our "recently read" data.
|
||||||
// So take what the browser has seen into consideration.
|
// So take what the browser has seen into consideration.
|
||||||
|
|||||||
@@ -129,20 +129,23 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
|||||||
this.set("messageCount", this.get("messageCount") + 1);
|
this.set("messageCount", this.get("messageCount") + 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
countNew: function(){
|
countNew: function(category_name){
|
||||||
return _.chain(this.states)
|
return _.chain(this.states)
|
||||||
.where({last_read_post_number: null})
|
.where({last_read_post_number: null})
|
||||||
|
.where(function(topic){ return topic.category_name === category_name || !category_name;})
|
||||||
.value()
|
.value()
|
||||||
.length;
|
.length;
|
||||||
},
|
},
|
||||||
|
|
||||||
countUnread: function(){
|
countUnread: function(category_name){
|
||||||
var count = 0;
|
return _.chain(this.states)
|
||||||
_.each(this.states, function(topic){
|
.where(function(topic){
|
||||||
count += (topic.last_read_post_number !== null &&
|
return topic.last_read_post_number !== null &&
|
||||||
topic.last_read_post_number < topic.highest_post_number) ? 1 : 0;
|
topic.last_read_post_number < topic.highest_post_number;
|
||||||
});
|
})
|
||||||
return count;
|
.where(function(topic){ return topic.category_name === category_name || !category_name;})
|
||||||
|
.value()
|
||||||
|
.length;
|
||||||
},
|
},
|
||||||
|
|
||||||
countCategory: function(category) {
|
countCategory: function(category) {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
<a href="{{unbound url}}" {{{bindAttr class=":age ageCold"}}} title='{{i18n first_post}}: {{{unboundDate created_at}}}' >{{unboundAge created_at}}</a>
|
<a href="{{unbound url}}" {{{bindAttr class=":age ageCold"}}} title='{{i18n first_post}}: {{{unboundDate created_at}}}' >{{unboundAge created_at}}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class='num activity last'>
|
<td class='num activity last'>
|
||||||
<a href="{{unbound lastPostUrl}}" class='age' title='{{i18n last_post}}: {{{unboundDate bumped_at}}}'>{{unboundAge bumped_at}}</a>
|
<a href="{{unbound lastPosterUrl}}" class='age' title='{{i18n last_post}}: {{{unboundDate bumped_at}}}'>{{unboundAge bumped_at}}</a>
|
||||||
</td>
|
</td>
|
||||||
{{else}}
|
{{else}}
|
||||||
<td class='num activity'>
|
<td class='num activity'>
|
||||||
|
|||||||
@@ -14,6 +14,12 @@
|
|||||||
{{#each model.categories}}
|
{{#each model.categories}}
|
||||||
<tr>
|
<tr>
|
||||||
<td class='category'>{{categoryLink this}}
|
<td class='category'>{{categoryLink this}}
|
||||||
|
{{#if unreadTopics}}
|
||||||
|
<a href={{unbound url}} class='badge new-posts badge-notification' title='{{i18n topic.unread_topics count="unreadTopics"}}'>{{unbound unreadTopics}}</a>
|
||||||
|
{{/if}}
|
||||||
|
{{#if newTopics}}
|
||||||
|
<a href={{unbound url}} class='badge new-posts badge-notification' title='{{i18n topic.new_topics count="newTopics"}}'>{{unbound newTopics}} <i class='icon icon-asterisk'></i></a>
|
||||||
|
{{/if}}
|
||||||
{{#if description_excerpt}}
|
{{#if description_excerpt}}
|
||||||
<div class="category-description">
|
<div class="category-description">
|
||||||
{{{description_excerpt}}}
|
{{{description_excerpt}}}
|
||||||
@@ -25,12 +31,15 @@
|
|||||||
{{/each}}
|
{{/each}}
|
||||||
</td>
|
</td>
|
||||||
<td>{{number topic_count}}</td>
|
<td>{{number topic_count}}</td>
|
||||||
<td>{{number posts_total}}</td>
|
<td>{{number post_count}}</td>
|
||||||
{{#with latestTopic}}
|
{{#with latestTopic}}
|
||||||
<td {{bindAttr class="archived"}}>
|
<td {{bindAttr class="archived"}}>
|
||||||
{{topicStatus topic=this}}
|
{{topicStatus topic=this}}
|
||||||
{{{topicLink this}}}
|
{{{topicLink this}}}
|
||||||
|
<div class='lastUserInfo'>
|
||||||
|
{{i18n categories.by}} <a href="{{{unbound lastPosterUrl}}}">{{unbound last_poster.username}}</a>
|
||||||
|
{{unboundAge last_posted_at}}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
{{/with}}
|
{{/with}}
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -9,7 +9,14 @@ class CategoriesController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
@description = SiteSetting.site_description
|
@description = SiteSetting.site_description
|
||||||
|
|
||||||
@list = CategoryList.new(guardian)
|
wide_mode = SiteSetting.enable_wide_category_list
|
||||||
|
|
||||||
|
options = {}
|
||||||
|
options[:latest_post_only] = params[:latest_post_only] || wide_mode
|
||||||
|
|
||||||
|
@list = CategoryList.new(guardian,options)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@list.draft_key = Draft::NEW_TOPIC
|
@list.draft_key = Draft::NEW_TOPIC
|
||||||
@list.draft_sequence = DraftSequence.current(current_user, Draft::NEW_TOPIC)
|
@list.draft_sequence = DraftSequence.current(current_user, Draft::NEW_TOPIC)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class Category < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
belongs_to :latest_post, class_name: "Post"
|
||||||
|
|
||||||
has_many :topics
|
has_many :topics
|
||||||
has_many :category_featured_topics
|
has_many :category_featured_topics
|
||||||
@@ -227,6 +228,26 @@ SQL
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_latest
|
||||||
|
latest_post_id = Post
|
||||||
|
.order("posts.created_at desc")
|
||||||
|
.where("NOT hidden")
|
||||||
|
.joins("join topics on topics.id = topic_id")
|
||||||
|
.where("topics.category_id = :id", id: self.id)
|
||||||
|
.limit(1)
|
||||||
|
.pluck("posts.id")
|
||||||
|
.first
|
||||||
|
|
||||||
|
latest_topic_id = Topic
|
||||||
|
.order("topics.created_at desc")
|
||||||
|
.where("visible")
|
||||||
|
.where("topics.category_id = :id", id: self.id)
|
||||||
|
.limit(1)
|
||||||
|
.pluck("topics.id")
|
||||||
|
.first
|
||||||
|
|
||||||
|
self.update_attributes(latest_topic_id: latest_topic_id, latest_post_id: latest_post_id)
|
||||||
|
end
|
||||||
|
|
||||||
def self.resolve_permissions(permissions)
|
def self.resolve_permissions(permissions)
|
||||||
read_restricted = true
|
read_restricted = true
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ class CategoryList
|
|||||||
:draft_key,
|
:draft_key,
|
||||||
:draft_sequence
|
:draft_sequence
|
||||||
|
|
||||||
def initialize(guardian=nil)
|
def initialize(guardian=nil, options = {})
|
||||||
@guardian = guardian || Guardian.new
|
@guardian = guardian || Guardian.new
|
||||||
|
@options = options
|
||||||
|
|
||||||
find_relevant_topics
|
find_relevant_topics unless latest_post_only?
|
||||||
find_categories
|
find_categories
|
||||||
|
|
||||||
prune_empty
|
prune_empty
|
||||||
@@ -21,6 +22,10 @@ class CategoryList
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def latest_post_only?
|
||||||
|
@options[:latest_post_only]
|
||||||
|
end
|
||||||
|
|
||||||
# Retrieve a list of all the topics we'll need
|
# Retrieve a list of all the topics we'll need
|
||||||
def find_relevant_topics
|
def find_relevant_topics
|
||||||
@topics_by_category_id = {}
|
@topics_by_category_id = {}
|
||||||
@@ -47,7 +52,25 @@ class CategoryList
|
|||||||
.order('COALESCE(categories.topics_month, 0) DESC')
|
.order('COALESCE(categories.topics_month, 0) DESC')
|
||||||
.order('COALESCE(categories.topics_year, 0) DESC')
|
.order('COALESCE(categories.topics_year, 0) DESC')
|
||||||
|
|
||||||
|
if latest_post_only?
|
||||||
|
@categories = @categories.includes(:latest_post => :topic )
|
||||||
|
end
|
||||||
|
|
||||||
@categories = @categories.to_a
|
@categories = @categories.to_a
|
||||||
|
|
||||||
|
if latest_post_only?
|
||||||
|
@all_topics = []
|
||||||
|
@categories.each do |c|
|
||||||
|
if c.latest_post && c.latest_post.topic
|
||||||
|
c.displayable_topics = [c.latest_post.topic]
|
||||||
|
topic = c.latest_post.topic
|
||||||
|
topic.include_last_poster = true # hint for serialization
|
||||||
|
@all_topics << topic
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if @topics_by_category_id
|
||||||
@categories.each do |c|
|
@categories.each do |c|
|
||||||
topics_in_cat = @topics_by_category_id[c.id]
|
topics_in_cat = @topics_by_category_id[c.id]
|
||||||
if topics_in_cat.present?
|
if topics_in_cat.present?
|
||||||
@@ -62,6 +85,7 @@ class CategoryList
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Add the uncategorized "magic" category
|
# Add the uncategorized "magic" category
|
||||||
def add_uncategorized
|
def add_uncategorized
|
||||||
|
|||||||
@@ -84,6 +84,9 @@ class Post < ActiveRecord::Base
|
|||||||
super
|
super
|
||||||
update_flagged_posts_count
|
update_flagged_posts_count
|
||||||
TopicLink.extract_from(self)
|
TopicLink.extract_from(self)
|
||||||
|
if topic && topic.category_id
|
||||||
|
topic.category.update_latest
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# The key we use in redis to ensure unique posts
|
# The key we use in redis to ensure unique posts
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ class Topic < ActiveRecord::Base
|
|||||||
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 :topic_list
|
attr_accessor :topic_list
|
||||||
|
attr_accessor :include_last_poster
|
||||||
|
|
||||||
# The regular order
|
# The regular order
|
||||||
scope :topic_list_order, lambda { order('topics.bumped_at desc') }
|
scope :topic_list_order, lambda { order('topics.bumped_at desc') }
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ class CategoryDetailedSerializer < ApplicationSerializer
|
|||||||
:text_color,
|
:text_color,
|
||||||
:slug,
|
:slug,
|
||||||
:topic_count,
|
:topic_count,
|
||||||
|
:post_count,
|
||||||
:topics_week,
|
:topics_week,
|
||||||
:topics_month,
|
:topics_month,
|
||||||
:topics_year,
|
:topics_year,
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ class ListableTopicSerializer < BasicTopicSerializer
|
|||||||
:closed,
|
:closed,
|
||||||
:archived
|
:archived
|
||||||
|
|
||||||
|
has_one :last_poster, serializer: BasicUserSerializer, embed: :objects
|
||||||
|
|
||||||
|
def include_associations!
|
||||||
|
include! :last_poster if object.include_last_poster
|
||||||
|
end
|
||||||
|
|
||||||
def bumped
|
def bumped
|
||||||
object.created_at < object.bumped_at
|
object.created_at < object.bumped_at
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ en:
|
|||||||
posts: "Posts"
|
posts: "Posts"
|
||||||
topics: "Topics"
|
topics: "Topics"
|
||||||
latest: "Latest"
|
latest: "Latest"
|
||||||
|
by: "by"
|
||||||
|
|
||||||
user:
|
user:
|
||||||
said: "{{username}} said:"
|
said: "{{username}} said:"
|
||||||
@@ -591,6 +592,12 @@ en:
|
|||||||
private_message: 'Start a private message'
|
private_message: 'Start a private message'
|
||||||
list: 'Topics'
|
list: 'Topics'
|
||||||
new: 'new topic'
|
new: 'new topic'
|
||||||
|
new_topics:
|
||||||
|
one: '1 new topic'
|
||||||
|
other: '{{count}} new topics'
|
||||||
|
unread_topics:
|
||||||
|
one: '1 unread topic'
|
||||||
|
other: '{{count}} unread topics'
|
||||||
title: 'Topic'
|
title: 'Topic'
|
||||||
loading_more: "Loading more Topics..."
|
loading_more: "Loading more Topics..."
|
||||||
loading: 'Loading topic...'
|
loading: 'Loading topic...'
|
||||||
|
|||||||
33
db/migrate/20131017030605_add_latest_to_categories.rb
Normal file
33
db/migrate/20131017030605_add_latest_to_categories.rb
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
class AddLatestToCategories < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
add_column :categories, :latest_post_id, :integer
|
||||||
|
add_column :categories, :latest_topic_id, :integer
|
||||||
|
|
||||||
|
execute <<SQL
|
||||||
|
UPDATE categories c
|
||||||
|
SET latest_post_id = x.post_id
|
||||||
|
FROM (select category_id, max(p.id) post_id FROM posts p
|
||||||
|
JOIN topics t on t.id = p.topic_id
|
||||||
|
WHERE p.deleted_at IS NULL AND NOT p.hidden AND t.visible
|
||||||
|
GROUP BY category_id
|
||||||
|
) x
|
||||||
|
WHERE x.category_id = c.id
|
||||||
|
SQL
|
||||||
|
|
||||||
|
execute <<SQL
|
||||||
|
UPDATE categories c
|
||||||
|
SET latest_topic_id = x.topic_id
|
||||||
|
FROM (select category_id, max(t.id) topic_id
|
||||||
|
FROM topics t
|
||||||
|
WHERE t.deleted_at IS NULL AND t.visible
|
||||||
|
GROUP BY category_id
|
||||||
|
) x
|
||||||
|
WHERE x.category_id = c.id
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_column :categories, :latest_post_id
|
||||||
|
remove_column :categories, :latest_topic_id
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -80,10 +80,13 @@ class PostCreator
|
|||||||
SpamRulesEnforcer.enforce!(@post)
|
SpamRulesEnforcer.enforce!(@post)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
track_latest_on_category
|
||||||
|
|
||||||
enqueue_jobs
|
enqueue_jobs
|
||||||
@post
|
@post
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def self.create(user, opts)
|
def self.create(user, opts)
|
||||||
PostCreator.new(user, opts).create
|
PostCreator.new(user, opts).create
|
||||||
end
|
end
|
||||||
@@ -107,6 +110,15 @@ class PostCreator
|
|||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
def track_latest_on_category
|
||||||
|
if @post && @post.errors.count == 0 && @topic && @topic.category_id
|
||||||
|
Category.update_all( {latest_post_id: @post.id}, {id: @topic.category_id} )
|
||||||
|
if @post.post_number == 1
|
||||||
|
Category.update_all( {latest_topic_id: @topic.id}, {id: @topic.category_id} )
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def ensure_in_allowed_users
|
def ensure_in_allowed_users
|
||||||
return unless @topic.private_message?
|
return unless @topic.private_message?
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,15 @@ class PostDestroyer
|
|||||||
Notification.delete_all topic_id: @post.topic_id, post_number: @post.post_number
|
Notification.delete_all topic_id: @post.topic_id, post_number: @post.post_number
|
||||||
|
|
||||||
@post.topic.trash!(@user) if @post.topic and @post.post_number == 1
|
@post.topic.trash!(@user) if @post.topic and @post.post_number == 1
|
||||||
|
|
||||||
|
if @post.topic && @post.topic.category && @post.id == @post.topic.category.latest_post_id
|
||||||
|
@post.topic.category.update_latest
|
||||||
|
end
|
||||||
|
|
||||||
|
if @post.post_number == 1 && @post.topic && @post.topic.category && @post.topic_id == @post.topic.category.latest_topic_id
|
||||||
|
@post.topic.category.update_latest
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
require_dependency 'post_creator'
|
||||||
|
|
||||||
describe Category do
|
describe Category do
|
||||||
it { should validate_presence_of :user_id }
|
it { should validate_presence_of :user_id }
|
||||||
@@ -248,6 +249,31 @@ describe Category do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'latest' do
|
||||||
|
it 'should be updated correctly' do
|
||||||
|
category = Fabricate(:category)
|
||||||
|
post = create_post(category: category.name)
|
||||||
|
|
||||||
|
category.reload
|
||||||
|
category.latest_post_id.should == post.id
|
||||||
|
category.latest_topic_id.should == post.topic_id
|
||||||
|
|
||||||
|
post2 = create_post(category: category.name)
|
||||||
|
post3 = create_post(topic_id: post.topic_id, category: category.name)
|
||||||
|
|
||||||
|
category.reload
|
||||||
|
category.latest_post_id.should == post3.id
|
||||||
|
category.latest_topic_id.should == post2.topic_id
|
||||||
|
|
||||||
|
|
||||||
|
destroyer = PostDestroyer.new(Fabricate(:admin), post3)
|
||||||
|
destroyer.destroy
|
||||||
|
|
||||||
|
category.reload
|
||||||
|
category.latest_post_id.should == post2.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'update_stats' do
|
describe 'update_stats' do
|
||||||
before do
|
before do
|
||||||
@category = Fabricate(:category)
|
@category = Fabricate(:category)
|
||||||
|
|||||||
Reference in New Issue
Block a user