From 6aab8cb331ac3349dcf02cab0ec9a449b87f185c Mon Sep 17 00:00:00 2001
From: Neil Lalonde <neillalonde@gmail.com>
Date: Thu, 2 Mar 2017 10:56:04 -0500
Subject: [PATCH] FEATURE: new category setting for whether to show latest
 topics or top topics by default

---
 .../components/edit-category-settings.js.es6  |  8 +++++++
 .../discourse/models/category.js.es6          |  3 ++-
 .../dynamic-route-builders.js.es6             |  6 ++---
 .../routes/build-category-route.js.es6        | 17 ++++++++-----
 .../components/edit-category-settings.hbs     |  7 ++++++
 app/controllers/categories_controller.rb      |  1 +
 app/controllers/list_controller.rb            | 10 ++++++++
 app/serializers/basic_category_serializer.rb  |  3 ++-
 config/locales/client.en.yml                  |  1 +
 config/routes.rb                              |  2 +-
 ...01215150_add_default_view_to_categories.rb |  5 ++++
 spec/controllers/list_controller_spec.rb      | 24 ++++++++++++++++++-
 12 files changed, 74 insertions(+), 13 deletions(-)
 create mode 100644 db/migrate/20170301215150_add_default_view_to_categories.rb

diff --git a/app/assets/javascripts/discourse/components/edit-category-settings.js.es6 b/app/assets/javascripts/discourse/components/edit-category-settings.js.es6
index 41a4d2b20f5..f1b089ac1ae 100644
--- a/app/assets/javascripts/discourse/components/edit-category-settings.js.es6
+++ b/app/assets/javascripts/discourse/components/edit-category-settings.js.es6
@@ -21,5 +21,13 @@ export default buildCategoryPanel('settings', {
       {name: I18n.t('category.sort_ascending'),  value: 'true'},
       {name: I18n.t('category.sort_descending'), value: 'false'}
     ];
+  },
+
+  @computed
+  availableViews() {
+    return [
+      {name: I18n.t('filters.latest.title'), value: 'latest'},
+      {name: I18n.t('filters.top.title'),    value: 'top'}
+    ];
   }
 });
diff --git a/app/assets/javascripts/discourse/models/category.js.es6 b/app/assets/javascripts/discourse/models/category.js.es6
index 8839be9bc8d..1ac1a9ca0c4 100644
--- a/app/assets/javascripts/discourse/models/category.js.es6
+++ b/app/assets/javascripts/discourse/models/category.js.es6
@@ -104,7 +104,8 @@ const Category = RestModel.extend({
         sort_ascending: this.get('sort_ascending'),
         topic_featured_link_allowed: this.get('topic_featured_link_allowed'),
         show_subcategory_list: this.get('show_subcategory_list'),
-        num_featured_topics: this.get('num_featured_topics')
+        num_featured_topics: this.get('num_featured_topics'),
+        default_view: this.get('default_view')
       },
       type: id ? 'PUT' : 'POST'
     });
