mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 11:23:25 +08:00
work in progress wide category list
This commit is contained in:
parent
7bf96ee690
commit
1ee49798b2
|
@ -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')
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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') }
|
||||
|
|
|
@ -6,6 +6,7 @@ class CategoryDetailedSerializer < ApplicationSerializer
|
|||
:text_color,
|
||||
:slug,
|
||||
:topic_count,
|
||||
:post_count,
|
||||
:topics_week,
|
||||
:topics_month,
|
||||
:topics_year,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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...'
|
||||
|
|
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)
|
||||
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?
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue
Block a user