From a5094411487f3fa5d1b7922717104ae4c80e5bfd Mon Sep 17 00:00:00 2001 From: Osama Sayegh Date: Mon, 27 Feb 2023 15:11:01 +0300 Subject: [PATCH] DEV: Include unread topics in New topic lists and link to it in sidebar (#20432) This commit introduces a few experimental changes to the New topics list and "Everything" link in the sidebar: 1. Make the New topics list include unread topics 2. Make the Everything section in the sidebar link to the New topics list (`/new`) 3. Remove "unread" or "new" text next to the count and keep the count 4. The count is a sum of new and unread topics counts All of these of changes are behind an off-by-default feature flag. I've not written extensive tests for these changes because they're highly experimental. Internal topic: t/77234. --- .../app/controllers/discovery/topics.js | 6 +++- .../everything-section-link.js | 30 +++++++++++++++---- .../discourse/app/models/nav-item.js | 4 +++ .../app/models/topic-tracking-state.js | 21 +++++++++++-- app/models/user.rb | 4 +++ app/serializers/current_user_serializer.rb | 3 +- config/locales/server.en.yml | 1 + config/site_settings.yml | 7 +++++ lib/topic_query.rb | 6 +++- 9 files changed, 70 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/discourse/app/controllers/discovery/topics.js b/app/assets/javascripts/discourse/app/controllers/discovery/topics.js index 3b95a44dee7..e1b7dc2c4cd 100644 --- a/app/assets/javascripts/discourse/app/controllers/discovery/topics.js +++ b/app/assets/javascripts/discourse/app/controllers/discovery/topics.js @@ -150,7 +150,6 @@ const controllerOpts = { hasTopics: gt("model.topics.length", 0), allLoaded: empty("model.more_topics_url"), latest: endWith("model.filter", "latest"), - new: endWith("model.filter", "new"), top: endWith("model.filter", "top"), yearly: equal("period", "yearly"), quarterly: equal("period", "quarterly"), @@ -158,6 +157,11 @@ const controllerOpts = { weekly: equal("period", "weekly"), daily: equal("period", "daily"), + @discourseComputed("model.filter") + new(filter) { + return filter?.endsWith("new") && !this.currentUser?.new_new_view_enabled; + }, + @discourseComputed("allLoaded", "model.topics.length") footerMessage(allLoaded, topicsLength) { if (!allLoaded) { diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/everything-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/everything-section-link.js index 08953f85c2e..44f5fc38953 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/everything-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/everything-section-link.js @@ -9,6 +9,7 @@ export default class EverythingSectionLink extends BaseSectionLink { @tracked totalNew = 0; @tracked hideCount = this.currentUser?.sidebarListDestination !== UNREAD_LIST_DESTINATION; + linkToNew = !!this.currentUser?.new_new_view_enabled; constructor() { super(...arguments); @@ -26,7 +27,7 @@ export default class EverythingSectionLink extends BaseSectionLink { this.totalUnread = this.topicTrackingState.countUnread(); - if (this.totalUnread === 0) { + if (this.totalUnread === 0 || this.linkToNew) { this.totalNew = this.topicTrackingState.countNew(); } } @@ -48,10 +49,17 @@ export default class EverythingSectionLink extends BaseSectionLink { } get currentWhen() { - return "discovery.latest discovery.new discovery.unread discovery.top"; + if (this.linkToNew) { + return "discovery.new"; + } else { + return "discovery.latest discovery.new discovery.unread discovery.top"; + } } get badgeText() { + if (this.linkToNew && this.#unreadAndNewCount > 0) { + return this.#unreadAndNewCount.toString(); + } if (this.hideCount) { return; } @@ -63,13 +71,15 @@ export default class EverythingSectionLink extends BaseSectionLink { return I18n.t("sidebar.new_count", { count: this.totalNew, }); - } else { - return; } } get route() { - if (this.currentUser?.sidebarListDestination === UNREAD_LIST_DESTINATION) { + if (this.linkToNew) { + return "discovery.new"; + } else if ( + this.currentUser?.sidebarListDestination === UNREAD_LIST_DESTINATION + ) { if (this.totalUnread > 0) { return "discovery.unread"; } @@ -93,8 +103,16 @@ export default class EverythingSectionLink extends BaseSectionLink { } get suffixValue() { - if (this.hideCount && (this.totalUnread || this.totalNew)) { + if ( + this.hideCount && + (this.totalUnread || this.totalNew) && + !this.linkToNew + ) { return "circle"; } } + + get #unreadAndNewCount() { + return this.totalUnread + this.totalNew; + } } diff --git a/app/assets/javascripts/discourse/app/models/nav-item.js b/app/assets/javascripts/discourse/app/models/nav-item.js index bfc3260832b..d676f65ff88 100644 --- a/app/assets/javascripts/discourse/app/models/nav-item.js +++ b/app/assets/javascripts/discourse/app/models/nav-item.js @@ -249,6 +249,10 @@ NavItem.reopenClass({ } let items = args.siteSettings.top_menu.split("|"); + const user = getOwner(this).lookup("service:current-user"); + if (user?.new_new_view_enabled) { + items = items.reject((item) => item === "unread"); + } const filterType = (args.filterMode || "").split("/").pop(); if (!items.some((i) => filterType === i)) { diff --git a/app/assets/javascripts/discourse/app/models/topic-tracking-state.js b/app/assets/javascripts/discourse/app/models/topic-tracking-state.js index b86ee96a948..e2605021726 100644 --- a/app/assets/javascripts/discourse/app/models/topic-tracking-state.js +++ b/app/assets/javascripts/discourse/app/models/topic-tracking-state.js @@ -709,12 +709,21 @@ const TopicTrackingState = EmberObject.extend({ let categoryId = category ? get(category, "id") : null; if (type === "new") { - return this.countNew({ + let count = this.countNew({ categoryId, tagId, noSubcategories, customFilterFn, }); + if (this.currentUser?.new_new_view_enabled) { + count += this.countUnread({ + categoryId, + tagId, + noSubcategories, + customFilterFn, + }); + } + return count; } else if (type === "unread") { return this.countUnread({ categoryId, @@ -790,8 +799,14 @@ const TopicTrackingState = EmberObject.extend({ // for a particular seen topic has not yet reached the server. _fixDelayedServerState(list, filter) { for (let index = list.topics.length - 1; index >= 0; index--) { - const state = this.findState(list.topics[index].id); - if (state && state.last_read_post_number > 0) { + const topic = list.topics[index]; + const state = this.findState(topic.id); + if ( + state && + state.last_read_post_number > 0 && + (topic.last_read_post_number === 0 || + !this.currentUser?.new_new_view_enabled) + ) { if (filter === "new") { list.topics.splice(index, 1); } else { diff --git a/app/models/user.rb b/app/models/user.rb index 92b2c72be26..7bcb9073dee 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1812,6 +1812,10 @@ class User < ActiveRecord::Base !SiteSetting.legacy_navigation_menu? || SiteSetting.enable_new_notifications_menu end + def new_new_view_enabled? + in_any_groups?(SiteSetting.experimental_new_new_view_groups_map) + end + protected def badge_grant diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb index 37d07d16330..a3b3df8548b 100644 --- a/app/serializers/current_user_serializer.rb +++ b/app/serializers/current_user_serializer.rb @@ -69,7 +69,8 @@ class CurrentUserSerializer < BasicUserSerializer :sidebar_category_ids, :sidebar_list_destination, :sidebar_sections, - :custom_sidebar_sections_enabled + :custom_sidebar_sections_enabled, + :new_new_view_enabled? delegate :user_stat, to: :object, private: true delegate :any_posts, :draft_count, :pending_posts_count, :read_faq?, to: :user_stat diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 32aa1a6d453..a192cbafcc7 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -2398,6 +2398,7 @@ en: default_sidebar_tags: "Selected tags will be displayed under Sidebar's Tags section by default." enable_new_notifications_menu: "Enables the new notifications menu for the legacy navigation menu." enable_experimental_hashtag_autocomplete: "EXPERIMENTAL: Use the new #hashtag autocompletion system for categories and tags that renders the selected item differently and has improved search" + experimental_new_new_view_groups: "EXPERIMENTAL: Enable a new topics list that combines unread and new topics and make the \"Everything\" link in the sidebar link to it." errors: invalid_css_color: "Invalid color. Enter a color name or hex value." diff --git a/config/site_settings.yml b/config/site_settings.yml index a942c53b524..e421bdb6df0 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -2073,6 +2073,13 @@ developer: client: true default: false hidden: true + experimental_new_new_view_groups: + client: true + type: group_list + list_type: compact + default: "" + allow_any: false + refresh: true navigation: navigation_menu: diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 4100ad39b10..b01bbca0cfb 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -267,7 +267,11 @@ class TopicQuery end def list_new - create_list(:new, { unordered: true }, new_results) + if @user&.new_new_view_enabled? + create_list(:new, { unordered: true }, new_and_unread_results) + else + create_list(:new, { unordered: true }, new_results) + end end def list_unread