work in progress wide category list

This commit is contained in:
Sam 2013-10-17 17:44:56 +11:00
parent 7bf96ee690
commit 1ee49798b2
17 changed files with 200 additions and 25 deletions

View File

@ -34,6 +34,7 @@ Discourse.Category = Discourse.Model.extend({
return Discourse.getURL("/category/") + (this.get('slug'));
}.property('name'),
style: function() {
return "background-color: #" + (this.get('category.color')) + "; color: #" + (this.get('category.text_color')) + ";";
}.property('color', 'text_color'),
@ -101,7 +102,15 @@ Discourse.Category = Discourse.Model.extend({
latestTopic: function(){
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')
});

View File

@ -87,6 +87,10 @@ Discourse.Topic = Discourse.Model.extend({
return this.urlForPostNumber(this.get('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
// tells us if we are still asynchronously flushing our "recently read" data.
// So take what the browser has seen into consideration.

View File

@ -129,20 +129,23 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
this.set("messageCount", this.get("messageCount") + 1);
},
countNew: function(){
countNew: function(category_name){
return _.chain(this.states)
.where({last_read_post_number: null})
.where(function(topic){ return topic.category_name === category_name || !category_name;})
.value()
.length;
},
countUnread: function(){
var count = 0;
_.each(this.states, function(topic){
count += (topic.last_read_post_number !== null &&
topic.last_read_post_number < topic.highest_post_number) ? 1 : 0;
});
return count;
countUnread: function(category_name){
return _.chain(this.states)
.where(function(topic){
return topic.last_read_post_number !== null &&
topic.last_read_post_number < topic.highest_post_number;
})
.where(function(topic){ return topic.category_name === category_name || !category_name;})
.value()
.length;
},
countCategory: function(category) {

View File

@ -46,7 +46,7 @@
<a href="{{unbound url}}" {{{bindAttr class=":age ageCold"}}} title='{{i18n first_post}}: {{{unboundDate created_at}}}' >{{unboundAge created_at}}</a>
</td>
<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>
{{else}}
<td class='num activity'>
@ -62,4 +62,4 @@
<div class='alert alert-info'>
{{i18n choose_topic.none_found}}
</div>
{{/if}}
{{/if}}

View File

@ -14,6 +14,12 @@
{{#each model.categories}}
<tr>
<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}}
<div class="category-description">
{{{description_excerpt}}}
@ -25,12 +31,15 @@
{{/each}}
</td>
<td>{{number topic_count}}</td>
<td>{{number posts_total}}</td>
<td>{{number post_count}}</td>
{{#with latestTopic}}
<td {{bindAttr class="archived"}}>
{{topicStatus topic=this}}
{{{topicLink this}}}
<div class='lastUserInfo'>
{{i18n categories.by}} <a href="{{{unbound lastPosterUrl}}}">{{unbound last_poster.username}}</a>
{{unboundAge last_posted_at}}
</div>
</td>
{{/with}}
</tr>

View File

@ -9,7 +9,14 @@ class CategoriesController < ApplicationController
def index
@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_sequence = DraftSequence.current(current_user, Draft::NEW_TOPIC)

View File

@ -13,6 +13,7 @@ class Category < ActiveRecord::Base
end
belongs_to :user
belongs_to :latest_post, class_name: "Post"
has_many :topics
has_many :category_featured_topics
@ -227,6 +228,26 @@ SQL
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)
read_restricted = true

View File

@ -8,10 +8,11 @@ class CategoryList
:draft_key,
:draft_sequence
def initialize(guardian=nil)
def initialize(guardian=nil, options = {})
@guardian = guardian || Guardian.new
@options = options
find_relevant_topics
find_relevant_topics unless latest_post_only?
find_categories
prune_empty
@ -21,6 +22,10 @@ class CategoryList
private
def latest_post_only?
@options[:latest_post_only]
end
# Retrieve a list of all the topics we'll need
def find_relevant_topics
@topics_by_category_id = {}
@ -47,16 +52,35 @@ class CategoryList
.order('COALESCE(categories.topics_month, 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.each do |c|
topics_in_cat = @topics_by_category_id[c.id]
if topics_in_cat.present?
c.displayable_topics = []
topics_in_cat.each do |topic_id|
topic = @topics_by_id[topic_id]
if topic.present?
topic.category = c
c.displayable_topics << topic
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|
topics_in_cat = @topics_by_category_id[c.id]
if topics_in_cat.present?
c.displayable_topics = []
topics_in_cat.each do |topic_id|
topic = @topics_by_id[topic_id]
if topic.present?
topic.category = c
c.displayable_topics << topic
end
end
end
end

View File

@ -84,6 +84,9 @@ class Post < ActiveRecord::Base
super
update_flagged_posts_count
TopicLink.extract_from(self)
if topic && topic.category_id
topic.category.update_latest
end
end
# The key we use in redis to ensure unique posts

View File

@ -98,6 +98,7 @@ class Topic < ActiveRecord::Base
attr_accessor :user_data
attr_accessor :posters # TODO: can replace with posters_summary once we remove old list code
attr_accessor :topic_list
attr_accessor :include_last_poster
# The regular order
scope :topic_list_order, lambda { order('topics.bumped_at desc') }

View File

@ -6,6 +6,7 @@ class CategoryDetailedSerializer < ApplicationSerializer
:text_color,
:slug,
:topic_count,
:post_count,
:topics_week,
:topics_month,
:topics_year,

View File

@ -19,6 +19,12 @@ class ListableTopicSerializer < BasicTopicSerializer
:closed,
:archived
has_one :last_poster, serializer: BasicUserSerializer, embed: :objects
def include_associations!
include! :last_poster if object.include_last_poster
end
def bumped
object.created_at < object.bumped_at
end

View File

@ -189,6 +189,7 @@ en:
posts: "Posts"
topics: "Topics"
latest: "Latest"
by: "by"
user:
said: "{{username}} said:"
@ -591,6 +592,12 @@ en:
private_message: 'Start a private message'
list: 'Topics'
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'
loading_more: "Loading more Topics..."
loading: 'Loading topic...'

View 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

View File

@ -80,10 +80,13 @@ class PostCreator
SpamRulesEnforcer.enforce!(@post)
end
track_latest_on_category
enqueue_jobs
@post
end
def self.create(user, opts)
PostCreator.new(user, opts).create
end
@ -107,6 +110,15 @@ class PostCreator
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
return unless @topic.private_message?

View File

@ -98,6 +98,15 @@ class PostDestroyer
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
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

View File

@ -1,6 +1,7 @@
# encoding: utf-8
require 'spec_helper'
require_dependency 'post_creator'
describe Category do
it { should validate_presence_of :user_id }
@ -248,6 +249,31 @@ describe Category do
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
before do
@category = Fabricate(:category)