diff --git a/app/assets/javascripts/discourse/pre-initializers/dynamic-route-builders.js.es6 b/app/assets/javascripts/discourse/pre-initializers/dynamic-route-builders.js.es6
index 93eddde83cf..6fef97d3454 100644
--- a/app/assets/javascripts/discourse/pre-initializers/dynamic-route-builders.js.es6
+++ b/app/assets/javascripts/discourse/pre-initializers/dynamic-route-builders.js.es6
@@ -13,9 +13,9 @@ export default {
     app.DiscoveryCategoryNoneController = DiscoverySortableController.extend();
     app.DiscoveryCategoryWithIDController = DiscoverySortableController.extend();
 
-    app.DiscoveryCategoryRoute = buildCategoryRoute('latest');
-    app.DiscoveryParentCategoryRoute = buildCategoryRoute('latest');
-    app.DiscoveryCategoryNoneRoute = buildCategoryRoute('latest', {no_subcategories: true});
+    app.DiscoveryCategoryRoute = buildCategoryRoute('default');
+    app.DiscoveryParentCategoryRoute = buildCategoryRoute('default');
+    app.DiscoveryCategoryNoneRoute = buildCategoryRoute('default', {no_subcategories: true});
 
     const site = Discourse.Site.current();
     site.get('filters').forEach(filter => {
diff --git a/app/assets/javascripts/discourse/routes/build-category-route.js.es6 b/app/assets/javascripts/discourse/routes/build-category-route.js.es6
index b7b41676325..0f8fb98d398 100644
--- a/app/assets/javascripts/discourse/routes/build-category-route.js.es6
+++ b/app/assets/javascripts/discourse/routes/build-category-route.js.es6
@@ -6,7 +6,7 @@ import CategoryList from 'discourse/models/category-list';
 import Category from 'discourse/models/category';
 
 // A helper function to create a category route with parameters
-export default (filter, params) => {
+export default (filterArg, params) => {
   return Discourse.Route.extend({
     queryParams,
 
@@ -37,9 +37,13 @@ export default (filter, params) => {
                           this._retrieveTopicList(model.category, transition)]);
     },
 
+    filter(category) {
+      return filterArg === 'default' ? (category.get('default_view') || 'latest') : filterArg;
+    },
+
     _setupNavigation(category) {
       const noSubcategories = params && !!params.no_subcategories,
-            filterMode = `c/${Discourse.Category.slugFor(category)}${noSubcategories ? "/none" : ""}/l/${filter}`;
+            filterMode = `c/${Discourse.Category.slugFor(category)}${noSubcategories ? "/none" : ""}/l/${this.filter(category)}`;
 
       this.controllerFor('navigation/category').setProperties({
         category,
@@ -60,7 +64,7 @@ export default (filter, params) => {
     },
 
     _retrieveTopicList(category, transition) {
-      const listFilter = `c/${Discourse.Category.slugFor(category)}/l/${filter}`,
+      const listFilter = `c/${Discourse.Category.slugFor(category)}/l/${this.filter(category)}`,
             findOpts = filterQueryParams(transition.queryParams, params),
              extras = { cached: this.isPoppedState(transition) };
 
@@ -72,8 +76,8 @@ export default (filter, params) => {
     },
 
     titleToken() {
-      const filterText = I18n.t('filters.' + filter.replace('/', '.') + '.title'),
-            category = this.currentModel.category;
+      const category = this.currentModel.category,
+            filterText = I18n.t('filters.' + this.filter(category).replace('/', '.') + '.title');
 
       return I18n.t('filters.with_category', { filter: filterText, category: category.get('name') });
     },
@@ -82,7 +86,8 @@ export default (filter, params) => {
       const topics = this.get('topics'),
             category = model.category,
             canCreateTopic = topics.get('can_create_topic'),
-            canCreateTopicOnCategory = category.get('permission') === PermissionType.FULL;
+            canCreateTopicOnCategory = category.get('permission') === PermissionType.FULL,
+            filter = this.filter(category);
 
       this.controllerFor('navigation/category').setProperties({
         canCreateTopicOnCategory: canCreateTopicOnCategory,
diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs
index 77862d06f3e..d3e6a73d3a4 100644
--- a/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs
+++ b/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs
@@ -56,6 +56,13 @@
   </label>
 </section>
 
+<section class="field default-view-field">
+  <label>
+    {{i18n "category.default_view"}}
+    {{combo-box valueAttribute="value" content=availableViews value=category.default_view}}
+  </label>
+</section>
+
 {{#if emailInEnabled}}
     <section class='field'>
       <label>
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb
index a787562065b..d11a8ad57bb 100644
--- a/app/controllers/categories_controller.rb
+++ b/app/controllers/categories_controller.rb
@@ -244,6 +244,7 @@ class CategoriesController < ApplicationController
                         :topic_featured_link_allowed,
                         :show_subcategory_list,
                         :num_featured_topics,
+                        :default_view,
                         :custom_fields => [params[:custom_fields].try(:keys)],
                         :permissions => [*p.try(:keys)],
                         :allowed_tags => [],
diff --git a/app/controllers/list_controller.rb b/app/controllers/list_controller.rb
index f2a4aabac77..431b68f1439 100644
--- a/app/controllers/list_controller.rb
+++ b/app/controllers/list_controller.rb
@@ -6,6 +6,7 @@ class ListController < ApplicationController
   skip_before_filter :check_xhr
 
   before_filter :set_category, only: [
+    :category_default,
     # filtered topics lists
     Discourse.filters.map { |f| :"category_#{f}" },
     Discourse.filters.map { |f| :"category_none_#{f}" },
@@ -29,6 +30,7 @@ class ListController < ApplicationController
     Discourse.anonymous_filters,
     Discourse.anonymous_filters.map { |f| "#{f}_feed" },
     # anonymous categorized filters
+    :category_default,
     Discourse.anonymous_filters.map { |f| :"category_#{f}" },
     Discourse.anonymous_filters.map { |f| :"category_none_#{f}" },
     Discourse.anonymous_filters.map { |f| :"parent_category_category_#{f}" },
@@ -106,6 +108,14 @@ class ListController < ApplicationController
     end
   end
 
+  def category_default
+    if @category.default_view == 'top'
+      top(category: @category.id)
+    else
+      self.send(@category.default_view || 'latest')
+    end
+  end
+
   def topics_by
     list_opts = build_topic_list_options
     target_user = fetch_user_from_params({ include_inactive: current_user.try(:staff?) }, [:user_stat, :user_option])
diff --git a/app/serializers/basic_category_serializer.rb b/app/serializers/basic_category_serializer.rb
index 81206a9c763..ab1cbac4a02 100644
--- a/app/serializers/basic_category_serializer.rb
+++ b/app/serializers/basic_category_serializer.rb
@@ -21,7 +21,8 @@ class BasicCategorySerializer < ApplicationSerializer
              :sort_order,
              :sort_ascending,
              :show_subcategory_list,
-             :num_featured_topics
+             :num_featured_topics,
+             :default_view
 
   has_one :uploaded_logo, embed: :object, serializer: CategoryUploadSerializer
   has_one :uploaded_background, embed: :object, serializer: CategoryUploadSerializer
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 0b31acdf64a..3f2a5f87c50 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -1974,6 +1974,7 @@ en:
       subcategory_num_featured_topics: "Number of featured topics on parent category's page:"
       all_topics_wiki: "Make new topics wikis by default."
       sort_order: "Default Sort:"
+      default_view: "Default View:"
       allow_badges_label: "Allow badges to be awarded in this category"
       edit_permissions: "Edit Permissions"
       add_permission: "Add Permission"
diff --git a/config/routes.rb b/config/routes.rb
index bc75ad0c621..2a1378e4cc9 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -491,7 +491,7 @@ Discourse::Application.routes.draw do
   get "c/:parent_category_slug/:category_slug/find_by_slug" => "categories#find_by_slug"
   get "c/:category.rss" => "list#category_feed", format: :rss
   get "c/:parent_category/:category.rss" => "list#category_feed", format: :rss
-  get "c/:category" => "list#category_latest"
+  get "c/:category" => "list#category_default", as: "category_default"
   get "c/:category/none" => "list#category_none_latest"
   get "c/:parent_category/:category/(:id)" => "list#parent_category_category_latest", constraints: { id: /\d+/ }
   get "c/:category/l/top" => "list#category_top", as: "category_top"
diff --git a/db/migrate/20170301215150_add_default_view_to_categories.rb b/db/migrate/20170301215150_add_default_view_to_categories.rb
new file mode 100644
index 00000000000..a70d1a7bab5
--- /dev/null
+++ b/db/migrate/20170301215150_add_default_view_to_categories.rb
@@ -0,0 +1,5 @@
+class AddDefaultViewToCategories < ActiveRecord::Migration
+  def change
+    add_column :categories, :default_view, :string, null: true, limit: 50
+  end
+end
diff --git a/spec/controllers/list_controller_spec.rb b/spec/controllers/list_controller_spec.rb
index 23c3d866abc..db81150915e 100644
--- a/spec/controllers/list_controller_spec.rb
+++ b/spec/controllers/list_controller_spec.rb
@@ -141,7 +141,6 @@ describe ListController do
 
           it { is_expected.not_to respond_with(:success) }
         end
-
       end
 
       describe 'feed' do
@@ -151,6 +150,29 @@ describe ListController do
           expect(response.content_type).to eq('application/rss+xml')
         end
       end
+
+      describe "category default views" do
+        it "top default view" do
+          category.update_attributes!(default_view: 'top')
+          described_class.expects(:best_period_for).returns('yearly')
+          xhr :get, :category_default, category: category.slug
+          expect(response).to be_success
+        end
+
+        it "default view is nil" do
+          category.update_attributes!(default_view: nil)
+          described_class.expects(:best_period_for).never
+          xhr :get, :category_default, category: category.slug
+          expect(response).to be_success
+        end
+
+        it "default view is latest" do
+          category.update_attributes!(default_view: 'latest')
+          described_class.expects(:best_period_for).never
+          xhr :get, :category_default, category: category.slug
+          expect(response).to be_success
+        end
+      end
     end
   end