mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: Display new/unread count for tracked categories in exp sidebar (#17046)
This commit is contained in:
parent
cd8c97debc
commit
946f8a65fd
@ -1,10 +1,20 @@
|
|||||||
|
import { cached } from "@glimmer/tracking";
|
||||||
|
|
||||||
import GlimmerComponent from "discourse/components/glimmer";
|
import GlimmerComponent from "discourse/components/glimmer";
|
||||||
import CategorySectionLink from "discourse/lib/sidebar/categories-section/category-section-link";
|
import CategorySectionLink from "discourse/lib/sidebar/categories-section/category-section-link";
|
||||||
|
|
||||||
export default class SidebarCategoriesSection extends GlimmerComponent {
|
export default class SidebarCategoriesSection extends GlimmerComponent {
|
||||||
|
@cached
|
||||||
get sectionLinks() {
|
get sectionLinks() {
|
||||||
return this.site.trackedCategoriesList.map((trackedCategory) => {
|
return this.site.trackedCategoriesList.map((trackedCategory) => {
|
||||||
return new CategorySectionLink({ category: trackedCategory });
|
return new CategorySectionLink({
|
||||||
|
category: trackedCategory,
|
||||||
|
topicTrackingState: this.topicTrackingState,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
willDestroy() {
|
||||||
|
this.sectionLinks.forEach((sectionLink) => sectionLink.teardown());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,42 @@
|
|||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
import { htmlSafe } from "@ember/template";
|
import { htmlSafe } from "@ember/template";
|
||||||
|
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
import { categoryBadgeHTML } from "discourse/helpers/category-link";
|
import { categoryBadgeHTML } from "discourse/helpers/category-link";
|
||||||
import Category from "discourse/models/category";
|
import Category from "discourse/models/category";
|
||||||
|
|
||||||
export default class CategorySectionLink {
|
export default class CategorySectionLink {
|
||||||
constructor({ category }) {
|
@tracked totalUnread = 0;
|
||||||
|
@tracked totalNew = 0;
|
||||||
|
|
||||||
|
constructor({ category, topicTrackingState }) {
|
||||||
this.category = category;
|
this.category = category;
|
||||||
|
this.topicTrackingState = topicTrackingState;
|
||||||
|
|
||||||
|
this.callbackId = this.topicTrackingState.onStateChange(
|
||||||
|
this._refreshCounts
|
||||||
|
);
|
||||||
|
|
||||||
|
this._refreshCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown() {
|
||||||
|
this.topicTrackingState.offStateChange(this.callbackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
_refreshCounts() {
|
||||||
|
this.totalUnread = this.topicTrackingState.countUnread({
|
||||||
|
categoryId: this.category.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.totalUnread === 0) {
|
||||||
|
this.totalNew = this.topicTrackingState.countNew({
|
||||||
|
categoryId: this.category.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
@ -31,4 +62,26 @@ export default class CategorySectionLink {
|
|||||||
get text() {
|
get text() {
|
||||||
return htmlSafe(categoryBadgeHTML(this.category, { link: false }));
|
return htmlSafe(categoryBadgeHTML(this.category, { link: false }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get badgeText() {
|
||||||
|
if (this.totalUnread > 0) {
|
||||||
|
return I18n.t("sidebar.unread_count", {
|
||||||
|
count: this.totalUnread,
|
||||||
|
});
|
||||||
|
} else if (this.totalNew > 0) {
|
||||||
|
return I18n.t("sidebar.new_count", {
|
||||||
|
count: this.totalNew,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get route() {
|
||||||
|
if (this.totalUnread > 0) {
|
||||||
|
return "discovery.unreadCategory";
|
||||||
|
} else if (this.totalNew > 0) {
|
||||||
|
return "discovery.newCategory";
|
||||||
|
} else {
|
||||||
|
return "discovery.latestCategory";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,12 @@ const Site = RestModel.extend({
|
|||||||
|
|
||||||
for (const category of categories) {
|
for (const category of categories) {
|
||||||
if (category.isTracked) {
|
if (category.isTracked) {
|
||||||
trackedCategories.push(category);
|
if (
|
||||||
|
!this.siteSettings.suppress_uncategorized_badge ||
|
||||||
|
category.id !== this.uncategorized_category_id
|
||||||
|
) {
|
||||||
|
trackedCategories.push(category);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,8 @@
|
|||||||
@title={{sectionLink.title}}
|
@title={{sectionLink.title}}
|
||||||
@content={{sectionLink.text}}
|
@content={{sectionLink.text}}
|
||||||
@currentWhen={{sectionLink.currentWhen}}
|
@currentWhen={{sectionLink.currentWhen}}
|
||||||
@model={{sectionLink.model}}>
|
@model={{sectionLink.model}}
|
||||||
|
@badgeText={{sectionLink.badgeText}} >
|
||||||
</Sidebar::SectionLink>
|
</Sidebar::SectionLink>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
|
||||||
import { click, currentURL, visit } from "@ember/test-helpers";
|
import { click, currentURL, settled, visit } from "@ember/test-helpers";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
acceptance,
|
acceptance,
|
||||||
conditionalTest,
|
conditionalTest,
|
||||||
exists,
|
exists,
|
||||||
|
publishToMessageBus,
|
||||||
query,
|
query,
|
||||||
queryAll,
|
queryAll,
|
||||||
} from "discourse/tests/helpers/qunit-helpers";
|
} from "discourse/tests/helpers/qunit-helpers";
|
||||||
@ -17,9 +18,57 @@ import discoveryFixture from "discourse/tests/fixtures/discovery-fixtures";
|
|||||||
import categoryFixture from "discourse/tests/fixtures/category-fixtures";
|
import categoryFixture from "discourse/tests/fixtures/category-fixtures";
|
||||||
import { cloneJSON } from "discourse-common/lib/object";
|
import { cloneJSON } from "discourse-common/lib/object";
|
||||||
|
|
||||||
|
acceptance(
|
||||||
|
"Sidebar - Categories Section - suppress_uncategorized_badge enabled",
|
||||||
|
function (needs) {
|
||||||
|
needs.settings({
|
||||||
|
suppress_uncategorized_badge: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
needs.user({ experimental_sidebar_enabled: true });
|
||||||
|
|
||||||
|
conditionalTest(
|
||||||
|
"uncategorized category is not shown",
|
||||||
|
!isLegacyEmber(),
|
||||||
|
async function (assert) {
|
||||||
|
const categories = Site.current().categories;
|
||||||
|
const category1 = categories[0];
|
||||||
|
|
||||||
|
const uncategorizedCategory = categories.find((category) => {
|
||||||
|
return category.id === Site.current().uncategorized_category_id;
|
||||||
|
});
|
||||||
|
|
||||||
|
category1.set("notification_level", NotificationLevels.TRACKING);
|
||||||
|
|
||||||
|
uncategorizedCategory.set(
|
||||||
|
"notification_level",
|
||||||
|
NotificationLevels.TRACKING
|
||||||
|
);
|
||||||
|
|
||||||
|
await visit("/");
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
queryAll(".sidebar-section-categories .sidebar-section-link").length,
|
||||||
|
1,
|
||||||
|
"there should only be one section link under the section"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
exists(`.sidebar-section-link-${category1.slug}`),
|
||||||
|
`only the ${category1.slug} section link is shown`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
acceptance("Sidebar - Categories Section", function (needs) {
|
acceptance("Sidebar - Categories Section", function (needs) {
|
||||||
needs.user({ experimental_sidebar_enabled: true });
|
needs.user({ experimental_sidebar_enabled: true });
|
||||||
|
|
||||||
|
needs.settings({
|
||||||
|
suppress_uncategorized_badge: false,
|
||||||
|
});
|
||||||
|
|
||||||
needs.pretender((server, helper) => {
|
needs.pretender((server, helper) => {
|
||||||
["latest", "top", "new", "unread"].forEach((type) => {
|
["latest", "top", "new", "unread"].forEach((type) => {
|
||||||
server.get(`/c/:categorySlug/:categoryId/l/${type}.json`, () => {
|
server.get(`/c/:categorySlug/:categoryId/l/${type}.json`, () => {
|
||||||
@ -77,6 +126,30 @@ acceptance("Sidebar - Categories Section", function (needs) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
conditionalTest(
|
||||||
|
"uncategorized category is shown when tracked",
|
||||||
|
!isLegacyEmber(),
|
||||||
|
async function (assert) {
|
||||||
|
const categories = Site.current().categories;
|
||||||
|
|
||||||
|
const uncategorizedCategory = categories.find((category) => {
|
||||||
|
return category.id === Site.current().uncategorized_category_id;
|
||||||
|
});
|
||||||
|
|
||||||
|
uncategorizedCategory.set(
|
||||||
|
"notification_level",
|
||||||
|
NotificationLevels.TRACKING
|
||||||
|
);
|
||||||
|
|
||||||
|
await visit("/");
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
exists(`.sidebar-section-link-${uncategorizedCategory.slug}`),
|
||||||
|
`displays the section link for ${uncategorizedCategory.slug} category`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
conditionalTest(
|
conditionalTest(
|
||||||
"category section links for tracked categories",
|
"category section links for tracked categories",
|
||||||
!isLegacyEmber(),
|
!isLegacyEmber(),
|
||||||
@ -263,4 +336,159 @@ acceptance("Sidebar - Categories Section", function (needs) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
conditionalTest(
|
||||||
|
"new and unread count for categories link",
|
||||||
|
!isLegacyEmber(),
|
||||||
|
async function (assert) {
|
||||||
|
const { category1, category2 } = setupTrackedCategories();
|
||||||
|
|
||||||
|
this.container.lookup("topic-tracking-state:main").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,
|
||||||
|
unread_not_too_old: 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,
|
||||||
|
unread_not_too_old: true,
|
||||||
|
treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
topic_id: 3,
|
||||||
|
highest_post_number: 15,
|
||||||
|
last_read_post_number: 14,
|
||||||
|
created_at: "2021-06-14T12:41:02.477Z",
|
||||||
|
category_id: category2.id,
|
||||||
|
notification_level: 2,
|
||||||
|
created_in_new_period: false,
|
||||||
|
unread_not_too_old: true,
|
||||||
|
treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
topic_id: 4,
|
||||||
|
highest_post_number: 17,
|
||||||
|
last_read_post_number: 16,
|
||||||
|
created_at: "2020-10-31T03:41:42.257Z",
|
||||||
|
category_id: category2.id,
|
||||||
|
notification_level: 2,
|
||||||
|
created_in_new_period: false,
|
||||||
|
unread_not_too_old: true,
|
||||||
|
treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await visit("/");
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
query(
|
||||||
|
`.sidebar-section-link-${category1.slug} .sidebar-section-link-content-badge`
|
||||||
|
).textContent.trim(),
|
||||||
|
I18n.t("sidebar.unread_count", { count: 1 }),
|
||||||
|
`displays 1 unread count for ${category1.slug} section link`
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
query(
|
||||||
|
`.sidebar-section-link-${category2.slug} .sidebar-section-link-content-badge`
|
||||||
|
).textContent.trim(),
|
||||||
|
I18n.t("sidebar.unread_count", { count: 2 }),
|
||||||
|
`displays 2 unread count for ${category2.slug} section link`
|
||||||
|
);
|
||||||
|
|
||||||
|
publishToMessageBus("/unread", {
|
||||||
|
topic_id: 2,
|
||||||
|
message_type: "read",
|
||||||
|
payload: {
|
||||||
|
last_read_post_number: 12,
|
||||||
|
highest_post_number: 12,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await settled();
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
query(
|
||||||
|
`.sidebar-section-link-${category1.slug} .sidebar-section-link-content-badge`
|
||||||
|
).textContent.trim(),
|
||||||
|
I18n.t("sidebar.new_count", { count: 1 }),
|
||||||
|
`displays 1 new count for ${category1.slug} section link`
|
||||||
|
);
|
||||||
|
|
||||||
|
publishToMessageBus("/unread", {
|
||||||
|
topic_id: 1,
|
||||||
|
message_type: "read",
|
||||||
|
payload: {
|
||||||
|
last_read_post_number: 1,
|
||||||
|
highest_post_number: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await settled();
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
!exists(
|
||||||
|
`.sidebar-section-link-${category1.slug} .sidebar-section-link-content-badge`
|
||||||
|
),
|
||||||
|
`does not display any badge ${category1.slug} section link`
|
||||||
|
);
|
||||||
|
|
||||||
|
publishToMessageBus("/unread", {
|
||||||
|
topic_id: 3,
|
||||||
|
message_type: "read",
|
||||||
|
payload: {
|
||||||
|
last_read_post_number: 15,
|
||||||
|
highest_post_number: 15,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await settled();
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
query(
|
||||||
|
`.sidebar-section-link-${category2.slug} .sidebar-section-link-content-badge`
|
||||||
|
).textContent.trim(),
|
||||||
|
I18n.t("sidebar.unread_count", { count: 1 }),
|
||||||
|
`displays 1 unread count for ${category2.slug} section link`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
conditionalTest(
|
||||||
|
"clean up topic tracking state state changed callbacks when section is destroyed",
|
||||||
|
!isLegacyEmber(),
|
||||||
|
async function (assert) {
|
||||||
|
setupTrackedCategories();
|
||||||
|
|
||||||
|
await visit("/");
|
||||||
|
|
||||||
|
const topicTrackingState = this.container.lookup(
|
||||||
|
"topic-tracking-state:main"
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialCallbackCount = Object.keys(
|
||||||
|
topicTrackingState.stateChangeCallbacks
|
||||||
|
).length;
|
||||||
|
|
||||||
|
await click(".header-sidebar-toggle .btn");
|
||||||
|
await click(".header-sidebar-toggle .btn");
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
Object.keys(topicTrackingState.stateChangeCallbacks).length,
|
||||||
|
initialCallbackCount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user