FEATURE: Always show All, Topics, and Replies subtabs for unified new (#35292)

Currently, if a user is part of a group in
`experimental_new_new_view_groups`,
they will see tabs for All, Topics, and Replies underneath the New tab
in the
/new  topic list.

However, if one of the tabs has no topics (i.e. no more unread replies
or no more new topics), we hide the tabs entirely. This can be confusing
and inconsistent for members.

This commit changes the behavior so that the tabs are always shown.
Also, we change the text and route of the empty topic list CTA based on
the subtab
the user is on:

* Topics subtab, when there are new Replies:
  * Button text: Browse new replies
  * Button points to the Replies subtab
* Replies subtab, when there are new Topics:
  * Button text: Browse new topics
  * Button points to Topics subtab
* All subtab, or any subtab when there are no new topics of any kind:
  * Button text: Browse latest topics
  * Button points to /latest
This commit is contained in:
Martin Brennan
2025-10-15 09:47:23 +10:00
committed by GitHub
parent df6a38a1e2
commit 66179c60cd
12 changed files with 178 additions and 129 deletions
@@ -33,7 +33,6 @@ export default class DiscoveryTopics extends Component {
@service documentTitle;
@service currentUser;
@service topicTrackingState;
@service site;
@tracked loadingNew;
@@ -184,11 +183,7 @@ export default class DiscoveryTopics extends Component {
}
get renderNewListHeaderControls() {
return (
this.site.mobileView &&
this.showTopicsAndRepliesToggle &&
!this.args.bulkSelectEnabled
);
return this.showTopicsAndRepliesToggle && !this.args.bulkSelectEnabled;
}
get expandGloballyPinned() {
@@ -327,12 +322,8 @@ export default class DiscoveryTopics extends Component {
@topics={{@model.topics}}
@discoveryList={{true}}
@focusLastVisitedTopic={{true}}
@showTopicsAndRepliesToggle={{this.showTopicsAndRepliesToggle}}
@newListSubset={{@model.params.subset}}
@changeNewListSubset={{@changeNewListSubset}}
@newRepliesCount={{this.newRepliesCount}}
@newTopicsCount={{this.newTopicsCount}}
/>
<LoadMore @action={{this.loadMore}} />
{{/if}}
@@ -413,6 +404,12 @@ export default class DiscoveryTopics extends Component {
<EmptyTopicFilter
@newFilter={{this.new}}
@unreadFilter={{this.unread}}
@trackingCounts={{hash
newTopics=this.newTopicsCount
newReplies=this.newRepliesCount
}}
@changeNewListSubset={{@changeNewListSubset}}
@newListSubset={{@model.params.subset}}
/>
{{/if}}
</:afterMessage>
@@ -21,12 +21,49 @@ export default class EmptyTopicFilter extends Component {
}
}
get ctaLabelWithAction() {
if (this.currentUser.new_new_view_enabled) {
if (this.args.newListSubset === "topics") {
if (this.args.trackingCounts.newReplies > 0) {
return {
action: () => this.args.changeNewListSubset("replies"),
label: i18n("topic.browse_new_replies"),
};
} else {
return { action: null, label: i18n("topic.browse_latest_topics") };
}
}
if (this.args.newListSubset === "replies") {
if (this.args.trackingCounts.newTopics > 0) {
return {
action: () => this.args.changeNewListSubset("topics"),
label: i18n("topic.browse_new_topics"),
};
} else {
return { action: null, label: i18n("topic.browse_latest_topics") };
}
}
}
return { action: null, label: i18n("topic.browse_latest_topics") };
}
get ctaRoute() {
if (this.currentUser.new_new_view_enabled && this.args.newListSubset) {
return;
}
return "discovery.latest";
}
<template>
<EmptyState
@identifier="empty-topic-filter"
@title={{this.educationText}}
@ctaLabel={{i18n "topic.browse_latest_topics"}}
@ctaRoute="discovery.latest"
@ctaLabel={{this.ctaLabelWithAction.label}}
@ctaRoute={{this.ctaRoute}}
@ctaAction={{this.ctaLabelWithAction.action}}
@tipIcon="circle-info"
@tipText={{htmlSafe
(i18n
@@ -19,12 +19,7 @@ const Header = <template>
@showBulkToggle={{@toggleInTitle}}
@canBulkSelect={{@canBulkSelect}}
@canDoBulkActions={{@canDoBulkActions}}
@showTopicsAndRepliesToggle={{@showTopicsAndRepliesToggle}}
@newListSubset={{@newListSubset}}
@newRepliesCount={{@newRepliesCount}}
@newTopicsCount={{@newTopicsCount}}
@bulkSelectHelper={{@bulkSelectHelper}}
@changeNewListSubset={{@changeNewListSubset}}
/>
{{/each}}
</tr>
@@ -3,7 +3,6 @@ import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { service } from "@ember/service";
import PluginOutlet from "discourse/components/plugin-outlet";
import NewListHeaderControls from "discourse/components/topic-list/new-list-header-controls";
import TopicBulkSelectDropdown from "discourse/components/topic-list/topic-bulk-select-dropdown";
import concatClass from "discourse/helpers/concat-class";
import icon from "discourse/helpers/d-icon";
@@ -116,14 +115,6 @@ export default class SortableColumn extends Component {
{{/if}}
{{#unless @bulkSelectEnabled}}
{{#if @showTopicsAndRepliesToggle}}
<NewListHeaderControls
@current={{@newListSubset}}
@newRepliesCount={{@newRepliesCount}}
@newTopicsCount={{@newTopicsCount}}
@changeNewListSubset={{@changeNewListSubset}}
/>
{{else}}
{{#if @sortable}}
<button aria-pressed={{this.isSorting}}>
{{this.localizedName}}
@@ -136,7 +127,6 @@ export default class SortableColumn extends Component {
{{this.localizedName}}
</span>
{{/if}}
{{/if}}
{{/unless}}
<PluginOutlet
@@ -12,12 +12,7 @@ const TopicCell = <template>
@showBulkToggle={{@showBulkToggle}}
@canBulkSelect={{@canBulkSelect}}
@canDoBulkActions={{@canDoBulkActions}}
@showTopicsAndRepliesToggle={{@showTopicsAndRepliesToggle}}
@newListSubset={{@newListSubset}}
@newRepliesCount={{@newRepliesCount}}
@newTopicsCount={{@newTopicsCount}}
@bulkSelectHelper={{@bulkSelectHelper}}
@changeNewListSubset={{@changeNewListSubset}}
/>
</template>;
@@ -208,11 +208,6 @@ export default class TopicList extends Component {
@bulkSelectHelper={{@bulkSelectHelper}}
@bulkSelectEnabled={{this.bulkSelectEnabled}}
@canDoBulkActions={{this.canDoBulkActions}}
@showTopicsAndRepliesToggle={{@showTopicsAndRepliesToggle}}
@newListSubset={{@newListSubset}}
@newRepliesCount={{@newRepliesCount}}
@newTopicsCount={{@newTopicsCount}}
@changeNewListSubset={{@changeNewListSubset}}
/>
</thead>
@@ -37,25 +37,7 @@ export default class NewListHeaderControls extends Component {
}
}
get staticLabel() {
if (
this.args.noStaticLabel ||
(this.args.newTopicsCount > 0 && this.args.newRepliesCount > 0)
) {
return;
}
if (this.args.newTopicsCount > 0) {
return this.topicsButtonLabel;
} else {
return this.repliesButtonLabel;
}
}
<template>
{{#if this.staticLabel}}
<span class="static-label">{{this.staticLabel}}</span>
{{else}}
<button
{{on "click" (fn @changeNewListSubset null)}}
class={{concatClass
@@ -88,6 +70,5 @@ export default class NewListHeaderControls extends Component {
>
{{this.repliesButtonLabel}}
</button>
{{/if}}
</template>
}
+14 -13
View File
@@ -77,19 +77,6 @@
}
}
.topics-replies-toggle {
background: none;
border: none;
line-height: var(--line-height-large);
padding-left: var(--space-2);
padding-right: var(--space-2);
position: relative;
&.active {
@include nav-active;
}
}
$td-posters-height: 29px; // min-height of td with avatar glow
$td-posters-more-lh: $td-posters-height - 4;
@@ -291,6 +278,20 @@
}
}
.topic-replies-toggle-wrapper {
.topics-replies-toggle {
background: none;
border: none;
line-height: var(--line-height-large);
padding: var(--space-2);
position: relative;
&.active {
@include nav-active;
}
}
}
@include viewport.until(lg) {
.nav-pills {
> li > a {
+2
View File
@@ -3701,6 +3701,8 @@ en:
browse_all_categories_latest_or_top: "<a href='%{basePath}/categories'>Browse all categories</a>, <a href='%{basePath}/latest'>view latest topics</a> or see top:"
browse_all_tags_or_latest: "<a href='%{basePath}/tags'>Browse all tags</a> or <a href='%{basePath}/latest'>view latest topics</a>."
browse_latest_topics: "Browse latest topics"
browse_new_replies: "Browse new replies"
browse_new_topics: "Browse new topics"
suggest_create_topic: Ready to <a href>start a new conversation?</a>
jump_reply: "Jump to post's original location"
@@ -0,0 +1,15 @@
# frozen_string_literal: true
module PageObjects
module Components
class EmptyState < PageObjects::Components::Base
def has_cta_text?(text)
has_css?(".empty-state__cta", text: text)
end
def click_cta
find(".empty-state__cta").click
end
end
end
end
@@ -13,6 +13,10 @@ module PageObjects
has_no_css?(COMMON_SELECTOR)
end
def rendered?
has_css?(COMMON_SELECTOR)
end
def all_tab
@all_tab ||= PageObjects::Components::NewTopicListToggleTab.new("all", ALL_SELECTOR)
end
@@ -1,6 +1,6 @@
# frozen_string_literal: true
describe "New topic list", type: :system do
describe "Unified new topic list", type: :system do
fab!(:user)
fab!(:group) { Fabricate(:group, users: [user]) }
fab!(:category)
@@ -26,11 +26,12 @@ describe "New topic list", type: :system do
let(:topic_list) { PageObjects::Components::TopicList.new }
let(:tabs_toggle) { PageObjects::Components::NewTopicListToggle.new }
let(:empty_state) { PageObjects::Components::EmptyState.new }
before { sign_in(user) }
shared_examples "new list new topics and replies toggle" do
context "when the new new view is enabled" do
context "when unified new is enabled" do
before { SiteSetting.experimental_new_new_view_groups = group.name }
it "shows all new topics and replies by default" do
@@ -302,7 +303,7 @@ describe "New topic list", type: :system do
end
end
context "when the new new view is not enabled" do
context "when unified new is not enabled" do
before { SiteSetting.experimental_new_new_view_groups = "" }
it "doesn't show the tabs toggle" do
@@ -324,15 +325,23 @@ describe "New topic list", type: :system do
end
end
it "keeps the Topics tab even when there are no new topics" do
it "shows All, Topics, and Replies tabs" do
visit("/new")
expect(tabs_toggle).to be_rendered
expect(tabs_toggle.all_tab).to be_visible
expect(tabs_toggle.replies_tab).to be_visible
expect(tabs_toggle.topics_tab).to be_visible
end
expect(tabs_toggle.replies_tab).to have_count(3)
expect(tabs_toggle.topics_tab).to have_count(0)
it "shows a CTA to show replies when on the new topics tab" do
visit("/new?subset=topics")
expect(empty_state).to have_cta_text(I18n.t("js.topic.browse_new_replies"))
empty_state.click_cta
expect(tabs_toggle.replies_tab).to be_active
expect(page).to have_current_path("/new?subset=replies")
end
end
@@ -345,15 +354,23 @@ describe "New topic list", type: :system do
end
end
it "keeps the Replies tab even when there are no new replies" do
it "shows All, Topics, and Replies tabs" do
visit("/new")
expect(tabs_toggle).to be_rendered
expect(tabs_toggle.all_tab).to be_visible
expect(tabs_toggle.replies_tab).to be_visible
expect(tabs_toggle.topics_tab).to be_visible
end
expect(tabs_toggle.replies_tab).to have_count(0)
expect(tabs_toggle.topics_tab).to have_count(3)
it "shows a CTA to show topics when on the new replies tab" do
visit("/new?subset=replies")
expect(empty_state).to have_cta_text(I18n.t("js.topic.browse_new_topics"))
empty_state.click_cta
expect(tabs_toggle.topics_tab).to be_active
expect(page).to have_current_path("/new?subset=topics")
end
end
end
@@ -370,13 +387,23 @@ describe "New topic list", type: :system do
end
end
it "doesn't render the toggle and only shows a static label for new topics" do
it "shows All, Topics, and Replies tabs" do
visit("/new")
expect(tabs_toggle).to be_not_rendered
expect(find(".topic-list-header .static-label").text).to eq(
I18n.t("js.filters.new.topics_with_count", count: 3),
)
expect(tabs_toggle).to be_rendered
expect(tabs_toggle.all_tab).to be_visible
expect(tabs_toggle.replies_tab).to be_visible
expect(tabs_toggle.topics_tab).to be_visible
end
it "shows a CTA to show topics when on the new replies tab" do
visit("/new?subset=replies")
expect(empty_state).to have_cta_text(I18n.t("js.topic.browse_new_topics"))
empty_state.click_cta
expect(tabs_toggle.topics_tab).to be_active
expect(page).to have_current_path("/new?subset=topics")
end
end
@@ -389,13 +416,23 @@ describe "New topic list", type: :system do
end
end
it "doesn't render the toggle and only shows a static label for new replies" do
it "shows All, Topics, and Replies tabs" do
visit("/new")
expect(tabs_toggle).to be_not_rendered
expect(find(".topic-list-header .static-label").text).to eq(
I18n.t("js.filters.new.replies_with_count", count: 3),
)
expect(tabs_toggle).to be_rendered
expect(tabs_toggle.all_tab).to be_visible
expect(tabs_toggle.replies_tab).to be_visible
expect(tabs_toggle.topics_tab).to be_visible
end
it "shows a CTA to show replies when on the new topics tab" do
visit("/new?subset=topics")
expect(empty_state).to have_cta_text(I18n.t("js.topic.browse_new_replies"))
empty_state.click_cta
expect(tabs_toggle.replies_tab).to be_active
expect(page).to have_current_path("/new?subset=replies")
end
end
end