FEATURE: Add new/unread counts to tags section links exp sidebar (#17057)
This commit is contained in:
parent
95fa4c5d52
commit
e9a77e7f19
|
@ -4,10 +4,27 @@ import GlimmerComponent from "discourse/components/glimmer";
|
||||||
import TagSectionLink from "discourse/lib/sidebar/tags-section/tag-section-link";
|
import TagSectionLink from "discourse/lib/sidebar/tags-section/tag-section-link";
|
||||||
|
|
||||||
export default class SidebarTagsSection extends GlimmerComponent {
|
export default class SidebarTagsSection extends GlimmerComponent {
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
|
||||||
|
this.callbackId = this.topicTrackingState.onStateChange(() => {
|
||||||
|
this.sectionLinks.forEach((sectionLink) => {
|
||||||
|
sectionLink.refreshCounts();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
willDestroy() {
|
||||||
|
this.topicTrackingState.offStateChange(this.callbackId);
|
||||||
|
}
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
get sectionLinks() {
|
get sectionLinks() {
|
||||||
return this.currentUser.trackedTags.map((trackedTag) => {
|
return this.currentUser.trackedTags.map((trackedTag) => {
|
||||||
return new TagSectionLink({ tag: trackedTag });
|
return new TagSectionLink({
|
||||||
|
tag: trackedTag,
|
||||||
|
topicTrackingState: this.topicTrackingState,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,30 @@
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
|
||||||
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export default class TagSectionLink {
|
export default class TagSectionLink {
|
||||||
constructor({ tag }) {
|
@tracked totalUnread = 0;
|
||||||
|
@tracked totalNew = 0;
|
||||||
|
|
||||||
|
constructor({ tag, topicTrackingState }) {
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
|
this.topicTrackingState = topicTrackingState;
|
||||||
|
this.refreshCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
refreshCounts() {
|
||||||
|
this.totalUnread = this.topicTrackingState.countUnread({
|
||||||
|
tagId: this.tag,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.totalUnread === 0) {
|
||||||
|
this.totalNew = this.topicTrackingState.countNew({
|
||||||
|
tagId: this.tag,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
|
@ -22,4 +46,26 @@ export default class TagSectionLink {
|
||||||
get text() {
|
get text() {
|
||||||
return this.tag;
|
return this.tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 "tag.showUnread";
|
||||||
|
} else if (this.totalNew > 0) {
|
||||||
|
return "tag.showNew";
|
||||||
|
} else {
|
||||||
|
return "tag.show";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
@title={{sectionLink.title}}
|
@title={{sectionLink.title}}
|
||||||
@content={{sectionLink.text}}
|
@content={{sectionLink.text}}
|
||||||
@currentWhen={{sectionLink.currentWhen}}
|
@currentWhen={{sectionLink.currentWhen}}
|
||||||
|
@badgeText={{sectionLink.badgeText}}
|
||||||
@model={{sectionLink.model}}>
|
@model={{sectionLink.model}}>
|
||||||
</Sidebar::SectionLink>
|
</Sidebar::SectionLink>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
|
@ -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,
|
||||||
updateCurrentUser,
|
updateCurrentUser,
|
||||||
|
@ -226,4 +227,147 @@ acceptance("Sidebar - Tags section", function (needs) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
conditionalTest(
|
||||||
|
"new and unread count for tag section links",
|
||||||
|
!isLegacyEmber(),
|
||||||
|
async function (assert) {
|
||||||
|
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: 1,
|
||||||
|
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",
|
||||||
|
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,
|
||||||
|
unread_not_too_old: true,
|
||||||
|
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,
|
||||||
|
unread_not_too_old: true,
|
||||||
|
treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z",
|
||||||
|
tags: ["tag2"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
topic_id: 4,
|
||||||
|
highest_post_number: 17,
|
||||||
|
last_read_post_number: 16,
|
||||||
|
created_at: "2020-10-31T03:41:42.257Z",
|
||||||
|
category_id: 4,
|
||||||
|
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",
|
||||||
|
tags: ["tag4"],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await visit("/");
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
query(
|
||||||
|
`.sidebar-section-link-tag1 .sidebar-section-link-content-badge`
|
||||||
|
).textContent.trim(),
|
||||||
|
I18n.t("sidebar.unread_count", { count: 1 }),
|
||||||
|
`displays 1 unread count for tag1 section link`
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
query(
|
||||||
|
`.sidebar-section-link-tag2 .sidebar-section-link-content-badge`
|
||||||
|
).textContent.trim(),
|
||||||
|
I18n.t("sidebar.unread_count", { count: 1 }),
|
||||||
|
`displays 1 unread count for tag2 section link`
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
!exists(
|
||||||
|
`.sidebar-section-link-tag3 .sidebar-section-link-content-badge`
|
||||||
|
),
|
||||||
|
"does not display any badge for tag3 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-tag1 .sidebar-section-link-content-badge`
|
||||||
|
).textContent.trim(),
|
||||||
|
I18n.t("sidebar.new_count", { count: 1 }),
|
||||||
|
`displays 1 new count for tag1 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-tag1 .sidebar-section-link-content-badge`
|
||||||
|
),
|
||||||
|
`does not display any badge tag1 section link`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
conditionalTest(
|
||||||
|
"cleans 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -278,7 +278,7 @@ class TopicTrackingState
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.include_tags_in_report?
|
def self.include_tags_in_report?
|
||||||
SiteSetting.tagging_enabled && @include_tags_in_report
|
SiteSetting.tagging_enabled && (@include_tags_in_report || SiteSetting.enable_experimental_sidebar)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.include_tags_in_report=(v)
|
def self.include_tags_in_report=(v)
|
||||||
|
|
|
@ -9,7 +9,8 @@ class TopicTrackingStateSerializer < ApplicationSerializer
|
||||||
:notification_level,
|
:notification_level,
|
||||||
:created_in_new_period,
|
:created_in_new_period,
|
||||||
:unread_not_too_old,
|
:unread_not_too_old,
|
||||||
:treat_as_new_topic_start_date
|
:treat_as_new_topic_start_date,
|
||||||
|
:tags
|
||||||
|
|
||||||
def created_in_new_period
|
def created_in_new_period
|
||||||
return true if !scope
|
return true if !scope
|
||||||
|
@ -20,4 +21,8 @@ class TopicTrackingStateSerializer < ApplicationSerializer
|
||||||
return true if object.first_unread_at.blank?
|
return true if object.first_unread_at.blank?
|
||||||
object.updated_at >= object.first_unread_at
|
object.updated_at >= object.first_unread_at
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def include_tags?
|
||||||
|
object.respond_to?(:tags)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -539,14 +539,7 @@ describe TopicTrackingState do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "tag support" do
|
context "tag support" do
|
||||||
after do
|
before do
|
||||||
# this is a bit of an odd hook, but this is a global change
|
|
||||||
# used by plugins that leverage tagging heavily and need
|
|
||||||
# tag information in topic tracking state
|
|
||||||
TopicTrackingState.include_tags_in_report = false
|
|
||||||
end
|
|
||||||
|
|
||||||
it "correctly handles tags" do
|
|
||||||
SiteSetting.tagging_enabled = true
|
SiteSetting.tagging_enabled = true
|
||||||
|
|
||||||
post.topic.notifier.watch_topic!(post.topic.user_id)
|
post.topic.notifier.watch_topic!(post.topic.user_id)
|
||||||
|
@ -556,6 +549,27 @@ describe TopicTrackingState do
|
||||||
Guardian.new(Discourse.system_user),
|
Guardian.new(Discourse.system_user),
|
||||||
['bananas', 'apples']
|
['bananas', 'apples']
|
||||||
)
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "includes tags when SiteSetting.enable_experimental_sidebar is true" do
|
||||||
|
report = TopicTrackingState.report(user)
|
||||||
|
expect(report.length).to eq(1)
|
||||||
|
row = report[0]
|
||||||
|
expect(row.respond_to?(:tags)).to eq(false)
|
||||||
|
|
||||||
|
SiteSetting.enable_experimental_sidebar = true
|
||||||
|
|
||||||
|
report = TopicTrackingState.report(user)
|
||||||
|
expect(report.length).to eq(1)
|
||||||
|
row = report[0]
|
||||||
|
expect(row.tags).to contain_exactly("apples", "bananas")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "includes tags when TopicTrackingState.include_tags_in_report option is enabled" do
|
||||||
|
report = TopicTrackingState.report(user)
|
||||||
|
expect(report.length).to eq(1)
|
||||||
|
row = report[0]
|
||||||
|
expect(row.respond_to? :tags).to eq(false)
|
||||||
|
|
||||||
TopicTrackingState.include_tags_in_report = true
|
TopicTrackingState.include_tags_in_report = true
|
||||||
|
|
||||||
|
@ -563,13 +577,8 @@ describe TopicTrackingState do
|
||||||
expect(report.length).to eq(1)
|
expect(report.length).to eq(1)
|
||||||
row = report[0]
|
row = report[0]
|
||||||
expect(row.tags).to contain_exactly("apples", "bananas")
|
expect(row.tags).to contain_exactly("apples", "bananas")
|
||||||
|
ensure
|
||||||
TopicTrackingState.include_tags_in_report = false
|
TopicTrackingState.include_tags_in_report = false
|
||||||
|
|
||||||
report = TopicTrackingState.report(user)
|
|
||||||
expect(report.length).to eq(1)
|
|
||||||
row = report[0]
|
|
||||||
expect(row.respond_to? :tags).to eq(false)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe TopicTrackingStateSerializer do
|
||||||
|
fab!(:user) { Fabricate(:user) }
|
||||||
|
fab!(:post) { create_post }
|
||||||
|
|
||||||
|
it 'serializes topic tracking state reports' do
|
||||||
|
report = TopicTrackingState.report(user)
|
||||||
|
serialized = described_class.new(report[0], scope: Guardian.new(user), root: false).as_json
|
||||||
|
|
||||||
|
expect(serialized[:topic_id]).to eq(post.topic_id)
|
||||||
|
expect(serialized[:highest_post_number]).to eq(post.topic.highest_post_number)
|
||||||
|
expect(serialized[:last_read_post_number]).to eq(nil)
|
||||||
|
expect(serialized[:created_at]).to be_present
|
||||||
|
expect(serialized[:notification_level]).to eq(nil)
|
||||||
|
expect(serialized[:created_in_new_period]).to eq(true)
|
||||||
|
expect(serialized[:unread_not_too_old]).to eq(true)
|
||||||
|
expect(serialized[:treat_as_new_topic_start_date]).to be_present
|
||||||
|
expect(serialized.has_key?(:tags)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "includes tags attribute when tags are present" do
|
||||||
|
TopicTrackingState.include_tags_in_report = true
|
||||||
|
|
||||||
|
post.topic.notifier.watch_topic!(post.topic.user_id)
|
||||||
|
|
||||||
|
DiscourseTagging.tag_topic_by_names(
|
||||||
|
post.topic,
|
||||||
|
Guardian.new(Discourse.system_user),
|
||||||
|
['bananas', 'apples']
|
||||||
|
)
|
||||||
|
|
||||||
|
report = TopicTrackingState.report(user)
|
||||||
|
serialized = described_class.new(report[0], scope: Guardian.new(user), root: false).as_json
|
||||||
|
|
||||||
|
expect(serialized[:tags]).to contain_exactly("bananas", "apples")
|
||||||
|
ensure
|
||||||
|
TopicTrackingState.include_tags_in_report = false
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue