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 { export default class SidebarTopicsSection extends GlimmerComponent {
get sectionLinks() { configuredSectionLinks = [...DEFAULT_SECTION_LINKS, ...customSectionLinks];
return [...DEFAULT_SECTION_LINKS, ...customSectionLinks].map(
(sectionLinkClass) => { sectionLinks = this.configuredSectionLinks.map((sectionLinkClass) => {
return new sectionLinkClass({ return new sectionLinkClass({
topicTrackingState: this.topicTrackingState, topicTrackingState: this.topicTrackingState,
currentUser: this.currentUser, currentUser: this.currentUser,
}); });
} });
);
willDestroy() {
this.sectionLinks.forEach((sectionLink) => sectionLink.teardown());
} }
@action @action

View File

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

View File

@ -1,14 +1,43 @@
import I18n from "I18n"; 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 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 { export default class TrackedSectionLink extends BaseSectionLink {
get name() { @tracked totalUnread = 0;
return "tracked"; @tracked totalNew = 0;
callbackId = null;
constructor() {
super(...arguments);
this.callbackId = this.topicTrackingState.onStateChange(
this._refreshCounts
);
this._refreshCounts();
} }
get route() { teardown() {
return "discovery.latest"; 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() { get query() {
@ -22,4 +51,32 @@ export default class TrackedSectionLink extends BaseSectionLink {
get text() { get text() {
return I18n.t("sidebar.sections.topics.links.tracked.content"); 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 topicFixtures from "discourse/tests/fixtures/discovery-fixtures";
import { cloneJSON } from "discourse-common/lib/object"; import { cloneJSON } from "discourse-common/lib/object";
import { withPluginApi } from "discourse/lib/plugin-api"; 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) { 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) => { needs.pretender((server, helper) => {
server.get("/new.json", () => { server.get("/new.json", () => {
@ -322,7 +329,7 @@ acceptance("Sidebar - Topics Section", function (needs) {
assert.ok( assert.ok(
query(".sidebar-section-link-everything").href.endsWith("/unread"), query(".sidebar-section-link-everything").href.endsWith("/unread"),
"is links to unread filter" "it links to unread filter"
); );
// simulate reading topic 2 // simulate reading topic 2
@ -380,7 +387,7 @@ acceptance("Sidebar - Topics Section", function (needs) {
assert.ok( assert.ok(
query(".sidebar-section-link-everything").href.endsWith("/new"), query(".sidebar-section-link-everything").href.endsWith("/new"),
"is links to new filter" "it links to new filter"
); );
publishToMessageBus("/unread", { publishToMessageBus("/unread", {
@ -404,7 +411,242 @@ acceptance("Sidebar - Topics Section", function (needs) {
assert.ok( assert.ok(
query(".sidebar-section-link-everything").href.endsWith("/latest"), 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
);
}
);
}); });