From 2531828973aab651493e17935071401d0f381d92 Mon Sep 17 00:00:00 2001 From: Kris Date: Wed, 2 Nov 2022 07:55:05 -0400 Subject: [PATCH] UX: hide new/unread counts in sidebar, use dot by default (#18797) This updates the behavior of the list destination setting for links in the sidebar. By default, new/unread content will show a dot like chat, rather than the count of new/unread topics. If a user chooses to link to new/unread in the sidebar, we'll show the count. The goal here is to find a simple default for typical users (new/unread indication, no counts, default links) while providing a different workflow for power users (showing new/unread counts, and linking directly to new/unread). Internal Ref: /t/82626 --- .../sidebar/common/community-section.hbs | 5 +- .../sidebar/user/categories-section.hbs | 5 +- .../components/sidebar/user/tags-section.hbs | 5 +- .../app/controllers/preferences/sidebar.js | 4 + .../everything-section-link.js | 19 +++ .../category-section-link.js | 19 +++ .../my-posts-section-link.js | 19 ++- .../community-section/tracked-section-link.js | 19 +++ .../user/tags-section/tag-section-link.js | 19 +++ .../sidebar-user-categories-section-test.js | 76 +++++++++ .../sidebar-user-community-section-test.js | 146 ++++++++++++++++++ .../sidebar-user-tags-section-test.js | 91 +++++++++++ config/locales/client.en.yml | 10 +- 13 files changed, 428 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/sidebar/common/community-section.hbs b/app/assets/javascripts/discourse/app/components/sidebar/common/community-section.hbs index 2727a2093bf..8479c2324eb 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/common/community-section.hbs +++ b/app/assets/javascripts/discourse/app/components/sidebar/common/community-section.hbs @@ -25,7 +25,10 @@ @model={{sectionLink.model}} @models={{sectionLink.models}} @prefixType={{sectionLink.prefixType}} - @prefixValue={{sectionLink.prefixValue}} /> + @prefixValue={{sectionLink.prefixValue}} + @suffixCSSClass={{sectionLink.suffixCSSClass}} + @suffixValue={{sectionLink.suffixValue}} + @suffixType={{sectionLink.suffixType}}/> {{/each}} diff --git a/app/assets/javascripts/discourse/app/components/sidebar/user/categories-section.hbs b/app/assets/javascripts/discourse/app/components/sidebar/user/categories-section.hbs index e44793bc4aa..70f706a05af 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/user/categories-section.hbs +++ b/app/assets/javascripts/discourse/app/components/sidebar/user/categories-section.hbs @@ -21,7 +21,10 @@ @prefixType={{sectionLink.prefixType}} @prefixValue={{sectionLink.prefixValue}} @prefixColor={{sectionLink.prefixColor}} - @prefixElementColors={{sectionLink.prefixElementColors}} > + @prefixElementColors={{sectionLink.prefixElementColors}} + @suffixCSSClass={{sectionLink.suffixCSSClass}} + @suffixValue={{sectionLink.suffixValue}} + @suffixType={{sectionLink.suffixType}} > {{/each}} {{else}} diff --git a/app/assets/javascripts/discourse/app/components/sidebar/user/tags-section.hbs b/app/assets/javascripts/discourse/app/components/sidebar/user/tags-section.hbs index c097fe86c02..e044cea8e4b 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/user/tags-section.hbs +++ b/app/assets/javascripts/discourse/app/components/sidebar/user/tags-section.hbs @@ -17,7 +17,10 @@ @prefixType={{sectionLink.prefixType}} @prefixValue={{sectionLink.prefixValue}} @badgeText={{sectionLink.badgeText}} - @models={{sectionLink.models}} > + @models={{sectionLink.models}} + @suffixCSSClass={{sectionLink.suffixCSSClass}} + @suffixValue={{sectionLink.suffixValue}} + @suffixType={{sectionLink.suffixType}} > {{/each}} {{else}} diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/sidebar.js b/app/assets/javascripts/discourse/app/controllers/preferences/sidebar.js index 9e7f5b80cb8..d74cbdde0cf 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/sidebar.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/sidebar.js @@ -27,6 +27,7 @@ export default class extends Controller { @action save() { const initialSidebarCategoryIds = this.model.sidebarCategoryIds; + const initialSidebarListDestination = this.model.sidebar_list_destination; this.model.set( "sidebarCategoryIds", @@ -59,6 +60,9 @@ export default class extends Controller { }) .finally(() => { this.model.set("sidebar_tag_names", []); + if (initialSidebarListDestination !== this.newSidebarListDestination) { + window.location.reload(); + } }); } } 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 197c2f96697..08953f85c2e 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 @@ -7,6 +7,8 @@ import { UNREAD_LIST_DESTINATION } from "discourse/controllers/preferences/sideb export default class EverythingSectionLink extends BaseSectionLink { @tracked totalUnread = 0; @tracked totalNew = 0; + @tracked hideCount = + this.currentUser?.sidebarListDestination !== UNREAD_LIST_DESTINATION; constructor() { super(...arguments); @@ -50,6 +52,9 @@ export default class EverythingSectionLink extends BaseSectionLink { } get badgeText() { + if (this.hideCount) { + return; + } if (this.totalUnread > 0) { return I18n.t("sidebar.unread_count", { count: this.totalUnread, @@ -78,4 +83,18 @@ export default class EverythingSectionLink extends BaseSectionLink { get prefixValue() { return "layer-group"; } + + get suffixCSSClass() { + return "unread"; + } + + get suffixType() { + return "icon"; + } + + get suffixValue() { + if (this.hideCount && (this.totalUnread || this.totalNew)) { + return "circle"; + } + } } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/user/categories-section/category-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/user/categories-section/category-section-link.js index 5057dc399af..e855089d0af 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/user/categories-section/category-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/user/categories-section/category-section-link.js @@ -9,6 +9,8 @@ import { UNREAD_LIST_DESTINATION } from "discourse/controllers/preferences/sideb export default class CategorySectionLink { @tracked totalUnread = 0; @tracked totalNew = 0; + @tracked hideCount = + this.currentUser?.sidebarListDestination !== UNREAD_LIST_DESTINATION; constructor({ category, topicTrackingState, currentUser }) { this.category = category; @@ -69,6 +71,9 @@ export default class CategorySectionLink { } get badgeText() { + if (this.hideCount) { + return; + } if (this.totalUnread > 0) { return I18n.t("sidebar.unread_count", { count: this.totalUnread, @@ -91,4 +96,18 @@ export default class CategorySectionLink { } return "discovery.category"; } + + get suffixCSSClass() { + return "unread"; + } + + get suffixType() { + return "icon"; + } + + get suffixValue() { + if (this.hideCount && (this.totalUnread || this.totalNew)) { + return "circle"; + } + } } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/my-posts-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/my-posts-section-link.js index 9c11a9722b3..6c25a32e255 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/my-posts-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/my-posts-section-link.js @@ -2,11 +2,14 @@ import I18n from "I18n"; import { tracked } from "@glimmer/tracking"; import BaseSectionLink from "discourse/lib/sidebar/base-community-section-link"; +import { UNREAD_LIST_DESTINATION } from "discourse/controllers/preferences/sidebar"; const USER_DRAFTS_CHANGED_EVENT = "user-drafts:changed"; export default class MyPostsSectionLink extends BaseSectionLink { @tracked draftCount = this.currentUser.draft_count; + @tracked hideCount = + this.currentUser?.sidebarListDestination !== UNREAD_LIST_DESTINATION; constructor() { super(...arguments); @@ -52,7 +55,7 @@ export default class MyPostsSectionLink extends BaseSectionLink { } get badgeText() { - if (this._hasDraft) { + if (this._hasDraft && !this.hideCount) { return I18n.t("sidebar.sections.community.links.my_posts.draft_count", { count: this.draftCount, }); @@ -66,4 +69,18 @@ export default class MyPostsSectionLink extends BaseSectionLink { get prefixValue() { return "user"; } + + get suffixCSSClass() { + return "unread"; + } + + get suffixType() { + return "icon"; + } + + get suffixValue() { + if (this.hideCount) { + return "circle"; + } + } } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/tracked-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/tracked-section-link.js index 87d7d49f0dd..8eaeb471295 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/tracked-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/tracked-section-link.js @@ -8,6 +8,8 @@ import { UNREAD_LIST_DESTINATION } from "discourse/controllers/preferences/sideb export default class TrackedSectionLink extends BaseSectionLink { @tracked totalUnread = 0; @tracked totalNew = 0; + @tracked hideCount = + this.currentUser?.sidebarListDestination !== UNREAD_LIST_DESTINATION; constructor() { super(...arguments); @@ -51,6 +53,9 @@ export default class TrackedSectionLink extends BaseSectionLink { } get badgeText() { + if (this.hideCount) { + return; + } if (this.totalUnread > 0) { return I18n.t("sidebar.unread_count", { count: this.totalUnread, @@ -79,4 +84,18 @@ export default class TrackedSectionLink extends BaseSectionLink { get prefixValue() { return "bell"; } + + get suffixCSSClass() { + return "unread"; + } + + get suffixType() { + return "icon"; + } + + get suffixValue() { + if (this.hideCount && (this.totalUnread || this.totalNew)) { + return "circle"; + } + } } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/tag-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/tag-section-link.js index 1d46de97fd7..3d3c4121bae 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/tag-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/tag-section-link.js @@ -9,6 +9,8 @@ import { UNREAD_LIST_DESTINATION } from "discourse/controllers/preferences/sideb export default class TagSectionLink extends BaseTagSectionLink { @tracked totalUnread = 0; @tracked totalNew = 0; + @tracked hideCount = + this.currentUser?.sidebarListDestination !== UNREAD_LIST_DESTINATION; constructor({ topicTrackingState, currentUser }) { super(...arguments); @@ -51,6 +53,9 @@ export default class TagSectionLink extends BaseTagSectionLink { } get badgeText() { + if (this.hideCount) { + return; + } if (this.totalUnread > 0) { return I18n.t("sidebar.unread_count", { count: this.totalUnread, @@ -61,4 +66,18 @@ export default class TagSectionLink extends BaseTagSectionLink { }); } } + + get suffixCSSClass() { + return "unread"; + } + + get suffixType() { + return "icon"; + } + + get suffixValue() { + if (this.hideCount && (this.totalUnread || this.totalNew)) { + return "circle"; + } + } } diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-categories-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-categories-section-test.js index 1c4fe5086f3..98cf656cdf2 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-categories-section-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-categories-section-test.js @@ -527,9 +527,85 @@ acceptance("Sidebar - Logged on user - Categories Section", function (needs) { ); }); + test("show suffix indicator for unread and new content on categories link", async function (assert) { + const { category1 } = setupUserSidebarCategories(); + + updateCurrentUser({ + sidebar_list_destination: "default", + }); + + this.container.lookup("service:topic-tracking-state").loadStates([ + { + topic_id: 1, + highest_post_number: 1, + last_read_post_number: null, + created_at: "2022-05-11T03:09:31.959Z", + category_id: category1.id, + notification_level: null, + created_in_new_period: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { + topic_id: 2, + highest_post_number: 12, + last_read_post_number: 11, + created_at: "2020-02-09T09:40:02.672Z", + category_id: category1.id, + notification_level: 2, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + ]); + + await visit("/"); + + assert.ok( + exists( + `.sidebar-section-link-${category1.slug} .sidebar-section-link-suffix` + ), + "shows suffix indicator for unread content on categories link" + ); + + await publishToMessageBus("/unread", { + topic_id: 2, + message_type: "read", + payload: { + last_read_post_number: 12, + highest_post_number: 12, + }, + }); + + assert.ok( + exists( + `.sidebar-section-link-${category1.slug} .sidebar-section-link-suffix` + ), + "shows suffix indicator for new topics on categories link" + ); + + await publishToMessageBus("/unread", { + topic_id: 1, + message_type: "read", + payload: { + last_read_post_number: 1, + highest_post_number: 1, + }, + }); + + assert.ok( + !exists( + `.sidebar-section-link-${category1.slug} .sidebar-section-link-suffix` + ), + "hides suffix indicator when there's no new/unread content on category link" + ); + }); + test("new and unread count for categories link", async function (assert) { const { category1, category2 } = setupUserSidebarCategories(); + updateCurrentUser({ + sidebar_list_destination: "unread_new", + }); + this.container.lookup("service:topic-tracking-state").loadStates([ { topic_id: 1, diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-community-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-community-section-test.js index dbfdc5ebfd0..26f110febca 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-community-section-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-community-section-test.js @@ -749,7 +749,79 @@ acceptance("Sidebar - Logged on user - Community Section", function (needs) { ); }); + test("show suffix indicator for unread and new content on everything link", async function (assert) { + updateCurrentUser({ + sidebar_list_destination: "default", + }); + + this.container.lookup("service:topic-tracking-state").loadStates([ + { + topic_id: 1, + highest_post_number: 1, + last_read_post_number: null, + created_at: "2022-05-11T03:09:31.959Z", + category_id: 1, + notification_level: null, + created_in_new_period: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { + topic_id: 2, + highest_post_number: 12, + last_read_post_number: 11, + created_at: "2020-02-09T09:40:02.672Z", + category_id: 2, + notification_level: 2, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + ]); + + await visit("/"); + + assert.ok( + exists(".sidebar-section-link-everything .sidebar-section-link-suffix"), + "shows suffix indicator for unread posts on everything link" + ); + + // simulate reading topic 2 + await publishToMessageBus("/unread", { + topic_id: 2, + message_type: "read", + payload: { + last_read_post_number: 12, + highest_post_number: 12, + notification_level: 2, + }, + }); + + assert.ok( + exists(".sidebar-section-link-everything .sidebar-section-link-suffix"), + "shows suffix indicator for new topics on categories link" + ); + + // simulate reading topic 1 + await publishToMessageBus("/unread", { + topic_id: 1, + message_type: "read", + payload: { + last_read_post_number: 1, + highest_post_number: 1, + notification_level: 2, + }, + }); + + assert.ok( + !exists(".sidebar-section-link-everything .sidebar-section-link-suffix"), + "it removes the suffix indicator when all topics are read" + ); + }); + test("new and unread count for everything link", async function (assert) { + updateCurrentUser({ + sidebar_list_destination: "unread_new", + }); + this.container.lookup("service:topic-tracking-state").loadStates([ { topic_id: 1, @@ -1002,6 +1074,10 @@ acceptance("Sidebar - Logged on user - Community Section", function (needs) { const category = categories.find((c) => c.id === 1001); category.set("notification_level", NotificationLevels.TRACKING); + updateCurrentUser({ + sidebar_list_destination: "unread_new", + }); + this.container.lookup("service:topic-tracking-state").loadStates([ { topic_id: 1, @@ -1140,6 +1216,76 @@ acceptance("Sidebar - Logged on user - Community Section", function (needs) { ); }); + test("show suffix indicator for new content on tracked link", async function (assert) { + const categories = Site.current().categories; + + // Category id 1001 has two subcategories + const category = categories.find((c) => c.id === 1001); + category.set("notification_level", NotificationLevels.TRACKING); + + updateCurrentUser({ + sidebar_list_destination: "default", + }); + + this.container.lookup("service:topic-tracking-state").loadStates([ + { + topic_id: 1, + highest_post_number: 1, + last_read_post_number: null, + created_at: "2022-05-11T03:09:31.959Z", + category_id: category.id, + notification_level: NotificationLevels.TRACKING, + created_in_new_period: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { + topic_id: 2, + highest_post_number: 12, + last_read_post_number: 11, + created_at: "2020-02-09T09:40:02.672Z", + category_id: category.subcategories[0].id, + notification_level: NotificationLevels.TRACKING, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + ]); + + await visit("/"); + + assert.ok( + exists(".sidebar-section-link-tracked .sidebar-section-link-suffix") + ); + + // simulate reading topic id 2 + await publishToMessageBus("/unread", { + topic_id: 2, + message_type: "read", + payload: { + last_read_post_number: 12, + highest_post_number: 12, + }, + }); + + assert.ok( + exists(".sidebar-section-link-tracked .sidebar-section-link-suffix") + ); + + // simulate reading topic id 1 + await publishToMessageBus("/unread", { + topic_id: 1, + message_type: "read", + payload: { + last_read_post_number: 1, + highest_post_number: 1, + }, + }); + + assert.ok( + !exists(".sidebar-section-link-tracked .sidebar-section-link-suffix"), + "it removes the suffix indicator if there are no tracked new topics" + ); + }); + test("adding section link via plugin API with Object", async function (assert) { withPluginApi("1.2.0", (api) => { api.addCommunitySectionLink({ diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-tags-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-tags-section-test.js index 2e6da6ed49d..7b80114b9cc 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-tags-section-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-tags-section-test.js @@ -413,7 +413,98 @@ acceptance("Sidebar - Logged on user - Tags section", function (needs) { ); }); + test("show suffix indicator for new content on tag section links", async function (assert) { + updateCurrentUser({ + sidebar_list_destination: "default", + }); + + this.container.lookup("service:topic-tracking-state").loadStates([ + { + topic_id: 1, + highest_post_number: 1, + last_read_post_number: null, + created_at: "2022-05-11T03:09:31.959Z", + category_id: 1, + notification_level: null, + created_in_new_period: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + tags: ["tag1"], + }, + { + topic_id: 2, + highest_post_number: 12, + last_read_post_number: 11, + created_at: "2020-02-09T09:40:02.672Z", + category_id: 2, + notification_level: 2, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + tags: ["tag1"], + }, + { + topic_id: 3, + highest_post_number: 15, + last_read_post_number: 14, + created_at: "2021-06-14T12:41:02.477Z", + category_id: 3, + notification_level: 2, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + tags: ["tag2"], + }, + ]); + + await visit("/"); + + assert.ok( + exists(`.sidebar-section-link-tag1 .sidebar-section-link-suffix`), + "shows suffix indicator for new content on tag1 link" + ); + + assert.ok( + exists(`.sidebar-section-link-tag2 .sidebar-section-link-suffix`), + "shows suffix indicator for new content on tag2 link" + ); + + assert.ok( + !exists(`.sidebar-section-link-tag3 .sidebar-section-link-suffix`), + "hides suffix indicator when there's no new content on tag3 link" + ); + + await publishToMessageBus("/unread", { + topic_id: 2, + message_type: "read", + payload: { + last_read_post_number: 12, + highest_post_number: 12, + }, + }); + + assert.ok( + exists(`.sidebar-section-link-tag1 .sidebar-section-link-suffix`), + "shows suffix indicator for new topic on tag1 link" + ); + + await publishToMessageBus("/unread", { + topic_id: 1, + message_type: "read", + payload: { + last_read_post_number: 1, + highest_post_number: 1, + }, + }); + + assert.ok( + !exists(`.sidebar-section-link-tag1 .sidebar-section-link-suffix`), + "hides suffix indicator for tag1 section link" + ); + }); + test("new and unread count for tag section links", async function (assert) { + updateCurrentUser({ + sidebar_list_destination: "unread_new", + }); + this.container.lookup("service:topic-tracking-state").loadStates([ { topic_id: 1, diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index fe77f335f8b..27df40ec82a 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1043,7 +1043,7 @@ en: user_fields: none: "(select an option)" required: 'Please enter a value for "%{name}"' - same_as_password: 'Your password should not be repeated in other fields.' + same_as_password: "Your password should not be repeated in other fields." user: said: "%{username}:" @@ -1188,9 +1188,9 @@ en: tags_section: "Tags Section" tags_section_instruction: "Selected tags will be displayed under Sidebar's tags section." navigation_section: "Navigation" - list_destination_instruction: "When I click a topic list link in the sidebar with new or unread topics, take me to" - list_destination_default: "Default" - list_destination_unread_new: "New/Unread" + list_destination_instruction: "When there's new content in the sidebar..." + list_destination_default: "use the default link and show a badge for new items" + list_destination_unread_new: "link to unread/new and show a count of new items" change: "change" featured_topic: "Featured Topic" moderator: "%{user} is a moderator" @@ -4254,7 +4254,7 @@ en: welcome_topic_banner: title: "Create your Welcome Topic" - description: 'Your welcome topic is the first thing new members will read. Think of it as your “elevator pitch” or “mission statement.” Let everyone know who this community is for, what they can expect to find here, and what you’d like them to do first.' + description: "Your welcome topic is the first thing new members will read. Think of it as your “elevator pitch” or “mission statement.” Let everyone know who this community is for, what they can expect to find here, and what you’d like them to do first." button_title: "Start Editing" until: "Until:"