diff --git a/app/assets/javascripts/discourse/components/latest-topic-list.js.es6 b/app/assets/javascripts/discourse/components/categories-and-top-topics.js.es6 similarity index 54% rename from app/assets/javascripts/discourse/components/latest-topic-list.js.es6 rename to app/assets/javascripts/discourse/components/categories-and-top-topics.js.es6 index 664eb94c31e..4f1c8886929 100644 --- a/app/assets/javascripts/discourse/components/latest-topic-list.js.es6 +++ b/app/assets/javascripts/discourse/components/categories-and-top-topics.js.es6 @@ -1,3 +1,3 @@ export default Ember.Component.extend({ - classNames: ['latest-topic-list'] + classNames: ["categories-and-top"] }); diff --git a/app/assets/javascripts/discourse/routes/discovery-categories.js.es6 b/app/assets/javascripts/discourse/routes/discovery-categories.js.es6 index 0ba2194e501..9753a776eb6 100644 --- a/app/assets/javascripts/discourse/routes/discovery-categories.js.es6 +++ b/app/assets/javascripts/discourse/routes/discovery-categories.js.es6 @@ -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() { diff --git a/app/assets/javascripts/discourse/templates/components/categories-and-latest-topics.hbs b/app/assets/javascripts/discourse/templates/components/categories-and-latest-topics.hbs index 1a32cea92d3..be7ee3e4a31 100644 --- a/app/assets/javascripts/discourse/templates/components/categories-and-latest-topics.hbs +++ b/app/assets/javascripts/discourse/templates/components/categories-and-latest-topics.hbs @@ -3,5 +3,5 @@
- {{latest-topic-list topics=topics}} + {{categories-topic-list topics=topics filter="latest" class="latest-topic-list"}}
diff --git a/app/assets/javascripts/discourse/templates/components/categories-and-top-topics.hbs b/app/assets/javascripts/discourse/templates/components/categories-and-top-topics.hbs new file mode 100644 index 00000000000..4e84c4d6318 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/categories-and-top-topics.hbs @@ -0,0 +1,7 @@ +
+ {{categories-only categories=categories}} +
+ +
+ {{categories-topic-list topics=topics filter="top" class="top-topic-list"}} +
diff --git a/app/assets/javascripts/discourse/templates/components/categories-topic-list.hbs b/app/assets/javascripts/discourse/templates/components/categories-topic-list.hbs new file mode 100644 index 00000000000..7761050a38a --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/categories-topic-list.hbs @@ -0,0 +1,16 @@ +
+ {{i18n (concat "filters." filter ".title")}} +
+ +{{#if topics}} + {{#each topics as |t|}} + {{latest-topic-list-item topic=t}} + {{/each}} +
+ {{i18n "more"}} +
+{{else}} +
+

{{i18n (concat "topics.none." filter)}}

+
+{{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/latest-topic-list-contents.hbs b/app/assets/javascripts/discourse/templates/components/latest-topic-list-contents.hbs deleted file mode 100644 index 33b8ec95b90..00000000000 --- a/app/assets/javascripts/discourse/templates/components/latest-topic-list-contents.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{#each topics as |t|}} - {{latest-topic-list-item topic=t}} -{{/each}} diff --git a/app/assets/javascripts/discourse/templates/components/latest-topic-list.hbs b/app/assets/javascripts/discourse/templates/components/latest-topic-list.hbs deleted file mode 100644 index 4b339c3f3f0..00000000000 --- a/app/assets/javascripts/discourse/templates/components/latest-topic-list.hbs +++ /dev/null @@ -1,14 +0,0 @@ -
- {{i18n "filters.latest.title"}} -
- -{{#if topics}} - {{latest-topic-list-contents topics=topics tagName=""}} -
- {{i18n "more"}} -
-{{else}} -
-

{{i18n "topics.none.latest"}}

-
-{{/if}} diff --git a/app/assets/stylesheets/desktop/category-list.scss b/app/assets/stylesheets/desktop/category-list.scss index 9eb594f9e5a..f3263e96616 100644 --- a/app/assets/stylesheets/desktop/category-list.scss +++ b/app/assets/stylesheets/desktop/category-list.scss @@ -110,7 +110,7 @@ } } -.categories-and-latest { +.categories-and-latest, .categories-and-top { display: flex; flex-flow: row wrap; diff --git a/app/assets/stylesheets/desktop/latest-topic-list.scss b/app/assets/stylesheets/desktop/latest-topic-list.scss index 16cbb60b6b0..6cdb77df6b0 100644 --- a/app/assets/stylesheets/desktop/latest-topic-list.scss +++ b/app/assets/stylesheets/desktop/latest-topic-list.scss @@ -1,4 +1,4 @@ -.latest-topic-list { +.latest-topic-list, .top-topic-list { @extend .topic-list-icons; .table-heading { diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index 9c95a1da071..62163e2a174 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -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 diff --git a/app/models/category_page_style.rb b/app/models/category_page_style.rb index fe82ea95db0..d45376ad1f1 100644 --- a/app/models/category_page_style.rb +++ b/app/models/category_page_style.rb @@ -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 diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 7a3f86ab745..65ebd21d03c 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -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' diff --git a/config/routes.rb b/config/routes.rb index ed289687e3c..de27e9079aa 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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" diff --git a/spec/requests/list_controller_spec.rb b/spec/requests/list_controller_spec.rb index abe82cea777..e7a92f1f8be 100644 --- a/spec/requests/list_controller_spec.rb +++ b/spec/requests/list_controller_spec.rb @@ -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