diff --git a/app/assets/javascripts/discourse/widgets/hamburger-categories.js.es6 b/app/assets/javascripts/discourse/widgets/hamburger-categories.js.es6 index 56ffa45a99..af0c7be0ba 100644 --- a/app/assets/javascripts/discourse/widgets/hamburger-categories.js.es6 +++ b/app/assets/javascripts/discourse/widgets/hamburger-categories.js.es6 @@ -51,7 +51,7 @@ export default createWidget("hamburger-categories", { html(attrs) { const href = Discourse.getURL("/categories"); - const result = [ + let result = [ h( "li.heading", h( @@ -66,8 +66,23 @@ export default createWidget("hamburger-categories", { if (categories.length === 0) { return; } - return result.concat( + result = result.concat( categories.map(c => this.attach("hamburger-category", c)) ); + + if (attrs.showMore) { + result = result.concat( + h( + "li.footer", + h( + "a.d-link.more-link", + { attributes: { href } }, + I18n.t("categories.more") + ) + ) + ); + } + + return result; } }); diff --git a/app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6 b/app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6 index 66ef51a080..ca141b5fd4 100644 --- a/app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6 +++ b/app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6 @@ -176,20 +176,29 @@ export default createWidget("hamburger-menu", { }, listCategories() { - const hideUncategorized = !this.siteSettings.allow_uncategorized_topics; - const isStaff = Discourse.User.currentProp("staff"); + const maxCategoriesToDisplay = 6; + const categoriesList = this.site + .get("categoriesByCount") + .reject(c => c.parent_category_id); + let categories = []; + let showMore = categoriesList.length > maxCategoriesToDisplay; - const categories = this.site.get("categoriesList").reject(c => { - if (c.get("parentCategory.show_subcategory_list")) { - return true; - } - if (hideUncategorized && c.get("isUncategorizedCategory") && !isStaff) { - return true; - } - return false; - }); + if (this.currentUser) { + let categoryIds = this.currentUser.get("top_category_ids") || []; + categoryIds = categoryIds.concat(categoriesList.map(c => c.id)).uniq(); - return this.attach("hamburger-categories", { categories }); + showMore = categoryIds.length > maxCategoriesToDisplay; + categoryIds = categoryIds.slice(0, maxCategoriesToDisplay); + + categories = categoryIds.map(id => { + return categoriesList.find(c => c.id === id); + }); + } else { + showMore = categoriesList.length > maxCategoriesToDisplay; + categories = categoriesList.slice(0, maxCategoriesToDisplay); + } + + return this.attach("hamburger-categories", { categories, showMore }); }, footerLinks(prioritizeFaq, faqUrl) { diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss index 18c5083e8d..9410596337 100644 --- a/app/assets/stylesheets/common/base/menu-panel.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -108,6 +108,15 @@ } } + .category-links { + .footer { + clear: both; + display: block; + text-align: right; + padding-right: 12px; + } + } + // note these topic counts only appear for anons in the category hamburger drop down b.topics-count { color: dark-light-choose($primary-medium, $secondary-medium); diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb index 58a078c68d..a2fffd2187 100644 --- a/app/serializers/current_user_serializer.rb +++ b/app/serializers/current_user_serializer.rb @@ -2,6 +2,8 @@ require_dependency 'new_post_manager' class CurrentUserSerializer < BasicUserSerializer + MAX_TOP_CATEGORIES_COUNT = 6.freeze + attributes :name, :unread_notifications, :unread_private_messages, @@ -41,7 +43,8 @@ class CurrentUserSerializer < BasicUserSerializer :primary_group_name, :can_create_topic, :link_posting_access, - :external_id + :external_id, + :top_category_ids def link_posting_access scope.link_posting_access @@ -153,9 +156,20 @@ class CurrentUserSerializer < BasicUserSerializer end def muted_category_ids - @muted_category_ids ||= CategoryUser.where(user_id: object.id, - notification_level: TopicUser.notification_levels[:muted]) + CategoryUser.lookup(object, :muted).pluck(:category_id) + end + + def top_category_ids + CategoryUser.where(user_id: object.id) + .where.not(notification_level: CategoryUser.notification_levels[:muted]) + .order(" + CASE + WHEN notification_level = 3 THEN 1 + WHEN notification_level = 2 THEN 2 + WHEN notification_level = 4 THEN 3 + END") .pluck(:category_id) + .slice(0, MAX_TOP_CATEGORIES_COUNT) end def dismissed_banner_key diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index b42fbfd170..1d36c4c642 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -571,6 +571,7 @@ en: topic_stat_sentence: one: "%{count} new topic in the past %{unit}." other: "%{count} new topics in the past %{unit}." + more: "more" ip_lookup: title: IP Address Lookup diff --git a/spec/serializers/current_user_serializer_spec.rb b/spec/serializers/current_user_serializer_spec.rb index 5e11505bb0..46ca334f71 100644 --- a/spec/serializers/current_user_serializer_spec.rb +++ b/spec/serializers/current_user_serializer_spec.rb @@ -32,4 +32,31 @@ RSpec.describe CurrentUserSerializer do expect(payload[:external_id]).to eq("12345") end end + + context "#top_category_ids" do + let(:user) { Fabricate(:user) } + let(:category1) { Fabricate(:category) } + let(:category2) { Fabricate(:category) } + let :serializer do + CurrentUserSerializer.new(user, scope: Guardian.new, root: false) + end + + it "should include empty top_category_ids array" do + payload = serializer.as_json + expect(payload[:top_category_ids]).to eq([]) + end + + it "should include correct id in top_category_ids array" do + category = Category.first + CategoryUser.create!(user_id: user.id, + category_id: category1.id, + notification_level: CategoryUser.notification_levels[:tracking]) + + CategoryUser.create!(user_id: user.id, + category_id: category2.id, + notification_level: CategoryUser.notification_levels[:watching]) + payload = serializer.as_json + expect(payload[:top_category_ids]).to eq([category2.id, category1.id]) + end + end end diff --git a/test/javascripts/widgets/hamburger-menu-test.js.es6 b/test/javascripts/widgets/hamburger-menu-test.js.es6 index 1dbdfcca6c..04d30f5369 100644 --- a/test/javascripts/widgets/hamburger-menu-test.js.es6 +++ b/test/javascripts/widgets/hamburger-menu-test.js.es6 @@ -2,6 +2,9 @@ import { moduleForWidget, widgetTest } from "helpers/widget-test"; moduleForWidget("hamburger-menu"); +const maxCategoriesToDisplay = 6; +const topCategoryIds = [2, 3, 1]; + widgetTest("prioritize faq", { template: '{{mount-widget widget="hamburger-menu"}}', @@ -125,42 +128,40 @@ widgetTest("general links", { } }); -widgetTest("category links", { +widgetTest("top categories - anonymous", { template: '{{mount-widget widget="hamburger-menu"}}', anonymous: true, + test(assert) { + const count = this.site.get("categoriesByCount").length; + const maximum = + count <= maxCategoriesToDisplay ? count : maxCategoriesToDisplay; + assert.equal(find(".category-link").length, maximum); + } +}); + +widgetTest("top categories", { + template: '{{mount-widget widget="hamburger-menu"}}', + beforeEach() { - const cat = this.site.get("categoriesList")[0]; - - const parent = Discourse.Category.create({ - id: 1, - topic_count: 5, - name: "parent", - url: "https://test.com/parent", - show_subcategory_list: true, - topicTrackingState: cat.get("topicTrackingState") - }); - const child = Discourse.Category.create({ - id: 2, - parent_category_id: 1, - parentCategory: parent, - topic_count: 4, - name: "child", - url: "https://test.com/child", - topicTrackingState: cat.get("topicTrackingState") - }); - - parent.subcategories = [child]; - - const list = [parent, child]; - this.site.set("categoriesList", list); + this.currentUser.set("top_category_ids", topCategoryIds); }, test(assert) { - // if show_subcategory_list is enabled we suppress the categories from hamburger - // this means that people can be confused about counts - assert.equal(this.$(".category-link").length, 1); - assert.equal(this.$(".category-link .topics-count").text(), "9"); + assert.equal(find(".category-link").length, maxCategoriesToDisplay); + + const categoriesList = this.site + .get("categoriesByCount") + .reject(c => c.parent_category_id); + let ids = topCategoryIds + .concat(categoriesList.map(c => c.id)) + .uniq() + .slice(0, maxCategoriesToDisplay); + + assert.equal( + find(".category-link .category-name").text(), + ids.map(i => categoriesList.find(c => c.id === i).name).join("") + ); } });