FEATURE: suppress categories from the homepage

This commit is contained in:
Régis Hanol 2015-09-02 20:25:18 +02:00
parent a76d1079b2
commit a501947d67
12 changed files with 116 additions and 135 deletions

View File

@ -77,7 +77,8 @@ Discourse.Category = Discourse.Model.extend({
background_url: this.get('background_url'),
allow_badges: this.get('allow_badges'),
custom_fields: this.get('custom_fields'),
topic_template: this.get('topic_template')
topic_template: this.get('topic_template'),
suppress_from_homepage: this.get('suppress_from_homepage'),
},
type: this.get('id') ? 'PUT' : 'POST'
});

View File

@ -40,7 +40,6 @@ function findTopicList(store, filter, filterParams, extras) {
session.setProperties({topicList: null, topicListScrollPosition: null});
}
// Clean up any string parameters that might slip through
filterParams = filterParams || {};
Ember.keys(filterParams).forEach(function(k) {
@ -50,17 +49,7 @@ function findTopicList(store, filter, filterParams, extras) {
}
});
const findParams = {};
Discourse.SiteSettings.top_menu.split('|').forEach(function (i) {
if (i.indexOf(filter) === 0) {
const exclude = i.split("-");
if (exclude && exclude.length === 2) {
findParams.exclude_category = exclude[1];
}
}
});
return resolve(store.findFiltered('topicList', { filter, params:_.extend(findParams, filterParams || {})}));
return resolve(store.findFiltered('topicList', { filter, params: filterParams || {} }));
}).then(function(list) {
list.set('listParams', filterParams);
if (tracking) {

View File

@ -44,4 +44,11 @@
{{/if}}
</section>
<section class="field">
<label class="checkbox-label">
{{input type="checkbox" checked=category.suppress_from_homepage}}
{{i18n "category.suppress_from_homepage"}}
</label>
</section>
{{plugin-outlet "category-custom-settings"}}

View File

@ -16,6 +16,7 @@ class CategoriesController < ApplicationController
options = {}
options[:latest_posts] = params[:latest_posts] || SiteSetting.category_featured_topics
options[:parent_category_id] = params[:parent_category_id]
options[:is_homepage] = current_homepage == "categories".freeze
@list = CategoryList.new(guardian, options)
@list.draft_key = Draft::NEW_TOPIC
@ -24,7 +25,7 @@ class CategoriesController < ApplicationController
discourse_expires_in 1.minute
unless current_homepage == 'categories'
unless current_homepage == "categories"
@title = I18n.t('js.filters.categories.title')
end
@ -139,6 +140,7 @@ class CategoriesController < ApplicationController
:position,
:email_in,
:email_in_allow_strangers,
:suppress_from_homepage,
:parent_category_id,
:auto_close_hours,
:auto_close_based_on_last_post,

View File

@ -5,36 +5,34 @@ class ListController < ApplicationController
skip_before_filter :check_xhr
@@categories = [
before_filter :set_category, only: [
# filtered topics lists
Discourse.filters.map { |f| "category_#{f}".to_sym },
Discourse.filters.map { |f| "category_none_#{f}".to_sym },
Discourse.filters.map { |f| "parent_category_category_#{f}".to_sym },
Discourse.filters.map { |f| "parent_category_category_none_#{f}".to_sym },
Discourse.filters.map { |f| :"category_#{f}" },
Discourse.filters.map { |f| :"category_none_#{f}" },
Discourse.filters.map { |f| :"parent_category_category_#{f}" },
Discourse.filters.map { |f| :"parent_category_category_none_#{f}" },
# top summaries
:category_top,
:category_none_top,
:parent_category_category_top,
# top pages (ie. with a period)
TopTopic.periods.map { |p| "category_top_#{p}".to_sym },
TopTopic.periods.map { |p| "category_none_top_#{p}".to_sym },
TopTopic.periods.map { |p| "parent_category_category_top_#{p}".to_sym },
TopTopic.periods.map { |p| :"category_top_#{p}" },
TopTopic.periods.map { |p| :"category_none_top_#{p}" },
TopTopic.periods.map { |p| :"parent_category_category_top_#{p}" },
# category feeds
:category_feed,
].flatten
before_filter :set_category, only: @@categories
before_filter :ensure_logged_in, except: [
:topics_by,
# anonymous filters
Discourse.anonymous_filters,
Discourse.anonymous_filters.map { |f| "#{f}_feed".to_sym },
Discourse.anonymous_filters.map { |f| "#{f}_feed" },
# anonymous categorized filters
Discourse.anonymous_filters.map { |f| "category_#{f}".to_sym },
Discourse.anonymous_filters.map { |f| "category_none_#{f}".to_sym },
Discourse.anonymous_filters.map { |f| "parent_category_category_#{f}".to_sym },
Discourse.anonymous_filters.map { |f| "parent_category_category_none_#{f}".to_sym },
Discourse.anonymous_filters.map { |f| :"category_#{f}" },
Discourse.anonymous_filters.map { |f| :"category_none_#{f}" },
Discourse.anonymous_filters.map { |f| :"parent_category_category_#{f}" },
Discourse.anonymous_filters.map { |f| :"parent_category_category_none_#{f}" },
# category feeds
:category_feed,
# top summaries
@ -43,14 +41,14 @@ class ListController < ApplicationController
:category_none_top,
:parent_category_category_top,
# top pages (ie. with a period)
TopTopic.periods.map { |p| "top_#{p}".to_sym },
TopTopic.periods.map { |p| "category_top_#{p}".to_sym },
TopTopic.periods.map { |p| "category_none_top_#{p}".to_sym },
TopTopic.periods.map { |p| "parent_category_category_top_#{p}".to_sym },
TopTopic.periods.map { |p| :"top_#{p}" },
TopTopic.periods.map { |p| :"category_top_#{p}" },
TopTopic.periods.map { |p| :"category_none_top_#{p}" },
TopTopic.periods.map { |p| :"parent_category_category_top_#{p}" },
].flatten
# Create our filters
Discourse.filters.each_with_index do |filter, idx|
Discourse.filters.each do |filter|
define_method(filter) do |options = nil|
list_opts = build_topic_list_options
list_opts.merge!(options) if options
@ -60,6 +58,10 @@ class ListController < ApplicationController
list_opts[:no_definitions] = true
end
if filter.to_s == current_homepage
list_opts.merge!(exclude_category_ids: get_excluded_category_ids(list_opts[:category]))
end
list = TopicQuery.new(user, list_opts).public_send("list_#{filter}")
list.more_topics_url = construct_url_with(:next, list_opts)
list.prev_topics_url = construct_url_with(:prev, list_opts)
@ -83,34 +85,20 @@ class ListController < ApplicationController
define_method("category_#{filter}") do
canonical_url "#{Discourse.base_url}#{@category.url}"
self.send(filter, { category: @category.id })
self.send(filter, category: @category.id)
end
define_method("category_none_#{filter}") do
self.send(filter, { category: @category.id, no_subcategories: true })
self.send(filter, category: @category.id, no_subcategories: true)
end
define_method("parent_category_category_#{filter}") do
canonical_url "#{Discourse.base_url}#{@category.url}"
self.send(filter, { category: @category.id })
self.send(filter, category: @category.id)
end
define_method("parent_category_category_none_#{filter}") do
self.send(filter, { category: @category.id })
end
end
Discourse.feed_filters.each do |filter|
define_method("#{filter}_feed") do
discourse_expires_in 1.minute
@title = "#{SiteSetting.title} - #{I18n.t("rss_description.#{filter}")}"
@link = "#{Discourse.base_url}/#{filter}"
@description = I18n.t("rss_description.#{filter}")
@atom_link = "#{Discourse.base_url}/#{filter}.rss"
@topic_list = TopicQuery.new(nil, order: 'created').public_send("list_#{filter}")
render 'list', formats: [:rss]
self.send(filter, category: @category.id)
end
end
@ -127,14 +115,26 @@ class ListController < ApplicationController
end
end
def latest_feed
discourse_expires_in 1.minute
@title = "#{SiteSetting.title} - #{I18n.t("rss_description.latest")}"
@link = "#{Discourse.base_url}/latest"
@atom_link = "#{Discourse.base_url}/latest.rss"
@description = I18n.t("rss_description.latest")
@topic_list = TopicQuery.new(nil, order: 'created').list_latest
render 'list', formats: [:rss]
end
def category_feed
guardian.ensure_can_see!(@category)
discourse_expires_in 1.minute
@title = @category.name
@link = "#{Discourse.base_url}#{@category.url}"
@description = "#{I18n.t('topics_in_category', category: @category.name)} #{@category.description}"
@atom_link = "#{Discourse.base_url}#{@category.url}.rss"
@description = "#{I18n.t('topics_in_category', category: @category.name)} #{@category.description}"
@topic_list = TopicQuery.new.list_new_in_category(@category)
render 'list', formats: [:rss]
@ -147,15 +147,15 @@ class ListController < ApplicationController
end
def category_top
top({ category: @category.id })
top(category: @category.id)
end
def category_none_top
top({ category: @category.id, no_subcategories: true })
top(category: @category.id, no_subcategories: true)
end
def parent_category_category_top
top({ category: @category.id })
top(category: @category.id)
end
TopTopic.periods.each do |period|
@ -163,6 +163,11 @@ class ListController < ApplicationController
top_options = build_topic_list_options
top_options.merge!(options) if options
top_options[:per_page] = SiteSetting.topics_per_period_in_top_page
if "top".freeze == current_homepage
top_options.merge!(exclude_category_ids: get_excluded_category_ids(top_options[:category]))
end
user = list_target_user
list = TopicQuery.new(user, top_options).list_top_for(period)
list.for_period = period
@ -177,15 +182,15 @@ class ListController < ApplicationController
end
define_method("category_top_#{period}") do
self.send("top_#{period}", { category: @category.id })
self.send("top_#{period}", category: @category.id)
end
define_method("category_none_top_#{period}") do
self.send("top_#{period}", { category: @category.id, no_subcategories: true })
self.send("top_#{period}", category: @category.id, no_subcategories: true)
end
define_method("parent_category_category_top_#{period}") do
self.send("top_#{period}", { category: @category.id })
self.send("top_#{period}", category: @category.id)
end
end
@ -204,16 +209,15 @@ class ListController < ApplicationController
end
end
private
def page_params(opts = nil)
opts ||= {}
route_params = {format: 'json'}
route_params[:category] = @category.slug_for_url if @category
route_params = { format: 'json' }
route_params[:category] = @category.slug_for_url if @category
route_params[:parent_category] = @category.parent_category.slug_for_url if @category && @category.parent_category
route_params[:order] = opts[:order] if opts[:order].present?
route_params[:ascending] = opts[:ascending] if opts[:ascending].present?
route_params[:order] = opts[:order] if opts[:order].present?
route_params[:ascending] = opts[:ascending] if opts[:ascending].present?
route_params
end
@ -235,11 +239,10 @@ class ListController < ApplicationController
end
def build_topic_list_options
# exclude_category = 1. from params / 2. parsed from top menu / 3. nil
options = {
page: params[:page],
topic_ids: param_to_integer_list(:topic_ids),
exclude_category: (params[:exclude_category] || select_menu_item.try(:filter)),
exclude_category_ids: params[:exclude_category_ids],
category: params[:category],
order: params[:order],
ascending: params[:ascending],
@ -257,17 +260,6 @@ class ListController < ApplicationController
options
end
def select_menu_item
menu_item = SiteSetting.top_menu_items.select do |mu|
(mu.has_specific_category? && mu.specific_category == @category.try(:slug)) ||
action_name == mu.name ||
(action_name.include?("top") && mu.name == "top")
end.first
menu_item = nil if menu_item.try(:has_specific_category?) && menu_item.specific_category == @category.try(:slug)
menu_item
end
def list_target_user
if params[:user_id] && guardian.is_staff?
User.find(params[:user_id].to_i)
@ -290,25 +282,16 @@ class ListController < ApplicationController
url.sub('.json?','?')
end
def generate_top_lists(options)
top = TopLists.new
options[:per_page] = SiteSetting.topics_per_period_in_top_summary
topic_query = TopicQuery.new(current_user, options)
periods = [ListController.best_period_for(current_user.try(:previous_visit_at), options[:category])]
periods.each { |period| top.send("#{period}=", topic_query.list_top_for(period)) }
top
def get_excluded_category_ids(current_category=nil)
exclude_category_ids = Category.where(suppress_from_homepage: true)
exclude_category_ids = exclude_category_ids.where.not(id: current_category) if current_category
exclude_category_ids.pluck(:id)
end
def self.best_period_for(previous_visit_at, category_id=nil)
best_periods_for(previous_visit_at).each do |period|
top_topics = TopTopic.where("#{period}_score > 0")
if category_id
top_topics = top_topics.joins(:topic).where("topics.category_id = ?", category_id)
end
top_topics = top_topics.joins(:topic).where("topics.category_id = ?", category_id) if category_id
return period if top_topics.count >= SiteSetting.topics_per_period_in_top_page
end
# default period is yearly
@ -318,8 +301,8 @@ class ListController < ApplicationController
def self.best_periods_for(date)
date ||= 1.year.ago
periods = []
periods << :daily if date > 8.days.ago
periods << :weekly if date > 35.days.ago
periods << :daily if date > 8.days.ago
periods << :weekly if date > 35.days.ago
periods << :monthly if date > 180.days.ago
periods << :yearly
periods

View File

@ -66,6 +66,8 @@ class CategoryList
@categories = @categories.where('categories.parent_category_id = ?', @options[:parent_category_id].to_i)
end
@categories = @categories.where(suppress_from_homepage: false) if @options[:is_homepage]
if SiteSetting.fixed_category_positions
@categories = @categories.order('position ASC').order('id ASC')
else

View File

@ -8,6 +8,7 @@ class CategorySerializer < BasicCategorySerializer
:position,
:email_in,
:email_in_allow_strangers,
:suppress_from_homepage,
:can_delete,
:cannot_delete_reason,
:allow_badges,
@ -56,4 +57,8 @@ class CategorySerializer < BasicCategorySerializer
scope && scope.can_edit?(object)
end
def include_suppress_from_homepage?
scope && scope.can_edit?(object)
end
end

View File

@ -1539,6 +1539,7 @@ en:
email_in_allow_strangers: "Accept emails from anonymous users with no accounts"
email_in_disabled: "Posting new topics via email is disabled in the Site Settings. To enable posting new topics via email, "
email_in_disabled_click: 'enable the "email in" setting.'
suppress_from_homepage: "Suppress this category from the homepage."
allow_badges_label: "Allow badges to be awarded in this category"
edit_permissions: "Edit Permissions"
add_permission: "Add Permission"

View File

@ -0,0 +1,5 @@
class AddSuppressFromHomepageToCategory < ActiveRecord::Migration
def change
add_column :categories, :suppress_from_homepage, :boolean, default: false
end
end

View File

@ -64,18 +64,10 @@ module Discourse
@filters ||= [:latest, :unread, :new, :read, :posted, :bookmarks]
end
def self.feed_filters
@feed_filters ||= [:latest]
end
def self.anonymous_filters
@anonymous_filters ||= [:latest, :top, :categories]
end
def self.logged_in_filters
@logged_in_filters ||= Discourse.filters - Discourse.anonymous_filters
end
def self.top_menu_items
@top_menu_items ||= Discourse.filters + [:category, :categories, :top]
end

View File

@ -1,15 +1,16 @@
#
# Helps us find topics. Returns a TopicList object containing the topics
# found.
# Helps us find topics.
# Returns a TopicList object containing the topics found.
#
require_dependency 'topic_list'
require_dependency 'suggested_topics_builder'
require_dependency 'topic_query_sql'
class TopicQuery
# Could be rewritten to %i if Ruby 1.9 is no longer supported
VALID_OPTIONS = %w(except_topic_ids
exclude_category
VALID_OPTIONS = %i(except_topic_ids
exclude_category_ids
limit
page
per_page
@ -27,8 +28,7 @@ class TopicQuery
search
slow_platform
filter
q
).map(&:to_sym)
q)
# Maps `order` to a columns in `topics`
SORTABLE_MAPPING = {
@ -301,14 +301,17 @@ class TopicQuery
if options[:no_subcategories]
result = result.where('categories.id = ?', category_id)
else
result = result.where('categories.id = ? or (categories.parent_category_id = ? AND categories.topic_id <> topics.id)', category_id, category_id)
result = result.where('categories.id = :category_id OR (categories.parent_category_id = :category_id AND categories.topic_id <> topics.id)', category_id: category_id)
end
result = result.references(:categories)
end
result = apply_ordering(result, options)
result = result.listable_topics.includes(:category)
result = result.where('categories.name is null or categories.name <> ?', options[:exclude_category]).references(:categories) if options[:exclude_category]
if options[:exclude_category_ids] && options[:exclude_category_ids].is_a?(Array) && options[:exclude_category_ids].size > 0
result = result.where("categories.id NOT IN (?)", options[:exclude_category_ids]).references(:categories)
end
# Don't include the category topics if excluded
if options[:no_definitions]
@ -393,19 +396,20 @@ class TopicQuery
def remove_muted_categories(list, user, opts=nil)
category_id = get_category_id(opts[:exclude]) if opts
if user
list = list.where("NOT EXISTS(
SELECT 1 FROM category_users cu
WHERE cu.user_id = ? AND
cu.category_id = topics.category_id AND
cu.notification_level = ? AND
cu.category_id <> ?
)",
user.id,
CategoryUser.notification_levels[:muted],
category_id || -1
)
.references('cu')
list = list.references("cu")
.where("
NOT EXISTS (
SELECT 1
FROM category_users cu
WHERE cu.user_id = :user_id
AND cu.category_id = topics.category_id
AND cu.notification_level = :muted
AND cu.category_id <> :category_id
)", user_id: user.id,
muted: CategoryUser.notification_levels[:muted],
category_id: category_id || -1)
end
list

View File

@ -32,12 +32,6 @@ describe ListController do
end
end
Discourse.logged_in_filters.each do |filter|
context "#{filter}" do
it { expect { xhr :get, filter }.to raise_error(Discourse::NotLoggedIn) }
end
end
it 'allows users to filter on a set of topic ids' do
p = create_post
@ -51,14 +45,10 @@ describe ListController do
describe 'RSS feeds' do
Discourse.feed_filters.each do |filter|
it 'renders RSS' do
get "#{filter}_feed", format: :rss
expect(response).to be_success
expect(response.content_type).to eq('application/rss+xml')
end
it 'renders RSS' do
get "latest_feed", format: :rss
expect(response).to be_success
expect(response.content_type).to eq('application/rss+xml')
end
end