mirror of
https://github.com/discourse/discourse.git
synced 2025-05-24 14:12:10 +08:00
FEATURE: New "Categories and Top" homepage style
Select this option if you want to show top topics on the homepage instead of latest topics.
This commit is contained in:
@ -1,3 +1,3 @@
|
|||||||
export default Ember.Component.extend({
|
export default Ember.Component.extend({
|
||||||
classNames: ['latest-topic-list']
|
classNames: ["categories-and-top"]
|
||||||
});
|
});
|
@ -12,20 +12,24 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, {
|
|||||||
this.render("discovery/categories", { outlet: "list-container" });
|
this.render("discovery/categories", { outlet: "list-container" });
|
||||||
},
|
},
|
||||||
|
|
||||||
model() {
|
findCategories() {
|
||||||
const style = !this.site.mobileView && this.siteSettings.desktop_category_page_style;
|
let style = !this.site.mobileView &&
|
||||||
const parentCategory = this.get("model.parentCategory");
|
this.siteSettings.desktop_category_page_style;
|
||||||
|
|
||||||
let promise;
|
let parentCategory = this.get("model.parentCategory");
|
||||||
if (parentCategory) {
|
if (parentCategory) {
|
||||||
promise = CategoryList.listForParent(this.store, parentCategory);
|
return CategoryList.listForParent(this.store, parentCategory);
|
||||||
} else if (style === "categories_and_latest_topics") {
|
} else if (style === "categories_and_latest_topics") {
|
||||||
promise = this._loadCategoriesAndLatestTopics();
|
return this._findCategoriesAndTopics('latest');
|
||||||
} else {
|
} else if (style === "categories_and_top_topics") {
|
||||||
promise = CategoryList.list(this.store);
|
return this._findCategoriesAndTopics('top');
|
||||||
}
|
}
|
||||||
|
|
||||||
return promise.then(model => {
|
return CategoryList.list(this.store);
|
||||||
|
},
|
||||||
|
|
||||||
|
model() {
|
||||||
|
return this.findCategories().then(model => {
|
||||||
const tracking = this.topicTrackingState;
|
const tracking = this.topicTrackingState;
|
||||||
if (tracking) {
|
if (tracking) {
|
||||||
tracking.sync(model, "categories");
|
tracking.sync(model, "categories");
|
||||||
@ -35,26 +39,31 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_loadCategoriesAndLatestTopics() {
|
_findCategoriesAndTopics(filter) {
|
||||||
const wrappedCategoriesList = PreloadStore.getAndRemove("categories_list");
|
return Ember.RSVP.hash({
|
||||||
const topicListLatest = PreloadStore.getAndRemove("topic_list_latest");
|
wrappedCategoriesList: PreloadStore.getAndRemove("categories_list"),
|
||||||
const categoriesList = wrappedCategoriesList && wrappedCategoriesList.category_list;
|
topicsList: PreloadStore.getAndRemove(`topic_list_${filter}`)
|
||||||
if (categoriesList && topicListLatest) {
|
}).then(hash => {
|
||||||
return new Ember.RSVP.Promise(resolve => {
|
let { wrappedCategoriesList, topicsList } = hash;
|
||||||
const result = Ember.Object.create({
|
let categoriesList = wrappedCategoriesList &&
|
||||||
categories: CategoryList.categoriesFrom(this.store, wrappedCategoriesList),
|
wrappedCategoriesList.category_list;
|
||||||
topics: TopicList.topicsFrom(this.store, topicListLatest),
|
|
||||||
|
if (categoriesList && topicsList) {
|
||||||
|
return Ember.Object.create({
|
||||||
|
categories: CategoryList.categoriesFrom(
|
||||||
|
this.store,
|
||||||
|
wrappedCategoriesList
|
||||||
|
),
|
||||||
|
topics: TopicList.topicsFrom(this.store, topicsList),
|
||||||
can_create_category: categoriesList.can_create_category,
|
can_create_category: categoriesList.can_create_category,
|
||||||
can_create_topic: categoriesList.can_create_topic,
|
can_create_topic: categoriesList.can_create_topic,
|
||||||
draft_key: categoriesList.draft_key,
|
draft_key: categoriesList.draft_key,
|
||||||
draft: categoriesList.draft,
|
draft: categoriesList.draft,
|
||||||
draft_sequence: categoriesList.draft_sequence
|
draft_sequence: categoriesList.draft_sequence
|
||||||
});
|
});
|
||||||
|
}
|
||||||
resolve(result);
|
// Otherwise, return the ajax result
|
||||||
});
|
return ajax(`/categories_and_${filter}`).then(result => {
|
||||||
} else {
|
|
||||||
return ajax("/categories_and_latest").then(result => {
|
|
||||||
return Ember.Object.create({
|
return Ember.Object.create({
|
||||||
categories: CategoryList.categoriesFrom(this.store, result),
|
categories: CategoryList.categoriesFrom(this.store, result),
|
||||||
topics: TopicList.topicsFrom(this.store, result),
|
topics: TopicList.topicsFrom(this.store, result),
|
||||||
@ -65,7 +74,7 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, {
|
|||||||
draft_sequence: result.category_list.draft_sequence
|
draft_sequence: result.category_list.draft_sequence
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
titleToken() {
|
titleToken() {
|
||||||
|
@ -3,5 +3,5 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class='column'>
|
<div class='column'>
|
||||||
{{latest-topic-list topics=topics}}
|
{{categories-topic-list topics=topics filter="latest" class="latest-topic-list"}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
<div class='column categories'>
|
||||||
|
{{categories-only categories=categories}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='column'>
|
||||||
|
{{categories-topic-list topics=topics filter="top" class="top-topic-list"}}
|
||||||
|
</div>
|
@ -0,0 +1,16 @@
|
|||||||
|
<div class='table-heading' aria-role="heading" aria-level="2">
|
||||||
|
{{i18n (concat "filters." filter ".title")}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if topics}}
|
||||||
|
{{#each topics as |t|}}
|
||||||
|
{{latest-topic-list-item topic=t}}
|
||||||
|
{{/each}}
|
||||||
|
<div class="more-topics">
|
||||||
|
<a href="/{{filter}}" class="btn pull-right">{{i18n "more"}}</a>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class='no-topics'>
|
||||||
|
<h3>{{i18n (concat "topics.none." filter)}}</h3>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
@ -1,3 +0,0 @@
|
|||||||
{{#each topics as |t|}}
|
|
||||||
{{latest-topic-list-item topic=t}}
|
|
||||||
{{/each}}
|
|
@ -1,14 +0,0 @@
|
|||||||
<div class='table-heading' aria-role="heading" aria-level="2">
|
|
||||||
{{i18n "filters.latest.title"}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if topics}}
|
|
||||||
{{latest-topic-list-contents topics=topics tagName=""}}
|
|
||||||
<div class="more-topics">
|
|
||||||
<a href="/latest" class="btn pull-right">{{i18n "more"}}</a>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div class='no-topics'>
|
|
||||||
<h3>{{i18n "topics.none.latest"}}</h3>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
@ -110,7 +110,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.categories-and-latest {
|
.categories-and-latest, .categories-and-top {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
.latest-topic-list {
|
.latest-topic-list, .top-topic-list {
|
||||||
@extend .topic-list-icons;
|
@extend .topic-list-icons;
|
||||||
|
|
||||||
.table-heading {
|
.table-heading {
|
||||||
|
@ -2,11 +2,11 @@ require_dependency 'category_serializer'
|
|||||||
|
|
||||||
class CategoriesController < ApplicationController
|
class CategoriesController < ApplicationController
|
||||||
|
|
||||||
requires_login except: [:index, :categories_and_latest, :show, :redirect, :find_by_slug]
|
requires_login except: [:index, :categories_and_latest, :categories_and_top, :show, :redirect, :find_by_slug]
|
||||||
|
|
||||||
before_action :fetch_category, only: [:show, :update, :destroy]
|
before_action :fetch_category, only: [:show, :update, :destroy]
|
||||||
before_action :initialize_staff_action_logger, only: [:create, :update, :destroy]
|
before_action :initialize_staff_action_logger, only: [:create, :update, :destroy]
|
||||||
skip_before_action :check_xhr, only: [:index, :categories_and_latest, :redirect]
|
skip_before_action :check_xhr, only: [:index, :categories_and_latest, :categories_and_top, :redirect]
|
||||||
|
|
||||||
def redirect
|
def redirect
|
||||||
redirect_to path("/c/#{params[:path]}")
|
redirect_to path("/c/#{params[:path]}")
|
||||||
@ -27,7 +27,10 @@ class CategoriesController < ApplicationController
|
|||||||
|
|
||||||
@category_list = CategoryList.new(guardian, category_options)
|
@category_list = CategoryList.new(guardian, category_options)
|
||||||
@category_list.draft_key = Draft::NEW_TOPIC
|
@category_list.draft_key = Draft::NEW_TOPIC
|
||||||
@category_list.draft_sequence = DraftSequence.current(current_user, Draft::NEW_TOPIC)
|
@category_list.draft_sequence = DraftSequence.current(
|
||||||
|
current_user,
|
||||||
|
Draft::NEW_TOPIC
|
||||||
|
)
|
||||||
@category_list.draft = Draft.get(current_user, Draft::NEW_TOPIC, @category_list.draft_sequence) if current_user
|
@category_list.draft = Draft.get(current_user, Draft::NEW_TOPIC, @category_list.draft_sequence) if current_user
|
||||||
|
|
||||||
@title = "#{I18n.t('js.filters.categories.title')} - #{SiteSetting.title}" unless category_options[:is_homepage]
|
@title = "#{I18n.t('js.filters.categories.title')} - #{SiteSetting.title}" unless category_options[:is_homepage]
|
||||||
@ -36,10 +39,23 @@ class CategoriesController < ApplicationController
|
|||||||
format.html do
|
format.html do
|
||||||
store_preloaded(@category_list.preload_key, MultiJson.dump(CategoryListSerializer.new(@category_list, scope: guardian)))
|
store_preloaded(@category_list.preload_key, MultiJson.dump(CategoryListSerializer.new(@category_list, scope: guardian)))
|
||||||
|
|
||||||
if SiteSetting.desktop_category_page_style == "categories_and_latest_topics".freeze
|
style = SiteSetting.desktop_category_page_style
|
||||||
topic_options = { per_page: SiteSetting.categories_topics, no_definitions: true }
|
topic_options = {
|
||||||
topic_list = TopicQuery.new(current_user, topic_options).list_latest
|
per_page: SiteSetting.categories_topics,
|
||||||
store_preloaded(topic_list.preload_key, MultiJson.dump(TopicListSerializer.new(topic_list, scope: guardian)))
|
no_definitions: true
|
||||||
|
}
|
||||||
|
|
||||||
|
if style == "categories_and_latest_topics".freeze
|
||||||
|
@topic_list = TopicQuery.new(current_user, topic_options).list_latest
|
||||||
|
elsif style == "categories_and_top_topics".freeze
|
||||||
|
@topic_list = TopicQuery.new(nil, topic_options).list_top_for(SiteSetting.top_page_default_timeframe.to_sym)
|
||||||
|
end
|
||||||
|
|
||||||
|
if @topic_list.present?
|
||||||
|
store_preloaded(
|
||||||
|
@topic_list.preload_key,
|
||||||
|
MultiJson.dump(TopicListSerializer.new(@topic_list, scope: guardian))
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
render
|
render
|
||||||
@ -50,35 +66,11 @@ class CategoriesController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def categories_and_latest
|
def categories_and_latest
|
||||||
discourse_expires_in 1.minute
|
categories_and_topics(:latest)
|
||||||
|
end
|
||||||
|
|
||||||
category_options = {
|
def categories_and_top
|
||||||
is_homepage: current_homepage == "categories".freeze,
|
categories_and_topics(:top)
|
||||||
parent_category_id: params[:parent_category_id],
|
|
||||||
include_topics: false
|
|
||||||
}
|
|
||||||
|
|
||||||
topic_options = {
|
|
||||||
per_page: SiteSetting.categories_topics,
|
|
||||||
no_definitions: true,
|
|
||||||
exclude_category_ids: Category.where(suppress_from_latest: true).pluck(:id)
|
|
||||||
}
|
|
||||||
|
|
||||||
result = CategoryAndTopicLists.new
|
|
||||||
result.category_list = CategoryList.new(guardian, category_options)
|
|
||||||
result.topic_list = TopicQuery.new(current_user, topic_options).list_latest
|
|
||||||
|
|
||||||
draft_key = Draft::NEW_TOPIC
|
|
||||||
draft_sequence = DraftSequence.current(current_user, draft_key)
|
|
||||||
draft = Draft.get(current_user, draft_key, draft_sequence) if current_user
|
|
||||||
|
|
||||||
%w{category topic}.each do |type|
|
|
||||||
result.send(:"#{type}_list").draft = draft
|
|
||||||
result.send(:"#{type}_list").draft_key = draft_key
|
|
||||||
result.send(:"#{type}_list").draft_sequence = draft_sequence
|
|
||||||
end
|
|
||||||
|
|
||||||
render_serialized(result, CategoryAndTopicListsSerializer, root: false)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def move
|
def move
|
||||||
@ -208,6 +200,43 @@ class CategoriesController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
def categories_and_topics(topics_filter)
|
||||||
|
discourse_expires_in 1.minute
|
||||||
|
|
||||||
|
category_options = {
|
||||||
|
is_homepage: current_homepage == "categories".freeze,
|
||||||
|
parent_category_id: params[:parent_category_id],
|
||||||
|
include_topics: false
|
||||||
|
}
|
||||||
|
|
||||||
|
topic_options = {
|
||||||
|
per_page: SiteSetting.categories_topics,
|
||||||
|
no_definitions: true,
|
||||||
|
exclude_category_ids: Category.where(suppress_from_latest: true).pluck(:id)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = CategoryAndTopicLists.new
|
||||||
|
result.category_list = CategoryList.new(guardian, category_options)
|
||||||
|
|
||||||
|
if topics_filter == :latest
|
||||||
|
result.topic_list = TopicQuery.new(current_user, topic_options).list_latest
|
||||||
|
elsif topics_filter == :top
|
||||||
|
result.topic_list = TopicQuery.new(nil, topic_options).list_top_for(SiteSetting.top_page_default_timeframe.to_sym)
|
||||||
|
end
|
||||||
|
|
||||||
|
draft_key = Draft::NEW_TOPIC
|
||||||
|
draft_sequence = DraftSequence.current(current_user, draft_key)
|
||||||
|
draft = Draft.get(current_user, draft_key, draft_sequence) if current_user
|
||||||
|
|
||||||
|
%w{category topic}.each do |type|
|
||||||
|
result.send(:"#{type}_list").draft = draft
|
||||||
|
result.send(:"#{type}_list").draft_key = draft_key
|
||||||
|
result.send(:"#{type}_list").draft_sequence = draft_sequence
|
||||||
|
end
|
||||||
|
|
||||||
|
render_serialized(result, CategoryAndTopicListsSerializer, root: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def required_param_keys
|
def required_param_keys
|
||||||
[:name, :color, :text_color]
|
[:name, :color, :text_color]
|
||||||
@ -269,9 +298,11 @@ class CategoriesController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def include_topics(parent_category = nil)
|
def include_topics(parent_category = nil)
|
||||||
|
style = SiteSetting.desktop_category_page_style
|
||||||
view_context.mobile_view? ||
|
view_context.mobile_view? ||
|
||||||
params[:include_topics] ||
|
params[:include_topics] ||
|
||||||
(parent_category && parent_category.subcategory_list_includes_topics?) ||
|
(parent_category && parent_category.subcategory_list_includes_topics?) ||
|
||||||
SiteSetting.desktop_category_page_style == "categories_with_featured_topics".freeze
|
style == "categories_with_featured_topics".freeze ||
|
||||||
|
style == "categories_with_top_topics".freeze
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -11,6 +11,7 @@ class CategoryPageStyle < EnumSiteSetting
|
|||||||
{ name: 'category_page_style.categories_only', value: 'categories_only' },
|
{ name: 'category_page_style.categories_only', value: 'categories_only' },
|
||||||
{ name: 'category_page_style.categories_with_featured_topics', value: 'categories_with_featured_topics' },
|
{ name: 'category_page_style.categories_with_featured_topics', value: 'categories_with_featured_topics' },
|
||||||
{ name: 'category_page_style.categories_and_latest_topics', value: 'categories_and_latest_topics' },
|
{ name: 'category_page_style.categories_and_latest_topics', value: 'categories_and_latest_topics' },
|
||||||
|
{ name: 'category_page_style.categories_and_top_topics', value: 'categories_and_top_topics' },
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1202,6 +1202,7 @@ en:
|
|||||||
categories_only: "Categories Only"
|
categories_only: "Categories Only"
|
||||||
categories_with_featured_topics: "Categories with Featured Topics"
|
categories_with_featured_topics: "Categories with Featured Topics"
|
||||||
categories_and_latest_topics: "Categories and Latest Topics"
|
categories_and_latest_topics: "Categories and Latest Topics"
|
||||||
|
categories_and_top_topics: "Categories and Top Topics"
|
||||||
|
|
||||||
shortcut_modifier_key:
|
shortcut_modifier_key:
|
||||||
shift: 'Shift'
|
shift: 'Shift'
|
||||||
|
@ -542,6 +542,7 @@ Discourse::Application.routes.draw do
|
|||||||
put "category/:category_id/slug" => "categories#update_slug"
|
put "category/:category_id/slug" => "categories#update_slug"
|
||||||
|
|
||||||
get "categories_and_latest" => "categories#categories_and_latest"
|
get "categories_and_latest" => "categories#categories_and_latest"
|
||||||
|
get "categories_and_top" => "categories#categories_and_top"
|
||||||
|
|
||||||
get "c/:id/show" => "categories#show"
|
get "c/:id/show" => "categories#show"
|
||||||
get "c/:category_slug/find_by_slug" => "categories#find_by_slug"
|
get "c/:category_slug/find_by_slug" => "categories#find_by_slug"
|
||||||
|
@ -15,6 +15,21 @@ RSpec.describe ListController do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "categories and X" do
|
||||||
|
it "returns top topics" do
|
||||||
|
Fabricate(:topic, like_count: 1000, posts_count: 100)
|
||||||
|
TopTopic.refresh!
|
||||||
|
|
||||||
|
get "/categories_and_top.json"
|
||||||
|
data = JSON.parse(response.body)
|
||||||
|
expect(data["topic_list"]["topics"].length).to eq(1)
|
||||||
|
|
||||||
|
get "/categories_and_latest.json"
|
||||||
|
data = JSON.parse(response.body)
|
||||||
|
expect(data["topic_list"]["topics"].length).to eq(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'suppress from latest' do
|
describe 'suppress from latest' do
|
||||||
|
|
||||||
it 'supresses categories' do
|
it 'supresses categories' do
|
||||||
|
Reference in New Issue
Block a user