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:
Robin Ward
2018-03-02 21:53:29 -05:00
parent 5e323139ae
commit 31e3bf6d8d
14 changed files with 147 additions and 83 deletions

View File

@ -1,3 +1,3 @@
export default Ember.Component.extend({
classNames: ['latest-topic-list']
classNames: ["categories-and-top"]
});

View File

@ -12,20 +12,24 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, {
this.render("discovery/categories", { outlet: "list-container" });
},
model() {
const style = !this.site.mobileView && this.siteSettings.desktop_category_page_style;
const parentCategory = this.get("model.parentCategory");
findCategories() {
let style = !this.site.mobileView &&
this.siteSettings.desktop_category_page_style;
let promise;
let parentCategory = this.get("model.parentCategory");
if (parentCategory) {
promise = CategoryList.listForParent(this.store, parentCategory);
return CategoryList.listForParent(this.store, parentCategory);
} else if (style === "categories_and_latest_topics") {
promise = this._loadCategoriesAndLatestTopics();
} else {
promise = CategoryList.list(this.store);
return this._findCategoriesAndTopics('latest');
} else if (style === "categories_and_top_topics") {
return this._findCategoriesAndTopics('top');
}
return promise.then(model => {
return CategoryList.list(this.store);
},
model() {
return this.findCategories().then(model => {
const tracking = this.topicTrackingState;
if (tracking) {
tracking.sync(model, "categories");
@ -35,26 +39,31 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, {
});
},
_loadCategoriesAndLatestTopics() {
const wrappedCategoriesList = PreloadStore.getAndRemove("categories_list");
const topicListLatest = PreloadStore.getAndRemove("topic_list_latest");
const categoriesList = wrappedCategoriesList && wrappedCategoriesList.category_list;
if (categoriesList && topicListLatest) {
return new Ember.RSVP.Promise(resolve => {
const result = Ember.Object.create({
categories: CategoryList.categoriesFrom(this.store, wrappedCategoriesList),
topics: TopicList.topicsFrom(this.store, topicListLatest),
_findCategoriesAndTopics(filter) {
return Ember.RSVP.hash({
wrappedCategoriesList: PreloadStore.getAndRemove("categories_list"),
topicsList: PreloadStore.getAndRemove(`topic_list_${filter}`)
}).then(hash => {
let { wrappedCategoriesList, topicsList } = hash;
let categoriesList = wrappedCategoriesList &&
wrappedCategoriesList.category_list;
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_topic: categoriesList.can_create_topic,
draft_key: categoriesList.draft_key,
draft: categoriesList.draft,
draft_sequence: categoriesList.draft_sequence
});
resolve(result);
});
} else {
return ajax("/categories_and_latest").then(result => {
}
// Otherwise, return the ajax result
return ajax(`/categories_and_${filter}`).then(result => {
return Ember.Object.create({
categories: CategoryList.categoriesFrom(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
});
});
}
});
},
titleToken() {

View File

@ -3,5 +3,5 @@
</div>
<div class='column'>
{{latest-topic-list topics=topics}}
{{categories-topic-list topics=topics filter="latest" class="latest-topic-list"}}
</div>

View File

@ -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>

View File

@ -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}}

View File

@ -1,3 +0,0 @@
{{#each topics as |t|}}
{{latest-topic-list-item topic=t}}
{{/each}}

View File

@ -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}}

View File

@ -110,7 +110,7 @@
}
}
.categories-and-latest {
.categories-and-latest, .categories-and-top {
display: flex;
flex-flow: row wrap;

View File

@ -1,4 +1,4 @@
.latest-topic-list {
.latest-topic-list, .top-topic-list {
@extend .topic-list-icons;
.table-heading {

View File

@ -2,11 +2,11 @@ require_dependency 'category_serializer'
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 :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
redirect_to path("/c/#{params[:path]}")
@ -27,7 +27,10 @@ class CategoriesController < ApplicationController
@category_list = CategoryList.new(guardian, category_options)
@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
@title = "#{I18n.t('js.filters.categories.title')} - #{SiteSetting.title}" unless category_options[:is_homepage]
@ -36,10 +39,23 @@ class CategoriesController < ApplicationController
format.html do
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
topic_options = { per_page: SiteSetting.categories_topics, no_definitions: true }
topic_list = TopicQuery.new(current_user, topic_options).list_latest
store_preloaded(topic_list.preload_key, MultiJson.dump(TopicListSerializer.new(topic_list, scope: guardian)))
style = SiteSetting.desktop_category_page_style
topic_options = {
per_page: SiteSetting.categories_topics,
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
render
@ -50,35 +66,11 @@ class CategoriesController < ApplicationController
end
def categories_and_latest
discourse_expires_in 1.minute
categories_and_topics(:latest)
end
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)
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)
def categories_and_top
categories_and_topics(:top)
end
def move
@ -208,6 +200,43 @@ class CategoriesController < ApplicationController
end
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
[:name, :color, :text_color]
@ -269,9 +298,11 @@ class CategoriesController < ApplicationController
end
def include_topics(parent_category = nil)
style = SiteSetting.desktop_category_page_style
view_context.mobile_view? ||
params[:include_topics] ||
(parent_category && parent_category.subcategory_list_includes_topics?) ||
SiteSetting.desktop_category_page_style == "categories_with_featured_topics".freeze
params[:include_topics] ||
(parent_category && parent_category.subcategory_list_includes_topics?) ||
style == "categories_with_featured_topics".freeze ||
style == "categories_with_top_topics".freeze
end
end

View File

@ -11,6 +11,7 @@ class CategoryPageStyle < EnumSiteSetting
{ 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_and_latest_topics', value: 'categories_and_latest_topics' },
{ name: 'category_page_style.categories_and_top_topics', value: 'categories_and_top_topics' },
]
end

View File

@ -1202,6 +1202,7 @@ en:
categories_only: "Categories Only"
categories_with_featured_topics: "Categories with Featured Topics"
categories_and_latest_topics: "Categories and Latest Topics"
categories_and_top_topics: "Categories and Top Topics"
shortcut_modifier_key:
shift: 'Shift'

View File

@ -542,6 +542,7 @@ Discourse::Application.routes.draw do
put "category/:category_id/slug" => "categories#update_slug"
get "categories_and_latest" => "categories#categories_and_latest"
get "categories_and_top" => "categories#categories_and_top"
get "c/:id/show" => "categories#show"
get "c/:category_slug/find_by_slug" => "categories#find_by_slug"

View File

@ -15,6 +15,21 @@ RSpec.describe ListController do
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
it 'supresses categories' do