DEV: Display new/unread count for tracked link in sidebar (#16957)

This commit is contained in:
Alan Guo Xiang Tan 2022-06-03 15:48:35 +08:00 committed by GitHub
parent 0fa0094531
commit 3b3f60218e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 360 additions and 39 deletions

View File

@ -17,15 +17,17 @@ const DEFAULT_SECTION_LINKS = [
];
export default class SidebarTopicsSection extends GlimmerComponent {
get sectionLinks() {
return [...DEFAULT_SECTION_LINKS, ...customSectionLinks].map(
(sectionLinkClass) => {
return new sectionLinkClass({
topicTrackingState: this.topicTrackingState,
currentUser: this.currentUser,
});
}
);
configuredSectionLinks = [...DEFAULT_SECTION_LINKS, ...customSectionLinks];
sectionLinks = this.configuredSectionLinks.map((sectionLinkClass) => {
return new sectionLinkClass({
topicTrackingState: this.topicTrackingState,
currentUser: this.currentUser,
});
});
willDestroy() {
this.sectionLinks.forEach((sectionLink) => sectionLink.teardown());
}
@action

View File

@ -7,6 +7,11 @@ export default class BaseSectionLink {
this.currentUser = currentUser;
}
/**
* Called when topics-section component is torn down.
*/
teardown() {}
/**
* @returns {string} The name of the section link. Needs to be dasherized and lowercase.
*/

View File

@ -1,44 +1,35 @@
import I18n from "I18n";
import { tracked } from "@glimmer/tracking";
import discourseDebounce from "discourse-common/lib/debounce";
import { bind } from "discourse-common/utils/decorators";
import BaseSectionLink from "discourse/lib/sidebar/topics-section/base-section-link";
export default class EverythingSectionLink extends BaseSectionLink {
@tracked totalUnread = 0;
@tracked totalNew = 0;
callbackId = null;
constructor() {
super(...arguments);
this._refreshCounts();
this.topicTrackingState.onStateChange(
this._topicTrackingStateUpdated.bind(this)
this.callbackId = this.topicTrackingState.onStateChange(
this._refreshCounts
);
this._refreshCounts();
}
_topicTrackingStateUpdated() {
// refreshing section counts by looping through the states in topicTrackingState is an expensive operation so
// we debounce this.
discourseDebounce(this, this._refreshCounts, 100);
teardown() {
this.topicTrackingState.offStateChange(this.callbackId);
}
@bind
_refreshCounts() {
let totalUnread = 0;
let totalNew = 0;
this.totalUnread = this.topicTrackingState.countUnread();
this.topicTrackingState.forEachTracked((topic, isNew, isUnread) => {
if (isNew) {
totalNew += 1;
} else if (isUnread) {
totalUnread += 1;
}
});
this.totalUnread = totalUnread;
this.totalNew = totalNew;
if (this.totalUnread === 0) {
this.totalNew = this.topicTrackingState.countNew();
}
}
get name() {

View File

@ -1,14 +1,43 @@
import I18n from "I18n";
import { tracked } from "@glimmer/tracking";
import { bind } from "discourse-common/utils/decorators";
import BaseSectionLink from "discourse/lib/sidebar/topics-section/base-section-link";
import { isTrackedTopic } from "discourse/lib/topic-list-tracked-filter";
export default class TrackedSectionLink extends BaseSectionLink {
get name() {
return "tracked";
@tracked totalUnread = 0;
@tracked totalNew = 0;
callbackId = null;
constructor() {
super(...arguments);
this.callbackId = this.topicTrackingState.onStateChange(
this._refreshCounts
);
this._refreshCounts();
}
get route() {
return "discovery.latest";
teardown() {
this.topicTrackingState.offStateChange(this.callbackId);
}
@bind
_refreshCounts() {
this.totalUnread = this.topicTrackingState.countUnread({
customFilterFn: isTrackedTopic,
});
if (this.totalUnread === 0) {
this.totalNew = this.topicTrackingState.countNew({
customFilterFn: isTrackedTopic,
});
}
}
get name() {
return "tracked";
}
get query() {
@ -22,4 +51,32 @@ export default class TrackedSectionLink extends BaseSectionLink {
get text() {
return I18n.t("sidebar.sections.topics.links.tracked.content");
}
get currentWhen() {
return "discovery.latest discovery.new discovery.unread discovery.top";
}
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,
});
} else {
return;
}
}
get route() {
if (this.totalUnread > 0) {
return "discovery.unread";
} else if (this.totalNew > 0) {
return "discovery.new";
} else {
return "discovery.latest";
}
}
}

View File

@ -13,9 +13,16 @@ import { isLegacyEmber } from "discourse-common/config/environment";
import topicFixtures from "discourse/tests/fixtures/discovery-fixtures";
import { cloneJSON } from "discourse-common/lib/object";
import { withPluginApi } from "discourse/lib/plugin-api";
import Site from "discourse/models/site";
import { NotificationLevels } from "discourse/lib/notification-levels";
acceptance("Sidebar - Topics Section", function (needs) {
needs.user({ experimental_sidebar_enabled: true });
needs.user({
experimental_sidebar_enabled: true,
tracked_tags: ["tag1"],
watched_tags: ["tag2"],
watching_first_post_tags: ["tag3"],
});
needs.pretender((server, helper) => {
server.get("/new.json", () => {
@ -322,7 +329,7 @@ acceptance("Sidebar - Topics Section", function (needs) {
assert.ok(
query(".sidebar-section-link-everything").href.endsWith("/unread"),
"is links to unread filter"
"it links to unread filter"
);
// simulate reading topic 2
@ -380,7 +387,7 @@ acceptance("Sidebar - Topics Section", function (needs) {
assert.ok(
query(".sidebar-section-link-everything").href.endsWith("/new"),
"is links to new filter"
"it links to new filter"
);
publishToMessageBus("/unread", {
@ -404,7 +411,242 @@ acceptance("Sidebar - Topics Section", function (needs) {
assert.ok(
query(".sidebar-section-link-everything").href.endsWith("/latest"),
"is links to latest filter"
"it links to latest filter"
);
}
);
conditionalTest(
"visiting top route with tracked filter",
!isLegacyEmber(),
async function (assert) {
await visit("/top?f=tracked");
assert.strictEqual(
queryAll(".sidebar-section-topics .sidebar-section-link.active").length,
1,
"only one link is marked as active"
);
assert.ok(
exists(".sidebar-section-topics .sidebar-section-link-tracked.active"),
"the tracked link is marked as active"
);
}
);
conditionalTest(
"visiting unread route with tracked filter",
!isLegacyEmber(),
async function (assert) {
await visit("/unread?f=tracked");
assert.strictEqual(
queryAll(".sidebar-section-topics .sidebar-section-link.active").length,
1,
"only one link is marked as active"
);
assert.ok(
exists(".sidebar-section-topics .sidebar-section-link-tracked.active"),
"the tracked link is marked as active"
);
}
);
conditionalTest(
"visiting new route with tracked filter",
!isLegacyEmber(),
async function (assert) {
await visit("/new?f=tracked");
assert.strictEqual(
queryAll(".sidebar-section-topics .sidebar-section-link.active").length,
1,
"only one link is marked as active"
);
assert.ok(
exists(".sidebar-section-topics .sidebar-section-link-tracked.active"),
"the tracked link is marked as active"
);
}
);
conditionalTest(
"new and unread count for tracked link",
!isLegacyEmber(),
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);
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: category.id,
notification_level: NotificationLevels.TRACKING,
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: category.subcategories[0].id,
notification_level: NotificationLevels.TRACKING,
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: 12,
last_read_post_number: 11,
created_at: "2020-02-09T09:40:02.672Z",
category_id: category.subcategories[0].subcategories[0].id,
notification_level: NotificationLevels.TRACKING,
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: 15,
last_read_post_number: 14,
created_at: "2021-06-14T12:41:02.477Z",
category_id: 3,
notification_level: NotificationLevels.TRACKING,
created_in_new_period: false,
unread_not_too_old: true,
treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z",
},
{
topic_id: 5,
highest_post_number: 1,
last_read_post_number: null,
created_at: "2021-06-14T12:41:02.477Z",
category_id: 3,
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: 6,
highest_post_number: 17,
last_read_post_number: 16,
created_at: "2020-10-31T03:41:42.257Z",
category_id: 1234,
notification_level: NotificationLevels.TRACKING,
created_in_new_period: false,
unread_not_too_old: true,
treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z",
tags: ["tag3"],
},
]);
await visit("/");
assert.strictEqual(
query(
".sidebar-section-link-tracked .sidebar-section-link-content-badge"
).textContent.trim(),
"3 unread",
"it displays the right unread count"
);
assert.ok(
query(".sidebar-section-link-tracked").href.endsWith(
"/unread?f=tracked"
),
"it links to unread url with tracked filter"
);
// simulate reading topic id 2
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-tracked .sidebar-section-link-content-badge"
).textContent.trim(),
"2 unread",
"it updates the unread count"
);
// simulate reading topic id 3
publishToMessageBus("/unread", {
topic_id: 3,
message_type: "read",
payload: {
last_read_post_number: 17,
highest_post_number: 17,
},
});
// simulate reading topic id 6
publishToMessageBus("/unread", {
topic_id: 6,
message_type: "read",
payload: {
last_read_post_number: 17,
highest_post_number: 17,
},
});
assert.strictEqual(
query(
".sidebar-section-link-tracked .sidebar-section-link-content-badge"
).textContent.trim(),
"1 new",
"it displays the new count once there are no tracked unread topics"
);
assert.ok(
query(".sidebar-section-link-tracked").href.endsWith("/new?f=tracked"),
"it links to new url with tracked filter"
);
// simulate reading topic id 1
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-tracked .sidebar-section-link-content-badge"
),
"it removes new count once there are no tracked new topics"
);
assert.ok(
query(".sidebar-section-link-tracked").href.endsWith(
"/latest?f=tracked"
),
"it links to latest url with tracked filter"
);
}
);
@ -494,4 +736,28 @@ acceptance("Sidebar - Topics Section", function (needs) {
);
}
);
conditionalTest(
"clean up topic tracking state state changed callbacks when section is destroyed",
!isLegacyEmber(),
async function (assert) {
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
);
}
);
});